Τί είναι το SableCC;
Το SableCC είναι ένα πρόγραμμα που παίρνει ως είσοδο ένα σύνολο από κανόνες που περιγράφουν μία γλώσσα και παράγει τις απαραίτητες τάξεις για τη λεκτική και συντακτική ανάλυση εκφράσεων της γλώσσας αλλά και το μετασχηματισμό του συντακτικού δέντρου σε αφηρημένο και τη διάσχιση του δέντρου αυτού.
Το SableCC 3.2 είναι διαθέσιμο εδώ: http://sablecc.org/wiki/DownloadPage
Εμείς θα περιγράψουμε πως μπορούμε να κατασκευάσουμε ένα λεκτικό αναλυτή (lexer) για ένα μικρό υποσύνολο της QBasic που θα παίρνει ως είσοδο ένα πρόγραμμα γραμμένο σε αυτό το υποσύνολο της QBasic και στη συνέχεια θα το "σπάει" στα κομμάτια (tokens) από τα οποία αποτελείται και θα προσπαθεί να αναγνωρίσει κάθε κομμάτι. Έτσι στο τέλος θα έχουμε ένα σύνολο από tokens που μπορούμε να τα κάνουμε ότι θέλουμε - εμείς θα παράγουμε ένα έγγραφο html μορφοποιημένο κατάλληλα (π.χ. έντονα keywords) - σημαντικό είναι οτι σε αυτή την περίπτωση μας χρειάζονται και τα κενά, τα tabs και οι αλλαγές γραμμής(για να έχει την ίδια μορφή και στοίχιση και το τελικό αρχείο html).
Η γλώσσα που θα αναγνωρίζει ο lexer μας θα είναι αυτή:
Δεσμευμένες Λέξεις (token: keyword)
CLS
DO
WHILE
LOOP
INPUT
IF
THEN
ELSE
END
SELECT
CASE
OR
FOR
TO
NEXT
STEP
CONST
(εδώ δε χρειαζόμαστε διαφορετικό token για κάθε δεσμευμένη λέξη - π.χ. για συντακτική ανάλυση - αφού απλά θέλουμε οι δεσμευμένες λέξεις να έχουν το ίδιο στυλ)
Μεταβλητές (για αλφαριθμητικά, token: string_var)
γράμμα ακολουθούμενο από γράμματα ή ψηφία ή κάτω παύλες και στο τέλος $
π.χ. alphanum_1$
Μεταβλητές (για ακεραίους, token: num_var)
γράμμα ακολουθούμενο από γράμματα ή ψηφία ή κάτω παύλες
π.χ. var_001
Αλφαριθμητικές σταθερές (token: string_const)
οποιοαδήποτε ακολουθία χαρακτήρων ανάμεσα σε διπλά εισαγωγικά (quotes)
π.χ. "This is - a - test string. "
Αριθμητικές σταθερές (token: num_const)
οποιοαδήποτε ακολουθία ενός ή περισσότερων ψηφίων
π.χ. 0 12 213 κλπ.
Σύμβολα (token: symbol)
+-*/(),=;
Για τα σύμβολα '<' και '>' πρέπει να ορίσουμε ειδικά tokens αφού θα απαιτούν ξεχωριστό χειρισμό (δεν επιτρέπονται στην html οι αντίστοιχοι χαρακτήρες) :
lt
<
gt
>
Σχόλια (token: comment)
Μονό εισαγωγικό (') ακολουθούμενο από οποιοδήποτε χαρακτήρα έως την αλλαγή γραμμής
Επίσης, θα χρειαστούμε και τα κενά και tabs για διατήρηση της στοίχισης του κώδικα (ενώ αν ο lexer προοριζόταν για χρήση από έναν συντακτικό αναλυτή τότε θα έπρεπε να αγνοούνται). Ακόμη, τα κενά θα παράγουν διαφορετικό token από τα tabs αφού θέλουμε να τα χειριστούμε διαφορετικά στη συνέχεια (για κάθε tab θα παράγουμε 8 στην html):
Κενό (token: space)
' '
Tab (token: tab)
ο χαρακτήρας με ascii-code 9
Ομοίως πρέπει να χειριστούμε και τις αλλαγές γραμμής:
Αλλαγή γραμμής (token: newline)
cr, lf ή cr lf
Ορισμός των Helpers
Τώρα πρέπει με κάποιο τρόπο να εκφράσουμε τα παραπάνω με τρόπο που να μπορεί να τα καταλάβει το SableCC.
Αρχικά δημιουργούμε ένα νέο κενό αρχείο απλού κειμένου και γράφουμε τα εξής:
Code: Select all
Package subbasic;
Helpers
Tokens
Code: Select all
Package subbasic;
θα χρησιμοποιηθεί από το SableCC για τη δημιουργία ενός φακέλου με το όνομα subbasic όπου θα τοποθετηθούν όλα τα αρχεία που θα παράγει.
Κάτω από το Helpers μπορούμε να ορίσουμε σύμβολα που θα μας χρειαστούν για τον ορισμό των tokens.
Π.χ. για να ορίσουμε τις αριθμητικές σταθερές πρέπει πρώτα να έχουμε ορίσει το ψηφίο (δε θέλουμε όμως να αναγνωρίζεται ως token γι' αυτό το ορίζουμε ως helper ώστε να μην υπάρχει στην έξοδο που παράγει ο λεκτικός αναλυτής). Έτσι, ορίζουμε τα ψηφία στο τμήμα helpers ως εξής:
Code: Select all
digit = ['0'..'9'];
Code: Select all
quote = '"';
cr = 13;
lf = 10;
single_quote = 0x0027; //Ο χαρακτήρας ' (απλό εισαγωγικό)
Αν ο helper αντιστοιχεί σε ένα σύνολο χαρακτήρων μπορούμε να τον ορίσουμε τοποθετώντας ανάμεσα σε αγκύλες την αρχική και τελική τιμή χωρισμένες από δύο τελείες:
Code: Select all
any_unicode_char = [0..0xffff] ; //Όλοι οι χαρακτήρες Unicode
Αν ο helper αντιστοιχεί σε περισσότερους από έναν χαρακτήρες ή σετ ή ακολουθίες τότε μπορούμε να χρησιμοποιήσουμε την κάθετο | για διάζευξη μεταξύ των επιλογών π.χ.
Code: Select all
letter = ['a'..'z'] | ['A'..'Z'];
Code: Select all
non_quote = [any_unicode_char - quote];
permitted_in_string = [non_quote - new_line];
permitted_in_comment = [any_unicode_char - new_line];
new_line = [cr + lf];
Τέλος, θα μπορούσαμε να χρησιμοποιήσουμε τα σύμβολα *, + και ?:
- a* -> το a καμία, μία η περισσότερες φορές
- a+ -> το a μία η περισσότερες φορές
- a? -> το a μία ή καμία φορά
Έτσι πλέον το αρχείο μας έχει τη μορφή:
Code: Select all
Package subbasic;
Helpers
any_unicode_char = [0..0xffff] ; //Όλοι οι χαρακτήρες Unicode
cr = 13;
lf = 10;
new_line = [cr + lf];
quote = '"';
single_quote = 0x0027; //Ο χαρακτήρας ' (απλό εισαγωγικό)
non_quote = [any_unicode_char - quote];
permitted_in_string = [non_quote - new_line];
permitted_in_comment = [any_unicode_char - new_line];
letter = ['a'..'z'] | ['A'..'Z'];
digit = ['0'..'9'];
Tokens
Πλέον είναι ώρα να ορίσουμε τα tokens. Οι ορισμοί γράφονται κάτω από τη γραμμή :
Code: Select all
Tokens
Αρχικά ορίζουμε το κενό, την αλλαγή γραμμής και το tab:
Code: Select all
space = ' ';
newline = cr | lf | cr lf;
tab = 9;
Ορίζουμε τις δεσμευμένες λέξεις(case sensitive):
Code: Select all
keyword = 'PRINT' | 'CLS' | 'DO' | 'WHILE' | 'LOOP' | 'INPUT'
| 'IF' | 'THEN' | 'ELSE' | 'END' | 'SELECT' | 'CASE' | 'OR'
| 'FOR' | 'TO' | 'NEXT' | 'STEP' | 'CONST';
Μεταβλητές:
Code: Select all
string_var = letter (letter | digit | '_')* '$';
//γράμμα ακολουθούμενο από γράμματα ή ψηφία ή κάτω παύλες και στο τέλος $
num_var = letter (letter | digit | '_')*;
Σταθερές:
Code: Select all
string_const = quote permitted_in_string* quote;
num_const = digit+; // ένα ή περισσότερα ψηφία
Σύμβολα:
Code: Select all
symbol = '+'| '-' | '*' | '/' | '(' | ')' | '=' | ',' | ';' ;
lt = '<';
gt = '>';
Σχόλια:
Code: Select all
comment = single_quote permitted_in_comment*;
Code: Select all
misc = any_unicode_char ;
Το τελικό αρχείο θα είναι αυτό:
Code: Select all
Package subbasic;
Helpers
any_unicode_char = [0..0xffff] ; //Όλοι οι χαρακτήρες Unicode
cr = 13;
lf = 10;
new_line = [cr + lf];
quote = '"';
single_quote = 0x0027; //Ο χαρακτήρας ' (απλό εισαγωγικό)
non_quote = [any_unicode_char - quote];
permitted_in_string = [non_quote - new_line];
permitted_in_comment = [any_unicode_char - new_line];
letter = ['a'..'z'] | ['A'..'Z'];
digit = ['0'..'9'];
Tokens
space = ' ';
newline = cr | lf | cr lf;
tab = 9;
keyword = 'PRINT' | 'CLS' | 'DO' | 'WHILE' | 'LOOP' | 'INPUT'
| 'IF' | 'THEN' | 'ELSE' | 'END' | 'SELECT' | 'CASE' | 'OR'
| 'FOR' | 'TO' | 'NEXT' | 'STEP' | 'CONST';
string_var = letter (letter | digit | '_')* '$';
//γράμμα ακολουθούμενο από γράμματα ή ψηφία ή κάτω παύλες και στο τέλος $
num_var = letter (letter | digit | '_')*;
string_const = quote permitted_in_string* quote;
num_const = digit+; // ένα ή περισσότερα ψηφία
symbol = '+'| '-' | '*' | '/' | '(' | ')' | '=' | ',' | ';';
lt = '<';
gt = '>';
comment = single_quote permitted_in_comment*;
misc = any_unicode_char;
Το αποθηκεύουμε ως subbasic.grammar και το περνάμε ως είσοδο στο SableCC:
Code: Select all
java -jar sablecc.jar
Τώρα μένει να κατασκευάσουμε μία εφαρμογή που θα παίρνει ως είσοδο ένα αρχείο με κώδικα γραμμένο στη γλώσσα που περιγράψαμε πιο πάνω και θα παράγει ένα αρχείο html με την κατάλληλη μορφοποίηση.
Αρχικά δημιουργούμε ένα νέο κενό αρχείο με το όνομα
SubBasic2Html.java
μέσα στο φάκελο subbasic που δημιούργησε το SableCC.
Ανοίγουμε το SubBasic2Html.java και κατασκευάζουμε το βασικό σκελετό (imports, main() κλπ.):
Code: Select all
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:
Code: Select all
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):
Code: Select all
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>");
}
}
Code: Select all
private void outputHtmlFooter(PrintWriter out) {
out.println("");
out.println(" </p>");
out.println(" </body>");
out.println("");
out.println("</html>");
out.println("");
}
και τέλος, μία μέθοδος που θα τα συνδυάζει όλα αυτά ώστε να παραχθεί το τελικό έγγραφο:
Code: Select all
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 και να προσδιορίσουμε ένα αρχείο εξόδου και ένα τίτλο για το τελικό έγγραφο (π.χ. μέσω των παραμέτρων εκτέλεσης του προγράμματος):
Code: Select all
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 θα είναι αυτό:
Code: Select all
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) {
}
}
}
Code: Select all
javac subbasic/*.java
Code: Select all
java subbasic.SubBasic2Html <αρχείο_εισόδου> <αρχείο_εξόδου> <τίτλος_εγγράφου>
π.χ. αν έχουμε ένα αρχείο test.bas με τον κώδικα:
Code: Select all
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"
Code: Select all
java subbasic.SubBasic2Html test.bas out.html "SubBasic Test Code"
Code: Select all
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title>SubBasic Test Code</title>
<style type="text/css">
.keyword{
color: rgb(0,41,103);
font-weight: bold;
font-family: monospace;
}
.num_const{
color: rgb(1,104,0);
font-family: monospace;
}
.string_const{
color: rgb(101,0,103);
font-family: monospace;
}
.symbol{
color: rgb(103,0,1);
font-weight: bold;
font-family: monospace;
}
.comment{
color: rgb(17,78,33);
font-style: italic;
font-family: monospace;
}
.num_var, .string_var{
color: rgb(0,0,0);
font-family: monospace;
}
.misc{
color: rgb(255, 0, 0);
font-family: monospace;
}
</style>
</head>
<body>
<p>
<span class="keyword">CLS</span><br/>
<span class="keyword">DO</span><br/>
<span class="keyword">INPUT</span> <span class="string_const">"Enter the first number: "</span><span class="symbol">,</span> <span class="num_var">A</span><br/>
<span class="keyword">INPUT</span> <span class="string_const">"Enter the second number: "</span><span class="symbol">,</span> <span class="num_var">B</span><br/>
<span class="keyword">PRINT</span> <span class="string_const">"The answer is: "</span><span class="symbol">;</span> <span class="num_var">A</span> <span class="symbol">*</span> <span class="num_var">B</span><br/>
<br/>
<span class="keyword">INPUT</span> <span class="string_const">"Would you like to do it again? "</span><span class="symbol">,</span> <span class="string_var">Answer$</span><br/>
<span class="string_var">FirstLetter$</span> <span class="symbol">=</span> <span class="string_var">LEFT$</span><span class="symbol">(</span><span class="string_var">Answer$</span><span class="symbol">,</span> <span class="num_const">1</span><span class="symbol">)</span><br/>
<span class="keyword">LOOP</span> <span class="keyword">WHILE</span> <span class="string_var">FirstLetter$</span><span class="symbol">=</span><span class="string_const">"y"</span> <span class="keyword">OR</span> <span class="string_var">FirstLetter$</span><span class="symbol">=</span><span class="string_const">"Y"</span><br/>
<br/>
</p>
</body>
</html>