1 // Copyright (c) 2017-2021, Lawrence Livermore National Security, LLC and
2 // other Axom Project Developers. See the top-level LICENSE file for details.
3 //
4 // SPDX-License-Identifier: (BSD-3-Clause)
5 
6 /*!
7  *******************************************************************************
8  * \file LuaReader.cpp
9  *
10  * \brief This file contains the class implementation of the LuaReader.
11  *******************************************************************************
12  */
13 
14 #include <fstream>
15 
16 #include "axom/inlet/LuaReader.hpp"
17 
18 #include "axom/core/utilities/FileUtilities.hpp"
19 #include "axom/core/utilities/StringUtilities.hpp"
20 #include "axom/inlet/inlet_utils.hpp"
21 
22 #include "axom/fmt.hpp"
23 #include "axom/slic.hpp"
24 
25 namespace axom
26 {
27 namespace inlet
28 {
29 namespace detail
30 {
31 /*!
32  *******************************************************************************
33  * \brief Extracts an object from sol into a concrete type, implemented to support
34  * extracting to a VariantKey
35  *
36  * \tparam T The type to extract to
37  *******************************************************************************
38  */
39 template <typename T>
extractAs(const axom::sol::object & obj)40 T extractAs(const axom::sol::object& obj)
41 {
42   // By default, just ask sol to cast it
43   return obj.as<T>();
44 }
45 /// \overload
46 template <>
extractAs(const axom::sol::object & obj)47 VariantKey extractAs(const axom::sol::object& obj)
48 {
49   // FIXME: Floating-point indices?
50   if(obj.get_type() == axom::sol::type::number)
51   {
52     return obj.as<int>();
53   }
54   else
55   {
56     return obj.as<std::string>();
57   }
58 }
59 
60 /*!
61  *******************************************************************************
62  * \brief Recursive name retrieval function - adds the names of all descendents
63  * of \a node as an Inlet-style path
64  *
65  * \param [in] ignores The vector of paths to ignore, used for pre-loaded entries
66  * in Lua's global table
67  * \param [in] table The Lua table to "visit"
68  * \param [in] prefix The Inlet-style path to \a table relative to the "root" of
69  * the input file
70  * \param [out] names The vector of paths to add to
71  *******************************************************************************
72  */
nameRetrievalHelper(const std::vector<std::string> & ignores,const axom::sol::table & table,const std::string & prefix,std::vector<std::string> & names)73 void nameRetrievalHelper(const std::vector<std::string>& ignores,
74                          const axom::sol::table& table,
75                          const std::string& prefix,
76                          std::vector<std::string>& names)
77 {
78   auto toString = [](const VariantKey& key) {
79     return key.type() == InletType::String
80       ? static_cast<std::string>(key)
81       : std::to_string(static_cast<int>(key));
82   };
83   for(const auto& entry : table)
84   {
85     const auto variantKey = detail::extractAs<VariantKey>(entry.first);
86     const std::string fullName =
87       utilities::string::appendPrefix(prefix, toString(variantKey));
88     if(std::find(ignores.begin(), ignores.end(), fullName) == ignores.end())
89     {
90       names.push_back(fullName);
91       if(entry.second.get_type() == axom::sol::type::table &&
92          (ignores.back() != fullName))
93       {
94         nameRetrievalHelper(ignores, entry.second, fullName, names);
95       }
96     }
97   }
98 }
99 
100 }  // end namespace detail
101 
LuaReader()102 LuaReader::LuaReader()
103 {
104   m_lua.open_libraries(axom::sol::lib::base,
105                        axom::sol::lib::math,
106                        axom::sol::lib::string,
107                        axom::sol::lib::package);
108   auto vec_type = m_lua.new_usertype<FunctionType::Vector>(
109     "Vector",  // Name of the class in Lua
110     // Add make_vector as a constructor to enable "new Vector(x,y,z)"
111     // Use lambdas for 2D and "default" cases - default arguments cannot be
112     // propagated automatically
113     "new",
114     axom::sol::factories(
115       [](double x, double y, double z) {
116         return FunctionType::Vector {x, y, z};
117       },
118       [](double x, double y) {
119         return FunctionType::Vector {x, y};
120       },
121       // Assume three for a default constructor
122       [] { return FunctionType::Vector {}; }),
123     // Add vector addition operation
124     axom::sol::meta_function::addition,
125     [](const FunctionType::Vector& u, const FunctionType::Vector& v) {
126       SLIC_ASSERT_MSG(
127         u.dim == v.dim,
128         "[Inlet] Operands to InletVector addition are of different dimension");
129       return FunctionType::Vector {u.vec + v.vec, u.dim};
130     },
131     axom::sol::meta_function::subtraction,
132     [](const FunctionType::Vector& u, const FunctionType::Vector& v) {
133       SLIC_ASSERT_MSG(u.dim == v.dim,
134                       "[Inlet] Operands to InletVector subtraction are of "
135                       "different dimension");
136       return FunctionType::Vector {u.vec - v.vec, u.dim};
137     },
138     // Needs to be resolved in the same way as operator+
139     axom::sol::meta_function::unary_minus,
140     [](const FunctionType::Vector& u) {
141       return FunctionType::Vector {-u.vec, u.dim};
142     },
143     // To allow both "directions" of a scalar multiplication, the overloads
144     // have to be manually specified + resolved
145     axom::sol::meta_function::multiplication,
146     axom::sol::overload(
147       [](const FunctionType::Vector& u, const double a) {
148         return FunctionType::Vector {a * u.vec, u.dim};
149       },
150       [](const double a, const FunctionType::Vector& u) {
151         return FunctionType::Vector {a * u.vec, u.dim};
152       }),
153     // Separate functions from get/set via index - subtract 1 as lua is 1-indexed
154     axom::sol::meta_function::index,
155     [](const FunctionType::Vector& vec, const int key) { return vec[key - 1]; },
156     // A lambda is used here as the set-via-returned reference is insufficient
157     axom::sol::meta_function::new_index,
158     [](FunctionType::Vector& vec, const int key, const double value) {
159       vec[key - 1] = value;
160     },
161     // Set up the mathematical operations by name
162     "norm",
163     [](const FunctionType::Vector& u) { return u.vec.norm(); },
164     "squared_norm",
165     [](const FunctionType::Vector& u) { return u.vec.squared_norm(); },
166     "unitVector",
167     [](const FunctionType::Vector& u) {
168       return FunctionType::Vector {u.vec.unitVector(), u.dim};
169     },
170     "dot",
171     [](const FunctionType::Vector& u, const FunctionType::Vector& v) {
172       SLIC_ASSERT_MSG(u.dim == v.dim,
173                       "[Inlet] Operands to InletVector dot product are of "
174                       "different dimension");
175       return u.vec.dot(v.vec);
176     },
177     // Implemented as a member function like dot products are, for simplicity
178     "cross",
179     // Needs to be resolved as it is an overloaded static method
180     [](const FunctionType::Vector& u, const FunctionType::Vector& v) {
181       SLIC_ASSERT_MSG(u.dim == v.dim,
182                       "[Inlet] Operands to InletVector cross product are of "
183                       "different dimension");
184       return FunctionType::Vector {primal::Vector3D::cross_product(u.vec, v.vec),
185                                    u.dim};
186     },
187     "dim",
188     axom::sol::property([](const FunctionType::Vector& u) { return u.dim; }),
189     "x",
190     axom::sol::property([](const FunctionType::Vector& u) { return u.vec[0]; }),
191     "y",
192     axom::sol::property([](const FunctionType::Vector& u) { return u.vec[1]; }),
193     "z",
194     axom::sol::property([](const FunctionType::Vector& u) { return u.vec[2]; }));
195 
196   // Pass the preloaded globals as both the set to ignore and the set to add
197   // to, such that only the top-level preloaded globals are added
198   detail::nameRetrievalHelper(m_preloaded_globals,
199                               m_lua.globals(),
200                               "",
201                               m_preloaded_globals);
202 }
203 
parseFile(const std::string & filePath)204 bool LuaReader::parseFile(const std::string& filePath)
205 {
206   if(!axom::utilities::filesystem::pathExists(filePath))
207   {
208     SLIC_WARNING(
209       fmt::format("Inlet: Given Lua input file does not exist: {0}", filePath));
210     return false;
211   }
212 
213   auto script = m_lua.script_file(filePath);
214   if(!script.valid())
215   {
216     SLIC_WARNING(
217       fmt::format("Inlet: Given Lua input file is invalid: {0}", filePath));
218   }
219   return script.valid();
220 }
221 
parseString(const std::string & luaString)222 bool LuaReader::parseString(const std::string& luaString)
223 {
224   if(luaString.empty())
225   {
226     SLIC_WARNING("Inlet: Given an empty Lua string to parse.");
227     return false;
228   }
229   m_lua.script(luaString);
230   return true;
231 }
232 
233 // TODO allow alternate delimiter at sidre level
234 #define SCOPE_DELIMITER '/'
235 
getBool(const std::string & id,bool & value)236 ReaderResult LuaReader::getBool(const std::string& id, bool& value)
237 {
238   return getValue(id, value);
239 }
240 
getDouble(const std::string & id,double & value)241 ReaderResult LuaReader::getDouble(const std::string& id, double& value)
242 {
243   return getValue(id, value);
244 }
245 
getInt(const std::string & id,int & value)246 ReaderResult LuaReader::getInt(const std::string& id, int& value)
247 {
248   return getValue(id, value);
249 }
250 
getString(const std::string & id,std::string & value)251 ReaderResult LuaReader::getString(const std::string& id, std::string& value)
252 {
253   return getValue(id, value);
254 }
255 
getIntMap(const std::string & id,std::unordered_map<int,int> & values)256 ReaderResult LuaReader::getIntMap(const std::string& id,
257                                   std::unordered_map<int, int>& values)
258 {
259   return getMap(id, values, axom::sol::type::number);
260 }
261 
getDoubleMap(const std::string & id,std::unordered_map<int,double> & values)262 ReaderResult LuaReader::getDoubleMap(const std::string& id,
263                                      std::unordered_map<int, double>& values)
264 {
265   return getMap(id, values, axom::sol::type::number);
266 }
267 
getBoolMap(const std::string & id,std::unordered_map<int,bool> & values)268 ReaderResult LuaReader::getBoolMap(const std::string& id,
269                                    std::unordered_map<int, bool>& values)
270 {
271   return getMap(id, values, axom::sol::type::boolean);
272 }
273 
getStringMap(const std::string & id,std::unordered_map<int,std::string> & values)274 ReaderResult LuaReader::getStringMap(const std::string& id,
275                                      std::unordered_map<int, std::string>& values)
276 {
277   return getMap(id, values, axom::sol::type::string);
278 }
279 
getIntMap(const std::string & id,std::unordered_map<VariantKey,int> & values)280 ReaderResult LuaReader::getIntMap(const std::string& id,
281                                   std::unordered_map<VariantKey, int>& values)
282 {
283   return getMap(id, values, axom::sol::type::number);
284 }
285 
getDoubleMap(const std::string & id,std::unordered_map<VariantKey,double> & values)286 ReaderResult LuaReader::getDoubleMap(const std::string& id,
287                                      std::unordered_map<VariantKey, double>& values)
288 {
289   return getMap(id, values, axom::sol::type::number);
290 }
291 
getBoolMap(const std::string & id,std::unordered_map<VariantKey,bool> & values)292 ReaderResult LuaReader::getBoolMap(const std::string& id,
293                                    std::unordered_map<VariantKey, bool>& values)
294 {
295   return getMap(id, values, axom::sol::type::boolean);
296 }
297 
getStringMap(const std::string & id,std::unordered_map<VariantKey,std::string> & values)298 ReaderResult LuaReader::getStringMap(
299   const std::string& id,
300   std::unordered_map<VariantKey, std::string>& values)
301 {
302   return getMap(id, values, axom::sol::type::string);
303 }
304 
305 template <typename Iter>
traverseToTable(Iter begin,Iter end,axom::sol::table & table)306 bool LuaReader::traverseToTable(Iter begin, Iter end, axom::sol::table& table)
307 {
308   // Nothing to traverse
309   if(begin == end)
310   {
311     return true;
312   }
313 
314   if(!m_lua[*begin].valid())
315   {
316     return false;
317   }
318 
319   table = m_lua[*begin];  // Use the first one to index into the global lua state
320   ++begin;
321 
322   // Then use the remaining keys to walk down to the requested table
323   for(auto curr = begin; curr != end; ++curr)
324   {
325     auto key = *curr;
326     bool is_int = conduit::utils::string_is_integer(key);
327     int key_as_int = conduit::utils::string_to_value<int>(key);
328     if(is_int && table[key_as_int].valid())
329     {
330       table = table[key_as_int];
331     }
332     else if(table[key].valid())
333     {
334       table = table[key];
335     }
336     else
337     {
338       return false;
339     }
340   }
341   return true;
342 }
343 
getIndices(const std::string & id,std::vector<int> & indices)344 ReaderResult LuaReader::getIndices(const std::string& id,
345                                    std::vector<int>& indices)
346 {
347   return getIndicesInternal(id, indices);
348 }
349 
getIndices(const std::string & id,std::vector<VariantKey> & indices)350 ReaderResult LuaReader::getIndices(const std::string& id,
351                                    std::vector<VariantKey>& indices)
352 {
353   return getIndicesInternal(id, indices);
354 }
355 
356 // A set of pure functions for handling the conversion of Lua functions to C++
357 // callables
358 namespace detail
359 {
360 /*!
361  *****************************************************************************
362  * \brief Templated function for calling a sol function
363  *
364  * \param [in] func The sol function of unknown concrete type
365  * \tparam Args The argument types of the function
366  *
367  * \return A checkable version of the function's result
368  *****************************************************************************
369  */
370 template <typename... Args>
callWith(const axom::sol::protected_function & func,Args &&...args)371 axom::sol::protected_function_result callWith(
372   const axom::sol::protected_function& func,
373   Args&&... args)
374 {
375   auto tentative_result = func(std::forward<Args>(args)...);
376   SLIC_ERROR_IF(
377     !tentative_result.valid(),
378     "[Inlet] Lua function call failed, argument types possibly incorrect");
379   return tentative_result;
380 }
381 
382 /*!
383  *****************************************************************************
384  * \brief Templated function for extracting a concrete type from a sol function
385  * result, used to allow for returning nonprimitive types, specifically, vectors
386  *
387  * \param [in] res The sol result of unknown concrete type
388  * \tparam Ret The return type of the function
389  *
390  * \return The function's result
391  *****************************************************************************
392  */
393 template <typename Ret>
extractResult(axom::sol::protected_function_result && res)394 Ret extractResult(axom::sol::protected_function_result&& res)
395 {
396   axom::sol::optional<Ret> option = res;
397   SLIC_ERROR_IF(
398     !option,
399     "[Inlet] Lua function call failed, return types possibly incorrect");
400   return option.value();
401 }
402 
403 template <>
extractResult(axom::sol::protected_function_result &&)404 FunctionType::Void extractResult<FunctionType::Void>(
405   axom::sol::protected_function_result&&)
406 { }
407 
408 /*!
409  *****************************************************************************
410  * \brief Creates a std::function given a Lua function and template parameters
411  * corresponding to the function signature
412  *
413  * \param [in] func The sol object containing the lua function of unknown signature
414  * \tparam Ret The return type of the function
415  * \tparam Args... The argument types of the function
416  *
417  * \return A std::function that wraps the lua function
418  *
419  * \note This is needed as a layer of indirection for bindArgType so it can
420  * properly deduce the constructor call
421  *****************************************************************************
422  */
423 template <typename Ret, typename... Args>
424 std::function<Ret(typename detail::inlet_function_arg_type<Args>::type...)>
buildStdFunction(axom::sol::protected_function && func)425 buildStdFunction(axom::sol::protected_function&& func)
426 {
427   // Generalized lambda capture needed to move into lambda
428   return [func(std::move(func))](
429            typename detail::inlet_function_arg_type<Args>::type... args) {
430     return extractResult<Ret>(callWith(func, args...));
431   };
432 }
433 
434 /*!
435  *****************************************************************************
436  * \brief Adds argument types to a parameter pack based on the contents
437  * of a std::vector of type tags
438  *
439  * \param [in] func The sol object containing the lua function of unknown signature
440  * \param [in] arg_types The vector of argument types
441  *
442  * \tparam I The number of arguments processed, or "stack size", used to mitigate
443  * infinite compile-time recursion
444  * \tparam Ret The function's return type
445  * \tparam Args... The function's current arguments (already processed), remaining
446  * arguments are in the arg_types vector
447  *
448  * \return A callable wrapper
449  *****************************************************************************
450  */
451 template <std::size_t I, typename Ret, typename... Args>
bindArgType(axom::sol::protected_function &&,const std::vector<FunctionTag> &)452 typename std::enable_if<(I > MAX_NUM_ARGS), FunctionVariant>::type bindArgType(
453   axom::sol::protected_function&&,
454   const std::vector<FunctionTag>&)
455 {
456   SLIC_ERROR("[Inlet] Maximum number of function arguments exceeded: " << I);
457   return {};
458 }
459 
460 template <std::size_t I, typename Ret, typename... Args>
bindArgType(axom::sol::protected_function && func,const std::vector<FunctionTag> & arg_types)461 typename std::enable_if<I <= MAX_NUM_ARGS, FunctionVariant>::type bindArgType(
462   axom::sol::protected_function&& func,
463   const std::vector<FunctionTag>& arg_types)
464 {
465   if(arg_types.size() == I)
466   {
467     return buildStdFunction<Ret, Args...>(std::move(func));
468   }
469   else
470   {
471     switch(arg_types[I])
472     {
473     case FunctionTag::Vector:
474       return bindArgType<I + 1, Ret, Args..., FunctionType::Vector>(
475         std::move(func),
476         arg_types);
477     case FunctionTag::Double:
478       return bindArgType<I + 1, Ret, Args..., double>(std::move(func), arg_types);
479     case FunctionTag::String:
480       return bindArgType<I + 1, Ret, Args..., std::string>(std::move(func),
481                                                            arg_types);
482     default:
483       SLIC_ERROR("[Inlet] Unexpected function argument type");
484     }
485   }
486   return {};  // Never reached but needed as errors do not imply control flow as with exceptions
487 }
488 
489 /*!
490  *****************************************************************************
491  * \brief Performs a type-checked access to a Lua table
492  *
493  * \param [in]  proxy The axom::sol::proxy object to retrieve from
494  * \param [out] val The value to write to, if it is of the correct type
495  *
496  * \return ReaderResult::Success if the object was of the correct type,
497  * ReaderResult::WrongType otherwise
498  *****************************************************************************
499  */
500 template <typename Proxy, typename Value>
checkedGet(const Proxy & proxy,Value & val)501 ReaderResult checkedGet(const Proxy& proxy, Value& val)
502 {
503   axom::sol::optional<Value> option = proxy;
504   if(option)
505   {
506     val = option.value();
507     return ReaderResult::Success;
508   }
509   return ReaderResult::WrongType;
510 }
511 
512 }  // end namespace detail
513 
getFunction(const std::string & id,const FunctionTag ret_type,const std::vector<FunctionTag> & arg_types)514 FunctionVariant LuaReader::getFunction(const std::string& id,
515                                        const FunctionTag ret_type,
516                                        const std::vector<FunctionTag>& arg_types)
517 {
518   auto lua_func = getFunctionInternal(id);
519   if(lua_func)
520   {
521     switch(ret_type)
522     {
523     case FunctionTag::Vector:
524       return detail::bindArgType<0u, FunctionType::Vector>(std::move(lua_func),
525                                                            arg_types);
526     case FunctionTag::Double:
527       return detail::bindArgType<0u, double>(std::move(lua_func), arg_types);
528     case FunctionTag::Void:
529       return detail::bindArgType<0u, void>(std::move(lua_func), arg_types);
530     case FunctionTag::String:
531       return detail::bindArgType<0u, std::string>(std::move(lua_func), arg_types);
532     default:
533       SLIC_ERROR("[Inlet] Unexpected function return type");
534     }
535   }
536   return {};  // Return an empty function to indicate that the function was not found
537 }
538 
539 template <typename T>
getValue(const std::string & id,T & value)540 ReaderResult LuaReader::getValue(const std::string& id, T& value)
541 {
542   std::vector<std::string> tokens =
543     axom::utilities::string::split(id, SCOPE_DELIMITER);
544 
545   if(tokens.size() == 1)
546   {
547     if(m_lua[tokens[0]].valid())
548     {
549       return detail::checkedGet(m_lua[tokens[0]], value);
550     }
551     return ReaderResult::NotFound;
552   }
553 
554   axom::sol::table t;
555   // Don't traverse through the last token as it doesn't contain a table
556   if(traverseToTable(tokens.begin(), tokens.end() - 1, t))
557   {
558     if(t[tokens.back()].valid())
559     {
560       return detail::checkedGet(t[tokens.back()], value);
561     }
562   }
563 
564   return ReaderResult::NotFound;
565 }
566 
getAllNames()567 std::vector<std::string> LuaReader::getAllNames()
568 {
569   std::vector<std::string> result;
570   detail::nameRetrievalHelper(m_preloaded_globals, m_lua.globals(), "", result);
571   return result;
572 }
573 
574 template <typename Key, typename Val>
getMap(const std::string & id,std::unordered_map<Key,Val> & values,axom::sol::type type)575 ReaderResult LuaReader::getMap(const std::string& id,
576                                std::unordered_map<Key, Val>& values,
577                                axom::sol::type type)
578 {
579   values.clear();
580   std::vector<std::string> tokens =
581     axom::utilities::string::split(id, SCOPE_DELIMITER);
582 
583   axom::sol::table t;
584   if(tokens.empty() || !traverseToTable(tokens.begin(), tokens.end(), t))
585   {
586     return ReaderResult::NotFound;
587   }
588 
589   // Allows for filtering out keys of incorrect type
590   const auto is_correct_key_type = [](const axom::sol::type type) {
591     bool is_number = type == axom::sol::type::number;
592     // Arrays only
593     if(std::is_same<Key, int>::value)
594     {
595       return is_number;
596     }
597     // Dictionaries can have both string-valued and numeric keys
598     else
599     {
600       return is_number || (type == axom::sol::type::string);
601     }
602   };
603   bool contains_other_type = false;
604   for(const auto& entry : t)
605   {
606     // Gets only indexed items in the table.
607     if(is_correct_key_type(entry.first.get_type()) &&
608        entry.second.get_type() == type)
609     {
610       values[detail::extractAs<Key>(entry.first)] =
611         detail::extractAs<Val>(entry.second);
612     }
613     else
614     {
615       contains_other_type = true;
616     }
617   }
618   return collectionRetrievalResult(contains_other_type, !values.empty());
619 }
620 
621 template <typename T>
getIndicesInternal(const std::string & id,std::vector<T> & indices)622 ReaderResult LuaReader::getIndicesInternal(const std::string& id,
623                                            std::vector<T>& indices)
624 {
625   std::vector<std::string> tokens =
626     axom::utilities::string::split(id, SCOPE_DELIMITER);
627 
628   axom::sol::table t;
629 
630   if(tokens.empty() || !traverseToTable(tokens.begin(), tokens.end(), t))
631   {
632     return ReaderResult::NotFound;
633   }
634 
635   indices.clear();
636 
637   // std::transform ends up being messier here
638   for(const auto& entry : t)
639   {
640     indices.push_back(detail::extractAs<T>(entry.first));
641   }
642   return ReaderResult::Success;
643 }
644 
getFunctionInternal(const std::string & id)645 axom::sol::protected_function LuaReader::getFunctionInternal(const std::string& id)
646 {
647   std::vector<std::string> tokens =
648     axom::utilities::string::split(id, SCOPE_DELIMITER);
649   axom::sol::protected_function lua_func;
650 
651   if(tokens.size() == 1)
652   {
653     if(m_lua[tokens[0]].valid())
654     {
655       lua_func = m_lua[tokens[0]];
656       detail::checkedGet(m_lua[tokens[0]], lua_func);
657     }
658   }
659   else
660   {
661     axom::sol::table t;
662     // Don't traverse through the last token as it doesn't contain a table
663     if(traverseToTable(tokens.begin(), tokens.end() - 1, t) &&
664        t[tokens.back()].valid())
665     {
666       detail::checkedGet(t[tokens.back()], lua_func);
667     }
668   }
669   return lua_func;
670 }
671 
672 }  // end namespace inlet
673 }  // end namespace axom
674