1 /**
2   * Wrapper for translating Jinja calls into native D function calls
3   *
4   * Copyright:
5   *     Copyright (c) 2018, Maxim Tyapkin.
6   * Authors:
7   *     Maxim Tyapkin
8   * License:
9   *     This software is licensed under the terms of the BSD 3-clause license.
10   *     The full terms of the license can be found in the LICENSE.md file.
11   */
12 
13 module djinja.algo.wrapper;
14 
15 private
16 {
17     import std.algorithm : min;
18     import std.format : fmt = format;
19     import std.functional : toDelegate;
20     import std.traits;
21     import std.typecons : Tuple;
22     import std..string : join;
23 
24     import djinja.exception : assertJinja = assertJinjaException;
25     import djinja.uninode;
26 }
27 
28 
29 alias Function = UniNode delegate(UniNode);
30 
31 
32 template wrapper(alias F)
33     if (isSomeFunction!F)
34 {
35     alias ParameterIdents = ParameterIdentifierTuple!F;
36     alias ParameterTypes = Parameters!F;
37     alias ParameterDefs = ParameterDefaults!F;
38     alias RT = ReturnType!F;
39     alias PT = Tuple!ParameterTypes;
40 
41 
42     Function wrapper()
43     {
44         UniNode func (UniNode params)
45         {
46             assertJinja(params.kind == UniNode.Kind.object, "Non object params");
47             assertJinja(cast(bool)("varargs" in params), "Missing varargs in params");
48             assertJinja(cast(bool)("kwargs" in params), "Missing kwargs in params");
49 
50             bool[string] filled;
51             PT args;
52 
53             foreach(i, def; ParameterDefs)
54             {
55                 alias key = ParameterIdents[i];
56                 static if (key == "varargs")
57                     args[i] = UniNode.emptyArray;
58                 else static if (key == "kwargs")
59                     args[i] = UniNode.emptyObject;
60                 else static if (!is(def == void))
61                     args[i] = def;
62                 else
63                     filled[key] = false;
64             }
65 
66             void fillArg(size_t idx, PType)(string key, UniNode val)
67             {
68                 // TODO toBoolType, toStringType
69                 try
70                     args[idx] = val.deserialize!PType;
71                 catch
72                     assertJinja(0, "Can't deserialize param `%s` from `%s` to `%s` in function `%s`"
73                                             .fmt(key, val.kind, PType.stringof, fullyQualifiedName!F));
74             }
75 
76             UniNode varargs = UniNode.emptyArray;
77             UniNode kwargs = UniNode.emptyObject;
78 
79             bool isVarargs = false;
80             int varargsFilled = 0;
81             static foreach (int i; 0 .. PT.length)
82             {
83                 static if (ParameterIdents[i] == "varargs" || ParameterIdents[i] == "kwargs")
84                 {
85                     isVarargs = true;
86                 }
87                 if (params["varargs"].length > i)
88                 {
89                     if (!isVarargs)
90                     {
91                         fillArg!(i, ParameterTypes[i])(ParameterIdents[i], params["varargs"][i]);
92                         filled[ParameterIdents[i]] = true;
93                     }
94                     else
95                         varargs ~= params["varargs"][i];
96                     varargsFilled++;
97                 }
98             }
99             // Filled missed varargs
100             if (varargsFilled < params["varargs"].length)
101                 foreach(i; varargsFilled .. params["varargs"].length)
102                     varargs ~= params["varargs"][i];
103 
104             bool[string] kwargsFilled;
105             static foreach(i, key; ParameterIdents)
106             {
107                 if (key in params["kwargs"])
108                 {
109                     fillArg!(i, ParameterTypes[i])(key, params["kwargs"][key]);
110                     filled[ParameterIdents[i]] = true;
111                     kwargsFilled[key] = true;
112                 }
113             }
114             // Filled missed kwargs
115             foreach (string key, ref UniNode val; params["kwargs"])
116             {
117                 if (key !in kwargsFilled)
118                     kwargs[key] = val;
119             }
120 
121             // Fill varargs/kwargs
122             foreach(i, key; ParameterIdents)
123             {
124                 static if (key == "varargs")
125                     args[i] = varargs;
126                 else static if (key == "kwargs")
127                     args[i] = kwargs;
128             }
129 
130             string[] missedArgs = [];
131             foreach(key, val; filled)
132                 if (!val)
133                     missedArgs ~= key;
134 
135             if (missedArgs.length)
136                 assertJinja(0, "Missed values for args `%s`".fmt(missedArgs.join(", ")));
137 
138             static if (is (RT == void))
139             {
140                 F(args.expand);
141                 return UniNode(null);
142             }
143             else
144             {
145                 auto ret = F(args.expand);
146                 return ret.serialize!RT;
147             }
148         }
149 
150         return toDelegate(&func);
151     }
152 }