# Úloha: Výrazy I
Implementujte aplikaci pro správu výrazů.
Aplikace čte příkazy z příkazové řádky a výstup vypisuje na standardní výstup.

Pro toto zadání není třeba ošetřovat chybové stavy a lze předpokládat pouze validní vstup.

Výsledné řešení by naopak mělo:
- Vhodně využívat chytrých ukazatelů a standardních kontejnerů.
- Rozumě oddělovat IO a aplikační logiku.
- Využívat rozdělení do více souborů, včetně vhodného použití makroprocesoru (guards - #ifndef, #define, #endif).
- Mít rozumný objektový návrh, třídy: Node, UnaryNode, BinaryNode, AdditionNode, SubtractionNode, ...
- Být rozumě efektivní, tedy například neprocházet všechny hodnoty, pokud to není nutné.
  Využití move semantiky není nutné.

Následující odstavce popisují jednotlivou funkcionalitu.
Zadání je možné přešít celé najednou, nebo postupovat postupně.
Postupnou implementací si můžete simulovat nekonfliktně rostoucí zadání.

V průběhu řešení můžete předpokládat validní vstup.
Není tedy třeba řešit chybové stavy, nebo zacyklené výrazy.

## Základní operace
Aplikace podporuje následující příkazy:
- `set {name} = {expression}` : uloží výraz v prefix notaci pod dané jméno.
- `unset {name}` : odstraní výraz.
- `evaluate {name}` : vyhodnotí výraz a vypíše výsledek.
- `print {name}` : vypíše výraz v infix notaci.
Hodnota `{name}` odpovídá jménu výrazu a může obsahovat pouze znaky a-z a `_`.

Příklad vstupu:
```
# Vytvoříme nový výraz, 'area = a * a'.
# Absenci definice výrazu 'a' nemusíme řešit.
set area = * a a
# Vypíše výraz.
print area
> a * a
# Vytvoříme nový výraz.
set a = 5
# Vyhodnotíme výraz 'a'.
evaluate a
> 5
# Vyhodnovíme výraz 'area'.
# Zatím není třeba řešit situaci, kdy 'a' nebude definováno.
evaluate area
> 25
```

Výrazy je nutné reprezentovat pomocí tříd vhodné hierarchie (polymorphismus).
Jednotlivé třídy musí bý schopny reprezentovat:
- nezáporné celé číslo
- binární operace `+`, `-`, `*` a `/`
- jméno výrazu

## Operace přejmenování výrazu
Přijdete podporu operace pro přejmenování výrazu: `rename {source} {target}`.
Operace změní jméno výrazu a to včetně všech míst jeho použití.
Pokud výraz daného jména není definován, operace nic neprovede.

Příklad použití:
```
set area = * a a
# Přejmenujeme výraz `area` na `square_area`.
rename area square_area
# Obsah výrazu samotného se nezměnil.
print square_area
> a * a
```

Příklad použití:
```
set area = * a a
set a = 5
# V tomto případě musí dojít i ke změně pojmenování ve výrazu `area`.
rename a b
print area
> b * b
```

Příkaz je možné použít i tehdy, pokud nebyl ještě výraz explicitně vytvořen.
Stačí, aby byl výraz někde použitý.
```
set area = * a a
# Zde přejmenováváme `a` na `b`, aniž by `a` bylo samostatně vytvořeno.
rename a b
print area
> b * b
```

Pokud cílový výraz již existuje, je nahrazen.
```
set area = * a a
set a = 5
set b = 3
# Vyhodcenoví výrazu `area`, zde dle očekávání.
evaluate area
> 25
# Přejmenujeme `a` na `b`.
# Hodnota výrazu `b` se nahradí hodnotou výrazu `a`.
rename a b
print b
> 5
print area
> b * b
evaluate area
> 25
```

## Operace kopírování výrazu
Přidejte podporu pro příkaz kopírování výrazu pro nové jméno: `copy {source-name} {target-name}`.
Příkaz zkopíruje výraz a uloží ho pod novým jménem.
Původní a nový příkaz spolu nesmí sdílet data.
Modifikace původného výrazu, tedy nezmění jeho kopii.

Příklad použití:
```
set area = * a a
copy area square_area
print square_area
> a * a
# Upravíme původní výraz.
set area = * b b
# Zkopírovaný výraz se nezmění.
print square_area
> a * a
```