Smyslem celého projektu je procvičit si programovací techniky, které se probírají na přednáškách a cvičeních předmětu Překladače. Je třeba, aby projekt splňoval všechny náležitosti uvedené na této stránce. Je to do značné míry tvořivá činnost, žádné dva projekty by proto neměly být stejné! Naprogramujete jednoduchý interpretační překladač.
Lexikální analýza
Tato část projektu je součástí zápočtu, má být odevzdána do konce listopadu daného akademického roku. Dejte si záležet, protože na této části projektu postavíte celý zbytek. Nepodceňujte to, zvláště pokud jste zatím nepracovali na žádném podobně rozsáhlém projektu.
Co dělat, jak postupovat:
- Vymyslete si jakýkoliv jazyk. Ke konci této stránky máte ukázky pro inspiraci, může jít o značně zjednodušený běžný jazyk typu Pascal nebo C, nebo třeba jazyk, jehož příkazy mají grafický výstup (inspirujte se například u programů Logo a Karel, což jsou jednoduché programovací jazyky pro děti). Základní požadavky na jazyk jsou uvedeny níže.
- Určete základní strukturu jazyka - jaké příkazy je možné zadávat, co se dá napsat do jejich parametrů, zda a jak se deklarují proměnné, jaká jsou klíčová slova, jaké závorky se kde používají, jak začíná a končí program, zda použijete určitý symbol (obvykle středník) pro ukončení příkazu nebo oddělení dvou příkazů (to není totéž z hlediska syntaxe), případně jak vypadají podmíněné příkazy, apod.
- Vytvořte nejdřív symbolickou lexikální analýzu jazyka, tj. na papír nebo ve Wordu/Excelu vytvořte potřebné struktury a tabulku, postup je v druhé kapitole ve skriptech.
- Programujte (v některém jazyce, který vcelku ovládáte, například v Pascalu, Delphi, C, C++, apod.) podle struktur, které jste vytvořili v předchozím bodu. Nezapomeňte na komentáře a podrobná chybová hlášení, obojí vám usnadní ladění programu.
- Ve vlastním zájmu (kvůli ladění i z důvodu snadnější návaznosti další části projektu) naprogramujte lexikální analýzu tak, aby šlo o funkci či proceduru, která při každém svém zavolání načte ze vstupu právě jeden symbol (a vypíše ho do souboru, okna či na obrazovku) a je cyklicky volána, dokud nedojde na konec zdroje.
- Při programování je nutné používat programovací techniky, které probíráme, protože účelem projektu je procvičit si učivo. Zvláště ad-hoc techniky nebudou akceptovány.
-
Odevzdejte mailem:
- zdrojový kód vašeho programu,
- spustitelný soubor, který je funkční,
- dokumentaci (informaci o tom, co program dělá, jaké příkazy přijímá, co jsou jejich parametry, zda je jazyk case-sensitive, dále všechny struktury a tabulky vytvořené pro lexikální analýzu např. ve Wordu či Excelu nebo naskenované či vyfocené),
- ukázkový vstup, na kterém jste překladač testovali.
V této fázi bude mít program zjednodušený výstup, zatím stačí, aby prostě vypsal do okna nebo na obrazovku seznam všech symbolů, které se v zadaném zdroji vyskytují, aby byla lexikální analýza důkladně odladěná.
Náležitosti jazyka:
- jazyk obsahuje alespoň 5 klíčových slov, většina z nich o délce větší než 2, jedná se o řetězce písmen podle volby studenta (podle zvoleného jazyka),
- čísla alespoň přirozená, a to i víceciferná,
- proměnné, názvy proměnných mohou být jakékoliv, začínají písmenem (pozor, nevyžadujte jako první znak něco konkrétního), následují písmena nebo číslice, délka větší nebo rovna 1,
- musí existovat i možnost, jak do proměnné načíst hodnotu od uživatele (něco jako příkaz read, nacti, scan), dále možnost pro výpis momentální hodnoty proměnné (write, vypis nebo podobně),
- součástí jazyka jsou operátory pro základní aritmetické operace (+,-,*,/), závorky a operátor přiřazení výsledku do proměnné, ve výpočtu musejí být zachovávány priority operátorů, alternativně logické operátory and, or, not zároveň s relačními operátory (např. <, >, <=, >=, ==),
- překladač bude vyhodnocovat jednoduché matematické výrazy (s prioritou operátorů a závorkami), viz předchozí bod,
- ve výrazech se vyskytují i proměnné (na pravé straně přiřazovacího příkazu/výrazu), tj. hodnotu proměnné lze použít pro výpočet hodnoty jiné proměnné,
- určete, jaký symbol použijete pro přiřazovací příkaz (přiřazení hodnoty výrazu do proměnné), promyslete si, zda budete vyžadovat, aby proměnná byla předem deklarována (a jakým způsobem),
- jazyk dovoluje používat i prázdné znaky (tj. programátor může volně používat mezery a prázdné řádky, příp. několik příkazů na řádku), ale nesmí být k tomu nucen na místech, kde to není nutné (například mezi dvěma klíčovými slovy je žádoucí alespoň jeden prázdný znak, ale třeba mezi aritmetickým operátorem a číslem nebo před středníkem to přece není nutné),
- příkazy je možné vnořovat (nebo alespoň matematické výrazy v jakémkoliv tvaru jako argumenty jiných příkazů).
V jazyce můžete používat i podmíněné příkazy (například if-then-else), ale nedoporučuji používat cykly (for, while, atd.), protože implementace jejich sémantiky je složitější (ale jestli chcete, klidně můžete, nebudu protestovat).
Postupujte tak, aby byl překladač co nejsnadněji rozšiřitelný. Budete přidávat syntaktickou a sémantickou analýzu, syntaktická bude podle potřeby volat lexikální analýzu jako proceduru (funkci), která ze vstupu načte vždy jeden symbol. Z důvodů snadnějšího ladění při psaní lexikální analýzy budeme chtít, aby zpracovala celý vstup, ale je lepší do budoucna počítat s tím, že její činnost bude "rozkouskovaná", a tak ji můžeme napsat rovnou jako funkci, která při svém zavolání načte ze vstupu jeden symbol a v počátku je z hlavního programu v cyklu volaná, dokud nedojde ke konci vstupu.
Neměli byste zapomenout, že je třeba vytvořit příslušný datový typ (výčtový typ, enum, posloupnost konstant apod.) pro typy symbolů, tento datový typ pak bude využíván v rámci celého překladače. Z praktických důvodů doporučuji vytvořit funkci, která bude prvky tohoto výčtového typu převádět na řetězec, aby bylo možné je vypisovat (při ladění lexikální analýzy na výstup, později při ošetření chyb v syntaktické a sémantické analýze).
Syntaxe a sémantika
Až budete mít hotovou, odladěnou a schválenou lexikální analýzu, můžete začít pracovat na zbytku projektu. Dále předpokládáme, že máte naprogramovanou funkci nebo proceduru, která zatím načtený symbol prostě někam vypsala. Teď v této funkci proveďte změnu:
- tato funkce při svém zavolání načte jeden symbol ze vstupu a do příslušné globální proměnné (či prvku ve výše postavené třídě) načte typ a případně hodnotu tohoto symbolu, pak skončí a čeká na další zavolání,
- pokud pracujete v jazyce, který neumožňuje vytvářet globální proměnné (třeba Java), je třeba symbol předat jiným způsobem (je možné řešit vhodně navrženou třídou).
Takže změna původního kódu je jen drobná.
Odevzdat nejpozději do konce zkouškového období zimního semestru, ideálně však už v prosinci, později nebudete mít dostatek času.
Případné dodatečné změny jazyka:
Teď už budete znát více z teorie překladu, takže možná budete chtít svůj původní koncept trochu pozměnit.
Syntaktická analýza:
- Tvořte jednoprůchodový překladač, tj. jednotlivé části překladače budou mezi sebou interaktivně spolupracovat. Hlavní součástí bude syntaktický analyzátor, který bude pravidelně volat funkci, kterou jste naprogramovali pro lexikální analýzu, později přidáme sémantiku.
- Jen pokud máte závažný důvod (třeba používání příkazů cyklů), může být lexikální analýza v samostatném průchodu. Výstup lexikální analýzy by pak měl být ve formátu, který nevyžaduje načítání řetězců, třeba dynamický seznam.
- Vytváříte interpretační překladač, to mějte na paměti. Tedy budete nejen analyzovat, ale i provádět kód.
- Opět začněte návrhem struktury (na papíru nebo ve Wordu/Excelu), zde konkrétně syntaktickou gramatikou (dbejte na to, aby byla LL(1), otestujte, testování do dokumentace). Můžete použít LL(1) gramatiku pro matematické výrazy ze skript, kterou obklopíte "zbytkem" definice jazyka.
-
Programujte podle metod uvedených ve skriptech. Měla by to být jedna z těchto metod:
- rekurzivní sestup, doporučuji,
- podle rozkladové tabulky, ale v tom případě zpracování matematických výrazů naprogramujte metodou uvedenou v poslední kapitole skript (dva zásobníky, od str. 158).
- Nezapomeňte na komentáře a dále podrobná chybová hlášení, všechny možné chyby by měly být ošetřeny (tj. u každého podmíněného příkazu by mělo být else).
Pro ty, kdo pozapomněli, jak se programuje rekurze:
-
V Pascalu deklarujte rekurzívně volané funkce předem pomocí direktivy
forward
, tj. například
procedure XXX(parametry); forward;
a pak někde v kódu bude definice funkce (procedury) včetně jejího těla. -
V jazyce C nebo C++ je obvyklé umístit deklarace do hlavičkového souboru, tedy například v souboru lex.h bude
void Lex();
a pak v souboru lex.c nebo lex.cpp budou tyto funkce včetně jejich těla. Podobně v souboru synt.h budou rekurzívně volané funkce deklarované formou
void error(char *hlaska);
void XXX(parametry);
a pak v souboru synt.c nebo synt.cpp k hlavičkám funkcí přidáme i tělo. Nemusím doufám připomínat, že hlavičkové soubory jsou opravdu jen na datové typy, deklarace sdílených proměnných a hlavičky funkcí, ne na kód funkcí! - Jistý produkt od jisté nejmenované firmy, která má na svědomí i Windows, zdánlivě nutí programátory, aby CPP soubory nechali pro kód automaticky generovaný vývojovým prostředím a aby svůj kód zapisovali do hlavičkových souborů (což je nesmysl, ty soubory jsou pro jiné účely). Ovšem uvědomte si, že CPP soubory si můžete klidně vytvářet i sami, není nutné cpát kód tam, kde není vítán nebo kam nepatří (do hlavičkových souborů). Takže do vnucovaného hlavičkového souboru můžete napsat jen include pro váš vlastní soubor s kódem.
Sémantická analýza:
- Promyslete si rozhraní překladače (může být textové nebo grafické), ať už jde o vstupy nebo výstupy. Jde o to, jak bude překladač přijímat vstup (zdrojový kód, který má analyzovat a provést), a dále jde o rozhraní po spuštění tohoto zdrojového kódu. Zřejmě bude třeba načítat čísla (nebo řetězce) od uživatele, něco vypisovat či vykreslovat, apod., podle toho, jaké příkazy jsou zapsány v zdrojovém kódu, který má být překladačem zpracován.
- Naprogramujte datovou strukturu (tabulku symbolů), ve které budete uchovávat proměnné, a dále všechny potřebné související funkce (například přidání nové proměnné, vyhledání existující či změna její hodnoty). Dále naprogramujte ostatní sémantické funkce (například vykreslování). Přidejte jejich volání do syntaktických funkcí.
- Také doporučuji zvážit další změnu - ve skriptech na straně 102 (kap. 4.1.3) je popsán způsob, jak se v sémantickém analyzátoru vyhnout porovnávání řetězců při práci s názvy proměnných, tento postup vyžaduje úpravu činnosti lexikálního analyzátoru (jen malou, stačí, aby tabulku symbolů vytvářel už lexikální analyzátor a aby při nalezení symbolu typu proměnná dal do sémantického atributu odkaz do tabulky symbolů místo samotného názvu proměnné).
- Nezapomeňte ošetřit dělení nulou!
Odevzdejte mailem:
- zdrojový kód vašeho programu, tentokrát celý (všechny analýzy),
- spustitelný soubor, který je funkční,
- dokumentaci ke všem analýzám (popis jazyka alespoň syntaktickou gramatikou, jaké příkazy přijímá, co jsou jejich parametry, dále všechny struktury a tabulky vytvořené pro lexikální, syntaktickou a sémantickou analýzu např. ve Wordu či Excelu), u sémantické minimálně datové typy, popis typů možných parametrů příkazů.
- ukázkový vstup, na kterém jste překladač testovali.
Příklady projektů pro inspiraci
Fantazii se meze nekladou, pokud ovšem vymyslíte něco proveditelného. Obecně volte to, co dokážete naprogramovat (také po grafické stránce). Může to být textový program, jednoduchá 2D grafika, 3D grafika, grafy, klasický nebo funkcionální jazyk. Je možné zpracovávat vstup "vlastního" jazyka na vstup jiného jazyka (HTML, LaTeX apod.), ale musíte přidat ještě nějaké zpracování (například interpretaci výrazů na konstanty), nestačí pouhý překlad syntaxe.
Silně zjednodušený Pascal se změněnými klíčovými slovy:
Program pracuje pouze s celočíselnými proměnnými, proto není nutné zadávat jejich datový typ. Proměnné jsou deklarovány před začátkem programu. Blok programu je ohraničen příslušnými klíčovými slovy. Jsou přijímány příkazy:
- přiřazení hodnoty výrazu do proměnné, běžné matematické výpočty,
- načtení hodnoty proměnné ze vstupu, výpis hodnoty výrazu na výstup,
- speciální příkaz vypisující konstantní řetězec (pak zařádkuje),
- jednoduchý podmíněný výraz if-then (bez větve else), jednoduché výrazy s relačními operátory (žádné logické operátory, jen relační).
prom x, vysl; zacatek vypisretezec("Program na výpočet druhé mocniny kladného čísla."); vypisretezec("Zadej číslo: "); nacti(x); if (x<=0) vypisretezec("Toto číslo není kladné!"); if (x>0) vystup(x*x); konec
Fantazii se meze nekladou, můžete si například vymyslet nějaký speciální příkaz, který vypočítá a vypíše druhou mocninu či odmocninu z výsledku zadaného výrazu, apod. Překladač vždy načte zdrojový soubor s něčím podobným, co je uvedeno výše, a interpretuje. Případně je možné využít grafické prostředí (zdroj je v jednom podokně, v dalším se vypisují výsledky).
Grafický editor:
Jeden příkaz na jednom řádku, po stisknutí klávesy ENTER se vždy vyhodnotí a provede. Příkazy:
- kružnice(x, y, poloměr), úsečka(x1, y1, x2, y2), ..., parametry mohou být matematické výrazy s proměnnými
- ... a další podle možností zvoleného programovacího jazyka, např. také určení výplně objektů,
- můžeme vymyslet speciální příkazy fungující jako tzv. razítka, tj. na zadané souřadnice bude vykreslen nějaký složitější objekt, například strom, ryba nebo něco podobného,
- vytvoření proměnné,
- přiřazení hodnoty výrazu do proměnné,
- nápověda, dále dle vlastní fantazie.
Kalkulačka pracující s výrazy a proměnnými:
Jeden příkaz na jednom řádku, po stisknutí ENTER se vždy vyhodnotí a provede, ale je schopen také načíst dávku ze souboru stejně jako Příkazový řádek ve Windows. Příkazy:
- vytvoř proměnnou s urč. jménem a počáteční hodnotou,
- přiřaď do proměnné výsledek výrazu (např. x = 24 + x * (8 - pocet)),
- vypiš hodnotu určité proměnné,
- vypiš hodnoty všech nadefinovaných proměnných,
- odstraň proměnnou,
- načti soubor s dávkou příkazů,
- nápověda, atd.
Vytváření dialogů pod DOSem:
Textový vstup těchto příkazů (vhodně je pojmenujte):
- ulož výřez obrazovky (left, top, width, height)
- vykresli rámeček (left, top, width, height, type), typ stanovte jako konstantu s předem danými hodnotami, v dále použitých příkazech budou hodnoty určující pozici chápány lokálně pro oblast uvnitř tohoto rámečku
- vypiš text (x, y, text)
- barva pozadí textu (color)
- barva textu (color)
- vybarvi pozadí
- nakresli čáru (...)
- proměnná ... (např. klíčové slovo VAR), proměnné stejně jako matematické výrazy mohou být používány pro jakýkoliv číselný parametr předchozích příkazů, pak budou interpretovány výpočtem čísla
- ...
Interpretace matematických výrazů:
- proměnné, celá čísla, operátory =, +, -, *, / (celočíselné dělení), závorky, ošetřit dělení nulou, možné rozšířit na reálný či racionální datový typ
- např. x = 2 * (y-1)
- konverzační překladač, tj. vždy se vyhodnocuje jeden výraz
- definování proměnných, výpis hodnoty proměnné, načtení hodnoty proměnné
- další vhodné příkazy (třeba podmíněný výpočet: přiřazení se provede, jen když při výpočtu nedojde k sémantické chybě - použití neexistující proměnné nebo dělení nulou)
Hilbertovská výroková logika:
S ::= A [implikace] S | A
A ::= (S) | [negace] (S) | p | [negace] p | true | false
- kde p představuje logické proměnné (řetězce písmen, podtržítek a číslic, začínající písmenem)
- program dovolí příkazem určit valuaci proměnných (tj. hodnoty true-false), po zadání jednoho výrazu interpretuje a vrátí hodnotu true nebo false podle hodnoty výrazu
- příkazy pro definování proměnné (můžeme také přímo zadat hodnotu), výpis hodnoty, načtení hodnoty, může být příkaz větvení
Interpretace logických výrazů v Pascalu:
S ::= S or A | A
A ::= A and B | B
B ::= (S) | C relop C
C ::= i | n
(nejdřív přidáme zbytek syntaxe a upravíme na LL(1) gramatiku)
- program umožní nadefinovat proměnné, pak interpretuje zadaný výraz, výsledkem je hodnota true nebo false
- jinak podobné jako u předchozího
Kreslení:
Pokud znáte program Logo, tento příklad je podobný.
L = {doprava, doleva, nahoru, dolu, krok(počet) } – pohyb kurzoru
- dolu: tužka se dotkne papíru (tj. může kreslit)
- nahoru: tužka se zvedne z papíru
- doprava, doleva: otočka o 90 stupňů
- krok(počet): posun o zadaný počet kroků, pokud je tužka položená na papíře, zároveň kreslí, počet je matematický výraz obsahující celá čísla, operátory +, -, *, / (celočíselné)
- nutné ošetřit, aby výsledné číslo bylo z vhodného intervalu, a dělení nulou
- program má 2 okna nebo 1 okno rozdělené na 2 části, v jedné části je výstup, v 2. části vstup, ten se interpretuje
- možné načítat textové soubory obsahující vstup, nebude to tedy konverzační překladač
- zvolí se vhodná délka úseku (např. 20 pixelů)
Chodící robot/strom/dort/auto/kruh s vyznačeným čelem/atd.:
Zvolíme nějaký element podle vlastního vkusu, co dokážete rychle vygenerovat. Je to obdoba známého programu Karel, dá se naprogramovat podobně jako předchozí příklad (prostě příkazy řídíme pohyb daného elementu), nezapomeňme, že do definice jazyka musíme nějak vpašovat matematické výrazy.
Interpretace hudby:
Jazyk obsahuje tyto příkazy:
- generování zvuku (frekvence, délka) - frekvence a délka (v ms) jsou čísla (zadáváme matematickým výrazem, který může obsahovat i proměnné), je třeba stanovit sémantické omezení intervalu vhodných hodnot pro oba parametry (délka by určitě neměla být záporná, i pro frekvenci existují určitá omezení)
- pomlka (délka)
- práce s proměnnými, jako u předchozích příkladů
- další, třeba větvení programu nebo použití podprogramů
Implementace grafových algoritmů:
Například se rozhodnete pro implementaci algoritmu obchodního cestujícího. Příkazy zahrnují možnost zadávání souřadnic pro "města", a dále třeba příkazy zjišťující, zda existuje cesta mezi městy kratší než xxx (parametr příkazu). Dále je samozřejmě nutné vytvořit příkazy pro práci s proměnnými jako výše, případně větvení.
Jednoduchý značkovací jazyk pro dokumenty s překladem do HTML:
Příkazy pro odstavec, nadpis, barvu textu, deklarování proměnných, načítání hodnoty proměnné apod., veškeré číselné údaje (včetně barvy textu) lze zadat jako matematický výraz s využitím proměnných. Výstupem je soubor ve formátu HTML s tím, že veškeré matematické výrazy jsou vypočteny a do HTML kódu jsou dosazena pouze čísla (proměnné se ve výstupu nebudou vyskytovat).