Source code for cli.parser

"""A module with Parser responsibility.

Parsing is one of the later steps in
program compilation or intepreting.

It ensures that the stream of lexemes
form a valid program. The result
is a tree that represents a program.

In our case, the result will be
:class:`commands.RunnableCommand`.
"""
from cli.commands import CommandChainPipe, CommandAssignment
from cli.single_command import SingleCommandFactory
from cli.exceptions import ParseException
from cli.lexer import LexemType


[docs]class Parser: """A static class for parsing a list of lexemes. Parser ensures that a stream of lexemes match a syntactic structure of a valid command. It also builds a representation of this command alongway. """ @staticmethod
[docs] def build_command(lexemes): """Build :class:`commands.RunnableCommand` out of list of lexemes. Our grammar is as following:: <start> ::= <command> (PIPE <command>)* <command> ::= <assignment> | <single_command> <assignment> ::= ASSIGNMENT <single_command> ::= STRING (STRING | QUOTED_STRING | ASSIGNMENT)* where ASSIGNMENT, QUOTED_STRING, STRING and PIPE are lexemes. Every rule is implemented as a static method with name _parse_`smth`. It returns a pair: - a resulting :class:`commands.RunnableCommand` - a list of unparsed lexemes """ runnable, unparsed_lexemes = Parser._parse_start(lexemes) if unparsed_lexemes: raise ParseException('Not all lexemes were parsed. The first starts '\ 'at {}'.format(unparsed_lexemes[0].get_position())) return runnable
@staticmethod def first_lex_matches_type(lexemes, tp): return lexemes and lexemes[0].get_type() == tp @staticmethod def _consume_one_lexem(lexem_list, desired_lexem_type): """Consume a lexem of the desired type. Return list of lexemes without consumed one. Raises: ParseException, if the list is empty or the first lexem is not of a type `desired_lexem_type`. """ if not lexem_list: raise ParseException('Expected lexem of type {}, found ' \ 'none.'.format(desired_lexem_type.name)) if lexem_list[0].get_type() != desired_lexem_type: raise ParseException('Expected lexem of type {}, found '\ 'lexem of type {}.'.format(desired_lexem_type.name, lexem_list[0].get_type().name)) return lexem_list[1:] @staticmethod def _parse_start(lexemes): first_command, unparsed_lexemes = Parser._parse_command(lexemes) result_command = first_command while unparsed_lexemes: unparsed_lexemes = Parser._consume_one_lexem(unparsed_lexemes, LexemType.PIPE) current_command, unparsed_lexemes = Parser._parse_command(unparsed_lexemes) result_command = CommandChainPipe(result_command, current_command) return result_command, unparsed_lexemes @staticmethod def _parse_command(lexemes): if Parser.first_lex_matches_type(lexemes, LexemType.ASSIGNMENT): return Parser._parse_assignment(lexemes) return Parser._parse_single_command(lexemes) @staticmethod def _parse_assignment(lexemes): unprocessed_lexemes = Parser._consume_one_lexem(lexemes, LexemType.ASSIGNMENT) command = CommandAssignment([lexemes[0].get_value()]) return command, unprocessed_lexemes @staticmethod def _parse_single_command(lexemes): rest_lexemes = Parser._consume_one_lexem(lexemes, LexemType.STRING) cmd_name_and_args = [lexemes[0]] while rest_lexemes: first_lex_type = rest_lexemes[0].get_type() if first_lex_type in (LexemType.QUOTED_STRING, LexemType.STRING, LexemType.ASSIGNMENT): cmd_name_and_args.append(rest_lexemes[0]) rest_lexemes = Parser._consume_one_lexem(rest_lexemes, first_lex_type) else: break command = SingleCommandFactory.build_command(cmd_name_and_args) return command, rest_lexemes