Source code for robot.parsing.lexer.statementlexers

#  Copyright 2008-2015 Nokia Networks
#  Copyright 2016-     Robot Framework Foundation
#
#  Licensed under the Apache License, Version 2.0 (the "License");
#  you may not use this file except in compliance with the License.
#  You may obtain a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
#  Unless required by applicable law or agreed to in writing, software
#  distributed under the License is distributed on an "AS IS" BASIS,
#  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
#  See the License for the specific language governing permissions and
#  limitations under the License.

from abc import ABC, abstractmethod
from typing import List

from robot.errors import DataError
from robot.utils import normalize_whitespace
from robot.variables import is_assign

from .context import FileContext, LexingContext, KeywordContext, TestCaseContext
from .tokens import StatementTokens, Token


[docs]class Lexer(ABC): def __init__(self, ctx: LexingContext): self.ctx = ctx
[docs] def handles(self, statement: StatementTokens) -> bool: return True
[docs] @abstractmethod def accepts_more(self, statement: StatementTokens) -> bool: raise NotImplementedError
[docs] @abstractmethod def input(self, statement: StatementTokens): raise NotImplementedError
[docs] @abstractmethod def lex(self): raise NotImplementedError
[docs]class StatementLexer(Lexer, ABC): token_type: str def __init__(self, ctx: LexingContext): super().__init__(ctx) self.statement: StatementTokens = []
[docs] def accepts_more(self, statement: StatementTokens) -> bool: return False
[docs] def input(self, statement: StatementTokens): self.statement = statement
[docs] def lex(self): raise NotImplementedError
def _lex_options(self, *names: str, end_index: 'int|None' = None): for token in reversed(self.statement[:end_index]): if not token.value.startswith(names): break token.type = Token.OPTION
[docs]class SingleType(StatementLexer, ABC):
[docs] def lex(self): for token in self.statement: token.type = self.token_type
[docs]class TypeAndArguments(StatementLexer, ABC):
[docs] def lex(self): self.statement[0].type = self.token_type for token in self.statement[1:]: token.type = Token.ARGUMENT
[docs]class SectionHeaderLexer(SingleType, ABC): ctx: FileContext
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value.startswith('*')
[docs]class SettingSectionHeaderLexer(SectionHeaderLexer): token_type = Token.SETTING_HEADER
[docs]class VariableSectionHeaderLexer(SectionHeaderLexer): token_type = Token.VARIABLE_HEADER
[docs]class TestCaseSectionHeaderLexer(SectionHeaderLexer): token_type = Token.TESTCASE_HEADER
[docs]class TaskSectionHeaderLexer(SectionHeaderLexer): token_type = Token.TASK_HEADER
[docs]class KeywordSectionHeaderLexer(SectionHeaderLexer): token_type = Token.KEYWORD_HEADER
[docs]class CommentSectionHeaderLexer(SectionHeaderLexer): token_type = Token.COMMENT_HEADER
[docs]class InvalidSectionHeaderLexer(SectionHeaderLexer): token_type = Token.INVALID_HEADER
[docs] def lex(self): self.ctx.lex_invalid_section(self.statement)
[docs]class CommentLexer(SingleType): token_type = Token.COMMENT
[docs]class ImplicitCommentLexer(CommentLexer): ctx: FileContext
[docs] def input(self, statement: StatementTokens): super().input(statement) if len(statement) == 1 and statement[0].value.lower().startswith('language:'): lang = statement[0].value.split(':', 1)[1].strip() try: self.ctx.add_language(lang) except DataError: statement[0].set_error( f"Invalid language configuration: " f"Language '{lang}' not found nor importable as a language module." ) else: statement[0].type = Token.CONFIG
[docs] def lex(self): for token in self.statement: if not token.type: token.type = self.token_type
[docs]class SettingLexer(StatementLexer): ctx: FileContext
[docs] def lex(self): self.ctx.lex_setting(self.statement)
[docs]class TestCaseSettingLexer(StatementLexer): ctx: TestCaseContext
[docs] def lex(self): self.ctx.lex_setting(self.statement)
[docs] def handles(self, statement: StatementTokens) -> bool: marker = statement[0].value return bool(marker and marker[0] == '[' and marker[-1] == ']')
[docs]class KeywordSettingLexer(StatementLexer): ctx: KeywordContext
[docs] def lex(self): self.ctx.lex_setting(self.statement)
[docs] def handles(self, statement: StatementTokens) -> bool: marker = statement[0].value return bool(marker and marker[0] == '[' and marker[-1] == ']')
[docs]class VariableLexer(TypeAndArguments): ctx: FileContext token_type = Token.VARIABLE
[docs]class KeywordCallLexer(StatementLexer): ctx: 'TestCaseContext|KeywordContext'
[docs] def lex(self): if self.ctx.template_set: self._lex_as_template() else: self._lex_as_keyword_call()
def _lex_as_template(self): for token in self.statement: token.type = Token.ARGUMENT def _lex_as_keyword_call(self): keyword_seen = False for token in self.statement: if keyword_seen: token.type = Token.ARGUMENT elif is_assign(token.value, allow_assign_mark=True, allow_items=True): token.type = Token.ASSIGN else: token.type = Token.KEYWORD keyword_seen = True
[docs]class ForHeaderLexer(StatementLexer): separators = ('IN', 'IN RANGE', 'IN ENUMERATE', 'IN ZIP')
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'FOR'
[docs] def lex(self): self.statement[0].type = Token.FOR separator = None for token in self.statement[1:]: if separator: token.type = Token.ARGUMENT elif normalize_whitespace(token.value) in self.separators: token.type = Token.FOR_SEPARATOR separator = normalize_whitespace(token.value) else: token.type = Token.VARIABLE if separator == 'IN ENUMERATE': self._lex_options('start=') elif separator == 'IN ZIP': self._lex_options('mode=', 'fill=')
[docs]class IfHeaderLexer(TypeAndArguments): token_type = Token.IF
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'IF' and len(statement) <= 2
[docs]class InlineIfHeaderLexer(StatementLexer): token_type = Token.INLINE_IF
[docs] def handles(self, statement: StatementTokens) -> bool: for token in statement: if token.value == 'IF': return True if not is_assign(token.value, allow_assign_mark=True, allow_items=True): return False return False
[docs] def lex(self): if_seen = False for token in self.statement: if if_seen: token.type = Token.ARGUMENT elif token.value == 'IF': token.type = Token.INLINE_IF if_seen = True else: token.type = Token.ASSIGN
[docs]class ElseIfHeaderLexer(TypeAndArguments): token_type = Token.ELSE_IF
[docs] def handles(self, statement: StatementTokens) -> bool: return normalize_whitespace(statement[0].value) == 'ELSE IF'
[docs]class ElseHeaderLexer(TypeAndArguments): token_type = Token.ELSE
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'ELSE'
[docs]class TryHeaderLexer(TypeAndArguments): token_type = Token.TRY
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'TRY'
[docs]class ExceptHeaderLexer(StatementLexer): token_type = Token.EXCEPT
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'EXCEPT'
[docs] def lex(self): self.statement[0].type = Token.EXCEPT as_index: 'int|None' = None for index, token in enumerate(self.statement[1:], start=1): if token.value == 'AS': token.type = Token.AS as_index = index elif as_index: token.type = Token.VARIABLE else: token.type = Token.ARGUMENT self._lex_options('type=', end_index=as_index)
[docs]class FinallyHeaderLexer(TypeAndArguments): token_type = Token.FINALLY
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'FINALLY'
[docs]class WhileHeaderLexer(StatementLexer): token_type = Token.WHILE
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'WHILE'
[docs] def lex(self): self.statement[0].type = Token.WHILE for token in self.statement[1:]: token.type = Token.ARGUMENT self._lex_options('limit=', 'on_limit=', 'on_limit_message=')
[docs]class EndLexer(TypeAndArguments): token_type = Token.END
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'END'
[docs]class ReturnLexer(TypeAndArguments): token_type = Token.RETURN_STATEMENT
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'RETURN'
[docs]class ContinueLexer(TypeAndArguments): token_type = Token.CONTINUE
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'CONTINUE'
[docs]class BreakLexer(TypeAndArguments): token_type = Token.BREAK
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == 'BREAK'
[docs]class SyntaxErrorLexer(TypeAndArguments): token_type = Token.ERROR
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value in {'ELSE', 'ELSE IF', 'EXCEPT', 'FINALLY', 'BREAK', 'CONTINUE', 'RETURN', 'END'}
[docs] def lex(self): token = self.statement[0] token.set_error(f'{token.value} is not allowed in this context.') for t in self.statement[1:]: t.type = Token.ARGUMENT