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 robot.errors import DataError
from robot.utils import normalize_whitespace
from robot.variables import is_assign

from .context import FileContext, KeywordContext, LexingContext, 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] @abstractmethod def lex(self): raise NotImplementedError
def _lex_options(self, *names: str, end_index: "int|None" = None): seen = set() for token in reversed(self.statement[:end_index]): if "=" in token.value: name = token.value.split("=")[0] if name in names and name not in seen: token.type = Token.OPTION seen.add(name) continue break
[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 statement[0].value.lower().startswith("language:"): value = " ".join(token.value for token in statement) lang = value.split(":", 1)[1].strip() try: self.ctx.add_language(lang) except DataError: for token in statement: token.set_error( f"Invalid language configuration: Language '{lang}' " f"not found nor importable as a language module." ) else: for token in statement: token.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] def lex(self): super().lex() if self.statement[0].value[:1] == "$": self._lex_options("separator")
[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_nested=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_nested=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 GroupHeaderLexer(TypeAndArguments): token_type = Token.GROUP
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == "GROUP"
[docs] class EndLexer(TypeAndArguments): token_type = Token.END
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == "END"
[docs] class VarLexer(StatementLexer): token_type = Token.VAR
[docs] def handles(self, statement: StatementTokens) -> bool: return statement[0].value == "VAR"
[docs] def lex(self): self.statement[0].type = Token.VAR if len(self.statement) > 1: name, *values = self.statement[1:] name.type = Token.VARIABLE for value in values: value.type = Token.ARGUMENT options = ["scope", "separator"] if name.value[:1] == "$" else ["scope"] self._lex_options(*options)
[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