diff --git a/cli/README.md b/cli/README.md new file mode 100644 index 0000000..f3c3a0b --- /dev/null +++ b/cli/README.md @@ -0,0 +1,31 @@ +Интерпретатор командной строки, поддерживающий следующие команды: +* cat [FILE] — вывести на экран содержимое файла; +* echo — вывести на экран свой аргумент (или аргументы); +* wc [FILE] — вывести количество строк, слов и байт в файле; +* pwd — распечатать текущую директорию; +* exit — выйти из интерпретатора. + +Архитектура: + +Класс IToken - представляет собой интерфейс для токены, поиск которых будет осуществляться в исходном выражении, от него наследуются TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, +TokenAssignment, TokenWord, которые отвечают токенам в соответствии со своим названием. + +В последствии список классов токенов, которые необходимо распарсить передаются в класс, наследованный от интерфейса IParser, +который в свою очередь должен преобразовать входную строчку в набор токенов + +Класс IStorage представляет собой интерфейс для представления хранилища для переменных окружения, +его метод evaluate_variables, должен принимать строку и заменять вхождения всех переменных на их значения, +переменные ищутся по регулярному выражению, переданному в конструктор + +Команды и интерфейс ICommand представляют собой логику работы отдельной команды, из ключевых методов - +name, который должен вернуть имя команды, по которому будет выполнена подстановка и execute логика самой команды. Команды +реализованы согласно списку в начале файла. + +Интерпретатор ICommandInterpreter должен реализовывать преобразование потока токенов в поток команд с помощью +метода retrieve_commands, также при его создании необходимо указать разделитель команд (в нашем случае TokenPipe), а также +команду, которая будет выбрана по умолчанию, если все остальные команды не подошли + +IExecutor - аккумулирует все вышеперечисленное и должен преобразовывать входное выражение в результат его исполнения, что делает его +единственный метод execute_expression + +![alt text](./class_diagram.png) \ No newline at end of file diff --git a/cli/class_diagram.png b/cli/class_diagram.png new file mode 100644 index 0000000..56f04ff Binary files /dev/null and b/cli/class_diagram.png differ diff --git a/cli/cli.py b/cli/cli.py new file mode 100644 index 0000000..dda4e55 --- /dev/null +++ b/cli/cli.py @@ -0,0 +1,52 @@ +""" +интерпретатор командной строки, поддерживающий следующие команды: +• cat [FILE] — вывести на экран содержимое файла; +• echo — вывести на экран свой аргумент (или аргументы); +• wc [FILE] — вывести количество строк, слов и байт в файле; +• pwd — распечатать текущую директорию; +• exit — выйти из интерпретатора. +""" + +import os +from subprocess import run, PIPE +from storage import Storage +from commands import CommandCat, CommandEcho, CommandWC, CommandPwd, \ + CommandExit, CommandDefault +from tokens import TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, \ + TokenAssignment, TokenWord +from interpreter import CommandInterpreterWithStorage +from pparser import Parser +from executor import Executor +from evaluator import Evaluator + + +def main_loop(): + """ + Главный цикл интерпретатора + """ + + if os.name == 'nt': # установка кодировки utf-8 для windows + run(['chcp', '65001'], stdout=PIPE, shell=True) + + storage = Storage() + evaluator = Evaluator(storage, r'\$[^ \'\"$]+') + commands = [CommandCat, CommandEcho, CommandWC, + CommandPwd, CommandExit] + token_types = [TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, + TokenAssignment, TokenWord] + + executor = Executor(CommandInterpreterWithStorage + (evaluator, commands, TokenPipe, CommandDefault), + Parser(token_types), evaluator) + + while True: + try: + result = executor.execute_expression(input("> ")) + if result: + print(result) + except Exception as error: + print(str(error)) + + +if __name__ == "__main__": + main_loop() diff --git a/cli/commands.py b/cli/commands.py new file mode 100644 index 0000000..45d436a --- /dev/null +++ b/cli/commands.py @@ -0,0 +1,139 @@ +""" + Модуль с командами с которыми работает интерпретатор +""" +import os +from abc import ABCMeta, abstractmethod +from typing import List +from subprocess import run, PIPE +from storage import IStorage + + +class ICommand(metaclass=ABCMeta): + """ Интерфейс для команды """ + + def __init__(self, args: List[str]): + self._args = args + + @staticmethod + @abstractmethod + def name() -> str: + """ Вернуть имя команды + по нему будет происходить разбор команд""" + pass + + @abstractmethod + def execute(self, pipe: str, storage: IStorage) -> str: + """ Выполнить команду с переданным pipeом + вернуть результат выполнения """ + pass + + def prepend_arg(self, arg: str): + """ Добавить аргумент перед всеми аргументами """ + self._args = [arg] + self._args + + def append_arg(self, arg: str): + """ Добавить аргумент после всех аргументов """ + self._args.append(arg) + + +class CommandCat(ICommand): + """ Вывести на экран содержимое файла """ + + @staticmethod + def name() -> str: + return "cat" + + def execute(self, pipe: str, storage: IStorage) -> str: + result = "" + if not self._args: + result += pipe + '\n' + else: + for filename in self._args: + try: + with open(filename) as f: + result += f.read() + '\n' + except IOError as error: + result += "cat: '%s' No such file or directory\n" % filename + + return result[:-1] + + +class CommandEcho(ICommand): + """ Вывести на экран свой аргумент (или аргументы) """ + + @staticmethod + def name() -> str: + return "echo" + + def execute(self, pipe: str, storage: IStorage) -> str: + return ' '.join(map(str, self._args)) + + +class CommandWC(ICommand): + """ Вывести количество строк, слов и байт в файле """ + + @staticmethod + def name() -> str: + return "wc" + + def execute(self, pipe: str, storage: IStorage) -> str: + result = "" + if pipe: + result = "%d %d %d\n" % (pipe.count('\n') + 1, + len(pipe.split()), + len(pipe)) + else: + if not self._args: + raise RuntimeError("wc: must specify file names!") + + result_list = [] + for filename in self._args: + try: + with open(filename) as f: + data = f.read() + result_list.append("%d %d %d\n" % (data.count('\n') + 1, + len(data.split()), + len(data))) + except IOError as error: + result_list.append("wc: '%s' No such file or directory\n" + % filename) + + result = ''.join(result_list) + + return result[:-1] + + +class CommandPwd(ICommand): + """ Распечатать текущую директорию """ + + @staticmethod + def name() -> str: + return "pwd" + + def execute(self, pipe: str, storage: IStorage) -> str: + return os.getcwd() + + +class CommandExit(ICommand): + """ Выйти из интерпретатора """ + + @staticmethod + def name() -> str: + return "exit" + + def execute(self, pipe: str, storage: IStorage) -> str: + quit(0) + return "" + + +class CommandDefault(ICommand): + """ Что будет выполнено, если не одна команда не подойдет """ + + @staticmethod + def name() -> str: + return "" + + def execute(self, pipe: str, storage: IStorage) -> str: + process = run(self._args, stdout=PIPE, input=pipe, + shell=True, encoding="utf8", errors='ignore') + return process.stdout diff --git a/cli/evaluator.py b/cli/evaluator.py new file mode 100644 index 0000000..fe257a3 --- /dev/null +++ b/cli/evaluator.py @@ -0,0 +1,44 @@ +""" + Модуль для работы с переменными окружения +""" + +import re +from abc import ABCMeta, abstractmethod +from storage import IStorage + + +class IEvaluator(metaclass=ABCMeta): + """ Интерфейс для работы с переменными окружения """ + + @abstractmethod + def __init__(self, storage: IStorage, regexp: str): + """ regexp - регулярное выражение для поиска переменных окружения """ + """ storage - хранилище переменных окружения """ + pass + + @abstractmethod + def get_storage(self) -> IStorage: + """ вернуть хранилище переменных окружения """ + pass + + @abstractmethod + def evaluate_variables(self, expression: str) -> str: + """ Заменить переменные окружения на их значения из хранилища + возвращает новую строку с проведенной заменой """ + pass + + +class Evaluator(IEvaluator): + """ Реализация интерфейса для работы с переменными окружения """ + + def __init__(self, storage: IStorage, regexp: str): + self.__storage = storage + self.__regexp = regexp + + def get_storage(self) -> IStorage: + return self.__storage + + def evaluate_variables(self, expression: str) -> str: + return re.sub(self.__regexp, + lambda var: self.__storage[var.group()[1:]], + expression) diff --git a/cli/example.txt b/cli/example.txt new file mode 100644 index 0000000..65be4ed --- /dev/null +++ b/cli/example.txt @@ -0,0 +1 @@ +Some example text \ No newline at end of file diff --git a/cli/executor.py b/cli/executor.py new file mode 100644 index 0000000..3a439b3 --- /dev/null +++ b/cli/executor.py @@ -0,0 +1,37 @@ +""" + Модуль исполняющий команды интерпретатора +""" + +from abc import ABCMeta, abstractmethod +from interpreter import ICommandInterpreter +from pparser import IParser +from evaluator import IEvaluator + + +class IExecutor(metaclass=ABCMeta): + """ Интерфейс исполнителя выражений """ + + def __init__(self, command_interpreter: ICommandInterpreter, + parser: IParser, evaluator: IEvaluator): + self._command_interpreter = command_interpreter + self._parser = parser + self._evaluator = evaluator + + @abstractmethod + def execute_expression(self, expr: str) -> str: + """ Исполнить выражение и вернуть результат """ + pass + + +class Executor(IExecutor): + """ Реализация исполнителя выражений """ + + def execute_expression(self, expr: str) -> str: + tokens = self._parser.tokenize(expr) + commands = self._command_interpreter.retrieve_commands(iter(tokens)) + + result = "" + for command in commands: + result = command.execute(result, self._evaluator.get_storage()) + + return result diff --git a/cli/interpreter.py b/cli/interpreter.py new file mode 100644 index 0000000..42616e3 --- /dev/null +++ b/cli/interpreter.py @@ -0,0 +1,82 @@ +""" + Модуль интерпретатора команд +""" + +from abc import ABCMeta, abstractmethod +from typing import List, Type, Iterator, Generator +from tokens import IToken +from commands import ICommand +from evaluator import IEvaluator + + +class ICommandInterpreter(metaclass=ABCMeta): + """ Интерфейс парсера команд из потока токенов """ + + def __init__(self, command_types: List[Type[ICommand]], + delimiter: Type[IToken], default: Type[ICommand]): + self.__commands = dict() + # заполняем словарь комманд по их именам + for command in command_types: + if command != default: + self.__commands[command.name()] = command + + self.__delimiter = delimiter + self.__default = default + + @abstractmethod + def retrieve_commands(self, tokens: Iterator[IToken]) -> \ + Generator[ICommand, None, None]: + """ Преобразать список токенов в список команд """ + pass + + def default_command(self) -> Type[ICommand]: + """ Вернуть команду, которая будет выполнена, + если остальные команды не подойдут """ + return self.__default + + def delimiter(self) -> Type[IToken]: + """ Вернуть разделитель для команд внутри выражения """ + return self.__delimiter + + def retrieve_command(self, expr: str) -> Type[ICommand]: + """ Получить класс команды по имени команды """ + return self.__commands.get(expr, self.__default) + + +class CommandInterpreterWithStorage(ICommandInterpreter): + """ Реализация парсера команд из потока токенов """ + + def __init__(self, evaluator: IEvaluator, commands: List[Type[ICommand]], + delimiter: Type[IToken], default: Type[ICommand]): + super().__init__(commands, delimiter, default) + self.__evaluator = evaluator + + def retrieve_commands(self, tokens: Iterator[IToken]) -> \ + Generator[ICommand, None, None]: + token = next(tokens, None) + + if not token: + return + + if not token.is_possibly_command(): + raise RuntimeError("Unexpected token: " + token.get_value()) + else: + token.eval_vars(self.__evaluator) + if token.execute(self.__evaluator.get_storage()): + args = [] + if self.retrieve_command(token.get_value()) \ + == self.default_command(): + args.append(token.get_value()) + + for arg_token in tokens: + if arg_token.__class__ != self.delimiter(): + arg_token.eval_vars(self.__evaluator) + args.append(arg_token.get_value()) + else: + yield self.retrieve_command(token.get_value())(args) + yield from self.retrieve_commands(tokens) + break + else: + yield self.retrieve_command(token.get_value())(args) + else: + yield from self.retrieve_commands(tokens) diff --git a/cli/pparser.py b/cli/pparser.py new file mode 100644 index 0000000..10eadd9 --- /dev/null +++ b/cli/pparser.py @@ -0,0 +1,56 @@ +""" + Модуль парсера токенов +""" + +from abc import ABCMeta, abstractmethod +import itertools +import copy +import re +from typing import List, Type, Iterator +from tokens import IToken + + +class IParser(metaclass=ABCMeta): + """ Интерфейс парсера для токенов """ + + def __init__(self, token_types: List[Type[IToken]]): + # сортируем по приоритету + self._token_types = sorted(token_types, + key=lambda token_cls: token_cls.priority()) + + @abstractmethod + def tokenize(self, expression: str) -> List[IToken]: + """ Парсинг токенов из выражения """ + pass + + +class Parser(IParser): + """ Реализация парсера токенов """ + + def tokenize(self, expression: str) -> List[IToken]: + def tokenize_with_types(expr: str, token_types: Iterator[Type[IToken]]) \ + -> List[IToken]: + """ Рекурсивно парсим выражение по приориету токенов, + каждый токен разбивает выражение на два подвыражения и т.д. """ + tokens = [] + token_class = next(token_types, None) + if expr and token_class: + tokens_of_type = re.findall(token_class.regexp(), expr) + + # удаляем группы из регулярного выражения, чтобы не было + # лишней информации в подвыражениях + tokens_not_of_type = re.split(token_class.split_regexp(), expr) + + for not_token, token in itertools.zip_longest( + tokens_not_of_type, + tokens_of_type, + fillvalue=[]): + tokens += tokenize_with_types(not_token, + copy.copy( + token_types)) + if token: + tokens.append(token_class(token)) + + return tokens + + return tokenize_with_types(expression, iter(self._token_types)) diff --git a/cli/storage.py b/cli/storage.py new file mode 100644 index 0000000..4a0f966 --- /dev/null +++ b/cli/storage.py @@ -0,0 +1,40 @@ +""" + Модуль для работы с хранилищем переменных +""" + +from abc import ABCMeta, abstractmethod + + +class IStorage(metaclass=ABCMeta): + """ Интерфейс для хранения и доступа к переменным окружения """ + + @abstractmethod + def __contains__(self, item: str) -> bool: + """ Проверка наличия переменной в окружении """ + pass + + @abstractmethod + def __setitem__(self, key: str, value: str) -> None: + """ Добавить или обновить переменную окружения """ + pass + + @abstractmethod + def __getitem__(self, key: str): + """ Запросить значение переменной окружения """ + pass + + +class Storage(IStorage): + """ Реализация интерфейса для хранения и доступа к переменным окружения """ + + def __init__(self): + self.__storage = dict() + + def __contains__(self, key: str) -> bool: + return key in self.__storage + + def __setitem__(self, key: str, value: str) -> None: + self.__storage[key] = value + + def __getitem__(self, key: str) -> str: + return self.__storage[key] if key in self.__storage else "" diff --git a/cli/tests/test_command_cat.py b/cli/tests/test_command_cat.py new file mode 100644 index 0000000..99e492a --- /dev/null +++ b/cli/tests/test_command_cat.py @@ -0,0 +1,13 @@ +from unittest import TestCase + +from commands import CommandCat + + +class TestCommandCat(TestCase): + def test_execute(self): + command = CommandCat(['../example.txt']) + self.assertEqual(command.execute(""), "Some example text") + + command = CommandCat(['dsakfjhakdsljf']) + self.assertEqual(command.execute(""), "cat: 'dsakfjhakdsljf' " + "No such file or directory") diff --git a/cli/tests/test_command_echo.py b/cli/tests/test_command_echo.py new file mode 100644 index 0000000..b00f95d --- /dev/null +++ b/cli/tests/test_command_echo.py @@ -0,0 +1,9 @@ +from unittest import TestCase + +from commands import CommandEcho + + +class TestCommandEcho(TestCase): + def test_execute(self): + command = CommandEcho(['123', 'asd']) + self.assertEqual(command.execute(""), "123 asd") diff --git a/cli/tests/test_command_interpreterpy.py b/cli/tests/test_command_interpreterpy.py new file mode 100644 index 0000000..74ebd78 --- /dev/null +++ b/cli/tests/test_command_interpreterpy.py @@ -0,0 +1,31 @@ +from unittest import TestCase + +from commands import CommandCat, CommandExit, CommandEcho, CommandWC, \ + CommandPwd, CommandDefault +from interpreter import CommandInterpreterWithStorage +from pparser import Parser +from storage import Storage +from tokens import TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, \ + TokenAssignment, TokenWord + + +class TestCommandInterpreterWithStorage(TestCase): + def test_retrieve_commands(self): + storage = Storage(r'\$[^ \'\"$]+') + commands = [CommandCat, CommandEcho, CommandWC, + CommandPwd, CommandExit] + parser = Parser([TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, + TokenAssignment, TokenWord]) + interpreter = CommandInterpreterWithStorage(storage, commands, + TokenPipe, CommandDefault) + + tokens = parser.tokenize("echo 123 | exit | wc | cat | pwd | sdfxvc") + commands = list(interpreter.retrieve_commands(iter(tokens))) + + self.assertEqual(len(commands), 6) + self.assertEqual(commands[0].__class__, CommandEcho) + self.assertEqual(commands[1].__class__, CommandExit) + self.assertEqual(commands[2].__class__, CommandWC) + self.assertEqual(commands[3].__class__, CommandCat) + self.assertEqual(commands[4].__class__, CommandPwd) + self.assertEqual(commands[5].__class__, CommandDefault) diff --git a/cli/tests/test_command_wc.py b/cli/tests/test_command_wc.py new file mode 100644 index 0000000..185e22c --- /dev/null +++ b/cli/tests/test_command_wc.py @@ -0,0 +1,16 @@ +from unittest import TestCase + +from commands import CommandWC + + +class TestCommandWC(TestCase): + def test_execute(self): + command = CommandWC(['../example.txt']) + self.assertEqual(command.execute(""), "1 3 17") + + command = CommandWC([]) + self.assertEqual(command.execute('Some example text'), "1 3 17") + + command = CommandWC(['sdfsdfsdf']) + self.assertEqual(command.execute(''), "wc: 'sdfsdfsdf'" + " No such file or directory") diff --git a/cli/tests/test_executor.py b/cli/tests/test_executor.py new file mode 100644 index 0000000..eeb15ab --- /dev/null +++ b/cli/tests/test_executor.py @@ -0,0 +1,34 @@ +from unittest import TestCase + +from storage import Storage +from commands import CommandCat, CommandEcho, CommandWC, CommandPwd, \ + CommandExit, CommandDefault +from tokens import TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, \ + TokenAssignment, TokenWord +from interpreter import CommandInterpreterWithStorage +from pparser import Parser +from executor import Executor + + +class TestExecutor(TestCase): + def test_execute_expression(self): + storage = Storage(r'\$[^ \'\"$]+') + commands = [CommandCat, CommandEcho, CommandWC, + CommandPwd, CommandExit] + token_types = [TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, + TokenAssignment, TokenWord] + + executor = Executor(CommandInterpreterWithStorage + (storage, commands, TokenPipe, CommandDefault), + Parser(token_types)) + + self.assertEqual(executor.execute_expression('echo "Hello, world!"'), + 'Hello, world!') + + self.assertEqual(executor.execute_expression('FILE=../example.txt'), '') + + self.assertEqual(executor.execute_expression('cat $FILE'), + 'Some example text') + + self.assertEqual(executor.execute_expression('cat $FILE | wc'), + '1 3 17') diff --git a/cli/tests/test_parser.py b/cli/tests/test_parser.py new file mode 100644 index 0000000..a22f8a2 --- /dev/null +++ b/cli/tests/test_parser.py @@ -0,0 +1,16 @@ +from unittest import TestCase + +from pparser import Parser +from tokens import TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, \ + TokenAssignment, TokenWord + + +class TestParser(TestCase): + def test_tokenize(self): + parser = Parser([TokenInSingleQuotes, TokenInDoubleQuotes, TokenPipe, + TokenAssignment, TokenWord]) + + self.assertEqual(len(parser.tokenize("echo 123 | wc")), 4) + self.assertEqual(len(parser.tokenize("echo \"13 xc kdf akf\" | wc")), 4) + self.assertEqual(len(parser.tokenize("echo '13 xc kdf akf' | wc")), 4) + self.assertEqual(len(parser.tokenize("xcv=3234 nnn=123")), 2) diff --git a/cli/tests/test_storage.py b/cli/tests/test_storage.py new file mode 100644 index 0000000..cf1109d --- /dev/null +++ b/cli/tests/test_storage.py @@ -0,0 +1,12 @@ +from unittest import TestCase + +from storage import Storage + + +class TestStorage(TestCase): + def test_evaluate_variables(self): + storage = Storage(r'\$[^ \'\"$]+') + storage['a'] = '123' + self.assertEqual(storage.evaluate_variables("qwe$a"), "qwe123") + self.assertEqual(storage.evaluate_variables("$a$a$a"), "123123123") + self.assertEqual(storage.evaluate_variables("aaa aaa"), "aaa aaa") diff --git a/cli/tokens.py b/cli/tokens.py new file mode 100644 index 0000000..1eccc7f --- /dev/null +++ b/cli/tokens.py @@ -0,0 +1,232 @@ +""" + Модуль с токенамами с которыми работает интерпретатор +""" + +from abc import ABCMeta, abstractmethod +from storage import IStorage +from evaluator import IEvaluator + + +class IToken(metaclass=ABCMeta): + """ Интерфейс для токена, поиск которого будет выполняться при обработке + входных данных интерпретатора """ + + @abstractmethod + def __init__(self, regexp_result): + pass + + @staticmethod + @abstractmethod + def priority() -> int: + """ Приоритет поиска токена во входном выражении + должен вернуть число от 0 до inf """ + pass + + @staticmethod + @abstractmethod + def regexp() -> str: + """ Регулярное выражение для поиска токена + результат поиска вернется в метод __init__ """ + pass + + @staticmethod + @abstractmethod + def split_regexp() -> str: + """ Регулярное выражение для splitа выражения, + должно быть без групп """ + pass + + @abstractmethod + def set_value(self, value: str) -> None: + """ Установка значения токена """ + pass + + @abstractmethod + def get_value(self) -> str: + """ Получение значения токена """ + pass + + @abstractmethod + def eval_vars(self, evaluator: IEvaluator) -> None: + """ Замена переменных окружения на их значения, + если это необходимо """ + pass + + @abstractmethod + def is_possibly_command(self) -> bool: + """ Возможность токена содержать в себе команду """ + pass + + def execute(self, storage: IStorage) -> bool: + """ Код, который должен быть исполнен, + если токен станет командой, + возвращает true если его нужно исполнить как команду """ + return True + + +class TokenInSingleQuotes(IToken): + """ Токен для работы с одинарными кавычками в выражении """ + + def __init__(self, regexp_result): + self.__value = regexp_result if regexp_result else "" + + @staticmethod + def priority() -> int: + return 32 + + @staticmethod + def regexp() -> str: + return '[\']([^\']*)[\']' + + @staticmethod + def split_regexp() -> str: + return '[\'][^\']*[\']' + + def set_value(self, value: str) -> None: + self.__value = value + + def get_value(self) -> str: + return self.__value + + def eval_vars(self, evaluator: IEvaluator) -> None: + pass + + def is_possibly_command(self) -> bool: + return True + + +class TokenInDoubleQuotes(IToken): + """ Токен для двойных кавычек в выражении """ + + def __init__(self, regexp_result): + self.__value = regexp_result if regexp_result else "" + + @staticmethod + def priority() -> int: + return 16 + + @staticmethod + def regexp() -> str: + return '\"([^\"]*)\"(?=(?:[^\']*\'[^\']*\')*[^\']*$)' + + @staticmethod + def split_regexp() -> str: + return '\"[^\"]*\"(?=(?:[^\']*\'[^\']*\')*[^\']*$)' + + def set_value(self, value: str) -> None: + self.__value = value + + def get_value(self) -> str: + return self.__value + + def eval_vars(self, evaluator: IEvaluator) -> None: + self.set_value(evaluator.evaluate_variables(self.get_value())) + + def is_possibly_command(self) -> bool: + return True + + +class TokenPipe(IToken): + """ Токен для пайпа в выражении""" + + def __init__(self, regexp_result): + pass + + @staticmethod + def priority() -> int: + return 48 + + @staticmethod + def regexp() -> str: + return '\\|' + + @staticmethod + def split_regexp() -> str: + return '\\|' + + def set_value(self, value: str) -> None: + pass + + def get_value(self) -> str: + return '|' + + def eval_vars(self, evaluator: IEvaluator) -> None: + pass + + def is_possibly_command(self) -> bool: + return False + + +class TokenAssignment(IToken): + """ Токен для работы с заданием переменных окружения в выражении """ + + def __init__(self, regexp_result): + self.__var = regexp_result[0] if regexp_result else "" + self.__val = regexp_result[1] if regexp_result else "" + + @staticmethod + def priority() -> int: + return 64 + + @staticmethod + def regexp() -> str: + return '([^ =]+)=([^ ]*)' + + @staticmethod + def split_regexp() -> str: + return '[^ =]+=[^ ]*' + + def set_value(self, value: str) -> None: + buffer = value.split('=') + if len(buffer) == 2: + self.__var = buffer[0] + self.__val = buffer[1] + else: + raise RuntimeError("Invalid value for TokenAssignment: " + value) + + def get_value(self) -> str: + return self.__var + '=' + self.__val + + def eval_vars(self, evaluator: IEvaluator) -> None: + self.__val = evaluator.evaluate_variables(self.__val) + self.__var = evaluator.evaluate_variables(self.__var) + + def is_possibly_command(self) -> bool: + return True + + def execute(self, storage: IStorage) -> bool: + """ Сохранить переменную в окружении """ + storage[self.__var] = self.__val + return False + + +class TokenWord(IToken): + """ Токен для парсинга прочих ключевых слов, которые не подошли под + остальные токены """ + + def __init__(self, regexp_result): + self.__value = regexp_result if regexp_result else "" + + @staticmethod + def priority() -> int: + return 128 + + @staticmethod + def regexp() -> str: + return '[^ \'\"|]+' + + @staticmethod + def split_regexp() -> str: + return '[^ \'\"|]+' + + def set_value(self, value: str) -> None: + self.__value = value + + def get_value(self) -> str: + return self.__value + + def eval_vars(self, evaluator: IEvaluator) -> None: + self.set_value(evaluator.evaluate_variables(self.get_value())) + + def is_possibly_command(self) -> bool: + return True