예제 #1
0
    def examine_fortran(self, subject: FortranSource):
        issues: List[Issue] = []

        candidates: List[Fortran2003.StmtBase] = []
        # Component variables
        candidates.extend(subject.find_all(
            Fortran2003.Data_Component_Def_Stmt))
        # Variable declaration
        candidates.extend(subject.find_all(Fortran2003.Type_Declaration_Stmt))

        for candidate in candidates:

            problem = 'Use of dimension attribute for {name}.'

            attributes = candidate.items[1]
            if attributes is None:
                continue
            cannon_attr = list(
                str(item).lower().replace(' ', '')
                for item in attributes.items)
            argument_def = 'intent(in)' in cannon_attr \
                           or 'intent(out)' in cannon_attr \
                           or 'intent(inout)' in cannon_attr
            if any(x.startswith('dimension') for x in cannon_attr):
                for variable in candidate.items[2].items:
                    name = str(variable.items[0])
                    message = problem.format(name=name)
                    line_number = candidate.item.span[0]
                    issues.append(Issue(message, line=line_number))

        issues.sort(key=lambda x: (x.filename, x.line, x.description))
        return issues
예제 #2
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []

        for exit in subject.find_all(Fortran2003.Exit_Stmt):
            if exit.items[1] is None:
                issues.append(
                    Issue(
                        'Usage of "exit" without label indicating '
                        'which "do" construct is being exited '
                        'from.',
                        line=exit.item.span[0]))

        # Above doesn't catch exits in inline if statements
        for statement in subject.find_all(Fortran2003.If_Stmt):
            action = statement.items[1]
            if type(action
                    ) == Fortran2003.Exit_Stmt and action.items[1] is None:
                issues.append(
                    Issue(
                        'Usage of "exit" without label indicating '
                        'which "do" construct is being exited '
                        'from.',
                        line=statement.item.span[0]))

        return issues
예제 #3
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []

        scope_units = subject.path('Program_Unit')
        scope_units.extend(
            subject.path([
                'Main_Program', 'Internal_Subprogram_Part',
                'Internal_Subprogram'
            ]))
        scope_units.extend(
            subject.path(
                ['Module', 'Module_Subprogram_Part', 'Module_Subprogram']))
        for scope in scope_units:
            scope_statement = subject.get_first_statement(root=scope)

            implication = subject.path(
                ['Specification_Part', 'Implicit_Part', 'Implicit_Stmt'],
                root=scope.content[1:])
            if not implication:
                nature = MissingImplicit._NATURE_MAP[scope_statement.__class__]
                name = scope_statement.items[1]
                description = "{thing} '{name}' is missing " \
                              + "an implicit statement"
                description = description.format(thing=nature, name=name)
                issues.append(
                    Issue(description, line=scope_statement.item.span[0]))
        return issues
예제 #4
0
 def test_path_string(self, path_case):
     '''
     Checks that matching a path to the source works.
     '''
     # pylint: disable=no-self-use
     reader = SourceStringReader(path_case[0])
     unit_under_test = FortranSource(reader)
     result = unit_under_test.path('/'.join(path_case[1]))
     assert [obj.__class__.__name__ for obj in result] == path_case[2]
 def test_path_string(self, path_case: Tuple[str, List[str], List[str]]) \
         -> None:
     """
     Checks that matching a path to the source works.
     """
     reader = SourceStringReader(path_case[0])
     unit_under_test = FortranSource(reader)
     result = unit_under_test.path('/'.join(path_case[1]))
     assert [obj.__class__.__name__ for obj in result] == path_case[2]
 def test_find_all(self) -> None:
     """
     Checks that finding all occurrences of a source part works.
     """
     reader = SourceStringReader(self._MULTI_PROC_MODULE)
     unit_under_test = FortranSource(reader)
     wanted = fparser.two.Fortran2003.Module_Subprogram
     result = unit_under_test.find_all(wanted)
     assert str(next(result).content[0].items[1]) == 'one'
     assert str(next(result).content[0].items[1]) == 'two'
     with pytest.raises(StopIteration):
         next(result)
예제 #7
0
    def examine(self, subject: FortranSource) -> List[Issue]:
        issues = super(FortranRule, self).examine(subject)

        if not isinstance(subject, FortranSource):
            description = 'Non-Fortran source passed to a Fortran rule'
            raise Exception(description)

        if not subject.get_tree():
            description = "Unable to perform {} as source didn't parse: {}"
            issues.append(Issue(description.format(self.__class__.__name__,
                                                   subject.get_tree_error())))
            return issues

        issues.extend(self.examine_fortran(subject))
        return issues
    def test_exit_labels(self, module_name: str) -> None:
        """
        Checks that the rule reports missing "implicit" labels correctly
        """
        template = '''
program test
contains
    function function1()
        use {module_name}
    end function function1

    function function2()
        use, intrinsic :: {module_name}
    end function function2
end program test
'''

        expectation: List[str] = []
        message = '{line}: Usage of intrinsic module "{module_name}" without '\
                  '"intrinsic" clause.'
        if module_name.lower() in [
                "iso_c_binding", "iso_fortran_env", "ieee_exceptions",
                "ieee_arithmetic", "ieee_features"
        ]:
            expectation.extend([
                message.format(line=5, module_name=module_name),
            ])
        text = template.format(module_name=module_name)
        print(text)  # Shows up in failure reports, for debugging
        reader = SourceStringReader(text)
        source = FortranSource(reader)
        unit_under_test = IntrinsicModule()
        issues = unit_under_test.examine(source)
        issue_descriptions = [str(issue) for issue in issues]
        assert issue_descriptions == expectation
예제 #9
0
    def test_implicit_double(
            self,
            containing_program_unit: Tuple[str, List[str]],
            subprogram_implicit: Tuple[str, List[str]],
            second_subprogram_implicit: Tuple[str, List[str]]) -> None:
        """
        Checks all the permutations of two contained procedures.
        """
        procedure = '\n'.join([subprogram_implicit[0],
                               second_subprogram_implicit[0]]).strip()
        text = containing_program_unit[0].format(procedure=procedure)
        reader = SourceStringReader(text)
        source = FortranSource(reader)

        insert_point = containing_program_unit[0].find('{procedure}')
        insert_line = containing_program_unit[0].count('\n',
                                                       0,
                                                       insert_point) + 1
        first_len = subprogram_implicit[0].count('\n')
        if first_len > 0:
            first_len += 1

        expectation = []
        for thing in containing_program_unit[1]:
            expectation.append(f"1: {thing}")
        for thing in subprogram_implicit[1]:
            expectation.append(f"{insert_line}: {thing}")
        for thing in second_subprogram_implicit[1]:
            expectation.append(f"{insert_line + first_len}: {thing}")

        unit_under_test = stylist.fortran.MissingImplicit('none')
        issues = unit_under_test.examine(source)
        issue_descriptions = [str(issue) for issue in issues]
        assert issue_descriptions == expectation
예제 #10
0
class TestKindPattern:
    """
    Test the checker of kind patterns.
    """
    def test_passing(self):
        case = dedent("""
        module passing_mod
          implicit none
          integer(medium_beef) :: global_int
          integer(salt_beef) :: global_first, global_second
          real(soft_cheese) :: global_float
          logical(who_cares) :: global_bool
          type some_type
            integer(bloody_beef) :: member_int
            integer(marbled_beef) :: member_int_1, member_int_2
            real(blue_cheese) :: member_float
            real(green_cheese) :: member_float_1, member_float_2
            logical(no_difference) :: member_bool
          contains
            procedure method
          end type some_type
        contains
          function thing(arg_int, &
                         arg_float, &
                         arg_float_1, &
                         arg_float_2, &
                         arg_bool) result(return_int)
            implicit none
            integer(rare_beef), intent(in) :: arg_int
            real(hard_cheese), intent(out) :: arg_float
            real(stinky_cheese), intent(in) :: arg_float_1, arg_float_2
            logical(no_one_cares), intent(inout) :: arg_bool
            integer(well_done_beef) :: return_int
          end function thing
          function method(this, &
                          marg_int, &
                          marg_int_1, &
                          marg_int_2, &
                          marg_float, &
                          marg_bool) result(return_float)
            implicit none
            integer(cremated_beef), intent(in) :: marg_int
            integer(shredded_beef), intent(out) :: marg_int_1, marg_int_2
            real(sheep_cheese), intent(out) :: marg_float
            logical(sigh), intent(inout) :: marg_bool
            real(goat_cheese) :: return_float
          end function method
        end module passing_mod
        """)

        reader = SourceStringReader(case)
        source = FortranSource(reader)
        unit_under_test = KindPattern(integer=r'.+_beef',
                                      real=re.compile(r'.+_cheese'))
        issues = unit_under_test.examine(source)

        assert len(issues) == 0
예제 #11
0
    def test_use(self, unit_type, unit_usage, procedure_usage, ignorance):
        """
        Checks that the rule reports missing "use" clauses correctly.
        """
        def prepare(line_number: int, params):
            usage = []
            expectations = []
            for details in params:
                line = None
                if details[0] is not None:
                    line = 'use {0}'.format(details[0])
                    if details[1]:
                        line += ', only : {0}'.format(', '.join(details[1]))
                        line_number += 1
                    elif details[0] not in ignorance:
                        message = f'{line_number}: Usage of "{details[0]}" ' \
                                  f'without "only" clause.'
                        expectations.append(message)
                        line_number += 1
                if line:
                    usage.append(line)
            return usage, expectations, line_number

        text = '''{type} test
                   {uusage}
                   implicit none
                 contains
                   subroutine foo()
                     {pusage}
                     implicit none
                   end subroutine foo
                 end {type} test
               '''
        unit_lines, unit_expects, last_line = prepare(2, unit_usage)
        if len(unit_lines) == 0:
            last_line += 1
        proc_lines, proc_expects, _ = prepare(last_line + 3, procedure_usage)
        reader = SourceStringReader(
            text.format(type=unit_type,
                        uusage='\n'.join(unit_lines),
                        pusage='\n'.join(proc_lines)))
        source = FortranSource(reader)

        expectation = list(unit_expects)
        expectation.extend(proc_expects)
        print(
            text.format(type=unit_type,
                        uusage='\n'.join(unit_lines),
                        pusage='\n'.join(proc_lines)))
        print(expectation)
        if ignorance:
            unit_under_test = stylist.fortran.MissingOnly(ignore=ignorance)
        else:
            unit_under_test = stylist.fortran.MissingOnly()
        issues = unit_under_test.examine(source)
        issue_descriptions = [str(issue) for issue in issues]
        assert issue_descriptions == expectation
예제 #12
0
 def test_simple(self, simple_source: Tuple[str, List[str]]) -> None:
     """
     Ensures a given input source generates the correct issue list.
     """
     unit_under_test = stylist.fortran.FortranCharacterset()
     reader = SourceStringReader(simple_source[0])
     source = FortranSource(reader)
     issues = unit_under_test.examine(source)
     assert [str(issue) for issue in issues] == simple_source[1]
예제 #13
0
 def test_simple(self, simple_source):
     # pylint: disable=no-self-use
     '''
     Ensures a given input source generates the correct issue list.
     '''
     unit_under_test = stylist.fortran.FortranCharacterset()
     reader = SourceStringReader(simple_source[0])
     source = FortranSource(reader)
     issues = unit_under_test.examine(source)
     assert [str(issue) for issue in issues] == simple_source[1]
예제 #14
0
    def test_constructor(self) -> None:
        """
        Checks that the source file is correctly parsed on construction.
        """
        inject = r"""! Test program
program test
  implicit none
  write(6, '("Hello ", A)') 'world'
end program test
"""
        reader = SourceStringReader(inject)
        unit_under_test = FortranSource(reader)
        assert unit_under_test.get_text() == inject

        expected_string = """! Test program
PROGRAM test
  IMPLICIT NONE
  WRITE(6, FMT = '("Hello ", A)') 'world'
END PROGRAM test"""
        assert str(unit_under_test.get_tree()) == expected_string
예제 #15
0
    def test(self):
        """
        Ensures the test case produces exactly the issues in expectation
        """
        reader = SourceStringReader(TEST_CASE)
        source = FortranSource(reader)
        unit_under_test = stylist.fortran.AutoCharArrayIntent()
        issues = unit_under_test.examine(source)
        strings = [str(issue) for issue in issues]

        assert strings == TEST_EXPECTATION
예제 #16
0
 def test_examples(self, example_source):
     '''
     Ensures trailing whitespace is detected on the correct lines.
     '''
     unit_under_test = stylist.rule.TrailingWhitespace()
     reader = SourceStringReader(example_source[0])
     source = FortranSource(reader)
     issues = unit_under_test.examine(source)
     assert ([str(issue) for issue in issues] == [
         str(eln) + ': Found trailing white space'
         for eln in example_source[1]
     ])
예제 #17
0
 def test_examination(self):
     """
     Checks that all the rules in a style get a look at the program.
     """
     rule_one = TestStyle._RuleHarness()
     rule_two = TestStyle._RuleHarness()
     unit_under_test = TestStyle._StyleHarness([rule_one, rule_two])
     reader = SourceStringReader('module foo\nend module foo\n')
     source = FortranSource(reader)
     unit_under_test.check(source)
     assert rule_one.examined == ['module foo\nend module foo\n']
     assert rule_two.examined == ['module foo\nend module foo\n']
예제 #18
0
    def test_constructor(self):
        '''
        Checks that the source file is correctly parsed on construction.
        '''
        # pylint: disable=no-self-use
        inject = r'''! Test program
program test
  implicit none
  write(6, '("Hello ", A)') 'world'
end program test
'''
        reader = SourceStringReader(inject)
        unit_under_test = FortranSource(reader)
        assert unit_under_test.get_text() == inject

        expected_string = '''! Test program
PROGRAM test
  IMPLICIT NONE
  WRITE(6, FMT = '("Hello ", A)') 'world'
END PROGRAM test'''
        assert str(unit_under_test.get_tree()) == expected_string
예제 #19
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues: List[Issue] = []

        candidates: List[Fortran2003.Base] = []
        # Component variables
        candidates.extend(subject.find_all(
            Fortran2003.Data_Component_Def_Stmt))
        # Component Procedure
        candidates.extend(subject.find_all(
            Fortran2003.Proc_Component_Def_Stmt))
        # Procedure declaration
        candidates.extend(
            subject.find_all(Fortran2003.Procedure_Declaration_Stmt))
        # Variable declaration
        candidates.extend(subject.find_all(Fortran2003.Type_Declaration_Stmt))

        for candidate in candidates:
            if isinstance(
                    candidate,  # Is variable
                (Fortran2003.Data_Component_Def_Stmt,
                 Fortran2003.Type_Declaration_Stmt)):
                type_spec: Fortran2003.Intrinsic_Type_Spec = candidate.items[0]
                kind_selector: Fortran2003.Kind_Selector = type_spec.items[1]

                if isinstance(kind_selector, Fortran2003.Kind_Selector):
                    data_type: str = type_spec.items[0].lower()
                    kind: str = kind_selector.string.strip('()')
                    match = self._patterns[data_type].match(kind)
                    if match is None:
                        entity_declaration = candidate.items[2]
                        message = self._ISSUE_TEMPLATE.format(
                            type=data_type,
                            kind=kind,
                            name=entity_declaration,
                            pattern=self._patterns[data_type].pattern)
                        issues.append(
                            Issue(message, line=candidate.item.span[0]))

        issues.sort(key=lambda x: (x.filename, x.line, x.description))
        return issues
예제 #20
0
    def examine(self, subject: FortranSource) -> List[Issue]:
        """
        Base for rules which scruitinise the parse tree of Fortran source.

        :param subject: Source file to examine.
        :return: Issues found with the source.
        """
        issues = []
        if not isinstance(subject, FortranSource):
            description = 'Non-Fortran source passed to a Fortran rule'
            raise Exception(description)

        if not subject.get_tree():
            description = "Unable to perform {} as source didn't parse: {}"
            issues.append(
                Issue(
                    description.format(self.__class__.__name__,
                                       subject.get_tree_error())))
            return issues

        issues.extend(self.examine_fortran(subject))
        return issues
예제 #21
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []

        for statement in subject.find_all(Fortran2003.Use_Stmt):
            module = statement.items[2]
            onlies = statement.items[4]
            if str(module).lower() not in self._ignore:
                if onlies is None:
                    description = 'Usage of "{module}" without "only" clause.'
                    issues.append(Issue(description.format(module=module),
                                        line=statement.item.span[0]))

        return issues
예제 #22
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []
        for statement in subject.find_all(Fortran2003.Use_Stmt):
            module = statement.items[2]
            if str(module).lower() in self._INTRINSICS:
                nature = statement.items[0]
                if nature is None or nature.string.lower() != 'intrinsic':
                    description = 'Usage of intrinsic module "{module}" ' \
                                  'without "intrinsic" clause.'
                    issues.append(
                        Issue(description.format(module=module),
                              line=statement.item.span[0]))

        return issues
예제 #23
0
    def examine(self, subject: FortranSource) -> List[Issue]:
        # pylint: disable=too-many-branches
        """
        Examines the source code for none Fortran characters.

        This is complicated by the fact that the source must consist of only
        certain characters except comments and strings. These may contain
        anything.
        """
        issues = super(FortranCharacterset, self).examine(subject)

        text = subject.get_text()
        index = 0
        line = 1
        state = 'code'
        while index < len(text):
            character = text[index]
            if state == 'code':
                if character == self._NEWLINE:
                    line += 1
                elif character == self._EXCLAMATION:
                    state = 'comment'
                elif character == self._APOSTROPHY:
                    state = 'apostraphystring'
                elif character == self._QUOTE:
                    state = 'quotestring'
                elif character in self._FORTRAN_CHARACTERSET:
                    pass
                else:
                    description = "Found character {char} " \
                                  + "not in Fortran character set"
                    description = description.format(char=repr(character))
                    issues.append(Issue(description, line=line))
            elif state == 'comment':
                if character == self._NEWLINE:
                    line += 1
                    state = 'code'
            elif state == 'apostraphystring':
                if character == self._APOSTROPHY:
                    state = 'code'
            elif state == 'quotestring':
                if character == self._QUOTE:
                    state = 'code'
            else:
                raise Exception('Parser in unknown state: ' + state)
            index += 1

        return issues
예제 #24
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []

        # Collect all variable declarations
        declarations: List[Fortran2003.Type_Declaration_Stmt] = list(
            subject.find_all(Fortran2003.Type_Declaration_Stmt))

        # keep only the ones in subroutines
        declarations = [
            declaration for declaration in declarations if isinstance(
                declaration.parent.parent, Fortran2003.Subroutine_Subprogram)
        ]

        for declaration in declarations:
            type_spec = declaration.items[0]
            # If not a character, no concern
            if not type_spec.items[0] == "CHARACTER":
                continue
            param_value = type_spec.items[1]
            # This might be a length selector, if so get the param value
            if isinstance(param_value, Fortran2003.Length_Selector):
                param_value = param_value.items[1]
            # If not an auto length, no concern
            if not param_value.string == "*":
                continue
            attr_spec_list = declaration.items[1]
            # If no attributes specified, no concern
            if attr_spec_list is None:
                continue
            # Get intent attr and not other attributes
            intent_attr = None
            for item in attr_spec_list.items:
                if isinstance(item, Fortran2003.Intent_Attr_Spec):
                    intent_attr = item
                    break
            # If no intent, no conecern
            # Ensuring arguments specify intent should be enforced elsewhere
            if intent_attr is None:
                continue
            # Intent in, no conern
            if intent_attr.items[1].string == "IN":
                continue
            issues.append(
                Issue(self._message(declaration.items[2].string,
                                    intent_attr.items[1]),
                      line=declaration.item.span[0]))

        return issues
    def test_implicit(self, empty_program_unit_implicit):
        # pylint: disable=no-self-use
        '''
        Checks all permutations of program units.
        '''
        reader = SourceStringReader(empty_program_unit_implicit[0])
        source = FortranSource(reader)

        expectation = []
        for thing in empty_program_unit_implicit[1]:
            expectation.append(f"1: {thing}")

        unit_under_test = stylist.fortran.MissingImplicit('none')
        issues = unit_under_test.examine(source)
        issue_descriptions = [str(issue) for issue in issues]
        assert issue_descriptions == expectation
예제 #26
0
    def examine(self, subject: FortranSource) -> List[Issue]:
        # pylint: disable=too-many-branches
        """
        Examines the source code for none Fortran characters.

        This is complicated by the fact that the source must consist of only
        certain characters except comments and strings. These may contain
        anything.
        """
        issues = super(FortranOldComparisons, self).examine(subject)

        text = subject.get_text()
        index = 0
        line = 1
        state = 'code'
        while index < len(text) - 4:
            characters = text[index:index + 4]
            if state == 'code':
                if characters[0] == self._NEWLINE:
                    line += 1
                elif characters[0] == self._EXCLAMATION:
                    state = 'comment'
                elif characters[0] == self._APOSTROPHY:
                    state = 'apostraphystring'
                elif characters[0] == self._QUOTE:
                    state = 'quotestring'
                elif characters == ".eq." or characters == ".ne." or characters == ".gt." or characters == ".lt." or characters == ".ge." or characters == ".le.":
                    description = "Found " + characters
                    issues.append(Issue(description, line=line))
                elif characters[0] in self._FORTRAN_CHARACTERSET:
                    pass
            elif state == 'comment':
                if characters[0] == self._NEWLINE:
                    line += 1
                    state = 'code'
            elif state == 'apostraphystring':
                if characters[0] == self._APOSTROPHY:
                    state = 'code'
            elif state == 'quotestring':
                if characters[0] == self._QUOTE:
                    state = 'code'
            else:
                raise Exception('Parser in unknown state: ' + state)
            index += 1

        return issues
예제 #27
0
    def test_implicit(self,
                      empty_program_unit_implicit: Tuple[str, List[str]]) \
            -> None:
        """
        Checks all permutations of program units.
        """
        reader = SourceStringReader(empty_program_unit_implicit[0])
        source = FortranSource(reader)

        expectation = []
        for thing in empty_program_unit_implicit[1]:
            expectation.append(f"1: {thing}")

        unit_under_test = stylist.fortran.MissingImplicit('none')
        issues = unit_under_test.examine(source)
        issue_descriptions = [str(issue) for issue in issues]
        assert issue_descriptions == expectation
예제 #28
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []

        text = subject.get_text()
        index = 0
        line = 1
        state = 'code'
        while index < len(text):
            character = text[index]
            if character == self._NEWLINE:
                line += 1
            elif character in self._FORTRAN_CHARACTERSET:
                pass
            else:
                description = "Found tab"
                issues.append(Issue(description, line=line))
            index += 1
        return issues
예제 #29
0
    def examine_fortran(self, subject: FortranSource) -> List[Issue]:
        issues = []

        for statement in subject.find_all(Fortran2003.Use_Stmt):
            print ("statement = ", statement)
        for statement in subject.find_all(Fortran2003.Data_Component_Def_Stmt):
            print ("statement = ", statement)
        for statement in subject.find_all(Fortran2003.Proc_Component_Def_Stmt):
            print ("statement = ", statement)
        for statement in subject.find_all(Fortran2003.Procedure_Declaration_Stmt):
            print ("statement = ", statement)
        for statement in subject.find_all(Fortran2003.Type_Declaration_Stmt):
            print ("statement = ", statement)
        print ("text = ", subject.get_text())
        return issues
예제 #30
0
    def examine(self, subject: FortranSource) -> List[Issue]:
        issues = []

        text = subject.get_text()
        index = 0
        line = 1
        state = 'code'
        while index < len(text):
            character = text[index]
            if state == 'code':
                if character == self._NEWLINE:
                    line += 1
                elif character == self._EXCLAMATION:
                    state = 'comment'
                elif character == self._APOSTROPHY:
                    state = 'apostraphystring'
                elif character == self._QUOTE:
                    state = 'quotestring'
                elif character in self._FORTRAN_CHARACTERSET:
                    pass
                else:
                    description = "Found character {char} " \
                                  + "not in Fortran character set"
                    description = description.format(char=repr(character))
                    issues.append(Issue(description, line=line))
            elif state == 'comment':
                if character == self._NEWLINE:
                    line += 1
                    state = 'code'
            elif state == 'apostraphystring':
                if character == self._APOSTROPHY:
                    state = 'code'
            elif state == 'quotestring':
                if character == self._QUOTE:
                    state = 'code'
            else:
                raise Exception('Parser in unknown state: ' + state)
            index += 1

        return issues