Esempio n. 1
0
def write(nml, nml_path, force=False):
    """Save a namelist to disk using either a file object or its file path.

    File object usage:

    >>> with open(nml_path, 'w') as nml_file:
    >>>     f90nml.write(nml, nml_file)

    File path usage:

    >>> f90nml.write(nml, 'data.nml')

    This function is equivalent to the ``write`` function of the ``Namelist``
    object ``nml``.

    >>> nml.write('data.nml')

    By default, ``write`` will not overwrite an existing file.  To override
    this, use the ``force`` flag.

    >>> nml.write('data.nml', force=True)
    """
    # Promote dicts to Namelists
    if not isinstance(nml, Namelist) and isinstance(nml, dict):
        nml_in = Namelist(nml)
    else:
        nml_in = nml

    nml_in.write(nml_path, force=force)
Esempio n. 2
0
    def test_f90repr(self):
        nml = Namelist()
        self.assertEqual(nml._f90repr(1), '1')
        self.assertEqual(nml._f90repr(1.), '1.0')
        self.assertEqual(nml._f90repr(1+2j), '(1.0, 2.0)')
        self.assertEqual(nml._f90repr(True), '.true.')
        self.assertEqual(nml._f90repr(False), '.false.')
        self.assertEqual(nml._f90repr('abc'), "'abc'")

        for ptype in ({}, [], set()):
            self.assertRaises(ValueError, nml._f90repr, ptype)
Esempio n. 3
0
    def parse_variable(self, parent, patch_nml=None):
        """Parse a variable and return its name and values."""

        if not patch_nml:
            patch_nml = Namelist()

        v_name = self.prior_token
        v_values = []

        # Patch state
        patch_values = None

        if self.token == '(':

            v_idx_bounds = self.parse_indices()
            v_idx = FIndex(v_idx_bounds)

            # Update starting index against namelist record
            if v_name in parent.first_index:
                p_idx = parent.first_index[v_name]

                v_idx.first = [min(p_i, v_i)
                               for p_i, v_i in zip(p_idx, v_idx.first)]

                # Resize vector based on starting index
                for i_p, i_v in zip(p_idx, v_idx.first):
                    if i_v < i_p:
                        pad = [None for _ in range(i_p - i_v)]
                        parent[v_name] = pad + parent[v_name]

            parent.first_index[v_name] = v_idx.first

            self.update_tokens()
        else:
            v_idx = None

        if self.token == '%':

            # Resolve the derived type

            if parent and v_name in parent:
                v_parent = parent[v_name]
            else:
                v_parent = Namelist()

            self.update_tokens()
            self.update_tokens()

            v_att, v_att_vals = self.parse_variable(v_parent)

            next_value = Namelist()
            next_value[v_att] = v_att_vals
            self.append_value(v_values, next_value, v_idx)

        else:
            # Construct the variable array

            assert self.token == '='
            n_vals = None
            prior_ws_sep = ws_sep = False

            self.update_tokens()

            # Check if value is in the namelist patch
            # TODO: Edit `Namelist` to support case-insensitive `pop` calls
            #       (Currently only a problem in PyPy2)
            if v_name in patch_nml:
                patch_values = patch_nml.pop(v_name.lower())

                if not isinstance(patch_values, list):
                    patch_values = [patch_values]

                p_idx = 0

            # Add variables until next variable trigger
            while (self.token not in ('=', '(', '%') or
                   (self.prior_token, self.token) == ('=', '(')):

                # Check for repeated values
                if self.token == '*':
                    n_vals = self.parse_value()
                    assert isinstance(n_vals, int)
                    self.update_tokens()
                elif not n_vals:
                    n_vals = 1

                # First check for implicit null values
                if self.prior_token in ('=', '%', ','):
                    if (self.token in (',', '/', '&', '$') and
                            not (self.prior_token == ',' and
                                 self.token in ('/', '&', '$'))):
                        self.append_value(v_values, None, v_idx, n_vals)

                elif self.prior_token == '*':

                    if self.token not in ('/', '&', '$'):
                        self.update_tokens()

                    if (self.token == '=' or (self.token in ('/', '&', '$') and
                                              self.prior_token == '*')):
                        next_value = None
                    else:
                        next_value = self.parse_value()

                    self.append_value(v_values, next_value, v_idx, n_vals)

                else:
                    next_value = self.parse_value()

                    # Check for escaped strings
                    if (v_values and isinstance(v_values[-1], str) and
                            isinstance(next_value, str) and not prior_ws_sep):

                        quote_char = self.prior_token[0]
                        v_values[-1] = quote_char.join([v_values[-1],
                                                        next_value])
                    else:
                        self.append_value(v_values, next_value, v_idx, n_vals)

                # Exit for end of nml group (/, &, $) or null broadcast (=)
                if self.token in ('/', '&', '$', '='):
                    break
                else:
                    prior_ws_sep = ws_sep
                    if (patch_values and p_idx < len(patch_values) and
                            len(patch_values) > 0 and self.token != ','):
                        p_val = patch_values[p_idx]
                        p_repr = patch_nml.f90repr(patch_values[p_idx])
                        p_idx += 1
                        ws_sep = self.update_tokens(override=p_repr)
                        if isinstance(p_val, complex):
                            # Skip over the complex content
                            # NOTE: Assumes input and patch are complex
                            self.update_tokens(write_token=False)
                            self.update_tokens(write_token=False)
                            self.update_tokens(write_token=False)
                            self.update_tokens(write_token=False)
                    else:
                        ws_sep = self.update_tokens()

        if patch_values:
            v_values = patch_values

        if not v_idx:
            v_values = delist(v_values)

        return v_name, v_values
Esempio n. 4
0
    def readstream(self, nml_file, nml_patch):
        """Parse an input stream containing a Fortran namelist."""

        f90lex = shlex.shlex(nml_file)
        f90lex.whitespace = ''
        f90lex.wordchars += '.-+'       # Include floating point tokens
        if nml_patch:
            f90lex.commenters = ''
        else:
            f90lex.commenters = self.comment_tokens

        self.tokens = iter(f90lex)

        nmls = Namelist()

        # TODO: Replace "while True" with an update_token() iterator
        self.update_tokens(write_token=False)
        while True:
            try:
                # Check for classic group terminator
                if self.token == 'end':
                    self.update_tokens()

                # Ignore tokens outside of namelist groups
                while self.token not in ('&', '$'):
                    self.update_tokens()

            except StopIteration:
                break

            # Create the next namelist
            self.update_tokens()
            g_name = self.token

            g_vars = Namelist()
            v_name = None

            # TODO: Edit `Namelist` to support case-insensitive `get` calls
            grp_patch = nml_patch.get(g_name.lower(), {})

            # Populate the namelist group
            while g_name:

                if self.token not in ('=', '%', '('):
                    self.update_tokens()

                # Set the next active variable
                if self.token in ('=', '(', '%'):

                    v_name, v_values = self.parse_variable(g_vars,
                                                           patch_nml=grp_patch)

                    if v_name in g_vars:
                        v_prior_values = g_vars[v_name]
                        v_values = merge_values(v_prior_values, v_values)

                    if v_name in g_vars and isinstance(g_vars[v_name], dict):
                        g_vars[v_name].update(v_values)
                    else:
                        g_vars[v_name] = v_values

                    # Deselect variable
                    v_name = None
                    v_values = []

                # Finalise namelist group
                if self.token in ('/', '&', '$'):

                    # Append any remaining patched variables
                    for v_name, v_val in grp_patch.items():
                        g_vars[v_name] = v_val
                        v_strs = nmls.var_strings(v_name, v_val)
                        for v_str in v_strs:
                            self.pfile.write('    {0}\n'.format(v_str))

                    # Append the grouplist to the namelist
                    if g_name in nmls:
                        g_update = nmls[g_name]

                        # Update to list of groups
                        if not isinstance(g_update, list):
                            g_update = [g_update]

                        g_update.append(g_vars)

                    else:
                        g_update = g_vars

                    nmls[g_name] = g_update

                    # Reset state
                    g_name, g_vars = None, None

            try:
                self.update_tokens()
            except StopIteration:
                break

        return nmls
Esempio n. 5
0
    def _parse_variable(self, parent, patch_nml=None):
        """Parse a variable and return its name and values."""
        if not patch_nml:
            patch_nml = Namelist()

        v_name = self.prior_token
        v_values = []

        # Patch state
        patch_values = None

        # Derived type parent index (see notes below)
        dt_idx = None

        if self.token == '(':

            v_idx_bounds = self._parse_indices()
            v_idx = FIndex(v_idx_bounds, self.global_start_index)

            # Update starting index against namelist record
            if v_name.lower() in parent.start_index:
                p_idx = parent.start_index[v_name.lower()]

                for idx, pv in enumerate(zip(p_idx, v_idx.first)):
                    if all(i is None for i in pv):
                        i_first = None
                    else:
                        i_first = min(i for i in pv if i is not None)

                    v_idx.first[idx] = i_first

                # Resize vector based on starting index
                parent[v_name] = prepad_array(parent[v_name], p_idx,
                                              v_idx.first)
            else:
                # If variable already existed without an index, then assume a
                #   1-based index
                # FIXME: Need to respect undefined `None` starting indexes?
                if v_name in parent:
                    v_idx.first = [self.default_start_index
                                   for _ in v_idx.first]

            parent.start_index[v_name.lower()] = v_idx.first

            self._update_tokens()

            # Derived type parent check
            # NOTE: This assumes single-dimension derived type vectors
            #       (which I think is the only case supported in Fortran)
            if self.token == '%':
                assert v_idx_bounds[0][1] - v_idx_bounds[0][0] == 1
                dt_idx = v_idx_bounds[0][0] - v_idx.first[0]

                # NOTE: This is the sensible play to call `parse_variable`
                # but not yet sure how to implement it, so we currently pass
                # along `dt_idx` to the `%` handler.

        else:
            v_idx = None

            # If indexed variable already exists, then re-index this new
            #   non-indexed variable using the global start index

            if v_name in parent.start_index:
                p_start = parent.start_index[v_name.lower()]
                v_start = [self.default_start_index for _ in p_start]

                # Resize vector based on new starting index
                for i_p, i_v in zip(p_start, v_start):
                    if i_v < i_p:
                        pad = [None for _ in range(i_p - i_v)]
                        parent[v_name] = pad + parent[v_name]

                parent.start_index[v_name.lower()] = v_start

        if self.token == '%':

            # Resolve the derived type

            # Check for value in patch
            v_patch_nml = None
            if v_name in patch_nml:
                v_patch_nml = patch_nml.pop(v_name.lower())

            if parent:
                vpar = parent.get(v_name.lower())
                if vpar and isinstance(vpar, list):
                    # If new element is not a list, then assume it's the first
                    # element of the list.
                    if dt_idx is None:
                        dt_idx = self.default_start_index

                    try:
                        v_parent = vpar[dt_idx]
                    except IndexError:
                        v_parent = Namelist()
                elif vpar:
                    v_parent = vpar
                else:
                    v_parent = Namelist()
            else:
                v_parent = Namelist()
                parent[v_name] = v_parent

            self._update_tokens()
            self._update_tokens()

            v_att, v_att_vals = self._parse_variable(
                v_parent,
                patch_nml=v_patch_nml
            )

            next_value = Namelist()
            next_value[v_att] = v_att_vals
            self._append_value(v_values, next_value, v_idx)

        else:
            # Construct the variable array

            assert self.token == '='
            n_vals = None

            self._update_tokens()

            # Check if value is in the namelist patch
            # TODO: Edit `Namelist` to support case-insensitive `pop` calls
            #       (Currently only a problem in PyPy2)
            if v_name in patch_nml:
                patch_values = patch_nml.pop(v_name.lower())

                if not isinstance(patch_values, list):
                    patch_values = [patch_values]

                p_idx = 0

            # Add variables until next variable trigger
            while (self.token not in ('=', '(', '%') or
                   (self.prior_token, self.token) in (('=', '('), (',', '('))):

                # Check for repeated values
                if self.token == '*':
                    n_vals = self._parse_value()
                    assert isinstance(n_vals, int)
                    self._update_tokens()
                elif not n_vals:
                    n_vals = 1

                # First check for implicit null values
                if self.prior_token in ('=', '%', ','):
                    if (self.token in (',', '/', '&', '$') and
                            not (self.prior_token == ',' and
                                 self.token in ('/', '&', '$'))):
                        self._append_value(v_values, None, v_idx, n_vals)

                elif self.prior_token == '*':

                    if self.token not in ('/', '&', '$'):
                        self._update_tokens()

                    if (self.token == '=' or (self.token in ('/', '&', '$') and
                                              self.prior_token == '*')):
                        next_value = None
                    else:
                        next_value = self._parse_value()

                    self._append_value(v_values, next_value, v_idx, n_vals)

                else:
                    next_value = self._parse_value()
                    self._append_value(v_values, next_value, v_idx, n_vals)

                # Reset default repeat factor for subsequent values
                n_vals = 1

                # Exit for end of nml group (/, &, $) or null broadcast (=)
                if self.token in ('/', '&', '$', '='):
                    break
                else:
                    # Get the remaining length of the unpatched vector?

                    # NOTE: it is probably very inefficient to keep re-creating
                    # iterators upon every element; this solution reflects the
                    # absence of mature lookahead in the script.
                    #
                    # This is a temporary fix to address errors caused by
                    # patches of different length from the original value, and
                    # represents a direction to fully rewrite the parser using
                    # `tee`.

                    self.tokens, lookahead = itertools.tee(self.tokens)
                    n_vals_remain = count_values(lookahead)

                    if patch_values:
                        # XXX: The (p_idx - 1) <= n_vals_remain test is dodgy
                        # and does not really make sense to me, but it appears
                        # to work.

                        # TODO: Patch indices that are not set in the namelist

                        if (p_idx < len(patch_values) and
                                (p_idx - 1) <= n_vals_remain and
                                len(patch_values) > 0 and self.token != ','):
                            p_val = patch_values[p_idx]
                            p_repr = patch_nml._f90repr(patch_values[p_idx])
                            p_idx += 1
                            self._update_tokens(override=p_repr)
                            if isinstance(p_val, complex):
                                # Skip over the complex content
                                # NOTE: Assumes input and patch are complex
                                self._update_tokens(write_token=False)
                                self._update_tokens(write_token=False)
                                self._update_tokens(write_token=False)
                                self._update_tokens(write_token=False)
                        else:
                            # Skip any values beyond the patch size
                            skip = (p_idx >= len(patch_values))
                            self._update_tokens(patch_skip=skip)
                    else:
                        self._update_tokens()

        if patch_values:
            v_values = patch_values

        if not v_idx:
            v_values = delist(v_values)

        return v_name, v_values
Esempio n. 6
0
    def _readstream(self, nml_file, nml_patch_in=None):
        """Parse an input stream containing a Fortran namelist."""
        nml_patch = nml_patch_in if nml_patch_in is not None else Namelist()

        tokenizer = Tokenizer()
        f90lex = []
        for line in nml_file:
            toks = tokenizer.parse(line)
            while tokenizer.prior_delim:
                new_toks = tokenizer.parse(next(nml_file))

                # Skip empty lines
                if not new_toks:
                    continue

                # The tokenizer always pre-tokenizes the whitespace (leftover
                # behaviour from Fortran source parsing) so this must be added
                # manually.
                if new_toks[0].isspace():
                    toks[-1] += new_toks.pop(0)

                # Append the rest of the string (if present)
                if new_toks:
                    toks[-1] += new_toks[0]

                    # Attach the rest of the tokens
                    toks.extend(new_toks[1:])

            toks.append('\n')
            f90lex.extend(toks)

        self.tokens = iter(f90lex)

        nmls = Namelist()

        # Attempt to get first token; abort on empty file
        try:
            self._update_tokens(write_token=False)
        except StopIteration:
            return nmls

        # TODO: Replace "while True" with an update_token() iterator
        while True:
            try:
                # Check for classic group terminator
                if self.token == 'end':
                    self._update_tokens()

                # Ignore tokens outside of namelist groups
                while self.token not in ('&', '$'):
                    self._update_tokens()

            except StopIteration:
                break

            # Create the next namelist
            self._update_tokens()
            g_name = self.token

            g_vars = Namelist()
            v_name = None

            # TODO: Edit `Namelist` to support case-insensitive `get` calls
            grp_patch = nml_patch.get(g_name.lower(), Namelist())

            # Populate the namelist group
            while g_name:

                if self.token not in ('=', '%', '('):
                    self._update_tokens()

                # Set the next active variable
                if self.token in ('=', '(', '%'):

                    v_name, v_values = self._parse_variable(
                        g_vars,
                        patch_nml=grp_patch
                    )

                    if v_name in g_vars:
                        v_prior_values = g_vars[v_name]
                        v_values = merge_values(v_prior_values, v_values)

                    g_vars[v_name] = v_values

                    # Deselect variable
                    v_name = None
                    v_values = []

                # Finalise namelist group
                if self.token in ('/', '&', '$'):

                    # Append any remaining patched variables
                    for v_name, v_val in grp_patch.items():
                        g_vars[v_name] = v_val
                        v_strs = nmls._var_strings(v_name, v_val)
                        for v_str in v_strs:
                            self.pfile.write(
                                '{0}{1}\n'.format(nml_patch.indent, v_str)
                            )

                    # Append the grouplist to the namelist
                    if g_name in nmls:
                        g_update = nmls[g_name]

                        # Update to list of groups
                        if not isinstance(g_update, list):
                            g_update = [g_update]

                        g_update.append(g_vars)

                    else:
                        g_update = g_vars

                    nmls[g_name] = g_update

                    # Reset state
                    g_name, g_vars = None, None

            try:
                self._update_tokens()
            except StopIteration:
                break

        return nmls
Esempio n. 7
0
    def parse_variable(self, parent, patch_nml=None):
        """Parse a variable and return its name and values."""

        if not patch_nml:
            patch_nml = Namelist()

        v_name = self.prior_token
        v_values = []

        # Patch state
        patch_values = None

        if self.token == '(':

            v_idx_bounds = self.parse_indices()
            v_idx = FIndex(v_idx_bounds, self.global_start_index)

            # Update starting index against namelist record
            if v_name in parent.start_index:
                p_idx = parent.start_index[v_name]

                for idx, pv in enumerate(zip(p_idx, v_idx.first)):
                    if all(i is None for i in pv):
                        i_first = None
                    else:
                        i_first = min(i for i in pv if i is not None)

                    v_idx.first[idx] = i_first

                # Resize vector based on starting index
                for i_p, i_v in zip(p_idx, v_idx.first):
                    if i_p is not None and i_v is not None and i_v < i_p:
                        pad = [None for _ in range(i_p - i_v)]
                        parent[v_name] = pad + parent[v_name]

            else:
                # If variable already existed without an index, then assume a
                #   1-based index
                # FIXME: Need to respect undefined `None` starting indexes?
                if v_name in parent:
                    v_idx.first = [self.default_start_index
                                   for _ in v_idx.first]

            parent.start_index[v_name] = v_idx.first

            self.update_tokens()
        else:
            v_idx = None

            # If indexed variable already exists, then re-index this new
            #   non-indexed variable using the global start index

            # FIXME: The `hasattr` check is a hack to deal with the "list of
            # parents" bug metioned in the `%` parsing block below.  The real
            # solution here is to prevent the list of parents being set as
            # parent.

            if hasattr(parent, 'start_index') and v_name in parent.start_index:
                p_start = parent.start_index[v_name]
                v_start = [self.default_start_index for _ in p_start]

                # Resize vector based on new starting index
                for i_p, i_v in zip(p_start, v_start):
                    if i_v < i_p:
                        pad = [None for _ in range(i_p - i_v)]
                        parent[v_name] = pad + parent[v_name]

                parent.start_index[v_name] = v_start

        if self.token == '%':

            # Resolve the derived type

            # FIXME: If we were in an index (v_idx != None) then v_parent is
            # incorrectly set as the list of dicts rather than the respective
            # dict (which must somehow be deduced from v_idx at some later
            # stage)

            # This is not causing any errors, but the parent value is nonsense
            # and could break something in the future.

            # Check for value in patch
            v_patch_nml = None
            if v_name in patch_nml:
                v_patch_nml = patch_nml.pop(v_name.lower())

            if parent and v_name in parent:
                v_parent = parent[v_name]
            else:
                v_parent = Namelist()
                parent[v_name] = v_parent

            self.update_tokens()
            self.update_tokens()

            v_att, v_att_vals = self.parse_variable(v_parent,
                                                    patch_nml=v_patch_nml)

            next_value = Namelist()
            next_value[v_att] = v_att_vals
            self.append_value(v_values, next_value, v_idx)

        else:
            # Construct the variable array

            assert self.token == '='
            n_vals = None
            prior_ws_sep = ws_sep = False

            self.update_tokens()

            # Check if value is in the namelist patch
            # TODO: Edit `Namelist` to support case-insensitive `pop` calls
            #       (Currently only a problem in PyPy2)
            if v_name in patch_nml:
                patch_values = patch_nml.pop(v_name.lower())

                if not isinstance(patch_values, list):
                    patch_values = [patch_values]

                p_idx = 0

            # Add variables until next variable trigger
            while (self.token not in ('=', '(', '%') or
                   (self.prior_token, self.token) == ('=', '(')):

                # Check for repeated values
                if self.token == '*':
                    n_vals = self.parse_value()
                    assert isinstance(n_vals, int)
                    self.update_tokens()
                elif not n_vals:
                    n_vals = 1

                # First check for implicit null values
                if self.prior_token in ('=', '%', ','):
                    if (self.token in (',', '/', '&', '$') and
                            not (self.prior_token == ',' and
                                 self.token in ('/', '&', '$'))):
                        self.append_value(v_values, None, v_idx, n_vals)

                elif self.prior_token == '*':

                    if self.token not in ('/', '&', '$'):
                        self.update_tokens()

                    if (self.token == '=' or (self.token in ('/', '&', '$') and
                                              self.prior_token == '*')):
                        next_value = None
                    else:
                        next_value = self.parse_value()

                    self.append_value(v_values, next_value, v_idx, n_vals)

                else:
                    next_value = self.parse_value()

                    # Check for escaped strings
                    if (v_values and isinstance(v_values[-1], str) and
                            isinstance(next_value, str) and not prior_ws_sep):

                        quote_char = self.prior_token[0]
                        v_values[-1] = quote_char.join([v_values[-1],
                                                        next_value])
                    else:
                        self.append_value(v_values, next_value, v_idx, n_vals)

                # Exit for end of nml group (/, &, $) or null broadcast (=)
                if self.token in ('/', '&', '$', '='):
                    break
                else:
                    prior_ws_sep = ws_sep
                    if patch_values:
                        if (p_idx < len(patch_values) and
                                len(patch_values) > 0 and self.token != ','):
                            p_val = patch_values[p_idx]
                            p_repr = patch_nml.f90repr(patch_values[p_idx])
                            p_idx += 1
                            ws_sep = self.update_tokens(override=p_repr)
                            if isinstance(p_val, complex):
                                # Skip over the complex content
                                # NOTE: Assumes input and patch are complex
                                self.update_tokens(write_token=False)
                                self.update_tokens(write_token=False)
                                self.update_tokens(write_token=False)
                                self.update_tokens(write_token=False)

                        else:
                            # Skip any values beyond the patch size
                            skip = (p_idx >= len(patch_values))
                            self.update_tokens(patch_skip=skip)
                    else:
                        ws_sep = self.update_tokens()

        if patch_values:
            v_values = patch_values

        if not v_idx:
            v_values = delist(v_values)

        return v_name, v_values
Esempio n. 8
0
 def test_groups(self):
     d = {'a': {'b': 1.}}
     nml = Namelist(d)
     key, value = next(nml.groups())
     self.assertEqual(key, ('a', 'b'))
     self.assertEqual(value, 1.)
Esempio n. 9
0
    def parse_variable(self, parent, patch_nml=None):
        """Parse a variable and return its name and values."""

        if not patch_nml:
            patch_nml = Namelist()

        v_name = self.prior_token
        v_values = []

        # Patch state
        patch_values = None

        # Derived type parent index (see notes below)
        dt_idx = None

        if self.token == '(':

            v_idx_bounds = self.parse_indices()
            v_idx = FIndex(v_idx_bounds, self.global_start_index)

            # Update starting index against namelist record
            if v_name.lower() in parent.start_index:
                p_idx = parent.start_index[v_name.lower()]

                for idx, pv in enumerate(zip(p_idx, v_idx.first)):
                    if all(i is None for i in pv):
                        i_first = None
                    else:
                        i_first = min(i for i in pv if i is not None)

                    v_idx.first[idx] = i_first

                # Resize vector based on starting index
                for i_p, i_v in zip(p_idx, v_idx.first):
                    if i_p is not None and i_v is not None and i_v < i_p:
                        pad = [None for _ in range(i_p - i_v)]
                        parent[v_name] = pad + parent[v_name]

            else:
                # If variable already existed without an index, then assume a
                #   1-based index
                # FIXME: Need to respect undefined `None` starting indexes?
                if v_name in parent:
                    v_idx.first = [self.default_start_index
                                   for _ in v_idx.first]

            parent.start_index[v_name.lower()] = v_idx.first

            self.update_tokens()

            # Derived type parent check
            # NOTE: This assumes single-dimension derived type vectors
            #       (which I think is the only case supported in Fortran)
            if self.token == '%':
                assert(v_idx_bounds[0][1] - v_idx_bounds[0][0] == 1)
                dt_idx = v_idx_bounds[0][0] - v_idx.first[0]

                # NOTE: This is the sensible play to call `parse_variable`
                # but not yet sure how to implement it, so we currently pass
                # along `dt_idx` to the `%` handler.

        else:
            v_idx = None

            # If indexed variable already exists, then re-index this new
            #   non-indexed variable using the global start index

            if v_name in parent.start_index:
                p_start = parent.start_index[v_name.lower()]
                v_start = [self.default_start_index for _ in p_start]

                # Resize vector based on new starting index
                for i_p, i_v in zip(p_start, v_start):
                    if i_v < i_p:
                        pad = [None for _ in range(i_p - i_v)]
                        parent[v_name] = pad + parent[v_name]

                parent.start_index[v_name.lower()] = v_start

        if self.token == '%':

            # Resolve the derived type

            # Check for value in patch
            v_patch_nml = None
            if v_name in patch_nml:
                v_patch_nml = patch_nml.pop(v_name.lower())

            if parent:
                vpar = parent.get(v_name.lower())
                if vpar and isinstance(vpar, list):
                    assert dt_idx is not None
                    try:
                        v_parent = vpar[dt_idx]
                    except IndexError:
                        v_parent = Namelist()
                elif vpar:
                    v_parent = vpar
                else:
                    v_parent = Namelist()
            else:
                v_parent = Namelist()
                parent[v_name] = v_parent

            self.update_tokens()
            self.update_tokens()

            v_att, v_att_vals = self.parse_variable(v_parent,
                                                    patch_nml=v_patch_nml)

            next_value = Namelist()
            next_value[v_att] = v_att_vals
            self.append_value(v_values, next_value, v_idx)

        else:
            # Construct the variable array

            assert self.token == '='
            n_vals = None

            self.update_tokens()

            # Check if value is in the namelist patch
            # TODO: Edit `Namelist` to support case-insensitive `pop` calls
            #       (Currently only a problem in PyPy2)
            if v_name in patch_nml:
                patch_values = patch_nml.pop(v_name.lower())

                if not isinstance(patch_values, list):
                    patch_values = [patch_values]

                p_idx = 0

            # Add variables until next variable trigger
            while (self.token not in ('=', '(', '%') or
                   (self.prior_token, self.token) == ('=', '(')):

                # Check for repeated values
                if self.token == '*':
                    n_vals = self.parse_value()
                    assert isinstance(n_vals, int)
                    self.update_tokens()
                elif not n_vals:
                    n_vals = 1

                # First check for implicit null values
                if self.prior_token in ('=', '%', ','):
                    if (self.token in (',', '/', '&', '$') and
                            not (self.prior_token == ',' and
                                 self.token in ('/', '&', '$'))):
                        self.append_value(v_values, None, v_idx, n_vals)

                elif self.prior_token == '*':

                    if self.token not in ('/', '&', '$'):
                        self.update_tokens()

                    if (self.token == '=' or (self.token in ('/', '&', '$') and
                                              self.prior_token == '*')):
                        next_value = None
                    else:
                        next_value = self.parse_value()

                    self.append_value(v_values, next_value, v_idx, n_vals)

                else:
                    next_value = self.parse_value()
                    self.append_value(v_values, next_value, v_idx, n_vals)

                # Reset default repeat factor for subsequent values
                n_vals = 1

                # Exit for end of nml group (/, &, $) or null broadcast (=)
                if self.token in ('/', '&', '$', '='):
                    break
                else:
                    if patch_values:
                        if (p_idx < len(patch_values) and
                                len(patch_values) > 0 and self.token != ','):
                            p_val = patch_values[p_idx]
                            p_repr = patch_nml.f90repr(patch_values[p_idx])
                            p_idx += 1
                            self.update_tokens(override=p_repr)
                            if isinstance(p_val, complex):
                                # Skip over the complex content
                                # NOTE: Assumes input and patch are complex
                                self.update_tokens(write_token=False)
                                self.update_tokens(write_token=False)
                                self.update_tokens(write_token=False)
                                self.update_tokens(write_token=False)

                        else:
                            # Skip any values beyond the patch size
                            skip = (p_idx >= len(patch_values))
                            self.update_tokens(patch_skip=skip)
                    else:
                        self.update_tokens()

        if patch_values:
            v_values = patch_values

        if not v_idx:
            v_values = delist(v_values)

        return v_name, v_values
Esempio n. 10
0
    def readstream(self, nml_file, nml_patch):
        """Parse an input stream containing a Fortran namelist."""

        tokenizer = Tokenizer()
        f90lex = []
        for line in nml_file:
            toks = tokenizer.parse(line)
            toks.append('\n')
            f90lex.extend(toks)

        self.tokens = iter(f90lex)

        nmls = Namelist()

        # Attempt to get first token; abort on empty file
        try:
            self.update_tokens(write_token=False)
        except StopIteration:
            return nmls

        # TODO: Replace "while True" with an update_token() iterator
        while True:
            try:
                # Check for classic group terminator
                if self.token == 'end':
                    self.update_tokens()

                # Ignore tokens outside of namelist groups
                while self.token not in ('&', '$'):
                    self.update_tokens()

            except StopIteration:
                break

            # Create the next namelist
            self.update_tokens()
            g_name = self.token

            g_vars = Namelist()
            v_name = None

            # TODO: Edit `Namelist` to support case-insensitive `get` calls
            grp_patch = nml_patch.get(g_name.lower(), {})

            # Populate the namelist group
            while g_name:

                if self.token not in ('=', '%', '('):
                    self.update_tokens()

                # Set the next active variable
                if self.token in ('=', '(', '%'):

                    v_name, v_values = self.parse_variable(g_vars,
                                                           patch_nml=grp_patch)

                    if v_name in g_vars:
                        v_prior_values = g_vars[v_name]
                        v_values = merge_values(v_prior_values, v_values)

                    g_vars[v_name] = v_values

                    # Deselect variable
                    v_name = None
                    v_values = []

                # Finalise namelist group
                if self.token in ('/', '&', '$'):

                    # Append any remaining patched variables
                    for v_name, v_val in grp_patch.items():
                        g_vars[v_name] = v_val
                        v_strs = nmls.var_strings(v_name, v_val)
                        for v_str in v_strs:
                            self.pfile.write('    {0}\n'.format(v_str))

                    # Append the grouplist to the namelist
                    if g_name in nmls:
                        g_update = nmls[g_name]

                        # Update to list of groups
                        if not isinstance(g_update, list):
                            g_update = [g_update]

                        g_update.append(g_vars)

                    else:
                        g_update = g_vars

                    nmls[g_name] = g_update

                    # Reset state
                    g_name, g_vars = None, None

            try:
                self.update_tokens()
            except StopIteration:
                break

        return nmls
Esempio n. 11
0
    def read(self, nml_fname, nml_patch_in=None, patch_fname=None, row_major=None, strict_logical=None):
        """Parse a Fortran 90 namelist file and store the contents.

        >>> from f90nml.parser import Parser
        >>> parser = Parser()
        >>> data_nml = parser.read('data.nml')"""

        if row_major is not None:
            if not isinstance(row_major, bool):
                raise ValueError("f90nml: error: row_major must be a logical " "value.")
            else:
                self.row_major = row_major

        if strict_logical is not None:
            if not isinstance(strict_logical, bool):
                raise ValueError("f90nml: error: strict_logical must be a " "logical value.")
            else:
                self.strict_logical = strict_logical

        nml_file = open(nml_fname, "r")

        if nml_patch_in:
            if not isinstance(nml_patch_in, dict):
                nml_file.close()
                raise ValueError("Input patch must be a dict or an Namelist.")

            nml_patch = copy.deepcopy(Namelist(nml_patch_in))

            if not patch_fname:
                patch_fname = nml_fname + "~"
            elif nml_fname == patch_fname:
                nml_file.close()
                raise ValueError("f90nml: error: Patch filepath cannot be the " "same as the original filepath.")
            self.pfile = open(patch_fname, "w")
        else:
            nml_patch = Namelist()

        f90lex = shlex.shlex(nml_file)
        f90lex.whitespace = ""
        f90lex.wordchars += ".-+"  # Include floating point tokens
        if nml_patch:
            f90lex.commenters = ""
        else:
            f90lex.commenters = "!"

        self.tokens = iter(f90lex)

        nmls = Namelist()

        # TODO: Replace "while True" with an update_token() iterator
        self.update_tokens(write_token=False)
        while True:
            try:
                # Check for classic group terminator
                if self.token == "end":
                    self.update_tokens()

                # Ignore tokens outside of namelist groups
                while self.token not in ("&", "$"):
                    self.update_tokens()

            except StopIteration:
                break

            # Create the next namelist
            self.update_tokens()
            g_name = self.token

            g_vars = Namelist()
            v_name = None

            grp_patch = nml_patch.get(g_name, {})

            # Populate the namelist group
            while g_name:

                if self.token not in ("=", "%", "("):
                    self.update_tokens()

                # Set the next active variable
                if self.token in ("=", "(", "%"):

                    try:
                        v_name, v_values = self.parse_variable(g_vars, patch_nml=grp_patch)
                    except ValueError:
                        nml_file.close()
                        if self.pfile:
                            self.pfile.close()
                        raise

                    if v_name in g_vars:
                        v_prior_values = g_vars[v_name]
                        v_values = merge_values(v_prior_values, v_values)

                    if v_name in g_vars and isinstance(g_vars[v_name], dict):
                        g_vars[v_name].update(v_values)
                    else:
                        g_vars[v_name] = v_values

                    # Deselect variable
                    v_name = None
                    v_values = []

                # Finalise namelist group
                if self.token in ("/", "&", "$"):

                    # Append any remaining patched variables
                    for v_name, v_val in grp_patch.items():
                        g_vars[v_name] = v_val
                        v_strs = nmls.var_strings(v_name, v_val)
                        for v_str in v_strs:
                            self.pfile.write("    {0}\n".format(v_str))

                    # Append the grouplist to the namelist
                    if g_name in nmls:
                        g_update = nmls[g_name]

                        # Update to list of groups
                        if not isinstance(g_update, list):
                            g_update = [g_update]

                        g_update.append(g_vars)

                    else:
                        g_update = g_vars

                    nmls[g_name] = g_update

                    # Reset state
                    g_name, g_vars = None, None

            try:
                self.update_tokens()
            except StopIteration:
                break

        nml_file.close()
        if self.pfile:
            self.pfile.close()

        return nmls
Esempio n. 12
0
    def parse_variable(self, parent, patch_nml=None):
        """Parse a variable and return its name and values."""

        if not patch_nml:
            patch_nml = Namelist()

        v_name = self.prior_token
        v_values = []

        # Patch state
        patch_values = None
        write_token = v_name not in patch_nml

        if self.token == "(":

            v_idx_bounds = self.parse_indices()
            v_idx = FIndex(v_idx_bounds)

            self.update_tokens()
        else:
            v_idx = None

        if self.token == "%":

            # Resolve the derived type

            if parent and v_name in parent:
                v_parent = parent[v_name]
            else:
                v_parent = []

            self.update_tokens()
            self.update_tokens()

            v_att, v_att_vals = self.parse_variable(v_parent)

            next_value = Namelist()
            next_value[v_att] = v_att_vals
            self.append_value(v_values, next_value, v_idx)

        else:
            # Construct the variable array

            assert self.token == "="
            n_vals = None
            prior_ws_sep = ws_sep = False

            self.update_tokens()

            # Check if value is in the namelist patch
            if v_name in patch_nml:
                patch_values = patch_nml.f90repr(patch_nml.pop(v_name))
                if not isinstance(patch_values, list):
                    patch_values = [patch_values]

                for p_val in patch_values:
                    self.pfile.write(p_val)

            # Add variables until next variable trigger
            while self.token not in ("=", "(", "%") or (self.prior_token, self.token) == ("=", "("):

                # Check for repeated values
                if self.token == "*":
                    n_vals = self.parse_value(write_token)
                    assert isinstance(n_vals, int)
                    self.update_tokens(write_token)
                elif not n_vals:
                    n_vals = 1

                # First check for implicit null values
                if self.prior_token in ("=", "%", ","):
                    if self.token in (",", "/", "&", "$") and not (
                        self.prior_token == "," and self.token in ("/", "&", "$")
                    ):
                        self.append_value(v_values, None, v_idx, n_vals)

                elif self.prior_token == "*":

                    if self.token not in ("/", "&", "$"):
                        self.update_tokens(write_token)

                    if self.token == "=" or (self.token in ("/", "&", "$") and self.prior_token == "*"):
                        next_value = None
                    else:
                        next_value = self.parse_value(write_token)

                    self.append_value(v_values, next_value, v_idx, n_vals)

                else:
                    next_value = self.parse_value(write_token)

                    # Finished reading old value, we can again write tokens
                    write_token = True

                    # Check for escaped strings
                    if v_values and isinstance(v_values[-1], str) and isinstance(next_value, str) and not prior_ws_sep:

                        quote_char = self.prior_token[0]
                        v_values[-1] = quote_char.join([v_values[-1], next_value])
                    else:
                        self.append_value(v_values, next_value, v_idx, n_vals)

                # Exit for end of nml group (/, &, $) or null broadcast (=)
                if self.token in ("/", "&", "$", "="):
                    break
                else:
                    prior_ws_sep = ws_sep
                    ws_sep = self.update_tokens(write_token)

        if patch_values:
            v_values = patch_values

        if not v_idx:
            v_values = delist(v_values)

        return v_name, v_values
Esempio n. 13
0
def pull_cfg_from_parameters_out(parameters_out,
                                 namelist_to_read="nml_allcfgs"):
    """
    Pull out a single config set from a parameters_out namelist.

    This function returns a single file with the config that needs to be passed to
    MAGICC in order to do the same run as is represented by the values in
    ``parameters_out``.

    Parameters
    ----------
    parameters_out : dict, f90nml.Namelist
        The parameters to dump

    namelist_to_read : str
        The namelist to read from the file.

    Returns
    -------
    :obj:`f90nml.Namelist`
        An f90nml object with the cleaned, read out config.

    Examples
    --------
    >>> cfg = pull_cfg_from_parameters_out(magicc.metadata["parameters"])
    >>> cfg.write("/somewhere/else/ANOTHERNAME.cfg")
    """
    single_cfg = Namelist({namelist_to_read: {}})
    for key, value in parameters_out[namelist_to_read].items():
        if "file_tuning" in key:
            single_cfg[namelist_to_read][key] = ""
        else:
            try:
                if isinstance(value, str):
                    single_cfg[namelist_to_read][key] = value.strip(
                        " \t\n\r").replace("\x00", "")
                elif isinstance(value, list):
                    clean_list = [
                        v.strip(" \t\n\r").replace("\x00", "") for v in value
                    ]
                    single_cfg[namelist_to_read][key] = [
                        v for v in clean_list if v
                    ]
                else:
                    if not isinstance(value, Number):
                        raise AssertionError(
                            "value is not a number: {}".format(value))

                    single_cfg[namelist_to_read][key] = value
            except AttributeError:
                if isinstance(value, list):
                    if not all([isinstance(v, Number) for v in value]):
                        raise AssertionError(
                            "List where not all values are numbers? "
                            "{}".format(value))

                    single_cfg[namelist_to_read][key] = value
                else:
                    raise AssertionError(
                        "Unexpected cause in out parameters conversion")

    return single_cfg