1 /**
2  * Authors: Tomoya Tanjo
3  * Copyright: © 2021 Tomoya Tanjo
4  * License: Apache-2.0
5  */
6 module salad.parser;
7 
8 import dyaml : Node;
9 
10 import salad.context : LoadingContext;
11 import salad.exception;
12 import salad.meta;
13 import salad.schema;
14 import salad.type;
15 
16 ///
17 auto parse(alias module_)(Node node, string uri)
18 {
19     import std.traits : moduleName;
20     mixin("import "~moduleName!module_~";");
21     import dyaml : NodeType;
22 
23     alias T = DocumentRootType!module_;
24     alias ReturnType = SumType!(T, T[]);
25 
26     if (node.type == NodeType.mapping)
27     {
28         import std.algorithm : map;
29         import std.array : array;
30 
31         string baseuri;
32         if (auto base = "$base" in node)
33         {
34             baseuri = base.as!string;
35         }
36         else
37         {
38             baseuri = uri;
39         }
40         string[string] namespaces;
41         if (auto ns = "$namespaces" in node)
42         {
43             import std.array : assocArray;
44             import std.typecons : tuple;
45             namespaces = ns.mapping
46                            .map!(a => tuple(a.key.as!string, a.value.as!string))
47                            .assocArray;
48         }
49         string[] schemas;
50         if (auto s = "$schemas" in node)
51         {
52             schemas = s.sequence.map!(a => a.as!string).array;
53         }
54 
55         auto context = LoadingContext(baseuri, namespaces);
56 
57         if (auto g = "$graph" in node)
58         {
59             import salad.resolver : preprocess;
60 
61             return ReturnType(g.sequence.map!((a) {
62                 import salad.util : edig;
63                 a = a.preprocess(context);
64                 T ret;
65                 mixin(Assign_!("a", "ret", T));
66                 return ret;
67             }).array);
68         }
69         else
70         {
71             import salad.util : edig;
72             T ret;
73             mixin(Assign_!("node", "ret", T));
74             return ReturnType(ret);
75         }
76     }
77     else
78     {
79         assert(false, "Not yet supported");
80     }
81 }
82 
83 ///
84 auto importFromURI(alias module_)(string uri, string fragment = "")
85 {
86     import salad.fetcher : fetchNode, fragment_ = fragment;
87     import std.format : format;
88     import std.range : empty;
89 
90     auto frag = uri.fragment_;
91     auto baseuri = uri[0..$-frag.length];
92     auto node = fetchNode(baseuri);
93     auto objs = parse!module_(node, baseuri);
94     if (frag.empty)
95     {
96         frag = fragment;
97     }
98 
99     alias RetType = typeof(objs);
100     alias DocType = DocumentRootType!module_;
101     return objs.match!(
102         (DocType doc) {
103             docEnforce(frag.empty || doc.match!(d => d.identifier) == frag,
104                        "Mismatched fragment", node);
105             return objs;
106         },
107         (DocType[] docs) {
108             if (frag.empty)
109             {
110                 return objs;
111             }
112             else
113             {
114                 import std.algorithm : filter;
115                 import std.array : array;
116                 auto elems = docs.filter!(e => e.tryMatch!(d => d.identifier) == frag)
117                                  .array;
118                 docEnforce(!elems.empty, format!"No objects for fragment `%s`"(frag), node);
119                 docEnforce(elems.length == 1, format!"Duplicated objects for fragment `%s`"(frag), node);
120                 return RetType(elems[0]);
121             }
122         }
123     );
124 }