Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 14 additions & 1 deletion cmake/ccpp_capgen.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
# SUITES - CMake list of suite xml files
function(ccpp_capgen)
set(optionalArgs CAPGEN_DEBUG CAPGEN_EXPECT_THROW_ERROR)
set(oneValueArgs HOST_NAME OUTPUT_ROOT VERBOSITY)
set(oneValueArgs HOST_NAME OUTPUT_ROOT VERBOSITY KIND_SPECS)
set(multi_value_keywords HOSTFILES SCHEMEFILES SUITES)

cmake_parse_arguments(arg "${optionalArgs}" "${oneValueArgs}" "${multi_value_keywords}" ${ARGN})
Expand Down Expand Up @@ -51,6 +51,19 @@ function(ccpp_capgen)
separate_arguments(VERBOSE_PARAMS UNIX_COMMAND "${VERBOSE_PARAMS_SEPARATED}")
list(APPEND CCPP_CAPGEN_CMD_LIST ${VERBOSE_PARAMS})
endif()
if(DEFINED arg_KIND_SPECS)
string(REPLACE "," ";" KIND_SPEC_LIST "${arg_KIND_SPECS}")
set(KIND_ARGS "") # start empty
foreach(pair IN LISTS KIND_SPEC_LIST)
# Append each pair prefixed with --kind-type and quoted.
# The surrounding double‑quotes are added explicitly so the
# resulting string contains them.
set(KIND_ARGS "${KIND_ARGS}--kind-type \"${pair}\"")
string(STRIP "${KIND_ARGS}" KIND_ARGS)
endforeach()

list(APPEND CCPP_CAPGEN_CMD_LIST ${KIND_SPEC_PARAMS})
endif()

message(STATUS "Running ccpp_capgen.py from ${CMAKE_CURRENT_SOURCE_DIR}")

Expand Down
11 changes: 8 additions & 3 deletions scripts/ccpp_capgen.py
Original file line number Diff line number Diff line change
Expand Up @@ -132,9 +132,14 @@ def create_kinds_file(run_env, output_dir):
with FortranWriter(kinds_filepath, "w",
"kinds for CCPP", KINDS_MODULE) as kindf:
for kind_type in kind_types:
use_stmt = "use ISO_FORTRAN_ENV, only: {} => {}"
kindf.write(use_stmt.format(kind_type,
run_env.kind_spec(kind_type)), 1)
kind_spec = run_env.kind_spec(kind_type)
use_stmt = f"use {run_env.kind_module(kind_type)},"
if kind_spec == kind_type:
use_stmt += f" only: {kind_type}"
else:
use_stmt += f" only: {kind_type} => {kind_spec}"
# end if
kindf.write(use_stmt, 1)
# end for
kindf.write_preamble()
for kind_type in kind_types:
Expand Down
113 changes: 98 additions & 15 deletions scripts/framework_env.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,14 @@
import argparse
import os
from parse_tools import verbose

_EPILOG = '''
'''

## List of kinds in ISO_FORTRAN_ENV (that are useful in CCPP)
## Note: this is defined here instead of in fortran_tools to prevent a
## circular dependency
ISO_FORTRAN_KINDS = ['int8', 'int16', 'int32', 'int64', 'real32', 'real64', 'real128']

###############################################################################
class CCPPFrameworkEnv:
###############################################################################
Expand All @@ -31,6 +35,19 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False,
<ndict> is a dict with the parsed command-line arguments (or a
dictionary created with the necessary arguments).
<logger> is a logger to be used by users of this object.
<kind_types> is a list defining the Fortran kind types which will be
public in ccpp_kinds.F90.
It has entries of the form:
kind_type=kind_specification[:kind_module]
where <kind_type> is a string defining the kind type name,
<kind_specification> is the Fortran kind parameter name
(e.g., 'REAL64'), and <kind_module> is the (optional) Fortran
module that contains <kind_specification>. If <kind_module> is
not specified, then <kind_specification> must be a type defined in
ISO_FORTRAN_ENV.
It is allowed to have a duplicate entry for <kind_type> as long as
it does not specify a different type or module.
<kind_type> will be made available as a kind in ccpp_kinds.F90
"""
emsg = ''
esep = ''
Expand Down Expand Up @@ -143,30 +160,44 @@ def __init__(self, logger, ndict=None, verbose=0, clean=False,
# end if
self.__generate_host_cap = self.host_name != ''
self.__kind_dict = {}
if ndict and ("kind_type" in ndict):
kind_list = ndict["kind_type"]
del ndict["kind_type"]
if ndict and ("kind_types" in ndict):
kind_list = ndict["kind_types"]
del ndict["kind_types"]
else:
kind_list = kind_types
# end if
# Note that the command line uses repeated calls to 'kind_type'
for kind in kind_list:
kargs = [x.strip() for x in kind.strip().split('=')]
errstr = ""
if len(kargs) != 2:
emsg += esep
emsg += "Error: '{}' is not a valid kind specification "
emsg += "(should be of the form <kind_name>=<kind_spec>)"
emsg = emsg.format(kind)
emsg += (f"{esep}Error: '{kind}' is not a valid kind specification "
"(should be of the form <kind_name>=<kind_spec>)")
esep = '\n'
else:
kind_name, kind_spec = kargs
# Do not worry about duplicates, just use last value
self.__kind_dict[kind_name] = kind_spec
kind_specs = kind_spec.split(':')
if len(kind_specs) == 1:
errstr = self.add_kind_type(kind_name, kind_specs[0])
elif len(kind_specs) > 2:
emsg += (f"{esep}Error: Invalid format for '{kind_name}' "
"should be [<kind_spec>] or [<kind_spec>, <module name>]")
esep = '\n'
else:
errstr = self.add_kind_type(kind_name, kind_specs[0], kind_specs[1])
# end if
if errstr:
emsg += f"{esep}{errstr}"
esep = '\n'
# end if
# end if
# end for

# We always need a kind_phys so add a default if necessary
if "kind_phys" not in self.__kind_dict:
self.__kind_dict["kind_phys"] = "REAL64"
# Use ISO-Fortran 64-bit real
# definition for default physics kind:
self.__kind_dict['kind_phys'] = ['REAL64', 'ISO_FORTRAN_ENV']
# end if
if ndict and ('use_error_obj' in ndict):
self.__use_error_obj = ndict['use_error_obj']
Expand Down Expand Up @@ -269,16 +300,68 @@ def generate_host_cap(self):
CCPPFrameworkEnv object."""
return self.__generate_host_cap

def kind_module(self, kind_type):
"""Return the Fortran module that
contains the kind specification
for kind type, <kind_type>,
for this CCPPFrameworkEnv object.
If there is no entry for <kind_type>,
return None."""
kind_mod = None
if kind_type in self.__kind_dict:
# The kind module should always be
# the second element in the list:
kind_mod = self.__kind_dict[kind_type][1]
# end if
return kind_mod

def kind_spec(self, kind_type):
"""Return the kind specification for kind type, <kind_type>
for this CCPPFrameworkEnv object.
If there is no entry for <kind_type>, return None."""
kind_spec = None
if kind_type in self.__kind_dict:
kind_spec = self.__kind_dict[kind_type]
# The kind specification should always be
# the first element in the list:
kind_spec = self.__kind_dict[kind_type][0]
# end if
return kind_spec

def add_kind_type(self, new_ccpp_kind, new_kind, new_module=None):
"""Add <new_kind> to our kind dictionary.
<new_module> is the name of the Fortran module that defined <new_kind>
<new_ccpp_kind> is the kind name as published in ccpp_kinds.f90
This method assumes the inputs have been parsed.
Returns None or an error string if <new_kind> is already in the
kinds dictionary.
"""
emsg = ""
esep = ""
# Make sure we have a valid module
if new_module == None:
if new_kind.lower() in ISO_FORTRAN_KINDS:
new_module = 'ISO_FORTRAN_ENV'
else:
emsg += (f"{esep}Error: unknown kind, '{new_kind}' "
"and no Fortran module name specified")
esep = '\n'
# end if
# end if
# Check for incompatible duplicates
if ((new_ccpp_kind in self.__kind_dict) and
((self.kind_spec(new_ccpp_kind) != new_kind) or
(self.kind_module(new_ccpp_kind) != new_module))):
emsg += (f"{esep}Error: '{new_ccpp_kind} = [{new_kind}, {new_module}]'"
f"is an invalid duplicate. {new_ccpp_kind} "
f"is already '{str(self.__kind_dict[new_ccpp_kind])}")
esep = '\n'
else:
if new_module:
self.__kind_dict[new_ccpp_kind] = [new_kind, new_module]
# end if
# end if
return emsg

def kind_types(self):
"""Return a list of all kind types defined in this
CCPPFrameworkEnv object."""
Expand Down Expand Up @@ -381,12 +464,12 @@ def parse_command_line(args, description, logger=None):
help='Remove files created by this script, then exit')

parser.add_argument("--kind-type", type=str, action='append',
metavar="kind_type", default=list(),
help="""Data size for real(<kind_type>) data.
metavar="kind_spec", dest="kind_types", default=list(),
help="""Data size for <kind_type> data (e.g., real(<kind_type>)).
Entry in the form of <kind_type>=<kind_val>
e.g., --kind-type "kind_phys=REAL64"
Enter more than one --kind-type entry to define multiple CCPP kinds.
<kind_val> SHOULD be a valid ISO_FORTRAN_ENV type""")
<kind_val> MUST be a valid ISO_FORTRAN_ENV type""")
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This rule only applies on the command line.


parser.add_argument("--generate-docfiles",
metavar='HTML | Latex | HTML,Latex', type=str,
Expand Down
74 changes: 67 additions & 7 deletions scripts/metadata_table.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,43 @@

A 'ccpp-table-properties' section entries are:
name = <name> : the name of the following ccpp-arg-table entries (required).
It is one of the following possibilities:
It is one of the following possibilities:
- SchemeName: the name of a scheme (i.e., the name of
a scheme interface (related to SubroutineName below).
a scheme interface (related to SubroutineName below).
- DerivedTypeName: a derived type name for a type which will be used
somewhere in the CCPP interface.
somewhere in the CCPP interface.
- ModuleName: the name of the module whose module variables will be
used somewhere in the CCPP interface
used somewhere in the CCPP interface
- HostName: the name of the host model. Variables in this section become
part of the CCPP UI, the CCPP routines called by the
host model (e.g., <HostName>_ccpp_physics_run).
part of the CCPP UI, the CCPP routines called by the
host model (e.g., <HostName>_ccpp_physics_run).
type = <type> : The type of header (required), one of:
- scheme: A CCPP subroutine
- ddt: A header for a derived data type
- module: A header on some module data
- host: A header on data which will be part of the CCPP UI
dependencies = <dependencies> : Comma-separated list of module dependencies
Each item should appear in one or more use statements in the
corresponding Fortran module
dependencies_path = <relative path> : A path, relative to the location of
metadata table file, where dependencies can be found.
module_name = <module name> : only needed if module name differs from filename
source_path = <relative source directory of Fortran source (if different)>
dynamic_constituent_routine = ??? : @peverwhee?
kind_spec = <kind_spec> : One or more optional Fortran kinds defined
in the corresponding Fortran file.
The format is fortran_module:ccpp_kind_name=>kind_name or
fortran_module:kind_name
- <fortran_module> is the module name of the corresponding Fortran module
- <kind_name> is defined in the corresponding Fortran module
- <ccpp_kind_name> is optional and describes the kind name used in CCPP
metadata tables and Fortran files.
These entries are added to the framework_env object and
thus to ccpp_kinds.F90
The entries in ccpp_kinds.F90 are:
use <module name>, only <kind_name>
use <module name>, only <ccpp_kind_name> => <kind_name>
where the first form is used if <ccpp_kind_name> is omitted.

The ccpp-arg-table section entries in this section are:
name = <name> : the name of the file object which immediately follows the
Expand Down Expand Up @@ -63,7 +85,7 @@
type = scheme
dependencies_path = <relative path>
dependencies = <dependencies>
module = <module name> # only needed if module name differs from filename
module_name = <module name> # only needed if module name differs from filename
source_path = <relative source directory of Fortran source (if different)>
dynamic_constituent_routine = <routine name>

Expand Down Expand Up @@ -512,6 +534,44 @@ def __init_from_file(self, known_ddts, run_env, skip_ddt_check=False):
self.__dependencies_path = value
elif key == 'source_path':
self.__fortran_src_path = os.path.join(my_dirname, value)
elif key == 'kind_spec':
# Add spec to the runtime environment's kinds dict
spec_list = [x.strip() for x in value.split(':', maxsplit=2)]
fort_module = spec_list[0]
if len(spec_list) < 2:
emsg = f"A Fortran kind name is required for '{value}'"
self.__pobj.add_syntax_err(emsg)
continue
# end if
new_ccpp_kind = spec_list[1]
spec_list = [x.strip() for x in new_ccpp_kind.split('=>', maxsplit=1)]
if len(spec_list) > 1:
new_kind = spec_list[1]
new_ccpp_kind = spec_list[0]
else:
new_kind = new_ccpp_kind
# end if
try:
check_fortran_id(new_kind, {}, True)
except CCPPError as err:
self.__pobj.add_syntax_err(f"{err}")
new_kind = None
# end try
if new_kind and (new_ccpp_kind != new_kind):
try:
check_fortran_id(new_ccpp_kind, {}, True)
except CCPPError as err:
self.__pobj.add_syntax_err(f"{err}")
new_ccpp_kind = None
# end try
# end if
if new_kind:
emsg = run_env.add_kind_type(new_ccpp_kind,
new_kind, fort_module)
if emsg:
self.__pobj.add_syntax_err(emsg)
# end if
# end if
else:
tok_type = "metadata table start property"
self.__pobj.add_syntax_err(tok_type, token=key)
Expand Down
2 changes: 1 addition & 1 deletion scripts/parse_tools/fortran_conditional.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,4 +10,4 @@
FORTRAN_CONDITIONAL_REGEX_WORDS = [' ', '(', ')', '==', '/=', '<=', '>=', '<', '>', '.eqv.', '.neqv.',
'.true.', '.false.', '.lt.', '.le.', '.eq.', '.ge.', '.gt.', '.ne.',
'.not.', '.and.', '.or.', '.xor.']
FORTRAN_CONDITIONAL_REGEX = re.compile(r"[\w']+|" + "|".join([word.replace('(','\(').replace(')', '\)') for word in FORTRAN_CONDITIONAL_REGEX_WORDS]))
FORTRAN_CONDITIONAL_REGEX = re.compile(r"[\w']+|" + "|".join([word.replace('(',r'\(').replace(')', r'\)') for word in FORTRAN_CONDITIONAL_REGEX_WORDS]))
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed due to warnings in the test output.

4 changes: 2 additions & 2 deletions scripts/parse_tools/parse_checkers.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@
########################################################################

_UNITLESS_REGEX = "1"
_NON_LEADING_ZERO_NUM = "[1-9]\d*"
_NON_LEADING_ZERO_NUM = r"[1-9]\d*"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed due to warnings in the test output.

_CHAR_WITH_UNDERSCORE = "([a-zA-Z]+_[a-zA-Z]+)+"
_NEGATIVE_NON_LEADING_ZERO_NUM = f"[-]{_NON_LEADING_ZERO_NUM}"
_POSITIVE_NON_LEADING_ZERO_NUM = f"[+]{_NON_LEADING_ZERO_NUM}"
_UNIT_EXPONENT = f"({_NEGATIVE_NON_LEADING_ZERO_NUM}|{_POSITIVE_NON_LEADING_ZERO_NUM}|{_NON_LEADING_ZERO_NUM})"
_UNIT_REGEX = f"[a-zA-Z]+{_UNIT_EXPONENT}?"
_UNITS_REGEX = f"^({_CHAR_WITH_UNDERSCORE}|{_UNIT_REGEX}(\s{_UNIT_REGEX})*|{_UNITLESS_REGEX})$"
_UNITS_REGEX = rf"^({_CHAR_WITH_UNDERSCORE}|{_UNIT_REGEX}(\s{_UNIT_REGEX})*|{_UNITLESS_REGEX})$"
Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed due to warnings in the test output.

_UNITS_RE = re.compile(_UNITS_REGEX)
_MAX_MOLAR_MASS = 10000.0

Expand Down
8 changes: 4 additions & 4 deletions scripts/var_props.py
Original file line number Diff line number Diff line change
Expand Up @@ -835,7 +835,7 @@ class VarCompatObj:
'scheme_files':'', \
'suites':''}, \
kind_types=["kind_phys=REAL64", \
"kind_dyn=REAL32", \
"kind_dyn=REAL32", \
"kind_host=REAL64"])
>>> VarCompatObj("var_stdname", "real", "kind_phys", "m", [], "var1_lname", False,\
"var_stdname", "real", "kind_phys", "m", [], "var2_lname", False,\
Expand Down Expand Up @@ -1172,7 +1172,7 @@ def _get_kind_convstrs(self, var1_kind, var2_kind, run_env):
'scheme_files':'', \
'suites':''}, \
kind_types=["kind_phys=REAL64", \
"kind_dyn=REAL32", \
"kind_dyn=REAL32", \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"kind_dyn=REAL32", \
"kind_dyn=REAL32", \

"kind_host=REAL64"])
>>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90')
>>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90')
Expand Down Expand Up @@ -1227,7 +1227,7 @@ def _get_unit_convstrs(self, var1_units, var2_units):
'scheme_files':'', \
'suites':''}, \
kind_types=["kind_phys=REAL64", \
"kind_dyn=REAL32", \
"kind_dyn=REAL32", \
"kind_host=REAL64"])
>>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90')
>>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90')
Expand Down Expand Up @@ -1318,7 +1318,7 @@ def _get_dim_transforms(self, var1_dims, var2_dims):
'scheme_files':'', \
'suites':''}, \
kind_types=["kind_phys=REAL64", \
"kind_dyn=REAL32", \
"kind_dyn=REAL32", \
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"kind_dyn=REAL32", \
"kind_dyn=REAL32", \

"kind_host=REAL64"])
>>> _DOCTEST_CONTEXT1 = ParseContext(linenum=3, filename='foo.F90')
>>> _DOCTEST_CONTEXT2 = ParseContext(linenum=5, filename='bar.F90')
Expand Down
Loading