# 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
from collections.abc import Iterator
from robot.utils import normalize_whitespace
from .context import (
FileContext, KeywordContext, LexingContext, SuiteFileContext, TestCaseContext
)
from .statementlexers import (
BreakLexer, CommentLexer, CommentSectionHeaderLexer, ContinueLexer, ElseHeaderLexer,
ElseIfHeaderLexer, EndLexer, ExceptHeaderLexer, FinallyHeaderLexer, ForHeaderLexer,
GroupHeaderLexer, IfHeaderLexer, ImplicitCommentLexer, InlineIfHeaderLexer,
InvalidSectionHeaderLexer, KeywordCallLexer, KeywordSectionHeaderLexer,
KeywordSettingLexer, Lexer, ReturnLexer, SettingLexer, SettingSectionHeaderLexer,
SyntaxErrorLexer, TaskSectionHeaderLexer, TestCaseSectionHeaderLexer,
TestCaseSettingLexer, TryHeaderLexer, VariableLexer, VariableSectionHeaderLexer,
VarLexer, WhileHeaderLexer
)
from .tokens import StatementTokens, Token
[docs]
class BlockLexer(Lexer, ABC):
def __init__(self, ctx: LexingContext):
super().__init__(ctx)
self.lexers: "list[Lexer]" = []
[docs]
def accepts_more(self, statement: StatementTokens) -> bool:
return True
[docs]
def lexer_for(self, statement: StatementTokens) -> Lexer:
for cls in self.lexer_classes():
lexer = cls(self.ctx)
if lexer.handles(statement):
return lexer
raise TypeError(
f"{type(self).__name__} does not have lexer for statement {statement}."
)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return ()
[docs]
def lex(self):
for lexer in self.lexers:
lexer.lex()
def _lex_with_priority(self, priority: "type[Lexer]"):
for lexer in self.lexers:
if isinstance(lexer, priority):
lexer.lex()
for lexer in self.lexers:
if not isinstance(lexer, priority):
lexer.lex()
[docs]
class FileLexer(BlockLexer):
[docs]
def lex(self):
self._lex_with_priority(priority=SettingSectionLexer)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
SettingSectionLexer,
VariableSectionLexer,
TestCaseSectionLexer,
TaskSectionLexer,
KeywordSectionLexer,
CommentSectionLexer,
InvalidSectionLexer,
ImplicitCommentSectionLexer,
)
[docs]
class SectionLexer(BlockLexer, ABC):
ctx: FileContext
[docs]
def accepts_more(self, statement: StatementTokens) -> bool:
return not statement[0].value.startswith("*")
[docs]
class SettingSectionLexer(SectionLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return self.ctx.setting_section(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (SettingSectionHeaderLexer, SettingLexer)
[docs]
class VariableSectionLexer(SectionLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return self.ctx.variable_section(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (VariableSectionHeaderLexer, VariableLexer)
[docs]
class TestCaseSectionLexer(SectionLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return self.ctx.test_case_section(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (TestCaseSectionHeaderLexer, TestCaseLexer)
[docs]
class TaskSectionLexer(SectionLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return self.ctx.task_section(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (TaskSectionHeaderLexer, TestCaseLexer)
[docs]
class KeywordSectionLexer(SettingSectionLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return self.ctx.keyword_section(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (KeywordSectionHeaderLexer, KeywordLexer)
[docs]
class InvalidSectionLexer(SectionLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return bool(statement and statement[0].value.startswith("*"))
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (InvalidSectionHeaderLexer, CommentLexer)
[docs]
class TestOrKeywordLexer(BlockLexer, ABC):
name_type: str
_name_seen = False
[docs]
def accepts_more(self, statement: StatementTokens) -> bool:
return not statement[0].value
def _handle_name_or_indentation(self, statement: StatementTokens):
if not self._name_seen:
name_token = statement.pop(0)
name_token.type = self.name_type
if statement:
name_token._add_eos_after = True
self._name_seen = True
else:
while statement and not statement[0].value:
statement.pop(0).type = None # These tokens will be ignored
[docs]
class TestCaseLexer(TestOrKeywordLexer):
name_type = Token.TESTCASE_NAME
def __init__(self, ctx: SuiteFileContext):
super().__init__(ctx.test_case_context())
[docs]
def lex(self):
self._lex_with_priority(priority=TestCaseSettingLexer)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
TestCaseSettingLexer,
ForLexer,
InlineIfLexer,
IfLexer,
TryLexer,
WhileLexer,
GroupLexer,
VarLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class KeywordLexer(TestOrKeywordLexer):
name_type = Token.KEYWORD_NAME
def __init__(self, ctx: FileContext):
super().__init__(ctx.keyword_context())
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
KeywordSettingLexer,
ForLexer,
InlineIfLexer,
IfLexer,
TryLexer,
WhileLexer,
GroupLexer,
VarLexer,
ReturnLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class NestedBlockLexer(BlockLexer, ABC):
ctx: "TestCaseContext|KeywordContext"
def __init__(self, ctx: "TestCaseContext|KeywordContext"):
super().__init__(ctx)
self._block_level = 0
[docs]
def accepts_more(self, statement: StatementTokens) -> bool:
return self._block_level > 0
[docs]
class ForLexer(NestedBlockLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return ForHeaderLexer(self.ctx).handles(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
ForHeaderLexer,
InlineIfLexer,
IfLexer,
TryLexer,
WhileLexer,
EndLexer,
GroupLexer,
VarLexer,
ReturnLexer,
ContinueLexer,
BreakLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class WhileLexer(NestedBlockLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return WhileHeaderLexer(self.ctx).handles(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
WhileHeaderLexer,
ForLexer,
InlineIfLexer,
IfLexer,
TryLexer,
EndLexer,
GroupLexer,
VarLexer,
ReturnLexer,
ContinueLexer,
BreakLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class TryLexer(NestedBlockLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return TryHeaderLexer(self.ctx).handles(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
TryHeaderLexer,
ExceptHeaderLexer,
ElseHeaderLexer,
FinallyHeaderLexer,
ForLexer,
InlineIfLexer,
IfLexer,
WhileLexer,
EndLexer,
VarLexer,
GroupLexer,
ReturnLexer,
BreakLexer,
ContinueLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class GroupLexer(NestedBlockLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return GroupHeaderLexer(self.ctx).handles(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
GroupHeaderLexer,
InlineIfLexer,
IfLexer,
ForLexer,
TryLexer,
WhileLexer,
EndLexer,
VarLexer,
ReturnLexer,
ContinueLexer,
BreakLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class IfLexer(NestedBlockLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
return IfHeaderLexer(self.ctx).handles(statement)
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
InlineIfLexer,
IfHeaderLexer,
ElseIfHeaderLexer,
ElseHeaderLexer,
ForLexer,
TryLexer,
WhileLexer,
EndLexer,
VarLexer,
GroupLexer,
ReturnLexer,
ContinueLexer,
BreakLexer,
SyntaxErrorLexer,
KeywordCallLexer,
)
[docs]
class InlineIfLexer(NestedBlockLexer):
[docs]
def handles(self, statement: StatementTokens) -> bool:
if len(statement) <= 2:
return False
return InlineIfHeaderLexer(self.ctx).handles(statement)
[docs]
def accepts_more(self, statement: StatementTokens) -> bool:
return False
[docs]
def lexer_classes(self) -> "tuple[type[Lexer], ...]":
return (
InlineIfHeaderLexer,
ElseIfHeaderLexer,
ElseHeaderLexer,
VarLexer,
GroupLexer,
ReturnLexer,
ContinueLexer,
BreakLexer,
KeywordCallLexer,
)
def _split(self, statement: StatementTokens) -> "Iterator[StatementTokens]":
current = []
expect_condition = False
for token in statement:
if expect_condition:
if token is not statement[-1]:
token._add_eos_after = True
current.append(token)
yield current
current = []
expect_condition = False
elif token.value == "IF":
current.append(token)
expect_condition = True
elif normalize_whitespace(token.value) == "ELSE IF":
token._add_eos_before = True
yield current
current = [token]
expect_condition = True
elif token.value == "ELSE":
token._add_eos_before = True
if token is not statement[-1]:
token._add_eos_after = True
yield current
current = []
yield [token]
else:
current.append(token)
yield current