---
# System prepended metadata

title: university shell design project

---

## Требования
- функциональные требования
    - Ввод и исполнение команд
        - `cat [FILE]` — вывести на экран содержимое файла;
        - `echo` — вывести на экран свой аргумент (или аргументы);
        - `wc [FILE] ` — вывести количество строк, слов и байт в файле;
        - `pwd` — распечатать текущую директорию;
        - `exit` — выйти из интерпретатора.
    - одинарные и двойные кавычки (full and weak quoting);
        - внутри одинарных кавычек значение строки берется как есть
        - внутри двойных кавычек доступен  слеш “\” и подставляются значения переменных
            - “path is $PATH” -> `path is /etc/passwd`
    - окружение (команды вида “имя=значение” для объявления переменных), оператор $ для подстановки значения переменной;
    - вызов внешней программы, если введено что-то, чего интерпретатор не знает;
    - пайплайны (оператор «|»), перенаправляющий вывод одной команды на вход другой
## Архитектурные требования
- легко добавлять новые команды;
- четкое разграничение ответственности между элементами архитектуры;
    - это не должен быть просто клубок классов, требуется некая компонентная структура;
- наличие словесного архитектурного описания.
## Функциональные тесты
- Следующие команды выполнимы
    ```bash
    > echo "Hello, world!"
    Hello, world!
    ```
    ```bash
    > FILE=example.txt
    > cat $FILE
    Some example text
    ```
    ```bash
    > cat example.txt | wc
    1 3 18
    ```
    ```bash
    > echo 123 | wc
    1 1 3
    ```
    ```bash
    > x=ex
    > y=it
    > $x$y
    ```
    ```bash
    > echo “path is $PATH”
    path is /some/path
    ```
    ```bash
    > echo ‘path is $PATH’
    path is $PATH
    ```
    ```bash
    > main.exe
    Hello, World!
    ```
    
    ```bash
    > cat file | exit
    ```
- Легко добавить новую команду

## Описание архитектуры
Главным классом приложения является класс `InterpretatorApp`, который содержит входную точку в приложение интерпретатора. Поле `environment` содержит все переменные, обьявленные пользователем. Работа класса `InterpretatorApp` может быть описана следующим псевдокодом:
```java
void main() {
    while (true) {
        line = input();
        if (Parser.isAssignment(line)) {
            this.set(Parser.parseAssignment(line));
        } else {
            cmd = Parser.parseLine(line);
            res, exited = cmd.exec();
            print(res);
            if (exited)
                break;
        }
    }
}
```
В процессе работы командная строка вводится пользователем в переменную `line`, интерпретатор парсит ее с помощью метода `parseLine` статического класса `Parser`, который возвращает обьект, позволяющий выполнить команду, и затем эта команда исполняется и ее вывод отображается пользователю. Если командная строка - присваивание переменной, то переменная и ее значение заносятся в `InterpreterApp.environment` с помощью метода `InterpreterApp.set`.

Статический класс `Parser` реализует функциональность разбора командной строки и конструирования команды по ней. Метод `parse` принимает командную строку и возвращает экземпляр `Executable`, выполняющий данную команду. Методы `isAssignment` и `parseAssignment` используются для проверки, что введеная строка обьявляет переменную, и выделения названия этой переменной и ее значения. Приватные методы класса `Parser` нужны для разбора строки и подстановки значений переменных. Метод `parseArbitaryQuotedString` принимает строку с произвольными кавычками и обрабатывает ее соответственно. Для работы с строкой с двойными кавычками используется метод `parseStringWithDoubleQuotes`.

В `cmd` записывается результат метода `Parser.parse`. Эта переменная хранит экземпляр интерфейса `Executable`, а значит является либо экземпляром `PipedCommand`, либо экземпляром одного из наследников абстрактного класса `AbstractCommand`. Далее вызывается метод `exec` этого обьекта, выполняющий соответствующую команду. Для поддержки пайплайнов класс `AbstractCommand` содержит метод `pipe`, который принимает вывод предыдущей команды и так же, как и `execute` возвращает результат текущей команды. Тогда метод `PipedCommand.exec` выглядит следующим образом:
```java
(String, bool) exec() {
    out, exited = this.commands.exec();
    for cmd in this.commands[1:] {
        if (exited)
            break;
        out, exited = cmd.pipe(out);
    }
}
```
Сами команды реализуются в виде наследников абстрактного класса `AbstractCommand` с помощью реализации методов `exec` и `pipe` для соответствующего поведения. Дефолтное поведение для команды определено в классе `AbstractCommand` подобным образом:
```java
(String, bool) exec() {
    return ("", false);
}
(String, bool) pipe(input) {
    return this.exec();
}
```
Таким образом, можно не переопределять метод `pipe`, так как будет использован переопределенный метод `exec`, что полезно, например, для команды `pwd`.
Команда `echo` будет выглядеть так:
```java
class Echo : AbstractCommand {
    override (String, bool) exec() {
        res = " ".join(this.arguments);
        return (res, false);
    }
    override (String, bool) pipe(input) {
        res = " ".join(this.arguments + [input]);
        return (res, false);
    }
}
```
Для добавления новой команды достаточно реализовать нового наследника класса `AbstractCommand` и добавить конструктор класса новой команды в класс `Parser`.

Приведенная архитектура проходит все описанные выше функциональные требования, позволяет легко добавлять новые команды и содержит модули с четко разделенной ответственностью.