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