Domácí úkoly 3:



From Coq Require Import Lists.List. Import ListNotations.
From Coq Require Import Strings.String.

V následujících úkolech bude využita níže uvedená implementace aritmetických výrazů.
Jak vidíte, ve výrazech se mohou vyskytovat proměnné. Tyto proměnné jsou reprezentovány datovým typem var, který je zde implementován pomocí datového typu string. Typ state reprezentuje aktuální přiřazení hodnot proměnným. Pro jednoduchost jsou proměnným přiřazovány pouze hodnoty typu nat.

Definition var := string.
Definition var_eqb : varvarbool := String.eqb.

Definition state := varnat.

Definition empty_st (_ : var) := 0.

Definition update_state (st : state) (x : var) (v : nat) :=
    fun x'if var_eqb x x' then v else st x'.

Notation "x '!->' v ';' st" := (update_state st x v)
                              (at level 100, v at next level, right associativity).

Notation "x '!->' v" := (x !-> v ; empty_st) (at level 100).

Definice aritmetických výrazů:

Inductive aexp : Type :=
  | ANum (n : nat)
  | AId (x : var)
  | APlus (a1 a2 : aexp)
  | AMinus (a1 a2 : aexp)
  | AMult (a1 a2 : aexp).

Definition X : var := "X"%string.
Definition Y : var := "Y"%string.

Definice koercí a uživatelsky definovaných notací pro usnadnění zápisu aritmetických výrazů:

Coercion AId : var >-> aexp.
Coercion ANum : nat >-> aexp.

Declare Custom Entry com.
Declare Scope com_scope.

Notation "<{ e }>" := e (at level 0, e custom com at level 99) : com_scope.
Notation "( x )" := x (in custom com, x at level 99) : com_scope.
Notation "x" := x (in custom com at level 0, x constr at level 0) : com_scope.
Notation "x + y" := (APlus x y) (in custom com at level 50, left associativity).
Notation "x - y" := (AMinus x y) (in custom com at level 50, left associativity).
Notation "x * y" := (AMult x y) (in custom com at level 40, left associativity).

Open Scope com_scope.

Funkce pro vyhodnocení hodnoty aritmetického výrazu:

Fixpoint aeval (st : state) (a : aexp) : nat :=
  match a with
  | ANum nn
  | AId xst x
  | <{a1 + a2}> ⇒ (aeval st a1) + (aeval st a2)
  | <{a1 - a2}> ⇒ (aeval st a1) - (aeval st a2)
  | <{a1 × a2}> ⇒ (aeval st a1) × (aeval st a2)
  end.


Příklad A: 1 bod

Staré kalkulačky HP, programovací jazyky jako Forth a Postscript i abstraktní stroje jako Java Virtual Machine všechny vyhodnocují aritmetické výrazy pomocí zásobníku. Například výraz
       (2*3)+(3*(4-2))
by byl zapsán jako
       2 3 * 3 4 2 - * +
a vyhodnocen následujícím způsobem (kde program, který je vyhodnocován, je zobrazen vpravo a obsah zásobníku vlevo):
      [ ]           |    2 3 * 3 4 2 - * +
      [2]           |    3 * 3 4 2 - * +
      [3, 2]        |    * 3 4 2 - * +
      [6]           |    3 4 2 - * +
      [3, 6]        |    4 2 - * +
      [4, 3, 6]     |    2 - * +
      [2, 4, 3, 6]  |    - * +
      [2, 3, 6]     |    * +
      [6, 6]        |    +
      [12]          |
Cílem tohoto cvičení je napsat malý kompilátor, který přeloží výrazy typu aexp na instrukce zásobníkového stroje.
Instrukční sada našeho zásobníkového stroje se bude skládat z následujících instrukcí:
  • SPush n: Uloží číslo n na zásobník.
  • SLoad x: Načte hodnotu proměnné x z aktuálního stavu a uloží ji na zásobník.
  • SPlus: Odebere z vrcholu zásobníku dvě čísla, sečte je a výsledek uloží na zásobník.
  • SMinus: Podobné, ale odečte první číslo od druhého.
  • SMult: Podobné, ale čísla vynásobí.

Inductive sinstr : Type :=
| SPush (n : nat)
| SLoad (x : var)
| SPlus
| SMinus
| SMult.

Napište funkci, která vyhodnotí programy ve výše popsaném zásobníkovém jazyce. Tato funkce dostane jako vstup aktuální stav přiřazující hodnoty proměnným, počáteční obsah zásobníku, který je reprezentován jako seznam tvořený čísly (vrchol zásobníku odpovídá začátku seznamu), a program reprezentovaný jako seznam tvořený jednotlivými instrukcemi. Funkce by měla vrátit jako výsledek seznam reprezentující obsah zásobníku po vykonání daného programu. Otestujte vaši funkci na níže uvedených příkladech.
Všimněte si, že není specifikováno, co se má udělat v případě, kdy se má provést instrukce SPlus, SMinus nebo SMult a zásobník obsahuje méně než dva prvky. Z určitého pohledu se dá říci, že na tom nezáleží, protože korektní kompilátor nikdy nevyprodukuje takový chybně utvořený program. Pro jednoduchost bude asi nejsnadnější takovou problematickou instrukci přeskočit a pokračovat vykonáváním následujících instrukcí.

Fixpoint s_execute (st : state) (stack : list nat)
                   (prog : list sinstr)
                 : list nat
  . Admitted.

Check s_execute.

Example s_execute1 :
     s_execute empty_st []
       [SPush 5; SPush 3; SPush 1; SMinus]
   = [2; 5].
Admitted.

Example s_execute2 :
     s_execute (X !-> 3) [3;4]
       [SPush 4; SLoad X; SMult; SPlus]
   = [15; 4].
Admitted.


Příklad B: 1 bod

Dále napište funkci, která zkompiluje výraz typu aexp do podoby programu pro zásobníkový stroj. Výsledek provedení daného programu by měl být stejný jako uložit hodnotu daného výrazu na vrchol zásobníku pomocí instrukce push.

Fixpoint s_compile (e : aexp) : list sinstr
  . Admitted.

Poté, co definujete funkci s_compile, dokažte následující tvrzení, abyste ověřili, že vaše funkce funguje správně.

Example s_compile1 :
  s_compile <{ X - (2 × Y) }>
  = [SLoad X; SPush 2; SLoad Y; SMult; SMinus].
Admitted.


Příklad C: 1 bod

Vykonávání instrukcí zásobníkového stroje je možné v následujícím smyslu rozložit: vykonat zásobníkový program p1 ++ p2 je to samé jako vykonat instrukce p1, vzít výsledný obsah zásobníku a vykonat instrukce p2 s tímto obsahem zásobníku jako s počátečním stavem. Dokažte, že to tak opravdu je.

Theorem execute_app : st p1 p2 stack,
  s_execute st stack (p1 ++ p2) = s_execute st (s_execute st stack p1) p2.
Proof.
Admitted.



Příklad D: 2 body

Nyní dokažte korektnost kompilátoru navrženého v příkladu B. Začněte tím, že dokážete následující lemma. Pokud se ukáže, že je to příliš složité, zvažte, zda není možné nějak zjednodušit vaši implementaci funkcí s_execute a s_compile.

Lemma s_compile_correct_aux : st e stack,
  s_execute st stack (s_compile e) = aeval st e :: stack.
Proof.
Admitted.

Hlavní teorém by pak měl být velmi snadno odvoditelným důsledkem výše uvedeného lemmatu.

Theorem s_compile_correct : (st : state) (e : aexp),
  s_execute st [] (s_compile e) = [ aeval st e ].
Proof.
Admitted.



This page has been generated by coqdoc