diff --git a/.travis.yml b/.travis.yml index 43d6332..ba17c87 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,16 @@ os: linux -dist: bionic +dist: focal language: python python: 3.7 - env: - - BUILD_SHARED_LIBS=false - - BUILD_SHARED_LIBS=true + - BUILD_SHARED_LIBS=False + - BUILD_SHARED_LIBS=True addons: - apt: - packages: + apt: + packages: - cmake - gfortran - libblas-dev @@ -19,7 +18,17 @@ addons: - libopenmpi-dev install: - - pip install fypp + - pip install fypp script: - - mkdir -p _build && pushd _build && cmake -DCMAKE_BUILD_TYPE=Debug -DCMAKE_INSTALL_PREFIX=$PWD/install -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} .. && make -j all install && popd + - > + FC=gfortran cmake -DBUILD_SHARED_LIBS=${BUILD_SHARED_LIBS} -B _build . + && cmake --build _build -- -j + && cmake --install _build + - > + CMAKE_PREFIX_PATH="${PWD}/_build/install:${CMAKE_PREFIX_PATH}" + ./test/integration/cmake/runtest.sh _build_cmake + - > + PKG_CONFIG_PATH="${PWD}/_build/install/lib/pkgconfig:${PKG_CONFIG_PATH}" + FC=mpifort + ./test/integration/pkgconfig/runtest.sh _build_pkgconfig diff --git a/CMakeLists.txt b/CMakeLists.txt index 7cb8a1c..44a313d 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -1,39 +1,64 @@ -cmake_minimum_required(VERSION 3.5) +cmake_minimum_required(VERSION 3.16) -project(mpifx VERSION 0.1 LANGUAGES Fortran) +set(CMAKE_MODULE_PATH ${CMAKE_CURRENT_SOURCE_DIR}/cmake) +include(${CMAKE_CURRENT_SOURCE_DIR}/config.cmake) -set(LIBRARY_ONLY FALSE CACHE BOOL "Whether only library should be compiled") +project(MpiFx VERSION 0.1 LANGUAGES Fortran) -option(BUILD_SHARED_LIBS "Whether the library should be a shared one" FALSE) +include(MpiFxUtils) +setup_build_type() -option(INSTALL_INCLUDE_FILES "Whether include / module files should be installed" TRUE) +# +# Prerequisites +# +find_package(MPI REQUIRED) +find_program(FYPP fypp) +if(NOT FYPP) + message(FATAL_ERROR "Preprocessor fypp could not be found") +endif() -# Installation paths -set(INSTALL_BIN_DIR "${CMAKE_INSTALL_PREFIX}/bin" CACHE PATH - "Installation directory for executables") +# +# Build instructions +# +add_subdirectory(lib) +if(NOT BUILD_EXPORTED_TARGETS_ONLY) + add_subdirectory(test) +endif() -set(INSTALL_LIB_DIR "${CMAKE_INSTALL_PREFIX}/lib" CACHE PATH - "Installation directory for libraries") +# +# Installation +# +add_library(MpiFx INTERFACE) +target_link_libraries(MpiFx INTERFACE mpifx) +install(TARGETS MpiFx EXPORT mpifx-targets) -set(INSTALL_MOD_DIR "${CMAKE_INSTALL_PREFIX}/include" CACHE PATH - "Installation directory for Fortran module files") +install(EXPORT mpifx-targets + FILE mpifx-targets.cmake + NAMESPACE MpiFx:: + DESTINATION "${CMAKE_INSTALL_LIBDIR}/cmake/mpifx") -set(INSTALL_CMAKE_DIR "${CMAKE_INSTALL_PREFIX}/lib/cmake" CACHE PATH - "Installation directory for CMake package export files") +include(CMakePackageConfigHelpers) +configure_package_config_file( + ${CMAKE_CURRENT_SOURCE_DIR}/utils/export/mpifx-config.cmake.in + ${CMAKE_CURRENT_BINARY_DIR}/cmake/mpifx-config.cmake + INSTALL_DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mpifx) -option(BUILD_SHARED_LIBS "Whether the library should be shared" FALSE) +write_basic_package_version_file( + ${CMAKE_CURRENT_BINARY_DIR}/cmake/mpifx-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) -option(INSTALL_INCLUDE_FILES "Whether include and module files should be installed" TRUE) +install( + FILES ${CMAKE_CURRENT_BINARY_DIR}/cmake/mpifx-config.cmake + ${CMAKE_CURRENT_BINARY_DIR}/cmake/mpifx-config-version.cmake + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/mpifx) -find_package(MPI REQUIRED) -find_program(FYPP fypp) -if(FYPP) - message(STATUS "Preprocessor fypp: ${FYPP}") -else() - message(FATAL_ERROR "Prepropcessor fypp not found") -endif() +include(GNUInstallDirs) +GNUInstallDirs_get_absolute_install_dir(CMAKE_INSTALL_FULL_MODULEDIR CMAKE_INSTALL_MODULEDIR) -add_subdirectory(lib) -if(NOT LIBRARY_ONLY) - add_subdirectory(test) -endif() +get_pkgconfig_params(PKGCONFIG_REQUIRES PKGCONFIG_LIBS PKGCONFIG_LIBS_PRIVATE PKGCONFIG_C_FLAGS) +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/utils/export/mpifx.pc.in + ${CMAKE_CURRENT_BINARY_DIR}/mpifx.pc @ONLY) +install( + FILES "${CMAKE_CURRENT_BINARY_DIR}/mpifx.pc" + DESTINATION "${CMAKE_INSTALL_LIBDIR}/pkgconfig") diff --git a/LICENSE b/LICENSE index ae5d9b4..e33defa 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (C) 2018 Bálint Aradi +Copyright (C) 2018 - 2020 DFTB+ developers group All rights reserved. Redistribution and use in source and binary forms, with or without modification, diff --git a/README.rst b/README.rst index b26337e..484d74e 100644 --- a/README.rst +++ b/README.rst @@ -1,55 +1,94 @@ **************************************** -MPIFX - Modern Fortran Interface for MPI +MpiFx - Modern Fortran Interface for MPI **************************************** -The open source library `MPIFX `_ is -an effort to provide modern Fortran (Fortran 2003) wrappers around -routines of the MPI library to make their use as simple as possible. The -documentation is included inside the repository, but is also available at +The open source library `MpiFx `_ provides +modern Fortran (Fortran 2003) wrappers around routines of the MPI library to +make their use as simple as possible. Currently several data distribution +routines are covered. + +The documentation is included inside the repository, but is also available at `dftbplus.github.io `_. -It currently contains only a few routines so far, but if those happen to be the -ones you need, feel free to use this project. MPIFX is licensed under the -**simplified BSD license**. -If your desired MPI routine is not yet wrapped up, feel free to contribute to -the project to include the target functionality. +Installation +============ +Prerequisites +------------- -INSTALL -======= +* CMake (version >= 3.16) + +* Fortran 2003 compatible Fortran compiler + +* MPI-library and wrappers for your compiler + +* `Fypp preprocessor `_. + + +Building and installing the library +----------------------------------- + +The library can be built and installed with the usual CMake-workflow:: -Stand-alone building --------------------- + FC=gfortran cmake -B _build + cmake --build _build + cmake --install _build -#. Make a copy of the file `make.arch.template` as `make.arch`:: +You can influence the configuration via CMake-variables, which are listed in +`config.cmake `_. You can either modify the values directly there +or pass them as command line options at the configuration phase, e.g.:: - cp make.arch.template make.arch + FC=ifort cmake -B _build -DBUILD_LIBRARY_ONLY=True + -#. Configure any settings in `make.arch` in order to adapt it to your - environment. +Testing +------- -#. Issue :: +A few tests / usage examples can be found in the `test/` subdirectory. The +compiled test programs will be in the `test/` subfolder of your build directory. - make - in order to build and library and :: +Using the library +================= - make install +CMake build +----------- - in order to install it. +* Make sure to add the root folder of the installed library to the + ``CMAKE_PREFIX_PATH`` environment variable. -#. You may build the examples in the `test/` subfolder with :: +* Use ``find_package()`` in `CMakeLists.txt` to locate the library and link + ``MpiFx::MpiFx`` to every target which relies directly on the library :: - make test + cmake_minimum_required(VERSION 3.16) + + project(TestMpiFx LANGUAGES Fortran) + + find_package(MpiFx REQUIRED) + + add_executable(test_mpifx test_mpifx.f90) + target_link_libraries(test_mpifx MpiFx::MpiFx) - -Build the library as part of a build process --------------------------------------------- +Pkg-config build +---------------- + +* Make sure to add the `lib/pkgconfig` folder of the installed library to the + ``PKG_CONFIG_PATH`` environment variable. + +* Query the include and library options needed for the build with the usual + ``pkg-config`` commands:: + + mpifort $(pkg-config --cflags mpifx) test_mpifx.f90 $(pkg-config --libs mpifx) + + Note, that neither ``-cflags`` or ``--libs`` return any options related to + your MPI-framework nor is the MPI-framework specified as dependency in the + pkg-config file. Use the MPI-wrapper of your compiler to compile and link your + executable or pass the additional include and library options by hand. + + +License +======= -You may build the library on-the-fly during the build of your program. Invoke -the library makefile `lib/make.build` during your build process from the folder -where you wish to build the library. Make sure to pass the necessary -make-variables (as documented in the library makfile). See the `makefile` in -this folder for an example how to invoke the library makefile. +MpiFx is licensed under the `2-Clause BSD License `_. diff --git a/cmake/MpiFxUtils.cmake b/cmake/MpiFxUtils.cmake new file mode 100644 index 0000000..7fa02b3 --- /dev/null +++ b/cmake/MpiFxUtils.cmake @@ -0,0 +1,58 @@ +# Register custom commands for processing source files with fypp (.fpp -> .f90) +# +# Args: +# oldfiles [in]: List of files to preprocess (must have .fpp suffix) +# newfiles [out]: List of preprocessed files (will have .f90 suffix). +# +function(fypp_preprocess oldfiles newfiles) + + set(_newfiles) + foreach(oldfile IN LISTS oldfiles) + string(REGEX REPLACE "\\.fpp" ".f90" newfile ${oldfile}) + add_custom_command( + OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${newfile} + COMMAND ${FYPP} ${FYPP_FLAGS} ${CMAKE_CURRENT_SOURCE_DIR}/${oldfile} ${CMAKE_CURRENT_BINARY_DIR}/${newfile} + MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${oldfile}) + list(APPEND _newfiles ${CMAKE_CURRENT_BINARY_DIR}/${newfile}) + endforeach() + set(${newfiles} ${_newfiles} PARENT_SCOPE) + +endfunction() + + +# Returns the parameters needed to create a pkg-config export file +# +# Args: +# pkgconfig_requires [out]: Value for the Requires field. +# pkgconfig_libs [out]: Value for the Libs field. +# pkgconfig_libs_private [out]: Value for the Libs.private field. +# pkgconfig_c_flags [out]: Value for the cflags field. +# pkgconfig_prefix [out]: Value for the installation prefix. +# +function(get_pkgconfig_params pkgconfig_requires pkgconfig_libs pkgconfig_libs_private + pkgconfig_c_flags) + + set(_pkgconfig_requires) + + set(_pkgconfig_libs "-L${CMAKE_INSTALL_FULL_LIBDIR} -lmpifx") + + set(_pkgconfig_libs_private "${CMAKE_EXE_LINKER_FLAGS}") + + set(_pkgconfig_c_flags "-I${CMAKE_INSTALL_FULL_MODULEDIR}") + + set(${pkgconfig_requires} "${_pkgconfig_requires}" PARENT_SCOPE) + set(${pkgconfig_libs} "${_pkgconfig_libs}" PARENT_SCOPE) + set(${pkgconfig_libs_private} "${_pkgconfig_libs_private}" PARENT_SCOPE) + set(${pkgconfig_c_flags} "${_pkgconfig_c_flags}" PARENT_SCOPE) + +endfunction() + + +# Sets up the build type. +function (setup_build_type) + set(default_build_type "RelWithDebInfo") + if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) + message(STATUS "Setting build type to ${default_build_type} as none was specified") + set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Build type" FORCE) + endif() +endfunction() diff --git a/config.cmake b/config.cmake new file mode 100644 index 0000000..28d2389 --- /dev/null +++ b/config.cmake @@ -0,0 +1,37 @@ +# +# Build options +# + +# CMAKE_BUILD_TYPE is commented out in order to allow for multi-configuration builds. It will +# automatically default to RelWithDebInfo if used in a single configuration build. Uncomment or +# override it only if you want a non-default single configuration build. +# +#set(CMAKE_BUILD_TYPE "Debug" CACHE STRING "Build type (Release|RelWithDebInfo|Debug|MinSizeRel)") + +# If set to True, only those public targets (typically the library) will be built, which are usually +# exported via CMake export files. Otherwise all targets all built (default case). Set this option +# to True, if you invoke this project as part of an other CMake project via the add_subdirectory() +# command without the EXCLUDE_FROM_ALL option (e.g. if you want this project to install its targets +# as part of the top projects installation process). +# +option(BUILD_EXPORTED_TARGETS_ONLY + "Whether only exported targets (the library, but no tests) should be built" FALSE) + +option(BUILD_SHARED_LIBS "Whether the library should be a shared one" FALSE) + +# +# Installation options +# + +option(INSTALL_INCLUDE_FILES "Whether include / module files should be installed" TRUE) + +set(CMAKE_INSTALL_PREFIX "${CMAKE_BINARY_DIR}/install" CACHE STRING + "Directory to install the compiled code into") + +set(CMAKE_INSTALL_LIBDIR "lib" CACHE PATH "Installation directory for libraries") + +set(CMAKE_INSTALL_INCLUDEDIR "include/mpifx" CACHE PATH + "Installation directory for header and include files") + +set(CMAKE_INSTALL_MODULEDIR "${CMAKE_INSTALL_INCLUDEDIR}/modfiles" CACHE PATH + "Installation directory for Fortran module files") diff --git a/doc/doxygen/fyppf90.sh b/doc/doxygen/fyppf90.sh index 3fe0ab2..405b459 100755 --- a/doc/doxygen/fyppf90.sh +++ b/doc/doxygen/fyppf90.sh @@ -1,4 +1,3 @@ #!/bin/bash srcdir=$(dirname $1) -fyppdir=$srcdir/../external/fypp -$fyppdir/fypp -I$(dirname $1) $1 +fypp -I$(dirname $1) $1 diff --git a/external/fypp/LICENSE.txt b/external/fypp/LICENSE.txt deleted file mode 100644 index a6a775c..0000000 --- a/external/fypp/LICENSE.txt +++ /dev/null @@ -1,24 +0,0 @@ -Copyright (c) 2016-2020 Bálint Aradi, Universität Bremen - -All rights reserved. - -Redistribution and use in source and binary forms, with or without modification, -are permitted provided that the following conditions are met: - -1. Redistributions of source code must retain the above copyright notice, this -list of conditions and the following disclaimer. - -2. Redistributions in binary form must reproduce the above copyright notice, -this list of conditions and the following disclaimer in the documentation and/or -other materials provided with the distribution. - -THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND -ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED -WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR -ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES -(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; -LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON -ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT -(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS -SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/external/fypp/README.rst b/external/fypp/README.rst deleted file mode 100644 index 3d122fa..0000000 --- a/external/fypp/README.rst +++ /dev/null @@ -1,232 +0,0 @@ -********************************************* -Fypp — Python powered Fortran metaprogramming -********************************************* - -.. image:: https://travis-ci.org/aradi/fypp.svg?branch=develop - :target: https://travis-ci.org/aradi/fypp - -Fypp is a Python powered preprocessor. It can be used for any programming -languages but its primary aim is to offer a Fortran preprocessor, which helps to -extend Fortran with condititional compiling and template metaprogramming -capabilities. Instead of introducing its own expression syntax, it uses Python -expressions in its preprocessor directives, offering the consistency and -versatility of Python when formulating metaprogramming tasks. It puts strong -emphasis on robustness and on neat integration into developing toolchains. - -The project is `hosted on github `_. - -`Detailed DOCUMENTATION `_ is available on -`readthedocs.org `_. - -Fypp is released under the *BSD 2-clause license*. - - -Main features -============= - -* Definition, evaluation and removal of variables:: - - #:if DEBUG > 0 - print *, "Some debug information" - #:endif - - #:set LOGLEVEL = 2 - print *, "LOGLEVEL: ${LOGLEVEL}$" - - #:del LOGLEVEL - -* Macro definitions and macro calls:: - - #:def ASSERT(cond) - #:if DEBUG > 0 - if (.not. ${cond}$) then - print *, "Assert failed in file ${_FILE_}$, line ${_LINE_}$" - error stop - end if - #:endif - #:enddef ASSERT - - ! Invoked via direct call (argument needs no quotation) - @:ASSERT(size(myArray) > 0) - - ! Invoked as Python expression (argument needs quotation) - $:ASSERT('size(myArray) > 0') - -* Conditional output:: - - program test - #:if defined('WITH_MPI') - use mpi - #:elif defined('WITH_OPENMP') - use openmp - #:else - use serial - #:endif - -* Iterated output (e.g. for generating Fortran templates):: - - interface myfunc - #:for dtype in ['real', 'dreal', 'complex', 'dcomplex'] - module procedure myfunc_${dtype}$ - #:endfor - end interface myfunc - -* Inline directives:: - - logical, parameter :: hasMpi = #{if defined('MPI')}# .true. #{else}# .false. #{endif}# - -* Insertion of arbitrary Python expressions:: - - character(*), parameter :: comp_date = "${time.strftime('%Y-%m-%d')}$" - -* Inclusion of files during preprocessing:: - - #:include "macrodefs.fypp" - -* Using Fortran-style continutation lines in preprocessor directives:: - - #:if var1 > var2 & - & or var2 > var4 - print *, "Doing something here" - #:endif - -* Passing (unquoted) multiline string arguments to callables:: - - #! Callable needs only string argument - #:def DEBUG_CODE(code) - #:if DEBUG > 0 - $:code - #:endif - #:enddef DEBUG_CODE - - #! Pass code block as first positional argument - #:block DEBUG_CODE - if (size(array) > 100) then - print *, "DEBUG: spuriously large array" - end if - #:endblock DEBUG_CODE - - #! Callable needs also non-string argument types - #:def REPEAT_CODE(code, repeat) - #:for ind in range(repeat) - $:code - #:endfor - #:enddef REPEAT_CODE - - #! Pass code block as positional argument and 3 as keyword argument "repeat" - #:block REPEAT_CODE(repeat=3) - this will be repeated 3 times - #:endblock REPEAT_CODE - -* Preprocessor comments:: - - #! This will not show up in the output - #! Also the newline characters at the end of the lines will be suppressed - -* Suppressing the preprocessor output in selected regions:: - - #! Definitions are read, but no output (e.g. newlines) will be produced - #:mute - #:include "macrodefs.fypp" - #:endmute - -* Explicit request for stopping the preprocessor:: - - #:if DEBUGLEVEL < 0 - #:stop 'Negative debug level not allowed!' - #:endif - -* Easy check for macro parameter sanity:: - - #:def mymacro(RANK) - #! Macro only works for RANK 1 and above - #:assert RANK > 0 - : - #:enddef mymacro - -* Line numbering directives in output:: - - program test - #:if defined('MPI') - use mpi - #:endif - : - - transformed to :: - - # 1 "test.fypp" 1 - program test - # 3 "test.fypp" - use mpi - # 5 "test.fypp" - : - - when variable ``MPI`` is defined and Fypp was instructed to generate line - markers. - -* Automatic folding of generated lines exceeding line length limit - - -Installing -========== - -Fypp needs a working Python interpreter. It is compatible with Python 2 (version -2.6 and above) and Python 3 (all versions). - -Automatic install ------------------ - -Use Pythons command line installer ``pip`` in order to download the stable -release from the `Fypp page on PyPI `_ and -install it on your system:: - - pip install fypp - -This installs both, the command line tool ``fypp`` and the Python module -``fypp.py``. Latter you can import if you want to access the functionality of -Fypp directly from within your Python scripts. - - -Manual install --------------- - -For a manual install, you can download the source code of the **stable** -releases from the `Fypp project website -`_. - -If you wish to obtain the latest **development** version, clone the projects -repository:: - - git clone https://github.com/aradi/fypp.git - -and check out the default branch. - -The command line tool is a single stand-alone script. You can run it directly -from the source folder :: - - FYPP_SOURCE_FOLDER/bin/fypp - -or after copying it from the `bin` folder to any location listed in your `PATH` -environment variable, by just issuing :: - - fypp - -The python module ``fypp.py`` can be found in ``FYP_SOURCE_FOLDER/src``. - - -Running -======= - -The Fypp command line tool reads a file, preprocesses it and writes it to -another file, so you would typically invoke it like:: - - fypp source.fpp source.f90 - -which would process `source.fpp` and write the result to `source.f90`. If -input and output files are not specified, information is read from stdin and -written to stdout. - -The behavior of Fypp can be influenced with various command line options. A -summary of all command line options can be obtained by:: - - fypp -h diff --git a/external/fypp/fypp b/external/fypp/fypp deleted file mode 100755 index 31e5e32..0000000 --- a/external/fypp/fypp +++ /dev/null @@ -1,3020 +0,0 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- -################################################################################ -# -# fypp -- Python powered Fortran preprocessor -# -# Copyright (c) 2016-2020 Bálint Aradi, Universität Bremen -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# 1. Redistributions of source code must retain the above copyright notice, this -# list of conditions and the following disclaimer. -# -# 2. Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 'AS IS' -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE -# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE -# FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL -# DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR -# SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER -# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -# OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE -# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. -# -################################################################################ - -'''For using the functionality of the Fypp preprocessor from within -Python, one usually interacts with the following two classes: - -* `Fypp`_: The actual Fypp preprocessor. It returns for a given input - the preprocessed output. - -* `FyppOptions`_: Contains customizable settings controling the behaviour of - `Fypp`_. Alternatively, the function `get_option_parser()`_ can be used to - obtain an option parser, which can create settings based on command line - arguments. - -If processing stops prematurely, an instance of one of the following -subclasses of `FyppError`_ is raised: - -* FyppFatalError: Unexpected error (e.g. bad input, missing files, etc.) - -* FyppStopRequest: Stop was triggered by an explicit request in the input - (by a stop- or an assert-directive). -''' - -from __future__ import print_function -import sys -import types -import inspect -import re -import os -import errno -import time -import optparse -import io -if sys.version_info[0] >= 3: - import builtins -else: - import __builtin__ as builtins - -# Prevent cluttering user directory with Python bytecode -sys.dont_write_bytecode = True - -VERSION = '3.0' - -STDIN = '' - -FILEOBJ = '' - -STRING = '' - -ERROR_EXIT_CODE = 1 - -USER_ERROR_EXIT_CODE = 2 - -_ALL_DIRECTIVES_PATTERN = r''' -# comment block -(?:^[ \t]*\#!.*\n)+ -| -# line directive (with optional continuation lines) -^[ \t]*(?P[\#\$@]):[ \t]* -(?P.+?(?:&[ \t]*\n(?:[ \t]*&)?.*?)*)?[ \t]*\n -| -# inline eval directive -(?P[$\#@])\{[ \t]*(?P.+?)?[ \t]*\}(?P=idirtype) -''' - -_ALL_DIRECTIVES_REGEXP = re.compile( - _ALL_DIRECTIVES_PATTERN, re.VERBOSE | re.MULTILINE) - -_CONTROL_DIR_REGEXP = re.compile( - r'(?P[a-zA-Z_]\w*)[ \t]*(?:[ \t]+(?P[^ \t].*))?$') - -_DIRECT_CALL_REGEXP = re.compile( - r'(?P[a-zA-Z_][\w.]*)[ \t]*\((?P.+?)?\)$') - -_DIRECT_CALL_KWARG_REGEXP = re.compile( - r'(?:(?P[a-zA-Z_]\w*)\s*=(?=[^=]|$))?') - -_DEF_PARAM_REGEXP = re.compile( - r'^(?P[a-zA-Z_]\w*)[ \t]*\(\s*(?P.+)?\s*\)$') - -_SIMPLE_CALLABLE_REGEXP = re.compile( - r'^(?P[a-zA-Z_][\w.]*)[ \t]*(?:\([ \t]*(?P.*)[ \t]*\))?$') - -_IDENTIFIER_NAME_REGEXP = re.compile(r'^(?P[a-zA-Z_]\w*)$') - -_PREFIXED_IDENTIFIER_NAME_REGEXP = re.compile(r'^(?P[a-zA-Z_][\w.]*)$') - -_SET_PARAM_REGEXP = re.compile( - r'^(?P(?:[(]\s*)?[a-zA-Z_]\w*(?:\s*,\s*[a-zA-Z_]\w*)*(?:\s*[)])?)\s*'\ - r'(?:=\s*(?P.*))?$') - -_DEL_PARAM_REGEXP = re.compile( - r'^(?:[(]\s*)?[a-zA-Z_]\w*(?:\s*,\s*[a-zA-Z_]\w*)*(?:\s*[)])?$') - -_FOR_PARAM_REGEXP = re.compile( - r'^(?P[a-zA-Z_]\w*(\s*,\s*[a-zA-Z_]\w*)*)\s+in\s+(?P.+)$') - -_INCLUDE_PARAM_REGEXP = re.compile(r'^(\'|")(?P.*?)\1$') - -_COMMENTLINE_REGEXP = re.compile(r'^[ \t]*!.*$') - -_CONTLINE_REGEXP = re.compile(r'&[ \t]*\n(?:[ \t]*&)?') - -_UNESCAPE_TEXT_REGEXP1 = re.compile(r'([$#@])\\(\\*)([{:])') - -_UNESCAPE_TEXT_REGEXP2 = re.compile(r'#\\(\\*)([!])') - -_UNESCAPE_TEXT_REGEXP3 = re.compile(r'(\})\\(\\*)([$#@])') - -_INLINE_EVAL_REGION_REGEXP = re.compile(r'\${.*?}\$') - -_RESERVED_PREFIX = '__' - -_RESERVED_NAMES = set(['defined', 'setvar', 'getvar', 'delvar', 'globalvar', - '_LINE_', '_FILE_', '_THIS_FILE_', '_THIS_LINE_', - '_TIME_', '_DATE_']) - -_LINENUM_NEW_FILE = 1 - -_LINENUM_RETURN_TO_FILE = 2 - -_QUOTES_FORTRAN = '\'"' - -_OPENING_BRACKETS_FORTRAN = '{([' - -_CLOSING_BRACKETS_FORTRAN = '})]' - -_ARGUMENT_SPLIT_CHAR_FORTRAN = ',' - - -class FyppError(Exception): - '''Signalizes error occuring during preprocessing. - - Args: - msg (str): Error message. - fname (str): File name. None (default) if file name is not available. - span (tuple of int): Beginning and end line of the region where error - occured or None if not available. If fname was not None, span must - not be None. - cause (Exception): Contains the exception, which triggered this - exception or None, if this exception is not masking any underlying - one. (Emulates Python 3 exception chaining in a Python 2 compatible - way.) - - Attributes: - msg (str): Error message. - fname (str or None): File name or None if not available. - span (tuple of int or None): Beginning and end line of the region - where error occured or None if not available. Line numbers start - from zero. For directives, which do not consume end of the line, - start and end lines are identical. - cause (Exception): In case this exception is raised in an except block, - the original exception should be passed here. (Emulates Python 3 - exception chaining in a Python 2 compatible way.) - ''' - - def __init__(self, msg, fname=None, span=None, cause=None): - super(FyppError, self).__init__() - self.msg = msg - self.fname = fname - self.span = span - self.cause = cause - - - def __str__(self): - msg = [self.__class__.__name__, ': '] - if self.fname is not None: - msg.append("file '" + self.fname + "'") - if self.span[1] > self.span[0] + 1: - msg.append(', lines {0}-{1}'.format( - self.span[0] + 1, self.span[1])) - else: - msg.append(', line {0}'.format(self.span[0] + 1)) - msg.append('\n') - if self.msg: - msg.append(self.msg) - if self.cause is not None: - msg.append('\n' + str(self.cause)) - return ''.join(msg) - - -class FyppFatalError(FyppError): - '''Signalizes an unexpected error during processing.''' - - -class FyppStopRequest(FyppError): - '''Signalizes an explicitely triggered stop (e.g. via stop directive)''' - - -class Parser: - '''Parses a text and generates events when encountering Fypp constructs. - - Args: - includedirs (list): List of directories, in which include files should - be searched for, when they are not found at the default location. - - encoding (str): Encoding to use when reading the file (default: utf-8) - ''' - - def __init__(self, includedirs=None, encoding='utf-8'): - - # Directories to search for include files - if includedirs is None: - self._includedirs = [] - else: - self._includedirs = includedirs - - # Encoding - self._encoding = encoding - - # Name of current file - self._curfile = None - - # Directory of current file - self._curdir = None - - - def parsefile(self, fobj): - '''Parses file or a file like object. - - Args: - fobj (str or file): Name of a file or a file like object. - ''' - if isinstance(fobj, str): - if fobj == STDIN: - self._includefile(None, sys.stdin, STDIN, os.getcwd()) - else: - inpfp = _open_input_file(fobj, self._encoding) - self._includefile(None, inpfp, fobj, os.path.dirname(fobj)) - inpfp.close() - else: - self._includefile(None, fobj, FILEOBJ, os.getcwd()) - - - def _includefile(self, span, fobj, fname, curdir): - oldfile = self._curfile - olddir = self._curdir - self._curfile = fname - self._curdir = curdir - self._parse_txt(span, fname, fobj.read()) - self._curfile = oldfile - self._curdir = olddir - - - def parse(self, txt): - '''Parses string. - - Args: - txt (str): Text to parse. - ''' - self._curfile = STRING - self._curdir = '' - self._parse_txt(None, self._curfile, txt) - - - def handle_include(self, span, fname): - '''Called when parser starts to process a new file. - - It is a dummy methond and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the include directive - or None if called the first time for the main input. - fname (str): Name of the file. - ''' - self._log_event('include', span, filename=fname) - - - def handle_endinclude(self, span, fname): - '''Called when parser finished processing a file. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the include directive - or None if called the first time for the main input. - fname (str): Name of the file. - ''' - self._log_event('endinclude', span, filename=fname) - - - def handle_set(self, span, name, expr): - '''Called when parser encounters a set directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the variable. - expr (str): String representation of the expression to be assigned - to the variable. - ''' - self._log_event('set', span, name=name, expression=expr) - - - def handle_def(self, span, name, args): - '''Called when parser encounters a def directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the macro to be defined. - argexpr (str): String with argument definition (or None) - ''' - self._log_event('def', span, name=name, arguments=args) - - - def handle_enddef(self, span, name): - '''Called when parser encounters an enddef directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name found after the enddef directive. - ''' - self._log_event('enddef', span, name=name) - - - def handle_del(self, span, name): - '''Called when parser encounters a del directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the variable to delete. - ''' - self._log_event('del', span, name=name) - - - def handle_if(self, span, cond): - '''Called when parser encounters an if directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - cond (str): String representation of the branching condition. - ''' - self._log_event('if', span, condition=cond) - - - def handle_elif(self, span, cond): - '''Called when parser encounters an elif directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - cond (str): String representation of the branching condition. - ''' - self._log_event('elif', span, condition=cond) - - - def handle_else(self, span): - '''Called when parser encounters an else directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('else', span) - - - def handle_endif(self, span): - '''Called when parser encounters an endif directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('endif', span) - - - def handle_for(self, span, varexpr, iterator): - '''Called when parser encounters a for directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - varexpr (str): String representation of the loop variable - expression. - iterator (str): String representation of the iterable. - ''' - self._log_event('for', span, variable=varexpr, iterable=iterator) - - - def handle_endfor(self, span): - '''Called when parser encounters an endfor directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('endfor', span) - - - def handle_call(self, span, name, argexpr, blockcall): - '''Called when parser encounters a call directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the callable to call - argexpr (str or None): Argument expression containing additional - arguments for the call. - blockcall (bool): Whether the alternative "block / contains / - endblock" calling directive has been used. - ''' - self._log_event('call', span, name=name, argexpr=argexpr, - blockcall=blockcall) - - - def handle_nextarg(self, span, name, blockcall): - '''Called when parser encounters a nextarg directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str or None): Name of the argument following next or - None if it should be the next positional argument. - blockcall (bool): Whether the alternative "block / contains / - endblock" calling directive has been used. - ''' - self._log_event('nextarg', span, name=name, blockcall=blockcall) - - - def handle_endcall(self, span, name, blockcall): - '''Called when parser encounters an endcall directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name found after the endcall directive. - blockcall (bool): Whether the alternative "block / contains / - endblock" calling directive has been used. - ''' - self._log_event('endcall', span, name=name, blockcall=blockcall) - - - def handle_eval(self, span, expr): - '''Called when parser encounters an eval directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - expr (str): String representation of the Python expression to - be evaluated. - ''' - self._log_event('eval', span, expression=expr) - - - def handle_global(self, span, name): - '''Called when parser encounters a global directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the variable which should be made global. - ''' - self._log_event('global', span, name=name) - - - def handle_text(self, span, txt): - '''Called when parser finds text which must left unaltered. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - txt (str): Text. - ''' - self._log_event('text', span, content=txt) - - - def handle_comment(self, span): - '''Called when parser finds a preprocessor comment. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('comment', span) - - - def handle_mute(self, span): - '''Called when parser finds a mute directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('mute', span) - - - def handle_endmute(self, span): - '''Called when parser finds an endmute directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('endmute', span) - - - def handle_stop(self, span, msg): - '''Called when parser finds an stop directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - msg (str): Stop message. - ''' - self._log_event('stop', span, msg=msg) - - - def handle_assert(self, span): - '''Called when parser finds an assert directive. - - It is a dummy method and should be overriden for actual use. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._log_event('assert', span) - - - @staticmethod - def _log_event(event, span=(-1, -1), **params): - print('{0}: {1} --> {2}'.format(event, span[0], span[1])) - for parname, parvalue in params.items(): - print(' {0}: ->|{1}|<-'.format(parname, parvalue)) - print() - - - def _parse_txt(self, includespan, fname, txt): - self.handle_include(includespan, fname) - self._parse(txt) - self.handle_endinclude(includespan, fname) - - - def _parse(self, txt, linenr=0, directcall=False): - pos = 0 - for match in _ALL_DIRECTIVES_REGEXP.finditer(txt): - start, end = match.span() - if start > pos: - endlinenr = linenr + txt.count('\n', pos, start) - self._process_text(txt[pos:start], (linenr, endlinenr)) - linenr = endlinenr - endlinenr = linenr + txt.count('\n', start, end) - span = (linenr, endlinenr) - ldirtype, ldir, idirtype, idir = match.groups() - if directcall and (idirtype is None or idirtype != '$'): - msg = 'only inline eval directives allowed in direct calls' - raise FyppFatalError(msg, self._curfile, span) - elif idirtype is not None: - if idir is None: - msg = 'missing inline directive content' - raise FyppFatalError(msg, self._curfile, span) - dirtype = idirtype - content = idir - elif ldirtype is not None: - if ldir is None: - msg = 'missing line directive content' - raise FyppFatalError(msg, self._curfile, span) - dirtype = ldirtype - content = _CONTLINE_REGEXP.sub('', ldir) - else: - # Comment directive - dirtype = None - if dirtype == '$': - self.handle_eval(span, content) - elif dirtype == '#': - self._process_control_dir(content, span) - elif dirtype == '@': - self._process_direct_call(content, span) - else: - self.handle_comment(span) - pos = end - linenr = endlinenr - if pos < len(txt): - endlinenr = linenr + txt.count('\n', pos) - self._process_text(txt[pos:], (linenr, endlinenr)) - - - def _process_text(self, txt, span): - escaped_txt = self._unescape(txt) - self.handle_text(span, escaped_txt) - - - def _process_control_dir(self, content, span): - match = _CONTROL_DIR_REGEXP.match(content) - if not match: - msg = "invalid control directive content '{0}'".format(content) - raise FyppFatalError(msg, self._curfile, span) - directive, param = match.groups() - if directive == 'if': - self._check_param_presence(True, 'if', param, span) - self.handle_if(span, param) - elif directive == 'else': - self._check_param_presence(False, 'else', param, span) - self.handle_else(span) - elif directive == 'elif': - self._check_param_presence(True, 'elif', param, span) - self.handle_elif(span, param) - elif directive == 'endif': - self._check_param_presence(False, 'endif', param, span) - self.handle_endif(span) - elif directive == 'def': - self._check_param_presence(True, 'def', param, span) - self._check_not_inline_directive('def', span) - self._process_def(param, span) - elif directive == 'enddef': - self._process_enddef(param, span) - elif directive == 'set': - self._check_param_presence(True, 'set', param, span) - self._process_set(param, span) - elif directive == 'del': - self._check_param_presence(True, 'del', param, span) - self._process_del(param, span) - elif directive == 'for': - self._check_param_presence(True, 'for', param, span) - self._process_for(param, span) - elif directive == 'endfor': - self._check_param_presence(False, 'endfor', param, span) - self.handle_endfor(span) - elif directive == 'call' or directive == 'block': - self._check_param_presence(True, directive, param, span) - self._process_call(param, span, directive == 'block') - elif directive == 'nextarg' or directive == 'contains': - self._process_nextarg(param, span, directive == 'contains') - elif directive == 'endcall' or directive == 'endblock': - self._process_endcall(param, span, directive == 'endblock') - elif directive == 'include': - self._check_param_presence(True, 'include', param, span) - self._check_not_inline_directive('include', span) - self._process_include(param, span) - elif directive == 'mute': - self._check_param_presence(False, 'mute', param, span) - self._check_not_inline_directive('mute', span) - self.handle_mute(span) - elif directive == 'endmute': - self._check_param_presence(False, 'endmute', param, span) - self._check_not_inline_directive('endmute', span) - self.handle_endmute(span) - elif directive == 'stop': - self._check_param_presence(True, 'stop', param, span) - self._check_not_inline_directive('stop', span) - self.handle_stop(span, param) - elif directive == 'assert': - self._check_param_presence(True, 'assert', param, span) - self._check_not_inline_directive('assert', span) - self.handle_assert(span, param) - elif directive == 'global': - self._check_param_presence(True, 'global', param, span) - self._process_global(param, span) - else: - msg = "unknown directive '{0}'".format(directive) - raise FyppFatalError(msg, self._curfile, span) - - - def _process_direct_call(self, callexpr, span): - match = _DIRECT_CALL_REGEXP.match(callexpr) - if not match: - msg = "invalid direct call expression" - raise FyppFatalError(msg, self._curfile, span) - callname = match.group('callname') - self.handle_call(span, callname, None, False) - callparams = match.group('callparams') - if callparams is None or not callparams.strip(): - args = [] - else: - try: - args = [arg.strip() for arg in _argsplit_fortran(callparams)] - except Exception as exc: - msg = 'unable to parse direct call argument' - raise FyppFatalError(msg, self._curfile, span, exc) - for arg in args: - match = _DIRECT_CALL_KWARG_REGEXP.match(arg) - argval = arg[match.end():].strip() - # Remove enclosing braces if present - if argval.startswith('{'): - argval = argval[1:-1] - keyword = match.group('kwname') - self.handle_nextarg(span, keyword, False) - self._parse(argval, linenr=span[0], directcall=True) - self.handle_endcall(span, callname, False) - - - def _process_def(self, param, span): - match = _DEF_PARAM_REGEXP.match(param) - if not match: - msg = "invalid macro definition '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - name = match.group('name') - argexpr = match.group('args') - self.handle_def(span, name, argexpr) - - - def _process_enddef(self, param, span): - if param is not None: - match = _IDENTIFIER_NAME_REGEXP.match(param) - if not match: - msg = "invalid enddef parameter '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - param = match.group('name') - self.handle_enddef(span, param) - - - def _process_set(self, param, span): - match = _SET_PARAM_REGEXP.match(param) - if not match: - msg = "invalid variable assignment '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - self.handle_set(span, match.group('name'), match.group('expr')) - - - def _process_global(self, param, span): - match = _DEL_PARAM_REGEXP.match(param) - if not match: - msg = "invalid variable specification '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - self.handle_global(span, param) - - - def _process_del(self, param, span): - match = _DEL_PARAM_REGEXP.match(param) - if not match: - msg = "invalid variable specification '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - self.handle_del(span, param) - - - def _process_for(self, param, span): - match = _FOR_PARAM_REGEXP.match(param) - if not match: - msg = "invalid for loop declaration '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - loopexpr = match.group('loopexpr') - loopvars = [s.strip() for s in loopexpr.split(',')] - self.handle_for(span, loopvars, match.group('iter')) - - - def _process_call(self, param, span, blockcall): - match = _SIMPLE_CALLABLE_REGEXP.match(param) - if not match: - msg = "invalid callable expression '{}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - name, args = match.groups() - self.handle_call(span, name, args, blockcall) - - - def _process_nextarg(self, param, span, blockcall): - if param is not None: - match = _IDENTIFIER_NAME_REGEXP.match(param) - if not match: - msg = "invalid nextarg parameter '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - param = match.group('name') - self.handle_nextarg(span, param, blockcall) - - - def _process_endcall(self, param, span, blockcall): - if param is not None: - match = _PREFIXED_IDENTIFIER_NAME_REGEXP.match(param) - if not match: - msg = "invalid endcall parameter '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - param = match.group('name') - self.handle_endcall(span, param, blockcall) - - - def _process_include(self, param, span): - match = _INCLUDE_PARAM_REGEXP.match(param) - if not match: - msg = "invalid include file declaration '{0}'".format(param) - raise FyppFatalError(msg, self._curfile, span) - fname = match.group('fname') - for incdir in [self._curdir] + self._includedirs: - fpath = os.path.join(incdir, fname) - if os.path.exists(fpath): - break - else: - msg = "include file '{0}' not found".format(fname) - raise FyppFatalError(msg, self._curfile, span) - inpfp = _open_input_file(fpath, self._encoding) - self._includefile(span, inpfp, fpath, os.path.dirname(fpath)) - inpfp.close() - - - def _process_mute(self, span): - if span[0] == span[1]: - msg = 'Inline form of mute directive not allowed' - raise FyppFatalError(msg, self._curfile, span) - self.handle_mute(span) - - - def _process_endmute(self, span): - if span[0] == span[1]: - msg = 'Inline form of endmute directive not allowed' - raise FyppFatalError(msg, self._curfile, span) - self.handle_endmute(span) - - - def _check_param_presence(self, presence, directive, param, span): - if (param is not None) != presence: - if presence: - msg = 'missing data in {0} directive'.format(directive) - else: - msg = 'forbidden data in {0} directive'.format(directive) - raise FyppFatalError(msg, self._curfile, span) - - - def _check_not_inline_directive(self, directive, span): - if span[0] == span[1]: - msg = 'Inline form of {0} directive not allowed'.format(directive) - raise FyppFatalError(msg, self._curfile, span) - - - @staticmethod - def _unescape(txt): - txt = _UNESCAPE_TEXT_REGEXP1.sub(r'\1\2\3', txt) - txt = _UNESCAPE_TEXT_REGEXP2.sub(r'#\1\2', txt) - txt = _UNESCAPE_TEXT_REGEXP3.sub(r'\1\2\3', txt) - return txt - - -class Builder: - '''Builds a tree representing a text with preprocessor directives. - ''' - - def __init__(self): - # The tree, which should be built. - self._tree = [] - - # List of all open constructs - self._open_blocks = [] - - # Nodes to which the open blocks have to be appended when closed - self._path = [] - - # Nr. of open blocks when file was opened. Used for checking whether all - # blocks have been closed, when file processing finishes. - self._nr_prev_blocks = [] - - # Current node, to which content should be added - self._curnode = self._tree - - # Current file - self._curfile = None - - - def reset(self): - '''Resets the builder so that it starts to build a new tree.''' - self._tree = [] - self._open_blocks = [] - self._path = [] - self._nr_prev_blocks = [] - self._curnode = self._tree - self._curfile = None - - - def handle_include(self, span, fname): - '''Should be called to signalize change to new file. - - Args: - span (tuple of int): Start and end line of the include directive - or None if called the first time for the main input. - fname (str): Name of the file to be included. - ''' - self._path.append(self._curnode) - self._curnode = [] - self._open_blocks.append( - ('include', self._curfile, [span], fname, None)) - self._curfile = fname - self._nr_prev_blocks.append(len(self._open_blocks)) - - - def handle_endinclude(self, span, fname): - '''Should be called when processing of a file finished. - - Args: - span (tuple of int): Start and end line of the include directive - or None if called the first time for the main input. - fname (str): Name of the file which has been included. - ''' - nprev_blocks = self._nr_prev_blocks.pop(-1) - if len(self._open_blocks) > nprev_blocks: - directive, fname, spans = self._open_blocks[-1][0:3] - msg = '{0} directive still unclosed when reaching end of file'\ - .format(directive) - raise FyppFatalError(msg, self._curfile, spans[0]) - block = self._open_blocks.pop(-1) - directive, blockfname, spans = block[0:3] - if directive != 'include': - msg = 'internal error: last open block is not \'include\' when '\ - 'closing file \'{0}\''.format(fname) - raise FyppFatalError(msg) - if span != spans[0]: - msg = 'internal error: span for include and endinclude differ ('\ - '{0} vs {1}'.format(span, spans[0]) - raise FyppFatalError(msg) - oldfname, _ = block[3:5] - if fname != oldfname: - msg = 'internal error: mismatching file name in close_file event'\ - " (expected: '{0}', got: '{1}')".format(oldfname, fname) - raise FyppFatalError(msg, fname) - block = directive, blockfname, spans, fname, self._curnode - self._curnode = self._path.pop(-1) - self._curnode.append(block) - self._curfile = blockfname - - - def handle_if(self, span, cond): - '''Should be called to signalize an if directive. - - Args: - span (tuple of int): Start and end line of the directive. - param (str): String representation of the branching condition. - ''' - self._path.append(self._curnode) - self._curnode = [] - self._open_blocks.append(('if', self._curfile, [span], [cond], [])) - - - def handle_elif(self, span, cond): - '''Should be called to signalize an elif directive. - - Args: - span (tuple of int): Start and end line of the directive. - cond (str): String representation of the branching condition. - ''' - self._check_for_open_block(span, 'elif') - block = self._open_blocks[-1] - directive, _, spans = block[0:3] - self._check_if_matches_last(directive, 'if', spans[-1], span, 'elif') - conds, contents = block[3:5] - conds.append(cond) - contents.append(self._curnode) - spans.append(span) - self._curnode = [] - - - def handle_else(self, span): - '''Should be called to signalize an else directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._check_for_open_block(span, 'else') - block = self._open_blocks[-1] - directive, _, spans = block[0:3] - self._check_if_matches_last(directive, 'if', spans[-1], span, 'else') - conds, contents = block[3:5] - conds.append('True') - contents.append(self._curnode) - spans.append(span) - self._curnode = [] - - - def handle_endif(self, span): - '''Should be called to signalize an endif directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._check_for_open_block(span, 'endif') - block = self._open_blocks.pop(-1) - directive, _, spans = block[0:3] - self._check_if_matches_last(directive, 'if', spans[-1], span, 'endif') - _, contents = block[3:5] - contents.append(self._curnode) - spans.append(span) - self._curnode = self._path.pop(-1) - self._curnode.append(block) - - - def handle_for(self, span, loopvar, iterator): - '''Should be called to signalize a for directive. - - Args: - span (tuple of int): Start and end line of the directive. - varexpr (str): String representation of the loop variable - expression. - iterator (str): String representation of the iterable. - ''' - self._path.append(self._curnode) - self._curnode = [] - self._open_blocks.append(('for', self._curfile, [span], loopvar, - iterator, None)) - - - def handle_endfor(self, span): - '''Should be called to signalize an endfor directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._check_for_open_block(span, 'endfor') - block = self._open_blocks.pop(-1) - directive, fname, spans = block[0:3] - self._check_if_matches_last(directive, 'for', spans[-1], span, 'endfor') - loopvar, iterator, dummy = block[3:6] - spans.append(span) - block = (directive, fname, spans, loopvar, iterator, self._curnode) - self._curnode = self._path.pop(-1) - self._curnode.append(block) - - - def handle_def(self, span, name, argexpr): - '''Should be called to signalize a def directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the macro to be defined. - argexpr (str): Macro argument definition or None - ''' - self._path.append(self._curnode) - self._curnode = [] - defblock = ('def', self._curfile, [span], name, argexpr, None) - self._open_blocks.append(defblock) - - - def handle_enddef(self, span, name): - '''Should be called to signalize an enddef directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the enddef statement. Could be None, if enddef - was specified without name. - ''' - self._check_for_open_block(span, 'enddef') - block = self._open_blocks.pop(-1) - directive, fname, spans = block[0:3] - self._check_if_matches_last(directive, 'def', spans[-1], span, 'enddef') - defname, argexpr, dummy = block[3:6] - if name is not None and name != defname: - msg = "wrong name in enddef directive "\ - "(expected '{0}', got '{1}')".format(defname, name) - raise FyppFatalError(msg, fname, span) - spans.append(span) - block = (directive, fname, spans, defname, argexpr, self._curnode) - self._curnode = self._path.pop(-1) - self._curnode.append(block) - - - def handle_call(self, span, name, argexpr, blockcall): - '''Should be called to signalize a call directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the callable to call - argexpr (str or None): Argument expression containing additional - arguments for the call. - blockcall (bool): Whether the alternative "block / contains / - endblock" calling directive has been used. - ''' - self._path.append(self._curnode) - self._curnode = [] - directive = 'block' if blockcall else 'call' - self._open_blocks.append( - (directive, self._curfile, [span, span], name, argexpr, [], [])) - - - def handle_nextarg(self, span, name, blockcall): - '''Should be called to signalize a nextarg directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str or None): Name of the argument following next or - None if it should be the next positional argument. - blockcall (bool): Whether the alternative "block / contains / - endblock" calling directive has been used. - ''' - self._check_for_open_block(span, 'nextarg') - block = self._open_blocks[-1] - directive, fname, spans = block[0:3] - if blockcall: - opened, current = 'block', 'contains' - else: - opened, current = 'call', 'nextarg' - self._check_if_matches_last(directive, opened, spans[-1], span, current) - args, argnames = block[5:7] - args.append(self._curnode) - spans.append(span) - if name is not None: - argnames.append(name) - elif argnames: - msg = 'non-keyword argument following keyword argument' - raise FyppFatalError(msg, fname, span) - self._curnode = [] - - - def handle_endcall(self, span, name, blockcall): - '''Should be called to signalize an endcall directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the endcall statement. Could be None, if endcall - was specified without name. - blockcall (bool): Whether the alternative "block / contains / - endblock" calling directive has been used. - ''' - self._check_for_open_block(span, 'endcall') - block = self._open_blocks.pop(-1) - directive, fname, spans = block[0:3] - callname, callargexpr, args, argnames = block[3:7] - if blockcall: - opened, current = 'block', 'endblock' - else: - opened, current = 'call', 'endcall' - self._check_if_matches_last(directive, opened, spans[0], span, current) - - if name is not None and name != callname: - msg = "wrong name in {0} directive "\ - "(expected '{1}', got '{2}')".format(current, callname, name) - raise FyppFatalError(msg, fname, span) - args.append(self._curnode) - # If nextarg or endcall immediately followed call, then first argument - # is empty and should be removed (to allow for calls without arguments - # and named first argument in calls) - if args and not args[0]: - if len(argnames) == len(args): - del argnames[0] - del args[0] - del spans[1] - spans.append(span) - block = (directive, fname, spans, callname, callargexpr, args, argnames) - self._curnode = self._path.pop(-1) - self._curnode.append(block) - - - def handle_set(self, span, name, expr): - '''Should be called to signalize a set directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the variable. - expr (str): String representation of the expression to be assigned - to the variable. - ''' - self._curnode.append(('set', self._curfile, span, name, expr)) - - - def handle_global(self, span, name): - '''Should be called to signalize a global directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the variable(s) to make global. - ''' - self._curnode.append(('global', self._curfile, span, name)) - - - def handle_del(self, span, name): - '''Should be called to signalize a del directive. - - Args: - span (tuple of int): Start and end line of the directive. - name (str): Name of the variable(s) to delete. - ''' - self._curnode.append(('del', self._curfile, span, name)) - - - def handle_eval(self, span, expr): - '''Should be called to signalize an eval directive. - - Args: - span (tuple of int): Start and end line of the directive. - expr (str): String representation of the Python expression to - be evaluated. - ''' - self._curnode.append(('eval', self._curfile, span, expr)) - - - def handle_comment(self, span): - '''Should be called to signalize a comment directive. - - The content of the comment is not needed by the builder, but it needs - the span of the comment to generate proper line numbers if needed. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._curnode.append(('comment', self._curfile, span)) - - - def handle_text(self, span, txt): - '''Should be called to pass text which goes to output unaltered. - - Args: - span (tuple of int): Start and end line of the text. - txt (str): Text. - ''' - self._curnode.append(('txt', self._curfile, span, txt)) - - - def handle_mute(self, span): - '''Should be called to signalize a mute directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._path.append(self._curnode) - self._curnode = [] - self._open_blocks.append(('mute', self._curfile, [span], None)) - - - def handle_endmute(self, span): - '''Should be called to signalize an endmute directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._check_for_open_block(span, 'endmute') - block = self._open_blocks.pop(-1) - directive, fname, spans = block[0:3] - self._check_if_matches_last(directive, 'mute', spans[-1], span, - 'endmute') - spans.append(span) - block = (directive, fname, spans, self._curnode) - self._curnode = self._path.pop(-1) - self._curnode.append(block) - - - def handle_stop(self, span, msg): - '''Should be called to signalize a stop directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._curnode.append(('stop', self._curfile, span, msg)) - - - def handle_assert(self, span, cond): - '''Should be called to signalize an assert directive. - - Args: - span (tuple of int): Start and end line of the directive. - ''' - self._curnode.append(('assert', self._curfile, span, cond)) - - - @property - def tree(self): - '''Returns the tree built by the Builder.''' - return self._tree - - - def _check_for_open_block(self, span, directive): - if len(self._open_blocks) <= self._nr_prev_blocks[-1]: - msg = 'unexpected {0} directive'.format(directive) - raise FyppFatalError(msg, self._curfile, span) - - - def _check_if_matches_last(self, lastdir, curdir, lastspan, curspan, - directive): - if curdir != lastdir: - msg = "mismatching '{0}' directive (last block opened was '{1}')"\ - .format(directive, lastdir) - raise FyppFatalError(msg, self._curfile, curspan) - inline_last = lastspan[0] == lastspan[1] - inline_cur = curspan[0] == curspan[1] - if inline_last != inline_cur: - if inline_cur: - msg = 'expecting line form of directive {0}'.format(directive) - else: - msg = 'expecting inline form of directive {0}'.format(directive) - raise FyppFatalError(msg, self._curfile, curspan) - elif inline_cur and curspan[0] != lastspan[0]: - msg = 'inline directives of the same construct must be in the '\ - 'same row' - raise FyppFatalError(msg, self._curfile, curspan) - - -class Renderer: - - ''''Renders a tree. - - Args: - evaluator (Evaluator, optional): Evaluator to use when rendering eval - directives. If None (default), Evaluator() is used. - linenums (bool, optional): Whether linenums should be generated, - defaults to False. - contlinenums (bool, optional): Whether linenums for continuation - should be generated, defaults to False. - linenumformat (str, optional): If set to "gfortran5", a workaround - for broken gfortran versions (version 5.1 and above) is applied when - emitting line numbering directives. - linefolder (callable): Callable to use when folding a line. - ''' - - def __init__(self, evaluator=None, linenums=False, contlinenums=False, - linenumformat=None, linefolder=None): - # Evaluator to use for Python expressions - self._evaluator = Evaluator() if evaluator is None else evaluator - - # Whether rendered output is diverted and will be processed - # further before output (if True: no line numbering and post processing) - self._diverted = False - - # Whether file name and line numbers should be kept fixed and - # not updated (typically when rendering macro content) - self._fixedposition = False - - # Whether line numbering directives should be emitted - self._linenums = linenums - - # Whether line numbering directives in continuation lines are needed. - self._contlinenums = contlinenums - - # Whether to use the fix for GFortran in the line numbering directives - self._linenum_gfortran5 = (linenumformat == 'gfortran5') - - # Callable to be used for folding lines - if linefolder is None: - self._linefolder = lambda line: [line] - else: - self._linefolder = linefolder - - - def render(self, tree, divert=False, fixposition=False): - '''Renders a tree. - - Args: - tree (fypp-tree): Tree to render. - divert (bool): Whether output will be diverted and sent for further - processing, so that no line numbering directives and - postprocessing are needed at this stage. (Default: False) - fixposition (bool): Whether file name and line position (variables - _FILE_ and _LINE_) should be kept at their current values or - should be updated continuously. (Default: False). - - Returns: str: Rendered string. - ''' - diverted = self._diverted - self._diverted = divert - fixedposition_old = self._fixedposition - self._fixedposition = self._fixedposition or fixposition - output, eval_inds, eval_pos = self._render(tree) - if not self._diverted and eval_inds: - self._postprocess_eval_lines(output, eval_inds, eval_pos) - self._diverted = diverted - self._fixedposition = fixedposition_old - txt = ''.join(output) - - return txt - - - def _render(self, tree): - output = [] - eval_inds = [] - eval_pos = [] - for node in tree: - cmd = node[0] - if cmd == 'txt': - output.append(node[3]) - elif cmd == 'if': - out, ieval, peval = self._get_conditional_content(*node[1:5]) - eval_inds += _shiftinds(ieval, len(output)) - eval_pos += peval - output += out - elif cmd == 'eval': - out, ieval, peval = self._get_eval(*node[1:4]) - eval_inds += _shiftinds(ieval, len(output)) - eval_pos += peval - output += out - elif cmd == 'def': - result = self._define_macro(*node[1:6]) - output.append(result) - elif cmd == 'set': - result = self._define_variable(*node[1:5]) - output.append(result) - elif cmd == 'del': - self._delete_variable(*node[1:4]) - elif cmd == 'for': - out, ieval, peval = self._get_iterated_content(*node[1:6]) - eval_inds += _shiftinds(ieval, len(output)) - eval_pos += peval - output += out - elif cmd == 'call' or cmd == 'block': - out, ieval, peval = self._get_called_content(*node[1:7]) - eval_inds += _shiftinds(ieval, len(output)) - eval_pos += peval - output += out - elif cmd == 'include': - out, ieval, peval = self._get_included_content(*node[1:5]) - eval_inds += _shiftinds(ieval, len(output)) - eval_pos += peval - output += out - elif cmd == 'comment': - output.append(self._get_comment(*node[1:3])) - elif cmd == 'mute': - output.append(self._get_muted_content(*node[1:4])) - elif cmd == 'stop': - self._handle_stop(*node[1:4]) - elif cmd == 'assert': - result = self._handle_assert(*node[1:4]) - output.append(result) - elif cmd == 'global': - self._add_global(*node[1:4]) - else: - msg = "internal error: unknown command '{0}'".format(cmd) - raise FyppFatalError(msg) - return output, eval_inds, eval_pos - - - def _get_eval(self, fname, span, expr): - try: - result = self._evaluate(expr, fname, span[0]) - except Exception as exc: - msg = "exception occured when evaluating '{0}'".format(expr) - raise FyppFatalError(msg, fname, span, exc) - out = [] - ieval = [] - peval = [] - if result is not None: - out.append(str(result)) - if not self._diverted: - ieval.append(0) - peval.append((span, fname)) - if span[0] != span[1]: - out.append('\n') - return out, ieval, peval - - - def _get_conditional_content(self, fname, spans, conditions, contents): - out = [] - ieval = [] - peval = [] - multiline = (spans[0][0] != spans[-1][1]) - for condition, content, span in zip(conditions, contents, spans): - try: - cond = bool(self._evaluate(condition, fname, span[0])) - except Exception as exc: - msg = "exception occured when evaluating '{0}'"\ - .format(condition) - raise FyppFatalError(msg, fname, span, exc) - if cond: - if self._linenums and not self._diverted and multiline: - out.append(linenumdir(span[1], fname)) - outcont, ievalcont, pevalcont = self._render(content) - ieval += _shiftinds(ievalcont, len(out)) - peval += pevalcont - out += outcont - break - if self._linenums and not self._diverted and multiline: - out.append(linenumdir(spans[-1][1], fname)) - return out, ieval, peval - - - def _get_iterated_content(self, fname, spans, loopvars, loopiter, content): - out = [] - ieval = [] - peval = [] - try: - iterobj = iter(self._evaluate(loopiter, fname, spans[0][0])) - except Exception as exc: - msg = "exception occured when evaluating '{0}'"\ - .format(loopiter) - raise FyppFatalError(msg, fname, spans[0], exc) - multiline = (spans[0][0] != spans[-1][1]) - for var in iterobj: - if len(loopvars) == 1: - self._define(loopvars[0], var) - else: - for varname, value in zip(loopvars, var): - self._define(varname, value) - if self._linenums and not self._diverted and multiline: - out.append(linenumdir(spans[0][1], fname)) - outcont, ievalcont, pevalcont = self._render(content) - ieval += _shiftinds(ievalcont, len(out)) - peval += pevalcont - out += outcont - if self._linenums and not self._diverted and multiline: - out.append(linenumdir(spans[1][1], fname)) - return out, ieval, peval - - - def _get_called_content(self, fname, spans, name, argexpr, contents, - argnames): - posargs, kwargs = self._get_call_arguments(fname, spans, argexpr, - contents, argnames) - try: - callobj = self._evaluate(name, fname, spans[0][0]) - result = callobj(*posargs, **kwargs) - except Exception as exc: - msg = "exception occured when calling '{0}'".format(name) - raise FyppFatalError(msg, fname, spans[0], exc) - self._update_predef_globals(fname, spans[0][0]) - span = (spans[0][0], spans[-1][1]) - out = [] - ieval = [] - peval = [] - if result is not None: - out = [str(result)] - if not self._diverted: - ieval = [0] - peval = [(span, fname)] - if span[0] != span[1]: - out.append('\n') - return out, ieval, peval - - - def _get_call_arguments(self, fname, spans, argexpr, contents, argnames): - if argexpr is None: - posargs = [] - kwargs = {} - else: - # Parse and evaluate arguments passed in call header - self._evaluator.openscope() - try: - posargs, kwargs = self._evaluate( - '__getargvalues(' + argexpr + ')', fname, spans[0][0]) - except Exception as exc: - msg = "unable to parse argument expression '{0}'"\ - .format(argexpr) - raise FyppFatalError(msg, fname, spans[0], exc) - self._evaluator.closescope() - - # Render arguments passed in call body - args = [] - for content in contents: - self._evaluator.openscope() - rendered = self.render(content, divert=True) - self._evaluator.closescope() - if rendered.endswith('\n'): - rendered = rendered[:-1] - args.append(rendered) - - # Separate arguments in call body into positional and keyword ones: - if argnames: - posargs += args[:len(args) - len(argnames)] - offset = len(args) - len(argnames) - for iargname, argname in enumerate(argnames): - ind = offset + iargname - if argname in kwargs: - msg = "keyword argument '{0}' already defined"\ - .format(argname) - raise FyppFatalError(msg, fname, spans[ind + 1]) - kwargs[argname] = args[ind] - else: - posargs += args - - return posargs, kwargs - - - def _get_included_content(self, fname, spans, includefname, content): - includefile = spans[0] is not None - out = [] - if self._linenums and not self._diverted: - if includefile or self._linenum_gfortran5: - out += linenumdir(0, includefname, _LINENUM_NEW_FILE) - else: - out += linenumdir(0, includefname) - outcont, ieval, peval = self._render(content) - ieval = _shiftinds(ieval, len(out)) - out += outcont - if self._linenums and not self._diverted and includefile: - out += linenumdir(spans[0][1], fname, _LINENUM_RETURN_TO_FILE) - return out, ieval, peval - - - def _define_macro(self, fname, spans, name, argexpr, content): - if argexpr is None: - args = [] - defaults = {} - varpos = None - varkw = None - else: - # Try to create a lambda function with the argument expression - self._evaluator.openscope() - lambdaexpr = 'lambda ' + argexpr + ': None' - try: - func = self._evaluate(lambdaexpr, fname, spans[0][0]) - except Exception as exc: - msg = "exception occured when evaluating argument expression "\ - "'{0}'".format(argexpr) - raise FyppFatalError(msg, fname, spans[0], exc) - self._evaluator.closescope() - try: - args, defaults, varpos, varkw = _GET_CALLABLE_ARGSPEC(func) - except Exception as exc: - msg = "invalid argument expression '{0}'".format(argexpr) - raise FyppFatalError(msg, fname, spans[0], exc) - named_args = args if varpos is None else args + [varpos] - named_args = named_args if varkw is None else named_args + [varkw] - for arg in named_args: - if arg in _RESERVED_NAMES or arg.startswith(_RESERVED_PREFIX): - msg = "invalid argument name '{0}'".format(arg) - raise FyppFatalError(msg, fname, spans[0]) - result = '' - try: - macro = _Macro( - name, fname, spans, args, defaults, varpos, varkw, content, - self, self._evaluator, self._evaluator.localscope) - self._define(name, macro) - except Exception as exc: - msg = "exception occured when defining macro '{0}'"\ - .format(name) - raise FyppFatalError(msg, fname, spans[0], exc) - if self._linenums and not self._diverted: - result = linenumdir(spans[1][1], fname) - return result - - - def _define_variable(self, fname, span, name, valstr): - result = '' - try: - if valstr is None: - expr = None - else: - expr = self._evaluate(valstr, fname, span[0]) - self._define(name, expr) - except Exception as exc: - msg = "exception occured when setting variable(s) '{0}' to '{1}'"\ - .format(name, valstr) - raise FyppFatalError(msg, fname, span, exc) - multiline = (span[0] != span[1]) - if self._linenums and not self._diverted and multiline: - result = linenumdir(span[1], fname) - return result - - - def _delete_variable(self, fname, span, name): - result = '' - try: - self._evaluator.undefine(name) - except Exception as exc: - msg = "exception occured when deleting variable(s) '{0}'"\ - .format(name) - raise FyppFatalError(msg, fname, span, exc) - multiline = (span[0] != span[1]) - if self._linenums and not self._diverted and multiline: - result = linenumdir(span[1], fname) - return result - - - def _add_global(self, fname, span, name): - result = '' - try: - self._evaluator.addglobal(name) - except Exception as exc: - msg = "exception occured when making variable(s) '{0}' global"\ - .format(name) - raise FyppFatalError(msg, fname, span, exc) - multiline = (span[0] != span[1]) - if self._linenums and not self._diverted and multiline: - result = linenumdir(span[1], fname) - return result - - - def _get_comment(self, fname, span): - if self._linenums and not self._diverted: - return linenumdir(span[1], fname) - return '' - - - def _get_muted_content(self, fname, spans, content): - self._render(content) - if self._linenums and not self._diverted: - return linenumdir(spans[-1][1], fname) - return '' - - - def _handle_stop(self, fname, span, msgstr): - try: - msg = str(self._evaluate(msgstr, fname, span[0])) - except Exception as exc: - msg = "exception occured when evaluating stop message '{0}'"\ - .format(msgstr) - raise FyppFatalError(msg, fname, span, exc) - raise FyppStopRequest(msg, fname, span) - - - def _handle_assert(self, fname, span, expr): - result = '' - try: - cond = bool(self._evaluate(expr, fname, span[0])) - except Exception as exc: - msg = "exception occured when evaluating assert condition '{0}'"\ - .format(expr) - raise FyppFatalError(msg, fname, span, exc) - if not cond: - msg = "Assertion failed ('{0}')".format(expr) - raise FyppStopRequest(msg, fname, span) - if self._linenums and not self._diverted: - result = linenumdir(span[1], fname) - return result - - - def _evaluate(self, expr, fname, linenr): - self._update_predef_globals(fname, linenr) - result = self._evaluator.evaluate(expr) - self._update_predef_globals(fname, linenr) - return result - - - def _update_predef_globals(self, fname, linenr): - self._evaluator.updatelocals( - _DATE_=time.strftime('%Y-%m-%d'), _TIME_=time.strftime('%H:%M:%S'), - _THIS_FILE_=fname, _THIS_LINE_=linenr + 1) - if not self._fixedposition: - self._evaluator.updateglobals(_FILE_=fname, _LINE_=linenr + 1) - - - def _define(self, var, value): - self._evaluator.define(var, value) - - - def _postprocess_eval_lines(self, output, eval_inds, eval_pos): - ilastproc = -1 - for ieval, ind in enumerate(eval_inds): - span, fname = eval_pos[ieval] - if ind <= ilastproc: - continue - iprev, eolprev = self._find_last_eol(output, ind) - inext, eolnext = self._find_next_eol(output, ind) - curline = self._glue_line(output, ind, iprev, eolprev, inext, - eolnext) - output[iprev + 1:inext] = [''] * (inext - iprev - 1) - output[ind] = self._postprocess_eval_line(curline, fname, span) - ilastproc = inext - - - @staticmethod - def _find_last_eol(output, ind): - 'Find last newline before current position.' - iprev = ind - 1 - while iprev >= 0: - eolprev = output[iprev].rfind('\n') - if eolprev != -1: - break - iprev -= 1 - else: - iprev = 0 - eolprev = -1 - return iprev, eolprev - - - @staticmethod - def _find_next_eol(output, ind): - 'Find last newline before current position.' - # find first eol after expr. evaluation - inext = ind + 1 - while inext < len(output): - eolnext = output[inext].find('\n') - if eolnext != -1: - break - inext += 1 - else: - inext = len(output) - 1 - eolnext = len(output[-1]) - 1 - return inext, eolnext - - - @staticmethod - def _glue_line(output, ind, iprev, eolprev, inext, eolnext): - 'Create line from parts between specified boundaries.' - curline_parts = [] - if iprev != ind: - curline_parts = [output[iprev][eolprev + 1:]] - output[iprev] = output[iprev][:eolprev + 1] - curline_parts.extend(output[iprev + 1:ind]) - curline_parts.extend(output[ind]) - curline_parts.extend(output[ind + 1:inext]) - if inext != ind: - curline_parts.append(output[inext][:eolnext + 1]) - output[inext] = output[inext][eolnext + 1:] - return ''.join(curline_parts) - - - def _postprocess_eval_line(self, evalline, fname, span): - lines = evalline.split('\n') - # If line ended on '\n', last element is ''. We remove it and - # add the trailing newline later manually. - trailing_newline = (lines[-1] == '') - if trailing_newline: - del lines[-1] - lnum = linenumdir(span[0], fname) if self._linenums else '' - clnum = lnum if self._contlinenums else '' - linenumsep = '\n' + lnum - clinenumsep = '\n' + clnum - foldedlines = [self._foldline(line) for line in lines] - outlines = [clinenumsep.join(lines) for lines in foldedlines] - result = linenumsep.join(outlines) - # Add missing trailing newline - if trailing_newline: - trailing = '\n' - if self._linenums: - # Last line was folded, but no linenums were generated for - # the continuation lines -> current line position is not - # in sync with the one calculated from the last line number - unsync = ( - len(foldedlines) and len(foldedlines[-1]) > 1 - and not self._contlinenums) - # Eval directive in source consists of more than one line - multiline = span[1] - span[0] > 1 - if unsync or multiline: - # For inline eval directives span[0] == span[1] - # -> next line is span[0] + 1 and not span[1] as for - # line eval directives - nextline = max(span[1], span[0] + 1) - trailing += linenumdir(nextline, fname) - else: - trailing = '' - return result + trailing - - - def _foldline(self, line): - if _COMMENTLINE_REGEXP.match(line) is None: - return self._linefolder(line) - return [line] - - -class Evaluator: - - '''Provides an isolated environment for evaluating Python expressions. - - It restricts the builtins which can be used within this environment to a - (hopefully safe) subset. Additionally it defines the functions which are - provided by the preprocessor for the eval directives. - - Args: - env (dict, optional): Initial definitions for the environment, defaults - to None. - ''' - - # Restricted builtins working in all supported Python verions. Version - # specific ones are added dynamically in _get_restricted_builtins(). - _RESTRICTED_BUILTINS = { - 'abs': builtins.abs, - 'all': builtins.all, - 'any': builtins.any, - 'bin': builtins.bin, - 'bool': builtins.bool, - 'bytearray': builtins.bytearray, - 'bytes': builtins.bytes, - 'chr': builtins.chr, - 'classmethod': builtins.classmethod, - 'complex': builtins.complex, - 'delattr': builtins.delattr, - 'dict': builtins.dict, - 'dir': builtins.dir, - 'divmod': builtins.divmod, - 'enumerate': builtins.enumerate, - 'filter': builtins.filter, - 'float': builtins.float, - 'format': builtins.format, - 'frozenset': builtins.frozenset, - 'getattr': builtins.getattr, - 'globals': builtins.globals, - 'hasattr': builtins.hasattr, - 'hash': builtins.hash, - 'hex': builtins.hex, - 'id': builtins.id, - 'int': builtins.int, - 'isinstance': builtins.isinstance, - 'issubclass': builtins.issubclass, - 'iter': builtins.iter, - 'len': builtins.len, - 'list': builtins.list, - 'locals': builtins.locals, - 'map': builtins.map, - 'max': builtins.max, - 'min': builtins.min, - 'next': builtins.next, - 'object': builtins.object, - 'oct': builtins.oct, - 'ord': builtins.ord, - 'pow': builtins.pow, - 'property': builtins.property, - 'range': builtins.range, - 'repr': builtins.repr, - 'reversed': builtins.reversed, - 'round': builtins.round, - 'set': builtins.set, - 'setattr': builtins.setattr, - 'slice': builtins.slice, - 'sorted': builtins.sorted, - 'staticmethod': builtins.staticmethod, - 'str': builtins.str, - 'sum': builtins.sum, - 'super': builtins.super, - 'tuple': builtins.tuple, - 'type': builtins.type, - 'vars': builtins.vars, - 'zip': builtins.zip, - } - - - def __init__(self, env=None): - - # Global scope - self._globals = env if env is not None else {} - - # Local scope(s) - self._locals = None - self._locals_stack = [] - - # Variables which are references to entries in global scope - self._globalrefs = None - self._globalrefs_stack = [] - - # Current scope (globals + locals in all embedding and in current scope) - self._scope = self._globals - - # Turn on restricted mode - self._restrict_builtins() - - - def evaluate(self, expr): - '''Evaluate a Python expression using the `eval()` builtin. - - Args: - expr (str): String represantion of the expression. - - Return: - Python object: Result of the expression evaluation. - ''' - result = eval(expr, self._scope) - return result - - - def import_module(self, module): - '''Import a module into the evaluator. - - Note: Import only trustworthy modules! Module imports are global, - therefore, importing a malicious module which manipulates other global - modules could affect code behaviour outside of the Evaluator as well. - - Args: - module (str): Python module to import. - - Raises: - FyppFatalError: If module could not be imported. - - ''' - rootmod = module.split('.', 1)[0] - try: - imported = __import__(module, self._scope) - self.define(rootmod, imported) - except Exception as exc: - msg = "failed to import module '{0}'".format(module) - raise FyppFatalError(msg, cause=exc) - - - def define(self, name, value): - '''Define a Python entity. - - Args: - name (str): Name of the entity. - value (Python object): Value of the entity. - - Raises: - FyppFatalError: If name starts with the reserved prefix or if it is - a reserved name. - ''' - varnames = self._get_variable_names(name) - if len(varnames) == 1: - value = (value,) - elif len(varnames) != len(value): - msg = 'value for tuple assignment has incompatible length' - raise FyppFatalError(msg) - for varname, varvalue in zip(varnames, value): - self._check_variable_name(varname) - if self._locals is None: - self._globals[varname] = varvalue - else: - if varname in self._globalrefs: - self._globals[varname] = varvalue - else: - self._locals[varname] = varvalue - self._scope[varname] = varvalue - - - def undefine(self, name): - '''Undefine a Python entity. - - Args: - name (str): Name of the entity to undefine. - - Raises: - FyppFatalError: If name starts with the reserved prefix or if it is - a reserved name. - ''' - varnames = self._get_variable_names(name) - for varname in varnames: - self._check_variable_name(varname) - deleted = False - if self._locals is None: - if varname in self._globals: - del self._globals[varname] - deleted = True - else: - if varname in self._locals: - del self._locals[varname] - del self._scope[varname] - deleted = True - elif varname in self._globalrefs and varname in self._globals: - del self._globals[varname] - del self._scope[varname] - deleted = True - if not deleted: - msg = "lookup for an erasable instance of '{0}' failed"\ - .format(varname) - raise FyppFatalError(msg) - - - def addglobal(self, name): - '''Define a given entity as global. - - Args: - name (str): Name of the entity to make global. - - Raises: - FyppFatalError: If entity name is invalid or if the current scope is - a local scope and entity is already defined in it. - ''' - varnames = self._get_variable_names(name) - for varname in varnames: - self._check_variable_name(varname) - if self._locals is not None: - if varname in self._locals: - msg = "variable '{0}' already defined in local scope"\ - .format(varname) - raise FyppFatalError(msg) - self._globalrefs.add(varname) - - - def updateglobals(self, **vardict): - '''Update variables in the global scope. - - This is a shortcut function to inject protected variables in the global - scope without extensive checks (as in define()). Vardict must not - contain any global entries which can be shadowed in local scopes - (e.g. should only contain variables with forbidden prefix). - - Args: - **vardict: variable defintions. - - ''' - self._scope.update(vardict) - if self._locals is not None: - self._globals.update(vardict) - - - def updatelocals(self, **vardict): - '''Update variables in the local scope. - - This is a shortcut function to inject variables in the local scope - without extensive checks (as in define()). Vardict must not contain any - entries which have been made global via addglobal() before. In order to - ensure this, updatelocals() should be called immediately after - openscope(), or with variable names, which are warrantedly not globals - (e.g variables starting with forbidden prefix) - - Args: - **vardict: variable defintions. - ''' - self._scope.update(vardict) - if self._locals is not None: - self._locals.update(vardict) - - - def openscope(self, customlocals=None): - '''Opens a new (embedded) scope. - - Args: - customlocals (dict): By default, the locals of the embedding scope - are visible in the new one. When this is not the desired - behaviour a dictionary of customized locals can be passed, - and those locals will become the only visible ones. - ''' - self._locals_stack.append(self._locals) - self._globalrefs_stack.append(self._globalrefs) - if customlocals is not None: - self._locals = customlocals.copy() - elif self._locals is not None: - self._locals = self._locals.copy() - else: - self._locals = {} - self._globalrefs = set() - self._scope = self._globals.copy() - self._scope.update(self._locals) - - - def closescope(self): - '''Close scope and restore embedding scope.''' - self._locals = self._locals_stack.pop(-1) - self._globalrefs = self._globalrefs_stack.pop(-1) - if self._locals is not None: - self._scope = self._globals.copy() - self._scope.update(self._locals) - else: - self._scope = self._globals - - - @property - def globalscope(self): - 'Dictionary of the global scope.' - return self._globals - - - @property - def localscope(self): - 'Dictionary of the current local scope.' - return self._locals - - - def _restrict_builtins(self): - builtindict = self._get_restricted_builtins() - builtindict['__import__'] = self._func_import - builtindict['defined'] = self._func_defined - builtindict['setvar'] = self._func_setvar - builtindict['getvar'] = self._func_getvar - builtindict['delvar'] = self._func_delvar - builtindict['globalvar'] = self._func_globalvar - builtindict['__getargvalues'] = self._func_getargvalues - self._globals['__builtins__'] = builtindict - - - @classmethod - def _get_restricted_builtins(cls): - bidict = dict(cls._RESTRICTED_BUILTINS) - major = sys.version_info[0] - if major == 2: - bidict['True'] = True - bidict['False'] = False - return bidict - - - @staticmethod - def _get_variable_names(varexpr): - lpar = varexpr.startswith('(') - rpar = varexpr.endswith(')') - if lpar != rpar: - msg = "unbalanced paranthesis around variable varexpr(s) in '{0}'"\ - .format(varexpr) - raise FyppFatalError(msg, None, None) - if lpar: - varexpr = varexpr[1:-1] - varnames = [s.strip() for s in varexpr.split(',')] - return varnames - - - @staticmethod - def _check_variable_name(varname): - if varname.startswith(_RESERVED_PREFIX): - msg = "Name '{0}' starts with reserved prefix '{1}'"\ - .format(varname, _RESERVED_PREFIX) - raise FyppFatalError(msg, None, None) - if varname in _RESERVED_NAMES: - msg = "Name '{0}' is reserved and can not be redefined"\ - .format(varname) - raise FyppFatalError(msg, None, None) - - - def _func_defined(self, var): - defined = var in self._scope - return defined - - - def _func_import(self, name, *_, **__): - module = self._scope.get(name, None) - if module is not None and isinstance(module, types.ModuleType): - return module - msg = "Import of module '{0}' via '__import__' not allowed".format(name) - raise ImportError(msg) - - - def _func_setvar(self, *namesvalues): - if len(namesvalues) % 2: - msg = 'setvar function needs an even number of arguments' - raise FyppFatalError(msg) - for ind in range(0, len(namesvalues), 2): - self.define(namesvalues[ind], namesvalues[ind + 1]) - - - def _func_getvar(self, name, defvalue=None): - if name in self._scope: - return self._scope[name] - return defvalue - - - def _func_delvar(self, *names): - for name in names: - self.undefine(name) - - - def _func_globalvar(self, *names): - for name in names: - self.addglobal(name) - - - @staticmethod - def _func_getargvalues(*args, **kwargs): - return list(args), kwargs - - - -class _Macro: - - '''Represents a user defined macro. - - This object should only be initiatied by a Renderer instance, as it - needs access to Renderers internal variables and methods. - - Args: - name (str): Name of the macro. - fname (str): The file where the macro was defined. - spans (str): Line spans of macro defintion. - argnames (list of str): Macro dummy arguments. - varpos (str): Name of variable positional argument or None. - varkw (str): Name of variable keyword argument or None. - content (list): Content of the macro as tree. - renderer (Renderer): Renderer to use for evaluating macro content. - localscope (dict): Dictionary with local variables, which should be used - the local scope, when the macro is called. Default: None (empty - local scope). - ''' - - def __init__(self, name, fname, spans, argnames, defaults, varpos, varkw, - content, renderer, evaluator, localscope=None): - self._name = name - self._fname = fname - self._spans = spans - self._argnames = argnames - self._defaults = defaults - self._varpos = varpos - self._varkw = varkw - self._content = content - self._renderer = renderer - self._evaluator = evaluator - self._localscope = localscope if localscope is not None else {} - - - def __call__(self, *args, **keywords): - argdict = self._process_arguments(args, keywords) - self._evaluator.openscope(customlocals=self._localscope) - self._evaluator.updatelocals(**argdict) - output = self._renderer.render(self._content, divert=True, - fixposition=True) - self._evaluator.closescope() - if output.endswith('\n'): - return output[:-1] - return output - - - def _process_arguments(self, args, keywords): - kwdict = dict(keywords) - argdict = {} - nargs = min(len(args), len(self._argnames)) - for iarg in range(nargs): - argdict[self._argnames[iarg]] = args[iarg] - if nargs < len(args): - if self._varpos is None: - msg = "macro '{0}' called with too many positional arguments "\ - "(expected: {1}, received: {2})"\ - .format(self._name, len(self._argnames), len(args)) - raise FyppFatalError(msg, self._fname, self._spans[0]) - else: - argdict[self._varpos] = list(args[nargs:]) - elif self._varpos is not None: - argdict[self._varpos] = [] - for argname in self._argnames[:nargs]: - if argname in kwdict: - msg = "got multiple values for argument '{0}'".format(argname) - raise FyppFatalError(msg, self._fname, self._spans[0]) - if nargs < len(self._argnames): - for argname in self._argnames[nargs:]: - if argname in kwdict: - argdict[argname] = kwdict.pop(argname) - elif argname in self._defaults: - argdict[argname] = self._defaults[argname] - else: - msg = "macro '{0}' called without mandatory positional "\ - "argument '{1}'".format(self._name, argname) - raise FyppFatalError(msg, self._fname, self._spans[0]) - if kwdict and self._varkw is None: - kwstr = "', '".join(kwdict.keys()) - msg = "macro '{0}' called with unknown keyword argument(s) '{1}'"\ - .format(self._name, kwstr) - raise FyppFatalError(msg, self._fname, self._spans[0]) - if self._varkw is not None: - argdict[self._varkw] = kwdict - return argdict - - - -class Processor: - - '''Connects various objects with each other to create a processor. - - Args: - parser (Parser, optional): Parser to use for parsing text. If None - (default), `Parser()` is used. - builder (Builder, optional): Builder to use for building the tree - representation of the text. If None (default), `Builder()` is used. - renderer (Renderer, optional): Renderer to use for rendering the - output. If None (default), `Renderer()` is used with a default - Evaluator(). - evaluator (Evaluator, optional): Evaluator to use for evaluating Python - expressions. If None (default), `Evaluator()` is used. - ''' - - def __init__(self, parser=None, builder=None, renderer=None, - evaluator=None): - self._parser = Parser() if parser is None else parser - self._builder = Builder() if builder is None else builder - if renderer is None: - evaluator = Evaluator() if evaluator is None else evaluator - self._renderer = Renderer(evaluator) - else: - self._renderer = renderer - - self._parser.handle_include = self._builder.handle_include - self._parser.handle_endinclude = self._builder.handle_endinclude - self._parser.handle_if = self._builder.handle_if - self._parser.handle_else = self._builder.handle_else - self._parser.handle_elif = self._builder.handle_elif - self._parser.handle_endif = self._builder.handle_endif - self._parser.handle_eval = self._builder.handle_eval - self._parser.handle_text = self._builder.handle_text - self._parser.handle_def = self._builder.handle_def - self._parser.handle_enddef = self._builder.handle_enddef - self._parser.handle_set = self._builder.handle_set - self._parser.handle_del = self._builder.handle_del - self._parser.handle_global = self._builder.handle_global - self._parser.handle_for = self._builder.handle_for - self._parser.handle_endfor = self._builder.handle_endfor - self._parser.handle_call = self._builder.handle_call - self._parser.handle_nextarg = self._builder.handle_nextarg - self._parser.handle_endcall = self._builder.handle_endcall - self._parser.handle_comment = self._builder.handle_comment - self._parser.handle_mute = self._builder.handle_mute - self._parser.handle_endmute = self._builder.handle_endmute - self._parser.handle_stop = self._builder.handle_stop - self._parser.handle_assert = self._builder.handle_assert - - - def process_file(self, fname): - '''Processeses a file. - - Args: - fname (str): Name of the file to process. - - Returns: - str: Processed content. - ''' - self._parser.parsefile(fname) - return self._render() - - - def process_text(self, txt): - '''Processes a string. - - Args: - txt (str): Text to process. - - Returns: - str: Processed content. - ''' - self._parser.parse(txt) - return self._render() - - - def _render(self): - output = self._renderer.render(self._builder.tree) - self._builder.reset() - return ''.join(output) - - -class Fypp: - - '''Fypp preprocessor. - - You can invoke it like :: - - tool = fypp.Fypp() - tool.process_file('file.in', 'file.out') - - to initialize Fypp with default options, process `file.in` and write the - result to `file.out`. If the input should be read from a string, the - ``process_text()`` method can be used:: - - tool = fypp.Fypp() - output = tool.process_text('#:if DEBUG > 0\\nprint *, "DEBUG"\\n#:endif\\n') - - If you want to fine tune Fypps behaviour, pass a customized `FyppOptions`_ - instance at initialization:: - - options = fypp.FyppOptions() - options.fixed_format = True - tool = fypp.Fypp(options) - - Alternatively, you can use the command line parser ``optparse.OptionParser`` - to set options for Fypp. The function ``get_option_parser()`` returns you a - default option parser. You can then use its ``parse_args()`` method to - obtain settings by reading the command line arguments:: - - optparser = fypp.get_option_parser() - options, leftover = optparser.parse_args() - tool = fypp.Fypp(options) - - The command line options can also be passed directly as a list when - calling ``parse_args()``:: - - args = ['-DDEBUG=0', 'input.fpp', 'output.f90'] - optparser = fypp.get_option_parser() - options, leftover = optparser.parse_args(args=args) - tool = fypp.Fypp(options) - - - Args: - options (object): Object containing the settings for Fypp. You typically - would pass a customized `FyppOptions`_ instance or an - ``optparse.Values`` object as returned by the option parser. If not - present, the default settings in `FyppOptions`_ are used. - ''' - - def __init__(self, options=None): - syspath = self._get_syspath_without_scriptdir() - self._adjust_syspath(syspath) - if options is None: - options = FyppOptions() - evaluator = Evaluator() - self._encoding = options.encoding - if options.modules: - self._import_modules(options.modules, evaluator, syspath, - options.moduledirs) - if options.defines: - self._apply_definitions(options.defines, evaluator) - parser = Parser(includedirs=options.includes, encoding=self._encoding) - builder = Builder() - - fixed_format = options.fixed_format - linefolding = not options.no_folding - if linefolding: - folding = 'brute' if fixed_format else options.folding_mode - linelength = 72 if fixed_format else options.line_length - indentation = 5 if fixed_format else options.indentation - prefix = '&' - suffix = '' if fixed_format else '&' - linefolder = FortranLineFolder(linelength, indentation, folding, - prefix, suffix) - else: - linefolder = DummyLineFolder() - linenums = options.line_numbering - contlinenums = (options.line_numbering_mode != 'nocontlines') - self._create_parent_folder = options.create_parent_folder - renderer = Renderer( - evaluator, linenums=linenums, contlinenums=contlinenums, - linenumformat=options.line_marker_format, linefolder=linefolder) - self._preprocessor = Processor(parser, builder, renderer) - - - def process_file(self, infile, outfile=None): - '''Processes input file and writes result to output file. - - Args: - infile (str): Name of the file to read and process. If its value is - '-', input is read from stdin. - outfile (str, optional): Name of the file to write the result to. - If its value is '-', result is written to stdout. If not - present, result will be returned as string. - env (dict, optional): Additional definitions for the evaluator. - - Returns: - str: Result of processed input, if no outfile was specified. - ''' - infile = STDIN if infile == '-' else infile - output = self._preprocessor.process_file(infile) - if outfile is None: - return output - if outfile == '-': - outfile = sys.stdout - else: - outfile = _open_output_file(outfile, self._encoding, - self._create_parent_folder) - outfile.write(output) - if outfile != sys.stdout: - outfile.close() - return None - - - def process_text(self, txt): - '''Processes a string. - - Args: - txt (str): String to process. - env (dict, optional): Additional definitions for the evaluator. - - Returns: - str: Processed content. - ''' - return self._preprocessor.process_text(txt) - - - @staticmethod - def _apply_definitions(defines, evaluator): - for define in defines: - words = define.split('=', 2) - name = words[0] - value = None - if len(words) > 1: - try: - value = evaluator.evaluate(words[1]) - except Exception as exc: - msg = "exception at evaluating '{0}' in definition for " \ - "'{1}'".format(words[1], name) - raise FyppFatalError(msg, cause=exc) - evaluator.define(name, value) - - - def _import_modules(self, modules, evaluator, syspath, moduledirs): - lookuppath = [] - if moduledirs is not None: - lookuppath += [os.path.abspath(moddir) for moddir in moduledirs] - lookuppath.append(os.path.abspath('.')) - lookuppath += syspath - self._adjust_syspath(lookuppath) - for module in modules: - evaluator.import_module(module) - self._adjust_syspath(syspath) - - - @staticmethod - def _get_syspath_without_scriptdir(): - '''Remove the folder of the fypp binary from the search path''' - syspath = list(sys.path) - scriptdir = os.path.abspath(os.path.dirname(sys.argv[0])) - if os.path.abspath(syspath[0]) == scriptdir: - del syspath[0] - return syspath - - - @staticmethod - def _adjust_syspath(syspath): - sys.path = syspath - - -class FyppOptions(optparse.Values): - - '''Container for Fypp options with default values. - - Attributes: - defines (list of str): List of variable definitions in the form of - 'VARNAME=VALUE'. Default: [] - includes (list of str): List of paths to search when looking for include - files. Default: [] - line_numbering (bool): Whether line numbering directives should appear - in the output. Default: False - line_numbering_mode (str): Line numbering mode 'full' or 'nocontlines'. - Default: 'full'. - line_marker_format (str): Line marker format. Currently 'cpp' and - 'gfortran5' are supported. Later fixes the line marker handling bug - introduced in GFortran 5. Default: 'cpp'. - line_length (int): Length of output lines. Default: 132. - folding_mode (str): Folding mode 'smart', 'simple' or 'brute'. Default: - 'smart'. - no_folding (bool): Whether folding should be suppresed. Default: False. - indentation (int): Indentation in continuation lines. Default: 4. - modules (list of str): Modules to import at initialization. Default: []. - moduledirs (list of str): Module lookup directories for importing user - specified modules. The specified paths are looked up *before* the - standard module locations in sys.path. - fixed_format (bool): Whether input file is in fixed format. - Default: False. - encoding (str): Character encoding for reading/writing files. Allowed - values are Pythons codec identifiers, e.g. 'ascii', 'utf-8', etc. - Default: 'utf-8'. Reading from stdin and writing to stdout is always - encoded according to the current locale and is not affected by this - setting. - create_parent_folder (bool): Whether the parent folder for the output - file should be created if it does not exist. Default: False. - ''' - - def __init__(self): - optparse.Values.__init__(self) - self.defines = [] - self.includes = [] - self.line_numbering = False - self.line_numbering_mode = 'full' - self.line_marker_format = 'cpp' - self.line_length = 132 - self.folding_mode = 'smart' - self.no_folding = False - self.indentation = 4 - self.modules = [] - self.moduledirs = [] - self.fixed_format = False - self.encoding = 'utf-8' - self.create_parent_folder = False - - -class FortranLineFolder: - - '''Implements line folding with Fortran continuation lines. - - Args: - maxlen (int, optional): Maximal line length (default: 132). - indent (int, optional): Indentation for continuation lines (default: 4). - method (str, optional): Folding method with following options: - - * ``brute``: folding with maximal length of continuation lines, - * ``simple``: indents with respect of indentation of first line, - * ``smart``: like ``simple``, but tries to fold at whitespaces. - - prefix (str, optional): String to use at the beginning of a continuation - line (default: '&'). - suffix (str, optional): String to use at the end of the line preceeding - a continuation line (default: '&') - ''' - - def __init__(self, maxlen=132, indent=4, method='smart', prefix='&', - suffix='&'): - # Line length should be long enough that contintuation lines can host at - # east one character apart of indentation and two continuation signs - minmaxlen = indent + len(prefix) + len(suffix) + 1 - if maxlen < minmaxlen: - msg = 'Maximal line length less than {0} when using an indentation'\ - ' of {1}'.format(minmaxlen, indent) - raise FyppFatalError(msg) - self._maxlen = maxlen - self._indent = indent - self._prefix = ' ' * self._indent + prefix - self._suffix = suffix - if method not in ['brute', 'smart', 'simple']: - raise FyppFatalError('invalid folding type') - if method == 'brute': - self._inherit_indent = False - self._fold_position_finder = self._get_maximal_fold_pos - elif method == 'simple': - self._inherit_indent = True - self._fold_position_finder = self._get_maximal_fold_pos - elif method == 'smart': - self._inherit_indent = True - self._fold_position_finder = self._get_smart_fold_pos - - - def __call__(self, line): - '''Folds a line. - - Can be directly called to return the list of folded lines:: - - linefolder = FortranLineFolder(maxlen=10) - linefolder(' print *, "some Fortran line"') - - Args: - line (str): Line to fold. - - Returns: - list of str: Components of folded line. They should be - assembled via ``\\n.join()`` to obtain the string - representation. - ''' - if self._maxlen < 0 or len(line) <= self._maxlen: - return [line] - if self._inherit_indent: - indent = len(line) - len(line.lstrip()) - prefix = ' ' * indent + self._prefix - else: - indent = 0 - prefix = self._prefix - suffix = self._suffix - return self._split_line(line, self._maxlen, prefix, suffix, - self._fold_position_finder) - - - @staticmethod - def _split_line(line, maxlen, prefix, suffix, fold_position_finder): - # length of continuation lines with 1 or two continuation chars. - maxlen1 = maxlen - len(prefix) - maxlen2 = maxlen1 - len(suffix) - start = 0 - end = fold_position_finder(line, start, maxlen - len(suffix)) - result = [line[start:end] + suffix] - while end < len(line) - maxlen1: - start = end - end = fold_position_finder(line, start, start + maxlen2) - result.append(prefix + line[start:end] + suffix) - result.append(prefix + line[end:]) - return result - - - @staticmethod - def _get_maximal_fold_pos(_, __, end): - return end - - - @staticmethod - def _get_smart_fold_pos(line, start, end): - linelen = end - start - ispace = line.rfind(' ', start, end) - # The space we waste for smart folding should be max. 1/3rd of the line - if ispace != -1 and ispace >= start + (2 * linelen) // 3: - return ispace - return end - - -class DummyLineFolder: - - '''Implements a dummy line folder returning the line unaltered.''' - - def __call__(self, line): - '''Returns the entire line without any folding. - - Returns: - list of str: Components of folded line. They should be - assembled via ``\\n.join()`` to obtain the string - representation. - ''' - return [line] - - -def get_option_parser(): - '''Returns an option parser for the Fypp command line tool. - - Returns: - OptionParser: Parser which can create an optparse.Values object with - Fypp settings based on command line arguments. - ''' - defs = FyppOptions() - fypp_name = 'fypp' - fypp_desc = 'Preprocesses source code with Fypp directives. The input is '\ - 'read from INFILE (default: \'-\', stdin) and written to '\ - 'OUTFILE (default: \'-\', stdout).' - fypp_version = fypp_name + ' ' + VERSION - usage = '%prog [options] [INFILE] [OUTFILE]' - parser = optparse.OptionParser(prog=fypp_name, description=fypp_desc, - version=fypp_version, usage=usage) - msg = 'define variable, value is interpreted as ' \ - 'Python expression (e.g \'-DDEBUG=1\' sets DEBUG to the ' \ - 'integer 1) or set to None if ommitted' - parser.add_option('-D', '--define', action='append', dest='defines', - metavar='VAR[=VALUE]', default=defs.defines, help=msg) - msg = 'add directory to the search paths for include files' - parser.add_option('-I', '--include', action='append', dest='includes', - metavar='INCDIR', default=defs.includes, help=msg) - msg = 'import a python module at startup (import only trustworthy modules '\ - 'as they have access to an **unrestricted** Python environment!)' - parser.add_option('-m', '--module', action='append', dest='modules', - metavar='MOD', default=defs.modules, help=msg) - msg = 'directory to be searched for user imported modules before '\ - 'looking up standard locations in sys.path' - parser.add_option('-M', '--module-dir', action='append', - dest='moduledirs', metavar='MODDIR', - default=defs.moduledirs, help=msg) - msg = 'emit line numbering markers' - parser.add_option('-n', '--line-numbering', action='store_true', - dest='line_numbering', default=defs.line_numbering, - help=msg) - msg = 'line numbering mode, \'full\' (default): line numbering '\ - 'markers generated whenever source and output lines are out '\ - 'of sync, \'nocontlines\': line numbering markers omitted '\ - 'for continuation lines' - parser.add_option('-N', '--line-numbering-mode', metavar='MODE', - choices=['full', 'nocontlines'], - default=defs.line_numbering_mode, - dest='line_numbering_mode', help=msg) - msg = 'line numbering marker format, \'cpp\' (default): GNU cpp format, '\ - '\'gfortran5\': modified markers to work around bug in GFortran 5 '\ - 'and above' - parser.add_option('--line-marker-format', metavar='FMT', - choices=['cpp', 'gfortran5'], dest='line_marker_format', - default=defs.line_marker_format, help=msg) - msg = 'maximal line length (default: 132), lines modified by the '\ - 'preprocessor are folded if becoming longer' - parser.add_option('-l', '--line-length', type=int, metavar='LEN', - dest='line_length', default=defs.line_length, help=msg) - msg = 'line folding mode, \'smart\' (default): indentation context '\ - 'and whitespace aware, \'simple\': indentation context aware, '\ - '\'brute\': mechnical folding' - parser.add_option('-f', '--folding-mode', metavar='MODE', - choices=['smart', 'simple', 'brute'], dest='folding_mode', - default=defs.folding_mode, help=msg) - msg = 'suppress line folding' - parser.add_option('-F', '--no-folding', action='store_true', - dest='no_folding', default=defs.no_folding, help=msg) - msg = 'indentation to use for continuation lines (default 4)' - parser.add_option('--indentation', type=int, metavar='IND', - dest='indentation', default=defs.indentation, help=msg) - msg = 'produce fixed format output (any settings for options '\ - '--line-length, --folding-method and --indentation are ignored)' - parser.add_option('--fixed-format', action='store_true', - dest='fixed_format', default=defs.fixed_format, help=msg) - msg = 'character encoding for reading/writing files. Default: \'utf-8\'. '\ - 'Note: reading from stdin and writing to stdout is encoded '\ - 'according to the current locale and is not affected by this setting.' - parser.add_option('--encoding', metavar='ENC', default=defs.encoding, - help=msg) - msg = 'create parent folders of the output file if they do not exist' - parser.add_option('-p', '--create-parents', action='store_true', - dest='create_parent_folder', - default=defs.create_parent_folder, help=msg) - return parser - - -def run_fypp(): - '''Run the Fypp command line tool.''' - options = FyppOptions() - optparser = get_option_parser() - opts, leftover = optparser.parse_args(values=options) - infile = leftover[0] if len(leftover) > 0 else '-' - outfile = leftover[1] if len(leftover) > 1 else '-' - try: - tool = Fypp(opts) - tool.process_file(infile, outfile) - except FyppStopRequest as exc: - sys.stderr.write(_formatted_exception(exc)) - sys.exit(USER_ERROR_EXIT_CODE) - except FyppFatalError as exc: - sys.stderr.write(_formatted_exception(exc)) - sys.exit(ERROR_EXIT_CODE) - - -def linenumdir(linenr, fname, flag=None): - '''Returns a line numbering directive. - - Args: - linenr (int): Line nr (starting with 0). - fname (str): File name. - ''' - if flag is None: - return '# {0} "{1}"\n'.format(linenr + 1, fname) - return '# {0} "{1}" {2}\n'.format(linenr + 1, fname, flag) - - -def _shiftinds(inds, shift): - return [ind + shift for ind in inds] - - -def _open_input_file(inpfile, encoding=None): - try: - inpfp = io.open(inpfile, 'r', encoding=encoding) - except IOError as exc: - msg = "Failed to open file '{0}' for read".format(inpfile) - raise FyppFatalError(msg, cause=exc) - return inpfp - - -def _open_output_file(outfile, encoding=None, create_parents=False): - if create_parents: - parentdir = os.path.abspath(os.path.dirname(outfile)) - if not os.path.exists(parentdir): - try: - os.makedirs(parentdir) - except OSError as exc: - if exc.errno != errno.EEXIST: - msg = "Folder '{0}' can not be created"\ - .format(parentdir) - raise FyppFatalError(msg, cause=exc) - try: - outfp = io.open(outfile, 'w', encoding=encoding) - except IOError as exc: - msg = "Failed to open file '{0}' for write".format(outfile) - raise FyppFatalError(msg, cause=exc) - return outfp - - -def _get_callable_argspec_py2(func): - argspec = inspect.getargspec(func) - varpos = argspec.varargs - varkw = argspec.keywords - args = argspec.args - tuplearg = False - for elem in args: - tuplearg = tuplearg or isinstance(elem, list) - if tuplearg: - msg = 'tuple argument(s) found' - raise FyppFatalError(msg) - defaults = {} - if argspec.defaults is not None: - for ind, default in enumerate(argspec.defaults): - iarg = len(args) - len(argspec.defaults) + ind - defaults[args[iarg]] = default - return args, defaults, varpos, varkw - - -def _get_callable_argspec_py3(func): - sig = inspect.signature(func) - args = [] - defaults = {} - varpos = None - varkw = None - for param in sig.parameters.values(): - if param.kind == param.POSITIONAL_OR_KEYWORD: - args.append(param.name) - if param.default != param.empty: - defaults[param.name] = param.default - elif param.kind == param.VAR_POSITIONAL: - varpos = param.name - elif param.kind == param.VAR_KEYWORD: - varkw = param.name - else: - msg = "argument '{0}' has invalid argument type".format(param.name) - raise FyppFatalError(msg) - return args, defaults, varpos, varkw - - -# Signature objects are available from Python 3.3 (and deprecated from 3.5) - -if sys.version_info[0] >= 3 and sys.version_info[1] >= 3: - _GET_CALLABLE_ARGSPEC = _get_callable_argspec_py3 -else: - _GET_CALLABLE_ARGSPEC = _get_callable_argspec_py2 - - -def _blank_match(match): - size = match.end() - match.start() - return " " * size - - -def _argsplit_fortran(argtxt): - txt = _INLINE_EVAL_REGION_REGEXP.sub(_blank_match, argtxt) - splitpos = [-1] - quote = None - closing_brace_stack = [] - closing_brace = None - for ind, char in enumerate(txt): - if quote: - if char == quote: - quote = None - continue - if char in _QUOTES_FORTRAN: - quote = char - continue - if char in _OPENING_BRACKETS_FORTRAN: - closing_brace_stack.append(closing_brace) - ind = _OPENING_BRACKETS_FORTRAN.index(char) - closing_brace = _CLOSING_BRACKETS_FORTRAN[ind] - continue - if char in _CLOSING_BRACKETS_FORTRAN: - if char == closing_brace: - closing_brace = closing_brace_stack.pop(-1) - continue - else: - msg = "unexpected closing delimiter '{0}' in expression '{1}' "\ - "at position {2}".format(char, argtxt, ind + 1) - raise FyppFatalError(msg) - if not closing_brace and char == _ARGUMENT_SPLIT_CHAR_FORTRAN: - splitpos.append(ind) - if quote or closing_brace: - msg = "open quotes or brackets in expression '{0}'".format(argtxt) - raise FyppFatalError(msg) - splitpos.append(len(txt)) - fragments = [argtxt[start + 1 : end] - for start, end in zip(splitpos, splitpos[1:])] - return fragments - - -def _formatted_exception(exc): - error_header_formstr = '{file}:{line}: ' - error_body_formstr = 'error: {errormsg} [{errorclass}]' - if not isinstance(exc, FyppError): - return error_body_formstr.format( - errormsg=str(exc), errorclass=exc.__class__.__name__) - out = [] - if exc.fname is not None: - if exc.span[1] > exc.span[0] + 1: - line = '{0}-{1}'.format(exc.span[0] + 1, exc.span[1]) - else: - line = '{0}'.format(exc.span[0] + 1) - out.append(error_header_formstr.format(file=exc.fname, line=line)) - out.append(error_body_formstr.format(errormsg=exc.msg, - errorclass=exc.__class__.__name__)) - if exc.cause is not None: - out.append('\n' + _formatted_exception(exc.cause)) - out.append('\n') - return ''.join(out) - - -if __name__ == '__main__': - run_fypp() diff --git a/lib/CMakeLists.txt b/lib/CMakeLists.txt index f373dc2..0175432 100644 --- a/lib/CMakeLists.txt +++ b/lib/CMakeLists.txt @@ -21,23 +21,16 @@ set(sources-fpp mpifx_scatterv.fpp mpifx_send.fpp) -set(sources-f90-preproc) - -foreach(fppsrc IN LISTS sources-fpp) - string(REGEX REPLACE "\\.fpp" ".f90" f90src ${fppsrc}) - add_custom_command( - OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/${f90src} - COMMAND ${FYPP} -I${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_SOURCE_DIR}/${fppsrc} ${CMAKE_CURRENT_BINARY_DIR}/${f90src} - MAIN_DEPENDENCY ${CMAKE_CURRENT_SOURCE_DIR}/${fppsrc}) - list(APPEND sources-f90-preproc ${CMAKE_CURRENT_BINARY_DIR}/${f90src}) -endforeach() - -# NAG compiler won't compile this files without the '-mismatch' option +fypp_preprocess("${sources-fpp}" sources-f90) + +# NAG compiler won't compile these files without the '-mismatch' option if("${CMAKE_Fortran_COMPILER_ID}" STREQUAL "NAG") - set_source_files_properties(SOURCE ${sources-f90-preproc} PROPERTY COMPILE_FLAGS -mismatch) + set_source_files_properties(SOURCE ${sources-f90} PROPERTY COMPILE_FLAGS -mismatch) endif() -add_library(mpifx ${sources-f90-preproc}) +add_library(mpifx ${sources-f90}) + +target_link_libraries(mpifx PRIVATE MPI::MPI_Fortran) set(BUILD_MOD_DIR ${CMAKE_CURRENT_BINARY_DIR}/include) @@ -45,16 +38,13 @@ set_target_properties(mpifx PROPERTIES Fortran_MODULE_DIRECTORY ${BUILD_MOD_DIR} target_include_directories(mpifx PUBLIC $ - $) - -target_include_directories(mpifx PRIVATE ${MPI_Fortran_MODULE_DIR}) -target_link_libraries(mpifx PRIVATE ${MPI_Fortran_LIBRARIES}) + $) install(TARGETS mpifx - EXPORT ${INSTALL_EXPORT_NAME} - ARCHIVE DESTINATION ${INSTALL_LIB_DIR} - LIBRARY DESTINATION ${INSTALL_LIB_DIR}) + EXPORT mpifx-targets + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}) if(INSTALL_INCLUDE_FILES) - install(DIRECTORY ${BUILD_MOD_DIR}/ DESTINATION ${INSTALL_MOD_DIR}) + install(DIRECTORY ${BUILD_MOD_DIR}/ DESTINATION ${CMAKE_INSTALL_MODULEDIR}) endif() diff --git a/lib/make.build b/lib/make.build deleted file mode 100644 index 328ef96..0000000 --- a/lib/make.build +++ /dev/null @@ -1,47 +0,0 @@ -############################################################################### -# -# Library makefile -# -# Compiles and links mpifx in the current directory. -# -# Needs the following variables: -# FXX: Fortran 2003 compiler -# FXXOPT: Options for the Fortran 2003 compiler -# LN: Linker -# LNOPT: Linker options -# FYPP: FYPP pre-processor -# FYPPOPT: Options for the FYPP pre-processor. You should use the -I option -# with this directory, if you are invoking the makefile from somewhere -# else. You may also use the -D option to define macros (e.g. DEBUG) -# SRCDIR: Folder where source files are located -# -############################################################################### - -.SUFFIXES: -.SUFFIXES: .f90 .fpp .o - -TARGET = libmpifx.a - -vpath % $(SRCDIR) - -.PHONY: all -all: $(TARGET) - -include $(SRCDIR)/make.deps - -$(TARGET): $(module.o) - ar r $@ $^ - -%.f90: %.fpp - $(FYPP) -I$(SRCDIR) $(FYPPOPT) $< $@ - -%.o: %.f90 - $(FXX) $(FXXOPT) -c $< - -.PHONY: clean -clean: - rm -f *.o - -### Local Variables: -### mode:makefile -### End: diff --git a/lib/make.deps b/lib/make.deps deleted file mode 100644 index 6a29eb7..0000000 --- a/lib/make.deps +++ /dev/null @@ -1,87 +0,0 @@ -.SECONDEXPANSION: - -mpifx_helper.o: $$(_modobj_mpi) $$(_modobj_mpifx_constants_module) -mpifx_helper.o = mpifx_helper.o $($(_modobj_mpi)) $($(_modobj_mpifx_constants_module)) -_modobj_mpifx_helper_module = mpifx_helper.o - -mpifx_recv.o: $$(_modobj_mpifx_common_module) -mpifx_recv.o = mpifx_recv.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_recv_module = mpifx_recv.o - -mpifx_gather.o: $$(_modobj_mpifx_common_module) -mpifx_gather.o = mpifx_gather.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_gather_module = mpifx_gather.o - -mpifx_gatherv.o: $$(_modobj_mpifx_common_module) -mpifx_gatherv.o = mpifx_gatherv.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_gatherv_module = mpifx_gatherv.o - -mpifx_finalize.o: $$(_modobj_mpifx_common_module) -mpifx_finalize.o = mpifx_finalize.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_finalize_module = mpifx_finalize.o - -mpifx_send.o: $$(_modobj_mpifx_common_module) -mpifx_send.o = mpifx_send.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_send_module = mpifx_send.o - -mpifx_allgather.o: $$(_modobj_mpifx_common_module) -mpifx_allgather.o = mpifx_allgather.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_allgather_module = mpifx_allgather.o - -mpifx_allgatherv.o: $$(_modobj_mpifx_common_module) -mpifx_allgatherv.o = mpifx_allgatherv.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_allgatherv_module = mpifx_allgatherv.o - -mpifx_constants.o: $$(_modobj_mpi) -mpifx_constants.o = mpifx_constants.o $($(_modobj_mpi)) -_modobj_mpifx_constants_module = mpifx_constants.o - -module.o: $$(_modobj_mpifx_send_module) $$(_modobj_mpifx_scatter_module) $$(_modobj_mpifx_scatterv_module) $$(_modobj_mpifx_allgather_module) $$(_modobj_mpifx_allgatherv_module) $$(_modobj_mpifx_finalize_module) $$(_modobj_mpifx_barrier_module) $$(_modobj_mpifx_get_processor_name_module) $$(_modobj_mpifx_abort_module) $$(_modobj_mpifx_init_module) $$(_modobj_mpifx_constants_module) $$(_modobj_mpifx_recv_module) $$(_modobj_mpifx_bcast_module) $$(_modobj_mpifx_gather_module) $$(_modobj_mpifx_gatherv_module) $$(_modobj_mpifx_allreduce_module) $$(_modobj_mpifx_reduce_module) $$(_modobj_mpifx_comm_module) -module.o = module.o $($(_modobj_mpifx_send_module)) $($(_modobj_mpifx_scatter_module)) $($(_modobj_mpifx_scatterv_module)) $($(_modobj_mpifx_allgather_module)) $($(_modobj_mpifx_allgatherv_module)) $($(_modobj_mpifx_finalize_module)) $($(_modobj_mpifx_barrier_module)) $($(_modobj_mpifx_get_processor_name_module)) $($(_modobj_mpifx_abort_module)) $($(_modobj_mpifx_init_module)) $($(_modobj_mpifx_constants_module)) $($(_modobj_mpifx_recv_module)) $($(_modobj_mpifx_bcast_module)) $($(_modobj_mpifx_gather_module)) $($(_modobj_mpifx_gatherv_module)) $($(_modobj_mpifx_allreduce_module)) $($(_modobj_mpifx_reduce_module)) $($(_modobj_mpifx_comm_module)) - -_modobj_libmpifx_module = module.o - -mpifx_allreduce.o: $$(_modobj_mpifx_common_module) -mpifx_allreduce.o = mpifx_allreduce.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_allreduce_module = mpifx_allreduce.o - -mpifx_init.o: $$(_modobj_mpifx_common_module) $$(_modobj_mpifx_constants_module) -mpifx_init.o = mpifx_init.o $($(_modobj_mpifx_common_module)) $($(_modobj_mpifx_constants_module)) -_modobj_mpifx_init_module = mpifx_init.o - -mpifx_common.o: $$(_modobj_mpifx_helper_module) $$(_modobj_mpi) $$(_modobj_mpifx_comm_module) -mpifx_common.o = mpifx_common.o $($(_modobj_mpifx_helper_module)) $($(_modobj_mpi)) $($(_modobj_mpifx_comm_module)) -_modobj_mpifx_common_module = mpifx_common.o - -mpifx_reduce.o: $$(_modobj_mpifx_common_module) -mpifx_reduce.o = mpifx_reduce.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_reduce_module = mpifx_reduce.o - -mpifx_barrier.o: $$(_modobj_mpifx_common_module) -mpifx_barrier.o = mpifx_barrier.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_barrier_module = mpifx_barrier.o - -mpifx_comm.o: $$(_modobj_mpifx_helper_module) $$(_modobj_mpi) -mpifx_comm.o = mpifx_comm.o $($(_modobj_mpifx_helper_module)) $($(_modobj_mpi)) -_modobj_mpifx_comm_module = mpifx_comm.o - -mpifx_scatter.o: $$(_modobj_mpifx_common_module) -mpifx_scatter.o = mpifx_scatter.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_scatter_module = mpifx_scatter.o - -mpifx_scatterv.o: $$(_modobj_mpifx_common_module) -mpifx_scatterv.o = mpifx_scatterv.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_scatterv_module = mpifx_scatterv.o - -mpifx_abort.o: $$(_modobj_mpifx_common_module) -mpifx_abort.o = mpifx_abort.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_abort_module = mpifx_abort.o - -mpifx_bcast.o: $$(_modobj_mpifx_common_module) -mpifx_bcast.o = mpifx_bcast.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_bcast_module = mpifx_bcast.o - -mpifx_get_processor_name.o: $$(_modobj_mpifx_common_module) -mpifx_get_processor_name.o = mpifx_get_processor_name.o $($(_modobj_mpifx_common_module)) -_modobj_mpifx_get_processor_name_module = mpifx_get_processor_name.o - diff --git a/make.arch.template b/make.arch.template deleted file mode 100644 index df379e3..0000000 --- a/make.arch.template +++ /dev/null @@ -1,24 +0,0 @@ -############################################################################ -# Architecture dependent makefile settings -############################################################################ - -# Fortran 2003 compiler -FXX = mpif90 - -# Fortran compiler otions -FXXOPT = - -# Linker -LN = $(FXX) - -# Linker options -LNOPT = - -# M4 interpreter -FYPP = fypp - -# M4 interpreter options -FYPPOPT = "" - -# Where to build the library (ROOT = root of the source distribution) -BUILDDIR = $(ROOT)/_build diff --git a/makefile b/makefile deleted file mode 100644 index 5b06dbe..0000000 --- a/makefile +++ /dev/null @@ -1,32 +0,0 @@ - -ROOT := $(PWD) - -.PHONY: all -all: lib - -include $(ROOT)/make.arch - -.PHONY: lib -lib: - mkdir -p $(BUILDDIR)/lib - $(MAKE) -C $(BUILDDIR)/lib ROOT=$(ROOT) SRCDIR=$(ROOT)/lib \ - FXX="$(FXX)" FXXOPT="$(FXXOPT)" LN="$(LN)" LNOPT="$(LNOPT)" \ - FYPP="$(FYPP)" FYPPOPT="$(FYPPOPT)" -f $(ROOT)/lib/make.build - -.PHONY: install -install: lib - mkdir -p $(INSTALLDIR)/lib - cp $(BUILDDIR)/lib/*.a $(INSTALLDIR)/lib - mkdir -p $(INSTALLDIR)/include - cp $(BUILDDIR)/lib/*.mod $(INSTALLDIR)/include - -.PHONY: test -test: lib - mkdir -p $(BUILDDIR)/test - $(MAKE) -C $(BUILDDIR)/test ROOT=$(ROOT) BUILDROOT=$(BUILDDIR) \ - -f $(ROOT)/test/make.build - - -.PHONY: distclean -distclean: - rm -rf $(BUILDDIR) diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index efaf04f..33983d7 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -12,5 +12,5 @@ set(targets foreach(target IN LISTS targets) add_executable(${target} ${target}.f90) - target_link_libraries(${target} mpifx ${MPI_Fortran_LIBRARIES}) + target_link_libraries(${target} MpiFx) endforeach() diff --git a/test/integration/cmake/CMakeLists.txt b/test/integration/cmake/CMakeLists.txt new file mode 100644 index 0000000..a1be0f3 --- /dev/null +++ b/test/integration/cmake/CMakeLists.txt @@ -0,0 +1,8 @@ +cmake_minimum_required(VERSION 3.16) + +project(TestMpiFxBuild LANGUAGES Fortran) + +find_package(MpiFx REQUIRED) + +add_executable(test_mpifxbuild test_mpifxbuild.f90) +target_link_libraries(test_mpifxbuild MpiFx::MpiFx) diff --git a/test/integration/cmake/runtest.sh b/test/integration/cmake/runtest.sh new file mode 100755 index 0000000..7216511 --- /dev/null +++ b/test/integration/cmake/runtest.sh @@ -0,0 +1,26 @@ +#!/bin/bash +# +# Tests whether the installed MpiFx library can be used within a CMake project. +# +# Arguments: +# +# - building directory (will be created, should not exist) +# +# Requirements: +# +# - Environment variable FC contains the same Fortran compiler as used for MpiFx +# +# - Environment variable CMAKE_PREFIX_PATH contains the MpiFx install root. +# +SCRIPTDIR=$(dirname $0) +SCRIPTNAME=$(basename $0) +BUILDDIR=$1 + +if [ -d ${BUILDDIR} ]; then + echo "${SCRIPTNAME}: Test build directory '${BUILDDIR}' already exists." >&2 + exit 1 +fi + +FC=$FC cmake -B ${BUILDDIR} ${SCRIPTDIR} || { echo "Configuration step failed" >&2; exit 1; } +cmake --build ${BUILDDIR} -- VERBOSE=1 || { echo "Build step failed" >&2; exit 1; } +echo "CMake build succeeded!" diff --git a/test/integration/cmake/test_mpifxbuild.f90 b/test/integration/cmake/test_mpifxbuild.f90 new file mode 100644 index 0000000..af9b342 --- /dev/null +++ b/test/integration/cmake/test_mpifxbuild.f90 @@ -0,0 +1,5 @@ +program test_mpifxbuild + use libmpifx_module + implicit none + +end program test_mpifxbuild diff --git a/test/integration/pkgconfig/runtest.sh b/test/integration/pkgconfig/runtest.sh new file mode 100755 index 0000000..f6ad9a8 --- /dev/null +++ b/test/integration/pkgconfig/runtest.sh @@ -0,0 +1,45 @@ +#!/bin/bash +# +# Tests whether the installed MpiFx library can be used with pkg-config based builds. +# +# Arguments: +# +# - building directory (will be created if it does not exist) +# +# Requirements: +# +# - Environment variable FC contains the same Fortran compiler as used for MpiFx +# +# - Environment variable PKG_CONFIG_PATH contains the lib/pkgconfig folder within +# the installed MpiFx tree. +# +# - You pass all linker options as arguments, which are needed to link an MPI-binary +# with your compiler. Alternatively, you can specify the name of the MPI-wrapper +# as your Fortran compiler in FC. +# +SCRIPTDIR=$(dirname $0) +SCRIPTNAME=$(basename $0) +BUILDDIR=$1 +shift +CUSTOMLIBS=$* + +if [ ! -d ${BUILDDIR} ]; then + mkdir ${BUILDDIR} || { echo "Could not create build dir '${BUILDDIR}'" >&2; exit 1; } +fi + +# Make sure, scriptdir is absoulte +cd ${SCRIPTDIR} +SCRIPTDIR=${PWD} +cd - + +cd ${BUILDDIR} || { echo "Could not change to build dir '${BUILDDIR}'" >&2; exit 1; } +pkg-config --exists mpifx || { echo "No PKG-CONFIG found for MpiFx" >&2; exit 1; } + +cflags=$(pkg-config --cflags mpifx) +libs=$(pkg-config --libs mpifx) + +cmd="${FC} ${cflags} ${SCRIPTDIR}/test_mpifxbuild.f90 ${libs} ${CUSTOMLIBS}" + +echo "Build command: ${cmd}" +${cmd} || { echo "Build command failed" >&2; exit 1; } +echo "PKG-CONFIG build succeeded." diff --git a/test/integration/pkgconfig/test_mpifxbuild.f90 b/test/integration/pkgconfig/test_mpifxbuild.f90 new file mode 100644 index 0000000..d4e5ba0 --- /dev/null +++ b/test/integration/pkgconfig/test_mpifxbuild.f90 @@ -0,0 +1,72 @@ +program test_bcast + use libmpifx_module + implicit none + + integer, parameter :: dp = kind(1.0d0) + integer, parameter :: sp = kind(1.0) + + type(mpifx_comm) :: mycomm + integer :: buffer(3) + logical :: lbuffer(3) + real(dp) :: rbuffer(2, 2) + complex(sp) :: cbuffer + character(5) :: text + + ! Integer vector + call mpifx_init() + call mycomm%init() + buffer(:) = 0 + print "(A,I2.2,A,3I5)", "CHK01:", mycomm%rank, ":", buffer + if (mycomm%lead) then + buffer(:) = [ 1, 2, 3 ] + end if + print "(A,I2.2,A,3I5)", "CHK02:", mycomm%rank, ":", buffer + call mpifx_bcast(mycomm, buffer) + print "(A,I2.2,A,3I5)", "CHK03:", mycomm%rank, ":", buffer + call mpifx_barrier(mycomm) + + ! Logical vector + lbuffer(:) = .false. + print "(A,I2.2,A,3L5)", "CHK04:", mycomm%rank, ":", lbuffer + if (mycomm%lead) then + lbuffer(:) = [ .true., .false., .true. ] + end if + print "(A,I2.2,A,3L5)", "CHK05:", mycomm%rank, ":", lbuffer + call mpifx_bcast(mycomm, lbuffer) + print "(A,I2.2,A,3L5)", "CHK06:", mycomm%rank, ":", lbuffer + call mpifx_barrier(mycomm) + + ! Real rank 2 array + rbuffer(:,:) = 0.0_dp + print "(A,I2.2,A,4F10.6)", "CHK07:", mycomm%rank, ":", rbuffer + if (mycomm%lead) then + rbuffer(:,:) = reshape([ real(dp) :: 1, 2, 3, 4 ], [ 2, 2 ]) + end if + print "(A,I2.2,A,4F10.6)", "CHK08:", mycomm%rank, ":", rbuffer + call mpifx_bcast(mycomm, rbuffer) + print "(A,I2.2,A,4F10.6)", "CHK09:", mycomm%rank, ":", rbuffer + call mpifx_barrier(mycomm) + + ! Complex scalar + cbuffer = cmplx(0, 0, sp) + print "(A,I2.2,A,2F10.6)", "CHK10:", mycomm%rank, ":", cbuffer + if (mycomm%lead) then + cbuffer = cmplx(-1, 1, sp) + end if + print "(A,I2.2,A,2F10.6)", "CHK11:", mycomm%rank, ":", cbuffer + call mpifx_bcast(mycomm, cbuffer) + print "(A,I2.2,A,2F10.6)", "CHK12:", mycomm%rank, ":", cbuffer + + ! Character + text = " " + print "(A,I2.2,A,A6)", "CHK13:", mycomm%rank, ":", text + if (mycomm%lead) then + text = "hello" + end if + print "(A,I2.2,A,A6)", "CHK14:", mycomm%rank, ":", text + call mpifx_bcast(mycomm, text) + print "(A,I2.2,A,A6)", "CHK15:", mycomm%rank, ":", text + + call mpifx_finalize() + +end program test_bcast diff --git a/test/make.build b/test/make.build deleted file mode 100644 index 4a0f8ef..0000000 --- a/test/make.build +++ /dev/null @@ -1,77 +0,0 @@ -############################################################################ -# -# Makefile for building some example programs -# -# Needs as variable: -# ROOT Source root directory -# BUILDROOT Build root directory -# -# The mpifx library must be already built in $(BUILDROOT)/lib -# -############################################################################ - - -############################################################################ -# Building some test/example programs. -############################################################################ - -.SUFFIXES: -.SUFFIXES: .f90 .o - -TARGETS = test_bcast test_send_recv test_comm_split test_reduce \ - test_allreduce test_gather test_allgather test_scatter \ - test_scatterv - -all: $(TARGETS) - -MPIFX_LIBDIR = $(BUILDROOT)/lib -MPIFX_INCDIR = $(BUILDROOT)/lib - -include $(ROOT)/make.arch - -# Directory where library source can be found -SRCDIR = $(ROOT)/test - -vpath % $(SRCDIR) - -%.o: %.f90 - $(FXX) $(FXXOPT) -I$(MPIFX_INCDIR) -c $< - -# Linking rules for targets -define link-target -$(LN) $(LNOPT) -o $@ $^ -L$(MPIFX_LIBDIR) -lmpifx -endef - -.PHONY: clean -clean: - rm -f *.mod *.o _* - - -include $(SRCDIR)/make.deps - -test_bcast: $(test_bcast.o) - $(link-target) - -test_send_recv: $(test_send_recv.o) - $(link-target) - -test_comm_split: $(test_comm_split.o) - $(link-target) - -test_reduce: $(test_reduce.o) - $(link-target) - -test_allreduce: $(test_allreduce.o) - $(link-target) - -test_gather: $(test_gather.o) - $(link-target) - -test_allgather: $(test_allgather.o) - $(link-target) - -test_scatter: $(test_scatter.o) - $(link-target) - -test_scatterv: $(test_scatterv.o) - $(link-target) diff --git a/test/make.deps b/test/make.deps deleted file mode 100644 index e2d11be..0000000 --- a/test/make.deps +++ /dev/null @@ -1,35 +0,0 @@ -.SECONDEXPANSION: - -test_allgather.o: $$(_modobj_libmpifx_module) -test_allgather.o = test_allgather.o $($(_modobj_libmpifx_module)) - -test_allgatherv.o: $$(_modobj_libmpifx_module) -test_allgatherv.o = test_allgatherv.o $($(_modobj_libmpifx_module)) - -test_gather.o: $$(_modobj_libmpifx_module) -test_gather.o = test_gather.o $($(_modobj_libmpifx_module)) - -test_gatherv.o: $$(_modobj_libmpifx_module) -test_gatherv.o = test_gatherv.o $($(_modobj_libmpifx_module)) - -test_send_recv.o: $$(_modobj_libmpifx_module) -test_send_recv.o = test_send_recv.o $($(_modobj_libmpifx_module)) - -test_bcast.o: $$(_modobj_libmpifx_module) -test_bcast.o = test_bcast.o $($(_modobj_libmpifx_module)) - -test_scatter.o: $$(_modobj_libmpifx_module) -test_scatter.o = test_scatter.o $($(_modobj_libmpifx_module)) - -test_scatterv.o: $$(_modobj_libmpifx_module) -test_scatterv.o = test_scatterv.o $($(_modobj_libmpifx_module)) - -test_comm_split.o: $$(_modobj_libmpifx_module) -test_comm_split.o = test_comm_split.o $($(_modobj_libmpifx_module)) - -test_reduce.o: $$(_modobj_libmpifx_module) -test_reduce.o = test_reduce.o $($(_modobj_libmpifx_module)) - -test_allreduce.o: $$(_modobj_libmpifx_module) -test_allreduce.o = test_allreduce.o $($(_modobj_libmpifx_module)) - diff --git a/utils/cr_makedep b/utils/cr_makedep deleted file mode 100755 index cb52d73..0000000 --- a/utils/cr_makedep +++ /dev/null @@ -1,394 +0,0 @@ -#!/usr/bin/env python -############################################################################### -# -# Copyright (c) 2013, Balint Aradi -# -# All rights reserved. -# -# Redistribution and use in source and binary forms, with or without -# modification, are permitted provided that the following conditions are met: -# -# * Redistributions of source code must retain the above copyright notice, -# this list of conditions and the following disclaimer. -# -# * Redistributions in binary form must reproduce the above copyright notice, -# this list of conditions and the following disclaimer in the documentation -# and/or other materials provided with the distribution. -# -# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" -# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE -# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE -# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE -# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR -# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF -# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS -# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN -# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) -# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE -# POSSIBILITY OF SUCH DAMAGE. -# -############################################################################### -from __future__ import print_function -import argparse -import re -import os -import copy - -DESCRIPTION = """Creates dependency information for the GNU Make system by -analyzing Fortran 90+ source files. - -It searches the source files in the given directory for module inclusions and -module definitions (via the 'use' and 'module' statements). In every directory -it creates a file 'Makefile.dep' which can be included by the actual makefile. -If the source files contain CPP conditionals (#if, #ifdef, #else, -#endif), they will be included in the dependency file, so that preprocessing -the dependency file with CPP will give the correct dependencies. -""" - -# Patterns for branch constructs: #if*, #else, #endif -PAT_IF = re.compile(r"^[ \t]*#[ \t]*if(?P(?:n?def)?[ \t]+.*)$", - re.MULTILINE) -PAT_ELSE = re.compile(r"^[ \t]*#[ \t]*else[ \t]*$", re.MULTILINE) -PAT_ENDIF = re.compile(r"^[ \t]*#[ \t]*endif\s*$", re.MULTILINE) - -# Patterns for other constructs: #include, use, module -PAT_INCLUDE = re.compile(r"""^[ \t]*\#[ \t]*include\s+ - (?:'(?P[^']+)' - |\"(?P[^\"]+)\") - """, re.MULTILINE | re.VERBOSE) - -PAT_USE = re.compile(r"^[ \t]*use[ \t]+(?P[^ \s,]*)", - re.MULTILINE | re.IGNORECASE) - -PAT_MODULE = re.compile(r"^[ \t]*module[ \t]+(?P\S+)[ \t]*$", - re.MULTILINE | re.IGNORECASE) - -PAT_INCLUDE2 = re.compile(r"^[ \t]*include\s*['\"(](?P[^'\")]+)['\")]", - re.MULTILINE | re.IGNORECASE) - -# List of all patterns -PATTERNS = ( PAT_IF, PAT_ELSE, PAT_ENDIF, PAT_INCLUDE, PAT_USE, PAT_MODULE, - PAT_INCLUDE2 ) - -# Dependency information types -DEP_MODULE = 0 -DEP_INCLUDE = 1 - -# Definition types -DFN_MODULE = 0 - -# Pattern to select files to process: -PAT_FILE = re.compile(r"\.f90$|\.h$|\.inc|\.fpp$", re.IGNORECASE) - -# Extensions to be considered Fortran source files -FORTRAN_EXTENSIONS = (".f90", ".f", ".fpp") - -# Name of the dependency output -DEPFILE = "Makefile.dep" - -class MakedepException(Exception): - pass - - -class BranchBlock(object): - """Contains information on a block which may contain dependency information - and a branch point""" - - def __init__(self): - """Initialises a BranchBlock""" - self._dependencies = set() # Dependencies - self._definitions = set() # Defined entities - self._condition = "" # Condition for the branch point - self._truechild = None # True block of the branch - self._falsechild = None # False block of the branch - self._hasbranch = False # If current block contains a branch - - - def add_dependency(self, dep, deptype): - """Adds a dependency to the current block - dep -- name of the dependency - deptype -- type of the dependency - """ - self._dependencies.add((dep, deptype)) - - - def add_definition(self, dfn, dfntype): - """Adds a dependency to the current block - dfn -- name of the definition - deptype -- type of the definition - """ - self._definitions.add((dfn, dfntype)) - - - def add_branch(self, condition, true, false): - """Adds a branch to the current block - condition -- Branching condition - true -- True block of the branch - false -- False block of the branch - """ - # Make sure, all branches are proper objects - true = true or BranchBlock() - false = false or BranchBlock() - - if self._hasbranch: - # We have a branch point already, add new branch to them - if self._condition == condition: - self._truechild.extendBlock(true) - self._falsechild.extendBlock(false) - else: - self._truechild.add_branch(condition, true, false) - self._falsechild.add_branch(condition, true, false) - else: - # No branch point yet: branch point added to the current block - self._hasbranch = True - self._condition = condition - self._truechild = copy.deepcopy(true) - self._falsechild = copy.deepcopy(false) - - - def extend_block(self, block): - """Extends a block with the content of an other one. - block -- Contains the information to add - """ - self._dependencies.update(block._dependencies) - self._definitions.update(block._definitions) - if block._hasbranch: - self.add_branch(block._condition, block._truechild, - block._falsechild) - - - def hasbranch(self): - """Returns flag, if current block contains a branch or not""" - return self._hasbranch - - - def has_deps_or_defs(self): - """Flags, if current block contains any dependencies or definitions""" - return (len(self._dependencies) != 0 or len(self._definitions) != 0) - - - def write_tree(self, fp, fbase, fext, fsrc): - """Prints the dependency tree in the appropriate format - fp -- pointer to an open file - fbase -- base name of the processed file - fext -- extension of the processed file - fsrc -- flags if processed file was a fortran file or not - (A more elegant implementation would do this with a writer class...) - """ - self._write_tree_recursive(fp, [], [], fbase, fext, fsrc) - - - def _write_tree_recursive(self, fp, deps, defs, fbase, fext, fsrc): - """Working horse for the write_tree routine - fp: file pointer - deps: Dependencies so far - defs: Definitions so far - fbase: base name of the processed file - fext: extension of the processed file - fsrc: flags if processed file was a fortran source file - """ - - newdeps = deps + list(self._dependencies) - newdefs = defs + list(self._definitions) - - if self._hasbranch: - # We have a branch point, dive into the true and false branch - fp.write("#if{}\n".format(self._condition)) - self._truechild._write_tree_recursive(fp, newdeps, newdefs, fbase, - fext, fsrc) - fp.write("#else\n") - self._falsechild._write_tree_recursive(fp, newdeps, newdefs, fbase, - fext, fsrc) - fp.write("#endif\n") - else: - # No further branch points: write all dependencies in order - filedeps = [] - vardeps = [] - for (depname, deptype) in newdeps: - if deptype == DEP_MODULE: - filedeps.append("$$({0}{1})".format("_modobj_", depname)) - vardeps.append("$($({0}{1}))".format("_modobj_", depname)) - else: - filedeps.append(depname) - vardeps.append("$({0})".format(depname)) - if fsrc: - fp.write("{0}.o: ".format(fbase)) - if filedeps: - fp.write(" ".join(filedeps)) - fp.write("\n") - fp.write("{0}.o = {0}.o ".format(fbase)) - if vardeps: - fp.write(" ".join(vardeps)) - fp.write("\n") - else: - if filedeps: - fp.write("{0}{1}: ".format(fbase, fext)) - fp.write(" ".join(filedeps) + "\n") - fp.write("{0}{1} = ".format(fbase, fext)) - if vardeps: - fp.write(" ".join(vardeps)) - fp.write("\n") - - # Write definitions: - for (dfnname, dfntype) in newdefs: - if dfntype == DFN_MODULE: - fp.write("{0}{1} = {2}.o\n".format("_modobj_", dfnname, - fbase)) - - -def build_dependency_tree(txt): - """Creates a dependency tree for the given text""" - - end = len(txt) - matches = [ pat.search(txt) for pat in PATTERNS ] - starts = [] - for match in matches: - if match: - starts.append(match.start()) - else: - starts.append(end) - (itype, node) = build_dependency_recursive(txt, matches, starts) - return node - - - -def nextmatch(txt, matches, starts, itype): - # Helper function for build_dependency_recursive, updating matches and - # starts by replacing the entries for itype with the next occurance. - - if matches[itype] == None: - raise MakedepException("Invalid nesting of blocks " - "(probably unclosed #if* block)") - match = PATTERNS[itype].search(txt, matches[itype].end()) - matches[itype] = match - if match: - starts[itype] = match.start() - else: - starts[itype] = len(txt) - - -def build_dependency_recursive(txt, matches, starts): - """Working function for the build_dependency_tree routine. - txt -- text to parse - matches -- last match for each pattern in PATTERNS - starts -- starting position of the last matches (len(txt) if no match) - return -- (itype, node), where itype is the type of the closing block - and node is the tree built. - """ - - block = BranchBlock() - end = len(txt) - firstpos = min(starts) - itype = -1 - - # Loop as long we did not reach the end of the text - while firstpos < end: - - # get entry type and match object for the first pttern match - itype = starts.index(firstpos) - match = matches[itype] - - if itype == 0: - # Branch opening (#ifdef) - condition = match.group("cond") - nextmatch(txt, matches, starts, itype) - (itype, ifbranch) = build_dependency_recursive(txt, matches, starts) - if itype == 1: - # If branch ended with #else -> parse the else branch as well - nextmatch(txt, matches, starts, itype) - (itype, elsebranch) = build_dependency_recursive(txt, matches, - starts) - else: - elsebranch = None - # Sanity check: #if must be closed by #endif - if itype != 2: - raise MakedepException("ERROR, #else must be terminted by " - "#endif") - # if any of the two branches contains usefull info, add the branch - # to the current block - if ifbranch or elsebranch: - block.add_branch(condition, ifbranch, elsebranch) - elif itype == 1 or itype == 2: - # block closing #else or #endif found -> escape to higher level - break - elif itype == 3: - # #include found - groups = match.groups() - name = groups[0] - if not name: - name = groups[1] - block.add_dependency(name, DEP_INCLUDE) - elif itype == 4: - # module found - block.add_dependency(match.group("mod").lower(), DEP_MODULE) - elif itype == 5: - # module defintion found - block.add_definition(match.group("mod").lower(), DFN_MODULE) - elif itype == 6: - # include with ' or " or () - block.add_dependency(match.group("name"), DEP_INCLUDE) - else: - raise MakedepException("Unknown itype: {:d}".format(itype)) - - # Get next occurance for processed entry - nextmatch(txt, matches, starts, itype) - firstpos = min(starts) - - # Pass block back, if it contains usefull info - if block.has_deps_or_defs() or block.hasbranch(): - return (itype, block) - else: - return (itype, None) - - -def write_depfile(fp, sources): - """Writes dependency file. - fp -- File descriptor for file to write to. - sources -- Fortran source files to investigate - """ - - fp.write(".SECONDEXPANSION:\n\n") - for source in sources: - print("Processing: {}".format(source)) - fpsource = open(source, "r") - txt = fpsource.read() - fpsource.close() - tree = build_dependency_tree(txt) - if tree: - fbase, fext = os.path.splitext(os.path.basename(source)) - fextlow = fext.lower() - fsrc = fextlow in FORTRAN_EXTENSIONS - tree.write_tree(fp, fbase, fext, fsrc) - fp.write("\n") - - - -def main(): - """Main procedure""" - - parser = argparse.ArgumentParser(description=DESCRIPTION) - parser.add_argument( - 'dirnames', metavar='DIR', nargs='*', default=["."], - help="Directory in which dependency file should be created " - "(default: '.')") - - args = parser.parse_args() - - for dirname in args.dirnames: - outname = os.path.join(dirname, DEPFILE) - print("Creating:", outname) - fp = open(outname, "w") - fnames = [ os.path.join(dirname, fname) - for fname in os.listdir(dirname) - if PAT_FILE.search(fname) ] - write_depfile(fp, fnames) - fp.close() - - -if __name__ == "__main__": - main() - - -### Local Variables: -### mode:python -### End: diff --git a/utils/export/mpifx-config.cmake.in b/utils/export/mpifx-config.cmake.in new file mode 100644 index 0000000..49f67b6 --- /dev/null +++ b/utils/export/mpifx-config.cmake.in @@ -0,0 +1,10 @@ +@PACKAGE_INIT@ + +include(CMakeFindDependencyMacro) + +if(NOT TARGET MpiFx::MpiFx) + if(NOT TARGET MPI::MPI_Fortran) + find_dependency(MPI) + endif() + include(${CMAKE_CURRENT_LIST_DIR}/mpifx-targets.cmake) +endif() diff --git a/utils/export/mpifx.pc.in b/utils/export/mpifx.pc.in new file mode 100644 index 0000000..dfd8105 --- /dev/null +++ b/utils/export/mpifx.pc.in @@ -0,0 +1,9 @@ +Name: mpifx +Description: Modern Fortran wrappers for MPI +Version: @PROJECT_VERSION@ +URL: https://github.com/dftbplus/mpifx + +Requires: @PKGCONFIG_REQUIRES@ +Libs: @PKGCONFIG_LIBS@ +Libs.private: @PKGCONFIG_LIBS_PRIVATE@ +Cflags: @PKGCONFIG_C_FLAGS@