Γράφοντας ένα Lexer με το SableCC 3.2 (μέρος 2/2)
Στο προηγούμενο μέρος δημιουργήσαμε ένα λεκτικό αναλυτή (βασικά τις απαραίτητες τάξεις για τη λεκτική ανάλυση) για ένα μικρό υποσύνολο της QBasic.
Τώρα μένει να κατασκευάσουμε μία εφαρμογή που θα παίρνει ως είσοδο ένα αρχείο με κώδικα γραμμένο στη γλώσσα που περιγράψαμε και θα παράγει ένα αρχείο html με την κατάλληλη μορφοποίηση.
Αρχικά δημιουργούμε ένα νέο κενό αρχείο με το όνομα
SubBasic2Html.java
μέσα στο φάκελο subbasic που δημιούργησε το SableCC.
Ανοίγουμε το SubBasic2Html.java και κατασκευάζουμε το βασικό σκελετό (imports, main() κλπ.):
package subbasic;
import subbasic.lexer.Lexer; import subbasic.node.*; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.PushbackReader; public class SubBasic2Html{ public static void main(String[] args){ } }
Για να μην γράφουμε συνέχεια font tags κλπ. θα χρησιμοποιήσουμε CSS:
private void outputHtmlHeader(PrintWriter out, String documentTitle) { out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"); out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">"); out.println(""); out.println(" <head>"); out.println(" <title>" + documentTitle + "</title>");// Ο τίτλος του εγγράφου HTML out.println(" <style type=\"text/css\">"); //Έναρξη του CSS out.println(".keyword{"); out.println(" color: rgb(0,41,103);"); out.println(" font-weight: bold;"); out.println(" font-family: monospace;"); out.println("}"); out.println(".num_const{"); out.println(" color: rgb(1,104,0);"); out.println(" font-family: monospace;"); out.println("}"); out.println(".string_const{"); out.println(" color: rgb(101,0,103);"); out.println(" font-family: monospace;"); out.println("}"); out.println(".symbol{"); out.println(" color: rgb(103,0,1);"); out.println(" font-weight: bold;"); out.println(" font-family: monospace;"); out.println("}"); out.println(".comment{"); out.println(" color: rgb(17,78,33);"); out.println(" font-style: italic;"); out.println(" font-family: monospace;"); out.println("}"); out.println(".num_var, .string_var{"); out.println(" color: rgb(0,0,0);"); out.println(" font-family: monospace;"); out.println("}"); out.println(".misc{"); out.println(" color: rgb(255, 0, 0);"); out.println(" font-family: monospace;"); out.println("}"); //Τέλος του CSS out.println(" </style>"); out.println(" </head>"); out.println(""); out.println(" <body>"); out.println(" <p>"); out.println(""); }
Αν παρατηρήσουμε λίγο τα αρχεία που έχει παράγει το SableCC θα δούμε οτι για κάθε token έχει παραχθεί μία τάξη με το όνομα του token με ένα κεφαλαίο “T” στην αρχή. Οπότε χρησιμοποιώντας το όνομα της τάξης και τον τελεστή instanceof μπορούμε να καθορίσουμε την τάξη του token και να παράγουμε τον αντίστοιχο κώδικα html (θα χρησιμοποιήσουμε το span tag για την εφαρμογή του CSS):
private void outputHtmlForToken(Token token, PrintWriter out) { Class cls = token.getClass(); String tokenHtmlClass = ""; if (token instanceof TComment) { tokenHtmlClass = "comment"; } else if (token instanceof TKeyword) { tokenHtmlClass = "keyword"; } else if (token instanceof TSymbol) { tokenHtmlClass = "symbol"; } else if (token instanceof TLt) { tokenHtmlClass = "lt"; } else if (token instanceof TGt) { tokenHtmlClass = "gt"; } else if (token instanceof TNumConst) { tokenHtmlClass = "num_const"; } else if (token instanceof TStringConst) { tokenHtmlClass = "string_const"; } else if (token instanceof TNumVar) { tokenHtmlClass = "num_var"; } else if (token instanceof TStringVar) { tokenHtmlClass = "string_var"; } else if (token instanceof TMisc) { tokenHtmlClass = "misc"; } else if (token instanceof TNewline) { tokenHtmlClass = "lineEnd"; } else if (token instanceof TSpace) { tokenHtmlClass = "space"; } else if (token instanceof TTab) { tokenHtmlClass = "tab"; } else { tokenHtmlClass = "-"; } if (tokenHtmlClass.equals("-")) { out.print(token.getText()); } else if (tokenHtmlClass.equals("space")) { out.print(" "); } else if (tokenHtmlClass.equals("tab")) { out.print(" "); } else if (tokenHtmlClass.equals("lt")) { out.print("<span class=\"symbol\"><</span>"); // και τα '<' και '>' θα χρησιμοποιήσουν } else if (tokenHtmlClass.equals("gt")) { // το style της class symbol από το CSS out.print("<span class=\"symbol\">></span>"); } else if (tokenHtmlClass.equals("lineEnd")) { out.println("<br/>"); } else if (tokenHtmlClass.equals("comment")) { //πρέπει να αντικαταστήσουμε το "'" με "'" String s = token.getText(); out.print("<span class=\"comment\">'"+s.substring(1,s.length())+"</span>"); } else { out.print("<span class=\"" + tokenHtmlClass + "\">" + token.getText() + "</span>"); } }
Μία μέθοδος που θα κλείνει το html έγγραφό μας:
private void outputHtmlFooter(PrintWriter out) { out.println(""); out.println(" </p>"); out.println(" </body>"); out.println(""); out.println("</html>"); out.println(""); }
και τέλος, μία μέθοδος που θα τα συνδυάζει όλα αυτά ώστε να παραχθεί το τελικό έγγραφο:
public void generateHtml(InputStreamReader input, File output, String title) { try { Lexer lexer = new Lexer(new PushbackReader(input)); PrintWriter p = new PrintWriter(output); outputHtmlHeader(p, title); Token token = lexer.next(); while (!token.getText().equals("")) { String s = new String(); outputHtmlForToken(token, p); token = lexer.next(); } outputHtmlFooter(p); p.flush(); p.close(); } catch (Exception e) { System.err.println(e); } }
Το τελευταίο βήμα είναι να γράψουμε λίγες γραμμές κώδικα στη main ώστε να δημιουργήσουμε ένα FileReader και να προσδιορίσουμε ένα αρχείο εξόδου και ένα τίτλο για το τελικό έγγραφο (π.χ. μέσω των παραμέτρων εκτέλεσης του προγράμματος):
public static void main(String[] args){ if ((args.length != 3)) { System.out.println("-Invalid parameters list."); System.exit(0); } SubBasic2Html converter = new SubBasic2Html(); try { converter.generateHtml(new FileReader(new File(args[0])), new File(args[1]), args[2]); } catch (Exception e) { } }
Το τελικό αρχείο SubBasic2Html.java θα είναι αυτό:
package subbasic; import subbasic.lexer.Lexer; import subbasic.node.*; import java.io.BufferedReader; import java.io.File; import java.io.FileReader; import java.io.InputStream; import java.io.InputStreamReader; import java.io.PrintWriter; import java.io.PushbackReader; public class SubBasic2Html{ private void outputHtmlHeader(PrintWriter out, String documentTitle) { out.println("<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Strict//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd\">"); out.println("<html xmlns=\"http://www.w3.org/1999/xhtml\">"); out.println(""); out.println(" <head>"); out.println(" <title>" + documentTitle + "</title>");// Ο τίτλος του εγγράφου HTML out.println(" <style type=\"text/css\">"); //Έναρξη του CSS out.println(".keyword{"); out.println(" color: rgb(0,41,103);"); out.println(" font-weight: bold;"); out.println(" font-family: monospace;"); out.println("}"); out.println(".num_const{"); out.println(" color: rgb(1,104,0);"); out.println(" font-family: monospace;"); out.println("}"); out.println(".string_const{"); out.println(" color: rgb(101,0,103);"); out.println(" font-family: monospace;"); out.println("}"); out.println(".symbol{"); out.println(" color: rgb(103,0,1);"); out.println(" font-weight: bold;"); out.println(" font-family: monospace;"); out.println("}"); out.println(".comment{"); out.println(" color: rgb(17,78,33);"); out.println(" font-style: italic;"); out.println(" font-family: monospace;"); out.println("}"); out.println(".num_var, .string_var{"); out.println(" color: rgb(0,0,0);"); out.println(" font-family: monospace;"); out.println("}"); out.println(".misc{"); out.println(" color: rgb(255, 0, 0);"); out.println(" font-family: monospace;"); out.println("}"); //Τέλος του CSS out.println(" </style>"); out.println(" </head>"); out.println(""); out.println(" <body>"); out.println(" <p>"); out.println(""); } private void outputHtmlForToken(Token token, PrintWriter out) { Class cls = token.getClass(); String tokenHtmlClass = ""; if (token instanceof TComment) { tokenHtmlClass = "comment"; } else if (token instanceof TKeyword) { tokenHtmlClass = "keyword"; } else if (token instanceof TSymbol) { tokenHtmlClass = "symbol"; } else if (token instanceof TLt) { tokenHtmlClass = "lt"; } else if (token instanceof TGt) { tokenHtmlClass = "gt"; } else if (token instanceof TNumConst) { tokenHtmlClass = "num_const"; } else if (token instanceof TStringConst) { tokenHtmlClass = "string_const"; } else if (token instanceof TNumVar) { tokenHtmlClass = "num_var"; } else if (token instanceof TStringVar) { tokenHtmlClass = "string_var"; } else if (token instanceof TMisc) { tokenHtmlClass = "misc"; } else if (token instanceof TNewline) { tokenHtmlClass = "lineEnd"; } else if (token instanceof TSpace) { tokenHtmlClass = "space"; } else if (token instanceof TTab) { tokenHtmlClass = "tab"; } else { tokenHtmlClass = "-"; } if (tokenHtmlClass.equals("-")) { out.print(token.getText()); } else if (tokenHtmlClass.equals("space")) { out.print(" "); } else if (tokenHtmlClass.equals("tab")) { out.print(" "); } else if (tokenHtmlClass.equals("lt")) { out.print("<span class=\"symbol\"><</span>"); // και τα '<' και '>' θα χρησιμοποιήσουν } else if (tokenHtmlClass.equals("gt")) { // το style της class symbol από το CSS out.print("<span class=\"symbol\">></span>"); } else if (tokenHtmlClass.equals("lineEnd")) { out.println("<br/>"); } else if (tokenHtmlClass.equals("comment")) { //πρέπει να αντικαταστήσουμε το "'" με "'" String s = token.getText(); out.print("<span class=\"comment\">'"+s.substring(1,s.length())+"</span>"); } else { out.print("<span class=\"" + tokenHtmlClass + "\">" + token.getText() + "</span>"); } } private void outputHtmlFooter(PrintWriter out) { out.println(""); out.println(" </p>"); out.println(" </body>"); out.println(""); out.println("</html>"); out.println(""); } public void generateHtml(InputStreamReader input, File output, String title) { try { Lexer lexer = new Lexer(new PushbackReader(input)); PrintWriter p = new PrintWriter(output); outputHtmlHeader(p, title); Token token = lexer.next(); while (!token.getText().equals("")) { String s = new String(); outputHtmlForToken(token, p); token = lexer.next(); } outputHtmlFooter(p); p.flush(); p.close(); } catch (Exception e) { System.err.println(e); } } public static void main(String[] args){ if ((args.length != 3)) { System.out.println("-Invalid parameters list."); System.exit(0); } SubBasic2Html converter = new SubBasic2Html(); try { converter.generateHtml(new FileReader(new File(args[0])), new File(args[1]), args[2]); } catch (Exception e) { } } }
Κάνουμε compile το πρόγραμμά μας (από το φάκελο στον οποίο βρίσκεται το αρχείο subbasic.grammar) με:
javac subbasic/*.java
και τρέχουμε την εφαρμογή με:
java subbasic.SubBasic2Html <αρχείο_εισόδου> <αρχείο_εξόδου> <τίτλος_εγγράφου>
π.χ. αν έχουμε ένα αρχείο test.bas με τον κώδικα:
CLS
DO
INPUT “Enter the first number: “, A
INPUT “Enter the second number: “, B
PRINT “The answer is: “; A * B
INPUT “Would you like to do it again? “, Answer$
FirstLetter$ = LEFT$(Answer$, 1)
LOOP WHILE FirstLetter$=”y” OR FirstLetter$=”Y”
και τρέξουμε το πρόγραμμά μας ως εξής:
java subbasic.SubBasic2Html test.bas out.html “SubBasic Test Code”
Θα πάρουμε το παρακάτω αποτέλεσμα (out.html):
