Υλικό και Λογισμικό Ι

Διομήδης Σπινέλλης
Τμήμα Διοικητικής Επιστήμης και Τεχνολογίας
Οικονομικό Πανεπιστήμιο Αθηνών
dds@aueb.gr

Εισαγωγή

Καλώς ήρθατε

Υλικό και Λογισμικό Ι

Τι περιλαμβάνει το μάθημα

  1. Εισαγωγή
  2. Προγραμματισμός σε επίπεδο μηχανής
  3. Αυτόματα
  4. Γραμματικές
  5. Λεκτική ανάλυση
  6. Το μεταεργαλείο lex
  7. Συντακτική ανάλυση
  8. Το μεταεργαλείο yacc
  9. Πίνακες συμβόλων
  10. Διερμηνευτές
  11. Σημασιολογική ανάλυση
  12. Παραγωγή ενδιάμεσου κώδικα
  13. Βελτιστοποίηση κώδικα
  14. Παραγωγή τελικού κώδικα

Οι σημειώσεις

Το χάσμα υλικού και λογισμικού

Οι δυνατότητες που προσφέρει το υλικό και το λογισμικό έρχονται συχνά σε αντίθεση ή αλληλοσυμπληρώνονται. Μερικά καθοριστικά χαρακτηριστικά είναι τα παρακάτω: Οι γλώσσες προγραμματισμού και τα λειτουργικά συστήματα είναι δύο τεχνολογίες που επιτρέπουν τη βέλτιστη συνύπαρξη του υλικού με το λογισμικό.

Υλοποίηση γλωσσών προγραμματισμού

Μια γλώσσα προγραμματισμού μπορεί - ανάλογα με τη γλώσσα - να υλοποιηθεί με τους παρακάτω τρόπους: καθώς και με συνδυασμούς τους.

Ο ορισμός ενός μεταγλωττιστή

Ένας μεταγλωττιστής ορίζεται από: Ο συνδυασμός αυτός παριστάνεται από ένα διάγραμμα Τ (T diagram) όπως το παρακάτω:

Ο ίδιος συνδυασμός για ένα μεταγλωττιστή Μ συμβολίζεται και ως MATY.

Τελικός κώδικας

Ο τελικός κώδικας που παράγεται από έναν μεταγλωττιστή μπορεί να είναι:

Παραδείγματα

Τα παρακάτω παραδείγματα έχουν ως είσοδο το πρόγραμμα που τυπώνει "hello, world".
Συμβολική γλώσσα Linux Intel 386
        .file   "hello.c"
        .version        "01.01"
gcc2_compiled.:
.section        .rodata
.LC0:
        .string "helloworld\n"
.text
        .align 4
.globl main
        .type    main,@function
main:
        pushl %ebp
        movl %esp,%ebp
        pushl $.LC0
        call printf
        addl $4,%esp
.L1:
        leave
        ret
.Lfe1:
        .size    main,.Lfe1-main
        .ident  "GCC: (GNUegcs-2.91.66 19990314/Linux (egcs-1.1.2 release)"
Γλώσσα μηχανής από δυαδικό αρχείο Linux Intel 386 σε μορφή δεκαεξαδική και ASCII
hello.o:     file format elf32-i386

Contents of section .text:
 0000 5589e568 00000000 e8fcffff ff83c404  U..h............
 0010 c9c3                                 ..              
Contents of section .data:
Contents of section .note:
 0000 08000000 00000000 01000000 30312e30  ............01.0
 0010 31000000                             1...            
Contents of section .rodata:
 0000 68656c6c 6f2c2077 6f726c64 0a00      hello, world..  
Contents of section .comment:
 0000 00474343 3a202847 4e552920 65676373  .GCC: (GNU) egcs
 0010 2d322e39 312e3636 20313939 39303331  -2.91.66 1999031
 0020 342f4c69 6e757820 28656763 732d312e  4/Linux (egcs-1.
 0030 312e3220 72656c65 61736529 00        1.2 release).   
Συμβολική γλώσσα της ιδεατής μηχανής Java Virtual Machine
Compiled from hello.java
class hello extends java.lang.Object {
    hello();
    public static void main(java.lang.String[]);
}

Method hello()
   0 aload_0
   1 invokespecial #9 <Method java.lang.Object()>
   4 return

Method void main(java.lang.String[])
   0 getstatic #12 <Field java.io.PrintStream out>
   3 ldc #2 <String "Hello ">
   5 invokevirtual #13 <Method void print(java.lang.String)>
   8 getstatic #12 <Field java.io.PrintStream out>
  11 new #7 <Class java.lang.StringBuffer>
  14 dup
  15 aload_0
  16 iconst_0
  17 aaload
  18 invokestatic #16 <Method java.lang.String valueOf(java.lang.Object)>
  21 invokespecial #10 <Method java.lang.StringBuffer(java.lang.String)>
  24 ldc #1 <String " ">
  26 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)>
  29 aload_0
  30 iconst_1
  31 aaload
  32 invokevirtual #11 <Method java.lang.StringBuffer append(java.lang.String)>
  35 invokevirtual #15 <Method java.lang.String toString()>
  38 invokevirtual #14 <Method void println(java.lang.String)>
  41 return

Απαιτήσεις

Ο σχεδιασμός και η υλοποίηση ενός μεταγλωττιστή ή ενός διερμηνευτή (και συχνά και μιας γλώσσας προγραμματισμού) έχουν ως στόχο να ικανοποιήσουν τις παρακάτω, συχνά αντικρουόμενες, απαιτήσεις:

Σχεδίαση

Η διεργασία της μεταγλώττισης μπορεί να διαχωριστεί στις παρακάτω ξεχωριστές λειτουργίες οι οποίες εκτελούνται σε διαδοχικές φάσεις: Επιπλέον, οι παρακάτω δύο λειτουργίες υποστηρίζουν όλες τις φάσεις της διεργασίας:

Βιβλιογραφία

Γενική βιβλιογραφία

Ασκήσεις

Άσκηση 1 (προαιρετική)

  1. Αναλύστε πως διαφορετικές τεχνικές υλοποίησης γλωσσών προγραμματισμού επηρεάζουν της απαιτήσεις που έχουμε από τα αντίστοιχα εργαλεία.

    Παράδειγμα:

    Η υλοποίηση μιας γλώσσας με μεταγλωττιστή επηρεάζει ... την αποδοτικότητα του παραγώμενου κώδικα σε ταχύτητα διότι ...

Προγραμματισμός σε επίπεδο μηχανής

Δομή ενός υπολογιστή

Κεντρική μονάδα επεξεργασίας

O κύκλος εντολών

  1. Ανάκληση εντολής (Instruction fetch)
  2. Αποκωδικοποίηση εντολής (Instruction decode)
  3. Εκτέλεση εντολής (Instruction execution)
  4. Πρόσβαση μνήμης (Memory access)
  5. Εγγραφή

Οικογένειες επεξεργαστών

Η οικογένεια επεξεργαστών iAPX86

Καταχωρητές

Προσδιορισμός τελεστέων

Σημείωση

Η σύνταξη που χρησιμοποιούμε είναι αυτή των συμβολομεταφραστών της AT&T που υποστηρίζονται στο λειτουργικό σύστημα Unix (π.χ. GNU as). Η σύνταξη αυτή διαφέρει σημαντικά από αυτή που χρησιμοποιεί η Intel.

Το μέγεθος του τελεστή ορίζεται από το τελευταίο γράμμα της εντολής:
l
long (4 byte, long ή int)
w
word (2 byte, short)
b
byte (1 byte, char)

Συνδιασμοί τελεστών:

Παράσταση εντολών

Ενδείκτης διακλάδωσης

CF:
(Carry flag)
Κρατούμενο (Carry):
1 αν η πράξη δημιούργησε κρατούμενο.
ZF:
(Zero flag)
Μηδέν:
1 αν το αποτέλεσμα της πράξης είναι 0.
SF:
(Sign flag)
Πρόσημο (sign)
1 αν το αποτέλεσμα είναι αρνητικός αριθμός.
PF:
(Parity flag)
Ισοτιμία
1 αν το αποτέλεσμα έχει μονό αριθμό από bit που είναι 1.
OF:
(Overflow flag)
Υπερχείλιση:
1 αν το αποτέλεσμα δε μπορεί να παρασταθεί ως θετικός η αρνητικός αριθμός.

Εντολές μεταφοράς δεδομένων

mov πηγή, προορισμός
(Move)
Μεταφορά
πηγή -> προορισμό
push πηγή
Μεταφορά στη στοίβα
%esp <- %esp - $4
(%esp) <- πηγή
pop προορισμός
Μεταφορά από τη στοίβα
προορισμός <- (%esp)
%esp <- %esp + $4
xchg προορισμός, πηγή
(Exchange)
Εναλλαγή
προορισμός <- πηγή
πηγή <- προορισμός
(ταυτόχρονα)

Αριθμητικές εντολές

add πηγή, προορισμός
(Add)
Πρόσθεση
προορισμός <- προορισμός + πηγή
inc προορισμός
(Increment)
Αύξηση κατά ένα
προορισμός <- προορισμός + 1
sub πηγή, προορισμός
(Subtract)
Αφαίρεση
προορισμός <- προορισμός - πηγή
dec προορισμός
(Decrement)
Μείωση κατά ένα
προορισμός <- προορισμός - 1
neg προορισμός
(Negate)
Αλλαγή προσήμου
προορισμός <- - προορισμός
cmp πηγή, προορισμός
(Compare)
Σύγκριση
Εκτελείται η πράξη προορισμός - πηγή και ενημερώνονται οι ενδείκτες διακλάδωσης.
mul πηγή
(Multiply)
Πολλαπλασιασμός
%edx:%eax <- %eax * πηγή
div πηγή
(Divide)
Διαίρεση
%eax <- %edx:%eax / πηγή
%edx <- %edx:%eax mod πηγή
Για να μετατρέψουμε τον ακέραιο 32 bit στον καταχωρητή %eax σε ακέραιο διαιρετέο 64 bit στο ζευγάρι καταχωρητών %edx:%eax (όπως απαιτεί η div) χρησιμοποιούμε την εντολή cltd (χωρίς παραμέτρους) (convert long to double long).

Εντολές δυαδικών ψηφίων

and πηγή, προορισμός
Σύζευξη
προορισμός <- προορισμός & πηγή
or πηγή, προορισμός
Διάζευξη
προορισμός <- προορισμός | πηγή
xor πηγή, προορισμός
Αποκλειστική διάζευξη
προορισμός <- προορισμός ^ πηγή
not προορισμός
Αντιστροφή
προορισμός <- ~ προορισμός
test πηγή, προορισμός
Έλεγχος
shl αριθμός, προορισμός
(Shift left)
Ολίσθηση (Shift) αριστερά
προορισμός <- προορισμός << πηγή
shr αριθμός, προορισμός
(Shift right)
Ολίσθηση δεξιά
προορισμός <- προορισμός >> πηγή
sar αριθμός, προορισμός
(Shift arithmetic right)
Αριθμητική ολίσθηση δεξιά
προορισμός <- προορισμός >> πηγή
rol αριθμός, προορισμός
(Rotate left)
Περιστροφή αριστερά
ror αριθμός, προορισμός
(Rotate right)
Περιστροφή δεξιά

Εντολές άλματος

JMP προορισμός
Άλμα
CALL προορισμός
Κλήση
RET
(Return)
Επιστροφή
JA/JNBE προορισμός
(Jump above)
Άλμα αν μεγαλύτερο (χωρίς πρόσημο)
JAE/JNB προορισμός
(Jump above or equal)
Άλμα αν μεγαλύτερο ή ίσο (χωρίς πρόσημο)
JB/JNAE προορισμός
(Jump bellow)
Άλμα αν μικρότερο (χωρίς πρόσημο)
JBE/JNA προορισμός
(Jump below or equal)
Άλμα αν μικρότερο ή ίσο (χωρίς πρόσημο)
JC προορισμός
(Jump carry)
Άλμα αν κρατούμενο
JNC προορισμός
(Jump no carry)
Άλμα αν όχι κρατούμενο
JE/JZ προορισμός
(Jump equal)
Άλμα αν ίσο
JNE/JNZ προορισμός
(Jump not equal)
Άλμα αν όχι ίσο
JG/JNLE προορισμός
(Jump greater)
Άλμα αν μεγαλύτερο (με πρόσημο)
JGE/JNL προορισμός
(Jump greater or equal)
Άλμα αν μεγαλύτερο ή ίσο (με πρόσημο)
JL/JNGE προορισμός
(Jump less)
Άλμα αν μικρότερο (με πρόσημο)
JLE/JNG προορισμός
(Jump less or equal)
Άλμα αν μικρότερο ή ίσο (με πρόσημο)
JO προορισμός
(Jump overflow)
Άλμα αν υπερχείλιση
JNO προορισμός
(Jump no overflow)
Άλμα αν όχι υπερχείλιση
JS προορισμός
(Jump sign)
Άλμα αν πρόσημο
JNS προορισμός
(Jump no sign)
Άλμα αν όχι πρόσημο

Βιβλιογραφία

Ασκήσεις

Συμβολική γλώσσα και η εκτέλεση γλωσσών υψηλού επιπέδου

Λειτουργίες του συμβολομεταφραστή

Δομή του πηγαίου κώδικα

Παράδειγμα:
        cmp $10,%ebx
        je end
        push %ebx
        mov %ebx, %esi
        inc %esi
        push $intform

Προσδιορισμός σταθερών

Στη συμβολική γλώσσα εκτός από εντολές του επεξεργαστή χρειάζεται να παραστήσουμε και σταθερές. Μπορούμε να εισάγουμε:

Μπορούμε να συνδυάσουμε σταθερές με τελεστές με σημασιολογία ίδια με αυτή της C. Μοναδιαίοι (unary) τελεστές:

Δυαδικοί (binary) τελεστές:
  1. Μέγιστη προτεραιότητα
  2. Ενδιάμεση προτεραιότητα
  3. Ελάχιστη προτεραιότητα
Η εισαγωγή των σταθερών στη μνήμη γίνεται με εντολές του συμβολομεταφραστή: Παράδειγμα:
.byte   'a', 'b'
.short 578
.word 324234
.string "helloworld\n"

Χρήση συμβόλων

Παράδειγμα:
intform.string        "%d\n"
.globl main
main:
        mov  $0, %ebx
loop:
        cmp $10,%ebx

Το περιβάλλον εκτέλεσης της C

Για να τους σκοπούς του μαθήματος τα προγράμματα σε συμβολική γλώσσα θα γράφονται και θα συνδέονται στο περιβάλλον εκτέλεσης των προγραμμάτων της C. Αυτό μας δίνει τις παρακάτω δυνατότητες: Παράδειγμα:
hi.string "helloworld\n"
.globl main
main:
        push $hi
        call printf
        addl $5, %sp
        pushl $0
        call exit

Κλήση συναρτήσεων

Χρήση καταχωρητών

Στο περιβάλλον λειτουργίας της C τα προγράμματα σε συμβολική γλώσσα πρέπει να χρησιμοποιούν τους καταχωρητές με συγκεκριμένο τρόπο:

Ένα ολοκληρωμένο πρόγραμμα

Το παρακάτω πρόγραμμα αντιγράφει την είσοδό του στην έξοδο μέχρι να συναντήσει τέλος του αρχείου (τη σταθερά -1).
// Copy standard input to standard output until EOF is read

.globl main
main:                           // Entry point

loop:   call    getchar         // Read a character in %eax
        cmpl    $-1, %eax       // EOF
        je      end             // Yesfinish
        push    %eax            // Pass it on
        call    putchar         // Print character pushed
        addl    $4, %esp        // Adjust back stack
        jmp     loop
end:    pushl   $             // Exit code
        call    exit

Βιβλιογραφία

Εργαλεία προγραμματισμού στο Unix

Σύνδεση με τον υπολογιστή

Απλές εντολές

date Τυπώνει τη μέρα και την ώρα
whoami Τυπώνει το όνομά μας
cal Τυπώνει το ημερολογίου του μήνα
echo hello Τυπώνει hello
echo $HOST Τυπώνει την τιμή της μεταβλητής HOST
finger Τυπώνει τους τρέχοντες χρήστες

Παράδειγμα:

University of the Aegean
Dept of Mathematics
Samos - Greece

*** Network Connection on ttyp0
Local Time: 19:00 on Wednesday, 29 October 1997

This is node athena.math.aegean.gr

UNAUTHORIZED ACCESS PROHIBITED

login: dspin
dspin's Password:
Last login: Wed Oct 29 18:29:10 on ttyp0 from kerkis.math.aeg
No mail.
There are no messages in your incoming mailbox.

athena:~> date
Wed Oct 29 19:01:20 EET 1997

athena:~> whoami
dspin

athena:~> echo hello
hello

athena:~> echo $HOST
athena

athena:~> cal
   October 1999
 S  M Tu  W Th  F  S
                1  2
 3  4  5  6  7  8  9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
31

athena:~> finger
Login    Name                 Tty  Idle  Login Time   Office     Office Phone
dspin    Diomidis Spinelis     p0        Oct 29 19:00 [ rls105.math.aeg ]
s95138   Antoniou Niki         p1        Oct 29 18:35 [ pyth ]                                                

Το πρόγραμμα βοηθείας

Παράδειγμα:

athena:~> apropos topological
tsort (1)            - Topological sort of a directed graph

athena:~> man tsort 

TSORT(1)                     UNIX Reference Manual                    TSORT(1)

NAME
     tsort - topological sort of a directed graph

SYNOPSIS
     tsort [file]

DESCRIPTION                                           
...

Αρχεία

touch αρχείο Δημιουργεί ένα κενό αρχείο
ls Τυπώνει τα αρχεία ενός καταλόγου
rm Διαγράφει αρχεία
mv Αλλάζει όνομα σε αρχεία
more Τυπώνει αρχεία σελίδα-σελίδα
cat Ενώνει και τυπώνει αρχεία
grep λέξη [αρχείο] Ψάχνει για τη λέξη στο αρχείο

Παράδειγμα:

athena:~/eg> touch myfile
athena:~/eg> ls
myfile
athena:~/eg> rm myfile
athena:~/eg> ls
athena:~/eg> touch one
athena:~/eg> ls
one
athena:~/eg> mv one two
athena:~/eg> ls
two
athena:~/eg> grep Elen /etc/passwd
s95128:XyiD4md/Jk9.E:690:100:Xatzinikolaou Eleni:/home/stud/c95/s95128:/bin/tcsh
s96027:savucWqVCSLHQ:587:100:Gkougkoulia Eleni:/home/stud/c96/s96027:/bin/tcsh
s96035:Qys4Lwmsy.uLI:599:100:Drimtzia Eleni:/home/stud/c96/s96035:/bin/tcsh
s96050:FokFq/a5cJBgs:626:100:Kontaraki Eleni:/home/stud/c96/s96050:/bin/tcsh
s96052:7T6k.D7lzkyK.:631:100:Kote Maria-Elena:/home/stud/c96/s96052:/bin/tcsh
s96138:GhPFvxg0F7W.g:840:100:Pozoukidou Eleni:/home/stud/c96/s96138:/bin/tcsh  

Αλλαγή εισόδου και εξόδου

Σύντομη λίστα εντολών του διορθωτή vi

Προσοχή όταν δίνετε μία εντολή έχει σημασία αν τα γράμματα είναι κεφαλαία ή μικρά.

Ανοίγοντας ένα αρχείο:
vi όνομα

Μετακίνηση μέσα στο αρχείο:
h (ή το αριστερό βελάκι) Μία θέση αριστερά
l (ή το δεξί βελάκι) Μία θέση δεξιά
k (ή το πάνω βελάκι) Μία γραμμή πάνω
j (ή το κάτω βελάκι) Μία γραμμή κάτω
w Στην επόμενη λέξη

Εισαγωγή και διαγραφή κειμένου:
Ο vi έχει δύο καταστάσεις, κατάσταση εντολών και κατάσταση εισαγωγής κειμένου.

Για να μπείτε στην κατάσταση εισαγωγής κειμένου δίνετε:
i Για να μπείτε στην κατάσταση εισαγωγής κειμένου χωρίς να μετακινηθεί ο δρομέας.
a Για να μπείτε στην κατάσταση εισαγωγής κειμένου μετακινούμενοι μία θέση δεξιά.
I Για να μπείτε στην κατάσταση εισαγωγής στην αρχή της γραμμής
A Για να μπείτε στην κατάσταση εισαγωγής στο τέλος της γραμμής

Για να μπείτε στην κατάσταση εντολών δίνετε:
ESC ή ctrl-[

Για να κάνετε αλλαγές στο κείμενό σας:
x διαγράφει τον χαρακτήρα όπου είναι ο δρομέας
dd διαγράφει την γραμμή που είναι ο δρομέας
u ακυρώνει την τελευταία αλλαγή

Αναζήτηση χαρακτήρων/λέξεων μέσα στο κείμενο:
/χαρακτήρες Βρίσκει την επόμενη γραμμή όπου εμφανίζεται το "χαρακτήρες".
?χαρακτήρες Βρίσκει την προηγούμενη γραμμή όπου εμφανίζεται το "χαρακτήρες".
n Επαναλαμβάνει την αναζήτηση στις επόμενες γραμμές.

Σώζοντας και βγαίνοντας από ένα αρχείο:
:w Σώζει τις τελευταίες αλλαγές και παραμένει μέσα στο αρχείο.
:x Σώζει τις τελευταίες αλλαγές και βγαίνει από το αρχείο
:q! Βγαίνει από το αρχείο χωρίς να σώσει τις αλλαγές

Βασισμένο σε κείμενο του Αντώνη Δανάλη <danalis@edu.physics.uch.gr (http://www.edu.physics.uch.gr/~danalis/index3.html)>

Ο διορθωτής vi: εντολές ανά κατηγορία

Η εντολές που ακολουθούν είναι χωρισμένες σε κατηγορίες ανάλογα με τη λειτουργία που κάνουν.

Εντολές που σβήνουν ή αντιγράφουν κομμάτια του κειμένου

" Ορίζει ένα ενταμιευτή ο οποίος μπορεί να χρησιμοποιηθεί από όλες τις εντολές που χρησιμοποιούν ενταμιευτές. Αν το " ακολουθείται από ένα γράμμα ή ένα νούμερο τότε αυτό είναι το όνομα του ενταμιευτή.
D Σβήνει από εκεί που βρίσκεται ο δρομέας μέχρι το τέλος της γραμμής
p Αντιγράφει τον ενταμιευτή που του ορίζουμε μετά την θέση ή την γραμμή που βρίσκεται ο δρομέας. Αν δεν ορίσουμε κάποιον συγκεκριμένο ενταμιευτή τότε η εντολή p χρησιμοποιεί τον γενικό.(Δείτε το παράδειγμα της εντολής ")
P Αντιγράφει τον ενταμιευτή που του ορίζουμε πριν την θέση ή την γραμμή που βρίσκεται ο δρομέας. Αν δεν ορίσουμε κάποιον συγκεκριμένο ενταμιευτή τότε η εντολή P χρησιμοποιεί τον γενικό.(Δείτε το παράδειγμα της εντολής ")
x Σβήνει τον χαρακτήρα πάνω στον οποίο βρίσκεται ο δρομέας. Αν πριν το x δώσουμε ένα αριθμό τότε θα σβηστούν τόσοι χαρακτήρες μετά το δρομέα.
X Σβήνει τον χαρακτήρα που βρίσκεται πριν το δρομέα.
d Σβήνει μέχρι εκεί που του ορίζουμε.Αν δώσουμε dd σβήνει όλη την γραμμή. Ό,τι σβήνεται τοποθετείται στον ενταμιευτή που του ορίζουμε με την εντολή " , αν δεν ορισθεί ενταμιευτής τότε χρησιμοποιείται ο γενικός.
Y Καταχωρεί την γραμμή στον ενταμιευτή που ορίζουμε, αν δεν ορίσουμε ενταμιευτή χρησιμοποιεί τον γενικό.
y Καταχωρεί στον ενταμιευτή που του ορίζουμε (ή στον γενικό) το κομμάτι του κειμένου που του ορίζουμε. Οι κανόνες που ακολουθεί είναι αυτοί που ακολουθεί και η d εκτός από την καταχώρηση μίας γραμμής που γίνεται δίνοντας
yy

Εντολές εισαγωγής κειμένου

A Ξεκινάει την εισαγωγή του κειμένου από το τέλος της γραμμής
I Ξεκινάει την εισαγωγή του κειμένου από την αρχή της γραμμής
o Ξεκινάει την εισαγωγή του κειμένου στην από κάτω γραμμή από αυτή που βρίσκεται ο δρομέας.
O Το κεφαλαίο γράμμα Ο ξεκινάει την εισαγωγή του κειμένου στην από πάνω γραμμή από αυτή που βρίσκεται ο δρομέας.
a Ξεκινάει την εισαγωγή κειμένου μία θέση μετά από εκεί που βρίσκεται ο δρομέας. Συνδυάζοντας την εντολή a με έναν αριθμό n πετυχαίνουμε εισαγωγή κειμένου n φορές.
i Ξεκινάει την εισαγωγή κειμένου μία θέση πριν από εκεί που βρίσκεται ο δρομέας. Συνδυάζοντας την εντολή i με έναν αριθμό n πετυχαίνουμε εισαγωγή κειμένου n φορές.

Μετακίνηση του δρομέα μέσα σε ένα αρχείο

^B Πάει πίσω μία σελίδα. Μαζί με ένα νούμερο n γυρνάει n σελίδες πίσω.
^F Πάει μπροστά μία σελίδα. Μαζί με ένα νούμερο n πάει n σελίδες μπροστά.
^D Πάει μπροστά μισό παράθυρο. Μαζί με ένα νούμερο n πάει n παράθυρα μπροστά
^H Μετακινεί το δρομέα μία θέση αριστερά. Μαζί με ένα νούμερο n πάει n θέσεις δεξιά
^J Μετακινεί το δρομέα μία γραμμή κάτω στην ίδια γραμμή. Μαζί με ένα νούμερο n πάει n γραμμές κάτω.
^M Πηγαίνει στον πρώτο χαρακτήρα της επόμενης γραμμής.
^N Ό,τι και το ^J
^P Μετακινεί το δρομέα μία γραμμή κάτω στην ίδια γραμμή. Μαζί με ένα νούμερο n πάει n γραμμές κάτω.
^U Πάει πίσω μισό παράθυρο. Μαζί με ένα νούμερο n πάει n παράθυρα πίσω
$ Μετακινεί το δρομέα στο τέλος της γραμμής. Μαζί με ένα νούμερο n πάει στο τέλος της γραμμής που είναι n γραμμές κάτω από το δρομέα.
% Αν ο δρομέας είναι πάνω σε παρένθεση ή αγκύλη τότε τον μετακινεί σε αυτήν που της ταιριάζει
( Μετακινεί το δρομέα στην αρχή της περιόδου
) Μετακινεί το δρομέα στην αρχή της επόμενης περιόδου
{ Μετακινεί το δρομέα στην αρχή της παραγράφου
} Μετακινεί το δρομέα στην επόμενη παράγραφο
| n| Μετακινει το δρομέα στην στήλη n
+ Μετακινεί το δρομέα στον πρώτο όχι-κενό χαρακτήρα της επόμενης γραμμής
- Μετακινεί το δρομέα στον πρώτο όχι-κενό χαρακτήρα της προηγούμενης γραμμής
^ Μετακινεί το δρομέα στον πρώτο όχι-κενό χαρακτήρα της γραμμής
_ Ό,τι και το ^
0 Ο χαρακτήρας μηδέν μετακινεί το δρομέα στην πρώτη στήλη της γραμμής
B Μετακινεί το δρομέα μία λέξη πίσω
E Μετακινεί το δρομέα μία λέξη μπροστά
nG Μετακινεί το δρομέα στην γραμμή n. Αν δε δώσετε n, τότε πάει στο τέλος του κειμένου.
H Μετακινεί το δρομέα στον πρώτο όχι-κενό χαρακτήρα στην κορυφή της οθόνης
L Μετακινεί το δρομέα στον πρώτο όχι-κενό χαρακτήρα στο κάτω μέρος της οθόνης
M Μετακινεί το δρομέα στον πρώτο όχι-κενό χαρακτήρα στο κέντρο της οθόνης
W Μετακινεί το δρομέα μία λέξη πίσω
b Μετακινεί το δρομέα μία λέξη πίσω. Αν ο δρομέας είναι μέσα σε λέξη τότε τον πάει στο πρώτο της γράμμα
e Μετακινεί το δρομέα μία λέξη μπροστά. Αν ο δρομέας είναι μέσα σε λέξη τότε τον πάει στο τελευταίο της γράμμα
w Ό,τι και το e
h Μετακινεί το δρομέα μία θέση αριστερά
j Μετακινεί το δρομέα μία γραμμή κάτω
k Μετακινεί το δρομέα μία γραμμή πάνω
l Μετακινεί το δρομέα μία θέση δεξιά

Μετακίνηση του δρομέα στην οθόνη

n^E Μετακινεί την οθόνη n γραμμές πάνω, χωρίς να την μετακινεί μία γραμμή.
n^Y Μετακινεί την οθόνη n γραμμές κάτω, χωρίς να την μετακινεί μία γραμμή.
z Αλλάζει την οθόνη ως εξής: z τοποθετεί την γραμμή στην κορυφή της οθόνης. z. τοποθετεί την γραμμή στο κέντρο της οθόνης. z- τοποθετεί την γραμμή στο κάτω μέρος της οθόνης. Αν πριν το z υπάρχει ένα νούμερο n, τότε κάνει αυτές τις αλλαγές για την γραμμή n. Για παράδειγμα 16z τοποθετεί στην κορυφή της οθόνης την γραμμή 16

Αντικατάσταση κειμένου

C Αλλάζει την γραμμή από την θέση του δρομέα μέχρι το τέλος της
R Αλλάζει τόσους χαρακτήρες όσους δίνουμε και αφήνει άθικτους τους υπόλοιπους
S Αλλάζει ολόκληρη την γραμμή
c Αλλάζει την γραμμή μέχρι να βρει μία τελεία. Το cc αλλάζει ολόκληρη την γραμμή
r Αλλάζει τον χαρακτήρα που βρίσκεται ο δρομέας
s Αλλάζει τον χαρακτηρα που είναι ο δρομέας και μπαίνει σε insert mode. Με έναν αριθμό n αλλάζει n χαρακτήρες. Ο τελευταίος χαρακτήρας που είναι να αλλάξει αντικαθίσταται προσωρινά με ένα $

Εντολές για να ψάχνετε μέσα σε ένα κείμενο

/ Ψάχνει προς τα κάτω το κείμενο για τη συμβολοσειρά (κανονική έκφραση) που ορίζουμε μετά το /
? Ψάχνει προς τα επάνω το κείμενο για τη συμβολοσειρά (κανονική έκφραση) που ορίζουμε μετά το ?
n Επαναλαμβάνει την τελευταία εντολή / ή ?
N Ό,τι και το n αλλά στην αντίθετη κατεύθυνση
f Ψάχνει μέσα στην γραμμή για τον χαρακτήρα που δίνουμε μετά το f και μετακινεί το δρομέα εκεί.
F Ψάχνει προς τα πίσω μέσα στην γραμμή για τον χαρακτήρα που δίνουμε μετά το f και μετακινεί το δρομέα εκεί.
T Ό,τι και το T αλλά πάει το δρομέα στην επόμενη θέση από τον χαρακτήρα.
t Ό,τι και το f αλλά πάει το δρομέα στην προηγούμενη θέση από τον χαρακτήρα
; Επαναλαμβάνει την τελευταία εντολή f ή F ή t ή T
, Επαναλαμβάνει την τελευταία εντολή f ή F ή t ή T αλλά στην αντίθετη κατεύθυνση

Εντολές μορφοποίησης χαρακτήρων και γραμμών

~ Αλλάζει από κεφαλαίο σε μικρό και από μικρό σε κεφαλαίο το γράμμα που είναι ο δρομέας
<< Μετακινεί την γραμμή προς τα αριστερά. Η τιμή του είναι μεταβλητή και μπορεί να ορισθεί από την set shiftwidth
>> Μετακινεί την γραμμή προς τα δεξιά. Η τιμή του είναι μεταβλητή και μπορεί να ορισθεί από την set shiftwidth
J Ενώνει την γραμμή που βρίσκεται ο δρομέας με την επόμενη. Με ένα νούμερο n μπροστά ενώνει n γραμμές

Εντολές για να σώσετε και να βγείτε από ένα αρχείο

^\ Μπαίνει σε ΕΧ mode. Ο EX editor είναι αυτός πάνω στον οποίο "χτίστηκε" ο VI. Για να επιστρέψετε σε vi mode δίνετε :vi
Q Ό,τι και η ^\
ZZ Βγαίνει από το αρχείο σώζοντας τις αλλαγές

Μερικές ακόμα εντολές

^G Δείχνει το όνομα του αρχείου τον συνολικό αριθμό γραμμών που έχει και τον αριθμό της γραμμής που βρίσκεται ο δρομέας
^L Καθαρίζει την οθόνη.
^R Ξανασχεδιάζει την οθόνη και διορθώνει τα λάθη
^[ Το σύμβολο <ctrl>-[ είναι ουσιαστικά το <ESC>
! Η εντολή ! ξεκινάει ένα shell. Αμα ορίσετε κάποιες συγκεκριμένες γραμμές τότε χρησιμοποιεί αυτές σαν input και τις αντικαθιστά με το output. Η !! χρησιμοποιεί σαν input την γραμμή που είναι ο δρομέας.
& Επαναλαμβάνει την τελευταία εντολή αλλαγής :s
. Επαναλαμβάνει την τελευταία εντολή
: Δίνει την δυνατότητα να εκτελεστεί μια εντολή του ex.
U Φέρνει την γραμμή που είναι ο δρομέας στην γραμμή που ήταν πριν την αλλάξετε
m "Σημαδεύει" την θέση με τον χαρακτήρα που ακολουθεί το m
u Ακυρώνει την τελευταία αλλαγή. Δύο συνεχόμενα u αφήνουν το αρχείο όπως ήταν πριν από αυτά

Εντολές του EX

Ο vi editor είναι βασισμένος πάνω σε έναν διορθωτή γραμμών, τον ex. Ετσι ο vi δίνει την δυνατότητα να εκτελεστούν οι εντολές του ex.
:ab string strings Κάνει συντομογραφία μιας συμβολοσειράς σε μια άλλη. Ετσι αν δώσετε:
:ab usa United States of America
τότε κάθε φορά που θα γράφετε usa θα εμφανίζεται το
United States of America
:map keys new_seq Με την μέθοδο του mapping μπορείτε να κάνετε συντομογραφίες σε εντολες.
:q Βγαίνει από το αρχείο. Αν έχετε κάνει αλλαγές που δεν έχετε σώσει τότε εμφανίζεται ένα μήνυμα και δεν βγαίνει από το αρχείο
:q! Βγαίνει από το αρχείο χωρίς να σώσει τις τελευταίες αλλαγές
:s/from/to/options Αντικαθιστά την λέξη from στην λέξη to.
:set [all] Ορίζει της μεταβλητές του vi. Η εντολή set all δείχνει όλες τις μεταβλητές και για όσες υπάρχει, την τιμή τους.
:una string κάνει το ανάποδο από την ab, δηλαδή αφαιρεί από μια συμβολοσειρά την ιδιότητά του να είναι συντομογραφία κάποιας άλλης.
:unm keys Το αντίστοιχο του una αλλά για την εντολή map
:vi filename Ξεκινάει την επεξεργασία ενός άλλου αρχείου.
:w Σώζει το αρχείο και παραμένει μέσα σε αυτό
:w new_file Σώζει τα περιεχόμενα του αρχείου σε ένα άλλο με όνομα new_file
:w >> filename Προσθέτει το αρχείο, κάτω από το αρχείο filename
:wq Σώζει τις τελευταίές αλλαγές και βγαίνει από το αρχείο
:x Ότι και το x

Βασισμένο σε κείμενο του Αντώνη Δανάλη <danalis@edu.physics.uch.gr (http://www.edu.physics.uch.gr/~danalis/index3.html)>

Εργαλεία γλωσσικών επεξεργαστών

Το λειτουργικό σύστημα Unix παραδοσιακά περιλαμβάνει πολλά εργαλεία τεχνολογίας γλωσσικών επεξεργαστών. Στο μάθημα αυτό θα χρησιμοποιήσουμε τα παρακάτω εργαλεία:

Χρήση του συμβολομεταφραστή gas

Η χρήση του συμβολομεταφραστή γίνεται μέσω του μεταγλωττιστή της C. Αυτός καλείται με την εντολή gcc:
gcc -o output filename.s
όπου output είναι το εκτελέσιμο αρχείο που θα δημιουργηθεί και filename.s είναι το αρχείο που θα περιέχει τον πηγαίο κώδικα στη συμβολική γλώσσα. Για να εκτελέσουμε το αρχείο, αν ο τρέχων κατάλογος (.) δεν είναι στο μονοπάτι των καταλόγων από τους οποίους εκτελούνται τα προγράμματα πρέπει να προσδιορίσουμε τον τρέχοντα κατάλογο ως τμήμα της εντολής που θέλουμε να εκτελέσουμε:
./mycommand
Έτσι ο κύκλος για την υλοποίηση ενός προγράμματος σε συμβολκή γλώσσα είναι ο παρακάτω:
vi hello.s
gcc -o hello hello.s
./hello
Αν έχουμε κάνει κάποιο λογικό λάθος στο πρόγραμμα θα εμφανιστεί στην οθόνη μας το παρακάτω μήνυμα:
Segmentation fault (core dumped)

Γενική βιβλιογραφία

Ασκήσεις

Συμβολική γλώσσα

  1. Γράψτε ένα πρόγραμμα σε συμβολική γλώσσα Intel 386 σε περιβάλλον Unix το οποίο να τυπώνει τους αριθμούς από το 0 μέχρι το 9.

Πεπερασμένα αυτόματα και κανονικές εκφράσεις

Εισαγωγή

Συμβολισμοί

Αλφάβητο (alphabet):
Πεπερασμένο σύνολο συμβόλων
Συμβολοσειρά (string):
Πεπερασμένη ακολουθία συμβόλων. Το μήκος της συμβολοσειράς χ συμβολίζεται ως |χ|.
Κενή συμβολοσειρά (empty string):
Συμβολοσειρά με μήκος 0. Συμβολίζεται ως ε.
Παράθεση (concatenation):
Η δημιουργία μιας νέας συμβολοσειράς από δύο άλλες (x, y) με τη σειριακή παράθεση των συμβόλων τους. Συμβολίζεται ως xy
Γλώσσα (language):
Ένα υποσύνολο των συμβολοσειρών που μπορούν να δημιουργηθούν από ένα συγκεκριμένο αλφάβητο.
Με βάση τον ορισμό της γλώσσας ορίζουμε:

Πεπερασμένα αυτόματα

Το θεωρητικό μοντέλο για την αναγνώριση μιας γλώσσας είναι ένα αυτόματο (automaton). Δύο αυτόματα τα οποία θα εξετάσουμε είναι τα:

Τα πεπερασμένα αυτόματα ορίζονται από την πεντάδα Μ = (Κ, Σ, δ, S, F):

K
Πεπερασμένο σύνολο καταστάσεων
Σ
Πεπερασμένο αλφάβητο εισόδου
δ
συνάρτηση αλλαγής κατάστασης (transition function) από μια κατάσταση Χ σε μια Χ' με βάση ένα σύμβολο εισόδου σ. δ(Χ, σ) = Χ'
S
Αρχική κατάσταση
F
Σύνολο τελικών καταστάσεων

Αν η συνάρτηση δ είναι μονότιμη τότε το αυτόματο αυτόματο καλείται ντετερμινιστικό πεπερασμένο αυτόματο (ΝΠΑ) (deterministic finite automaton (DFA)). Αντίθετα, αν για κάποιες τιμές (Χ, σ) της δ υπάρχουν πολλές πιθανές νέες καταστάσεις X' τότε το αυτόματο καλείται μη ντετερμινιστικό πεπερασμένο αυτόματο (ΜΠΑ) (non-deterministic finite automaton (NFA)). Οι γλώσσες που αναγνωρίζονται από τα πεπερασμένα αυτόματα λέγονται κανονικές γλώσσες (regular languages). Οι γλώσσες αυτές είναι κατάλληλες για να περιγράψουν τις λεκτικές μονάδες ενός προγράμματος.

Ένα πεπερασμένο αυτόματο μπορεί να παρασταθεί ως:

Κανονικές εκφράσεις

Μια κανονική έκφραση (regular expression) είναι ένας συμβολισμός κατάλληλος για την περιγραφή μιας κανονικής γλώσσας. Πολλά εργαλεία προγραμματισμού βασίζονται σε κανονικές εκφράσεις για την περιγραφή και την αναγνώριση συμβολοσειρών. Μπορούμε να ορίσουμε μια κανονική έκφραση σύμφωνα με τους παρακάτω κανόνες: Ο τελεστής * έχει την υψηλότερη και ο τελεστής | τη χαμηλότερη προτεραιότητα.

Κανονικές εκφράσεις στο Unix

Σε πολλά εργαλεία του Unix οι κανονικές εκφράσεις επιτρέπουν τον ορισμό σύνθετων συμβολοσειρών με δηλωτικό τρόπο. Η εντολή egrep επιτρέπει την αναζήτηση γραμμών που ικανοποιούν μια κανονική έκφραση μέσα σε ένα αρχείο. Τα παρακάτω σύμβολα έχουν ειδικό νόημα:
^
Αρχή της γραμμής
$
Τέλος της γραμμής
.
Οποιοδήποτε γράμμα
[abc]
Ένα από τα γράμματα a, b, ή c
[a-z]
Ένα από τα γράμματα a μέχρι z
[^abc]
Οποιοδήποτε γράμμα εκτός από τα a, b, και c.
Έκφραση*
Η έκφραση μηδέν ή περισσότερες φορές
Έκφραση+
Η έκφραση μία ή περισσότερες φορές
Έκφραση?
Η έκφραση μία ή καμία φορά
Έκφραση1|Έκφραση1
Η έκφραση1 ή η έκφραση2
(Έκφραση)
Το περιεχόμενο στην παρένθεση

Παράδειγμα: (με την εντολή cd ~dspin φτάνετε σε έναν κατάλογο που περιέχει ένα αρχείο με πολλές λέξεις (words))

athena:~> egrep 'abo' words
...
sabotage
seaboard
taboo
thereabouts
turnabout
vagabond
whereabout
...

athena:~> egrep '^abo' words
aboard
abode
abolish
abolition
abominable
abominate
aboriginal                 

athena:~> egrep bent words
absorbent
bent
benthic
debenture
incumbent
recumbent

athena:~> egrep 'bent$' words
absorbent
bent
incumbent
recumbent                                  

athena:~> egrep "heaven|hell" words
eggshell
heaven
heavenly
heavens
hell
hellfire
hellish
hello
hells
...

athena:~> egrep "(ga)(ba)+" words
gabardine
megabaud

athena:~> egrep pe+l words
..
peel
peeled
...

Ασκήσεις

  1. Να γράψετε ως κανονικές εκφράσεις τις παρακάτω εκφράσεις του egrep:
    w[xyz]k*
    a[^a-x]q
    a(b?)
    
  2. Να σχεδιάσετε ένα γράφο μετάβασης που να αναγνωρίζει τη γλώσσα με τις παρακάτω συμβολοσειρές:
    Kadafi
    Gadafi
    Ghadafi
    Khadafi
    
  3. Να σχεδιάσετε ένα γράφο μετάβασης που να αναγνωρίζει την κανονική έκφραση a* . (b | c) . (d | b)+.

Βιβλιογραφία

Λεκτική ανάλυση

Ο λεκτικός αναλυτής

Ο λεκτικός αναλυτής παρέχει τις παρακάτω λειτουργίες: Με τον τρόπο αυτό διαχωρίζονται οι εργασίες της λεκτικής και της συντακτικής ανάλυσης και κάθε μια υλοποιείται με τον πιο αποδοτικό τρόπο.

Λεκτικές μονάδες

Διεπαφή

Οπισθοδρόμηση

Υλοποίηση με διάγραμμα μετάβασης

Ασκήσεις

  1. Να υλοποιήσετε σε C έναν λεκτικό αναλυτή που να αναγνωρίζει τα παρακάτω λεκτικά σύμβολα: Η συνάρτηση του λεκτικού αναλυτή θα λέγεται yylex και θα επιστρέφει: των κωδικό του χαρακτήρα για τους τελεστές, 256 για τους ακέραιους, 257 για τις μεταβλητές και -1 στο τέλος του αρχείου. Ακόμα θα φυλάει στα πεδία της ένωσης yylval s και i τις τιμές των μεταβλητών και των ακεραίων αντίστοιχα. Σε περίπτωση λάθους ο λεκτικός αναλυτής θα τερματίζει τη λειτουργία του εμφανίζοντας ένα μήνυμα λάθους. Ο λεκτικός αναλυτής θα καλείται από το παρακάτω πρόγραμμα:
    #include <stdio.h>

    union {
            char *s;                /* Variable name */
            int i;                  /* Integer value */
    } yylval;

    int yylex();

    main()
    {
            int k;

            while ((k = yylex()) != -1) {
                    switch (k) {
                    case 256:
                            printf("INT: %d\n", yylval.i);
                            break;
                    case 257:
                            printf("VAR: %s\n", yylval.s);
                            break;
                    default:
                            printf("OPERATOR: %c\n", k);
                            break;
                    }
            }
    }

Βιβλιογραφία

Το μεταεργαλείο lex

Μεταεργαλεία

Το μεταεργαλείο lex

Διαδικασία χρήσης

Αρχείο εισόδου

Κανονικές εκφράσεις

Στο αρχείο εισόδου επιτρέπεται η παρακάτω σύνταξη για τις κανονικές εκφράσεις:
`x'
match the character `x'
`.'
any character (byte) except newline
`[xyz]'
a "character class"; in this case, the pattern matches either an `x', a `y', or a `z'
`[abj-oZ]'
a "character class" with a range in it; matches an `a', a `b', any letter from `j' through `o', or a `Z'
`[^A-Z]'
a "negated character class", i.e., any character but those in the class. In this case, any character EXCEPT an uppercase letter.
`[^A-Z\n]'
any character EXCEPT an uppercase letter or a newline
`r*'
zero or more r's, where r is any regular expression
`r+'
one or more r's
`r?'
zero or one r's (that is, "an optional r")
`r{2,5}'
anywhere from two to five r's
`r{2,}'
two or more r's
`r{4}'
exactly 4 r's
`{name}'
the expansion of the "name" definition (see above)
`"[xyz]\"foo"'
the literal string: `[xyz]"foo'
`\x'
if x is an `a', `b', `f', `n', `r', `t', or `v', then the ANSI-C interpretation of \x. Otherwise, a literal `x' (used to escape operators such as `*')
`\0'
a NUL character (ASCII code 0)
`\123'
the character with octal value 123
`\x2a'
the character with hexadecimal value 2a
`(r)'
match an r; parentheses are used to override precedence (see below)
`rs'
the regular expression r followed by the regular expression s; called "concatenation"
`r|s'
either an r or an s
`r/s'
an r but only if it is followed by an s. The text matched by s is included when determining whether this rule is the longest match, but is then returned to the input before the action is executed. So the action only sees the text matched by r. This type of pattern is called trailing context. (There are some combinations of `r/s' that flex cannot match correctly; see notes in the Deficiencies / Bugs section below regarding "dangerous trailing context".)
`^r'
an r, but only at the beginning of a line (i.e., which just starting to scan, or right after a newline has been scanned).
`r$'
an r, but only at the end of a line (i.e., just before a newline). Equivalent to "r/\n". Note that flex's notion of "newline" is exactly whatever the C compiler used to compile flex interprets '\n' as; in particular, on some DOS systems you must either filter out \r's in the input yourself, or explicitly use r/\r\n for "r$".
`<s>r'
an r, but only in start condition s (see below for discussion of start conditions) <s1,s2,s3>r same, but in any of start conditions s1, s2, or s3
`<*>r'
an r in any start condition, even an exclusive one.
`<<EOF>>'
an end-of-file <s1,s2><<EOF>> an end-of-file when in start condition s1 or s2

Τιμές προσβάσιμες στο χρήστη

Απλό παράδειγμα

       int num_lines = 0, num_chars = 0;

%%
\n      ++num_lines; ++num_chars;
.       ++num_chars;

%%
main()
        {
        yylex();
        printf( "# of lines = %d, # of chars = %d\n",
                num_lines, num_chars );
        }

Παράδειγμα χρήσης

athena:~/t> cat foo.l
%option noyywrap

        int num_lines = 0, num_chars = 0;

%%
\n      ++num_lines; ++num_chars;
.       ++num_chars;

%%
main()
        {
        yylex();
        printf( "# of lines = %d, # of chars = %d\n",
                num_lines, num_chars );
        }
athena:~/t> lex foo.l
athena:~/t> cc -o foo lex.yy.c
athena:~/t> foo <foo.l
# of lines = 15, # of chars = 261
athena:~/t>

Σύνθετο παράδειγμα

/* scanner for a toy Pascal-like language */

%{
/* need this for the call to atof() below */
#include <math.h>
%}

DIGIT    [0-9]
ID       [a-z][a-z0-9]*

%%

{DIGIT}+    {
            printf( "An integer: %s (%d)\n", yytext,
                    atoi( yytext ) );
            }

{DIGIT}+"."{DIGIT}*        {
            printf( "A float: %s (%g)\n", yytext,
                    atof( yytext ) );
            }

if|then|begin|end|procedure|function        {
            printf( "A keyword: %s\n", yytext );
            }

{ID}        printf( "An identifier: %s\n", yytext );

"+"|"-"|"*"|"/"   printf( "An operator: %s\n", yytext );

"{"[^}\n]*"}"     /* eat up one-line comments */

[ \t\n]+          /* eat up whitespace */

.           printf( "Unrecognized character: %s\n", yytext );

%%

main()
{
    yylex();
}

Τρόπος λειτουργίας

Παράδειγμα

Αρχείο εισόδου
%option noyywrap

%%
hi      printf("HI\n");
\.      printf("DOT\n");

%%
main()
        {
        yylex();
        }
Μη αιτιοκρατικό πεπερασμένο αυτόματο
Το αυτόματο αρχίζει από την κατάσταση 12. Από κάθε κατάσταση μπορεί να οδηγηθεί σε δύο νέες καταστάσεις Κ1, Κ2. (ε είναι το κενό σύμβολο).
                 συμβ.   Κ1    Κ2
Κατάσταση    1	 ε :     0,    0
Κατάσταση    2	 ε :     0,    0
Κατάσταση    3	 h :     4,    0	// Διάβασε h
Κατάσταση    4	 i :     5,    0	// Διάβασε i
Κατάσταση    5	 ε :     0,    0  [1]	// Αναγνώρισε hi
Κατάσταση    6	 ε :     1,    3
Κατάσταση    7	 . :     8,    0	// Διάβασε .
Κατάσταση    8	 ε :     0,    0  [2]	// Αναγνώρισε .
Κατάσταση    9	 ε :     6,    7
Κατάσταση   10	 EOF:    11,   0
Κατάσταση   11	 ε :     0,    0  [3]	// Τέλος του αρχείου
Κατάσταση   12	 ε :     9,   10
Κλάσεις ισοδυναμίας
\000 = -1  ' ' = 1     @ = 1     ` = 1 
\001 = 1     ! = 1     A = 1     a = 1
\002 = 1     " = 1     B = 1     b = 1
\003 = 1     # = 1     C = 1     c = 1
\004 = 1     $ = 1     D = 1     d = 1
\005 = 1     % = 1     E = 1     e = 1
\006 = 1     & = 1     F = 1     f = 1
  \a = 1     ' = 1     G = 1     g = 1
  \b = 1     ( = 1     H = 1     h = 3
  \t = 1     ) = 1     I = 1     i = 4
  \n = 1     * = 1     J = 1     j = 1
  \v = 1     + = 1     K = 1     k = 1
  \f = 1     , = 1     L = 1     l = 1
  \r = 1     - = 1     M = 1     m = 1
\016 = 1     . = 2     N = 1     n = 1
\017 = 1     / = 1     O = 1     o = 1
\020 = 1     0 = 1     P = 1     p = 1
\021 = 1     1 = 1     Q = 1     q = 1
\022 = 1     2 = 1     R = 1     r = 1
\023 = 1     3 = 1     S = 1     s = 1
\024 = 1     4 = 1     T = 1     t = 1
\025 = 1     5 = 1     U = 1     u = 1
\026 = 1     6 = 1     V = 1     v = 1
\027 = 1     7 = 1     W = 1     w = 1
\030 = 1     8 = 1     X = 1     x = 1
\031 = 1     9 = 1     Y = 1     y = 1
\032 = 1     : = 1     Z = 1     z = 1
\033 = 1     ; = 1     [ = 1     { = 1
\034 = 1     < = 1     \ = 1     | = 1
\035 = 1     = = 1     ] = 1     } = 1
\036 = 1     > = 1     ^ = 1     ~ = 1
\037 = 1     ? = 1     _ = 1  \177 = 1 
Μετακλάσεις ισοδυναμίας
1 = 1
2 = 1
3 = 1
4 = 2
Αιτιοκρατικό πεπερασμένο αυτόματο
Κατάσταση # 1:
	1	4
	2	5	// .
	3	6	// h
	4	4
Κατάσταση # 2:
	1	4
	2	5	// .
	3	6	// h
	4	4
Κατάσταση # 3:
Κατάσταση # 4:
Κατάσταση # 5:		// Αναγνώρισε .
Κατάσταση # 6:
	4	7	// i
Κατάσταση # 7:		// Αναγνώρισε hi
Παραγόμενος κώδικας σε C (τμήματα)

/* A lexical scanner generated by flex */

/* ... */

/* Πίνακες μετάβασης */

static yyconst short int yy_def[10] =
    {   0,
        8,    1,    8,    8,    8,    9,    8,    0,    8
    } ;

static yyconst short int yy_nxt[12] =
    {   0,
        4,    5,    6,    4,    7,    8,    3,    8,    8,    8,
        8
    } ;

static yyconst short int yy_chk[12] =
    {   0,
        1,    1,    1,    1,    9,    3,    8,    8,    8,    8,
        8
    } ;

/* ... */

/* Υλοποίηση του ΝΠΑ */

yy_match:
                do
                        {
                        register YY_CHAR yy_c = yy_ec[YY_SC_TO_UI(*yy_cp)];
                        while ( yy_chk[yy_base[yy_current_state] + yy_c] != yy_current_state )
                                {
                                yy_current_state = (int) yy_def[yy_current_state];
                                if ( yy_current_state >= 9 )
                                        yy_c = yy_meta[(unsigned int) yy_c];
                                }
                        yy_current_state = yy_nxt[yy_base[yy_current_state] + (unsigned int) yy_c];
                        *yy_state_ptr++ = yy_current_state;
                        ++yy_cp;
                        }

/* ... */

/* Υλοποίηση ενεργειών χρήστη */
                switch ( yy_act )
                        { /* beginning of action switch */
                case 1:
                        printf("HI\n");
                        break;
                case 2:
                        printf("DOT\n");
                        break;

/* ... */

/* Κώδικας χρήστη */

main()
        {
        yylex();
        }

Ασκήσεις

  1. Να υλοποιήσετε με το μεταεργαλείο lex έναν λεκτικό αναλυτή που να αναγνωρίζει τα παρακάτω λεκτικά σύμβολα: Ο λεκτικός αναλυτής θα τυπώνει στην έξοδό του τον τύπο του κάθε συμβόλου που θα αναγνωρίζει καθώς και την τιμή του. Παράδειγμα:
    Read integer (42)
    Read variable (i)
    Read keyword (while)
    

Βιβλιογραφία

Αυτόματα στοίβας και γραμματικές

Αυτόματα στοίβας

Τα πεπερασμένα αυτόματα δεν μπορούν να αναγνωρίσουν έναν μεγάλο αριθμό από γλώσσες που έχουν πρακτική σημασία στην πληροφορική (π.χ. όλες τις γλώσσες υψηλού επιπέδου). Για το λόγο αυτό ορίζουμε και εξετάζουμε τα ισχυρότερα αυτόματα στοίβας.

Τα αυτόματα στοίβας (ΑΣ) ορίζονται από την εξάδα Μ = (Κ, Σ, Γ, δ, q0, Z0)

K
Πεπερασμένο σύνολο καταστάσεων
Σ
Αλφάβητο εισόδου
Γ
Αλφάβητο για τη στοίβα
δ
Σύνολο κινήσεων που εκτελεί το αυτόματο
q0
Αρχική κατάσταση
Ζ0
Αρχική σύμβολο της στοίβας

Κάθε εντολή του ΑΣ ονομάζεται κίνηση (move). Η κίνηση ορίζεται από:

Όταν σε κάθε τριάδα αντιστοιχεί μια το πολύ κίνηση τότε το αυτόματο λέγεται ντετερμινιστικό αυτόματο στοίβας (ΝΑΣ) (deterministic stack automaton (DSA)). Αντίθετα, αν για κάποιες τιμές της δ υπάρχουν πολλές πιθανές κινήσεις τότε το αυτόματο καλείται μη ντετερμινιστικό αυτόματο στοίβας (ΜΑΣ) (non-deterministic stack automaton (NSA)). (Οι λέξη ντετερμινιστικό που εμφανίζεται στην ελληνική βιβλιογραφία είναι συνώνυμη με τη λέξη αιτιοκρατικό).

Η λειτουργία του ΑΣ ορίζεται από τρεις υποεντολές που εκτελούνται διαδοχικά:

  1. την υποεντολή της στοίβας
  2. την υποεντολή εισόδου
  3. την υποεντολή επόμενης κατάστασης
Οι εντολές μπορούν να εκτελέσουν τις παρακάτω λειτουργίες:

Παράσταση κινήσεων

Κάθε εντολή κίνησης έχει τη μορφή: δ(σ1, σ2, σ3) = (Σ1, Σ2, Σ3) όπου:
σ1
η κατάσταση του ΑΣ,
σ2
το σύμβολο στην κορυφή της στοίβας,
σ3
το σύμβολο εισόδου,
Σ1
η υποεντολή της στοίβας,
Σ2
η υποεντολή εισόδου,
Σ3
η υποεντολή επόμενης κατάστασης.
Αν δεν προσδιορίζεται μια εντολή τότε αυτή υπονοείται ως εξής:

Λειτουργία

Αν ένα ΑΣ τότε θεωρούμε πως το ΑΣ αναγνώρισε τη συμβολοσειρά εισόδου.

Ένα ΜΑΣ όταν φτάσει σε στάδιο που δεν ορίζεται κίνηση οπισθοδρομεί μέχρι ένα στάδιο που να μπορέσει να εκτελέσει μια εναλλακτική κίνηση.

Παράδειγμα

Ορίζουμε ένα αυτόματο Μ = (Κ, Σ, Γ, δ, q0, Z0) ως εξής:
Κ
{Α}
Σ
{'(', ')'}
Γ
{Χ, I}
Δ
{ }
q0
A
Z0
X

Το αυτόματο αυτό αναγνωρίζει ζυγισμένα ζευγάρια παρενθέσεων.

Γραμματική

Τη σύνταξη των γλωσσών την ορίζουμε με γραμματικές.

Μια γραμματική ορίζεται από την τετράδα: G = (VN, VT, P, S):

VN
αλφάβητο με τα μη τερματικά σύμβολα (non-terminal symbols).
VT
αλφάβητο με τα τερματικά σύμβολα (terminal symbols).
P
συντακτικοί κανόνες (syntax rules) που αποτελούνται από ζευγάρια (α, β) που συμβολίζονται α -> β. Το α αποτελείται από ένα σύμβολο ενώ το β από μια ακολουθία συμβόλων.
S
Το αρχικό μη τερματικό σύμβολο εκκίνησης.

Από μια γραμματική μπορούμε να παράξουμε συμβολοσειρές αρχίζοντας από το σύμβολο εκκίνισης με διαδοχικές εφαρμογές των συντακτικών κανόνων. Κάθε εφαρμογή αντικαθιστά ένα σύμβολο που βρίσκεται στα αριστερά ενός συντακτικού κανόνα με τα αντίστοιχα σύμβολα που βρίσκονται δεξιά. Με τον τρόπο αυτό μπορούμε να καταλήξουμε σε μια συμβολοσειρά από τερματικά σύμβολα. Η συμβολοσειρά αυτή ανήκει στη γλώσσα που ορίζεται από την αντίστοιχη γραμματική. Η διαδικασία δημιουργίας της συμβολοσειράς από τη γραμματική ονομάζεται παραγωγή (derivation).

Παράδειγμα

G = (VN, VT, P, S):
VN
{SUM, PROD, NUM}
VT
{0, 1, +, *}
P
{ }
S
SUM

Συντακτικό δένδρο

Παράδειγμα

                SUM
            PROD + 0
           1 * 0

Ιεραρχία γραμματικών

Ανάλογα με τους συντακτικούς κανόνες που περιέχει μια γραμματική ορίζεται η ιεραρχία γραμματικών του Chomsky
Τύπου 0: ελεύθερη
με κανόνες της μορφής σ->τ όπου:
Τύπου 1: γραμματική με συμφραζόμενα (context sensitive grammar)
με κανόνες της μορφής μΑν->μχν όπου:
Τύπου 2: γραμματική χωρίς συμφραζόμενα (context free grammar)
με κανόνες της μορφής Α->χ όπου: Οι γραμματικές αυτές μπορούν να ορίσουν συχνά τη σύνταξη μιας γλώσσας.
Τύπου 3: κανονική γραμματική (regular grammar)
με κανόνες της μορφής Α->α ή Α->αΒ όπου: Οι γραμματικές αυτές μπορούν να ορίσουν συχνά τα λεκτικά στοιχεία μιας γλώσσας.

Συμβολισμός EBNF

Ο συμβολισμός EBNF (Extended Backus-Naur Form) μας επιτρέπει να ορίσουμε με περιεκτικό τρόπο μια γραμματική:

Παράδειγμα

expr	::= term ('+' term |'-' term) *
term	::= factor ('*' factor |'/' factor) *
factor	::= digit | '-' factor | '(' expr ')' 
digit	::= '0' | '1' | '2' | '3' |'4' | '5' | '6' | '7' | '8' | '9'

Ασκήσεις

  1. Να ορίσετε σε EBNF τη σύνταξη του ορισμού δομών (structure) της C.

Βιβλιογραφία

Συντακτική ανάλυση

Ο συντακτικός αναλυτής

Ο συντακτικός αναλυτής:

Τρόποι υλοποίησης

Διαχωρίζουμε δύο είδη συντακτικών αναλυτών ανάλογα με τον τρόπο που σχηματίζουν το δένδρο:
Συντακτικός αναλυτής από πάνω προς τα κάτω (top down parser)
ο αναλυτής αυτός ξεκινά από τη ρίζα του δένδρου και αντικαθιστά μη τερματικά σύμβολα από το αριστερό τμήμα παραγωγών με τα αντίστοιχα δεξιά τους τμήματα μέχρι να αναγνωρίσει όλη την είσοδο. Το μη τερματικό σύμβολο που επιλέγεται για αντικατάσταση είναι κάθε φορά το αριστερότερο (leftmost) (L) μη τερματικό σύμβολο. Ακόμα, ο αναλυτής για να μπορέσει να υλοποιηθεί πρέπει να αρκεί να διαβάζει κάθε φορά ένα (1) μόνο σύμβολο από την είσοδο από αριστερά προς τα δεξιά (L). Έτσι ο αναλυτής και η αντίστοιχη γραμματική ονομάζονται LL(1).

Ο αναλυτής αυτός μπορεί να υλοποιηθεί είτε ως ένας συντακτικός αναλυτής αναδρομικής κατάβασης (recursive descent parser) ή ως ένα ειδικό αυτόματο στοίβας. Ο συντακτικός αναλυτής αναδρομικής κατάβασης αποτελείται από συναρτήσεις, μια για κάθε μη τερματικό σύμβολο, που καλούν η μια την άλλη. Το ρόλο της στοίβας παίζει η στοίβα κλήσεων των συναρτήσεων που υλοποιεί ο επεξεργαστής. Για το λόγο αυτό οι συντακτικοί αυτοί αναλυτές είναι συχνά γρηγορότεροι από αυτούς που υλοποιούνται με αυτόματα.

Συντακτικός αναλυτής από κάτω προς τα πάνω (bottom up parser)
ο αναλυτής αυτός αντικαθιστά δεξιά τμήματα παραγωγών με τα αντίστοιχα αριστερά τους τμήματα.

Ο αναλυτής αυτός χρησιμοποιεί κάθε φορά τη δεξιότερη (rightmost) παραγωγή. Ανάλογα με το αν χρειάζεται να εξετάσει 0, 1 ή Κ σύμβολα εισόδου για να υλοποιήσει μια παραγωγή ονομάζεται LR(0), LR(1) ή LR(K). Η υλοποίηση του αναλυτή αυτού γίνεται από ειδικό αυτόματο στοίβας.

Και στις δύο περιπτώσεις οι γραμματικές πρέπει να εκπληρούν ορισμένους κανόνες για να μπορούν να σχηματίσουν τον αντίστοιχο συντακτικό αναλυτή και κατηγοριοποιούνται ανάλογα με το όνομα του αναλυτή (LL(1), LR(1), ...).

Αναλυτής αναδρομικής κατάβασης

Παράδειγμα

Ο παρακάτω συντακτικός αναλυτής ελέγχει για τον αν η είσοδός του ανήκει στη γλώσσα που ορίζεται από την παρακάτω γραμματική EBNF:
  expr		::= term ('+' term |'-' term) * 
  term		::= factor ('*' factor |'/' factor) * 
  factor	::= digit | '-' factor | '(' expr ')'  
  digit	::= 0 | 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | 9 
και τυπώνει "Parse ok", ") expected" ή "Syntax error".
/*
 * Recursive descent parser for the following expression grammar:
 * expr         ::= term ('+' term |'-' term) *
 * term         ::= factor ('*' factor |'/' factor) *
 * factor       ::= digit | '-' factor | '(' expr ')' 
 * digit        ::= 0 | 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | 9
 *
 * D. Spinellis, November 1999
 *
 * $Id: parse0.c 1.1 1999/11/10 00:14:12 dds Exp dds $
 *
 */

#include <stdio.h>

/* Forward declarations */
void term(void);
void factor(void);

/* expr ::= term ('+' term |'-' term) * */
void
expr(void)
{
        int c;

        term();
        for (;;) {
                switch (c = getchar()) {
                case '+':
                        term();
                        break;
                case '-':
                        term();
                        break;
                default:
                        ungetc(c, stdin);
                        return;
                }
        }
}

/* term ::= factor ('*' factor |'/' factor) * */
void
term(void)
{
        int c;

        factor();
        for (;;) {
                switch (c = getchar()) {
                case '*':
                        factor();
                        break;
                case '/':
                        factor();
                        break;
                default:
                        ungetc(c, stdin);
                        return;
                }
        }
}

/* factor ::= digit | '-' factor | '(' expr ')' */
void
factor(void)
{
        int c;

        switch (c = getchar()) {
        case '0'case '1'case '2'case '3'case '4':
        case '5'case '6'case '7'case '8'case '9':
                return;
        case '-':
                factor();
                ;
        case '(':
                expr();
                c = getchar();
                if (c != ')') {
                        printf("Expected ')'\n");
                        exit(1);
                }
                return;
        default:
                printf("Syntax error\n");
                exit(1);
        }
}

/*
 * Test harness
 */
main()
{
        expr();
        printf("Parse ok\n");
}

Χρήση του αναλυτή

% parse
1+2*4-7*(3-1)
Parse ok

% parse
3++3-3
Syntax error

% parse
3-2*(2+2
Expected ')'

% parse
2-
Syntax error

Παράσταση του συντακτικού δένδρου

Παράδειγμα ορισμού (tree.h)

/*
 * Tree type and building function declarations
 *
 * D. Spinellis, November 1999
 *
 * $Id: tree.h 1.1 1999/11/09 23:32:12 dds Exp $
 *
 */

enum e_nodetype {
        ent_binop,                              /* Binary operator expr */
        ent_unop,                               /* Unary operator expr */
        ent_digit                               /* Digit expr */
};

struct s_tree {
        enum e_nodetype nt;                     /* Expression type */
        union {
                struct s_binop {
                        struct s_tree *left, *right;
                        char op;
                } binop;                        /* Binary operator expr */
                struct s_unary {
                        struct s_tree *expr;
                        char op;
                } unop;                         /* Unary operator expr */
                int digit;                      /* Digit expression */
        } u;
};


struct s_tree * mk_binop(struct s_tree *left, char op, struct s_tree *right);
struct s_tree * mk_unop(char op, struct s_tree *expr);
struct s_tree * mk_digit(char c);

Παράδειγμα συναρτήσεων δημιουργία και εκτύπωσης (tree.c)

/*
 * Tree building functions
 *
 * D. Spinellis, November 1999
 *
 * $Id: tree.c 1.2 1999/11/10 09:15:35 dds Exp $
 *
 */

#include <stdlib.h>
#include "tree.h"

struct s_tree *
mk_binop(struct s_tree *left, char op, struct s_tree *right)
{
        struct s_tree *t = malloc(sizeof(struct s_tree));

        t->nt = ent_binop;
        t->u.binop.left = left;
        t->u.binop.op = op;
        t->u.binop.right = right;
        return (t);
}

struct s_tree *
mk_unop(char op, struct s_tree *expr)
{
        struct s_tree *t = malloc(sizeof(struct s_tree));

        t->nt = ent_unop;
        t->u.unop.op = op;
        t->u.unop.expr = expr;
        return (t);
}

struct s_tree *
mk_digit(char c)
{
        struct s_tree *t = malloc(sizeof(struct s_tree));

        t->nt = ent_digit;
        t->u.digit = c -'0';
        return (t);
}

/*
 * Indent output by the indent ammount given
 */
static void
indent(int level)
{
        int i;

        for (i = 0; i < level; i++)
                printf("    ");
}

/*
 * Dump an expression tree to the standard output
 */
void
dumptree(struct s_tree *t)
{
        static int il = 0;              /* Indent level */

        il++;
        switch (t->nt) {
        case ent_binop:
                dumptree(t->u.binop.left);
                indent(il); printf("%c\n", t->u.binop.op);
                dumptree(t->u.binop.right);
                break;
        case ent_unop:
                indent(il); printf("%c\n", t->u.unop.op);
                dumptree(t->u.unop.expr);
                break;
        case ent_digit:
                indent(il); printf("%d\n", t->u.digit);
                break;
        }
        il--;
}

Δημιουργία συντακτικού δένδρου

Για να κάνουμε έναν συντακτικό αναλυτή αναδρομικής καθόδου να δημιουργήσει το συντακτικό δένδρο, φροντίζουμε κάθε συνάρτηση του αναλυτή να επιστρέφει τον αντίστοιχο κλάδο του δένδρου. Το παρακάτω παράδειγμα δημιουργεί και τυπώνει ένα συντακτικό δένδρο για τη γραμματική που εξετάζουμε:
/*
 * Recursive descent parser for the following expression grammar:
 * expr         ::= term ('+' term |'-' term) *
 * term         ::= factor ('*' factor |'/' factor) *
 * factor       ::= digit | '-' factor | '(' expr ')' 
 * digit        ::= 0 | 1 | 2 | 3 |4 | 5 | 6 | 7 | 8 | 9
 *
 * D. Spinellis, November 1999
 *
 * $Id: parse.c 1.1 1999/11/09 23:32:12 dds Exp $
 *
 */

#include <stdio.h>
#include "tree.h"

/* Forward declarations */
struct s_tree *term(void);
struct s_tree *factor(void);

/* expr ::= term ('+' term |'-' term) * */
struct s_tree *
expr(void)
{
        struct s_tree *e1, *e2;
        int c;

        e1 = term();
        for (;;) {
                switch (c = getchar()) {
                case '+':
                        e2 = term();
                        e1 = mk_binop(e1, '+', e2);
                        break;
                case '-':
                        e2 = term();
                        e1 = mk_binop(e1, '-', e2);
                        break;
                default:
                        ungetc(c, stdin);
                        return (e1);
                }
        }
}

/* term ::= factor ('*' factor |'/' factor) * */
struct s_tree *
term(void)
{
        struct s_tree *t1, *t2;
        int c;

        t1 = factor();
        for (;;) {
                switch (c = getchar()) {
                case '*':
                        t2 = factor();
                        t1 = mk_binop(t1, '*', t2);
                        break;
                case '/':
                        t2 = factor();
                        t1 = mk_binop(t1, '/', t2);
                        break;
                default:
                        ungetc(c, stdin);
                        return (t1);
                }
        }
}

/* factor ::= digit | '-' factor | '(' expr ')' */
struct s_tree *
factor(void)
{
        struct s_tree *f;
        int c;

        switch (c = getchar()) {
        case '0'case '1'case '2'case '3'case '4':
        case '5'case '6'case '7'case '8'case '9':
                return (mk_digit((char)c));
        case '-':
                f = factor();
                return (mk_unop('-', f));
        case '(':
                f = expr();
                c = getchar();
                if (c != ')')
                        printf("Expected ')'\n");
                return (f);
        }
}

/*
 * Test harness
 */
main()
{
        struct s_tree *t;

        t = expr();
        dumptree(t);
}

Παράδειγμα χρήσης

Με τις παρακάτω εντολές βλέπουμε το συντακτικό δένδρο της έκφρασης 1+2*(4-5)*-3 :
% echo 1+2*(4-5)*-3 | parse
        1
    +
                2
            *
                    4
                -
                    5
        *
            -
                3

Ασκήσεις

  1. Να υλοποιήσετε σε C ένα συντακτικό αναλυτή που να αναγνωρίζει και να τυπώνει το συντακτικό δένδρο για αριθμητικές εκφράσεις που αποτελούνται από ψηφία, +, -, *, /, (, ), ^ (ύψωση σε δύναμη) σύμφωνα με τους συνηθισμένους κανόνες προτεραιότητας της αριθμητικής.

Βιβλιογραφία

Το μεταεργαλείο yacc

Το μεταεργαλείο yacc

Διαδικασία χρήσης

Σημείωση

Στο εργαστήριο θα χρησιμοποιηθεί το πρόγραμμα bison που είναι συμβατό υπερσύνολο του yacc.

Αρχείο εισόδου

Δηλώσεις του yacc

Οι δηλώσεις του yacc μας επιτρέπουν να ορίσουμε:

Συντακτικοί κανόνες

Τερματικά και μη τερματικά σύμβολα

Τιμές των συμβόλων

Σημασιολογικοί κανόνες

Απλό παράδειγμα

Το παρακάτω πρόγραμμα υλοποιεί μια απλή αριθμομηχανή.
/* Infix notation calculator--calc */

%{
#define YYSTYPE double
#include <math.h>               /* Needed for pow */
%}

/* YACC Declarations */
%token NUM
%left '-' '+'
%left '*' '/'
%left NEG     /* negation--unary minus */
%right '^'    /* exponentiation        */

/* Grammar follows */
%%
input:    /* empty string */
        | input line
;

line:     '\n'
        | exp '\n'  { printf ("\t%.10g\n", $1); }
;

exp:      NUM                { $$ = $1;         }
        | exp '+' exp        { $$ = $1 + $3;    }
        | exp '-' exp        { $$ = $1 - $3;    }
        | exp '*' exp        { $$ = $1 * $3;    }
        | exp '/' exp        { $$ = $1 / $3;    }
        | '-' exp  %prec NEG { $$ = -$2;        }
        | exp '^' exp        { $$ = pow ($1, $3); }
        | '(' exp ')'        { $$ = $2;         }
;
%%
#include <ctype.h>
#include <stdio.h>

/* Lexical analyzer */
int
yylex ()
{
        int c;

        /* skip white space  */
        while ((c = getchar ()) == ' ' || c == '\t')  
                ;
        /* process numbers   */
        if (c == '.' || isdigit (c)) {
                ungetc (c, stdin);
                scanf ("%lf", &yylval);
                return NUM;
        }
        /* return end-of-file  */
        if (c == EOF)                            
                return 0;
        /* return single chars */
        return c;                                
}

main ()
{
        yyparse();
}

/* error function */
void
yyerror (char *s)  /* Called by yyparse on error */
{
        printf("%s\n", s);
}

Μεταγλώττιση και χρήση

Η διαδικασία μεταγλώττισης και χρήσης του αρχείου της αριθμομηχανής (αν έχει ονομαστεί calc.y) φαίνεται από τις παρακάτω εντολές:
$ yacc -vd calc.y
$ ls -rtl
total 23
-rw-r--r--   1 dspin    users        1304 Nov 16 22:51 calc.y
-rw-r--r--   1 dspin    users          32 Nov 16 22:51 y.tab.h
-rw-r--r--   1 dspin    users       12078 Nov 16 22:51 y.tab.c
-rw-r--r--   1 dspin    users        3773 Nov 16 22:51 y.output
$ cc -o calc y.tab.c -lm
$ ./calc
1+2*3
        7
3*(1+2)
        9
sdkjfh
syntax error
$

Τρόπος λειτουργίας

Το παρακάτω απόσπασμα από το αρχείο y.output απεικονίζει μια κατάσταση, τους κανόνες που είναι υποψήφιοι για αναγνώριση (τι βρίσκεται στη στοίβα), και τις ενέργειες ανάλογα με το σύμβολο που θα διαβαστεί:
state 17
        exp : exp . '+' exp  (6)
        exp : exp . '-' exp  (7)
        exp : exp '-' exp .  (7)
        exp : exp . '*' exp  (8)
        exp : exp . '/' exp  (9)
        exp : exp . '^' exp  (11)

        '*'  shift 12
        '/'  shift 13
        '^'  shift 14
        '-'  reduce 7
        '+'  reduce 7
        '\n'  reduce 7
        ')'  reduce 7

Ασκήσεις

  1. Να υλοποιήσετε με το μεταεργαλείο yacc έναν υπολογιστή που να επεξεργάζεται διανύσματα (δύο διαστάσεων) με:
    1. τους δυαδικούς τελεστές σε διανύσματα + -
    2. το μοναδικό τελεστή -
    3. τους τελεστές μεταξύ διανύσματος και σταθεράς * /
    4. παρενθέσεις
    Το κάθε διάνυσμα θα μπορεί να γράφεται ως [χ, ψ]. Κάθε γραμμή εισόδου θα αποτελεί μια εντολή για πράξη. Παράδειγμα:
    [1, 2]
    Result = [1, 2]
    [1, 1] + [1, 2]
    Result = [2, 2]
    -([1, 2] * 2 + [1, 1])
    Result = [-3, -5]
    

Βιβλιογραφία

Πίνακες συμβόλων

Λειτουργία

Οι λειτουργίες που θέλουμε να εκτελούμε σε έναν πίνακα συμβόλων περιλαμβάνουν:

Περιεχόμενα

Τα στοιχεία που θέλουμε να αποθηκεύσουμε για κάθε σύμβολο είναι:

Δομές

Ένας πίνακας συμβόλων είναι πολύ βολικό να σχεδιαστεί ως ένας αφηρημένος τύπος δεδομένων ή ως μια κλάση. Με τον τρόπο αυτό: Δομές κατάλλληλες για τη φύλαξη των συμβόλων είναι οι παρακάτω:
Συνδεδεμένη λίστα ή πίνακας
Κατάλληλη μόνο για πίνακες με πολύ μικρό αριθμό συμβόλων (π.χ. τα μέλη μιας δομής)
Πίνακας κατακερματισμού
Γρήγορος στην εισαγωγή και αναζήτηση, αλλά απαιτεί προσεκτικό σχεδιασμό της συνάρτησης κατακερματισμού και πρόβλεψη για περιπτώσεις συγκρούσεων. Μπορεί να οδηγήσει σε σπατάλη μνήμης.
Δυαδικό δένδρο
Σχετικά γρήγορο στην εισαγωγή και αναζήτηση με μικρές απαιτήσεις μνήμης.

Εμβέλεια ονομάτων

Κάθε αναζήτηση στον πίνακα συμβόλων πρέπει να γίνεται σύμφωνα με τους κανόνες εμβέλειας ορατότητας της γλώσσας. Αυτό σημαίνει πως για το σύμβολο που γίνεται η αναζήτηση πρέπει να βρεθεί το πιο εσωτερικό ορατό σύμβολο με το ίδιο όνομα. Δύο πιθανοί τρόποι οργάνωσης είναι οι παρακάτω:

Βιβλιογραφία

Παραγωγή κώδικα

Εισαγωγή

Η παραγωγή κώδικα αποτελεί το πιο ενδιαφέρον και σύνθετο στάδιο της μεταγλώττισης. Κατά το στάδιο αυτό μπορούν να υλοποιηθούν τεχνικές βελτιστοποίησης προσανατολισμένες στη συγκεκριμένη γλώσσα ή αρχιτεκτονική και να υλοποιηθούν αλγόριθμοι βέλτιστης χρήσης των στοιχείων της αρχιτεκτονικής. Η παραγωγή του τελικού εκτελέσιμου κώδικα μπορεί να διακριθεί στα παρακάτω στάδια:

Παραγωγή ενδιάμεσου κώδικα

Ο ενδιάμεσος κώδικας είναι ένας τρόπος παράστασης του προγράμματος κοντά σε υπάρχουσες αρχιτεκτονικές υπολογιστών που παράγεται από το συντακτικό δένδρο. Οι λόγοι που το συντακτικό δένδρο μετασχηματίζεται σε ενδιάμεσο κώδικα και όχι κατευθείαν σε κώδικα μηχανής είναι οι παρακάτω:

Βελτιστοποίηση του ενδιάμεσου κώδικα

Η βελτιστοποίηση του κώδικα μετασχηματίζει τον κώδικα σε μια μορφή που παράγει τα ίδια αποτελέσματα με τον αρχικά αλλά βελτιωμένα κάποια κριτήρια απόδοσης. Τέτοια κριτήρια μπορεί να είναι: Η βελτιστοποίηση μπορεί να απαλλάξει τον προγραμματιστή από αντιπαραγωγικές αλλαγές του κώδικα που έχουν ως στόχο να ικανοποιήσουν τα κριτήρια αυτά και να του επιτρέψει να συγκεντρωθεί σε άλλα ποιοτικά στοιχεία του παραγόμενου κώδικα όπως την ορθότητα, την ασφάλεια, τη διαθεσιμότητα, τη συντηρησιμότητα, και τη μεταφερσιμότητα. Συχνά βελτιστοποιήσεις που γίνονται από τον προγραμματιστή έρχονται σε αντίθεση με τα παραπάνω ποιοτικά στοιχεία.

Ορισμένες κοινές βελτιστοποιήσεις είναι οι παρακάτω:

Απαλοιφή κοινών υποεκφράσεων
Εκφράσεις με κοινά στοιχεία υπολογίζονται μόνο μια φορά. Για παράδειγμα η ακολουθία:
x = b / c;
y = 42 + b / c;
μετασχηματίζεται στην ακολουθία:
x = b / c;
y = 42 + x;
Μετακίνηση κώδικα βρόχων (loop code motion)
Εκφράσεις που δεν αλλάζουν μέσα σε ένα βρόχο μετακινούνται έξω από αυτόν. Για παράδειγμα η ακολουθία:
{
        int a, b, z;

        a = 8; b = 4;
        for (i = 0; i < 10; i++) {
                z = a / b;
                printf("%d\n", z);
        }
}
μετασχηματίζεται στην ακολουθία:
{
        int a, b, z;

        a = 8; b = 4;
        z = a / b;
        for (i = 0; i < 10; i++) {
                printf("%d\n", z);
        }
}
Απαλοιφή άχρηστου κώδικα
Κώδικας που δεν εκτελείται ποτέ απαλείφεται. Για παράδειγμα η ακολουθία:
{
        int a, q;

        q = 48;
        return (q);
        a = q / 2;
}
μετασχηματίζεται στην ακολουθία:
{
        int a, q;

        q = 48;
        return (q);
}
Απαλοιφή κλήσεων σε συναρτήσεις (function inlining)
Κλήσεις σε συναρτήσεις μετασχηματίζονται σε απευθείας χρήση του αντίστοιχου κώδικα. Για παράδειγμα η ακολουθία:
int
sqr(int a)
{
        return (a * a);
}

main()
{
        printf("%d\n", sqr(12));
}
μετασχηματίζεται στην ακολουθία:
main()
{
        printf("%d\n", (12 * 12));
}
Απαλοιφή αναδρομής από το τέλος συνάρτησης (tail recursion elimination)
Αναδρομή στο τέλος μιας συνάρτησης μετασχηματίζεται σε βρόχο. Για παράδειγμα η ακολουθία:
foo()
{
        printf("foo\n");
        foo();
}
μετασχηματίζεται στην ακολουθία:
foo()
{
        for (;;)
                printf("foo\n";);
}

Παραγωγή τελικού κώδικα

Η παραγωγή τελικού κώδικα από το βελτιστοποιημένο ενδιάμεσο κώδικα βασίζεται στην αντικατάσταση συμβολικών εντολών του επεξεργαστή για τις συγκεκριμένες εντολές του ενδιάμεσου κώδικα. Στο στάδιο αυτό ενδιάμεσες μεταβλητές ανατίθενται σε καταχωρητές και μια συνάρτηση γεννάει μοναδικές ετικέτες για τις εντολές άλματος. Συγκεκριμένες στρατηγικές περιγράφονται στις σημειώσεις της άσκησης του μαθήματος.

Βελτιστοποίηση του τελικού κώδικα

Συχνά ο τελικός κώδικας που παράγεται μπορεί να περιέχει ακολουθίες εντολών οι οποίες να μπορούν να βελτιστοποιηθούν για το συγκεκριμένο επεξεργαστή. Οι ακολουθίες αυτές μπορούν να βρεθούν εξετάζοντας ένα μικρό παράθυρο του τελικού κώδικα, όπως αυτό θα φαίνονταν κοιτάζοντας τον κώδικα από μια κλειδαρότρυπα. Για το λόγο αυτό οι βελτιστοποιήσεις αυτές λέγονται και βελτιστοποιήσεις κλειδαρότρυπας (peephole optimizations).

Για παράδειγμα η ακολουθία:

        mov $12, %ax
        mov %ax, %bx
        mov $20, %ax
μετασχηματίζεται στην ακολουθία:
        mov $12, %bx
        mov $20, %ax
Επίσης η διάταξη των εντολών μπορεί να βελτιωθεί για να γίνεται καλύτερη χρήση πολλαπλών υπολογιστικών στοιχείων που διαθέτει ο επεξεργαστής. Για παράδειγμα σε ορισμένους επεξεργαστές η εναλλαγή εντολών κινητής υποδιαστολής με εντολές ακεραίων επιτρέπει στις δύο λειτουργικές μονάδες να δουλεύουν παράλληλα.

Σύνδεση

Κατά τη σύνδεση τα ανεξάρτητα μεταγλωττισμένα τμήματα του προγράμματος συνδέονται μεταξύ τους και με τις βιβλιοθήκες της γλώσσας για να δημιουργηθεί το τελικό εκτελέσιμο πρόγραμμα. Συχνά οι βιβλιοθήκες που χρησιμοποιούνται είναι κοινές ανάμεσα σε προγράμματα και φορτώνονται δυναμικά κατά το στάδιο εκτέλεσης του προγράμματος (Unix shared libraries, Windows DLLs). Στην περίπτωση αυτή οι κλήσεις σε συναρτήσεις της βιβλιοθήκης συνδέονται με ψευδοσυναρτήσεις που υλοποιούν τη λειτουργικότητα αυτή. Κατά τη φάση της σύνδεσης γίνεται έλεγχος πως όλες οι συναρτήσεις και μεταβλητές που χρησιμοποιούνται έχουν οριστεί και, για ορισμένες γλώσσες όπως η C++, πως οι τύποι των συμβόλων που έχουν οριστεί σε διαφορετικά αρχεία είναι συμβατοί μεταξύ τους. Ορισμένες γλώσσες όπως η Java υλοποιούν μεγάλο μέρος της σύνδεσης κατά τη φόρτωση του προγράμματος στη μνήμη για εκτέλεση.

Βιβλιογραφία

Εργασία του μαθήματος - H γλώσσα Aegean-C

Τελευταία νέα

15-12-1999
Νέα ενότητα: απαντήσεις σε συχνές ερωτήσεις.
15-12-1999
Νέα ενότητα: δημιουργία κώδικα για συμβολοσειρές.
15-12-1999
Νέα ενότητα: εκτέλεση και σύνδεση του μεταγλωττιστή.
14-12-1999
Καλύτερη τεκμηριώση για τη χρήση της εντολής div στις σημειώσεις για τις αριθμητικές εντολές.
8-12-1999
Αναλυτικές οδηγίες δημιουργίας κώδικα για βρόχους, μεταβλητές, κ.λπ.

Η άσκηση

Εισαγωγή

Η γλώσσα Aegean-C είναι ένα υποσύνολο της γλώσσας C. Δε διαθέτει πολλούς τύπους, δομές, δείκτες, αρκετούς τελεστές, τις περισσότερες συναρτήσεις της βιβλιοθήκης και ορισμένες εντολές. Παρόλ' αυτά περιλαμβάνει αρκετές δυνατότητες έτσι ώστε να μπορεί κανείς να γράψει ένα παιγνίδι όπως την προσγείωση στο φεγγάρι (Lunar Lander). Η παρακάτω συνοπτική περιγραφή της γλώσσας περιλαμβάνει μόνο τα στοιχεία που πρέπει να περιλαμβάνονται υποχρεωτικά στη γλώσσα. Η υλοποίηση της γλώσσας επιτρέπεται να προσθέσει δυνατότητες αρκεί να μην την κάνει ασύμβατη με τη βασική γλώσσα. Η σημασία όλων των στοιχείων της γλώσσας όπου δεν περιγράφεται διαφορετικά είναι ίδια με αυτή της C.

Λεκτικά στοιχεία

Η λεκτική δομή της Aegean-C είναι αρκετά απλή:

Μεταβλητές

Εντολές

Κάθε εντολή της Aegean-C τερματίζεται με ";". Πρέπει να υποστηρίζονται οι παρακάτω εντολές:

Εκφράσεις

Συναρτήσεις χρήστη

Ο χρήστης μπορεί να ορίσει νέες συναρτήσεις με την παρακάτω σύνταξη:
όνομα_συνάρτησης()
{
	εντολές
}
Η συνάρτηση με το όνομα aegean αποτελεί την είσοδο του προγράμματος.

Ορισμένες συναρτήσεις

Στη βιβλιοθήκη της Aegean-C ορίζονται οι παρακάτω συναρτήσεις:
putstring(string)
Εμφανίζει τη συμβολοσειρά string στην έξοδο. Παράδειγμα putstring("hello, world\n").
putint(integer-expression)
Εμφανίζει την τιμή της έκφρασης στην έξοδο. Για παράδειγμα putint(3+5) εμφανίζει 7.
getint(variable)
Διαβάζει έναν ακέραιο από την είσοδο του προγράμματος και θέτει την τιμή της μεταβλητής.
getint()
Διαβάζει έναν ακέραιο από την είσοδο του προγράμματος και τον επιστρέφει (εναλλακτική δυνατότητα υλοποίησης).

Παράδειγμα

Το παρακάτω παράδειγμα τυπώνει ένα ορθογώνιο τρίγωνο από αστεράκια:
// A simple program in Aegean-C

int numstars;
int i;
int j;

// Print numstars stars
stars()
{
        i = 0;
        while (i < numstars) {
                putstring("*");
                i = i + 1;
        }
        putstring("\n");
}

// Program entry point
aegean()
{
        j = 0;

        while (j < 10) {
                numstars = j;
                stars();
                j = j + 1;
        }
}
Παράδειγμα εξόδου:

*
**
***
****
*****
******
*******
********
*********

Το παράδειγμα μεταγλωττισμένο

Ο παρακάτω κώδικας είναι απλοποιημένη μορφή του κώδικα που παράγει ο μεταγλωττιστής της C (cc -S) για το αντίστοιχο παράδειγμα σε C.
.section        .rodata
.LC0:
        .string "*"
.LC1:
        .string "\n"
.text
        .align 4
.globl stars
        .type    stars,@function
stars:
        movl $0,i
.L2:
        movl i,%eax
        cmpl numstars,%eax
        jl .L4
        jmp .L3
.L4:
        pushl $.LC0
        call putstring
        addl $4,%esp
        incl i
        jmp .L2
.L3:
        pushl $.LC1
        call putstring
        addl $4,%esp
.L1:
        ret

.globl aegean
        .type    aegean,@function
aegean:
        movl $0,j
.L6:
        cmpl $9,j
        jle .L8
        jmp .L7
.L8:
        movl j,%eax
        movl %eax,numstars
        call stars
        incl j
        jmp .L6
.L7:
.L5:
        ret

        .comm   numstars,4,4
        .comm   i,4,4
        .comm   j,4,4

Προσθήκες

Η γλώσσα Aegean-C μπορεί να επεκταθεί με πολλούς και διαφορετικούς τρόπους. Ιδιαίτερα σημαντικές θεωρούνται επεκτάσεις με δυνατότητες που δεν υπάρχουν στη γλώσσα C. Μερικές ιδέες για επεκτάσεις είναι οι παρακάτω:

Αναφορά υλοποίησης

Κάθε υλοποίηση της γλώσσας Aegean-C πρέπει να συνοδεύεται από μια αναφορά υλοποίησης. Αυτή πρέπει να περιέχει τις παρακάτω ενότητες:

Πρόγραμμα εργασίας

Εβδομάδα 1

Τα παρακάτω πρέπει να έχουν ολοκληρωθεί από κάθε ομάδα:

Εβδομάδα 2

Τα παρακάτω πρέπει να έχουν ολοκληρωθεί από κάθε ομάδα:

Εβδομάδα 3

Τα παρακάτω πρέπει να έχουν ολοκληρωθεί από κάθε ομάδα:

Εβδομάδα 4

Απλές στρατηγικές παραγωγής κώδικα

Στις ενότητες περιγράφουμε μερικές απλές στρατηγικές για τη δημιουργία τελικού κώδικα. Οι στρατηγικές αυτές δεν παράγουν βέλτιστο κώδικα, αλλά είναι εύκολο να υλοποιηθούν.

Η παραγωγή κώδικα γίνεται αναδρομικά. Ο κανόνας της συντακτικής ανάλυσης που αναγνωρίζει ολόκληρο το πρόγραμμα μπορεί να καλεί τη συνάρτηση που παράγει τον κώδικα:

prog    : dec_seq               { codegen($1); }
        ;

dec_seq : /* ... */
Η συνάρτηση αυτή καλεί αναδρομικά άλλες συναρτήσεις ανάλογα με το είδος του κόμβου του δένδρου.

Εκφράσεις

Η παραγωγή του τελικού κώδικα για τον υπολογισμό εκφράσεων μπορεί να γίνει εύκολα με τη χρήση ενός μοντέλου στοίβας. Σύμφωνα με το μοντέλο αυτό κάθε τελεστής λαμβάνει τους τελεστέους από τη στοίβα και μεταφέρει το αποτέλεσμα πίσω στη στοίβα. Η διαδικασία αυτή ακολουθείται αναδρομικά για όλα τα στοιχεία του συντακτικού δένδρου. Στο τέλος του υπολογισμού της έκφρασης πρέπει το αποτέλεσμα που έχει μείνει στη στοίβα να αφαιρεθεί.

Η υλοποίηση αυτή είναι κατάλληλη για αρχιτεκτονικές επεξεργαστών που βασίζονται σε στοίβα όπως η Java Virtual Machine και ο επεξεργαστής αριθμών κινητής υποδιαστολής της σειράς Intel Pentium, αλλά μπορεί να χρησιμοποιηθεί και σε αρχιτεκτονικές που βασίζονται σε καταχωρητές. Έτσι η έκφραση b + d υλοποιείται με τη σειρά:

        pushl   b               // Push operands
        pushl   d
        pop     %eax            // Retrieve operand 1
        pop     %ebx            // Retrieve operand 2
        add     %ebx, %eax      // Perform operation
        push    %eax            // Push back result
από κώδικα της μορφής:
void
codegen_add(struct s_tree *left, struct s_tree *right)
{
        codegen(left);
        codegen(right);
        printf("\tpop %%eax\n");
        printf("\tpop %%ebx\n");
        printf("\tadd %%ebx, %%eax\n");
        printf("\tpush %%eax\n");
}

Παράδειγμα:

a = b + c * d;
Δένδρο:
    =
 a      +
     b    *
         c  d
Κώδικας:
        pushl   a
        pushl   b
        pushl   c
        pushl   d
        pop     %eax            // c *  d
        pop     %ebx
        mul     %ebx
        push    %eax
        pop     %eax            // b + (c * d)
        pop     %ebx
        add     %ebx, %eax
        push    %eax
        pop     %esi            // a = ...
        pop     %eax
        mov     %eax, (%esi)

Βρόχοι και αποφάσεις

Ο κώδικας για του βρόχους και τις αποφάσεις βασίζεται σε άλματα σε συγκεκριμένες ετικέτες. Τη δημιουργία του κώδικα διευκολύνει η υλοποίηση συνάρτησης new_label που επιστρέφει νέα ονόματα ετικετών (πιθανώς με τη χρήση των συναρτήσεων sprintf και malloc για την αποθήκευση του αποτελέσματος). Έτσι για παράδειγμα ο κώδικας για την εντολή while μπορεί να είναι της μορφής:
void
codegen_while(struct s_tree *expr, struct s_tree *stmt)
{
        char *loop = new_label();
        char *end = new_label();

        printf("%s:\n", loop);
        codegen(expr);
        printf("\tpop %%eax\n");
        printf("\tcmp %%eax, 0\n");
        printf("\tje %s\n", end);
        codegen(stmt);
        printf("\tjmp %s\n", loop);
        printf("%s:\n", end);
}
για να παράγει κώδικα όπως τον παρακάτω:
.L0053:
        pushl a
        pop %eax
        cmp %eax0
        je .L0054
        // code for the loop body
        jmp .L0053
.L0054:

Συναρτήσεις

Ο κώδικας για την υλοποίηση μιας συνάρτησης (π.χ. με όνομα fname) μπορεί να είναι της μορφής:
.globl fname
        .type    fname,@function
fname:
        // procedure body
        ret
Αντίστοιχα η κλήση σε μια συνάρτηση (π.χ. με όνομα fname) γίνεται με την ακολουθία call fname.

Μεταβλητές

Για κάθε ακέραια μεταβλητή αρκεί να υπάρχει στο αρχείο της συμβολικής γλώσσας (όχι ανάμεσα σε εντολές κώδικα) μια δήλωση της μορφής:
        .comm   varname,4,4
Η χρήση των μεταβλητών αυτών μπορεί να γίνει με απευθείας χρήση του ονόματος στις αντίστοιχες εντολές:
        movl %eax,varname
        // ...
        cmpl varname,%eax

Δημιουργία κώδικα για συμβολοσειρές

Αντίθετα με ό,τι αναφέρθηκε στο εργαστήριο, ο κώδικας για τη δημιουργία των συμβολοσειρών δεν είναι απαραίτητο να φτιαχτεί σε δεύτερη φάση μεταγλώττισης. Μπορεί μια συμβολοσειρά να περιληφθεί και μέσα στις εντολές του υπόλοιπου κώδικα αρκεί να προβλεφθεί μια εντολή jmp για να μην εκτελείται η συμβολοσειρά ως κώδικας. Οι ετικέτες για τη συμβολοσειρά και τον προορισμό της jmp θα δημιουργούνται από τη συνάρτηση new_label(). Παράδειγμα για την printf:
.globl main
        .type    main,@function
main:
        pushl %ebp
        movl %esp,%ebp
        jmp .LC2                // Skip the string data
.LC0:                           // Label for the string data
        .string "hello\n"       // String data
.LC2:                           // Label for skipping
        pushl $.LC0             // Push the address of the string data ...
        call printf             // ... for printf to print
        addl $4,%esp
.L1:
        leave
        ret
Στο παραπάνω παράδειγμα φαίνεται και ότι ο συμβολομεταφραστής μπορεί να χειριστεί τους κωδικούς διαφυγής όπως το \n χωρίς επέμβαση από το μεταγλωττιστή.

Εκτέλεση και σύνδεση του μεταγλωττιστή

Η εκτέλεση του μεταγλωττιστή της Aegean C (ac) μπορεί να γίνει με την παρακάτω διαδικασία:
# Compile the source file lander.a to assembly file lander.s
ac <lander.a >lander.s
# Assemble lander.s; compile and link-in the Aegean runtime library (aegean.c)
cc -o lander lander.s aegean.c
Μπορεί ακόμα να αυτοματοποιηθεί με το παρακάτω Bourne shell script:
#!/bin/sh

# Names for the library and the compiler driver
LIB=aegean.c
COMP=acd

if [ "$1" = '' ]
then
        echo "Usage: $0 basename (e.g. $0 lander)" 1>&2
        echo "Will compile basename.a to basename via basename.s" 1>&2
        exit 1
fi
$COMP <$1.a >$1.s
cc -i $1 $1.s $LIB

Απαντήσεις σε συχνές ερωτήσεις

  1. Χρειάζεται τελικά δεύτερη φάση δημιουργίας κώδικα για τις μεταβλητές και τις συμβολοσειρές;
    Όχι, α) ο κώδικας για τις μεταβλητές δημιουργείται εκτός των εντολών αφού αυτές ορίζονται έξω από τις συναρτήσεις β) για τις συμβολοσειρές υπάρχει μέθοδος δημιουργίας κώδικα που δεν απαιτεί δεύτερο πέρασμα.
  2. Τι να κάνουμε με τα λάθη reduce/reduce του yacc.
    Ψάξτε στο αρχείο y.output για να δείτε τους κανόνες που δημιουργούν αυτά τα λάθη.
  3. Τι σημαίνει το λάθος X rules never reduced;
    Κάποια τερματικά σύμβολα έχουν οριστεί σε κανόνες, αλλά δεν έχουν χρησιμοποιηθεί σε κανέναν άλλο κανόνα. Ψάξτε στο αρχείο y.output (στο τέλος του) για να δείτε τα μη τερματικά σύμβολα που έχουν αυτό το πρόβλημα.
  4. Πως μπορούμε να βρούμε σφάλματα στο πρόγραμμά μας;
    Με τον απασφαλματωτή gdb.

    Εντολές

    break
    καθορίζει σημεία που θέλετε να σταματήσετε. π.χ. break main.c:22
    cont
    συνεχίζει την εκτέλεση μετά από break.
    step
    εκτελεί την επόμενη γραμμή κώδικα
    next
    εκτελεί την επόμενη γραμμή κώδικα χωρίς να μπαίνει μέσα στις συναρτήσεις
    where
    μετά από ένα segmentation fauls σας λέει σε ποιο σημείο έγινε.
    print expr
    τυπώνει την τιμή της έκφρασης expr
    help
    τυπώνει βοήθεια για τον gdb
    quit
    τερματίζει τη λειτουργία του gdb

Βιβλιογραφία