From 6fbe67e769abf4b00eeaa6ff95964302c1ebc9f4 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Wed, 7 Jun 2023 14:17:02 +0000 Subject: [PATCH 01/91] wip first copy atdd --- Makefile | 1 + atdd.opam | 85 ++ atdd/.gitignore | 1 + atdd/Makefile | 33 + atdd/README.md | 3 + atdd/minimal.atd | 6 + atdd/minimal.py | 298 ++++++ atdd/src/bin/Atdd_main.ml | 134 +++ atdd/src/bin/dune | 10 + atdd/src/lib/Codegen.ml | 1255 +++++++++++++++++++++++ atdd/src/lib/Codegen.mli | 7 + atdd/src/lib/Indent.ml | 51 + atdd/src/lib/Indent.mli | 33 + atdd/src/lib/Python_annot.ml | 71 ++ atdd/src/lib/Python_annot.mli | 39 + atdd/src/lib/Version.ml | 1 + atdd/src/lib/dune | 7 + atdd/src/test/Main.ml | 14 + atdd/src/test/dune | 12 + atdd/test/.gitignore | 2 + atdd/test/atd-input/ALLCAPS.atd | 0 atdd/test/atd-input/everything.atd | 65 ++ atdd/test/dune | 17 + atdd/test/python-tests/deco.py | 16 + atdd/test/python-tests/dune | 29 + atdd/test/python-tests/manual_sample.py | 214 ++++ atdd/test/python-tests/test_atdpy.py | 273 +++++ dune | 1 + dune-project | 13 + 29 files changed, 2691 insertions(+) create mode 100644 atdd.opam create mode 100644 atdd/.gitignore create mode 100644 atdd/Makefile create mode 100644 atdd/README.md create mode 100644 atdd/minimal.atd create mode 100644 atdd/minimal.py create mode 100644 atdd/src/bin/Atdd_main.ml create mode 100644 atdd/src/bin/dune create mode 100644 atdd/src/lib/Codegen.ml create mode 100644 atdd/src/lib/Codegen.mli create mode 100644 atdd/src/lib/Indent.ml create mode 100644 atdd/src/lib/Indent.mli create mode 100644 atdd/src/lib/Python_annot.ml create mode 100644 atdd/src/lib/Python_annot.mli create mode 120000 atdd/src/lib/Version.ml create mode 100644 atdd/src/lib/dune create mode 100644 atdd/src/test/Main.ml create mode 100644 atdd/src/test/dune create mode 100644 atdd/test/.gitignore create mode 100644 atdd/test/atd-input/ALLCAPS.atd create mode 100644 atdd/test/atd-input/everything.atd create mode 100644 atdd/test/dune create mode 100644 atdd/test/python-tests/deco.py create mode 100644 atdd/test/python-tests/dune create mode 100644 atdd/test/python-tests/manual_sample.py create mode 100644 atdd/test/python-tests/test_atdpy.py diff --git a/Makefile b/Makefile index c4f8057b..a116797e 100644 --- a/Makefile +++ b/Makefile @@ -10,6 +10,7 @@ DUNE ?= dune .PHONY: all all: $(MAKE) -C atdpy clean-for-dune + $(MAKE) -C atdd clean-for-dune $(MAKE) -C atdts clean-for-dune $(DUNE) build diff --git a/atdd.opam b/atdd.opam new file mode 100644 index 00000000..dac87755 --- /dev/null +++ b/atdd.opam @@ -0,0 +1,85 @@ +# This file is generated by dune, edit dune-project instead +opam-version: "2.0" +synopsis: "DLang code generation for ATD APIs" +description: "DLang 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" + "alcotest" {with-test} + "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/atdd/.gitignore b/atdd/.gitignore new file mode 100644 index 00000000..5e56e040 --- /dev/null +++ b/atdd/.gitignore @@ -0,0 +1 @@ +/bin diff --git a/atdd/Makefile b/atdd/Makefile new file mode 100644 index 00000000..5ed39833 --- /dev/null +++ b/atdd/Makefile @@ -0,0 +1,33 @@ +# +# Dlang/JSON backend +# + +DUNE ?= dune + +.PHONY: build +build: + rm -f bin/atdd + $(MAKE) clean-for-dune + $(DUNE) build @all + mkdir -p bin + ln -s ../../_build/install/default/bin/atdd bin/atdd + +# 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/atdd/test/python-tests/everything.py \ + test/python-tests/everything.py && \ + exit "$$status" + +.PHONY: clean-for-dune +clean-for-dune: + rm -f test/python-tests/everything.py + +.PHONY: clean +clean: + $(MAKE) clean-for-dune + $(DUNE) clean + rm -rf bin diff --git a/atdd/README.md b/atdd/README.md new file mode 100644 index 00000000..3aabc212 --- /dev/null +++ b/atdd/README.md @@ -0,0 +1,3 @@ +Atdd +== + diff --git a/atdd/minimal.atd b/atdd/minimal.atd new file mode 100644 index 00000000..19dbfef3 --- /dev/null +++ b/atdd/minimal.atd @@ -0,0 +1,6 @@ +type root = { + id : string; + items: int list list; + ?maybe: int option; + ~extras: int list; +} diff --git a/atdd/minimal.py b/atdd/minimal.py new file mode 100644 index 00000000..e8808663 --- /dev/null +++ b/atdd/minimal.py @@ -0,0 +1,298 @@ +"""Generated by atdpy from type definitions in minimal.atd. + +This implements classes for the types defined in 'minimal.atd', providing +methods and functions to convert data from/to JSON. +""" + +# Disable flake8 entirely on this file: +# flake8: noqa + +# Import annotations to allow forward references +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union + +import json + +############################################################################ +# Private functions +############################################################################ + + +def _atd_missing_json_field(type_name: str, json_field_name: str) -> NoReturn: + raise ValueError(f"missing field '{json_field_name}'" + f" in JSON object of type '{type_name}'") + + +def _atd_bad_json(expected_type: str, json_value: Any) -> NoReturn: + value_str = str(json_value) + if len(value_str) > 200: + value_str = value_str[:200] + '…' + + raise ValueError(f"incompatible JSON value where" + f" type '{expected_type}' was expected: '{value_str}'") + + +def _atd_bad_python(expected_type: str, json_value: Any) -> NoReturn: + value_str = str(json_value) + if len(value_str) > 200: + value_str = value_str[:200] + '…' + + raise ValueError(f"incompatible Python value where" + f" type '{expected_type}' was expected: '{value_str}'") + + +def _atd_read_unit(x: Any) -> None: + if x is None: + return x + else: + _atd_bad_json('unit', x) + + +def _atd_read_bool(x: Any) -> bool: + if isinstance(x, bool): + return x + else: + _atd_bad_json('bool', x) + + +def _atd_read_int(x: Any) -> int: + if isinstance(x, int): + return x + else: + _atd_bad_json('int', x) + + +def _atd_read_float(x: Any) -> float: + if isinstance(x, (int, float)): + return x + else: + _atd_bad_json('float', x) + + +def _atd_read_string(x: Any) -> str: + if isinstance(x, str): + return x + else: + _atd_bad_json('str', x) + + +def _atd_read_list( + read_elt: Callable[[Any], Any] + ) -> Callable[[List[Any]], List[Any]]: + def read_list(elts: List[Any]) -> List[Any]: + if isinstance(elts, list): + return [read_elt(elt) for elt in elts] + else: + _atd_bad_json('array', elts) + return read_list + + +def _atd_read_assoc_array_into_dict( + read_key: Callable[[Any], Any], + read_value: Callable[[Any], Any], + ) -> Callable[[List[Any]], Dict[Any, Any]]: + def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: + if isinstance(elts, list): + return {read_key(elt[0]): read_value(elt[1]) for elt in elts} + else: + _atd_bad_json('array', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_assoc_object_into_dict( + read_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: + def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: + if isinstance(elts, dict): + return {_atd_read_string(k): read_value(v) + for k, v in elts.items()} + else: + _atd_bad_json('object', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_assoc_object_into_list( + read_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: + def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: + if isinstance(elts, dict): + return [(_atd_read_string(k), read_value(v)) + for k, v in elts.items()] + else: + _atd_bad_json('object', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def read_nullable(x: Any) -> Any: + if x is None: + return None + else: + return read_elt(x) + return read_nullable + + +def _atd_read_option(read_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def read_option(x: Any) -> Any: + if x == 'None': + return None + elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some': + return read_elt(x[1]) + else: + _atd_bad_json('option', x) + raise AssertionError('impossible') # keep mypy happy + return read_option + + +def _atd_write_unit(x: Any) -> None: + if x is None: + return x + else: + _atd_bad_python('unit', x) + + +def _atd_write_bool(x: Any) -> bool: + if isinstance(x, bool): + return x + else: + _atd_bad_python('bool', x) + + +def _atd_write_int(x: Any) -> int: + if isinstance(x, int): + return x + else: + _atd_bad_python('int', x) + + +def _atd_write_float(x: Any) -> float: + if isinstance(x, (int, float)): + return x + else: + _atd_bad_python('float', x) + + +def _atd_write_string(x: Any) -> str: + if isinstance(x, str): + return x + else: + _atd_bad_python('str', x) + + +def _atd_write_list( + write_elt: Callable[[Any], Any] + ) -> Callable[[List[Any]], List[Any]]: + def write_list(elts: List[Any]) -> List[Any]: + if isinstance(elts, list): + return [write_elt(elt) for elt in elts] + else: + _atd_bad_python('list', elts) + return write_list + + +def _atd_write_assoc_dict_to_array( + write_key: Callable[[Any], Any], + write_value: Callable[[Any], Any] + ) -> Callable[[Dict[Any, Any]], List[Tuple[Any, Any]]]: + def write_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: + if isinstance(elts, dict): + return [(write_key(k), write_value(v)) for k, v in elts.items()] + else: + _atd_bad_python('Dict[str, ]]', elts) + raise AssertionError('impossible') # keep mypy happy + return write_assoc + + +def _atd_write_assoc_dict_to_object( + write_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: + def write_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: + if isinstance(elts, dict): + return {_atd_write_string(k): write_value(v) + for k, v in elts.items()} + else: + _atd_bad_python('Dict[str, ]', elts) + raise AssertionError('impossible') # keep mypy happy + return write_assoc + + +def _atd_write_assoc_list_to_object( + write_value: Callable[[Any], Any], + ) -> Callable[[List[Any]], Dict[str, Any]]: + def write_assoc(elts: List[List[Any]]) -> Dict[str, Any]: + if isinstance(elts, list): + return {_atd_write_string(elt[0]): write_value(elt[1]) + for elt in elts} + else: + _atd_bad_python('List[Tuple[, ]]', elts) + raise AssertionError('impossible') # keep mypy happy + return write_assoc + + +def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def write_nullable(x: Any) -> Any: + if x is None: + return None + else: + return write_elt(x) + return write_nullable + + +def _atd_write_option(write_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def write_option(x: Any) -> Any: + if x is None: + return 'None' + else: + return ['Some', write_elt(x)] + return write_option + + +############################################################################ +# Public classes +############################################################################ + + +@dataclass +class Root: + """Original type: root = { ... }""" + + id: str + items: List[List[int]] + maybe: Optional[int] = None + extras: List[int] = field(default_factory=lambda: []) + + @classmethod + def from_json(cls, x: Any) -> 'Root': + if isinstance(x, dict): + return cls( + id=_atd_read_string(x['ID']) if 'ID' in x else _atd_missing_json_field('Root', 'ID'), + items=_atd_read_list(_atd_read_list(_atd_read_int))(x['items']) if 'items' in x else _atd_missing_json_field('Root', 'items'), + maybe=_atd_read_int(x['maybe']) if 'maybe' in x else None, + extras=_atd_read_list(_atd_read_int)(x['extras']) if 'extras' in x else [], + ) + else: + _atd_bad_json('Root', x) + + def to_json(self) -> Any: + res: Dict[str, Any] = {} + res['ID'] = _atd_write_string(self.id) + res['items'] = _atd_write_list(_atd_write_list(_atd_write_int))(self.items) + if self.maybe is not None: + res['maybe'] = _atd_write_int(self.maybe) + res['extras'] = _atd_write_list(_atd_write_int)(self.extras) + return res + + @classmethod + def from_json_string(cls, x: str) -> 'Root': + return cls.from_json(json.loads(x)) + + def to_json_string(self, **kw: Any) -> str: + return json.dumps(self.to_json(), **kw) diff --git a/atdd/src/bin/Atdd_main.ml b/atdd/src/bin/Atdd_main.ml new file mode 100644 index 00000000..477f2486 --- /dev/null +++ b/atdd/src/bin/Atdd_main.ml @@ -0,0 +1,134 @@ +(* + Entry point to the atdd command. +*) + +open Printf +open Cmdliner + +type conf = { + input_files: string list; + version: bool; +} + +let run conf = + if conf.version then ( + print_endline Atdd.Version.version; + exit 0 + ) + else + conf.input_files + |> List.iter (fun atd_file -> + Atdd.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 atdd and exits" + in + Arg.value (Arg.flag info) + +let doc = + "Type-safe JSON serializers for Python" + +(* + 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 "atdd turns a file containing type definitions into Python classes \ + that read, write, and validate JSON data. The generated code \ + can be type-checked statically with mypy 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.py' \ + with the command 'atdd 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 + "atdd" + 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/atdd/src/bin/dune b/atdd/src/bin/dune new file mode 100644 index 00000000..d333b3d5 --- /dev/null +++ b/atdd/src/bin/dune @@ -0,0 +1,10 @@ +(executable + (name Atdd_main) + (public_name atdd) + (package atdd) + (libraries + cmdliner + atdd + atd + ) +) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml new file mode 100644 index 00000000..28624400 --- /dev/null +++ b/atdd/src/lib/Codegen.ml @@ -0,0 +1,1255 @@ +(* + Python code generation for JSON support (no biniou support) + + Takes the contents of a .atd file and translates it to a .py file. + + Design: + - Python's standard 'json' module handles the parsing into generic + dictionaries and such. + - The generated code assigns one class to each ATD record. The use + of type annotations allows for some type checking with mypy. + - When converting from JSON to Python, the well-formedness of the data + is checked. + - When converting from Python to JSON, the well-formedness of the data + is checked as well since the type system is too easy to bypass. + - Sum types use one main class and one subclass per case. + - Tuples, like arrays, options, and nullables don't get a class of + their own. + - Generic functions are provided to deal with the case where the JSON + root is an array. + + 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 class: instance variables, including method names *) + translate_inst_variable: unit -> (string -> string); +} + +let annot_schema_python : Atd.Annot.schema_section = + { + section = "python"; + fields = [ + Module_head, "text"; + Module_head, "json_py.text"; + Type_def, "decorator"; + Type_expr, "repr"; + Field, "default"; + ] + } + +let annot_schema : Atd.Annot.schema = + annot_schema_python :: Atd.Json.annot_schema_json + +(* Translate a preferred variable name into an available Python 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 as recommended by PEP 8. *) +let class_name env id = + trans env (to_camel_case id) + +(* + Create a class identifier that hasn't been seen yet. + This is for internal disambiguation and still must translated using + the 'trans' function ('class_name' will not work due to trailing + underscores being added for disambiguation). +*) +let create_class_name env name = + let preferred_id = to_camel_case name in + env.create_variable preferred_id + +let init_env () : env = + let keywords = [ + (* Keywords + https://docs.python.org/3/reference/lexical_analysis.html#keywords + *) + "False"; "await"; "else"; "import"; "pass"; + "None"; "break"; "except"; "in"; "raise"; + "True"; "class"; "finally"; "is"; "return"; + "and"; "continue"; "for"; "lambda"; "try"; + "as"; "def"; "from"; "nonlocal"; "while"; + "assert"; "del"; "global"; "not"; "with"; + "async"; "elif"; "if"; "or"; "yield"; + + (* Soft keywords + https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords + *) + "match"; "case"; "_"; + ] + 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:( + ["from_json"; "to_json"; + "from_json_string"; "to_json_string"] + @ 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 atd_filename = + sprintf {|"""Generated by atdpy from type definitions in %s. + +This implements classes for the types defined in '%s', providing +methods and functions to convert data from/to JSON. +""" + +# Disable flake8 entirely on this file: +# flake8: noqa + +# Import annotations to allow forward references +from __future__ import annotations +from dataclasses import dataclass, field +from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union + +import json + +############################################################################ +# Private functions +############################################################################ + + +def _atd_missing_json_field(type_name: str, json_field_name: str) -> NoReturn: + raise ValueError(f"missing field '{json_field_name}'" + f" in JSON object of type '{type_name}'") + + +def _atd_bad_json(expected_type: str, json_value: Any) -> NoReturn: + value_str = str(json_value) + if len(value_str) > 200: + value_str = value_str[:200] + '…' + + raise ValueError(f"incompatible JSON value where" + f" type '{expected_type}' was expected: '{value_str}'") + + +def _atd_bad_python(expected_type: str, json_value: Any) -> NoReturn: + value_str = str(json_value) + if len(value_str) > 200: + value_str = value_str[:200] + '…' + + raise ValueError(f"incompatible Python value where" + f" type '{expected_type}' was expected: '{value_str}'") + + +def _atd_read_unit(x: Any) -> None: + if x is None: + return x + else: + _atd_bad_json('unit', x) + + +def _atd_read_bool(x: Any) -> bool: + if isinstance(x, bool): + return x + else: + _atd_bad_json('bool', x) + + +def _atd_read_int(x: Any) -> int: + if isinstance(x, int): + return x + else: + _atd_bad_json('int', x) + + +def _atd_read_float(x: Any) -> float: + if isinstance(x, (int, float)): + return x + else: + _atd_bad_json('float', x) + + +def _atd_read_string(x: Any) -> str: + if isinstance(x, str): + return x + else: + _atd_bad_json('str', x) + + +def _atd_read_list( + read_elt: Callable[[Any], Any] + ) -> Callable[[List[Any]], List[Any]]: + def read_list(elts: List[Any]) -> List[Any]: + if isinstance(elts, list): + return [read_elt(elt) for elt in elts] + else: + _atd_bad_json('array', elts) + return read_list + + +def _atd_read_assoc_array_into_dict( + read_key: Callable[[Any], Any], + read_value: Callable[[Any], Any], + ) -> Callable[[List[Any]], Dict[Any, Any]]: + def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: + if isinstance(elts, list): + return {read_key(elt[0]): read_value(elt[1]) for elt in elts} + else: + _atd_bad_json('array', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_assoc_object_into_dict( + read_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: + def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: + if isinstance(elts, dict): + return {_atd_read_string(k): read_value(v) + for k, v in elts.items()} + else: + _atd_bad_json('object', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_assoc_object_into_list( + read_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: + def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: + if isinstance(elts, dict): + return [(_atd_read_string(k), read_value(v)) + for k, v in elts.items()] + else: + _atd_bad_json('object', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def read_nullable(x: Any) -> Any: + if x is None: + return None + else: + return read_elt(x) + return read_nullable + + +def _atd_read_option(read_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def read_option(x: Any) -> Any: + if x == 'None': + return None + elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some': + return read_elt(x[1]) + else: + _atd_bad_json('option', x) + raise AssertionError('impossible') # keep mypy happy + return read_option + + +def _atd_write_unit(x: Any) -> None: + if x is None: + return x + else: + _atd_bad_python('unit', x) + + +def _atd_write_bool(x: Any) -> bool: + if isinstance(x, bool): + return x + else: + _atd_bad_python('bool', x) + + +def _atd_write_int(x: Any) -> int: + if isinstance(x, int): + return x + else: + _atd_bad_python('int', x) + + +def _atd_write_float(x: Any) -> float: + if isinstance(x, (int, float)): + return x + else: + _atd_bad_python('float', x) + + +def _atd_write_string(x: Any) -> str: + if isinstance(x, str): + return x + else: + _atd_bad_python('str', x) + + +def _atd_write_list( + write_elt: Callable[[Any], Any] + ) -> Callable[[List[Any]], List[Any]]: + def write_list(elts: List[Any]) -> List[Any]: + if isinstance(elts, list): + return [write_elt(elt) for elt in elts] + else: + _atd_bad_python('list', elts) + return write_list + + +def _atd_write_assoc_dict_to_array( + write_key: Callable[[Any], Any], + write_value: Callable[[Any], Any] + ) -> Callable[[Dict[Any, Any]], List[Tuple[Any, Any]]]: + def write_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: + if isinstance(elts, dict): + return [(write_key(k), write_value(v)) for k, v in elts.items()] + else: + _atd_bad_python('Dict[str, ]]', elts) + raise AssertionError('impossible') # keep mypy happy + return write_assoc + + +def _atd_write_assoc_dict_to_object( + write_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: + def write_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: + if isinstance(elts, dict): + return {_atd_write_string(k): write_value(v) + for k, v in elts.items()} + else: + _atd_bad_python('Dict[str, ]', elts) + raise AssertionError('impossible') # keep mypy happy + return write_assoc + + +def _atd_write_assoc_list_to_object( + write_value: Callable[[Any], Any], + ) -> Callable[[List[Any]], Dict[str, Any]]: + def write_assoc(elts: List[List[Any]]) -> Dict[str, Any]: + if isinstance(elts, list): + return {_atd_write_string(elt[0]): write_value(elt[1]) + for elt in elts} + else: + _atd_bad_python('List[Tuple[, ]]', elts) + raise AssertionError('impossible') # keep mypy happy + return write_assoc + + +def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def write_nullable(x: Any) -> Any: + if x is None: + return None + else: + return write_elt(x) + return write_nullable + + +def _atd_write_option(write_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def write_option(x: Any) -> Any: + if x is None: + return 'None' + else: + return ['Some', write_elt(x)] + return write_option + + +############################################################################ +# Public classes +############################################################################|} + atd_filename + atd_filename + +let not_implemented loc msg = + A.error_at loc ("not implemented in atdpy: " ^ msg) + +let todo hint = + failwith ("TODO: " ^ hint) + +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 Python. + 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 python_repr = Python_annot.get_python_assoc_repr an in + match e, json_repr, python_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 mypy types *) +let py_type_name env (name : string) = + match name with + | "unit" -> "None" + | "bool" -> "bool" + | "int" -> "int" + | "float" -> "float" + | "string" -> "str" + | "abstract" -> "Any" + | user_defined -> class_name env user_defined + +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 "Tuple[%s]" (String.concat ", " type_names) + | List (loc, e, an) -> + (match assoc_kind loc e an with + | Array_list + | Object_list _ -> + sprintf "List[%s]" + (type_name_of_expr env e) + | Array_dict (key, value) -> + sprintf "Dict[%s, %s]" + (type_name_of_expr env key) (type_name_of_expr env value) + | Object_dict value -> + sprintf "Dict[str, %s]" + (type_name_of_expr env value) + ) + | Option (loc, e, an) -> sprintf "Optional[%s]" (type_name_of_expr env e) + | Nullable (loc, e, an) -> sprintf "Optional[%s]" (type_name_of_expr env e) + | Shared (loc, e, an) -> not_implemented loc "shared" + | Wrap (loc, e, an) -> todo "wrap" + | Name (loc, (loc2, name, []), an) -> py_type_name 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 "None" + | 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" -> Some "None" + | "bool" -> Some "False" + | "int" -> Some "0" + | "float" -> Some "0.0" + | "string" -> Some {|""|} + | "abstract" -> Some "None" + | _ -> None + ) + | Name _ -> None + | Tvar _ -> None + +let get_python_default (e : type_expr) (an : annot) : string option = + let user_default = Python_annot.get_python_default an in + match user_default with + | Some s -> Some s + | None -> get_default_default e + +(* see explanation where this function is used *) +let has_no_class_inst_prop_default + ((loc, (name, kind, an), e) : simple_field) = + match kind with + | Required -> true + | Optional -> (* default is None *) false + | With_default -> + match get_python_default e an with + | Some _ -> false + | None -> + (* There's either no default at all which is an error, + or the default value is known to be mutable. *) + true + +(* If the field is '?foo: bar option', its python or json value has type + 'bar' rather than 'bar option'. *) +let unwrap_field_type loc field_name kind e = + 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 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 cells + | List (loc, e, an) -> + (match assoc_kind loc e an with + | Array_list -> + sprintf "_atd_write_list(%s)" (json_writer env e) + | Array_dict (key, value) -> + sprintf "_atd_write_assoc_dict_to_array(%s, %s)" + (json_writer env key) (json_writer env value) + | Object_dict value -> + sprintf "_atd_write_assoc_dict_to_object(%s)" + (json_writer env value) + | Object_list value -> + sprintf "_atd_write_assoc_list_to_object(%s)" + (json_writer env value) + ) + | Option (loc, e, an) -> + sprintf "_atd_write_option(%s)" (json_writer env e) + | Nullable (loc, e, an) -> + sprintf "_atd_write_nullable(%s)" (json_writer env e) + | Shared (loc, e, an) -> not_implemented loc "shared" + | Wrap (loc, e, an) -> json_writer env e + | Name (loc, (loc2, name, []), an) -> + (match name with + | "bool" | "int" | "float" | "string" -> sprintf "_atd_write_%s" name + | "abstract" -> "(lambda x: x)" + | _ -> "(lambda x: x.to_json())") + | Name (loc, _, _) -> not_implemented loc "parametrized types" + | Tvar (loc, _) -> not_implemented loc "type variables" + +(* + Convert python tuple to json list + + (lambda x: [write0(x[0]), write1(x[1])] if isinstance(x, tuple) else error()) +*) +and tuple_writer env cells = + let len = List.length cells in + let tuple_body = + List.mapi (fun i (loc, e, an) -> + sprintf "%s(x[%i])" (json_writer env e) i + ) cells + |> String.concat ", " + in + sprintf "(lambda x: [%s] \ + if isinstance(x, tuple) and len(x) == %d \ + else _atd_bad_python('tuple of length %d', x))" + tuple_body + len len + +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 = json_writer env unwrapped_type in + let assignment = + [ + Line (sprintf "res['%s'] = %s(self.%s)" + (Atd.Json.get_json_fname name an |> single_esc) + writer_function + (inst_var_name trans_meth name)) + ] + in + match kind with + | Required + | With_default -> assignment + | Optional -> + [ + Line (sprintf "if self.%s is not None:" + (inst_var_name trans_meth name)); + Block assignment + ] + +(* + Function value that can be applied to a JSON node, converting it + to the desired value. +*) +let rec json_reader 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_list(%s)" + (json_reader env e) + | Array_dict (key, value) -> + sprintf "_atd_read_assoc_array_into_dict(%s, %s)" + (json_reader env key) (json_reader env value) + | Object_dict value -> + sprintf "_atd_read_assoc_object_into_dict(%s)" + (json_reader env value) + | Object_list value -> + sprintf "_atd_read_assoc_object_into_list(%s)" + (json_reader env value) + ) + | Option (loc, e, an) -> + sprintf "_atd_read_option(%s)" (json_reader env e) + | Nullable (loc, e, an) -> + sprintf "_atd_read_nullable(%s)" (json_reader env e) + | Shared (loc, e, an) -> not_implemented loc "shared" + | Wrap (loc, e, an) -> json_reader env e + | Name (loc, (loc2, name, []), an) -> + (match name with + | "bool" | "int" | "float" | "string" -> sprintf "_atd_read_%s" name + | "abstract" -> "(lambda x: x)" + | _ -> sprintf "%s.from_json" (class_name env name)) + | Name (loc, _, _) -> not_implemented loc "parametrized types" + | Tvar (loc, _) -> not_implemented loc "type variables" + +(* + Convert json list to python tuple + + (lambda x: (read0(x[0]), read1(x[1])) if isinstance(x, list) else error()) +*) +and tuple_reader env cells = + let len = List.length cells in + let tuple_body = + List.mapi (fun i (loc, e, an) -> + sprintf "%s(x[%i])" (json_reader env e) i + ) cells + |> String.concat ", " + in + sprintf "(lambda x: (%s) \ + if isinstance(x, list) and len(x) == %d \ + else _atd_bad_json('array of length %d', x))" + tuple_body + len len + +let from_json_class_argument + env trans_meth py_class_name ((loc, (name, kind, an), e) : simple_field) = + let python_name = inst_var_name trans_meth name in + let json_name = Atd.Json.get_json_fname name an in + let unwrapped_type = + 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'" name) + in + let else_body = + match kind with + | Required -> + sprintf "_atd_missing_json_field('%s', '%s')" + (single_esc py_class_name) + (single_esc json_name) + | Optional -> "None" + | With_default -> + match get_python_default e an with + | Some x -> x + | None -> + A.error_at loc + (sprintf "missing default Python value for field '%s'" + name) + in + sprintf "%s=%s(x['%s']) if '%s' in x else %s," + python_name + (json_reader env unwrapped_type) + (single_esc json_name) + (single_esc json_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 -> " = None" + | With_default -> + match get_python_default unwrapped_e an with + | None -> "" + | Some x -> + (* This constructs ensures that a fresh default value is + evaluated for each class instanciation. It's important for + default lists since Python lists are mutable. *) + sprintf " = field(default_factory=lambda: %s)" x + in + [ + Line (sprintf "%s: %s%s" var_name type_name default) + ] + +let record env ~class_decorators loc name (fields : field list) an = + let py_class_name = class_name env name in + let trans_meth = env.translate_inst_variable () in + let fields = + List.map (function + | `Field x -> x + | `Inherit _ -> (* expanded at loading time *) assert false) + fields + in + (* + Reorder fields with no-defaults first as required by @dataclass. + Starting with Python 3.10, '@dataclass(kw_only=True)' solves this + problem and makes this reordering unnecessary. + TODO: remove once we require python >= 3.10. It could also be done + as command-line flag specifying the Python version. + *) + let fields = + let no_default, with_default = + List.partition has_no_class_inst_prop_default fields in + no_default @ with_default + in + let inst_var_declarations = + List.map (fun x -> Inline (inst_var_declaration env trans_meth x)) fields + in + let json_object_body = + List.map (fun x -> + Inline (construct_json_field env trans_meth x)) fields in + let from_json_class_arguments = + List.map (fun x -> + Line (from_json_class_argument env trans_meth py_class_name x) + ) fields in + let from_json = + [ + Line "@classmethod"; + Line (sprintf "def from_json(cls, x: Any) -> '%s':" + (single_esc py_class_name)); + Block [ + Line "if isinstance(x, dict):"; + Block [ + Line "return cls("; + Block from_json_class_arguments; + Line ")" + ]; + Line "else:"; + Block [ + Line (sprintf "_atd_bad_json('%s', x)" + (single_esc py_class_name)) + ] + ] + ] + in + let to_json = + [ + Line "def to_json(self) -> Any:"; + Block [ + Line "res: Dict[str, Any] = {}"; + Inline json_object_body; + Line "return res" + ] + ] + in + let from_json_string = + [ + Line "@classmethod"; + Line (sprintf "def from_json_string(cls, x: str) -> '%s':" + (single_esc py_class_name)); + Block [ + Line "return cls.from_json(json.loads(x))" + ] + ] + in + let to_json_string = + [ + Line "def to_json_string(self, **kw: Any) -> str:"; + Block [ + Line "return json.dumps(self.to_json(), **kw)" + ] + ] + in + [ + Inline class_decorators; + Line (sprintf "class %s:" py_class_name); + Block (spaced [ + Line (sprintf {|"""Original type: %s = { ... }"""|} name); + Inline inst_var_declarations; + Inline from_json; + Inline to_json; + Inline from_json_string; + Inline to_json_string; + ]) + ] + +(* + A general-purpose wrapper that provides json-related methods for a type. + This is used for tuples and for type aliases e.g. 'type foo = bar array'. + +class Foo: + def __init__(self, x: T): + ... + def to_json(self): + ... + def from_json(x): + ... + def to_json_string(self): + ... + def from_json_string(x): + ... +*) +let alias_wrapper env ~class_decorators name type_expr = + let py_class_name = class_name env name in + let value_type = type_name_of_expr env type_expr in + [ + Inline class_decorators; + Line (sprintf "class %s:" py_class_name); + Block [ + Line (sprintf {|"""Original type: %s"""|} name); + Line ""; + Line (sprintf "value: %s" value_type); + Line ""; + Line "@classmethod"; + Line (sprintf "def from_json(cls, x: Any) -> '%s':" + (single_esc py_class_name)); + Block [ + Line (sprintf "return cls(%s(x))" (json_reader env type_expr)) + ]; + Line ""; + Line "def to_json(self) -> Any:"; + Block [ + Line (sprintf "return %s(self.value)" (json_writer env type_expr)) + ]; + Line ""; + Line "@classmethod"; + Line (sprintf "def from_json_string(cls, x: str) -> '%s':" + (single_esc py_class_name)); + Block [ + Line "return cls.from_json(json.loads(x))" + ]; + Line ""; + Line "def to_json_string(self, **kw: Any) -> str:"; + Block [ + Line "return json.dumps(self.to_json(), **kw)" + ] + ] + ] + +let case_class env ~class_decorators type_name + (loc, orig_name, unique_name, an, opt_e) = + let json_name = Atd.Json.get_json_cons orig_name an in + match opt_e with + | None -> + [ + Inline class_decorators; + Line (sprintf "class %s:" (trans env unique_name)); + Block [ + Line (sprintf {|"""Original type: %s = [ ... | %s | ... ]"""|} + type_name + orig_name); + Line ""; + Line "@property"; + Line "def kind(self) -> str:"; + Block [ + Line {|"""Name of the class representing this variant."""|}; + Line (sprintf "return '%s'" (trans env unique_name)) + ]; + Line ""; + Line "@staticmethod"; + Line "def to_json() -> Any:"; + Block [ + Line (sprintf "return '%s'" (single_esc json_name)) + ]; + Line ""; + Line "def to_json_string(self, **kw: Any) -> str:"; + Block [ + Line "return json.dumps(self.to_json(), **kw)" + ] + ] + ] + | Some e -> + [ + Inline class_decorators; + Line (sprintf "class %s:" (trans env unique_name)); + Block [ + Line (sprintf {|"""Original type: %s = [ ... | %s of ... | ... ]"""|} + type_name + orig_name); + Line ""; + Line (sprintf "value: %s" (type_name_of_expr env e)); + Line ""; + Line "@property"; + Line "def kind(self) -> str:"; + Block [ + Line {|"""Name of the class representing this variant."""|}; + Line (sprintf "return '%s'" (trans env unique_name)) + ]; + Line ""; + Line "def to_json(self) -> Any:"; + Block [ + Line (sprintf "return ['%s', %s(self.value)]" + (single_esc json_name) + (json_writer env e)) + ]; + Line ""; + Line "def to_json_string(self, **kw: Any) -> str:"; + Block [ + Line "return json.dumps(self.to_json(), **kw)" + ] + ] + ] + +let read_cases0 env loc name cases0 = + 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 x == '%s':" (single_esc json_name)); + Block [ + Line (sprintf "return cls(%s())" (trans env unique_name)) + ] + ] + ) + in + [ + Inline ifs; + Line (sprintf "_atd_bad_json('%s', x)" + (class_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 cls(%s(%s(x[1])))" + (trans env unique_name) + (json_reader env e)) + ] + ] + ) + in + [ + Inline ifs; + Line (sprintf "_atd_bad_json('%s', x)" + (class_name env name |> single_esc)) + ] + +let sum_container env ~class_decorators loc name cases = + let py_class_name = class_name env name in + let type_list = + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + trans env unique_name + ) cases + |> String.concat ", " + 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 isinstance(x, str):"; + Block (read_cases0 env loc name cases0) + ] + else + [] + in + let cases1_block = + if cases1 <> [] then + [ + Line "if isinstance(x, List) and len(x) == 2:"; + Block [ + Line "cons = x[0]"; + Inline (read_cases1 env loc name cases1) + ] + ] + else + [] + in + [ + Inline class_decorators; + Line (sprintf "class %s:" py_class_name); + Block [ + Line (sprintf {|"""Original type: %s = [ ... ]"""|} name); + Line ""; + Line (sprintf "value: Union[%s]" type_list); + Line ""; + Line "@property"; + Line "def kind(self) -> str:"; + Block [ + Line {|"""Name of the class representing this variant."""|}; + Line (sprintf "return self.value.kind") + ]; + Line ""; + Line "@classmethod"; + Line (sprintf "def from_json(cls, x: Any) -> '%s':" + (single_esc py_class_name)); + Block [ + Inline cases0_block; + Inline cases1_block; + Line (sprintf "_atd_bad_json('%s', x)" + (single_esc (class_name env name))) + ]; + Line ""; + Line "def to_json(self) -> Any:"; + Block [ + Line "return self.value.to_json()"; + ]; + Line ""; + Line "@classmethod"; + Line (sprintf "def from_json_string(cls, x: str) -> '%s':" + (single_esc py_class_name)); + Block [ + Line "return cls.from_json(json.loads(x))" + ]; + Line ""; + Line "def to_json_string(self, **kw: Any) -> str:"; + Block [ + Line "return json.dumps(self.to_json(), **kw)" + ] + ] + ] + +let sum env ~class_decorators loc name cases = + let cases = + List.map (fun (x : variant) -> + match x with + | Variant (loc, (orig_name, an), opt_e) -> + let unique_name = create_class_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 ~class_decorators name x)) cases + |> double_spaced + in + let container_class = sum_container env ~class_decorators loc name cases in + [ + Inline case_classes; + Inline container_class; + ] + |> double_spaced + +let uses_dataclass_decorator = + let rex = Re.Pcre.regexp {|\A[ \t\r\n]*dataclass(\(|[ \t\r\n]|\z)|} in + fun s -> Re.Pcre.pmatch ~rex s + +let get_class_decorators an = + let decorators = Python_annot.get_python_decorators an in + (* Avoid duplicate use of the @dataclass decorator, which doesn't work + if some options like frozen=True are used. *) + if List.exists uses_dataclass_decorator decorators then + decorators + else + decorators @ ["dataclass"] + +let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = + if param <> [] then + not_implemented loc "parametrized type"; + let class_decorators = + get_class_decorators an + |> List.map (fun s -> Line ("@" ^ s)) + in + let rec unwrap e = + match e with + | Sum (loc, cases, an) -> + sum env ~class_decorators loc name cases + | Record (loc, fields, an) -> + record env ~class_decorators loc name fields an + | Tuple _ + | List _ + | Option _ + | Nullable _ + | Name _ -> alias_wrapper env ~class_decorators name e + | Shared _ -> not_implemented loc "cyclic references" + | Wrap (loc, e, an) -> unwrap e + | Tvar _ -> not_implemented loc "parametrized type" + in + unwrap e + +let module_body env x = + List.fold_left (fun acc (Type x) -> Inline (type_def env x) :: acc) [] x + |> List.rev + |> spaced + +let definition_group ~atd_filename env + (is_recursive, (items: A.module_body)) : B.t = + [ + Inline (module_body env items); + ] + +(* + Make sure that the types as defined in the atd file get a good name. + For example, type 'foo' should become class 'Foo'. + We do this because each case constructor of sum types will also + translate to a class 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_class_names env (items: A.module_body) = + List.iter + (fun (Type (loc, (name, param, an), e)) -> ignore (class_name env name)) + items + +let to_file ~atd_filename ~head (items : A.module_body) dst_path = + let env = init_env () in + reserve_good_class_names env items; + let head = List.map (fun s -> Line s) head in + let python_defs = + Atd.Util.tsort items + |> List.map (fun x -> Inline (definition_group ~atd_filename env x)) + in + Line (fixed_size_preamble atd_filename) :: Inline head :: python_defs + |> 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) ^ ".py" + |> String.lowercase_ascii + in + let dst_path = dst_name 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 = Python_annot.get_python_json_text (snd atd_head) in + to_file ~atd_filename:src_name ~head atd_module dst_path diff --git a/atdd/src/lib/Codegen.mli b/atdd/src/lib/Codegen.mli new file mode 100644 index 00000000..180fb5ef --- /dev/null +++ b/atdd/src/lib/Codegen.mli @@ -0,0 +1,7 @@ +(* + Dlang code generation for JSON support (no biniou support) +*) + +(** Take ATD type definitions and translate them to Dlang, writing + them out to a file which should have the '.d' extension. *) +val run_file : string -> unit diff --git a/atdd/src/lib/Indent.ml b/atdd/src/lib/Indent.ml new file mode 100644 index 00000000..f07cce71 --- /dev/null +++ b/atdd/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/atdd/src/lib/Indent.mli b/atdd/src/lib/Indent.mli new file mode 100644 index 00000000..ff0ed76d --- /dev/null +++ b/atdd/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/atdd/src/lib/Python_annot.ml b/atdd/src/lib/Python_annot.ml new file mode 100644 index 00000000..2778f152 --- /dev/null +++ b/atdd/src/lib/Python_annot.ml @@ -0,0 +1,71 @@ +(* + ATD annotations to be interpreted specifically by atdpy. + + Atdpy also honors json-related annotations defined in Atd.Json. +*) + +type assoc_repr = + | List + | Dict + +let get_python_default an : string option = + Atd.Annot.get_opt_field + ~parse:(fun s -> Some s) + ~sections:["python"] + ~field:"default" + an + +let get_python_assoc_repr an : assoc_repr = + Atd.Annot.get_field + ~parse:(function + | "list" -> Some List + | "dict" -> Some Dict + | _ -> None + ) + ~default:List + ~sections:["python"] + ~field:"repr" + an + +(* class decorator + + Later, we might want to add support for decorators on the from_json + and to_json methods. + + These would have more specific names such as "to_json_decorator" + and "from_json_decorator" or just "to_json" and "from_json". + If we want to be consistent with atdgen adapters, we might + want something like this: + + + + (which is less flexible than method decorators since method decorators + are left in charge of calling the origin method but the adapters + are simple functions from json to json) +*) +let get_python_decorators an : string list = + Atd.Annot.get_fields + ~parse:(fun s -> Some s) + ~sections:["python"] + ~field:"decorator" + an + +(* imports etc. *) +let get_python_text an : string list = + Atd.Annot.get_fields + ~parse:(fun s -> Some s) + ~sections:["python"] + ~field:"text" + an + +let get_python_json_text an : string list = + get_python_text an + @ Atd.Annot.get_fields + ~parse:(fun s -> Some s) + ~sections:["python"] + ~field:"json_py.text" + an diff --git a/atdd/src/lib/Python_annot.mli b/atdd/src/lib/Python_annot.mli new file mode 100644 index 00000000..5043c8af --- /dev/null +++ b/atdd/src/lib/Python_annot.mli @@ -0,0 +1,39 @@ +(** + Python-specific ATD annotations. + + This interface serves as a reference of which Python-specific + ATD annotations are supported. Atdpy also honors JSON-related annotations + defined in [Atd.Json]. +*) + +(** Extract ["42"] from []. + The provided default must be a well-formed Python immutable expression. + Python lists are mutable so they won't work (since they're stored + globally at the class level and they're not copied by the '__init__' + function magically generated by '@dataclass'). +*) +val get_python_default : Atd.Annot.t -> string option + +(** Whether an association list of ATD type [(string * foo) list] + must be represented in Python as a list of pairs or as a dictionary. + This is independent of the JSON representation. +*) +type assoc_repr = + | List + | Dict + +(** 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_python_assoc_repr : Atd.Annot.t -> assoc_repr + +(** Returns the list of class decorators as specified by the user without + [@] e.g. [] + gives [["foo"; "bar(baz)"]]. *) +val get_python_decorators : Atd.Annot.t -> string list + +(** Returns text the user wants to be inserted at the beginning of the + Python file such as imports. *) +val get_python_json_text : Atd.Annot.t -> string list diff --git a/atdd/src/lib/Version.ml b/atdd/src/lib/Version.ml new file mode 120000 index 00000000..f8f8bc2d --- /dev/null +++ b/atdd/src/lib/Version.ml @@ -0,0 +1 @@ +../../../atd/src/version.ml \ No newline at end of file diff --git a/atdd/src/lib/dune b/atdd/src/lib/dune new file mode 100644 index 00000000..74480725 --- /dev/null +++ b/atdd/src/lib/dune @@ -0,0 +1,7 @@ +(library + (name atdd) + (libraries + re + atd + ) +) diff --git a/atdd/src/test/Main.ml b/atdd/src/test/Main.ml new file mode 100644 index 00000000..6d20e7c9 --- /dev/null +++ b/atdd/src/test/Main.ml @@ -0,0 +1,14 @@ +(* + Entry point to the executable running the unit tests + + TODO: the only test suite we had moved to the atd library. Remove? +*) + +let test_suites : unit Alcotest.test list = [ + (* Unique_name.test *) +] + +let main () = + Alcotest.run "atdpy" test_suites + +let () = main () diff --git a/atdd/src/test/dune b/atdd/src/test/dune new file mode 100644 index 00000000..dd60da74 --- /dev/null +++ b/atdd/src/test/dune @@ -0,0 +1,12 @@ +(executable + (name Main) + (libraries + atdd + alcotest + ) +) + +(rule + (alias runtest) + (action (run ./Main.exe)) +) diff --git a/atdd/test/.gitignore b/atdd/test/.gitignore new file mode 100644 index 00000000..e3b3414c --- /dev/null +++ b/atdd/test/.gitignore @@ -0,0 +1,2 @@ +__pycache__ +everything.py diff --git a/atdd/test/atd-input/ALLCAPS.atd b/atdd/test/atd-input/ALLCAPS.atd new file mode 100644 index 00000000..e69de29b diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd new file mode 100644 index 00000000..faf7089c --- /dev/null +++ b/atdd/test/atd-input/everything.atd @@ -0,0 +1,65 @@ + + + + +type kind = [ + | Root (* class name conflict *) + | Thing of int + | WOW + | Amaze of string list +] + +type frozen + = [ + | A + | B of int +] + +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; + __init__: float; + items: int list list; + ?maybe: int option; + ~extras: int list; + ~answer : int; + aliased: alias; + point: (float * float); + 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; +} + +type alias = int 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; +} diff --git a/atdd/test/dune b/atdd/test/dune new file mode 100644 index 00000000..0df56117 --- /dev/null +++ b/atdd/test/dune @@ -0,0 +1,17 @@ +; +; We test in two phases: +; +; 1. Check that the generated Python code is what we expect. +; + +(rule + (alias runtest) + (package atdd) + (action + (diff dlang-expected/everything.py + dlang-tests/everything.py))) + +; 2. Run the generated Python code and check that is reads or writes JSON +; data as expected. +; +; See python-tests/dune diff --git a/atdd/test/python-tests/deco.py b/atdd/test/python-tests/deco.py new file mode 100644 index 00000000..3d545208 --- /dev/null +++ b/atdd/test/python-tests/deco.py @@ -0,0 +1,16 @@ +"""Test custom decorators. +""" + +from typing import Any + + +def deco1(cls: Any) -> Any: + print(f"Decorating class {cls.__name__} with deco1") + return cls + + +def deco2(n: int) -> Any: + def decorator(cls: Any) -> Any: + print(f"Decorating class {cls.__name__} with deco2({n})") + return cls + return decorator diff --git a/atdd/test/python-tests/dune b/atdd/test/python-tests/dune new file mode 100644 index 00000000..3814e0fa --- /dev/null +++ b/atdd/test/python-tests/dune @@ -0,0 +1,29 @@ +; +; Convert ATD -> Python +; +(rule + (targets + allcaps.py + everything.py + ) + (deps + ../atd-input/ALLCAPS.atd + ../atd-input/everything.atd + ) + (action + (run %{bin:atdpy} %{deps}))) + +; +; Typecheck and run the tests on the generated Python code. +; +(rule + (alias runtest) + (package atdpy) + (deps + everything.py + (glob_files *.py)) + (action + (progn + (run python3 -m flake8 .) + (run python3 -m mypy --strict .) + (run python3 -m pytest .)))) diff --git a/atdd/test/python-tests/manual_sample.py b/atdd/test/python-tests/manual_sample.py new file mode 100644 index 00000000..a6e868ab --- /dev/null +++ b/atdd/test/python-tests/manual_sample.py @@ -0,0 +1,214 @@ +""" +Handwritten code that serves as a model for generated code. +""" + +# Disable flake8 entirely on this file: +# flake8: noqa + +from dataclasses import dataclass +from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple + +import json + + +def _atd_missing_json_field(type_name: str, json_field_name: str) -> NoReturn: + raise ValueError(f"missing field '{json_field_name}'" + f" in JSON object of type '{type_name}'") + + +def _atd_bad_json(expected_type: str, json_value: Any) -> NoReturn: + value_str = str(json_value) + if len(value_str) > 200: + value_str = value_str[:200] + '…' + + raise ValueError(f"incompatible JSON value where" + f" type '{expected_type}' was expected: '{value_str}'") + + +def _atd_bad_python(expected_type: str, json_value: Any) -> NoReturn: + value_str = str(json_value) + if len(value_str) > 200: + value_str = value_str[:200] + '…' + + raise ValueError(f"incompatible Python value where" + f" type '{expected_type}' was expected: '{value_str}'") + + +def _atd_read_unit(x: Any) -> None: + if x is None: + return x + else: + _atd_bad_json('unit', x) + + +def _atd_read_bool(x: Any) -> bool: + if isinstance(x, bool): + return x + else: + _atd_bad_json('bool', x) + + +def _atd_read_int(x: Any) -> int: + if isinstance(x, int): + return x + else: + _atd_bad_json('int', x) + + +def _atd_read_float(x: Any) -> float: + if isinstance(x, (int, float)): + return x + else: + _atd_bad_json('float', x) + + +def _atd_read_string(x: Any) -> str: + if isinstance(x, str): + return x + else: + _atd_bad_json('str', x) + + +def _atd_read_list( + read_elt: Callable[[Any], Any] + ) -> Callable[[List[Any]], List[Any]]: + def read_list(elts: List[Any]) -> List[Any]: + if isinstance(elts, list): + return [read_elt(elt) for elt in elts] + else: + _atd_bad_json('array', elts) + return read_list + +def _atd_read_assoc_object_into_dict( + read_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: + def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: + if isinstance(elts, dict): + return {_atd_read_string(k): read_value(v) for k, v in elts.items()} + else: + _atd_bad_json('object', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_assoc_object_into_list( + read_value: Callable[[Any], Any] + ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: + def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: + if isinstance(elts, dict): + return [(_atd_read_string(k), read_value(v)) for k, v in elts.items()] + else: + _atd_bad_json('object', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_assoc_array_into_dict( + read_key: Callable[[Any], Any], + read_value: Callable[[Any], Any], + ) -> Callable[[List[Any]], Dict[str, Any]]: + def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: + if isinstance(elts, list): + return {read_key(elt[0]): read_value(elt[1]) for elt in elts} + else: + _atd_bad_json('array', elts) + raise AssertionError('impossible') # keep mypy happy + return read_assoc + + +def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def read_nullable(x: Any) -> Any: + if x is None: + return None + else: + return read_elt(x) + return read_nullable + + +def _atd_write_unit(x: Any) -> None: + if x is None: + return x + else: + _atd_bad_python('unit', x) + + +def _atd_write_bool(x: Any) -> bool: + if isinstance(x, bool): + return x + else: + _atd_bad_python('bool', x) + + +def _atd_write_int(x: Any) -> int: + if isinstance(x, int): + return x + else: + _atd_bad_python('int', x) + + +def _atd_write_float(x: Any) -> float: + if isinstance(x, (int, float)): + return x + else: + _atd_bad_python('float', x) + + +def _atd_write_string(x: Any) -> str: + if isinstance(x, str): + return x + else: + _atd_bad_python('str', x) + + +def _atd_write_list(write_elt: Callable[[Any], Any]) \ + -> Callable[[List[Any]], List[Any]]: + def write_list(elts: List[Any]) -> List[Any]: + if isinstance(elts, list): + return [write_elt(elt) for elt in elts] + else: + _atd_bad_python('list', elts) + return write_list + + +def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ + -> Callable[[Optional[Any]], Optional[Any]]: + def write_nullable(x: Any) -> Any: + if x is None: + return None + else: + return write_elt(x) + return write_nullable + + +@dataclass +class Root: + id: str + await_: bool + items: List[List[int]] + + @classmethod + def from_json(cls, x: Any) -> "Root": + if isinstance(x, dict): + return cls( + id=_atd_read_string(x['id']) if 'id' in x else _atd_missing_json_field('Root', 'id'), + await_=_atd_read_bool(x['await']) if 'await' in x else _atd_missing_json_field('Root', 'await'), + items=_atd_read_list(_atd_read_list(_atd_read_int))(x['items']) if 'items' in x else _atd_missing_json_field('Root', 'items'), + ) + else: + _atd_bad_json('Root', x) + raise AssertionError('impossible') # keep mypy happy + + def to_json(self) -> Any: + res: Dict[str, Any] = {} + res['id'] = _atd_write_string(self.id) + res['await'] = _atd_write_bool(self.await_) + res['items'] = _atd_write_list(_atd_write_list(_atd_write_int))(self.items) + return res + + @classmethod + def from_json_string(cls, x: str) -> "Root": + return cls.from_json(json.loads(x)) + + def to_json_string(self) -> str: + return json.dumps(self.to_json()) diff --git a/atdd/test/python-tests/test_atdpy.py b/atdd/test/python-tests/test_atdpy.py new file mode 100644 index 00000000..b505dc41 --- /dev/null +++ b/atdd/test/python-tests/test_atdpy.py @@ -0,0 +1,273 @@ +""" +Test suite for all Python code, some of which is generated by atdpy. + +Each function starting with 'test_' is executed as a test by pytest. +""" + +import manual_sample +import everything as e + + +def test_sample() -> None: + a_obj = manual_sample.Root(id="hello", await_=True, items=[[1, 2], [3]]) + a_str = a_obj.to_json_string() + + b_str = '{"id": "hello", "await": true, "items": [[1, 2], [3]]}' + b_obj = manual_sample.Root.from_json_string(a_str) + b_str2 = b_obj.to_json_string() + + assert b_str == b_str2 # depends on json formatting (whitespace...) + assert b_str2 == a_str + + +def test_sample_missing_field() -> None: + try: + manual_sample.Root.from_json_string('{}') + assert False + except ValueError: + pass + + +def test_sample_wrong_type() -> None: + try: + manual_sample.Root.from_json_string('["hello"]') + assert False + except ValueError: + pass + + +# mypy correctly rejects this. +# TODO: move to its own file and expect mypy to fail. +# def test_require_field() -> None: +# try: +# # Should fail because the 'req' field is required. +# e.RequireField() +# assert False +# except ValueError: +# pass + + +def test_everything_to_json() -> None: + a_obj = e.Root( + id="abc", + await_=True, + x___init__=1.5, + items=[[], [1, 2]], + extras=[17, 53], + answer=42, + aliased=e.Alias([8, 9, 10]), + point=(3.3, -77.22), + kinds=[ + e.Kind(e.WOW()), + e.Kind(e.Thing(99)), + e.Kind(e.Amaze(["a", "b"])), + e.Kind(e.Root_()) + ], + assoc1=[ + (1.1, 1), + (2.2, 2), + ], + assoc2=[ + ("c", 3), + ("d", 4), + ], + assoc3={ + 5.5: 5, + 6.6: 6, + }, + assoc4={ + "g": 7, + "h": 8, + }, + nullables=[12, None, 34], + options=[56, None, 78], + untyped_things=[[["hello"]], {}, None, 123], + parametrized_record=e.IntFloatParametrizedRecord( + field_a=42, + field_b=[9.9, 8.8], + ), + parametrized_tuple=e.KindParametrizedTuple( + (e.Kind(e.WOW()), e.Kind(e.WOW()), 100) + ) + ) + a_str = a_obj.to_json_string(indent=2) + print(a_str) + + # expected output copy-pasted from the output of the failing test + b_str = \ + """{ + "ID": "abc", + "await": true, + "__init__": 1.5, + "items": [ + [], + [ + 1, + 2 + ] + ], + "aliased": [ + 8, + 9, + 10 + ], + "point": [ + 3.3, + -77.22 + ], + "kinds": [ + "wow", + [ + "Thing", + 99 + ], + [ + "!!!", + [ + "a", + "b" + ] + ], + "Root" + ], + "assoc1": [ + [ + 1.1, + 1 + ], + [ + 2.2, + 2 + ] + ], + "assoc2": { + "c": 3, + "d": 4 + }, + "assoc3": [ + [ + 5.5, + 5 + ], + [ + 6.6, + 6 + ] + ], + "assoc4": { + "g": 7, + "h": 8 + }, + "nullables": [ + 12, + null, + 34 + ], + "options": [ + [ + "Some", + 56 + ], + "None", + [ + "Some", + 78 + ] + ], + "untyped_things": [ + [ + [ + "hello" + ] + ], + {}, + null, + 123 + ], + "parametrized_record": { + "field_a": 42, + "field_b": [ + 9.9, + 8.8 + ] + }, + "parametrized_tuple": [ + "wow", + "wow", + 100 + ], + "extras": [ + 17, + 53 + ], + "answer": 42 +}""" + b_obj = e.Root.from_json_string(a_str) + b_str2 = b_obj.to_json_string(indent=2) + + assert b_str == b_str2 # depends on json formatting (whitespace...) + assert b_str2 == a_str + + +def test_kind() -> None: + x = e.Kind(e.WOW()) + assert x.kind == x.value.kind + assert x.kind == 'WOW' + + +def test_pair() -> None: + try: + e.Pair.from_json_string('[1,2,3]') + assert False + except ValueError as exn: + print(f"Exception: {exn}") + assert str(exn) == ( + "incompatible JSON value where type " + "'array of length 2' was expected: '[1, 2, 3]'" + ) + + +def test_recursive_class() -> None: + child1 = e.RecursiveClass(id=1, flag=True, children=[]) + child2 = e.RecursiveClass(id=2, flag=True, children=[]) + a_obj = e.RecursiveClass(id=0, flag=False, children=[child1, child2]) + a_str = a_obj.to_json_string(indent=2) + + b_str = """{ + "id": 0, + "flag": false, + "children": [ + { + "id": 1, + "flag": true, + "children": [] + }, + { + "id": 2, + "flag": true, + "children": [] + } + ] +}""" + b_obj = e.RecursiveClass.from_json_string(a_str) + b_str2 = b_obj.to_json_string(indent=2) + + assert b_str == b_str2 + assert b_str2 == a_str + + +def test_default_list() -> None: + a = e.DefaultList(items=[]) + assert a.items == [] + b = e.DefaultList() + assert b.items == [] + c = e.DefaultList.from_json_string("{}") + assert c.items == [] + # We could emit '{}' instead of '{"items": []}' but it's more complicated + # and not always desired. + j = b.to_json_string() + assert j == '{"items": []}' + + +# print updated json +test_everything_to_json() diff --git a/dune b/dune index 1a643634..49ce9dd0 100644 --- a/dune +++ b/dune @@ -11,3 +11,4 @@ (rule (copy atd.opam.template atdpy.opam.template)) (rule (copy atd.opam.template atds.opam.template)) (rule (copy atd.opam.template atdts.opam.template)) +(rule (copy atd.opam.template atdd.opam.template)) diff --git a/dune-project b/dune-project index 13fc10b3..d03400fa 100644 --- a/dune-project +++ b/dune-project @@ -195,3 +195,16 @@ bucklescript backend") re (alcotest :with-test) (odoc :with-doc))) + + +(package + (name atdd) + (synopsis "DLang code generation for ATD APIs") + (description "DLang code generation for ATD APIs") + (depends + (ocaml (>= 4.08)) + (atd (>= 2.11.0)) + (cmdliner (>= 1.1.0)) + re + (alcotest :with-test) + (odoc :with-doc))) From 8f15686ff245a5776bd4d1cb5f23c608d40a9b32 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Wed, 7 Jun 2023 14:37:32 +0000 Subject: [PATCH 02/91] wip2 --- atdd/src/lib/Codegen.ml | 37 ++++++++----------------------------- 1 file changed, 8 insertions(+), 29 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 28624400..e642d3df 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -1,23 +1,7 @@ (* - Python code generation for JSON support (no biniou support) - - Takes the contents of a .atd file and translates it to a .py file. - - Design: - - Python's standard 'json' module handles the parsing into generic - dictionaries and such. - - The generated code assigns one class to each ATD record. The use - of type annotations allows for some type checking with mypy. - - When converting from JSON to Python, the well-formedness of the data - is checked. - - When converting from Python to JSON, the well-formedness of the data - is checked as well since the type system is too easy to bypass. - - Sum types use one main class and one subclass per case. - - Tuples, like arrays, options, and nullables don't get a class of - their own. - - Generic functions are provided to deal with the case where the JSON - root is an array. + Dlang 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. *) @@ -37,22 +21,19 @@ type env = { translate_inst_variable: unit -> (string -> string); } -let annot_schema_python : Atd.Annot.schema_section = +let annot_schema_dlang : Atd.Annot.schema_section = { - section = "python"; + section = "dlang"; fields = [ - Module_head, "text"; - Module_head, "json_py.text"; - Type_def, "decorator"; Type_expr, "repr"; Field, "default"; ] } let annot_schema : Atd.Annot.schema = - annot_schema_python :: Atd.Json.annot_schema_json + annot_schema_dlang :: Atd.Json.annot_schema_json -(* Translate a preferred variable name into an available Python identifier. *) +(* Translate a preferred variable name into an available Dlang identifier. *) let trans env id = env.translate_variable id @@ -97,6 +78,7 @@ let create_class_name env name = let preferred_id = to_camel_case name in env.create_variable preferred_id +(* TODO : edit list of keywoard based on dlang *) let init_env () : env = let keywords = [ (* Keywords @@ -193,15 +175,12 @@ let _double_esc s = escape_string_content Double s let fixed_size_preamble atd_filename = - sprintf {|"""Generated by atdpy from type definitions in %s. + sprintf {|"""Generated by atdd from type definitions in %s. This implements classes for the types defined in '%s', providing methods and functions to convert data from/to JSON. """ -# Disable flake8 entirely on this file: -# flake8: noqa - # Import annotations to allow forward references from __future__ import annotations from dataclasses import dataclass, field From 2947933f71d5bc8d3410d6ffdee176397c803b6f Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Wed, 7 Jun 2023 17:03:50 +0000 Subject: [PATCH 03/91] wipwip --- atdd/src/lib/Codegen.ml | 65 +++++++++---------- .../lib/{Python_annot.ml => Dlang_annot.ml} | 26 ++++---- atdd/src/lib/Dlang_annot.mli | 36 ++++++++++ atdd/src/lib/Python_annot.mli | 39 ----------- 4 files changed, 80 insertions(+), 86 deletions(-) rename atdd/src/lib/{Python_annot.ml => Dlang_annot.ml} (73%) create mode 100644 atdd/src/lib/Dlang_annot.mli delete mode 100644 atdd/src/lib/Python_annot.mli diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index e642d3df..72f531a2 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -454,7 +454,7 @@ let double_spaced blocks = spaced ~spacer:[Line ""; Line ""] blocks (* - Representations of ATD type '(string * value) list' in JSON and Python. + Representations of ATD type '(string * value) list' in JSON and Dlang. Key type or value type are provided when it's useful. *) type assoc_kind = @@ -466,8 +466,8 @@ type assoc_kind = let assoc_kind loc (e : type_expr) an : assoc_kind = let json_repr = Atd.Json.get_json_list an in - let python_repr = Python_annot.get_python_assoc_repr an in - match e, json_repr, python_repr with + let dlang_repr = Dlang_annot.get_dlang_assoc_repr an in + match e, json_repr, dlang_repr with | Tuple (loc, [(_, key, _); (_, value, _)], an2), Array, Dict -> Array_dict (key, value) | Tuple (loc, @@ -481,15 +481,15 @@ let assoc_kind loc (e : type_expr) an : assoc_kind = | _, Object, _ -> error_at loc "not a (string * _) list" | _, Array, _ -> error_at loc "not a (_ * _) list" -(* Map ATD built-in types to built-in mypy types *) -let py_type_name env (name : string) = +(* Map ATD built-in types to built-in Dlang types *) +let dlang_type_name env (name : string) = match name with - | "unit" -> "None" + | "unit" -> "void" | "bool" -> "bool" | "int" -> "int" | "float" -> "float" - | "string" -> "str" - | "abstract" -> "Any" + | "string" -> "string" + | "abstract" -> "abstract" (* TODO : figure out *) | user_defined -> class_name env user_defined let rec type_name_of_expr env (e : type_expr) : string = @@ -501,25 +501,25 @@ let rec type_name_of_expr env (e : type_expr) : string = xs |> List.map (fun (loc, x, an) -> type_name_of_expr env x) in - sprintf "Tuple[%s]" (String.concat ", " type_names) + sprintf "Tuple!(%s)" (String.concat ", " type_names) | List (loc, e, an) -> (match assoc_kind loc e an with | Array_list | Object_list _ -> - sprintf "List[%s]" + sprintf "%s[]" (type_name_of_expr env e) | Array_dict (key, value) -> - sprintf "Dict[%s, %s]" + sprintf "%s[%s]" (type_name_of_expr env key) (type_name_of_expr env value) | Object_dict value -> - sprintf "Dict[str, %s]" + sprintf "string[%s]" (* TODO : dubious*) (type_name_of_expr env value) ) - | Option (loc, e, an) -> sprintf "Optional[%s]" (type_name_of_expr env e) - | Nullable (loc, e, an) -> sprintf "Optional[%s]" (type_name_of_expr env e) - | Shared (loc, e, an) -> not_implemented loc "shared" + | Option (loc, e, an) -> sprintf "Nullable!%s" (type_name_of_expr env e) + | Nullable (loc, e, an) -> sprintf "Nullable!%s" (type_name_of_expr env e) + | Shared (loc, e, an) -> not_implemented loc "shared" (* TODO *) | Wrap (loc, e, an) -> todo "wrap" - | Name (loc, (loc2, name, []), an) -> py_type_name env name + | Name (loc, (loc2, name, []), an) -> dlang_type_name env name | Name (loc, (_, name, _::_), _) -> assert false | Tvar (loc, _) -> not_implemented loc "type variables" @@ -530,24 +530,24 @@ let rec get_default_default (e : type_expr) : string option = | Tuple _ (* a default tuple could be possible but we're lazy *) -> None | List _ -> Some "[]" | Option _ - | Nullable _ -> Some "None" + | Nullable _ -> Some "null" | 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" -> Some "None" - | "bool" -> Some "False" + | "unit" -> Some "null" + | "bool" -> Some "false" | "int" -> Some "0" | "float" -> Some "0.0" | "string" -> Some {|""|} - | "abstract" -> Some "None" + | "abstract" -> Some "null" | _ -> None ) | Name _ -> None | Tvar _ -> None -let get_python_default (e : type_expr) (an : annot) : string option = - let user_default = Python_annot.get_python_default an in +let get_dlang_default (e : type_expr) (an : annot) : string option = + let user_default = Dlang_annot.get_dlang_default an in match user_default with | Some s -> Some s | None -> get_default_default e @@ -559,7 +559,7 @@ let has_no_class_inst_prop_default | Required -> true | Optional -> (* default is None *) false | With_default -> - match get_python_default e an with + match get_dlang_default e an with | Some _ -> false | None -> (* There's either no default at all which is an error, @@ -588,6 +588,7 @@ let unwrap_field_type loc field_name kind e = let inst_var_name trans_meth field_name = trans_meth field_name +(* TODO : done up to here *) let rec json_writer env e = match e with | Sum (loc, _, _) -> not_implemented loc "inline sum types" @@ -622,7 +623,7 @@ let rec json_writer env e = | Tvar (loc, _) -> not_implemented loc "type variables" (* - Convert python tuple to json list + Convert dlang tuple to json list (lambda x: [write0(x[0]), write1(x[1])] if isinstance(x, tuple) else error()) *) @@ -716,11 +717,7 @@ and tuple_reader env cells = ) cells |> String.concat ", " in - sprintf "(lambda x: (%s) \ - if isinstance(x, list) and len(x) == %d \ - else _atd_bad_json('array of length %d', x))" - tuple_body - len len + sprintf "(JSONValue x) => tuple(%s)" tuple_body let from_json_class_argument env trans_meth py_class_name ((loc, (name, kind, an), e) : simple_field) = @@ -746,7 +743,7 @@ let from_json_class_argument (single_esc json_name) | Optional -> "None" | With_default -> - match get_python_default e an with + match get_dlang_default e an with | Some x -> x | None -> A.error_at loc @@ -770,7 +767,7 @@ let inst_var_declaration | Required -> "" | Optional -> " = None" | With_default -> - match get_python_default unwrapped_e an with + match get_dlang_default unwrapped_e an with | None -> "" | Some x -> (* This constructs ensures that a fresh default value is @@ -1140,7 +1137,7 @@ let uses_dataclass_decorator = fun s -> Re.Pcre.pmatch ~rex s let get_class_decorators an = - let decorators = Python_annot.get_python_decorators an in + let decorators = Dlang_annot.get_dlang_decorators an in (* Avoid duplicate use of the @dataclass decorator, which doesn't work if some options like frozen=True are used. *) if List.exists uses_dataclass_decorator decorators then @@ -1215,7 +1212,7 @@ let run_file src_path = (if Filename.check_suffix src_name ".atd" then Filename.chop_suffix src_name ".atd" else - src_name) ^ ".py" + src_name) ^ ".d" |> String.lowercase_ascii in let dst_path = dst_name in @@ -1230,5 +1227,5 @@ let run_file 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 = Python_annot.get_python_json_text (snd atd_head) in + let head = Dlang_annot.get_dlang_json_text (snd atd_head) in to_file ~atd_filename:src_name ~head atd_module dst_path diff --git a/atdd/src/lib/Python_annot.ml b/atdd/src/lib/Dlang_annot.ml similarity index 73% rename from atdd/src/lib/Python_annot.ml rename to atdd/src/lib/Dlang_annot.ml index 2778f152..808c5b90 100644 --- a/atdd/src/lib/Python_annot.ml +++ b/atdd/src/lib/Dlang_annot.ml @@ -1,21 +1,21 @@ (* - ATD annotations to be interpreted specifically by atdpy. + ATD annotations to be interpreted specifically by atdd. - Atdpy also honors json-related annotations defined in Atd.Json. + Atdd also honors json-related annotations defined in Atd.Json. *) type assoc_repr = | List | Dict -let get_python_default an : string option = +let get_dlang_default an : string option = Atd.Annot.get_opt_field ~parse:(fun s -> Some s) - ~sections:["python"] + ~sections:["dlang"] ~field:"default" an -let get_python_assoc_repr an : assoc_repr = +let get_dlang_assoc_repr an : assoc_repr = Atd.Annot.get_field ~parse:(function | "list" -> Some List @@ -23,7 +23,7 @@ let get_python_assoc_repr an : assoc_repr = | _ -> None ) ~default:List - ~sections:["python"] + ~sections:["dlang"] ~field:"repr" an @@ -47,25 +47,25 @@ let get_python_assoc_repr an : assoc_repr = are left in charge of calling the origin method but the adapters are simple functions from json to json) *) -let get_python_decorators an : string list = +let get_dlang_decorators an : string list = Atd.Annot.get_fields ~parse:(fun s -> Some s) - ~sections:["python"] + ~sections:["dlang"] ~field:"decorator" an (* imports etc. *) -let get_python_text an : string list = +let get_dlang_text an : string list = Atd.Annot.get_fields ~parse:(fun s -> Some s) - ~sections:["python"] + ~sections:["dlang"] ~field:"text" an -let get_python_json_text an : string list = - get_python_text an +let get_dlang_json_text an : string list = + get_dlang_text an @ Atd.Annot.get_fields ~parse:(fun s -> Some s) - ~sections:["python"] + ~sections:["dlang"] ~field:"json_py.text" an diff --git a/atdd/src/lib/Dlang_annot.mli b/atdd/src/lib/Dlang_annot.mli new file mode 100644 index 00000000..4ddf5a81 --- /dev/null +++ b/atdd/src/lib/Dlang_annot.mli @@ -0,0 +1,36 @@ +(** + Dlang-specific ATD annotations. + + This interface serves as a reference of which Dlang-specific + ATD annotations are supported. Atdd also honors JSON-related annotations + defined in [Atd.Json]. +*) + +(** Extract ["42"] from []. + The provided default must be a well-formed Dlang immutable expression. +*) +val get_dlang_default : Atd.Annot.t -> string option + +(** Whether an association list of ATD type [(string * foo) list] + must be represented in Dlang as a list of pairs or as a dictionary. + This is independent of the JSON representation. +*) +type assoc_repr = + | List + | Dict + +(** 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_dlang_assoc_repr : Atd.Annot.t -> assoc_repr + +(** Returns the list of class decorators as specified by the user without + [@] e.g. [] + gives [["foo"; "bar(baz)"]]. *) +val get_dlang_decorators : Atd.Annot.t -> string list + +(** Returns text the user wants to be inserted at the beginning of the + Dlang file such as imports. *) +val get_dlang_json_text : Atd.Annot.t -> string list diff --git a/atdd/src/lib/Python_annot.mli b/atdd/src/lib/Python_annot.mli deleted file mode 100644 index 5043c8af..00000000 --- a/atdd/src/lib/Python_annot.mli +++ /dev/null @@ -1,39 +0,0 @@ -(** - Python-specific ATD annotations. - - This interface serves as a reference of which Python-specific - ATD annotations are supported. Atdpy also honors JSON-related annotations - defined in [Atd.Json]. -*) - -(** Extract ["42"] from []. - The provided default must be a well-formed Python immutable expression. - Python lists are mutable so they won't work (since they're stored - globally at the class level and they're not copied by the '__init__' - function magically generated by '@dataclass'). -*) -val get_python_default : Atd.Annot.t -> string option - -(** Whether an association list of ATD type [(string * foo) list] - must be represented in Python as a list of pairs or as a dictionary. - This is independent of the JSON representation. -*) -type assoc_repr = - | List - | Dict - -(** 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_python_assoc_repr : Atd.Annot.t -> assoc_repr - -(** Returns the list of class decorators as specified by the user without - [@] e.g. [] - gives [["foo"; "bar(baz)"]]. *) -val get_python_decorators : Atd.Annot.t -> string list - -(** Returns text the user wants to be inserted at the beginning of the - Python file such as imports. *) -val get_python_json_text : Atd.Annot.t -> string list From 20e6295f7680a25e9a5a2b4e31987b199cbbc604 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 11:43:55 +0200 Subject: [PATCH 04/91] codegen: replace python keywords by dlang keywords --- atdd/src/lib/Codegen.ml | 28 +++++++++++++++------------- 1 file changed, 15 insertions(+), 13 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 72f531a2..094c8934 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -82,20 +82,22 @@ let create_class_name env name = let init_env () : env = let keywords = [ (* Keywords - https://docs.python.org/3/reference/lexical_analysis.html#keywords + https://dlang.org/spec/lex.html#keywords *) - "False"; "await"; "else"; "import"; "pass"; - "None"; "break"; "except"; "in"; "raise"; - "True"; "class"; "finally"; "is"; "return"; - "and"; "continue"; "for"; "lambda"; "try"; - "as"; "def"; "from"; "nonlocal"; "while"; - "assert"; "del"; "global"; "not"; "with"; - "async"; "elif"; "if"; "or"; "yield"; - - (* Soft keywords - https://docs.python.org/3/reference/lexical_analysis.html#soft-keywords - *) - "match"; "case"; "_"; + "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. From d7521f5b78c5a801aed6f322d3424ed600479e27 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Wed, 7 Jun 2023 17:11:44 +0000 Subject: [PATCH 05/91] wip: compile records --- atdd/src/lib/Codegen.ml | 83 +++++++++++++++-------------------------- 1 file changed, 30 insertions(+), 53 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 094c8934..0d3d16e9 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -554,20 +554,6 @@ let get_dlang_default (e : type_expr) (an : annot) : string option = | Some s -> Some s | None -> get_default_default e -(* see explanation where this function is used *) -let has_no_class_inst_prop_default - ((loc, (name, kind, an), e) : simple_field) = - match kind with - | Required -> true - | Optional -> (* default is None *) false - | With_default -> - match get_dlang_default e an with - | Some _ -> false - | None -> - (* There's either no default at all which is an error, - or the default value is known to be mutable. *) - true - (* If the field is '?foo: bar option', its python or json value has type 'bar' rather than 'bar option'. *) let unwrap_field_type loc field_name kind e = @@ -781,8 +767,8 @@ let inst_var_declaration Line (sprintf "%s: %s%s" var_name type_name default) ] -let record env ~class_decorators loc name (fields : field list) an = - let py_class_name = class_name env name in +let record env loc name (fields : field list) an = + let dlang_struct_name = class_name env name in let trans_meth = env.translate_inst_variable () in let fields = List.map (function @@ -790,18 +776,6 @@ let record env ~class_decorators loc name (fields : field list) an = | `Inherit _ -> (* expanded at loading time *) assert false) fields in - (* - Reorder fields with no-defaults first as required by @dataclass. - Starting with Python 3.10, '@dataclass(kw_only=True)' solves this - problem and makes this reordering unnecessary. - TODO: remove once we require python >= 3.10. It could also be done - as command-line flag specifying the Python version. - *) - let fields = - let no_default, with_default = - List.partition has_no_class_inst_prop_default fields in - no_default @ with_default - in let inst_var_declarations = List.map (fun x -> Inline (inst_var_declaration env trans_meth x)) fields in @@ -810,13 +784,12 @@ let record env ~class_decorators loc name (fields : field list) an = Inline (construct_json_field env trans_meth x)) fields in let from_json_class_arguments = List.map (fun x -> - Line (from_json_class_argument env trans_meth py_class_name x) + Line (from_json_class_argument env trans_meth dlang_struct_name x) ) fields in let from_json = [ - Line "@classmethod"; - Line (sprintf "def from_json(cls, x: Any) -> '%s':" - (single_esc py_class_name)); + Line (sprintf "%s fromJson(JSONValue j) {" + (single_esc dlang_struct_name)); Block [ Line "if isinstance(x, dict):"; Block [ @@ -827,42 +800,44 @@ let record env ~class_decorators loc name (fields : field list) an = Line "else:"; Block [ Line (sprintf "_atd_bad_json('%s', x)" - (single_esc py_class_name)) + (single_esc dlang_struct_name)) ] - ] + ]; + Line "}"; ] in let to_json = [ - Line "def to_json(self) -> Any:"; + Line "JSONValue toJson() {"; Block [ - Line "res: Dict[str, Any] = {}"; + Line ("JSONValue res = JSONValue.emptyObject;"); Inline json_object_body; - Line "return res" - ] + Line "return res;" + ]; + Line "}"; ] in let from_json_string = [ - Line "@classmethod"; - Line (sprintf "def from_json_string(cls, x: str) -> '%s':" - (single_esc py_class_name)); + Line (sprintf "%s fromJsonString(string x) {" + (single_esc dlang_struct_name)); Block [ Line "return cls.from_json(json.loads(x))" - ] + ]; + Line "}"; ] in let to_json_string = [ - Line "def to_json_string(self, **kw: Any) -> str:"; + Line "string toJsonString(%s obj) {"; Block [ Line "return json.dumps(self.to_json(), **kw)" - ] + ]; + Line "}"; ] in [ - Inline class_decorators; - Line (sprintf "class %s:" py_class_name); + Line (sprintf "struct %s {" dlang_struct_name); Block (spaced [ Line (sprintf {|"""Original type: %s = { ... }"""|} name); Inline inst_var_declarations; @@ -870,7 +845,8 @@ let record env ~class_decorators loc name (fields : field list) an = Inline to_json; Inline from_json_string; Inline to_json_string; - ]) + ]); + Line ("}"); ] (* @@ -890,11 +866,11 @@ class Foo: ... *) let alias_wrapper env ~class_decorators name type_expr = - let py_class_name = class_name env name in + let dlang_struct_name = class_name env name in let value_type = type_name_of_expr env type_expr in [ Inline class_decorators; - Line (sprintf "class %s:" py_class_name); + Line (sprintf "struct %s {" dlang_struct_name); Block [ Line (sprintf {|"""Original type: %s"""|} name); Line ""; @@ -902,7 +878,7 @@ let alias_wrapper env ~class_decorators name type_expr = Line ""; Line "@classmethod"; Line (sprintf "def from_json(cls, x: Any) -> '%s':" - (single_esc py_class_name)); + (single_esc dlang_struct_name)); Block [ Line (sprintf "return cls(%s(x))" (json_reader env type_expr)) ]; @@ -914,7 +890,7 @@ let alias_wrapper env ~class_decorators name type_expr = Line ""; Line "@classmethod"; Line (sprintf "def from_json_string(cls, x: str) -> '%s':" - (single_esc py_class_name)); + (single_esc dlang_struct_name)); Block [ Line "return cls.from_json(json.loads(x))" ]; @@ -922,7 +898,8 @@ let alias_wrapper env ~class_decorators name type_expr = Line "def to_json_string(self, **kw: Any) -> str:"; Block [ Line "return json.dumps(self.to_json(), **kw)" - ] + ]; + Line "}" ] ] @@ -1159,7 +1136,7 @@ let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = | Sum (loc, cases, an) -> sum env ~class_decorators loc name cases | Record (loc, fields, an) -> - record env ~class_decorators loc name fields an + record env loc name fields an | Tuple _ | List _ | Option _ From d6c13602e48d184829a9fdc3e3ba14650a4d3ed1 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 12:12:52 +0200 Subject: [PATCH 06/91] d: add 80% of readers --- atdd/src/lib/fixed_size_preamble.d | 427 +++++++++++++++++++++++++++++ 1 file changed, 427 insertions(+) create mode 100644 atdd/src/lib/fixed_size_preamble.d diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d new file mode 100644 index 00000000..bbbe1f34 --- /dev/null +++ b/atdd/src/lib/fixed_size_preamble.d @@ -0,0 +1,427 @@ +// """Generated by atdd 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 +// ############################################################################ + +import std.format; +import std.conv; +import std.json; +import std.typecons : tuple, Tuple; +import std.array : array; +import std.algorithm : map; + +class AtdException : Exception +{ + this(string msg, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line); + } +} + +void _atd_missing_json_field(string typeName, string jsonFieldName) +{ + throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); +} + +// TODO check later if template is right way to go +AtdException _atd_bad_json(T)(string expectedType, T jsonValue) +{ + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } + + return new AtdException( + "incompatible JSON value where type '%s' was expected: %s".format( + expectedType, valueStr + )); +} + +AtdException _atd_bad_d(T)(string expectedType, T jsonValue) +{ + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } + + return new AtdException( + "incompatible D value where type '%s' was expected: %s".format( + expectedType, valueStr + )); +} + +typeof(null) _atd_read_unit(JSONValue x) +{ + if (x.isNull) + return null; + else + throw _atd_bad_json("unit", x); +} + +bool _atd_read_bool(JSONValue x) +{ + try + return x.boolean; + catch (JSONException e) + throw _atd_bad_json("bool", x); +} + +long _atd_read_int(JSONValue x) +{ + try + return x.integer; + catch (JSONException e) + throw _atd_bad_json("int", x); +} + +float _atd_read_float(JSONValue x) +{ + try + return x.floating; + catch (JSONException e) + throw _atd_bad_json("float", x); +} + +string _atd_read_string(JSONValue x) +{ + try + return x.str; + catch (JSONException e) + throw _atd_bad_json("string", x); +} + +auto _atd_read_list(T)(T function(JSONValue) readElements) +{ + return (JSONValue[] list) { return array(list.map!readElements()); }; +} + +auto _atd_read_tuple_list_into_assoc_array(K, V)( + K function(JSONValue) readKey, + V function(JSONValue) readValue) +{ + auto innerFun = (Tuple!(JSONValue, JSONValue)[] tupList) { + V[K] ret; + foreach (tup; tupList) + ret[readKey(tup[0])] = readValue(tup[1]); + return ret; + }; + + return innerFun; +} + +auto _atd_read_tuple_list_into_tuple_list(K, V)( + K function(JSONValue) readKey, + V function(JSONValue) readValue) +{ + auto fun = (Tuple!(JSONValue, JSONValue)[] tupList) { + auto ret = new Tuple!(K, V)[](tupList.length); + foreach (i, tup; tupList) + ret[i] = tuple(readKey(tup[0]), readValue(tup[1])); + return ret; + }; + return fun; +} + +// We cannot use JSONValue as key, so we probably don't want to use that +// but the tuple versions +auto _atd_read_assoc_array_into_assoc_array(V)( + string function(string) readKey, + V function(JSONValue) readValue) +{ + auto fun = (JSONValue[string] assocArr) { + V[string] ret; + foreach (key, val; assocArr) + ret[readKey(key)] = readValue(val); + return ret; + }; + return fun; +} + +// ditto +auto _atd_read_assoc_array_into_tuple_list(V)( + string function(string) readKey, + V function(JSONValue) readValue) +{ + auto fun = (JSONValue[string] assocArr) { + auto ret = new Tuple!(string, V)[](assocArr.length); + uint i = 0; + foreach (key, val; assocArr) + ret[i++] = tuple(readKey(key), readValue(val)); + return ret; + }; + return fun; +} + + +unittest +{ + import std.stdio; + + try + _atd_bad_json("bool", null); + catch (AtdException e) + { + writeln(e.msg); + assert(true); + } +} + +unittest +{ + import std.stdio; + + auto fp = (JSONValue c) { return c.integer * 4; }; + auto m = _atd_read_list(fp); + JSONValue[] l = [ + JSONValue(1), JSONValue(2), JSONValue(3), JSONValue(4), JSONValue(5) + ]; +} + +unittest +{ + import std.stdio; + + auto m = _atd_read_tuple_list_into_assoc_array(&_atd_read_string, &_atd_read_int); + + auto l = [ + tuple(JSONValue("hello"), JSONValue(1)), + tuple(JSONValue("there"), JSONValue(2)), + tuple(JSONValue("general"), JSONValue(3)), + tuple(JSONValue("kenobi"), JSONValue(4)), + tuple(JSONValue("haha"), JSONValue(5)), + ]; + writeln(m(l)); +} + +unittest +{ + import std.stdio; + + auto m = _atd_read_tuple_list_into_tuple_list(&_atd_read_string, &_atd_read_int); + + auto l = [ + tuple(JSONValue("hello"), JSONValue(1)), + tuple(JSONValue("there"), JSONValue(2)), + tuple(JSONValue("general"), JSONValue(3)), + tuple(JSONValue("kenobi"), JSONValue(4)), + tuple(JSONValue("haha"), JSONValue(5)), + ]; + writeln(m(l)); +} + +unittest +{ + import std.stdio; + + auto m = _atd_read_assoc_array_into_assoc_array((string s) { return s; }, &_atd_read_int); + + auto l = [ + "hello": JSONValue(1), + "there": JSONValue(2), + "general": JSONValue(3), + "kenobi": JSONValue(4), + "haha": JSONValue(5), + ]; + writeln(m(l)); +} + +unittest +{ + import std.stdio; + + auto m = _atd_read_assoc_array_into_tuple_list((string s) { return s; }, &_atd_read_int); + + auto l = [ + "hello": JSONValue(1), + "there": JSONValue(2), + "general": JSONValue(3), + "kenobi": JSONValue(4), + "haha": JSONValue(5), + ]; + writeln(m(l)); +} + +// unittest +// { +// import std.stdio; + +// auto m = _atd_read_assoc_array_into_tuple_list(&_atd_read_string, &_atd_read_int); + +// auto l = [ +// JSONValue("hello"): JSONValue(1), +// JSONValue("there"): JSONValue(2), +// JSONValue("general"): JSONValue(3), +// JSONValue("kenobi"): JSONValue(4), +// JSONValue("haha"): JSONValue(5), +// ]; +// writeln(m(l)); +// } + +unittest +{ + // import std.stdio; + // auto fp = (uint c) { return c * 4; }; + // // Delegate d = int () { return x; }; + // auto m = _atd_read_list(fp); + + // uint[] l = cast(uint[]) [1, 2, 3, 4, 5]; + // writeln(m(l)); +} + +// def _atd_read_assoc_array_into_dict( +// read_key: Callable[[Any], Any], +// read_value: Callable[[Any], Any], +// ) -> Callable[[List[Any]], Dict[Any, Any]]: +// def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: +// if isinstance(elts, list): +// return {read_key(elt[0]): read_value(elt[1]) for elt in elts} +// else: +// _atd_bad_json('array', elts) +// raise AssertionError('impossible') # keep mypy happy +// return read_assoc + +// def _atd_read_assoc_object_into_dict( +// read_value: Callable[[Any], Any] +// ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: +// def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: +// if isinstance(elts, dict): +// return {_atd_read_string(k): read_value(v) +// for k, v in elts.items()} +// else: +// _atd_bad_json('object', elts) +// raise AssertionError('impossible') # keep mypy happy +// return read_assoc + +// def _atd_read_assoc_object_into_list( +// read_value: Callable[[Any], Any] +// ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: +// def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: +// if isinstance(elts, dict): +// return [(_atd_read_string(k), read_value(v)) +// for k, v in elts.items()] +// else: +// _atd_bad_json('object', elts) +// raise AssertionError('impossible') # keep mypy happy +// return read_assoc + +// def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ +// -> Callable[[Optional[Any]], Optional[Any]]: +// def read_nullable(x: Any) -> Any: +// if x is None: +// return None +// else: +// return read_elt(x) +// return read_nullable + +// def _atd_read_option(read_elt: Callable[[Any], Any]) \ +// -> Callable[[Optional[Any]], Optional[Any]]: +// def read_option(x: Any) -> Any: +// if x == 'None': +// return None +// elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some': +// return read_elt(x[1]) +// else: +// _atd_bad_json('option', x) +// raise AssertionError('impossible') # keep mypy happy +// return read_option + +// def _atd_write_unit(x: Any) -> None: +// if x is None: +// return x +// else: +// _atd_bad_python('unit', x) + +// def _atd_write_bool(x: Any) -> bool: +// if isinstance(x, bool): +// return x +// else: +// _atd_bad_python('bool', x) + +// def _atd_write_int(x: Any) -> int: +// if isinstance(x, int): +// return x +// else: +// _atd_bad_python('int', x) + +// def _atd_write_float(x: Any) -> float: +// if isinstance(x, (int, float)): +// return x +// else: +// _atd_bad_python('float', x) + +// def _atd_write_string(x: Any) -> str: +// if isinstance(x, str): +// return x +// else: +// _atd_bad_python('str', x) + +// def _atd_write_list( +// write_elt: Callable[[Any], Any] +// ) -> Callable[[List[Any]], List[Any]]: +// def write_list(elts: List[Any]) -> List[Any]: +// if isinstance(elts, list): +// return [write_elt(elt) for elt in elts] +// else: +// _atd_bad_python('list', elts) +// return write_list + +// def _atd_write_assoc_dict_to_array( +// write_key: Callable[[Any], Any], +// write_value: Callable[[Any], Any] +// ) -> Callable[[Dict[Any, Any]], List[Tuple[Any, Any]]]: +// def write_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: +// if isinstance(elts, dict): +// return [(write_key(k), write_value(v)) for k, v in elts.items()] +// else: +// _atd_bad_python('Dict[str, ]]', elts) +// raise AssertionError('impossible') # keep mypy happy +// return write_assoc + +// def _atd_write_assoc_dict_to_object( +// write_value: Callable[[Any], Any] +// ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: +// def write_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: +// if isinstance(elts, dict): +// return {_atd_write_string(k): write_value(v) +// for k, v in elts.items()} +// else: +// _atd_bad_python('Dict[str, ]', elts) +// raise AssertionError('impossible') # keep mypy happy +// return write_assoc + +// def _atd_write_assoc_list_to_object( +// write_value: Callable[[Any], Any], +// ) -> Callable[[List[Any]], Dict[str, Any]]: +// def write_assoc(elts: List[List[Any]]) -> Dict[str, Any]: +// if isinstance(elts, list): +// return {_atd_write_string(elt[0]): write_value(elt[1]) +// for elt in elts} +// else: +// _atd_bad_python('List[Tuple[, ]]', elts) +// raise AssertionError('impossible') # keep mypy happy +// return write_assoc + +// def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ +// -> Callable[[Optional[Any]], Optional[Any]]: +// def write_nullable(x: Any) -> Any: +// if x is None: +// return None +// else: +// return write_elt(x) +// return write_nullable + +// def _atd_write_option(write_elt: Callable[[Any], Any]) \ +// -> Callable[[Optional[Any]], Optional[Any]]: +// def write_option(x: Any) -> Any: +// if x is None: +// return 'None' +// else: +// return ['Some', write_elt(x)] +// return write_option From b3e14678a74d7df056aeb600710aa978a7510604 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 10:12:06 +0000 Subject: [PATCH 07/91] wip: remove unused var --- atdd/src/lib/Codegen.ml | 23 ++++++----------------- 1 file changed, 6 insertions(+), 17 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 0d3d16e9..cbf45aa0 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -635,7 +635,7 @@ let construct_json_field env trans_meth let writer_function = json_writer env unwrapped_type in let assignment = [ - Line (sprintf "res['%s'] = %s(self.%s)" + Line (sprintf "res[\"%s\"] = %s(self.%s);" (Atd.Json.get_json_fname name an |> single_esc) writer_function (inst_var_name trans_meth name)) @@ -698,7 +698,6 @@ let rec json_reader env (e : type_expr) = (lambda x: (read0(x[0]), read1(x[1])) if isinstance(x, list) else error()) *) and tuple_reader env cells = - let len = List.length cells in let tuple_body = List.mapi (fun i (loc, e, an) -> sprintf "%s(x[%i])" (json_reader env e) i @@ -790,19 +789,9 @@ let record env loc name (fields : field list) an = [ Line (sprintf "%s fromJson(JSONValue j) {" (single_esc dlang_struct_name)); - Block [ - Line "if isinstance(x, dict):"; - Block [ - Line "return cls("; - Block from_json_class_arguments; - Line ")" - ]; - Line "else:"; - Block [ - Line (sprintf "_atd_bad_json('%s', x)" - (single_esc dlang_struct_name)) - ] - ]; + Block + ([Line (sprintf "%s obj;" dlang_struct_name) ] @ from_json_class_arguments @ + [Line "return obj;"]); Line "}"; ] in @@ -810,9 +799,9 @@ let record env loc name (fields : field list) an = [ Line "JSONValue toJson() {"; Block [ - Line ("JSONValue res = JSONValue.emptyObject;"); + Line ("JSONValue jj;"); Inline json_object_body; - Line "return res;" + Line "return jj;" ]; Line "}"; ] From e861e48ed150a0f7cb3878e80d032863b846c266 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 10:35:17 +0000 Subject: [PATCH 08/91] wip: almost complete records <-> JSONValue --- atdd/src/lib/Codegen.ml | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index cbf45aa0..f285dddc 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -635,7 +635,7 @@ let construct_json_field env trans_meth let writer_function = json_writer env unwrapped_type in let assignment = [ - Line (sprintf "res[\"%s\"] = %s(self.%s);" + Line (sprintf "res[\"%s\"] = %s(this.%s);" (Atd.Json.get_json_fname name an |> single_esc) writer_function (inst_var_name trans_meth name)) @@ -646,7 +646,7 @@ let construct_json_field env trans_meth | With_default -> assignment | Optional -> [ - Line (sprintf "if self.%s is not None:" + Line (sprintf "if this.%s is not None:" (inst_var_name trans_meth name)); Block assignment ] @@ -708,7 +708,7 @@ and tuple_reader env cells = let from_json_class_argument env trans_meth py_class_name ((loc, (name, kind, an), e) : simple_field) = - let python_name = inst_var_name trans_meth name in + let dlang_name = inst_var_name trans_meth name in let json_name = Atd.Json.get_json_fname name an in let unwrapped_type = match kind with @@ -737,8 +737,8 @@ let from_json_class_argument (sprintf "missing default Python value for field '%s'" name) in - sprintf "%s=%s(x['%s']) if '%s' in x else %s," - python_name + sprintf "obj.%s=%s(x['%s']) if '%s' in x else %s," + dlang_name (json_reader env unwrapped_type) (single_esc json_name) (single_esc json_name) @@ -787,7 +787,7 @@ let record env loc name (fields : field list) an = ) fields in let from_json = [ - Line (sprintf "%s fromJson(JSONValue j) {" + Line (sprintf "static %s fromJson(JSONValue j) {" (single_esc dlang_struct_name)); Block ([Line (sprintf "%s obj;" dlang_struct_name) ] @ from_json_class_arguments @ @@ -799,9 +799,9 @@ let record env loc name (fields : field list) an = [ Line "JSONValue toJson() {"; Block [ - Line ("JSONValue jj;"); + Line ("JSONValue res;"); Inline json_object_body; - Line "return jj;" + Line "return res;" ]; Line "}"; ] From b2e5668c69f7e74842710c4b7bd74ba7c863dac8 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 15:09:49 +0200 Subject: [PATCH 09/91] add writer/reader for d --- atdd/src/lib/Codegen.ml | 435 ++++++++++++----------------- atdd/src/lib/fixed_size_preamble.d | 350 ++++++----------------- 2 files changed, 271 insertions(+), 514 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index f285dddc..7bc54983 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -177,258 +177,189 @@ let _double_esc s = escape_string_content Double s let fixed_size_preamble atd_filename = - sprintf {|"""Generated by atdd from type definitions in %s. - -This implements classes for the types defined in '%s', providing -methods and functions to convert data from/to JSON. -""" - -# Import annotations to allow forward references -from __future__ import annotations -from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union - -import json - -############################################################################ -# Private functions -############################################################################ - - -def _atd_missing_json_field(type_name: str, json_field_name: str) -> NoReturn: - raise ValueError(f"missing field '{json_field_name}'" - f" in JSON object of type '{type_name}'") - - -def _atd_bad_json(expected_type: str, json_value: Any) -> NoReturn: - value_str = str(json_value) - if len(value_str) > 200: - value_str = value_str[:200] + '…' - - raise ValueError(f"incompatible JSON value where" - f" type '{expected_type}' was expected: '{value_str}'") - - -def _atd_bad_python(expected_type: str, json_value: Any) -> NoReturn: - value_str = str(json_value) - if len(value_str) > 200: - value_str = value_str[:200] + '…' - - raise ValueError(f"incompatible Python value where" - f" type '{expected_type}' was expected: '{value_str}'") - - -def _atd_read_unit(x: Any) -> None: - if x is None: - return x - else: - _atd_bad_json('unit', x) - - -def _atd_read_bool(x: Any) -> bool: - if isinstance(x, bool): - return x - else: - _atd_bad_json('bool', x) - - -def _atd_read_int(x: Any) -> int: - if isinstance(x, int): - return x - else: - _atd_bad_json('int', x) - - -def _atd_read_float(x: Any) -> float: - if isinstance(x, (int, float)): - return x - else: - _atd_bad_json('float', x) - - -def _atd_read_string(x: Any) -> str: - if isinstance(x, str): - return x - else: - _atd_bad_json('str', x) - - -def _atd_read_list( - read_elt: Callable[[Any], Any] - ) -> Callable[[List[Any]], List[Any]]: - def read_list(elts: List[Any]) -> List[Any]: - if isinstance(elts, list): - return [read_elt(elt) for elt in elts] - else: - _atd_bad_json('array', elts) - return read_list - - -def _atd_read_assoc_array_into_dict( - read_key: Callable[[Any], Any], - read_value: Callable[[Any], Any], - ) -> Callable[[List[Any]], Dict[Any, Any]]: - def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: - if isinstance(elts, list): - return {read_key(elt[0]): read_value(elt[1]) for elt in elts} - else: - _atd_bad_json('array', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_assoc_object_into_dict( - read_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: - def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: - if isinstance(elts, dict): - return {_atd_read_string(k): read_value(v) - for k, v in elts.items()} - else: - _atd_bad_json('object', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_assoc_object_into_list( - read_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: - def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: - if isinstance(elts, dict): - return [(_atd_read_string(k), read_value(v)) - for k, v in elts.items()] - else: - _atd_bad_json('object', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def read_nullable(x: Any) -> Any: - if x is None: - return None - else: - return read_elt(x) - return read_nullable - - -def _atd_read_option(read_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def read_option(x: Any) -> Any: - if x == 'None': - return None - elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some': - return read_elt(x[1]) - else: - _atd_bad_json('option', x) - raise AssertionError('impossible') # keep mypy happy - return read_option - - -def _atd_write_unit(x: Any) -> None: - if x is None: - return x - else: - _atd_bad_python('unit', x) - - -def _atd_write_bool(x: Any) -> bool: - if isinstance(x, bool): - return x - else: - _atd_bad_python('bool', x) - - -def _atd_write_int(x: Any) -> int: - if isinstance(x, int): - return x - else: - _atd_bad_python('int', x) - - -def _atd_write_float(x: Any) -> float: - if isinstance(x, (int, float)): - return x - else: - _atd_bad_python('float', x) - - -def _atd_write_string(x: Any) -> str: - if isinstance(x, str): - return x - else: - _atd_bad_python('str', x) - - -def _atd_write_list( - write_elt: Callable[[Any], Any] - ) -> Callable[[List[Any]], List[Any]]: - def write_list(elts: List[Any]) -> List[Any]: - if isinstance(elts, list): - return [write_elt(elt) for elt in elts] - else: - _atd_bad_python('list', elts) - return write_list - - -def _atd_write_assoc_dict_to_array( - write_key: Callable[[Any], Any], - write_value: Callable[[Any], Any] - ) -> Callable[[Dict[Any, Any]], List[Tuple[Any, Any]]]: - def write_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: - if isinstance(elts, dict): - return [(write_key(k), write_value(v)) for k, v in elts.items()] - else: - _atd_bad_python('Dict[str, ]]', elts) - raise AssertionError('impossible') # keep mypy happy - return write_assoc - - -def _atd_write_assoc_dict_to_object( - write_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: - def write_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: - if isinstance(elts, dict): - return {_atd_write_string(k): write_value(v) - for k, v in elts.items()} - else: - _atd_bad_python('Dict[str, ]', elts) - raise AssertionError('impossible') # keep mypy happy - return write_assoc - - -def _atd_write_assoc_list_to_object( - write_value: Callable[[Any], Any], - ) -> Callable[[List[Any]], Dict[str, Any]]: - def write_assoc(elts: List[List[Any]]) -> Dict[str, Any]: - if isinstance(elts, list): - return {_atd_write_string(elt[0]): write_value(elt[1]) - for elt in elts} - else: - _atd_bad_python('List[Tuple[, ]]', elts) - raise AssertionError('impossible') # keep mypy happy - return write_assoc - - -def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def write_nullable(x: Any) -> Any: - if x is None: - return None - else: - return write_elt(x) - return write_nullable - - -def _atd_write_option(write_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def write_option(x: Any) -> Any: - if x is None: - return 'None' - else: - return ['Some', write_elt(x)] - return write_option + sprintf {|// """Generated by atdd 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 + // ############################################################################ + + import std.format; + import std.conv; + import std.json; + import std.typecons : tuple, Tuple, nullable, Nullable; + import std.array : array; + import std.algorithm : map; + + class AtdException : Exception + { + this(string msg, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line); + } + } + + void _atd_missing_json_field(string typeName, string jsonFieldName) + { + throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); + } + + // TODO check later if template is right way to go + AtdException _atd_bad_json(T)(string expectedType, T jsonValue) + { + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } + + return new AtdException( + "incompatible JSON value where type '%s' was expected: %s".format( + expectedType, valueStr + )); + } + + AtdException _atd_bad_d(T)(string expectedType, T jsonValue) + { + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } + + return new AtdException( + "incompatible D value where type '%s' was expected: %s".format( + expectedType, valueStr + )); + } + + typeof(null) _atd_read_unit(JSONValue x) + { + if (x.isNull) + return null; + else + throw _atd_bad_json("unit", x); + } + + bool _atd_read_bool(JSONValue x) + { + try + return x.boolean; + catch (JSONException e) + throw _atd_bad_json("bool", x); + } + + long _atd_read_int(JSONValue x) + { + try + return x.integer; + catch (JSONException e) + throw _atd_bad_json("int", x); + } + + float _atd_read_float(JSONValue x) + { + try + return x.floating; + catch (JSONException e) + throw _atd_bad_json("float", x); + } + + string _atd_read_string(JSONValue x) + { + try + return x.str; + catch (JSONException e) + throw _atd_bad_json("string", x); + } + + auto _atd_read_list(T)(T function(JSONValue) readElements) + { + return (JSONValue[] list) { return array(list.map!readElements()); }; + } + + auto _atd_read_assoc_array(V)( + V function(JSONValue) readValue) + { + auto fun = (JSONValue[string] assocArr) { + V[string] ret; + foreach (key, val; assocArr) + ret[key] = readValue(val); + return ret; + }; + return fun; + } + + // TODO probably need to change that + auto _atd_read_nullable(T)(T function(JSONValue) readElm) + { + auto fun = (JSONValue e) { + if (e.isNull) + return Nullable!T.init; + else + return Nullable!T(readElm(e)); + }; + return fun; + } + + // this whole set of function could be remplaced by one templated _atd_write_value function + // not sure it is what we want though + + JSONValue _atd_write_unit(typeof(null) n) + { + return JSONValue(null); + } + + JSONValue _atd_write_bool(bool b) + { + return JSONValue(b); + } + + JSONValue _atd_write_int(long i) + { + return JSONValue(i); + } + + JSONValue _atd_write_float(float f) + { + return JSONValue(f); + } + + JSONValue _atd_write_string(string s) + { + return JSONValue(s); + } + + auto _atd_write_list(T)(JSONValue function(T) writeElm) + { + return (T[] list) { return JSONValue(array(list.map!writeElm())); }; + } + + auto _atd_write_assoc_array(T)( + JSONValue function(T) writeValue) + { + auto fun = (T[string] assocArr) { + JSONValue[string] ret; + foreach (key, val; assocArr) + ret[key] = writeValue(val); + return JSONValue(ret); + }; + return fun; + } + + auto _atd_write_nullable(T)(JSONValue function(T) writeElm) + { + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue(null); + else + return writeElm(elm.get); + }; + return fun; + } ############################################################################ diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index bbbe1f34..c129dffd 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -11,7 +11,7 @@ import std.format; import std.conv; import std.json; -import std.typecons : tuple, Tuple; +import std.typecons : tuple, Tuple, nullable, Nullable; import std.array : array; import std.algorithm : map; @@ -102,142 +102,139 @@ auto _atd_read_list(T)(T function(JSONValue) readElements) return (JSONValue[] list) { return array(list.map!readElements()); }; } -auto _atd_read_tuple_list_into_assoc_array(K, V)( - K function(JSONValue) readKey, +auto _atd_read_assoc_array(V)( V function(JSONValue) readValue) { - auto innerFun = (Tuple!(JSONValue, JSONValue)[] tupList) { - V[K] ret; - foreach (tup; tupList) - ret[readKey(tup[0])] = readValue(tup[1]); + auto fun = (JSONValue[string] assocArr) { + V[string] ret; + foreach (key, val; assocArr) + ret[key] = readValue(val); return ret; }; - - return innerFun; + return fun; } -auto _atd_read_tuple_list_into_tuple_list(K, V)( - K function(JSONValue) readKey, - V function(JSONValue) readValue) +// TODO probably need to change that +auto _atd_read_nullable(T)(T function(JSONValue) readElm) { - auto fun = (Tuple!(JSONValue, JSONValue)[] tupList) { - auto ret = new Tuple!(K, V)[](tupList.length); - foreach (i, tup; tupList) - ret[i] = tuple(readKey(tup[0]), readValue(tup[1])); - return ret; + auto fun = (JSONValue e) { + if (e.isNull) + return Nullable!T.init; + else + return Nullable!T(readElm(e)); }; return fun; } -// We cannot use JSONValue as key, so we probably don't want to use that -// but the tuple versions -auto _atd_read_assoc_array_into_assoc_array(V)( - string function(string) readKey, - V function(JSONValue) readValue) +// this whole set of function could be remplaced by one templated _atd_write_value function +// not sure it is what we want though + +JSONValue _atd_write_unit(typeof(null) n) { - auto fun = (JSONValue[string] assocArr) { - V[string] ret; - foreach (key, val; assocArr) - ret[readKey(key)] = readValue(val); - return ret; - }; - return fun; + return JSONValue(null); } -// ditto -auto _atd_read_assoc_array_into_tuple_list(V)( - string function(string) readKey, - V function(JSONValue) readValue) +JSONValue _atd_write_bool(bool b) { - auto fun = (JSONValue[string] assocArr) { - auto ret = new Tuple!(string, V)[](assocArr.length); - uint i = 0; - foreach (key, val; assocArr) - ret[i++] = tuple(readKey(key), readValue(val)); - return ret; - }; - return fun; + return JSONValue(b); } +JSONValue _atd_write_int(long i) +{ + return JSONValue(i); +} -unittest +JSONValue _atd_write_float(float f) { - import std.stdio; + return JSONValue(f); +} - try - _atd_bad_json("bool", null); - catch (AtdException e) - { - writeln(e.msg); - assert(true); - } +JSONValue _atd_write_string(string s) +{ + return JSONValue(s); } -unittest +auto _atd_write_list(T)(JSONValue function(T) writeElm) { - import std.stdio; + return (T[] list) { return JSONValue(array(list.map!writeElm())); }; +} - auto fp = (JSONValue c) { return c.integer * 4; }; - auto m = _atd_read_list(fp); - JSONValue[] l = [ - JSONValue(1), JSONValue(2), JSONValue(3), JSONValue(4), JSONValue(5) - ]; +auto _atd_write_assoc_array(T)( + JSONValue function(T) writeValue) +{ + auto fun = (T[string] assocArr) { + JSONValue[string] ret; + foreach (key, val; assocArr) + ret[key] = writeValue(val); + return JSONValue(ret); + }; + return fun; +} + +auto _atd_write_nullable(T)(JSONValue function(T) writeElm) +{ + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue(null); + else + return writeElm(elm.get); + }; + return fun; } +// ====================== +// ====== TESTS ========= +// ====================== + unittest { import std.stdio; - auto m = _atd_read_tuple_list_into_assoc_array(&_atd_read_string, &_atd_read_int); + auto l = [JSONValue(1), JSONValue(5)]; - auto l = [ - tuple(JSONValue("hello"), JSONValue(1)), - tuple(JSONValue("there"), JSONValue(2)), - tuple(JSONValue("general"), JSONValue(3)), - tuple(JSONValue("kenobi"), JSONValue(4)), - tuple(JSONValue("haha"), JSONValue(5)), - ]; - writeln(m(l)); + auto m = _atd_read_list(&_atd_read_int); + auto mm = _atd_write_list(&_atd_write_int); + + auto read_l = m(l); + auto write_l = mm(read_l); + + assert(write_l == JSONValue(l)); } unittest { import std.stdio; - auto m = _atd_read_tuple_list_into_tuple_list(&_atd_read_string, &_atd_read_int); + auto potentiallyNull = JSONValue(null); - auto l = [ - tuple(JSONValue("hello"), JSONValue(1)), - tuple(JSONValue("there"), JSONValue(2)), - tuple(JSONValue("general"), JSONValue(3)), - tuple(JSONValue("kenobi"), JSONValue(4)), - tuple(JSONValue("haha"), JSONValue(5)), - ]; - writeln(m(l)); + auto m = _atd_read_nullable(&_atd_read_int); + auto mm = _atd_write_nullable(&_atd_write_int); + + Nullable!long readN = m(potentiallyNull); + auto writeN = mm(readN); + + assert(potentiallyNull == writeN); } unittest { import std.stdio; - auto m = _atd_read_assoc_array_into_assoc_array((string s) { return s; }, &_atd_read_int); + auto notNull = JSONValue(54); - auto l = [ - "hello": JSONValue(1), - "there": JSONValue(2), - "general": JSONValue(3), - "kenobi": JSONValue(4), - "haha": JSONValue(5), - ]; - writeln(m(l)); + auto m = _atd_read_nullable(&_atd_read_int); + auto mm = _atd_write_nullable(&_atd_write_int); + + auto readv = m(notNull); + auto writev = mm(readv); + + assert(notNull == writev); } unittest { import std.stdio; - auto m = _atd_read_assoc_array_into_tuple_list((string s) { return s; }, &_atd_read_int); - auto l = [ "hello": JSONValue(1), "there": JSONValue(2), @@ -245,183 +242,12 @@ unittest "kenobi": JSONValue(4), "haha": JSONValue(5), ]; - writeln(m(l)); -} - -// unittest -// { -// import std.stdio; -// auto m = _atd_read_assoc_array_into_tuple_list(&_atd_read_string, &_atd_read_int); + auto m = _atd_read_assoc_array(&_atd_read_int); + auto mm = _atd_write_assoc_array(&_atd_write_int); -// auto l = [ -// JSONValue("hello"): JSONValue(1), -// JSONValue("there"): JSONValue(2), -// JSONValue("general"): JSONValue(3), -// JSONValue("kenobi"): JSONValue(4), -// JSONValue("haha"): JSONValue(5), -// ]; -// writeln(m(l)); -// } + auto readv = m(l); + auto writev = mm(readv); -unittest -{ - // import std.stdio; - // auto fp = (uint c) { return c * 4; }; - // // Delegate d = int () { return x; }; - // auto m = _atd_read_list(fp); - - // uint[] l = cast(uint[]) [1, 2, 3, 4, 5]; - // writeln(m(l)); + assert(writev == JSONValue(l)); } - -// def _atd_read_assoc_array_into_dict( -// read_key: Callable[[Any], Any], -// read_value: Callable[[Any], Any], -// ) -> Callable[[List[Any]], Dict[Any, Any]]: -// def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: -// if isinstance(elts, list): -// return {read_key(elt[0]): read_value(elt[1]) for elt in elts} -// else: -// _atd_bad_json('array', elts) -// raise AssertionError('impossible') # keep mypy happy -// return read_assoc - -// def _atd_read_assoc_object_into_dict( -// read_value: Callable[[Any], Any] -// ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: -// def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: -// if isinstance(elts, dict): -// return {_atd_read_string(k): read_value(v) -// for k, v in elts.items()} -// else: -// _atd_bad_json('object', elts) -// raise AssertionError('impossible') # keep mypy happy -// return read_assoc - -// def _atd_read_assoc_object_into_list( -// read_value: Callable[[Any], Any] -// ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: -// def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: -// if isinstance(elts, dict): -// return [(_atd_read_string(k), read_value(v)) -// for k, v in elts.items()] -// else: -// _atd_bad_json('object', elts) -// raise AssertionError('impossible') # keep mypy happy -// return read_assoc - -// def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ -// -> Callable[[Optional[Any]], Optional[Any]]: -// def read_nullable(x: Any) -> Any: -// if x is None: -// return None -// else: -// return read_elt(x) -// return read_nullable - -// def _atd_read_option(read_elt: Callable[[Any], Any]) \ -// -> Callable[[Optional[Any]], Optional[Any]]: -// def read_option(x: Any) -> Any: -// if x == 'None': -// return None -// elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some': -// return read_elt(x[1]) -// else: -// _atd_bad_json('option', x) -// raise AssertionError('impossible') # keep mypy happy -// return read_option - -// def _atd_write_unit(x: Any) -> None: -// if x is None: -// return x -// else: -// _atd_bad_python('unit', x) - -// def _atd_write_bool(x: Any) -> bool: -// if isinstance(x, bool): -// return x -// else: -// _atd_bad_python('bool', x) - -// def _atd_write_int(x: Any) -> int: -// if isinstance(x, int): -// return x -// else: -// _atd_bad_python('int', x) - -// def _atd_write_float(x: Any) -> float: -// if isinstance(x, (int, float)): -// return x -// else: -// _atd_bad_python('float', x) - -// def _atd_write_string(x: Any) -> str: -// if isinstance(x, str): -// return x -// else: -// _atd_bad_python('str', x) - -// def _atd_write_list( -// write_elt: Callable[[Any], Any] -// ) -> Callable[[List[Any]], List[Any]]: -// def write_list(elts: List[Any]) -> List[Any]: -// if isinstance(elts, list): -// return [write_elt(elt) for elt in elts] -// else: -// _atd_bad_python('list', elts) -// return write_list - -// def _atd_write_assoc_dict_to_array( -// write_key: Callable[[Any], Any], -// write_value: Callable[[Any], Any] -// ) -> Callable[[Dict[Any, Any]], List[Tuple[Any, Any]]]: -// def write_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: -// if isinstance(elts, dict): -// return [(write_key(k), write_value(v)) for k, v in elts.items()] -// else: -// _atd_bad_python('Dict[str, ]]', elts) -// raise AssertionError('impossible') # keep mypy happy -// return write_assoc - -// def _atd_write_assoc_dict_to_object( -// write_value: Callable[[Any], Any] -// ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: -// def write_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: -// if isinstance(elts, dict): -// return {_atd_write_string(k): write_value(v) -// for k, v in elts.items()} -// else: -// _atd_bad_python('Dict[str, ]', elts) -// raise AssertionError('impossible') # keep mypy happy -// return write_assoc - -// def _atd_write_assoc_list_to_object( -// write_value: Callable[[Any], Any], -// ) -> Callable[[List[Any]], Dict[str, Any]]: -// def write_assoc(elts: List[List[Any]]) -> Dict[str, Any]: -// if isinstance(elts, list): -// return {_atd_write_string(elt[0]): write_value(elt[1]) -// for elt in elts} -// else: -// _atd_bad_python('List[Tuple[, ]]', elts) -// raise AssertionError('impossible') # keep mypy happy -// return write_assoc - -// def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ -// -> Callable[[Optional[Any]], Optional[Any]]: -// def write_nullable(x: Any) -> Any: -// if x is None: -// return None -// else: -// return write_elt(x) -// return write_nullable - -// def _atd_write_option(write_elt: Callable[[Any], Any]) \ -// -> Callable[[Optional[Any]], Optional[Any]]: -// def write_option(x: Any) -> Any: -// if x is None: -// return 'None' -// else: -// return ['Some', write_elt(x)] -// return write_option From 88740a2af877010984c8bea101c77a337730aa80 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Thu, 8 Jun 2023 13:25:58 +0000 Subject: [PATCH 10/91] remove unrequired file + some edit on tuple_writer --- atdd/minimal.atd | 6 - atdd/minimal.py | 298 ---------------------------------------- atdd/src/lib/Codegen.ml | 13 +- 3 files changed, 5 insertions(+), 312 deletions(-) delete mode 100644 atdd/minimal.atd delete mode 100644 atdd/minimal.py diff --git a/atdd/minimal.atd b/atdd/minimal.atd deleted file mode 100644 index 19dbfef3..00000000 --- a/atdd/minimal.atd +++ /dev/null @@ -1,6 +0,0 @@ -type root = { - id : string; - items: int list list; - ?maybe: int option; - ~extras: int list; -} diff --git a/atdd/minimal.py b/atdd/minimal.py deleted file mode 100644 index e8808663..00000000 --- a/atdd/minimal.py +++ /dev/null @@ -1,298 +0,0 @@ -"""Generated by atdpy from type definitions in minimal.atd. - -This implements classes for the types defined in 'minimal.atd', providing -methods and functions to convert data from/to JSON. -""" - -# Disable flake8 entirely on this file: -# flake8: noqa - -# Import annotations to allow forward references -from __future__ import annotations -from dataclasses import dataclass, field -from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple, Union - -import json - -############################################################################ -# Private functions -############################################################################ - - -def _atd_missing_json_field(type_name: str, json_field_name: str) -> NoReturn: - raise ValueError(f"missing field '{json_field_name}'" - f" in JSON object of type '{type_name}'") - - -def _atd_bad_json(expected_type: str, json_value: Any) -> NoReturn: - value_str = str(json_value) - if len(value_str) > 200: - value_str = value_str[:200] + '…' - - raise ValueError(f"incompatible JSON value where" - f" type '{expected_type}' was expected: '{value_str}'") - - -def _atd_bad_python(expected_type: str, json_value: Any) -> NoReturn: - value_str = str(json_value) - if len(value_str) > 200: - value_str = value_str[:200] + '…' - - raise ValueError(f"incompatible Python value where" - f" type '{expected_type}' was expected: '{value_str}'") - - -def _atd_read_unit(x: Any) -> None: - if x is None: - return x - else: - _atd_bad_json('unit', x) - - -def _atd_read_bool(x: Any) -> bool: - if isinstance(x, bool): - return x - else: - _atd_bad_json('bool', x) - - -def _atd_read_int(x: Any) -> int: - if isinstance(x, int): - return x - else: - _atd_bad_json('int', x) - - -def _atd_read_float(x: Any) -> float: - if isinstance(x, (int, float)): - return x - else: - _atd_bad_json('float', x) - - -def _atd_read_string(x: Any) -> str: - if isinstance(x, str): - return x - else: - _atd_bad_json('str', x) - - -def _atd_read_list( - read_elt: Callable[[Any], Any] - ) -> Callable[[List[Any]], List[Any]]: - def read_list(elts: List[Any]) -> List[Any]: - if isinstance(elts, list): - return [read_elt(elt) for elt in elts] - else: - _atd_bad_json('array', elts) - return read_list - - -def _atd_read_assoc_array_into_dict( - read_key: Callable[[Any], Any], - read_value: Callable[[Any], Any], - ) -> Callable[[List[Any]], Dict[Any, Any]]: - def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: - if isinstance(elts, list): - return {read_key(elt[0]): read_value(elt[1]) for elt in elts} - else: - _atd_bad_json('array', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_assoc_object_into_dict( - read_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: - def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: - if isinstance(elts, dict): - return {_atd_read_string(k): read_value(v) - for k, v in elts.items()} - else: - _atd_bad_json('object', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_assoc_object_into_list( - read_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: - def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: - if isinstance(elts, dict): - return [(_atd_read_string(k), read_value(v)) - for k, v in elts.items()] - else: - _atd_bad_json('object', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def read_nullable(x: Any) -> Any: - if x is None: - return None - else: - return read_elt(x) - return read_nullable - - -def _atd_read_option(read_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def read_option(x: Any) -> Any: - if x == 'None': - return None - elif isinstance(x, List) and len(x) == 2 and x[0] == 'Some': - return read_elt(x[1]) - else: - _atd_bad_json('option', x) - raise AssertionError('impossible') # keep mypy happy - return read_option - - -def _atd_write_unit(x: Any) -> None: - if x is None: - return x - else: - _atd_bad_python('unit', x) - - -def _atd_write_bool(x: Any) -> bool: - if isinstance(x, bool): - return x - else: - _atd_bad_python('bool', x) - - -def _atd_write_int(x: Any) -> int: - if isinstance(x, int): - return x - else: - _atd_bad_python('int', x) - - -def _atd_write_float(x: Any) -> float: - if isinstance(x, (int, float)): - return x - else: - _atd_bad_python('float', x) - - -def _atd_write_string(x: Any) -> str: - if isinstance(x, str): - return x - else: - _atd_bad_python('str', x) - - -def _atd_write_list( - write_elt: Callable[[Any], Any] - ) -> Callable[[List[Any]], List[Any]]: - def write_list(elts: List[Any]) -> List[Any]: - if isinstance(elts, list): - return [write_elt(elt) for elt in elts] - else: - _atd_bad_python('list', elts) - return write_list - - -def _atd_write_assoc_dict_to_array( - write_key: Callable[[Any], Any], - write_value: Callable[[Any], Any] - ) -> Callable[[Dict[Any, Any]], List[Tuple[Any, Any]]]: - def write_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: - if isinstance(elts, dict): - return [(write_key(k), write_value(v)) for k, v in elts.items()] - else: - _atd_bad_python('Dict[str, ]]', elts) - raise AssertionError('impossible') # keep mypy happy - return write_assoc - - -def _atd_write_assoc_dict_to_object( - write_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: - def write_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: - if isinstance(elts, dict): - return {_atd_write_string(k): write_value(v) - for k, v in elts.items()} - else: - _atd_bad_python('Dict[str, ]', elts) - raise AssertionError('impossible') # keep mypy happy - return write_assoc - - -def _atd_write_assoc_list_to_object( - write_value: Callable[[Any], Any], - ) -> Callable[[List[Any]], Dict[str, Any]]: - def write_assoc(elts: List[List[Any]]) -> Dict[str, Any]: - if isinstance(elts, list): - return {_atd_write_string(elt[0]): write_value(elt[1]) - for elt in elts} - else: - _atd_bad_python('List[Tuple[, ]]', elts) - raise AssertionError('impossible') # keep mypy happy - return write_assoc - - -def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def write_nullable(x: Any) -> Any: - if x is None: - return None - else: - return write_elt(x) - return write_nullable - - -def _atd_write_option(write_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def write_option(x: Any) -> Any: - if x is None: - return 'None' - else: - return ['Some', write_elt(x)] - return write_option - - -############################################################################ -# Public classes -############################################################################ - - -@dataclass -class Root: - """Original type: root = { ... }""" - - id: str - items: List[List[int]] - maybe: Optional[int] = None - extras: List[int] = field(default_factory=lambda: []) - - @classmethod - def from_json(cls, x: Any) -> 'Root': - if isinstance(x, dict): - return cls( - id=_atd_read_string(x['ID']) if 'ID' in x else _atd_missing_json_field('Root', 'ID'), - items=_atd_read_list(_atd_read_list(_atd_read_int))(x['items']) if 'items' in x else _atd_missing_json_field('Root', 'items'), - maybe=_atd_read_int(x['maybe']) if 'maybe' in x else None, - extras=_atd_read_list(_atd_read_int)(x['extras']) if 'extras' in x else [], - ) - else: - _atd_bad_json('Root', x) - - def to_json(self) -> Any: - res: Dict[str, Any] = {} - res['ID'] = _atd_write_string(self.id) - res['items'] = _atd_write_list(_atd_write_list(_atd_write_int))(self.items) - if self.maybe is not None: - res['maybe'] = _atd_write_int(self.maybe) - res['extras'] = _atd_write_list(_atd_write_int)(self.extras) - return res - - @classmethod - def from_json_string(cls, x: str) -> 'Root': - return cls.from_json(json.loads(x)) - - def to_json_string(self, **kw: Any) -> str: - return json.dumps(self.to_json(), **kw) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 7bc54983..5a90d8d2 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -507,12 +507,11 @@ let unwrap_field_type loc field_name kind e = let inst_var_name trans_meth field_name = trans_meth field_name -(* TODO : done up to here *) let rec json_writer 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 cells + | Tuple (loc, cells, an) -> tuple_writer env (loc, cells, an) | List (loc, e, an) -> (match assoc_kind loc e an with | Array_list -> @@ -546,19 +545,17 @@ let rec json_writer env e = (lambda x: [write0(x[0]), write1(x[1])] if isinstance(x, tuple) else error()) *) -and tuple_writer env cells = - let len = List.length cells in +and tuple_writer env (loc, cells, an) = let tuple_body = List.mapi (fun i (loc, e, an) -> sprintf "%s(x[%i])" (json_writer env e) i ) cells |> String.concat ", " in - sprintf "(lambda x: [%s] \ - if isinstance(x, tuple) and len(x) == %d \ - else _atd_bad_python('tuple of length %d', x))" + sprintf "(%s x) => array(%s)" + (type_name_of_expr env (Tuple (loc, cells, an))) tuple_body - len len + let construct_json_field env trans_meth ((loc, (name, kind, an), e) : simple_field) = From 430975c97f033a674919845b78db8b230cb81a4c Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Thu, 8 Jun 2023 13:28:33 +0000 Subject: [PATCH 11/91] properly escape % --- atdd/src/lib/Codegen.ml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 5a90d8d2..db06d339 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -204,7 +204,7 @@ let fixed_size_preamble atd_filename = void _atd_missing_json_field(string typeName, string jsonFieldName) { - throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); + throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); } // TODO check later if template is right way to go @@ -217,7 +217,7 @@ let fixed_size_preamble atd_filename = } return new AtdException( - "incompatible JSON value where type '%s' was expected: %s".format( + "incompatible JSON value where type '%%s' was expected: %%s".format( expectedType, valueStr )); } @@ -231,7 +231,7 @@ let fixed_size_preamble atd_filename = } return new AtdException( - "incompatible D value where type '%s' was expected: %s".format( + "incompatible D value where type '%%s' was expected: %%s".format( expectedType, valueStr )); } From 6f4dbd2441e3520b3dd36b54c3e3f62a01da9d26 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 13:38:55 +0000 Subject: [PATCH 12/91] make 'private functions' header a comment --- atdd/src/lib/Codegen.ml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index db06d339..255e8810 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -362,9 +362,9 @@ let fixed_size_preamble atd_filename = } -############################################################################ -# Public classes -############################################################################|} + // ############################################################################ + // # Public classes + // ############################################################################|} atd_filename atd_filename From 7ffd14d00d5d9e594ce86999e0b4ab8c965c09ad Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 14:15:34 +0000 Subject: [PATCH 13/91] wip records except for missing field check --- atdd/src/lib/Codegen.ml | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 255e8810..d632caeb 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -715,7 +715,7 @@ let record env loc name (fields : field list) an = ) fields in let from_json = [ - Line (sprintf "static %s fromJson(JSONValue j) {" + Line (sprintf "static %s fromJson(JSONValue x) {" (single_esc dlang_struct_name)); Block ([Line (sprintf "%s obj;" dlang_struct_name) ] @ from_json_class_arguments @ @@ -736,19 +736,23 @@ let record env loc name (fields : field list) an = in let from_json_string = [ - Line (sprintf "%s fromJsonString(string x) {" + Line (sprintf "static %s fromJsonString(string x) {" (single_esc dlang_struct_name)); Block [ - Line "return cls.from_json(json.loads(x))" + Line "JSONValue res = parseJSON(x);"; + Inline json_object_body; + Line "return res;" ]; Line "}"; ] in let to_json_string = [ - Line "string toJsonString(%s obj) {"; + Line (sprintf "string toJsonString(%s obj) {" dlang_struct_name); Block [ - Line "return json.dumps(self.to_json(), **kw)" + Line ("JSONValue res;"); + Inline json_object_body; + Line "return res.toString;" ]; Line "}"; ] From 4ca8832bb1475d61d7c89d4b30c4aa12878d7a8c Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 14:52:57 +0000 Subject: [PATCH 14/91] reduce duplication in dlang record serialization --- atdd/src/lib/Codegen.ml | 35 ++++++++++++++++++----------------- 1 file changed, 18 insertions(+), 17 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index d632caeb..7d7795a5 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -715,17 +715,19 @@ let record env loc name (fields : field list) an = ) fields in let from_json = [ - Line (sprintf "static %s fromJson(JSONValue x) {" - (single_esc dlang_struct_name)); - Block - ([Line (sprintf "%s obj;" dlang_struct_name) ] @ from_json_class_arguments @ - [Line "return obj;"]); + Line (sprintf "%s fromJson(%s)(JSONValue x) {" + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); + Block [ + Line (sprintf "%s obj;" dlang_struct_name); + Inline from_json_class_arguments; + Line "return obj;"; + ]; Line "}"; ] in let to_json = [ - Line "JSONValue toJson() {"; + Line (sprintf "JSONValue toJson(%s) {" (single_esc dlang_struct_name)); Block [ Line ("JSONValue res;"); Inline json_object_body; @@ -736,22 +738,20 @@ let record env loc name (fields : field list) an = in let from_json_string = [ - Line (sprintf "static %s fromJsonString(string x) {" - (single_esc dlang_struct_name)); + Line (sprintf "%s fromJsonString(%s)(string x) {" + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ Line "JSONValue res = parseJSON(x);"; - Inline json_object_body; - Line "return res;" + Line(sprintf "return res.fromJson!%s;" (single_esc dlang_struct_name)) ]; Line "}"; ] in let to_json_string = [ - Line (sprintf "string toJsonString(%s obj) {" dlang_struct_name); + Line (sprintf "string toJsonString(%s) (%s obj) {" (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ - Line ("JSONValue res;"); - Inline json_object_body; + Line ("JSONValue res = obj.toJson;"); Line "return res.toString;" ]; Line "}"; @@ -762,12 +762,13 @@ let record env loc name (fields : field list) an = Block (spaced [ Line (sprintf {|"""Original type: %s = { ... }"""|} name); Inline inst_var_declarations; - Inline from_json; - Inline to_json; - Inline from_json_string; - Inline to_json_string; ]); Line ("}"); + Line ""; + Inline from_json; + Inline to_json; + Inline from_json_string; + Inline to_json_string; ] (* From fba5d3706f6f95c188b497de2238d40c31c3106a Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Thu, 8 Jun 2023 14:52:57 +0000 Subject: [PATCH 15/91] d-ify things --- atdd/src/lib/Codegen.ml | 46 ++++++++++++++++++++--------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 7d7795a5..ed4a9bf5 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -445,7 +445,7 @@ let rec type_name_of_expr env (e : type_expr) : string = sprintf "%s[%s]" (type_name_of_expr env key) (type_name_of_expr env value) | Object_dict value -> - sprintf "string[%s]" (* TODO : dubious*) + sprintf "%s[string]" (type_name_of_expr env value) ) | Option (loc, e, an) -> sprintf "Nullable!%s" (type_name_of_expr env e) @@ -507,7 +507,7 @@ let unwrap_field_type loc field_name kind e = let inst_var_name trans_meth field_name = trans_meth field_name -let rec json_writer env e = +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" @@ -515,28 +515,28 @@ let rec json_writer env e = | List (loc, e, an) -> (match assoc_kind loc e an with | Array_list -> - sprintf "_atd_write_list(%s)" (json_writer env e) + sprintf "%s_atd_write_list(%s)" (if nested then "&" else "") (json_writer ~nested:true env e) | Array_dict (key, value) -> - sprintf "_atd_write_assoc_dict_to_array(%s, %s)" - (json_writer env key) (json_writer env value) + sprintf "%s_atd_write_assoc_dict_to_array(%s, %s)" (if nested then "&" else "") + (json_writer ~nested:true env key) (json_writer ~nested:true env value) | Object_dict value -> - sprintf "_atd_write_assoc_dict_to_object(%s)" - (json_writer env value) + sprintf "%s_atd_write_assoc_dict_to_object(%s)" (if nested then "&" else "") + (json_writer ~nested:true env value) | Object_list value -> - sprintf "_atd_write_assoc_list_to_object(%s)" - (json_writer env value) + sprintf "%s_atd_write_assoc_list_to_object(%s)" (if nested then "&" else "") + (json_writer ~nested:true env value) ) | Option (loc, e, an) -> - sprintf "_atd_write_option(%s)" (json_writer env e) + sprintf "%s_atd_write_option(%s)" (if nested then "&" else "") (json_writer ~nested:true env e) | Nullable (loc, e, an) -> - sprintf "_atd_write_nullable(%s)" (json_writer env e) + sprintf "%s_atd_write_nullable(%s)" (if nested then "&" else "") (json_writer ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" - | Wrap (loc, e, an) -> json_writer env e + | Wrap (loc, e, an) -> json_writer ~nested:true env e | Name (loc, (loc2, name, []), an) -> (match name with - | "bool" | "int" | "float" | "string" -> sprintf "_atd_write_%s" name - | "abstract" -> "(lambda x: x)" - | _ -> "(lambda x: x.to_json())") + | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s" (if nested then "&" else "") name + | "abstract" -> "(auto x => x)" + | _ -> "(auto x => x.to_json())") | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" @@ -552,7 +552,7 @@ and tuple_writer env (loc, cells, an) = ) cells |> String.concat ", " in - sprintf "(%s x) => array(%s)" + sprintf "(%s x) => JSONValue([%s])" (type_name_of_expr env (Tuple (loc, cells, an))) tuple_body @@ -563,7 +563,7 @@ let construct_json_field env trans_meth let writer_function = json_writer env unwrapped_type in let assignment = [ - Line (sprintf "res[\"%s\"] = %s(this.%s);" + Line (sprintf "res[\"%s\"] = %s(%s);" (Atd.Json.get_json_fname name an |> single_esc) writer_function (inst_var_name trans_meth name)) @@ -574,7 +574,7 @@ let construct_json_field env trans_meth | With_default -> assignment | Optional -> [ - Line (sprintf "if this.%s is not None:" + Line (sprintf "if (!%s.isNull)" (inst_var_name trans_meth name)); Block assignment ] @@ -656,13 +656,13 @@ let from_json_class_argument sprintf "_atd_missing_json_field('%s', '%s')" (single_esc py_class_name) (single_esc json_name) - | Optional -> "None" + | Optional -> "null" | With_default -> match get_dlang_default e an with | Some x -> x | None -> A.error_at loc - (sprintf "missing default Python value for field '%s'" + (sprintf "missing default Dlang value for field '%s'" name) in sprintf "obj.%s=%s(x['%s']) if '%s' in x else %s," @@ -680,7 +680,7 @@ let inst_var_declaration let default = match kind with | Required -> "" - | Optional -> " = None" + | Optional -> " = null" | With_default -> match get_dlang_default unwrapped_e an with | None -> "" @@ -688,10 +688,10 @@ let inst_var_declaration (* This constructs ensures that a fresh default value is evaluated for each class instanciation. It's important for default lists since Python lists are mutable. *) - sprintf " = field(default_factory=lambda: %s)" x + sprintf " = %s" x in [ - Line (sprintf "%s: %s%s" var_name type_name default) + Line (sprintf "%s %s%s;" type_name var_name default) ] let record env loc name (fields : field list) an = From 9bad076b02e5b38a0f2a30c1ef6259a3e2596043 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 14:58:35 +0000 Subject: [PATCH 16/91] atdd: remove unneeded comments --- atdd/src/lib/Codegen.ml | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ed4a9bf5..be07a0e5 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -760,7 +760,6 @@ let record env loc name (fields : field list) an = [ Line (sprintf "struct %s {" dlang_struct_name); Block (spaced [ - Line (sprintf {|"""Original type: %s = { ... }"""|} name); Inline inst_var_declarations; ]); Line ("}"); @@ -771,22 +770,6 @@ let record env loc name (fields : field list) an = Inline to_json_string; ] -(* - A general-purpose wrapper that provides json-related methods for a type. - This is used for tuples and for type aliases e.g. 'type foo = bar array'. - -class Foo: - def __init__(self, x: T): - ... - def to_json(self): - ... - def from_json(x): - ... - def to_json_string(self): - ... - def from_json_string(x): - ... -*) let alias_wrapper env ~class_decorators name type_expr = let dlang_struct_name = class_name env name in let value_type = type_name_of_expr env type_expr in From 256dd93a94ceaef93c1014cabd05bd337386c0a0 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 17:10:20 +0200 Subject: [PATCH 17/91] read / write fns : add obj -> tuplelist -> obj --- atdd/src/lib/Codegen.ml | 37 ++++++++++++++-- atdd/src/lib/fixed_size_preamble.d | 68 ++++++++++++++++++++++++++---- 2 files changed, 92 insertions(+), 13 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index be07a0e5..693a438a 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -281,18 +281,35 @@ let fixed_size_preamble atd_filename = return (JSONValue[] list) { return array(list.map!readElements()); }; } - auto _atd_read_assoc_array(V)( + auto _atd_read_object_to_assoc_array(V)( V function(JSONValue) readValue) { - auto fun = (JSONValue[string] assocArr) { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); V[string] ret; - foreach (key, val; assocArr) + foreach (key, val; jsonVal.object) ret[key] = readValue(val); return ret; }; return fun; } + auto _atd_read_object_to_tuple_list(T)( + T function(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + auto tupList = new Tuple!(string, T)[](jsonVal.object.length); + int i = 0; + foreach (key, val; jsonVal.object) + tupList[i++] = tuple(key, readValue(val)); + return tupList; + }; + return fun; + } + // TODO probably need to change that auto _atd_read_nullable(T)(T function(JSONValue) readElm) { @@ -338,7 +355,7 @@ let fixed_size_preamble atd_filename = return (T[] list) { return JSONValue(array(list.map!writeElm())); }; } - auto _atd_write_assoc_array(T)( + auto _atd_write_assoc_array_to_object(T)( JSONValue function(T) writeValue) { auto fun = (T[string] assocArr) { @@ -350,6 +367,18 @@ let fixed_size_preamble atd_filename = return fun; } + auto _atd_write_tuple_list_to_object(T)( + JSONValue function(T) writeValue) + { + auto fun = (Tuple!(string, T)[] tupList) { + JSONValue[string] ret; + foreach (tup; tupList) + ret[tup[0]] = writeValue(tup[1]); + return JSONValue(ret); + }; + return fun; + } + auto _atd_write_nullable(T)(JSONValue function(T) writeElm) { auto fun = (Nullable!T elm) { diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index c129dffd..38fefe3f 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -102,18 +102,35 @@ auto _atd_read_list(T)(T function(JSONValue) readElements) return (JSONValue[] list) { return array(list.map!readElements()); }; } -auto _atd_read_assoc_array(V)( +auto _atd_read_object_to_assoc_array(V)( V function(JSONValue) readValue) { - auto fun = (JSONValue[string] assocArr) { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); V[string] ret; - foreach (key, val; assocArr) + foreach (key, val; jsonVal.object) ret[key] = readValue(val); return ret; }; return fun; } +auto _atd_read_object_to_tuple_list(T)( + T function(JSONValue) readValue) +{ + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + auto tupList = new Tuple!(string, T)[](jsonVal.object.length); + int i = 0; + foreach (key, val; jsonVal.object) + tupList[i++] = tuple(key, readValue(val)); + return tupList; + }; + return fun; +} + // TODO probably need to change that auto _atd_read_nullable(T)(T function(JSONValue) readElm) { @@ -159,7 +176,7 @@ auto _atd_write_list(T)(JSONValue function(T) writeElm) return (T[] list) { return JSONValue(array(list.map!writeElm())); }; } -auto _atd_write_assoc_array(T)( +auto _atd_write_assoc_array_to_object(T)( JSONValue function(T) writeValue) { auto fun = (T[string] assocArr) { @@ -171,6 +188,18 @@ auto _atd_write_assoc_array(T)( return fun; } +auto _atd_write_tuple_list_to_object(T)( + JSONValue function(T) writeValue) +{ + auto fun = (Tuple!(string, T)[] tupList) { + JSONValue[string] ret; + foreach (tup; tupList) + ret[tup[0]] = writeValue(tup[1]); + return JSONValue(ret); + }; + return fun; +} + auto _atd_write_nullable(T)(JSONValue function(T) writeElm) { auto fun = (Nullable!T elm) { @@ -235,19 +264,40 @@ unittest { import std.stdio; - auto l = [ + auto l = JSONValue([ + "hello": JSONValue(1), + "there": JSONValue(2), + "general": JSONValue(3), + "kenobi": JSONValue(4), + "haha": JSONValue(5), + ]); + + auto m = _atd_read_object_to_assoc_array(&_atd_read_int); + auto mm = _atd_write_assoc_array_to_object(&_atd_write_int); + + auto readv = m(l); + auto writev = mm(readv); + + assert(writev == l); +} + +unittest +{ + import std.stdio; + + auto l = JSONValue([ "hello": JSONValue(1), "there": JSONValue(2), "general": JSONValue(3), "kenobi": JSONValue(4), "haha": JSONValue(5), - ]; + ]); - auto m = _atd_read_assoc_array(&_atd_read_int); - auto mm = _atd_write_assoc_array(&_atd_write_int); + auto m = _atd_read_object_to_tuple_list(&_atd_read_int); + auto mm = _atd_write_tuple_list_to_object(&_atd_write_int); auto readv = m(l); auto writev = mm(readv); - assert(writev == JSONValue(l)); + assert(writev == l); } From d6cd638e0558017500870112dc3099ef31860a5c Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 17:20:22 +0200 Subject: [PATCH 18/91] fix check in when fromJSON --- atdd/src/lib/Codegen.ml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 693a438a..2d055dbb 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -682,7 +682,7 @@ let from_json_class_argument let else_body = match kind with | Required -> - sprintf "_atd_missing_json_field('%s', '%s')" + sprintf "_atd_missing_json_field(\"%s\", \"%s\")" (single_esc py_class_name) (single_esc json_name) | Optional -> "null" @@ -694,10 +694,10 @@ let from_json_class_argument (sprintf "missing default Dlang value for field '%s'" name) in - sprintf "obj.%s=%s(x['%s']) if '%s' in x else %s," + sprintf "obj.%s = (\"%s\" in x) ? %s(x[\"%s\"]) : %s;" dlang_name - (json_reader env unwrapped_type) (single_esc json_name) + (json_reader env unwrapped_type) (single_esc json_name) else_body From 13fb231b7459e0719a28aadedd409d31c855b586 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 15:02:41 +0000 Subject: [PATCH 19/91] atdd : fix aliasing --- atdd/src/lib/Codegen.ml | 33 +-------------------------------- 1 file changed, 1 insertion(+), 32 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 2d055dbb..d93940df 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -803,38 +803,7 @@ let alias_wrapper env ~class_decorators name type_expr = let dlang_struct_name = class_name env name in let value_type = type_name_of_expr env type_expr in [ - Inline class_decorators; - Line (sprintf "struct %s {" dlang_struct_name); - Block [ - Line (sprintf {|"""Original type: %s"""|} name); - Line ""; - Line (sprintf "value: %s" value_type); - Line ""; - Line "@classmethod"; - Line (sprintf "def from_json(cls, x: Any) -> '%s':" - (single_esc dlang_struct_name)); - Block [ - Line (sprintf "return cls(%s(x))" (json_reader env type_expr)) - ]; - Line ""; - Line "def to_json(self) -> Any:"; - Block [ - Line (sprintf "return %s(self.value)" (json_writer env type_expr)) - ]; - Line ""; - Line "@classmethod"; - Line (sprintf "def from_json_string(cls, x: str) -> '%s':" - (single_esc dlang_struct_name)); - Block [ - Line "return cls.from_json(json.loads(x))" - ]; - Line ""; - Line "def to_json_string(self, **kw: Any) -> str:"; - Block [ - Line "return json.dumps(self.to_json(), **kw)" - ]; - Line "}" - ] + Line (sprintf "alias %s = %s;" dlang_struct_name value_type); ] let case_class env ~class_decorators type_name From fb15eb7667bac0e35f43ddbe8fd5684c411a9412 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 15:26:05 +0000 Subject: [PATCH 20/91] atdd : fix writing of record fields --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index d93940df..0f0be0f7 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -592,7 +592,7 @@ let construct_json_field env trans_meth let writer_function = json_writer env unwrapped_type in let assignment = [ - Line (sprintf "res[\"%s\"] = %s(%s);" + Line (sprintf "res[\"%s\"] = %s(obj.%s);" (Atd.Json.get_json_fname name an |> single_esc) writer_function (inst_var_name trans_meth name)) @@ -756,7 +756,7 @@ let record env loc name (fields : field list) an = in let to_json = [ - Line (sprintf "JSONValue toJson(%s) {" (single_esc dlang_struct_name)); + Line (sprintf "JSONValue toJson(%s obj) {" (single_esc dlang_struct_name)); Block [ Line ("JSONValue res;"); Inline json_object_body; From 2ad62a715309600775460e6914bfed5ca6478836 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 17:33:47 +0200 Subject: [PATCH 21/91] cast to int instead of using long --- atdd/src/lib/Codegen.ml | 6 +++--- atdd/src/lib/fixed_size_preamble.d | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 0f0be0f7..6aae88cc 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -252,10 +252,10 @@ let fixed_size_preamble atd_filename = throw _atd_bad_json("bool", x); } - long _atd_read_int(JSONValue x) + int _atd_read_int(JSONValue x) { try - return x.integer; + return cast(int) x.integer; catch (JSONException e) throw _atd_bad_json("int", x); } @@ -335,7 +335,7 @@ let fixed_size_preamble atd_filename = return JSONValue(b); } - JSONValue _atd_write_int(long i) + JSONValue _atd_write_int(int i) { return JSONValue(i); } diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 38fefe3f..95eebbf6 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -73,10 +73,10 @@ bool _atd_read_bool(JSONValue x) throw _atd_bad_json("bool", x); } -long _atd_read_int(JSONValue x) +int _atd_read_int(JSONValue x) { try - return x.integer; + return cast(int) x.integer; catch (JSONException e) throw _atd_bad_json("int", x); } @@ -156,7 +156,7 @@ JSONValue _atd_write_bool(bool b) return JSONValue(b); } -JSONValue _atd_write_int(long i) +JSONValue _atd_write_int(int i) { return JSONValue(i); } From 4f6ba4789ba1cc6b3cbb907aac2bdd22bda2edbd Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 15:44:18 +0000 Subject: [PATCH 22/91] atdd : use correct toJson for list map --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 6aae88cc..94bc7bd7 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -565,7 +565,7 @@ let rec json_writer ?(nested=false) env e = (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s" (if nested then "&" else "") name | "abstract" -> "(auto x => x)" - | _ -> "(auto x => x.to_json())") + | _ -> "(auto x => x.toJson())") | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" From f235f5f558cfca74114ff526e934842e819bab92 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 15:47:54 +0000 Subject: [PATCH 23/91] atdd : depythonify comments --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 94bc7bd7..acb72b8b 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -177,11 +177,11 @@ let _double_esc s = escape_string_content Double s let fixed_size_preamble atd_filename = - sprintf {|// """Generated by atdd from type definitions in %s. + sprintf {|// Generated by atdd 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 From 8a525ec31b74245515f96bf3d3eb5c9bffe573c7 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Thu, 8 Jun 2023 15:58:17 +0000 Subject: [PATCH 24/91] atdd : explicitly name class when converting list elt to json --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index acb72b8b..233e54b4 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -565,7 +565,7 @@ let rec json_writer ?(nested=false) env e = (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s" (if nested then "&" else "") name | "abstract" -> "(auto x => x)" - | _ -> "(auto x => x.toJson())") + | _ -> sprintf "((%s x) => x.toJson())" (dlang_type_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" From f323562b036604c5dc35f6b1be4ef3767fcc0f49 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Thu, 8 Jun 2023 16:52:43 +0000 Subject: [PATCH 25/91] atdd: 80% done with variant --- atdd/src/lib/Codegen.ml | 165 ++++++++++++---------------------------- 1 file changed, 49 insertions(+), 116 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 233e54b4..1b6462d1 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -799,76 +799,44 @@ let record env loc name (fields : field list) an = Inline to_json_string; ] -let alias_wrapper env ~class_decorators name type_expr = +let alias_wrapper env name type_expr = let dlang_struct_name = class_name env name in let value_type = type_name_of_expr env type_expr in [ Line (sprintf "alias %s = %s;" dlang_struct_name value_type); ] -let case_class env ~class_decorators type_name +let case_class env type_name (loc, orig_name, unique_name, an, opt_e) = let json_name = Atd.Json.get_json_cons orig_name an in match opt_e with | None -> [ - Inline class_decorators; - Line (sprintf "class %s:" (trans env unique_name)); - Block [ - Line (sprintf {|"""Original type: %s = [ ... | %s | ... ]"""|} + Line (sprintf {|\\Original type: %s = [ ... | %s | ... ]|} type_name orig_name); - Line ""; - Line "@property"; - Line "def kind(self) -> str:"; - Block [ - Line {|"""Name of the class representing this variant."""|}; - Line (sprintf "return '%s'" (trans env unique_name)) - ]; - Line ""; - Line "@staticmethod"; - Line "def to_json() -> Any:"; - Block [ - Line (sprintf "return '%s'" (single_esc json_name)) - ]; - Line ""; - Line "def to_json_string(self, **kw: Any) -> str:"; - Block [ - Line "return json.dumps(self.to_json(), **kw)" - ] + Line (sprintf "struct %s {}" (trans env unique_name)); + Line (sprintf "JSONValue to_json(%s e) {" orig_name); + Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; + Line("}"); + Line (sprintf "JSONValue to_json_string(%s e) {" orig_name); + Block[ Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; + Line("}"); ] - ] | Some e -> [ - Inline class_decorators; - Line (sprintf "class %s:" (trans env unique_name)); - Block [ - Line (sprintf {|"""Original type: %s = [ ... | %s of ... | ... ]"""|} + Line (sprintf {|\\Original type: %s = [ ... | %s of ... | ... ]|} type_name orig_name); - Line ""; - Line (sprintf "value: %s" (type_name_of_expr env e)); - Line ""; - Line "@property"; - Line "def kind(self) -> str:"; - Block [ - Line {|"""Name of the class representing this variant."""|}; - Line (sprintf "return '%s'" (trans env unique_name)) - ]; - Line ""; - Line "def to_json(self) -> Any:"; - Block [ - Line (sprintf "return ['%s', %s(self.value)]" - (single_esc json_name) - (json_writer env e)) - ]; - Line ""; - Line "def to_json_string(self, **kw: Any) -> str:"; - Block [ - Line "return json.dumps(self.to_json(), **kw)" - ] + Line (sprintf "struct %s { value %s; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) + Line (sprintf "JSONValue to_json(%s e) {" orig_name); + Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; + Line("}"); + Line (sprintf "JSONValue to_json_string(%s e) {" orig_name); + Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; + Line("}"); ] - ] + let read_cases0 env loc name cases0 = let ifs = @@ -876,10 +844,10 @@ let read_cases0 env loc name 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 x == '%s':" (single_esc json_name)); + Line (sprintf "if (x.str == \"%s\") " (single_esc json_name)); Block [ - Line (sprintf "return cls(%s())" (trans env unique_name)) - ] + Line (sprintf "return (%s());" (trans env unique_name)) + ]; ] ) in @@ -900,9 +868,9 @@ let read_cases1 env loc name cases1 = in let json_name = Atd.Json.get_json_cons orig_name an in Inline [ - Line (sprintf "if cons == '%s':" (single_esc json_name)); + Line (sprintf "if (cons == \"%s\")" (single_esc json_name)); Block [ - Line (sprintf "return cls(%s(%s(x[1])))" + Line (sprintf "return (%s(%s(x[1])))" (trans env unique_name) (json_reader env e)) ] @@ -915,7 +883,7 @@ let read_cases1 env loc name cases1 = (class_name env name |> single_esc)) ] -let sum_container env ~class_decorators loc name cases = +let sum_container env loc name cases = let py_class_name = class_name env name in let type_list = List.map (fun (loc, orig_name, unique_name, an, opt_e) -> @@ -931,8 +899,9 @@ let sum_container env ~class_decorators loc name cases = let cases0_block = if cases0 <> [] then [ - Line "if isinstance(x, str):"; - Block (read_cases0 env loc name cases0) + Line "if (x.type == JSONType.string) {"; + Block (read_cases0 env loc name cases0); + Line "}"; ] else [] @@ -940,9 +909,9 @@ let sum_container env ~class_decorators loc name cases = let cases1_block = if cases1 <> [] then [ - Line "if isinstance(x, List) and len(x) == 2:"; + Line "if (x.type == JSONType.array AND x.array.length == 2 AND x[0].type == JSONType.string)"; Block [ - Line "cons = x[0]"; + Line "string cons = x[0].str"; Inline (read_cases1 env loc name cases1) ] ] @@ -950,22 +919,8 @@ let sum_container env ~class_decorators loc name cases = [] in [ - Inline class_decorators; - Line (sprintf "class %s:" py_class_name); - Block [ - Line (sprintf {|"""Original type: %s = [ ... ]"""|} name); - Line ""; - Line (sprintf "value: Union[%s]" type_list); - Line ""; - Line "@property"; - Line "def kind(self) -> str:"; - Block [ - Line {|"""Name of the class representing this variant."""|}; - Line (sprintf "return self.value.kind") - ]; - Line ""; - Line "@classmethod"; - Line (sprintf "def from_json(cls, x: Any) -> '%s':" + Line (sprintf "alias %s = SumType!(%s);" py_class_name type_list); + Line (sprintf "%s from_json(JSONValue x) {" (single_esc py_class_name)); Block [ Inline cases0_block; @@ -974,26 +929,21 @@ let sum_container env ~class_decorators loc name cases = (single_esc (class_name env name))) ]; Line ""; - Line "def to_json(self) -> Any:"; - Block [ - Line "return self.value.to_json()"; - ]; - Line ""; - Line "@classmethod"; - Line (sprintf "def from_json_string(cls, x: str) -> '%s':" - (single_esc py_class_name)); - Block [ - Line "return cls.from_json(json.loads(x))" - ]; - Line ""; - Line "def to_json_string(self, **kw: Any) -> str:"; - Block [ - Line "return json.dumps(self.to_json(), **kw)" - ] - ] + Line (sprintf "JSONValue to_json(%s x) {" (py_class_name)); + Block [ + Line "return x.match!("; + Line ( + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + sprintf "(%s v) => v.to_json_string" (trans env unique_name) + ) cases + |> String.concat ",\n"); + Line ");" + ]; + Line "}"; ] -let sum env ~class_decorators loc name cases = + +let sum env loc name cases = let cases = List.map (fun (x : variant) -> match x with @@ -1004,47 +954,30 @@ let sum env ~class_decorators loc name cases = ) cases in let case_classes = - List.map (fun x -> Inline (case_class env ~class_decorators name x)) cases + List.map (fun x -> Inline (case_class env name x)) cases |> double_spaced in - let container_class = sum_container env ~class_decorators loc name cases in + let container_class = sum_container env loc name cases in [ Inline case_classes; Inline container_class; ] |> double_spaced -let uses_dataclass_decorator = - let rex = Re.Pcre.regexp {|\A[ \t\r\n]*dataclass(\(|[ \t\r\n]|\z)|} in - fun s -> Re.Pcre.pmatch ~rex s - -let get_class_decorators an = - let decorators = Dlang_annot.get_dlang_decorators an in - (* Avoid duplicate use of the @dataclass decorator, which doesn't work - if some options like frozen=True are used. *) - if List.exists uses_dataclass_decorator decorators then - decorators - else - decorators @ ["dataclass"] - let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = if param <> [] then not_implemented loc "parametrized type"; - let class_decorators = - get_class_decorators an - |> List.map (fun s -> Line ("@" ^ s)) - in let rec unwrap e = match e with | Sum (loc, cases, an) -> - sum env ~class_decorators loc name cases + sum env loc name cases | Record (loc, fields, an) -> record env loc name fields an | Tuple _ | List _ | Option _ | Nullable _ - | Name _ -> alias_wrapper env ~class_decorators name e + | Name _ -> alias_wrapper env name e | Shared _ -> not_implemented loc "cyclic references" | Wrap (loc, e, an) -> unwrap e | Tvar _ -> not_implemented loc "parametrized type" From be42403884471fbfead638ecbcb070f45259eb8f Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 19:21:51 +0200 Subject: [PATCH 26/91] write / read fns: change from function to delegate, make read list read from jsonvalue --- atdd/src/lib/Codegen.ml | 34 +++++++------ atdd/src/lib/fixed_size_preamble.d | 76 +++++++++++++++++++----------- 2 files changed, 68 insertions(+), 42 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 1b6462d1..cc0a2b86 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -181,18 +181,18 @@ let fixed_size_preamble atd_filename = // This implements classes for the types defined in '%s', providing // methods and functions to convert data from/to JSON. - // // ############################################################################ // # Private functions // ############################################################################ - import std.format; + import std.algorithm : map; + import std.array : array; import std.conv; + import std.format; + import std.functional; import std.json; - import std.typecons : tuple, Tuple, nullable, Nullable; - import std.array : array; - import std.algorithm : map; + import std.typecons : nullable, Nullable, tuple, Tuple; class AtdException : Exception { @@ -276,13 +276,18 @@ let fixed_size_preamble atd_filename = throw _atd_bad_json("string", x); } - auto _atd_read_list(T)(T function(JSONValue) readElements) + auto _atd_read_list(T)(T delegate(JSONValue) readElements) { - return (JSONValue[] list) { return array(list.map!readElements()); }; + return (JSONValue jsonVal) { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("array", jsonVal); + auto list = jsonVal.array; + return array(list.map!readElements()); + }; } auto _atd_read_object_to_assoc_array(V)( - V function(JSONValue) readValue) + V delegate(JSONValue) readValue) { auto fun = (JSONValue jsonVal) { if (jsonVal.type != JSONType.object) @@ -296,7 +301,7 @@ let fixed_size_preamble atd_filename = } auto _atd_read_object_to_tuple_list(T)( - T function(JSONValue) readValue) + T delegate(JSONValue) readValue) { auto fun = (JSONValue jsonVal) { if (jsonVal.type != JSONType.object) @@ -311,7 +316,7 @@ let fixed_size_preamble atd_filename = } // TODO probably need to change that - auto _atd_read_nullable(T)(T function(JSONValue) readElm) + auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) { auto fun = (JSONValue e) { if (e.isNull) @@ -350,13 +355,14 @@ let fixed_size_preamble atd_filename = return JSONValue(s); } - auto _atd_write_list(T)(JSONValue function(T) writeElm) + + auto _atd_write_list(T)(JSONValue delegate(T) writeElm) { return (T[] list) { return JSONValue(array(list.map!writeElm())); }; } auto _atd_write_assoc_array_to_object(T)( - JSONValue function(T) writeValue) + JSONValue delegate(T) writeValue) { auto fun = (T[string] assocArr) { JSONValue[string] ret; @@ -368,7 +374,7 @@ let fixed_size_preamble atd_filename = } auto _atd_write_tuple_list_to_object(T)( - JSONValue function(T) writeValue) + JSONValue delegate(T) writeValue) { auto fun = (Tuple!(string, T)[] tupList) { JSONValue[string] ret; @@ -379,7 +385,7 @@ let fixed_size_preamble atd_filename = return fun; } - auto _atd_write_nullable(T)(JSONValue function(T) writeElm) + auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) { auto fun = (Nullable!T elm) { if (elm.isNull) diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 95eebbf6..a112ad80 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -1,19 +1,19 @@ -// """Generated by atdd from type definitions in %s. +// Generated by atdd 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 // ############################################################################ -import std.format; +import std.algorithm : map; +import std.array : array; import std.conv; +import std.format; +import std.functional; import std.json; -import std.typecons : tuple, Tuple, nullable, Nullable; -import std.array : array; -import std.algorithm : map; +import std.typecons : nullable, Nullable, tuple, Tuple; class AtdException : Exception { @@ -97,13 +97,18 @@ string _atd_read_string(JSONValue x) throw _atd_bad_json("string", x); } -auto _atd_read_list(T)(T function(JSONValue) readElements) +auto _atd_read_list(T)(T delegate(JSONValue) readElements) { - return (JSONValue[] list) { return array(list.map!readElements()); }; + return (JSONValue jsonVal) { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("array", jsonVal); + auto list = jsonVal.array; + return array(list.map!readElements()); + }; } auto _atd_read_object_to_assoc_array(V)( - V function(JSONValue) readValue) + V delegate(JSONValue) readValue) { auto fun = (JSONValue jsonVal) { if (jsonVal.type != JSONType.object) @@ -117,7 +122,7 @@ auto _atd_read_object_to_assoc_array(V)( } auto _atd_read_object_to_tuple_list(T)( - T function(JSONValue) readValue) + T delegate(JSONValue) readValue) { auto fun = (JSONValue jsonVal) { if (jsonVal.type != JSONType.object) @@ -132,7 +137,7 @@ auto _atd_read_object_to_tuple_list(T)( } // TODO probably need to change that -auto _atd_read_nullable(T)(T function(JSONValue) readElm) +auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) { auto fun = (JSONValue e) { if (e.isNull) @@ -171,13 +176,14 @@ JSONValue _atd_write_string(string s) return JSONValue(s); } -auto _atd_write_list(T)(JSONValue function(T) writeElm) + +auto _atd_write_list(T)(JSONValue delegate(T) writeElm) { return (T[] list) { return JSONValue(array(list.map!writeElm())); }; } auto _atd_write_assoc_array_to_object(T)( - JSONValue function(T) writeValue) + JSONValue delegate(T) writeValue) { auto fun = (T[string] assocArr) { JSONValue[string] ret; @@ -189,7 +195,7 @@ auto _atd_write_assoc_array_to_object(T)( } auto _atd_write_tuple_list_to_object(T)( - JSONValue function(T) writeValue) + JSONValue delegate(T) writeValue) { auto fun = (Tuple!(string, T)[] tupList) { JSONValue[string] ret; @@ -200,7 +206,7 @@ auto _atd_write_tuple_list_to_object(T)( return fun; } -auto _atd_write_nullable(T)(JSONValue function(T) writeElm) +auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) { auto fun = (Nullable!T elm) { if (elm.isNull) @@ -215,19 +221,33 @@ auto _atd_write_nullable(T)(JSONValue function(T) writeElm) // ====== TESTS ========= // ====================== + +unittest +{ + import std.stdio; + import std.functional; + + auto l = JSONValue([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); + + auto m = _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate)); + auto mm = _atd_write_list(_atd_write_list((&_atd_write_int).toDelegate)); + + assert(l == mm(m(l))); +} + unittest { import std.stdio; - auto l = [JSONValue(1), JSONValue(5)]; + auto l = JSONValue([JSONValue(1), JSONValue(5)]); - auto m = _atd_read_list(&_atd_read_int); - auto mm = _atd_write_list(&_atd_write_int); + auto m = _atd_read_list((&_atd_read_int).toDelegate); + auto mm = _atd_write_list((&_atd_write_int).toDelegate); auto read_l = m(l); auto write_l = mm(read_l); - assert(write_l == JSONValue(l)); + assert(write_l == l); } unittest @@ -236,10 +256,10 @@ unittest auto potentiallyNull = JSONValue(null); - auto m = _atd_read_nullable(&_atd_read_int); - auto mm = _atd_write_nullable(&_atd_write_int); + auto m = _atd_read_nullable((&_atd_read_int).toDelegate); + auto mm = _atd_write_nullable((&_atd_write_int).toDelegate); - Nullable!long readN = m(potentiallyNull); + Nullable!int readN = m(potentiallyNull); auto writeN = mm(readN); assert(potentiallyNull == writeN); @@ -251,8 +271,8 @@ unittest auto notNull = JSONValue(54); - auto m = _atd_read_nullable(&_atd_read_int); - auto mm = _atd_write_nullable(&_atd_write_int); + auto m = _atd_read_nullable((&_atd_read_int).toDelegate); + auto mm = _atd_write_nullable((&_atd_write_int).toDelegate); auto readv = m(notNull); auto writev = mm(readv); @@ -272,8 +292,8 @@ unittest "haha": JSONValue(5), ]); - auto m = _atd_read_object_to_assoc_array(&_atd_read_int); - auto mm = _atd_write_assoc_array_to_object(&_atd_write_int); + auto m = _atd_read_object_to_assoc_array((&_atd_read_int).toDelegate); + auto mm = _atd_write_assoc_array_to_object((&_atd_write_int).toDelegate); auto readv = m(l); auto writev = mm(readv); @@ -293,8 +313,8 @@ unittest "haha": JSONValue(5), ]); - auto m = _atd_read_object_to_tuple_list(&_atd_read_int); - auto mm = _atd_write_tuple_list_to_object(&_atd_write_int); + auto m = _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate); + auto mm = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate); auto readv = m(l); auto writev = mm(readv); From 3608cc6caa7b52cbe3b8429550bf5bf6df1071bf Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 8 Jun 2023 19:39:48 +0200 Subject: [PATCH 27/91] tentative fix to nested function toDelegate issue --- atdd/src/lib/Codegen.ml | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index cc0a2b86..982f9261 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -550,26 +550,27 @@ let rec json_writer ?(nested=false) env e = | List (loc, e, an) -> (match assoc_kind loc e an with | Array_list -> - sprintf "%s_atd_write_list(%s)" (if nested then "&" else "") (json_writer ~nested:true env e) + sprintf "_atd_write_list(%s)" (json_writer ~nested:true env e) | Array_dict (key, value) -> - sprintf "%s_atd_write_assoc_dict_to_array(%s, %s)" (if nested then "&" else "") + sprintf "_atd_write_assoc_dict_to_array(%s, %s)" (json_writer ~nested:true env key) (json_writer ~nested:true env value) | Object_dict value -> - sprintf "%s_atd_write_assoc_dict_to_object(%s)" (if nested then "&" else "") + sprintf "_atd_write_assoc_dict_to_object(%s)" (json_writer ~nested:true env value) | Object_list value -> - sprintf "%s_atd_write_assoc_list_to_object(%s)" (if nested then "&" else "") + sprintf "_atd_write_assoc_list_to_object(%s)" (json_writer ~nested:true env value) ) | Option (loc, e, an) -> - sprintf "%s_atd_write_option(%s)" (if nested then "&" else "") (json_writer ~nested:true env e) + sprintf "_atd_write_option(%s)"(json_writer ~nested:true env e) | Nullable (loc, e, an) -> - sprintf "%s_atd_write_nullable(%s)" (if nested then "&" else "") (json_writer ~nested:true env e) + sprintf "_atd_write_nullable(%s)" (json_writer ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" | Wrap (loc, e, an) -> json_writer ~nested:true env e | Name (loc, (loc2, name, []), an) -> (match name with - | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s" (if nested then "&" else "") name + | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" + (if nested then "(&" else "") name (if nested then ").toDelegate" else "") | "abstract" -> "(auto x => x)" | _ -> sprintf "((%s x) => x.toJson())" (dlang_type_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" From 6e689dcbd9c3fe533a8c7898961956c9042afe4d Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 10:41:32 +0200 Subject: [PATCH 28/91] fix backslash -> forward flash for comments --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 982f9261..c38543c7 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -819,7 +819,7 @@ let case_class env type_name match opt_e with | None -> [ - Line (sprintf {|\\Original type: %s = [ ... | %s | ... ]|} + Line (sprintf {|// Original type: %s = [ ... | %s | ... ]|} type_name orig_name); Line (sprintf "struct %s {}" (trans env unique_name)); @@ -832,7 +832,7 @@ let case_class env type_name ] | Some e -> [ - Line (sprintf {|\\Original type: %s = [ ... | %s of ... | ... ]|} + Line (sprintf {|// Original type: %s = [ ... | %s of ... | ... ]|} type_name orig_name); Line (sprintf "struct %s { value %s; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) From 01d26ca4fe287092e78549360c94cbb9a2b6dc28 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 08:40:26 +0000 Subject: [PATCH 29/91] minor syntax fix --- atdd/src/lib/Codegen.ml | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index c38543c7..1f362591 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -860,7 +860,7 @@ let read_cases0 env loc name cases0 = in [ Inline ifs; - Line (sprintf "_atd_bad_json('%s', x)" + Line (sprintf "_atd_bad_json('%s', x);" (class_name env name |> single_esc)) ] @@ -877,7 +877,7 @@ let read_cases1 env loc name cases1 = Inline [ Line (sprintf "if (cons == \"%s\")" (single_esc json_name)); Block [ - Line (sprintf "return (%s(%s(x[1])))" + Line (sprintf "return (%s(%s(x[1])));" (trans env unique_name) (json_reader env e)) ] @@ -886,7 +886,7 @@ let read_cases1 env loc name cases1 = in [ Inline ifs; - Line (sprintf "_atd_bad_json('%s', x)" + Line (sprintf "_atd_bad_json('%s', x);" (class_name env name |> single_esc)) ] @@ -916,26 +916,29 @@ let sum_container env loc name cases = let cases1_block = if cases1 <> [] then [ - Line "if (x.type == JSONType.array AND x.array.length == 2 AND x[0].type == JSONType.string)"; + Line "if (x.type == JSONType.array AND x.array.length == 2 AND x[0].type == JSONType.string) {"; Block [ - Line "string cons = x[0].str"; + Line "string cons = x[0].str;"; Inline (read_cases1 env loc name cases1) - ] + ]; + Line "}"; ] else [] in [ Line (sprintf "alias %s = SumType!(%s);" py_class_name type_list); + Line ""; Line (sprintf "%s from_json(JSONValue x) {" (single_esc py_class_name)); Block [ Inline cases0_block; Inline cases1_block; - Line (sprintf "_atd_bad_json('%s', x)" + Line (sprintf "_atd_bad_json('%s', x);" (single_esc (class_name env name))) ]; - Line ""; + Line "}"; + Line ""; Line (sprintf "JSONValue to_json(%s x) {" (py_class_name)); Block [ Line "return x.match!("; From f560aefebb6877e30fe14ea81249f65b1a7be86d Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 10:46:12 +0200 Subject: [PATCH 30/91] fix single value struct order --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 1f362591..626c5355 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -835,7 +835,7 @@ let case_class env type_name Line (sprintf {|// Original type: %s = [ ... | %s of ... | ... ]|} type_name orig_name); - Line (sprintf "struct %s { value %s; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) + Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); Line (sprintf "JSONValue to_json(%s e) {" orig_name); Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); From c3af51ad012ef0f528d16a1f67c3caa7a2076f6e Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 08:50:53 +0000 Subject: [PATCH 31/91] camlcase all the thing --- atdd/src/lib/Codegen.ml | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 626c5355..ff344e27 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -130,8 +130,8 @@ let init_env () : env = let method_names () = Atd.Unique_name.init ~reserved_identifiers:( - ["from_json"; "to_json"; - "from_json_string"; "to_json_string"] + ["fromJson"; "toJson"; + "fromJsonString"; "toJsonString"] @ keywords ) ~reserved_prefixes:["__"] @@ -652,7 +652,7 @@ let rec json_reader env (e : type_expr) = (match name with | "bool" | "int" | "float" | "string" -> sprintf "_atd_read_%s" name | "abstract" -> "(lambda x: x)" - | _ -> sprintf "%s.from_json" (class_name env name)) + | _ -> sprintf "%s.fromJson" (class_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" @@ -823,10 +823,10 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s {}" (trans env unique_name)); - Line (sprintf "JSONValue to_json(%s e) {" orig_name); + Line (sprintf "JSONValue toJson(%s e) {" orig_name); Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); - Line (sprintf "JSONValue to_json_string(%s e) {" orig_name); + Line (sprintf "JSONValue toJsonString(%s e) {" orig_name); Block[ Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); ] @@ -835,11 +835,11 @@ let case_class env type_name Line (sprintf {|// Original type: %s = [ ... | %s of ... | ... ]|} type_name orig_name); - Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); - Line (sprintf "JSONValue to_json(%s e) {" orig_name); + Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) + Line (sprintf "JSONValue toJson(%s e) {" orig_name); Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); - Line (sprintf "JSONValue to_json_string(%s e) {" orig_name); + Line (sprintf "JSONValue toJsonString(%s e) {" orig_name); Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); ] @@ -929,7 +929,7 @@ let sum_container env loc name cases = [ Line (sprintf "alias %s = SumType!(%s);" py_class_name type_list); Line ""; - Line (sprintf "%s from_json(JSONValue x) {" + Line (sprintf "%s fromJson(JSONValue x) {" (single_esc py_class_name)); Block [ Inline cases0_block; @@ -939,12 +939,12 @@ let sum_container env loc name cases = ]; Line "}"; Line ""; - Line (sprintf "JSONValue to_json(%s x) {" (py_class_name)); + Line (sprintf "JSONValue toJson(%s x) {" (py_class_name)); Block [ Line "return x.match!("; Line ( List.map (fun (loc, orig_name, unique_name, an, opt_e) -> - sprintf "(%s v) => v.to_json_string" (trans env unique_name) + sprintf "(%s v) => v.toJsonString" (trans env unique_name) ) cases |> String.concat ",\n"); Line ");" From e3c8c046d2708eb06b1c4b67d5446b81c591118f Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 09:40:29 +0000 Subject: [PATCH 32/91] handle abstract --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ff344e27..cde9d84e 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -457,7 +457,7 @@ let dlang_type_name env (name : string) = | "int" -> "int" | "float" -> "float" | "string" -> "string" - | "abstract" -> "abstract" (* TODO : figure out *) + | "abstract" -> "JSONValue" | user_defined -> class_name env user_defined let rec type_name_of_expr env (e : type_expr) : string = @@ -651,7 +651,7 @@ let rec json_reader env (e : type_expr) = | Name (loc, (loc2, name, []), an) -> (match name with | "bool" | "int" | "float" | "string" -> sprintf "_atd_read_%s" name - | "abstract" -> "(lambda x: x)" + | "abstract" -> "((JSONValue x) => x)" | _ -> sprintf "%s.fromJson" (class_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" From 31db0869c76755b2c304a395fac515d2f78f8cfc Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 09:47:03 +0000 Subject: [PATCH 33/91] m --- atdd/src/lib/Codegen.ml | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index cde9d84e..ca995a5f 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -823,10 +823,10 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s {}" (trans env unique_name)); - Line (sprintf "JSONValue toJson(%s e) {" orig_name); + Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); - Line (sprintf "JSONValue toJsonString(%s e) {" orig_name); + Line (sprintf "JSONValue toJsonString(%s e) {" (trans env unique_name)); Block[ Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); ] @@ -836,10 +836,10 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) - Line (sprintf "JSONValue toJson(%s e) {" orig_name); + Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); - Line (sprintf "JSONValue toJsonString(%s e) {" orig_name); + Line (sprintf "JSONValue toJsonString(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); ] @@ -929,8 +929,8 @@ let sum_container env loc name cases = [ Line (sprintf "alias %s = SumType!(%s);" py_class_name type_list); Line ""; - Line (sprintf "%s fromJson(JSONValue x) {" - (single_esc py_class_name)); + Line (sprintf "%s fromJson(%s)(JSONValue x) {" + (single_esc py_class_name) (single_esc py_class_name)); Block [ Inline cases0_block; Inline cases1_block; From 4075e8dad85138cb3a205c2d7dabdde8b45b5d60 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 11:55:09 +0200 Subject: [PATCH 34/91] small changes --- atdd/src/lib/Codegen.ml | 9 +++++---- atdd/src/lib/fixed_size_preamble.d | 1 + 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ca995a5f..078acea6 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -192,6 +192,7 @@ let fixed_size_preamble atd_filename = import std.format; import std.functional; import std.json; + import std.sumtype; import std.typecons : nullable, Nullable, tuple, Tuple; class AtdException : Exception @@ -860,7 +861,7 @@ let read_cases0 env loc name cases0 = in [ Inline ifs; - Line (sprintf "_atd_bad_json('%s', x);" + Line (sprintf "throw _atd_bad_json(\"%s\", x);" (class_name env name |> single_esc)) ] @@ -886,7 +887,7 @@ let read_cases1 env loc name cases1 = in [ Inline ifs; - Line (sprintf "_atd_bad_json('%s', x);" + Line (sprintf "throw _atd_bad_json(\"%s\", x);" (class_name env name |> single_esc)) ] @@ -916,7 +917,7 @@ let sum_container env loc name cases = let cases1_block = if cases1 <> [] then [ - Line "if (x.type == JSONType.array AND x.array.length == 2 AND x[0].type == JSONType.string) {"; + Line "if (x.type == JSONType.array && x.array.length == 2 && x[0].type == JSONType.string) {"; Block [ Line "string cons = x[0].str;"; Inline (read_cases1 env loc name cases1) @@ -934,7 +935,7 @@ let sum_container env loc name cases = Block [ Inline cases0_block; Inline cases1_block; - Line (sprintf "_atd_bad_json('%s', x);" + Line (sprintf "throw _atd_bad_json(\"%s\", x);" (single_esc (class_name env name))) ]; Line "}"; diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index a112ad80..97d8a21c 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -13,6 +13,7 @@ import std.conv; import std.format; import std.functional; import std.json; +import std.sumtype; import std.typecons : nullable, Nullable, tuple, Tuple; class AtdException : Exception From 3926a45a99f9f9d896b8b6fca4ebbac8ab3c61d7 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 11:57:03 +0200 Subject: [PATCH 35/91] fix lambda for untyped (abstract) --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 078acea6..fe1e3006 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -572,7 +572,7 @@ let rec json_writer ?(nested=false) env e = (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" (if nested then "(&" else "") name (if nested then ").toDelegate" else "") - | "abstract" -> "(auto x => x)" + | "abstract" -> "(auto x) => x" | _ -> sprintf "((%s x) => x.toJson())" (dlang_type_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" From 2556ae89e0197b6a3bd522bb5208c5fd1d3879ed Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:00:16 +0000 Subject: [PATCH 36/91] don't explictly specify null --- atdd/src/lib/Codegen.ml | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index fe1e3006..ade94173 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -499,17 +499,17 @@ let rec get_default_default (e : type_expr) : string option = | Tuple _ (* a default tuple could be possible but we're lazy *) -> None | List _ -> Some "[]" | Option _ - | Nullable _ -> Some "null" + | Nullable _ -> None | 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" -> Some "null" + | "unit" -> None | "bool" -> Some "false" | "int" -> Some "0" | "float" -> Some "0.0" | "string" -> Some {|""|} - | "abstract" -> Some "null" + | "abstract" -> None | _ -> None ) | Name _ -> None @@ -716,8 +716,8 @@ let inst_var_declaration let unwrapped_e = unwrap_field_type loc name kind e in let default = match kind with - | Required -> "" - | Optional -> " = null" + | Required + | Optional -> "" | With_default -> match get_dlang_default unwrapped_e an with | None -> "" From 72236c35dc5c276ebf2b0853863fd11a758bbe86 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:11:17 +0000 Subject: [PATCH 37/91] wrap variant key in jsonvalue --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ade94173..31c61057 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -838,10 +838,10 @@ let case_class env type_name orig_name); Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); - Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; + Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); Line (sprintf "JSONValue toJsonString(%s e) {" (trans env unique_name)); - Block [Line(sprintf "return JSONValue([\"%s\", %s(e.value)]);" (single_esc json_name) (json_writer env e))]; + Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); ] From c7c9f8922a62c3697f470d9f244db75e4d9de98d Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:14:21 +0000 Subject: [PATCH 38/91] fix opt --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 31c61057..ca5dd785 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -600,7 +600,7 @@ let construct_json_field env trans_meth let writer_function = json_writer env unwrapped_type in let assignment = [ - Line (sprintf "res[\"%s\"] = %s(obj.%s);" + Line (sprintf "res[\"%s\"] = %s(obj.%s.get);" (Atd.Json.get_json_fname name an |> single_esc) writer_function (inst_var_name trans_meth name)) @@ -611,7 +611,7 @@ let construct_json_field env trans_meth | With_default -> assignment | Optional -> [ - Line (sprintf "if (!%s.isNull)" + Line (sprintf "if (!obj.%s.isNull)" (inst_var_name trans_meth name)); Block assignment ] From f8710c5272dc96664a2480d8f09f5e6b11ed1399 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:16:27 +0000 Subject: [PATCH 39/91] w --- atdd/src/lib/Codegen.ml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ca5dd785..87497a5b 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -600,7 +600,7 @@ let construct_json_field env trans_meth let writer_function = json_writer env unwrapped_type in let assignment = [ - Line (sprintf "res[\"%s\"] = %s(obj.%s.get);" + Line (sprintf "res[\"%s\"] = %s(obj.%s);" (Atd.Json.get_json_fname name an |> single_esc) writer_function (inst_var_name trans_meth name)) @@ -613,7 +613,11 @@ let construct_json_field env trans_meth [ Line (sprintf "if (!obj.%s.isNull)" (inst_var_name trans_meth name)); - Block assignment + Block [ Line(sprintf "res[\"%s\"] = %s(obj.%s.get);" + (Atd.Json.get_json_fname name an |> single_esc) + writer_function + (inst_var_name trans_meth name))]; + ] (* From 632f59ea3f86c59f5c64c21905761dcc700d78cf Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:27:08 +0000 Subject: [PATCH 40/91] implement reader/writer for alias --- atdd/src/lib/Codegen.ml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 87497a5b..f3fe2b0e 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -816,6 +816,15 @@ let alias_wrapper env name type_expr = let value_type = type_name_of_expr env type_expr in [ Line (sprintf "alias %s = %s;" dlang_struct_name value_type); + Line (sprintf "JSONValue toJson(%s e) {" dlang_struct_name); + Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; + Line("}"); + Line (sprintf "JSONValue toJsonString(%s e) {" dlang_struct_name); + Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; + Line("}"); + Line (sprintf "%s fromJson(JSONValue e) {" dlang_struct_name); + Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; + Line("}"); ] let case_class env type_name From 908314b077308e9920a8f983c8a2dc097589ae2c Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:28:20 +0000 Subject: [PATCH 41/91] fix tuple lambda --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index f3fe2b0e..fbbffdbf 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -589,7 +589,7 @@ and tuple_writer env (loc, cells, an) = ) cells |> String.concat ", " in - sprintf "(%s x) => JSONValue([%s])" + sprintf "((%s x) => JSONValue([%s]))" (type_name_of_expr env (Tuple (loc, cells, an))) tuple_body From fb59292c3afe52090187fc54f5550a86df04557c Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:31:06 +0000 Subject: [PATCH 42/91] spf --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index fbbffdbf..3dea54c1 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -822,7 +822,7 @@ let alias_wrapper env name type_expr = Line (sprintf "JSONValue toJsonString(%s e) {" dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; Line("}"); - Line (sprintf "%s fromJson(JSONValue e) {" dlang_struct_name); + Line (sprintf "%s fromJson(%s)(JSONValue e) {" dlang_struct_name dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; Line("}"); ] From 598e6eeb6197a1c638e1c2a3ceca288b35664c60 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 9 Jun 2023 10:53:32 +0000 Subject: [PATCH 43/91] fix object writers --- atdd/src/lib/Codegen.ml | 28 ++++++++++------------------ 1 file changed, 10 insertions(+), 18 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 3dea54c1..43cf37e3 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -479,7 +479,8 @@ let rec type_name_of_expr env (e : type_expr) : string = (type_name_of_expr env e) | Array_dict (key, value) -> sprintf "%s[%s]" - (type_name_of_expr env key) (type_name_of_expr env value) + (type_name_of_expr env value) + (type_name_of_expr env key) | Object_dict value -> sprintf "%s[string]" (type_name_of_expr env value) @@ -556,10 +557,10 @@ let rec json_writer ?(nested=false) env e = sprintf "_atd_write_assoc_dict_to_array(%s, %s)" (json_writer ~nested:true env key) (json_writer ~nested:true env value) | Object_dict value -> - sprintf "_atd_write_assoc_dict_to_object(%s)" + sprintf "_atd_write_assoc_array_to_object(%s)" (json_writer ~nested:true env value) | Object_list value -> - sprintf "_atd_write_assoc_list_to_object(%s)" + sprintf "_atd_write_tuple_list_to_object(%s)" (json_writer ~nested:true env value) ) | Option (loc, e, an) -> @@ -638,13 +639,13 @@ let rec json_reader env (e : type_expr) = sprintf "_atd_read_list(%s)" (json_reader env e) | Array_dict (key, value) -> - sprintf "_atd_read_assoc_array_into_dict(%s, %s)" + sprintf "_atd_read_array_to_assoc_dict(%s, %s)" (json_reader env key) (json_reader env value) | Object_dict value -> - sprintf "_atd_read_assoc_object_into_dict(%s)" + sprintf "_atd_read_object_to_assoc_array(%s)" (json_reader env value) | Object_list value -> - sprintf "_atd_read_assoc_object_into_list(%s)" + sprintf "_atd_read_object_to_tuple_list(%s)" (json_reader env value) ) | Option (loc, e, an) -> @@ -661,11 +662,6 @@ let rec json_reader env (e : type_expr) = | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" -(* - Convert json list to python tuple - - (lambda x: (read0(x[0]), read1(x[1])) if isinstance(x, list) else error()) -*) and tuple_reader env cells = let tuple_body = List.mapi (fun i (loc, e, an) -> @@ -725,11 +721,7 @@ let inst_var_declaration | With_default -> match get_dlang_default unwrapped_e an with | None -> "" - | Some x -> - (* This constructs ensures that a fresh default value is - evaluated for each class instanciation. It's important for - default lists since Python lists are mutable. *) - sprintf " = %s" x + | Some x -> sprintf " = %s" x in [ Line (sprintf "%s %s%s;" type_name var_name default) @@ -1037,11 +1029,11 @@ let to_file ~atd_filename ~head (items : A.module_body) dst_path = let env = init_env () in reserve_good_class_names env items; let head = List.map (fun s -> Line s) head in - let python_defs = + let dlang_defs = Atd.Util.tsort items |> List.map (fun x -> Inline (definition_group ~atd_filename env x)) in - Line (fixed_size_preamble atd_filename) :: Inline head :: python_defs + Line (fixed_size_preamble atd_filename) :: Inline head :: dlang_defs |> double_spaced |> Indent.to_file ~indent:4 dst_path From cbe47feab55cb11e06c0c083cca187390264925d Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 12:55:20 +0200 Subject: [PATCH 44/91] add writer for assoc dic into array --- atdd/src/lib/Codegen.ml | 37 ++++++++++++++++++++++++++--- atdd/src/lib/fixed_size_preamble.d | 38 ++++++++++++++++++++++++++---- 2 files changed, 68 insertions(+), 7 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 43cf37e3..af5a19be 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -279,11 +279,11 @@ let fixed_size_preamble atd_filename = auto _atd_read_list(T)(T delegate(JSONValue) readElements) { - return (JSONValue jsonVal) { + return (JSONValue jsonVal) { if (jsonVal.type != JSONType.array) throw _atd_bad_json("array", jsonVal); auto list = jsonVal.array; - return array(list.map!readElements()); + return array(list.map!readElements()); }; } @@ -301,6 +301,25 @@ let fixed_size_preamble atd_filename = return fun; } + auto _atd_read_array_to_assoc_dict(K, V)( + K delegate(JSONValue) readKey, + V delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.list) + throw _atd_bad_json("list", jsonVal); + V[K] ret; + foreach (jsonInnerVal; jsonVal.object) + { + if (jsonInnerVal.type != JSONType.list) + throw _atd_bad_json("list", jsonInnerVal); + ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + } + return ret; + }; + return fun; + } + auto _atd_read_object_to_tuple_list(T)( T delegate(JSONValue) readValue) { @@ -356,7 +375,6 @@ let fixed_size_preamble atd_filename = return JSONValue(s); } - auto _atd_write_list(T)(JSONValue delegate(T) writeElm) { return (T[] list) { return JSONValue(array(list.map!writeElm())); }; @@ -374,6 +392,19 @@ let fixed_size_preamble atd_filename = return fun; } + auto _atd_write_assoc_dict_to_array(K, V)( + JSONValue delegate(K) writeKey, + JSONValue delegate(V) writeValue) + { + auto fun = (V[K] assocArr) { + JSONValue[] ret; + foreach (key, val; assocArr) + ret ~= JSONValue[writeKey(key), writeValue(val)]; + return JSONValue(ret); + }; + return fun; + } + auto _atd_write_tuple_list_to_object(T)( JSONValue delegate(T) writeValue) { diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 97d8a21c..647d45e1 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -100,11 +100,11 @@ string _atd_read_string(JSONValue x) auto _atd_read_list(T)(T delegate(JSONValue) readElements) { - return (JSONValue jsonVal) { + return (JSONValue jsonVal) { if (jsonVal.type != JSONType.array) throw _atd_bad_json("array", jsonVal); auto list = jsonVal.array; - return array(list.map!readElements()); + return array(list.map!readElements()); }; } @@ -122,6 +122,25 @@ auto _atd_read_object_to_assoc_array(V)( return fun; } +auto _atd_read_array_to_assoc_dict(K, V)( + K delegate(JSONValue) readKey, + V delegate(JSONValue) readValue) +{ + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.list) + throw _atd_bad_json("list", jsonVal); + V[K] ret; + foreach (jsonInnerVal; jsonVal.object) + { + if (jsonInnerVal.type != JSONType.list) + throw _atd_bad_json("list", jsonInnerVal); + ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + } + return ret; + }; + return fun; +} + auto _atd_read_object_to_tuple_list(T)( T delegate(JSONValue) readValue) { @@ -177,7 +196,6 @@ JSONValue _atd_write_string(string s) return JSONValue(s); } - auto _atd_write_list(T)(JSONValue delegate(T) writeElm) { return (T[] list) { return JSONValue(array(list.map!writeElm())); }; @@ -195,6 +213,19 @@ auto _atd_write_assoc_array_to_object(T)( return fun; } +auto _atd_write_assoc_dict_to_array(K, V)( + JSONValue delegate(K) writeKey, + JSONValue delegate(V) writeValue) +{ + auto fun = (V[K] assocArr) { + JSONValue[] ret; + foreach (key, val; assocArr) + ret ~= JSONValue[writeKey(key), writeValue(val)]; + return JSONValue(ret); + }; + return fun; +} + auto _atd_write_tuple_list_to_object(T)( JSONValue delegate(T) writeValue) { @@ -222,7 +253,6 @@ auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) // ====== TESTS ========= // ====================== - unittest { import std.stdio; From 375762a116782d6e637b5a04bfdabf643c522053 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 13:14:14 +0200 Subject: [PATCH 45/91] add option read and write --- atdd/src/lib/Codegen.ml | 29 ++++++++++++++++++++++++++--- atdd/src/lib/fixed_size_preamble.d | 29 ++++++++++++++++++++++++++--- 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index af5a19be..64ff00f2 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -306,12 +306,12 @@ let fixed_size_preamble atd_filename = V delegate(JSONValue) readValue) { auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.list) + if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; foreach (jsonInnerVal; jsonVal.object) { - if (jsonInnerVal.type != JSONType.list) + if (jsonInnerVal.type != JSONType.array) throw _atd_bad_json("list", jsonInnerVal); ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); } @@ -335,7 +335,6 @@ let fixed_size_preamble atd_filename = return fun; } - // TODO probably need to change that auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) { auto fun = (JSONValue e) { @@ -347,6 +346,19 @@ let fixed_size_preamble atd_filename = return fun; } + auto _atd_read_option(T)(T delegate(JSONValue) readElm) + { + auto fun = (JSONValue e) { + if (e.type == JSONType.string && e.str == "None") + return Nullable!T.init; + else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") + return Nullable!T(readElm(e)); + else + throw _atd_bad_json("option", e); + }; + return fun; + } + // this whole set of function could be remplaced by one templated _atd_write_value function // not sure it is what we want though @@ -427,6 +439,17 @@ let fixed_size_preamble atd_filename = }; return fun; } + + auto _atd_write_option(T)(JSONValue delegate(T) writeElm) + { + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue("None"); + else + return JSONValue([JSONValue("Some"), writeElm(elm.get)]); + }; + return fun; + } // ############################################################################ diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 647d45e1..1b94e855 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -127,12 +127,12 @@ auto _atd_read_array_to_assoc_dict(K, V)( V delegate(JSONValue) readValue) { auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.list) + if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; foreach (jsonInnerVal; jsonVal.object) { - if (jsonInnerVal.type != JSONType.list) + if (jsonInnerVal.type != JSONType.array) throw _atd_bad_json("list", jsonInnerVal); ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); } @@ -156,7 +156,6 @@ auto _atd_read_object_to_tuple_list(T)( return fun; } -// TODO probably need to change that auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) { auto fun = (JSONValue e) { @@ -168,6 +167,19 @@ auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) return fun; } +auto _atd_read_option(T)(T delegate(JSONValue) readElm) +{ + auto fun = (JSONValue e) { + if (e.type == JSONType.string && e.str == "None") + return Nullable!T.init; + else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") + return Nullable!T(readElm(e)); + else + throw _atd_bad_json("option", e); + }; + return fun; +} + // this whole set of function could be remplaced by one templated _atd_write_value function // not sure it is what we want though @@ -249,6 +261,17 @@ auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) return fun; } +auto _atd_write_option(T)(JSONValue delegate(T) writeElm) +{ + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue("None"); + else + return JSONValue([JSONValue("Some"), writeElm(elm.get)]); + }; + return fun; +} + // ====================== // ====== TESTS ========= // ====================== From 98e23fcf401a7e4e111865fd7d000e93ef8db9dd Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Fri, 9 Jun 2023 11:37:11 +0000 Subject: [PATCH 46/91] atdd : wip work on dlang test harness --- atdd/Makefile | 6 +- atdd/src/lib/Codegen.ml | 2 +- atdd/src/test/Main.ml | 2 +- atdd/test/.gitignore | 2 +- atdd/test/atd-input/everything.atd | 17 +- atdd/test/{python-tests => dlang-tests}/dune | 17 +- atdd/test/dune | 10 +- atdd/test/python-tests/deco.py | 16 -- atdd/test/python-tests/manual_sample.py | 214 --------------- atdd/test/python-tests/test_atdpy.py | 273 ------------------- 10 files changed, 25 insertions(+), 534 deletions(-) rename atdd/test/{python-tests => dlang-tests}/dune (50%) delete mode 100644 atdd/test/python-tests/deco.py delete mode 100644 atdd/test/python-tests/manual_sample.py delete mode 100644 atdd/test/python-tests/test_atdpy.py diff --git a/atdd/Makefile b/atdd/Makefile index 5ed39833..93f3b9d0 100644 --- a/atdd/Makefile +++ b/atdd/Makefile @@ -18,13 +18,13 @@ build: test: $(MAKE) clean-for-dune $(DUNE) runtest -f; status=$$?; \ - ln -s ../../../_build/default/atdd/test/python-tests/everything.py \ - test/python-tests/everything.py && \ + ln -s ../../../_build/default/atdd/test/dlang-tests/everything.d \ + test/dlang-tests/everything.d && \ exit "$$status" .PHONY: clean-for-dune clean-for-dune: - rm -f test/python-tests/everything.py + rm -f test/dlang-tests/everything.d .PHONY: clean clean: diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 64ff00f2..7a70fc27 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -459,7 +459,7 @@ let fixed_size_preamble atd_filename = atd_filename let not_implemented loc msg = - A.error_at loc ("not implemented in atdpy: " ^ msg) + A.error_at loc ("not implemented in atdd: " ^ msg) let todo hint = failwith ("TODO: " ^ hint) diff --git a/atdd/src/test/Main.ml b/atdd/src/test/Main.ml index 6d20e7c9..62867672 100644 --- a/atdd/src/test/Main.ml +++ b/atdd/src/test/Main.ml @@ -9,6 +9,6 @@ let test_suites : unit Alcotest.test list = [ ] let main () = - Alcotest.run "atdpy" test_suites + Alcotest.run "atdd" test_suites let () = main () diff --git a/atdd/test/.gitignore b/atdd/test/.gitignore index e3b3414c..8c670430 100644 --- a/atdd/test/.gitignore +++ b/atdd/test/.gitignore @@ -1,2 +1,2 @@ __pycache__ -everything.py +everything.d diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index faf7089c..acdf579c 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -1,7 +1,3 @@ - - - - type kind = [ | Root (* class name conflict *) | Thing of int @@ -9,8 +5,7 @@ type kind = [ | Amaze of string list ] -type frozen - = [ +type frozen = [ | A | B of int ] @@ -29,14 +24,14 @@ type root = { items: int list list; ?maybe: int option; ~extras: int list; - ~answer : int; + ~answer : int; aliased: alias; point: (float * float); kinds: kind list; assoc1: (float * int) list; assoc2: (string * int) list ; - assoc3: (float * int) list ; - assoc4: (string * int) list ; + assoc3: (float * int) list; + assoc4: (string * int) list ; nullables: int nullable list; options: int option list; untyped_things: abstract list; @@ -48,9 +43,7 @@ type alias = int list type pair = (string * int) -type require_field = { +type require_field = { req: string; } diff --git a/atdd/test/python-tests/dune b/atdd/test/dlang-tests/dune similarity index 50% rename from atdd/test/python-tests/dune rename to atdd/test/dlang-tests/dune index 3814e0fa..43f34a8c 100644 --- a/atdd/test/python-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -1,28 +1,29 @@ ; -; Convert ATD -> Python +; Convert ATD -> Dlang ; (rule (targets - allcaps.py - everything.py + allcaps.d + everything.d ) (deps ../atd-input/ALLCAPS.atd ../atd-input/everything.atd ) (action - (run %{bin:atdpy} %{deps}))) + (run %{bin:atdd} %{deps}))) ; -; Typecheck and run the tests on the generated Python code. +; Typecheck and run the tests on the generated Dlang code. ; (rule (alias runtest) - (package atdpy) + (package atdd) (deps - everything.py - (glob_files *.py)) + everything.d + (glob_files *.d)) (action + ; TODO: update to run Dlang typechecker and unit testing (progn (run python3 -m flake8 .) (run python3 -m mypy --strict .) diff --git a/atdd/test/dune b/atdd/test/dune index 0df56117..5662169b 100644 --- a/atdd/test/dune +++ b/atdd/test/dune @@ -1,17 +1,17 @@ ; ; We test in two phases: ; -; 1. Check that the generated Python code is what we expect. +; 1. Check that the generated Dlang code is what we expect. ; (rule (alias runtest) (package atdd) (action - (diff dlang-expected/everything.py - dlang-tests/everything.py))) + (diff dlang-expected/everything.d + dlang-tests/everything.d))) -; 2. Run the generated Python code and check that is reads or writes JSON +; 2. Run the generated Dlang code and check that is reads or writes JSON ; data as expected. ; -; See python-tests/dune +; See dlang-tests/dune diff --git a/atdd/test/python-tests/deco.py b/atdd/test/python-tests/deco.py deleted file mode 100644 index 3d545208..00000000 --- a/atdd/test/python-tests/deco.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Test custom decorators. -""" - -from typing import Any - - -def deco1(cls: Any) -> Any: - print(f"Decorating class {cls.__name__} with deco1") - return cls - - -def deco2(n: int) -> Any: - def decorator(cls: Any) -> Any: - print(f"Decorating class {cls.__name__} with deco2({n})") - return cls - return decorator diff --git a/atdd/test/python-tests/manual_sample.py b/atdd/test/python-tests/manual_sample.py deleted file mode 100644 index a6e868ab..00000000 --- a/atdd/test/python-tests/manual_sample.py +++ /dev/null @@ -1,214 +0,0 @@ -""" -Handwritten code that serves as a model for generated code. -""" - -# Disable flake8 entirely on this file: -# flake8: noqa - -from dataclasses import dataclass -from typing import Any, Callable, Dict, List, NoReturn, Optional, Tuple - -import json - - -def _atd_missing_json_field(type_name: str, json_field_name: str) -> NoReturn: - raise ValueError(f"missing field '{json_field_name}'" - f" in JSON object of type '{type_name}'") - - -def _atd_bad_json(expected_type: str, json_value: Any) -> NoReturn: - value_str = str(json_value) - if len(value_str) > 200: - value_str = value_str[:200] + '…' - - raise ValueError(f"incompatible JSON value where" - f" type '{expected_type}' was expected: '{value_str}'") - - -def _atd_bad_python(expected_type: str, json_value: Any) -> NoReturn: - value_str = str(json_value) - if len(value_str) > 200: - value_str = value_str[:200] + '…' - - raise ValueError(f"incompatible Python value where" - f" type '{expected_type}' was expected: '{value_str}'") - - -def _atd_read_unit(x: Any) -> None: - if x is None: - return x - else: - _atd_bad_json('unit', x) - - -def _atd_read_bool(x: Any) -> bool: - if isinstance(x, bool): - return x - else: - _atd_bad_json('bool', x) - - -def _atd_read_int(x: Any) -> int: - if isinstance(x, int): - return x - else: - _atd_bad_json('int', x) - - -def _atd_read_float(x: Any) -> float: - if isinstance(x, (int, float)): - return x - else: - _atd_bad_json('float', x) - - -def _atd_read_string(x: Any) -> str: - if isinstance(x, str): - return x - else: - _atd_bad_json('str', x) - - -def _atd_read_list( - read_elt: Callable[[Any], Any] - ) -> Callable[[List[Any]], List[Any]]: - def read_list(elts: List[Any]) -> List[Any]: - if isinstance(elts, list): - return [read_elt(elt) for elt in elts] - else: - _atd_bad_json('array', elts) - return read_list - -def _atd_read_assoc_object_into_dict( - read_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], Dict[str, Any]]: - def read_assoc(elts: Dict[str, Any]) -> Dict[str, Any]: - if isinstance(elts, dict): - return {_atd_read_string(k): read_value(v) for k, v in elts.items()} - else: - _atd_bad_json('object', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_assoc_object_into_list( - read_value: Callable[[Any], Any] - ) -> Callable[[Dict[str, Any]], List[Tuple[str, Any]]]: - def read_assoc(elts: Dict[str, Any]) -> List[Tuple[str, Any]]: - if isinstance(elts, dict): - return [(_atd_read_string(k), read_value(v)) for k, v in elts.items()] - else: - _atd_bad_json('object', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_assoc_array_into_dict( - read_key: Callable[[Any], Any], - read_value: Callable[[Any], Any], - ) -> Callable[[List[Any]], Dict[str, Any]]: - def read_assoc(elts: List[List[Any]]) -> Dict[str, Any]: - if isinstance(elts, list): - return {read_key(elt[0]): read_value(elt[1]) for elt in elts} - else: - _atd_bad_json('array', elts) - raise AssertionError('impossible') # keep mypy happy - return read_assoc - - -def _atd_read_nullable(read_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def read_nullable(x: Any) -> Any: - if x is None: - return None - else: - return read_elt(x) - return read_nullable - - -def _atd_write_unit(x: Any) -> None: - if x is None: - return x - else: - _atd_bad_python('unit', x) - - -def _atd_write_bool(x: Any) -> bool: - if isinstance(x, bool): - return x - else: - _atd_bad_python('bool', x) - - -def _atd_write_int(x: Any) -> int: - if isinstance(x, int): - return x - else: - _atd_bad_python('int', x) - - -def _atd_write_float(x: Any) -> float: - if isinstance(x, (int, float)): - return x - else: - _atd_bad_python('float', x) - - -def _atd_write_string(x: Any) -> str: - if isinstance(x, str): - return x - else: - _atd_bad_python('str', x) - - -def _atd_write_list(write_elt: Callable[[Any], Any]) \ - -> Callable[[List[Any]], List[Any]]: - def write_list(elts: List[Any]) -> List[Any]: - if isinstance(elts, list): - return [write_elt(elt) for elt in elts] - else: - _atd_bad_python('list', elts) - return write_list - - -def _atd_write_nullable(write_elt: Callable[[Any], Any]) \ - -> Callable[[Optional[Any]], Optional[Any]]: - def write_nullable(x: Any) -> Any: - if x is None: - return None - else: - return write_elt(x) - return write_nullable - - -@dataclass -class Root: - id: str - await_: bool - items: List[List[int]] - - @classmethod - def from_json(cls, x: Any) -> "Root": - if isinstance(x, dict): - return cls( - id=_atd_read_string(x['id']) if 'id' in x else _atd_missing_json_field('Root', 'id'), - await_=_atd_read_bool(x['await']) if 'await' in x else _atd_missing_json_field('Root', 'await'), - items=_atd_read_list(_atd_read_list(_atd_read_int))(x['items']) if 'items' in x else _atd_missing_json_field('Root', 'items'), - ) - else: - _atd_bad_json('Root', x) - raise AssertionError('impossible') # keep mypy happy - - def to_json(self) -> Any: - res: Dict[str, Any] = {} - res['id'] = _atd_write_string(self.id) - res['await'] = _atd_write_bool(self.await_) - res['items'] = _atd_write_list(_atd_write_list(_atd_write_int))(self.items) - return res - - @classmethod - def from_json_string(cls, x: str) -> "Root": - return cls.from_json(json.loads(x)) - - def to_json_string(self) -> str: - return json.dumps(self.to_json()) diff --git a/atdd/test/python-tests/test_atdpy.py b/atdd/test/python-tests/test_atdpy.py deleted file mode 100644 index b505dc41..00000000 --- a/atdd/test/python-tests/test_atdpy.py +++ /dev/null @@ -1,273 +0,0 @@ -""" -Test suite for all Python code, some of which is generated by atdpy. - -Each function starting with 'test_' is executed as a test by pytest. -""" - -import manual_sample -import everything as e - - -def test_sample() -> None: - a_obj = manual_sample.Root(id="hello", await_=True, items=[[1, 2], [3]]) - a_str = a_obj.to_json_string() - - b_str = '{"id": "hello", "await": true, "items": [[1, 2], [3]]}' - b_obj = manual_sample.Root.from_json_string(a_str) - b_str2 = b_obj.to_json_string() - - assert b_str == b_str2 # depends on json formatting (whitespace...) - assert b_str2 == a_str - - -def test_sample_missing_field() -> None: - try: - manual_sample.Root.from_json_string('{}') - assert False - except ValueError: - pass - - -def test_sample_wrong_type() -> None: - try: - manual_sample.Root.from_json_string('["hello"]') - assert False - except ValueError: - pass - - -# mypy correctly rejects this. -# TODO: move to its own file and expect mypy to fail. -# def test_require_field() -> None: -# try: -# # Should fail because the 'req' field is required. -# e.RequireField() -# assert False -# except ValueError: -# pass - - -def test_everything_to_json() -> None: - a_obj = e.Root( - id="abc", - await_=True, - x___init__=1.5, - items=[[], [1, 2]], - extras=[17, 53], - answer=42, - aliased=e.Alias([8, 9, 10]), - point=(3.3, -77.22), - kinds=[ - e.Kind(e.WOW()), - e.Kind(e.Thing(99)), - e.Kind(e.Amaze(["a", "b"])), - e.Kind(e.Root_()) - ], - assoc1=[ - (1.1, 1), - (2.2, 2), - ], - assoc2=[ - ("c", 3), - ("d", 4), - ], - assoc3={ - 5.5: 5, - 6.6: 6, - }, - assoc4={ - "g": 7, - "h": 8, - }, - nullables=[12, None, 34], - options=[56, None, 78], - untyped_things=[[["hello"]], {}, None, 123], - parametrized_record=e.IntFloatParametrizedRecord( - field_a=42, - field_b=[9.9, 8.8], - ), - parametrized_tuple=e.KindParametrizedTuple( - (e.Kind(e.WOW()), e.Kind(e.WOW()), 100) - ) - ) - a_str = a_obj.to_json_string(indent=2) - print(a_str) - - # expected output copy-pasted from the output of the failing test - b_str = \ - """{ - "ID": "abc", - "await": true, - "__init__": 1.5, - "items": [ - [], - [ - 1, - 2 - ] - ], - "aliased": [ - 8, - 9, - 10 - ], - "point": [ - 3.3, - -77.22 - ], - "kinds": [ - "wow", - [ - "Thing", - 99 - ], - [ - "!!!", - [ - "a", - "b" - ] - ], - "Root" - ], - "assoc1": [ - [ - 1.1, - 1 - ], - [ - 2.2, - 2 - ] - ], - "assoc2": { - "c": 3, - "d": 4 - }, - "assoc3": [ - [ - 5.5, - 5 - ], - [ - 6.6, - 6 - ] - ], - "assoc4": { - "g": 7, - "h": 8 - }, - "nullables": [ - 12, - null, - 34 - ], - "options": [ - [ - "Some", - 56 - ], - "None", - [ - "Some", - 78 - ] - ], - "untyped_things": [ - [ - [ - "hello" - ] - ], - {}, - null, - 123 - ], - "parametrized_record": { - "field_a": 42, - "field_b": [ - 9.9, - 8.8 - ] - }, - "parametrized_tuple": [ - "wow", - "wow", - 100 - ], - "extras": [ - 17, - 53 - ], - "answer": 42 -}""" - b_obj = e.Root.from_json_string(a_str) - b_str2 = b_obj.to_json_string(indent=2) - - assert b_str == b_str2 # depends on json formatting (whitespace...) - assert b_str2 == a_str - - -def test_kind() -> None: - x = e.Kind(e.WOW()) - assert x.kind == x.value.kind - assert x.kind == 'WOW' - - -def test_pair() -> None: - try: - e.Pair.from_json_string('[1,2,3]') - assert False - except ValueError as exn: - print(f"Exception: {exn}") - assert str(exn) == ( - "incompatible JSON value where type " - "'array of length 2' was expected: '[1, 2, 3]'" - ) - - -def test_recursive_class() -> None: - child1 = e.RecursiveClass(id=1, flag=True, children=[]) - child2 = e.RecursiveClass(id=2, flag=True, children=[]) - a_obj = e.RecursiveClass(id=0, flag=False, children=[child1, child2]) - a_str = a_obj.to_json_string(indent=2) - - b_str = """{ - "id": 0, - "flag": false, - "children": [ - { - "id": 1, - "flag": true, - "children": [] - }, - { - "id": 2, - "flag": true, - "children": [] - } - ] -}""" - b_obj = e.RecursiveClass.from_json_string(a_str) - b_str2 = b_obj.to_json_string(indent=2) - - assert b_str == b_str2 - assert b_str2 == a_str - - -def test_default_list() -> None: - a = e.DefaultList(items=[]) - assert a.items == [] - b = e.DefaultList() - assert b.items == [] - c = e.DefaultList.from_json_string("{}") - assert c.items == [] - # We could emit '{}' instead of '{"items": []}' but it's more complicated - # and not always desired. - j = b.to_json_string() - assert j == '{"items": []}' - - -# print updated json -test_everything_to_json() From c323103bc7060f4acfc7064383ff90e708d0f658 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 15:25:28 +0200 Subject: [PATCH 47/91] fix input type of lambda --- atdd/src/lib/Codegen.ml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 7a70fc27..3bc7f79d 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -627,7 +627,7 @@ let rec json_writer ?(nested=false) env e = (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" (if nested then "(&" else "") name (if nested then ").toDelegate" else "") - | "abstract" -> "(auto x) => x" + | "abstract" -> "(JSONValue x) => x" | _ -> sprintf "((%s x) => x.toJson())" (dlang_type_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" From 08b508de356ccb139001a6c4f63554dbd058e8a2 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 17:15:57 +0200 Subject: [PATCH 48/91] remove to and from json string generated code, fix module name, fix delegate for inner function when reading --- atdd/src/lib/Codegen.ml | 66 +++++++++++++++++++---------------------- 1 file changed, 30 insertions(+), 36 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 3bc7f79d..4c3b44fc 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -177,8 +177,9 @@ let _double_esc s = escape_string_content Double s let fixed_size_preamble atd_filename = - sprintf {|// Generated by atdd from type definitions in %s. + sprintf {| + // Generated by atdd from type definitions in %s. // This implements classes for the types defined in '%s', providing // methods and functions to convert data from/to JSON. @@ -186,6 +187,8 @@ let fixed_size_preamble atd_filename = // # Private functions // ############################################################################ + module %s; + import std.algorithm : map; import std.array : array; import std.conv; @@ -451,12 +454,25 @@ let fixed_size_preamble atd_filename = return fun; } - // ############################################################################ // # Public classes - // ############################################################################|} + // ############################################################################ + + T fromJsonString(T)(string s) + { + JSONValue res = parseJSON(s); + return res.fromJson!T; + } + + string toJsonString(T)(T obj) + { + JSONValue res = obj.toJson; + return res.toString; + }|} atd_filename atd_filename + (Filename.remove_extension atd_filename) + let not_implemented loc msg = A.error_at loc ("not implemented in atdd: " ^ msg) @@ -679,7 +695,7 @@ let construct_json_field env trans_meth Function value that can be applied to a JSON node, converting it to the desired value. *) -let rec json_reader env (e : type_expr) = +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" @@ -691,26 +707,27 @@ let rec json_reader env (e : type_expr) = (match assoc_kind loc e an with | Array_list -> sprintf "_atd_read_list(%s)" - (json_reader env e) + (json_reader ~nested:true env e) | Array_dict (key, value) -> sprintf "_atd_read_array_to_assoc_dict(%s, %s)" - (json_reader env key) (json_reader env value) + (json_reader ~nested:true env key) (json_reader env value) | Object_dict value -> sprintf "_atd_read_object_to_assoc_array(%s)" - (json_reader env value) + (json_reader ~nested:true env value) | Object_list value -> sprintf "_atd_read_object_to_tuple_list(%s)" - (json_reader env value) + (json_reader ~nested:true env value) ) | Option (loc, e, an) -> - sprintf "_atd_read_option(%s)" (json_reader env e) + sprintf "_atd_read_option(%s)" (json_reader ~nested:true env e) | Nullable (loc, e, an) -> - sprintf "_atd_read_nullable(%s)" (json_reader env e) + sprintf "_atd_read_nullable(%s)" (json_reader ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" - | Wrap (loc, e, an) -> json_reader env e + | Wrap (loc, e, an) -> json_reader ~nested:true env e | Name (loc, (loc2, name, []), an) -> (match name with - | "bool" | "int" | "float" | "string" -> sprintf "_atd_read_%s" name + | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_read_%s%s" + (if nested then "(&" else "") name (if nested then ").toDelegate" else "") | "abstract" -> "((JSONValue x) => x)" | _ -> sprintf "%s.fromJson" (class_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" @@ -802,7 +819,7 @@ let record env loc name (fields : field list) an = ) fields in let from_json = [ - Line (sprintf "%s fromJson(%s)(JSONValue x) {" + Line (sprintf "%s fromJson(T : %s)(JSONValue x) {" (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ Line (sprintf "%s obj;" dlang_struct_name); @@ -823,27 +840,6 @@ let record env loc name (fields : field list) an = Line "}"; ] in - let from_json_string = - [ - Line (sprintf "%s fromJsonString(%s)(string x) {" - (single_esc dlang_struct_name) (single_esc dlang_struct_name)); - Block [ - Line "JSONValue res = parseJSON(x);"; - Line(sprintf "return res.fromJson!%s;" (single_esc dlang_struct_name)) - ]; - Line "}"; - ] - in - let to_json_string = - [ - Line (sprintf "string toJsonString(%s) (%s obj) {" (single_esc dlang_struct_name) (single_esc dlang_struct_name)); - Block [ - Line ("JSONValue res = obj.toJson;"); - Line "return res.toString;" - ]; - Line "}"; - ] - in [ Line (sprintf "struct %s {" dlang_struct_name); Block (spaced [ @@ -853,8 +849,6 @@ let record env loc name (fields : field list) an = Line ""; Inline from_json; Inline to_json; - Inline from_json_string; - Inline to_json_string; ] let alias_wrapper env name type_expr = From e8164d8b3333e89bcabefeec48ee9957da7ae1fd Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 17:33:39 +0200 Subject: [PATCH 49/91] handle parametrized return type for function that throws in ternary operator when reading --- atdd/src/lib/Codegen.ml | 5 +++-- atdd/src/lib/fixed_size_preamble.d | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 4c3b44fc..2764c2f0 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -206,7 +206,7 @@ let fixed_size_preamble atd_filename = } } - void _atd_missing_json_field(string typeName, string jsonFieldName) + T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); } @@ -761,7 +761,8 @@ let from_json_class_argument let else_body = match kind with | Required -> - sprintf "_atd_missing_json_field(\"%s\", \"%s\")" + sprintf "_atd_missing_json_field!(typeof(obj.%s))(\"%s\", \"%s\")" + dlang_name (single_esc py_class_name) (single_esc json_name) | Optional -> "null" diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 1b94e855..3eec2aca 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -24,7 +24,7 @@ class AtdException : Exception } } -void _atd_missing_json_field(string typeName, string jsonFieldName) +T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); } From ecf5a9d47bd63e753608c3b4cbc61f254eae5304 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 9 Jun 2023 17:35:36 +0200 Subject: [PATCH 50/91] add simple test that pass --- atdd/test/dlang-tests/test_atdd.d | 33 +++++++++++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 atdd/test/dlang-tests/test_atdd.d diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d new file mode 100644 index 00000000..e9ff3f17 --- /dev/null +++ b/atdd/test/dlang-tests/test_atdd.d @@ -0,0 +1,33 @@ +import everything; + +void testeasy() +{ + auto record = IntFloatParametrizedRecord(32, [5.4, 3.3]); + + auto json = record.toJson(); + auto recordFromJson = fromJson!IntFloatParametrizedRecord(json); + + assert(record == recordFromJson); +} + +void recordMissingInt() +{ + auto json = "{\"field_b\":[5.40000009536743164,3.29999995231628418]}"; + + try + { + fromJsonString!IntFloatParametrizedRecord(json); + assert(false); + } + catch (Exception e) + { + assert(true); + } +} + +int main() +{ + testeasy(); + recordMissingInt(); + return 0; +} \ No newline at end of file From 39e1068d5e4f3e43d1ddb7be192194ac123e4870 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Fri, 9 Jun 2023 15:43:59 +0000 Subject: [PATCH 51/91] atdd : fix diff from expected test --- atdd/test/.gitignore | 5 +- atdd/test/dlang-expected/everything.d | 588 ++++++++++++++++++++++++++ atdd/test/dlang-tests/dune | 7 +- 3 files changed, 596 insertions(+), 4 deletions(-) create mode 100644 atdd/test/dlang-expected/everything.d diff --git a/atdd/test/.gitignore b/atdd/test/.gitignore index 8c670430..8ea94de0 100644 --- a/atdd/test/.gitignore +++ b/atdd/test/.gitignore @@ -1,2 +1,5 @@ __pycache__ -everything.d + +!dlang-expected/ +dlang-tests/*.d +dlang-tests/*.py \ No newline at end of file diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d new file mode 100644 index 00000000..a84d5da5 --- /dev/null +++ b/atdd/test/dlang-expected/everything.d @@ -0,0 +1,588 @@ + + + // Generated by atdd 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 + // ############################################################################ + + module everything; + + import std.algorithm : map; + import std.array : array; + import std.conv; + import std.format; + import std.functional; + import std.json; + import std.sumtype; + import std.typecons : nullable, Nullable, tuple, Tuple; + + class AtdException : Exception + { + this(string msg, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line); + } + } + + void _atd_missing_json_field(string typeName, string jsonFieldName) + { + throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); + } + + // TODO check later if template is right way to go + AtdException _atd_bad_json(T)(string expectedType, T jsonValue) + { + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } + + return new AtdException( + "incompatible JSON value where type '%s' was expected: %s".format( + expectedType, valueStr + )); + } + + AtdException _atd_bad_d(T)(string expectedType, T jsonValue) + { + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } + + return new AtdException( + "incompatible D value where type '%s' was expected: %s".format( + expectedType, valueStr + )); + } + + typeof(null) _atd_read_unit(JSONValue x) + { + if (x.isNull) + return null; + else + throw _atd_bad_json("unit", x); + } + + bool _atd_read_bool(JSONValue x) + { + try + return x.boolean; + catch (JSONException e) + throw _atd_bad_json("bool", x); + } + + int _atd_read_int(JSONValue x) + { + try + return cast(int) x.integer; + catch (JSONException e) + throw _atd_bad_json("int", x); + } + + float _atd_read_float(JSONValue x) + { + try + return x.floating; + catch (JSONException e) + throw _atd_bad_json("float", x); + } + + string _atd_read_string(JSONValue x) + { + try + return x.str; + catch (JSONException e) + throw _atd_bad_json("string", x); + } + + auto _atd_read_list(T)(T delegate(JSONValue) readElements) + { + return (JSONValue jsonVal) { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("array", jsonVal); + auto list = jsonVal.array; + return array(list.map!readElements()); + }; + } + + auto _atd_read_object_to_assoc_array(V)( + V delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + V[string] ret; + foreach (key, val; jsonVal.object) + ret[key] = readValue(val); + return ret; + }; + return fun; + } + + auto _atd_read_array_to_assoc_dict(K, V)( + K delegate(JSONValue) readKey, + V delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("list", jsonVal); + V[K] ret; + foreach (jsonInnerVal; jsonVal.object) + { + if (jsonInnerVal.type != JSONType.array) + throw _atd_bad_json("list", jsonInnerVal); + ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + } + return ret; + }; + return fun; + } + + auto _atd_read_object_to_tuple_list(T)( + T delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + auto tupList = new Tuple!(string, T)[](jsonVal.object.length); + int i = 0; + foreach (key, val; jsonVal.object) + tupList[i++] = tuple(key, readValue(val)); + return tupList; + }; + return fun; + } + + auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) + { + auto fun = (JSONValue e) { + if (e.isNull) + return Nullable!T.init; + else + return Nullable!T(readElm(e)); + }; + return fun; + } + + auto _atd_read_option(T)(T delegate(JSONValue) readElm) + { + auto fun = (JSONValue e) { + if (e.type == JSONType.string && e.str == "None") + return Nullable!T.init; + else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") + return Nullable!T(readElm(e)); + else + throw _atd_bad_json("option", e); + }; + return fun; + } + + // this whole set of function could be remplaced by one templated _atd_write_value function + // not sure it is what we want though + + JSONValue _atd_write_unit(typeof(null) n) + { + return JSONValue(null); + } + + JSONValue _atd_write_bool(bool b) + { + return JSONValue(b); + } + + JSONValue _atd_write_int(int i) + { + return JSONValue(i); + } + + JSONValue _atd_write_float(float f) + { + return JSONValue(f); + } + + JSONValue _atd_write_string(string s) + { + return JSONValue(s); + } + + auto _atd_write_list(T)(JSONValue delegate(T) writeElm) + { + return (T[] list) { return JSONValue(array(list.map!writeElm())); }; + } + + auto _atd_write_assoc_array_to_object(T)( + JSONValue delegate(T) writeValue) + { + auto fun = (T[string] assocArr) { + JSONValue[string] ret; + foreach (key, val; assocArr) + ret[key] = writeValue(val); + return JSONValue(ret); + }; + return fun; + } + + auto _atd_write_assoc_dict_to_array(K, V)( + JSONValue delegate(K) writeKey, + JSONValue delegate(V) writeValue) + { + auto fun = (V[K] assocArr) { + JSONValue[] ret; + foreach (key, val; assocArr) + ret ~= JSONValue[writeKey(key), writeValue(val)]; + return JSONValue(ret); + }; + return fun; + } + + auto _atd_write_tuple_list_to_object(T)( + JSONValue delegate(T) writeValue) + { + auto fun = (Tuple!(string, T)[] tupList) { + JSONValue[string] ret; + foreach (tup; tupList) + ret[tup[0]] = writeValue(tup[1]); + return JSONValue(ret); + }; + return fun; + } + + auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) + { + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue(null); + else + return writeElm(elm.get); + }; + return fun; + } + + auto _atd_write_option(T)(JSONValue delegate(T) writeElm) + { + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue("None"); + else + return JSONValue([JSONValue("Some"), writeElm(elm.get)]); + }; + return fun; + } + + // ############################################################################ + // # Public classes + // ############################################################################ + + T fromJsonString(T)(string s) + { + JSONValue res = parseJSON(s); + return res.fromJson!T; + } + + string toJsonString(T)(T obj) + { + JSONValue res = obj.toJson; + return res.toString; + } + + +struct RecursiveClass { + int id; + bool flag; + RecursiveClass[] children; +} + +RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { + RecursiveClass obj; + obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field("RecursiveClass", "id"); + obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field("RecursiveClass", "flag"); + obj.children = ("children" in x) ? _atd_read_list(RecursiveClass.fromJson)(x["children"]) : _atd_missing_json_field("RecursiveClass", "children"); + return obj; +} +JSONValue toJson(RecursiveClass obj) { + JSONValue res; + res["id"] = _atd_write_int(obj.id); + res["flag"] = _atd_write_bool(obj.flag); + res["children"] = _atd_write_list(((RecursiveClass x) => x.toJson()))(obj.children); + return res; +} + + +// Original type: kind = [ ... | Root | ... ] +struct Root_ {} +JSONValue toJson(Root_ e) { + return JSONValue("Root"); +} +JSONValue toJsonString(Root_ e) { + return JSONValue("Root"); +} + + +// Original type: kind = [ ... | Thing of ... | ... ] +struct Thing { int value; } +JSONValue toJson(Thing e) { + return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); +} +JSONValue toJsonString(Thing e) { + return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); +} + + +// Original type: kind = [ ... | WOW | ... ] +struct WOW {} +JSONValue toJson(WOW e) { + return JSONValue("wow"); +} +JSONValue toJsonString(WOW e) { + return JSONValue("wow"); +} + + +// Original type: kind = [ ... | Amaze of ... | ... ] +struct Amaze { string[] value; } +JSONValue toJson(Amaze e) { + return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); +} +JSONValue toJsonString(Amaze e) { + return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); +} + + +alias Kind = SumType!(Root_, Thing, WOW, Amaze); + +Kind fromJson(Kind)(JSONValue x) { + if (x.type == JSONType.string) { + if (x.str == "Root") + return (Root_()); + if (x.str == "wow") + return (WOW()); + throw _atd_bad_json("Kind", x); + } + if (x.type == JSONType.array && x.array.length == 2 && x[0].type == JSONType.string) { + string cons = x[0].str; + if (cons == "Thing") + return (Thing(_atd_read_int(x[1]))); + if (cons == "!!!") + return (Amaze(_atd_read_list((&_atd_read_string).toDelegate)(x[1]))); + throw _atd_bad_json("Kind", x); + } + throw _atd_bad_json("Kind", x); +} + +JSONValue toJson(Kind x) { + return x.match!( + (Root_ v) => v.toJsonString, +(Thing v) => v.toJsonString, +(WOW v) => v.toJsonString, +(Amaze v) => v.toJsonString + ); +} + + +alias Alias = int[]; +JSONValue toJson(Alias e) { + return _atd_write_list((&_atd_write_int).toDelegate)(e); +} +JSONValue toJsonString(Alias e) { + return _atd_write_list((&_atd_write_int).toDelegate)(e); +} +Alias fromJson(Alias)(JSONValue e) { + return _atd_read_list((&_atd_read_int).toDelegate)(e); +} + + +alias KindParametrizedTuple = Tuple!(Kind, Kind, int); +JSONValue toJson(KindParametrizedTuple e) { + return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e); +} +JSONValue toJsonString(KindParametrizedTuple e) { + return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e); +} +KindParametrizedTuple fromJson(KindParametrizedTuple)(JSONValue e) { + return (JSONValue x) => tuple(Kind.fromJson(x[0]), Kind.fromJson(x[1]), _atd_read_int(x[2]))(e); +} + + +struct IntFloatParametrizedRecord { + int field_a; + float[] field_b = []; +} + +IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { + IntFloatParametrizedRecord obj; + obj.field_a = ("field_a" in x) ? _atd_read_int(x["field_a"]) : _atd_missing_json_field("IntFloatParametrizedRecord", "field_a"); + obj.field_b = ("field_b" in x) ? _atd_read_list((&_atd_read_float).toDelegate)(x["field_b"]) : []; + return obj; +} +JSONValue toJson(IntFloatParametrizedRecord obj) { + JSONValue res; + res["field_a"] = _atd_write_int(obj.field_a); + res["field_b"] = _atd_write_list((&_atd_write_float).toDelegate)(obj.field_b); + return res; +} + + +struct Root { + string id; + bool await; + float x___init__; + int[][] items; + Nullable!int maybe; + int[] extras = []; + int answer = 42; + Alias aliased; + Tuple!(float, float) point; + Kind[] kinds; + Tuple!(float, int)[] assoc1; + Tuple!(string, int)[] assoc2; + Tuple!(float, int)[] assoc3; + Tuple!(string, int)[] assoc4; + Nullable!int[] nullables; + Nullable!int[] options; + JSONValue[] untyped_things; + IntFloatParametrizedRecord parametrized_record; + KindParametrizedTuple parametrized_tuple; +} + +Root fromJson(T : Root)(JSONValue x) { + Root obj; + obj.id = ("ID" in x) ? _atd_read_string(x["ID"]) : _atd_missing_json_field("Root", "ID"); + obj.await = ("await" in x) ? _atd_read_bool(x["await"]) : _atd_missing_json_field("Root", "await"); + obj.x___init__ = ("__init__" in x) ? _atd_read_float(x["__init__"]) : _atd_missing_json_field("Root", "__init__"); + obj.items = ("items" in x) ? _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate))(x["items"]) : _atd_missing_json_field("Root", "items"); + obj.maybe = ("maybe" in x) ? _atd_read_int(x["maybe"]) : null; + obj.extras = ("extras" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["extras"]) : []; + obj.answer = ("answer" in x) ? _atd_read_int(x["answer"]) : 42; + obj.aliased = ("aliased" in x) ? Alias.fromJson(x["aliased"]) : _atd_missing_json_field("Root", "aliased"); + obj.point = ("point" in x) ? (JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_float(x[1]))(x["point"]) : _atd_missing_json_field("Root", "point"); + obj.kinds = ("kinds" in x) ? _atd_read_list(Kind.fromJson)(x["kinds"]) : _atd_missing_json_field("Root", "kinds"); + obj.assoc1 = ("assoc1" in x) ? _atd_read_list((JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_int(x[1])))(x["assoc1"]) : _atd_missing_json_field("Root", "assoc1"); + obj.assoc2 = ("assoc2" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc2"]) : _atd_missing_json_field("Root", "assoc2"); + obj.assoc3 = ("assoc3" in x) ? _atd_read_list((JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_int(x[1])))(x["assoc3"]) : _atd_missing_json_field("Root", "assoc3"); + obj.assoc4 = ("assoc4" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc4"]) : _atd_missing_json_field("Root", "assoc4"); + obj.nullables = ("nullables" in x) ? _atd_read_list(_atd_read_nullable((&_atd_read_int).toDelegate))(x["nullables"]) : _atd_missing_json_field("Root", "nullables"); + obj.options = ("options" in x) ? _atd_read_list(_atd_read_option((&_atd_read_int).toDelegate))(x["options"]) : _atd_missing_json_field("Root", "options"); + obj.untyped_things = ("untyped_things" in x) ? _atd_read_list(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field("Root", "untyped_things"); + obj.parametrized_record = ("parametrized_record" in x) ? IntFloatParametrizedRecord.fromJson(x["parametrized_record"]) : _atd_missing_json_field("Root", "parametrized_record"); + obj.parametrized_tuple = ("parametrized_tuple" in x) ? KindParametrizedTuple.fromJson(x["parametrized_tuple"]) : _atd_missing_json_field("Root", "parametrized_tuple"); + return obj; +} +JSONValue toJson(Root obj) { + JSONValue res; + res["ID"] = _atd_write_string(obj.id); + res["await"] = _atd_write_bool(obj.await); + res["__init__"] = _atd_write_float(obj.x___init__); + res["items"] = _atd_write_list(_atd_write_list((&_atd_write_int).toDelegate))(obj.items); + if (!obj.maybe.isNull) + res["maybe"] = _atd_write_int(obj.maybe.get); + res["extras"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.extras); + res["answer"] = _atd_write_int(obj.answer); + res["aliased"] = ((Alias x) => x.toJson())(obj.aliased); + res["point"] = ((Tuple!(float, float) x) => JSONValue([_atd_write_float(x[0]), _atd_write_float(x[1])]))(obj.point); + res["kinds"] = _atd_write_list(((Kind x) => x.toJson()))(obj.kinds); + res["assoc1"] = _atd_write_list(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc1); + res["assoc2"] = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate)(obj.assoc2); + res["assoc3"] = _atd_write_list(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc3); + res["assoc4"] = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate)(obj.assoc4); + res["nullables"] = _atd_write_list(_atd_write_nullable((&_atd_write_int).toDelegate))(obj.nullables); + res["options"] = _atd_write_list(_atd_write_option((&_atd_write_int).toDelegate))(obj.options); + res["untyped_things"] = _atd_write_list((JSONValue x) => x)(obj.untyped_things); + res["parametrized_record"] = ((IntFloatParametrizedRecord x) => x.toJson())(obj.parametrized_record); + res["parametrized_tuple"] = ((KindParametrizedTuple x) => x.toJson())(obj.parametrized_tuple); + return res; +} + + +struct RequireField { + string req; +} + +RequireField fromJson(T : RequireField)(JSONValue x) { + RequireField obj; + obj.req = ("req" in x) ? _atd_read_string(x["req"]) : _atd_missing_json_field("RequireField", "req"); + return obj; +} +JSONValue toJson(RequireField obj) { + JSONValue res; + res["req"] = _atd_write_string(obj.req); + return res; +} + + +alias Pair = Tuple!(string, int); +JSONValue toJson(Pair e) { + return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); +} +JSONValue toJsonString(Pair e) { + return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); +} +Pair fromJson(Pair)(JSONValue e) { + return (JSONValue x) => tuple(_atd_read_string(x[0]), _atd_read_int(x[1]))(e); +} + + +// Original type: frozen = [ ... | A | ... ] +struct A {} +JSONValue toJson(A e) { + return JSONValue("A"); +} +JSONValue toJsonString(A e) { + return JSONValue("A"); +} + + +// Original type: frozen = [ ... | B of ... | ... ] +struct B { int value; } +JSONValue toJson(B e) { + return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); +} +JSONValue toJsonString(B e) { + return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); +} + + +alias Frozen = SumType!(A, B); + +Frozen fromJson(Frozen)(JSONValue x) { + if (x.type == JSONType.string) { + if (x.str == "A") + return (A()); + throw _atd_bad_json("Frozen", x); + } + if (x.type == JSONType.array && x.array.length == 2 && x[0].type == JSONType.string) { + string cons = x[0].str; + if (cons == "B") + return (B(_atd_read_int(x[1]))); + throw _atd_bad_json("Frozen", x); + } + throw _atd_bad_json("Frozen", x); +} + +JSONValue toJson(Frozen x) { + return x.match!( + (A v) => v.toJsonString, +(B v) => v.toJsonString + ); +} + + +struct DefaultList { + int[] items = []; +} + +DefaultList fromJson(T : DefaultList)(JSONValue x) { + DefaultList obj; + obj.items = ("items" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["items"]) : []; + return obj; +} +JSONValue toJson(DefaultList obj) { + JSONValue res; + res["items"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.items); + return res; +} diff --git a/atdd/test/dlang-tests/dune b/atdd/test/dlang-tests/dune index 43f34a8c..7f579ded 100644 --- a/atdd/test/dlang-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -25,6 +25,7 @@ (action ; TODO: update to run Dlang typechecker and unit testing (progn - (run python3 -m flake8 .) - (run python3 -m mypy --strict .) - (run python3 -m pytest .)))) + (run ldc2) + ;(run python3 -m mypy --strict .) + ;(run python3 -m pytest .) + ))) From 6c6136a3cfdb6666f0f4386efd92d33fd11a9433 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Fri, 9 Jun 2023 17:13:41 +0000 Subject: [PATCH 52/91] atdd : fix lcd compile check --- atdd/test/dlang-tests/dune | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/atdd/test/dlang-tests/dune b/atdd/test/dlang-tests/dune index 7f579ded..38682c38 100644 --- a/atdd/test/dlang-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -20,12 +20,9 @@ (alias runtest) (package atdd) (deps - everything.d (glob_files *.d)) (action - ; TODO: update to run Dlang typechecker and unit testing + ; TODO: ensure that compile check is run on unit tests, too (progn - (run ldc2) - ;(run python3 -m mypy --strict .) - ;(run python3 -m pytest .) + (run ldc2 %{deps}) ))) From 146900e40301555a18e61575a55235532fa37849 Mon Sep 17 00:00:00 2001 From: Rytis Jonynas Date: Fri, 9 Jun 2023 19:30:58 +0000 Subject: [PATCH 53/91] atdd : mention ldc version constraint in README --- atdd/README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/atdd/README.md b/atdd/README.md index 3aabc212..cc43d7bd 100644 --- a/atdd/README.md +++ b/atdd/README.md @@ -1,3 +1,4 @@ Atdd == +Requires ldc >= 1.27.0 \ No newline at end of file From 05357374a959a9ba5ad7d749323fdeac01ca3a26 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Mon, 12 Jun 2023 08:35:09 +0000 Subject: [PATCH 54/91] some more cleanup --- atdd/src/lib/Codegen.ml | 75 ++++++++++++++---------------- atdd/src/lib/Dlang_annot.ml | 29 +----------- atdd/src/lib/Dlang_annot.mli | 5 -- atdd/test/atd-input/everything.atd | 4 +- 4 files changed, 37 insertions(+), 76 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 2764c2f0..54748b5a 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -17,7 +17,7 @@ type env = { (* Global *) create_variable: string -> string; translate_variable: string -> string; - (* Local to a class: instance variables, including method names *) + (* Local to a struct: instance variables, including method names *) translate_inst_variable: unit -> (string -> string); } @@ -64,21 +64,20 @@ let to_camel_case s = | 'A'..'Z' | 'a'..'z' | '_' -> name | _ -> "X" ^ name -(* Use CamelCase as recommended by PEP 8. *) -let class_name env id = +(* Use CamelCase *) +let struct_name env id = trans env (to_camel_case id) (* - Create a class identifier that hasn't been seen yet. + Create a struct identifier that hasn't been seen yet. This is for internal disambiguation and still must translated using - the 'trans' function ('class_name' will not work due to trailing + the 'trans' function ('struct_name' will not work due to trailing underscores being added for disambiguation). *) -let create_class_name env name = +let create_struct_name env name = let preferred_id = to_camel_case name in env.create_variable preferred_id -(* TODO : edit list of keywoard based on dlang *) let init_env () : env = let keywords = [ (* Keywords @@ -529,7 +528,7 @@ let dlang_type_name env (name : string) = | "float" -> "float" | "string" -> "string" | "abstract" -> "JSONValue" - | user_defined -> class_name env user_defined + | user_defined -> struct_name env user_defined let rec type_name_of_expr env (e : type_expr) : string = match e with @@ -592,9 +591,9 @@ let get_dlang_default (e : type_expr) (an : annot) : string option = | Some s -> Some s | None -> get_default_default e -(* If the field is '?foo: bar option', its python or json value has type +(* If the field is '?foo: bar option', its dlang or json value has type 'bar' rather than 'bar option'. *) -let unwrap_field_type loc field_name kind e = +let unwrap_field_type loc field_name kind e = (* todo : dubious for dlang*) match kind with | Required | With_default -> e @@ -648,11 +647,6 @@ let rec json_writer ?(nested=false) env e = | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" -(* - Convert dlang tuple to json list - - (lambda x: [write0(x[0]), write1(x[1])] if isinstance(x, tuple) else error()) -*) and tuple_writer env (loc, cells, an) = let tuple_body = List.mapi (fun i (loc, e, an) -> @@ -664,7 +658,6 @@ and tuple_writer env (loc, cells, an) = (type_name_of_expr env (Tuple (loc, cells, an))) tuple_body - 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 @@ -729,7 +722,7 @@ let rec json_reader ?(nested=false) env (e : type_expr) = | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_read_%s%s" (if nested then "(&" else "") name (if nested then ").toDelegate" else "") | "abstract" -> "((JSONValue x) => x)" - | _ -> sprintf "%s.fromJson" (class_name env name)) + | _ -> sprintf "%s.fromJson" (struct_name env name)) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" @@ -743,7 +736,7 @@ and tuple_reader env cells = sprintf "(JSONValue x) => tuple(%s)" tuple_body let from_json_class_argument - env trans_meth py_class_name ((loc, (name, kind, an), e) : simple_field) = + env trans_meth dlang_struct_name ((loc, (name, kind, an), e) : simple_field) = let dlang_name = inst_var_name trans_meth name in let json_name = Atd.Json.get_json_fname name an in let unwrapped_type = @@ -763,7 +756,7 @@ let from_json_class_argument | Required -> sprintf "_atd_missing_json_field!(typeof(obj.%s))(\"%s\", \"%s\")" dlang_name - (single_esc py_class_name) + (single_esc dlang_struct_name) (single_esc json_name) | Optional -> "null" | With_default -> @@ -800,7 +793,7 @@ let inst_var_declaration ] let record env loc name (fields : field list) an = - let dlang_struct_name = class_name env name in + let dlang_struct_name = struct_name env name in let trans_meth = env.translate_inst_variable () in let fields = List.map (function @@ -853,15 +846,15 @@ let record env loc name (fields : field list) an = ] let alias_wrapper env name type_expr = - let dlang_struct_name = class_name env name in + let dlang_struct_name = struct_name env name in let value_type = type_name_of_expr env type_expr in [ Line (sprintf "alias %s = %s;" dlang_struct_name value_type); Line (sprintf "JSONValue toJson(%s e) {" dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; Line("}"); - Line (sprintf "JSONValue toJsonString(%s e) {" dlang_struct_name); - Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; + Line (sprintf "string toJsonString(%s e) {" dlang_struct_name); + Block [Line(sprintf "return %s(e).toString;" (json_writer env type_expr))]; Line("}"); Line (sprintf "%s fromJson(%s)(JSONValue e) {" dlang_struct_name dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; @@ -881,8 +874,8 @@ let case_class env type_name Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); - Line (sprintf "JSONValue toJsonString(%s e) {" (trans env unique_name)); - Block[ Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; + Line (sprintf "string toJsonString(%s e) {" (trans env unique_name)); + Block[ Line(sprintf "return JSONValue(\"%s\").toString;" (single_esc json_name))]; Line("}"); ] | Some e -> @@ -894,8 +887,8 @@ let case_class env type_name Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); - Line (sprintf "JSONValue toJsonString(%s e) {" (trans env unique_name)); - Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; + Line (sprintf "string toJsonString(%s e) {" (trans env unique_name)); + Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]).toString;" (single_esc json_name) (json_writer env e))]; Line("}"); ] @@ -916,7 +909,7 @@ let read_cases0 env loc name cases0 = [ Inline ifs; Line (sprintf "throw _atd_bad_json(\"%s\", x);" - (class_name env name |> single_esc)) + (struct_name env name |> single_esc)) ] let read_cases1 env loc name cases1 = @@ -942,11 +935,11 @@ let read_cases1 env loc name cases1 = [ Inline ifs; Line (sprintf "throw _atd_bad_json(\"%s\", x);" - (class_name env name |> single_esc)) + (struct_name env name |> single_esc)) ] let sum_container env loc name cases = - let py_class_name = class_name env name in + let dlang_struct_name = struct_name env name in let type_list = List.map (fun (loc, orig_name, unique_name, an, opt_e) -> trans env unique_name @@ -982,24 +975,24 @@ let sum_container env loc name cases = [] in [ - Line (sprintf "alias %s = SumType!(%s);" py_class_name type_list); + Line (sprintf "alias %s = SumType!(%s);" dlang_struct_name type_list); Line ""; Line (sprintf "%s fromJson(%s)(JSONValue x) {" - (single_esc py_class_name) (single_esc py_class_name)); + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ Inline cases0_block; Inline cases1_block; Line (sprintf "throw _atd_bad_json(\"%s\", x);" - (single_esc (class_name env name))) + (single_esc (struct_name env name))) ]; Line "}"; Line ""; - Line (sprintf "JSONValue toJson(%s x) {" (py_class_name)); + Line (sprintf "JSONValue toJson(%s x) {" (dlang_struct_name)); Block [ Line "return x.match!("; Line ( List.map (fun (loc, orig_name, unique_name, an, opt_e) -> - sprintf "(%s v) => v.toJsonString" (trans env unique_name) + sprintf "(%s v) => v.toJson" (trans env unique_name) ) cases |> String.concat ",\n"); Line ");" @@ -1013,7 +1006,7 @@ let sum env loc name cases = List.map (fun (x : variant) -> match x with | Variant (loc, (orig_name, an), opt_e) -> - let unique_name = create_class_name env orig_name in + let unique_name = create_struct_name env orig_name in (loc, orig_name, unique_name, an, opt_e) | Inherit _ -> assert false ) cases @@ -1062,21 +1055,21 @@ let definition_group ~atd_filename env (* Make sure that the types as defined in the atd file get a good name. - For example, type 'foo' should become class 'Foo'. + For example, type 'foo' should become struct 'Foo'. We do this because each case constructor of sum types will also - translate to a class in the same namespace. For example, + 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_class_names env (items: A.module_body) = +let reserve_good_struct_names env (items: A.module_body) = List.iter - (fun (Type (loc, (name, param, an), e)) -> ignore (class_name env name)) + (fun (Type (loc, (name, param, an), e)) -> ignore (struct_name env name)) items let to_file ~atd_filename ~head (items : A.module_body) dst_path = let env = init_env () in - reserve_good_class_names env items; + reserve_good_struct_names env items; let head = List.map (fun s -> Line s) head in let dlang_defs = Atd.Util.tsort items diff --git a/atdd/src/lib/Dlang_annot.ml b/atdd/src/lib/Dlang_annot.ml index 808c5b90..759c69fa 100644 --- a/atdd/src/lib/Dlang_annot.ml +++ b/atdd/src/lib/Dlang_annot.ml @@ -27,33 +27,6 @@ let get_dlang_assoc_repr an : assoc_repr = ~field:"repr" an -(* class decorator - - Later, we might want to add support for decorators on the from_json - and to_json methods. - - These would have more specific names such as "to_json_decorator" - and "from_json_decorator" or just "to_json" and "from_json". - If we want to be consistent with atdgen adapters, we might - want something like this: - - - - (which is less flexible than method decorators since method decorators - are left in charge of calling the origin method but the adapters - are simple functions from json to json) -*) -let get_dlang_decorators an : string list = - Atd.Annot.get_fields - ~parse:(fun s -> Some s) - ~sections:["dlang"] - ~field:"decorator" - an - (* imports etc. *) let get_dlang_text an : string list = Atd.Annot.get_fields @@ -67,5 +40,5 @@ let get_dlang_json_text an : string list = @ Atd.Annot.get_fields ~parse:(fun s -> Some s) ~sections:["dlang"] - ~field:"json_py.text" + ~field:"json_dlang.text" an diff --git a/atdd/src/lib/Dlang_annot.mli b/atdd/src/lib/Dlang_annot.mli index 4ddf5a81..4b3a5e4b 100644 --- a/atdd/src/lib/Dlang_annot.mli +++ b/atdd/src/lib/Dlang_annot.mli @@ -26,11 +26,6 @@ type assoc_repr = *) val get_dlang_assoc_repr : Atd.Annot.t -> assoc_repr -(** Returns the list of class decorators as specified by the user without - [@] e.g. [] - gives [["foo"; "bar(baz)"]]. *) -val get_dlang_decorators : Atd.Annot.t -> string list - (** Returns text the user wants to be inserted at the beginning of the Dlang file such as imports. *) val get_dlang_json_text : Atd.Annot.t -> string list diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index acdf579c..665a7bba 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -30,8 +30,8 @@ type root = { kinds: kind list; assoc1: (float * int) list; assoc2: (string * int) list ; - assoc3: (float * int) list; - assoc4: (string * int) list ; + assoc3: (float * int) list ; + assoc4: (string * int) list ; nullables: int nullable list; options: int option list; untyped_things: abstract list; From bc2190be60603dfacd6f5ebec2ba6b5abb297769 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Mon, 12 Jun 2023 08:39:49 +0000 Subject: [PATCH 55/91] atdd: regenerate expected test result --- atdd/test/dlang-expected/everything.d | 100 +++++++++++++------------- 1 file changed, 50 insertions(+), 50 deletions(-) diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index a84d5da5..d1993376 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -27,7 +27,7 @@ } } - void _atd_missing_json_field(string typeName, string jsonFieldName) + T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); } @@ -300,9 +300,9 @@ struct RecursiveClass { RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { RecursiveClass obj; - obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field("RecursiveClass", "id"); - obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field("RecursiveClass", "flag"); - obj.children = ("children" in x) ? _atd_read_list(RecursiveClass.fromJson)(x["children"]) : _atd_missing_json_field("RecursiveClass", "children"); + obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field!(typeof(obj.id))("RecursiveClass", "id"); + obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field!(typeof(obj.flag))("RecursiveClass", "flag"); + obj.children = ("children" in x) ? _atd_read_list(RecursiveClass.fromJson)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); return obj; } JSONValue toJson(RecursiveClass obj) { @@ -319,8 +319,8 @@ struct Root_ {} JSONValue toJson(Root_ e) { return JSONValue("Root"); } -JSONValue toJsonString(Root_ e) { - return JSONValue("Root"); +string toJsonString(Root_ e) { + return JSONValue("Root").toString; } @@ -329,8 +329,8 @@ struct Thing { int value; } JSONValue toJson(Thing e) { return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); } -JSONValue toJsonString(Thing e) { - return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); +string toJsonString(Thing e) { + return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]).toString; } @@ -339,8 +339,8 @@ struct WOW {} JSONValue toJson(WOW e) { return JSONValue("wow"); } -JSONValue toJsonString(WOW e) { - return JSONValue("wow"); +string toJsonString(WOW e) { + return JSONValue("wow").toString; } @@ -349,8 +349,8 @@ struct Amaze { string[] value; } JSONValue toJson(Amaze e) { return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); } -JSONValue toJsonString(Amaze e) { - return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); +string toJsonString(Amaze e) { + return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]).toString; } @@ -377,10 +377,10 @@ Kind fromJson(Kind)(JSONValue x) { JSONValue toJson(Kind x) { return x.match!( - (Root_ v) => v.toJsonString, -(Thing v) => v.toJsonString, -(WOW v) => v.toJsonString, -(Amaze v) => v.toJsonString + (Root_ v) => v.toJson, +(Thing v) => v.toJson, +(WOW v) => v.toJson, +(Amaze v) => v.toJson ); } @@ -389,8 +389,8 @@ alias Alias = int[]; JSONValue toJson(Alias e) { return _atd_write_list((&_atd_write_int).toDelegate)(e); } -JSONValue toJsonString(Alias e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e); +string toJsonString(Alias e) { + return _atd_write_list((&_atd_write_int).toDelegate)(e).toString; } Alias fromJson(Alias)(JSONValue e) { return _atd_read_list((&_atd_read_int).toDelegate)(e); @@ -401,8 +401,8 @@ alias KindParametrizedTuple = Tuple!(Kind, Kind, int); JSONValue toJson(KindParametrizedTuple e) { return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e); } -JSONValue toJsonString(KindParametrizedTuple e) { - return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e); +string toJsonString(KindParametrizedTuple e) { + return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e).toString; } KindParametrizedTuple fromJson(KindParametrizedTuple)(JSONValue e) { return (JSONValue x) => tuple(Kind.fromJson(x[0]), Kind.fromJson(x[1]), _atd_read_int(x[2]))(e); @@ -416,7 +416,7 @@ struct IntFloatParametrizedRecord { IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { IntFloatParametrizedRecord obj; - obj.field_a = ("field_a" in x) ? _atd_read_int(x["field_a"]) : _atd_missing_json_field("IntFloatParametrizedRecord", "field_a"); + obj.field_a = ("field_a" in x) ? _atd_read_int(x["field_a"]) : _atd_missing_json_field!(typeof(obj.field_a))("IntFloatParametrizedRecord", "field_a"); obj.field_b = ("field_b" in x) ? _atd_read_list((&_atd_read_float).toDelegate)(x["field_b"]) : []; return obj; } @@ -441,8 +441,8 @@ struct Root { Kind[] kinds; Tuple!(float, int)[] assoc1; Tuple!(string, int)[] assoc2; - Tuple!(float, int)[] assoc3; - Tuple!(string, int)[] assoc4; + int[float] assoc3; + int[string] assoc4; Nullable!int[] nullables; Nullable!int[] options; JSONValue[] untyped_things; @@ -452,25 +452,25 @@ struct Root { Root fromJson(T : Root)(JSONValue x) { Root obj; - obj.id = ("ID" in x) ? _atd_read_string(x["ID"]) : _atd_missing_json_field("Root", "ID"); - obj.await = ("await" in x) ? _atd_read_bool(x["await"]) : _atd_missing_json_field("Root", "await"); - obj.x___init__ = ("__init__" in x) ? _atd_read_float(x["__init__"]) : _atd_missing_json_field("Root", "__init__"); - obj.items = ("items" in x) ? _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate))(x["items"]) : _atd_missing_json_field("Root", "items"); + obj.id = ("ID" in x) ? _atd_read_string(x["ID"]) : _atd_missing_json_field!(typeof(obj.id))("Root", "ID"); + obj.await = ("await" in x) ? _atd_read_bool(x["await"]) : _atd_missing_json_field!(typeof(obj.await))("Root", "await"); + obj.x___init__ = ("__init__" in x) ? _atd_read_float(x["__init__"]) : _atd_missing_json_field!(typeof(obj.x___init__))("Root", "__init__"); + obj.items = ("items" in x) ? _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate))(x["items"]) : _atd_missing_json_field!(typeof(obj.items))("Root", "items"); obj.maybe = ("maybe" in x) ? _atd_read_int(x["maybe"]) : null; obj.extras = ("extras" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["extras"]) : []; obj.answer = ("answer" in x) ? _atd_read_int(x["answer"]) : 42; - obj.aliased = ("aliased" in x) ? Alias.fromJson(x["aliased"]) : _atd_missing_json_field("Root", "aliased"); - obj.point = ("point" in x) ? (JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_float(x[1]))(x["point"]) : _atd_missing_json_field("Root", "point"); - obj.kinds = ("kinds" in x) ? _atd_read_list(Kind.fromJson)(x["kinds"]) : _atd_missing_json_field("Root", "kinds"); - obj.assoc1 = ("assoc1" in x) ? _atd_read_list((JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_int(x[1])))(x["assoc1"]) : _atd_missing_json_field("Root", "assoc1"); - obj.assoc2 = ("assoc2" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc2"]) : _atd_missing_json_field("Root", "assoc2"); - obj.assoc3 = ("assoc3" in x) ? _atd_read_list((JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_int(x[1])))(x["assoc3"]) : _atd_missing_json_field("Root", "assoc3"); - obj.assoc4 = ("assoc4" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc4"]) : _atd_missing_json_field("Root", "assoc4"); - obj.nullables = ("nullables" in x) ? _atd_read_list(_atd_read_nullable((&_atd_read_int).toDelegate))(x["nullables"]) : _atd_missing_json_field("Root", "nullables"); - obj.options = ("options" in x) ? _atd_read_list(_atd_read_option((&_atd_read_int).toDelegate))(x["options"]) : _atd_missing_json_field("Root", "options"); - obj.untyped_things = ("untyped_things" in x) ? _atd_read_list(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field("Root", "untyped_things"); - obj.parametrized_record = ("parametrized_record" in x) ? IntFloatParametrizedRecord.fromJson(x["parametrized_record"]) : _atd_missing_json_field("Root", "parametrized_record"); - obj.parametrized_tuple = ("parametrized_tuple" in x) ? KindParametrizedTuple.fromJson(x["parametrized_tuple"]) : _atd_missing_json_field("Root", "parametrized_tuple"); + obj.aliased = ("aliased" in x) ? Alias.fromJson(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); + obj.point = ("point" in x) ? (JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_float(x[1]))(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); + obj.kinds = ("kinds" in x) ? _atd_read_list(Kind.fromJson)(x["kinds"]) : _atd_missing_json_field!(typeof(obj.kinds))("Root", "kinds"); + obj.assoc1 = ("assoc1" in x) ? _atd_read_list((JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_int(x[1])))(x["assoc1"]) : _atd_missing_json_field!(typeof(obj.assoc1))("Root", "assoc1"); + obj.assoc2 = ("assoc2" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc2"]) : _atd_missing_json_field!(typeof(obj.assoc2))("Root", "assoc2"); + obj.assoc3 = ("assoc3" in x) ? _atd_read_array_to_assoc_dict((&_atd_read_float).toDelegate, _atd_read_int)(x["assoc3"]) : _atd_missing_json_field!(typeof(obj.assoc3))("Root", "assoc3"); + obj.assoc4 = ("assoc4" in x) ? _atd_read_object_to_assoc_array((&_atd_read_int).toDelegate)(x["assoc4"]) : _atd_missing_json_field!(typeof(obj.assoc4))("Root", "assoc4"); + obj.nullables = ("nullables" in x) ? _atd_read_list(_atd_read_nullable((&_atd_read_int).toDelegate))(x["nullables"]) : _atd_missing_json_field!(typeof(obj.nullables))("Root", "nullables"); + obj.options = ("options" in x) ? _atd_read_list(_atd_read_option((&_atd_read_int).toDelegate))(x["options"]) : _atd_missing_json_field!(typeof(obj.options))("Root", "options"); + obj.untyped_things = ("untyped_things" in x) ? _atd_read_list(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field!(typeof(obj.untyped_things))("Root", "untyped_things"); + obj.parametrized_record = ("parametrized_record" in x) ? IntFloatParametrizedRecord.fromJson(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); + obj.parametrized_tuple = ("parametrized_tuple" in x) ? KindParametrizedTuple.fromJson(x["parametrized_tuple"]) : _atd_missing_json_field!(typeof(obj.parametrized_tuple))("Root", "parametrized_tuple"); return obj; } JSONValue toJson(Root obj) { @@ -488,8 +488,8 @@ JSONValue toJson(Root obj) { res["kinds"] = _atd_write_list(((Kind x) => x.toJson()))(obj.kinds); res["assoc1"] = _atd_write_list(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc1); res["assoc2"] = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate)(obj.assoc2); - res["assoc3"] = _atd_write_list(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc3); - res["assoc4"] = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate)(obj.assoc4); + res["assoc3"] = _atd_write_assoc_dict_to_array((&_atd_write_float).toDelegate, (&_atd_write_int).toDelegate)(obj.assoc3); + res["assoc4"] = _atd_write_assoc_array_to_object((&_atd_write_int).toDelegate)(obj.assoc4); res["nullables"] = _atd_write_list(_atd_write_nullable((&_atd_write_int).toDelegate))(obj.nullables); res["options"] = _atd_write_list(_atd_write_option((&_atd_write_int).toDelegate))(obj.options); res["untyped_things"] = _atd_write_list((JSONValue x) => x)(obj.untyped_things); @@ -505,7 +505,7 @@ struct RequireField { RequireField fromJson(T : RequireField)(JSONValue x) { RequireField obj; - obj.req = ("req" in x) ? _atd_read_string(x["req"]) : _atd_missing_json_field("RequireField", "req"); + obj.req = ("req" in x) ? _atd_read_string(x["req"]) : _atd_missing_json_field!(typeof(obj.req))("RequireField", "req"); return obj; } JSONValue toJson(RequireField obj) { @@ -519,8 +519,8 @@ alias Pair = Tuple!(string, int); JSONValue toJson(Pair e) { return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); } -JSONValue toJsonString(Pair e) { - return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); +string toJsonString(Pair e) { + return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e).toString; } Pair fromJson(Pair)(JSONValue e) { return (JSONValue x) => tuple(_atd_read_string(x[0]), _atd_read_int(x[1]))(e); @@ -532,8 +532,8 @@ struct A {} JSONValue toJson(A e) { return JSONValue("A"); } -JSONValue toJsonString(A e) { - return JSONValue("A"); +string toJsonString(A e) { + return JSONValue("A").toString; } @@ -542,8 +542,8 @@ struct B { int value; } JSONValue toJson(B e) { return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); } -JSONValue toJsonString(B e) { - return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); +string toJsonString(B e) { + return JSONValue([JSONValue("B"), _atd_write_int(e.value)]).toString; } @@ -566,8 +566,8 @@ Frozen fromJson(Frozen)(JSONValue x) { JSONValue toJson(Frozen x) { return x.match!( - (A v) => v.toJsonString, -(B v) => v.toJsonString + (A v) => v.toJson, +(B v) => v.toJson ); } From 02a62090c72c2b592800b8aa536a25c9974117a7 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Sat, 10 Jun 2023 17:58:55 +0200 Subject: [PATCH 56/91] small fixes --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 54748b5a..23f9a08a 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -733,7 +733,7 @@ and tuple_reader env cells = ) cells |> String.concat ", " in - sprintf "(JSONValue x) => tuple(%s)" tuple_body + sprintf "((JSONValue x) => tuple(%s))" tuple_body let from_json_class_argument env trans_meth dlang_struct_name ((loc, (name, kind, an), e) : simple_field) = @@ -856,7 +856,7 @@ let alias_wrapper env name type_expr = Line (sprintf "string toJsonString(%s e) {" dlang_struct_name); Block [Line(sprintf "return %s(e).toString;" (json_writer env type_expr))]; Line("}"); - Line (sprintf "%s fromJson(%s)(JSONValue e) {" dlang_struct_name dlang_struct_name); + Line (sprintf "%s fromJson(T : %s)(JSONValue e) {" dlang_struct_name dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; Line("}"); ] From 422eac567048fe7a12dd573d1adc402a8604d717 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 12 Jun 2023 10:57:50 +0200 Subject: [PATCH 57/91] add missing parenthessis in _atd_write_assoc_dict --- atdd/src/lib/Codegen.ml | 2 +- atdd/src/lib/fixed_size_preamble.d | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 23f9a08a..19654f05 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -413,7 +413,7 @@ let fixed_size_preamble atd_filename = auto fun = (V[K] assocArr) { JSONValue[] ret; foreach (key, val; assocArr) - ret ~= JSONValue[writeKey(key), writeValue(val)]; + ret ~= JSONValue([writeKey(key), writeValue(val)]); return JSONValue(ret); }; return fun; diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 3eec2aca..d3acf1f4 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -232,7 +232,7 @@ auto _atd_write_assoc_dict_to_array(K, V)( auto fun = (V[K] assocArr) { JSONValue[] ret; foreach (key, val; assocArr) - ret ~= JSONValue[writeKey(key), writeValue(val)]; + ret ~= JSONValue([writeKey(key), writeValue(val)]); return JSONValue(ret); }; return fun; From 861eff26e8917b629705f82ca25893931674a654 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 12 Jun 2023 11:00:06 +0200 Subject: [PATCH 58/91] make atd functions private to module --- atdd/src/lib/Codegen.ml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 19654f05..dcfef5ca 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -197,6 +197,8 @@ let fixed_size_preamble atd_filename = import std.sumtype; import std.typecons : nullable, Nullable, tuple, Tuple; +private +{ class AtdException : Exception { this(string msg, string file = __FILE__, size_t line = __LINE__) @@ -452,7 +454,7 @@ let fixed_size_preamble atd_filename = }; return fun; } - +} // ############################################################################ // # Public classes // ############################################################################ From 14b66a159f6959673a8a042cc00ea6dc7b3f70f8 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 12 Jun 2023 11:24:20 +0200 Subject: [PATCH 59/91] fix tuple lambda: check for expect size --- atdd/src/lib/Codegen.ml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index dcfef5ca..7ba682b5 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -735,7 +735,11 @@ and tuple_reader env cells = ) cells |> String.concat ", " in - sprintf "((JSONValue x) => tuple(%s))" tuple_body + sprintf "((JSONValue x) { + if (x.type != JSONType.array || x.array.length != %d) + throw _atd_bad_json(\"Tuple of size %d\", x); + return tuple(%s); + })" (List.length cells) (List.length cells) tuple_body let from_json_class_argument env trans_meth dlang_struct_name ((loc, (name, kind, an), e) : simple_field) = From 8ac0ea7ca2b77371327d4095b983f57a6da6d5e1 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 12 Jun 2023 15:48:59 +0200 Subject: [PATCH 60/91] set of fixes to make everything test pass --- atdd/src/lib/Codegen.ml | 48 ++++++++++-------------------- atdd/src/lib/fixed_size_preamble.d | 4 +-- atdd/test/atd-input/everything.atd | 5 +++- 3 files changed, 22 insertions(+), 35 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 7ba682b5..9e78657a 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -313,7 +313,7 @@ private if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; - foreach (jsonInnerVal; jsonVal.object) + foreach (jsonInnerVal; jsonVal.array) { if (jsonInnerVal.type != JSONType.array) throw _atd_bad_json("list", jsonInnerVal); @@ -356,7 +356,7 @@ private if (e.type == JSONType.string && e.str == "None") return Nullable!T.init; else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e)); + return Nullable!T(readElm(e[1])); else throw _atd_bad_json("option", e); }; @@ -705,7 +705,7 @@ let rec json_reader ?(nested=false) env (e : type_expr) = (json_reader ~nested:true env e) | Array_dict (key, value) -> sprintf "_atd_read_array_to_assoc_dict(%s, %s)" - (json_reader ~nested:true env key) (json_reader env value) + (json_reader ~nested:true env key) (json_reader ~nested:true env value) | Object_dict value -> sprintf "_atd_read_object_to_assoc_array(%s)" (json_reader ~nested:true env value) @@ -724,7 +724,11 @@ let rec json_reader ?(nested=false) env (e : type_expr) = | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_read_%s%s" (if nested then "(&" else "") name (if nested then ").toDelegate" else "") | "abstract" -> "((JSONValue x) => x)" - | _ -> sprintf "%s.fromJson" (struct_name env name)) + | _ -> sprintf "%sfromJson!%s%s" + (if nested then "(&" else "") + (struct_name env name) + (if nested then ").toDelegate" else "") + ) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" @@ -745,18 +749,6 @@ let from_json_class_argument env trans_meth dlang_struct_name ((loc, (name, kind, an), e) : simple_field) = let dlang_name = inst_var_name trans_meth name in let json_name = Atd.Json.get_json_fname name an in - let unwrapped_type = - 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'" name) - in let else_body = match kind with | Required -> @@ -764,7 +756,7 @@ let from_json_class_argument dlang_name (single_esc dlang_struct_name) (single_esc json_name) - | Optional -> "null" + | Optional -> (sprintf "typeof(obj.%s).init" dlang_name) | With_default -> match get_dlang_default e an with | Some x -> x @@ -776,7 +768,7 @@ let from_json_class_argument sprintf "obj.%s = (\"%s\" in x) ? %s(x[\"%s\"]) : %s;" dlang_name (single_esc json_name) - (json_reader env unwrapped_type) + (json_reader env e) (single_esc json_name) else_body @@ -792,7 +784,7 @@ let inst_var_declaration | With_default -> match get_dlang_default unwrapped_e an with | None -> "" - | Some x -> sprintf " = %s" x + | Some x -> sprintf " = %s" x in [ Line (sprintf "%s %s%s;" type_name var_name default) @@ -859,9 +851,6 @@ let alias_wrapper env name type_expr = Line (sprintf "JSONValue toJson(%s e) {" dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; Line("}"); - Line (sprintf "string toJsonString(%s e) {" dlang_struct_name); - Block [Line(sprintf "return %s(e).toString;" (json_writer env type_expr))]; - Line("}"); Line (sprintf "%s fromJson(T : %s)(JSONValue e) {" dlang_struct_name dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; Line("}"); @@ -880,9 +869,6 @@ let case_class env type_name Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); - Line (sprintf "string toJsonString(%s e) {" (trans env unique_name)); - Block[ Line(sprintf "return JSONValue(\"%s\").toString;" (single_esc json_name))]; - Line("}"); ] | Some e -> [ @@ -893,9 +879,6 @@ let case_class env type_name Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); - Line (sprintf "string toJsonString(%s e) {" (trans env unique_name)); - Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]).toString;" (single_esc json_name) (json_writer env e))]; - Line("}"); ] @@ -907,7 +890,7 @@ let read_cases0 env loc name cases0 = Inline [ Line (sprintf "if (x.str == \"%s\") " (single_esc json_name)); Block [ - Line (sprintf "return (%s());" (trans env unique_name)) + Line (sprintf "return %s(%s());" (struct_name env name) (trans env unique_name)) ]; ] ) @@ -931,7 +914,8 @@ let read_cases1 env loc name cases1 = Inline [ Line (sprintf "if (cons == \"%s\")" (single_esc json_name)); Block [ - Line (sprintf "return (%s(%s(x[1])));" + Line (sprintf "return %s(%s(%s(x[1])));" + (struct_name env name) (trans env unique_name) (json_reader env e)) ] @@ -983,8 +967,8 @@ let sum_container env loc name cases = [ Line (sprintf "alias %s = SumType!(%s);" dlang_struct_name type_list); Line ""; - Line (sprintf "%s fromJson(%s)(JSONValue x) {" - (single_esc dlang_struct_name) (single_esc dlang_struct_name)); + Line (sprintf "%s fromJson(T : %s)(JSONValue x) {" + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ Inline cases0_block; Inline cases1_block; diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index d3acf1f4..8f0bc39b 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -130,7 +130,7 @@ auto _atd_read_array_to_assoc_dict(K, V)( if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; - foreach (jsonInnerVal; jsonVal.object) + foreach (jsonInnerVal; jsonVal.array) { if (jsonInnerVal.type != JSONType.array) throw _atd_bad_json("list", jsonInnerVal); @@ -173,7 +173,7 @@ auto _atd_read_option(T)(T delegate(JSONValue) readElm) if (e.type == JSONType.string && e.str == "None") return Nullable!T.init; else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e)); + return Nullable!T(readElm(e[1])); else throw _atd_bad_json("option", e); }; diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index 665a7bba..580b10d1 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -20,7 +20,10 @@ type 'a parametrized_tuple = ('a * 'a * int) type root = { id : string; await: bool; - __init__: float; + integer: int; + __init__ : float; + ~float_with_auto_default: float; + ~float_with_default : float; items: int list list; ?maybe: int option; ~extras: int list; From 9380638761dd504151fb26cc1b96136fb995c89f Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 12 Jun 2023 15:51:04 +0200 Subject: [PATCH 61/91] add tests --- atdd/test/dlang-tests/test_atdd.d | 180 ++++++++++++++++++++++++++++-- 1 file changed, 168 insertions(+), 12 deletions(-) diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index e9ff3f17..d468be02 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -1,33 +1,189 @@ import everything; -void testeasy() +import std.traits; +import std.stdio; +import std.conv; +import core.exception; + +void function()[string] tests; +bool testFailed = false; + +void setupTests() { - auto record = IntFloatParametrizedRecord(32, [5.4, 3.3]); + tests["simpleRecord"] = { + auto record = IntFloatParametrizedRecord(32, [5.4, 3.3]); + + auto json = record.toJson(); + auto recordFromJson = fromJson!IntFloatParametrizedRecord(json); + + assert(record == recordFromJson); + }; + + tests["simpleRecordMissingInt"] = { + auto json = "{\"field_b\":[5.40000009536743164,3.29999995231628418]}"; + try + { + fromJsonString!IntFloatParametrizedRecord(json); + assert(false); + } + catch (Exception e) + { + assert(true); + } + }; + + tests["validPair"] = { + + auto str = "[\"hello\",2]"; + auto p = str.fromJsonString!Pair; + assert(str == p.toJsonString); + }; + + tests["invalidPairWrongType"] = { + + assertThrows( + { "[2, 3]".fromJsonString!Pair; } + ); + }; + + tests["invalidPairTooLong"] = { + + assertThrows( + { "[\"hello\", 2, 3]".fromJsonString!Pair; } + ); + }; + + tests["everything"] = { + import std.typecons; + import std.json; + + auto obj = Root(); + + obj.x___init__ = 0.32f; + obj.items = [[], [1, 2]]; + obj.extras = [17, 53]; + obj.aliased = [1, 6, 8]; + obj.point = tuple(4.3, 1.2); + obj.assoc1 = [tuple(3.4f, 2), tuple(1.1f, 2)]; // Can be not ordered by key + obj.assoc2 = [tuple("d", 3), tuple("e", 7)]; // Must be ordered by key because we lose ordering when writing + obj.assoc3 = [4.4f: 4, 5.5f: 5]; + obj.assoc4 = ["g": 7, "h": 8]; + obj.kinds = [ + Kind(WOW()), Kind(Thing(99)), Kind(Amaze(["a", "b"])), Kind(Root_()) + ]; + obj.nullables = [ + 12.Nullable!int, Nullable!int.init, Nullable!int.init, + 42.Nullable!int + ]; + obj.options = [ + 56.Nullable!int, Nullable!int.init, 78.Nullable!int + ]; + obj.untyped_things = [ + JSONValue("hello"), + JSONValue(), + JSONValue(new int[string]), + JSONValue(123) + ]; + obj.parametrized_record = IntFloatParametrizedRecord(42, [9.9f, 8.8f]); + obj.parametrized_tuple = KindParametrizedTuple( + tuple(Kind(WOW()), Kind(WOW()), 100)); + + auto jsonStr = obj.toJsonString; + auto newObj = jsonStr.fromJsonString!Root; + + assert(obj == newObj); + }; + + tests["defaultListWithThings"] = { + auto obj = DefaultList(); + obj.items = [3, 4, 5, 6]; + + auto newObj = obj.toJsonString.fromJsonString!DefaultList; + + assert(obj == newObj); + }; + + tests["defaultWithoutThings"] = { + auto obj = DefaultList(); + + auto newObj = obj.toJsonString.fromJsonString!DefaultList; + + assert(obj == newObj); + }; - auto json = record.toJson(); - auto recordFromJson = fromJson!IntFloatParametrizedRecord(json); + tests["frozenDefaultA"] = { + auto obj = Frozen(); + auto newObj = obj.toJsonString.fromJsonString!Frozen; + assert(obj == newObj); + }; - assert(record == recordFromJson); + tests["frozenA"] = { + auto obj = Frozen(A()); + + auto newObj = obj.toJsonString.fromJsonString!Frozen; + assert(obj == newObj); + }; + + tests["frozenB"] = { + auto obj = Frozen(B(-43)); + + auto newObj = obj.toJsonString.fromJsonString!Frozen; + assert(obj == newObj); + }; + + tests["frozenBadJson"] = { + auto json = "[\"B\"]"; + + assertThrows({ json.fromJsonString!Frozen; }); + }; } -void recordMissingInt() +void assertThrows(T)(T fn, bool writeMsg = false) { - auto json = "{\"field_b\":[5.40000009536743164,3.29999995231628418]}"; - try { - fromJsonString!IntFloatParametrizedRecord(json); + fn(); assert(false); } catch (Exception e) { + if (writeMsg) + writeln(e.msg); assert(true); } } +int tryRunning(T)(T test, string name, bool verbose = false) +{ + try + { + test(); + if (verbose) + writefln("\t✅ Test %s", name); + + return 1; + } + catch (Exception e) + { + writefln("\t❌ Test %s with: %s", name, e); + return 0; + } +} + int main() { - testeasy(); - recordMissingInt(); + setupTests(); + writeln("Running tests..."); + + int c = 0; + foreach (key, value; tests) + c += tryRunning(value, key, true); + + if (c != tests.length) + { + writefln("Failure, [%s/%s] tests passed", c, tests.length); + return 1; + } + writefln("Tests successfully passed [%s/%s]", c, c); return 0; -} \ No newline at end of file +} From c51ccef0d5c45dc53583da0e30673c89acdf872c Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 12 Jun 2023 16:31:31 +0200 Subject: [PATCH 62/91] write options properly (so that they can be read into option type) and add more tests --- atdd/src/lib/Codegen.ml | 6 +++--- atdd/test/dlang-tests/test_atdd.d | 34 ++++++++++++++++++++++++++++++- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 9e78657a..3d3907eb 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -679,11 +679,11 @@ let construct_json_field env trans_meth [ Line (sprintf "if (!obj.%s.isNull)" (inst_var_name trans_meth name)); - Block [ Line(sprintf "res[\"%s\"] = %s(obj.%s.get);" + Block [ Line(sprintf "res[\"%s\"] = %s(%s)(obj.%s);" (Atd.Json.get_json_fname name an |> single_esc) - writer_function + "_atd_write_option" + (json_writer ~nested:true env unwrapped_type) (inst_var_name trans_meth name))]; - ] (* diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index d468be02..956f47c9 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -63,6 +63,7 @@ void setupTests() obj.items = [[], [1, 2]]; obj.extras = [17, 53]; obj.aliased = [1, 6, 8]; + obj.maybe = 43; obj.point = tuple(4.3, 1.2); obj.assoc1 = [tuple(3.4f, 2), tuple(1.1f, 2)]; // Can be not ordered by key obj.assoc2 = [tuple("d", 3), tuple("e", 7)]; // Must be ordered by key because we lose ordering when writing @@ -107,10 +108,15 @@ void setupTests() auto obj = DefaultList(); auto newObj = obj.toJsonString.fromJsonString!DefaultList; - assert(obj == newObj); }; + tests["defaultListInit"] = { + auto json = "{}"; + + json.fromJsonString!DefaultList; + }; + tests["frozenDefaultA"] = { auto obj = Frozen(); auto newObj = obj.toJsonString.fromJsonString!Frozen; @@ -136,6 +142,32 @@ void setupTests() assertThrows({ json.fromJsonString!Frozen; }); }; + + tests["requireFieldLoop"] = { + auto obj = RequireField("test"); + + assert(obj == obj.toJsonString.fromJsonString!RequireField); + }; + + tests["requireFieldFails"] = { + auto json = "{}"; + + assertThrows({json.fromJsonString!RequireField;}); + }; + + tests["requireFieldNotFail"] = { + auto json = "{ \"req\" : \"hello\"}"; + + json.fromJsonString!RequireField; + }; + + tests["recursiveClass"] = { + auto child1 = RecursiveClass(1, true, []); + auto child2 = RecursiveClass(2, true, []); + auto a_obj = RecursiveClass(0, false, [child1, child2]); + + assert (a_obj == a_obj.toJsonString.fromJsonString!RecursiveClass); + }; } void assertThrows(T)(T fn, bool writeMsg = false) From 7de97b78aa08019f3cea7a0ac5b45339bb8f35f4 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 1 Sep 2023 18:30:20 +0100 Subject: [PATCH 63/91] test: make make test run atd tests --- atdd/test/atd-input/ALLCAPS.atd | 0 atdd/test/dlang-expected/everything.d | 108 +++++++++++++------------- atdd/test/dlang-tests/dune | 6 +- 3 files changed, 56 insertions(+), 58 deletions(-) delete mode 100644 atdd/test/atd-input/ALLCAPS.atd diff --git a/atdd/test/atd-input/ALLCAPS.atd b/atdd/test/atd-input/ALLCAPS.atd deleted file mode 100644 index e69de29b..00000000 diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index d1993376..08d5bfe7 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -19,6 +19,8 @@ import std.sumtype; import std.typecons : nullable, Nullable, tuple, Tuple; +private +{ class AtdException : Exception { this(string msg, string file = __FILE__, size_t line = __LINE__) @@ -133,7 +135,7 @@ if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; - foreach (jsonInnerVal; jsonVal.object) + foreach (jsonInnerVal; jsonVal.array) { if (jsonInnerVal.type != JSONType.array) throw _atd_bad_json("list", jsonInnerVal); @@ -176,7 +178,7 @@ if (e.type == JSONType.string && e.str == "None") return Nullable!T.init; else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e)); + return Nullable!T(readElm(e[1])); else throw _atd_bad_json("option", e); }; @@ -235,7 +237,7 @@ auto fun = (V[K] assocArr) { JSONValue[] ret; foreach (key, val; assocArr) - ret ~= JSONValue[writeKey(key), writeValue(val)]; + ret ~= JSONValue([writeKey(key), writeValue(val)]); return JSONValue(ret); }; return fun; @@ -274,7 +276,7 @@ }; return fun; } - +} // ############################################################################ // # Public classes // ############################################################################ @@ -302,7 +304,7 @@ RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { RecursiveClass obj; obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field!(typeof(obj.id))("RecursiveClass", "id"); obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field!(typeof(obj.flag))("RecursiveClass", "flag"); - obj.children = ("children" in x) ? _atd_read_list(RecursiveClass.fromJson)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); + obj.children = ("children" in x) ? _atd_read_list((&fromJson!RecursiveClass).toDelegate)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); return obj; } JSONValue toJson(RecursiveClass obj) { @@ -319,9 +321,6 @@ struct Root_ {} JSONValue toJson(Root_ e) { return JSONValue("Root"); } -string toJsonString(Root_ e) { - return JSONValue("Root").toString; -} // Original type: kind = [ ... | Thing of ... | ... ] @@ -329,9 +328,6 @@ struct Thing { int value; } JSONValue toJson(Thing e) { return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); } -string toJsonString(Thing e) { - return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]).toString; -} // Original type: kind = [ ... | WOW | ... ] @@ -339,9 +335,6 @@ struct WOW {} JSONValue toJson(WOW e) { return JSONValue("wow"); } -string toJsonString(WOW e) { - return JSONValue("wow").toString; -} // Original type: kind = [ ... | Amaze of ... | ... ] @@ -349,27 +342,24 @@ struct Amaze { string[] value; } JSONValue toJson(Amaze e) { return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); } -string toJsonString(Amaze e) { - return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]).toString; -} alias Kind = SumType!(Root_, Thing, WOW, Amaze); -Kind fromJson(Kind)(JSONValue x) { +Kind fromJson(T : Kind)(JSONValue x) { if (x.type == JSONType.string) { if (x.str == "Root") - return (Root_()); + return Kind(Root_()); if (x.str == "wow") - return (WOW()); + return Kind(WOW()); throw _atd_bad_json("Kind", x); } if (x.type == JSONType.array && x.array.length == 2 && x[0].type == JSONType.string) { string cons = x[0].str; if (cons == "Thing") - return (Thing(_atd_read_int(x[1]))); + return Kind(Thing(_atd_read_int(x[1]))); if (cons == "!!!") - return (Amaze(_atd_read_list((&_atd_read_string).toDelegate)(x[1]))); + return Kind(Amaze(_atd_read_list((&_atd_read_string).toDelegate)(x[1]))); throw _atd_bad_json("Kind", x); } throw _atd_bad_json("Kind", x); @@ -389,10 +379,7 @@ alias Alias = int[]; JSONValue toJson(Alias e) { return _atd_write_list((&_atd_write_int).toDelegate)(e); } -string toJsonString(Alias e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e).toString; -} -Alias fromJson(Alias)(JSONValue e) { +Alias fromJson(T : Alias)(JSONValue e) { return _atd_read_list((&_atd_read_int).toDelegate)(e); } @@ -401,11 +388,12 @@ alias KindParametrizedTuple = Tuple!(Kind, Kind, int); JSONValue toJson(KindParametrizedTuple e) { return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e); } -string toJsonString(KindParametrizedTuple e) { - return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e).toString; -} -KindParametrizedTuple fromJson(KindParametrizedTuple)(JSONValue e) { - return (JSONValue x) => tuple(Kind.fromJson(x[0]), Kind.fromJson(x[1]), _atd_read_int(x[2]))(e); +KindParametrizedTuple fromJson(T : KindParametrizedTuple)(JSONValue e) { + return ((JSONValue x) { + if (x.type != JSONType.array || x.array.length != 3) + throw _atd_bad_json("Tuple of size 3", x); + return tuple(fromJson!Kind(x[0]), fromJson!Kind(x[1]), _atd_read_int(x[2])); + })(e); } @@ -431,7 +419,10 @@ JSONValue toJson(IntFloatParametrizedRecord obj) { struct Root { string id; bool await; + int integer; float x___init__; + float float_with_auto_default = 0.0; + float float_with_default = 0.1; int[][] items; Nullable!int maybe; int[] extras = []; @@ -454,33 +445,47 @@ Root fromJson(T : Root)(JSONValue x) { Root obj; obj.id = ("ID" in x) ? _atd_read_string(x["ID"]) : _atd_missing_json_field!(typeof(obj.id))("Root", "ID"); obj.await = ("await" in x) ? _atd_read_bool(x["await"]) : _atd_missing_json_field!(typeof(obj.await))("Root", "await"); + obj.integer = ("integer" in x) ? _atd_read_int(x["integer"]) : _atd_missing_json_field!(typeof(obj.integer))("Root", "integer"); obj.x___init__ = ("__init__" in x) ? _atd_read_float(x["__init__"]) : _atd_missing_json_field!(typeof(obj.x___init__))("Root", "__init__"); + obj.float_with_auto_default = ("float_with_auto_default" in x) ? _atd_read_float(x["float_with_auto_default"]) : 0.0; + obj.float_with_default = ("float_with_default" in x) ? _atd_read_float(x["float_with_default"]) : 0.1; obj.items = ("items" in x) ? _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate))(x["items"]) : _atd_missing_json_field!(typeof(obj.items))("Root", "items"); - obj.maybe = ("maybe" in x) ? _atd_read_int(x["maybe"]) : null; + obj.maybe = ("maybe" in x) ? _atd_read_option((&_atd_read_int).toDelegate)(x["maybe"]) : typeof(obj.maybe).init; obj.extras = ("extras" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["extras"]) : []; obj.answer = ("answer" in x) ? _atd_read_int(x["answer"]) : 42; - obj.aliased = ("aliased" in x) ? Alias.fromJson(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); - obj.point = ("point" in x) ? (JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_float(x[1]))(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); - obj.kinds = ("kinds" in x) ? _atd_read_list(Kind.fromJson)(x["kinds"]) : _atd_missing_json_field!(typeof(obj.kinds))("Root", "kinds"); - obj.assoc1 = ("assoc1" in x) ? _atd_read_list((JSONValue x) => tuple(_atd_read_float(x[0]), _atd_read_int(x[1])))(x["assoc1"]) : _atd_missing_json_field!(typeof(obj.assoc1))("Root", "assoc1"); + obj.aliased = ("aliased" in x) ? fromJson!Alias(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); + obj.point = ("point" in x) ? ((JSONValue x) { + if (x.type != JSONType.array || x.array.length != 2) + throw _atd_bad_json("Tuple of size 2", x); + return tuple(_atd_read_float(x[0]), _atd_read_float(x[1])); + })(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); + obj.kinds = ("kinds" in x) ? _atd_read_list((&fromJson!Kind).toDelegate)(x["kinds"]) : _atd_missing_json_field!(typeof(obj.kinds))("Root", "kinds"); + obj.assoc1 = ("assoc1" in x) ? _atd_read_list(((JSONValue x) { + if (x.type != JSONType.array || x.array.length != 2) + throw _atd_bad_json("Tuple of size 2", x); + return tuple(_atd_read_float(x[0]), _atd_read_int(x[1])); + }))(x["assoc1"]) : _atd_missing_json_field!(typeof(obj.assoc1))("Root", "assoc1"); obj.assoc2 = ("assoc2" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc2"]) : _atd_missing_json_field!(typeof(obj.assoc2))("Root", "assoc2"); - obj.assoc3 = ("assoc3" in x) ? _atd_read_array_to_assoc_dict((&_atd_read_float).toDelegate, _atd_read_int)(x["assoc3"]) : _atd_missing_json_field!(typeof(obj.assoc3))("Root", "assoc3"); + obj.assoc3 = ("assoc3" in x) ? _atd_read_array_to_assoc_dict((&_atd_read_float).toDelegate, (&_atd_read_int).toDelegate)(x["assoc3"]) : _atd_missing_json_field!(typeof(obj.assoc3))("Root", "assoc3"); obj.assoc4 = ("assoc4" in x) ? _atd_read_object_to_assoc_array((&_atd_read_int).toDelegate)(x["assoc4"]) : _atd_missing_json_field!(typeof(obj.assoc4))("Root", "assoc4"); obj.nullables = ("nullables" in x) ? _atd_read_list(_atd_read_nullable((&_atd_read_int).toDelegate))(x["nullables"]) : _atd_missing_json_field!(typeof(obj.nullables))("Root", "nullables"); obj.options = ("options" in x) ? _atd_read_list(_atd_read_option((&_atd_read_int).toDelegate))(x["options"]) : _atd_missing_json_field!(typeof(obj.options))("Root", "options"); obj.untyped_things = ("untyped_things" in x) ? _atd_read_list(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field!(typeof(obj.untyped_things))("Root", "untyped_things"); - obj.parametrized_record = ("parametrized_record" in x) ? IntFloatParametrizedRecord.fromJson(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); - obj.parametrized_tuple = ("parametrized_tuple" in x) ? KindParametrizedTuple.fromJson(x["parametrized_tuple"]) : _atd_missing_json_field!(typeof(obj.parametrized_tuple))("Root", "parametrized_tuple"); + obj.parametrized_record = ("parametrized_record" in x) ? fromJson!IntFloatParametrizedRecord(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); + obj.parametrized_tuple = ("parametrized_tuple" in x) ? fromJson!KindParametrizedTuple(x["parametrized_tuple"]) : _atd_missing_json_field!(typeof(obj.parametrized_tuple))("Root", "parametrized_tuple"); return obj; } JSONValue toJson(Root obj) { JSONValue res; res["ID"] = _atd_write_string(obj.id); res["await"] = _atd_write_bool(obj.await); + res["integer"] = _atd_write_int(obj.integer); res["__init__"] = _atd_write_float(obj.x___init__); + res["float_with_auto_default"] = _atd_write_float(obj.float_with_auto_default); + res["float_with_default"] = _atd_write_float(obj.float_with_default); res["items"] = _atd_write_list(_atd_write_list((&_atd_write_int).toDelegate))(obj.items); if (!obj.maybe.isNull) - res["maybe"] = _atd_write_int(obj.maybe.get); + res["maybe"] = _atd_write_option((&_atd_write_int).toDelegate)(obj.maybe); res["extras"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.extras); res["answer"] = _atd_write_int(obj.answer); res["aliased"] = ((Alias x) => x.toJson())(obj.aliased); @@ -519,11 +524,12 @@ alias Pair = Tuple!(string, int); JSONValue toJson(Pair e) { return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); } -string toJsonString(Pair e) { - return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e).toString; -} -Pair fromJson(Pair)(JSONValue e) { - return (JSONValue x) => tuple(_atd_read_string(x[0]), _atd_read_int(x[1]))(e); +Pair fromJson(T : Pair)(JSONValue e) { + return ((JSONValue x) { + if (x.type != JSONType.array || x.array.length != 2) + throw _atd_bad_json("Tuple of size 2", x); + return tuple(_atd_read_string(x[0]), _atd_read_int(x[1])); + })(e); } @@ -532,9 +538,6 @@ struct A {} JSONValue toJson(A e) { return JSONValue("A"); } -string toJsonString(A e) { - return JSONValue("A").toString; -} // Original type: frozen = [ ... | B of ... | ... ] @@ -542,23 +545,20 @@ struct B { int value; } JSONValue toJson(B e) { return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); } -string toJsonString(B e) { - return JSONValue([JSONValue("B"), _atd_write_int(e.value)]).toString; -} alias Frozen = SumType!(A, B); -Frozen fromJson(Frozen)(JSONValue x) { +Frozen fromJson(T : Frozen)(JSONValue x) { if (x.type == JSONType.string) { if (x.str == "A") - return (A()); + return Frozen(A()); throw _atd_bad_json("Frozen", x); } if (x.type == JSONType.array && x.array.length == 2 && x[0].type == JSONType.string) { string cons = x[0].str; if (cons == "B") - return (B(_atd_read_int(x[1]))); + return Frozen(B(_atd_read_int(x[1]))); throw _atd_bad_json("Frozen", x); } throw _atd_bad_json("Frozen", x); diff --git a/atdd/test/dlang-tests/dune b/atdd/test/dlang-tests/dune index 38682c38..ad755f9a 100644 --- a/atdd/test/dlang-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -3,11 +3,9 @@ ; (rule (targets - allcaps.d everything.d ) (deps - ../atd-input/ALLCAPS.atd ../atd-input/everything.atd ) (action @@ -22,7 +20,7 @@ (deps (glob_files *.d)) (action - ; TODO: ensure that compile check is run on unit tests, too (progn - (run ldc2 %{deps}) + (run ldc2 %{deps} --of test) + (bash ./test) ))) From c7db33f2f846b168f01d1dda4d16a29c43629fca Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Sun, 3 Sep 2023 11:15:36 +0100 Subject: [PATCH 64/91] atdd: support aliasing the same type multiple times, and provide helper function for unwrapping alias --- atdd/src/lib/Codegen.ml | 22 +- atdd/src/lib/fixed_size_preamble.d | 549 +++++++++++++---------------- atdd/test/atd-input/everything.atd | 1 + 3 files changed, 255 insertions(+), 317 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 3d3907eb..3a2df0aa 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -195,7 +195,7 @@ let fixed_size_preamble atd_filename = import std.functional; import std.json; import std.sumtype; - import std.typecons : nullable, Nullable, tuple, Tuple; + import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private { @@ -469,7 +469,19 @@ private { JSONValue res = obj.toJson; return res.toString; - }|} + } + + TypedefType!T unwrapAlias(T)(T e) @safe + { + return cast(TypedefType!T) e; + } + + T wrapAlias(T)(TypedefType!T e) @safe + { + return cast(T) e; + } + + |} atd_filename atd_filename (Filename.remove_extension atd_filename) @@ -847,12 +859,12 @@ let alias_wrapper env name type_expr = let dlang_struct_name = struct_name env name in let value_type = type_name_of_expr env type_expr in [ - Line (sprintf "alias %s = %s;" dlang_struct_name value_type); + Line (sprintf "alias %s = Typedef!(%s, (%s).init, \"%s\");" dlang_struct_name value_type value_type dlang_struct_name); Line (sprintf "JSONValue toJson(%s e) {" dlang_struct_name); - Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; + Block [Line(sprintf "return %s(e.unwrapAlias);" (json_writer env type_expr))]; Line("}"); Line (sprintf "%s fromJson(T : %s)(JSONValue e) {" dlang_struct_name dlang_struct_name); - Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; + Block [Line(sprintf "return %s(e).wrapAlias!%s;" (json_reader env type_expr) dlang_struct_name)]; Line("}"); ] diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d index 8f0bc39b..d97a9183 100644 --- a/atdd/src/lib/fixed_size_preamble.d +++ b/atdd/src/lib/fixed_size_preamble.d @@ -1,5 +1,4 @@ // Generated by atdd from type definitions in %s. - // This implements classes for the types defined in '%s', providing // methods and functions to convert data from/to JSON. @@ -7,6 +6,8 @@ // # Private functions // ############################################################################ +module fixed; + import std.algorithm : map; import std.array : array; import std.conv; @@ -14,364 +15,288 @@ import std.format; import std.functional; import std.json; import std.sumtype; -import std.typecons : nullable, Nullable, tuple, Tuple; +import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; -class AtdException : Exception +private { - this(string msg, string file = __FILE__, size_t line = __LINE__) + class AtdException : Exception { - super(msg, file, line); + this(string msg, string file = __FILE__, size_t line = __LINE__) + { + super(msg, file, line); + } } -} -T _atd_missing_json_field(T)(string typeName, string jsonFieldName) -{ - throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); -} - -// TODO check later if template is right way to go -AtdException _atd_bad_json(T)(string expectedType, T jsonValue) -{ - string valueStr = jsonValue.to!string; - if (valueStr.length > 200) + T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { - valueStr = valueStr[0 .. 200]; + throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); } - return new AtdException( - "incompatible JSON value where type '%s' was expected: %s".format( - expectedType, valueStr - )); -} - -AtdException _atd_bad_d(T)(string expectedType, T jsonValue) -{ - string valueStr = jsonValue.to!string; - if (valueStr.length > 200) + // TODO check later if template is right way to go + AtdException _atd_bad_json(T)(string expectedType, T jsonValue) { - valueStr = valueStr[0 .. 200]; - } - - return new AtdException( - "incompatible D value where type '%s' was expected: %s".format( - expectedType, valueStr - )); -} - -typeof(null) _atd_read_unit(JSONValue x) -{ - if (x.isNull) - return null; - else - throw _atd_bad_json("unit", x); -} - -bool _atd_read_bool(JSONValue x) -{ - try - return x.boolean; - catch (JSONException e) - throw _atd_bad_json("bool", x); -} - -int _atd_read_int(JSONValue x) -{ - try - return cast(int) x.integer; - catch (JSONException e) - throw _atd_bad_json("int", x); -} - -float _atd_read_float(JSONValue x) -{ - try - return x.floating; - catch (JSONException e) - throw _atd_bad_json("float", x); -} - -string _atd_read_string(JSONValue x) -{ - try - return x.str; - catch (JSONException e) - throw _atd_bad_json("string", x); -} - -auto _atd_read_list(T)(T delegate(JSONValue) readElements) -{ - return (JSONValue jsonVal) { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("array", jsonVal); - auto list = jsonVal.array; - return array(list.map!readElements()); - }; -} + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) + { + valueStr = valueStr[0 .. 200]; + } -auto _atd_read_object_to_assoc_array(V)( - V delegate(JSONValue) readValue) -{ - auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - V[string] ret; - foreach (key, val; jsonVal.object) - ret[key] = readValue(val); - return ret; - }; - return fun; -} + return new AtdException( + "incompatible JSON value where type '%%s' was expected: %%s".format( + expectedType, valueStr + )); + } -auto _atd_read_array_to_assoc_dict(K, V)( - K delegate(JSONValue) readKey, - V delegate(JSONValue) readValue) -{ - auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("list", jsonVal); - V[K] ret; - foreach (jsonInnerVal; jsonVal.array) + AtdException _atd_bad_d(T)(string expectedType, T jsonValue) + { + string valueStr = jsonValue.to!string; + if (valueStr.length > 200) { - if (jsonInnerVal.type != JSONType.array) - throw _atd_bad_json("list", jsonInnerVal); - ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + valueStr = valueStr[0 .. 200]; } - return ret; - }; - return fun; -} - -auto _atd_read_object_to_tuple_list(T)( - T delegate(JSONValue) readValue) -{ - auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - auto tupList = new Tuple!(string, T)[](jsonVal.object.length); - int i = 0; - foreach (key, val; jsonVal.object) - tupList[i++] = tuple(key, readValue(val)); - return tupList; - }; - return fun; -} -auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) -{ - auto fun = (JSONValue e) { - if (e.isNull) - return Nullable!T.init; - else - return Nullable!T(readElm(e)); - }; - return fun; -} + return new AtdException( + "incompatible D value where type '%%s' was expected: %%s".format( + expectedType, valueStr + )); + } -auto _atd_read_option(T)(T delegate(JSONValue) readElm) -{ - auto fun = (JSONValue e) { - if (e.type == JSONType.string && e.str == "None") - return Nullable!T.init; - else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e[1])); + typeof(null) _atd_read_unit(JSONValue x) + { + if (x.isNull) + return null; else - throw _atd_bad_json("option", e); - }; - return fun; -} + throw _atd_bad_json("unit", x); + } -// this whole set of function could be remplaced by one templated _atd_write_value function -// not sure it is what we want though + bool _atd_read_bool(JSONValue x) + { + try + return x.boolean; + catch (JSONException e) + throw _atd_bad_json("bool", x); + } -JSONValue _atd_write_unit(typeof(null) n) -{ - return JSONValue(null); -} + int _atd_read_int(JSONValue x) + { + try + return cast(int) x.integer; + catch (JSONException e) + throw _atd_bad_json("int", x); + } -JSONValue _atd_write_bool(bool b) -{ - return JSONValue(b); -} + float _atd_read_float(JSONValue x) + { + try + return x.floating; + catch (JSONException e) + throw _atd_bad_json("float", x); + } -JSONValue _atd_write_int(int i) -{ - return JSONValue(i); -} + string _atd_read_string(JSONValue x) + { + try + return x.str; + catch (JSONException e) + throw _atd_bad_json("string", x); + } -JSONValue _atd_write_float(float f) -{ - return JSONValue(f); -} + auto _atd_read_list(T)(T delegate(JSONValue) readElements) + { + return (JSONValue jsonVal) { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("array", jsonVal); + auto list = jsonVal.array; + return array(list.map!readElements()); + }; + } -JSONValue _atd_write_string(string s) -{ - return JSONValue(s); -} + auto _atd_read_object_to_assoc_array(V)( + V delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + V[string] ret; + foreach (key, val; jsonVal.object) + ret[key] = readValue(val); + return ret; + }; + return fun; + } -auto _atd_write_list(T)(JSONValue delegate(T) writeElm) -{ - return (T[] list) { return JSONValue(array(list.map!writeElm())); }; -} + auto _atd_read_array_to_assoc_dict(K, V)( + K delegate(JSONValue) readKey, + V delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("list", jsonVal); + V[K] ret; + foreach (jsonInnerVal; jsonVal.array) + { + if (jsonInnerVal.type != JSONType.array) + throw _atd_bad_json("list", jsonInnerVal); + ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + } + return ret; + }; + return fun; + } -auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) writeValue) -{ - auto fun = (T[string] assocArr) { - JSONValue[string] ret; - foreach (key, val; assocArr) - ret[key] = writeValue(val); - return JSONValue(ret); - }; - return fun; -} + auto _atd_read_object_to_tuple_list(T)( + T delegate(JSONValue) readValue) + { + auto fun = (JSONValue jsonVal) { + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + auto tupList = new Tuple!(string, T)[](jsonVal.object.length); + int i = 0; + foreach (key, val; jsonVal.object) + tupList[i++] = tuple(key, readValue(val)); + return tupList; + }; + return fun; + } -auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) writeKey, - JSONValue delegate(V) writeValue) -{ - auto fun = (V[K] assocArr) { - JSONValue[] ret; - foreach (key, val; assocArr) - ret ~= JSONValue([writeKey(key), writeValue(val)]); - return JSONValue(ret); - }; - return fun; -} + auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) + { + auto fun = (JSONValue e) { + if (e.isNull) + return Nullable!T.init; + else + return Nullable!T(readElm(e)); + }; + return fun; + } -auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) writeValue) -{ - auto fun = (Tuple!(string, T)[] tupList) { - JSONValue[string] ret; - foreach (tup; tupList) - ret[tup[0]] = writeValue(tup[1]); - return JSONValue(ret); - }; - return fun; -} + auto _atd_read_option(T)(T delegate(JSONValue) readElm) + { + auto fun = (JSONValue e) { + if (e.type == JSONType.string && e.str == "None") + return Nullable!T.init; + else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") + return Nullable!T(readElm(e[1])); + else + throw _atd_bad_json("option", e); + }; + return fun; + } -auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) -{ - auto fun = (Nullable!T elm) { - if (elm.isNull) - return JSONValue(null); - else - return writeElm(elm.get); - }; - return fun; -} + // this whole set of function could be remplaced by one templated _atd_write_value function + // not sure it is what we want though -auto _atd_write_option(T)(JSONValue delegate(T) writeElm) -{ - auto fun = (Nullable!T elm) { - if (elm.isNull) - return JSONValue("None"); - else - return JSONValue([JSONValue("Some"), writeElm(elm.get)]); - }; - return fun; -} + JSONValue _atd_write_unit(typeof(null) n) + { + return JSONValue(null); + } -// ====================== -// ====== TESTS ========= -// ====================== + JSONValue _atd_write_bool(bool b) + { + return JSONValue(b); + } -unittest -{ - import std.stdio; - import std.functional; + JSONValue _atd_write_int(int i) + { + return JSONValue(i); + } - auto l = JSONValue([[1, 2, 3], [4, 5, 6], [7, 8, 9]]); + JSONValue _atd_write_float(float f) + { + return JSONValue(f); + } - auto m = _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate)); - auto mm = _atd_write_list(_atd_write_list((&_atd_write_int).toDelegate)); + JSONValue _atd_write_string(string s) + { + return JSONValue(s); + } - assert(l == mm(m(l))); -} + auto _atd_write_list(T)(JSONValue delegate(T) writeElm) + { + return (T[] list) { return JSONValue(array(list.map!writeElm())); }; + } -unittest -{ - import std.stdio; + auto _atd_write_assoc_array_to_object(T)( + JSONValue delegate(T) writeValue) + { + auto fun = (T[string] assocArr) { + JSONValue[string] ret; + foreach (key, val; assocArr) + ret[key] = writeValue(val); + return JSONValue(ret); + }; + return fun; + } - auto l = JSONValue([JSONValue(1), JSONValue(5)]); + auto _atd_write_assoc_dict_to_array(K, V)( + JSONValue delegate(K) writeKey, + JSONValue delegate(V) writeValue) + { + auto fun = (V[K] assocArr) { + JSONValue[] ret; + foreach (key, val; assocArr) + ret ~= JSONValue([writeKey(key), writeValue(val)]); + return JSONValue(ret); + }; + return fun; + } - auto m = _atd_read_list((&_atd_read_int).toDelegate); - auto mm = _atd_write_list((&_atd_write_int).toDelegate); + auto _atd_write_tuple_list_to_object(T)( + JSONValue delegate(T) writeValue) + { + auto fun = (Tuple!(string, T)[] tupList) { + JSONValue[string] ret; + foreach (tup; tupList) + ret[tup[0]] = writeValue(tup[1]); + return JSONValue(ret); + }; + return fun; + } - auto read_l = m(l); - auto write_l = mm(read_l); + auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) + { + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue(null); + else + return writeElm(elm.get); + }; + return fun; + } - assert(write_l == l); + auto _atd_write_option(T)(JSONValue delegate(T) writeElm) + { + auto fun = (Nullable!T elm) { + if (elm.isNull) + return JSONValue("None"); + else + return JSONValue([JSONValue("Some"), writeElm(elm.get)]); + }; + return fun; + } } +// ############################################################################ +// # Public classes +// ############################################################################ -unittest +T fromJsonString(T)(string s) { - import std.stdio; - - auto potentiallyNull = JSONValue(null); - - auto m = _atd_read_nullable((&_atd_read_int).toDelegate); - auto mm = _atd_write_nullable((&_atd_write_int).toDelegate); - - Nullable!int readN = m(potentiallyNull); - auto writeN = mm(readN); - - assert(potentiallyNull == writeN); + JSONValue res = parseJSON(s); + return res.fromJson!T; } -unittest +string toJsonString(T)(T obj) { - import std.stdio; - - auto notNull = JSONValue(54); - - auto m = _atd_read_nullable((&_atd_read_int).toDelegate); - auto mm = _atd_write_nullable((&_atd_write_int).toDelegate); - - auto readv = m(notNull); - auto writev = mm(readv); - - assert(notNull == writev); + JSONValue res = obj.toJson; + return res.toString; } -unittest +TypedefType!T unwrapAlias(T)(T e) @safe { - import std.stdio; - - auto l = JSONValue([ - "hello": JSONValue(1), - "there": JSONValue(2), - "general": JSONValue(3), - "kenobi": JSONValue(4), - "haha": JSONValue(5), - ]); - - auto m = _atd_read_object_to_assoc_array((&_atd_read_int).toDelegate); - auto mm = _atd_write_assoc_array_to_object((&_atd_write_int).toDelegate); - - auto readv = m(l); - auto writev = mm(readv); - - assert(writev == l); + return cast(TypedefType!T) e; } -unittest +T wrapAlias(T)(TypedefType!T e) @safe { - import std.stdio; - - auto l = JSONValue([ - "hello": JSONValue(1), - "there": JSONValue(2), - "general": JSONValue(3), - "kenobi": JSONValue(4), - "haha": JSONValue(5), - ]); - - auto m = _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate); - auto mm = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate); - - auto readv = m(l); - auto writev = mm(readv); - - assert(writev == l); -} + return cast(T) e; +} \ No newline at end of file diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index 580b10d1..e5792615 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -43,6 +43,7 @@ type root = { } type alias = int list +type alias2 = int list type pair = (string * int) From 001f1d055f0fb85ef01ec36f2f7de15ebe0000be Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Sun, 3 Sep 2023 12:01:07 +0100 Subject: [PATCH 65/91] atdd: add some tests for aliases --- atdd/test/dlang-tests/test_atdd.d | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index 956f47c9..a2ab46cc 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -168,6 +168,14 @@ void setupTests() assert (a_obj == a_obj.toJsonString.fromJsonString!RecursiveClass); }; + + tests["aliases are different"] = { + Alias a1 = [32, 43]; + Alias2 a2 = [32, 43]; + + assert(a1.unwrapAlias == a2.unwrapAlias); + assert(a1 == a1.unwrapAlias.wrapAlias!Alias); + }; } void assertThrows(T)(T fn, bool writeMsg = false) From d287ab4ef7edeef46ed5b952ec6fe65b5def610f Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 4 Sep 2023 10:22:15 -0700 Subject: [PATCH 66/91] atdd: fix indentation and add type checking --- atdd/src/lib/Codegen.ml | 43 ++++++++-------- atdd/test/dlang-expected/everything.d | 72 +++++++++++++++++---------- 2 files changed, 67 insertions(+), 48 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 3a2df0aa..5b21c2e5 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -177,25 +177,24 @@ let _double_esc s = let fixed_size_preamble atd_filename = sprintf {| +// Generated by atdd from type definitions in %s. +// This implements classes for the types defined in '%s', providing +// methods and functions to convert data from/to JSON. - // Generated by atdd 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 - // ############################################################################ - - module %s; +// ############################################################################ +// # Private functions +// ############################################################################ + +module %s; - import std.algorithm : map; - import std.array : array; - import std.conv; - import std.format; - import std.functional; - import std.json; - import std.sumtype; - import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; +import std.algorithm : map; +import std.array : array; +import std.conv; +import std.format; +import std.functional; +import std.json; +import std.sumtype; +import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private { @@ -471,14 +470,14 @@ private return res.toString; } - TypedefType!T unwrapAlias(T)(T e) @safe + @safe TypedefType!T unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) { - return cast(TypedefType!T) e; + return cast(TypedefType!T) e; } - - T wrapAlias(T)(TypedefType!T e) @safe + + @safe T wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) { - return cast(T) e; + return cast(T) e; } |} diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index 08d5bfe7..5e523940 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -1,23 +1,22 @@ +// Generated by atdd 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. - // Generated by atdd 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 - // ############################################################################ - - module everything; +// ############################################################################ +// # Private functions +// ############################################################################ - import std.algorithm : map; - import std.array : array; - import std.conv; - import std.format; - import std.functional; - import std.json; - import std.sumtype; - import std.typecons : nullable, Nullable, tuple, Tuple; +module everything; + +import std.algorithm : map; +import std.array : array; +import std.conv; +import std.format; +import std.functional; +import std.json; +import std.sumtype; +import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private { @@ -292,6 +291,18 @@ private JSONValue res = obj.toJson; return res.toString; } + + @safe TypedefType!T unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) + { + return cast(TypedefType!T) e; + } + + @safe T wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) + { + return cast(T) e; + } + + struct RecursiveClass { @@ -375,25 +386,25 @@ JSONValue toJson(Kind x) { } -alias Alias = int[]; +alias Alias = Typedef!(int[], (int[]).init, "Alias"); JSONValue toJson(Alias e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e); + return _atd_write_list((&_atd_write_int).toDelegate)(e.unwrapAlias); } Alias fromJson(T : Alias)(JSONValue e) { - return _atd_read_list((&_atd_read_int).toDelegate)(e); + return _atd_read_list((&_atd_read_int).toDelegate)(e).wrapAlias!Alias; } -alias KindParametrizedTuple = Tuple!(Kind, Kind, int); +alias KindParametrizedTuple = Typedef!(Tuple!(Kind, Kind, int), (Tuple!(Kind, Kind, int)).init, "KindParametrizedTuple"); JSONValue toJson(KindParametrizedTuple e) { - return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e); + return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e.unwrapAlias); } KindParametrizedTuple fromJson(T : KindParametrizedTuple)(JSONValue e) { return ((JSONValue x) { if (x.type != JSONType.array || x.array.length != 3) throw _atd_bad_json("Tuple of size 3", x); return tuple(fromJson!Kind(x[0]), fromJson!Kind(x[1]), _atd_read_int(x[2])); - })(e); + })(e).wrapAlias!KindParametrizedTuple; } @@ -520,16 +531,16 @@ JSONValue toJson(RequireField obj) { } -alias Pair = Tuple!(string, int); +alias Pair = Typedef!(Tuple!(string, int), (Tuple!(string, int)).init, "Pair"); JSONValue toJson(Pair e) { - return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); + return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e.unwrapAlias); } Pair fromJson(T : Pair)(JSONValue e) { return ((JSONValue x) { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_string(x[0]), _atd_read_int(x[1])); - })(e); + })(e).wrapAlias!Pair; } @@ -586,3 +597,12 @@ JSONValue toJson(DefaultList obj) { res["items"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.items); return res; } + + +alias Alias2 = Typedef!(int[], (int[]).init, "Alias2"); +JSONValue toJson(Alias2 e) { + return _atd_write_list((&_atd_write_int).toDelegate)(e.unwrapAlias); +} +Alias2 fromJson(T : Alias2)(JSONValue e) { + return _atd_read_list((&_atd_read_int).toDelegate)(e).wrapAlias!Alias2; +} From 54f06073f380d433073af76ea993b1339830168e Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 4 Sep 2023 18:39:30 -0700 Subject: [PATCH 67/91] wrapping --- atdd/src/lib/Codegen.ml | 28 ++++++++++----- atdd/src/lib/Dlang_annot.ml | 55 ++++++++++++++++++++++++++++++ atdd/src/lib/Dlang_annot.mli | 11 ++++++ atdd/test/atd-input/everything.atd | 4 +++ atdd/test/dlang-tests/test_atdd.d | 5 +++ 5 files changed, 95 insertions(+), 8 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 5b21c2e5..a4258046 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -25,7 +25,10 @@ let annot_schema_dlang : Atd.Annot.schema_section = { section = "dlang"; fields = [ + Type_expr, "t"; Type_expr, "repr"; + Type_expr, "unwrap"; + Type_expr, "wrap"; Field, "default"; ] } @@ -489,9 +492,6 @@ private let not_implemented loc msg = A.error_at loc ("not implemented in atdd: " ^ msg) -let todo hint = - failwith ("TODO: " ^ hint) - 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 @@ -570,7 +570,11 @@ let rec type_name_of_expr env (e : type_expr) : string = | Option (loc, e, an) -> sprintf "Nullable!%s" (type_name_of_expr env e) | Nullable (loc, e, an) -> sprintf "Nullable!%s" (type_name_of_expr env e) | Shared (loc, e, an) -> not_implemented loc "shared" (* TODO *) - | Wrap (loc, e, an) -> todo "wrap" + | Wrap (loc, e, an) -> + (match Dlang_annot.get_dlang_wrap loc an with + None -> assert false (* TODO : dubious*) + | Some { dlang_wrap_t ; _ } -> dlang_wrap_t + ) | Name (loc, (loc2, name, []), an) -> dlang_type_name env name | Name (loc, (_, name, _::_), _) -> assert false | Tvar (loc, _) -> not_implemented loc "type variables" @@ -650,7 +654,11 @@ let rec json_writer ?(nested=false) env e = | Nullable (loc, e, an) -> sprintf "_atd_write_nullable(%s)" (json_writer ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" - | Wrap (loc, e, an) -> json_writer ~nested:true env e + | Wrap (loc, e, an) -> + (match Dlang_annot.get_dlang_wrap loc an with + None -> assert false (* TODO : dubious*) + | Some { dlang_wrap ; _ } -> dlang_wrap + ) | Name (loc, (loc2, name, []), an) -> (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" @@ -729,7 +737,11 @@ let rec json_reader ?(nested=false) env (e : type_expr) = | Nullable (loc, e, an) -> sprintf "_atd_read_nullable(%s)" (json_reader ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" - | Wrap (loc, e, an) -> json_reader ~nested:true env e + | Wrap (loc, e, an) -> + (match Dlang_annot.get_dlang_wrap loc an with + None -> assert false (* TODO : dubious*) + | Some { dlang_unwrap ; _ } -> dlang_unwrap + ) | Name (loc, (loc2, name, []), an) -> (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_read_%s%s" @@ -1026,7 +1038,7 @@ let sum env loc name cases = let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = if param <> [] then not_implemented loc "parametrized type"; - let rec unwrap e = + let unwrap e = match e with | Sum (loc, cases, an) -> sum env loc name cases @@ -1036,9 +1048,9 @@ let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = | List _ | Option _ | Nullable _ + | Wrap _ | Name _ -> alias_wrapper env name e | Shared _ -> not_implemented loc "cyclic references" - | Wrap (loc, e, an) -> unwrap e | Tvar _ -> not_implemented loc "parametrized type" in unwrap e diff --git a/atdd/src/lib/Dlang_annot.ml b/atdd/src/lib/Dlang_annot.ml index 759c69fa..a1e6234a 100644 --- a/atdd/src/lib/Dlang_annot.ml +++ b/atdd/src/lib/Dlang_annot.ml @@ -8,6 +8,12 @@ type assoc_repr = | List | Dict +type atd_dlang_wrap = { + dlang_wrap_t : string; + dlang_wrap : string; + dlang_unwrap : string; +} + let get_dlang_default an : string option = Atd.Annot.get_opt_field ~parse:(fun s -> Some s) @@ -42,3 +48,52 @@ let get_dlang_json_text an : string list = ~sections:["dlang"] ~field:"json_dlang.text" an + +let get_dlang_wrap loc an = + let path = ["dlang"] 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 + match t, wrap, unwrap with + None, None, None -> None + | Some t, Some wrap, Some unwrap -> + Some { dlang_wrap_t = t; dlang_wrap = wrap; dlang_unwrap = unwrap } + | _ -> + Atd.Ast.error_at loc "Incomplete annotation. Missing t, wrap or unwrap" diff --git a/atdd/src/lib/Dlang_annot.mli b/atdd/src/lib/Dlang_annot.mli index 4b3a5e4b..0771f9be 100644 --- a/atdd/src/lib/Dlang_annot.mli +++ b/atdd/src/lib/Dlang_annot.mli @@ -29,3 +29,14 @@ val get_dlang_assoc_repr : Atd.Annot.t -> assoc_repr (** Returns text the user wants to be inserted at the beginning of the Dlang file such as imports. *) val get_dlang_json_text : Atd.Annot.t -> string list + + + +type atd_dlang_wrap = { + dlang_wrap_t : string; + dlang_wrap : string; + dlang_unwrap : string; +} + +val get_dlang_wrap : Atd.Ast.loc -> + Atd.Annot.t -> atd_dlang_wrap option diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index e5792615..fda04739 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -60,3 +60,7 @@ type recursive_class = { type default_list = { ~items: int list; } + +type record_with_wrapped_type = { + item: string wrap ; +} diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index a2ab46cc..43505a11 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -176,6 +176,11 @@ void setupTests() assert(a1.unwrapAlias == a2.unwrapAlias); assert(a1 == a1.unwrapAlias.wrapAlias!Alias); }; + + tests["using wrapped type"] = { + auto og = RecordWithWrappedType(42); + assert(og.toJsonString.fromJsonString!RecordWithWrappedType == og); + }; } void assertThrows(T)(T fn, bool writeMsg = false) From eda5cbb4971bb1272b3e1c57929e5b3efff3db2d Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Tue, 5 Sep 2023 10:59:29 -0700 Subject: [PATCH 68/91] wrapping: handle wrap and unwrap while preserving type --- atdd/src/lib/Codegen.ml | 28 +++++++++++++++++++++++++--- atdd/test/atd-input/everything.atd | 2 ++ 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index a4258046..ad10e9a8 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -197,6 +197,7 @@ import std.format; import std.functional; import std.json; import std.sumtype; +import std.stdint; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private @@ -364,6 +365,15 @@ private }; return fun; } + + auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) readElm, Unwrapped delegate(Wrapped) unwrap) + { + auto fun = (JSONValue e) { + auto elm = readElm(e); + return unwrap(elm); + }; + return fun; + } // this whole set of function could be remplaced by one templated _atd_write_value function // not sure it is what we want though @@ -456,7 +466,17 @@ private }; return fun; } + + auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) writeElm, Wrapped delegate(Unwrapped) wrap) + { + auto fun = (Unwrapped elm) { + auto e = wrap(elm); + return writeElm(e); + }; + return fun; + } } + // ############################################################################ // # Public classes // ############################################################################ @@ -657,8 +677,9 @@ let rec json_writer ?(nested=false) env e = | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with None -> assert false (* TODO : dubious*) - | Some { dlang_wrap ; _ } -> dlang_wrap - ) + | Some { dlang_wrap_t; dlang_wrap ; _ } -> + sprintf "_atd_write_wrap(%s, (%s e) => %s(e))" (json_writer ~nested:true env e) dlang_wrap_t dlang_wrap + ) | Name (loc, (loc2, name, []), an) -> (match name with | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" @@ -740,7 +761,8 @@ let rec json_reader ?(nested=false) env (e : type_expr) = | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with None -> assert false (* TODO : dubious*) - | Some { dlang_unwrap ; _ } -> dlang_unwrap + | Some { dlang_unwrap ; _ } -> + sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr env e) dlang_unwrap ) | Name (loc, (loc2, name, []), an) -> (match name with diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index fda04739..c524f240 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -44,6 +44,8 @@ type root = { type alias = int list type alias2 = int list +type alias3 = int list wrap + type pair = (string * int) From 5792392b4c36705cf471b117226414584bb173fa Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Tue, 5 Sep 2023 12:32:08 -0700 Subject: [PATCH 69/91] global imports --- atdd/src/lib/Codegen.ml | 7 ++-- atdd/src/lib/Dlang_annot.ml | 12 ++----- atdd/src/lib/Dlang_annot.mli | 2 +- atdd/test/atd-input/everything.atd | 3 ++ atdd/test/dlang-expected/everything.d | 47 +++++++++++++++++++++++++++ 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ad10e9a8..f0deffdc 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -30,6 +30,7 @@ let annot_schema_dlang : Atd.Annot.schema_section = Type_expr, "unwrap"; Type_expr, "wrap"; Field, "default"; + Module_head, "import"; ] } @@ -197,7 +198,6 @@ import std.format; import std.functional; import std.json; import std.sumtype; -import std.stdint; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private @@ -1135,5 +1135,8 @@ let run_file 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 = Dlang_annot.get_dlang_json_text (snd atd_head) in + let head = + Dlang_annot.get_dlang_import (snd atd_head) + |> List.map (sprintf "import %s;") + in to_file ~atd_filename:src_name ~head atd_module dst_path diff --git a/atdd/src/lib/Dlang_annot.ml b/atdd/src/lib/Dlang_annot.ml index a1e6234a..30791474 100644 --- a/atdd/src/lib/Dlang_annot.ml +++ b/atdd/src/lib/Dlang_annot.ml @@ -34,19 +34,11 @@ let get_dlang_assoc_repr an : assoc_repr = an (* imports etc. *) -let get_dlang_text an : string list = +let get_dlang_import an : string list = Atd.Annot.get_fields ~parse:(fun s -> Some s) ~sections:["dlang"] - ~field:"text" - an - -let get_dlang_json_text an : string list = - get_dlang_text an - @ Atd.Annot.get_fields - ~parse:(fun s -> Some s) - ~sections:["dlang"] - ~field:"json_dlang.text" + ~field:"import" an let get_dlang_wrap loc an = diff --git a/atdd/src/lib/Dlang_annot.mli b/atdd/src/lib/Dlang_annot.mli index 0771f9be..64de6cf7 100644 --- a/atdd/src/lib/Dlang_annot.mli +++ b/atdd/src/lib/Dlang_annot.mli @@ -28,7 +28,7 @@ val get_dlang_assoc_repr : Atd.Annot.t -> assoc_repr (** Returns text the user wants to be inserted at the beginning of the Dlang file such as imports. *) -val get_dlang_json_text : Atd.Annot.t -> string list +val get_dlang_import : Atd.Annot.t -> string list diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index c524f240..91261df6 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -1,3 +1,6 @@ + + + type kind = [ | Root (* class name conflict *) | Thing of int diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index 5e523940..422a10c7 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -183,6 +183,15 @@ private }; return fun; } + + auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) readElm, Unwrapped delegate(Wrapped) unwrap) + { + auto fun = (JSONValue e) { + auto elm = readElm(e); + return unwrap(elm); + }; + return fun; + } // this whole set of function could be remplaced by one templated _atd_write_value function // not sure it is what we want though @@ -275,7 +284,17 @@ private }; return fun; } + + auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) writeElm, Wrapped delegate(Unwrapped) wrap) + { + auto fun = (Unwrapped elm) { + auto e = wrap(elm); + return writeElm(e); + }; + return fun; + } } + // ############################################################################ // # Public classes // ############################################################################ @@ -305,6 +324,9 @@ private +import std.stdint : uint32_t; + + struct RecursiveClass { int id; bool flag; @@ -531,6 +553,22 @@ JSONValue toJson(RequireField obj) { } +struct RecordWithWrappedType { + int item; +} + +RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { + RecordWithWrappedType obj; + obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!(int)(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); + return obj; +} +JSONValue toJson(RecordWithWrappedType obj) { + JSONValue res; + res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!(string)(e))(obj.item); + return res; +} + + alias Pair = Typedef!(Tuple!(string, int), (Tuple!(string, int)).init, "Pair"); JSONValue toJson(Pair e) { return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e.unwrapAlias); @@ -599,6 +637,15 @@ JSONValue toJson(DefaultList obj) { } +alias Alias3 = Typedef!(uint32_t[], (uint32_t[]).init, "Alias3"); +JSONValue toJson(Alias3 e) { + return _atd_write_wrap(_atd_write_list((&_atd_write_int).toDelegate), (uint32_t[] e) => to!(int[])(e))(e.unwrapAlias); +} +Alias3 fromJson(T : Alias3)(JSONValue e) { + return _atd_read_wrap(_atd_read_list((&_atd_read_int).toDelegate), (int[] e) => to!(uint32_t[])(e))(e).wrapAlias!Alias3; +} + + alias Alias2 = Typedef!(int[], (int[]).init, "Alias2"); JSONValue toJson(Alias2 e) { return _atd_write_list((&_atd_write_int).toDelegate)(e.unwrapAlias); From 319c91e0011106b0ed58d802712a886171624098 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 6 Sep 2023 16:21:46 -0700 Subject: [PATCH 70/91] wip deal with aliases of aliases --- atdd/src/lib/Codegen.ml | 25 ++++++++++++++++++++----- atdd/test/atd-input/everything.atd | 11 ++++++++--- 2 files changed, 28 insertions(+), 8 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index f0deffdc..21ba85fe 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -21,6 +21,8 @@ type env = { translate_inst_variable: unit -> (string -> string); } +type aliasing = Keep | None_ + let annot_schema_dlang : Atd.Annot.schema_section = { section = "dlang"; @@ -503,6 +505,14 @@ private return cast(T) e; } + template RemoveTypedef(T) + { + static if (is(T : Typedef!Arg, Arg)) + alias RemoveTypedef = RemoveTypedef!Arg; + else + alias RemoveTypedef = T; + } + |} atd_filename atd_filename @@ -553,7 +563,7 @@ let assoc_kind loc (e : type_expr) an : assoc_kind = | _, Array, _ -> error_at loc "not a (_ * _) list" (* Map ATD built-in types to built-in Dlang types *) -let dlang_type_name env (name : string) = +let dlang_type_name ?(aliasing=Keep) env (name : string) = match name with | "unit" -> "void" | "bool" -> "bool" @@ -561,9 +571,14 @@ let dlang_type_name env (name : string) = | "float" -> "float" | "string" -> "string" | "abstract" -> "JSONValue" - | user_defined -> struct_name env user_defined + | user_defined -> + let typename = (struct_name env user_defined) in + match aliasing with + | None_ -> sprintf "RemoveTypedef!(%s)" typename + (* | Remove_One -> sprintf "TypedefType!(%s)" typename *) + | Keep -> typename -let rec type_name_of_expr env (e : type_expr) : string = +let rec type_name_of_expr ?(aliasing=Keep) env (e : type_expr) : string = match e with | Sum (loc, _, _) -> not_implemented loc "inline sum types" | Record (loc, _, _) -> not_implemented loc "inline records" @@ -595,7 +610,7 @@ let rec type_name_of_expr env (e : type_expr) : string = None -> assert false (* TODO : dubious*) | Some { dlang_wrap_t ; _ } -> dlang_wrap_t ) - | Name (loc, (loc2, name, []), an) -> dlang_type_name env name + | Name (loc, (loc2, name, []), an) -> dlang_type_name ~aliasing:aliasing env name | Name (loc, (_, name, _::_), _) -> assert false | Tvar (loc, _) -> not_implemented loc "type variables" @@ -890,7 +905,7 @@ let record env loc name (fields : field list) an = let alias_wrapper env name type_expr = let dlang_struct_name = struct_name env name in - let value_type = type_name_of_expr env type_expr in + let value_type = type_name_of_expr ~aliasing:None_ env type_expr in [ Line (sprintf "alias %s = Typedef!(%s, (%s).init, \"%s\");" dlang_struct_name value_type value_type dlang_struct_name); Line (sprintf "JSONValue toJson(%s e) {" dlang_struct_name); diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index 91261df6..e66de263 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -1,5 +1,5 @@ - + type kind = [ | Root (* class name conflict *) @@ -43,11 +43,16 @@ type root = { untyped_things: abstract list; parametrized_record: (int, float) parametrized_record; parametrized_tuple: kind parametrized_tuple; -} + wrapped: st wrap +} +type st = int type alias = int list type alias2 = int list -type alias3 = int list wrap +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 pair = (string * int) From 92ca06ed6ebf669d1cc784db5d30aa6731475974 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 6 Sep 2023 16:52:39 -0700 Subject: [PATCH 71/91] make toJson a template and use typedef to pick right one --- atdd/src/lib/Codegen.ml | 34 +++-- atdd/test/atd-input/everything.atd | 6 +- atdd/test/dlang-expected/everything.d | 206 +++++++++++++++++++------- atdd/test/dlang-tests/test_atdd.d | 7 +- 4 files changed, 182 insertions(+), 71 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 21ba85fe..eaceaf07 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -491,7 +491,7 @@ private string toJsonString(T)(T obj) { - JSONValue res = obj.toJson; + JSONValue res = obj.toJson!T; return res.toString; } @@ -700,7 +700,9 @@ let rec json_writer ?(nested=false) env e = | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" (if nested then "(&" else "") name (if nested then ").toDelegate" else "") | "abstract" -> "(JSONValue x) => x" - | _ -> sprintf "((%s x) => x.toJson())" (dlang_type_name env name)) + | _ -> let dtype_name = (dlang_type_name env name) in + let rawtype_name = (dlang_type_name ~aliasing:None_ env name) in + sprintf "((%s x) => x.toJson!(%s))" rawtype_name dtype_name) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" @@ -777,7 +779,7 @@ let rec json_reader ?(nested=false) env (e : type_expr) = (match Dlang_annot.get_dlang_wrap loc an with None -> assert false (* TODO : dubious*) | Some { dlang_unwrap ; _ } -> - sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr env e) dlang_unwrap + sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr ~aliasing:None_ env e) dlang_unwrap ) | Name (loc, (loc2, name, []), an) -> (match name with @@ -835,7 +837,7 @@ let from_json_class_argument 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 type_name = type_name_of_expr ~aliasing:None_ env e in let unwrapped_e = unwrap_field_type loc name kind e in let default = match kind with @@ -883,7 +885,7 @@ let record env loc name (fields : field list) an = in let to_json = [ - Line (sprintf "JSONValue toJson(%s obj) {" (single_esc dlang_struct_name)); + Line (sprintf "JSONValue toJson(T : %s)(T obj) {" (single_esc dlang_struct_name)); Block [ Line ("JSONValue res;"); Inline json_object_body; @@ -908,11 +910,17 @@ let alias_wrapper env name type_expr = let value_type = type_name_of_expr ~aliasing:None_ env type_expr in [ Line (sprintf "alias %s = Typedef!(%s, (%s).init, \"%s\");" dlang_struct_name value_type value_type dlang_struct_name); - Line (sprintf "JSONValue toJson(%s e) {" dlang_struct_name); - Block [Line(sprintf "return %s(e.unwrapAlias);" (json_writer env type_expr))]; + Line (sprintf "JSONValue toJson(T : %s)(%s e) {" dlang_struct_name value_type); + Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; Line("}"); - Line (sprintf "%s fromJson(T : %s)(JSONValue e) {" dlang_struct_name dlang_struct_name); - Block [Line(sprintf "return %s(e).wrapAlias!%s;" (json_reader env type_expr) dlang_struct_name)]; + Line (sprintf "string toJsonString(T : %s)(%s obj) {" dlang_struct_name value_type); + Block [Line(sprintf "return obj.toJson!(%s).toString;" dlang_struct_name)]; + Line("}"); + Line (sprintf "%s fromJson(T : %s)(JSONValue e) {" value_type dlang_struct_name); + Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; + Line("}"); + Line (sprintf "%s fromJsonString(T : %s)(string s) {" value_type dlang_struct_name); + Block [Line(sprintf "return parseJSON(s).fromJson!(%s);" dlang_struct_name)]; Line("}"); ] @@ -926,7 +934,7 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s {}" (trans env unique_name)); - Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); + Line (sprintf "JSONValue toJson(T : %s)(T e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); ] @@ -936,7 +944,7 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) - Line (sprintf "JSONValue toJson(%s e) {" (trans env unique_name)); + Line (sprintf "JSONValue toJson(T : %s)(T e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); ] @@ -1037,12 +1045,12 @@ let sum_container env loc name cases = ]; Line "}"; Line ""; - Line (sprintf "JSONValue toJson(%s x) {" (dlang_struct_name)); + Line (sprintf "JSONValue toJson(T : %s)(T x) {" (dlang_struct_name)); Block [ Line "return x.match!("; Line ( List.map (fun (loc, orig_name, unique_name, an, opt_e) -> - sprintf "(%s v) => v.toJson" (trans env unique_name) + sprintf "(%s v) => v.toJson!(%s)" (trans env unique_name) (trans env unique_name) ) cases |> String.concat ",\n"); Line ");" diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index e66de263..c6cfc02d 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -1,4 +1,3 @@ - type kind = [ @@ -43,14 +42,15 @@ type root = { untyped_things: abstract list; parametrized_record: (int, float) parametrized_record; parametrized_tuple: kind parametrized_tuple; - wrapped: st wrap + wrapped: st wrap ; + aaa: alias_of_alias_of_alias; } 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 = alias3 wrap type alias_of_alias_not_Wrapped = alias3 type alias_of_alias_of_alias = alias_of_alias_not_Wrapped diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index 422a10c7..06b411d4 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -307,7 +307,7 @@ private string toJsonString(T)(T obj) { - JSONValue res = obj.toJson; + JSONValue res = obj.toJson!T; return res.toString; } @@ -321,10 +321,18 @@ private return cast(T) e; } + template RemoveTypedef(T) + { + static if (is(T : Typedef!Arg, Arg)) + alias RemoveTypedef = RemoveTypedef!Arg; + else + alias RemoveTypedef = T; + } + -import std.stdint : uint32_t; +import std.stdint : uint32_t, uint16_t; struct RecursiveClass { @@ -340,39 +348,54 @@ RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { obj.children = ("children" in x) ? _atd_read_list((&fromJson!RecursiveClass).toDelegate)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); return obj; } -JSONValue toJson(RecursiveClass obj) { +JSONValue toJson(T : RecursiveClass)(T obj) { JSONValue res; res["id"] = _atd_write_int(obj.id); res["flag"] = _atd_write_bool(obj.flag); - res["children"] = _atd_write_list(((RecursiveClass x) => x.toJson()))(obj.children); + res["children"] = _atd_write_list(((RemoveTypedef!(RecursiveClass) x) => x.toJson!(RecursiveClass)))(obj.children); return res; } +alias St = Typedef!(int, (int).init, "St"); +JSONValue toJson(T : St)(int e) { + return _atd_write_int(e); +} +string toJsonString(T : St)(int obj) { + return obj.toJson!(St).toString; +} +int fromJson(T : St)(JSONValue e) { + return _atd_read_int(e); +} +int fromJsonString(T : St)(string s) { + return parseJSON(s).fromJson!(St); +} + + // Original type: kind = [ ... | Root | ... ] struct Root_ {} -JSONValue toJson(Root_ e) { +JSONValue toJson(T : Root_)(T e) { return JSONValue("Root"); } // Original type: kind = [ ... | Thing of ... | ... ] struct Thing { int value; } -JSONValue toJson(Thing e) { +JSONValue toJson(T : Thing)(T e) { return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); } // Original type: kind = [ ... | WOW | ... ] struct WOW {} -JSONValue toJson(WOW e) { +JSONValue toJson(T : WOW)(T e) { return JSONValue("wow"); } // Original type: kind = [ ... | Amaze of ... | ... ] struct Amaze { string[] value; } -JSONValue toJson(Amaze e) { +JSONValue toJson(T : Amaze)(T e) { return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); } @@ -398,35 +421,92 @@ Kind fromJson(T : Kind)(JSONValue x) { throw _atd_bad_json("Kind", x); } -JSONValue toJson(Kind x) { +JSONValue toJson(T : Kind)(T x) { return x.match!( - (Root_ v) => v.toJson, -(Thing v) => v.toJson, -(WOW v) => v.toJson, -(Amaze v) => v.toJson + (Root_ v) => v.toJson!(Root_), +(Thing v) => v.toJson!(Thing), +(WOW v) => v.toJson!(WOW), +(Amaze v) => v.toJson!(Amaze) ); } +alias Alias3 = Typedef!(uint32_t, (uint32_t).init, "Alias3"); +JSONValue toJson(T : Alias3)(uint32_t e) { + return _atd_write_wrap((&_atd_write_int).toDelegate, (uint32_t e) => to!int(e))(e); +} +string toJsonString(T : Alias3)(uint32_t obj) { + return obj.toJson!(Alias3).toString; +} +uint32_t fromJson(T : Alias3)(JSONValue e) { + return _atd_read_wrap((&_atd_read_int).toDelegate, (int e) => to!uint32_t(e))(e); +} +uint32_t fromJsonString(T : Alias3)(string s) { + return parseJSON(s).fromJson!(Alias3); +} + + +alias AliasOfAliasNotWrapped = Typedef!(RemoveTypedef!(Alias3), (RemoveTypedef!(Alias3)).init, "AliasOfAliasNotWrapped"); +JSONValue toJson(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) e) { + return ((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3))(e); +} +string toJsonString(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) obj) { + return obj.toJson!(AliasOfAliasNotWrapped).toString; +} +RemoveTypedef!(Alias3) fromJson(T : AliasOfAliasNotWrapped)(JSONValue e) { + return fromJson!Alias3(e); +} +RemoveTypedef!(Alias3) fromJsonString(T : AliasOfAliasNotWrapped)(string s) { + return parseJSON(s).fromJson!(AliasOfAliasNotWrapped); +} + + +alias AliasOfAliasOfAlias = Typedef!(RemoveTypedef!(AliasOfAliasNotWrapped), (RemoveTypedef!(AliasOfAliasNotWrapped)).init, "AliasOfAliasOfAlias"); +JSONValue toJson(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) e) { + return ((RemoveTypedef!(AliasOfAliasNotWrapped) x) => x.toJson!(AliasOfAliasNotWrapped))(e); +} +string toJsonString(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) obj) { + return obj.toJson!(AliasOfAliasOfAlias).toString; +} +RemoveTypedef!(AliasOfAliasNotWrapped) fromJson(T : AliasOfAliasOfAlias)(JSONValue e) { + return fromJson!AliasOfAliasNotWrapped(e); +} +RemoveTypedef!(AliasOfAliasNotWrapped) fromJsonString(T : AliasOfAliasOfAlias)(string s) { + return parseJSON(s).fromJson!(AliasOfAliasOfAlias); +} + + alias Alias = Typedef!(int[], (int[]).init, "Alias"); -JSONValue toJson(Alias e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e.unwrapAlias); +JSONValue toJson(T : Alias)(int[] e) { + return _atd_write_list((&_atd_write_int).toDelegate)(e); +} +string toJsonString(T : Alias)(int[] obj) { + return obj.toJson!(Alias).toString; } -Alias fromJson(T : Alias)(JSONValue e) { - return _atd_read_list((&_atd_read_int).toDelegate)(e).wrapAlias!Alias; +int[] fromJson(T : Alias)(JSONValue e) { + return _atd_read_list((&_atd_read_int).toDelegate)(e); +} +int[] fromJsonString(T : Alias)(string s) { + return parseJSON(s).fromJson!(Alias); } alias KindParametrizedTuple = Typedef!(Tuple!(Kind, Kind, int), (Tuple!(Kind, Kind, int)).init, "KindParametrizedTuple"); -JSONValue toJson(KindParametrizedTuple e) { - return ((Tuple!(Kind, Kind, int) x) => JSONValue([((Kind x) => x.toJson())(x[0]), ((Kind x) => x.toJson())(x[1]), _atd_write_int(x[2])]))(e.unwrapAlias); +JSONValue toJson(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) e) { + return ((Tuple!(Kind, Kind, int) x) => JSONValue([((RemoveTypedef!(Kind) x) => x.toJson!(Kind))(x[0]), ((RemoveTypedef!(Kind) x) => x.toJson!(Kind))(x[1]), _atd_write_int(x[2])]))(e); +} +string toJsonString(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) obj) { + return obj.toJson!(KindParametrizedTuple).toString; } -KindParametrizedTuple fromJson(T : KindParametrizedTuple)(JSONValue e) { +Tuple!(Kind, Kind, int) fromJson(T : KindParametrizedTuple)(JSONValue e) { return ((JSONValue x) { if (x.type != JSONType.array || x.array.length != 3) throw _atd_bad_json("Tuple of size 3", x); return tuple(fromJson!Kind(x[0]), fromJson!Kind(x[1]), _atd_read_int(x[2])); - })(e).wrapAlias!KindParametrizedTuple; + })(e); +} +Tuple!(Kind, Kind, int) fromJsonString(T : KindParametrizedTuple)(string s) { + return parseJSON(s).fromJson!(KindParametrizedTuple); } @@ -441,7 +521,7 @@ IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) obj.field_b = ("field_b" in x) ? _atd_read_list((&_atd_read_float).toDelegate)(x["field_b"]) : []; return obj; } -JSONValue toJson(IntFloatParametrizedRecord obj) { +JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { JSONValue res; res["field_a"] = _atd_write_int(obj.field_a); res["field_b"] = _atd_write_list((&_atd_write_float).toDelegate)(obj.field_b); @@ -460,7 +540,7 @@ struct Root { Nullable!int maybe; int[] extras = []; int answer = 42; - Alias aliased; + RemoveTypedef!(Alias) aliased; Tuple!(float, float) point; Kind[] kinds; Tuple!(float, int)[] assoc1; @@ -470,8 +550,10 @@ struct Root { Nullable!int[] nullables; Nullable!int[] options; JSONValue[] untyped_things; - IntFloatParametrizedRecord parametrized_record; - KindParametrizedTuple parametrized_tuple; + RemoveTypedef!(IntFloatParametrizedRecord) parametrized_record; + RemoveTypedef!(KindParametrizedTuple) parametrized_tuple; + uint16_t wrapped; + RemoveTypedef!(AliasOfAliasOfAlias) aaa; } Root fromJson(T : Root)(JSONValue x) { @@ -506,9 +588,11 @@ Root fromJson(T : Root)(JSONValue x) { obj.untyped_things = ("untyped_things" in x) ? _atd_read_list(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field!(typeof(obj.untyped_things))("Root", "untyped_things"); obj.parametrized_record = ("parametrized_record" in x) ? fromJson!IntFloatParametrizedRecord(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); obj.parametrized_tuple = ("parametrized_tuple" in x) ? fromJson!KindParametrizedTuple(x["parametrized_tuple"]) : _atd_missing_json_field!(typeof(obj.parametrized_tuple))("Root", "parametrized_tuple"); + obj.wrapped = ("wrapped" in x) ? _atd_read_wrap((&fromJson!St).toDelegate, (RemoveTypedef!(St) e) => to!uint16_t(e))(x["wrapped"]) : _atd_missing_json_field!(typeof(obj.wrapped))("Root", "wrapped"); + obj.aaa = ("aaa" in x) ? fromJson!AliasOfAliasOfAlias(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); return obj; } -JSONValue toJson(Root obj) { +JSONValue toJson(T : Root)(T obj) { JSONValue res; res["ID"] = _atd_write_string(obj.id); res["await"] = _atd_write_bool(obj.await); @@ -521,9 +605,9 @@ JSONValue toJson(Root obj) { res["maybe"] = _atd_write_option((&_atd_write_int).toDelegate)(obj.maybe); res["extras"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.extras); res["answer"] = _atd_write_int(obj.answer); - res["aliased"] = ((Alias x) => x.toJson())(obj.aliased); + res["aliased"] = ((RemoveTypedef!(Alias) x) => x.toJson!(Alias))(obj.aliased); res["point"] = ((Tuple!(float, float) x) => JSONValue([_atd_write_float(x[0]), _atd_write_float(x[1])]))(obj.point); - res["kinds"] = _atd_write_list(((Kind x) => x.toJson()))(obj.kinds); + res["kinds"] = _atd_write_list(((RemoveTypedef!(Kind) x) => x.toJson!(Kind)))(obj.kinds); res["assoc1"] = _atd_write_list(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc1); res["assoc2"] = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate)(obj.assoc2); res["assoc3"] = _atd_write_assoc_dict_to_array((&_atd_write_float).toDelegate, (&_atd_write_int).toDelegate)(obj.assoc3); @@ -531,8 +615,10 @@ JSONValue toJson(Root obj) { res["nullables"] = _atd_write_list(_atd_write_nullable((&_atd_write_int).toDelegate))(obj.nullables); res["options"] = _atd_write_list(_atd_write_option((&_atd_write_int).toDelegate))(obj.options); res["untyped_things"] = _atd_write_list((JSONValue x) => x)(obj.untyped_things); - res["parametrized_record"] = ((IntFloatParametrizedRecord x) => x.toJson())(obj.parametrized_record); - res["parametrized_tuple"] = ((KindParametrizedTuple x) => x.toJson())(obj.parametrized_tuple); + res["parametrized_record"] = ((RemoveTypedef!(IntFloatParametrizedRecord) x) => x.toJson!(IntFloatParametrizedRecord))(obj.parametrized_record); + res["parametrized_tuple"] = ((RemoveTypedef!(KindParametrizedTuple) x) => x.toJson!(KindParametrizedTuple))(obj.parametrized_tuple); + res["wrapped"] = _atd_write_wrap(((RemoveTypedef!(St) x) => x.toJson!(St)), (uint16_t e) => to!int(e))(obj.wrapped); + res["aaa"] = ((RemoveTypedef!(AliasOfAliasOfAlias) x) => x.toJson!(AliasOfAliasOfAlias))(obj.aaa); return res; } @@ -546,7 +632,7 @@ RequireField fromJson(T : RequireField)(JSONValue x) { obj.req = ("req" in x) ? _atd_read_string(x["req"]) : _atd_missing_json_field!(typeof(obj.req))("RequireField", "req"); return obj; } -JSONValue toJson(RequireField obj) { +JSONValue toJson(T : RequireField)(T obj) { JSONValue res; res["req"] = _atd_write_string(obj.req); return res; @@ -562,7 +648,7 @@ RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!(int)(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); return obj; } -JSONValue toJson(RecordWithWrappedType obj) { +JSONValue toJson(T : RecordWithWrappedType)(T obj) { JSONValue res; res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!(string)(e))(obj.item); return res; @@ -570,28 +656,34 @@ JSONValue toJson(RecordWithWrappedType obj) { alias Pair = Typedef!(Tuple!(string, int), (Tuple!(string, int)).init, "Pair"); -JSONValue toJson(Pair e) { - return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e.unwrapAlias); +JSONValue toJson(T : Pair)(Tuple!(string, int) e) { + return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); } -Pair fromJson(T : Pair)(JSONValue e) { +string toJsonString(T : Pair)(Tuple!(string, int) obj) { + return obj.toJson!(Pair).toString; +} +Tuple!(string, int) fromJson(T : Pair)(JSONValue e) { return ((JSONValue x) { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_string(x[0]), _atd_read_int(x[1])); - })(e).wrapAlias!Pair; + })(e); +} +Tuple!(string, int) fromJsonString(T : Pair)(string s) { + return parseJSON(s).fromJson!(Pair); } // Original type: frozen = [ ... | A | ... ] struct A {} -JSONValue toJson(A e) { +JSONValue toJson(T : A)(T e) { return JSONValue("A"); } // Original type: frozen = [ ... | B of ... | ... ] struct B { int value; } -JSONValue toJson(B e) { +JSONValue toJson(T : B)(T e) { return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); } @@ -613,10 +705,10 @@ Frozen fromJson(T : Frozen)(JSONValue x) { throw _atd_bad_json("Frozen", x); } -JSONValue toJson(Frozen x) { +JSONValue toJson(T : Frozen)(T x) { return x.match!( - (A v) => v.toJson, -(B v) => v.toJson + (A v) => v.toJson!(A), +(B v) => v.toJson!(B) ); } @@ -630,26 +722,38 @@ DefaultList fromJson(T : DefaultList)(JSONValue x) { obj.items = ("items" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["items"]) : []; return obj; } -JSONValue toJson(DefaultList obj) { +JSONValue toJson(T : DefaultList)(T obj) { JSONValue res; res["items"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.items); return res; } -alias Alias3 = Typedef!(uint32_t[], (uint32_t[]).init, "Alias3"); -JSONValue toJson(Alias3 e) { - return _atd_write_wrap(_atd_write_list((&_atd_write_int).toDelegate), (uint32_t[] e) => to!(int[])(e))(e.unwrapAlias); +alias AliasOfAlias = Typedef!(uint16_t, (uint16_t).init, "AliasOfAlias"); +JSONValue toJson(T : AliasOfAlias)(uint16_t e) { + return _atd_write_wrap(((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3)), (uint16_t e) => to!uint32_t(e))(e); } -Alias3 fromJson(T : Alias3)(JSONValue e) { - return _atd_read_wrap(_atd_read_list((&_atd_read_int).toDelegate), (int[] e) => to!(uint32_t[])(e))(e).wrapAlias!Alias3; +string toJsonString(T : AliasOfAlias)(uint16_t obj) { + return obj.toJson!(AliasOfAlias).toString; +} +uint16_t fromJson(T : AliasOfAlias)(JSONValue e) { + return _atd_read_wrap((&fromJson!Alias3).toDelegate, (RemoveTypedef!(Alias3) e) => to!uint16_t(e))(e); +} +uint16_t fromJsonString(T : AliasOfAlias)(string s) { + return parseJSON(s).fromJson!(AliasOfAlias); } alias Alias2 = Typedef!(int[], (int[]).init, "Alias2"); -JSONValue toJson(Alias2 e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e.unwrapAlias); +JSONValue toJson(T : Alias2)(int[] e) { + return _atd_write_list((&_atd_write_int).toDelegate)(e); +} +string toJsonString(T : Alias2)(int[] obj) { + return obj.toJson!(Alias2).toString; +} +int[] fromJson(T : Alias2)(JSONValue e) { + return _atd_read_list((&_atd_read_int).toDelegate)(e); } -Alias2 fromJson(T : Alias2)(JSONValue e) { - return _atd_read_list((&_atd_read_int).toDelegate)(e).wrapAlias!Alias2; +int[] fromJsonString(T : Alias2)(string s) { + return parseJSON(s).fromJson!(Alias2); } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index 43505a11..1180d04a 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -13,7 +13,7 @@ void setupTests() tests["simpleRecord"] = { auto record = IntFloatParametrizedRecord(32, [5.4, 3.3]); - auto json = record.toJson(); + auto json = record.toJson; auto recordFromJson = fromJson!IntFloatParametrizedRecord(json); assert(record == recordFromJson); @@ -36,7 +36,7 @@ void setupTests() auto str = "[\"hello\",2]"; auto p = str.fromJsonString!Pair; - assert(str == p.toJsonString); + assert(str == p.toJsonString!Pair); }; tests["invalidPairWrongType"] = { @@ -86,8 +86,7 @@ void setupTests() JSONValue(123) ]; obj.parametrized_record = IntFloatParametrizedRecord(42, [9.9f, 8.8f]); - obj.parametrized_tuple = KindParametrizedTuple( - tuple(Kind(WOW()), Kind(WOW()), 100)); + obj.parametrized_tuple = tuple(Kind(WOW()), Kind(WOW()), 100); auto jsonStr = obj.toJsonString; auto newObj = jsonStr.fromJsonString!Root; From f148dbb778f1436a18b2bb688eaba6e6611d63a9 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 6 Sep 2023 17:57:12 -0700 Subject: [PATCH 72/91] add auto everywhere to infer types --- atdd/src/lib/Codegen.ml | 45 ++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 21 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index eaceaf07..60a31658 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -197,28 +197,31 @@ import std.algorithm : map; import std.array : array; import std.conv; import std.format; -import std.functional; import std.json; import std.sumtype; +import std.traits : isCallable; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; +import std.functional; private { class AtdException : Exception { - this(string msg, string file = __FILE__, size_t line = __LINE__) + @safe this(string msg, string file = __FILE__, size_t line = __LINE__) { super(msg, file, line); } } - T _atd_missing_json_field(T)(string typeName, string jsonFieldName) + auto _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); + // hack so that the return type is the same as the field we are instantiating + return T.init; } // TODO check later if template is right way to go - AtdException _atd_bad_json(T)(string expectedType, T jsonValue) + auto _atd_bad_json(T)(string expectedType, T jsonValue) { string valueStr = jsonValue.to!string; if (valueStr.length > 200) @@ -232,7 +235,7 @@ private )); } - AtdException _atd_bad_d(T)(string expectedType, T jsonValue) + auto _atd_bad_d(T)(string expectedType, T jsonValue) { string valueStr = jsonValue.to!string; if (valueStr.length > 200) @@ -246,7 +249,7 @@ private )); } - typeof(null) _atd_read_unit(JSONValue x) + auto _atd_read_unit(JSONValue x) { if (x.isNull) return null; @@ -254,7 +257,7 @@ private throw _atd_bad_json("unit", x); } - bool _atd_read_bool(JSONValue x) + auto _atd_read_bool(JSONValue x) { try return x.boolean; @@ -262,7 +265,7 @@ private throw _atd_bad_json("bool", x); } - int _atd_read_int(JSONValue x) + auto _atd_read_int(JSONValue x) { try return cast(int) x.integer; @@ -270,15 +273,15 @@ private throw _atd_bad_json("int", x); } - float _atd_read_float(JSONValue x) + auto _atd_read_float(JSONValue x) { try - return x.floating; + return cast(float) x.floating; catch (JSONException e) throw _atd_bad_json("float", x); } - string _atd_read_string(JSONValue x) + auto _atd_read_string(JSONValue x) { try return x.str; @@ -286,7 +289,7 @@ private throw _atd_bad_json("string", x); } - auto _atd_read_list(T)(T delegate(JSONValue) readElements) + auto _atd_read_list(F)(F readElements) if (isCallable!(F)) { return (JSONValue jsonVal) { if (jsonVal.type != JSONType.array) @@ -380,27 +383,27 @@ private // this whole set of function could be remplaced by one templated _atd_write_value function // not sure it is what we want though - JSONValue _atd_write_unit(typeof(null) n) + auto _atd_write_unit(typeof(null) n) { return JSONValue(null); } - JSONValue _atd_write_bool(bool b) + auto _atd_write_bool(bool b) { return JSONValue(b); } - JSONValue _atd_write_int(int i) + auto _atd_write_int(int i) { return JSONValue(i); } - JSONValue _atd_write_float(float f) + auto _atd_write_float(float f) { return JSONValue(f); } - JSONValue _atd_write_string(string s) + auto _atd_write_string(string s) { return JSONValue(s); } @@ -483,24 +486,24 @@ private // # Public classes // ############################################################################ - T fromJsonString(T)(string s) + auto fromJsonString(T)(string s) { JSONValue res = parseJSON(s); return res.fromJson!T; } - string toJsonString(T)(T obj) + auto toJsonString(T)(T obj) { JSONValue res = obj.toJson!T; return res.toString; } - @safe TypedefType!T unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) + auto unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) { return cast(TypedefType!T) e; } - @safe T wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) + auto wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) { return cast(T) e; } From f87bf41def3fe0839521e7a03beae44ac3dc5287 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 6 Sep 2023 17:58:16 -0700 Subject: [PATCH 73/91] remove fixed size premable file --- atdd/src/lib/fixed_size_preamble.d | 302 -------------------------- atdd/test/dlang-expected/everything.d | 170 ++++++++------- 2 files changed, 88 insertions(+), 384 deletions(-) delete mode 100644 atdd/src/lib/fixed_size_preamble.d diff --git a/atdd/src/lib/fixed_size_preamble.d b/atdd/src/lib/fixed_size_preamble.d deleted file mode 100644 index d97a9183..00000000 --- a/atdd/src/lib/fixed_size_preamble.d +++ /dev/null @@ -1,302 +0,0 @@ -// Generated by atdd 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 -// ############################################################################ - -module fixed; - -import std.algorithm : map; -import std.array : array; -import std.conv; -import std.format; -import std.functional; -import std.json; -import std.sumtype; -import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; - -private -{ - class AtdException : Exception - { - this(string msg, string file = __FILE__, size_t line = __LINE__) - { - super(msg, file, line); - } - } - - T _atd_missing_json_field(T)(string typeName, string jsonFieldName) - { - throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); - } - - // TODO check later if template is right way to go - AtdException _atd_bad_json(T)(string expectedType, T jsonValue) - { - string valueStr = jsonValue.to!string; - if (valueStr.length > 200) - { - valueStr = valueStr[0 .. 200]; - } - - return new AtdException( - "incompatible JSON value where type '%%s' was expected: %%s".format( - expectedType, valueStr - )); - } - - AtdException _atd_bad_d(T)(string expectedType, T jsonValue) - { - string valueStr = jsonValue.to!string; - if (valueStr.length > 200) - { - valueStr = valueStr[0 .. 200]; - } - - return new AtdException( - "incompatible D value where type '%%s' was expected: %%s".format( - expectedType, valueStr - )); - } - - typeof(null) _atd_read_unit(JSONValue x) - { - if (x.isNull) - return null; - else - throw _atd_bad_json("unit", x); - } - - bool _atd_read_bool(JSONValue x) - { - try - return x.boolean; - catch (JSONException e) - throw _atd_bad_json("bool", x); - } - - int _atd_read_int(JSONValue x) - { - try - return cast(int) x.integer; - catch (JSONException e) - throw _atd_bad_json("int", x); - } - - float _atd_read_float(JSONValue x) - { - try - return x.floating; - catch (JSONException e) - throw _atd_bad_json("float", x); - } - - string _atd_read_string(JSONValue x) - { - try - return x.str; - catch (JSONException e) - throw _atd_bad_json("string", x); - } - - auto _atd_read_list(T)(T delegate(JSONValue) readElements) - { - return (JSONValue jsonVal) { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("array", jsonVal); - auto list = jsonVal.array; - return array(list.map!readElements()); - }; - } - - auto _atd_read_object_to_assoc_array(V)( - V delegate(JSONValue) readValue) - { - auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - V[string] ret; - foreach (key, val; jsonVal.object) - ret[key] = readValue(val); - return ret; - }; - return fun; - } - - auto _atd_read_array_to_assoc_dict(K, V)( - K delegate(JSONValue) readKey, - V delegate(JSONValue) readValue) - { - auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("list", jsonVal); - V[K] ret; - foreach (jsonInnerVal; jsonVal.array) - { - if (jsonInnerVal.type != JSONType.array) - throw _atd_bad_json("list", jsonInnerVal); - ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); - } - return ret; - }; - return fun; - } - - auto _atd_read_object_to_tuple_list(T)( - T delegate(JSONValue) readValue) - { - auto fun = (JSONValue jsonVal) { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - auto tupList = new Tuple!(string, T)[](jsonVal.object.length); - int i = 0; - foreach (key, val; jsonVal.object) - tupList[i++] = tuple(key, readValue(val)); - return tupList; - }; - return fun; - } - - auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) - { - auto fun = (JSONValue e) { - if (e.isNull) - return Nullable!T.init; - else - return Nullable!T(readElm(e)); - }; - return fun; - } - - auto _atd_read_option(T)(T delegate(JSONValue) readElm) - { - auto fun = (JSONValue e) { - if (e.type == JSONType.string && e.str == "None") - return Nullable!T.init; - else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e[1])); - else - throw _atd_bad_json("option", e); - }; - return fun; - } - - // this whole set of function could be remplaced by one templated _atd_write_value function - // not sure it is what we want though - - JSONValue _atd_write_unit(typeof(null) n) - { - return JSONValue(null); - } - - JSONValue _atd_write_bool(bool b) - { - return JSONValue(b); - } - - JSONValue _atd_write_int(int i) - { - return JSONValue(i); - } - - JSONValue _atd_write_float(float f) - { - return JSONValue(f); - } - - JSONValue _atd_write_string(string s) - { - return JSONValue(s); - } - - auto _atd_write_list(T)(JSONValue delegate(T) writeElm) - { - return (T[] list) { return JSONValue(array(list.map!writeElm())); }; - } - - auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) writeValue) - { - auto fun = (T[string] assocArr) { - JSONValue[string] ret; - foreach (key, val; assocArr) - ret[key] = writeValue(val); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) writeKey, - JSONValue delegate(V) writeValue) - { - auto fun = (V[K] assocArr) { - JSONValue[] ret; - foreach (key, val; assocArr) - ret ~= JSONValue([writeKey(key), writeValue(val)]); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) writeValue) - { - auto fun = (Tuple!(string, T)[] tupList) { - JSONValue[string] ret; - foreach (tup; tupList) - ret[tup[0]] = writeValue(tup[1]); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) - { - auto fun = (Nullable!T elm) { - if (elm.isNull) - return JSONValue(null); - else - return writeElm(elm.get); - }; - return fun; - } - - auto _atd_write_option(T)(JSONValue delegate(T) writeElm) - { - auto fun = (Nullable!T elm) { - if (elm.isNull) - return JSONValue("None"); - else - return JSONValue([JSONValue("Some"), writeElm(elm.get)]); - }; - return fun; - } -} -// ############################################################################ -// # Public classes -// ############################################################################ - -T fromJsonString(T)(string s) -{ - JSONValue res = parseJSON(s); - return res.fromJson!T; -} - -string toJsonString(T)(T obj) -{ - JSONValue res = obj.toJson; - return res.toString; -} - -TypedefType!T unwrapAlias(T)(T e) @safe -{ - return cast(TypedefType!T) e; -} - -T wrapAlias(T)(TypedefType!T e) @safe -{ - return cast(T) e; -} \ No newline at end of file diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index 06b411d4..391e81fc 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -13,28 +13,34 @@ import std.algorithm : map; import std.array : array; import std.conv; import std.format; -import std.functional; import std.json; import std.sumtype; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private { + + @trusted auto toDelegate(T)(T fn) + { + import std.functional; + return std.functional.toDelegate(fn); + } + class AtdException : Exception { - this(string msg, string file = __FILE__, size_t line = __LINE__) + @safe this(string msg, string file = __FILE__, size_t line = __LINE__) { super(msg, file, line); } } - T _atd_missing_json_field(T)(string typeName, string jsonFieldName) + auto _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); } // TODO check later if template is right way to go - AtdException _atd_bad_json(T)(string expectedType, T jsonValue) + auto _atd_bad_json(T)(string expectedType, T jsonValue) { string valueStr = jsonValue.to!string; if (valueStr.length > 200) @@ -48,7 +54,7 @@ private )); } - AtdException _atd_bad_d(T)(string expectedType, T jsonValue) + auto _atd_bad_d(T)(string expectedType, T jsonValue) { string valueStr = jsonValue.to!string; if (valueStr.length > 200) @@ -62,7 +68,7 @@ private )); } - typeof(null) _atd_read_unit(JSONValue x) + auto _atd_read_unit(JSONValue x) { if (x.isNull) return null; @@ -70,7 +76,7 @@ private throw _atd_bad_json("unit", x); } - bool _atd_read_bool(JSONValue x) + auto _atd_read_bool(JSONValue x) { try return x.boolean; @@ -78,7 +84,7 @@ private throw _atd_bad_json("bool", x); } - int _atd_read_int(JSONValue x) + auto _atd_read_int(JSONValue x) { try return cast(int) x.integer; @@ -86,15 +92,15 @@ private throw _atd_bad_json("int", x); } - float _atd_read_float(JSONValue x) + auto _atd_read_float(JSONValue x) { try - return x.floating; + return cast(float) x.floating; catch (JSONException e) throw _atd_bad_json("float", x); } - string _atd_read_string(JSONValue x) + auto _atd_read_string(JSONValue x) { try return x.str; @@ -196,38 +202,38 @@ private // this whole set of function could be remplaced by one templated _atd_write_value function // not sure it is what we want though - JSONValue _atd_write_unit(typeof(null) n) + auto _atd_write_unit(typeof(null) n) { return JSONValue(null); } - JSONValue _atd_write_bool(bool b) + auto _atd_write_bool(bool b) { return JSONValue(b); } - JSONValue _atd_write_int(int i) + auto _atd_write_int(int i) { return JSONValue(i); } - JSONValue _atd_write_float(float f) + auto _atd_write_float(float f) { return JSONValue(f); } - JSONValue _atd_write_string(string s) + auto _atd_write_string(string s) { return JSONValue(s); } - auto _atd_write_list(T)(JSONValue delegate(T) writeElm) + auto _atd_write_list(T)(JSONValue delegate(T) writeElm ) { return (T[] list) { return JSONValue(array(list.map!writeElm())); }; } auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) writeValue) + JSONValue delegate(T) writeValue ) { auto fun = (T[string] assocArr) { JSONValue[string] ret; @@ -239,8 +245,8 @@ private } auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) writeKey, - JSONValue delegate(V) writeValue) + JSONValue delegate(K) writeKey , + JSONValue delegate(V) writeValue ) { auto fun = (V[K] assocArr) { JSONValue[] ret; @@ -252,7 +258,7 @@ private } auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) writeValue) + JSONValue delegate(T) writeValue ) { auto fun = (Tuple!(string, T)[] tupList) { JSONValue[string] ret; @@ -263,7 +269,7 @@ private return fun; } - auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) + auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm ) { auto fun = (Nullable!T elm) { if (elm.isNull) @@ -274,7 +280,7 @@ private return fun; } - auto _atd_write_option(T)(JSONValue delegate(T) writeElm) + auto _atd_write_option(T)(JSONValue delegate(T) writeElm ) { auto fun = (Nullable!T elm) { if (elm.isNull) @@ -285,7 +291,7 @@ private return fun; } - auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) writeElm, Wrapped delegate(Unwrapped) wrap) + auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) writeElm , Wrapped delegate(Unwrapped) wrap ) { auto fun = (Unwrapped elm) { auto e = wrap(elm); @@ -299,24 +305,24 @@ private // # Public classes // ############################################################################ - T fromJsonString(T)(string s) + auto fromJsonString(T)(string s) { JSONValue res = parseJSON(s); return res.fromJson!T; } - string toJsonString(T)(T obj) + auto toJsonString(T)(T obj) { JSONValue res = obj.toJson!T; return res.toString; } - @safe TypedefType!T unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) + auto unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) { return cast(TypedefType!T) e; } - @safe T wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) + auto wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) { return cast(T) e; } @@ -341,14 +347,14 @@ struct RecursiveClass { RecursiveClass[] children; } -RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { + RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { RecursiveClass obj; obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field!(typeof(obj.id))("RecursiveClass", "id"); obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field!(typeof(obj.flag))("RecursiveClass", "flag"); obj.children = ("children" in x) ? _atd_read_list((&fromJson!RecursiveClass).toDelegate)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); return obj; } -JSONValue toJson(T : RecursiveClass)(T obj) { + JSONValue toJson(T : RecursiveClass)(T obj) { JSONValue res; res["id"] = _atd_write_int(obj.id); res["flag"] = _atd_write_bool(obj.flag); @@ -358,44 +364,44 @@ JSONValue toJson(T : RecursiveClass)(T obj) { alias St = Typedef!(int, (int).init, "St"); -JSONValue toJson(T : St)(int e) { + JSONValue toJson(T : St)(int e) { return _atd_write_int(e); } -string toJsonString(T : St)(int obj) { + string toJsonString(T : St)(int obj) { return obj.toJson!(St).toString; } -int fromJson(T : St)(JSONValue e) { + int fromJson(T : St)(JSONValue e) { return _atd_read_int(e); } -int fromJsonString(T : St)(string s) { + int fromJsonString(T : St)(string s) { return parseJSON(s).fromJson!(St); } // Original type: kind = [ ... | Root | ... ] struct Root_ {} -JSONValue toJson(T : Root_)(T e) { + JSONValue toJson(T : Root_)(T e) { return JSONValue("Root"); } // Original type: kind = [ ... | Thing of ... | ... ] struct Thing { int value; } -JSONValue toJson(T : Thing)(T e) { + JSONValue toJson(T : Thing)(T e) { return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); } // Original type: kind = [ ... | WOW | ... ] struct WOW {} -JSONValue toJson(T : WOW)(T e) { + JSONValue toJson(T : WOW)(T e) { return JSONValue("wow"); } // Original type: kind = [ ... | Amaze of ... | ... ] struct Amaze { string[] value; } -JSONValue toJson(T : Amaze)(T e) { + JSONValue toJson(T : Amaze)(T e) { return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); } @@ -432,80 +438,80 @@ JSONValue toJson(T : Kind)(T x) { alias Alias3 = Typedef!(uint32_t, (uint32_t).init, "Alias3"); -JSONValue toJson(T : Alias3)(uint32_t e) { + JSONValue toJson(T : Alias3)(uint32_t e) { return _atd_write_wrap((&_atd_write_int).toDelegate, (uint32_t e) => to!int(e))(e); } -string toJsonString(T : Alias3)(uint32_t obj) { + string toJsonString(T : Alias3)(uint32_t obj) { return obj.toJson!(Alias3).toString; } -uint32_t fromJson(T : Alias3)(JSONValue e) { + uint32_t fromJson(T : Alias3)(JSONValue e) { return _atd_read_wrap((&_atd_read_int).toDelegate, (int e) => to!uint32_t(e))(e); } -uint32_t fromJsonString(T : Alias3)(string s) { + uint32_t fromJsonString(T : Alias3)(string s) { return parseJSON(s).fromJson!(Alias3); } alias AliasOfAliasNotWrapped = Typedef!(RemoveTypedef!(Alias3), (RemoveTypedef!(Alias3)).init, "AliasOfAliasNotWrapped"); -JSONValue toJson(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) e) { + JSONValue toJson(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) e) { return ((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3))(e); } -string toJsonString(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) obj) { + string toJsonString(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) obj) { return obj.toJson!(AliasOfAliasNotWrapped).toString; } -RemoveTypedef!(Alias3) fromJson(T : AliasOfAliasNotWrapped)(JSONValue e) { + RemoveTypedef!(Alias3) fromJson(T : AliasOfAliasNotWrapped)(JSONValue e) { return fromJson!Alias3(e); } -RemoveTypedef!(Alias3) fromJsonString(T : AliasOfAliasNotWrapped)(string s) { + RemoveTypedef!(Alias3) fromJsonString(T : AliasOfAliasNotWrapped)(string s) { return parseJSON(s).fromJson!(AliasOfAliasNotWrapped); } alias AliasOfAliasOfAlias = Typedef!(RemoveTypedef!(AliasOfAliasNotWrapped), (RemoveTypedef!(AliasOfAliasNotWrapped)).init, "AliasOfAliasOfAlias"); -JSONValue toJson(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) e) { + JSONValue toJson(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) e) { return ((RemoveTypedef!(AliasOfAliasNotWrapped) x) => x.toJson!(AliasOfAliasNotWrapped))(e); } -string toJsonString(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) obj) { + string toJsonString(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) obj) { return obj.toJson!(AliasOfAliasOfAlias).toString; } -RemoveTypedef!(AliasOfAliasNotWrapped) fromJson(T : AliasOfAliasOfAlias)(JSONValue e) { + RemoveTypedef!(AliasOfAliasNotWrapped) fromJson(T : AliasOfAliasOfAlias)(JSONValue e) { return fromJson!AliasOfAliasNotWrapped(e); } -RemoveTypedef!(AliasOfAliasNotWrapped) fromJsonString(T : AliasOfAliasOfAlias)(string s) { + RemoveTypedef!(AliasOfAliasNotWrapped) fromJsonString(T : AliasOfAliasOfAlias)(string s) { return parseJSON(s).fromJson!(AliasOfAliasOfAlias); } alias Alias = Typedef!(int[], (int[]).init, "Alias"); -JSONValue toJson(T : Alias)(int[] e) { + JSONValue toJson(T : Alias)(int[] e) { return _atd_write_list((&_atd_write_int).toDelegate)(e); } -string toJsonString(T : Alias)(int[] obj) { + string toJsonString(T : Alias)(int[] obj) { return obj.toJson!(Alias).toString; } -int[] fromJson(T : Alias)(JSONValue e) { + int[] fromJson(T : Alias)(JSONValue e) { return _atd_read_list((&_atd_read_int).toDelegate)(e); } -int[] fromJsonString(T : Alias)(string s) { + int[] fromJsonString(T : Alias)(string s) { return parseJSON(s).fromJson!(Alias); } alias KindParametrizedTuple = Typedef!(Tuple!(Kind, Kind, int), (Tuple!(Kind, Kind, int)).init, "KindParametrizedTuple"); -JSONValue toJson(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) e) { + JSONValue toJson(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) e) { return ((Tuple!(Kind, Kind, int) x) => JSONValue([((RemoveTypedef!(Kind) x) => x.toJson!(Kind))(x[0]), ((RemoveTypedef!(Kind) x) => x.toJson!(Kind))(x[1]), _atd_write_int(x[2])]))(e); } -string toJsonString(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) obj) { + string toJsonString(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) obj) { return obj.toJson!(KindParametrizedTuple).toString; } -Tuple!(Kind, Kind, int) fromJson(T : KindParametrizedTuple)(JSONValue e) { + Tuple!(Kind, Kind, int) fromJson(T : KindParametrizedTuple)(JSONValue e) { return ((JSONValue x) { if (x.type != JSONType.array || x.array.length != 3) throw _atd_bad_json("Tuple of size 3", x); return tuple(fromJson!Kind(x[0]), fromJson!Kind(x[1]), _atd_read_int(x[2])); })(e); } -Tuple!(Kind, Kind, int) fromJsonString(T : KindParametrizedTuple)(string s) { + Tuple!(Kind, Kind, int) fromJsonString(T : KindParametrizedTuple)(string s) { return parseJSON(s).fromJson!(KindParametrizedTuple); } @@ -515,13 +521,13 @@ struct IntFloatParametrizedRecord { float[] field_b = []; } -IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { + IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { IntFloatParametrizedRecord obj; obj.field_a = ("field_a" in x) ? _atd_read_int(x["field_a"]) : _atd_missing_json_field!(typeof(obj.field_a))("IntFloatParametrizedRecord", "field_a"); obj.field_b = ("field_b" in x) ? _atd_read_list((&_atd_read_float).toDelegate)(x["field_b"]) : []; return obj; } -JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { + JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { JSONValue res; res["field_a"] = _atd_write_int(obj.field_a); res["field_b"] = _atd_write_list((&_atd_write_float).toDelegate)(obj.field_b); @@ -556,7 +562,7 @@ struct Root { RemoveTypedef!(AliasOfAliasOfAlias) aaa; } -Root fromJson(T : Root)(JSONValue x) { + Root fromJson(T : Root)(JSONValue x) { Root obj; obj.id = ("ID" in x) ? _atd_read_string(x["ID"]) : _atd_missing_json_field!(typeof(obj.id))("Root", "ID"); obj.await = ("await" in x) ? _atd_read_bool(x["await"]) : _atd_missing_json_field!(typeof(obj.await))("Root", "await"); @@ -592,7 +598,7 @@ Root fromJson(T : Root)(JSONValue x) { obj.aaa = ("aaa" in x) ? fromJson!AliasOfAliasOfAlias(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); return obj; } -JSONValue toJson(T : Root)(T obj) { + JSONValue toJson(T : Root)(T obj) { JSONValue res; res["ID"] = _atd_write_string(obj.id); res["await"] = _atd_write_bool(obj.await); @@ -627,12 +633,12 @@ struct RequireField { string req; } -RequireField fromJson(T : RequireField)(JSONValue x) { + RequireField fromJson(T : RequireField)(JSONValue x) { RequireField obj; obj.req = ("req" in x) ? _atd_read_string(x["req"]) : _atd_missing_json_field!(typeof(obj.req))("RequireField", "req"); return obj; } -JSONValue toJson(T : RequireField)(T obj) { + JSONValue toJson(T : RequireField)(T obj) { JSONValue res; res["req"] = _atd_write_string(obj.req); return res; @@ -643,12 +649,12 @@ struct RecordWithWrappedType { int item; } -RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { + RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { RecordWithWrappedType obj; obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!(int)(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); return obj; } -JSONValue toJson(T : RecordWithWrappedType)(T obj) { + JSONValue toJson(T : RecordWithWrappedType)(T obj) { JSONValue res; res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!(string)(e))(obj.item); return res; @@ -656,34 +662,34 @@ JSONValue toJson(T : RecordWithWrappedType)(T obj) { alias Pair = Typedef!(Tuple!(string, int), (Tuple!(string, int)).init, "Pair"); -JSONValue toJson(T : Pair)(Tuple!(string, int) e) { + JSONValue toJson(T : Pair)(Tuple!(string, int) e) { return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); } -string toJsonString(T : Pair)(Tuple!(string, int) obj) { + string toJsonString(T : Pair)(Tuple!(string, int) obj) { return obj.toJson!(Pair).toString; } -Tuple!(string, int) fromJson(T : Pair)(JSONValue e) { + Tuple!(string, int) fromJson(T : Pair)(JSONValue e) { return ((JSONValue x) { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_string(x[0]), _atd_read_int(x[1])); })(e); } -Tuple!(string, int) fromJsonString(T : Pair)(string s) { + Tuple!(string, int) fromJsonString(T : Pair)(string s) { return parseJSON(s).fromJson!(Pair); } // Original type: frozen = [ ... | A | ... ] struct A {} -JSONValue toJson(T : A)(T e) { + JSONValue toJson(T : A)(T e) { return JSONValue("A"); } // Original type: frozen = [ ... | B of ... | ... ] struct B { int value; } -JSONValue toJson(T : B)(T e) { + JSONValue toJson(T : B)(T e) { return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); } @@ -717,12 +723,12 @@ struct DefaultList { int[] items = []; } -DefaultList fromJson(T : DefaultList)(JSONValue x) { + DefaultList fromJson(T : DefaultList)(JSONValue x) { DefaultList obj; obj.items = ("items" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["items"]) : []; return obj; } -JSONValue toJson(T : DefaultList)(T obj) { + JSONValue toJson(T : DefaultList)(T obj) { JSONValue res; res["items"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.items); return res; @@ -730,30 +736,30 @@ JSONValue toJson(T : DefaultList)(T obj) { alias AliasOfAlias = Typedef!(uint16_t, (uint16_t).init, "AliasOfAlias"); -JSONValue toJson(T : AliasOfAlias)(uint16_t e) { + JSONValue toJson(T : AliasOfAlias)(uint16_t e) { return _atd_write_wrap(((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3)), (uint16_t e) => to!uint32_t(e))(e); } -string toJsonString(T : AliasOfAlias)(uint16_t obj) { + string toJsonString(T : AliasOfAlias)(uint16_t obj) { return obj.toJson!(AliasOfAlias).toString; } -uint16_t fromJson(T : AliasOfAlias)(JSONValue e) { + uint16_t fromJson(T : AliasOfAlias)(JSONValue e) { return _atd_read_wrap((&fromJson!Alias3).toDelegate, (RemoveTypedef!(Alias3) e) => to!uint16_t(e))(e); } -uint16_t fromJsonString(T : AliasOfAlias)(string s) { + uint16_t fromJsonString(T : AliasOfAlias)(string s) { return parseJSON(s).fromJson!(AliasOfAlias); } alias Alias2 = Typedef!(int[], (int[]).init, "Alias2"); -JSONValue toJson(T : Alias2)(int[] e) { + JSONValue toJson(T : Alias2)(int[] e) { return _atd_write_list((&_atd_write_int).toDelegate)(e); } -string toJsonString(T : Alias2)(int[] obj) { + string toJsonString(T : Alias2)(int[] obj) { return obj.toJson!(Alias2).toString; } -int[] fromJson(T : Alias2)(JSONValue e) { + int[] fromJson(T : Alias2)(JSONValue e) { return _atd_read_list((&_atd_read_int).toDelegate)(e); } -int[] fromJsonString(T : Alias2)(string s) { + int[] fromJsonString(T : Alias2)(string s) { return parseJSON(s).fromJson!(Alias2); } From e83ba263ba5e599cb5458bc1ee8f8e7a8954dba8 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 6 Sep 2023 18:05:12 -0700 Subject: [PATCH 74/91] add readme --- atdd/README.md | 46 +++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 45 insertions(+), 1 deletion(-) diff --git a/atdd/README.md b/atdd/README.md index cc43d7bd..fa786783 100644 --- a/atdd/README.md +++ b/atdd/README.md @@ -1,4 +1,48 @@ Atdd == -Requires ldc >= 1.27.0 \ No newline at end of file +Atdd takes type definitions in the ATD format and derives `dlang` +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 D output [everything.d](test/dlang-expected/everything.d). + +Requirements +-- + +Requirements for building and testing `atdd`: +* Opam and dependencies installed from the [`atd` project root](..) + with `make setup`. +* ldc >= 1.27.0 + +Requirements for generating D code: +* the `atdd` executable + +Requirements for compiling the generated D code: +* ldc >= 1.27.0 + +Documentation +-- + +* TODO + +Development notes +-- + +Build or rebuild with `make`. Test with `make test`. This requires +ldc2. + +Running the tests is done from the `atdd/` main folder with `make +test`. + +We have two kinds of tests for atdpy: +* [unit tests](src/test) for testing internal OCaml code +* code generation and D tests: + * they generate D code from ATD files and compare the D output + against the [expectations](dlang-expected). + * the generated code is executed by some tests. From e775b21f6846c0af251988a110cc4d4cb9bc3b0b Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 6 Sep 2023 18:27:06 -0700 Subject: [PATCH 75/91] edit main readme --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 110bd5f9..ece26133 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ Target languages currently supported: * Python: [atdpy](atdpy) * Scala: [atds](atds) * TypeScript: [atdts](atdts) +* DLang: [atdd](atdd) All can installed with opam e.g. ``` From 729ad7d56b7a1d9b65df8613715b732f59ec9247 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 11:40:49 -0700 Subject: [PATCH 76/91] make toJson and fromJson callable from @safe code --- atdd/src/lib/Codegen.ml | 113 ++++++------- atdd/test/dlang-expected/everything.d | 220 +++++++++++++------------- atdd/test/dlang-tests/test_atdd.d | 10 +- 3 files changed, 176 insertions(+), 167 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 60a31658..e0f83842 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -201,8 +201,7 @@ import std.json; import std.sumtype; import std.traits : isCallable; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; -import std.functional; - + private { class AtdException : Exception @@ -212,6 +211,21 @@ private super(msg, file, line); } } + + // workaround to make toDelegate callable from safe + @trusted auto toDelegate(F)(auto ref F fp) if (isCallable!F) + { + import std.functional; + return std.functional.toDelegate(fp); + } + + template RemoveTypedef(T) + { + static if (is(T : Typedef!Arg, Arg)) + alias RemoveTypedef = RemoveTypedef!Arg; + else + alias RemoveTypedef = T; + } auto _atd_missing_json_field(T)(string typeName, string jsonFieldName) { @@ -220,7 +234,6 @@ private return T.init; } - // TODO check later if template is right way to go auto _atd_bad_json(T)(string expectedType, T jsonValue) { string valueStr = jsonValue.to!string; @@ -289,9 +302,9 @@ private throw _atd_bad_json("string", x); } - auto _atd_read_list(F)(F readElements) if (isCallable!(F)) + auto _atd_read_list(E)(E delegate(JSONValue) @safe readElements) { - return (JSONValue jsonVal) { + return (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.array) throw _atd_bad_json("array", jsonVal); auto list = jsonVal.array; @@ -300,9 +313,9 @@ private } auto _atd_read_object_to_assoc_array(V)( - V delegate(JSONValue) readValue) + V delegate(JSONValue) @safe readValue) { - auto fun = (JSONValue jsonVal) { + auto fun = (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.object) throw _atd_bad_json("object", jsonVal); V[string] ret; @@ -314,10 +327,10 @@ private } auto _atd_read_array_to_assoc_dict(K, V)( - K delegate(JSONValue) readKey, - V delegate(JSONValue) readValue) + K delegate(JSONValue) @safe readKey, + V delegate(JSONValue) @safe readValue) { - auto fun = (JSONValue jsonVal) { + auto fun = (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; @@ -333,9 +346,9 @@ private } auto _atd_read_object_to_tuple_list(T)( - T delegate(JSONValue) readValue) + T delegate(JSONValue) @safe readValue) { - auto fun = (JSONValue jsonVal) { + auto fun = (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.object) throw _atd_bad_json("object", jsonVal); auto tupList = new Tuple!(string, T)[](jsonVal.object.length); @@ -347,9 +360,9 @@ private return fun; } - auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) + auto _atd_read_nullable(T)(T delegate(JSONValue) @safe readElm) { - auto fun = (JSONValue e) { + auto fun = (JSONValue e) @safe { if (e.isNull) return Nullable!T.init; else @@ -358,9 +371,9 @@ private return fun; } - auto _atd_read_option(T)(T delegate(JSONValue) readElm) + auto _atd_read_option(T)(T delegate(JSONValue) @safe readElm) { - auto fun = (JSONValue e) { + auto fun = (JSONValue e) @trusted { if (e.type == JSONType.string && e.str == "None") return Nullable!T.init; else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") @@ -371,9 +384,9 @@ private return fun; } - auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) readElm, Unwrapped delegate(Wrapped) unwrap) + auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) @safe readElm, Unwrapped delegate(Wrapped) @safe unwrap) { - auto fun = (JSONValue e) { + auto fun = (JSONValue e) @safe { auto elm = readElm(e); return unwrap(elm); }; @@ -408,15 +421,15 @@ private return JSONValue(s); } - auto _atd_write_list(T)(JSONValue delegate(T) writeElm) + auto _atd_write_list(T)(JSONValue delegate(T) @safe writeElm) { - return (T[] list) { return JSONValue(array(list.map!writeElm())); }; + return (T[] list) @safe { return JSONValue(array(list.map!writeElm())); }; } auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) writeValue) + JSONValue delegate(T) @safe writeValue) { - auto fun = (T[string] assocArr) { + auto fun = (T[string] assocArr) @safe { JSONValue[string] ret; foreach (key, val; assocArr) ret[key] = writeValue(val); @@ -426,10 +439,10 @@ private } auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) writeKey, - JSONValue delegate(V) writeValue) + JSONValue delegate(K) @safe writeKey, + JSONValue delegate(V) @safe writeValue) { - auto fun = (V[K] assocArr) { + auto fun = (V[K] assocArr) @safe { JSONValue[] ret; foreach (key, val; assocArr) ret ~= JSONValue([writeKey(key), writeValue(val)]); @@ -439,9 +452,9 @@ private } auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) writeValue) + JSONValue delegate(T) @safe writeValue) { - auto fun = (Tuple!(string, T)[] tupList) { + auto fun = (Tuple!(string, T)[] tupList) @safe { JSONValue[string] ret; foreach (tup; tupList) ret[tup[0]] = writeValue(tup[1]); @@ -450,9 +463,9 @@ private return fun; } - auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm) + auto _atd_write_nullable(T)(JSONValue delegate(T) @safe writeElm) { - auto fun = (Nullable!T elm) { + auto fun = (Nullable!T elm) @safe { if (elm.isNull) return JSONValue(null); else @@ -461,9 +474,9 @@ private return fun; } - auto _atd_write_option(T)(JSONValue delegate(T) writeElm) + auto _atd_write_option(T)(JSONValue delegate(T) @safe writeElm) { - auto fun = (Nullable!T elm) { + auto fun = (Nullable!T elm) @safe { if (elm.isNull) return JSONValue("None"); else @@ -472,9 +485,9 @@ private return fun; } - auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) writeElm, Wrapped delegate(Unwrapped) wrap) + auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) @safe writeElm, Wrapped delegate(Unwrapped) @safe wrap) { - auto fun = (Unwrapped elm) { + auto fun = (Unwrapped elm) @safe { auto e = wrap(elm); return writeElm(e); }; @@ -508,14 +521,6 @@ private return cast(T) e; } - template RemoveTypedef(T) - { - static if (is(T : Typedef!Arg, Arg)) - alias RemoveTypedef = RemoveTypedef!Arg; - else - alias RemoveTypedef = T; - } - |} atd_filename atd_filename @@ -804,7 +809,7 @@ and tuple_reader env cells = ) cells |> String.concat ", " in - sprintf "((JSONValue x) { + sprintf "((JSONValue x) @trusted { if (x.type != JSONType.array || x.array.length != %d) throw _atd_bad_json(\"Tuple of size %d\", x); return tuple(%s); @@ -876,8 +881,8 @@ let record env loc name (fields : field list) an = ) fields in let from_json = [ - Line (sprintf "%s fromJson(T : %s)(JSONValue x) {" - (single_esc dlang_struct_name) (single_esc dlang_struct_name)); + Line (sprintf "@trusted %s fromJson(T : %s)(JSONValue x) {" + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ Line (sprintf "%s obj;" dlang_struct_name); Inline from_json_class_arguments; @@ -888,7 +893,7 @@ let record env loc name (fields : field list) an = in let to_json = [ - Line (sprintf "JSONValue toJson(T : %s)(T obj) {" (single_esc dlang_struct_name)); + Line (sprintf "@trusted JSONValue toJson(T : %s)(T obj) {" (single_esc dlang_struct_name)); Block [ Line ("JSONValue res;"); Inline json_object_body; @@ -913,16 +918,16 @@ let alias_wrapper env name type_expr = let value_type = type_name_of_expr ~aliasing:None_ env type_expr in [ Line (sprintf "alias %s = Typedef!(%s, (%s).init, \"%s\");" dlang_struct_name value_type value_type dlang_struct_name); - Line (sprintf "JSONValue toJson(T : %s)(%s e) {" dlang_struct_name value_type); + Line (sprintf "@trusted JSONValue toJson(T : %s)(%s e) {" dlang_struct_name value_type); Block [Line(sprintf "return %s(e);" (json_writer env type_expr))]; Line("}"); - Line (sprintf "string toJsonString(T : %s)(%s obj) {" dlang_struct_name value_type); + Line (sprintf "@trusted string toJsonString(T : %s)(%s obj) {" dlang_struct_name value_type); Block [Line(sprintf "return obj.toJson!(%s).toString;" dlang_struct_name)]; Line("}"); - Line (sprintf "%s fromJson(T : %s)(JSONValue e) {" value_type dlang_struct_name); + Line (sprintf "@trusted %s fromJson(T : %s)(JSONValue e) {" value_type dlang_struct_name); Block [Line(sprintf "return %s(e);" (json_reader env type_expr))]; Line("}"); - Line (sprintf "%s fromJsonString(T : %s)(string s) {" value_type dlang_struct_name); + Line (sprintf "@trusted %s fromJsonString(T : %s)(string s) {" value_type dlang_struct_name); Block [Line(sprintf "return parseJSON(s).fromJson!(%s);" dlang_struct_name)]; Line("}"); ] @@ -937,7 +942,7 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s {}" (trans env unique_name)); - Line (sprintf "JSONValue toJson(T : %s)(T e) {" (trans env unique_name)); + Line (sprintf "@trusted JSONValue toJson(T : %s)(T e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue(\"%s\");" (single_esc json_name))]; Line("}"); ] @@ -947,7 +952,7 @@ let case_class env type_name type_name orig_name); Line (sprintf "struct %s { %s value; }" (trans env unique_name) (type_name_of_expr env e)); (* TODO : very dubious*) - Line (sprintf "JSONValue toJson(T : %s)(T e) {" (trans env unique_name)); + Line (sprintf "@trusted JSONValue toJson(T : %s)(T e) {" (trans env unique_name)); Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e.value)]);" (single_esc json_name) (json_writer env e))]; Line("}"); ] @@ -1038,8 +1043,8 @@ let sum_container env loc name cases = [ Line (sprintf "alias %s = SumType!(%s);" dlang_struct_name type_list); Line ""; - Line (sprintf "%s fromJson(T : %s)(JSONValue x) {" - (single_esc dlang_struct_name) (single_esc dlang_struct_name)); + Line (sprintf "@trusted %s fromJson(T : %s)(JSONValue x) {" + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); Block [ Inline cases0_block; Inline cases1_block; @@ -1048,7 +1053,7 @@ let sum_container env loc name cases = ]; Line "}"; Line ""; - Line (sprintf "JSONValue toJson(T : %s)(T x) {" (dlang_struct_name)); + Line (sprintf "@trusted JSONValue toJson(T : %s)(T x) {" (dlang_struct_name)); Block [ Line "return x.match!("; Line ( diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index 391e81fc..fbe42eef 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -15,17 +15,11 @@ import std.conv; import std.format; import std.json; import std.sumtype; +import std.traits : isCallable; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; - + private { - - @trusted auto toDelegate(T)(T fn) - { - import std.functional; - return std.functional.toDelegate(fn); - } - class AtdException : Exception { @safe this(string msg, string file = __FILE__, size_t line = __LINE__) @@ -33,13 +27,29 @@ private super(msg, file, line); } } + + // workaround to make toDelegate callable from safe + @trusted auto toDelegate(F)(auto ref F fp) if (isCallable!F) + { + import std.functional; + return std.functional.toDelegate(fp); + } + + template RemoveTypedef(T) + { + static if (is(T : Typedef!Arg, Arg)) + alias RemoveTypedef = RemoveTypedef!Arg; + else + alias RemoveTypedef = T; + } auto _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); + // hack so that the return type is the same as the field we are instantiating + return T.init; } - // TODO check later if template is right way to go auto _atd_bad_json(T)(string expectedType, T jsonValue) { string valueStr = jsonValue.to!string; @@ -108,9 +118,9 @@ private throw _atd_bad_json("string", x); } - auto _atd_read_list(T)(T delegate(JSONValue) readElements) + auto _atd_read_list(E)(E delegate(JSONValue) @safe readElements) { - return (JSONValue jsonVal) { + return (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.array) throw _atd_bad_json("array", jsonVal); auto list = jsonVal.array; @@ -119,9 +129,9 @@ private } auto _atd_read_object_to_assoc_array(V)( - V delegate(JSONValue) readValue) + V delegate(JSONValue) @safe readValue) { - auto fun = (JSONValue jsonVal) { + auto fun = (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.object) throw _atd_bad_json("object", jsonVal); V[string] ret; @@ -133,10 +143,10 @@ private } auto _atd_read_array_to_assoc_dict(K, V)( - K delegate(JSONValue) readKey, - V delegate(JSONValue) readValue) + K delegate(JSONValue) @safe readKey, + V delegate(JSONValue) @safe readValue) { - auto fun = (JSONValue jsonVal) { + auto fun = (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.array) throw _atd_bad_json("list", jsonVal); V[K] ret; @@ -152,9 +162,9 @@ private } auto _atd_read_object_to_tuple_list(T)( - T delegate(JSONValue) readValue) + T delegate(JSONValue) @safe readValue) { - auto fun = (JSONValue jsonVal) { + auto fun = (JSONValue jsonVal) @trusted { if (jsonVal.type != JSONType.object) throw _atd_bad_json("object", jsonVal); auto tupList = new Tuple!(string, T)[](jsonVal.object.length); @@ -166,9 +176,9 @@ private return fun; } - auto _atd_read_nullable(T)(T delegate(JSONValue) readElm) + auto _atd_read_nullable(T)(T delegate(JSONValue) @safe readElm) { - auto fun = (JSONValue e) { + auto fun = (JSONValue e) @safe { if (e.isNull) return Nullable!T.init; else @@ -177,9 +187,9 @@ private return fun; } - auto _atd_read_option(T)(T delegate(JSONValue) readElm) + auto _atd_read_option(T)(T delegate(JSONValue) @safe readElm) { - auto fun = (JSONValue e) { + auto fun = (JSONValue e) @trusted { if (e.type == JSONType.string && e.str == "None") return Nullable!T.init; else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") @@ -190,9 +200,9 @@ private return fun; } - auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) readElm, Unwrapped delegate(Wrapped) unwrap) + auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) @safe readElm, Unwrapped delegate(Wrapped) @safe unwrap) { - auto fun = (JSONValue e) { + auto fun = (JSONValue e) @safe { auto elm = readElm(e); return unwrap(elm); }; @@ -227,15 +237,15 @@ private return JSONValue(s); } - auto _atd_write_list(T)(JSONValue delegate(T) writeElm ) + auto _atd_write_list(T)(JSONValue delegate(T) @safe writeElm) { - return (T[] list) { return JSONValue(array(list.map!writeElm())); }; + return (T[] list) @safe { return JSONValue(array(list.map!writeElm())); }; } auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) writeValue ) + JSONValue delegate(T) @safe writeValue) { - auto fun = (T[string] assocArr) { + auto fun = (T[string] assocArr) @safe { JSONValue[string] ret; foreach (key, val; assocArr) ret[key] = writeValue(val); @@ -245,10 +255,10 @@ private } auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) writeKey , - JSONValue delegate(V) writeValue ) + JSONValue delegate(K) @safe writeKey, + JSONValue delegate(V) @safe writeValue) { - auto fun = (V[K] assocArr) { + auto fun = (V[K] assocArr) @safe { JSONValue[] ret; foreach (key, val; assocArr) ret ~= JSONValue([writeKey(key), writeValue(val)]); @@ -258,9 +268,9 @@ private } auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) writeValue ) + JSONValue delegate(T) @safe writeValue) { - auto fun = (Tuple!(string, T)[] tupList) { + auto fun = (Tuple!(string, T)[] tupList) @safe { JSONValue[string] ret; foreach (tup; tupList) ret[tup[0]] = writeValue(tup[1]); @@ -269,9 +279,9 @@ private return fun; } - auto _atd_write_nullable(T)(JSONValue delegate(T) writeElm ) + auto _atd_write_nullable(T)(JSONValue delegate(T) @safe writeElm) { - auto fun = (Nullable!T elm) { + auto fun = (Nullable!T elm) @safe { if (elm.isNull) return JSONValue(null); else @@ -280,9 +290,9 @@ private return fun; } - auto _atd_write_option(T)(JSONValue delegate(T) writeElm ) + auto _atd_write_option(T)(JSONValue delegate(T) @safe writeElm) { - auto fun = (Nullable!T elm) { + auto fun = (Nullable!T elm) @safe { if (elm.isNull) return JSONValue("None"); else @@ -291,9 +301,9 @@ private return fun; } - auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) writeElm , Wrapped delegate(Unwrapped) wrap ) + auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) @safe writeElm, Wrapped delegate(Unwrapped) @safe wrap) { - auto fun = (Unwrapped elm) { + auto fun = (Unwrapped elm) @safe { auto e = wrap(elm); return writeElm(e); }; @@ -327,14 +337,6 @@ private return cast(T) e; } - template RemoveTypedef(T) - { - static if (is(T : Typedef!Arg, Arg)) - alias RemoveTypedef = RemoveTypedef!Arg; - else - alias RemoveTypedef = T; - } - @@ -347,14 +349,14 @@ struct RecursiveClass { RecursiveClass[] children; } - RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { +@trusted RecursiveClass fromJson(T : RecursiveClass)(JSONValue x) { RecursiveClass obj; obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field!(typeof(obj.id))("RecursiveClass", "id"); obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field!(typeof(obj.flag))("RecursiveClass", "flag"); obj.children = ("children" in x) ? _atd_read_list((&fromJson!RecursiveClass).toDelegate)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); return obj; } - JSONValue toJson(T : RecursiveClass)(T obj) { +@trusted JSONValue toJson(T : RecursiveClass)(T obj) { JSONValue res; res["id"] = _atd_write_int(obj.id); res["flag"] = _atd_write_bool(obj.flag); @@ -364,51 +366,51 @@ struct RecursiveClass { alias St = Typedef!(int, (int).init, "St"); - JSONValue toJson(T : St)(int e) { +@trusted JSONValue toJson(T : St)(int e) { return _atd_write_int(e); } - string toJsonString(T : St)(int obj) { +@trusted string toJsonString(T : St)(int obj) { return obj.toJson!(St).toString; } - int fromJson(T : St)(JSONValue e) { +@trusted int fromJson(T : St)(JSONValue e) { return _atd_read_int(e); } - int fromJsonString(T : St)(string s) { +@trusted int fromJsonString(T : St)(string s) { return parseJSON(s).fromJson!(St); } // Original type: kind = [ ... | Root | ... ] struct Root_ {} - JSONValue toJson(T : Root_)(T e) { +@trusted JSONValue toJson(T : Root_)(T e) { return JSONValue("Root"); } // Original type: kind = [ ... | Thing of ... | ... ] struct Thing { int value; } - JSONValue toJson(T : Thing)(T e) { +@trusted JSONValue toJson(T : Thing)(T e) { return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); } // Original type: kind = [ ... | WOW | ... ] struct WOW {} - JSONValue toJson(T : WOW)(T e) { +@trusted JSONValue toJson(T : WOW)(T e) { return JSONValue("wow"); } // Original type: kind = [ ... | Amaze of ... | ... ] struct Amaze { string[] value; } - JSONValue toJson(T : Amaze)(T e) { +@trusted JSONValue toJson(T : Amaze)(T e) { return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); } alias Kind = SumType!(Root_, Thing, WOW, Amaze); -Kind fromJson(T : Kind)(JSONValue x) { +@trusted Kind fromJson(T : Kind)(JSONValue x) { if (x.type == JSONType.string) { if (x.str == "Root") return Kind(Root_()); @@ -427,7 +429,7 @@ Kind fromJson(T : Kind)(JSONValue x) { throw _atd_bad_json("Kind", x); } -JSONValue toJson(T : Kind)(T x) { +@trusted JSONValue toJson(T : Kind)(T x) { return x.match!( (Root_ v) => v.toJson!(Root_), (Thing v) => v.toJson!(Thing), @@ -438,80 +440,80 @@ JSONValue toJson(T : Kind)(T x) { alias Alias3 = Typedef!(uint32_t, (uint32_t).init, "Alias3"); - JSONValue toJson(T : Alias3)(uint32_t e) { +@trusted JSONValue toJson(T : Alias3)(uint32_t e) { return _atd_write_wrap((&_atd_write_int).toDelegate, (uint32_t e) => to!int(e))(e); } - string toJsonString(T : Alias3)(uint32_t obj) { +@trusted string toJsonString(T : Alias3)(uint32_t obj) { return obj.toJson!(Alias3).toString; } - uint32_t fromJson(T : Alias3)(JSONValue e) { +@trusted uint32_t fromJson(T : Alias3)(JSONValue e) { return _atd_read_wrap((&_atd_read_int).toDelegate, (int e) => to!uint32_t(e))(e); } - uint32_t fromJsonString(T : Alias3)(string s) { +@trusted uint32_t fromJsonString(T : Alias3)(string s) { return parseJSON(s).fromJson!(Alias3); } alias AliasOfAliasNotWrapped = Typedef!(RemoveTypedef!(Alias3), (RemoveTypedef!(Alias3)).init, "AliasOfAliasNotWrapped"); - JSONValue toJson(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) e) { +@trusted JSONValue toJson(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) e) { return ((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3))(e); } - string toJsonString(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) obj) { +@trusted string toJsonString(T : AliasOfAliasNotWrapped)(RemoveTypedef!(Alias3) obj) { return obj.toJson!(AliasOfAliasNotWrapped).toString; } - RemoveTypedef!(Alias3) fromJson(T : AliasOfAliasNotWrapped)(JSONValue e) { +@trusted RemoveTypedef!(Alias3) fromJson(T : AliasOfAliasNotWrapped)(JSONValue e) { return fromJson!Alias3(e); } - RemoveTypedef!(Alias3) fromJsonString(T : AliasOfAliasNotWrapped)(string s) { +@trusted RemoveTypedef!(Alias3) fromJsonString(T : AliasOfAliasNotWrapped)(string s) { return parseJSON(s).fromJson!(AliasOfAliasNotWrapped); } alias AliasOfAliasOfAlias = Typedef!(RemoveTypedef!(AliasOfAliasNotWrapped), (RemoveTypedef!(AliasOfAliasNotWrapped)).init, "AliasOfAliasOfAlias"); - JSONValue toJson(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) e) { +@trusted JSONValue toJson(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) e) { return ((RemoveTypedef!(AliasOfAliasNotWrapped) x) => x.toJson!(AliasOfAliasNotWrapped))(e); } - string toJsonString(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) obj) { +@trusted string toJsonString(T : AliasOfAliasOfAlias)(RemoveTypedef!(AliasOfAliasNotWrapped) obj) { return obj.toJson!(AliasOfAliasOfAlias).toString; } - RemoveTypedef!(AliasOfAliasNotWrapped) fromJson(T : AliasOfAliasOfAlias)(JSONValue e) { +@trusted RemoveTypedef!(AliasOfAliasNotWrapped) fromJson(T : AliasOfAliasOfAlias)(JSONValue e) { return fromJson!AliasOfAliasNotWrapped(e); } - RemoveTypedef!(AliasOfAliasNotWrapped) fromJsonString(T : AliasOfAliasOfAlias)(string s) { +@trusted RemoveTypedef!(AliasOfAliasNotWrapped) fromJsonString(T : AliasOfAliasOfAlias)(string s) { return parseJSON(s).fromJson!(AliasOfAliasOfAlias); } alias Alias = Typedef!(int[], (int[]).init, "Alias"); - JSONValue toJson(T : Alias)(int[] e) { +@trusted JSONValue toJson(T : Alias)(int[] e) { return _atd_write_list((&_atd_write_int).toDelegate)(e); } - string toJsonString(T : Alias)(int[] obj) { +@trusted string toJsonString(T : Alias)(int[] obj) { return obj.toJson!(Alias).toString; } - int[] fromJson(T : Alias)(JSONValue e) { +@trusted int[] fromJson(T : Alias)(JSONValue e) { return _atd_read_list((&_atd_read_int).toDelegate)(e); } - int[] fromJsonString(T : Alias)(string s) { +@trusted int[] fromJsonString(T : Alias)(string s) { return parseJSON(s).fromJson!(Alias); } alias KindParametrizedTuple = Typedef!(Tuple!(Kind, Kind, int), (Tuple!(Kind, Kind, int)).init, "KindParametrizedTuple"); - JSONValue toJson(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) e) { +@trusted JSONValue toJson(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) e) { return ((Tuple!(Kind, Kind, int) x) => JSONValue([((RemoveTypedef!(Kind) x) => x.toJson!(Kind))(x[0]), ((RemoveTypedef!(Kind) x) => x.toJson!(Kind))(x[1]), _atd_write_int(x[2])]))(e); } - string toJsonString(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) obj) { +@trusted string toJsonString(T : KindParametrizedTuple)(Tuple!(Kind, Kind, int) obj) { return obj.toJson!(KindParametrizedTuple).toString; } - Tuple!(Kind, Kind, int) fromJson(T : KindParametrizedTuple)(JSONValue e) { - return ((JSONValue x) { +@trusted Tuple!(Kind, Kind, int) fromJson(T : KindParametrizedTuple)(JSONValue e) { + return ((JSONValue x) @trusted { if (x.type != JSONType.array || x.array.length != 3) throw _atd_bad_json("Tuple of size 3", x); return tuple(fromJson!Kind(x[0]), fromJson!Kind(x[1]), _atd_read_int(x[2])); })(e); } - Tuple!(Kind, Kind, int) fromJsonString(T : KindParametrizedTuple)(string s) { +@trusted Tuple!(Kind, Kind, int) fromJsonString(T : KindParametrizedTuple)(string s) { return parseJSON(s).fromJson!(KindParametrizedTuple); } @@ -521,13 +523,13 @@ struct IntFloatParametrizedRecord { float[] field_b = []; } - IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { +@trusted IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { IntFloatParametrizedRecord obj; obj.field_a = ("field_a" in x) ? _atd_read_int(x["field_a"]) : _atd_missing_json_field!(typeof(obj.field_a))("IntFloatParametrizedRecord", "field_a"); obj.field_b = ("field_b" in x) ? _atd_read_list((&_atd_read_float).toDelegate)(x["field_b"]) : []; return obj; } - JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { +@trusted JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { JSONValue res; res["field_a"] = _atd_write_int(obj.field_a); res["field_b"] = _atd_write_list((&_atd_write_float).toDelegate)(obj.field_b); @@ -562,7 +564,7 @@ struct Root { RemoveTypedef!(AliasOfAliasOfAlias) aaa; } - Root fromJson(T : Root)(JSONValue x) { +@trusted Root fromJson(T : Root)(JSONValue x) { Root obj; obj.id = ("ID" in x) ? _atd_read_string(x["ID"]) : _atd_missing_json_field!(typeof(obj.id))("Root", "ID"); obj.await = ("await" in x) ? _atd_read_bool(x["await"]) : _atd_missing_json_field!(typeof(obj.await))("Root", "await"); @@ -575,13 +577,13 @@ struct Root { obj.extras = ("extras" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["extras"]) : []; obj.answer = ("answer" in x) ? _atd_read_int(x["answer"]) : 42; obj.aliased = ("aliased" in x) ? fromJson!Alias(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); - obj.point = ("point" in x) ? ((JSONValue x) { + obj.point = ("point" in x) ? ((JSONValue x) @trusted { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_float(x[0]), _atd_read_float(x[1])); })(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); obj.kinds = ("kinds" in x) ? _atd_read_list((&fromJson!Kind).toDelegate)(x["kinds"]) : _atd_missing_json_field!(typeof(obj.kinds))("Root", "kinds"); - obj.assoc1 = ("assoc1" in x) ? _atd_read_list(((JSONValue x) { + obj.assoc1 = ("assoc1" in x) ? _atd_read_list(((JSONValue x) @trusted { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_float(x[0]), _atd_read_int(x[1])); @@ -598,7 +600,7 @@ struct Root { obj.aaa = ("aaa" in x) ? fromJson!AliasOfAliasOfAlias(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); return obj; } - JSONValue toJson(T : Root)(T obj) { +@trusted JSONValue toJson(T : Root)(T obj) { JSONValue res; res["ID"] = _atd_write_string(obj.id); res["await"] = _atd_write_bool(obj.await); @@ -633,12 +635,12 @@ struct RequireField { string req; } - RequireField fromJson(T : RequireField)(JSONValue x) { +@trusted RequireField fromJson(T : RequireField)(JSONValue x) { RequireField obj; obj.req = ("req" in x) ? _atd_read_string(x["req"]) : _atd_missing_json_field!(typeof(obj.req))("RequireField", "req"); return obj; } - JSONValue toJson(T : RequireField)(T obj) { +@trusted JSONValue toJson(T : RequireField)(T obj) { JSONValue res; res["req"] = _atd_write_string(obj.req); return res; @@ -649,12 +651,12 @@ struct RecordWithWrappedType { int item; } - RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { +@trusted RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { RecordWithWrappedType obj; obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!(int)(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); return obj; } - JSONValue toJson(T : RecordWithWrappedType)(T obj) { +@trusted JSONValue toJson(T : RecordWithWrappedType)(T obj) { JSONValue res; res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!(string)(e))(obj.item); return res; @@ -662,41 +664,41 @@ struct RecordWithWrappedType { alias Pair = Typedef!(Tuple!(string, int), (Tuple!(string, int)).init, "Pair"); - JSONValue toJson(T : Pair)(Tuple!(string, int) e) { +@trusted JSONValue toJson(T : Pair)(Tuple!(string, int) e) { return ((Tuple!(string, int) x) => JSONValue([_atd_write_string(x[0]), _atd_write_int(x[1])]))(e); } - string toJsonString(T : Pair)(Tuple!(string, int) obj) { +@trusted string toJsonString(T : Pair)(Tuple!(string, int) obj) { return obj.toJson!(Pair).toString; } - Tuple!(string, int) fromJson(T : Pair)(JSONValue e) { - return ((JSONValue x) { +@trusted Tuple!(string, int) fromJson(T : Pair)(JSONValue e) { + return ((JSONValue x) @trusted { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_string(x[0]), _atd_read_int(x[1])); })(e); } - Tuple!(string, int) fromJsonString(T : Pair)(string s) { +@trusted Tuple!(string, int) fromJsonString(T : Pair)(string s) { return parseJSON(s).fromJson!(Pair); } // Original type: frozen = [ ... | A | ... ] struct A {} - JSONValue toJson(T : A)(T e) { +@trusted JSONValue toJson(T : A)(T e) { return JSONValue("A"); } // Original type: frozen = [ ... | B of ... | ... ] struct B { int value; } - JSONValue toJson(T : B)(T e) { +@trusted JSONValue toJson(T : B)(T e) { return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); } alias Frozen = SumType!(A, B); -Frozen fromJson(T : Frozen)(JSONValue x) { +@trusted Frozen fromJson(T : Frozen)(JSONValue x) { if (x.type == JSONType.string) { if (x.str == "A") return Frozen(A()); @@ -711,7 +713,7 @@ Frozen fromJson(T : Frozen)(JSONValue x) { throw _atd_bad_json("Frozen", x); } -JSONValue toJson(T : Frozen)(T x) { +@trusted JSONValue toJson(T : Frozen)(T x) { return x.match!( (A v) => v.toJson!(A), (B v) => v.toJson!(B) @@ -723,12 +725,12 @@ struct DefaultList { int[] items = []; } - DefaultList fromJson(T : DefaultList)(JSONValue x) { +@trusted DefaultList fromJson(T : DefaultList)(JSONValue x) { DefaultList obj; obj.items = ("items" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["items"]) : []; return obj; } - JSONValue toJson(T : DefaultList)(T obj) { +@trusted JSONValue toJson(T : DefaultList)(T obj) { JSONValue res; res["items"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.items); return res; @@ -736,30 +738,30 @@ struct DefaultList { alias AliasOfAlias = Typedef!(uint16_t, (uint16_t).init, "AliasOfAlias"); - JSONValue toJson(T : AliasOfAlias)(uint16_t e) { +@trusted JSONValue toJson(T : AliasOfAlias)(uint16_t e) { return _atd_write_wrap(((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3)), (uint16_t e) => to!uint32_t(e))(e); } - string toJsonString(T : AliasOfAlias)(uint16_t obj) { +@trusted string toJsonString(T : AliasOfAlias)(uint16_t obj) { return obj.toJson!(AliasOfAlias).toString; } - uint16_t fromJson(T : AliasOfAlias)(JSONValue e) { +@trusted uint16_t fromJson(T : AliasOfAlias)(JSONValue e) { return _atd_read_wrap((&fromJson!Alias3).toDelegate, (RemoveTypedef!(Alias3) e) => to!uint16_t(e))(e); } - uint16_t fromJsonString(T : AliasOfAlias)(string s) { +@trusted uint16_t fromJsonString(T : AliasOfAlias)(string s) { return parseJSON(s).fromJson!(AliasOfAlias); } alias Alias2 = Typedef!(int[], (int[]).init, "Alias2"); - JSONValue toJson(T : Alias2)(int[] e) { +@trusted JSONValue toJson(T : Alias2)(int[] e) { return _atd_write_list((&_atd_write_int).toDelegate)(e); } - string toJsonString(T : Alias2)(int[] obj) { +@trusted string toJsonString(T : Alias2)(int[] obj) { return obj.toJson!(Alias2).toString; } - int[] fromJson(T : Alias2)(JSONValue e) { +@trusted int[] fromJson(T : Alias2)(JSONValue e) { return _atd_read_list((&_atd_read_int).toDelegate)(e); } - int[] fromJsonString(T : Alias2)(string s) { +@trusted int[] fromJsonString(T : Alias2)(string s) { return parseJSON(s).fromJson!(Alias2); } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index 1180d04a..b63200fc 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -53,7 +53,7 @@ void setupTests() ); }; - tests["everything"] = { + tests["everything"] = () { import std.typecons; import std.json; @@ -88,10 +88,12 @@ void setupTests() obj.parametrized_record = IntFloatParametrizedRecord(42, [9.9f, 8.8f]); obj.parametrized_tuple = tuple(Kind(WOW()), Kind(WOW()), 100); - auto jsonStr = obj.toJsonString; - auto newObj = jsonStr.fromJsonString!Root; + () @safe { + auto jsonStr = obj.toJsonString; + auto newObj = jsonStr.fromJsonString!Root; - assert(obj == newObj); + assert(obj == newObj); + }(); }; tests["defaultListWithThings"] = { From 412c8bc81748d5b296f104b8e35281b9a475a75f Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 13:19:01 -0700 Subject: [PATCH 77/91] atdd: show appropriate error message when no annotation is present for wrapped type --- atdd/src/lib/Codegen.ml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index e0f83842..c52de668 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -615,7 +615,7 @@ let rec type_name_of_expr ?(aliasing=Keep) env (e : type_expr) : string = | Shared (loc, e, an) -> not_implemented loc "shared" (* TODO *) | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with - None -> assert false (* TODO : dubious*) + | None -> error_at loc "wrap type declared, but no dlang annotation found" | Some { dlang_wrap_t ; _ } -> dlang_wrap_t ) | Name (loc, (loc2, name, []), an) -> dlang_type_name ~aliasing:aliasing env name @@ -699,7 +699,7 @@ let rec json_writer ?(nested=false) env e = | Shared (loc, e, an) -> not_implemented loc "shared" | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with - None -> assert false (* TODO : dubious*) + | None -> error_at loc "wrap type declared, but no dlang annotation found" | Some { dlang_wrap_t; dlang_wrap ; _ } -> sprintf "_atd_write_wrap(%s, (%s e) => %s(e))" (json_writer ~nested:true env e) dlang_wrap_t dlang_wrap ) From 16e090a42c784e8674cd07695421d3edd34f13af Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 13:52:08 -0700 Subject: [PATCH 78/91] atdd: fix: inverse wrap and unwrap functions --- atdd/src/lib/Codegen.ml | 8 ++++---- atdd/test/atd-input/everything.atd | 8 ++++---- atdd/test/dlang-expected/everything.d | 4 ++-- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index c52de668..4ae6970c 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -700,8 +700,8 @@ let rec json_writer ?(nested=false) env e = | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with | None -> error_at loc "wrap type declared, but no dlang annotation found" - | Some { dlang_wrap_t; dlang_wrap ; _ } -> - sprintf "_atd_write_wrap(%s, (%s e) => %s(e))" (json_writer ~nested:true env e) dlang_wrap_t dlang_wrap + | Some { dlang_wrap_t; dlang_unwrap ; _ } -> + sprintf "_atd_write_wrap(%s, (%s e) => %s(e))" (json_writer ~nested:true env e) dlang_wrap_t dlang_unwrap ) | Name (loc, (loc2, name, []), an) -> (match name with @@ -786,8 +786,8 @@ let rec json_reader ?(nested=false) env (e : type_expr) = | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with None -> assert false (* TODO : dubious*) - | Some { dlang_unwrap ; _ } -> - sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr ~aliasing:None_ env e) dlang_unwrap + | Some { dlang_wrap ; _ } -> + sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr ~aliasing:None_ env e) dlang_wrap ) | Name (loc, (loc2, name, []), an) -> (match name with diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index c6cfc02d..68166d7c 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -42,15 +42,15 @@ type root = { untyped_things: abstract list; parametrized_record: (int, float) parametrized_record; parametrized_tuple: kind parametrized_tuple; - wrapped: st wrap ; + wrapped: st wrap ; aaa: alias_of_alias_of_alias; } type st = int type alias = int list type alias2 = int list -type alias3 = int wrap -type alias_of_alias = alias3 wrap +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 @@ -72,5 +72,5 @@ type default_list = { } type record_with_wrapped_type = { - item: string wrap ; + item: string wrap ; } diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index fbe42eef..a22e5cee 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -653,12 +653,12 @@ struct RecordWithWrappedType { @trusted RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { RecordWithWrappedType obj; - obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!(int)(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); + obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!int(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); return obj; } @trusted JSONValue toJson(T : RecordWithWrappedType)(T obj) { JSONValue res; - res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!(string)(e))(obj.item); + res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!string(e))(obj.item); return res; } From e4a8bbf6630cadb34e2dea092080094725372522 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 16:24:26 -0700 Subject: [PATCH 79/91] atdd: remove mentions of python, add changelog, remove unused test --- CHANGES.md | 4 ++++ atdd/README.md | 3 +-- atdd/src/bin/Atdd_main.ml | 10 +++++----- atdd/src/test/Main.ml | 14 -------------- atdd/src/test/dune | 12 ------------ atdd/test/.gitignore | 3 --- atdd/test/dlang-tests/dune | 2 +- 7 files changed, 11 insertions(+), 37 deletions(-) delete mode 100644 atdd/src/test/Main.ml delete mode 100644 atdd/src/test/dune diff --git a/CHANGES.md b/CHANGES.md index 30455596..f0a37dca 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,7 @@ +master +------------------- +* atdd: Add `dlang` backend to generate D code from ATD definitions + 2.12.0 (2023-05-12) ------------------- diff --git a/atdd/README.md b/atdd/README.md index fa786783..d2fe61c6 100644 --- a/atdd/README.md +++ b/atdd/README.md @@ -40,8 +40,7 @@ ldc2. Running the tests is done from the `atdd/` main folder with `make test`. -We have two kinds of tests for atdpy: -* [unit tests](src/test) for testing internal OCaml code +We have two kinds of tests for atdd: * code generation and D tests: * they generate D code from ATD files and compare the D output against the [expectations](dlang-expected). diff --git a/atdd/src/bin/Atdd_main.ml b/atdd/src/bin/Atdd_main.ml index 477f2486..da4576e2 100644 --- a/atdd/src/bin/Atdd_main.ml +++ b/atdd/src/bin/Atdd_main.ml @@ -46,7 +46,7 @@ let version_term = Arg.value (Arg.flag info) let doc = - "Type-safe JSON serializers for Python" + "Type-safe JSON serializers for D" (* The structure of the help page. @@ -55,15 +55,15 @@ let man = [ (* 'NAME' and 'SYNOPSIS' sections are inserted here by cmdliner. *) `S Manpage.s_description; (* standard 'DESCRIPTION' section *) - `P "atdd turns a file containing type definitions into Python classes \ + `P "atdd turns a file containing type definitions into D classes \ that read, write, and validate JSON data. The generated code \ - can be type-checked statically with mypy to ensure user code agrees \ + 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.py' \ + `P "The following is a sample ATD file. 'sample.atd' becomes 'sample.d' \ with the command 'atdd sample.atd'."; `Pre "\ (* Sample ATD file sample.atd *) @@ -72,7 +72,7 @@ type foo = { name: string; (* required field *) ?description: string option; (* optional field *) ~tags: string list; (* optional with implicit default *) - ~price : float; (* explicit default *) + ~price : float; (* explicit default *) items: bar list; } diff --git a/atdd/src/test/Main.ml b/atdd/src/test/Main.ml deleted file mode 100644 index 62867672..00000000 --- a/atdd/src/test/Main.ml +++ /dev/null @@ -1,14 +0,0 @@ -(* - Entry point to the executable running the unit tests - - TODO: the only test suite we had moved to the atd library. Remove? -*) - -let test_suites : unit Alcotest.test list = [ - (* Unique_name.test *) -] - -let main () = - Alcotest.run "atdd" test_suites - -let () = main () diff --git a/atdd/src/test/dune b/atdd/src/test/dune deleted file mode 100644 index dd60da74..00000000 --- a/atdd/src/test/dune +++ /dev/null @@ -1,12 +0,0 @@ -(executable - (name Main) - (libraries - atdd - alcotest - ) -) - -(rule - (alias runtest) - (action (run ./Main.exe)) -) diff --git a/atdd/test/.gitignore b/atdd/test/.gitignore index 8ea94de0..9d9a6629 100644 --- a/atdd/test/.gitignore +++ b/atdd/test/.gitignore @@ -1,5 +1,2 @@ -__pycache__ - !dlang-expected/ dlang-tests/*.d -dlang-tests/*.py \ No newline at end of file diff --git a/atdd/test/dlang-tests/dune b/atdd/test/dlang-tests/dune index ad755f9a..ed91f516 100644 --- a/atdd/test/dlang-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -12,7 +12,7 @@ (run %{bin:atdd} %{deps}))) ; -; Typecheck and run the tests on the generated Dlang code. +; Compile and run the tests on the generated Dlang code. ; (rule (alias runtest) From 0a3a08165388167da7c2d9cd3e2c52fb6f8345d1 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 16:28:08 -0700 Subject: [PATCH 80/91] atdd: add ldc2 dependency in opam file --- atdd.opam | 1 + 1 file changed, 1 insertion(+) diff --git a/atdd.opam b/atdd.opam index dac87755..5801473a 100644 --- a/atdd.opam +++ b/atdd.opam @@ -68,6 +68,7 @@ depends: [ "re" "alcotest" {with-test} "odoc" {with-doc} + "ldc2" {with-test} ] dev-repo: "git+https://github.com/ahrefs/atd.git" build: [ From 3ba7f0e8686648e7407f8f24d269808cb77d3d14 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 17:17:00 -0700 Subject: [PATCH 81/91] atdd: remove alcotest dep for atdd --- atdd.opam | 1 - 1 file changed, 1 deletion(-) diff --git a/atdd.opam b/atdd.opam index 5801473a..41f18c53 100644 --- a/atdd.opam +++ b/atdd.opam @@ -66,7 +66,6 @@ depends: [ "atd" {>= "2.11.0"} "cmdliner" {>= "1.1.0"} "re" - "alcotest" {with-test} "odoc" {with-doc} "ldc2" {with-test} ] From 463326e66c68d15e54fe3515dbcad6e04b44c595 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 18:17:02 -0700 Subject: [PATCH 82/91] atdd: add proper error message for wrapping and fix warning during compilation --- atdd/src/lib/Codegen.ml | 6 ++---- atdd/test/dlang-expected/everything.d | 4 +--- 2 files changed, 3 insertions(+), 7 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 4ae6970c..ece3edb8 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -227,11 +227,9 @@ private alias RemoveTypedef = T; } - auto _atd_missing_json_field(T)(string typeName, string jsonFieldName) + @trusted T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); - // hack so that the return type is the same as the field we are instantiating - return T.init; } auto _atd_bad_json(T)(string expectedType, T jsonValue) @@ -785,7 +783,7 @@ let rec json_reader ?(nested=false) env (e : type_expr) = | Shared (loc, e, an) -> not_implemented loc "shared" | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with - None -> assert false (* TODO : dubious*) + | None -> error_at loc "wrap type declared, but no dlang annotation found" | Some { dlang_wrap ; _ } -> sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr ~aliasing:None_ env e) dlang_wrap ) diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index a22e5cee..91e2ece9 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -43,11 +43,9 @@ private alias RemoveTypedef = T; } - auto _atd_missing_json_field(T)(string typeName, string jsonFieldName) + @trusted T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); - // hack so that the return type is the same as the field we are instantiating - return T.init; } auto _atd_bad_json(T)(string expectedType, T jsonValue) From a7c62bde66c100068bfa653f2b951d8e8ada9ff5 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Thu, 7 Sep 2023 18:40:19 -0700 Subject: [PATCH 83/91] atdd: fix error message order --- atdd/src/lib/Codegen.ml | 2 +- atdd/test/dlang-expected/everything.d | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ece3edb8..9d818c44 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -229,7 +229,7 @@ private @trusted T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { - throw new AtdException("missing field %%s in JSON object of type %%s".format(typeName, jsonFieldName)); + throw new AtdException("missing field %%s in JSON object of type %%s".format(jsonFieldName, typeName)); } auto _atd_bad_json(T)(string expectedType, T jsonValue) diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index 91e2ece9..bae5b067 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -45,7 +45,7 @@ private @trusted T _atd_missing_json_field(T)(string typeName, string jsonFieldName) { - throw new AtdException("missing field %s in JSON object of type %s".format(typeName, jsonFieldName)); + throw new AtdException("missing field %s in JSON object of type %s".format(jsonFieldName, typeName)); } auto _atd_bad_json(T)(string expectedType, T jsonValue) From 6cea0a9b9ab6ab450b11bca3cdcb5ccaef82e497 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Louis=20Roch=C3=A9=20=28Ahrefs=29?= Date: Tue, 12 Sep 2023 07:36:27 +0000 Subject: [PATCH 84/91] atdd: fix opam file --- atdd.opam | 2 +- scripts/install-opam-dependencies | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd.opam b/atdd.opam index 41f18c53..dac87755 100644 --- a/atdd.opam +++ b/atdd.opam @@ -66,8 +66,8 @@ depends: [ "atd" {>= "2.11.0"} "cmdliner" {>= "1.1.0"} "re" + "alcotest" {with-test} "odoc" {with-doc} - "ldc2" {with-test} ] dev-repo: "git+https://github.com/ahrefs/atd.git" build: [ diff --git a/scripts/install-opam-dependencies b/scripts/install-opam-dependencies index 3a796a28..fc689394 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"' "$x" \ + '"atd"\|"atdgen"\|"atdgen-codec-runtime"\|"atdgen-runtime"\|"atdj"\|"atdpy"\|"atds"\|"atdts"\|"atdd"' "$x" \ > "$workspace"/"$x" done From a8026bebaa277bc020b591b3df50bee625f15b44 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Sat, 9 Sep 2023 16:49:16 -0700 Subject: [PATCH 85/91] atdd: use templates instead of passing delegates --- atdd/src/lib/Codegen.ml | 471 +++++++++++------------ atdd/test/dlang-expected/everything.d | 515 +++++++++++++------------- 2 files changed, 496 insertions(+), 490 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 9d818c44..8988e062 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -199,7 +199,7 @@ import std.conv; import std.format; import std.json; import std.sumtype; -import std.traits : isCallable; +import std.traits : isCallable, ReturnType; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private @@ -300,224 +300,229 @@ private throw _atd_bad_json("string", x); } - auto _atd_read_list(E)(E delegate(JSONValue) @safe readElements) - { - return (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("array", jsonVal); - auto list = jsonVal.array; - return array(list.map!readElements()); - }; - } - - auto _atd_read_object_to_assoc_array(V)( - V delegate(JSONValue) @safe readValue) - { - auto fun = (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - V[string] ret; - foreach (key, val; jsonVal.object) - ret[key] = readValue(val); - return ret; - }; - return fun; - } - - auto _atd_read_array_to_assoc_dict(K, V)( - K delegate(JSONValue) @safe readKey, - V delegate(JSONValue) @safe readValue) - { - auto fun = (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("list", jsonVal); - V[K] ret; - foreach (jsonInnerVal; jsonVal.array) - { - if (jsonInnerVal.type != JSONType.array) - throw _atd_bad_json("list", jsonInnerVal); - ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); - } - return ret; - }; - return fun; - } - - auto _atd_read_object_to_tuple_list(T)( - T delegate(JSONValue) @safe readValue) - { - auto fun = (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - auto tupList = new Tuple!(string, T)[](jsonVal.object.length); - int i = 0; - foreach (key, val; jsonVal.object) - tupList[i++] = tuple(key, readValue(val)); - return tupList; - }; - return fun; - } - - auto _atd_read_nullable(T)(T delegate(JSONValue) @safe readElm) - { - auto fun = (JSONValue e) @safe { - if (e.isNull) - return Nullable!T.init; - else - return Nullable!T(readElm(e)); - }; - return fun; - } - - auto _atd_read_option(T)(T delegate(JSONValue) @safe readElm) - { - auto fun = (JSONValue e) @trusted { - if (e.type == JSONType.string && e.str == "None") - return Nullable!T.init; - else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e[1])); - else - throw _atd_bad_json("option", e); - }; - return fun; - } + template _atd_read_list(alias readElements) + { + auto _atd_read_list(JSONValue jsonVal) + { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("array", jsonVal); + auto list = jsonVal.array; + return array(list.map!readElements()); + } + } - auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) @safe readElm, Unwrapped delegate(Wrapped) @safe unwrap) - { - auto fun = (JSONValue e) @safe { - auto elm = readElm(e); - return unwrap(elm); - }; - return fun; - } - - // this whole set of function could be remplaced by one templated _atd_write_value function - // not sure it is what we want though - - auto _atd_write_unit(typeof(null) n) - { - return JSONValue(null); - } - - auto _atd_write_bool(bool b) - { - return JSONValue(b); - } - - auto _atd_write_int(int i) - { - return JSONValue(i); - } - - auto _atd_write_float(float f) - { - return JSONValue(f); - } - - auto _atd_write_string(string s) - { - return JSONValue(s); - } - - auto _atd_write_list(T)(JSONValue delegate(T) @safe writeElm) - { - return (T[] list) @safe { return JSONValue(array(list.map!writeElm())); }; - } - - auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) @safe writeValue) - { - auto fun = (T[string] assocArr) @safe { - JSONValue[string] ret; - foreach (key, val; assocArr) - ret[key] = writeValue(val); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) @safe writeKey, - JSONValue delegate(V) @safe writeValue) - { - auto fun = (V[K] assocArr) @safe { - JSONValue[] ret; - foreach (key, val; assocArr) - ret ~= JSONValue([writeKey(key), writeValue(val)]); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) @safe writeValue) - { - auto fun = (Tuple!(string, T)[] tupList) @safe { - JSONValue[string] ret; - foreach (tup; tupList) - ret[tup[0]] = writeValue(tup[1]); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_nullable(T)(JSONValue delegate(T) @safe writeElm) - { - auto fun = (Nullable!T elm) @safe { - if (elm.isNull) - return JSONValue(null); - else - return writeElm(elm.get); - }; - return fun; - } - - auto _atd_write_option(T)(JSONValue delegate(T) @safe writeElm) - { - auto fun = (Nullable!T elm) @safe { - if (elm.isNull) - return JSONValue("None"); - else - return JSONValue([JSONValue("Some"), writeElm(elm.get)]); - }; - return fun; - } + template _atd_read_object_to_assoc_array(alias readValue) + { + auto _atd_read_object_to_assoc_array(JSONValue jsonVal) + { + alias T = ReturnType!readValue; - auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) @safe writeElm, Wrapped delegate(Unwrapped) @safe wrap) - { - auto fun = (Unwrapped elm) @safe { - auto e = wrap(elm); - return writeElm(e); - }; - return fun; - } + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + T[string] ret; + foreach (key, val; jsonVal.object) + ret[key] = readValue(val); + return ret; + } + } + + template _atd_read_array_to_assoc_dict(alias readKey, alias readValue) + { + auto _atd_read_array_to_assoc_dict(JSONValue jsonVal) + { + alias K = ReturnType!readKey; + alias V = ReturnType!readValue; + + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("list", jsonVal); + V[K] ret; + foreach (jsonInnerVal; jsonVal.array) + { + if (jsonInnerVal.type != JSONType.array) + throw _atd_bad_json("list", jsonInnerVal); + ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + } + return ret; + } + } + + template _atd_read_object_to_tuple_list(alias readValue) + { + auto _atd_read_object_to_tuple_list(JSONValue jsonVal) + { + alias T = ReturnType!readValue; + + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + auto tupList = new Tuple!(string, T)[](jsonVal.object.length); + int i = 0; + foreach (key, val; jsonVal.object) + tupList[i++] = tuple(key, readValue(val)); + return tupList; + } + } + + template _atd_read_nullable(alias readElm) + { + auto _atd_read_nullable(JSONValue e) + { + alias T = ReturnType!readElm; + + if (e.isNull) + return Nullable!T.init; + else + return Nullable!T(readElm(e)); + } + } + + template _atd_read_option(alias readElm) + { + auto _atd_read_option(JSONValue e) + { + alias T = ReturnType!readElm; + + if (e.type == JSONType.string && e.str == "None") + return Nullable!T.init; + else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") + return Nullable!T(readElm(e[1])); + else + throw _atd_bad_json("option", e); + } + } + + template _atd_read_wrap(alias readElm, alias wrap) + { + auto _atd_read_wrap(JSONValue e) + { + return wrap(readElm(e)); + } + } + + // this whole set of function could be remplaced by one templated _atd_write_value function + // not sure it is what we want though + + auto _atd_write_unit(typeof(null) n) + { + return JSONValue(null); + } + + auto _atd_write_bool(bool b) + { + return JSONValue(b); + } + + auto _atd_write_int(int i) + { + return JSONValue(i); + } + + auto _atd_write_float(float f) + { + return JSONValue(f); + } + + auto _atd_write_string(string s) + { + return JSONValue(s); + } + + template _atd_write_list(alias writeElm) + { + auto _atd_write_list(T)(T[] list) + { + return JSONValue(array(list.map!writeElm())); + } + } + + template _atd_write_assoc_array_to_object(alias writeValue) + { + auto _atd_write_assoc_array_to_object(T)(T[string] assocArr) + { + JSONValue[string] ret; + foreach (key, val; assocArr) + ret[key] = writeValue(val); + return JSONValue(ret); + } + } + + template _atd_write_assoc_dict_to_array(alias writeKey, alias writeValue) + { + auto _atd_write_assoc_dict_to_array(K, V)(V[K] assocArr) + { + JSONValue[] ret; + foreach (key, val; assocArr) + ret ~= JSONValue([writeKey(key), writeValue(val)]); + return JSONValue(ret); + } + } + + template _atd_write_tuple_list_to_object(alias writeValue) + { + auto _atd_write_tuple_list_to_object(T)(Tuple!(string, T)[] tupList) + { + JSONValue[string] ret; + foreach (tup; tupList) + ret[tup[0]] = writeValue(tup[1]); + return JSONValue(ret); + } + } + + template _atd_write_nullable(alias writeElm) + { + auto _atd_write_nullable(T)(Nullable!T elm) + { + if (elm.isNull) + return JSONValue(null); + else + return writeElm(elm.get); + } + } + + template _atd_write_option(alias writeElm) + { + auto _atd_write_option(T)(Nullable!T elm) + { + if (elm.isNull) + return JSONValue("None"); + else + return JSONValue([JSONValue("Some"), writeElm(elm.get)]); + } + } + + template _atd_write_wrap(alias writeElm, alias unwrap) + { + auto _atd_write_wrap(Wrapped)(Wrapped e) + { + return writeElm(unwrap(e)); + } + } } - // ############################################################################ - // # Public classes - // ############################################################################ - - auto fromJsonString(T)(string s) - { - JSONValue res = parseJSON(s); - return res.fromJson!T; - } - - auto toJsonString(T)(T obj) - { +// ############################################################################ +// # Public classes +// ############################################################################ + +auto fromJsonString(T)(string s) +{ + JSONValue res = parseJSON(s); + return res.fromJson!T; +} + +auto toJsonString(T)(T obj) +{ JSONValue res = obj.toJson!T; return res.toString; - } - - auto unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) - { - return cast(TypedefType!T) e; - } - - auto wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) - { - return cast(T) e; - } +} + +auto unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) +{ + return cast(TypedefType!T) e; +} + +auto wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) +{ + return cast(T) e; +} |} atd_filename @@ -679,32 +684,31 @@ let rec json_writer ?(nested=false) env e = | List (loc, e, an) -> (match assoc_kind loc e an with | Array_list -> - sprintf "_atd_write_list(%s)" (json_writer ~nested:true env e) + sprintf "_atd_write_list!(%s)" (json_writer ~nested:true env e) | Array_dict (key, value) -> - sprintf "_atd_write_assoc_dict_to_array(%s, %s)" + sprintf "_atd_write_assoc_dict_to_array!(%s, %s)" (json_writer ~nested:true env key) (json_writer ~nested:true env value) | Object_dict value -> - sprintf "_atd_write_assoc_array_to_object(%s)" + sprintf "_atd_write_assoc_array_to_object!(%s)" (json_writer ~nested:true env value) | Object_list value -> - sprintf "_atd_write_tuple_list_to_object(%s)" + sprintf "_atd_write_tuple_list_to_object!(%s)" (json_writer ~nested:true env value) ) | Option (loc, e, an) -> - sprintf "_atd_write_option(%s)"(json_writer ~nested:true env e) + sprintf "_atd_write_option!(%s)"(json_writer ~nested:true env e) | Nullable (loc, e, an) -> - sprintf "_atd_write_nullable(%s)" (json_writer ~nested:true env e) + sprintf "_atd_write_nullable!(%s)" (json_writer ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with | None -> error_at loc "wrap type declared, but no dlang annotation found" | Some { dlang_wrap_t; dlang_unwrap ; _ } -> - sprintf "_atd_write_wrap(%s, (%s e) => %s(e))" (json_writer ~nested:true env e) dlang_wrap_t dlang_unwrap + sprintf "_atd_write_wrap!(%s, (%s e) => %s(e))" (json_writer ~nested:true env e) dlang_wrap_t dlang_unwrap ) | Name (loc, (loc2, name, []), an) -> (match name with - | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_write_%s%s" - (if nested then "(&" else "") name (if nested then ").toDelegate" else "") + | "bool" | "int" | "float" | "string" -> sprintf "_atd_write_%s" name | "abstract" -> "(JSONValue x) => x" | _ -> let dtype_name = (dlang_type_name env name) in let rawtype_name = (dlang_type_name ~aliasing:None_ env name) in @@ -744,7 +748,7 @@ let construct_json_field env trans_meth (inst_var_name trans_meth name)); Block [ Line(sprintf "res[\"%s\"] = %s(%s)(obj.%s);" (Atd.Json.get_json_fname name an |> single_esc) - "_atd_write_option" + "_atd_write_option!" (json_writer ~nested:true env unwrapped_type) (inst_var_name trans_meth name))]; ] @@ -764,38 +768,35 @@ let rec json_reader ?(nested=false) env (e : type_expr) = The default is to use JSON arrays and Python lists. *) (match assoc_kind loc e an with | Array_list -> - sprintf "_atd_read_list(%s)" + sprintf "_atd_read_list!(%s)" (json_reader ~nested:true env e) | Array_dict (key, value) -> - sprintf "_atd_read_array_to_assoc_dict(%s, %s)" + sprintf "_atd_read_array_to_assoc_dict!(%s, %s)" (json_reader ~nested:true env key) (json_reader ~nested:true env value) | Object_dict value -> - sprintf "_atd_read_object_to_assoc_array(%s)" + sprintf "_atd_read_object_to_assoc_array!(%s)" (json_reader ~nested:true env value) | Object_list value -> - sprintf "_atd_read_object_to_tuple_list(%s)" + sprintf "_atd_read_object_to_tuple_list!(%s)" (json_reader ~nested:true env value) ) | Option (loc, e, an) -> - sprintf "_atd_read_option(%s)" (json_reader ~nested:true env e) + sprintf "_atd_read_option!(%s)" (json_reader ~nested:true env e) | Nullable (loc, e, an) -> - sprintf "_atd_read_nullable(%s)" (json_reader ~nested:true env e) + sprintf "_atd_read_nullable!(%s)" (json_reader ~nested:true env e) | Shared (loc, e, an) -> not_implemented loc "shared" | Wrap (loc, e, an) -> (match Dlang_annot.get_dlang_wrap loc an with | None -> error_at loc "wrap type declared, but no dlang annotation found" | Some { dlang_wrap ; _ } -> - sprintf "_atd_read_wrap(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr ~aliasing:None_ env e) dlang_wrap + sprintf "_atd_read_wrap!(%s, (%s e) => %s(e))" (json_reader ~nested:true env e) (type_name_of_expr ~aliasing:None_ env e) dlang_wrap ) | Name (loc, (loc2, name, []), an) -> (match name with - | "bool" | "int" | "float" | "string" -> sprintf "%s_atd_read_%s%s" - (if nested then "(&" else "") name (if nested then ").toDelegate" else "") + | "bool" | "int" | "float" | "string" -> sprintf "_atd_read_%s" name | "abstract" -> "((JSONValue x) => x)" - | _ -> sprintf "%sfromJson!%s%s" - (if nested then "(&" else "") + | _ -> sprintf "fromJson!%s" (struct_name env name) - (if nested then ").toDelegate" else "") ) | Name (loc, _, _) -> not_implemented loc "parametrized types" | Tvar (loc, _) -> not_implemented loc "type variables" diff --git a/atdd/test/dlang-expected/everything.d b/atdd/test/dlang-expected/everything.d index bae5b067..f8fe0141 100644 --- a/atdd/test/dlang-expected/everything.d +++ b/atdd/test/dlang-expected/everything.d @@ -15,7 +15,7 @@ import std.conv; import std.format; import std.json; import std.sumtype; -import std.traits : isCallable; +import std.traits : isCallable, ReturnType; import std.typecons : nullable, Nullable, tuple, Tuple, Typedef, TypedefType; private @@ -116,224 +116,229 @@ private throw _atd_bad_json("string", x); } - auto _atd_read_list(E)(E delegate(JSONValue) @safe readElements) - { - return (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("array", jsonVal); - auto list = jsonVal.array; - return array(list.map!readElements()); - }; - } - - auto _atd_read_object_to_assoc_array(V)( - V delegate(JSONValue) @safe readValue) - { - auto fun = (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - V[string] ret; - foreach (key, val; jsonVal.object) - ret[key] = readValue(val); - return ret; - }; - return fun; - } - - auto _atd_read_array_to_assoc_dict(K, V)( - K delegate(JSONValue) @safe readKey, - V delegate(JSONValue) @safe readValue) - { - auto fun = (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.array) - throw _atd_bad_json("list", jsonVal); - V[K] ret; - foreach (jsonInnerVal; jsonVal.array) - { - if (jsonInnerVal.type != JSONType.array) - throw _atd_bad_json("list", jsonInnerVal); - ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); - } - return ret; - }; - return fun; - } - - auto _atd_read_object_to_tuple_list(T)( - T delegate(JSONValue) @safe readValue) - { - auto fun = (JSONValue jsonVal) @trusted { - if (jsonVal.type != JSONType.object) - throw _atd_bad_json("object", jsonVal); - auto tupList = new Tuple!(string, T)[](jsonVal.object.length); - int i = 0; - foreach (key, val; jsonVal.object) - tupList[i++] = tuple(key, readValue(val)); - return tupList; - }; - return fun; - } - - auto _atd_read_nullable(T)(T delegate(JSONValue) @safe readElm) - { - auto fun = (JSONValue e) @safe { - if (e.isNull) - return Nullable!T.init; - else - return Nullable!T(readElm(e)); - }; - return fun; - } - - auto _atd_read_option(T)(T delegate(JSONValue) @safe readElm) - { - auto fun = (JSONValue e) @trusted { - if (e.type == JSONType.string && e.str == "None") - return Nullable!T.init; - else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") - return Nullable!T(readElm(e[1])); - else - throw _atd_bad_json("option", e); - }; - return fun; - } + template _atd_read_list(alias readElements) + { + auto _atd_read_list(JSONValue jsonVal) + { + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("array", jsonVal); + auto list = jsonVal.array; + return array(list.map!readElements()); + } + } - auto _atd_read_wrap(Wrapped, Unwrapped)(Wrapped delegate(JSONValue) @safe readElm, Unwrapped delegate(Wrapped) @safe unwrap) - { - auto fun = (JSONValue e) @safe { - auto elm = readElm(e); - return unwrap(elm); - }; - return fun; - } - - // this whole set of function could be remplaced by one templated _atd_write_value function - // not sure it is what we want though - - auto _atd_write_unit(typeof(null) n) - { - return JSONValue(null); - } - - auto _atd_write_bool(bool b) - { - return JSONValue(b); - } - - auto _atd_write_int(int i) - { - return JSONValue(i); - } - - auto _atd_write_float(float f) - { - return JSONValue(f); - } - - auto _atd_write_string(string s) - { - return JSONValue(s); - } - - auto _atd_write_list(T)(JSONValue delegate(T) @safe writeElm) - { - return (T[] list) @safe { return JSONValue(array(list.map!writeElm())); }; - } - - auto _atd_write_assoc_array_to_object(T)( - JSONValue delegate(T) @safe writeValue) - { - auto fun = (T[string] assocArr) @safe { - JSONValue[string] ret; - foreach (key, val; assocArr) - ret[key] = writeValue(val); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_assoc_dict_to_array(K, V)( - JSONValue delegate(K) @safe writeKey, - JSONValue delegate(V) @safe writeValue) - { - auto fun = (V[K] assocArr) @safe { - JSONValue[] ret; - foreach (key, val; assocArr) - ret ~= JSONValue([writeKey(key), writeValue(val)]); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_tuple_list_to_object(T)( - JSONValue delegate(T) @safe writeValue) - { - auto fun = (Tuple!(string, T)[] tupList) @safe { - JSONValue[string] ret; - foreach (tup; tupList) - ret[tup[0]] = writeValue(tup[1]); - return JSONValue(ret); - }; - return fun; - } - - auto _atd_write_nullable(T)(JSONValue delegate(T) @safe writeElm) - { - auto fun = (Nullable!T elm) @safe { - if (elm.isNull) - return JSONValue(null); - else - return writeElm(elm.get); - }; - return fun; - } - - auto _atd_write_option(T)(JSONValue delegate(T) @safe writeElm) - { - auto fun = (Nullable!T elm) @safe { - if (elm.isNull) - return JSONValue("None"); - else - return JSONValue([JSONValue("Some"), writeElm(elm.get)]); - }; - return fun; - } + template _atd_read_object_to_assoc_array(alias readValue) + { + auto _atd_read_object_to_assoc_array(JSONValue jsonVal) + { + alias T = ReturnType!readValue; + + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + T[string] ret; + foreach (key, val; jsonVal.object) + ret[key] = readValue(val); + return ret; + } + } - auto _atd_write_wrap(Wrapped, Unwrapped)(JSONValue delegate(Wrapped) @safe writeElm, Wrapped delegate(Unwrapped) @safe wrap) - { - auto fun = (Unwrapped elm) @safe { - auto e = wrap(elm); - return writeElm(e); - }; - return fun; - } + template _atd_read_array_to_assoc_dict(alias readKey, alias readValue) + { + auto _atd_read_array_to_assoc_dict(JSONValue jsonVal) + { + alias K = ReturnType!readKey; + alias V = ReturnType!readValue; + + if (jsonVal.type != JSONType.array) + throw _atd_bad_json("list", jsonVal); + V[K] ret; + foreach (jsonInnerVal; jsonVal.array) + { + if (jsonInnerVal.type != JSONType.array) + throw _atd_bad_json("list", jsonInnerVal); + ret[readKey(jsonInnerVal[0])] = readValue(jsonInnerVal[1]); + } + return ret; + } + } + + template _atd_read_object_to_tuple_list(alias readValue) + { + auto _atd_read_object_to_tuple_list(JSONValue jsonVal) + { + alias T = ReturnType!readValue; + + if (jsonVal.type != JSONType.object) + throw _atd_bad_json("object", jsonVal); + auto tupList = new Tuple!(string, T)[](jsonVal.object.length); + int i = 0; + foreach (key, val; jsonVal.object) + tupList[i++] = tuple(key, readValue(val)); + return tupList; + } + } + + template _atd_read_nullable(alias readElm) + { + auto _atd_read_nullable(JSONValue e) + { + alias T = ReturnType!readElm; + + if (e.isNull) + return Nullable!T.init; + else + return Nullable!T(readElm(e)); + } + } + + template _atd_read_option(alias readElm) + { + auto _atd_read_option(JSONValue e) + { + alias T = ReturnType!readElm; + + if (e.type == JSONType.string && e.str == "None") + return Nullable!T.init; + else if (e.type == JSONType.array && e.array.length == 2 && e[0].type == JSONType.string && e[0].str == "Some") + return Nullable!T(readElm(e[1])); + else + throw _atd_bad_json("option", e); + } + } + + template _atd_read_wrap(alias readElm, alias wrap) + { + auto _atd_read_wrap(JSONValue e) + { + return wrap(readElm(e)); + } + } + + // this whole set of function could be remplaced by one templated _atd_write_value function + // not sure it is what we want though + + auto _atd_write_unit(typeof(null) n) + { + return JSONValue(null); + } + + auto _atd_write_bool(bool b) + { + return JSONValue(b); + } + + auto _atd_write_int(int i) + { + return JSONValue(i); + } + + auto _atd_write_float(float f) + { + return JSONValue(f); + } + + auto _atd_write_string(string s) + { + return JSONValue(s); + } + + template _atd_write_list(alias writeElm) + { + auto _atd_write_list(T)(T[] list) + { + return JSONValue(array(list.map!writeElm())); + } + } + + template _atd_write_assoc_array_to_object(alias writeValue) + { + auto _atd_write_assoc_array_to_object(T)(T[string] assocArr) + { + JSONValue[string] ret; + foreach (key, val; assocArr) + ret[key] = writeValue(val); + return JSONValue(ret); + } + } + + template _atd_write_assoc_dict_to_array(alias writeKey, alias writeValue) + { + auto _atd_write_assoc_dict_to_array(K, V)(V[K] assocArr) + { + JSONValue[] ret; + foreach (key, val; assocArr) + ret ~= JSONValue([writeKey(key), writeValue(val)]); + return JSONValue(ret); + } + } + + template _atd_write_tuple_list_to_object(alias writeValue) + { + auto _atd_write_tuple_list_to_object(T)(Tuple!(string, T)[] tupList) + { + JSONValue[string] ret; + foreach (tup; tupList) + ret[tup[0]] = writeValue(tup[1]); + return JSONValue(ret); + } + } + + template _atd_write_nullable(alias writeElm) + { + auto _atd_write_nullable(T)(Nullable!T elm) + { + if (elm.isNull) + return JSONValue(null); + else + return writeElm(elm.get); + } + } + + template _atd_write_option(alias writeElm) + { + auto _atd_write_option(T)(Nullable!T elm) + { + if (elm.isNull) + return JSONValue("None"); + else + return JSONValue([JSONValue("Some"), writeElm(elm.get)]); + } + } + + template _atd_write_wrap(alias writeElm, alias unwrap) + { + auto _atd_write_wrap(Wrapped)(Wrapped e) + { + return writeElm(unwrap(e)); + } + } } - // ############################################################################ - // # Public classes - // ############################################################################ - - auto fromJsonString(T)(string s) - { - JSONValue res = parseJSON(s); - return res.fromJson!T; - } - - auto toJsonString(T)(T obj) - { +// ############################################################################ +// # Public classes +// ############################################################################ + +auto fromJsonString(T)(string s) +{ + JSONValue res = parseJSON(s); + return res.fromJson!T; +} + +auto toJsonString(T)(T obj) +{ JSONValue res = obj.toJson!T; return res.toString; - } - - auto unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) - { - return cast(TypedefType!T) e; - } - - auto wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) - { - return cast(T) e; - } +} + +auto unwrapAlias(T)(T e) if (is(T : Typedef!Arg, Arg)) +{ + return cast(TypedefType!T) e; +} + +auto wrapAlias(T)(TypedefType!T e) if (is(T : Typedef!Arg, Arg)) +{ + return cast(T) e; +} @@ -351,14 +356,14 @@ struct RecursiveClass { RecursiveClass obj; obj.id = ("id" in x) ? _atd_read_int(x["id"]) : _atd_missing_json_field!(typeof(obj.id))("RecursiveClass", "id"); obj.flag = ("flag" in x) ? _atd_read_bool(x["flag"]) : _atd_missing_json_field!(typeof(obj.flag))("RecursiveClass", "flag"); - obj.children = ("children" in x) ? _atd_read_list((&fromJson!RecursiveClass).toDelegate)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); + obj.children = ("children" in x) ? _atd_read_list!(fromJson!RecursiveClass)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); return obj; } @trusted JSONValue toJson(T : RecursiveClass)(T obj) { JSONValue res; res["id"] = _atd_write_int(obj.id); res["flag"] = _atd_write_bool(obj.flag); - res["children"] = _atd_write_list(((RemoveTypedef!(RecursiveClass) x) => x.toJson!(RecursiveClass)))(obj.children); + res["children"] = _atd_write_list!(((RemoveTypedef!(RecursiveClass) x) => x.toJson!(RecursiveClass)))(obj.children); return res; } @@ -402,7 +407,7 @@ struct WOW {} // Original type: kind = [ ... | Amaze of ... | ... ] struct Amaze { string[] value; } @trusted JSONValue toJson(T : Amaze)(T e) { - return JSONValue([JSONValue("!!!"), _atd_write_list((&_atd_write_string).toDelegate)(e.value)]); + return JSONValue([JSONValue("!!!"), _atd_write_list!(_atd_write_string)(e.value)]); } @@ -421,7 +426,7 @@ alias Kind = SumType!(Root_, Thing, WOW, Amaze); if (cons == "Thing") return Kind(Thing(_atd_read_int(x[1]))); if (cons == "!!!") - return Kind(Amaze(_atd_read_list((&_atd_read_string).toDelegate)(x[1]))); + return Kind(Amaze(_atd_read_list!(_atd_read_string)(x[1]))); throw _atd_bad_json("Kind", x); } throw _atd_bad_json("Kind", x); @@ -439,13 +444,13 @@ alias Kind = SumType!(Root_, Thing, WOW, Amaze); alias Alias3 = Typedef!(uint32_t, (uint32_t).init, "Alias3"); @trusted JSONValue toJson(T : Alias3)(uint32_t e) { - return _atd_write_wrap((&_atd_write_int).toDelegate, (uint32_t e) => to!int(e))(e); + return _atd_write_wrap!(_atd_write_int, (uint32_t e) => to!int(e))(e); } @trusted string toJsonString(T : Alias3)(uint32_t obj) { return obj.toJson!(Alias3).toString; } @trusted uint32_t fromJson(T : Alias3)(JSONValue e) { - return _atd_read_wrap((&_atd_read_int).toDelegate, (int e) => to!uint32_t(e))(e); + return _atd_read_wrap!(_atd_read_int, (int e) => to!uint32_t(e))(e); } @trusted uint32_t fromJsonString(T : Alias3)(string s) { return parseJSON(s).fromJson!(Alias3); @@ -484,13 +489,13 @@ alias AliasOfAliasOfAlias = Typedef!(RemoveTypedef!(AliasOfAliasNotWrapped), (Re alias Alias = Typedef!(int[], (int[]).init, "Alias"); @trusted JSONValue toJson(T : Alias)(int[] e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e); + return _atd_write_list!(_atd_write_int)(e); } @trusted string toJsonString(T : Alias)(int[] obj) { return obj.toJson!(Alias).toString; } @trusted int[] fromJson(T : Alias)(JSONValue e) { - return _atd_read_list((&_atd_read_int).toDelegate)(e); + return _atd_read_list!(_atd_read_int)(e); } @trusted int[] fromJsonString(T : Alias)(string s) { return parseJSON(s).fromJson!(Alias); @@ -524,13 +529,13 @@ struct IntFloatParametrizedRecord { @trusted IntFloatParametrizedRecord fromJson(T : IntFloatParametrizedRecord)(JSONValue x) { IntFloatParametrizedRecord obj; obj.field_a = ("field_a" in x) ? _atd_read_int(x["field_a"]) : _atd_missing_json_field!(typeof(obj.field_a))("IntFloatParametrizedRecord", "field_a"); - obj.field_b = ("field_b" in x) ? _atd_read_list((&_atd_read_float).toDelegate)(x["field_b"]) : []; + obj.field_b = ("field_b" in x) ? _atd_read_list!(_atd_read_float)(x["field_b"]) : []; return obj; } @trusted JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { JSONValue res; res["field_a"] = _atd_write_int(obj.field_a); - res["field_b"] = _atd_write_list((&_atd_write_float).toDelegate)(obj.field_b); + res["field_b"] = _atd_write_list!(_atd_write_float)(obj.field_b); return res; } @@ -570,9 +575,9 @@ struct Root { obj.x___init__ = ("__init__" in x) ? _atd_read_float(x["__init__"]) : _atd_missing_json_field!(typeof(obj.x___init__))("Root", "__init__"); obj.float_with_auto_default = ("float_with_auto_default" in x) ? _atd_read_float(x["float_with_auto_default"]) : 0.0; obj.float_with_default = ("float_with_default" in x) ? _atd_read_float(x["float_with_default"]) : 0.1; - obj.items = ("items" in x) ? _atd_read_list(_atd_read_list((&_atd_read_int).toDelegate))(x["items"]) : _atd_missing_json_field!(typeof(obj.items))("Root", "items"); - obj.maybe = ("maybe" in x) ? _atd_read_option((&_atd_read_int).toDelegate)(x["maybe"]) : typeof(obj.maybe).init; - obj.extras = ("extras" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["extras"]) : []; + obj.items = ("items" in x) ? _atd_read_list!(_atd_read_list!(_atd_read_int))(x["items"]) : _atd_missing_json_field!(typeof(obj.items))("Root", "items"); + obj.maybe = ("maybe" in x) ? _atd_read_option!(_atd_read_int)(x["maybe"]) : typeof(obj.maybe).init; + obj.extras = ("extras" in x) ? _atd_read_list!(_atd_read_int)(x["extras"]) : []; obj.answer = ("answer" in x) ? _atd_read_int(x["answer"]) : 42; obj.aliased = ("aliased" in x) ? fromJson!Alias(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); obj.point = ("point" in x) ? ((JSONValue x) @trusted { @@ -580,21 +585,21 @@ struct Root { throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_float(x[0]), _atd_read_float(x[1])); })(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); - obj.kinds = ("kinds" in x) ? _atd_read_list((&fromJson!Kind).toDelegate)(x["kinds"]) : _atd_missing_json_field!(typeof(obj.kinds))("Root", "kinds"); - obj.assoc1 = ("assoc1" in x) ? _atd_read_list(((JSONValue x) @trusted { + obj.kinds = ("kinds" in x) ? _atd_read_list!(fromJson!Kind)(x["kinds"]) : _atd_missing_json_field!(typeof(obj.kinds))("Root", "kinds"); + obj.assoc1 = ("assoc1" in x) ? _atd_read_list!(((JSONValue x) @trusted { if (x.type != JSONType.array || x.array.length != 2) throw _atd_bad_json("Tuple of size 2", x); return tuple(_atd_read_float(x[0]), _atd_read_int(x[1])); }))(x["assoc1"]) : _atd_missing_json_field!(typeof(obj.assoc1))("Root", "assoc1"); - obj.assoc2 = ("assoc2" in x) ? _atd_read_object_to_tuple_list((&_atd_read_int).toDelegate)(x["assoc2"]) : _atd_missing_json_field!(typeof(obj.assoc2))("Root", "assoc2"); - obj.assoc3 = ("assoc3" in x) ? _atd_read_array_to_assoc_dict((&_atd_read_float).toDelegate, (&_atd_read_int).toDelegate)(x["assoc3"]) : _atd_missing_json_field!(typeof(obj.assoc3))("Root", "assoc3"); - obj.assoc4 = ("assoc4" in x) ? _atd_read_object_to_assoc_array((&_atd_read_int).toDelegate)(x["assoc4"]) : _atd_missing_json_field!(typeof(obj.assoc4))("Root", "assoc4"); - obj.nullables = ("nullables" in x) ? _atd_read_list(_atd_read_nullable((&_atd_read_int).toDelegate))(x["nullables"]) : _atd_missing_json_field!(typeof(obj.nullables))("Root", "nullables"); - obj.options = ("options" in x) ? _atd_read_list(_atd_read_option((&_atd_read_int).toDelegate))(x["options"]) : _atd_missing_json_field!(typeof(obj.options))("Root", "options"); - obj.untyped_things = ("untyped_things" in x) ? _atd_read_list(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field!(typeof(obj.untyped_things))("Root", "untyped_things"); + obj.assoc2 = ("assoc2" in x) ? _atd_read_object_to_tuple_list!(_atd_read_int)(x["assoc2"]) : _atd_missing_json_field!(typeof(obj.assoc2))("Root", "assoc2"); + obj.assoc3 = ("assoc3" in x) ? _atd_read_array_to_assoc_dict!(_atd_read_float, _atd_read_int)(x["assoc3"]) : _atd_missing_json_field!(typeof(obj.assoc3))("Root", "assoc3"); + obj.assoc4 = ("assoc4" in x) ? _atd_read_object_to_assoc_array!(_atd_read_int)(x["assoc4"]) : _atd_missing_json_field!(typeof(obj.assoc4))("Root", "assoc4"); + obj.nullables = ("nullables" in x) ? _atd_read_list!(_atd_read_nullable!(_atd_read_int))(x["nullables"]) : _atd_missing_json_field!(typeof(obj.nullables))("Root", "nullables"); + obj.options = ("options" in x) ? _atd_read_list!(_atd_read_option!(_atd_read_int))(x["options"]) : _atd_missing_json_field!(typeof(obj.options))("Root", "options"); + obj.untyped_things = ("untyped_things" in x) ? _atd_read_list!(((JSONValue x) => x))(x["untyped_things"]) : _atd_missing_json_field!(typeof(obj.untyped_things))("Root", "untyped_things"); obj.parametrized_record = ("parametrized_record" in x) ? fromJson!IntFloatParametrizedRecord(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); obj.parametrized_tuple = ("parametrized_tuple" in x) ? fromJson!KindParametrizedTuple(x["parametrized_tuple"]) : _atd_missing_json_field!(typeof(obj.parametrized_tuple))("Root", "parametrized_tuple"); - obj.wrapped = ("wrapped" in x) ? _atd_read_wrap((&fromJson!St).toDelegate, (RemoveTypedef!(St) e) => to!uint16_t(e))(x["wrapped"]) : _atd_missing_json_field!(typeof(obj.wrapped))("Root", "wrapped"); + obj.wrapped = ("wrapped" in x) ? _atd_read_wrap!(fromJson!St, (RemoveTypedef!(St) e) => to!uint16_t(e))(x["wrapped"]) : _atd_missing_json_field!(typeof(obj.wrapped))("Root", "wrapped"); obj.aaa = ("aaa" in x) ? fromJson!AliasOfAliasOfAlias(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); return obj; } @@ -606,24 +611,24 @@ struct Root { res["__init__"] = _atd_write_float(obj.x___init__); res["float_with_auto_default"] = _atd_write_float(obj.float_with_auto_default); res["float_with_default"] = _atd_write_float(obj.float_with_default); - res["items"] = _atd_write_list(_atd_write_list((&_atd_write_int).toDelegate))(obj.items); + res["items"] = _atd_write_list!(_atd_write_list!(_atd_write_int))(obj.items); if (!obj.maybe.isNull) - res["maybe"] = _atd_write_option((&_atd_write_int).toDelegate)(obj.maybe); - res["extras"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.extras); + res["maybe"] = _atd_write_option!(_atd_write_int)(obj.maybe); + res["extras"] = _atd_write_list!(_atd_write_int)(obj.extras); res["answer"] = _atd_write_int(obj.answer); res["aliased"] = ((RemoveTypedef!(Alias) x) => x.toJson!(Alias))(obj.aliased); res["point"] = ((Tuple!(float, float) x) => JSONValue([_atd_write_float(x[0]), _atd_write_float(x[1])]))(obj.point); - res["kinds"] = _atd_write_list(((RemoveTypedef!(Kind) x) => x.toJson!(Kind)))(obj.kinds); - res["assoc1"] = _atd_write_list(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc1); - res["assoc2"] = _atd_write_tuple_list_to_object((&_atd_write_int).toDelegate)(obj.assoc2); - res["assoc3"] = _atd_write_assoc_dict_to_array((&_atd_write_float).toDelegate, (&_atd_write_int).toDelegate)(obj.assoc3); - res["assoc4"] = _atd_write_assoc_array_to_object((&_atd_write_int).toDelegate)(obj.assoc4); - res["nullables"] = _atd_write_list(_atd_write_nullable((&_atd_write_int).toDelegate))(obj.nullables); - res["options"] = _atd_write_list(_atd_write_option((&_atd_write_int).toDelegate))(obj.options); - res["untyped_things"] = _atd_write_list((JSONValue x) => x)(obj.untyped_things); + res["kinds"] = _atd_write_list!(((RemoveTypedef!(Kind) x) => x.toJson!(Kind)))(obj.kinds); + res["assoc1"] = _atd_write_list!(((Tuple!(float, int) x) => JSONValue([_atd_write_float(x[0]), _atd_write_int(x[1])])))(obj.assoc1); + res["assoc2"] = _atd_write_tuple_list_to_object!(_atd_write_int)(obj.assoc2); + res["assoc3"] = _atd_write_assoc_dict_to_array!(_atd_write_float, _atd_write_int)(obj.assoc3); + res["assoc4"] = _atd_write_assoc_array_to_object!(_atd_write_int)(obj.assoc4); + res["nullables"] = _atd_write_list!(_atd_write_nullable!(_atd_write_int))(obj.nullables); + res["options"] = _atd_write_list!(_atd_write_option!(_atd_write_int))(obj.options); + res["untyped_things"] = _atd_write_list!((JSONValue x) => x)(obj.untyped_things); res["parametrized_record"] = ((RemoveTypedef!(IntFloatParametrizedRecord) x) => x.toJson!(IntFloatParametrizedRecord))(obj.parametrized_record); res["parametrized_tuple"] = ((RemoveTypedef!(KindParametrizedTuple) x) => x.toJson!(KindParametrizedTuple))(obj.parametrized_tuple); - res["wrapped"] = _atd_write_wrap(((RemoveTypedef!(St) x) => x.toJson!(St)), (uint16_t e) => to!int(e))(obj.wrapped); + res["wrapped"] = _atd_write_wrap!(((RemoveTypedef!(St) x) => x.toJson!(St)), (uint16_t e) => to!int(e))(obj.wrapped); res["aaa"] = ((RemoveTypedef!(AliasOfAliasOfAlias) x) => x.toJson!(AliasOfAliasOfAlias))(obj.aaa); return res; } @@ -651,12 +656,12 @@ struct RecordWithWrappedType { @trusted RecordWithWrappedType fromJson(T : RecordWithWrappedType)(JSONValue x) { RecordWithWrappedType obj; - obj.item = ("item" in x) ? _atd_read_wrap((&_atd_read_string).toDelegate, (string e) => to!int(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); + obj.item = ("item" in x) ? _atd_read_wrap!(_atd_read_string, (string e) => to!int(e))(x["item"]) : _atd_missing_json_field!(typeof(obj.item))("RecordWithWrappedType", "item"); return obj; } @trusted JSONValue toJson(T : RecordWithWrappedType)(T obj) { JSONValue res; - res["item"] = _atd_write_wrap((&_atd_write_string).toDelegate, (int e) => to!string(e))(obj.item); + res["item"] = _atd_write_wrap!(_atd_write_string, (int e) => to!string(e))(obj.item); return res; } @@ -725,25 +730,25 @@ struct DefaultList { @trusted DefaultList fromJson(T : DefaultList)(JSONValue x) { DefaultList obj; - obj.items = ("items" in x) ? _atd_read_list((&_atd_read_int).toDelegate)(x["items"]) : []; + obj.items = ("items" in x) ? _atd_read_list!(_atd_read_int)(x["items"]) : []; return obj; } @trusted JSONValue toJson(T : DefaultList)(T obj) { JSONValue res; - res["items"] = _atd_write_list((&_atd_write_int).toDelegate)(obj.items); + res["items"] = _atd_write_list!(_atd_write_int)(obj.items); return res; } alias AliasOfAlias = Typedef!(uint16_t, (uint16_t).init, "AliasOfAlias"); @trusted JSONValue toJson(T : AliasOfAlias)(uint16_t e) { - return _atd_write_wrap(((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3)), (uint16_t e) => to!uint32_t(e))(e); + return _atd_write_wrap!(((RemoveTypedef!(Alias3) x) => x.toJson!(Alias3)), (uint16_t e) => to!uint32_t(e))(e); } @trusted string toJsonString(T : AliasOfAlias)(uint16_t obj) { return obj.toJson!(AliasOfAlias).toString; } @trusted uint16_t fromJson(T : AliasOfAlias)(JSONValue e) { - return _atd_read_wrap((&fromJson!Alias3).toDelegate, (RemoveTypedef!(Alias3) e) => to!uint16_t(e))(e); + return _atd_read_wrap!(fromJson!Alias3, (RemoveTypedef!(Alias3) e) => to!uint16_t(e))(e); } @trusted uint16_t fromJsonString(T : AliasOfAlias)(string s) { return parseJSON(s).fromJson!(AliasOfAlias); @@ -752,13 +757,13 @@ alias AliasOfAlias = Typedef!(uint16_t, (uint16_t).init, "AliasOfAlias"); alias Alias2 = Typedef!(int[], (int[]).init, "Alias2"); @trusted JSONValue toJson(T : Alias2)(int[] e) { - return _atd_write_list((&_atd_write_int).toDelegate)(e); + return _atd_write_list!(_atd_write_int)(e); } @trusted string toJsonString(T : Alias2)(int[] obj) { return obj.toJson!(Alias2).toString; } @trusted int[] fromJson(T : Alias2)(JSONValue e) { - return _atd_read_list((&_atd_read_int).toDelegate)(e); + return _atd_read_list!(_atd_read_int)(e); } @trusted int[] fromJsonString(T : Alias2)(string s) { return parseJSON(s).fromJson!(Alias2); From e020cd7f51232bd9c0040ae277b105f73c9c4dbf Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 13 Sep 2023 10:30:20 +0800 Subject: [PATCH 86/91] atdd: edit dune-project to add ldc2 dep, and remove alcotest --- atdd.opam | 2 +- dune-project | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/atdd.opam b/atdd.opam index dac87755..c5fca9ab 100644 --- a/atdd.opam +++ b/atdd.opam @@ -66,7 +66,7 @@ depends: [ "atd" {>= "2.11.0"} "cmdliner" {>= "1.1.0"} "re" - "alcotest" {with-test} + "ldc2" {with-test} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ahrefs/atd.git" diff --git a/dune-project b/dune-project index d03400fa..42bacd3d 100644 --- a/dune-project +++ b/dune-project @@ -206,5 +206,5 @@ bucklescript backend") (atd (>= 2.11.0)) (cmdliner (>= 1.1.0)) re - (alcotest :with-test) + (ldc2 :with-test) (odoc :with-doc))) From 3234a8c1794826b7ef496ec4561e3a036401239c Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 13 Sep 2023 11:03:48 +0800 Subject: [PATCH 87/91] atdd: add test-d target. remove duplicate test-ts target. remove ldc2 from deps. should be installed manually for running tests --- Makefile | 8 +++++++- atdd.opam | 1 - dune-project | 1 - 3 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Makefile b/Makefile index a116797e..7fb0516a 100644 --- a/Makefile +++ b/Makefile @@ -44,12 +44,13 @@ docker-build: # to support all the target languages. .PHONY: test test: - $(MAKE) test-ts $(MAKE) test-ocaml $(MAKE) test-scala $(MAKE) test-java $(MAKE) test-python $(MAKE) test-ts + $(MAKE) test-d + # Test the OCaml code used by all the backends test-common: @@ -87,6 +88,11 @@ test-ts: $(MAKE) test-common $(MAKE) -C atdts test +.PHONY: test-d +test-d: + $(MAKE) test-common + $(MAKE) -C atdd test + ############################################################################ .PHONY: js diff --git a/atdd.opam b/atdd.opam index c5fca9ab..f3bd9e3a 100644 --- a/atdd.opam +++ b/atdd.opam @@ -66,7 +66,6 @@ depends: [ "atd" {>= "2.11.0"} "cmdliner" {>= "1.1.0"} "re" - "ldc2" {with-test} "odoc" {with-doc} ] dev-repo: "git+https://github.com/ahrefs/atd.git" diff --git a/dune-project b/dune-project index 42bacd3d..9b99a1cb 100644 --- a/dune-project +++ b/dune-project @@ -206,5 +206,4 @@ bucklescript backend") (atd (>= 2.11.0)) (cmdliner (>= 1.1.0)) re - (ldc2 :with-test) (odoc :with-doc))) From 3d46a4329dde42cfcce21f06609b903fd17cc899 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 13 Sep 2023 11:11:10 +0800 Subject: [PATCH 88/91] atdd: add install ldc to ci system setup --- .circleci/setup-system | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.circleci/setup-system b/.circleci/setup-system index 2367ca0d..e958d73b 100755 --- a/.circleci/setup-system +++ b/.circleci/setup-system @@ -20,7 +20,8 @@ sudo apt-get install -y \ python3 \ python3-pip \ python-is-python3 \ - scala + scala \ + ldc # For JSON Schema and atdpy testing pip install jsonschema pytest mypy flake8 @@ -56,3 +57,6 @@ node --version echo 'check npm' npm --version + +echo 'check ldc2 (dlang)' +ldc2 --version From 17b48879ecc2f7ac9fbf036f712db55b938f061b Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 13 Sep 2023 13:22:35 +0800 Subject: [PATCH 89/91] atdd: install ldc manually instead of through apt, to get latest stable version --- .circleci/setup-system | 7 +++++-- atdd/test/dlang-tests/dune | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/.circleci/setup-system b/.circleci/setup-system index e958d73b..b7128c14 100755 --- a/.circleci/setup-system +++ b/.circleci/setup-system @@ -20,12 +20,15 @@ sudo apt-get install -y \ python3 \ python3-pip \ python-is-python3 \ - scala \ - ldc + scala # For JSON Schema and atdpy testing pip install jsonschema pytest mypy flake8 +# To get latest ldc version +source $(curl https://dlang.org/install.sh | bash -s ldc -a) +BASH_ENV=~/.bashrc + ###### Sanity checks ###### echo 'check opam' diff --git a/atdd/test/dlang-tests/dune b/atdd/test/dlang-tests/dune index ed91f516..bcabec99 100644 --- a/atdd/test/dlang-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -21,6 +21,6 @@ (glob_files *.d)) (action (progn - (run ldc2 %{deps} --of test) + (bash "source $(curl -fsS https://dlang.org/install.sh | bash -s ldc -a) && ldc2 %{deps} --of test") (bash ./test) ))) From 9e0394447edf64e745981419f5de4cf56118aec3 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 13 Sep 2023 17:14:20 +0800 Subject: [PATCH 90/91] atdd: try to fix ci --- .circleci/setup-system | 2 +- atdd/test/dlang-tests/dune | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.circleci/setup-system b/.circleci/setup-system index b7128c14..1eaab0b7 100755 --- a/.circleci/setup-system +++ b/.circleci/setup-system @@ -27,7 +27,7 @@ pip install jsonschema pytest mypy flake8 # To get latest ldc version source $(curl https://dlang.org/install.sh | bash -s ldc -a) -BASH_ENV=~/.bashrc +echo "source $(~/dlang/install.sh ldc -a)" >> "$BASH_ENV" ###### Sanity checks ###### diff --git a/atdd/test/dlang-tests/dune b/atdd/test/dlang-tests/dune index bcabec99..60425b1f 100644 --- a/atdd/test/dlang-tests/dune +++ b/atdd/test/dlang-tests/dune @@ -21,6 +21,6 @@ (glob_files *.d)) (action (progn - (bash "source $(curl -fsS https://dlang.org/install.sh | bash -s ldc -a) && ldc2 %{deps} --of test") + (bash "ldc2 %{deps} --of test") (bash ./test) ))) From de2b52089dce3715c7074d3419f2e7fb11909bcf Mon Sep 17 00:00:00 2001 From: Louis Date: Tue, 19 Sep 2023 14:00:52 +0800 Subject: [PATCH 91/91] changes: add PR number for atdd --- CHANGES.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index f0a37dca..4b813768 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ master ------------------- -* atdd: Add `dlang` backend to generate D code from ATD definitions +* atdd: Add `dlang` backend to generate D code from ATD definitions (#349) 2.12.0 (2023-05-12) -------------------