Програмски преводиоци 1/Пројекат
Пројекат на предмету важи за једну од најтежих активности на њему због тога што захтева практичну примену знања стечених у трећем блоку (и понеких из првог и другог блока). Његова израда може захтевати око недељу дана константног рада (у зависности од нивоа за који радите) али се такође велики део тога може искористити из постојећих материјала са вежби и званичних видео водича за пројекат, на које ће се овај водич надаље ослањати.
Осно вно
Идеја пројекта јесте конструкција преводиоца за Микројаву — поједностављену, школску варијанту језика Јава чија се спецификација помало мења из године у годину (али неке суштинске ствари остају исте). Преводилац из једног фајла са изворним кодом чита Микројава код по спецификацији датој уз текст пројекта (а одвојеној од саме поставке), пролази кроз четири фазе превођења (лексичку анализу, синтаксну анализу, семантичку анализу и генерисање кода) и његов крајњи резултат јесте објектна датотека са Микројава бајткодом. Тај објектни фајл се затим може извршавати преко Микројава виртуелне машине (чија је имплементација већ дата и не може се мењати) и на основу неког уноса произвести неки излаз.
За пројекат је потребно гледати вежбе трећег блока из табеле симбола и Микројава виртуелне машине, и евентуално вежбе првог и другог блока из JFlex и CUP. Такође су доступни видео водичи за пројекат са странице предмета, које је корисно погледати као увод у алате и нека генерална очекивања. Ти водичи су енкодовани неким јако застарелим кодеком, а реенкодовани снимци се могу наћи овде.
Пре него што пређете на даље одељке, препоручује се да прочитате поставку пројекта. Микројава спецификацију не морате читати, јер ће вам она највише значити приликом самог развијања пројекта.
Поставка
Сада када сте прочитали поставку, о њој је потребно рећи пар речи.
- Структура пројекта уопште не мора да буде онаква каква пише у поставци. То значи:
- Пакет не мора да се зове
rs.ac.bg.etf.pp1. - Не постоји конкретан директоријум у који морате да сместите спецификације лексера и парсера.
- Није обавезно коришћење JDK 1.8 (мада је препоручено)
- Класе не морају да се зову
Compiler,SemanticAnalyzeriCodeGenerator.
- Пакет не мора да се зове
- На одбрани се мање-више гледа само излаз преведеног Микројава програма за модификацију и за јавни тест одговарајућег нивоа за који радите. Ово у пракси значи:
- Нико неће проверавати формат грешке лексера.
- Нико неће проверавати да ли се успешно ради опоравак од грешке.
- Ипак, препоручује се да пробате овај део да одрадите, али ако на неком месту не ради то није велики проблем.
- Нико неће проверавати да ли нисте користили
%precedence(али нико неће ни објаснити како се користи). - Нико неће проверавати како су именовани нетерминали нити класе које одговарају гранама тих нетерминала.
- Нико неће проверавати да ли сте додали акције у спецификацију парсера.
- Свакако се препоручује да потребне акције обављате кроз одговарајуће посетиоце стабла, јер у спецификацији парсера није доступан IntelliSense.
- Није много вероватно да ће предметни сарадници погледати да ли користите њихову табелу симбола, да ли сте је распаковали и превели поново или користите неку потпуно другу имплементацију.
- Ипак, коришћење њихове табеле симбола носи са собом погодност да ћете увежбати рад са њом па нећете много морати да обнављате за такве задатке на испиту.
- Нико неће проверавати да ли сте имплементирали методу
tsdump(). - Нико неће проверавати да ли исписујете симболе при њиховој детекцији.
- Слабо ће се проверавати да ли се пријављују семантичке грешке.
- Ипак, пошто током семантичке обраде свакако мора да се попуни табела симбола, вреди додати ове провере, у крајњем случају зато што ће вама значити уколико будете писали своје тест примере и напишете нешто семантички неисправно.
- Може да се деси да неке ствари из јавних тестова или тестова за модификације које су закоментарисане морају да пријаве семантичке грешке, и да их предметни сарадници откоментаришу приликом одбране.
- Нико неће проверавати да ли се путање до улазних и излазних фајлова прослеђују кроз аргументе командне линије.
- На одбрани се не тражи да се било шта покреће из командне линије, нити преусмерава стандардни излаз и излаз за грешке.
- Примери Микројава кода из поставке и спецификације могу често бити синтаксно или семантички неисправни. Ово је због тога што предметни сарадници не напишу преводилац за спецификацију Микројаве коју су задали, а пример вероватно ископирају из поставке односно спецификације од претходне године, па не провере да ли је исправан.
- На одбрани нико не погледа извештај са пројекта.
- Нико неће тражити да се покрену студентски тестови пројекта.
- Супротно поставци, дорада пројеката на одбрани је дозвољена.
- У одељку за контекстне услове у оквиру спецификације могу бити описане ствари које се не раде у фази семантичке анализе, већ или описују генерално функционисање тог програмског конструкта, или описују ствари које се морају обезбедити у фази генрисања кода.
- Приликом спецификације синтаксе користи се EBNF нотација.
- Подела функционалности по нивоима може бити јако конфузна. Спецификација Микројаве може посебно напоменути за само пар ствари да се имплементирају само на одређеним нивоима (остављајући утисак да је потребно препознати синтаксу за методе и класе чак и у пројекту А нивоа), док поставка може непотпуно излиставати смене које су потребне да се имплементирају за одређени ниво. Најбољи начин за одређивање шта се имплементира за који ниво јесу одговарајући јавни тестови.
Алати
У овом одељку наведене су све напомене у вези са алатима које ћете користити на пројекту, од којих ће неке имати смисла тек након што погледате видео водиче.
- Неколико ствари из видео водича урађено је на неоптималан начин:
- Једна од првих ствари поменутих у видео водичима јесте инсталирање Ant. За овиме нема потребе, јер је Ant већ инсталиран у оквиру Eclipse. Такође, Ant правила се доста лакше могу покретати одласком на Window → Show View → Ant, и затим додавањем
build.xmlфајла у пројекту. - Уколико крећете од кода из видео водича, могуће је да ће вам избацивати deprecation упозорења поводом коришћења
new Integer()конструктора. Ово можете заменити саInteger.parseInt(). - Бројање параметара и локалних променљивих коришћењем
VarCounterиFormParamCounterније заправо потребно, већ их можете бројати приликом обиласка тих чворова стабла. - На неколико места се користи
Tab.insert()ради прављењаObjчвора који нема потребе заправо убацивати у табелу симбола. Уместо овога, могу се користити регуларни конструктори заObj.
- Једна од првих ствари поменутих у видео водичима јесте инсталирање Ant. За овиме нема потребе, јер је Ant већ инсталиран у оквиру Eclipse. Такође, Ant правила се доста лакше могу покретати одласком на Window → Show View → Ant, и затим додавањем
- У рачунарским лабораторијама би требало да је доступан и IntelliJ, па можете у њему такође радити пројекат.
- Обавезно преузети библиотеке са странице предмета уместо коришћења оних из шаблона пројекта или видео водича, јер њихове верзије могу бити застареле и проузроковати проблеме.
- Унос са стандардног улаза неће радити уколико се преведени Микројава програм покреће кроз Ant, па је потребно додати директиву
<redirector input="input.txt" />како би се стандардни улаз читао из датотекеinput.txtкоја се налази у кореном директоријуму пројекта.- На овај исти начин могу се преусмерити стандардни излаз и излаз за грешке:
<redirector input="input.txt" output="output.txt" error="error.txt" /> - Уколико уместо овога програм покренете кроз командну линију, могуће је да ћете морати да различите податке за унос пишете у истом реду.
- На овај исти начин могу се преусмерити стандардни излаз и излаз за грешке:
- Није неопходно користити Log4j библииотеку за испис уколико не желите.
- Подразумевано, Log4j библиотека неће слати свој излаз на излаз за грешке већ на стандардни излаз, чак и кад су у питање поруке са грешкама. Ово може да се конфигурише, али свакако нико неће обраћати пажњу на то на одбрани.
Фазе израде
Лексичка анализа
- Да бисте почели са развојем ове фазе не морате куцати свој
sym.javaфајл, већ се он може генерисати из CUP спецификације чим покренетеparserGenправило у Ant, уколико сте све своје терминале написали у CUP спецификацији (terminal). - Ова фаза је најлакша и могуће ју је урадити за само пар сати, али је битно урадити је како треба. Грешке у лексеру могу изазвати проблеме приликом парсирања, само што лексер у том тренутку може бити место на којем ћете најмање посумњати да се налази грешка. Постоји неколико ствари на које треба обратити пажњу:
- Генералнија правила иду на дно. На пример, уколико се правило за
ifналази испод правила за детекцију идентификатора, лексер ћеifпрепознати као идентификатор и зато ће парсер избацити грешку приликом парсирања if наредбе. - На само дно убацити једно match-all правило (као што је урађено у видео водичу). Уколико то не урадите, лексер ће избацити Error: could not match input грешку уколико се наиђе на карактер који није у спецификацији Микројаве, што само по себи није проблем, али вам ваша сопствена грешка може дати више информација о томе где је тачно проблем.
- Уколико радите на оперативном систему Linux или macOS, потребно је одвојити правило за
\r\nна правила за\rи\nкако би их правилно игнорисао. - У видео водичу се за препознавање идентификатора користи
([a-z]|[A-Z])[a-z|A-Z|0-9|_]*регуларни израз, где је карактер|грешком дозвољен у оквиру идентификатора, док је правилно[a-zA-Z][a-zA-Z0-9_]*. Ово је мала грешка, али може направити проблем приликом конструката попутa||b, који ће бити препознати као један идентификатор уместо два идентификатора са оператором између њих.
- Генералнија правила иду на дно. На пример, уколико се правило за
Синтаксна анализа
- Најважније правило током развоја ове фазе јесте да сва правила пишете корак по корак и са тестирањем између. Током развоја алати могу пријавити грешке које вам ни на који начин не сугеришу где је заправо проблем, и такве грешке је далеко лакше пронаћи уколико знате који део синтаксе је тестиран и ради, а који је новододат. Ово значи да када преузмете пројекат из видео водича обришете сва правила из њега (осим једног, попут
Program ::= PROG;, како би генерисање парсера уопште радило) и кренете са додавањем правила редом по спецификацији. Кад видите да сте додали неку мању али потпуну целину, тестирајте да ли то што сте додали ради. - Када кренете са развојом ове фазе, најбоље је да уопште не постављате називе класа на нетерминалима. Ови називи класа се много лакше постављају након што сте већ развили целу граматику (пре следеће фазе) и имате цео контекст, а њихово додавање током развоја граматике може изазвати неке од честих грешки. Исто тако, нема потребе додељивати типове терминалима и нетерминалима док не стигнете до следеће фазе, већ је довољно само декларисати их.
- Како се у оквиру ове фазе такође ради и опоравак од грешке, вредно је напоменути да је сврха тог опоравка да се пријаве све постојеће синтаксне грешке у програму (уместо да се пријави само једна и изађе), али да се при детекцији било какве грешке не наставља на следећу фазу, чак иако се од свих синтаксних грешки парсер успешно опоравио.
- Од користи може бити следећа скрипта за генерисање листе нетерминала које је потребно декларисати током ове фазе (која престаје да буде корисна у тренутку када нетерминалима треба додељивати типове), коју је потребно покренути Python интерпретером из кореног директоријума пројекта:
from re import compile NTERM_REGEX = compile(r'^(\w+)\s*::=') nterms = [] with open('spec/mjparser.cup') as file: for line in file: match = NTERM_REGEX.match(line) if match: nterms.append(match.group(1)) print(f'nonterminal {", ".join(nterms)};')
Семантичка анализа
- Пре почетка ове фазе не заборавите да свим терминалима који са собом носе неке смислене вредности (идентификатори, константе...). Уколико ово не урадите, у нетерминалима који садрже те терминале неће се изгенерисати поља са вредностима ових терминала.
- Уколико сте дошли до овог дела а потребно вам је да регенеришете парсер, не заборавите да након регенерисања парсера освежите пројекат десним кликом на пројекат и опцијом Refresh. Овај корак је потребан због тога што генерисање парсера позива спољашњи програм који без знања Eclipse мења фајлове унутар пројекта, и како би Eclipse знао да су се ти фајлови променили потребно је освежити их. Уколико ово не урадите, IntelliSense може пријављивати грешке које немају смисла и Eclipse може спречавати покретање компајлера због тога.
Генерисање кода
Одбрана
Референце
- Водич за пројекат 2021/2022. на којем су делови овог водича засновани.