Va trebui sa realizati, folosind Bison si LLVM IR Builder, interpretarea si translatarea unui program din limbajul SQ2 descris mai jos, intr-o reprezentare intermediara în limbaj de asamblare LLVM IR. Limbajul SQ2 este un limbaj procedural foarte simplu.
Punctare:
Implementarea trebuie sa fie suficient de generica pentru a putea rula fara probleme si pe alte teste similare.
Se vor puncta analiza sintactica si semantica, afisarea erorilor, facilitatile de limbaj implementate, descrierea din README, si modul in care a fost realizat programul.
Programul vostru se va numi sq2c, va citi un fisier sursa SQ2 al carui nume se va trimite pe linia de comanda, si va efectua analiza lexicala, sintactica si semantica.
Daca pe linia de comanda exista un singur parametru, aplicatia va rula programul in modul interpretor.
Daca pe linia de comanda exista doi parametri, aplicatia va genera un fisier LLVM IR ce contine codul translatat.
In cazul in care programul este eronat, compilatorul trebuie sa emita un mesaj de eroare relevant. Pentru erorile sintactice, mesajul implicit oferit de Bison este suficient. Nu este necesara revenirea din eroare si continuarea compilarii. Compilatorul nu trebuie sa se blocheze sau sa produca un crash, in nicio circumstanta.
Cel mai simplu program SQ2, ce tipareste un sir de caractere pe standard output, contine: * Functia start * O constanta tip sir de caractere * Un apel catre functia predefinita print
Executia programului incepe cu functia start, care nu are parametri sau valoare intoarsa.
print este o functie predefinita care primeste un argument de tip sir de caractere, si il afiseaza.
Limbajul nu accepta variabile de tip siruri de caractere. Singurul loc in care pot aparea sirurile este argumentul functiei print;
Spatiile, taburile, si caracterele sfarsit de linie nu sunt semnificative. Comentariile incep cu caracterul # si continua pana la sfarsitul liniei.
Instructiunile sunt terminate cu caracterul ;
Cuvant cheie: end Functii predefinite: start, print
start: [print "Hello world!\n"]; end;
Limbajul accepta constante de tip siruri de caractere pot contine caractere escape: \" \\ \n . Orice altceva reprezinta caracterele in sine.
Mai jos: un program ce tipareste “ASCII art”.
# _ # "/ \" # "\_/" start: [print " _\n\"/ \\\"\n\"\\_/\"\n"]; end;
Un alt tip al limbajului este number - intregi cu semn pe 32 de biti. Pot exista constante pozitive si negative de acest tip: (de ex. -20 , 0 , 403 ). O functie poate avea variabile locale, ce sunt definite in sectiunea local.
Variabilelor locale li se pot atribui valori folosind instructiuni de atribuire: variabila = expresie. Expresiile sunt numerice, si pot contine operatori aritmetici binari ( + - * / ), minus unar, si paranteze. Expresiile pot contine apeluri de functii.
Functiile pot avea parametri si intoarce valori. read este o functie predefinita ce citeste o linie de la intrarea standard, o converteste la un numar intreg, si intoarce acel numar. write este o functie predefinita cu un singur parametru de tip number, pe care il tipareste pe iesirea standard.
Cuvinte cheie: local, number Functii predefinite: read, write
Functiile din biblioteca standard trebuie sa fie implementate de voi. Arhiva de pornire contine o astfel de implementare functionala, bazata pe biblioteca standard C (stdio.h).
start: local number r1; number r2; number r3; end; [print ">"]; r1 = [ read ]; [print ">"]; r2 = [ read ]; [print ">"]; r3 = [ read ]; [print "Average = "]; [write -(r1 + r2 + r3) / 3]; [print "\n"]; end;
Se pot defini functii de catre utilizator, in afara de start. Functiile pot avea parametri si intoarce valori. Se pot apela functii care sunt definite oriunde in fisier, chiar si dupa locul apelului. Argumentele de tip number sunt transmise prin valoare - schimbarile unui argument nu ii afecteaza valoarea in functia apelanta.
return este o structura de control care indica sfarsitul executiei unei functii. Daca functia trebuie sa intoarca o valoare, atunci return trebuie sa aiba un parametru.
Cuvinte cheie: return
start: local number r; end; [print "Radius? "]; r = [ read ]; [print "Circle Surface = "]; [write [ surf r ]]; [print "\n"]; end; pi100 -> number: return 314; end; surf number r -> number: return ([ pi100 ] * r * r) / 100; end;
Un alt tip este un tablou unidimensional de elemente de tip number. Dimensiunea unui tablou este o constanta intreaga pozitiva. Tablourile sunt si ele variabile locale, si sunt declarate similar. Un element dintr-un tablou poate fi folosit in orice loc unde poate fi folosita o variabila locala de tip number. Primul element are indexul 0.
Un tablou este trimis ca argument catre o functie prin referinta, nu prin valoare. O functie poate avea mai multi parametri de tipuri diferite, number sau tablou, dar nu poate intoarce un tablou.
Structurile de control includ if, while si until ; ultima cicleaza atat timp cat expresia este falsa. Structurile de control au ca parametru o expresie conditionala. Conditiile sunt comparatii intre expresii intregi. Operatorii permisi sunt < , = , >
Cuvinte cheie: if, else, while, until
# Average array values average number[256] data, number count -> number: local number i; number sum; end; i = 0; sum = 0; while i < count; sum = sum + data[i]; i = i + 1; end; return sum / count; end; # Read an array, return the size of the data read readArray number[256] data -> number: local number c; number i; end; [print "Count? "]; c = [read]; if c > 0; if c > 256; [ print "ERROR: Buffer overflow!\n"]; return 0; else i = c; until i = 0; [print ">"]; data[c - i] = [ read ]; i = i - 1; end; end; return c; end; if c = 0; # do nothing else [ print "ERROR: Count is negative!\n"]; end; return 0; end; start: local number c; number[256] d; end; c = [readArray d]; if c > 0; [print "Average value: "]; [write [average d,c]]; [print "\n"]; end; end;
O functie se poate apela pe ea insasi recursiv. Mai jos, o implementare de quick sort:
start: local number c; number[256] d; number i; end; c = [readArray d]; [sort d,c]; [writeArray d,c]; end; #readArray - is reused from the Unlimited Average example writeArray number[256] data, number count: local number i; end; i = 0; while i < count; [print ">"]; [write data[i]]; [print "\n"]; i = i + 1; end; end; sort number[256] data, number count: [quickSort data, 0, count]; end; quickSort number[256] data, number first, number count: local number idx; number value; number i; end; if count > 1; idx = first + count / 2; value = data[idx]; [swap data, idx, first+count-1]; idx = first; i = first; while i - first < count - 1; if data[i] < value; [swap data, i, idx]; idx = idx + 1; end; i = i + 1; end; [swap data, idx, first+count-1]; [quickSort data, first, idx - first]; [quickSort data, idx + 1, first + count - idx - 1]; end; end; swap number[256] data, number p1, number p2: local number t; end; t = data[p1]; data[p1] = data[p2]; data[p2] = t; end; readArray number[256] data -> number: local number c; number i; end; [print "Count? "]; c = [read]; if c > 0; if c > 256; [ print "ERROR: Buffer overflow!\n"]; return 0; else i = c; until i = 0; [print ">"]; data[c - i] = [ read ]; i = i - 1; end; end; return c; end; if c = 0; # do nothing else [ print "ERROR: Count is negative!\n"]; end; return 0; end;
Pentru a preda tema, va trebui sa realizati o arhiva .zip, care sa contina urmatoarele in directorul ei radacina:
* Codul sursa al temei, incluzand fisierele *.l, *.y, *.cpp si *.h (fara binare!). * Un fisier README in care sa prezentati solutia aleasa si cum se face build * Un script de build (Makefile sau build.xml pentru ANT) care sa creeze executabilul sq2c
Pentru a rula tema, se va rula scriptul de build, si apoi
sq2c file.sq2
sq2c file.sq2 file.ir llvm-as file.ir -o file.bc lli file.bc
Se presupune ca pe sistemul pe care se va testa tema este instalat LLVM 3.7 sau LLVM 3.8, inclusiv pachetul de dezvoltare (biblioteci si header files). Atentie, daca folositi versiuni mai vechi de LLVM puteti avea probleme, deoarece API-ul este incompatibil cu 3.7 .
Gasiti atasate: