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, 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] @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 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] 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 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