├── .clang-format ├── .clang-tidy ├── .gitignore ├── CMakeLists.txt ├── CONTRIBUTING.md ├── LICENSE ├── LibraryLinkUtilities.wl ├── README.md ├── cmake ├── FindBreathe.cmake ├── FindSphinx.cmake ├── FindWSTP.cmake ├── FindWolframLanguage.cmake ├── FindWolframLibrary.cmake ├── LLUConfig.cmake.in └── Wolfram │ ├── CVSUtilities.cmake │ ├── Common.cmake │ └── PacletUtilities.cmake ├── docs ├── CMakeLists.txt ├── DoxygenLayout.xml ├── DoxygenMain.md ├── LibraryLinkUtilities.doxyfile.in ├── Makefile ├── _extensions │ └── wolfram.py ├── _static │ ├── css │ │ └── custom.css │ ├── img │ │ ├── LLULogo.png │ │ ├── LLULogo_144.png │ │ ├── LibFunNoProg.gif │ │ ├── LibFunWithProg.gif │ │ ├── Logger1.png │ │ ├── Logger2.png │ │ ├── Logger3.png │ │ └── favicon-32.png │ └── js │ │ └── custom.js ├── basic │ └── how_to_use.rst ├── conf.py.in ├── docutils.conf ├── globals.rst ├── index.rst ├── modules │ ├── containers.rst │ ├── error_handling.rst │ ├── functions.rst │ ├── logger.rst │ ├── managed_expressions.rst │ ├── progress_monitor.rst │ └── wstp.rst ├── other │ ├── cmake_utilities.rst │ └── paclet_use.rst └── tutorial │ ├── DataStore.png │ ├── IntegratingCppWithWL.md │ └── WSTPFunction.png ├── include └── LLU │ ├── Async │ ├── Queue.h │ ├── ThreadPool.h │ ├── Utilities.h │ └── WorkStealingQueue.h │ ├── Containers │ ├── DataList.h │ ├── Generic │ │ ├── Base.hpp │ │ ├── DataStore.hpp │ │ ├── DataVector.hpp │ │ ├── Image.hpp │ │ ├── NumericArray.hpp │ │ ├── SparseArray.hpp │ │ └── Tensor.hpp │ ├── Image.h │ ├── Interfaces.h │ ├── Iterators │ │ ├── DataList.hpp │ │ ├── DataNode.hpp │ │ ├── DataStore.hpp │ │ └── IterableContainer.hpp │ ├── MArray.hpp │ ├── MArrayDimensions.h │ ├── NumericArray.h │ ├── SparseArray.h │ ├── Tensor.h │ └── Views │ │ ├── Image.hpp │ │ ├── NumericArray.hpp │ │ └── Tensor.hpp │ ├── ErrorLog │ ├── ErrorManager.h │ ├── Errors.h │ ├── LibraryLinkError.h │ └── Logger.h │ ├── FileUtilities.h │ ├── LLU.h │ ├── LibraryData.h │ ├── LibraryLinkFunctionMacro.h │ ├── MArgument.h │ ├── MArgumentManager.h │ ├── ManagedExpression.hpp │ ├── NoMinMaxWindows.h │ ├── ProgressMonitor.h │ ├── TypedMArgument.h │ ├── UniquePtr.h │ ├── Utilities.hpp │ └── WSTP │ ├── EncodingTraits.hpp │ ├── Get.h │ ├── Put.h │ ├── Release.h │ ├── Strings.h │ ├── Utilities.h │ ├── UtilityTypeTraits.hpp │ └── WSStream.hpp ├── src ├── Containers │ ├── DataStore.cpp │ ├── DataVector.cpp │ ├── Image.cpp │ ├── MArrayDimensions.cpp │ ├── NumericArray.cpp │ ├── SparseArray.cpp │ └── Tensor.cpp ├── ErrorLog │ ├── ErrorManager.cpp │ ├── Errors.cpp │ ├── LibraryLinkError.cpp │ └── Logger.cpp ├── FileUtilities.cpp ├── LibraryData.cpp ├── MArgument.cpp ├── MArgumentManager.cpp ├── ProgressMonitor.cpp ├── TypedMArgument.cpp └── WSTP │ ├── Get.cpp │ ├── Put.cpp │ ├── Release.cpp │ ├── Strings.cpp │ └── Utilities.cpp └── tests ├── Demo ├── CMakeLists.txt ├── Demo │ ├── Kernel │ │ └── Demo.wl │ └── PacletInfo.wl ├── Sources │ └── demo.cpp └── Tests │ └── test.wl └── UnitTests ├── .clang-tidy ├── Async ├── AsyncTestSuite.mt └── TestSources │ └── PoolTest.cpp ├── CMakeLists.txt ├── DataList ├── DataListTestSuite.mt └── TestSources │ ├── DataListCompilationErrors.cpp │ └── DataListTest.cpp ├── DataVector ├── DataVectorTestSuite.mt └── TestSources │ └── DataVectorTest.cpp ├── ErrorReporting ├── ErrorReportingTestSuite.mt ├── LoggerTests.nb └── TestSources │ ├── ErrorReportingTest.cpp │ └── LoggerTest.cpp ├── GenericContainers ├── GenericContainersTestSuite.mt └── TestSources │ ├── GenericContainersTest.cpp │ └── PoliciesCompilationErrors.cpp ├── Image ├── ImageTestSuite.mt └── TestSources │ ├── EchoImage.cpp │ ├── ImageDimensions.cpp │ └── ImageNegate.cpp ├── MArgumentManager ├── MArgumentManagerTestSuite.mt └── TestSources │ ├── MArgumentManagerCompilationErrors.cpp │ └── MArgumentManagerTest.cpp ├── ManagedExpressions ├── ManagedExpressionsTestSuite.mt └── TestSources │ └── ManagedExprTest.cpp ├── NumericArray ├── NumericArrayTestSuite.mt └── TestSources │ ├── NumericArrayOperations.cpp │ └── NumericArrayOperations.wl ├── ProgressMonitor ├── ProgressMonitorTests.nb └── TestSources │ └── ProgressTest.cpp ├── RunAllTests.mt ├── Scalar ├── ScalarTestSuite.mt └── TestSources │ ├── Boolean.cpp │ ├── Complex.cpp │ ├── Integer.cpp │ └── Real.cpp ├── SparseArray ├── SparseArrayTestSuite.mt └── TestSources │ └── SparseArrayOperations.cpp ├── String ├── StringTestSuite.mt └── TestSources │ └── String.cpp ├── Tensor ├── TensorTestSuite.mt └── TestSources │ ├── Basic.cpp │ ├── ScalarOperations.cpp │ └── SharedData.cpp ├── TestConfig.wl ├── Utilities ├── TestSources │ └── UtilitiesTest.cpp └── UtilitiesTestSuite.mt └── WSTP ├── TestSources ├── WSEncodings.cpp ├── WSTest.cpp └── WSTestCompilationErrors.cpp └── WSTPTestSuite.mt /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | # https://clang.llvm.org/docs/ClangFormatStyleOptions.html 3 | Language: Cpp 4 | # BasedOnStyle: WebKit 5 | AccessModifierOffset: -4 6 | AlignAfterOpenBracket: Align 7 | AlignConsecutiveAssignments: false 8 | AlignConsecutiveDeclarations: false 9 | AlignEscapedNewlines: Left 10 | AlignOperands: true 11 | AlignTrailingComments: true 12 | AllowAllParametersOfDeclarationOnNextLine: true 13 | AllowShortBlocksOnASingleLine: Empty 14 | AllowShortCaseLabelsOnASingleLine: true 15 | AllowShortFunctionsOnASingleLine: Empty 16 | AllowShortLambdasOnASingleLine: Inline 17 | AllowShortIfStatementsOnASingleLine: Never 18 | AllowShortLoopsOnASingleLine: false 19 | AlwaysBreakAfterDefinitionReturnType: None 20 | AlwaysBreakAfterReturnType: None 21 | AlwaysBreakBeforeMultilineStrings: true 22 | AlwaysBreakTemplateDeclarations: Yes 23 | BinPackArguments: true 24 | BinPackParameters: true 25 | BraceWrapping: 26 | AfterClass: false 27 | AfterControlStatement: Never 28 | AfterEnum: false 29 | AfterFunction: false 30 | AfterNamespace: false 31 | AfterObjCDeclaration: false 32 | AfterStruct: false 33 | AfterUnion: false 34 | AfterExternBlock: false 35 | BeforeCatch: false 36 | BeforeElse: false 37 | IndentBraces: false 38 | SplitEmptyFunction: true 39 | SplitEmptyRecord: true 40 | SplitEmptyNamespace: true 41 | BreakBeforeBinaryOperators: None 42 | BreakBeforeBraces: Attach 43 | BreakBeforeInheritanceComma: false 44 | BreakBeforeTernaryOperators: true 45 | BreakConstructorInitializersBeforeComma: false 46 | BreakConstructorInitializers: BeforeColon 47 | BreakAfterJavaFieldAnnotations: false 48 | BreakStringLiterals: true 49 | ColumnLimit: 160 50 | CommentPragmas: '^ IWYU pragma:' 51 | CompactNamespaces: false 52 | ConstructorInitializerAllOnOneLineOrOnePerLine: false 53 | ConstructorInitializerIndentWidth: 4 54 | ContinuationIndentWidth: 4 55 | Cpp11BracedListStyle: true 56 | DerivePointerAlignment: false 57 | DisableFormat: false 58 | ExperimentalAutoDetectBinPacking: false 59 | FixNamespaceComments: true 60 | ForEachMacros: 61 | - foreach 62 | - Q_FOREACH 63 | - BOOST_FOREACH 64 | IncludeBlocks: Regroup 65 | IncludeCategories: 66 | - Regex: '^(<|")(mathlink|wstp)\.h' 67 | Priority: 3 68 | - Regex: '^(<|")Wolfram.*' 69 | Priority: 4 70 | - Regex: '^(<|")LLU/.*' 71 | Priority: 5 72 | - Regex: '^' 73 | Priority: 2 74 | - Regex: '^<.*\.h>' 75 | Priority: 1 76 | - Regex: '^<.*' 77 | Priority: 2 78 | - Regex: '.*' 79 | Priority: 6 80 | IncludeIsMainRegex: '([-_](test|unittest))?$' 81 | IndentCaseLabels: true 82 | IndentPPDirectives: None 83 | IndentWidth: 4 84 | IndentWrappedFunctionNames: false 85 | JavaScriptQuotes: Leave 86 | JavaScriptWrapImports: true 87 | KeepEmptyLinesAtTheStartOfBlocks: true 88 | MacroBlockBegin: '' 89 | MacroBlockEnd: '' 90 | MaxEmptyLinesToKeep: 1 91 | NamespaceIndentation: All 92 | ObjCBlockIndentWidth: 4 93 | ObjCSpaceAfterProperty: true 94 | ObjCSpaceBeforeProtocolList: true 95 | PenaltyBreakAssignment: 2 96 | PenaltyBreakBeforeFirstCallParameter: 1 97 | PenaltyBreakComment: 300 98 | PenaltyBreakFirstLessLess: 120 99 | PenaltyBreakString: 1000 100 | PenaltyExcessCharacter: 1000000 101 | PenaltyReturnTypeOnItsOwnLine: 30 102 | PointerAlignment: Left 103 | ReflowComments: true 104 | SortIncludes: true 105 | SortUsingDeclarations: false 106 | SpaceAfterCStyleCast: false 107 | SpaceAfterTemplateKeyword: false 108 | SpaceBeforeAssignmentOperators: true 109 | SpaceBeforeCpp11BracedList: true 110 | SpaceBeforeCtorInitializerColon: true 111 | SpaceBeforeInheritanceColon: true 112 | SpaceBeforeParens: ControlStatements 113 | SpaceBeforeRangeBasedForLoopColon: true 114 | SpaceInEmptyParentheses: false 115 | SpacesBeforeTrailingComments: 4 116 | SpacesInAngles: false 117 | SpacesInContainerLiterals: true 118 | SpacesInCStyleCastParentheses: false 119 | SpacesInParentheses: false 120 | SpacesInSquareBrackets: false 121 | Standard: c++17 122 | TabWidth: 4 123 | UseTab: Always 124 | ... 125 | 126 | -------------------------------------------------------------------------------- /.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | Checks: > 3 | -*, 4 | bugprone-*, 5 | cert-*, 6 | -cert-dcl21-cpp, 7 | -cert-err58-cpp, 8 | -cert-oop11-cpp, 9 | cppcoreguidelines-*, 10 | -cppcoreguidelines-avoid-c-arrays, 11 | -cppcoreguidelines-non-private-member-variables-in-classes, 12 | -cppcoreguidelines-owning-memory, 13 | google-*, 14 | -google-readability-braces-around-statements, 15 | -google-runtime-references, 16 | -google-runtime-int, 17 | hicpp-avoid-goto 18 | hicpp-exception-baseclass 19 | hicpp-multiway-paths-covered 20 | hicpp-no-assembler 21 | hicpp-signed-bitwise 22 | llvm-*, 23 | -llvm-namespace-comment, 24 | -llvm-qualified-auto, 25 | misc-*, 26 | modernize-*, 27 | -modernize-avoid-c-arrays, 28 | -modernize-use-nodiscard, 29 | -modernize-use-trailing-return-type, 30 | performance-*, 31 | portability-*, 32 | readability-*, 33 | -readability-magic-numbers, 34 | -readability-redundant-access-specifiers 35 | WarningsAsErrors: false 36 | HeaderFilterRegex: '' 37 | AnalyzeTemporaryDtors: false 38 | FormatStyle: file 39 | User: rafal 40 | CheckOptions: 41 | - key: cppcoreguidelines-macro-usage.AllowedRegexp 42 | value: '^DEBUG_*|^LLU_*|^WS_*|LIBRARY_(.*)_FUNCTION|^DEFINE_MANAGED_STORE_AND_SPECIALIZATION' 43 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 44 | value: '1' 45 | - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor 46 | value: '1' 47 | - key: performance-unnecessary-value-param.AllowedTypes 48 | value: 'MArrayDimensions' 49 | - key: readability-implicit-bool-conversion.AllowPointerConditions 50 | value: '1' 51 | - key: readability-uppercase-literal-suffix.NewSuffixes 52 | value: 'L;LL;LU;LLU' 53 | ... 54 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDEs metadata 2 | .project 3 | .cproject 4 | .settings/ 5 | .vscode/ 6 | .vs/ 7 | CMakeSettings.json 8 | .idea/ 9 | 10 | # OS metadata 11 | .DS_Store 12 | 13 | # Other 14 | Debug/ 15 | lib/ 16 | build*/ 17 | cmake-build*/ 18 | tests/Demo/build*/ 19 | install/ -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing to Wolfram® 2 | 3 | Thank you for taking the time to contribute to the [Wolfram Research](https://github.com/wolframresearch) repos on GitHub. 4 | 5 | ## Licensing of Contributions 6 | 7 | By contributing to Wolfram, you agree and affirm that: 8 | 9 | > Wolfram may release your contribution under the terms of the [MIT license](https://opensource.org/licenses/MIT); and 10 | 11 | > You have read and agreed to the [Developer Certificate of Origin](http://developercertificate.org/), version 1.1 or later. 12 | 13 | Please see [LICENSE](LICENSE) for licensing conditions pertaining 14 | to individual repositories. 15 | 16 | 17 | ## Bug reports 18 | 19 | ### Security Bugs 20 | 21 | Please **DO NOT** file a public issue regarding a security issue. 22 | Rather, send your report privately to security@wolfram.com. Security 23 | reports are appreciated and we will credit you for it. We do not offer 24 | a security bounty, but the forecast in your neighborhood will be cloudy 25 | with a chance of Wolfram schwag! 26 | 27 | ### General Bugs 28 | 29 | Please use the repository issues page to submit general bug issues. 30 | 31 | Please do not duplicate issues. 32 | 33 | Please do send a complete and well-written report to us. Note: **the 34 | thoroughness of your report will positively correlate to our willingness 35 | and ability to address it**. 36 | 37 | When reporting issues, always include: 38 | 39 | * Your version of *Mathematica*® or the Wolfram Language. 40 | * Your operating system. 41 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright 2019-2020 Wolfram Research Inc. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of 7 | the Software, and to permit persons to whom the Software is furnished to do so, 8 | subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS 15 | FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR 16 | COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER 17 | IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 18 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. -------------------------------------------------------------------------------- /cmake/FindBreathe.cmake: -------------------------------------------------------------------------------- 1 | # FindBreathe.cmake 2 | # 3 | # Simple module that finds a Python module called Breathe. It assumes that Python interpreter is available. 4 | # 5 | # This will define the following variables 6 | # 7 | # BREATHE_FOUND 8 | # BREATHE_MODULE_LOCATION 9 | # 10 | # You can skip the search by providing BREATHE_MODULE_LOCATION manually 11 | 12 | # Find the Breathe module 13 | if (NOT BREATHE_MODULE_LOCATION) 14 | execute_process( 15 | COMMAND "${Python_EXECUTABLE}" "-c" 16 | "from __future__ import print_function; import re, breathe; print(re.compile('/__init__.py.*').sub('', breathe.__file__))" 17 | RESULT_VARIABLE _BREATHE_STATUS 18 | OUTPUT_VARIABLE _BREATHE_LOCATION 19 | ERROR_QUIET 20 | OUTPUT_STRIP_TRAILING_WHITESPACE) 21 | if (NOT _BREATHE_STATUS) 22 | set(BREATHE_MODULE_LOCATION ${_BREATHE_LOCATION} CACHE STRING "Location of Breathe") 23 | mark_as_advanced(BREATHE_MODULE_LOCATION) 24 | endif () 25 | endif () 26 | 27 | # Let FindPackageHandleStandardArgs do the rest 28 | include(FindPackageHandleStandardArgs) 29 | find_package_handle_standard_args(Breathe DEFAULT_MSG BREATHE_MODULE_LOCATION) -------------------------------------------------------------------------------- /cmake/FindSphinx.cmake: -------------------------------------------------------------------------------- 1 | # FindSphinx.cmake 2 | # 3 | # Simple module that finds Sphinx executable, does not check version 4 | # 5 | # This will define the following variables 6 | # 7 | # SPHINX_FOUND 8 | # SPHINX_EXECUTABLE 9 | # 10 | # You can specify custom location to search for Sphinx by specifying SPHINX_EXE_PATH either as regular or environment variable 11 | # 12 | # Author: Rafal Chojna - rafalc@wolfram.com 13 | 14 | if (SPHINX_EXE_PATH) 15 | set(_SPHINX_EXE_PATH "${SPHINX_EXE_PATH}") 16 | elseif ($ENV{SPHINX_EXE_PATH}) 17 | set(_SPHINX_EXE_PATH "$ENV{SPHINX_EXE_PATH}") 18 | endif () 19 | 20 | if (_SPHINX_EXE_PATH) 21 | set(_SPHINX_SEARCH_OPTS NO_DEFAULT_PATH) 22 | else () 23 | set(_SPHINX_SEARCH_OPTS) 24 | endif () 25 | 26 | # Find Sphinx. The executable is called sphinx-build 27 | find_program(SPHINX_EXECUTABLE 28 | NAMES sphinx-build 29 | PATHS ${_SPHINX_EXE_PATH} 30 | ${_SPHINX_SEARCH_OPTS} 31 | DOC "Path to sphinx-build executable") 32 | 33 | 34 | # Let FindPackageHandleStandardArgs do the rest 35 | include(FindPackageHandleStandardArgs) 36 | find_package_handle_standard_args(Sphinx DEFAULT_MSG SPHINX_EXECUTABLE) 37 | -------------------------------------------------------------------------------- /cmake/FindWSTP.cmake: -------------------------------------------------------------------------------- 1 | # FindWSTP.cmake 2 | # 3 | # Finds the Wolfram Symbolic Transfer Protocol installation 4 | # 5 | # This will define the following variables 6 | # 7 | # WSTP_FOUND 8 | # WSTP_INCLUDE_DIRS 9 | # WSTP_LIBRARIES 10 | # WSTP_VERSION 11 | # 12 | # and the following imported target 13 | # 14 | # WSTP::WSTP 15 | # 16 | # You can specify custom location to search for WSTP either by specifying WOLFRAM_WSTP_PATH explicitly, 17 | # or if that variable is not set, by providing WolframLanguage_INSTALL_DIR variable with a path to a WolframLanguage installation. 18 | # 19 | # Author: Rafal Chojna - rafalc@wolfram.com 20 | 21 | include(Wolfram/Common) 22 | 23 | detect_system_id(_WOLFSTP_SYSTEMID) 24 | 25 | if(WOLFRAM_WSTP_PATH) 26 | set(_WOLFSTP_LIBRARY_PATH "${WOLFRAM_WSTP_PATH}") 27 | else() 28 | set(_WOLFSTP_LIBRARY_PATH "$ENV{WOLFRAM_WSTP_PATH}") 29 | endif() 30 | 31 | if(NOT _WOLFSTP_LIBRARY_PATH AND WolframLanguage_INSTALL_DIR) 32 | set(_WOLFSTP_LIBRARY_PATH "${WolframLanguage_INSTALL_DIR}/SystemFiles/Links/WSTP/DeveloperKit/${_WOLFSTP_SYSTEMID}/CompilerAdditions") 33 | endif() 34 | 35 | if(NOT _WOLFSTP_LIBRARY_PATH AND Mathematica_INSTALL_DIR) 36 | set(_WOLFSTP_LIBRARY_PATH "${Mathematica_INSTALL_DIR}/SystemFiles/Links/WSTP/DeveloperKit/${_WOLFSTP_SYSTEMID}/CompilerAdditions") 37 | endif() 38 | 39 | if(_WOLFSTP_LIBRARY_PATH) 40 | set(_WOLFSTP_SEARCH_OPTS NO_DEFAULT_PATH) 41 | else() 42 | set(_WOLFSTP_SEARCH_OPTS) 43 | endif() 44 | 45 | find_path(WSTP_INCLUDE_DIR 46 | NAMES wstp.h 47 | PATHS "${_WOLFSTP_LIBRARY_PATH}" 48 | ${_WOLFSTP_SEARCH_OPTS} 49 | DOC "Path to the wstp.h" 50 | ) 51 | 52 | if(WSTP_INCLUDE_DIR) 53 | file(STRINGS "${WSTP_INCLUDE_DIR}/wstp.h" _WOLFSTP_HEADER_CONTENTS REGEX "#define WS[A-Z]+ ") 54 | string(REGEX REPLACE ".*#define WSINTERFACE ([0-9]+).*" "\\1" WSTP_VERSION_MAJOR "${_WOLFSTP_HEADER_CONTENTS}") 55 | string(REGEX REPLACE ".*#define WSREVISION ([0-9]+).*" "\\1" WSTP_VERSION_MINOR "${_WOLFSTP_HEADER_CONTENTS}") 56 | 57 | set(WSTP_VERSION_STRING "${WSTP_VERSION_MAJOR}.${WSTP_VERSION_MINOR}") 58 | 59 | get_wstp_library_name(${WSTP_VERSION_MAJOR} _WOLFSTP_LIB_NAME) 60 | 61 | find_library(WSTP_LIBRARY 62 | NAMES "wstp" ${_WOLFSTP_LIB_NAME} 63 | PATHS "${_WOLFSTP_LIBRARY_PATH}" 64 | ${_WOLFSTP_SEARCH_OPTS} 65 | DOC "Path to the WSTP library" 66 | ) 67 | 68 | mark_as_advanced(WSTP_FOUND WSTP_INCLUDE_DIR WSTP_VERSION_MAJOR WSTP_VERSION_MINOR WSTP_VERSION_STRING) 69 | endif() 70 | 71 | include(FindPackageHandleStandardArgs) 72 | find_package_handle_standard_args(WSTP 73 | REQUIRED_VARS WSTP_LIBRARY WSTP_INCLUDE_DIR 74 | VERSION_VAR WSTP_VERSION_STRING 75 | ) 76 | 77 | if (WSTP_FOUND) 78 | set(WSTP_INCLUDE_DIRS ${WSTP_INCLUDE_DIR}) 79 | set(WSTP_LIBRARIES ${WSTP_LIBRARY}) 80 | endif() 81 | 82 | if(WSTP_FOUND AND NOT TARGET WSTP::WSTP) 83 | add_library(WSTP::WSTP SHARED IMPORTED) 84 | set_target_properties(WSTP::WSTP PROPERTIES 85 | INTERFACE_INCLUDE_DIRECTORIES "${WSTP_INCLUDE_DIR}" 86 | IMPORTED_LOCATION "${WSTP_LIBRARIES}" 87 | IMPORTED_IMPLIB "${WSTP_LIBRARIES}" 88 | ) 89 | if(APPLE) 90 | find_library(FOUNDATION_FRAMEWORK Foundation) 91 | set_target_properties(WSTP::WSTP PROPERTIES 92 | INTERFACE_LINK_LIBRARIES "${FOUNDATION_FRAMEWORK};c++" 93 | IMPORTED_LOCATION "${WSTP_LIBRARIES}/wstp" 94 | ) 95 | endif() 96 | endif() -------------------------------------------------------------------------------- /cmake/FindWolframLibrary.cmake: -------------------------------------------------------------------------------- 1 | # FindWolframLibrary.cmake 2 | # 3 | # Finds the Wolfram Library header files 4 | # 5 | # This will define the following variables 6 | # 7 | # WolframLibrary_FOUND 8 | # WolframLibrary_INCLUDE_DIRS 9 | # WolframLibrary_VERSION 10 | # 11 | # and the following imported target 12 | # 13 | # WolframLibrary::WolframLibrary 14 | # 15 | # You can specify custom location to search for Wolfram Library either by specifying WOLFRAM_LIBRARY_PATH explicitly, 16 | # or if that variable is not set, by providing WolframLanguage_INSTALL_DIR variable with a path to a WolframLanguage installation. 17 | # 18 | # Author: Rafal Chojna - rafalc@wolfram.com 19 | 20 | 21 | if(WOLFRAM_LIBRARY_PATH) 22 | set(_WOLFLIB_LIBRARY_PATH "${WOLFRAM_LIBRARY_PATH}") 23 | else() 24 | set(_WOLFLIB_LIBRARY_PATH "$ENV{WOLFRAM_LIBRARY_PATH}") 25 | endif() 26 | 27 | if(NOT _WOLFLIB_LIBRARY_PATH AND WolframLanguage_INSTALL_DIR) 28 | set(_WOLFLIB_LIBRARY_PATH "${WolframLanguage_INSTALL_DIR}/SystemFiles/IncludeFiles/C") 29 | endif() 30 | 31 | if(NOT _WOLFLIB_LIBRARY_PATH AND Mathematica_INSTALL_DIR) 32 | set(_WOLFLIB_LIBRARY_PATH "${Mathematica_INSTALL_DIR}/SystemFiles/IncludeFiles/C") 33 | endif() 34 | 35 | if (_WOLFLIB_LIBRARY_PATH) 36 | set(_WOLFLIB_SEARCH_OPTS NO_DEFAULT_PATH) 37 | else() 38 | set(_WOLFLIB_SEARCH_OPTS) 39 | endif() 40 | 41 | find_path(WolframLibrary_INCLUDE_DIR 42 | NAMES WolframLibrary.h 43 | PATHS "${_WOLFLIB_LIBRARY_PATH}" 44 | ${_WOLFLIB_SEARCH_OPTS} 45 | DOC "Path to the WolframLibrary.h and other header files from Wolfram Library" 46 | ) 47 | 48 | if(WolframLibrary_FOUND) 49 | file(STRINGS "${WolframLibrary_INCLUDE_DIR}/WolframLibrary.h" _WOLFLIB_HEADER_CONTENTS REGEX "#define WolframLibraryVersion ") 50 | string(REGEX REPLACE ".*#define WolframLibraryVersion ([0-9]+).*" "\\1" WolframLibrary_VERSION "${_WOLFLIB_HEADER_CONTENTS}") 51 | 52 | mark_as_advanced(WolframLibrary_FOUND WolframLibrary_INCLUDE_DIR WolframLibrary_VERSION) 53 | set(WolframLibrary_INCLUDE_DIRS ${WolframLibrary_INCLUDE_DIR}) 54 | endif() 55 | 56 | include(FindPackageHandleStandardArgs) 57 | find_package_handle_standard_args(WolframLibrary 58 | REQUIRED_VARS WolframLibrary_INCLUDE_DIR 59 | VERSION_VAR WolframLibrary_VERSION 60 | ) 61 | 62 | if(WolframLibrary_FOUND AND NOT TARGET WolframLibrary::WolframLibrary) 63 | add_library(WolframLibrary::WolframLibrary INTERFACE IMPORTED) 64 | set_target_properties(WolframLibrary::WolframLibrary PROPERTIES 65 | INTERFACE_INCLUDE_DIRECTORIES "${WolframLibrary_INCLUDE_DIR}" 66 | ) 67 | endif() -------------------------------------------------------------------------------- /cmake/LLUConfig.cmake.in: -------------------------------------------------------------------------------- 1 | # LLUConfig file 2 | # Inspired by https://pabloariasal.github.io/2018/02/19/its-time-to-do-cmake-right/ 3 | # Author: Rafal Chojna - rafalc@wolfram.com 4 | 5 | get_filename_component(LLU_CMAKE_DIR "${CMAKE_CURRENT_LIST_FILE}" PATH) 6 | include(CMakeFindDependencyMacro) 7 | 8 | # Include cmake script with utility functions 9 | include(${LLU_CMAKE_DIR}/Wolfram/Common.cmake) 10 | 11 | # Include cmake script with utility functions 12 | include(${LLU_CMAKE_DIR}/Wolfram/PacletUtilities.cmake) 13 | 14 | list(APPEND CMAKE_MODULE_PATH ${LLU_CMAKE_DIR}) 15 | 16 | find_package(WolframLanguage 12.0 QUIET COMPONENTS WolframLibrary WSTP OPTIONAL_COMPONENTS wolframscript) 17 | 18 | find_dependency(WolframLibrary 5) 19 | find_dependency(WSTP 4.25) 20 | 21 | if(NOT TARGET LLU::LLU) 22 | include("${LLU_CMAKE_DIR}/LLUTargets.cmake") 23 | endif() 24 | 25 | set(LLU_LIBRARIES LLU::LLU) -------------------------------------------------------------------------------- /docs/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | find_package(Doxygen 1.8.15 REQUIRED OPTIONAL_COMPONENTS dot) 2 | 3 | set(DOCUMENTATION_SOURCE ${CMAKE_CURRENT_SOURCE_DIR}) 4 | 5 | # Set the location for generated doxygen documentation 6 | set(DOXYGEN_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/doxy) 7 | 8 | # Additional doxygen options 9 | set(DOXYGEN_GENERATE_HTML NO) 10 | set(DOXYGEN_GENERATE_XML YES) 11 | set(DOXYGEN_GENERATE_LATEX NO) 12 | 13 | # First we go through LibraryLinkUtilities.doxyfile.in substituting only @XXX@ tags which provide basic info like LLU version and description, 14 | # but this step still leaves ${XXX} tags in the doxyfile which can be substituted by different tool for custom styling. That is why we create an intermediate 15 | # doxyfile in this step. 16 | set(INTERMEDIATE_DOXYFILE "${DOXYGEN_OUTPUT_DIR}/LibraryLinkUtilities.doxyfile.intermediate") 17 | configure_file( 18 | "${DOCUMENTATION_SOURCE}/LibraryLinkUtilities.doxyfile.in" 19 | ${INTERMEDIATE_DOXYFILE} 20 | @ONLY) 21 | 22 | # In the next step we replace remaining ${XXXX} in the intermediate doxyfile. 23 | set(GENERATED_DOXYFILE "${DOXYGEN_OUTPUT_DIR}/LibraryLinkUtilities.doxyfile") 24 | configure_file( 25 | "${DOXYGEN_OUTPUT_DIR}/LibraryLinkUtilities.doxyfile.intermediate" 26 | ${GENERATED_DOXYFILE}) 27 | 28 | # Doxygen generation output 29 | set(DOXYGEN_OUTPUT_INDEX ${DOXYGEN_OUTPUT_DIR}/xml/index.xml) 30 | 31 | # Custom command that when run will actually generate the documentation 32 | add_custom_command( 33 | OUTPUT ${DOXYGEN_OUTPUT_INDEX} 34 | # Generate doxygen documentation 35 | COMMAND Doxygen::doxygen ${GENERATED_DOXYFILE} 36 | # We need to work where the original doxyfile resides because source paths in doxyfiles are often relative 37 | WORKING_DIRECTORY ${DOCUMENTATION_SOURCE} 38 | COMMENT "Generating documentation with Doxygen" 39 | VERBATIM) 40 | 41 | # Custom target for generating documentation 42 | add_custom_target(doxygen DEPENDS ${DOXYGEN_OUTPUT_INDEX}) 43 | 44 | # Helper macro for locating dependencies for Breathe+Sphinx documentation build. If they are missing we just return and create only the "doxygen" target. 45 | macro(find_or_exit DEPENDENCY) 46 | find_package(${DEPENDENCY}) 47 | if(NOT ${DEPENDENCY}_FOUND) 48 | message(WARNING "Could not find ${DEPENDENCY}. You will not be able to generate Breathe+Sphinx documentation but you can still generate Doxygen.") 49 | return() 50 | endif() 51 | endmacro() 52 | 53 | find_or_exit(Python) 54 | find_or_exit(Sphinx) 55 | find_or_exit(Breathe) 56 | 57 | # Determine Sphinx theme for the docs. Use the following variables: 58 | # SPHINX_THEME - this should be one of the built-in Sphinx themes, see https://www.sphinx-doc.org/en/master/usage/theming.html 59 | # SPHINX_CUSTOM_THEME - if you don't want a built-in theme, use custom one that you have installed, this takes precedence over SPHINX_THEME 60 | # When you use a custom theme, you will also need to specify SPHINX_THEME_PATH and possibly SPHINX_CUSTOM_THEME_INIT which allow Sphinx to find your theme. 61 | # For example, imagine I want to use a neo_rtd_theme from https://github.com/LinxiFan/Sphinx-theme. To do this, I need to pass: 62 | # -DSPHINX_CUSTOM_THEME=neo_rtd_theme 63 | # -DSPHINX_CUSTOM_THEME_INIT="import sphinx_theme" 64 | # -DSPHINX_THEME_PATH="sphinx_theme.get_html_theme_path('neo_rtd_theme')" 65 | # The default theme is "bizstyle" from built-in Sphinx themes. 66 | if (SPHINX_CUSTOM_THEME) 67 | set(SPHINX_THEME ${SPHINX_CUSTOM_THEME}) 68 | elseif(NOT SPHINX_THEME) 69 | set(SPHINX_THEME bizstyle) 70 | endif() 71 | 72 | # Go through conf.py.in substituting custom values 73 | set(SPHINX_CONF_DIR ${DOCUMENTATION_SOURCE}) 74 | configure_file( 75 | "${SPHINX_CONF_DIR}/conf.py.in" 76 | "${CMAKE_CURRENT_BINARY_DIR}/conf.py" 77 | @ONLY) 78 | 79 | # Set the location for the generated Sphinx docs 80 | set(SPHINX_OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/LLU) 81 | 82 | # Create a target that runs Breathe and Sphinx on the doxygen output. This depends on the doxygen target. 83 | add_custom_target( 84 | docs 85 | DEPENDS 86 | ${DOXYGEN_OUTPUT_INDEX} 87 | COMMAND 88 | ${SPHINX_EXECUTABLE} 89 | -q 90 | -b html 91 | -c ${CMAKE_CURRENT_BINARY_DIR} 92 | -j auto 93 | ${DOCUMENTATION_SOURCE} 94 | ${SPHINX_OUTPUT} 95 | WORKING_DIRECTORY 96 | ${DOCUMENTATION_SOURCE} 97 | COMMENT 98 | "Generating HTML documentation with Doxygen + Breathe + Sphinx" 99 | VERBATIM) 100 | -------------------------------------------------------------------------------- /docs/DoxygenMain.md: -------------------------------------------------------------------------------- 1 | # LibraryLink Utilities API Reference 2 | 3 | **LibraryLink Utilities** (abbr. LLU) is a set of modern C++ wrappers over most parts of *LibraryLink* - the Wolfram Language framework for connecting 4 | to C/C++ libraries. LLU started as an internal Wolfram project which can now be used as a stand-alone library and is available publicly on GitHub. 5 | 6 | The following site contains [Doxygen](https://www.doxygen.nl/index.html) documentation automatically generated from the source code comments. 7 | It is intended to serve both users and developers of LLU therefore internal functions are often documented too. 8 | 9 | Doxygen documentation covers the entire API and is great when you know what to look for. Before you dive into detailed description of specific 10 | functions and classes it is recommended to read the tutorial-like introduction to LLU, which can be found here: 11 | 12 | https://wolframresearch.github.io/LibraryLinkUtilities/ 13 | 14 | The linked document contains general overview, build instructions and different sections for each core part of LLU. 15 | Every section provides examples and description of related C++ entities. -------------------------------------------------------------------------------- /docs/_extensions/wolfram.py: -------------------------------------------------------------------------------- 1 | from docutils import nodes 2 | 3 | 4 | def setup(app): 5 | app.add_role('wlref', make_ref_link) 6 | return { 7 | 'parallel_read_safe': True, 8 | 'version': '0.1' 9 | } 10 | 11 | 12 | def make_ref_link(name, rawtext, text, lineno, inliner, options={}, content=[]): 13 | url = 'https://reference.wolfram.com/language/ref/%s' % text 14 | node = nodes.reference(rawtext, text, refuri=url, **options) 15 | return [node], [] 16 | -------------------------------------------------------------------------------- /docs/_static/css/custom.css: -------------------------------------------------------------------------------- 1 | .wy-side-nav-search { 2 | background-color: unset; 3 | } 4 | 5 | .wy-nav-content { 6 | max-width: 1440px; 7 | } 8 | 9 | .rst-content .cpp.function > dt { 10 | width: 100%; 11 | } 12 | 13 | .rst-content .cpp.class > dt { 14 | width: 100%; 15 | font-size: 120%; 16 | } 17 | 18 | .rst-content .cpp.struct > dt { 19 | width: 100%; 20 | font-size: 120%; 21 | } 22 | 23 | .rst-content .highlight > pre { 24 | line-height: 1.5; 25 | } 26 | 27 | .dropdown-icon { 28 | float: right; 29 | } 30 | 31 | /* Work around WL code parser bug in Sphinx, which marks valid code as erroneous */ 32 | .highlight .err { 33 | color: inherit; 34 | background-color: inherit; 35 | } 36 | 37 | kbd > kbd { 38 | background-color: #eee; 39 | border-radius: 3px; 40 | border: 1px solid #b4b4b4; 41 | box-shadow: 0 1px 1px rgba(0, 0, 0, .2), 0 2px 0 0 rgba(255, 255, 255, .7) inset; 42 | font-size: .85em; 43 | font-weight: 700; 44 | line-height: 1; 45 | padding: 2px 4px; 46 | white-space: nowrap; 47 | } -------------------------------------------------------------------------------- /docs/_static/img/LLULogo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/LLULogo.png -------------------------------------------------------------------------------- /docs/_static/img/LLULogo_144.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/LLULogo_144.png -------------------------------------------------------------------------------- /docs/_static/img/LibFunNoProg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/LibFunNoProg.gif -------------------------------------------------------------------------------- /docs/_static/img/LibFunWithProg.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/LibFunWithProg.gif -------------------------------------------------------------------------------- /docs/_static/img/Logger1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/Logger1.png -------------------------------------------------------------------------------- /docs/_static/img/Logger2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/Logger2.png -------------------------------------------------------------------------------- /docs/_static/img/Logger3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/Logger3.png -------------------------------------------------------------------------------- /docs/_static/img/favicon-32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/_static/img/favicon-32.png -------------------------------------------------------------------------------- /docs/_static/js/custom.js: -------------------------------------------------------------------------------- 1 | $(function () { 2 | const iconDown = "▼"; 3 | const iconUp = "▲"; 4 | 5 | $(".cpp .function > dd > dl").hide(); 6 | 7 | $(".cpp.function > dt").each(function(index) { 8 | if($(this).next("dd").has("dl").length > 0) { 9 | $(this).children("br:last-of-type").before("" + iconDown + ""); 10 | } 11 | }); 12 | 13 | $(".cpp .function").click((event) => { 14 | $(event.currentTarget).children("dd").children("dl").toggle(); 15 | $(event.currentTarget).find(".dropdown-icon").toggleClass(["icon-down", "icon-up"]); 16 | $(event.currentTarget).find(".dropdown-icon").text(function(i, t) { 17 | return t === iconDown ? iconUp : iconDown; 18 | }); 19 | }); 20 | }); -------------------------------------------------------------------------------- /docs/docutils.conf: -------------------------------------------------------------------------------- 1 | [restructuredtext parser] 2 | tab_width: 4 -------------------------------------------------------------------------------- /docs/globals.rst: -------------------------------------------------------------------------------- 1 | .. default-role:: code 2 | 3 | .. By default, code blocks are php 4 | 5 | .. highlight:: cpp 6 | 7 | .. role:: wl(code) 8 | :language: wolfram-language 9 | :class: highlight 10 | 11 | .. role:: wldef 12 | :class: wl-symbol 13 | 14 | .. role:: raw-html(raw) 15 | :format: html -------------------------------------------------------------------------------- /docs/modules/logger.rst: -------------------------------------------------------------------------------- 1 | =========================================== 2 | Logging and debug printing 3 | =========================================== 4 | 5 | In the process of developing a C++ paclet it is often useful to print some information from the C++ code, either for debugging purpose or in the form of a log. 6 | The most common way of achieving this in C++ is simple printing to the console (e.g. with ``std::cout`` or ``std::cerr``) but this approach has noticeable 7 | drawbacks: 8 | 9 | - requires running Wolfram Language from the command line to be able to see printed messages from the paclet library (and even then may not work on Windows) 10 | - print statements must be surrounded with ``#ifdef``\ s, disabled in some other way, or simply removed from the codebase before making it production-ready 11 | 12 | One alternative is to log messages to a text file, but this approach has its own problems like choosing the right location for the file, taking care of closing 13 | the file, etc. 14 | 15 | LLU provides a logging and debug printing mechanism integrated with the Wolfram Language which: 16 | 17 | - is cross platform 18 | - has zero overhead when logging is disabled (so the print statements can stay in the code even in the Release mode) 19 | - is flexible and easy to customize 20 | 21 | It is achieved by directing all the message data via WSTP to the Kernel and processing/formatting each message there according to the developer's settings. 22 | To be able to use the logging facility from LLU, first: 23 | 24 | .. code-block:: cpp 25 | 26 | #include 27 | 28 | and then simply use one of the 3 predefined macros: 29 | 30 | .. doxygendefine:: LLU_DEBUG 31 | 32 | .. doxygendefine:: LLU_WARNING 33 | 34 | .. doxygendefine:: LLU_ERROR 35 | 36 | Each macro takes an arbitrary number of parameters of any WSTP-supported types (see :doc:`wstp` for details). Additionally, each log will be automatically 37 | equipped with file name, line number and function name, all related to the place were the log message was issued. 38 | 39 | By default the macros are disabled and the compiler will not generate any extra machine code (so the 0 overhead condition is fulfilled). 40 | To enable all macros compile your paclet with ``-D``, where ```` is one of the following: 41 | 42 | .. doxygendefine:: LLU_LOG_DEBUG 43 | 44 | .. doxygendefine:: LLU_LOG_WARNING 45 | 46 | .. doxygendefine:: LLU_LOG_ERROR 47 | 48 | For example, consider a simple library function that takes a number of integers and the first of them is the index of the argument that will be returned: 49 | 50 | .. code-block:: cpp 51 | :linenos: 52 | :emphasize-lines: 2, 8, 14 53 | 54 | EXTERN_C DLLEXPORT int LogDemo(WolframLibraryData libData, mint argc, MArgument* args, MArgument res) { 55 | LLU_DEBUG("Library function entered with ", argc, " arguments."); 56 | auto err = LLErrorCode::NoError; 57 | try { 58 | MArgumentManager mngr(libData, argc, args, res); 59 | auto index = mngr.getInteger(0); 60 | if (index >= argc) { 61 | LLU_WARNING("Index ", index, " is too big for the number of arguments: ", argc, ". Changing to ", argc - 1); 62 | index = argc - 1; 63 | } 64 | auto value = mngr.getInteger(index); 65 | mngr.setInteger(value); 66 | } catch (const LibraryLinkError& e) { 67 | LLU_ERROR("Caught LLU exception ", e.what(), ": ", e.debug()); 68 | err = e.which(); 69 | } 70 | return err; 71 | } 72 | 73 | Compile with ``-DLLU_LOG_DEBUG``, load in the notebook and try: 74 | 75 | .. image:: ../_static/img/Logger1.png 76 | :alt: Basic example of LLU Logger output in a notebook. 77 | 78 | A less verbose option is overriding the ```LLU`Logger`FormattedLog``` symbol: 79 | 80 | .. image:: ../_static/img/Logger2.png 81 | :alt: More concise LLU Logger output in a notebook. 82 | 83 | Or drop formatting and accumulate logs as Strings in a List: 84 | 85 | .. image:: ../_static/img/Logger3.png 86 | :alt: Logging to a symbol. 87 | 88 | Other features include: 89 | 90 | - Easy modification of log styling 91 | - Formatting logs as :wlref:`Association` or :wlref:`List` 92 | - Printing logs to Messages window 93 | - Filtering messages by file name, function name, log severity 94 | - Blocking all logs in top-level (so you don't have to rebuild your paclet to temporarily disable logging, 95 | but the logs will still be sent via WSTP to top-level, only immediately discarded) 96 | 97 | -------------------------------------------------------------------------------- /docs/other/paclet_use.rst: -------------------------------------------------------------------------------- 1 | ==================================== 2 | Use in paclets 3 | ==================================== 4 | 5 | Submodule 6 | ============================= 7 | 8 | In :doc:`../basic/how_to_use` you can learn how to build and install :term:`LLU` on your system and then use it as a dependent library for your Wolfram Language 9 | paclets. There exists an alternative approach which is often quite convenient if you plan to work on multiple paclets that may use different versions of LLU. 10 | This approach is to include LLU in a project as a git submodule. Submodules are simply git repos inside other repos but working with them may sometimes be 11 | tricky. See this excellent `tutorial on submodules `_. 12 | 13 | For SourceTree users there is also a helpful `blog post `_. 14 | 15 | In most cases you will access LibraryLink Utilities in "read-only" manner, i.e. you will just update the submodule to make sure you use the most recent version. 16 | 17 | Here is a list of commands that might be useful to developers who want to use LLU as a submodule. For the sake of example, assume that we have a sample paclet 18 | with a directory :file:`CPPSource/` containing all the C++ source code and that we want to place the submodule in this directory. 19 | It is easy to modify these commands so that they work for other locations too. 20 | 21 | * Adding LibraryLink Utilities to your paclet 22 | 23 | .. code-block:: console 24 | 25 | git submodule add **TODO: Insert LLU clone link** CPPSource/LibraryLinkUtilities 26 | 27 | * Cloning a project that already uses LibraryLink Utilities 28 | 29 | .. code-block:: console 30 | 31 | git clone --recursive 32 | 33 | * Updating LibraryLink Utilities in your project 34 | 35 | .. code-block:: console 36 | 37 | git submodule update --remote CPPSource/LibraryLinkUtilities/ 38 | 39 | Submodules work in a "detached head" state which means they stick to a chosen commit, so even if there are backwards incompatible changes merged to LLU master 40 | your project will not be affected unless you manually update the submodule. 41 | 42 | With LLU attached to your project in a submodule you always have the sources so you only need to follow steps 2 - 4 described 43 | :doc:`in the official build instructions<../basic/how_to_use>`. 44 | 45 | Paclets that use LLU 46 | ========================================================== 47 | 48 | If you look for examples of LLU usage and whatever is in this documentation and in the Demo project was not enough, you can take a look at paclets built into 49 | the Wolfram Language that use LLU. You will not be able to see the C++ source code or the CMake build script but you can still investigate the paclet structure 50 | and the Wolfram Language code. Paclets can be found in the :file:`SystemFiles/Links` subdirectory of your installation. 51 | 52 | The list below may not be complete. 53 | 54 | - ArchiveTools 55 | - DICOMTools 56 | - FFmpegTools 57 | - SVTools 58 | -------------------------------------------------------------------------------- /docs/tutorial/DataStore.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/tutorial/DataStore.png -------------------------------------------------------------------------------- /docs/tutorial/WSTPFunction.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/WolframResearch/LibraryLinkUtilities/73b142add466f51e7481bf58a0278f232f75478a/docs/tutorial/WSTPFunction.png -------------------------------------------------------------------------------- /include/LLU/Async/Queue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @author Rafal Chojna 4 | * @brief Definition and implementation of a thread-safe queue, taken from A. Williams "C++ Concurrency in Action" 2nd Edition, chapter 6. 5 | */ 6 | #ifndef LLU_ASYNC_QUEUE_H 7 | #define LLU_ASYNC_QUEUE_H 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | namespace LLU::Async { 14 | /** 15 | * @brief ThreadsafeQueue is a linked list of nodes which supports safe concurrent access to its head (removing elements) and tail (adding new elements). 16 | * ThreadsafeQueue is described in chapter 6 of A. Williams "C++ Concurrency in Action" 2nd Edition. 17 | * This implementation contains only slight modifications and minor bugfixes. 18 | * @tparam T - type of the data stored in the Queue 19 | */ 20 | template 21 | class ThreadsafeQueue { 22 | public: 23 | /// Value type of queue elements 24 | using value_type = T; 25 | public: 26 | /** 27 | * @brief Create new empty queue. 28 | */ 29 | ThreadsafeQueue() : head(new Node), tail(head.get()) {} 30 | 31 | /** 32 | * @brief Get data from the queue if available. 33 | * If data is not available in the queue, the calling thread will not wait. 34 | * @return Shared pointer to the data from the queue's head, nullptr if there is no data to be popped. 35 | */ 36 | std::shared_ptr tryPop(); 37 | 38 | /** 39 | * @brief Get data from the queue if available. 40 | * If data is not available in the queue, the calling thread will not wait. 41 | * @param[out] value - reference to the data from the queue 42 | * @return True iff there was data in the queue, otherwise the out-parameter remains unchanged. 43 | */ 44 | bool tryPop(value_type& value); 45 | 46 | /** 47 | * @brief Get data from the queue, possibly waiting for it. 48 | * @return Shared pointer to the data from the queue's head 49 | */ 50 | std::shared_ptr waitPop(); 51 | 52 | /** 53 | * @brief Get data from the queue, possibly waiting for it. 54 | * @param value - reference to the data from the queue 55 | */ 56 | void waitPop(value_type& value); 57 | 58 | /** 59 | * @brief Push new value to the end of the queue. 60 | * This operation can be performed even with other thread popping a value from the queue at the same time. 61 | * @param new_value - value to be pushed to the queue 62 | */ 63 | void push(value_type new_value); 64 | 65 | /** 66 | * @brief Check if the queue is empty. 67 | * @return True iff the queue is empty i.e. has no data to be popped. 68 | */ 69 | [[nodiscard]] bool empty() const; 70 | 71 | private: 72 | /// Internal structure that represents a single element of the queue 73 | struct Node { 74 | std::shared_ptr data; 75 | std::unique_ptr next; 76 | }; 77 | 78 | mutable std::mutex head_mutex; 79 | std::unique_ptr head; 80 | mutable std::mutex tail_mutex; 81 | Node* tail; 82 | std::condition_variable data_cond; 83 | 84 | const Node* getTail() const { 85 | std::lock_guard tail_lock(tail_mutex); 86 | return tail; 87 | } 88 | 89 | std::unique_ptr popHead() { 90 | std::unique_ptr old_head = std::move(head); 91 | head = std::move(old_head->next); 92 | return old_head; 93 | } 94 | 95 | std::unique_lock waitForData() { 96 | std::unique_lock head_lock(head_mutex); 97 | data_cond.wait(head_lock, [&] { return head.get() != getTail(); }); 98 | return head_lock; 99 | } 100 | 101 | std::unique_ptr waitPopHead() { 102 | std::unique_lock head_lock(waitForData()); 103 | return popHead(); 104 | } 105 | 106 | std::unique_ptr waitPopHead(value_type& value) { 107 | std::unique_lock head_lock(waitForData()); 108 | value = std::move(*head->data); 109 | return popHead(); 110 | } 111 | 112 | std::unique_ptr tryPopHead() { 113 | std::lock_guard head_lock(head_mutex); 114 | if (head.get() == getTail()) { 115 | return std::unique_ptr(); 116 | } 117 | return popHead(); 118 | } 119 | 120 | std::unique_ptr tryPopHead(value_type& value) { 121 | std::lock_guard head_lock(head_mutex); 122 | if (head.get() == getTail()) { 123 | return std::unique_ptr(); 124 | } 125 | value = std::move(*head->data); 126 | return popHead(); 127 | } 128 | }; 129 | 130 | template 131 | void ThreadsafeQueue::push(T new_value) { 132 | std::shared_ptr new_data(std::make_shared(std::move(new_value))); 133 | std::unique_ptr p(new Node); 134 | { 135 | std::lock_guard tail_lock(tail_mutex); 136 | tail->data = new_data; 137 | Node* const new_tail = p.get(); 138 | tail->next = std::move(p); 139 | tail = new_tail; 140 | } 141 | data_cond.notify_one(); 142 | } 143 | 144 | template 145 | std::shared_ptr ThreadsafeQueue::waitPop() { 146 | std::unique_ptr const old_head = waitPopHead(); 147 | return old_head->data; 148 | } 149 | 150 | template 151 | void ThreadsafeQueue::waitPop(T& value) { 152 | std::unique_ptr const old_head = waitPopHead(value); 153 | } 154 | 155 | template 156 | std::shared_ptr ThreadsafeQueue::tryPop() { 157 | std::unique_ptr const old_head = tryPopHead(); 158 | return old_head ? old_head->data : std::shared_ptr(); 159 | } 160 | 161 | template 162 | bool ThreadsafeQueue::tryPop(T& value) { 163 | std::unique_ptr const old_head = tryPopHead(value); 164 | return static_cast(old_head); 165 | } 166 | 167 | template 168 | bool ThreadsafeQueue::empty() const { 169 | std::lock_guard head_lock(head_mutex); 170 | return (head.get() == getTail()); 171 | } 172 | } // namespace LLU::Async 173 | #endif // LLU_ASYNC_QUEUE_H 174 | -------------------------------------------------------------------------------- /include/LLU/Async/WorkStealingQueue.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file WorkStealingQueue.h 3 | * @author Rafal Chojna 4 | * @brief Definition and implementation of a thread-safe queue, taken from A. Williams "C++ Concurrency in Action" 2nd Edition, chapter 6. 5 | */ 6 | #ifndef LLU_ASYNC_WORKSTEALINGQUEUE_H 7 | #define LLU_ASYNC_WORKSTEALINGQUEUE_H 8 | 9 | #include 10 | 11 | namespace LLU::Async { 12 | 13 | /** 14 | * @brief Wrapper class around a queue, that provides the interface for work stealing. 15 | * Work stealing is when one thread pops a task from another thread's queue. In WorkStealingQueue tasks are popped from the front 16 | * and stolen from the back. 17 | * @tparam BaseQueue - any class implementing a queue with push_front, push_back, pop_front, pop_back, empty, front and back methods. 18 | */ 19 | template 20 | class WorkStealingQueue { 21 | using DataType = typename BaseQueue::value_type; 22 | 23 | BaseQueue theQueue; 24 | mutable std::mutex theMutex; 25 | 26 | public: 27 | /** 28 | * Push new element to the beginning of the queue 29 | * @param data - new element 30 | */ 31 | void push(DataType data) { 32 | std::lock_guard lock(theMutex); 33 | theQueue.push_front(std::move(data)); 34 | } 35 | 36 | /** 37 | * Check if the queue is empty 38 | * @return true iff the queue is empty 39 | */ 40 | [[nodiscard]] bool empty() const { 41 | std::lock_guard lock(theMutex); 42 | return theQueue.empty(); 43 | } 44 | 45 | /** 46 | * Try to pop a task from the beginning of the queue in a non-blocking way 47 | * @param[out] res - reference to which the new task should be assigned 48 | * @return true iff the queue was not empty and a task was popped 49 | */ 50 | [[nodiscard]] bool tryPop(DataType& res) { 51 | std::lock_guard lock(theMutex); 52 | if (theQueue.empty()) { 53 | return false; 54 | } 55 | res = std::move(theQueue.front()); 56 | theQueue.pop_front(); 57 | return true; 58 | } 59 | 60 | /** 61 | * Try to pop a task from the end of the queue (this is what we call "stealing") in a non-blocking way 62 | * @param[out] res - reference to which the new task should be assigned 63 | * @return true iff the queue was not empty and a task was popped 64 | */ 65 | [[nodiscard]] bool trySteal(DataType& res) { 66 | std::lock_guard lock(theMutex); 67 | if (theQueue.empty()) { 68 | return false; 69 | } 70 | res = std::move(theQueue.back()); 71 | theQueue.pop_back(); 72 | return true; 73 | } 74 | }; 75 | } // namespace LLU::Async 76 | 77 | #endif // LLU_ASYNC_WORKSTEALINGQUEUE_H 78 | -------------------------------------------------------------------------------- /include/LLU/Containers/Generic/NumericArray.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief GenericNumericArray definition and implementation 4 | */ 5 | #ifndef LLU_CONTAINERS_GENERIC_NUMERICARRAY_HPP 6 | #define LLU_CONTAINERS_GENERIC_NUMERICARRAY_HPP 7 | 8 | #include "LLU/Containers/Generic/Base.hpp" 9 | #include "LLU/Containers/Interfaces.h" 10 | 11 | namespace LLU { 12 | 13 | template<> 14 | class MContainer; 15 | 16 | /// MContainer specialization for MNumericArray is called GenericNumericArray 17 | using GenericNumericArray = MContainer; 18 | 19 | /** 20 | * @brief MContainer specialization for MNumericArray 21 | */ 22 | template<> 23 | class MContainer : public NumericArrayInterface, public MContainerBase { 24 | public: 25 | /// Inherit constructors from MContainerBase 26 | using MContainerBase::MContainerBase; 27 | 28 | /** 29 | * @brief Default constructor, the MContainer does not manage any instance of MNumericArray. 30 | */ 31 | MContainer() = default; 32 | 33 | /** 34 | * @brief Create GenericNumericArray of given type and shape 35 | * @param type - new GenericNumericArray type 36 | * @param rank - new GenericNumericArray rank 37 | * @param dims - new GenericNumericArray dimensions 38 | * @see 39 | */ 40 | MContainer(numericarray_data_t type, mint rank, const mint* dims); 41 | 42 | /** 43 | * @brief Convert this object to a new GenericNumericArray of given datatype, using specified conversion method 44 | * @param t - destination data type 45 | * @param method - conversion method 46 | * @param param - conversion method parameter (aka tolerance) 47 | * @return converted GenericNumericArray owned by the Library 48 | * @see 49 | */ 50 | GenericNumericArray convert(numericarray_data_t t, NA::ConversionMethod method, double param) const; 51 | 52 | /** 53 | * @brief Clone this MContainer, performs a deep copy of the underlying MNumericArray. 54 | * @note The cloned MContainer always belongs to the library (Ownership::Library) because LibraryLink has no idea of its existence. 55 | * @return new MContainer, by value 56 | */ 57 | MContainer clone() const { 58 | return MContainer {cloneContainer(), Ownership::Library}; 59 | } 60 | 61 | /// @copydoc NumericArrayInterface::getRank() 62 | mint getRank() const override { 63 | return LibraryData::NumericArrayAPI()->MNumericArray_getRank(this->getContainer()); 64 | } 65 | 66 | /// @copydoc NumericArrayInterface::getDimensions() 67 | mint const* getDimensions() const override { 68 | return LibraryData::NumericArrayAPI()->MNumericArray_getDimensions(this->getContainer()); 69 | } 70 | 71 | /// @copydoc NumericArrayInterface::getFlattenedLength() 72 | mint getFlattenedLength() const override { 73 | return LibraryData::NumericArrayAPI()->MNumericArray_getFlattenedLength(this->getContainer()); 74 | } 75 | 76 | /// @copydoc NumericArrayInterface::type() 77 | numericarray_data_t type() const override { 78 | return LibraryData::NumericArrayAPI()->MNumericArray_getType(this->getContainer()); 79 | } 80 | 81 | /// @copydoc NumericArrayInterface::rawData() 82 | void* rawData() const noexcept override { 83 | return LibraryData::NumericArrayAPI()->MNumericArray_getData(this->getContainer()); 84 | } 85 | 86 | private: 87 | 88 | /** 89 | * @brief Make a deep copy of the raw container 90 | * @see 91 | **/ 92 | Container cloneImpl() const override; 93 | 94 | /** 95 | * @copydoc MContainer::shareCount() 96 | * @see 97 | */ 98 | mint shareCountImpl() const noexcept override { 99 | return LibraryData::NumericArrayAPI()->MNumericArray_shareCount(this->getContainer()); 100 | } 101 | 102 | ///@copydoc MContainer::pass 103 | void passImpl(MArgument& res) const noexcept override { 104 | MArgument_setMNumericArray(res, this->getContainer()); 105 | } 106 | }; 107 | 108 | } // namespace LLU 109 | 110 | #endif // LLU_CONTAINERS_GENERIC_NUMERICARRAY_HPP 111 | -------------------------------------------------------------------------------- /include/LLU/Containers/Generic/Tensor.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @brief GenericTensor definition and implementation 4 | */ 5 | 6 | #ifndef LLU_CONTAINERS_GENERIC_TENSOR_HPP 7 | #define LLU_CONTAINERS_GENERIC_TENSOR_HPP 8 | 9 | #include "LLU/Containers/Generic/Base.hpp" 10 | #include "LLU/Containers/Interfaces.h" 11 | 12 | namespace LLU { 13 | 14 | template<> 15 | class MContainer; 16 | 17 | /// MContainer specialization for MTensor is called GenericTensor 18 | using GenericTensor = MContainer; 19 | 20 | /** 21 | * @brief MContainer specialization for MTensor 22 | */ 23 | template<> 24 | class MContainer : public TensorInterface, public MContainerBase { 25 | public: 26 | /// Inherit constructors from MContainerBase 27 | using MContainerBase::MContainerBase; 28 | 29 | /// Default constructor, the MContainer does not manage any instance of MTensor. 30 | MContainer() = default; 31 | 32 | /** 33 | * @brief Create GenericTensor of given type and shape 34 | * @param type - new GenericTensor type (MType_Integer, MType_Real or MType_Complex) 35 | * @param rank - new GenericTensor rank 36 | * @param dims - new GenericTensor dimensions 37 | * @see 38 | */ 39 | MContainer(mint type, mint rank, const mint* dims); 40 | 41 | /** 42 | * @brief Clone this MContainer, performs a deep copy of the underlying MTensor. 43 | * @note The cloned MContainer always belongs to the library (Ownership::Library) because LibraryLink has no idea of its existence. 44 | * @return new MContainer, by value 45 | */ 46 | MContainer clone() const { 47 | return MContainer {cloneContainer(), Ownership::Library}; 48 | } 49 | 50 | /// @copydoc TensorInterface::getRank() 51 | mint getRank() const override { 52 | return LibraryData::API()->MTensor_getRank(this->getContainer()); 53 | } 54 | 55 | /// @copydoc TensorInterface::getDimensions() 56 | mint const* getDimensions() const override { 57 | return LibraryData::API()->MTensor_getDimensions(this->getContainer()); 58 | } 59 | 60 | /// @copydoc TensorInterface::getFlattenedLength() 61 | mint getFlattenedLength() const override { 62 | return LibraryData::API()->MTensor_getFlattenedLength(this->getContainer()); 63 | } 64 | 65 | /// @copydoc TensorInterface::type() 66 | mint type() const override { 67 | return LibraryData::API()->MTensor_getType(this->getContainer()); 68 | } 69 | 70 | /// @copydoc TensorInterface::rawData() 71 | void* rawData() const override; 72 | 73 | private: 74 | 75 | /** 76 | * @copydoc MContainer::shareCount() 77 | * @see 78 | */ 79 | mint shareCountImpl() const noexcept override { 80 | return LibraryData::API()->MTensor_shareCount(this->getContainer()); 81 | } 82 | 83 | /// @copydoc MContainer::pass 84 | void passImpl(MArgument& res) const noexcept override { 85 | MArgument_setMTensor(res, this->getContainer()); 86 | } 87 | 88 | /** 89 | * @brief Make a deep copy of the raw container 90 | * @see 91 | **/ 92 | Container cloneImpl() const override; 93 | }; 94 | 95 | } // namespace LLU 96 | 97 | #endif // LLU_CONTAINERS_GENERIC_TENSOR_HPP 98 | -------------------------------------------------------------------------------- /include/LLU/Containers/Iterators/DataNode.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file DataNode.hpp 3 | * @author Rafal Chojna 4 | * @date May 06, 2020 5 | * @brief 6 | */ 7 | #ifndef LLU_CONTAINERS_ITERATORS_DATANODE_HPP 8 | #define LLU_CONTAINERS_ITERATORS_DATANODE_HPP 9 | 10 | #include 11 | 12 | #include "LLU/Containers/Generic/DataStore.hpp" 13 | #include "LLU/TypedMArgument.h" 14 | 15 | namespace LLU { 16 | 17 | /** 18 | * @class DataNode 19 | * @brief Wrapper over DataStoreNode structure from LibraryLink. 20 | */ 21 | template 22 | class DataNode { 23 | static constexpr bool isGeneric = std::is_same_v; 24 | static_assert(Argument::WrapperQ, "DataNode type is not a valid MArgument wrapper type."); 25 | 26 | public: 27 | /** 28 | * @brief Create DataNode from raw DataStoreNode structure 29 | * @param dsn - raw node 30 | */ 31 | explicit DataNode(DataStoreNode dsn); 32 | 33 | /** 34 | * @brief Create DataNode from raw GenericDataNode 35 | * @param gn - generic data node 36 | */ 37 | explicit DataNode(GenericDataNode gn); 38 | 39 | /** 40 | * @brief Get node value 41 | * @return Returns a reference to node value 42 | */ 43 | T& value() { 44 | return nodeArg; 45 | } 46 | 47 | /** 48 | * @brief Get node value 49 | * @return Returns a reference to node value 50 | */ 51 | const T& value() const { 52 | return nodeArg; 53 | } 54 | 55 | /** 56 | * @brief Get node name 57 | * @return string_view to the node name 58 | * @note If you store the result of this function make sure it does not outlive the underlying DataStore node, otherwise make a string copy 59 | */ 60 | std::string_view name() const { 61 | return node.name(); 62 | } 63 | 64 | /** 65 | * @brief Check if this node has a successor 66 | * @return true iff the current node is not the last one in its DataList 67 | */ 68 | bool hasNext() const { 69 | return static_cast(node.next()); 70 | } 71 | 72 | /** 73 | * @brief Get next node as GenericDataNode (because the next node may not necessarily have value of type T) 74 | * @return GenericDataNode wrapper of next node, or empty if this is the last node 75 | */ 76 | GenericDataNode next() const { 77 | return node.next(); 78 | } 79 | 80 | /** 81 | * @brief Get the actual type of node value. This is useful when working on a "generic" DataList. 82 | * @return Actual type of node value 83 | */ 84 | MArgumentType type() noexcept { 85 | return node.type(); 86 | } 87 | 88 | /** 89 | * Get N-th element of DataNode in a tuple-like way. This function enables structured bindings to DataNodes. 90 | * @tparam N - index (only 0 and 1 are valid) 91 | * @return either the node name for N == 0 or node value for N == 1 92 | */ 93 | template 94 | decltype(auto) get() { 95 | static_assert(N < 2, "Bad structure binding attempt to a DataNode."); 96 | if constexpr (N == 0) { 97 | return name(); 98 | } else { 99 | return (nodeArg); 100 | } 101 | } 102 | 103 | private: 104 | GenericDataNode node {}; 105 | T nodeArg; 106 | }; 107 | 108 | /* Definitions od DataNode methods */ 109 | template 110 | DataNode::DataNode(DataStoreNode dsn) : DataNode(GenericDataNode {dsn}) {} 111 | 112 | template 113 | DataNode::DataNode(GenericDataNode gn) : node {gn} { 114 | if (!node) { 115 | ErrorManager::throwException(ErrorName::DLNullRawNode); 116 | } 117 | if constexpr (isGeneric) { 118 | nodeArg = std::move(node.value()); 119 | } else{ 120 | nodeArg = std::move(node.as()); 121 | } 122 | } 123 | 124 | 125 | }/* namespace LLU */ 126 | 127 | namespace std { 128 | template 129 | class tuple_size> : public std::integral_constant {}; 130 | 131 | template 132 | class tuple_element> { 133 | public: 134 | using type = decltype(std::declval>().template get()); 135 | }; 136 | } 137 | 138 | #endif // LLU_CONTAINERS_ITERATORS_DATANODE_HPP 139 | -------------------------------------------------------------------------------- /include/LLU/Containers/Iterators/DataStore.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file DataStore.hpp 3 | * @author Rafal Chojna 4 | * @date May 04, 2020 5 | * @brief 6 | */ 7 | #ifndef LLU_CONTAINERS_ITERATORS_DATASTORE_HPP 8 | #define LLU_CONTAINERS_ITERATORS_DATASTORE_HPP 9 | 10 | #include 11 | 12 | #include "LLU/LibraryData.h" 13 | #include "LLU/MArgument.h" 14 | #include "LLU/TypedMArgument.h" 15 | 16 | namespace LLU { 17 | 18 | /** 19 | * @struct GenericDataNode 20 | * @brief Basic wrapper over DataStoreNode, provides class-like interface and conversion of the underlying value from MArgument to TypedArgument. 21 | */ 22 | struct GenericDataNode { 23 | /// Raw DataStore node 24 | DataStoreNode node; 25 | 26 | /** 27 | * Get GenericDataNode wrapper over the next node 28 | * @return next node wrapped in GenericDataNode 29 | */ 30 | GenericDataNode next() const noexcept; 31 | 32 | /** 33 | * Get type of the node value 34 | * @return type of the node value 35 | */ 36 | MArgumentType type() const noexcept; 37 | 38 | /** 39 | * Get node name 40 | * @return string view over the name of the node 41 | */ 42 | std::string_view name() const noexcept; 43 | 44 | /** 45 | * Get value of the node as the variant type 46 | * @return TypedArgument variant holding the value of the node 47 | */ 48 | [[nodiscard]] Argument::TypedArgument value() const; 49 | 50 | // defined in Containers/Generic/DataStore.hpp because the definition of GenericDataList must be available 51 | /** 52 | * Get node value if it is of type T, otherwise throw an exception. 53 | * @tparam T - any type from LLU::NodeType namespace 54 | * @return node value of type T 55 | */ 56 | template 57 | T as() const; 58 | 59 | /** 60 | * Bool conversion operator 61 | * @return true iff the node is not null 62 | */ 63 | explicit operator bool() const; 64 | 65 | /// Member of pointer operator, used by DataList iterators 66 | GenericDataNode* operator->() { 67 | return this; 68 | } 69 | }; 70 | 71 | /** 72 | * @class DataStoreIterator 73 | * @brief Proxy input iterator over DataStoreNodes, when dereferenced yields GenericDataNode proxy objects. 74 | */ 75 | class DataStoreIterator { 76 | DataStoreNode node; 77 | 78 | public: 79 | /// This iterator returns proxy objects of type GenericDataNode 80 | using value_type = GenericDataNode; 81 | 82 | /// DataStoreIterator is a proxy iterator and so the reference type is the same as value_type 83 | using reference = value_type; 84 | 85 | /// As with all proxy iterators, DataStoreIterator is only an input iterator 86 | using iterator_category = std::input_iterator_tag; 87 | 88 | /// DataStoreIterator is a proxy iterator and so the pointer type is the same as value_type 89 | using pointer = value_type; 90 | 91 | /// Provide difference_type as required for input iterators 92 | using difference_type = mint; 93 | 94 | /// Create a DataStoreIterator pointing to a given node 95 | explicit DataStoreIterator(DataStoreNode n) : node{n} {} 96 | 97 | /** 98 | * Get proxy object of the current node 99 | * @return proxy object of current node 100 | */ 101 | reference operator*() const { 102 | return reference {node}; 103 | } 104 | 105 | /** 106 | * Get proxy object of the current node 107 | * @return proxy object of current node 108 | */ 109 | pointer operator->() const { 110 | return pointer {node}; 111 | } 112 | 113 | /** 114 | * Pre-increment operator 115 | * @return this 116 | */ 117 | DataStoreIterator& operator++() { 118 | node = LLU::LibraryData::DataStoreAPI()->DataStoreNode_getNextNode(node); 119 | return *this; 120 | } 121 | 122 | /** 123 | * Post-increment operator 124 | * @return "old" copy of the iterator object 125 | */ 126 | DataStoreIterator operator++(int) { 127 | DataStoreIterator tmp {node}; 128 | ++(*this); 129 | return tmp; 130 | } 131 | 132 | /** 133 | * "Equal to" operator for DataStoreIterators 134 | * @param lhs - a DataStoreIterator 135 | * @param rhs - a DataStoreIterator 136 | * @return true iff both iterators point to the same node 137 | */ 138 | friend bool operator==(const DataStoreIterator& lhs, const DataStoreIterator& rhs) { 139 | return lhs.node == rhs.node; 140 | } 141 | 142 | /** 143 | * "Not equal to" operator for DataStoreIterators 144 | * @param lhs - a DataStoreIterator 145 | * @param rhs - a DataStoreIterator 146 | * @return false iff both iterators point to the same node 147 | */ 148 | friend bool operator!=(const DataStoreIterator& lhs, const DataStoreIterator& rhs) { 149 | return !(lhs == rhs); 150 | } 151 | }; 152 | } // namespace LLU 153 | 154 | #endif // LLU_CONTAINERS_ITERATORS_DATASTORE_HPP 155 | -------------------------------------------------------------------------------- /include/LLU/Containers/MArray.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MArray.hpp 3 | * @author Rafal Chojna 4 | * @date 6/07/2017 5 | * 6 | * @brief Template base class for C++ wrappers of LibraryLink containers 7 | * 8 | */ 9 | #ifndef LLU_CONTAINERS_MARRAY_HPP_ 10 | #define LLU_CONTAINERS_MARRAY_HPP_ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #include "LLU/Containers/Iterators/IterableContainer.hpp" 19 | #include "LLU/Containers/MArrayDimensions.h" 20 | #include "LLU/LibraryData.h" 21 | #include "LLU/Utilities.hpp" 22 | 23 | namespace LLU { 24 | 25 | /** 26 | * @class MArray 27 | * @brief This is a class template, where template parameter T is the type of data elements. MArray is the base class for NumericArray, Tensor and Image. 28 | * 29 | * Each MArray is an abstract class, it provides common interface to NumericArrays, Tensors and Images. One of the biggest benefits is that this 30 | * interface contains iterators over underlying data together with begin() and end() member functions which makes it possible to use containers derived from 31 | * MArray directly in many functions from standard library \. 32 | * 33 | * @tparam T - type of underlying data 34 | */ 35 | template 36 | class MArray : public IterableContainer { 37 | template 38 | friend class MArray; 39 | 40 | public: 41 | MArray() = default; 42 | 43 | /** 44 | * @brief Create new MArray given the dimensions object 45 | * @param d - dimensions for the new MArray 46 | */ 47 | explicit MArray(MArrayDimensions d) : dims(std::move(d)) {} 48 | 49 | /** 50 | * @brief Converts given MArray of type U into MArray of type T 51 | * @param[in] other - MArray of any type 52 | * @tparam U - any type convertible to T 53 | **/ 54 | template 55 | explicit MArray(const MArray& other) : dims(other.dims) {} 56 | 57 | /** 58 | * @brief Get container rank 59 | **/ 60 | mint rank() const noexcept { 61 | return dims.rank(); 62 | } 63 | 64 | /** 65 | * @brief Check whether container is empty 66 | **/ 67 | [[nodiscard]] bool empty() const noexcept { 68 | return dims.flatCount() == 0; 69 | } 70 | 71 | /** 72 | * @brief Get dimension value at position \p index 73 | */ 74 | mint dimension(mint index) const { 75 | return dims.get(index); 76 | } 77 | 78 | /** 79 | * @brief Get a const reference to dimensions object 80 | */ 81 | const MArrayDimensions& dimensions() const { 82 | return dims; 83 | } 84 | 85 | using IterableContainer::operator[]; 86 | 87 | /** 88 | * @brief Get a reference to the data element at given position in a multidimensional container 89 | * @param[in] indices - vector with coordinates of desired data element 90 | **/ 91 | T& operator[](const std::vector& indices) { 92 | return (*this)[dims.getIndex(indices)]; 93 | } 94 | 95 | /** 96 | * @brief Get a constant reference to the data element at given position in a multidimensional container 97 | * @param[in] indices - vector with coordinates of desired data element 98 | **/ 99 | const T& operator[](const std::vector& indices) const { 100 | return (*this)[dims.getIndex(indices)]; 101 | } 102 | 103 | /** 104 | * @brief Get a reference to the data element at given position with bound checking 105 | * @param[in] index - position of desired data element 106 | * @throws indexError() - if \c index is out-of-bounds 107 | **/ 108 | T& at(mint index); 109 | 110 | /** 111 | * @brief Get a constant reference to the data element at given position with bound checking 112 | * @param[in] index - position of desired data element 113 | * @throws indexError() - if \c index is out-of-bounds 114 | **/ 115 | const T& at(mint index) const; 116 | 117 | /** 118 | * @brief Get a reference to the data element at given position in a multidimensional container 119 | * @param[in] indices - vector with coordinates of desired data element 120 | * @throws indexError() - if \p indices are out-of-bounds 121 | **/ 122 | T& at(const std::vector& indices); 123 | 124 | /** 125 | * @brief Get a constant reference to the data element at given position in a multidimensional container 126 | * @param[in] indices - vector with coordinates of desired data element 127 | * @throws indexError() - if \p indices are out-of-bounds 128 | **/ 129 | const T& at(const std::vector& indices) const; 130 | 131 | private: 132 | /// Dimensions of the array 133 | MArrayDimensions dims; 134 | 135 | mint getSize() const noexcept override { 136 | return dims.flatCount(); 137 | } 138 | }; 139 | 140 | template 141 | T& MArray::at(mint index) { 142 | return (*this)[dims.getIndexChecked(index)]; 143 | } 144 | 145 | template 146 | const T& MArray::at(mint index) const { 147 | return (*this)[dims.getIndexChecked(index)]; 148 | } 149 | 150 | template 151 | T& MArray::at(const std::vector& indices) { 152 | return (*this)[dims.getIndexChecked(indices)]; 153 | } 154 | 155 | template 156 | const T& MArray::at(const std::vector& indices) const { 157 | return (*this)[dims.getIndexChecked(indices)]; 158 | } 159 | 160 | /** 161 | * @brief Insertion operator to allow pretty-printing of MArray 162 | * @tparam T - type of elements in the container 163 | * @param[out] os - output stream 164 | * @param[in] c - const& to the MArray we want to print 165 | */ 166 | template 167 | std::ostream& operator<<(std::ostream& os, const MArray& c) { 168 | os << "{ "; 169 | for (auto elem : c) { 170 | os << elem << " "; 171 | } 172 | os << "}"; 173 | return os; 174 | } 175 | 176 | } /* namespace LLU */ 177 | 178 | #endif /* LLU_CONTAINERS_MARRAY_HPP_ */ 179 | -------------------------------------------------------------------------------- /include/LLU/Containers/Views/Tensor.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @author Rafal Chojna 4 | * @brief Definition and implementation of TensorView and TensorTypedView. 5 | */ 6 | #ifndef LLU_CONTAINERS_VIEWS_TENSOR_HPP 7 | #define LLU_CONTAINERS_VIEWS_TENSOR_HPP 8 | 9 | #include "LLU/Containers/Generic/Tensor.hpp" 10 | #include "LLU/Containers/Interfaces.h" 11 | #include "LLU/Containers/Iterators/IterableContainer.hpp" 12 | 13 | namespace LLU { 14 | 15 | /** 16 | * @brief Simple, light-weight, non-owning wrappper over MTensor. 17 | * 18 | * Intended for use in functions that only need to access MTensor metadata, where it can alleviate the need for introducing template parameters 19 | * for MTensor passing mode (like in GenericTensor) or data type (like in Tensor class). 20 | */ 21 | class TensorView : public TensorInterface { 22 | public: 23 | TensorView() = default; 24 | 25 | /** 26 | * Create a NumericArrayView from a GenericNumericArray 27 | * @param gTen - a GenericNumericArray 28 | */ 29 | TensorView(const GenericTensor& gTen) : t {gTen.getContainer()} {} // NOLINT: implicit conversion to a view is useful and harmless 30 | 31 | /** 32 | * Create a NumericArrayView from a raw MNumericArray 33 | * @param mt - a raw MNumericArray 34 | */ 35 | TensorView(MTensor mt) : t {mt} {} // NOLINT 36 | 37 | /// @copydoc TensorInterface::getRank() 38 | mint getRank() const override { 39 | return LibraryData::API()->MTensor_getRank(t); 40 | } 41 | 42 | /// @copydoc TensorInterface::getDimensions() 43 | mint const* getDimensions() const override { 44 | return LibraryData::API()->MTensor_getDimensions(t); 45 | } 46 | 47 | /// @copydoc TensorInterface::getFlattenedLength() 48 | mint getFlattenedLength() const override { 49 | return LibraryData::API()->MTensor_getFlattenedLength(t); 50 | } 51 | 52 | /// @copydoc TensorInterface::type() 53 | mint type() const final { 54 | return LibraryData::API()->MTensor_getType(t); 55 | } 56 | 57 | /// @copydoc TensorInterface::rawData() 58 | void* rawData() const override { 59 | switch (type()) { 60 | case MType_Integer: return LibraryData::API()->MTensor_getIntegerData(t); 61 | case MType_Real: return LibraryData::API()->MTensor_getRealData(t); 62 | case MType_Complex: return LibraryData::API()->MTensor_getComplexData(t); 63 | default: return nullptr; 64 | } 65 | } 66 | 67 | private: 68 | MTensor t = nullptr; 69 | }; 70 | 71 | template 72 | class TensorTypedView : public TensorView, public IterableContainer { 73 | public: 74 | TensorTypedView() = default; 75 | 76 | /** 77 | * Create a TensorTypedView from a GenericTensor. 78 | * @param gTen - a GenericTensor 79 | * @throws ErrorName::TensorTypeError - if the actual datatype of \p gTen is not T 80 | */ 81 | TensorTypedView(const GenericTensor& gTen) : TensorView(gTen) { // NOLINT: implicit conversion to a view is useful and harmless 82 | if (TensorType != type()) { 83 | ErrorManager::throwException(ErrorName::TensorTypeError); 84 | } 85 | } 86 | 87 | /** 88 | * Create a TensorTypedView from a TensorView. 89 | * @param tv - a TensorView 90 | * @throws ErrorName::TensorTypeError - if the actual datatype of \p tv is not T 91 | */ 92 | TensorTypedView(TensorView tv) : TensorView(std::move(tv)) { // NOLINT 93 | if (TensorType != type()) { 94 | ErrorManager::throwException(ErrorName::TensorTypeError); 95 | } 96 | } 97 | 98 | /** 99 | * Create a TensorTypedView from a raw MTensor. 100 | * @param mt - a raw MTensor 101 | * @throws ErrorName::TensorTypeError - if the actual datatype of \p mt is not T 102 | */ 103 | TensorTypedView(MTensor mt) : TensorView(mt) { // NOLINT 104 | if (TensorType != type()) { 105 | ErrorManager::throwException(ErrorName::TensorTypeError); 106 | } 107 | } 108 | 109 | private: 110 | T* getData() const noexcept override { 111 | return static_cast(rawData()); 112 | } 113 | 114 | mint getSize() const noexcept override { 115 | return getFlattenedLength(); 116 | } 117 | }; 118 | 119 | /** 120 | * Take a Tensor-like object \p t and a function \p callable and call the function with a TensorTypedView created from \p t 121 | * @tparam TensorT - a Tensor-like type (GenericTensor, TensorView or MNumericAray) 122 | * @tparam F - any callable object 123 | * @param t - Tensor-like object on which an operation will be performed 124 | * @param callable - a callable object that can be called with a TensorTypedView of any type 125 | * @return result of calling \p callable on a TensorTypedView over \p t 126 | */ 127 | template 128 | auto asTypedTensor(TensorT&& t, F&& callable) { 129 | switch (t.type()) { 130 | case MType_Integer: return std::forward(callable)(TensorTypedView {std::forward(t)}); 131 | case MType_Real: return std::forward(callable)(TensorTypedView {std::forward(t)}); 132 | case MType_Complex: return std::forward(callable)(TensorTypedView> {std::forward(t)}); 133 | default: ErrorManager::throwException(ErrorName::TensorTypeError); 134 | } 135 | } 136 | 137 | /// @cond 138 | // Specialization of asTypedTensor for MTensor 139 | template 140 | auto asTypedTensor(MTensor t, F&& callable) { 141 | return asTypedTensor(TensorView {t}, std::forward(callable)); 142 | } 143 | /// @endcond 144 | } // namespace LLU 145 | 146 | #endif // LLU_CONTAINERS_VIEWS_TENSOR_HPP 147 | -------------------------------------------------------------------------------- /include/LLU/LLU.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file LLU.h 3 | * @brief Header file which includes all major parts of LLU. 4 | * 5 | * It's often convenient to use a single include file but bear in mind that if you only need a small subset of LLU in your project 6 | * you may get shorter compilation times by including only what you actually use. 7 | */ 8 | 9 | #ifndef LLU_LLU_H 10 | #define LLU_LLU_H 11 | 12 | /* Containers */ 13 | #include "LLU/Containers/DataList.h" 14 | #include "LLU/Containers/Generic/DataVector.hpp" 15 | #include "LLU/Containers/Image.h" 16 | #include "LLU/Containers/NumericArray.h" 17 | #include "LLU/Containers/SparseArray.h" 18 | #include "LLU/Containers/Tensor.h" 19 | #include "LLU/Containers/Views/Image.hpp" 20 | #include "LLU/Containers/Views/NumericArray.hpp" 21 | 22 | /* Error reporting */ 23 | #include "LLU/ErrorLog/ErrorManager.h" 24 | #include "LLU/ErrorLog/Errors.h" 25 | 26 | /* Library function arguments */ 27 | #include "LLU/MArgumentManager.h" 28 | 29 | /* WSTP */ 30 | #include "LLU/WSTP/WSStream.hpp" 31 | 32 | /* Others */ 33 | #include "LLU/FileUtilities.h" 34 | 35 | #endif // LLU_LLU_H 36 | -------------------------------------------------------------------------------- /include/LLU/LibraryData.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Definition of the LibraryData class. 4 | * This file is the only place in LLU where LibraryLink header files are included. It is done this way to solve the include order dependency issue which was 5 | * present in LibraryLink before WL version 12.1. 6 | */ 7 | #ifndef LLU_LIBRARYDATA_H 8 | #define LLU_LIBRARYDATA_H 9 | 10 | #include 11 | 12 | /* If wstp.h has not been included before WolframLibrary.h, we need to detect if we are dealing with WL 12.0- or 12.1+. 13 | * To achieve this we include a small header file dllexport.h which defines DLLIMPORT macro only since 12.1. */ 14 | 15 | #pragma push_macro("DLLIMPORT") /* Although unlikely, DLLIMPORT might have been defined elsewhere, so save it and temporarily undefine. */ 16 | #undef DLLIMPORT 17 | 18 | #include "dllexport.h" 19 | 20 | #ifndef DLLIMPORT /* We are dealing with WL 12.0-, restore DLLIMPORT macro and include wstp.h even though we don't really need it at this point. */ 21 | #pragma pop_macro("DLLIMPORT") 22 | 23 | #include "wstp.h" 24 | 25 | #define MLINK WSLINK 26 | #define MLENV WSENV 27 | 28 | #include "WolframLibrary.h" 29 | 30 | #else // 12.1+ 31 | 32 | #include "WolframLibrary.h" 33 | 34 | #endif /* DLLIMPORT */ 35 | 36 | #include "WolframTabularColumnLibrary.h" 37 | #include "WolframIOLibraryFunctions.h" 38 | #include "WolframImageLibrary.h" 39 | #include "WolframNumericArrayLibrary.h" 40 | #include "WolframSparseLibrary.h" 41 | 42 | namespace LLU { 43 | 44 | /** 45 | * @struct LibraryData 46 | * @brief This structure offers a static copy of WolframLibData accessible throughout the whole life of the DLL. 47 | */ 48 | struct LibraryData { 49 | /** 50 | * @brief Set WolframLibraryData structure as static member of LibDataHolder. Call this function in WolframLibrary_initialize. 51 | * @param[in] ld - WolframLibraryData passed to every library function via LibraryLink 52 | * @warning This function must be called before constructing the first MArgumentManager 53 | * unless you use a constructor that takes WolframLibraryData as argument 54 | **/ 55 | static void setLibraryData(WolframLibraryData ld); 56 | 57 | /** 58 | * @brief Check if libData is populated 59 | * @return true iff the libData is not a nullptr 60 | */ 61 | static bool hasLibraryData(); 62 | 63 | /** 64 | * @brief Get currently owned WolframLibraryData, if any. 65 | * @return a non-owning pointer to current instance of st_WolframLibraryData statically stored by LibraryData 66 | * @throws ErrorName::LibDataError - if libData is nullptr 67 | **/ 68 | static WolframLibraryData API(); 69 | 70 | /** 71 | * @brief Get a pointer to structure with function pointers to MNumericArray API 72 | * @return a pointer to raw LibraryLink MNumericArray API 73 | */ 74 | static const st_WolframNumericArrayLibrary_Functions* NumericArrayAPI(); 75 | 76 | /** 77 | * @brief Get a pointer to structure with function pointers to MSparseArray API 78 | * @return a pointer to raw LibraryLink MSparseArray API 79 | */ 80 | static const st_WolframSparseLibrary_Functions* SparseArrayAPI(); 81 | 82 | /** 83 | * @brief Get a pointer to structure with function pointers to MImage API 84 | * @return a pointer to raw LibraryLink MImage API 85 | */ 86 | static const st_WolframImageLibrary_Functions* ImageAPI(); 87 | 88 | /** 89 | * @brief Get a pointer to structure with function pointers to DataStore API 90 | * @return a pointer to raw LibraryLink DataStore API 91 | */ 92 | static const st_WolframIOLibrary_Functions* DataStoreAPI(); 93 | 94 | /** 95 | * @brief Get a pointer to structure with function pointers to Tabular API 96 | * @return a pointer to raw LibraryLink Tabular API 97 | */ 98 | static const st_WolframTabularColumnLibrary_Functions* TabularAPI(); 99 | 100 | /** 101 | * @brief Get currently owned WolframLibraryData, even if it is a nullptr. 102 | * @return raw pointer to st_WolframLibraryData statically stored by LibraryData 103 | */ 104 | static WolframLibraryData uncheckedAPI() noexcept; 105 | 106 | private: 107 | /// A copy of WolframLibraryData that will be accessible to all parts of LLU 108 | static WolframLibraryData libData; 109 | }; 110 | 111 | } // namespace LLU 112 | 113 | #endif // LLU_LIBRARYDATA_H 114 | -------------------------------------------------------------------------------- /include/LLU/LibraryLinkFunctionMacro.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file LibraryLinkFunctionMacro.h 3 | * @author Rafal Chojna 4 | * @date 10/08/2017 5 | * 6 | * @brief Small collection of macros designed to reduce the amount of boilerplate code and to work around certain MSVC bug. 7 | * Defined in a separate header file to limit their scope. 8 | * Use those macros only for interface functions (functions that will be loaded with LibraryFunctionLoad). 9 | * 10 | * @see https://stackoverflow.com/questions/45590594/generic-lambda-in-extern-c-function 11 | */ 12 | 13 | #ifndef LLU_LIBRARYLINKFUNCTIONMACRO_H 14 | #define LLU_LIBRARYLINKFUNCTIONMACRO_H 15 | 16 | /** 17 | * @brief This macro forward declares and begins the definition of an extern "C" LibraryLink function with given name. 18 | * @details For input parameter and return type explanation see the official LibraryLink guide. 19 | * WolframLibraryData parameter is marked [[maybe_unused]] because it is a common workflow to take the instance of WolframLibraryData passed to 20 | * WolframLibrary_initialize function and store it with LLU::LibraryData::setLibraryData so that it is accessible everywhere. 21 | * With such setup one does not need to use the WolframLibraryData copy provided to every LibraryLink function. 22 | */ 23 | #define LIBRARY_LINK_FUNCTION(name) \ 24 | EXTERN_C DLLEXPORT int name(WolframLibraryData, mint, MArgument*, MArgument); \ 25 | int name([[maybe_unused]] WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) 26 | 27 | /** 28 | * @brief This macro forward declares and begins the definition of an extern "C" LibraryLink function with given name, which uses WSTP 29 | * to exchange data with WolframLanguage. 30 | * @copydetails LIBRARY_LINK_FUNCTION 31 | */ 32 | #define LIBRARY_WSTP_FUNCTION(name) \ 33 | EXTERN_C DLLEXPORT int name(WolframLibraryData, WSLINK); \ 34 | int name([[maybe_unused]] WolframLibraryData libData, WSLINK wsl) 35 | 36 | /** 37 | * @brief This macro provides all the boilerplate code needed for a typical exception-safe LibraryLink function. 38 | * @details LLU_LIBRARY_FUNCTION(MyFunction) defines a LibraryLink function MyFunction and a regular function impl_MyFunction of type 39 | * void(LLU::MArgumentManager&), which is the one you need to provide a body for. All LLU::LibraryLinkError exceptions thrown from impl_MyFunction will be 40 | * caught and the error code returned to LibraryLink. All other exceptions will also be caught and translated to a FunctionError. 41 | * 42 | * @note While this macro saves quite a lot of typing it may also decrease code readability and make debugging harder so use with caution. 43 | */ 44 | #define LLU_LIBRARY_FUNCTION(name) \ 45 | void impl_##name(LLU::MArgumentManager&); /* forward declaration */ \ 46 | LIBRARY_LINK_FUNCTION(name) { \ 47 | auto err = LLU::ErrorCode::NoError; \ 48 | try { \ 49 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; \ 50 | impl_##name(mngr); \ 51 | } catch (const LLU::LibraryLinkError& e) { \ 52 | err = e.which(); \ 53 | } catch (...) { \ 54 | err = LLU::ErrorCode::FunctionError; \ 55 | } \ 56 | return err; \ 57 | } \ 58 | void impl_##name(LLU::MArgumentManager& mngr) 59 | 60 | #define LLU_WSTP_FUNCTION(name) \ 61 | void impl_##name(WSLINK&); /* forward declaration */ \ 62 | LIBRARY_WSTP_FUNCTION(name) { \ 63 | auto err = LLU::ErrorCode::NoError; \ 64 | try { \ 65 | impl_##name(wsl); \ 66 | } catch (const LLU::LibraryLinkError& e) { \ 67 | err = e.which(); \ 68 | } catch (...) { \ 69 | err = LLU::ErrorCode::FunctionError; \ 70 | } \ 71 | return err; \ 72 | } \ 73 | void impl_##name(WSLINK& wsl) 74 | 75 | #endif // LLU_LIBRARYLINKFUNCTIONMACRO_H 76 | -------------------------------------------------------------------------------- /include/LLU/NoMinMaxWindows.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file NoMinMaxWindows.h 3 | * @author Rafal Chojna 4 | * @date 3/10/2017 5 | * 6 | * @brief Include windows.h without \c min and \c max macros 7 | * 8 | */ 9 | #ifndef LLU_NOMINMAXWINDOWS_H 10 | #define LLU_NOMINMAXWINDOWS_H 11 | 12 | #ifdef _WIN32 13 | 14 | /* Prevent windows.h from defining max and min macros. They collide with std::max, std::min, etc. */ 15 | #ifndef NOMINMAX 16 | #define NOMINMAX 17 | #endif 18 | 19 | #include 20 | 21 | #undef NOMINMAX 22 | 23 | #endif 24 | 25 | #endif // LLU_NOMINMAXWINDOWS_H 26 | -------------------------------------------------------------------------------- /include/LLU/ProgressMonitor.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProgressMonitor.h 3 | * @author Rafal Chojna 4 | * @brief Definition of ProgressMonitor class 5 | */ 6 | #ifndef LLU_PROGRESSMONITOR_H 7 | #define LLU_PROGRESSMONITOR_H 8 | 9 | #include "LLU/Containers/Tensor.h" 10 | 11 | namespace LLU { 12 | 13 | /** 14 | * @brief Stores and updates current progress of computation in a location shared between the library and WL Kernel. 15 | * 16 | * ProgressMonitor receives an instance of a shared Tensor in constructor and becomes the (shared) owner. Progress is 17 | * a single number of type \c double between 0. and 1. 18 | * This class offers an interface for modifying the progress value (increase/decrease by a given step or set to an arbitrary value) and 19 | * one static function for checking if a user requested to abort the computation in WL Kernel. 20 | **/ 21 | class ProgressMonitor { 22 | public: 23 | /// A type to represent a buffer shared between LLU and the Kernel which is used to report progress 24 | using SharedTensor = Tensor; 25 | 26 | /** 27 | * @brief Construct a new ProgressMonitor 28 | * @param sharedIndicator - shared Tensor of type \c double. If tensor length is smaller than 1, the behavior is undefined. 29 | * @param step - by how much to modify the progress value in operator++ and operator-- 30 | */ 31 | explicit ProgressMonitor(SharedTensor sharedIndicator, double step = defaultStep); 32 | 33 | /// Copy-constructor is disabled because ProgressMonitor shares a Tensor with WL Kernel. 34 | ProgressMonitor(const ProgressMonitor&) = delete; 35 | /// Copy-assignment is disabled because ProgressMonitor shares a Tensor with WL Kernel. 36 | ProgressMonitor& operator=(const ProgressMonitor&) = delete; 37 | 38 | /// Default move-constructor. 39 | ProgressMonitor(ProgressMonitor&&) = default; 40 | /// Default move-assignment operator. 41 | ProgressMonitor& operator=(ProgressMonitor&&) = default; 42 | 43 | /** 44 | * @brief Default destructor. 45 | */ 46 | ~ProgressMonitor() = default; 47 | 48 | /** 49 | * @brief Get current value of the progress. 50 | * @return current value of the progress (a \c double between 0. and 1.) 51 | */ 52 | double get() const; 53 | 54 | /** 55 | * @brief Set current progress value. 56 | * @param progressValue - current progress (a \c double between 0. and 1.) 57 | */ 58 | void set(double progressValue); 59 | 60 | /** 61 | * @brief Get current step value. 62 | * @return current step value 63 | */ 64 | double getStep() const; 65 | 66 | /** 67 | * @brief Change step value to a given number. 68 | * @param stepValue - any real number between 0. and 1. 69 | */ 70 | void setStep(double stepValue); 71 | 72 | /** 73 | * @brief Check whether user requested to abort the computation in WL Kernel. 74 | */ 75 | static void checkAbort(); 76 | 77 | /** 78 | * @brief Increment current progress value by \c step. 79 | * @return self 80 | */ 81 | ProgressMonitor& operator++(); 82 | 83 | /** 84 | * @brief Increment current progress value by a given number. 85 | * @param progress - a real number between 0. and (1 - get()). No validation is done. 86 | * @return self 87 | */ 88 | ProgressMonitor& operator+=(double progress); 89 | 90 | /** 91 | * @brief Decrement current progress value by \c step. 92 | * @return self 93 | */ 94 | ProgressMonitor& operator--(); 95 | 96 | /** 97 | * @brief Decrement current progress value by a given number. 98 | * @param progress - a real number between 0. and get(). No validation is done. 99 | * @return self 100 | */ 101 | ProgressMonitor& operator-=(double progress); 102 | 103 | /** 104 | * @brief Return default step for the ProgressMonitor 105 | * @return default step value (0.1) 106 | */ 107 | static constexpr double getDefaultStep() noexcept { 108 | return defaultStep; 109 | } 110 | private: 111 | /// By default, progress changes by .1 each time 112 | static constexpr double defaultStep = .1; 113 | 114 | /// This tensor stores current progress as the first element. 115 | SharedTensor sharedIndicator; 116 | 117 | /// Step determines by how much will ++ or -- operators modify the current progress. 118 | double step; 119 | }; 120 | 121 | } // namespace LLU 122 | #endif // LLU_PROGRESSMONITOR_H 123 | -------------------------------------------------------------------------------- /include/LLU/UniquePtr.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UniquePtr.h 3 | * @author Rafal Chojna 4 | * @date March 18, 2024 5 | * @brief 6 | */ 7 | #pragma once 8 | 9 | #include 10 | #include 11 | #include 12 | 13 | #include "LibraryData.h" 14 | 15 | namespace LLU { 16 | namespace detail { 17 | template 18 | struct Deleter { 19 | static_assert(std::is_trivial_v && std::is_standard_layout_v, "In LLU::UniquePtr T must be a trivial, standard layout type."); 20 | 21 | using pointer = T*; 22 | 23 | Deleter() noexcept = default; 24 | 25 | template>> 26 | Deleter(const Deleter& /*other*/) noexcept {} 27 | 28 | void operator()(pointer p) { 29 | LibraryData::API()->WL_free(p); 30 | } 31 | }; 32 | 33 | template 34 | struct Deleter { 35 | static_assert(std::is_trivial_v && std::is_standard_layout_v, "In LLU::UniquePtr T must be a trivial, standard layout type."); 36 | 37 | Deleter() noexcept = default; 38 | 39 | template>> 40 | Deleter(const Deleter& /*other*/) noexcept {} 41 | 42 | template>> 43 | void operator()(U* p) { 44 | LibraryData::API()->WL_free(p); 45 | } 46 | }; 47 | } // namespace detail 48 | 49 | /// wrt::UniquePtr is a std::unique_ptr to an object of type T that was created in memory allocated with Wolfram's allocator. 50 | template 51 | using UniquePtr = std::unique_ptr>; 52 | 53 | /** 54 | * @brief Create an object on the heap and a unique pointer to it using wrt::Allocator. 55 | * @tparam T - type of the object to be created 56 | * @tparam Args - types of arguments for the object creation 57 | * @param args - arguments for the object's constructor 58 | * @return a unique pointer to the newly created object which will use Wolfram's deallocation function when destroying 59 | * the owned object 60 | */ 61 | template 62 | std::enable_if_t::value, UniquePtr> makeUnique() { 63 | auto* raw_memory = static_cast(LibraryData::API()->WL_malloc(sizeof(T))); 64 | if (!raw_memory) { 65 | ErrorManager::throwException(ErrorName::MemoryError); 66 | } 67 | return UniquePtr(raw_memory, detail::Deleter {}); 68 | } 69 | 70 | /** 71 | * @brief Create an array of n value-initialized objects in a dynamically allocated storage. 72 | * @tparam T - unbounded array type (e.g. mint[] is fine and will result in a wrt::UniquePtr to an array of mints, but mint[7] is not allowed) 73 | * @param n - number of objects in the array to create 74 | * @return a unique pointer to the newly created array of objects 75 | */ 76 | template 77 | std::enable_if_t, UniquePtr> makeUnique(std::size_t n) { 78 | using ElementType = std::remove_extent_t; 79 | auto* raw_memory = static_cast(LibraryData::API()->WL_malloc(n * sizeof(ElementType))); 80 | if (!raw_memory) { 81 | ErrorManager::throwException(ErrorName::MemoryError); 82 | } 83 | return UniquePtr(raw_memory); 84 | } 85 | 86 | // For consistency with std::make_unique disallow creating a unique pointer from a known-bound array type 87 | template 88 | std::enable_if_t> makeUnique(Args&&...) = delete; 89 | 90 | } 91 | -------------------------------------------------------------------------------- /include/LLU/WSTP/Release.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Release.h 3 | * @date Nov 28, 2017 4 | * @author Rafal Chojna 5 | * @brief Header file with classes responsible for releasing memory allocated by WSTP when receiving data. 6 | */ 7 | #ifndef LLU_WSTP_RELEASE_H_ 8 | #define LLU_WSTP_RELEASE_H_ 9 | 10 | #include 11 | 12 | #include "wstp.h" 13 | 14 | #include "LLU/Utilities.hpp" 15 | 16 | namespace LLU::WS { 17 | 18 | template 19 | struct ReleaseList { 20 | using Func = std::function; 21 | 22 | ReleaseList() = default; 23 | ReleaseList(WSLINK m, int l) : m(m), length(l) {} 24 | 25 | void operator()(T* data) { 26 | Release(m, data, length); 27 | } 28 | 29 | int getLength() const { 30 | return length; 31 | } 32 | 33 | private: 34 | static Func Release; 35 | 36 | WSLINK m = nullptr; 37 | int length = 0; 38 | }; 39 | 40 | template 41 | struct ReleaseArray { 42 | using Func = std::function; 43 | 44 | ReleaseArray() = default; 45 | ReleaseArray(WSLINK m, int* d, char** h, int r) : m(m), dims(d), heads(h), rank(r) {} 46 | 47 | void operator()(T* data) { 48 | Release(m, data, dims, heads, rank); 49 | } 50 | 51 | int* getDims() const { 52 | return dims; 53 | } 54 | 55 | char** getHeads() const { 56 | return heads; 57 | } 58 | 59 | int getRank() const { 60 | return rank; 61 | } 62 | 63 | private: 64 | static Func Release; 65 | 66 | WSLINK m = nullptr; 67 | int* dims = nullptr; 68 | char** heads = nullptr; 69 | int rank = 0; 70 | }; 71 | 72 | template 73 | typename ReleaseArray::Func ReleaseArray::Release = [](WSLINK /*link*/, T* /*array*/, int* /*dims*/, char** /*heads*/, int /*rank*/) { 74 | static_assert(dependent_false_v, "Trying to use WS::ReleaseArray::Release for unsupported type T"); 75 | }; 76 | 77 | template 78 | typename ReleaseList::Func ReleaseList::Release = [](WSLINK /*link*/, T* /*list*/, int /*length*/) { 79 | static_assert(dependent_false_v, "Trying to use WS::ReleaseList::Release for unsupported type T"); 80 | }; 81 | 82 | /// @cond 83 | #ifndef _WIN32 84 | 85 | #define WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(T) \ 86 | template<> \ 87 | ReleaseArray::Func ReleaseArray::Release; \ 88 | template<> \ 89 | ReleaseList::Func ReleaseList::Release; 90 | 91 | WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(unsigned char) 92 | WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(short) 93 | WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(int) 94 | WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(wsint64) 95 | WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(float) 96 | WS_RELEASE_DECLARE_SPECIALIZATIONS_OF_STATIC_MEMBERS(double) 97 | #else 98 | 99 | template<> 100 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger8Array; 101 | 102 | template<> 103 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger8List; 104 | 105 | template<> 106 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger16Array; 107 | 108 | template<> 109 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger16List; 110 | 111 | template<> 112 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger32Array; 113 | 114 | template<> 115 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger32List; 116 | 117 | template<> 118 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger64Array; 119 | 120 | template<> 121 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger64List; 122 | 123 | template<> 124 | ReleaseArray::Func ReleaseArray::Release = WSReleaseReal32Array; 125 | 126 | template<> 127 | ReleaseList::Func ReleaseList::Release = WSReleaseReal32List; 128 | 129 | template<> 130 | ReleaseArray::Func ReleaseArray::Release = WSReleaseReal64Array; 131 | 132 | template<> 133 | ReleaseList::Func ReleaseList::Release = WSReleaseReal64List; 134 | #endif 135 | /// @endcond 136 | 137 | } /* namespace LLU::WS */ 138 | 139 | #endif /* LLU_WSTP_RELEASE_H_ */ 140 | -------------------------------------------------------------------------------- /include/LLU/WSTP/UtilityTypeTraits.hpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UtilityTypeTraits.hpp 3 | * @date Feb 7, 2018 4 | * @author Rafal Chojna 5 | * @brief Type traits used by WSStream to identify types supported by WSTP 6 | */ 7 | #ifndef LLU_WSTP_UTILITYTYPETRAITS_HPP_ 8 | #define LLU_WSTP_UTILITYTYPETRAITS_HPP_ 9 | 10 | #include 11 | 12 | #include "wstp.h" 13 | 14 | #include "LLU/Utilities.hpp" 15 | 16 | namespace LLU::WS { 17 | 18 | /** 19 | * @brief Utility trait that determines whether type T is a suitable data type for functions like WSPut*Array, WSGet*List, WSPutScalar, etc. 20 | * @tparam T - any type 21 | */ 22 | template 23 | inline constexpr bool supportedInWSArithmeticQ = false; 24 | 25 | /// @cond 26 | template<> 27 | inline constexpr bool supportedInWSArithmeticQ = true; 28 | template<> 29 | inline constexpr bool supportedInWSArithmeticQ = true; 30 | template<> 31 | inline constexpr bool supportedInWSArithmeticQ = true; 32 | template<> 33 | inline constexpr bool supportedInWSArithmeticQ = true; 34 | template<> 35 | inline constexpr bool supportedInWSArithmeticQ = true; 36 | template<> 37 | inline constexpr bool supportedInWSArithmeticQ = true; 38 | /// @endcond 39 | 40 | /// Convenient alias for supportedInWSArithmeticQ that strips T from cv-qualifiers and reference. 41 | template 42 | inline constexpr bool ScalarSupportedTypeQ = supportedInWSArithmeticQ>; 43 | 44 | /** 45 | * @brief Utility trait that determines whether type T is a suitable character type for WSPut*String and WSGet*String 46 | * @tparam T - any type 47 | */ 48 | template 49 | inline constexpr bool supportedInWSStringQ = false; 50 | 51 | /// @cond 52 | template<> 53 | inline constexpr bool supportedInWSStringQ = true; 54 | template<> 55 | inline constexpr bool supportedInWSStringQ = true; 56 | template<> 57 | inline constexpr bool supportedInWSStringQ = true; 58 | template<> 59 | inline constexpr bool supportedInWSStringQ = true; 60 | /// @endcond 61 | 62 | /// Convenient alias for supportedInWSStringQ that strips T from cv-qualifiers and reference. 63 | template 64 | inline constexpr bool StringTypeQ = supportedInWSStringQ>; 65 | 66 | } // namespace LLU::WS 67 | 68 | #endif /* LLU_WSTP_UTILITYTYPETRAITS_HPP_ */ 69 | -------------------------------------------------------------------------------- /src/Containers/Image.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Image.cpp 3 | * @author Rafal Chojna 4 | * @date 18/04/2017 5 | * 6 | * @brief Template specializations of Image::type attribute for all data types that we want to support 7 | * 8 | */ 9 | #include 10 | 11 | #include "LLU/Containers/Generic/Image.hpp" 12 | #include "LLU/Containers/Image.h" 13 | 14 | namespace LLU { 15 | 16 | MContainer::MContainer(mint slices, mint width, mint height, mint channels, imagedata_t type, colorspace_t colorSpace, 17 | mbool interleaving) { 18 | Container tmp {}; 19 | bool is3DImage = (slices != 0); 20 | if (0 != (is3DImage ? LibraryData::ImageAPI()->MImage_new3D(slices, width, height, channels, type, colorSpace, interleaving, &tmp) 21 | : LibraryData::ImageAPI()->MImage_new2D(width, height, channels, type, colorSpace, interleaving, &tmp))) { 22 | ErrorManager::throwException(ErrorName::ImageNewError); 23 | } 24 | this->reset(tmp); 25 | } 26 | 27 | GenericImage GenericImage::convert(imagedata_t t, mbool interleavingQ) const { 28 | auto* newImage = LibraryData::ImageAPI()->MImage_convertType(this->getContainer(), t, interleavingQ); 29 | if (!newImage) { 30 | ErrorManager::throwException(ErrorName::ImageNewError, "Conversion to type " + std::to_string(static_cast(t)) + " failed."); 31 | } 32 | return {newImage, Ownership::Library}; 33 | } 34 | 35 | auto GenericImage::cloneImpl() const -> Container { 36 | Container tmp {}; 37 | if (0 != LibraryData::ImageAPI()->MImage_clone(this->getContainer(), &tmp)) { 38 | ErrorManager::throwException(ErrorName::ImageCloneError); 39 | } 40 | return tmp; 41 | } 42 | 43 | /// @cond 44 | // 45 | // Template specializations for Bit images 46 | // 47 | 48 | template<> 49 | int8_t TypedImage::getValueAt(mint* position, mint channel) const { 50 | raw_t_bit res {}; 51 | if (0 != LibraryData::ImageAPI()->MImage_getBit(this->getInternal(), position, channel, &res)) { 52 | this->indexError(); 53 | } 54 | return res; 55 | } 56 | 57 | template<> 58 | void TypedImage::setValueAt(mint* position, mint channel, int8_t newValue) { 59 | if (0 != LibraryData::ImageAPI()->MImage_setBit(this->getInternal(), position, channel, newValue)) { 60 | this->indexError(); 61 | } 62 | } 63 | 64 | // 65 | // Template specializations for Byte images 66 | // 67 | 68 | template<> 69 | uint8_t TypedImage::getValueAt(mint* position, mint channel) const { 70 | raw_t_ubit8 res {}; 71 | if (0 != LibraryData::ImageAPI()->MImage_getByte(this->getInternal(), position, channel, &res)) { 72 | this->indexError(); 73 | } 74 | return res; 75 | } 76 | 77 | template<> 78 | void TypedImage::setValueAt(mint* position, mint channel, uint8_t newValue) { 79 | if (0 != LibraryData::ImageAPI()->MImage_setByte(this->getInternal(), position, channel, newValue)) { 80 | this->indexError(); 81 | } 82 | } 83 | 84 | // 85 | // Template specializations for Bit16 images 86 | // 87 | 88 | template<> 89 | uint16_t TypedImage::getValueAt(mint* position, mint channel) const { 90 | raw_t_ubit16 res {}; 91 | if (0 != LibraryData::ImageAPI()->MImage_getBit16(this->getInternal(), position, channel, &res)) { 92 | this->indexError(); 93 | } 94 | return res; 95 | } 96 | 97 | template<> 98 | void TypedImage::setValueAt(mint* position, mint channel, uint16_t newValue) { 99 | if (0 != LibraryData::ImageAPI()->MImage_setBit16(this->getInternal(), position, channel, newValue)) { 100 | this->indexError(); 101 | } 102 | } 103 | 104 | // 105 | // Template specializations for Real32 images 106 | // 107 | 108 | template<> 109 | float TypedImage::getValueAt(mint* position, mint channel) const { 110 | raw_t_real32 res {}; 111 | if (0 != LibraryData::ImageAPI()->MImage_getReal32(this->getInternal(), position, channel, &res)) { 112 | this->indexError(); 113 | } 114 | return res; 115 | } 116 | 117 | template<> 118 | void TypedImage::setValueAt(mint* position, mint channel, float newValue) { 119 | if (0 != LibraryData::ImageAPI()->MImage_setReal32(this->getInternal(), position, channel, newValue)) { 120 | this->indexError(); 121 | } 122 | } 123 | 124 | // 125 | // Template specializations for Real images 126 | // 127 | 128 | template<> 129 | double TypedImage::getValueAt(mint* position, mint channel) const { 130 | raw_t_real64 res {}; 131 | if (0 != LibraryData::ImageAPI()->MImage_getReal(this->getInternal(), position, channel, &res)) { 132 | this->indexError(); 133 | } 134 | return res; 135 | } 136 | 137 | template<> 138 | void TypedImage::setValueAt(mint* position, mint channel, double newValue) { 139 | if (0 != LibraryData::ImageAPI()->MImage_setReal(this->getInternal(), position, channel, newValue)) { 140 | this->indexError(); 141 | } 142 | } 143 | /// @endcond 144 | } /* namespace LLU */ 145 | -------------------------------------------------------------------------------- /src/Containers/MArrayDimensions.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Definitions of non-template member functions and static data members from MArrayDimensions class 4 | */ 5 | 6 | #include "LLU/Containers/MArrayDimensions.h" 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | namespace { 13 | /** 14 | * @brief Check if initializer list size will fit into \b mint 15 | * @param[in] v - an initializer list 16 | * @throws ErrorName::DimensionsError - if \c v is too big 17 | **/ 18 | void checkInitializerListSize(std::initializer_list v) { 19 | if (v.size() > static_cast((std::numeric_limits::max)())) { 20 | LLU::ErrorManager::throwException(LLU::ErrorName::DimensionsError); 21 | } 22 | } 23 | 24 | [[noreturn]] void indexError(mint index) { 25 | LLU::ErrorManager::throwException(LLU::ErrorName::MArrayElementIndexError, index); 26 | } 27 | 28 | } // namespace 29 | 30 | namespace LLU { 31 | 32 | MArrayDimensions::MArrayDimensions(std::initializer_list dimensions) : dims {dimensions} { 33 | checkInitializerListSize(dimensions); 34 | flattenedLength = totalLengthFromDims(); 35 | fillOffsets(); 36 | } 37 | 38 | void MArrayDimensions::fillOffsets() { 39 | offsets.assign(dims.size(), 1); 40 | if (rank() >= 2) { 41 | std::transform(std::rbegin(offsets), std::rend(offsets) - 1, std::crbegin(dims), std::rbegin(offsets) + 1, 42 | [](auto off, auto dim) { return off * dim; }); 43 | } 44 | } 45 | 46 | mint MArrayDimensions::getIndexChecked(const std::vector& indices) const { 47 | if (indices.size() > dims.size()) { 48 | ErrorManager::throwException(ErrorName::MArrayDimensionIndexError, static_cast(indices.size())); 49 | } 50 | auto dimsIt = dims.cbegin(); 51 | for (auto idx : indices) { 52 | if (idx < 0 || idx >= *dimsIt++) { 53 | indexError(idx); 54 | } 55 | } 56 | return getIndex(indices); 57 | } 58 | 59 | mint MArrayDimensions::getIndexChecked(mint index) const { 60 | if (index < 0 || index >= flatCount()) { 61 | indexError(index); 62 | } 63 | return index; 64 | } 65 | 66 | mint MArrayDimensions::getIndex(const std::vector& indices) const { 67 | mint flatIndex = 0; 68 | auto offset = offsets.cbegin(); 69 | for (auto idx : indices) { 70 | flatIndex += idx * (*offset++); 71 | } 72 | return flatIndex; 73 | } 74 | 75 | mint MArrayDimensions::totalLengthFromDims() const noexcept { 76 | return std::accumulate(std::begin(dims), std::end(dims), static_cast(1), std::multiplies<>()); 77 | } 78 | 79 | } /* namespace LLU */ 80 | -------------------------------------------------------------------------------- /src/Containers/NumericArray.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file NumericArray.cpp 3 | * @author Rafal Chojna 4 | * @brief Definitions of MContainer (GenericNumericArray) methods 5 | */ 6 | 7 | #include "LLU/Containers/Generic/NumericArray.hpp" 8 | 9 | namespace LLU { 10 | MContainer::MContainer(numericarray_data_t type, mint rank, const mint* dims) { 11 | Container tmp {}; 12 | if (0 != LibraryData::NumericArrayAPI()->MNumericArray_new(type, rank, dims, &tmp)) { 13 | ErrorManager::throwException(ErrorName::NumericArrayNewError); 14 | } 15 | this->reset(tmp); 16 | } 17 | 18 | GenericNumericArray GenericNumericArray::convert(numericarray_data_t t, NA::ConversionMethod method, double param) const { 19 | Container newNA = nullptr; 20 | auto err = LibraryData::NumericArrayAPI()->MNumericArray_convertType(&newNA, this->getContainer(), t, 21 | static_cast(method), param); 22 | if (err != 0) { 23 | ErrorManager::throwException(ErrorName::NumericArrayConversionError, "Conversion to type " + std::to_string(static_cast(t)) + " failed."); 24 | } 25 | return {newNA, Ownership::Library}; 26 | } 27 | 28 | auto GenericNumericArray::cloneImpl() const -> Container { 29 | Container tmp {}; 30 | if (0 != LibraryData::NumericArrayAPI()->MNumericArray_clone(this->getContainer(), &tmp)) { 31 | ErrorManager::throwException(ErrorName::NumericArrayCloneError); 32 | } 33 | return tmp; 34 | } 35 | 36 | } // namespace LLU -------------------------------------------------------------------------------- /src/Containers/SparseArray.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SparseArray.cpp 3 | * @author Rafal Chojna 4 | * @date November 25, 2020 5 | * @brief Implementation of GenericSparseArray and SparseArray 6 | */ 7 | 8 | #include "LLU/Containers/SparseArray.h" 9 | 10 | namespace LLU { 11 | 12 | GenericSparseArray::MContainer(const GenericTensor& positions, const GenericTensor& values, const GenericTensor& dimensions, 13 | const GenericTensor& implicitValue) { 14 | Container tmp {}; 15 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_fromExplicitPositions(positions.getContainer(), values.getContainer(), dimensions.getContainer(), 16 | implicitValue.getContainer(), &tmp)) { 17 | ErrorManager::throwException(ErrorName::SparseArrayFromPositionsError); 18 | } 19 | this->reset(tmp); 20 | } 21 | 22 | GenericSparseArray::MContainer(const GenericTensor& data, const GenericTensor& implicitValue) { 23 | Container tmp {}; 24 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_fromMTensor(data.getContainer(), implicitValue.getContainer(), &tmp)) { 25 | ErrorManager::throwException(ErrorName::SparseArrayFromTensorError); 26 | } 27 | this->reset(tmp); 28 | } 29 | 30 | GenericSparseArray::MContainer(const GenericSparseArray& s, const GenericTensor& implicitValue) { 31 | Container tmp {}; 32 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_resetImplicitValue(s.getContainer(), implicitValue.getContainer(), &tmp)) { 33 | ErrorManager::throwException(ErrorName::SparseArrayImplicitValueResetError); 34 | } 35 | this->reset(tmp); 36 | } 37 | 38 | auto GenericSparseArray::cloneImpl() const -> Container { 39 | Container tmp {}; 40 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_clone(this->getContainer(), &tmp)) { 41 | ErrorManager::throwException(ErrorName::SparseArrayCloneError); 42 | } 43 | return tmp; 44 | } 45 | 46 | GenericTensor GenericSparseArray::getImplicitValueAsTensor() const { 47 | if (auto* implValue = LibraryData::SparseArrayAPI()->MSparseArray_getImplicitValue(this->getContainer()); implValue) { 48 | return {*implValue, Ownership::LibraryLink}; 49 | } 50 | ErrorManager::throwException(ErrorName::SparseArrayImplicitValueError); 51 | } 52 | 53 | void MContainer::setImplicitValueFromTensor(const GenericTensor& implicitValue) { 54 | Container rawSparse = getContainer(); 55 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_resetImplicitValue(rawSparse, implicitValue.getContainer(), &rawSparse)) { 56 | ErrorManager::throwException(ErrorName::SparseArrayImplicitValueResetError); 57 | } 58 | this->reset(rawSparse); 59 | } 60 | 61 | GenericTensor MContainer::getExplicitValues() const { 62 | if (auto* explicitValues = LibraryData::SparseArrayAPI()->MSparseArray_getExplicitValues(this->getContainer()); explicitValues) { 63 | return {*explicitValues, Ownership::LibraryLink}; 64 | } 65 | ErrorManager::throwException(ErrorName::SparseArrayExplicitValuesError); 66 | } 67 | 68 | GenericTensor MContainer::getRowPointers() const { 69 | if (auto* rowPointers = LibraryData::SparseArrayAPI()->MSparseArray_getRowPointers(this->getContainer()); rowPointers) { 70 | return {*rowPointers, Ownership::LibraryLink}; 71 | } 72 | ErrorManager::throwException(ErrorName::SparseArrayRowPointersError); 73 | } 74 | 75 | GenericTensor MContainer::getColumnIndices() const { 76 | if (auto* colIndices = LibraryData::SparseArrayAPI()->MSparseArray_getColumnIndices(this->getContainer()); colIndices) { 77 | return {*colIndices, Ownership::LibraryLink}; 78 | } 79 | ErrorManager::throwException(ErrorName::SparseArrayColumnIndicesError); 80 | } 81 | 82 | GenericTensor MContainer::getExplicitPositions() const { 83 | MTensor mt {}; 84 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_getExplicitPositions(getContainer(), &mt)) { 85 | ErrorManager::throwException(ErrorName::SparseArrayExplicitPositionsError); 86 | } 87 | return {mt, Ownership::Library}; 88 | } 89 | 90 | GenericTensor MContainer::toGenericTensor() const { 91 | MTensor mt {}; 92 | if (0 != LibraryData::SparseArrayAPI()->MSparseArray_toMTensor(getContainer(), &mt)) { 93 | ErrorManager::throwException(ErrorName::SparseArrayToTensorError); 94 | } 95 | return {mt, Ownership::Library}; 96 | } 97 | 98 | void GenericSparseArray::resparsify() { 99 | setImplicitValueFromTensor(GenericTensor{}); 100 | } 101 | 102 | mint GenericSparseArray::type() const { 103 | return getImplicitValueAsTensor().type(); 104 | } 105 | 106 | } // namespace LLU -------------------------------------------------------------------------------- /src/Containers/Tensor.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Tensor.cpp 3 | * @author Rafal Chojna 4 | * @date 18/04/2017 5 | * 6 | * @brief Template specialization of certain methods from TypedTensor class for all underlying data types that we support 7 | * 8 | */ 9 | 10 | #include "LLU/Containers/Tensor.h" 11 | 12 | #include 13 | 14 | namespace LLU { 15 | 16 | MContainer::MContainer(mint type, mint rank, const mint* dims) { 17 | Container tmp {}; 18 | if (0 != LibraryData::API()->MTensor_new(type, rank, dims, &tmp)) { 19 | ErrorManager::throwException(ErrorName::TensorNewError); 20 | } 21 | this->reset(tmp); 22 | } 23 | 24 | void* GenericTensor::rawData() const { 25 | switch (type()) { 26 | case MType_Integer: return LibraryData::API()->MTensor_getIntegerData(this->getContainer()); 27 | case MType_Real: return LibraryData::API()->MTensor_getRealData(this->getContainer()); 28 | case MType_Complex: return LibraryData::API()->MTensor_getComplexData(this->getContainer()); 29 | default: ErrorManager::throwException(ErrorName::TensorTypeError); 30 | } 31 | } 32 | 33 | auto GenericTensor::cloneImpl() const -> Container { 34 | Container tmp {}; 35 | if (0 != LibraryData::API()->MTensor_clone(this->getContainer(), &tmp)) { 36 | ErrorManager::throwException(ErrorName::TensorCloneError); 37 | } 38 | return tmp; 39 | } 40 | 41 | /// @cond 42 | template<> 43 | mint* TypedTensor::getData() const noexcept { 44 | return LibraryData::API()->MTensor_getIntegerData(this->getInternal()); 45 | } 46 | 47 | template<> 48 | double* TypedTensor::getData() const noexcept { 49 | return LibraryData::API()->MTensor_getRealData(this->getInternal()); 50 | } 51 | 52 | template<> 53 | std::complex* TypedTensor>::getData() const noexcept { 54 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast): std::complex is binary compatible with mcomplex 55 | return reinterpret_cast*>(LibraryData::API()->MTensor_getComplexData(this->getInternal())); 56 | } 57 | /// @endcond 58 | } /* namespace LLU */ 59 | -------------------------------------------------------------------------------- /src/ErrorLog/Errors.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Errors.cpp 3 | * @author Rafal Chojna 4 | * @date March 31, 2019 5 | */ 6 | 7 | #include "LLU/ErrorLog/Errors.h" 8 | 9 | /// Helper macro for defining new errors so that for each error we have a std::string variable equal to the error name. 10 | #define LLU_DEFINE_ERROR_NAME(errorIdentifier) const std::string errorIdentifier = #errorIdentifier 11 | 12 | namespace LLU::ErrorName { 13 | /// @cond 14 | LLU_DEFINE_ERROR_NAME(VersionError); 15 | LLU_DEFINE_ERROR_NAME(FunctionError); 16 | LLU_DEFINE_ERROR_NAME(MemoryError); 17 | LLU_DEFINE_ERROR_NAME(NumericalError); 18 | LLU_DEFINE_ERROR_NAME(DimensionsError); 19 | LLU_DEFINE_ERROR_NAME(RankError); 20 | LLU_DEFINE_ERROR_NAME(TypeError); 21 | LLU_DEFINE_ERROR_NAME(NoError); 22 | 23 | LLU_DEFINE_ERROR_NAME(LibDataError); 24 | 25 | LLU_DEFINE_ERROR_NAME(MArgumentIndexError); 26 | LLU_DEFINE_ERROR_NAME(MArgumentNumericArrayError); 27 | LLU_DEFINE_ERROR_NAME(MArgumentTensorError); 28 | LLU_DEFINE_ERROR_NAME(MArgumentImageError); 29 | 30 | LLU_DEFINE_ERROR_NAME(ErrorManagerThrowIdError); 31 | LLU_DEFINE_ERROR_NAME(ErrorManagerThrowNameError); 32 | LLU_DEFINE_ERROR_NAME(ErrorManagerCreateNameError); 33 | 34 | LLU_DEFINE_ERROR_NAME(NumericArrayNewError); 35 | LLU_DEFINE_ERROR_NAME(NumericArrayCloneError); 36 | LLU_DEFINE_ERROR_NAME(NumericArrayTypeError); 37 | LLU_DEFINE_ERROR_NAME(NumericArraySizeError); 38 | LLU_DEFINE_ERROR_NAME(NumericArrayIndexError); 39 | LLU_DEFINE_ERROR_NAME(NumericArrayConversionError); 40 | 41 | LLU_DEFINE_ERROR_NAME(TensorNewError); 42 | LLU_DEFINE_ERROR_NAME(TensorCloneError); 43 | LLU_DEFINE_ERROR_NAME(TensorTypeError); 44 | LLU_DEFINE_ERROR_NAME(TensorSizeError); 45 | LLU_DEFINE_ERROR_NAME(TensorIndexError); 46 | 47 | LLU_DEFINE_ERROR_NAME(SparseArrayCloneError); 48 | LLU_DEFINE_ERROR_NAME(SparseArrayTypeError); 49 | LLU_DEFINE_ERROR_NAME(SparseArrayFromPositionsError); 50 | LLU_DEFINE_ERROR_NAME(SparseArrayFromTensorError); 51 | LLU_DEFINE_ERROR_NAME(SparseArrayImplicitValueResetError); 52 | LLU_DEFINE_ERROR_NAME(SparseArrayImplicitValueError); 53 | LLU_DEFINE_ERROR_NAME(SparseArrayExplicitValuesError); 54 | LLU_DEFINE_ERROR_NAME(SparseArrayRowPointersError); 55 | LLU_DEFINE_ERROR_NAME(SparseArrayColumnIndicesError); 56 | LLU_DEFINE_ERROR_NAME(SparseArrayExplicitPositionsError); 57 | LLU_DEFINE_ERROR_NAME(SparseArrayToTensorError); 58 | 59 | LLU_DEFINE_ERROR_NAME(ImageNewError); 60 | LLU_DEFINE_ERROR_NAME(ImageCloneError); 61 | LLU_DEFINE_ERROR_NAME(ImageTypeError); 62 | LLU_DEFINE_ERROR_NAME(ImageSizeError); 63 | LLU_DEFINE_ERROR_NAME(ImageIndexError); 64 | 65 | LLU_DEFINE_ERROR_NAME(CreateFromNullError); 66 | LLU_DEFINE_ERROR_NAME(MArrayElementIndexError); 67 | LLU_DEFINE_ERROR_NAME(MArrayDimensionIndexError); 68 | 69 | LLU_DEFINE_ERROR_NAME(WSNullWSLinkError); 70 | LLU_DEFINE_ERROR_NAME(WSTestHeadError); 71 | LLU_DEFINE_ERROR_NAME(WSPutSymbolError); 72 | LLU_DEFINE_ERROR_NAME(WSPutFunctionError); 73 | LLU_DEFINE_ERROR_NAME(WSTestSymbolError); 74 | LLU_DEFINE_ERROR_NAME(WSWrongSymbolForBool); 75 | LLU_DEFINE_ERROR_NAME(WSGetListError); 76 | LLU_DEFINE_ERROR_NAME(WSGetScalarError); 77 | LLU_DEFINE_ERROR_NAME(WSGetStringError); 78 | LLU_DEFINE_ERROR_NAME(WSGetArrayError); 79 | LLU_DEFINE_ERROR_NAME(WSPutListError); 80 | LLU_DEFINE_ERROR_NAME(WSPutScalarError); 81 | LLU_DEFINE_ERROR_NAME(WSPutStringError); 82 | LLU_DEFINE_ERROR_NAME(WSPutArrayError); 83 | LLU_DEFINE_ERROR_NAME(WSGetSymbolError); 84 | LLU_DEFINE_ERROR_NAME(WSGetFunctionError); 85 | LLU_DEFINE_ERROR_NAME(WSPacketHandleError); 86 | LLU_DEFINE_ERROR_NAME(WSFlowControlError); 87 | LLU_DEFINE_ERROR_NAME(WSTransferToLoopbackError); 88 | LLU_DEFINE_ERROR_NAME(WSCreateLoopbackError); 89 | LLU_DEFINE_ERROR_NAME(WSLoopbackStackSizeError); 90 | 91 | LLU_DEFINE_ERROR_NAME(DLNullRawNode); 92 | LLU_DEFINE_ERROR_NAME(DLInvalidNodeType); 93 | LLU_DEFINE_ERROR_NAME(DLGetNodeDataError); 94 | LLU_DEFINE_ERROR_NAME(DLSharedDataStore); 95 | LLU_DEFINE_ERROR_NAME(DLPushBackTypeError); 96 | 97 | LLU_DEFINE_ERROR_NAME(ArgumentCreateNull); 98 | LLU_DEFINE_ERROR_NAME(ArgumentAddNodeMArgument); 99 | 100 | LLU_DEFINE_ERROR_NAME(Aborted); 101 | 102 | LLU_DEFINE_ERROR_NAME(ManagedExprInvalidID); 103 | LLU_DEFINE_ERROR_NAME(MLEDynamicTypeError); 104 | LLU_DEFINE_ERROR_NAME(MLENullInstance); 105 | 106 | LLU_DEFINE_ERROR_NAME(PathNotValidated); 107 | LLU_DEFINE_ERROR_NAME(InvalidOpenMode); 108 | LLU_DEFINE_ERROR_NAME(OpenFileFailed); 109 | 110 | LLU_DEFINE_ERROR_NAME(DVShared); 111 | LLU_DEFINE_ERROR_NAME(DVAPIError); 112 | LLU_DEFINE_ERROR_NAME(DVConstructorType); 113 | 114 | LLU_DEFINE_ERROR_NAME(BitVectorNew); 115 | LLU_DEFINE_ERROR_NAME(BitVectorClone); 116 | 117 | } // namespace LLU::ErrorName 118 | -------------------------------------------------------------------------------- /src/ErrorLog/LibraryLinkError.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file LibraryLinkError.cpp 3 | * @author Rafal Chojna 4 | * 5 | * @brief Contains definitions of ErrorManager members and implementation of interface function sendRegisteredErrors used by PacletFailure framework in LLU 6 | * 7 | */ 8 | #include "LLU/ErrorLog/LibraryLinkError.h" 9 | 10 | #include "LLU/LibraryLinkFunctionMacro.h" 11 | #include "LLU/MArgumentManager.h" 12 | #include "LLU/WSTP/WSStream.hpp" 13 | 14 | namespace LLU { 15 | 16 | std::string LibraryLinkError::exceptionDetailsSymbolContext; 17 | 18 | void LibraryLinkError::setExceptionDetailsSymbolContext(std::string newContext) { 19 | exceptionDetailsSymbolContext = std::move(newContext); 20 | } 21 | 22 | const std::string& LibraryLinkError::getExceptionDetailsSymbolContext() { 23 | return exceptionDetailsSymbolContext; 24 | } 25 | 26 | std::string LibraryLinkError::getExceptionDetailsSymbol() { 27 | return exceptionDetailsSymbolContext + exceptionDetailsSymbol; 28 | } 29 | 30 | WSLINK LibraryLinkError::openLoopback(WSENV env) { 31 | int err = WSEUNKNOWN; 32 | auto* link = WSLoopbackOpen(env, &err); 33 | if (err != WSEOK) { 34 | link = nullptr; 35 | } 36 | return link; 37 | } 38 | 39 | LibraryLinkError::LibraryLinkError(const LibraryLinkError& e) noexcept 40 | : std::runtime_error(e), errorId(e.errorId), messageTemplate(e.messageTemplate), debugInfo(e.debugInfo) { 41 | if (e.messageParams) { 42 | messageParams = openLoopback(WSLinkEnvironment(e.messageParams)); 43 | if (!messageParams) { 44 | return; 45 | } 46 | auto* mark = WSCreateMark(e.messageParams); 47 | WSTransferToEndOfLoopbackLink(messageParams, e.messageParams); 48 | WSSeekMark(e.messageParams, mark, 0); 49 | WSDestroyMark(e.messageParams, mark); 50 | } 51 | } 52 | 53 | LibraryLinkError& LibraryLinkError::operator=(const LibraryLinkError& e) noexcept { 54 | LibraryLinkError tmp {e}; 55 | *this = std::move(tmp); 56 | return *this; 57 | } 58 | 59 | LibraryLinkError::LibraryLinkError(LibraryLinkError&& e) noexcept 60 | // NOLINTNEXTLINE(performance-move-constructor-init) : deliberate and harmless 61 | : std::runtime_error(e), errorId(e.errorId), messageTemplate(e.messageTemplate), debugInfo(e.debugInfo), messageParams(e.messageParams) { 62 | e.messageParams = nullptr; 63 | } 64 | 65 | LibraryLinkError& LibraryLinkError::operator=(LibraryLinkError&& e) noexcept { 66 | std::runtime_error::operator=(e); 67 | errorId = e.errorId; 68 | messageTemplate = e.messageTemplate; 69 | debugInfo = e.debugInfo; 70 | if (messageParams) { 71 | WSClose(messageParams); 72 | } 73 | messageParams = e.messageParams; 74 | e.messageParams = nullptr; 75 | return *this; 76 | } 77 | 78 | LibraryLinkError::~LibraryLinkError() { 79 | if (messageParams) { 80 | WSClose(messageParams); 81 | } 82 | } 83 | 84 | auto LibraryLinkError::sendParameters(WolframLibraryData libData, const std::string& WLSymbol) const noexcept -> IdType { 85 | try { 86 | if (libData) { 87 | WSStream mls {libData->getWSLINK(libData)}; 88 | mls << WS::Function("EvaluatePacket", 1); 89 | mls << WS::Function("Set", 2); 90 | mls << WS::Symbol(WLSymbol); 91 | if (WSTransferToEndOfLoopbackLink(mls.get(), messageParams) == 0) { 92 | return ErrorCode::FunctionError; 93 | } 94 | libData->processWSLINK(mls.get()); 95 | auto pkt = WSNextPacket(mls.get()); 96 | if (pkt == RETURNPKT) { 97 | mls << WS::NewPacket; 98 | } 99 | } 100 | } catch (const LibraryLinkError& e) { 101 | return e.which(); 102 | } catch (...) { 103 | return ErrorCode::FunctionError; 104 | } 105 | return ErrorCode::NoError; 106 | } 107 | 108 | /** 109 | * LibraryLink function that LLU will call to set the context for the symbol, to which exception details are assigned. 110 | * This symbol is usually in the paclet's Private` context and it cannot be hardcoded in LLU. 111 | */ 112 | LIBRARY_LINK_FUNCTION(setExceptionDetailsContext) { 113 | auto err = ErrorCode::NoError; 114 | try { 115 | MArgumentManager mngr {libData, Argc, Args, Res}; 116 | auto newContext = mngr.getString(0); 117 | LibraryLinkError::setExceptionDetailsSymbolContext(std::move(newContext)); 118 | mngr.setString(LibraryLinkError::getExceptionDetailsSymbol()); 119 | } catch (LibraryLinkError& e) { 120 | err = e.which(); 121 | } catch (...) { 122 | err = ErrorCode::FunctionError; 123 | } 124 | return err; 125 | } 126 | } /* namespace LLU */ 127 | -------------------------------------------------------------------------------- /src/ErrorLog/Logger.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Logger.cpp 3 | * @author Rafal Chojna 4 | * @brief Implementation of functions defined in Logger.h. 5 | */ 6 | #include "LLU/ErrorLog/Logger.h" 7 | 8 | #include "LLU/LibraryLinkFunctionMacro.h" 9 | #include "LLU/MArgumentManager.h" 10 | 11 | namespace LLU { 12 | std::mutex Logger::mlinkGuard; 13 | std::string Logger::logSymbolContext; 14 | 15 | std::string Logger::to_string(Level l) { 16 | switch (l) { 17 | case Level::Debug: return "Debug"; 18 | case Level::Warning: return "Warning"; 19 | case Level::Error: return "Error"; 20 | default: return "Unknown"; 21 | } 22 | } 23 | 24 | /** 25 | * LibraryLink function that LLU will call to set the context for the symbol, to which log details are assigned. 26 | * This symbol is usually in the paclet's Private` context and it cannot be hardcoded in LLU. 27 | */ 28 | LIBRARY_LINK_FUNCTION(setLoggerContext) { 29 | auto err = ErrorCode::NoError; 30 | try { 31 | MArgumentManager mngr {libData, Argc, Args, Res}; 32 | auto newContext = mngr.getString(0); 33 | Logger::setContext(newContext); 34 | mngr.setString(Logger::getSymbol()); 35 | } catch (LibraryLinkError& e) { err = e.which(); } catch (...) { 36 | err = ErrorCode::FunctionError; 37 | } 38 | return err; 39 | } 40 | } // namespace LLU -------------------------------------------------------------------------------- /src/FileUtilities.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file FileUtilities.cpp 3 | * @author Rafal Chojna 4 | * @brief 5 | */ 6 | 7 | #include "LLU/NoMinMaxWindows.h" 8 | #include "LLU/FileUtilities.h" 9 | 10 | #ifdef _WIN32 11 | #include 12 | #endif 13 | 14 | #include "LLU/ErrorLog/ErrorManager.h" 15 | #include "LLU/LibraryData.h" 16 | #include "LLU/Utilities.hpp" 17 | 18 | namespace LLU { 19 | 20 | namespace { 21 | std::string openModeString(std::ios::openmode mode) { 22 | using std::ios; 23 | bool isBinary = (mode & ios::binary) != 0; 24 | mode &= ~ios::binary; 25 | std::string result; 26 | if (mode == ios::in) { 27 | result = "r"; 28 | } else if (mode == ios::out || mode == (ios::out | ios::trunc)) { 29 | result = "w"; 30 | } else if (mode == ios::app || mode == (ios::out | ios::app)) { 31 | result = "a"; 32 | } else if (mode == (ios::out | ios::in)) { 33 | result = "r+"; 34 | } else if (mode == (ios::out | ios::in | ios::trunc)) { 35 | result = "w+"; 36 | } else if (mode == (ios::out | ios::in | ios::app) || mode == (ios::in | ios::app)) { 37 | result = "a+"; 38 | } else { 39 | ErrorManager::throwException(ErrorName::InvalidOpenMode); 40 | } 41 | return isBinary ? result + "b" : result; 42 | } 43 | 44 | template 45 | std::basic_fstream openFileStream(const std::string& fileName, std::ios::openmode mode, const SharePolicy& shp) { 46 | validatePath(fileName, mode); 47 | std::basic_fstream result; 48 | #ifdef _WIN32 49 | std::wstring fileNameUTF16 = fromUTF8toUTF16(fileName); 50 | result = std::basic_fstream {fileNameUTF16.c_str(), mode, shp.flag(mode)}; 51 | #else 52 | Unused(shp); 53 | result = std::basic_fstream {fileName, mode}; 54 | #endif /* _WIN32 */ 55 | if (!result) { 56 | ErrorManager::throwException(ErrorName::OpenFileFailed, fileName); 57 | } 58 | return result; 59 | } 60 | } // namespace 61 | 62 | FilePtr claimFile(std::FILE* f) { 63 | return FilePtr(f, [](std::FILE* fp) { return fp ? std::fclose(fp) : 0; }); 64 | } 65 | 66 | void validatePath(const std::string& fileName, std::ios::openmode mode) { 67 | char pathMode = (mode & std::ios::out) != 0 || (mode & std::ios::app) != 0 ? 'W' : 'R'; 68 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): LibraryLink will not modify the string, so const_cast is safe here 69 | if (LibraryData::API()->validatePath(const_cast(fileName.c_str()), pathMode) == False) { 70 | ErrorManager::throwException(ErrorName::PathNotValidated, fileName); 71 | } 72 | } 73 | 74 | int SharePolicy::flag(std::ios::openmode /*mode*/) const { 75 | #ifdef _WIN32 76 | return _SH_SECURE; 77 | #else 78 | return 0; 79 | #endif 80 | } 81 | 82 | int AlwaysReadExclusiveWrite::flag(std::ios::openmode m) const { 83 | #ifdef _WIN32 84 | return (m & std::ios::out || m & std::ios::app) ? _SH_DENYWR : _SH_DENYNO; 85 | #else 86 | Unused(m); 87 | return 0; 88 | #endif 89 | } 90 | 91 | FilePtr openFile(const std::string& fileName, std::ios::openmode mode, const SharePolicy& shp) { 92 | validatePath(fileName, mode); 93 | 94 | FILE* file = nullptr; 95 | std::string modeStr = openModeString(mode); 96 | #ifdef _WIN32 97 | std::wstring fileNameUTF16 = fromUTF8toUTF16(fileName); 98 | std::wstring modeWstr = fromUTF8toUTF16(modeStr); 99 | int shareFlag = shp.flag(mode); 100 | file = _wfsopen(fileNameUTF16.c_str(), modeWstr.c_str(), shareFlag); 101 | #else 102 | Unused(shp); 103 | file = std::fopen(fileName.c_str(), modeStr.c_str()); 104 | #endif /* _WIN32 */ 105 | if (!file) { 106 | ErrorManager::throwException(ErrorName::OpenFileFailed, fileName); 107 | } 108 | return claimFile(file); 109 | } 110 | 111 | std::fstream openFileStream(const std::string& fileName, std::ios::openmode mode, const SharePolicy& shp) { 112 | return openFileStream(fileName, mode, shp); 113 | } 114 | 115 | } // namespace LLU -------------------------------------------------------------------------------- /src/LibraryData.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * Implementation of the LibraryData class. 4 | */ 5 | 6 | #include "LLU/LibraryData.h" 7 | 8 | #include "LLU/ErrorLog/ErrorManager.h" 9 | 10 | namespace LLU { 11 | 12 | WolframLibraryData LibraryData::libData = nullptr; 13 | 14 | void LibraryData::setLibraryData(WolframLibraryData ld) { 15 | libData = ld; 16 | } 17 | 18 | bool LibraryData::hasLibraryData() { 19 | return libData != nullptr; 20 | } 21 | 22 | WolframLibraryData LibraryData::API() { 23 | if (!libData) { 24 | ErrorManager::throwException(ErrorName::LibDataError); 25 | } 26 | return libData; 27 | } 28 | 29 | WolframLibraryData LibraryData::uncheckedAPI() noexcept { 30 | return libData; 31 | } 32 | 33 | const st_WolframNumericArrayLibrary_Functions* LibraryData::NumericArrayAPI() { 34 | return API()->numericarrayLibraryFunctions; 35 | } 36 | 37 | const st_WolframSparseLibrary_Functions* LibraryData::SparseArrayAPI() { 38 | return API()->sparseLibraryFunctions; 39 | } 40 | 41 | const st_WolframImageLibrary_Functions* LibraryData::ImageAPI() { 42 | return API()->imageLibraryFunctions; 43 | } 44 | 45 | const st_WolframIOLibrary_Functions* LibraryData::DataStoreAPI() { 46 | return API()->ioLibraryFunctions; 47 | } 48 | 49 | const st_WolframTabularColumnLibrary_Functions* LibraryData::TabularAPI() { 50 | return API()->tabularColumnLibraryFunctions; 51 | } 52 | } // namespace LLU 53 | -------------------------------------------------------------------------------- /src/ProgressMonitor.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file ProgressMonitor.cpp 3 | * @author Rafal Chojna 4 | * @brief Implementation file for ProgressMonitor class 5 | */ 6 | #include "LLU/ProgressMonitor.h" 7 | 8 | #include "LLU/ErrorLog/ErrorManager.h" 9 | #include "LLU/LibraryData.h" 10 | 11 | namespace LLU { 12 | 13 | ProgressMonitor::ProgressMonitor(SharedTensor sharedIndicator, double step) : sharedIndicator(std::move(sharedIndicator)), step(step) {} 14 | 15 | double ProgressMonitor::get() const { 16 | return sharedIndicator[0]; 17 | } 18 | 19 | void ProgressMonitor::set(double progressValue) { 20 | sharedIndicator[0] = progressValue; 21 | checkAbort(); 22 | } 23 | 24 | double ProgressMonitor::getStep() const { 25 | return step; 26 | } 27 | 28 | void ProgressMonitor::setStep(double stepValue) { 29 | step = stepValue; 30 | checkAbort(); 31 | } 32 | 33 | void ProgressMonitor::checkAbort() { 34 | if (LibraryData::API()->AbortQ() != 0) { 35 | ErrorManager::throwException(ErrorName::Aborted); 36 | } 37 | } 38 | 39 | ProgressMonitor& ProgressMonitor::operator++() { 40 | set(sharedIndicator[0] + step); 41 | return *this; 42 | } 43 | 44 | ProgressMonitor& ProgressMonitor::operator+=(double progress) { 45 | set(sharedIndicator[0] + progress); 46 | return *this; 47 | } 48 | 49 | ProgressMonitor& ProgressMonitor::operator--() { 50 | set(sharedIndicator[0] - step); 51 | return *this; 52 | } 53 | 54 | ProgressMonitor& ProgressMonitor::operator-=(double progress) { 55 | set(sharedIndicator[0] - progress); 56 | return *this; 57 | } 58 | 59 | } // namespace LLU -------------------------------------------------------------------------------- /src/TypedMArgument.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file TypedMArgument.cpp 3 | * @author Rafal Chojna 4 | * @date April 28, 2020 5 | * @brief 6 | */ 7 | 8 | #include "LLU/TypedMArgument.h" 9 | 10 | #include "LLU/Containers/Generic/DataStore.hpp" 11 | #include "LLU/Containers/Generic/Image.hpp" 12 | #include "LLU/Containers/Generic/NumericArray.hpp" 13 | #include "LLU/Containers/Generic/Tensor.hpp" 14 | #include "LLU/Containers/Generic/DataVector.hpp" 15 | 16 | namespace LLU::Argument { 17 | 18 | TypedArgument fromMArgument(MArgument m, MArgumentType t) { 19 | switch (t) { 20 | case MArgumentType::MArgument: break; 21 | case MArgumentType::Boolean: return static_cast(MArgument_getBoolean(m)); 22 | case MArgumentType::Integer: return MArgument_getInteger(m); 23 | case MArgumentType::Real: return MArgument_getReal(m); 24 | case MArgumentType::Complex: { 25 | auto* mc = MArgument_getComplexAddress(m); 26 | return std::complex {mc->ri[0], mc->ri[1]}; 27 | } 28 | case MArgumentType::Tensor: return GenericTensor {MArgument_getMTensor(m), Ownership::LibraryLink}; 29 | case MArgumentType::SparseArray: return GenericSparseArray {MArgument_getMSparseArray(m), Ownership::LibraryLink}; 30 | case MArgumentType::NumericArray: return GenericNumericArray {MArgument_getMNumericArray(m), Ownership::LibraryLink}; 31 | case MArgumentType::Image: return GenericImage {MArgument_getMImage(m), Ownership::LibraryLink}; 32 | case MArgumentType::UTF8String: return std::string_view {MArgument_getUTF8String(m)}; 33 | //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): c-style cast used in a macro in WolframIOLibraryFunctions.h 34 | case MArgumentType::DataStore: return GenericDataList {MArgument_getDataStore(m), Ownership::LibraryLink}; 35 | case MArgumentType::TabularColumn: return DataVector {MArgument_getTabularColumn(m), Ownership::LibraryLink}; 36 | } 37 | ErrorManager::throwException(ErrorName::TypeError); 38 | } 39 | 40 | void toMArgument(const TypedArgument& tma, MArgument& res) { 41 | switch (static_cast(tma.index())) { 42 | case MArgumentType::MArgument: ErrorManager::throwException(ErrorName::TypeError); 43 | case MArgumentType::Boolean: MArgument_setBoolean(res, *std::get_if(&tma)); break; 44 | case MArgumentType::Integer: MArgument_setInteger(res, *std::get_if(&tma)); break; 45 | case MArgumentType::Real: MArgument_setReal(res, *std::get_if(&tma)); break; 46 | case MArgumentType::Complex: { 47 | auto c = *std::get_if>(&tma); 48 | mcomplex mc {c.real(), c.imag()}; 49 | MArgument_setComplex(res, mc); 50 | break; 51 | } 52 | case MArgumentType::Tensor: MArgument_setMTensor(res, std::get_if(&tma)->abandonContainer()); break; 53 | case MArgumentType::SparseArray: MArgument_setMSparseArray(res, std::get_if(&tma)->abandonContainer()); break; 54 | case MArgumentType::NumericArray: MArgument_setMNumericArray(res, std::get_if(&tma)->abandonContainer()); break; 55 | case MArgumentType::Image: MArgument_setMImage(res, std::get_if(&tma)->abandonContainer()); break; 56 | // NOLINTNEXTLINE(cppcoreguidelines-pro-type-const-cast): LibraryLink will not modify the string, so const_cast is safe here 57 | case MArgumentType::UTF8String: MArgument_setUTF8String(res, const_cast(std::get_if(&tma)->data())); break; 58 | //NOLINTNEXTLINE(cppcoreguidelines-pro-type-cstyle-cast): c-style cast used in a macro in WolframIOLibraryFunctions.h 59 | case MArgumentType::DataStore: MArgument_setDataStore(res, std::get_if(&tma)->abandonContainer()); break; 60 | case MArgumentType::TabularColumn: MArgument_setTabularColumn(res, std::get_if(&tma)->abandonContainer()); break; 61 | } 62 | } 63 | } // namespace LLU::Argument -------------------------------------------------------------------------------- /src/WSTP/Release.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Release.cpp 3 | * @date Nov 28, 2017 4 | * @author Rafal Chojna 5 | * @brief Implementation file with classes responsible for releasing memory allocated by WSTP when receiving data. 6 | */ 7 | #ifndef _WIN32 8 | 9 | #include "LLU/WSTP/Release.h" 10 | 11 | namespace LLU::WS { 12 | template<> 13 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger8Array; 14 | 15 | template<> 16 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger8List; 17 | 18 | template<> 19 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger16Array; 20 | 21 | template<> 22 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger16List; 23 | 24 | template<> 25 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger32Array; 26 | 27 | template<> 28 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger32List; 29 | 30 | template<> 31 | ReleaseArray::Func ReleaseArray::Release = WSReleaseInteger64Array; 32 | 33 | template<> 34 | ReleaseList::Func ReleaseList::Release = WSReleaseInteger64List; 35 | 36 | template<> 37 | ReleaseArray::Func ReleaseArray::Release = WSReleaseReal32Array; 38 | 39 | template<> 40 | ReleaseList::Func ReleaseList::Release = WSReleaseReal32List; 41 | 42 | template<> 43 | ReleaseArray::Func ReleaseArray::Release = WSReleaseReal64Array; 44 | 45 | template<> 46 | ReleaseList::Func ReleaseList::Release = WSReleaseReal64List; 47 | 48 | } // namespace LLU::WS 49 | 50 | #endif 51 | -------------------------------------------------------------------------------- /src/WSTP/Strings.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Strings.cpp 3 | * @date Mar 23, 2018 4 | * @author Rafal Chojna 5 | * @brief Implementation file for all the functionality related to Strings in WSTP 6 | */ 7 | 8 | #include "LLU/WSTP/Strings.h" 9 | 10 | #include 11 | #include 12 | 13 | namespace LLU::WS { 14 | 15 | /// Definitions of configuration parameters, see header file for detailed description 16 | namespace EncodingConfig { 17 | /// Default substitute code is 26, which is an ascii character called "Substitute" 18 | constexpr long defaultSubstituteCode = 26; 19 | 20 | long substituteCodeForByteEncoding = defaultSubstituteCode; 21 | bool useFastUTF8 = true; 22 | } // namespace EncodingConfig 23 | 24 | #ifndef _WIN32 25 | template<> 26 | GetStringFuncT> String::Get = [](WSLINK m, const char** strData, int* len, int* charCnt) { 27 | *len = *charCnt = -1; 28 | return WSGetString(m, strData); 29 | }; 30 | template<> 31 | PutStringFuncT> String::Put = [](WSLINK m, const char* strData, int /*length*/) { 32 | return WSPutString(m, strData); 33 | }; 34 | 35 | template<> 36 | ReleaseStringFuncT> String::Release = [](WSLINK m, const char* strData, int /*length*/) { 37 | WSReleaseString(m, strData); 38 | }; 39 | template<> 40 | const std::string String::GetFName = "WSGetString"; 41 | template<> 42 | const std::string String::PutFName = "WSPutString"; 43 | 44 | template<> 45 | GetStringFuncT> String::Get = [](WSLINK m, const unsigned char** strData, int* len, int* charCnt) { 46 | *charCnt = -1; 47 | return WSGetByteString(m, strData, len, EncodingConfig::substituteCodeForByteEncoding); 48 | }; 49 | template<> 50 | PutStringFuncT> String::Put = WSPutByteString; 51 | template<> 52 | ReleaseStringFuncT> String::Release = WSReleaseByteString; 53 | template<> 54 | const std::string String::GetFName = "WSGetByteString"; 55 | template<> 56 | const std::string String::PutFName = "WSPutByteString"; 57 | 58 | template<> 59 | GetStringFuncT> String::Get = WSGetUTF8String; 60 | template<> 61 | PutStringFuncT> String::Put = [](WSLINK m, const unsigned char* strData, int len) { 62 | constexpr unsigned char maxValidASCII = 127; 63 | std::span strView {strData, static_cast(len)}; 64 | if (EncodingConfig::useFastUTF8 && std::all_of(strView.begin(), strView.end(), [&](unsigned char strChar) { return strChar <= maxValidASCII; })) { 65 | return WSPutByteString(m, strData, len); 66 | } 67 | return WSPutUTF8String(m, strData, len); 68 | }; 69 | template<> 70 | ReleaseStringFuncT> String::Release = WSReleaseUTF8String; 71 | template<> 72 | const std::string String::GetFName = "WSGetUTF8String"; 73 | template<> 74 | const std::string String::PutFName = "WSPut(UTF8/Byte)String"; 75 | 76 | template<> 77 | GetStringFuncT> String::Get = WSGetUTF16String; 78 | template<> 79 | PutStringFuncT> String::Put = WSPutUTF16String; 80 | template<> 81 | ReleaseStringFuncT> String::Release = WSReleaseUTF16String; 82 | template<> 83 | const std::string String::GetFName = "WSGetUTF16String"; 84 | template<> 85 | const std::string String::PutFName = "WSPutUTF16String"; 86 | 87 | template<> 88 | GetStringFuncT> String::Get = [](WSLINK m, const unsigned short** strData, int* len, int* charCnt) { 89 | *charCnt = -1; 90 | return WSGetUCS2String(m, strData, len); 91 | }; 92 | template<> 93 | PutStringFuncT> String::Put = WSPutUCS2String; 94 | template<> 95 | ReleaseStringFuncT> String::Release = WSReleaseUCS2String; 96 | template<> 97 | const std::string String::GetFName = "WSGetUCS2String"; 98 | template<> 99 | const std::string String::PutFName = "WSPutUCS2String"; 100 | 101 | template<> 102 | GetStringFuncT> String::Get = [](WSLINK m, const unsigned int** strData, int* len, int* charCnt) { 103 | *charCnt = -1; 104 | return WSGetUTF32String(m, strData, len); 105 | }; 106 | template<> 107 | PutStringFuncT> String::Put = WSPutUTF32String; 108 | template<> 109 | ReleaseStringFuncT> String::Release = WSReleaseUTF32String; 110 | template<> 111 | const std::string String::GetFName = "WSGetUTF32String"; 112 | template<> 113 | const std::string String::PutFName = "WSPutUTF32String"; 114 | #endif 115 | 116 | } /* namespace LLU::WS */ 117 | -------------------------------------------------------------------------------- /src/WSTP/Utilities.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file 3 | * @date Nov 26, 2017 4 | * @author Rafal Chojna 5 | * @brief Implementation file with miscellaneous definitions used throughout the WSTP-related part of LibraryLinkUtilities 6 | */ 7 | #include "LLU/WSTP/Utilities.h" 8 | 9 | #include "LLU/ErrorLog/ErrorManager.h" 10 | 11 | namespace LLU::WS { 12 | 13 | const std::string& Symbol::getHead() const { 14 | return head; 15 | } 16 | 17 | void Symbol::setHead(std::string h) { 18 | head = std::move(h); 19 | } 20 | 21 | int Function::getArgc() const { 22 | return argc; 23 | } 24 | 25 | void Function::setArgc(int newArgc) { 26 | argc = newArgc; 27 | } 28 | 29 | namespace Detail { 30 | namespace { 31 | std::string getWSErrorText(WSLINK mlp) { 32 | std::string err = "Error code reported by WSTP: " + std::to_string(WSError(mlp)) + "\n"; 33 | const auto* mlErrorMsg = WSErrorMessage(mlp); 34 | if (mlErrorMsg) { 35 | err += "\"" + std::string(mlErrorMsg) + "\""; 36 | WSReleaseErrorMessage(mlp, mlErrorMsg); 37 | } 38 | WSClearError(mlp); 39 | return err; 40 | } 41 | } // namespace 42 | 43 | void checkError(WSLINK m, int statusOk, const std::string& errorName, const std::string& debugInfo) { 44 | if (statusOk == 0) { 45 | ErrorManager::throwExceptionWithDebugInfo(errorName, getWSErrorText(m) + "\nDebug info: " + debugInfo); 46 | } 47 | } 48 | 49 | void throwLLUException(const std::string& errorName, const std::string& debugInfo) { 50 | ErrorManager::throwExceptionWithDebugInfo(errorName, debugInfo); 51 | } 52 | 53 | WSLINK getNewLoopback(WSLINK m) { 54 | int err = 0; 55 | auto* loopback = WSLoopbackOpen(WSLinkEnvironment(m), &err); 56 | if (loopback == nullptr || err != WSEOK) { 57 | ErrorManager::throwExceptionWithDebugInfo(ErrorName::WSCreateLoopbackError, "Error code: " + std::to_string(err)); 58 | } 59 | return loopback; 60 | } 61 | 62 | int countExpressionsInLoopbackLink(WSLINK& lpbckLink) { 63 | auto* helperLink = getNewLoopback(lpbckLink); 64 | int exprCnt = 0; 65 | while (WSTransferExpression(helperLink, lpbckLink) != 0) { 66 | exprCnt++; 67 | } 68 | WSClose(lpbckLink); 69 | lpbckLink = helperLink; 70 | return exprCnt; 71 | } 72 | } // namespace Detail 73 | 74 | } // namespace LLU::WS 75 | -------------------------------------------------------------------------------- /tests/Demo/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ###### 3 | ###### LLU Demo Project Configuration File 4 | ###### 5 | ################################################################################ 6 | 7 | cmake_minimum_required(VERSION 3.15.0) 8 | 9 | project(Demo 10 | VERSION 0.0.1 11 | DESCRIPTION "Demo paclet that shows how to use LibraryLinkUtilities." 12 | LANGUAGES CXX 13 | ) 14 | 15 | # By default install to the build directory 16 | if (CMAKE_INSTALL_PREFIX_INITIALIZED_TO_DEFAULT) 17 | set(CMAKE_INSTALL_PREFIX "${CMAKE_CURRENT_BINARY_DIR}" CACHE PATH "Demo paclet install prefix" FORCE) 18 | endif () 19 | 20 | #============================================= 21 | #=============== FIND LLU ==================== 22 | #============================================= 23 | 24 | set(LLU_ROOT "${CMAKE_CURRENT_SOURCE_DIR}/../../install" CACHE PATH "Location of LLU installation.") 25 | 26 | find_package(LLU 3.2.3 REQUIRED NO_MODULE PATH_SUFFIXES LLU) 27 | 28 | #============================================= 29 | #=========== MAIN PACLET LIBRARY ============= 30 | #============================================= 31 | 32 | # define source files 33 | set(DEMO_SOURCE_DIR ${CMAKE_CURRENT_SOURCE_DIR}/Sources/) 34 | set(DEMO_SOURCE_FILES ${DEMO_SOURCE_DIR}/demo.cpp) 35 | 36 | #add the main library 37 | add_library(Demo SHARED ${DEMO_SOURCE_FILES}) 38 | 39 | #in paclets we usually skip the "lib" prefix to get the same library name on all platforms 40 | set_property(TARGET Demo PROPERTY PREFIX "") 41 | 42 | set_target_properties(Demo PROPERTIES 43 | CXX_STANDARD 17 44 | CXX_STANDARD_REQUIRED YES 45 | CXX_EXTENSIONS NO 46 | CXX_VISIBILITY_PRESET hidden 47 | ) 48 | 49 | # set compile options 50 | # here we use a utility function from cmake/Wolfram/Common.cmake which sets a basic set of compiler flags in a cross-platform way 51 | set_default_compile_options(Demo O2) 52 | 53 | # Uncomment the line below if you built LLU on Windows with static linking to the Microsoft CRT 54 | # set_windows_static_runtime(Demo) 55 | 56 | # link to LLU 57 | target_link_libraries(Demo PRIVATE LLU::LLU) 58 | 59 | #============================================= 60 | #=========== INSTALL PACLET ================== 61 | #============================================= 62 | 63 | # run the install target to create a complete paclet directory structure, this will not create a .paclet file 64 | # LLU_LOCATION is required for this step because we need to copy LibraryLinkUtilities.wl file to the paclet files 65 | install_paclet_files( 66 | TARGET Demo 67 | LLU_LOCATION ${LLU_ROOT} 68 | # INSTALL_TO_LAYOUT <- uncomment to have CMake install all paclet files to the SystemFiles/Links directory of your Mathematica installation 69 | ) 70 | 71 | # Create a CMake target called "paclet" which will take the directory structure created by the "install" target and turn it into a proper .paclet file. 72 | # It can optionally validate paclet contents, run a test file or install paclet to a directory where Mathematica can automatically find it. 73 | # Requires Mathematica 12.1+. 74 | add_paclet_target(paclet # target name 75 | NAME Demo # paclet name 76 | VERIFY # verify contents of the .paclet file 77 | INSTALL # install to the user paclet directory 78 | TEST_FILE Tests/test.wl # run tests (test.wl may contain arbitrary WL code, not necessarily MUnit` tests) 79 | ) -------------------------------------------------------------------------------- /tests/Demo/Demo/Kernel/Demo.wl: -------------------------------------------------------------------------------- 1 | BeginPackage["Demo`"]; 2 | 3 | CaesarCipherEncode::usage = "CaesarCipherEncode[message_String, shift_Integer] encodes given message by shifting every character by shift positions in the \ 4 | English alphabet."; 5 | 6 | CaesarCipherDecode::usage = "CaesarCipherDecode[cipherText_String, shift_Integer] restores the original message encoded with Caesar's cipher given \ 7 | the encoded text and the shift."; 8 | 9 | Begin["`Private`"]; 10 | 11 | $BaseDemoDirectory = FileNameDrop[$InputFileName, -2]; 12 | Get[FileNameJoin[{$BaseDemoDirectory, "LibraryResources", "LibraryLinkUtilities.wl"}]]; 13 | 14 | `LLU`InitializePacletLibrary["Demo"]; 15 | 16 | `LLU`LazyPacletFunctionSet @@@ { 17 | {CaesarCipherEncode, "CaesarCipherEncode", {String, Integer}, String}, 18 | {CaesarCipherDecode, "CaesarCipherDecode", {String, Integer}, String} 19 | }; 20 | 21 | End[]; 22 | 23 | EndPackage[]; -------------------------------------------------------------------------------- /tests/Demo/Demo/PacletInfo.wl: -------------------------------------------------------------------------------- 1 | (* PacletInfo file template 2 | * Contains placeholders (@xxx@) that will be replaced at configuration time by CMake (in the function called install_paclet_files). 3 | * The PacletInfo.wl file will be then copied to the final paclet layout with proper values substituted. 4 | *) 5 | Paclet[ 6 | "Name" -> "@CMAKE_PROJECT_NAME@", 7 | "Version" -> "@CMAKE_PROJECT_VERSION@", 8 | "Description" -> "@CMAKE_PROJECT_DESCRIPTION@", 9 | "WolframVersion" -> "12.0+", 10 | "Updating" -> Automatic, 11 | "Extensions" -> { 12 | {"Kernel", Root -> "Kernel", Context -> "@CMAKE_PROJECT_NAME@`"}, 13 | {"LibraryLink"} 14 | } 15 | ] -------------------------------------------------------------------------------- /tests/Demo/Sources/demo.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file demo.cpp 3 | * @author Rafal Chojna 4 | * @brief 5 | */ 6 | 7 | #include 8 | 9 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { 10 | try { 11 | LLU::LibraryData::setLibraryData(libData); 12 | LLU::ErrorManager::registerPacletErrors({ 13 | {"InvalidCharacterError", "Message \"`m`\" contains non-ASCII character(s)."}, 14 | {"NegativeShiftError", "Requested negative shift `s`."}, 15 | }); 16 | 17 | } catch (const LLU::LibraryLinkError& e) { 18 | return e.which(); 19 | } 20 | return LLU::ErrorCode::NoError; 21 | } 22 | 23 | namespace { 24 | constexpr std::string_view alphabet {"ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"}; 25 | } 26 | 27 | std::string performCaesarCipher(const std::string& message, mint shift) { 28 | if (shift < 0) { 29 | LLU::ErrorManager::throwException("NegativeShiftError", shift); 30 | } 31 | 32 | auto resultStr {message}; 33 | for (auto& c : resultStr) { 34 | auto index = alphabet.find(c); 35 | if (index == std::string::npos) { 36 | LLU::ErrorManager::throwException("InvalidCharacterError", message); 37 | } 38 | c = alphabet[(index + shift) % alphabet.length()]; 39 | } 40 | return resultStr; 41 | } 42 | 43 | EXTERN_C DLLEXPORT int CaesarCipherEncode(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) { 44 | try { 45 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 46 | auto str = mngr.getString(0); 47 | const auto shift = mngr.getInteger(1); 48 | auto result = performCaesarCipher(str, shift); 49 | mngr.set(result); 50 | } catch (const LLU::LibraryLinkError& e) { 51 | return e.which(); 52 | } 53 | return LLU::ErrorCode::NoError; 54 | } 55 | 56 | EXTERN_C DLLEXPORT int CaesarCipherDecode(WolframLibraryData libData, mint Argc, MArgument *Args, MArgument Res) { 57 | try { 58 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 59 | auto str = mngr.getString(0); 60 | const auto shift = mngr.getInteger(1); 61 | auto result = performCaesarCipher(str, alphabet.length() - shift); 62 | mngr.set(result); 63 | } catch (const LLU::LibraryLinkError& e) { 64 | return e.which(); 65 | } 66 | return LLU::ErrorCode::NoError; 67 | } -------------------------------------------------------------------------------- /tests/Demo/Tests/test.wl: -------------------------------------------------------------------------------- 1 | Needs["Demo`"]; 2 | Needs["MUnit`"]; 3 | 4 | $tests = Hold @ { 5 | Test[ 6 | Demo`CaesarCipherDecode::usage 7 | , 8 | "CaesarCipherDecode[cipherText_String, shift_Integer] restores the original message encoded with Caesar's cipher given the encoded text and the shift." 9 | ], 10 | 11 | Test[ 12 | Demo`CaesarCipherDecode[Demo`CaesarCipherEncode["HelloWorld", 3], 3] 13 | , 14 | "HelloWorld" 15 | ], 16 | 17 | Test[ 18 | Demo`CaesarCipherDecode[Demo`CaesarCipherEncode["HelloWorld", 25], 25] 19 | , 20 | "HelloWorld" 21 | ], 22 | 23 | VerificationTest[ 24 | f = Catch[Demo`CaesarCipherEncode["HelloWorld", -1], "LLUExceptionTag"]; 25 | FailureQ[f] && First[f] === "NegativeShiftError" 26 | ], 27 | 28 | VerificationTest[ 29 | f = Catch[Demo`CaesarCipherEncode["HelloWorld\[HappySmiley]", 2], "LLUExceptionTag"]; 30 | FailureQ[f] && First[f] === "InvalidCharacterError" 31 | ] 32 | }; 33 | 34 | TestRun[ 35 | Evaluate @ $tests, 36 | Loggers :> {VerbosePrintLogger[]}, 37 | TestRunTitle -> "Basic test suite of the Demo paclet", 38 | MemoryConstraint -> Quantity[100, "Megabytes"], 39 | TimeConstraint -> Quantity[60, "Seconds"] 40 | ] 41 | -------------------------------------------------------------------------------- /tests/UnitTests/.clang-tidy: -------------------------------------------------------------------------------- 1 | --- 2 | # Same checks as in the root directory with a few more inspections disabled for the specific needs of unit tests 3 | Checks: > 4 | -*, 5 | bugprone-*, 6 | cert-*, 7 | -cert-dcl21-cpp, 8 | -cert-err58-cpp, 9 | -cert-oop11-cpp, 10 | cppcoreguidelines-*, 11 | -cppcoreguidelines-avoid-c-arrays, 12 | -cppcoreguidelines-avoid-magic-numbers, 13 | -cppcoreguidelines-non-private-member-variables-in-classes, 14 | -cppcoreguidelines-owning-memory, 15 | -cppcoreguidelines-pro-bounds-constant-array-index, 16 | google-*, 17 | -google-readability-braces-around-statements, 18 | -google-runtime-references, 19 | -google-runtime-int, 20 | hicpp-avoid-goto 21 | hicpp-exception-baseclass 22 | hicpp-multiway-paths-covered 23 | hicpp-no-assembler 24 | hicpp-signed-bitwise 25 | llvm-*, 26 | -llvm-namespace-comment, 27 | -llvm-qualified-auto, 28 | misc-*, 29 | modernize-*, 30 | -modernize-avoid-c-arrays, 31 | -modernize-use-nodiscard, 32 | -modernize-use-trailing-return-type, 33 | performance-*, 34 | portability-*, 35 | readability-*, 36 | -readability-magic-numbers, 37 | -readability-redundant-access-specifiers 38 | WarningsAsErrors: false 39 | AnalyzeTemporaryDtors: false 40 | CheckOptions: 41 | - key: cppcoreguidelines-macro-usage.CheckCapsOnly 42 | value: '1' 43 | - key: misc-non-private-member-variables-in-classes.IgnoreClassesWithAllMemberVariablesBeingPublic 44 | value: '1' 45 | - key: cppcoreguidelines-special-member-functions.AllowSoleDefaultDtor 46 | value: '1' 47 | - key: performance-unnecessary-value-param.AllowedTypes 48 | value: 'MArrayDimensions' 49 | - key: readability-implicit-bool-conversion.AllowPointerConditions 50 | value: '1' 51 | - key: readability-uppercase-literal-suffix.NewSuffixes 52 | value: 'L;LL;LU;LLU' 53 | ... 54 | -------------------------------------------------------------------------------- /tests/UnitTests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | ################################################################################ 2 | ###### 3 | ###### LLU unit tests CMake Configuration File 4 | ###### 5 | ###### Author: Rafal Chojna - rafalc@wolfram.com 6 | ################################################################################# 7 | 8 | message(STATUS "Creating test targets.") 9 | 10 | # Unit tests are split into independent modules. You can run them separately, for example: 11 | # 12 | # ctest -R WSTP 13 | # 14 | # or all at once: 15 | # 16 | # ctest (or make test) 17 | # 18 | # One helpful option for ctest is --verbose. It shows more output from wolframscript. 19 | 20 | set(TEST_MODULES 21 | "Async" 22 | "DataList" 23 | "DataVector" 24 | "ErrorReporting" 25 | "GenericContainers" 26 | "Image" 27 | "ManagedExpressions" 28 | "MArgumentManager" 29 | "NumericArray" 30 | "Scalar" 31 | "SparseArray" 32 | "String" 33 | "Tensor" 34 | "Utilities" 35 | "WSTP" 36 | ) 37 | 38 | 39 | find_package(WolframLanguage 12.0 COMPONENTS wolframscript) 40 | if(WolframLanguage_wolframscript_EXE) 41 | message(STATUS "Unit tests will be run with: ${WolframLanguage_wolframscript_EXE}") 42 | set(_WL_KERNEL_EXE ${WolframLanguage_wolframscript_EXE}) 43 | set(_WL_KERNEL_OPTION "-code") 44 | elseif(WolframLanguage_EXE) 45 | message(STATUS "Unit tests will be run with: ${WolframLanguage_EXE}") 46 | set(_WL_KERNEL_EXE ${WolframLanguage_EXE}) 47 | set(_WL_KERNEL_OPTION "-run") 48 | else() 49 | message(STATUS "Could not find any instance of the WolframLanguage Kernel. Unit tests target will not be generated.") 50 | return() 51 | endif() 52 | 53 | get_target_property(LLU_CRT_LINKING LLU MSVC_RUNTIME_LIBRARY) 54 | if(NOT LLU_CRT_LINKING) 55 | set(LLU_CRT_LINKING ${CMAKE_MSVC_RUNTIME_LIBRARY}) 56 | endif() 57 | 58 | foreach(UnitTest ${TEST_MODULES}) 59 | set(MODULE_CODE 60 | [===[ 61 | Needs["MUnit`"]; 62 | $LLUInstallDir = "${CMAKE_INSTALL_PREFIX}"; 63 | $CRTLinkingMode = "${LLU_CRT_LINKING}"; 64 | report = Catch[ 65 | TestReport["${UnitTest}TestSuite.mt", MemoryConstraint -> Quantity[500, "MB"], TimeConstraint -> 30], 66 | _String, 67 | Function[{value, tag}, Print["ERROR: " <> ToString[value]]; Exit[1]] 68 | ]; 69 | indexKeyword = If[$VersionNumber >= 13, "Keys", "Indices"]; 70 | propertiesToPrint = {"Title", "Aborted"}; 71 | failedCount = 0; 72 | Scan[ 73 | If[report["TestsFailed" <> # <> indexKeyword] != {}, 74 | AppendTo[propertiesToPrint, "TestsFailed" <> # <> indexKeyword]; 75 | failedCount += Length @ report["TestsFailed" <> # <> indexKeyword]; 76 | ]&, {"WrongResults", "WithMessages", "WithErrors"} 77 | ]; 78 | If[failedCount > 0, 79 | If[#["Outcome"] =!= "Success", 80 | Print @ TextString[#, AssociationFormat -> {"{\n\t", ",\n\t", "\n}", ": "}] 81 | ]& /@ report["TestResults"]; 82 | ]; 83 | Print[TextString[AssociationMap[report, propertiesToPrint], AssociationFormat -> {"{\n\t", ",\n\t", "\n}", ": "}]]; 84 | exitCode = Boole[failedCount > 0]; 85 | Exit[exitCode] 86 | ]===]) 87 | string(REGEX REPLACE "[\t\r\n]+" "" MODULE_CODE "${MODULE_CODE}") 88 | string(CONFIGURE "${MODULE_CODE}" MODULE_CODE) 89 | 90 | add_test(NAME ${UnitTest} 91 | WORKING_DIRECTORY "${CMAKE_CURRENT_LIST_DIR}/${UnitTest}" 92 | COMMAND ${_WL_KERNEL_EXE} ${_WL_KERNEL_OPTION} "${MODULE_CODE}" 93 | ) 94 | endforeach() 95 | 96 | -------------------------------------------------------------------------------- /tests/UnitTests/DataList/TestSources/DataListCompilationErrors.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file DataListCompilationErrors.cpp 3 | * @author Rafal Chojna 4 | * @date 07.09.18 5 | * @brief Source code for DataList unit tests containing functions that should fail at compile stage. 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | LLU_LIBRARY_FUNCTION(WrongNodeType) { 13 | auto dsIn = mngr.getDataList(0); 14 | dsIn.push_back(LLU::Tensor {2, 3, 4, 5, 6}); // OK 15 | dsIn.push_back(3.14); // compile time error - "Trying to add DataList node of incorrect type." 16 | mngr.setDataList(dsIn); 17 | } 18 | 19 | LIBRARY_LINK_FUNCTION(TryToAddMArgument) { 20 | using namespace LLU; 21 | MArgumentManager mngr {Argc, Args, Res}; 22 | auto dsIn = mngr.getDataList(0); 23 | 24 | PrimitiveWrapper::addDataStoreNode(dsIn.getContainer(), "", Args[0]); // compile time error - use of deleted function 25 | 26 | mngr.setDataList(dsIn); 27 | return ErrorCode::NoError; 28 | } 29 | 30 | LLU_LIBRARY_FUNCTION(AddMTensorByType) { 31 | auto dsIn = mngr.getGenericDataList(0); 32 | dsIn.push_back(LLU::Tensor {2, 3, 4, 5, 6}); // OK 33 | 34 | auto* rawMTensor = LLU::Tensor {2, 3, 4, 5, 6}.abandonContainer(); 35 | dsIn.push_back(rawMTensor); // OK 36 | dsIn.push_back(rawMTensor); // static assert failure 37 | 38 | auto* rawMNumericArray = LLU::NumericArray {2, 3, 4, 5, 6}.abandonContainer(); 39 | dsIn.push_back(rawMNumericArray); // static assert failure 40 | mngr.set(dsIn); 41 | } -------------------------------------------------------------------------------- /tests/UnitTests/ErrorReporting/TestSources/LoggerTest.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file LoggerTest.cpp 3 | * @brief Unit tests for Logger 4 | */ 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | using LLU::LibraryLinkError; 16 | namespace LLErrorCode = LLU::ErrorCode; 17 | 18 | LIBRARY_LINK_FUNCTION(GreaterAt) { 19 | LLU_DEBUG("Library function entered with ", Argc, " arguments."); 20 | auto err = LLErrorCode::NoError; 21 | try { 22 | LLU_DEBUG("Starting try-block, current error code: ", err); 23 | LLU::MArgumentManager mngr(Argc, Args, Res); 24 | auto fileName = mngr.getString(0); 25 | if (fileName.find(':') != std::string::npos) { 26 | LLU_WARNING("File name ", fileName, " contains a possibly problematic character \":\"."); 27 | } 28 | LLU_DEBUG("Input tensor is of type: ", mngr.getTensorType(1)); 29 | if (mngr.getTensorType(1) == MType_Complex) { 30 | LLU_ERROR("Input tensor contains complex numbers which is not supported"); 31 | mngr.setBoolean(false); 32 | return err; 33 | } 34 | auto t = mngr.getTensor(1); 35 | auto index1 = mngr.getInteger(2); 36 | auto index2 = mngr.getInteger(3); 37 | if (index1 <= 0 || index2 <= 0) { 38 | LLU::ErrorManager::throwExceptionWithDebugInfo(LLU::ErrorName::TensorIndexError, 39 | "Indices (" + std::to_string(index1) + ", " + std::to_string(index2) + ") must be positive."); 40 | } 41 | LLU_DEBUG("Comparing ", t.at(index1 - 1), " with ", t.at(index2 - 1)); 42 | mngr.setBoolean(t.at(index1 - 1) > t.at(index2 - 1)); 43 | } catch (const LibraryLinkError& e) { 44 | LLU_ERROR("Caught LLU exception ", e.what(), ": ", e.debug()); 45 | err = e.which(); 46 | } catch (...) { 47 | err = LLErrorCode::FunctionError; 48 | } 49 | return err; 50 | } 51 | 52 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { 53 | LLU::LibraryData::setLibraryData(libData); 54 | return 0; 55 | } 56 | 57 | LIBRARY_LINK_FUNCTION(LogDemo) { 58 | LLU_DEBUG("Library function entered with ", Argc, " arguments."); 59 | auto err = LLErrorCode::NoError; 60 | try { 61 | LLU::MArgumentManager mngr(Argc, Args, Res); 62 | auto index = mngr.getInteger(0); 63 | if (index >= Argc) { 64 | LLU_WARNING("Index ", index, " is too big for the number of arguments: ", Argc, ". Changing to ", Argc - 1); 65 | index = Argc - 1; 66 | } 67 | auto value = mngr.getInteger(static_cast(index)); 68 | mngr.setInteger(value); 69 | } catch (const LibraryLinkError& e) { 70 | LLU_ERROR("Caught LLU exception ", e.what(), ": ", e.debug()); 71 | err = e.which(); 72 | } 73 | return err; 74 | } 75 | 76 | LIBRARY_LINK_FUNCTION(LogsFromThreads) { 77 | using namespace std::chrono_literals; 78 | auto err = LLErrorCode::NoError; 79 | try { 80 | std::random_device rd; // Will be used to obtain a seed for the random number engine 81 | std::mt19937 gen(rd()); 82 | std::uniform_int_distribution<> dis(10, 1000); 83 | 84 | LLU::MArgumentManager mngr(Argc, Args, Res); 85 | auto threadCount = mngr.getInteger(0); 86 | std::vector threads(threadCount); 87 | LLU_DEBUG("Starting ", threadCount, " threads."); 88 | for (int i = 0; i < threadCount; ++i) { 89 | threads[i] = std::thread( 90 | [i](int sleepTime) { 91 | LLU_DEBUG("Thread ", i, " going to sleep."); 92 | std::this_thread::sleep_for(std::chrono::milliseconds(sleepTime)); 93 | LLU_DEBUG("Thread ", i, " slept for ", sleepTime, "ms."); 94 | }, 95 | dis(gen)); 96 | } 97 | for (auto& t : threads) { 98 | if (t.joinable()) { 99 | t.join(); 100 | } 101 | } 102 | LLU_DEBUG("All threads joined."); 103 | } catch (const LibraryLinkError& e) { 104 | LLU_ERROR("Caught LLU exception ", e.what(), ": ", e.debug()); 105 | err = e.which(); 106 | } catch (...) { 107 | err = LLErrorCode::FunctionError; 108 | } 109 | return err; 110 | } -------------------------------------------------------------------------------- /tests/UnitTests/GenericContainers/GenericContainersTestSuite.mt: -------------------------------------------------------------------------------- 1 | (* Wolfram Language Test file *) 2 | TestRequirement[$VersionNumber > 10.3]; 3 | (***************************************************************************************************************************************) 4 | (* 5 | Set of test cases to test LLU functionality related to passing modes of different containers 6 | *) 7 | (***************************************************************************************************************************************) 8 | TestExecute[ 9 | Needs["CCompilerDriver`"]; 10 | currentDirectory = DirectoryName[$TestFileName]; 11 | 12 | (* Get configuration (path to LLU sources, compilation options, etc.) *) 13 | Get[FileNameJoin[{ParentDirectory[currentDirectory], "TestConfig.wl"}]]; 14 | 15 | (* Compile the test library *) 16 | lib = CCompilerDriver`CreateLibrary[FileNameJoin[{currentDirectory, "TestSources", #}]& /@ {"GenericContainersTest.cpp"}, 17 | "GenericContainers", options, "Defines" -> {"LLU_LOG_DEBUG"}]; 18 | 19 | Get[FileNameJoin[{$LLUSharedDir, "LibraryLinkUtilities.wl"}]]; 20 | 21 | `LLU`InitializePacletLibrary[lib]; 22 | 23 | Off[General::stop]; (* because we want to see all error messages from CreateLibrary *) 24 | 25 | OwnerAutomatic = `LLU`PacletFunctionLoad["IsOwnerAutomatic", {Image}, "Boolean"]; 26 | OwnerManual = `LLU`PacletFunctionLoad["IsOwnerManual", {{Integer, _, "Manual"}}, "Boolean"]; 27 | OwnerShared = `LLU`PacletFunctionLoad["IsOwnerShared", {{NumericArray, "Shared"}}, "Boolean"]; 28 | 29 | CloneAutomatic = `LLU`PacletFunctionLoad["CloneAutomatic", {Image}, Image]; 30 | CloneManual = `LLU`PacletFunctionLoad["CloneManual", {{Integer, _, "Manual"}}, {Integer, _}]; 31 | CloneShared = `LLU`PacletFunctionLoad["CloneShared", {{NumericArray, "Shared"}}, NumericArray]; 32 | 33 | MoveAutomatic = `LLU`PacletFunctionLoad["MoveAutomatic", {Image}, Image]; 34 | MoveManual = `LLU`PacletFunctionLoad["MoveManual", {{Integer, _, "Manual"}}, {Integer, _}]; 35 | MoveShared = `LLU`PacletFunctionLoad["MoveShared", {{NumericArray, "Shared"}}, NumericArray]; 36 | 37 | img = RandomImage[]; 38 | tensor = {1, 2, 3, 4, 5}; 39 | na = NumericArray[{5, 4, 3, 2, 1}, "UnsignedInteger16"]; 40 | ds = Developer`DataStore["x" -> img, "y" -> 3]; 41 | 42 | ClearAll[TestLogSymbol]; 43 | `LLU`Logger`PrintLogFunctionSelector := Block[{`LLU`Logger`FormattedLog = `LLU`Logger`LogToShortString}, 44 | `LLU`Logger`PrintLogToSymbol[TestLogSymbol][##] 45 | ]&; 46 | ]; 47 | 48 | 49 | (* Compile-time errors *) 50 | Test[ 51 | CCompilerDriver`CreateLibrary[{FileNameJoin[{currentDirectory, "TestSources", "PoliciesCompilationErrors.cpp"}]}, "PoliciesErrors", options] 52 | , 53 | $Failed 54 | , 55 | {CreateLibrary::cmperr..} 56 | , 57 | TestID -> "GenericContainersTestSuite-20190712-R8A2K9" 58 | ]; 59 | 60 | VerificationTest[ 61 | OwnerAutomatic[img] 62 | , 63 | TestID -> "GenericContainersTestSuite-20190724-V1C6L5" 64 | ]; 65 | 66 | VerificationTest[ 67 | OwnerManual[tensor] 68 | , 69 | TestID -> "GenericContainersTestSuite-20190724-V7L9Q7" 70 | ]; 71 | 72 | VerificationTest[ 73 | OwnerShared[na] 74 | , 75 | TestID -> "GenericContainersTestSuite-20190724-D2Q7F2" 76 | ]; 77 | 78 | Test[ 79 | CloneAutomatic[img] 80 | , 81 | img 82 | , 83 | TestID -> "GenericContainersTestSuite-20190724-L8K9W7" 84 | ]; 85 | 86 | Test[ 87 | CloneManual[tensor] 88 | , 89 | tensor 90 | , 91 | TestID -> "GenericContainersTestSuite-20190724-H0H3Y5" 92 | ]; 93 | 94 | Test[ 95 | CloneShared[na] 96 | , 97 | na 98 | , 99 | TestID -> "GenericContainersTestSuite-20190724-E8P2L4" 100 | ]; 101 | 102 | Test[ 103 | MoveAutomatic[img] 104 | , 105 | img 106 | , 107 | TestID -> "GenericContainersTestSuite-20190724-H0P0K2" 108 | ]; 109 | 110 | Test[ 111 | MoveManual[tensor] 112 | , 113 | {-324, 2, 3, 4, 5} 114 | , 115 | TestID -> "GenericContainersTestSuite-20190724-X7Z5N5" 116 | ]; 117 | 118 | Test[ 119 | MoveShared[na] 120 | , 121 | na 122 | , 123 | TestID -> "GenericContainersTestSuite-20190724-E4T3F7" 124 | ]; 125 | 126 | Test[ 127 | TestLogSymbol 128 | , 129 | { 130 | "(MoveAutomatic): Automatic arg owner: LibraryLink", 131 | "(MoveAutomatic): Automatic arg owner: LibraryLink, clone owner: LibraryLink", 132 | "(MoveAutomatic): Automatic arg owner: LibraryLink, clone owner: LibraryLink", 133 | "(MoveManual): Manual arg owner: Library", 134 | "(MoveManual): Manual arg owner: Library, clone owner: Library", 135 | "(MoveManual): Manual arg owner: Library, clone owner: LibraryLink", 136 | "(MoveShared): Shared arg owner: Shared", 137 | "(MoveShared): Shared arg owner: Shared, clone owner: Shared", 138 | "(MoveShared): Shared arg owner: Shared, clone owner: Shared" 139 | } 140 | , 141 | SameTest -> LoggerStringTest 142 | , 143 | TestID -> "GenericContainersTestSuite-20190906-W5T3O4" 144 | ]; -------------------------------------------------------------------------------- /tests/UnitTests/GenericContainers/TestSources/GenericContainersTest.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file PoliciesTest.cpp 3 | * @brief Unit tests for passing policies and related functionality 4 | */ 5 | 6 | #include 7 | #include 8 | #include 9 | 10 | using LLU::Ownership; 11 | using LLU::Passing; 12 | namespace ErrorCode = LLU::ErrorCode; 13 | 14 | std::string to_string(Ownership o) { 15 | switch (o) { 16 | case Ownership::LibraryLink: 17 | return "LibraryLink"; 18 | case Ownership::Library: 19 | return "Library"; 20 | case Ownership::Shared: 21 | return "Shared"; 22 | } 23 | return "Unknown"; 24 | } 25 | 26 | template 27 | bool isShared(const Container& c) noexcept { 28 | return c.getOwner() == Ownership::Shared; 29 | } 30 | 31 | template 32 | bool libraryOwnedQ(const Container& c) noexcept { 33 | return c.getOwner() == Ownership::Library; 34 | } 35 | 36 | template 37 | bool libraryLinkOwnedQ(const Container& c) noexcept { 38 | return c.getOwner() == Ownership::LibraryLink; 39 | } 40 | 41 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { 42 | LLU::LibraryData::setLibraryData(libData); 43 | return 0; 44 | } 45 | 46 | LIBRARY_LINK_FUNCTION(IsOwnerAutomatic) { 47 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 48 | auto img = mngr.getGenericImage(0); 49 | mngr.set(libraryLinkOwnedQ(img)); 50 | return ErrorCode::NoError; 51 | } 52 | 53 | LIBRARY_LINK_FUNCTION(IsOwnerManual) { 54 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 55 | auto t = mngr.getGenericTensor(0); 56 | mngr.set(libraryOwnedQ(t)); 57 | return ErrorCode::NoError; 58 | } 59 | 60 | LIBRARY_LINK_FUNCTION(IsOwnerShared) { 61 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 62 | auto na = mngr.getGenericNumericArray(0); 63 | mngr.set(isShared(na)); 64 | return ErrorCode::NoError; 65 | } 66 | 67 | LIBRARY_LINK_FUNCTION(CloneAutomatic) { 68 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 69 | auto img = mngr.getGenericImage(0); 70 | LLU::GenericImage clone {img.clone()}; 71 | mngr.set(clone); 72 | return (libraryLinkOwnedQ(img) && libraryLinkOwnedQ(clone)) ? ErrorCode::NoError : ErrorCode::MemoryError; 73 | } 74 | 75 | LIBRARY_LINK_FUNCTION(CloneManual) { 76 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 77 | auto t = mngr.getGenericTensor(0); 78 | LLU::GenericTensor clone {t.clone()}; 79 | LLU::Tensor tensor {std::move(t)}; 80 | tensor[0] = -1; 81 | mngr.set(clone); 82 | // NOLINTNEXTLINE(bugprone-use-after-move): deliberate use after move for testing purposes 83 | return (libraryOwnedQ(t) && libraryOwnedQ(tensor) && libraryLinkOwnedQ(clone)) ? ErrorCode::NoError : ErrorCode::MemoryError; 84 | } 85 | 86 | LIBRARY_LINK_FUNCTION(CloneShared) { 87 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 88 | auto na = mngr.getGenericNumericArray(0); 89 | LLU::GenericNumericArray clone {na.clone()}; 90 | mngr.set(clone); 91 | return (isShared(na) && libraryLinkOwnedQ(clone)) ? ErrorCode::NoError : ErrorCode::MemoryError; 92 | } 93 | 94 | LIBRARY_LINK_FUNCTION(MoveAutomatic) { 95 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 96 | auto img = mngr.getGenericImage(0); 97 | LLU_DEBUG("Automatic arg owner: ", to_string(img.getOwner())); 98 | LLU::GenericImage clone = std::move(img); // we can create Automatic container if we move from another Automatic 99 | LLU_DEBUG("Automatic arg owner: ", to_string(img.getOwner()), ", clone owner: ", to_string(clone.getOwner())); 100 | mngr.set(clone); 101 | LLU_DEBUG("Automatic arg owner: ", to_string(img.getOwner()), ", clone owner: ", to_string(clone.getOwner())); 102 | // NOLINTNEXTLINE(bugprone-use-after-move): deliberate use after move for testing purposes 103 | return (libraryLinkOwnedQ(img) && libraryLinkOwnedQ(clone)) ? ErrorCode::NoError : ErrorCode::MemoryError; 104 | } 105 | 106 | LIBRARY_LINK_FUNCTION(MoveManual) { 107 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 108 | auto t = mngr.getGenericTensor(0); 109 | LLU_DEBUG("Manual arg owner: ", to_string(t.getOwner())); 110 | LLU::Tensor tensor {std::move(t)}; 111 | tensor[0] = -324; 112 | LLU::GenericTensor clone = std::move(tensor); 113 | LLU_DEBUG("Manual arg owner: ", to_string(t.getOwner()), ", clone owner: ", to_string(clone.getOwner())); 114 | mngr.set(clone); 115 | LLU_DEBUG("Manual arg owner: ", to_string(t.getOwner()), ", clone owner: ", to_string(clone.getOwner())); 116 | // NOLINTNEXTLINE(bugprone-use-after-move): deliberate use after move for testing purposes 117 | return (libraryOwnedQ(t) && libraryOwnedQ(tensor) && libraryLinkOwnedQ(clone)) ? ErrorCode::NoError : ErrorCode::MemoryError; 118 | } 119 | 120 | LIBRARY_LINK_FUNCTION(MoveShared) { 121 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 122 | auto na = mngr.getGenericNumericArray(0); 123 | LLU_DEBUG("Shared arg owner: ", to_string(na.getOwner())); 124 | LLU::GenericNumericArray clone = std::move(na); 125 | LLU_DEBUG("Shared arg owner: ", to_string(na.getOwner()), ", clone owner: ", to_string(clone.getOwner())); 126 | mngr.set(clone); 127 | LLU_DEBUG("Shared arg owner: ", to_string(na.getOwner()), ", clone owner: ", to_string(clone.getOwner())); 128 | // NOLINTNEXTLINE(bugprone-use-after-move): deliberate use after move for testing purposes 129 | return (isShared(na) && isShared(clone)) ? ErrorCode::NoError : ErrorCode::MemoryError; 130 | } -------------------------------------------------------------------------------- /tests/UnitTests/GenericContainers/TestSources/PoliciesCompilationErrors.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file PoliciesCompilationErrors.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | using LLU::Passing; 10 | 11 | LIBRARY_LINK_FUNCTION(DisownManual) { 12 | LLU::NumericArray na {1, 2, 3, 4, 5}; 13 | na.disown(); 14 | return 0; 15 | } 16 | 17 | LIBRARY_LINK_FUNCTION(CopyAutomatic) { 18 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 19 | LLU::GenericTensor t = mngr.getGenericTensor(0); 20 | auto copy = t; 21 | return 0; 22 | } 23 | 24 | LIBRARY_LINK_FUNCTION(CopyShared) { 25 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 26 | auto t = mngr.getGenericImage(0); 27 | auto copy = t; 28 | return 0; 29 | } 30 | 31 | LIBRARY_LINK_FUNCTION(SharedDataStore) { 32 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 33 | auto ds = mngr.getGenericDataList(0); 34 | return 0; 35 | } 36 | 37 | LIBRARY_LINK_FUNCTION(MoveShared) { 38 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 39 | auto na = mngr.getGenericNumericArray(0); 40 | LLU::GenericNumericArray clone { std::move(na), LLU::Ownership::LibraryLink }; // cannot move Shared to Automatic 41 | return 0; 42 | } -------------------------------------------------------------------------------- /tests/UnitTests/Image/TestSources/EchoImage.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | #include 6 | 7 | LLU_LIBRARY_FUNCTION(EchoImage1) { 8 | mngr.operateOnImage(0, [&mngr](auto im1) { 9 | using T = typename std::remove_reference_t::value_type; 10 | auto im2 {std::move(im1)}; // test move constructor 11 | LLU::Image im3; 12 | im3 = std::move(im2); // test move assignment 13 | mngr.setImage(im3); 14 | }); 15 | } 16 | 17 | LLU_LIBRARY_FUNCTION(EchoImage2) { 18 | mngr.operateOnImage(0, [&mngr](auto&& in) { 19 | auto slices = in.is3D() ? in.slices() : 0; 20 | auto columns = in.columns(); 21 | auto rows = in.rows(); 22 | auto channels = in.channels(); 23 | auto colorspace = in.colorspace(); 24 | auto interleaving = in.interleavedQ(); 25 | 26 | using T = typename std::remove_reference_t::value_type; 27 | LLU::Image out(slices, columns, rows, channels, colorspace, interleaving); 28 | 29 | for (mint column = 1; column <= columns; column++) { 30 | for (mint row = 1; row <= rows; row++) { 31 | if (in.is3D()) { 32 | for (mint slice = 1; slice <= slices; slice++) { 33 | out.set(slice, row, column, 1, in.get(slice, row, column, 1)); 34 | } 35 | } else { 36 | out.set(row, column, 1, in.get(row, column, 1)); 37 | } 38 | } 39 | } 40 | mngr.setImage(out); 41 | }); 42 | } 43 | 44 | LLU_LIBRARY_FUNCTION(EchoImage3) { 45 | auto img = mngr.getGenericImage(0); 46 | mngr.set(img); 47 | } 48 | 49 | LLU_LIBRARY_FUNCTION(ConvertImageToByte) { 50 | mngr.operateOnImage(0, [&mngr](auto&& in) { 51 | auto out {in.template convert()}; 52 | mngr.setImage(out); 53 | }); 54 | } 55 | 56 | LLU_LIBRARY_FUNCTION(UnifyImageTypes) { 57 | mngr.operateOnImage(0, [&mngr](auto&& in) { 58 | using T = typename std::remove_reference_t::value_type; 59 | mngr.operateOnImage(1, [&mngr](auto&& in2) { 60 | LLU::Image out {in2.template convert()}; 61 | mngr.setImage(out); 62 | }); 63 | }); 64 | } 65 | 66 | LLU_LIBRARY_FUNCTION(CloneImage) { 67 | mngr.operateOnImage(0, [&mngr](auto&& im1) { 68 | using T = typename std::remove_reference_t::value_type; 69 | LLU::Image im2 {im1.clone()}; 70 | LLU::Image im3; 71 | im3 = im2.clone(); 72 | mngr.setImage(im3); 73 | }); 74 | } 75 | 76 | LLU_LIBRARY_FUNCTION(EmptyWrapper) { 77 | LLU::Unused(mngr); 78 | LLU::Image im {nullptr, LLU::Ownership::Library}; // this should trigger an exception 79 | } 80 | -------------------------------------------------------------------------------- /tests/UnitTests/Image/TestSources/ImageDimensions.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | using LLU::ImageView; 5 | 6 | LIBRARY_LINK_FUNCTION(ImageColumnCount) { 7 | auto err = LLU::ErrorCode::NoError; 8 | try { 9 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 10 | auto image = mngr.getGenericImage(0); 11 | mngr.setInteger(image.columns()); 12 | } catch (const LLU::LibraryLinkError& e) { 13 | err = e.which(); 14 | } catch (...) { 15 | err = LLU::ErrorCode::FunctionError; 16 | } 17 | return err; 18 | } 19 | 20 | LIBRARY_LINK_FUNCTION(ImageRank) { 21 | auto err = LLU::ErrorCode::NoError; 22 | try { 23 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 24 | mngr.operateOnImage(0, [&mngr](auto&& image) { mngr.setInteger(image.rank()); }); 25 | } catch (const LLU::LibraryLinkError& e) { 26 | err = e.which(); 27 | } catch (...) { 28 | err = LLU::ErrorCode::FunctionError; 29 | } 30 | return err; 31 | } 32 | 33 | auto getLargest(const std::vector& imgs) { 34 | return std::max_element(std::cbegin(imgs), std::cend(imgs), [](const ImageView& img1, const ImageView& img2) { 35 | return img1.getFlattenedLength() < img2.getFlattenedLength(); 36 | }); 37 | } 38 | 39 | LLU_LIBRARY_FUNCTION(GetLargest) { 40 | auto imgAuto = mngr.getImage(0); 41 | auto imgConstant = mngr.getGenericImage(1); 42 | auto imgManual = mngr.getImage(2); 43 | std::vector imgs {ImageView {imgAuto}, ImageView {imgConstant}, ImageView {imgManual}}; 44 | auto largest = getLargest(imgs); 45 | mngr.set(static_cast(std::distance(std::cbegin(imgs), largest))); 46 | 47 | // perform some random assignments and copies to see it they compile 48 | std::swap(imgs[0], imgs[1]); 49 | ImageView iv = std::move(imgs[2]); 50 | imgs[2] = iv; 51 | } 52 | 53 | LIBRARY_LINK_FUNCTION(ImageRowCount) { 54 | auto err = LLU::ErrorCode::NoError; 55 | try { 56 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 57 | auto image = mngr.getGenericImage(0); 58 | mngr.setInteger(image.rows()); 59 | } catch (const LLU::LibraryLinkError& e) { 60 | err = e.which(); 61 | } catch (...) { 62 | err = LLU::ErrorCode::FunctionError; 63 | } 64 | return err; 65 | } 66 | 67 | LLU_LIBRARY_FUNCTION(EmptyView) { 68 | ImageView v; 69 | LLU::Tensor t {v.slices(), v.rows(), v.columns(), static_cast(v.colorspace()), static_cast(v.type())}; 70 | mngr.set(t); 71 | } -------------------------------------------------------------------------------- /tests/UnitTests/Image/TestSources/ImageNegate.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | template 5 | constexpr T negator = (std::numeric_limits::max)(); 6 | 7 | template<> 8 | constexpr int8_t negator = 1; 9 | 10 | template<> 11 | constexpr float negator = 1.f; 12 | 13 | template<> 14 | constexpr double negator = 1.; 15 | 16 | struct ImageNegator { 17 | template 18 | LLU::GenericImage operator()(LLU::Image in) { 19 | LLU::Image out {in.clone()}; 20 | std::transform(std::cbegin(in), std::cend(in), std::begin(out), [](T inElem) { return negator - inElem; }); 21 | return std::move(out); 22 | } 23 | 24 | template 25 | void operator()(LLU::ImageTypedView imgView) { 26 | std::for_each(imgView.begin(), imgView.end(), [](T& v) { v = negator - v; }); 27 | } 28 | }; 29 | 30 | LLU_LIBRARY_FUNCTION(ImageNegate) { 31 | mngr.set(mngr.operateOnImage(0)); 32 | } 33 | 34 | LLU_LIBRARY_FUNCTION(NegateImages) { 35 | auto imgList = mngr.getDataList(0); 36 | LLU::DataList outList; 37 | for (auto&& node : imgList) { 38 | auto& img = node.value(); 39 | LLU::asTypedImage(img, ImageNegator{}); 40 | outList.push_back(std::move(img)); 41 | } 42 | mngr.set(outList); 43 | } -------------------------------------------------------------------------------- /tests/UnitTests/MArgumentManager/TestSources/MArgumentManagerCompilationErrors.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file MArgumentManagerCompilationErrors.cpp 3 | * @author Rafal Chojna 4 | * @brief Source code for MArgumentManager unit tests containing functions that should fail at compile stage. 5 | */ 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | class A {}; 12 | 13 | LLU_LIBRARY_FUNCTION(UnregisteredArg) { 14 | [[maybe_unused]] auto f = mngr.get(0); // Compile time error - float does not correspond to any argument type known to MArgumentManager 15 | [[maybe_unused]] auto a = mngr.get(1); // Compile time error - A does not correspond to any argument type known to MArgumentManager 16 | } 17 | 18 | LLU_LIBRARY_FUNCTION(UnregisteredRetType) { 19 | A a; 20 | mngr.set(a); // Compile time error - A does not correspond to any return type known to MArgumentManager 21 | } 22 | 23 | LLU_LIBRARY_FUNCTION(GetArgsWithIndices) { 24 | // Compile time error - number of argument types does not match the number of indices in the call to mngr.get<..>(...) 25 | // gcc also triggers separate error for: "no matching function for call to ‘LLU::MArgumentManager::get()’" 26 | [[maybe_unused]] auto [x, y] = mngr.get({1, 2, 3}); 27 | } -------------------------------------------------------------------------------- /tests/UnitTests/NumericArray/TestSources/NumericArrayOperations.wl: -------------------------------------------------------------------------------- 1 | Needs["CCompilerDriver`"] 2 | lib = CreateLibrary[{"NumericArrayOperations.cpp"}, "NumericArrayOperations", options, "Defines" -> {"LLU_LOG_DEBUG"}]; 3 | Get[FileNameJoin[{$LLUSharedDir, "LibraryLinkUtilities.wl"}]]; 4 | `LLU`InitializePacletLibrary[lib]; 5 | 6 | `LLU`$Throws = False; (* library functions will not throw unless overriden on a per-function basis *) 7 | 8 | emptyVector = `LLU`PacletFunctionLoad["CreateEmptyVector", {}, NumericArray]; 9 | emptyMatrix = `LLU`PacletFunctionLoad["CreateEmptyMatrix", {}, NumericArray]; 10 | echoNumericArrays = `LLU`PacletFunctionLoad["echoNumericArrays", {NumericArray, {NumericArray, "Manual"}, {NumericArray, "Shared"}}, "DataStore"]; 11 | getNALength = `LLU`PacletFunctionLoad["getNumericArrayLength", {NumericArray}, Integer]; 12 | getNARank = `LLU`PacletFunctionLoad["getNumericArrayRank", {NumericArray}, Integer]; 13 | newNA = `LLU`PacletFunctionLoad["newNumericArray", {}, NumericArray]; 14 | cloneNA = `LLU`PacletFunctionLoad["cloneNumericArrays", {{NumericArray, "Constant"}, {NumericArray, "Manual"}, {NumericArray, "Shared"}}, "DataStore"]; 15 | changeSharedNA = `LLU`PacletFunctionLoad["changeSharedNumericArray", {{NumericArray, "Shared"}}, Integer]; 16 | getSharedNA = `LLU`PacletFunctionLoad["getSharedNumericArray", {}, NumericArray]; 17 | accumulateIntegers = `LLU`PacletFunctionLoad["accumulateIntegers", {{NumericArray, "Constant"}}, Integer]; 18 | convertMethodName = `LLU`PacletFunctionLoad["convertMethodName", {Integer}, String]; 19 | convert = `LLU`PacletFunctionLoad["convert", {{NumericArray, "Constant"}, Integer, Real}, NumericArray]; 20 | convertGeneric = `LLU`PacletFunctionLoad["convertGeneric", {{NumericArray, "Constant"}, Integer, Real}, NumericArray]; 21 | testDimensions = `LLU`PacletFunctionLoad["TestDimensions", {{Integer, 1, "Constant"}}, NumericArray]; 22 | testDimensions2 = `LLU`PacletFunctionLoad["TestDimensions2", {}, "DataStore"]; 23 | FlattenThroughList = `LLU`PacletFunctionLoad["FlattenThroughList", {NumericArray}, NumericArray]; 24 | CopyThroughTensor = `LLU`PacletFunctionLoad["CopyThroughTensor", {NumericArray}, NumericArray]; 25 | GetLargest = `LLU`PacletFunctionLoad["GetLargest", {NumericArray, {NumericArray, "Constant"}, {NumericArray, "Manual"}}, Integer]; 26 | EmptyView = `LLU`PacletFunctionLoad["EmptyView", {}, {Integer, 1}]; 27 | SumLargestDimensions = `LLU`PacletFunctionLoad["SumLargestDimensions", {NumericArray, {NumericArray, "Constant"}}, Integer]; 28 | ReverseNA = `LLU`PacletFunctionLoad["Reverse", {{NumericArray, "Constant"}}, NumericArray]; 29 | ReverseBA = `LLU`PacletFunctionLoad["Reverse", {{ByteArray, "Constant"}}, ByteArray]; -------------------------------------------------------------------------------- /tests/UnitTests/RunAllTests.mt: -------------------------------------------------------------------------------- 1 | Needs["MUnit`"] 2 | 3 | testFiles = FileNames["*TestSuite.mt", { DirectoryName[$TestFileName] }, 2]; 4 | TestSuite[testFiles]; -------------------------------------------------------------------------------- /tests/UnitTests/Scalar/ScalarTestSuite.mt: -------------------------------------------------------------------------------- 1 | (* Wolfram Language Test file *) 2 | TestRequirement[$VersionNumber >= 12.0]; 3 | (***************************************************************************************************************************************) 4 | (* 5 | Set of test cases to test LLU functionality related to handling and exchanging scalar data types 6 | *) 7 | (***************************************************************************************************************************************) 8 | TestExecute[ 9 | Needs["CCompilerDriver`"]; 10 | currentDirectory = DirectoryName[$TestFileName]; 11 | 12 | (* Get configuration (path to LLU sources, compilation options, etc.) *) 13 | Get[FileNameJoin[{ParentDirectory[currentDirectory], "TestConfig.wl"}]]; 14 | 15 | (* Compile the test library *) 16 | lib = CCompilerDriver`CreateLibrary[ 17 | FileNameJoin[{currentDirectory, "TestSources", #}]& /@ {"Boolean.cpp", "Complex.cpp", "Integer.cpp", "Real.cpp"}, 18 | "ScalarTest", 19 | options (* defined in TestConfig.wl *) 20 | ]; 21 | 22 | Get[FileNameJoin[{$LLUSharedDir, "LibraryLinkUtilities.wl"}]]; 23 | `LLU`InitializePacletLibrary[lib]; 24 | ]; 25 | 26 | 27 | (* 28 | Boolean 29 | *) 30 | TestExecute[ 31 | BooleanAnd = LibraryFunctionLoad[lib, "BooleanAnd", {"Boolean", "Boolean"}, "Boolean"]; 32 | BooleanNot = LibraryFunctionLoad[lib, "BooleanNot", {"Boolean"}, "Boolean"]; 33 | BooleanOr = LibraryFunctionLoad[lib, "BooleanOr", {"Boolean", "Boolean"}, "Boolean"]; 34 | ]; 35 | 36 | Test[ 37 | {BooleanAnd[True, False], BooleanAnd[False, True], BooleanAnd[False, False], BooleanAnd[True, True]} 38 | , 39 | {False, False, False, True} 40 | , 41 | TestID -> "ScalarBooleanOperations-20150806-H2A7H8" 42 | ]; 43 | 44 | Test[ 45 | {BooleanOr[True, False], BooleanOr[False, True], BooleanOr[False, False], BooleanOr[True, True]} 46 | , 47 | {True, True, False, True} 48 | , 49 | TestID -> "ScalarBooleanOperations-20150806-Q1W1V8" 50 | ]; 51 | 52 | Test[ 53 | {BooleanNot[False], BooleanNot[True]} 54 | , 55 | {True, False} 56 | , 57 | TestID -> "ScalarBooleanOperations-20150806-U1B6I7" 58 | ]; 59 | 60 | 61 | (* 62 | Complex 63 | *) 64 | TestExecute[ 65 | ComplexAdd = LibraryFunctionLoad[lib, "ComplexAdd", {_Complex, _Complex}, _Complex]; 66 | ComplexTimes = LibraryFunctionLoad[lib, "ComplexTimes", {_Complex, _Complex}, _Complex]; 67 | ]; 68 | 69 | Test[ 70 | ComplexAdd[1 + 2I, 3 + 10I] 71 | , 72 | 4. + 12.I 73 | , 74 | TestID -> "ScalarComplexOperations-20150806-D9W5E1" 75 | ]; 76 | 77 | Test[ 78 | ComplexTimes[1 + 2I, 3 + 10I] 79 | , 80 | -17. + 16.I 81 | , 82 | TestID -> "ScalarComplexOperations-20150806-A5D2Q3" 83 | ]; 84 | 85 | 86 | (* 87 | Integer 88 | *) 89 | TestExecute[ 90 | LLGet = LibraryFunctionLoad[lib, "llGet", {}, Integer]; 91 | LLSet = LibraryFunctionLoad[lib, "llSet", {Integer}, "Void"]; 92 | IntegerAdd = LibraryFunctionLoad[lib, "IntegerAdd", {Integer, Integer}, Integer]; 93 | IntegerTimes = LibraryFunctionLoad[lib, "IntegerTimes", {Integer, Integer}, Integer]; 94 | SquareInteger = LibraryFunctionLoad[lib, "SquareInteger", {Integer}, Integer]; 95 | ]; 96 | 97 | ExactTest[ 98 | LLSet[5]; 99 | LLGet[] 100 | , 101 | 5 102 | , 103 | TestID -> "ScalarIntegerOperations-20150806-M0L0V7" 104 | ]; 105 | 106 | ExactTest[ 107 | SquareInteger[2] 108 | , 109 | 4 110 | , 111 | TestID -> "ScalarIntegerOperations-20150806-F8V5U1" 112 | ]; 113 | 114 | ExactTest[ 115 | IntegerAdd[2, 3] 116 | , 117 | 5 118 | , 119 | TestID -> "ScalarIntegerOperations-20150806-D9W5E1" 120 | ]; 121 | 122 | ExactTest[ 123 | IntegerTimes[4, 6] 124 | , 125 | 24 126 | , 127 | TestID -> "ScalarIntegerOperations-20150806-A5D2Q3" 128 | ]; 129 | 130 | 131 | (* 132 | Real 133 | *) 134 | TestExecute[ 135 | RealAdd = LibraryFunctionLoad[lib, "RealAdd", {_Real, _Real}, _Real]; 136 | RealTimes = LibraryFunctionLoad[lib, "RealTimes", {_Real, _Real}, _Real]; 137 | ]; 138 | 139 | Test[ 140 | RealAdd[1., 2.] 141 | , 142 | 3. 143 | , 144 | TestID -> "ScalarRealOperations-20150806-D9W5E1" 145 | ]; 146 | 147 | Test[ 148 | RealTimes[4., 5.] 149 | , 150 | 20. 151 | , 152 | TestID -> "ScalarRealOperations-20150806-A5D2Q3" 153 | ]; -------------------------------------------------------------------------------- /tests/UnitTests/Scalar/TestSources/Boolean.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Boolean.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | 8 | EXTERN_C DLLEXPORT int BooleanAnd(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 9 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 10 | auto in1 = mngr.getBoolean(0); 11 | auto in2 = mngr.getBoolean(1); 12 | mngr.setBoolean(in1 && in2); 13 | return LIBRARY_NO_ERROR; 14 | } 15 | 16 | EXTERN_C DLLEXPORT int BooleanNot(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 17 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 18 | auto in1 = mngr.getBoolean(0); 19 | mngr.set(!in1); 20 | return LIBRARY_NO_ERROR; 21 | } 22 | 23 | EXTERN_C DLLEXPORT int BooleanOr(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 24 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 25 | auto in1 = mngr.getBoolean(0); 26 | auto in2 = mngr.getBoolean(1); 27 | mngr.set(in1 || in2); 28 | return LIBRARY_NO_ERROR; 29 | } 30 | -------------------------------------------------------------------------------- /tests/UnitTests/Scalar/TestSources/Complex.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Complex.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | 8 | EXTERN_C DLLEXPORT int ComplexAdd(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 9 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 10 | 11 | auto in1 = mngr.getComplex(0); 12 | auto in2 = mngr.getComplex(1); 13 | mngr.setComplex(in1 + in2); 14 | return LIBRARY_NO_ERROR; 15 | } 16 | 17 | EXTERN_C DLLEXPORT int ComplexTimes(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 18 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 19 | 20 | auto in1 = mngr.getComplex(0); 21 | auto in2 = mngr.getComplex(1); 22 | mngr.set(in1 * in2); 23 | return LIBRARY_NO_ERROR; 24 | } -------------------------------------------------------------------------------- /tests/UnitTests/Scalar/TestSources/Integer.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Integer.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | 8 | static mint value = 0; 9 | 10 | EXTERN_C DLLEXPORT int llGet(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 11 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 12 | mngr.setInteger(value); 13 | return LIBRARY_NO_ERROR; 14 | } 15 | 16 | EXTERN_C DLLEXPORT int llSet(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 17 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 18 | value = mngr.getInteger(0); 19 | return LIBRARY_NO_ERROR; 20 | } 21 | 22 | EXTERN_C DLLEXPORT int IntegerAdd(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 23 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 24 | 25 | auto in1 = mngr.getInteger(0); 26 | auto in2 = mngr.getInteger(1); 27 | auto out = in1 + in2; 28 | mngr.setInteger(out); 29 | return LIBRARY_NO_ERROR; 30 | } 31 | 32 | EXTERN_C DLLEXPORT int IntegerTimes(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 33 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 34 | 35 | auto in1 = mngr.getInteger(0); 36 | auto in2 = mngr.getInteger(1); 37 | auto out = in1 * in2; 38 | mngr.set(out); 39 | return LIBRARY_NO_ERROR; 40 | } 41 | 42 | EXTERN_C DLLEXPORT int SquareInteger(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 43 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 44 | 45 | auto x = mngr.getInteger(0); 46 | auto result = x * x; 47 | mngr.set(result); 48 | return LIBRARY_NO_ERROR; 49 | } -------------------------------------------------------------------------------- /tests/UnitTests/Scalar/TestSources/Real.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file Real.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | 8 | EXTERN_C DLLEXPORT int RealAdd(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 9 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 10 | 11 | auto in1 = mngr.getReal(0); 12 | auto in2 = mngr.getReal(1); 13 | auto out = in1 + in2; 14 | mngr.setReal(out); 15 | return LIBRARY_NO_ERROR; 16 | } 17 | 18 | EXTERN_C DLLEXPORT int RealTimes(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 19 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 20 | 21 | auto in1 = mngr.getReal(0); 22 | auto in2 = mngr.getReal(1); 23 | auto out = in1 * in2; 24 | mngr.set(out); 25 | return LIBRARY_NO_ERROR; 26 | } -------------------------------------------------------------------------------- /tests/UnitTests/SparseArray/TestSources/SparseArrayOperations.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file SparseArrayOperations.cpp 3 | * @author Rafal Chojna 4 | * @date November 25, 2020 5 | * @brief 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | 12 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { 13 | LLU::LibraryData::setLibraryData(libData); 14 | return 0; 15 | } 16 | 17 | LLU_LIBRARY_FUNCTION(GetImplicitValue) { 18 | const auto sp = mngr.getGenericSparseArray(0); 19 | auto implVal = sp.getImplicitValueAsTensor(); 20 | mngr.set(implVal.clone()); 21 | } 22 | 23 | LLU_LIBRARY_FUNCTION(GetExplicitValues) { 24 | const auto sp = mngr.getGenericSparseArray(0); 25 | auto explValues = sp.getExplicitValues(); 26 | mngr.set(explValues.clone()); 27 | } 28 | 29 | LLU_LIBRARY_FUNCTION(GetRowPointers) { 30 | const auto sp = mngr.getGenericSparseArray(0); 31 | auto rowPointers = sp.getRowPointers(); 32 | mngr.set(rowPointers.clone()); 33 | } 34 | 35 | LLU_LIBRARY_FUNCTION(GetColumnIndices) { 36 | const auto sp = mngr.getGenericSparseArray(0); 37 | auto colIndices = sp.getColumnIndices(); 38 | mngr.set(colIndices.clone()); 39 | } 40 | 41 | LLU_LIBRARY_FUNCTION(GetExplicitPositions) { 42 | const auto sp = mngr.getGenericSparseArray(0); 43 | auto explPositions = sp.getExplicitPositions(); 44 | mngr.set(explPositions); 45 | } 46 | 47 | LLU_LIBRARY_FUNCTION(ToTensor) { 48 | const auto sp = mngr.getGenericSparseArray(0); 49 | auto tensor = sp.toGenericTensor(); 50 | mngr.set(tensor); 51 | } 52 | 53 | LLU_LIBRARY_FUNCTION(SetImplicitValue) { 54 | auto sp = mngr.getGenericSparseArray(0); 55 | auto implValue = mngr.getGenericTensor(1); 56 | sp.setImplicitValueFromTensor(implValue); 57 | mngr.set(sp); 58 | } 59 | 60 | template 61 | void sparseModifyValues(LLU::SparseArray& sa, LLU::TensorTypedView newValues) { 62 | auto values = sa.explicitValues(); 63 | if (values.size() < newValues.size()) { 64 | throw std::runtime_error {"Too many values provided."}; 65 | } 66 | std::copy(std::cbegin(newValues), std::cend(newValues), std::begin(values)); 67 | 68 | /* Recompute explicit positions */ 69 | sa.resparsify(); 70 | } 71 | 72 | LLU_LIBRARY_FUNCTION(ModifyValues) { 73 | auto sp = mngr.getGenericSparseArray(0); 74 | auto values = mngr.getGenericTensor(1); 75 | auto dataType = sp.type(); 76 | if (dataType != values.type()) { 77 | throw std::runtime_error {"Inconsistent types."}; 78 | } 79 | LLU::asTypedSparseArray(sp, [&values](auto&& sparseArray) { 80 | using T = typename std::remove_reference_t::value_type; 81 | sparseModifyValues(sparseArray, LLU::TensorTypedView {values}); 82 | }); 83 | } 84 | 85 | LLU_LIBRARY_FUNCTION(ModifyValues2) { 86 | auto values = mngr.getGenericTensor(1); 87 | mngr.operateOnSparseArray(0, [](auto&& sparseArray, const LLU::GenericTensor& values) { 88 | using T = typename std::remove_reference_t::value_type; 89 | sparseModifyValues(sparseArray, LLU::TensorTypedView {values}); 90 | }, values); 91 | } 92 | 93 | LLU_LIBRARY_FUNCTION(GetImplicitValueTyped) { 94 | const auto sp = mngr.getSparseArray(0); 95 | auto implVal = sp.implicitValue(); 96 | mngr.set(implVal); 97 | } 98 | 99 | LLU_LIBRARY_FUNCTION(GetExplicitValuesTyped) { 100 | const auto sp = mngr.getSparseArray(0); 101 | auto explValues = sp.explicitValues(); 102 | mngr.set(explValues.clone()); 103 | } 104 | 105 | LLU_LIBRARY_FUNCTION(GetRowPointersTyped) { 106 | const auto sp = mngr.getSparseArray(0); 107 | auto rowPointers = sp.rowPointers(); 108 | mngr.set(rowPointers.clone()); 109 | } 110 | 111 | LLU_LIBRARY_FUNCTION(GetColumnIndicesTyped) { 112 | const auto sp = mngr.getSparseArray(0); 113 | auto colIndices = sp.columnIndices(); 114 | mngr.set(colIndices.clone()); 115 | } 116 | 117 | LLU_LIBRARY_FUNCTION(GetExplicitPositionsTyped) { 118 | const auto sp = mngr.getSparseArray(0); 119 | auto explPositions = sp.explicitPositions(); 120 | mngr.set(explPositions); 121 | } 122 | 123 | LLU_LIBRARY_FUNCTION(ToTensorTyped) { 124 | const auto sp = mngr.getSparseArray(0); 125 | auto tensor = sp.toTensor(); 126 | mngr.set(tensor); 127 | } 128 | 129 | LLU_LIBRARY_FUNCTION(SetImplicitValueTyped) { 130 | auto sp = mngr.getSparseArray(0); 131 | auto implValue = mngr.getReal(1); 132 | sp.setImplicitValue(implValue); 133 | mngr.set(sp); 134 | } -------------------------------------------------------------------------------- /tests/UnitTests/String/StringTestSuite.mt: -------------------------------------------------------------------------------- 1 | (* Wolfram Language Test file *) 2 | TestRequirement[$VersionNumber >= 12.0]; 3 | (***************************************************************************************************************************************) 4 | (* 5 | Set of test cases to test LLU functionality related to handling and exchanging Strings 6 | *) 7 | (***************************************************************************************************************************************) 8 | TestExecute[ 9 | Needs["CCompilerDriver`"]; 10 | currentDirectory = DirectoryName[$TestFileName]; 11 | 12 | (* Get configuration (path to LLU sources, compilation options, etc.) *) 13 | Get[FileNameJoin[{ParentDirectory[currentDirectory], "TestConfig.wl"}]]; 14 | 15 | (* Compile the test library *) 16 | lib = CCompilerDriver`CreateLibrary[ 17 | FileNameJoin[{currentDirectory, "TestSources", #}]& /@ {"String.cpp"}, 18 | "StringTest", 19 | options (* defined in TestConfig.wl *) 20 | ]; 21 | 22 | Get[FileNameJoin[{$LLUSharedDir, "LibraryLinkUtilities.wl"}]]; 23 | `LLU`InitializePacletLibrary[lib]; 24 | ]; 25 | 26 | ExactTest[ 27 | EchoString = LibraryFunctionLoad[lib, "EchoString", { LibraryDataType[String] }, LibraryDataType[String]]; 28 | EchoString["foo bar"] 29 | , 30 | "foo bar" 31 | , 32 | TestID -> "StringOperations-20150807-D1D4K5" 33 | ]; 34 | 35 | ExactTest[ 36 | Greetings = LibraryFunctionLoad[lib, "Greetings", {"UTF8String"}, "UTF8String"]; 37 | Greetings["wolfram"] 38 | , 39 | "Greetings wolfram!" 40 | , 41 | TestID -> "StringOperations-20150807-X0R7U9" 42 | ]; 43 | 44 | ExactTest[ 45 | HelloWorld = LibraryFunctionLoad[lib, "HelloWorld", {}, "UTF8String"]; 46 | HelloWorld[] 47 | , 48 | "Hello World" 49 | , 50 | TestID -> "StringOperations-20150807-K4Y4G8" 51 | ]; 52 | 53 | ExactTest[ 54 | CapitalizeFirst = LibraryFunctionLoad[lib, "CapitalizeFirst", {"UTF8String"}, "UTF8String"]; 55 | CapitalizeFirst["hello World"] 56 | , 57 | "Hello World" 58 | , 59 | TestID -> "StringTestSuite-20180821-Y4A0E2" 60 | ]; 61 | 62 | Test[ 63 | CapitalizeAll = LibraryFunctionLoad[lib, "CapitalizeAll", {"UTF8String"}, "UTF8String"]; 64 | CapitalizeAll["Hello World"] 65 | , 66 | ToUpperCase["Hello World"] 67 | , 68 | TestID -> "StringTestSuite-20180821-X0K5Z1" 69 | ]; 70 | 71 | Test[ 72 | StrLength = LibraryFunctionLoad[lib, "StringLength", { LibraryDataType[String] }, LibraryDataType[Integer]]; 73 | StrLength["this is my null terminated string"] 74 | , 75 | 33 76 | , 77 | TestID -> "StringOperations-20150813-B8G3E7" 78 | ]; 79 | -------------------------------------------------------------------------------- /tests/UnitTests/String/TestSources/String.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file String.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | #include 8 | 9 | #include 10 | #include 11 | 12 | EXTERN_C DLLEXPORT int Greetings(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 13 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 14 | 15 | auto name = mngr.getString(0); 16 | mngr.setString(std::string("Greetings ") + name + "!"); 17 | return LIBRARY_NO_ERROR; 18 | } 19 | 20 | EXTERN_C DLLEXPORT int EchoString(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 21 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 22 | 23 | auto* in1 = mngr.getCString(0); 24 | mngr.setString(in1); 25 | 26 | return LIBRARY_NO_ERROR; 27 | } 28 | 29 | EXTERN_C DLLEXPORT int HelloWorld(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 30 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 31 | 32 | mngr.setString("Hello World"); 33 | return LIBRARY_NO_ERROR; 34 | } 35 | 36 | LIBRARY_LINK_FUNCTION(CapitalizeFirst) { 37 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 38 | 39 | auto* in = mngr.getCString(0); 40 | char& firstChar = *in; 41 | firstChar = static_cast(std::toupper(static_cast(firstChar))); 42 | mngr.setString(in); 43 | return LIBRARY_NO_ERROR; 44 | } 45 | 46 | LIBRARY_LINK_FUNCTION(CapitalizeAll) { 47 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 48 | 49 | auto in = mngr.getString(0); 50 | for (auto& c : in) { 51 | c = static_cast(std::toupper(static_cast(c))); 52 | } 53 | mngr.setString(in); 54 | return LIBRARY_NO_ERROR; 55 | } 56 | 57 | EXTERN_C DLLEXPORT int StringLength(WolframLibraryData libData, mint Argc, MArgument* Args, MArgument Res) { 58 | LLU::MArgumentManager mngr(libData, Argc, Args, Res); 59 | 60 | auto name = mngr.getString(0); 61 | mngr.setInteger(name.length()); 62 | return LIBRARY_NO_ERROR; 63 | } -------------------------------------------------------------------------------- /tests/UnitTests/Tensor/TestSources/ScalarOperations.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | /* Gets the I0 th Real number from the rank 1 tensor T0 */ 5 | LLU_LIBRARY_FUNCTION(getNthRealFromTR1) { 6 | auto t = mngr.getTensor(0); 7 | auto i = mngr.getInteger(1) - 1; 8 | 9 | mngr.setReal(t.at(i)); // we use at() here to verify if the index i is not out-of-bounds 10 | } 11 | 12 | /* Gets the (m,n) Real number from the rank 2 tensor T0 */ 13 | LLU_LIBRARY_FUNCTION(getNthRealFromTR2) { 14 | auto t = mngr.getTensor(0); 15 | auto i = mngr.getInteger(1) - 1; 16 | auto j = mngr.getInteger(2) - 1; 17 | 18 | mngr.setReal(t[{i, j}]); 19 | } 20 | 21 | /* Gets the (m,n) Integer number from the rank 2 tensor T0 */ 22 | LLU_LIBRARY_FUNCTION(getNthIntegerFromTR2) { 23 | auto t = mngr.getTensor(0); 24 | auto i = mngr.getInteger(1) - 1; 25 | auto j = mngr.getInteger(2) - 1; 26 | 27 | mngr.setInteger(t[{i, j}]); 28 | } 29 | 30 | /** 31 | * Constructs a new rank 1 tensor of length I0, and sets the 32 | * ith element of the vector to 2*i 33 | **/ 34 | LLU_LIBRARY_FUNCTION(setNthIntegerT) { 35 | auto len = mngr.getInteger(0); 36 | 37 | LLU::Tensor t(0, {len}); 38 | mint val = 2; 39 | std::for_each(t.begin(), t.end(), [&val](mint& elem) { 40 | elem = val; 41 | val += 2; 42 | }); 43 | mngr.setTensor(t); 44 | } 45 | 46 | /* Sets the element in the I0,I1 position in T0 to its value in T1, returning T0 */ 47 | LIBRARY_LINK_FUNCTION(setI0I1T) { 48 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 49 | auto T0 = mngr.getGenericTensor(0); 50 | auto T1 = mngr.getGenericTensor(1); 51 | auto [I1, I2] = mngr.getTuple(2); 52 | std::array pos {I1, I2}; 53 | if (auto err = LLU::LibraryData::API()->MTensor_setMTensor(T0.getContainer(), T1.getContainer(), pos.data(), 2); err != LIBRARY_NO_ERROR) { 54 | return err; 55 | } 56 | mngr.set(T0); 57 | return LLU::ErrorCode::NoError; 58 | } 59 | 60 | /* Gets the subpart of the input tensor starting at the I0,I1 th position */ 61 | LIBRARY_LINK_FUNCTION(getSubpartT) { 62 | LLU::MArgumentManager mngr {libData, Argc, Args, Res}; 63 | auto T0 = mngr.getGenericTensor(0); 64 | auto [I1, I2] = mngr.getTuple(1); 65 | std::array pos {I1, I2}; 66 | MTensor T1 {}; 67 | if (auto err = LLU::LibraryData::API()->MTensor_getMTensor(T0.getContainer(), pos.data(), 2, &T1); err != LIBRARY_NO_ERROR) { 68 | return err; 69 | } 70 | mngr.setMTensor(T1); 71 | return LLU::ErrorCode::NoError; 72 | } 73 | -------------------------------------------------------------------------------- /tests/UnitTests/Tensor/TestSources/SharedData.cpp: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | namespace { 8 | std::unique_ptr> tensor {}; 9 | } 10 | 11 | EXTERN_C DLLEXPORT mint WolframLibrary_getVersion() { 12 | return WolframLibraryVersion; 13 | } 14 | 15 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { 16 | LLU::LibraryData::setLibraryData(libData); 17 | return 0; 18 | } 19 | 20 | LLU_LIBRARY_FUNCTION(loadRealArray) { 21 | auto genericTensor = mngr.getGenericTensor(0); 22 | tensor = std::make_unique>(std::move(genericTensor)); 23 | } 24 | 25 | LLU_LIBRARY_FUNCTION(getRealArray) { 26 | if (!tensor) { 27 | LLU::ErrorManager::throwException(LLU::ErrorName::FunctionError); 28 | } 29 | auto& out = *tensor; 30 | mngr.set(out); 31 | } 32 | 33 | LLU_LIBRARY_FUNCTION(doubleRealArray) { 34 | if (!tensor) { 35 | LLU::ErrorManager::throwException(LLU::ErrorName::FunctionError); 36 | } 37 | auto& out = *tensor; 38 | for (auto& elem : out) { 39 | elem *= 2; 40 | } 41 | mngr.set(out); 42 | } 43 | 44 | LLU_LIBRARY_FUNCTION(unloadRealArray) { 45 | if (!tensor) { 46 | LLU::ErrorManager::throwException(LLU::ErrorName::FunctionError); 47 | } 48 | mngr.setInteger(tensor->shareCount()); 49 | tensor.reset(); 50 | } 51 | 52 | // Modify the contents of tensor in C function 53 | LLU_LIBRARY_FUNCTION(add1) { 54 | auto tx = mngr.getTensor(0); 55 | for (auto& elem : tx) { 56 | elem++; 57 | } 58 | } 59 | 60 | LLU_LIBRARY_FUNCTION(copyShared) { 61 | auto sharedTensor = mngr.getTensor(0); 62 | auto sc = sharedTensor.shareCount(); 63 | LLU::Tensor copy {sharedTensor.clone()}; // create deep copy of the shared Tensor. The new Tensor is not Shared 64 | mngr.setInteger(100 * sc + 10 * sharedTensor.shareCount() + copy.shareCount()); 65 | } 66 | -------------------------------------------------------------------------------- /tests/UnitTests/TestConfig.wl: -------------------------------------------------------------------------------- 1 | (* Project base directory *) 2 | $baseDir = FileNameDrop[$TestFileName, -3]; 3 | 4 | $installDir = If[StringQ[$LLUInstallDir], $LLUInstallDir, FileNameJoin[{$baseDir, "install"}]]; 5 | 6 | $CRTLinkingFlag = Switch[$CRTLinkingMode, 7 | "MultiThreaded", "/MT", 8 | "MultiThreadedDLL", "/MD", 9 | "MultiThreadedDebug", "/MTd", 10 | "MultiThreadedDebugDLL", "/MDd", 11 | _, "/MD" 12 | ]; 13 | 14 | (* Path to directory containing include folder from LibraryLinkUtilities installation *) 15 | $LLUIncDir = FileNameJoin[{$installDir, "include"}]; 16 | 17 | $lib = FileNames[RegularExpression[".*LLU\\.(a|lib|" <> Internal`DynamicLibraryExtension[] <> ")"], $installDir, 2]; 18 | If[Length[$lib] =!= 1, 19 | Throw["Could not find LLU library.", "LLUTestConfig"]; 20 | ]; 21 | 22 | (* Path to LibraryLinkUtilities static lib *) 23 | $LLULibDir = DirectoryName @ First @ $lib; 24 | 25 | (* LibraryLinkUtilities library name *) 26 | $LLULib = "LLU"; 27 | 28 | (* Path to LibraryLinkUtilities shared resources *) 29 | $LLUSharedDir = FileNameJoin[{$installDir, "share"}]; 30 | 31 | (* C++ version to build unit tests with. Some parts of LLU require C++20. *) 32 | $CppVersion = "c++20"; 33 | 34 | (* Compilations options for all tests *) 35 | options := { 36 | "CleanIntermediate" -> True, 37 | "IncludeDirectories" -> { $LLUIncDir }, 38 | "Libraries" -> { $LLULib }, 39 | "LibraryDirectories" -> { $LLULibDir }, 40 | "CompileOptions" -> 41 | Switch[$OperatingSystem, 42 | "Windows", 43 | "/EHsc /W3 " <> $CRTLinkingFlag <> " /std:" <> $CppVersion <> " /D_SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING", 44 | "Unix", 45 | "-Wall --pedantic -fvisibility=hidden -std=" <> $CppVersion, 46 | "MacOSX", 47 | "-mmacosx-version-min=10.12 -Wall -Wextra --pedantic -fvisibility=hidden -std=" <> $CppVersion 48 | ], 49 | "ShellOutputFunction" -> Print, 50 | "ShellCommandFunction" -> Print, 51 | "Language" -> "C++", 52 | "TransferProtocolLibrary" -> "WSTP" 53 | }; 54 | 55 | (* If dynamic version of LLU was built, we want to load it to Mathematica before test libs are loaded *) 56 | LibraryLoad /@ FileNames[{"*.so", "*.dll", "*.dylib"}, $LLULibDir]; 57 | 58 | 59 | (* Helper definitions *) 60 | 61 | TopLevelErrorCodeQ[c_Integer] := c > 7; 62 | TopLevelErrorCodeQ[_] := False; 63 | 64 | LLErrorCodeQ[c_Integer] := 0 <= c <= 7; 65 | LLErrorCodeQ[_] := False; 66 | 67 | CppErrorCodeQ[c_Integer] := c < 0; 68 | CppErrorCodeQ[_] := False; 69 | 70 | LoggerStringTest = (AllTrue[MapThread[StringEndsQ, {##}], TrueQ]&); 71 | 72 | (* Memory leak test *) 73 | ClearAll[MemoryLeakTest]; 74 | SetAttributes[MemoryLeakTest, HoldAll]; 75 | Options[MemoryLeakTest] = {"ReturnValue" -> Last}; 76 | 77 | MemoryLeakTest[expression_, opts : OptionsPattern[]] := 78 | MemoryLeakTest[expression, {i, 10}, opts]; 79 | 80 | MemoryLeakTest[expression_, repetitions_Integer?Positive, opts : OptionsPattern[]] := 81 | MemoryLeakTest[expression, {i, repetitions}, opts]; 82 | 83 | MemoryLeakTest[expression_, {s_Symbol, repetitions__}, opts : OptionsPattern[]] := 84 | Block[{$MessageList}, 85 | Module[{res, memory}, 86 | $MessageList = {}; 87 | ClearSystemCache[]; 88 | res = Table[ 89 | memory = MemoryInUse[]; 90 | expression; 91 | $MessageList = {}; 92 | ClearSystemCache[]; 93 | MemoryInUse[] - memory 94 | , 95 | {s, repetitions} 96 | ]; 97 | OptionValue["ReturnValue"] @ res 98 | ] 99 | ]; 100 | 101 | MemoryLeakTestWithMessages[expression_] := 102 | MemoryLeakTestWithMessages[expression, 10]; 103 | 104 | MemoryLeakTestWithMessages[expression_, repetitions_Integer?Positive] := 105 | Block[{mem}, 106 | Do[ 107 | mem = MemoryInUse[]; 108 | expression; 109 | Print[MemoryInUse[] - mem] 110 | , 111 | {repetitions} 112 | ] 113 | ]; 114 | 115 | SetAttributes[CatchException, HoldAllComplete]; 116 | CatchException[a__] := Function[x, CatchException[a, x], HoldAll]; 117 | 118 | CatchException[tagPattern_String, body_] := Catch[body, _String?(StringMatchQ[tagPattern])]; 119 | CatchException[tagPattern_, body_] := Catch[body, tagPattern]; 120 | 121 | SetAttributes[CatchAll, HoldFirst]; 122 | CatchAll[body_] := CatchException[_, body]; -------------------------------------------------------------------------------- /tests/UnitTests/Utilities/TestSources/UtilitiesTest.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file UtilitiesTest.cpp 3 | * @brief 4 | */ 5 | 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | class File { 12 | public: 13 | File(const std::string& path, std::ios::openmode mode) { 14 | f = LLU::openFile(path, mode); // open with default Import/Export policy 15 | } 16 | private: 17 | LLU::FilePtr f {nullptr, [](FILE* /*unused*/) { return 0; }}; 18 | }; 19 | 20 | class FileStream { 21 | public: 22 | FileStream(const std::string& path, std::ios::openmode mode) { 23 | f = LLU::openFileStream(path, mode, LLU::SharePolicy {}); // open with _SH_SECURE 24 | } 25 | private: 26 | std::fstream f; 27 | }; 28 | 29 | DEFINE_MANAGED_STORE_AND_SPECIALIZATION(File) 30 | DEFINE_MANAGED_STORE_AND_SPECIALIZATION(FileStream) 31 | 32 | EXTERN_C DLLEXPORT int WolframLibrary_initialize(WolframLibraryData libData) { 33 | LLU::LibraryData::setLibraryData(libData); 34 | FileStore.registerType("MyFile"); 35 | FileStreamStore.registerType("FileStream"); 36 | return 0; 37 | } 38 | 39 | mint openFile(const std::string& path, std::ios::openmode mode) { 40 | auto filePtr = LLU::openFile(path, mode); 41 | return std::ftell(filePtr.get()); 42 | } 43 | 44 | LLU_LIBRARY_FUNCTION(OpenManagedFile) { 45 | auto args = mngr.getTuple(); 46 | auto id = std::get<0>(args); 47 | auto fname = std::get<1>(args); 48 | auto modeInt = std::get<2>(args); 49 | std::ios::openmode mode = modeInt == 0? std::ios::in : (modeInt == 1? std::ios::out : std::ios::in|std::ios::out); 50 | FileStore.createInstance(id, fname, mode); 51 | } 52 | 53 | LLU_LIBRARY_FUNCTION(OpenManagedFileStream) { 54 | auto id = mngr.getInteger(0); 55 | auto fname = mngr.getString(1); 56 | auto modeInt = mngr.getInteger(2); 57 | std::ios::openmode mode = modeInt == 0? std::ios::in : (modeInt == 1? std::ios::out : std::ios::in|std::ios::out); 58 | FileStreamStore.createInstance(id, fname, mode); 59 | } 60 | 61 | LLU_LIBRARY_FUNCTION(OpenForReading) { 62 | auto filePath = mngr.getString(0); 63 | mngr.setInteger(openFile(filePath, std::ios::in)); 64 | } 65 | 66 | LLU_LIBRARY_FUNCTION(OpenForWriting) { 67 | auto filePath = mngr.getString(0); 68 | mngr.setInteger(openFile(filePath, std::ios::out)); 69 | } 70 | 71 | LLU_LIBRARY_FUNCTION(OpenInvalidMode) { 72 | auto filePath = mngr.getString(0); 73 | mngr.setInteger(openFile(filePath, std::ios::in|std::ios::trunc)); 74 | } 75 | 76 | LLU_LIBRARY_FUNCTION(WriteStrings) { 77 | auto filePath = mngr.getString(0); 78 | auto wordList = mngr.getDataList(1); 79 | auto f = LLU::openFileStream(filePath, std::ios::out|std::ios::trunc); 80 | for (auto&& word : wordList) { 81 | f << word.value() << "\n"; 82 | } 83 | } 84 | 85 | LLU_LIBRARY_FUNCTION(ReadStrings) { 86 | auto filePath = mngr.getString(0); 87 | auto f = LLU::openFileStream(filePath, std::ios::in); 88 | LLU::DataList wordList; 89 | std::string word; 90 | while (f >> word) { 91 | wordList.push_back(word); 92 | } 93 | mngr.set(wordList); 94 | } 95 | 96 | static std::wstring toWideStr(const std::u16string& u16) { 97 | return {u16.begin(), u16.end()}; 98 | } 99 | 100 | LLU_LIBRARY_FUNCTION(WideStringUTF8UTF16Conversion) { 101 | std::string u8 = "z\u00df\u6c34\U0001f34c"; 102 | std::wstring u16wide = toWideStr(u"z\u00df\u6c34\U0001f34c"); 103 | 104 | auto u16to8 = LLU::fromUTF16toUTF8(u16wide); 105 | bool isU16ToU8ok = (u8 == u16to8); 106 | 107 | auto u8to16 = LLU::fromUTF8toUTF16(u8); 108 | bool isU8ToU16ok = (u16wide == u8to16); 109 | 110 | mngr.set(isU8ToU16ok && isU16ToU8ok); 111 | } 112 | 113 | LLU_LIBRARY_FUNCTION(Char16UTF8UTF16Conversion) { 114 | std::string u8 = "z\u00df\u6c34\U0001f34c"; 115 | std::u16string u16 = u"z\u00df\u6c34\U0001f34c"; 116 | 117 | auto u16to8 = LLU::fromUTF16toUTF8(u16); 118 | bool isU16ToU8ok = (u8 == u16to8); 119 | 120 | auto u8to16 = LLU::fromUTF8toUTF16(u8); 121 | bool isU8ToU16ok = (u16 == u8to16); 122 | 123 | mngr.set(isU8ToU16ok && isU16ToU8ok); 124 | } 125 | 126 | LLU_LIBRARY_FUNCTION(UTF8ToUTF16Bytes) { 127 | auto u8str = mngr.getString(0); 128 | std::u16string u16str = LLU::fromUTF8toUTF16(u8str); 129 | LLU::NumericArray u16bytes {0, LLU::MArrayDimensions {static_cast(u16str.length())}}; 130 | std::copy(u16str.cbegin(), u16str.cend(), u16bytes.begin()); 131 | mngr.set(u16bytes); 132 | } 133 | 134 | LLU_LIBRARY_FUNCTION(UTF16BytesToUTF8) { 135 | auto u16bytes = mngr.getNumericArray(0); 136 | std::wstring u16wideStr; 137 | std::transform(u16bytes.cbegin(), u16bytes.cend(), std::back_inserter(u16wideStr), [](uint16_t cp) { 138 | return static_cast(cp); 139 | }); 140 | std::string u8str = LLU::fromUTF16toUTF8(u16wideStr); 141 | mngr.set(u8str); 142 | } 143 | 144 | LLU_LIBRARY_FUNCTION(Char32UTF8UTF32Conversion) { 145 | std::string u8 = "z\u00df\u6c34\U0001f34c"; 146 | std::u32string u32 = U"z\u00df\u6c34\U0001f34c"; 147 | 148 | auto u32to8 = LLU::fromUTF32toUTF8(u32); 149 | bool isU32ToU8ok = (u8 == u32to8); 150 | 151 | auto u8to32 = LLU::fromUTF8toUTF32(u8); 152 | bool isU8ToU32ok = (u32 == u8to32); 153 | 154 | mngr.set(isU8ToU32ok && isU32ToU8ok); 155 | } 156 | 157 | LLU_LIBRARY_FUNCTION(UTF8ToUTF32Bytes) { 158 | auto u8str = mngr.getString(0); 159 | std::u32string u32str = LLU::fromUTF8toUTF32(u8str); 160 | LLU::NumericArray u32bytes {0, LLU::MArrayDimensions {static_cast(u32str.length())}}; 161 | std::copy(u32str.cbegin(), u32str.cend(), u32bytes.begin()); 162 | mngr.set(u32bytes); 163 | } 164 | 165 | LLU_LIBRARY_FUNCTION(UTF32BytesToUTF8) { 166 | auto u32bytes = mngr.getNumericArray(0); 167 | std::u32string u32str; 168 | std::transform(u32bytes.cbegin(), u32bytes.cend(), std::back_inserter(u32str), [](uint32_t cp) { 169 | return static_cast(cp); 170 | }); 171 | std::string u8str = LLU::fromUTF32toUTF8(u32str); 172 | mngr.set(u8str); 173 | } 174 | -------------------------------------------------------------------------------- /tests/UnitTests/WSTP/TestSources/WSEncodings.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file WSStringEncodings.cpp 3 | * @date Mar 30, 2018 4 | * @author Rafal Chojna 5 | * @brief Test suite for string related functionality of LLU::WSStream 6 | */ 7 | 8 | #include 9 | 10 | #include "wstp.h" 11 | 12 | #include 13 | #include 14 | #include 15 | 16 | using LLU::WS::Encoding; 17 | namespace ErrorCode = LLU::ErrorCode; 18 | 19 | template 20 | void forAllEncodingsDo(Args&&... params) { 21 | Operation::template run(std::forward(params)...); 22 | Operation::template run(std::forward(params)...); 23 | Operation::template run(std::forward(params)...); 24 | Operation::template run(std::forward(params)...); 25 | Operation::template run(std::forward(params)...); 26 | Operation::template run(std::forward(params)...); 27 | } 28 | 29 | LIBRARY_WSTP_FUNCTION(NestedPutAs) { 30 | auto err = ErrorCode::NoError; 31 | try { 32 | LLU::WSStream ml(wsl, 1); 33 | 34 | std::string s; 35 | ml >> LLU::WS::getAs(s); 36 | 37 | ml << LLU::WS::putAs(LLU::WS::putAs(s)); // the most nested encoding should be the one used 38 | } catch (LLU::LibraryLinkError& e) { 39 | err = e.which(); 40 | } catch (...) { 41 | err = ErrorCode::FunctionError; 42 | } 43 | return err; 44 | } 45 | 46 | template 47 | std::vector toIntegerCodes(const T* p, std::size_t n) { 48 | std::vector ret {p, std::next(p, n)}; 49 | return ret; 50 | } 51 | 52 | struct StringToCharCodes { 53 | template 54 | static void run(WSTPStream ml, WSMARK mark) { 55 | std::basic_string> s; 56 | if (WSSeekToMark(ml.get(), mark, 0)) { 57 | ml >> LLU::WS::getAs(s); 58 | ml << LLU::WS::Rule << LLU::WS::getEncodingName(E) << toIntegerCodes(s.c_str(), s.length()); 59 | } 60 | } 61 | }; 62 | 63 | LIBRARY_WSTP_FUNCTION(CharacterCodes) { 64 | auto err = ErrorCode::NoError; 65 | try { 66 | LLU::WSStream ml(wsl, 1); 67 | auto* mark = WSCreateMark(wsl); 68 | ml << LLU::WS::Association(6); // there are 6 encodings available 69 | forAllEncodingsDo(ml, mark); 70 | 71 | } catch (LLU::LibraryLinkError& e) { 72 | err = e.which(); 73 | } catch (...) { 74 | err = ErrorCode::FunctionError; 75 | } 76 | return err; 77 | } 78 | 79 | struct EncodingRoundtrip { 80 | template 81 | static void run(WSTPStream ml, WSMARK mark) { 82 | std::basic_string> s; 83 | if (WSSeekToMark(ml.get(), mark, 0)) { 84 | ml >> LLU::WS::getAs(s); 85 | ml << LLU::WS::Rule << LLU::WS::getEncodingName(E) << LLU::WS::putAs(s); 86 | } 87 | } 88 | }; 89 | 90 | LIBRARY_WSTP_FUNCTION(AllEncodingsRoundtrip) { 91 | auto err = ErrorCode::NoError; 92 | try { 93 | LLU::WSStream ml(wsl, 1); 94 | auto* mark = WSCreateMark(wsl); 95 | ml << LLU::WS::Association(6); // there are 6 encodings available 96 | forAllEncodingsDo(ml, mark); 97 | } catch (LLU::LibraryLinkError& e) { 98 | err = e.which(); 99 | } catch (...) { 100 | err = ErrorCode::FunctionError; 101 | } 102 | return err; 103 | } 104 | -------------------------------------------------------------------------------- /tests/UnitTests/WSTP/TestSources/WSTestCompilationErrors.cpp: -------------------------------------------------------------------------------- 1 | /** 2 | * @file WSTestCompilationErrors.cpp 3 | * @date Jan 30, 2018 4 | * @author Rafal Chojna 5 | * @brief Source code for WSTPStream unit tests. 6 | */ 7 | #include 8 | 9 | #include "wstp.h" 10 | 11 | #include 12 | #include 13 | #include 14 | 15 | using LLU::WSStream; 16 | namespace WS = LLU::WS; 17 | 18 | // This function should trigger compiler errors 19 | LIBRARY_WSTP_FUNCTION(Wrong) { 20 | auto err = LLU::ErrorCode::NoError; 21 | try { 22 | WSStream ml(wsl, "List", 0); 23 | 24 | ml << "Hello"; // ERROR (static_assert): "Character type does not match the encoding in WS::String::put" 25 | 26 | ml << WS::putAs("Hello"); // This should be fine 27 | 28 | std::basic_string s; 29 | 30 | ml >> s; // ERROR (static_assert): "Character type does not match the encoding in WS::String::getString" 31 | 32 | ml >> WS::getAs(s); // This should be fine 33 | 34 | unsigned int i {129}; 35 | 36 | ml >> i; // ERROR (static_assert): "Calling operator>> with unsupported type." 37 | 38 | ml << i; // ERROR (static_assert): "Calling operator<< with unsupported type." 39 | 40 | i = WS::GetScalar::get(wsl); // ERROR (static_assert): Trying to use WS::GetScalar for unsupported type T 41 | 42 | WS::PutScalar::put(wsl, i); // ERROR (static_assert): Trying to use WS::PutScalar for unsupported type T 43 | 44 | ml << static_cast(i); // This should be fine 45 | 46 | } catch (LLU::LibraryLinkError& e) { 47 | err = e.which(); 48 | } catch (...) { 49 | err = LLU::ErrorCode::FunctionError; 50 | } 51 | return err; 52 | } 53 | --------------------------------------------------------------------------------