From f4d9982e0f39f1450e99135375b2f80103b69b5a Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Fri, 1 Dec 2023 14:27:46 +0800 Subject: [PATCH 1/9] atdd: use alias this for variant with value, and improve construction --- atdd/src/lib/Codegen.ml | 3 ++- atdd/test/dlang-expected/everything_atd.d | 12 +++++++++--- atdd/test/dlang-tests/test_atdd.d | 2 +- 3 files changed, 12 insertions(+), 5 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 491d8b61..e6b897f6 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -931,7 +931,8 @@ 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)); (* TODO : very dubious*) + Line (sprintf "struct %s {\n%s value; alias value this;" (trans env unique_name) (type_name_of_expr env e) ); + Line (sprintf "@safe this(T)(T init) {value = init;} @safe this(%s init) {value = init.value;}}" (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("}"); diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index 0ec09b4a..51310906 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -373,7 +373,9 @@ struct Root_ {} // Original type: kind = [ ... | Thing of ... | ... ] -struct Thing { int value; } +struct Thing { +int value; alias value this; +@safe this(T)(T init) {value = init;} @safe this(Thing init) {value = init.value;}} @trusted JSONValue toJson(T : Thing)(T e) { return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); } @@ -387,7 +389,9 @@ struct WOW {} // Original type: kind = [ ... | Amaze of ... | ... ] -struct Amaze { string[] value; } +struct Amaze { +string[] value; alias value this; +@safe this(T)(T init) {value = init;} @safe this(Amaze init) {value = init.value;}} @trusted JSONValue toJson(T : Amaze)(T e) { return JSONValue([JSONValue("!!!"), _atd_write_list!(_atd_write_string)(e.value)]); } @@ -680,7 +684,9 @@ struct A {} // Original type: frozen = [ ... | B of ... | ... ] -struct B { int value; } +struct B { +int value; alias value this; +@safe this(T)(T init) {value = init;} @safe this(B init) {value = init.value;}} @trusted JSONValue toJson(T : B)(T e) { return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index b1ee8a85..325d84ad 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -71,7 +71,7 @@ void setupTests() obj.assoc4 = ["g": 7, "h": 8]; obj.kind = Root_().to!Kind; obj.kinds = [ - WOW().to!Kind, Thing(99).to!Kind, Amaze(["a", "b"]).to!Kind, Root_().to!Kind + WOW().to!Kind, 99.to!Thing.to!Kind, ["a", "b"].to!Amaze.to!Kind, Root_().to!Kind ]; obj.nullables = [ 12.Nullable!int, Nullable!int.init, Nullable!int.init, From 85de1f428e407594235f6fa91362fda4410bdeb4 Mon Sep 17 00:00:00 2001 From: Gregoire Lionnet Date: Fri, 1 Dec 2023 07:29:24 +0000 Subject: [PATCH 2/9] atdd: annot definition for type_shape (untested) --- atdd/src/lib/Codegen.ml | 1 + atdd/src/lib/Dlang_annot.ml | 18 ++++++++++++++++++ atdd/src/lib/Dlang_annot.mli | 12 ++++++++++++ 3 files changed, 31 insertions(+) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index e6b897f6..4c86bb98 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -26,6 +26,7 @@ let annot_schema_dlang : Atd.Annot.schema_section = { section = "dlang"; fields = [ + Type_def, "shape"; Type_expr, "t"; Type_expr, "repr"; Type_expr, "unwrap"; diff --git a/atdd/src/lib/Dlang_annot.ml b/atdd/src/lib/Dlang_annot.ml index 30791474..61471e56 100644 --- a/atdd/src/lib/Dlang_annot.ml +++ b/atdd/src/lib/Dlang_annot.ml @@ -8,6 +8,11 @@ type assoc_repr = | List | Dict +type shape = + | Enum + | Default + | Recursive + type atd_dlang_wrap = { dlang_wrap_t : string; dlang_wrap : string; @@ -21,6 +26,19 @@ let get_dlang_default an : string option = ~field:"default" an +let get_dlang_type_shape an : shape = + Atd.Annot.get_field + ~parse:(function + | "enum" -> Some Enum + | "default" -> Some Default + | "rec" | "recursive" -> Some Recursive + | _ -> None + ) + ~default:Default + ~sections:["dlang"] + ~field:"shape" + an + let get_dlang_assoc_repr an : assoc_repr = Atd.Annot.get_field ~parse:(function diff --git a/atdd/src/lib/Dlang_annot.mli b/atdd/src/lib/Dlang_annot.mli index 64de6cf7..22153223 100644 --- a/atdd/src/lib/Dlang_annot.mli +++ b/atdd/src/lib/Dlang_annot.mli @@ -19,6 +19,18 @@ type assoc_repr = | List | Dict + +(** How to represent a variant or record. + [Recursive] Use this option when the type has recursive references, leading to members defined as pointers. + [Enum] If a variant doesn't contain fields, this option will allow to represent it as an enum instead of a sum type. +*) +type shape = + | Enum + | Default + | Recursive + +val get_dlang_type_shape : Atd.Annot.t -> shape + (** Inspect annotations placed on lists of pairs such as [(string * foo) list ]. Permissible values for the [repr] field are ["dict"] and ["list"]. From f7f15387408db663ca6f2df2532ca500009fe01e Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 4 Dec 2023 14:31:43 +0800 Subject: [PATCH 3/9] atdd: add pointer support for non basic and non pointer types --- atdd/src/lib/Codegen.ml | 166 +++++++++++++++++----- atdd/test/dlang-expected/everything_atd.d | 147 +++++++++++++++---- atdd/test/dlang-tests/test_atdd.d | 21 ++- 3 files changed, 265 insertions(+), 69 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 4c86bb98..a9682bf5 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -261,7 +261,7 @@ private else throw _atd_bad_json("unit", x); } - + auto _atd_read_bool(JSONValue x) { try @@ -293,7 +293,7 @@ private catch (JSONException e) throw _atd_bad_json("string", x); } - + template _atd_read_list(alias readElements) { auto _atd_read_list(JSONValue jsonVal) @@ -508,6 +508,94 @@ auto toJsonString(T)(T obj) return res.toString; } +template _atd_write_ptr(alias writeElm) +{ + JSONValue _atd_write_ptr(P : T*, T)(P ptr) + { + if (ptr is null) + return JSONValue(null); + else + return writeElm(*ptr); + } +} + +template _atd_read_ptr(alias readElm) +{ + alias T = ReturnType!readElm; + alias P = T*; + + P _atd_read_ptr(JSONValue x) + { + if (x == JSONValue(null)) + return null; + + T* heapVal = new T; + *heapVal = readElm(x); + + return heapVal; + } +} + +// handling deserialisation into and from pointers +@trusted T* fromJson(P : T*, T)(JSONValue x) +{ + return _atd_read_ptr!(j => j.fromJson!T)(x); +} + +@trusted JSONValue toJson(P : T*, T)(P x) +{ + return _atd_write_ptr!(e => e.toJson!T)(x); +} + +bool fromJson(T : bool)(JSONValue x) +{ + return _atd_read_bool(x); +} + +int fromJson(T : int)(JSONValue x) +{ + return _atd_read_int(x); +} + +float fromJson(T : float)(JSONValue x) +{ + return _atd_read_float(x); +} + +string fromJson(T : string)(JSONValue x) +{ + return _atd_read_string(x); +} + +E[] fromJson(T : E[], E)(JSONValue x) +{ + return _atd_read_list!(j => j.fromJson!E)(x); +} + +JSONValue toJson(T : bool)(T x) +{ + return _atd_write_bool(x); +} + +JSONValue toJson(T : int)(T x) +{ + return _atd_write_int(x); +} + +JSONValue toJson(T : float)(T x) +{ + return _atd_write_float(x); +} + +JSONValue toJson(T : string)(T x) +{ + return _atd_write_string(x); +} + +JSONValue toJson(T : E[], E)(T x) +{ + return _atd_write_list!(e => e.toJson!E)(x); +} |} atd_filename atd_filename @@ -558,7 +646,11 @@ 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 ?(is_ptr=false) env (name : string) = + let ptr_symbol = match is_ptr with + | true -> "*" + | false -> "" + in match name with | "unit" -> "void" | "bool" -> "bool" @@ -568,9 +660,12 @@ let dlang_type_name env (name : string) = | "abstract" -> "JSONValue" | user_defined -> let typename = (struct_name env user_defined) in - typename + sprintf "%s%s" typename ptr_symbol -let rec type_name_of_expr env (e : type_expr) : string = +let rec type_name_of_expr ?(is_ptr=false) env (e : type_expr) : string = + let ptr_symbol = match is_ptr with + | true -> "*" + | false -> "" in match e with | Sum (loc, _, _) -> not_implemented loc "inline sum types" | Record (loc, _, _) -> not_implemented loc "inline records" @@ -579,7 +674,7 @@ 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)%s" (String.concat ", " type_names) ptr_symbol | List (loc, e, an) -> (match assoc_kind loc e an with | Array_list @@ -594,18 +689,24 @@ let rec type_name_of_expr env (e : type_expr) : string = sprintf "%s[string]" (type_name_of_expr env value) ) - | 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) + | Option (loc, e, an) -> sprintf "Nullable!(%s)%s" (type_name_of_expr env e) ptr_symbol + | Nullable (loc, e, an) -> sprintf "Nullable!(%s)%s" (type_name_of_expr env e) ptr_symbol | Shared (loc, e, an) -> not_implemented loc "shared" (* TODO *) | 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_t ) - | Name (loc, (loc2, name, []), an) -> dlang_type_name env name + | Name (loc, (loc2, name, []), an) -> dlang_type_name ~is_ptr env name | Name (loc, (_, name, _::_), _) -> assert false | Tvar (loc, _) -> not_implemented loc "type variables" +let should_wrap_ptr is_rec env (e : type_expr) : bool = + let name = type_name_of_expr ~is_ptr:is_rec env e in + let len = String.length name in + if len > 0 then name.[len - 1] = '*' + else false + let rec get_default_default (e : type_expr) : string option = match e with | Sum _ @@ -707,31 +808,27 @@ 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 +let construct_json_field ?(is_rec=false) 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 = + let writer_fn n = json_writer ~nested:n env unwrapped_type in + let wrap_in_optional writer = sprintf "_atd_write_option!(%s)" writer in + let wrap_in_ptr writer = match should_wrap_ptr is_rec env e with + | true -> sprintf "_atd_write_ptr!(%s)" writer + | false -> writer + in + let assignment writer = [ Line (sprintf "res[\"%s\"] = %s(obj.%s);" (Atd.Json.get_json_fname name an |> single_esc) - writer_function + writer (inst_var_name trans_meth name)) ] in match kind with | Required - | With_default -> assignment - | Optional -> - [ - Line (sprintf "if (!obj.%s.isNull)" - (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!" - (json_writer ~nested:true env unwrapped_type) - (inst_var_name trans_meth name))]; - ] + | With_default -> assignment (wrap_in_ptr (writer_fn false)) + | Optional -> assignment (wrap_in_ptr (wrap_in_optional (writer_fn true))) (* Function value that can be applied to a JSON node, converting it @@ -795,7 +892,7 @@ and tuple_reader env cells = })" (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) = + ?(is_rec=false) 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 else_body = @@ -814,17 +911,21 @@ let from_json_class_argument (sprintf "missing default Dlang value for field '%s'" name) in + let reader = match should_wrap_ptr is_rec env e with + | true -> sprintf "_atd_read_ptr!(%s)" (json_reader env e) + | false -> (json_reader env e) + in sprintf "obj.%s = (\"%s\" in x) ? %s(x[\"%s\"]) : %s;" dlang_name (single_esc json_name) - (json_reader env e) + reader (single_esc json_name) else_body let inst_var_declaration - env trans_meth ((loc, (name, kind, an), e) : simple_field) = + ?(is_rec=false) 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 ~is_ptr:is_rec env e in let unwrapped_e = unwrap_field_type loc name kind e in let default = match kind with @@ -834,12 +935,13 @@ let inst_var_declaration match get_dlang_default unwrapped_e an with | None -> "" | Some x -> sprintf " = %s" x - in + in [ Line (sprintf "%s %s%s;" type_name var_name default) ] let record env loc name (fields : field list) an = + let is_rec = true in let dlang_struct_name = struct_name env name in let trans_meth = env.translate_inst_variable () in let fields = @@ -849,14 +951,14 @@ let record env loc name (fields : field list) an = fields in let inst_var_declarations = - List.map (fun x -> Inline (inst_var_declaration env trans_meth x)) fields + List.map (fun x -> Inline (inst_var_declaration ~is_rec:is_rec env trans_meth x)) fields in let json_object_body = List.map (fun x -> - Inline (construct_json_field env trans_meth x)) fields in + Inline (construct_json_field ~is_rec:is_rec env trans_meth x)) fields in let from_json_class_arguments = List.map (fun x -> - Line (from_json_class_argument env trans_meth dlang_struct_name x) + Line (from_json_class_argument ~is_rec:is_rec env trans_meth dlang_struct_name x) ) fields in let from_json = [ diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index 51310906..73a800eb 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -77,7 +77,7 @@ private else throw _atd_bad_json("unit", x); } - + auto _atd_read_bool(JSONValue x) { try @@ -109,7 +109,7 @@ private catch (JSONException e) throw _atd_bad_json("string", x); } - + template _atd_read_list(alias readElements) { auto _atd_read_list(JSONValue jsonVal) @@ -324,6 +324,94 @@ auto toJsonString(T)(T obj) return res.toString; } +template _atd_write_ptr(alias writeElm) +{ + JSONValue _atd_write_ptr(P : T*, T)(P ptr) + { + if (ptr is null) + return JSONValue(null); + else + return writeElm(*ptr); + } +} + +template _atd_read_ptr(alias readElm) +{ + alias T = ReturnType!readElm; + alias P = T*; + + P _atd_read_ptr(JSONValue x) + { + if (x == JSONValue(null)) + return null; + + T* heapVal = new T; + *heapVal = readElm(x); + + return heapVal; + } +} + +// handling deserialisation into and from pointers +@trusted T* fromJson(P : T*, T)(JSONValue x) +{ + return _atd_read_ptr!(j => j.fromJson!T)(x); +} + +@trusted JSONValue toJson(P : T*, T)(P x) +{ + return _atd_write_ptr!(e => e.toJson!T)(x); +} + +bool fromJson(T : bool)(JSONValue x) +{ + return _atd_read_bool(x); +} + +int fromJson(T : int)(JSONValue x) +{ + return _atd_read_int(x); +} + +float fromJson(T : float)(JSONValue x) +{ + return _atd_read_float(x); +} + +string fromJson(T : string)(JSONValue x) +{ + return _atd_read_string(x); +} + +E[] fromJson(T : E[], E)(JSONValue x) +{ + return _atd_read_list!(j => j.fromJson!E)(x); +} + +JSONValue toJson(T : bool)(T x) +{ + return _atd_write_bool(x); +} + +JSONValue toJson(T : int)(T x) +{ + return _atd_write_int(x); +} + +JSONValue toJson(T : float)(T x) +{ + return _atd_write_float(x); +} + +JSONValue toJson(T : string)(T x) +{ + return _atd_write_string(x); +} + +JSONValue toJson(T : E[], E)(T x) +{ + return _atd_write_list!(e => e.toJson!E)(x); +} @@ -525,24 +613,24 @@ struct Root { float float_with_auto_default = 0.0; float float_with_default = 0.1; int[][] items; - Nullable!int maybe; + Nullable!(int)* maybe; int[] extras = []; int answer = 42; - Alias aliased; - Tuple!(float, float) point; - Kind kind; + Alias* aliased; + Tuple!(float, float)* point; + Kind* kind; Kind[] kinds; Tuple!(float, int)[] assoc1; Tuple!(string, int)[] assoc2; int[float] assoc3; int[string] assoc4; - Nullable!int[] nullables; - Nullable!int[] options; + Nullable!(int)[] nullables; + Nullable!(int)[] options; JSONValue[] untyped_things; - IntFloatParametrizedRecord parametrized_record; - KindParametrizedTuple parametrized_tuple; + IntFloatParametrizedRecord* parametrized_record; + KindParametrizedTuple* parametrized_tuple; uint16_t wrapped; - AliasOfAliasOfAlias aaa; + AliasOfAliasOfAlias* aaa; } @trusted Root fromJson(T : Root)(JSONValue x) { @@ -554,16 +642,16 @@ struct Root { 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))(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.maybe = ("maybe" in x) ? _atd_read_ptr!(_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 { + obj.aliased = ("aliased" in x) ? _atd_read_ptr!(fromJson!Alias)(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); + obj.point = ("point" in x) ? _atd_read_ptr!(((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.kind = ("kind" in x) ? fromJson!Kind(x["kind"]) : _atd_missing_json_field!(typeof(obj.kind))("Root", "kind"); + }))(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); + obj.kind = ("kind" in x) ? _atd_read_ptr!(fromJson!Kind)(x["kind"]) : _atd_missing_json_field!(typeof(obj.kind))("Root", "kind"); 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) @@ -576,10 +664,10 @@ struct Root { 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.parametrized_record = ("parametrized_record" in x) ? _atd_read_ptr!(fromJson!IntFloatParametrizedRecord)(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); + obj.parametrized_tuple = ("parametrized_tuple" in x) ? _atd_read_ptr!(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, (St e) => ((St st) => st.to!int.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"); + obj.aaa = ("aaa" in x) ? _atd_read_ptr!(fromJson!AliasOfAliasOfAlias)(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); return obj; } @trusted JSONValue toJson(T : Root)(T obj) { @@ -591,13 +679,12 @@ struct Root { 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))(obj.items); - if (!obj.maybe.isNull) - res["maybe"] = _atd_write_option!(_atd_write_int)(obj.maybe); + res["maybe"] = _atd_write_ptr!(_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"] = ((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["kind"] = ((Kind x) => x.toJson!(Kind))(obj.kind); + res["aliased"] = _atd_write_ptr!(((Alias x) => x.toJson!(Alias)))(obj.aliased); + res["point"] = _atd_write_ptr!(((Tuple!(float, float) x) => JSONValue([_atd_write_float(x[0]), _atd_write_float(x[1])])))(obj.point); + res["kind"] = _atd_write_ptr!(((Kind x) => x.toJson!(Kind)))(obj.kind); res["kinds"] = _atd_write_list!(((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); @@ -606,10 +693,10 @@ struct Root { 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"] = ((IntFloatParametrizedRecord x) => x.toJson!(IntFloatParametrizedRecord))(obj.parametrized_record); - res["parametrized_tuple"] = ((KindParametrizedTuple x) => x.toJson!(KindParametrizedTuple))(obj.parametrized_tuple); + res["parametrized_record"] = _atd_write_ptr!(((IntFloatParametrizedRecord x) => x.toJson!(IntFloatParametrizedRecord)))(obj.parametrized_record); + res["parametrized_tuple"] = _atd_write_ptr!(((KindParametrizedTuple x) => x.toJson!(KindParametrizedTuple)))(obj.parametrized_tuple); res["wrapped"] = _atd_write_wrap!(((St x) => x.toJson!(St)), (uint16_t e) => ((uint16_t e) => St(e.to!int))(e))(obj.wrapped); - res["aaa"] = ((AliasOfAliasOfAlias x) => x.toJson!(AliasOfAliasOfAlias))(obj.aaa); + res["aaa"] = _atd_write_ptr!(((AliasOfAliasOfAlias x) => x.toJson!(AliasOfAliasOfAlias)))(obj.aaa); return res; } @@ -736,19 +823,19 @@ struct DefaultList { struct Credential { string name; - Password password; + Password* password; } @trusted Credential fromJson(T : Credential)(JSONValue x) { Credential obj; obj.name = ("name" in x) ? _atd_read_string(x["name"]) : _atd_missing_json_field!(typeof(obj.name))("Credential", "name"); - obj.password = ("password" in x) ? fromJson!Password(x["password"]) : _atd_missing_json_field!(typeof(obj.password))("Credential", "password"); + obj.password = ("password" in x) ? _atd_read_ptr!(fromJson!Password)(x["password"]) : _atd_missing_json_field!(typeof(obj.password))("Credential", "password"); return obj; } @trusted JSONValue toJson(T : Credential)(T obj) { JSONValue res; res["name"] = _atd_write_string(obj.name); - res["password"] = ((Password x) => x.toJson!(Password))(obj.password); + res["password"] = _atd_write_ptr!(((Password x) => x.toJson!(Password)))(obj.password); return res; } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index 325d84ad..f5750024 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -10,6 +10,13 @@ bool testFailed = false; void setupTests() { + tests["basicTypes"] = { + auto floatList = [0.1, 0.5, -0.8]; + auto jsonStr = floatList.toJsonString; + + assert(floatList.toJsonString == jsonStr.fromJsonString!(float[]).toJsonString); + }; + tests["simpleRecord"] = { auto record = IntFloatParametrizedRecord(32, [5.4, 3.3]); @@ -62,14 +69,14 @@ void setupTests() obj.x___init__ = 0.32f; obj.items = [[], [1, 2]]; obj.extras = [17, 53]; - obj.aliased = [1, 6, 8]; - obj.maybe = 43; - obj.point = tuple(4.3, 1.2); + obj.aliased = new Alias([1, 6, 8]); + obj.maybe = new Nullable!int(43); + obj.point = new Tuple!(float, float)(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.kind = Root_().to!Kind; + obj.kind = new Kind(Root_().to!Kind); obj.kinds = [ WOW().to!Kind, 99.to!Thing.to!Kind, ["a", "b"].to!Amaze.to!Kind, Root_().to!Kind ]; @@ -86,14 +93,14 @@ void setupTests() JSONValue(new int[string]), JSONValue(123) ]; - obj.parametrized_record = IntFloatParametrizedRecord(42, [9.9f, 8.8f]); - obj.parametrized_tuple = KindParametrizedTuple(WOW(), WOW(), 100); + obj.parametrized_record = new IntFloatParametrizedRecord(42, [9.9f, 8.8f]); + obj.parametrized_tuple = new KindParametrizedTuple(WOW(), WOW(), 100); () @safe { auto jsonStr = obj.toJsonString; auto newObj = jsonStr.fromJsonString!Root; - assert(obj == newObj); + assert(obj.toJsonString == newObj.toJsonString); }(); }; From 3750a90e18cc4b39e1c9b77181ab4fdfdcd314d5 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 4 Dec 2023 15:43:02 +0800 Subject: [PATCH 4/9] atdd: support and tags for variants and records --- atdd/src/lib/Codegen.ml | 41 +++--- atdd/test/atd-input/everything.atd | 15 ++- atdd/test/dlang-expected/everything_atd.d | 152 ++++++++++++++++++---- atdd/test/dlang-tests/test_atdd.d | 19 ++- 4 files changed, 172 insertions(+), 55 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index a9682bf5..ff96beae 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -940,8 +940,7 @@ let inst_var_declaration Line (sprintf "%s %s%s;" type_name var_name default) ] -let record env loc name (fields : field list) an = - let is_rec = true in +let record env loc name (fields : field list) an is_rec = let dlang_struct_name = struct_name env name in let trans_meth = env.translate_inst_variable () in let fields = @@ -1177,19 +1176,31 @@ let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = if param <> [] then not_implemented loc "parametrized type"; let unwrap e = - match e with - | Sum (loc, cases, an) -> - sum env loc name cases - | Record (loc, fields, an) -> - record env loc name fields an - | Tuple _ - | List _ - | Option _ - | Nullable _ - | Wrap _ - | Name _ -> alias_wrapper env name e - | Shared _ -> not_implemented loc "cyclic references" - | Tvar _ -> not_implemented loc "parametrized type" + match Dlang_annot.get_dlang_type_shape an with + | Enum -> + (match e with + | Sum (loc, cases, an) -> + sum env loc name cases + | _ -> not_implemented loc "shape enum but not sumtype") + | Recursive -> + (match e with + | Record (loc, fields, an) -> + record env loc name fields an true + | _ -> not_implemented loc "shape recursive but not record") + | Default -> + (match e with + | Sum (loc, cases, an) -> + sum env loc name cases + | Record (loc, fields, an) -> + record env loc name fields an false + | Tuple _ + | List _ + | Option _ + | Nullable _ + | Wrap _ + | Name _ -> alias_wrapper env name e + | Shared _ -> not_implemented loc "cyclic references" + | Tvar _ -> not_implemented loc "parametrized type") in unwrap e diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index a41c6ad6..f2022d1a 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -12,6 +12,17 @@ type frozen = [ | B of int ] +type planet = [ + | Mercury + | Venus + | Earth + | Mars + | Saturn + | Jupiter + | Neptune + | Uranus +] + type ('a, 'b) parametrized_record = { field_a: 'a; ~field_b: 'b list; @@ -71,10 +82,10 @@ type require_field = { req: string; } -type recursive_class = { +type recursive_class = { id: int; flag: bool; - children: recursive_class list; + children: recursive_class; } type default_list = { diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index 73a800eb..a836683d 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -421,21 +421,21 @@ import std.stdint : uint32_t, uint16_t; struct RecursiveClass { int id; bool flag; - RecursiveClass[] children; + RecursiveClass* children; } @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)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); + obj.children = ("children" in x) ? _atd_read_ptr!(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!(((RecursiveClass x) => x.toJson!(RecursiveClass)))(obj.children); + res["children"] = _atd_write_ptr!(((RecursiveClass x) => x.toJson!(RecursiveClass)))(obj.children); return res; } @@ -613,12 +613,12 @@ struct Root { float float_with_auto_default = 0.0; float float_with_default = 0.1; int[][] items; - Nullable!(int)* maybe; + Nullable!(int) maybe; int[] extras = []; int answer = 42; - Alias* aliased; - Tuple!(float, float)* point; - Kind* kind; + Alias aliased; + Tuple!(float, float) point; + Kind kind; Kind[] kinds; Tuple!(float, int)[] assoc1; Tuple!(string, int)[] assoc2; @@ -627,10 +627,10 @@ struct Root { Nullable!(int)[] nullables; Nullable!(int)[] options; JSONValue[] untyped_things; - IntFloatParametrizedRecord* parametrized_record; - KindParametrizedTuple* parametrized_tuple; + IntFloatParametrizedRecord parametrized_record; + KindParametrizedTuple parametrized_tuple; uint16_t wrapped; - AliasOfAliasOfAlias* aaa; + AliasOfAliasOfAlias aaa; } @trusted Root fromJson(T : Root)(JSONValue x) { @@ -642,16 +642,16 @@ struct Root { 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))(x["items"]) : _atd_missing_json_field!(typeof(obj.items))("Root", "items"); - obj.maybe = ("maybe" in x) ? _atd_read_ptr!(_atd_read_option!(_atd_read_int))(x["maybe"]) : typeof(obj.maybe).init; + 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) ? _atd_read_ptr!(fromJson!Alias)(x["aliased"]) : _atd_missing_json_field!(typeof(obj.aliased))("Root", "aliased"); - obj.point = ("point" in x) ? _atd_read_ptr!(((JSONValue x) @trusted { + 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 { 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.kind = ("kind" in x) ? _atd_read_ptr!(fromJson!Kind)(x["kind"]) : _atd_missing_json_field!(typeof(obj.kind))("Root", "kind"); + })(x["point"]) : _atd_missing_json_field!(typeof(obj.point))("Root", "point"); + obj.kind = ("kind" in x) ? fromJson!Kind(x["kind"]) : _atd_missing_json_field!(typeof(obj.kind))("Root", "kind"); 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) @@ -664,10 +664,10 @@ struct Root { 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) ? _atd_read_ptr!(fromJson!IntFloatParametrizedRecord)(x["parametrized_record"]) : _atd_missing_json_field!(typeof(obj.parametrized_record))("Root", "parametrized_record"); - obj.parametrized_tuple = ("parametrized_tuple" in x) ? _atd_read_ptr!(fromJson!KindParametrizedTuple)(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"); obj.wrapped = ("wrapped" in x) ? _atd_read_wrap!(fromJson!St, (St e) => ((St st) => st.to!int.to!uint16_t)(e))(x["wrapped"]) : _atd_missing_json_field!(typeof(obj.wrapped))("Root", "wrapped"); - obj.aaa = ("aaa" in x) ? _atd_read_ptr!(fromJson!AliasOfAliasOfAlias)(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); + obj.aaa = ("aaa" in x) ? fromJson!AliasOfAliasOfAlias(x["aaa"]) : _atd_missing_json_field!(typeof(obj.aaa))("Root", "aaa"); return obj; } @trusted JSONValue toJson(T : Root)(T obj) { @@ -679,12 +679,12 @@ struct Root { 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))(obj.items); - res["maybe"] = _atd_write_ptr!(_atd_write_option!(_atd_write_int))(obj.maybe); + 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"] = _atd_write_ptr!(((Alias x) => x.toJson!(Alias)))(obj.aliased); - res["point"] = _atd_write_ptr!(((Tuple!(float, float) x) => JSONValue([_atd_write_float(x[0]), _atd_write_float(x[1])])))(obj.point); - res["kind"] = _atd_write_ptr!(((Kind x) => x.toJson!(Kind)))(obj.kind); + res["aliased"] = ((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["kind"] = ((Kind x) => x.toJson!(Kind))(obj.kind); res["kinds"] = _atd_write_list!(((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); @@ -693,10 +693,10 @@ struct Root { 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"] = _atd_write_ptr!(((IntFloatParametrizedRecord x) => x.toJson!(IntFloatParametrizedRecord)))(obj.parametrized_record); - res["parametrized_tuple"] = _atd_write_ptr!(((KindParametrizedTuple x) => x.toJson!(KindParametrizedTuple)))(obj.parametrized_tuple); + res["parametrized_record"] = ((IntFloatParametrizedRecord x) => x.toJson!(IntFloatParametrizedRecord))(obj.parametrized_record); + res["parametrized_tuple"] = ((KindParametrizedTuple x) => x.toJson!(KindParametrizedTuple))(obj.parametrized_tuple); res["wrapped"] = _atd_write_wrap!(((St x) => x.toJson!(St)), (uint16_t e) => ((uint16_t e) => St(e.to!int))(e))(obj.wrapped); - res["aaa"] = _atd_write_ptr!(((AliasOfAliasOfAlias x) => x.toJson!(AliasOfAliasOfAlias)))(obj.aaa); + res["aaa"] = ((AliasOfAliasOfAlias x) => x.toJson!(AliasOfAliasOfAlias))(obj.aaa); return res; } @@ -733,6 +733,102 @@ struct RecordWithWrappedType { } +// Original type: planet = [ ... | Mercury | ... ] +struct Mercury {} +@trusted JSONValue toJson(T : Mercury)(T e) { + return JSONValue("Mercury"); +} + + +// Original type: planet = [ ... | Venus | ... ] +struct Venus {} +@trusted JSONValue toJson(T : Venus)(T e) { + return JSONValue("Venus"); +} + + +// Original type: planet = [ ... | Earth | ... ] +struct Earth {} +@trusted JSONValue toJson(T : Earth)(T e) { + return JSONValue("Earth"); +} + + +// Original type: planet = [ ... | Mars | ... ] +struct Mars {} +@trusted JSONValue toJson(T : Mars)(T e) { + return JSONValue("Mars"); +} + + +// Original type: planet = [ ... | Saturn | ... ] +struct Saturn {} +@trusted JSONValue toJson(T : Saturn)(T e) { + return JSONValue("Saturn"); +} + + +// Original type: planet = [ ... | Jupiter | ... ] +struct Jupiter {} +@trusted JSONValue toJson(T : Jupiter)(T e) { + return JSONValue("Jupiter"); +} + + +// Original type: planet = [ ... | Neptune | ... ] +struct Neptune {} +@trusted JSONValue toJson(T : Neptune)(T e) { + return JSONValue("Neptune"); +} + + +// Original type: planet = [ ... | Uranus | ... ] +struct Uranus {} +@trusted JSONValue toJson(T : Uranus)(T e) { + return JSONValue("Uranus"); +} + + +struct Planet{ SumType!(Mercury, Venus, Earth, Mars, Saturn, Jupiter, Neptune, Uranus) _data; alias _data this; +@safe this(T)(T init) {_data = init;} @safe this(Planet init) {_data = init._data;}} + +@trusted Planet fromJson(T : Planet)(JSONValue x) { + if (x.type == JSONType.string) { + if (x.str == "Mercury") + return Planet(Mercury()); + if (x.str == "Venus") + return Planet(Venus()); + if (x.str == "Earth") + return Planet(Earth()); + if (x.str == "Mars") + return Planet(Mars()); + if (x.str == "Saturn") + return Planet(Saturn()); + if (x.str == "Jupiter") + return Planet(Jupiter()); + if (x.str == "Neptune") + return Planet(Neptune()); + if (x.str == "Uranus") + return Planet(Uranus()); + throw _atd_bad_json("Planet", x); + } + throw _atd_bad_json("Planet", x); +} + +@trusted JSONValue toJson(T : Planet)(T x) { + return x.match!( + (Mercury v) => v.toJson!(Mercury), +(Venus v) => v.toJson!(Venus), +(Earth v) => v.toJson!(Earth), +(Mars v) => v.toJson!(Mars), +(Saturn v) => v.toJson!(Saturn), +(Jupiter v) => v.toJson!(Jupiter), +(Neptune v) => v.toJson!(Neptune), +(Uranus v) => v.toJson!(Uranus) + ); +} + + struct Password{uint32_t _data; alias _data this; this(uint32_t init) @safe {_data = init;} this(Password init) @safe {_data = init._data;} @@ -823,19 +919,19 @@ struct DefaultList { struct Credential { string name; - Password* password; + Password password; } @trusted Credential fromJson(T : Credential)(JSONValue x) { Credential obj; obj.name = ("name" in x) ? _atd_read_string(x["name"]) : _atd_missing_json_field!(typeof(obj.name))("Credential", "name"); - obj.password = ("password" in x) ? _atd_read_ptr!(fromJson!Password)(x["password"]) : _atd_missing_json_field!(typeof(obj.password))("Credential", "password"); + obj.password = ("password" in x) ? fromJson!Password(x["password"]) : _atd_missing_json_field!(typeof(obj.password))("Credential", "password"); return obj; } @trusted JSONValue toJson(T : Credential)(T obj) { JSONValue res; res["name"] = _atd_write_string(obj.name); - res["password"] = _atd_write_ptr!(((Password x) => x.toJson!(Password)))(obj.password); + res["password"] = ((Password x) => x.toJson!(Password))(obj.password); return res; } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index f5750024..e86bd302 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -69,14 +69,14 @@ void setupTests() obj.x___init__ = 0.32f; obj.items = [[], [1, 2]]; obj.extras = [17, 53]; - obj.aliased = new Alias([1, 6, 8]); - obj.maybe = new Nullable!int(43); - obj.point = new Tuple!(float, float)(tuple(4.3, 1.2)); + 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 obj.assoc3 = [4.4f: 4, 5.5f: 5]; obj.assoc4 = ["g": 7, "h": 8]; - obj.kind = new Kind(Root_().to!Kind); + obj.kind = Root_().to!Kind; obj.kinds = [ WOW().to!Kind, 99.to!Thing.to!Kind, ["a", "b"].to!Amaze.to!Kind, Root_().to!Kind ]; @@ -93,8 +93,8 @@ void setupTests() JSONValue(new int[string]), JSONValue(123) ]; - obj.parametrized_record = new IntFloatParametrizedRecord(42, [9.9f, 8.8f]); - obj.parametrized_tuple = new KindParametrizedTuple(WOW(), WOW(), 100); + obj.parametrized_record = IntFloatParametrizedRecord(42, [9.9f, 8.8f]); + obj.parametrized_tuple = KindParametrizedTuple(WOW(), WOW(), 100); () @safe { auto jsonStr = obj.toJsonString; @@ -171,11 +171,10 @@ void setupTests() }; tests["recursiveClass"] = { - auto child1 = RecursiveClass(1, true, []); - auto child2 = RecursiveClass(2, true, []); - auto a_obj = RecursiveClass(0, false, [child1, child2]); + auto child1 = new RecursiveClass(1, true, null); + auto a_obj = RecursiveClass(0, false, child1); - assert (a_obj == a_obj.toJsonString.fromJsonString!RecursiveClass); + assert (a_obj.toJsonString == a_obj.toJsonString.fromJsonString!RecursiveClass.toJsonString); }; tests["using wrapped type"] = { From 7c5f3a34e4f081f5f9875de2806874514978d78b Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Mon, 4 Dec 2023 17:46:01 +0800 Subject: [PATCH 5/9] atdd: support enum representation for variants --- atdd/src/lib/Codegen.ml | 66 +++++++++- atdd/test/atd-input/everything.atd | 1 + atdd/test/dlang-expected/everything_atd.d | 147 +++++++++------------- atdd/test/dlang-tests/test_atdd.d | 8 ++ 4 files changed, 131 insertions(+), 91 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index ff96beae..3543699f 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -1087,7 +1087,69 @@ let read_cases1 env loc name cases1 = (struct_name env name |> single_esc)) ] -let sum_container env loc name cases = + +let enum_container env loc name cases = + let cases = + List.map (fun (x : variant) -> + match x with + | Variant (loc, (orig_name, an), opt_e) -> + let unique_name = create_struct_name env orig_name in + (loc, orig_name, unique_name, an, opt_e) + | Inherit _ -> assert false + ) cases + in + let cases0, cases1 = + List.partition (fun (loc, orig_name, unique_name, an, opt_e) -> + opt_e = None + ) cases + in + let fields_block = + List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + to_camel_case (trans env unique_name) + ) cases + |> String.concat ",\n" + in + let dlang_struct_name = struct_name env name in + if cases1 <> [] then + error_at loc "cannot use enum shape for variant with type" + else + [ + Line (sprintf "enum %s{" dlang_struct_name); + Line fields_block; + Line "}"; + Line ""; + Line (sprintf "%s fromJson(T : %s)(JSONValue x) @trusted {" + (single_esc dlang_struct_name) (single_esc dlang_struct_name)); + Line "if (x.type == JSONType.string) {"; + Block [ + Line "switch (x.str)"; + Line "{"; + (Line (List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + let json_name = Atd.Json.get_json_cons orig_name an in + (sprintf "case \"%s\":\n return Planet.%s;" (single_esc json_name) (trans env unique_name) ) + ) cases |> String.concat "\n");); + Line (sprintf "default: throw _atd_bad_json(\"%s\", x);" (single_esc (struct_name env name))); + Line "}"; + ]; + Line "}"; + Line (sprintf "throw _atd_bad_json(\"%s\", x);" + (single_esc (struct_name env name))); + Line "}"; + Line ""; + Line (sprintf "JSONValue toJson(T : %s)(T x) @trusted {" (dlang_struct_name)); + Block [ + Line "final switch (x) with (x)"; + Line "{"; + (Line (List.map (fun (loc, orig_name, unique_name, an, opt_e) -> + let json_name = Atd.Json.get_json_cons orig_name an in + (sprintf "case %s:\n return JSONValue(\"%s\");" (trans env unique_name) (single_esc json_name)) + ) cases |> String.concat "\n");); + Line "}"; + ]; + Line "}"; + ] + +let sum_container env loc name cases = let dlang_struct_name = struct_name env name in let type_list = List.map (fun (loc, orig_name, unique_name, an, opt_e) -> @@ -1180,7 +1242,7 @@ let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = | Enum -> (match e with | Sum (loc, cases, an) -> - sum env loc name cases + enum_container env loc name cases | _ -> not_implemented loc "shape enum but not sumtype") | Recursive -> (match e with diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index f2022d1a..9c774676 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -21,6 +21,7 @@ type planet = [ | Jupiter | Neptune | Uranus + | Pluto ] type ('a, 'b) parametrized_record = { diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index a836683d..ea3f2905 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -733,99 +733,68 @@ struct RecordWithWrappedType { } -// Original type: planet = [ ... | Mercury | ... ] -struct Mercury {} -@trusted JSONValue toJson(T : Mercury)(T e) { - return JSONValue("Mercury"); -} - - -// Original type: planet = [ ... | Venus | ... ] -struct Venus {} -@trusted JSONValue toJson(T : Venus)(T e) { - return JSONValue("Venus"); -} - - -// Original type: planet = [ ... | Earth | ... ] -struct Earth {} -@trusted JSONValue toJson(T : Earth)(T e) { - return JSONValue("Earth"); -} - - -// Original type: planet = [ ... | Mars | ... ] -struct Mars {} -@trusted JSONValue toJson(T : Mars)(T e) { - return JSONValue("Mars"); -} - - -// Original type: planet = [ ... | Saturn | ... ] -struct Saturn {} -@trusted JSONValue toJson(T : Saturn)(T e) { - return JSONValue("Saturn"); -} - - -// Original type: planet = [ ... | Jupiter | ... ] -struct Jupiter {} -@trusted JSONValue toJson(T : Jupiter)(T e) { - return JSONValue("Jupiter"); -} - - -// Original type: planet = [ ... | Neptune | ... ] -struct Neptune {} -@trusted JSONValue toJson(T : Neptune)(T e) { - return JSONValue("Neptune"); +enum Planet{ +Mercury, +Venus, +Earth, +Mars, +Saturn, +Jupiter, +Neptune, +Uranus, +Pluto +} + +Planet fromJson(T : Planet)(JSONValue x) @trusted { +if (x.type == JSONType.string) { + switch (x.str) + { + case "Mercury": + return Planet.Mercury; +case "Venus": + return Planet.Venus; +case "Earth": + return Planet.Earth; +case "Mars": + return Planet.Mars; +case "Saturn": + return Planet.Saturn; +case "Jupiter": + return Planet.Jupiter; +case "Neptune": + return Planet.Neptune; +case "Uranus": + return Planet.Uranus; +case "not a planet": + return Planet.Pluto; + default: throw _atd_bad_json("Planet", x); + } } - - -// Original type: planet = [ ... | Uranus | ... ] -struct Uranus {} -@trusted JSONValue toJson(T : Uranus)(T e) { - return JSONValue("Uranus"); +throw _atd_bad_json("Planet", x); } - -struct Planet{ SumType!(Mercury, Venus, Earth, Mars, Saturn, Jupiter, Neptune, Uranus) _data; alias _data this; -@safe this(T)(T init) {_data = init;} @safe this(Planet init) {_data = init._data;}} - -@trusted Planet fromJson(T : Planet)(JSONValue x) { - if (x.type == JSONType.string) { - if (x.str == "Mercury") - return Planet(Mercury()); - if (x.str == "Venus") - return Planet(Venus()); - if (x.str == "Earth") - return Planet(Earth()); - if (x.str == "Mars") - return Planet(Mars()); - if (x.str == "Saturn") - return Planet(Saturn()); - if (x.str == "Jupiter") - return Planet(Jupiter()); - if (x.str == "Neptune") - return Planet(Neptune()); - if (x.str == "Uranus") - return Planet(Uranus()); - throw _atd_bad_json("Planet", x); +JSONValue toJson(T : Planet)(T x) @trusted { + final switch (x) with (x) + { + case Mercury: + return JSONValue("Mercury"); +case Venus: + return JSONValue("Venus"); +case Earth: + return JSONValue("Earth"); +case Mars: + return JSONValue("Mars"); +case Saturn: + return JSONValue("Saturn"); +case Jupiter: + return JSONValue("Jupiter"); +case Neptune: + return JSONValue("Neptune"); +case Uranus: + return JSONValue("Uranus"); +case Pluto: + return JSONValue("not a planet"); } - throw _atd_bad_json("Planet", x); -} - -@trusted JSONValue toJson(T : Planet)(T x) { - return x.match!( - (Mercury v) => v.toJson!(Mercury), -(Venus v) => v.toJson!(Venus), -(Earth v) => v.toJson!(Earth), -(Mars v) => v.toJson!(Mars), -(Saturn v) => v.toJson!(Saturn), -(Jupiter v) => v.toJson!(Jupiter), -(Neptune v) => v.toJson!(Neptune), -(Uranus v) => v.toJson!(Uranus) - ); } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index e86bd302..e7dfe3a6 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -193,6 +193,14 @@ void setupTests() auto mapResult = credientials.map!((c) => c); }; + + tests["variant enum"] = { + Planet p = Planet.Earth; + + auto res = p.toJsonString.fromJsonString!Planet; + + assert(res.toJsonString == p.toJsonString); + }; } void assertThrows(T)(T fn, bool writeMsg = false) From b760518f596a6f8d2b551b617fce556831b914d7 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Tue, 5 Dec 2023 16:29:38 +0800 Subject: [PATCH 6/9] atdd: recursive variants implementation --- atdd/src/lib/Codegen.ml | 28 ++++++--- atdd/test/atd-input/everything.atd | 11 ++++ atdd/test/dlang-expected/everything_atd.d | 73 +++++++++++++++++++++-- atdd/test/dlang-tests/test_atdd.d | 11 ++++ 4 files changed, 110 insertions(+), 13 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 3543699f..bc870be8 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -1015,7 +1015,7 @@ let alias_wrapper env name type_expr = ] let case_class env type_name - (loc, orig_name, unique_name, an, opt_e) = + (loc, orig_name, unique_name, an, opt_e) is_rec = let json_name = Atd.Json.get_json_cons orig_name an in match opt_e with | None -> @@ -1033,10 +1033,22 @@ let case_class env type_name Line (sprintf {|// Original type: %s = [ ... | %s of ... | ... ]|} type_name orig_name); - Line (sprintf "struct %s {\n%s value; alias value this;" (trans env unique_name) (type_name_of_expr env e) ); - Line (sprintf "@safe this(T)(T init) {value = init;} @safe this(%s init) {value = init.value;}}" (trans env unique_name)); + (* Line (sprintf "struct %s {\n%s value; alias value this;" (trans env unique_name) (type_name_of_expr env e) ); *) + Inline (match is_rec with + | true -> ([ + Line (sprintf "struct %s {\n%s* value; alias getValue this;" (trans env unique_name) (type_name_of_expr env e)); + Line (sprintf "this(T)(T init) @safe {value = new %s(init);} %s getValue() @safe {return value is null ? (%s).init : *value;}" + (type_name_of_expr env e) (type_name_of_expr env e) (type_name_of_expr env e)); + Line (sprintf "this(%s init) @trusted {value = new %s; *value = init;}}" + (type_name_of_expr env e) (type_name_of_expr env e)); + ]) + | false -> ([ + Line (sprintf "struct %s {\n%s value; alias value this;" (trans env unique_name) (type_name_of_expr env e)); + Line (sprintf "this(T)(T init) @safe {value = init;} this(%s init) @safe{value = init.value;}}" (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))]; + Block [Line(sprintf "return JSONValue([JSONValue(\"%s\"), %s(e)]);" (single_esc json_name) (json_writer env e))]; Line("}"); ] @@ -1213,7 +1225,7 @@ let sum_container env loc name cases = ] -let sum env loc name cases = +let sum env loc name cases is_recursive = let cases = List.map (fun (x : variant) -> match x with @@ -1224,7 +1236,7 @@ let sum env loc name cases = ) cases in let case_classes = - List.map (fun x -> Inline (case_class env name x)) cases + List.map (fun x -> Inline (case_class env name x is_recursive)) cases |> double_spaced in let container_class = sum_container env loc name cases in @@ -1248,11 +1260,13 @@ let type_def env ((loc, (name, param, an), e) : A.type_def) : B.t = (match e with | Record (loc, fields, an) -> record env loc name fields an true + | Sum (loc, cases, an) -> + sum env loc name cases true | _ -> not_implemented loc "shape recursive but not record") | Default -> (match e with | Sum (loc, cases, an) -> - sum env loc name cases + sum env loc name cases false | Record (loc, fields, an) -> record env loc name fields an false | Tuple _ diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index 9c774676..9439c4c9 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -89,6 +89,17 @@ type recursive_class = { children: recursive_class; } +type record_that_uses_recursive_variant = +{ + v: recursive_variant; + i: int; +} + +type recursive_variant = [ + | Int of int + | Record of record_that_uses_recursive_variant +] + type default_list = { ~items: int list; } diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index ea3f2905..c655ba3e 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -440,6 +440,67 @@ struct RecursiveClass { } +struct RecordThatUsesRecursiveVariant { + RecursiveVariant v; + int i; +} + +@trusted RecordThatUsesRecursiveVariant fromJson(T : RecordThatUsesRecursiveVariant)(JSONValue x) { + RecordThatUsesRecursiveVariant obj; + obj.v = ("v" in x) ? fromJson!RecursiveVariant(x["v"]) : _atd_missing_json_field!(typeof(obj.v))("RecordThatUsesRecursiveVariant", "v"); + obj.i = ("i" in x) ? _atd_read_int(x["i"]) : _atd_missing_json_field!(typeof(obj.i))("RecordThatUsesRecursiveVariant", "i"); + return obj; +} +@trusted JSONValue toJson(T : RecordThatUsesRecursiveVariant)(T obj) { + JSONValue res; + res["v"] = ((RecursiveVariant x) => x.toJson!(RecursiveVariant))(obj.v); + res["i"] = _atd_write_int(obj.i); + return res; +} + +// Original type: recursive_variant = [ ... | Int of ... | ... ] +struct Int { +int* value; alias getValue this; +this(T)(T init) @safe {value = new int(init);} int getValue() @safe {return value is null ? (int).init : *value;} +this(int init) @trusted {value = new int; *value = init;}} +@trusted JSONValue toJson(T : Int)(T e) { + return JSONValue([JSONValue("Int"), _atd_write_int(e)]); +} + + +// Original type: recursive_variant = [ ... | Record of ... | ... ] +struct Record { +RecordThatUsesRecursiveVariant* value; alias getValue this; +this(T)(T init) @safe {value = new RecordThatUsesRecursiveVariant(init);} RecordThatUsesRecursiveVariant getValue() @safe {return value is null ? (RecordThatUsesRecursiveVariant).init : *value;} +this(RecordThatUsesRecursiveVariant init) @trusted {value = new RecordThatUsesRecursiveVariant; *value = init;}} +@trusted JSONValue toJson(T : Record)(T e) { + return JSONValue([JSONValue("Record"), ((RecordThatUsesRecursiveVariant x) => x.toJson!(RecordThatUsesRecursiveVariant))(e)]); +} + + +struct RecursiveVariant{ SumType!(Int, Record) _data; alias _data this; +@safe this(T)(T init) {_data = init;} @safe this(RecursiveVariant init) {_data = init._data;}} + +@trusted RecursiveVariant fromJson(T : RecursiveVariant)(JSONValue x) { + if (x.type == JSONType.array && x.array.length == 2 && x[0].type == JSONType.string) { + string cons = x[0].str; + if (cons == "Int") + return RecursiveVariant(Int(_atd_read_int(x[1]))); + if (cons == "Record") + return RecursiveVariant(Record(fromJson!RecordThatUsesRecursiveVariant(x[1]))); + throw _atd_bad_json("RecursiveVariant", x); + } + throw _atd_bad_json("RecursiveVariant", x); +} + +@trusted JSONValue toJson(T : RecursiveVariant)(T x) { + return x.match!( + (Int v) => v.toJson!(Int), +(Record v) => v.toJson!(Record) + ); +} + + struct St{int _data; alias _data this; this(int init) @safe {_data = init;} this(St init) @safe {_data = init._data;} @@ -463,9 +524,9 @@ struct Root_ {} // Original type: kind = [ ... | Thing of ... | ... ] struct Thing { int value; alias value this; -@safe this(T)(T init) {value = init;} @safe this(Thing init) {value = init.value;}} +this(T)(T init) @safe {value = init;} this(Thing init) @safe{value = init.value;}} @trusted JSONValue toJson(T : Thing)(T e) { - return JSONValue([JSONValue("Thing"), _atd_write_int(e.value)]); + return JSONValue([JSONValue("Thing"), _atd_write_int(e)]); } @@ -479,9 +540,9 @@ struct WOW {} // Original type: kind = [ ... | Amaze of ... | ... ] struct Amaze { string[] value; alias value this; -@safe this(T)(T init) {value = init;} @safe this(Amaze init) {value = init.value;}} +this(T)(T init) @safe {value = init;} this(Amaze init) @safe{value = init.value;}} @trusted JSONValue toJson(T : Amaze)(T e) { - return JSONValue([JSONValue("!!!"), _atd_write_list!(_atd_write_string)(e.value)]); + return JSONValue([JSONValue("!!!"), _atd_write_list!(_atd_write_string)(e)]); } @@ -838,9 +899,9 @@ struct A {} // Original type: frozen = [ ... | B of ... | ... ] struct B { int value; alias value this; -@safe this(T)(T init) {value = init;} @safe this(B init) {value = init.value;}} +this(T)(T init) @safe {value = init;} this(B init) @safe{value = init.value;}} @trusted JSONValue toJson(T : B)(T e) { - return JSONValue([JSONValue("B"), _atd_write_int(e.value)]); + return JSONValue([JSONValue("B"), _atd_write_int(e)]); } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index e7dfe3a6..e2cdaa8e 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -201,6 +201,17 @@ void setupTests() assert(res.toJsonString == p.toJsonString); }; + + tests["recursive variant"] = { + import std.sumtype; + import std.conv; + + auto v = RecursiveVariant(Int(43)); + auto r = RecordThatUsesRecursiveVariant(v, 42); + auto vv = RecursiveVariant(Record(r)); + + assert(vv.toJsonString == vv.toJsonString.fromJsonString!RecursiveVariant.toJsonString); + }; } void assertThrows(T)(T fn, bool writeMsg = false) From 4356881bda52aa5faca82e2436b2e9f3ce3e6c1b Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Tue, 5 Dec 2023 18:05:09 +0800 Subject: [PATCH 7/9] atdd: fix small issue with recursive records --- atdd/src/lib/Codegen.ml | 20 +++++++++++++++++--- atdd/test/atd-input/everything.atd | 2 +- atdd/test/dlang-expected/everything_atd.d | 7 ++++--- atdd/test/dlang-tests/test_atdd.d | 5 +++-- 4 files changed, 25 insertions(+), 9 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index bc870be8..2eadbc6c 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -925,7 +925,17 @@ let from_json_class_argument let inst_var_declaration ?(is_rec=false) 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 ~is_ptr:is_rec env e in + let is_nullable_ptr = match is_rec with + | false -> false + | true -> (match e with + | Option (_,_,_) | Nullable (_,_,_) -> true (* Workaround because d compiler cannot handle Nullable!T* :( *) + | _ -> false) + in + let alias_typename = sprintf "__%s_type__" var_name in + let type_name = match is_nullable_ptr with + | false -> type_name_of_expr ~is_ptr:is_rec env e + | true -> alias_typename (* Workaround because d compiler cannot handle Nullable!T* :( *) + in let unwrapped_e = unwrap_field_type loc name kind e in let default = match kind with @@ -936,8 +946,12 @@ let inst_var_declaration | None -> "" | Some x -> sprintf " = %s" x in - [ - Line (sprintf "%s %s%s;" type_name var_name default) + let type_line = Line (sprintf "%s %s%s;" type_name var_name default); in + match is_nullable_ptr with + | false -> [type_line] + | true -> [ + Line (sprintf "alias %s = %s;" alias_typename (type_name_of_expr ~is_ptr:is_rec env e)); + type_line ] let record env loc name (fields : field list) an is_rec = diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index 9439c4c9..045e5caa 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -86,7 +86,7 @@ type require_field = { type recursive_class = { id: int; flag: bool; - children: recursive_class; + children: recursive_class nullable; } type record_that_uses_recursive_variant = diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index c655ba3e..3a568318 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -421,21 +421,22 @@ import std.stdint : uint32_t, uint16_t; struct RecursiveClass { int id; bool flag; - RecursiveClass* children; + alias __children_type__ = Nullable!(RecursiveClass)*; + __children_type__ children; } @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_ptr!(fromJson!RecursiveClass)(x["children"]) : _atd_missing_json_field!(typeof(obj.children))("RecursiveClass", "children"); + obj.children = ("children" in x) ? _atd_read_ptr!(_atd_read_nullable!(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_ptr!(((RecursiveClass x) => x.toJson!(RecursiveClass)))(obj.children); + res["children"] = _atd_write_ptr!(_atd_write_nullable!(((RecursiveClass x) => x.toJson!(RecursiveClass))))(obj.children); return res; } diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index e2cdaa8e..98873164 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -171,8 +171,9 @@ void setupTests() }; tests["recursiveClass"] = { - auto child1 = new RecursiveClass(1, true, null); - auto a_obj = RecursiveClass(0, false, child1); + import std.typecons; + auto child = new Nullable!RecursiveClass(RecursiveClass(1, true, null)); + auto a_obj = RecursiveClass(0, false, child); assert (a_obj.toJsonString == a_obj.toJsonString.fromJsonString!RecursiveClass.toJsonString); }; From f0c1993be47790e755dfc97093abe319d311568e Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Tue, 5 Dec 2023 18:09:21 +0800 Subject: [PATCH 8/9] update changes.md --- CHANGES.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index c2fad7c6..47ccf5b4 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ Unreleased * atdgen: Breaking change, migrate from Bucklescript to Melange (#375) * atdd: Workaround d compiler bug regarding declaration order when using aliases (#393) Algebraic data types (SumType) now uses `alias this` syntax. +* atdd: Support recursive records, recursive variants through pointers. + Support using enum for variant with no data. 2.15.0 (2023-10-26) ------------------- From 17ac4ed4d7d74872fb02dc7635bc73b5615ac051 Mon Sep 17 00:00:00 2001 From: Alexandre Bourquelot Date: Wed, 20 Dec 2023 17:18:01 +0800 Subject: [PATCH 9/9] atdd: handle Optional and Default correctly --- atdd/src/lib/Codegen.ml | 57 ++++++++++++++------ atdd/test/atd-input/everything.atd | 9 ++++ atdd/test/dlang-expected/everything_atd.d | 66 ++++++++++++++++++++--- atdd/test/dlang-tests/test_atdd.d | 14 +++++ 4 files changed, 122 insertions(+), 24 deletions(-) diff --git a/atdd/src/lib/Codegen.ml b/atdd/src/lib/Codegen.ml index 2eadbc6c..d0c66b70 100644 --- a/atdd/src/lib/Codegen.ml +++ b/atdd/src/lib/Codegen.ml @@ -811,25 +811,35 @@ and tuple_writer env (loc, cells, an) = let construct_json_field ?(is_rec=false) env trans_meth ((loc, (name, kind, an), e) : simple_field) = let unwrapped_type = unwrap_field_type loc name kind e in + let var_name = (inst_var_name trans_meth name) in let writer_fn n = json_writer ~nested:n env unwrapped_type in - let wrap_in_optional writer = sprintf "_atd_write_option!(%s)" writer in + (* let wrap_in_optional writer = sprintf "_atd_write_opti`on!(%s)" writer in *) let wrap_in_ptr writer = match should_wrap_ptr is_rec env e with | true -> sprintf "_atd_write_ptr!(%s)" writer | false -> writer in - let assignment writer = - [ - Line (sprintf "res[\"%s\"] = %s(obj.%s);" + let conditional condition = + Line (sprintf "if (%s)" condition) + in + let assignment ?(get_nullable=false) writer = + Line (sprintf "res[\"%s\"] = %s(obj.%s%s);" (Atd.Json.get_json_fname name an |> single_esc) writer - (inst_var_name trans_meth name)) - ] + (inst_var_name trans_meth name) + (match get_nullable with | true -> ".get" | false -> "")) in + let ca c a = + [c; Block [a]] + in + let dlang_default = match (get_dlang_default e an) with | Some x -> x | None -> "" in match kind with - | Required - | With_default -> assignment (wrap_in_ptr (writer_fn false)) - | Optional -> assignment (wrap_in_ptr (wrap_in_optional (writer_fn true))) - + | Required -> [assignment (wrap_in_ptr (writer_fn false))] + | With_default -> + let c = conditional (sprintf "obj.%s != %s" var_name dlang_default) in + ca c (assignment (wrap_in_ptr (writer_fn false))) + | Optional -> + let c = conditional (sprintf "!obj.%s.isNull" var_name) in + ca c (assignment ~get_nullable:true (wrap_in_ptr ((writer_fn true)))) (* Function value that can be applied to a JSON node, converting it to the desired value. @@ -891,6 +901,11 @@ and tuple_reader env cells = return tuple(%s); })" (List.length cells) (List.length cells) tuple_body +let needs_nullable (e : type_expr) = + match e with + | Nullable _ | Option _ -> true + | _ -> false + let from_json_class_argument ?(is_rec=false) env trans_meth dlang_struct_name ((loc, (name, kind, an), e) : simple_field) = let dlang_name = inst_var_name trans_meth name in @@ -905,21 +920,30 @@ let from_json_class_argument | Optional -> (sprintf "typeof(obj.%s).init" dlang_name) | With_default -> match get_dlang_default e an with - | Some x -> x + | Some x -> (match (needs_nullable e) with | true -> (sprintf "%s.nullable" x) | false -> x) | None -> A.error_at loc (sprintf "missing default Dlang value for field '%s'" name) in - let reader = match should_wrap_ptr is_rec env e with - | true -> sprintf "_atd_read_ptr!(%s)" (json_reader env e) - | false -> (json_reader env e) + let reader = + (match kind with + | Optional -> + (match e with + | Option(_, e, _) -> (json_reader env e) + | _ -> A.error_at loc (sprintf "optional but not option")) + | _ -> (json_reader env e)) + in + let wrapped_reader = match should_wrap_ptr is_rec env e with + | true -> sprintf "_atd_read_ptr!(%s)" reader + | false -> reader in - sprintf "obj.%s = (\"%s\" in x) ? %s(x[\"%s\"]) : %s;" + sprintf "obj.%s = (\"%s\" in x) ? %s(x[\"%s\"])%s : %s;" dlang_name (single_esc json_name) - reader + wrapped_reader (single_esc json_name) + (match kind with | Optional -> ".nullable" | _ -> "") (* Needs to convert to nullable for optional *) else_body let inst_var_declaration @@ -990,6 +1014,7 @@ let record env loc name (fields : field list) an is_rec = Line (sprintf "@trusted JSONValue toJson(T : %s)(T obj) {" (single_esc dlang_struct_name)); Block [ Line ("JSONValue res;"); + Line ("res.object = new JSONValue[string];"); Inline json_object_body; Line "return res;" ]; diff --git a/atdd/test/atd-input/everything.atd b/atdd/test/atd-input/everything.atd index 045e5caa..3d54f08e 100644 --- a/atdd/test/atd-input/everything.atd +++ b/atdd/test/atd-input/everything.atd @@ -107,3 +107,12 @@ type default_list = { type record_with_wrapped_type = { item: string wrap ; } + +type null_opt = { + a : int; + b : int option; + c : int nullable; + ?f : int option; + ~h :int option; + ~i : int nullable; +} \ No newline at end of file diff --git a/atdd/test/dlang-expected/everything_atd.d b/atdd/test/dlang-expected/everything_atd.d index 3a568318..f99c7214 100644 --- a/atdd/test/dlang-expected/everything_atd.d +++ b/atdd/test/dlang-expected/everything_atd.d @@ -434,6 +434,7 @@ struct RecursiveClass { } @trusted JSONValue toJson(T : RecursiveClass)(T obj) { JSONValue res; + res.object = new JSONValue[string]; res["id"] = _atd_write_int(obj.id); res["flag"] = _atd_write_bool(obj.flag); res["children"] = _atd_write_ptr!(_atd_write_nullable!(((RecursiveClass x) => x.toJson!(RecursiveClass))))(obj.children); @@ -454,6 +455,7 @@ struct RecordThatUsesRecursiveVariant { } @trusted JSONValue toJson(T : RecordThatUsesRecursiveVariant)(T obj) { JSONValue res; + res.object = new JSONValue[string]; res["v"] = ((RecursiveVariant x) => x.toJson!(RecursiveVariant))(obj.v); res["i"] = _atd_write_int(obj.i); return res; @@ -661,8 +663,10 @@ struct IntFloatParametrizedRecord { } @trusted JSONValue toJson(T : IntFloatParametrizedRecord)(T obj) { JSONValue res; + res.object = new JSONValue[string]; res["field_a"] = _atd_write_int(obj.field_a); - res["field_b"] = _atd_write_list!(_atd_write_float)(obj.field_b); + if (obj.field_b != []) + res["field_b"] = _atd_write_list!(_atd_write_float)(obj.field_b); return res; } @@ -704,7 +708,7 @@ struct Root { 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))(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.maybe = ("maybe" in x) ? _atd_read_int(x["maybe"]).nullable : 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"); @@ -734,16 +738,22 @@ struct Root { } @trusted JSONValue toJson(T : Root)(T obj) { JSONValue res; + res.object = new JSONValue[string]; 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); + if (obj.float_with_auto_default != 0.0) + res["float_with_auto_default"] = _atd_write_float(obj.float_with_auto_default); + if (obj.float_with_default != 0.1) + res["float_with_default"] = _atd_write_float(obj.float_with_default); res["items"] = _atd_write_list!(_atd_write_list!(_atd_write_int))(obj.items); - 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); + if (!obj.maybe.isNull) + res["maybe"] = _atd_write_int(obj.maybe.get); + if (obj.extras != []) + res["extras"] = _atd_write_list!(_atd_write_int)(obj.extras); + if (obj.answer != 42) + res["answer"] = _atd_write_int(obj.answer); res["aliased"] = ((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["kind"] = ((Kind x) => x.toJson!(Kind))(obj.kind); @@ -774,6 +784,7 @@ struct RequireField { } @trusted JSONValue toJson(T : RequireField)(T obj) { JSONValue res; + res.object = new JSONValue[string]; res["req"] = _atd_write_string(obj.req); return res; } @@ -790,6 +801,7 @@ struct RecordWithWrappedType { } @trusted JSONValue toJson(T : RecordWithWrappedType)(T obj) { JSONValue res; + res.object = new JSONValue[string]; res["item"] = _atd_write_wrap!(_atd_write_string, (int e) => to!string(e))(obj.item); return res; } @@ -890,6 +902,41 @@ this(T...)(T args) @safe {_data = tuple(args);} } +struct NullOpt { + int a; + Nullable!(int) b; + Nullable!(int) c; + Nullable!(int) f; + Nullable!(int) h = 3; + Nullable!(int) i = 3; +} + +@trusted NullOpt fromJson(T : NullOpt)(JSONValue x) { + NullOpt obj; + obj.a = ("a" in x) ? _atd_read_int(x["a"]) : _atd_missing_json_field!(typeof(obj.a))("NullOpt", "a"); + obj.b = ("b" in x) ? _atd_read_option!(_atd_read_int)(x["b"]) : _atd_missing_json_field!(typeof(obj.b))("NullOpt", "b"); + obj.c = ("c" in x) ? _atd_read_nullable!(_atd_read_int)(x["c"]) : _atd_missing_json_field!(typeof(obj.c))("NullOpt", "c"); + obj.f = ("f" in x) ? _atd_read_int(x["f"]).nullable : typeof(obj.f).init; + obj.h = ("h" in x) ? _atd_read_option!(_atd_read_int)(x["h"]) : 3.nullable; + obj.i = ("i" in x) ? _atd_read_nullable!(_atd_read_int)(x["i"]) : 3.nullable; + return obj; +} +@trusted JSONValue toJson(T : NullOpt)(T obj) { + JSONValue res; + res.object = new JSONValue[string]; + res["a"] = _atd_write_int(obj.a); + res["b"] = _atd_write_option!(_atd_write_int)(obj.b); + res["c"] = _atd_write_nullable!(_atd_write_int)(obj.c); + if (!obj.f.isNull) + res["f"] = _atd_write_int(obj.f.get); + if (obj.h != 3) + res["h"] = _atd_write_option!(_atd_write_int)(obj.h); + if (obj.i != 3) + res["i"] = _atd_write_nullable!(_atd_write_int)(obj.i); + return res; +} + + // Original type: frozen = [ ... | A | ... ] struct A {} @trusted JSONValue toJson(T : A)(T e) { @@ -943,7 +990,9 @@ struct DefaultList { } @trusted JSONValue toJson(T : DefaultList)(T obj) { JSONValue res; - res["items"] = _atd_write_list!(_atd_write_int)(obj.items); + res.object = new JSONValue[string]; + if (obj.items != []) + res["items"] = _atd_write_list!(_atd_write_int)(obj.items); return res; } @@ -961,6 +1010,7 @@ struct Credential { } @trusted JSONValue toJson(T : Credential)(T obj) { JSONValue res; + res.object = new JSONValue[string]; res["name"] = _atd_write_string(obj.name); res["password"] = ((Password x) => x.toJson!(Password))(obj.password); return res; diff --git a/atdd/test/dlang-tests/test_atdd.d b/atdd/test/dlang-tests/test_atdd.d index 98873164..454471fe 100644 --- a/atdd/test/dlang-tests/test_atdd.d +++ b/atdd/test/dlang-tests/test_atdd.d @@ -213,6 +213,20 @@ void setupTests() assert(vv.toJsonString == vv.toJsonString.fromJsonString!RecursiveVariant.toJsonString); }; + + tests["optional nullable"] = { + import std.typecons : nullable, Nullable; + auto x = 3.nullable; + auto xx = Nullable!int.init; + auto somes = NullOpt(3, x, x, x, x, x); + auto nones = NullOpt(0, xx, xx, xx, xx, xx); + + auto somesJ = `{"a":3,"b":["Some",3],"c":3,"f":3}`; + assert(somesJ.fromJsonString!NullOpt.toJsonString == somes.toJsonString); + + auto nonesJ = `{"a":0,"b":"None","c":null,"h":"None","i":null}`; + assert(nonesJ.fromJsonString!NullOpt.toJsonString == nones.toJsonString); + }; } void assertThrows(T)(T fn, bool writeMsg = false)