Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions cli/README.md
Original file line number Diff line number Diff line change
@@ -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)
Binary file added cli/class_diagram.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
52 changes: 52 additions & 0 deletions cli/cli.py
Original file line number Diff line number Diff line change
@@ -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()
139 changes: 139 additions & 0 deletions cli/commands.py
Original file line number Diff line number Diff line change
@@ -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
44 changes: 44 additions & 0 deletions cli/evaluator.py
Original file line number Diff line number Diff line change
@@ -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)
1 change: 1 addition & 0 deletions cli/example.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Some example text
37 changes: 37 additions & 0 deletions cli/executor.py
Original file line number Diff line number Diff line change
@@ -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
82 changes: 82 additions & 0 deletions cli/interpreter.py
Original file line number Diff line number Diff line change
@@ -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)
Loading