# Haskell: ДЗ 3 -- Трансформеры монад и IO
Данное домашнее задание нацелено на обучение навыкам использования стандартных монад, таких как `Reader`, `State`, `IO`, а также трансформеров монад. Умение использовать эти инструменты крайне необходимо для написания больших и полноценных приложений.
В данном домашнем задании от Вас требуется реализовать интерпретатор уменьшенной версии `sh`.
Каждое задание требует реализовать какую-то часть функциональности, в конце же Вам будет необходимо покрыть все тестами.
При возникновении вопросов о семантике обращайтесь к документации `sh`, если не оговорено иное.
## Задание 1. Базовые конструкции
Реализуйте базовые конструкции языка:
* присвоение переменных
* обращение к аргументам командной строки через `$0`, `$1`, `$2`...
* одинарные кавычки (single quotes)
Cкобки `(` и `)` считаются символами только находясь в кавычках. В любом другом случае, они являются управляющими конструкциями.
Необходимо поддержать конструкцию `\$` и `\\` обозначающие символ `$` и `\` соответственно.
`$` должен считаться символом, если он находится в одинарных кавычках, иначе он может использоваться только в конструкции`\$`.
`\` считается символом находясь в одинарных кавычках, иначе он может использоваться только в конструкциях `\$` или `\\`. В любых других случаях должна выдаваться ошибка парсинга.
Для парсинга Вы можете пользоваться библиотекой `megaparsec`
Также в рамках данного задания, Вам требуется сделать запуск Вашего интерпрератора из командной строки.
Так, например, если Ваш исполняемый файл называется `mySh` и Вы запускаете его с аргументами:
`./mySh ~/script.sh 1 2 "aa aa"`
он должен выполнить скрипт `~/script.sh`, с аргументами `1 2 "aa aa"`.
Пример скрипта, который должен выполняться после выполнения этого задания, если Вы уберете из кода комментарии (такие примеры будут в каждом задании):
```bash
a='aaa $1 bb'
# a equals to 'aaa $1 bb' string
b=$2
# b equals to the second passed argument
a=b
# a equals to 'b'
c=$b
# c equals to the second passed argument
```
## Задание 2. Двойные кавычки
Реализуйте поддержку двойных кавычек.
Каждая подстрока, удовлетворяющая регулярному выражению:
`$[a-zA-Z_0-9]+` считается ссылкой на переменную или аргумент. Переменную `$_` поддержитвать не нужно.
Также не забудьте поддержать конструкцию `\"`.
```bash
foo=mystring
bla="My string: $1 and $2 -> $foo"
# bla="My string: kek and bek -> mystring"
# If `kek bek` was passed as an argument
```
## Задание 3. Встроенные команды
Поддержите в вашем интерпретаторе встроенные команды:
* `read <varname1> <varname2> ...` - считывание строки
* `echo <expr1> <expr2> ... ` - вывод на консоль
* `pwd` - вывести на консоль путь к текущей рабочей директорию
* `cd path` - изменить рабочую директорию
* `exit` - выход из скрипта
Также необходимо реализовать
Команда `read` может принимать произвольное количество переменных.
Если первыый аргумент `echo` `-n`, тогда `\n` печататься не должен.
Команда `cd` принимает ровно один аргумент и ведет себя также как `cd` из `sh`.
Если при запуске вашего исполняемого файла произошла какая-то синтаксическая ошибка в скрипте или выход с ненулевым кодом возврата - это должно сообщаться пользователю.
```bash
echo 'Arguments:' $1 $2 $3
bla=34
echo 'bla:' $bla
read i1
read i2
echo 'Read two lines:' "$i1 and $i2"
read n m
echo -n Read a line with a word $n
echo " and" rest of line: $m
cd ..
pwd
exit 0
echo "not reachable"
```
## Задание 4. Внешние команды
Реализуйте поддержку внешних команд `sh`.
Внешние команды вызываются в формате `cmd arg1 arg2 ...`.
Также необходимо реализовать поддержку встроенного вызова (inline call), который имеет вид `$(cmd1; cmd2;...)`
Для большей информации о внешних командах можно прочитать [здесь](https://www.geeksforgeeks.org/internal-and-external-commands-in-linux/).
Например, команда `pushd` не является внешней (хотя она и не является встроенной в наш язык).
Вы можете использовать утилиту `type` в своем `shell`, чтобы узнать, является ли команда внешней или нет.
```bash
cat $1
bla=$(ls)
file=$1
foo="line count in $file: $(wc -l "$file")"
echo "$bla ;; $foo ;; $non_existing"
```
Обратите внимание, что если в качестве аргумента скрипту выше передали `"foo bar"` (то есть два слова разделенных пробелом), `cat` будет вызвана с двумя аргументами.
Чтобы передать `"foo bar"` как один аргумент, необходимо использовать синтаксис с двойными кавычками: `cat "$1"`
## Задание 5. Больше встроенных команд: if/while
Реализуйте `if` and `while`.
`if` в качестве условия принимает список команд, разделенных `;`, выполняет эти команды, и затем выполняет первую ветку кода, если команды завершились с нулевым кодом возврата, иначе вторую (если она есть).
В условии `while` может находиться тоже самое, что и в `if`.
Тело `while` продолжает выполняться до тех пор, пока команды команды завершаются с нулевым кодом возврата.
```bash
while read l
do
echo "Line: $l"
done
if true; then
echo "true"
fi
if false; then
echo "won't happen"
fi
```
Обратите внимание, что `true` и `false` это стандартные внешние команды в UNIX.
Обратите внимание, что команды в условии `if` и `while` могут завершаться переводом строки.
Для большего понимания обращайтесь к документации `sh`.
## Задание 6*
На данный момент, Вы реализовали интерпретатор, который работает в монаде IO или в трансформере монад, где присутствует IO.
Допустим у нас есть следующий скрипт:
```bash
cd /usr
rm bin/ls
```
Мы хотим протестировать его, но не хотели бы запускать его на собственной машине.
Мы хотели бы иметь возможность запускать такие "in-memory", так чтобы они не производили изменений в нашей системе.
В данном задании от Вас требуется написать "чистый" эмулятор Вашего интерпретатора.
Эмулятор должен принимать структуру директорий (datatype описывающий ее, не привязанный к операционно системе), скрипт и данные которые являются входом с консоли, и запускать переданный скрипт.
Эмулятор должен интерпретировать скрипт без какого либо `IO`, после чего возвращая структуру директорий после выполнения, а также данные, которые были выведены в консоль этим скриптом.
После этого, Вам необходимо реализовать тесты используя этот эмулятор для вашего интерпретатора, которые должны находиться в папке `test/` и запускаться командной `stack test`.
В этих тестах должны поддерживаться следующие команды:
* встроенные команды
* `rm`, `ls`, `cat`
Обратите внимание, что в тестах можно не поддерживать полную семантику всех этих команд (например, не поддерживать флаги).
**Примечание**:
Чтобы реализовать это задание, Вам придется абстрагировать Ваш интерпретатор в `mtl` стиле и избавиться от конкретной монады в типе.
Tо есть тип вашего интерпретатора должен стать каким-то таким:
```haskell
interpret :: (MonadState Vars m, MonadConsole m) => m ()
```
где `MonadConsole` - это ваш тайпкласс, который содержит операции работы с вводом/выводом в консоль.
(Это всего лишь пример, на самом деле у Вас вероятно будет несколько больше констреинтов и они могут отличаться).
Также Вам может могут понадобиться `ExceptT` [отсюда](http://hackage.haskell.org/package/mtl-2.2.2/docs/Control-Monad-Except.html#t:ExceptT) или `CatchT` [отсюда](http://hackage.haskell.org/package/exceptions-0.10.1/docs/Control-Monad-Catch-Pure.html) (а также тайпклассы для них: `MonadError` и `MonadThrow`/`MonadCatch`), которые можно использовать в "чистом" эмуляторе.
## Бонус
Помимо описанного вы можете реализовать поддержку pipe'ов вашим интерпретатором.
Пример скрипта, который должен будет выполняться:
```bash
ls | while read ln
do
echo ">> $ln"
done | grep -v '.hs$'
```
За это задание не будут выставлены доп. баллы, но если за проверку других заданий будут минусы, до 3 минусованных баллов вам будет прощено.
Мотивация реализовывать эту часть, однако, не в получении баллов, а just for fun, если вы также как один из авторов регулярно пользуютесь shell'ом и знаете что без пайпов shell -- штука бесполезная.
## Формат решений
Ваше решение должно целиком содержаться в папке `hw3` корня репозитория.
В папке должен быть `README.md` с перечислением заданий которые вы выполнили. Также Вы можете оставить комментарием для проверяющего в свободной форме, но это необязательно.
Задание должно компилироваться с помощью `stack build --fast` и запускаться с помощью `stack exec -- mini-shell` из папки `hw3` (для чего нужно указать имя executable `mini-shell` в `.cabal` файле).
К заданиям еще не были написаны тестовые скрипты. Во время исполнения задания Вам предлагается придумать набор таких тестов самостоятельно. Рекомендуется добавлять такие скрипты посредством pull request'а в https://github.com/georgeee/haskell-itmo-2019-hw3.
Вы также можете использовать скрипты в этом репозитории для тестирования своих решения.
Если в этом репозитории на момент проверки этого ДЗ для каждого задания будет достаточно тестов, проверяющие не будут утруждать себя сочинением новых. В обратном случае они приступят к этому после дедлайна и за решения, которые упадут на том или ином скрипте (соответствующем условиям) будут снижены баллы.
## Рекомендации по выполнению
Пожалуйста, ознакомьтесь со слайдами лекций 6 и 7.
Полезно вспомнить про рекомендации [со слайдов](https://slides.com/fp-ctd/lecture-7#/27) и описанные следом анти-паттерны.
В частности, мы будем снижать баллы за комбинации `ExceptT IO` и `StateT IO`.
Если вы используете `ReaderT Ctx IO`, пожалуйста оборачивайте в `IORef` только те части `Ctx`, которые могут быть изменены в ходе исполнения. Например:
```haskell
data BadCtxImpl = BadCtxImpl
{ bciLog :: Text -> IO ()
, bciVars :: Map Int Text
}
type BadCtx = IORef BadCtxImpl
data GoodCtx = GoodCtx
{ gcLog :: Text -> IO ()
, gcVars :: IORef (Map Int Text)
}
```