Пример #1
0
def _new_var_entry(parent, var, full_entry=True):
    ###############################################################################
    """Create a variable sub-element of <parent> with information from <var>.
    If <full_entry> is False, only include standard name and intent.
    """
    prop_list = ["intent"]
    if full_entry:
        prop_list.extend([
            "allocatable", "active", "default_value", "diagnostic_name",
            "diagnostic_name_fixed", "kind", "persistence", "polymorphic",
            "protected", "state_variable", "type", "units"
        ])
        prop_list.extend(Var.constituent_property_names())
    # end if
    ventry = ET.SubElement(parent, "var")
    ventry.set("name", var.get_prop_value("standard_name"))
    for prop in prop_list:
        value = var.get_prop_value(prop)
        if value:
            ventry.set(prop, str(value))
        # end if
    # end for
    if full_entry:
        dims = var.get_dimensions()
        if dims:
            dim_entry = ET.SubElement(ventry, "dimensions")
            dim_entry.text = " ".join(dims)
Пример #2
0
 def _new_var(self, standard_name, units, dimensions, vtype, vkind=''):
     """Create and return a new Var object with the requested properties"""
     context = ParseContext(linenum=self.__linenum, filename="foo.meta")
     source = ParseSource("foo", "host", context)
     prop_dict = {'local_name' : f"foo{self.__linenum}",
                  'standard_name' : standard_name,
                  'units' : units,
                  'dimensions' : f"({', '.join(dimensions)})",
                  'type' : vtype, 'kind' : vkind}
     self.__linenum += 5
     return Var(prop_dict, source, self.__run_env)
Пример #3
0
def add_constituent_vars(cap, host_model, suite_list, logger):
###############################################################################
    """Create a DDT library containing array reference variables
    for each constituent field for all suites in <suite_list>.
    Create and return a dictionary containing an index variable for each of the
    constituents as well as the variables from the DDT object.
    Also, write declarations for these variables to <cap>.
    Since the constituents are in a DDT (ccpp_constituent_properties_t),
    create a metadata table with the required information, then parse it
    to create the dictionary.
    """
    # First create a MetadataTable for the constituents DDT
    stdname_layer = "ccpp_constituents_num_layer_consts"
    stdname_interface = "ccpp_constituents_num_interface_consts"
    stdname_2d = "ccpp_constituents_num_2d_consts"
    horiz_dim = "horizontal_dimension"
    vert_layer_dim = "vertical_layer_dimension"
    vert_interface_dim = "vertical_interface_dimension"
    array_layer = "vars_layer"
    array_interface = "vars_interface"
    array_2d = "vars_2d"
    # Table preamble (leave off ccpp-table-properties header)
    ddt_mdata = [
        #"[ccpp-table-properties]",
        " name = {}".format(CONST_DDT_NAME), " type = ddt",
        "[ccpp-arg-table]",
        " name = {}".format(CONST_DDT_NAME), " type = ddt",
        "[ num_layer_vars ]",
        " standard_name = {}".format(stdname_layer),
        " units = count", " dimensions = ()", " type = integer",
        "[ num_interface_vars ]",
        " standard_name = {}".format(stdname_interface),
        " units = count", " dimensions = ()", " type = integer",
        "[ num_2d_vars ]",
        " standard_name = {}".format(stdname_2d),
        " units = count", " dimensions = ()", " type = integer",
        "[ {} ]".format(array_layer),
        " standard_name = ccpp_constituents_array_of_layer_consts",
        " units = none",
        " dimensions = ({}, {}, {})".format(horiz_dim, vert_layer_dim,
                                            stdname_layer),
        " type = real", " kind = kind_phys",
        "[ {} ]".format(array_interface),
        " standard_name = ccpp_constituents_array_of_interface_consts",
        " units = none",
        " dimensions = ({}, {}, {})".format(horiz_dim,
                                            vert_interface_dim,
                                            stdname_interface),
        " type = real", " kind = kind_phys",
        "[ {} ]".format(array_2d),
        " standard_name = ccpp_constituents_array_of_2d_consts",
        " units = none",
        " dimensions = ({}, {})".format(horiz_dim, stdname_2d),
        " type = real", " kind = kind_phys"]
    # Add entries for each constituent (once per standard name)
    const_stdnames = set()
    for suite in suite_list:
        if logger is not None:
            lmsg = "Adding constituents from {} to {}"
            logger.debug(lmsg.format(suite.name, host_model.name))
        # end if
        scdict = suite.constituent_dictionary()
        for cvar in scdict.variable_list():
            std_name = cvar.get_prop_value('standard_name')
            if std_name not in const_stdnames:
                # Add a metadata entry for this constituent
                # Check dimensions and figure vertical dimension
                # Currently, we only support variables with first dimension,
                #   horizontal_dimension, and second (optional) dimension,
                #   vertical_layer_dimension or vertical_interface_dimension
                dims = cvar.get_dimensions()
                if (len(dims) < 1) or (len(dims) > 2):
                    emsg = "Unsupported constituent dimensions, '{}'"
                    dimstr = "({})".format(", ".join(dims))
                    raise CCPPError(emsg.format(dimstr))
                # end if
                hdim = dims[0].split(':')[-1]
                if hdim != 'horizontal_dimension':
                    emsg = "Unsupported first constituent dimension, '{}', "
                    emsg += "must be 'horizontal_dimension'"
                    raise CCPPError(emsg.format(hdim))
                # end if
                if len(dims) > 1:
                    vdim = dims[1].split(':')[-1]
                    if vdim == vert_layer_dim:
                        cvar_array_name = array_layer
                    elif vdim == vert_interface_dim:
                        cvar_array_name = array_interface
                    else:
                        emsg = "Unsupported vertical constituent dimension, "
                        emsg += "'{}', must be '{}' or '{}'"
                        raise CCPPError(emsg.format(vdim, vert_layer_dim,
                                                    vert_interface_dim))
                    # end if
                else:
                    cvar_array_name = array_2d
                # end if
                # First, create an index variable for <cvar>
                ind_std_name = "index_of_{}".format(std_name)
                loc_name = "{}(:,:,{})".format(cvar_array_name, ind_std_name)
                ddt_mdata.append("[ {} ]".format(loc_name))
                ddt_mdata.append(" standard_name = {}".format(std_name))
                units = cvar.get_prop_value('units')
                ddt_mdata.append(" units = {}".format(units))
                dimstr = "({})".format(", ".join(dims))
                ddt_mdata.append(" dimensions = {}".format(dimstr))
                vtype = cvar.get_prop_value('type')
                vkind = cvar.get_prop_value('kind')
                ddt_mdata.append(" type = {} | kind = {}".format(vtype, vkind))
                const_stdnames.add(std_name)
            # end if
        # end for
    # end for
    # Parse this table using a fake filename
    parse_obj = ParseObject("{}_constituent_mod.meta".format(host_model.name),
                            ddt_mdata)
    ddt_table = MetadataTable(parse_object=parse_obj, logger=logger)
    ddt_name = ddt_table.sections()[0].title
    ddt_lib = DDTLibrary('{}_constituent_ddtlib'.format(host_model.name),
                         ddts=ddt_table.sections(), logger=logger)
    # A bit of cleanup
    del parse_obj
    del ddt_mdata
    # Now, create the "host constituent module" dictionary
    const_dict = VarDictionary("{}_constituents".format(host_model.name),
                               parent_dict=host_model)
    # Add in the constituents object
    prop_dict = {'standard_name' : "ccpp_model_constituents_object",
                 'local_name' : constituent_model_object_name(host_model),
                 'dimensions' : '()', 'units' : "None", 'ddt_type' : ddt_name}
    const_var = Var(prop_dict, _API_SOURCE)
    const_var.write_def(cap, 1, const_dict)
    ddt_lib.collect_ddt_fields(const_dict, const_var)
    # Declare variable for the constituent standard names array
    max_csname = max([len(x) for x in const_stdnames]) if const_stdnames else 0
    num_const_fields = len(const_stdnames)
    cs_stdname = constituent_model_const_stdnames(host_model)
    const_list = sorted(const_stdnames)
    if const_list:
        const_strs = ['"{}{}"'.format(x, ' '*(max_csname - len(x)))
                      for x in const_list]
        cs_stdame_initstr = " = (/ " + ", ".join(const_strs) + " /)"
    else:
        cs_stdame_initstr = ""
    # end if
    cap.write("character(len={}) :: {}({}){}".format(max_csname, cs_stdname,
                                                     num_const_fields,
                                                     cs_stdame_initstr), 1)
    # Declare variable for the constituent standard names array
    array_name = constituent_model_const_indices(host_model)
    cap.write("integer :: {}({}) = -1".format(array_name, num_const_fields), 1)
    # Add individual variables for each index var to the const_dict
    for index, std_name in enumerate(const_list):
        ind_std_name = "index_of_{}".format(std_name)
        ind_loc_name = "{}({})".format(array_name, index + 1)
        prop_dict = {'standard_name' : ind_std_name,
                     'local_name' : ind_loc_name, 'dimensions' : '()',
                     'units' : 'index', 'protected' : "True",
                     'type' : 'integer', 'kind' : ''}
        ind_var = Var(prop_dict, _API_SOURCE)
        const_dict.add_variable(ind_var)
    # end for
    # Add vertical dimensions for DDT call strings
    pver = host_model.find_variable(standard_name=vert_layer_dim,
                                    any_scope=False)
    if pver is not None:
        prop_dict = {'standard_name' : vert_layer_dim,
                     'local_name' : pver.get_prop_value('local_name'),
                     'units' : 'count', 'type' : 'integer',
                     'protected' : 'True', 'dimensions' : '()'}
        if const_dict.find_variable(standard_name=vert_layer_dim,
                                    any_scope=False) is None:
            ind_var = Var(prop_dict, _API_SOURCE)
            const_dict.add_variable(ind_var)
        # end if
    # end if
    pver = host_model.find_variable(standard_name=vert_interface_dim,
                                    any_scope=False)
    if pver is not None:
        prop_dict = {'standard_name' : vert_interface_dim,
                     'local_name' : pver.get_prop_value('local_name'),
                     'units' : 'count', 'type' : 'integer',
                     'protected' : 'True', 'dimensions' : '()'}
        if const_dict.find_variable(standard_name=vert_interface_dim,
                                    any_scope=False) is None:
            ind_var = Var(prop_dict, _API_SOURCE)
            const_dict.add_variable(ind_var)
        # end if
    # end if

    return const_dict
Пример #4
0
_SUBHEAD = '''
   subroutine {host_model}_ccpp_physics_{stage}({api_vars})
'''

_SUBFOOT = '''
   end subroutine {host_model}_ccpp_physics_{stage}
'''

_API_SRC_NAME = "CCPP_API"

_API_SOURCE = ParseSource(_API_SRC_NAME, "MODULE",
                          ParseContext(filename="host_cap.F90"))

_SUITE_NAME_VAR = Var({'local_name':'suite_name',
                       'standard_name':'suite_name',
                       'intent':'in', 'type':'character',
                       'kind':'len=*', 'units':'', 'protected':'True',
                       'dimensions':'()'}, _API_SOURCE)

_SUITE_PART_VAR = Var({'local_name':'suite_part',
                       'standard_name':'suite_part',
                       'intent':'in', 'type':'character',
                       'kind':'len=*', 'units':'', 'protected':'True',
                       'dimensions':'()'}, _API_SOURCE)

# Used to prevent loop substitution lookups
_BLANK_DICT = VarDictionary(_API_SRC_NAME)

###############################################################################
def suite_part_list(suite, stage):
###############################################################################
Пример #5
0
def parse_fortran_var_decl(line, source, logger=None):
    ########################################################################
    """Parse a Fortran variable declaration line and return a list of
    Var objects representing the variables declared on <line>.
    >>> _VAR_ID_RE.match('foo') #doctest: +ELLIPSIS
    <re.Match object; span=(0, 3), match='foo'>
    >>> _VAR_ID_RE.match("foo()")

    >>> _VAR_ID_RE.match('foo').group(1)
    'foo'
    >>> _VAR_ID_RE.match('foo').group(2)

    >>> _VAR_ID_RE.match("foo(bar)").group(1)
    'foo'
    >>> _VAR_ID_RE.match("foo(bar)").group(2)
    '(bar)'
    >>> _VAR_ID_RE.match("foo(bar)").group(2)
    '(bar)'
    >>> _VAR_ID_RE.match("foo(bar, baz)").group(2)
    '(bar, baz)'
    >>> _VAR_ID_RE.match("foo(bar : baz)").group(2)
    '(bar : baz)'
    >>> _VAR_ID_RE.match("foo(bar:)").group(2)
    '(bar:)'
    >>> _VAR_ID_RE.match("foo(: baz)").group(2)
    '(: baz)'
    >>> _VAR_ID_RE.match("foo(:, :,:)").group(2)
    '(:, :,:)'
    >>> _VAR_ID_RE.match("foo(8)").group(2)
    '(8)'
    >>> _VAR_ID_RE.match("foo(::,a:b,a:,:b)").group(2)
    '(::,a:b,a:,:b)'
    >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('local_name')
    'foo'
    >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('local_name')
    'foo'
    >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('optional')

    >>> parse_fortran_var_decl("integer, optional :: foo", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('optional')
    'True'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('dimensions')
    '(:)'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo(bar)", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('dimensions')
    '(bar)'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('dimensions')
    '(:,:)'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'module', ParseContext()))[1].get_prop_value('dimensions')
    '(:)'
    >>> parse_fortran_var_decl("real (kind=kind_phys), pointer :: phii  (:,:) => null()   !< interface geopotential height", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('dimensions')
    '(:,:)'
    >>> parse_fortran_var_decl("real(kind=kind_phys), dimension(im, levs, ntrac), intent(in) :: qgrs", ParseSource('foo.F90', 'scheme', ParseContext()))[0].get_prop_value('dimensions')
    '(im, levs, ntrac)'
    >>> parse_fortran_var_decl("character(len=*), intent(out) :: errmsg", ParseSource('foo.F90', 'scheme', ParseContext()))[0].get_prop_value('local_name')
    'errmsg'
    >>> parse_fortran_var_decl("character(len=512), intent(out) :: errmsg", ParseSource('foo.F90', 'scheme', ParseContext()))[0].get_prop_value('kind')
    'len=512'
    >>> parse_fortran_var_decl("real(kind_phys), intent(out) :: foo(8)", ParseSource('foo.F90', 'scheme', ParseContext()))[0].get_prop_value('dimensions')
    '(8)'
    >>> parse_fortran_var_decl("real(kind_phys), intent(out) :: foo(size(bar))", ParseSource('foo.F90', 'scheme', ParseContext()))[0].get_prop_value('dimensions')
    '(size(bar))'
    >>> parse_fortran_var_decl("real(kind_phys), intent(out) :: foo(8)", ParseSource('foo.F90', 'scheme', ParseContext()))[0].get_dimensions()
    ['8']
    >>> parse_fortran_var_decl("character(len=*), intent(out) :: errmsg", ParseSource('foo.F90', 'module', ParseContext()))[0].get_prop_value('local_name') #doctest: +IGNORE_EXCEPTION_DETAIL
    Traceback (most recent call last):
    ParseSyntaxError: Invalid variable declaration, character(len=*), intent(out) :: errmsg, intent not allowed in module variable, in <standard input>
    """
    context = source.context
    sline = line.strip()
    # Strip comments first
    if '!' in sline:
        sline = sline[0:sline.index('!')].rstrip()
    # end if
    tobject = ftype_factory(sline, context)
    newvars = list()
    if tobject is not None:
        varprops = sline[tobject.type_len:].strip()
        def_dims = None  # Default dimensions
        intent = None
        dimensions = None
        if '::' in varprops:
            elements = varprops.split('::')
            varlist = elements[1].strip()
            varprops = Ftype.parse_attr_specs(elements[0].strip(), context)
            for prop in varprops:
                if prop[0:6] == 'intent':
                    if source.type != 'scheme':
                        typ = source.type
                        errmsg = 'Invalid variable declaration, {}, intent'
                        errmsg = errmsg + ' not allowed in {} variable'
                        if logger is not None:
                            ctx = context_string(context)
                            errmsg = "WARNING: " + errmsg + "{}"
                            logger.warning(errmsg.format(sline, typ, ctx))
                        else:
                            raise ParseSyntaxError(errmsg.format(sline, typ),
                                                   context=context)
                        # end if
                    else:
                        intent = prop[6:].strip()[1:-1].strip()
                    # end if
                elif prop[0:9:] == 'dimension':
                    dimensions = prop[9:].strip()
                # end if
            # end for
        else:
            # No attr_specs
            varlist = varprops
            varprops = list()
        # end if
        # Create Vars from these pieces
        # We may need to reassemble multi-dimensional specs
        var_list = Ftype.reassemble_parens(varlist, 'variable_list', context)
        for var in var_list:
            prop_dict = {}
            if '=' in var:
                # We do not care about initializers
                var = var[0:var.rindex('=')].rstrip()
            # end if
            # Scan <var> and gather variable pieces
            inchar = None  # Character context
            var_len = len(var)
            ploc = var.find('(')
            if ploc < 0:
                varname = var.strip()
                dimspec = None
            else:
                varname = var[0:ploc].strip()
                begin, end = check_balanced_paren(var)
                if (begin < 0) or (end < 0):
                    if logger is not None:
                        ctx = context_string(context)
                        errmsg = "WARNING: Invalid variable declaration, {}{}"
                        logger.warning(errmsg.format(var, ctx))
                    else:
                        raise ParseSyntaxError('variable declaration',
                                               token=var,
                                               context=context)
                    # end if
                else:
                    dimspec = var[begin:end + 1]
                # end if
            # end if
            prop_dict['local_name'] = varname
            prop_dict['standard_name'] = unique_standard_name()
            prop_dict['units'] = ''
            if isinstance(tobject, FtypeTypeDecl):
                prop_dict['ddt_type'] = tobject.typestr
            else:
                prop_dict['type'] = tobject.typestr
            # end if
            if tobject.kind() is not None:
                prop_dict['kind'] = tobject.kind()
            # end if
            if 'optional' in varprops:
                prop_dict['optional'] = 'True'
            # end if
            if 'allocatable' in varprops:
                prop_dict['allocatable'] = 'True'
            # end if
            if intent is not None:
                prop_dict['intent'] = intent
            # end if
            if dimspec is not None:
                prop_dict['dimensions'] = dimspec
            elif dimensions is not None:
                prop_dict['dimensions'] = dimensions
            else:
                prop_dict['dimensions'] = '()'
            # end if
            # XXgoldyXX: I am nervous about allowing invalid Var objects here
            # Also, this tends to cause an exception that ends up back here
            # which is not a good idea.
            var = Var(prop_dict,
                      source,
                      invalid_ok=(logger is not None),
                      logger=logger)
            newvars.append(var)
        # end for
    # No else (not a variable declaration)
    # end if
    return newvars
Пример #6
0
 def parse_variable(self, curr_line):
     # The header line has the format [ <valid_fortran_symbol> ]
     # Parse header
     valid_line = (curr_line is not None) and (
         not MetadataHeader.table_start(curr_line))
     if valid_line:
         local_name = self.variable_start(
             curr_line)  # caller handles exception
     else:
         local_name = None
     # End if
     if local_name is None:
         # This is not a valid variable line, punt (should be end of table)
         valid_line = False
     # End if
     # Parse lines until invalid line is found
     # NB: Header variables cannot have embedded blank lines
     if valid_line:
         var_props = {}
         var_props['local_name'] = local_name
     else:
         var_props = None
     # End if
     while valid_line:
         curr_line, curr_line_num = self._pobj.next_line()
         valid_line = ((curr_line is not None)
                       and (not MetadataHeader.is_blank(curr_line))
                       and (not MetadataHeader.table_start(curr_line))
                       and (self.variable_start(curr_line) is None))
         # A valid line may have multiple properties (separated by '|')
         if valid_line:
             properties = self.parse_config_line(curr_line)
             for property in properties:
                 try:
                     pname = property[0].strip()
                     pval_str = property[1].strip()
                     # Make sure this is a match
                     hp = Var.get_prop(pname)
                     if hp is not None:
                         pval = hp.valid_value(pval_str)
                     else:
                         raise ParseSyntaxError("variable property name",
                                                token=pname,
                                                context=self._pobj)
                     # End if
                     if pval is None:
                         raise ParseSyntaxError(
                             "'{}' property value".format(pname),
                             token=pval_str,
                             context=self._pobj)
                     # End if
                 except ParseSyntaxError as p:
                     raise p
                 # If we get this far, we have a valid property.
                 var_props[pname] = pval
             # End for
         # End if
     # End while
     if var_props is None:
         return None, curr_line
     else:
         try:
             newvar = Var(var_props, source=self)
         except CCPPError as ve:
             raise ParseSyntaxError(ve, context=self._pobj)
         return newvar, curr_line
Пример #7
0
def parse_fortran_var_decl(line, source, logger=None):
    ########################################################################
    """Parse a Fortran variable declaration line and return a list of
    Var objects representing the variables declared on <line>.
    >>> _var_id_re_.match('foo') #doctest: +ELLIPSIS
    <_sre.SRE_Match object at 0x...>
    >>> _var_id_re_.match("foo()")

    >>> _var_id_re_.match('foo').group(1)
    'foo'
    >>> _var_id_re_.match('foo').group(2)

    >>> _var_id_re_.match("foo(bar)").group(1)
    'foo'
    >>> _var_id_re_.match("foo(bar)").group(2)
    '(bar)'
    >>> _var_id_re_.match("foo(bar)").group(2)
    '(bar)'
    >>> _var_id_re_.match("foo(bar, baz)").group(2)
    '(bar, baz)'
    >>> _var_id_re_.match("foo(bar : baz)").group(2)
    '(bar : baz)'
    >>> _var_id_re_.match("foo(bar:)").group(2)
    '(bar:)'
    >>> _var_id_re_.match("foo(: baz)").group(2)
    '(: baz)'
    >>> _var_id_re_.match("foo(:, :,:)").group(2)
    '(:, :,:)'
    >>> _var_id_re_.match("foo(8)").group(2)
    '(8)'
    >>> _var_id_re_.match("foo(::,a:b,a:,:b)").group(2)
    '(::,a:b,a:,:b)'
    >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('local_name')
    'foo'
    >>> parse_fortran_var_decl("integer :: foo = 0", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('local_name')
    'foo'
    >>> parse_fortran_var_decl("integer :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('optional')
    False
    >>> parse_fortran_var_decl("integer, optional :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('optional')
    'True'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions')
    '(:)'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo(bar)", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions')
    '(bar)'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions')
    '(:,:)'
    >>> parse_fortran_var_decl("integer, dimension(:) :: foo(:,:), baz", ParseSource('foo.F90', 'MODULE', ParseContext()))[1].get_prop_value('dimensions')
    '(:)'
    >>> parse_fortran_var_decl("real (kind=kind_phys), pointer :: phii  (:,:) => null()   !< interface geopotential height", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions')
    '(:,:)'
    >>> parse_fortran_var_decl("real(kind=kind_phys), dimension(im, levs, ntrac), intent(in) :: qgrs", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('dimensions')
    '(im, levs, ntrac)'
    >>> parse_fortran_var_decl("character(len=*), intent(out) :: errmsg", ParseSource('foo.F90', 'MODULE', ParseContext()))[0].get_prop_value('local_name')
    'errmsg'
    """
    context = source.context
    sline = line.strip()
    # Strip comments first
    if '!' in sline:
        sline = sline[0:sline.index('!')].rstrip()
    # End if
    tobject = Ftype_factory(sline, context)
    newvars = list()
    if tobject is not None:
        varprops = sline[tobject.type_len:].strip()
        def_dims = None  # Default dimensions
        intent = None
        dimensions = None
        if '::' in varprops:
            elements = varprops.split('::')
            varlist = elements[1].strip()
            varprops = Ftype.parse_attr_specs(elements[0].strip(), context)
            for prop in varprops:
                if prop[0:6] == 'intent':
                    intent = prop[6:].strip()[1:-1].strip()
                elif prop[0:9:] == 'dimension':
                    dimensions = prop[9:].strip()
                # End if
            # End for
        else:
            # No attr_specs
            varlist = varprops
            varprops = list()
        # End if
        # Create Vars from these pieces
        # We may need to reassemble multi-dimensional specs
        vars = Ftype.reassemble_parens(varlist, 'variable_list', context)
        for var in vars:
            prop_dict = {}
            if '=' in var:
                # We do not care about initializers
                var = var[0:var.rindex('=')].rstrip()
            # End if
            # Scan <var> and gather variable pieces
            inchar = None  # Character context
            var_len = len(var)
            ploc = var.find('(')
            if ploc < 0:
                varname = var.strip()
                dimspec = None
            else:
                varname = var[0:ploc].strip()
                begin, end = check_balanced_paren(var)
                if (begin < 0) or (end < 0):
                    if logger is not None:
                        ctx = context_string(context)
                        logger.warning(
                            "WARNING: Invalid variable declaration, {}{}".
                            format(var, ctx))
                    else:
                        raise ParseSyntaxError('variable declaration',
                                               token=var,
                                               context=context)
                    # End if
                else:
                    dimspec = var[begin:end + 1]
                # End if
            # End if
            prop_dict['local_name'] = varname
            prop_dict['standard_name'] = Ftype.unique_standard_name()
            prop_dict['units'] = ''
            prop_dict['type'] = tobject.typestr
            if tobject.kind is not None:
                prop_dict['kind'] = tobject.kind
            # End if
            if 'optional' in varprops:
                prop_dict['optional'] = 'True'
            # End if
            if 'allocatable' in varprops:
                prop_dict['allocatable'] = 'True'
            # End if
            if intent is not None:
                prop_dict['intent'] = intent
            # End if
            if dimspec is not None:
                prop_dict['dimensions'] = dimspec
            elif dimensions is not None:
                prop_dict['dimensions'] = dimensions
            else:
                prop_dict['dimensions'] = '()'
            # End if
            # XXgoldyXX: I am nervous about allowing invalid Var objects here
            newvars.append(
                Var(prop_dict,
                    source,
                    invalid_ok=(logger is not None),
                    logger=logger))
        # End for
    # No else (not a variable declaration)
    # End if
    return newvars