chigraph  master
Systems programming language written for beginners in LLVM
JsonDeserializer.cpp
Go to the documentation of this file.
1 
4 #include "chi/Context.hpp"
5 #include "chi/GraphFunction.hpp"
6 #include "chi/GraphModule.hpp"
7 #include "chi/GraphStruct.hpp"
8 #include "chi/NodeInstance.hpp"
9 #include "chi/NodeType.hpp"
10 #include "chi/Support/Result.hpp"
11 
12 namespace chi {
13 
14 Result jsonToGraphModule(Context& createInside, const nlohmann::json& input,
15  const boost::filesystem::path& fullName, GraphModule** toFill) {
16  Result res;
17 
18  auto resCtx = res.addScopedContext({{"Loading Module Name", fullName.string()},
19  {"Workspace Path", createInside.workspacePath().string()}});
20 
21  // create the module
22  auto createdModule = createInside.newGraphModule(fullName);
23  if (toFill != nullptr) { *toFill = createdModule; }
24 
25  // load if it has C enabled
26  {
27  auto iter = input.find("has_c_support");
28  if (iter == input.end()) {
29  res.addEntry("EUKN", "No has_c_support section in module JSON, assuming none", {});
30  return res;
31  }
32  if (!iter->is_boolean()) {
33  res.addEntry("EUKN", "has_c_support section in module JSON isn't a bool",
34  {{"Actual Data", *iter}});
35  return res;
36  }
37 
38  createdModule->setCEnabled(*iter);
39  }
40 
41  // load dependencies
42  {
43  auto iter = input.find("dependencies");
44  if (iter == input.end()) {
45  res.addEntry("E38", "No dependencies element in module", {});
46  return res;
47  }
48  if (!iter->is_array()) {
49  res.addEntry("E39", "dependencies element isn't an array", {});
50  return res;
51  }
52 
53  for (const auto& dep : *iter) {
54  if (!dep.is_string()) {
55  res.addEntry("E40", "dependency isn't a string", {{"Actual Data", dep}});
56  continue;
57  }
58 
59  std::string depName = dep;
60  res += createdModule->addDependency(depName);
61 
62  if (!res) { return res; }
63  }
64  }
65 
66  // load types
67  {
68  auto iter = input.find("types");
69  if (iter == input.end() || !iter->is_object()) {
70  res.addEntry("EUKN", "No types object in module", {});
71  return res;
72  }
73 
74  // declare them
75  for (auto tyIter = iter->begin(); tyIter != iter->end(); ++tyIter) {
76  createdModule->getOrCreateStruct(tyIter.key());
77  }
78  // load them
79  for (auto tyIter = iter->begin(); tyIter != iter->end(); ++tyIter) {
80  res += jsonToGraphStruct(*createdModule, tyIter.key(), tyIter.value());
81  }
82  }
83 
84  // load graphs
85  {
86  auto iter = input.find("graphs");
87  if (iter == input.end()) {
88  res.addEntry("E41", "no graphs element in module", {});
89  return res;
90  }
91  if (!iter->is_array()) {
92  res.addEntry("E42", "graph element isn't an array", {{"Actual Data", *iter}});
93  return res;
94  }
95 
96  std::vector<GraphFunction*> functions;
97  functions.resize(iter->size());
98 
99  // create forward declarations
100  auto id = 0ull;
101  for (const auto& graph : *iter) {
102  res += createGraphFunctionDeclarationFromJson(*createdModule, graph, &functions[id]);
103 
104  ++id;
105  }
106 
107  if (!res) { return res; }
108 
109  // load the graphs
110  id = 0;
111  for (const auto& graph : *iter) {
112  res += jsonToGraphFunction(*functions[id], graph);
113 
114  ++id;
115  }
116  }
117 
118  return res;
119 }
120 
122  const nlohmann::json& input, GraphFunction** toFill) {
123  Result res;
124 
125  if (!input.is_object()) {
126  res.addEntry("E1", "Graph json isn't a JSON object", {});
127  return res;
128  }
129  // make sure it has a type element
130  if (input.find("type") == input.end() || !input["type"].is_string()) {
131  res.addEntry("E2", R"(JSON in graph doesn't have a "type" element)",
132  {{"Module Name", createInside.fullName()}});
133  return res;
134  }
135  if (input["type"] != "function") {
136  res.addEntry("E3", "JSON in graph doesn't have a function type",
137  {{"Module Name", createInside.fullName()}});
138  return res;
139  }
140  // make sure there is a name
141  if (input.find("name") == input.end() || !input["name"].is_string()) {
142  res.addEntry("E4", "JSON in graph doesn't have a name parameter",
143  {{"Module Name", createInside.fullName()}});
144  return res;
145  }
146  std::string name = input["name"];
147 
148  // load the description
149  if (input.find("description") == input.end() || !input["description"].is_string()) {
150  res.addEntry("E50", "JSON in graph doesn't have a description string",
151  {{"Function Name", name}, {"Module Name", createInside.fullName()}});
152  return res;
153  }
154  std::string description = input["description"];
155 
156  if (input.find("data_inputs") == input.end() || !input["data_inputs"].is_array()) {
157  res.addEntry("E43", "JSON in graph doesn't have an data_inputs array", {});
158  return res;
159  }
160 
161  std::vector<NamedDataType> datainputs;
162  for (auto param : input["data_inputs"]) {
163  std::string docString, qualifiedType;
164  std::tie(docString, qualifiedType) = parseObjectPair(param);
165 
166  std::string moduleName, name;
167  std::tie(moduleName, name) = parseColonPair(qualifiedType);
168 
169  DataType ty;
170  res += createInside.context().typeFromModule(moduleName, name, &ty);
171 
172  if (!res) { return res; }
173 
174  datainputs.emplace_back(docString, ty);
175  }
176 
177  if (input.find("data_outputs") == input.end() || !input["data_outputs"].is_array()) {
178  res.addEntry("E44", "JSON in graph doesn't have an data_outputs array", {});
179  return res;
180  }
181 
182  std::vector<NamedDataType> dataoutputs;
183  for (auto param : input["data_outputs"]) {
184  std::string docString, qualifiedType;
185  std::tie(docString, qualifiedType) = parseObjectPair(param);
186 
187  std::string moduleName, name;
188  std::tie(moduleName, name) = parseColonPair(qualifiedType);
189 
190  DataType ty;
191  res += createInside.context().typeFromModule(moduleName, name, &ty);
192 
193  if (!res) { return res; }
194 
195  dataoutputs.emplace_back(docString, ty);
196  }
197 
198  // get exec I/O
199  if (input.find("exec_inputs") == input.end() || !input["exec_inputs"].is_array()) {
200  res.addEntry("E48", "JSON in graph doesn't have an exec_inputs array", {});
201  return res;
202  }
203 
204  std::vector<std::string> execinputs;
205  for (const auto& param : input["exec_inputs"]) {
206  std::string name = param;
207 
208  execinputs.emplace_back(name);
209  }
210 
211  if (input.find("exec_outputs") == input.end() || !input["exec_outputs"].is_array()) {
212  res.addEntry("E49", "JSON in graph doesn't have an data_outputs array", {});
213  return res;
214  }
215 
216  std::vector<std::string> execoutputs;
217  for (const auto& param : input["exec_outputs"]) {
218  std::string name = param;
219 
220  execoutputs.emplace_back(name);
221  }
222 
223  // construct it
224  auto created =
225  createInside.getOrCreateFunction(name, datainputs, dataoutputs, execinputs, execoutputs);
226  created->setDescription(std::move(description));
227  if (toFill != nullptr) { *toFill = created; }
228 
229  return res;
230 }
231 
232 Result jsonToGraphFunction(GraphFunction& createInside, const nlohmann::json& input) {
233  Result res;
234 
235  // read the local variables
236  if (input.find("local_variables") == input.end() || !input["local_variables"].is_object()) {
237  res.addEntry("E45", "JSON in graph doesn't have a local_variables object", {});
238 
239  return res;
240  }
241 
242  for (auto localiter = input["local_variables"].begin();
243  localiter != input["local_variables"].end(); ++localiter) {
244  std::string localName = localiter.key();
245 
246  if (!localiter.value().is_string()) {
247  res.addEntry("E46", "Local variable vaue in json wasn't a string",
248  {{"Given local variable json", localiter.value()}});
249 
250  continue;
251  }
252 
253  // parse the type names
254  std::string qualifiedType = localiter.value();
255 
256  std::string moduleName, typeName;
257  std::tie(moduleName, typeName) = parseColonPair(qualifiedType);
258 
259  DataType ty;
260  res += createInside.context().typeFromModule(moduleName, typeName, &ty);
261 
262  if (!res) { continue; }
263 
264  createInside.getOrCreateLocalVariable(localName, ty);
265  }
266 
267  // read the nodes
268  if (input.find("nodes") == input.end() || !input["nodes"].is_object()) {
269  res.addEntry("E5", "JSON in graph doesn't have nodes object", {});
270  return res;
271  }
272 
273  for (auto nodeiter = input["nodes"].begin(); nodeiter != input["nodes"].end(); ++nodeiter) {
274  auto node = nodeiter.value();
275  std::string nodeid = nodeiter.key();
276  if (node.find("type") == node.end() || !node.find("type")->is_string()) {
277  res.addEntry("E6", R"(Node doesn't have a "type" string)", {{"Node ID", nodeid}});
278  return res;
279  }
280  std::string fullType = node["type"];
281  std::string moduleName, typeName;
282  std::tie(moduleName, typeName) = parseColonPair(fullType);
283 
284  if (moduleName.empty() || typeName.empty()) {
285  res.addEntry("E7", "Incorrect qualified module name (should be module:type)",
286  {{"Node ID", nodeid}, {"Requested Qualified Name", fullType}});
287  return res;
288  }
289 
290  if (node.find("data") == node.end()) {
291  res.addEntry("E9", "Node doens't have a data section", {{"Node ID", nodeid}});
292  return res;
293  }
294 
295  std::unique_ptr<NodeType> nodeType;
296  res += createInside.context().nodeTypeFromModule(moduleName, typeName, node["data"],
297  &nodeType);
298  if (!res) { continue; }
299 
300  auto testIter = node.find("location");
301  if (testIter == node.end()) {
302  res.addEntry("E12", "Node doesn't have a location.", {{"Node ID", nodeid}});
303  continue;
304  }
305 
306  // make sure it is the right size
307  if (!testIter.value().is_array()) {
308  res.addEntry("E10", "Node doesn't have a location that is an array.",
309  {{"Node ID", nodeid}});
310  continue;
311  }
312 
313  if (testIter.value().size() != 2) {
314  res.addEntry("E11", "Node doesn't have a location that is an array of size 2.",
315  {{"Node ID", nodeid}});
316  continue;
317  }
318 
319  try {
320  auto uuidNodeID = boost::uuids::string_generator()(nodeid);
321 
322  createInside.insertNode(std::move(nodeType), node["location"][0], node["location"][1],
323  uuidNodeID);
324  } catch (std::exception& e) {
325  res.addEntry("E51", "Invalid UUID string", {{"string", nodeid}});
326  }
327  }
328 
329  // read the connections
330  {
331  auto connIter = input.find("connections");
332  if (connIter == input.end() || !connIter->is_array()) {
333  res.addEntry("E13", "No connections array in function", {});
334  return res;
335  }
336 
337  auto connID = 0ull;
338  for (auto& connection : input["connections"]) {
339  if (connection.find("type") == connection.end() ||
340  !connection.find("type")->is_string()) {
341  res.addEntry("E14", "No type string in connection", {{"connectionid", connID}});
342 
343  ++connID;
344  continue;
345  }
346  std::string type = connection["type"];
347  bool isData = type == "data";
348  // it either has to be "input" or "exec"
349  if (!isData && type != "exec") {
350  res.addEntry("E15", "Unrecognized connection type",
351  {{"connectionid", connID}, {"Found Type", type}});
352 
353  ++connID;
354  continue;
355  }
356 
357  if (connection.find("input") == connection.end()) {
358  res.addEntry("E16", "No input element in connection", {{"connectionid", connID}});
359 
360  ++connID;
361  continue;
362  }
363  if (!connection.find("input")->is_array() || connection.find("input")->size() != 2 ||
364  !connection.find("input")->operator[](0).is_string() ||
365  !connection.find("input")->operator[](1).is_number_integer()) {
366  res.addEntry(
367  "E17",
368  "Incorrect connection input format, must be an array of of a string (node id) "
369  "and int (connection id)",
370  {{"connectionid", connID}, {"Requested Type", *connection.find("input")}});
371 
372  ++connID;
373  continue;
374  }
375  std::string InputNodeID = connection["input"][0];
376 
377  boost::uuids::uuid InputNodeIDUUID;
378  try {
379  InputNodeIDUUID = boost::uuids::string_generator()(InputNodeID);
380  } catch (std::exception&) {
381  res.addEntry("EUKN", "Invalid UUID string in connection",
382  {{"string", InputNodeID}});
383 
384  ++connID;
385  continue;
386  }
387  int InputConnectionID = connection["input"][1];
388 
389  if (connection.find("output") == connection.end()) {
390  res.addEntry("E18", "No output element in connection", {{"connectionid", connID}});
391 
392  ++connID;
393  continue;
394  }
395  if (!connection.find("output")->is_array() || connection.find("output")->size() != 2 ||
396  !connection.find("output")->operator[](0).is_string() ||
397  !connection.find("output")->operator[](1).is_number_integer()) {
398  res.addEntry(
399  "E19",
400  "Incorrect connection output format, must be an array of a string (node id) "
401  "and int (connection id)",
402  {{"connectionid", connID}, {"Requested Type", *connection.find("output")}});
403  ++connID;
404  continue;
405  }
406  std::string OutputNodeID = connection["output"][0];
407 
408  boost::uuids::uuid OutputNodeIDUUID;
409  try {
410  OutputNodeIDUUID = boost::uuids::string_generator()(OutputNodeID);
411  } catch (std::exception&) {
412  res.addEntry("EUKN", "Invalid UUID string in connection",
413  {{"string", OutputNodeID}});
414 
415  ++connID;
416  continue;
417  }
418  int OutputConnectionID = connection["output"][1];
419 
420  // make sure the nodes exist
421  if (createInside.nodes().find(InputNodeIDUUID) == createInside.nodes().end()) {
422  res.addEntry("E20", "Input node for connection doesn't exist",
423  {{"connectionid", connID}, {"Requested Node", InputNodeID}});
424  ++connID;
425  continue;
426  }
427  if (createInside.nodes().find(OutputNodeIDUUID) == createInside.nodes().end()) {
428  res.addEntry("E21", "Output node for connection doesn't exist",
429  {{"connectionid", connID}, {"Requested Node", OutputNodeID}});
430  ++connID;
431  continue;
432  }
433 
434  // connect
435  // these functions do bounds checking, it's okay
436  if (isData) {
437  res += connectData(*createInside.nodes()[InputNodeIDUUID], InputConnectionID,
438  *createInside.nodes()[OutputNodeIDUUID], OutputConnectionID);
439  } else {
440  res += connectExec(*createInside.nodes()[InputNodeIDUUID], InputConnectionID,
441  *createInside.nodes()[OutputNodeIDUUID], OutputConnectionID);
442  }
443 
444  ++connID;
445  }
446  }
447  return res;
448 }
449 
450 Result jsonToGraphStruct(GraphModule& mod, boost::string_view name, const nlohmann::json& input,
451  GraphStruct** toFill) {
452  Result res;
453 
454  if (!input.is_array()) {
455  res.addEntry("EUKN", "Graph Struct json has to be an array", {{"Given JSON", input}});
456  return res;
457  }
458 
459  auto createdStruct = mod.getOrCreateStruct(name.to_string());
460  if (toFill != nullptr) { *toFill = createdStruct; }
461 
462  for (const auto& str : input) {
463  if (!str.is_object()) {
464  res.addEntry("EUKN", "Graph Struct entry must be an object", {{"Given JSON", str}});
465  continue;
466  }
467 
468  if (str.size() != 1) {
469  res.addEntry("EUKN", "Graph Struct entry must have size of 1", {{"Size", str.size()}});
470  continue;
471  }
472 
473  std::string docstring, qualifiedType;
474  std::tie(docstring, qualifiedType) = parseObjectPair(str);
475 
476  // parse the type
477  std::string typeModuleName, typeName;
478  std::tie(typeModuleName, typeName) = parseColonPair(qualifiedType);
479 
480  DataType ty;
481  res += mod.context().typeFromModule(typeModuleName, typeName, &ty);
482 
483  if (!res) { continue; }
484 
485  createdStruct->addType(ty, docstring, createdStruct->types().size());
486  }
487 
488  return res;
489 }
490 
491 std::pair<std::string, std::string> parseObjectPair(const nlohmann::json& object) {
492  if (!object.is_object()) { return {}; }
493 
494  auto iter = object.begin();
495  if (iter == object.end()) { return {}; }
496 
497  std::string key = iter.key();
498  std::string val = iter.value();
499 
500  // make sure it's the only element
501  ++iter;
502  if (iter != object.end()) { return {}; }
503 
504  return {key, val};
505 }
506 } // namespace chi
Result jsonToGraphStruct(GraphModule &mod, boost::string_view name, const nlohmann::json &input, GraphStruct **toFill=nullptr)
Load a GraphStruct from json.
this is an AST-like representation of a function in a graph It is used for IDE-like behavior...
void addEntry(const char *ec, const char *overview, nlohmann::json data)
Add a entry to the result, either a warning or an error.
Definition: Result.cpp:52
Result nodeTypeFromModule(const boost::filesystem::path &moduleName, boost::string_view typeName, const nlohmann::json &data, std::unique_ptr< NodeType > *toFill) noexcept
Gets a NodeType from the JSON and name.
Definition: Context.cpp:253
A class holding a compound type defined in a GraphModule.
Definition: GraphStruct.hpp:18
std::pair< std::string, std::string > parseColonPair(const std::string &in)
Parse a colonated pair Example: lang:i32 would turn into {lang, i32}.
Result insertNode(std::unique_ptr< NodeType > type, float x, float y, boost::uuids::uuid id=boost::uuids::random_generator()(), NodeInstance **toFill=nullptr)
Add a node to the graph.
Defines the Result class and related functions.
Context & context() const
Get the context.
boost::filesystem::path workspacePath() const
Get the workspace path of the Context.
Definition: Context.hpp:138
void setDescription(std::string newDesc)
Set the description of the function.
Define GraphStruct.
Result jsonToGraphModule(Context &createInside, const nlohmann::json &input, const boost::filesystem::path &fullName, GraphModule **toFill=nullptr)
Load a GraphModule from json.
GraphFunction * getOrCreateFunction(std::string name, std::vector< NamedDataType > dataIns, std::vector< NamedDataType > dataOuts, std::vector< std::string > execIns, std::vector< std::string > execOuts, bool *inserted=nullptr)
Create a new function if it does&#39;t already exist.
std::string fullName() const
Get the full name of the module.
Definition: ChiModule.hpp:61
ScopedContext addScopedContext(const nlohmann::json &data)
Add a context with a scope Example usage: chi::Result res; res.contextJson(); // returns {} { aut...
Definition: Result.hpp:123
The class that handles the loading, creation, storing, and compilation of modules It also stores a LL...
Definition: Context.hpp:55
Result connectData(NodeInstance &lhs, size_t lhsConnID, NodeInstance &rhs, size_t rhsConnID)
Connects two nodes&#39; data connections.
GraphStruct * getOrCreateStruct(std::string name, bool *inserted=nullptr)
Create a new struct in the module.
Result connectExec(NodeInstance &lhs, size_t lhsConnID, NodeInstance &rhs, size_t rhsConnID)
Connects two nodes&#39; exec connections.
std::unordered_map< boost::uuids::uuid, std::unique_ptr< NodeInstance > > & nodes()
Get the nodes in the function Usually called by connectData or connectExec or GraphFunction.
Defines the NodeType class.
Result jsonToGraphFunction(GraphFunction &createInside, const nlohmann::json &input)
Load a GraphFunction–must already exist (use createGraphFunctionDeclarationFromJson) ...
Result typeFromModule(const boost::filesystem::path &module, boost::string_view name, DataType *toFill) noexcept
Gets a DataType from a module.
Definition: Context.cpp:232
Module that holds graph functions.
Definition: GraphModule.hpp:16
Defines the GraphModule class.
The namespace where chigraph lives.
Context & context() const
Get the Context that this module belongs to.
Definition: ChiModule.hpp:68
Defines the Context class and related functions.
A type of data Loose wrapper around llvm::Type*, except it knows which ChiModule it&#39;s in and it embed...
Definition: DataType.hpp:17
GraphModule * newGraphModule(const boost::filesystem::path &fullName)
Create a new GraphModule with the given full name.
Definition: Context.cpp:55
The result object, used for identifiying errors with good diagnostics.
Definition: Result.hpp:72
std::pair< std::string, std::string > parseObjectPair(const nlohmann::json &object)
Parse something that looks like: {"hello": "there"} into {"hello", "there"}.
Declares the GraphFunction class.
NamedDataType getOrCreateLocalVariable(std::string name, DataType type, bool *inserted=nullptr)
Create a new local varaible in the module.
Defines the NodeInstance class and related functions.
Define deserialization functions.
Result createGraphFunctionDeclarationFromJson(GraphModule &createInside, const nlohmann::json &input, GraphFunction **toFill=nullptr)
Create a forward declaration of a function in a module with an empty graph.