Σύνταξη (γλώσσα προγραμματισμού)

Στην επιστήμη των υπολογιστών, η σύνταξη μιας γλώσσας προγραμματισμού είναι το σύνολο από κανόνες που ορίζουν τους συνδυασμούς συμβόλων που θεωρούνται σωστά δομημένα προγράμματα στη γλώσσα αυτή. Η σύνταξη μιας γλώσσας ορίζει τη φόρμα της όπως φαίνεται. Οι γλώσσες προγραμματισμού που βασίζονται σε κείμενο, βασίζονται σε ακολουθίες χαρακτήρων, ενώ οι οπτικές γλώσσες προγραμματισμού βασίζονται στη χωρική διάταξη και στις συνδέσεις μεταξύ συμβόλων (που μπορούν να είναι κείμενο ή γραφικά). Η λεκτική γραμματική μιας γλώσσας βασισμένης στο κείμενο ορίζει πώς οι χαρακτήρες ομαδοποιούνται μαζί σε λεκτικές μονάδες (tokens). Άλλοι κανόνες της σύνταξης ορίζουν τις επιτρεπτές ακολουθίες αυτών των λεκτικών μονάδων και η διαδικασία με την οποία ανατίθεται σε αυτές τις ακολουθίες κάποια σημασία, αποτελεί μέρος της σημασιολογίας. Η συντακτική ανάλυση του πηγαίου κώδικα συνήθως αποτελείται από το μετασχηματισμό της γραμμικής ακολουθίας των λεκτικών μονάδων σε ένα ιεραρχικό συντακτικό δένδρο (με τα δένδρα αφηρημένης σύνταξης, τα "abstract syntax trees", να αποτελούν μια βολική μορφή συντακτικών δένδρων). Η διαδικασία αυτή ονομάζεται συντακτική ανάλυση (parsing), όπως η συντακτική ανάλυση της γλωσσολογίας. Έχουν γραφεί εργαλεία που δημιουργούν αυτόματα συντακτικούς αναλυτές από μια προδιαγραφή της γραμματικής της γλώσσας, γραμμένης σε μορφή Μπάκους-Νάουρ (Backus-Naur form), π.χ. ο Yacc.

 Η σύνταξη των γλωσσών προγραμματισμού βασισμένων σε κείμενο συνήθως ορίζεται από ένα συνδυασμό κανονικών εκφράσεων (για τη λεκτική δομή) και μορφής Μπάκους-Νάουρ (για τη γραμματική δομή) για τον επαγωγικό ορισμό των συντακτικών κατηγοριών (μη-τερματικών) και των τερματικών συμβόλων. Οι συντακτικές κατηγορίες ορίζονται από κανόνες που ονομάζονται παραγωγές (productions), οι οποίοι καθορίζουν τις τιμές που ανήκουν σε μια συγκεκριμένη συντακτική κατηγορία. Τα τερματικά σύμβολα είναι σταθεροί χαρακτήρες ή ακολουθίες χαρακτήρων (για παράδειγμα λέξεις-κλειδιά όπως οι define, if, let, ή void) από τα οποία κατασκευάζονται τα έγκυρα προγράμματα. Ακολουθεί μια απλή γραμματική, βασισμένη στη Lisp, που ορίζει κανόνες παραγωγής για τις συντακτικές κατηγορίες έκφραση, άτομο, αριθμός, σύμβολο, και λίστα: έκφραση ::= άτομο | λίστα άτομο ::= αριθμός | σύμβολο αριθμός ::= [+-]?['0'-'9']+ σύμβολο ::= ['A'-'Z''a'-'z'].* λίστα ::= '(' έκφραση* ')' Η γραμματική αυτή ορίζει τα εξής: μια έκφραση είναι είτε ένα άτομο είτε μια λίστα, ένα άτομο είναι είτε ένας αριθμός είτε ένα σύμβολο, ένας αριθμός είναι μια συνεχής ακολουθία από ένα ή περισσότερα δεκαδικά ψηφία, τα οποία προαιρετικά μπορεί να έχουν ένα σημείο συν ή πλην στην αρχή τους, ένα σύμβολο είναι ένα γράμμα που ακολουθείται από μηδέν ή περισσότερους χαρακτήρες (εκτός από κενά), και μια λίστα είναι ένα ταιριασμένο ζευγάρι παρενθέσεις, με μηδέν ή περισσότερες εκφράσεις μέσα σε αυτές.Εδώ, τα δεκαδικά ψηφία, οι χαρακτήρες (πεζά και κεφαλαία), και οι παρενθέσεις, είναι τα τερματικά σύμβολα. Τα επόμενα είναι παραδείγματα καλώς σχηματισμένων ακολουθιών από λεκτικές μονάδες για αυτήν τη γραμματική: '12345', '()', '(a b c232 (1))' Η γραμματική που χρειάζεται για να οριστεί μια γλώσσα προγραμματισμού κατηγοριοποιείται ανάλογα με τη θέση της στην ιεραρχία Τσόμσκι. Η σύνταξη των περισσότερων γλωσσών προγραμματισμού μπορεί να οριστεί χρησιμοποιώντας μια γραμματική Τύπου-2, δηλ. είναι γραμματικές χωρίς συμφραζόμενα. Υπάρχουν όμως εξαιρέσεις. Σε κάποιες γλώσσες όπως η Perl και η Lisp ο ορισμός (ή η υλοποίηση) της γλώσσας επιτρέπει δομές που εκτελούνται στη διάρκεια της φάσης της συντακτικής ανάλυσης. Επιπλέον, αυτές οι γλώσσες έχουν δομές που επιτρέπουν στον προγραμματιστή να τροποποιήσει τη συμπεριφορά του συντακτικού αναλυτή. Αυτός ο συνδυασμός πρακτικά κάνει δυσδιάκριτα τα όρια μεταξύ συντακτικής ανάλυσης και εκτέλεσης, και κάνει τη συντακτική ανάλυση μη-υπολογίσιμο πρόβλημα (undecidable problem) σε αυτές τις γλώσσες, δηλαδή η φάση της συντακτικής ανάλυσης μπορεί να μην τερματίζει. Για παράδειγμα, είναι δυνατό στην Perl να εκτελεστεί κώδικα κατά τη συντακτική ανάλυση με τη χρήση της εντολής BEGIN, και τα πρωτότυπα συναρτήσεων της Perl (function prototypes) μπορούν να επηρεάσουν τη συντακτική ερμηνεία, ακόμα και τη συντακτική εγκυρότητα του υπόλοιπου κώδικα. Όμοια, οι μακροεντολές της Lisp που εμφανίζονται με τη σύνταξη defmacro επίσης εκτελούνται κατά τη διάρκεια της συντακτικής ανάλυσης, επομένως ένας μεταγλωττιστής της Lisp πρέπει να διαθέτει ένα πλήρες σύστημα χρόνου εκτέλεσης για Lisp. Αντίθετα, οι μακροεντολές της C είναι απλά αντικαταστάσεις συμβολοσειρών, και δε χρειάζονται εκτέλεση κώδικα. Η σύνταξη μιας γλώσσας περιγράφει τη μορφή ενός έγκυρου προγράμματος, αλλά δεν παρέχει καμία πληροφορία για τη σημασία του ή για τα αποτελέσματα της εκτέλεσής του. Η σημασία που δίνεται σε ένα συνδυασμό συμβόλων ελέγχεται με τη σημασιολογία (μέσω της τυπικής σημασιολογίας των γλωσσών προγραμματισμού ή με μια ντε φάκτο υλοποίηση αναφοράς). Δεν είναι σημασιολογικά ορθά όλα τα προγράμματα που είναι συντακτικά ορθά. Πολλά συντακτικά ορθά προγράμματα μπορούν να έχουν λάθος μορφή, σύμφωνα με τους κανόνες της γλώσσας, και μπορεί (ανάλογα με τον ορισμό της γλώσσας και το πόσο σωστή είναι η υλοποίηση) να έχουν σαν αποτέλεσμα κάποιο σφάλμα στη μετάφραση ή στην εκτέλεση. Σε κάποιες περιπτώσεις, αυτά τα προγράμματα μπορεί να εμφανίζουν μη-ορισμένη συμπεριφορά (undefined behavior). Ακόμα και αν ένα πρόγραμμα είναι καλώς ορισμένο σε μια γλώσσα, μπορεί να έχει σημασία που δε συμφωνεί με αυτήν που ήθελε ο προγραμματιστής του. Χρησιμοποιώντας τη φυσική γλώσσα σαν παράδειγμα, μπορεί να μην είναι δυνατό να αποδοθεί κάποια σημασία σε μια γραμματικά σωστή πρόταση ή η πρόταση μπορεί να είναι ψευδής: "Colorless green ideas sleep furiously." ("Οι άχρωμες πράσινες ιδέες κοιμούνται με ορμή") είναι γραμματικά καλώς ορισμένη αλλά δεν υπάρχει κάποια γενικά αποδεκτή σημασία. "Ο Γιάννης είναι ένας παντρεμένος εργένης" είναι γραμματικά καλώς ορισμένη αλλά εκφράζει μια σημασία που δε μπορεί να είναι αληθής.Το ακόλουθο απόσπασμα κώδικα σε γλώσσα C είναι συντακτικά ορθό αλλά εκτελεί μια λειτουργία που δεν είναι ορισμένη σημασιολογικά (οι λειτουργίες p->real και p->im δεν έχουν κάποια σημασία γιατί ο p είναι κενός δείκτης, "null pointer"): complex *p = NULL; complex abs_p = sqrt (p->real * p->real + p->im * p->im);