Setty-Grammatik SG = (N, T, P, S) mit
N = {Program, SetU, SetI, SetD, Set, From, To},
T = {LPAREN1 := '(', RPAREN1 := ')',
LPAREN2 := '[', RPAREN2 := ']',
LPAREN3 := '{', RPAREN3 := '}',
PLUS := '+', MINUS := '-',
SEMICOLON := ';', QUESTIONMARK := '?',
COMPLEMENT := 'C', UNION := 'u'|'v',
INTERSECTION := 'n', DIFFERENCE := '\',
IS := 'Is', IN := 'in',
NOT := 'not',
NUMBER := ['+'|'-']('0'-'9')+['.'('0'-'9')+],
INFINITY := 'inf'},
P = {Program ::= IS NUMBER [ NOT ] IN SetU QUESTIONMARK,
SetU ::= SetI ( UNION SetI )*,
SetI ::= SetD ( INTERSECTION SetD )*,
SetD ::= Set [ DIFFERENCE Set ],
Set ::= COMPLEMENT LPAREN1 SetU RPAREN1
| LPAREN1 SetU RPAREN1
| LPAREN3 [ NUMBER ( SEMICOLON NUMBER )* ] RPAREN3
| From SEMICOLON To,
From ::= RPAREN2 ( NUMBER | MINUS INFINITY )
| LPAREN2 NUMBER,
To ::= NUMBER ( LPAREN2 | RPAREN2 )
| [ PLUS ] INFINITY LPAREN2} und
S = Program.
/**
* Die Klasse Token deklariert die mnemonischen Bezeichner
* für jedes Token der Setty-Sprache.
*/
public class Token {
/** Token UNKNOWN zur Fehlererkennung. */
public static final int UNKNOWN = 0; // (Unbekanntes Token)
/** Token EOS signalisiert das Ende des Quellprogramms. */
public static final int EOS = 10; // (End Of String)
/* Folgende Tokens bezeichnen alle Terminalsymbole der Setty-Grammatik. */
public static final int LPAREN1 = 100; // "("
public static final int RPAREN1 = 110; // ")"
public static final int LPAREN2 = 120; // "["
public static final int RPAREN2 = 130; // "]"
public static final int LPAREN3 = 140; // "{"
public static final int RPAREN3 = 150; // "}"
public static final int PLUS = 160; // "+"
public static final int MINUS = 170; // "-"
public static final int SEMICOLON = 180; // ";"
public static final int QUESTIONMARK = 190; // "?"
public static final int COMPLEMENT = 200; // "C" (Komplement)
public static final int UNION = 210; // "u" | "v" (Vereinigung)
public static final int INTERSECTION = 220; // "n" (Schnitt)
public static final int DIFFERENCE = 230; // "\" (Mengenminus)
public static final int IS = 300; // "Is"
public static final int IN = 310; // "in"
public static final int NOT = 320; // "not"
public static final int NUMBER = 400; // (Zahl)
public static final int INFINITY = 410; // "inf"
}
...
public static final int UNKNOWN = 0;
public static final int EOS = UNKNOWN + 1;
public static final int LPAREN1 = EOS + 1;
public static final int RPAREN1 = LPAREN1 + 1;
...
public enum TokenType {
UNKNOWN,
EOS,
LPAREN1,
RPAREN1,
...
}
source
zu lesen.
in
und
inf
(Regel des längsten Musters benötigt viel Programmcode).
nextToken()
stürtzen, da alles andere in der Klasse
Scanner
nur Detailkram ist und deshalb auch ein bisschen abschreckend wirken könnte.
/**
* Die Klasse Scanner liest vom aktuellen Zeichen im Setty-Programm stets ein
* weiteres Token, so dass zu jeder Zeit der Parser dem Scanner befehlen kann:
* "Gib' mir das nächste Token!" ;-)
*/
public class Scanner {
/**
* Repräsentiert das im Konstruktor übergebene Setty-Programm (Source).
*/
private String source;
/**
* Zeigt auf ein Zeichen im Setty-Programm Source.
*/
private int position;
/**
* Das aktuelle Token.
*/
private int token;
/**
* Den Wert des aktuellen Token.
*/
private String tokenValue;
/**
* Prüft, ob noch ein weiteres Zeichen von Source gelesen werden kann.
* @return true Ja, es kann.
*/
private boolean hasChar() {
return (this.position < this.source.length());
}
/**
* Gibt das Zeichen von der aktuellen Position in Source zurück.
* Die Position wird um 1 erhöht.
* @return Das Zeichen von der aktuellen Position in Source.
*/
private char getChar() {
return this.source.charAt(this.position++);
}
/**
* Prüft, ob noch weitere count Zeichen von Source gelesen werden können.
* @param count Die Anzahl der Zeichen.
* @return true Ja, es können.
*/
private boolean hasString(int count) {
return (this.position + count <= this.source.length());
}
/**
* Gibt count Zeichen ab der aktuellen Position in Source zurück.
* Die Position wird um count erhöht.
* @param count Die Anzahl der Zeichen.
* @return Die count Zeichen ab der aktuellen Position in Source.
*/
private String getString(int count) {
String result = this.source.substring(this.position, this.position + count);
this.position += count;
return result;
}
/**
* Prüft, ob aktuell in Source wertemäßig value vorliegt.
* @return true Ja, es liegt vor.
*/
private boolean testString(String value) {
boolean result = false;
int count = value.length();
if (this.hasString(count)) {
result = value.equals(this.getString(count));
this.back(count);
}
return result;
}
/**
* Die Position wird um count heruntergesetzt.
* @param count Die Anzahl der Zeichen.
*/
private void back(int count) {
this.position = Math.max(0, this.position - count);
}
/**
* Erzeugt und initialisiert einen Scanner mit einem Setty-Programm.
* @param source Das Setty-Programm.
*/
public Scanner(String source) {
this.source = source;
this.position = 0;
this.token = Token.UNKNOWN;
this.tokenValue = "";
}
/**
* Gibt das aktuelle Token, welches zuletzt gelesen wurde, zurück.
* @return Das aktuelle Token.
*/
public int getToken() {
return this.token;
}
/**
* Gibt den Wert des aktuellen Tokens, welches zuletzt gelesen wurde, zurück.
* @return Der Wert des aktuellen Tokens.
*/
public String getTokenValue() {
return this.tokenValue;
}
/**
* Liest das nächste Token von der aktuellen Position in Source und
* gibt es zurück.
* @return Das nächste Token.
*/
public int nextToken() {
this.tokenValue = "";
this.token = Token.UNKNOWN;
while (this.hasChar()) {
char value = this.getChar();
switch (value) {
case '\n': case '\r': case '\t': case '\0': case ' ':
// Whitespaces usw. werden ignoriert! (skip)
break;
case '(':
this.tokenValue = "" + value;
this.token = Token.LPAREN1;
return this.token;
case ')':
this.tokenValue = "" + value;
this.token = Token.RPAREN1;
return this.token;
case '[':
this.tokenValue = "" + value;
this.token = Token.LPAREN2;
return this.token;
case ']':
this.tokenValue = "" + value;
this.token = Token.RPAREN2;
return this.token;
case '{':
this.tokenValue = "" + value;
this.token = Token.LPAREN3;
return this.token;
case '}':
this.tokenValue = "" + value;
this.token = Token.RPAREN3;
return this.token;
case ';':
this.tokenValue = "" + value;
this.token = Token.SEMICOLON;
return this.token;
case '?':
this.tokenValue = "" + value;
this.token = Token.QUESTIONMARK;
return this.token;
case 'C':
this.tokenValue = "" + value;
this.token = Token.COMPLEMENT;
return this.token;
case 'u': case 'v':
this.tokenValue = "" + value;
this.token = Token.UNION;
return this.token;
case 'n':
this.back(1);
// Regel des längsten Musters: "n" könnte ja noch zu "not" werden.
if (this.testString("not")) {
this.tokenValue = this.getString(3); // "not"
this.token = Token.NOT;
} else {
this.tokenValue = this.getString(1); // "n"
this.token = Token.INTERSECTION;
}
return this.token;
case '\\':
this.token = Token.DIFFERENCE;
return this.token;
case 'I':
this.back(1);
if (this.testString("Is")) {
this.tokenValue = this.getString(2); // "Is"
this.token = Token.IS;
} else {
this.tokenValue = this.getString(1); // "I"
this.token = Token.UNKNOWN;
}
return this.token;
case 'i':
this.back(1);
// Regel des längsten Musters: "in" könnte ja noch zu "inf" werden.
if (this.testString("inf")) {
this.tokenValue = this.getString(3); // "inf"
this.token = Token.INFINITY;
} else if (this.testString("in")) {
this.tokenValue = this.getString(2); // "in"
this.token = Token.IN;
} else {
this.tokenValue = this.getString(1); // "i"
this.token = Token.UNKNOWN;
}
return this.token;
case '+': case '-':
case '.':
case '0': case '1': case '2': case '3': case '4':
case '5': case '6': case '7': case '8': case '9':
this.back(1);
if (this.testString("+")) {
this.tokenValue += this.getString(1);
this.token = Token.PLUS;
} else if (this.testString("-")) {
this.tokenValue += this.getString(1);
this.token = Token.MINUS;
}
// Regel des längsten Musters:
// "+" könnte ja noch zu einer Zahl, die mit "+" beginnt, werden.
// Das Selbe gilt auch für "-".
while (this.hasChar()) {
char digit = this.getChar();
if ("0123456789".indexOf(digit) >= 0) {
this.tokenValue += digit;
this.token = Token.NUMBER;
} else {
this.back(1);
break;
}
}
if (this.testString(".")) {
this.tokenValue += this.getString(1);
this.token = Token.UNKNOWN;
while (this.hasChar()) {
char digit = this.getChar();
if ("0123456789".indexOf(digit) >= 0) {
this.tokenValue += digit;
this.token = Token.NUMBER;
} else {
this.back(1);
break;
}
}
}
return this.token;
default:
this.tokenValue = "" + value;
this.token = Token.UNKNOWN;
return this.token;
}
}
this.tokenValue = "";
this.token = Token.EOS;
return this.token;
}
}
public class TestScanner {
public static void main(String[] arguments) {
String testSource = "Is 4711 not in C(]-inf;+inf[)?";
if (arguments.length > 0) {
testSource = arguments[0];
}
System.out.println("Source: '" + testSource + "'");
System.out.println("");
Scanner scanner = new Scanner(testSource);
do {
scanner.nextToken();
switch (scanner.getToken()) {
case Token.UNKNOWN: System.out.print(" UNKNOWN"); break;
case Token.EOS: System.out.print(" EOS"); break;
case Token.LPAREN1: System.out.print(" LPAREN1"); break;
case Token.RPAREN1: System.out.print(" RPAREN1"); break;
case Token.LPAREN2: System.out.print(" LPAREN2"); break;
case Token.RPAREN2: System.out.print(" RPAREN2"); break;
case Token.LPAREN3: System.out.print(" LPAREN3"); break;
case Token.RPAREN3: System.out.print(" RPAREN3"); break;
case Token.PLUS: System.out.print(" PLUS"); break;
case Token.MINUS: System.out.print(" MINUS"); break;
case Token.SEMICOLON: System.out.print(" SEMICOLON"); break;
case Token.QUESTIONMARK: System.out.print("QUESTIONMARK"); break;
case Token.COMPLEMENT: System.out.print(" COMPLEMENT"); break;
case Token.UNION: System.out.print(" UNION"); break;
case Token.INTERSECTION: System.out.print("INTERSECTION"); break;
case Token.DIFFERENCE: System.out.print(" DIFFERENCE"); break;
case Token.IS: System.out.print(" IS"); break;
case Token.IN: System.out.print(" IN"); break;
case Token.NOT: System.out.print(" NOT"); break;
case Token.NUMBER: System.out.print(" NUMBER"); break;
case Token.INFINITY: System.out.print(" INFINITY"); break;
default: System.out.print(" [undefined]");
}
System.out.println(": '" + scanner.getTokenValue() + "'");
} while (scanner.getToken() != Token.EOS);
}
}
Source: 'Is 4711 not in C(]-inf;+inf[)?'
IS: 'Is'
NUMBER: '4711'
NOT: 'not'
IN: 'in'
COMPLEMENT: 'C'
LPAREN1: '('
RPAREN2: ']'
MINUS: '-'
INFINITY: 'inf'
SEMICOLON: ';'
PLUS: '+'
INFINITY: 'inf'
LPAREN2: '['
RPAREN1: ')'
QUESTIONMARK: '?'
EOS: ''
Source: 'Is +5.0 in {30.0;-1}?'
IS: 'Is'
NUMBER: '+5.0'
IN: 'in'
LPAREN3: '{'
NUMBER: '30.0'
SEMICOLON: ';'
NUMBER: '-1'
RPAREN3: '}'
QUESTIONMARK: '?'
EOS: ''
UNKNOWN
zurückgibt.
So kann trotz eines Fehlers im Quellprogramm einfach weitergelesen werden.
Da dieser Scanner stets einen definierten Folgezustand besitzt, kann dieser Scanner auch als
totaler
Automat bezeichnet werden.
Ohne dem Ausnahmetoken
UNKNOWN
wäre dieser Scanner nur ein partieller Automat,
was nicht praktikabel wäre.
Source: 'Is dee 3 a drinn?'
IS: 'Is'
UNKNOWN: 'd'
UNKNOWN: 'e'
UNKNOWN: 'e'
NUMBER: '3'
UNKNOWN: 'a'
UNKNOWN: 'd'
UNKNOWN: 'r'
IN: 'in'
INTERSECTION: 'n'
QUESTIONMARK: '?'
EOS: ''
IN,
sondern gemäß der Grammatik mit
IS
beginnt.
Source: 'innotnun?Is+-+0815.000Cu)}[-inf'
IN: 'in'
NOT: 'not'
INTERSECTION: 'n'
UNION: 'u'
INTERSECTION: 'n'
QUESTIONMARK: '?'
IS: 'Is'
PLUS: '+'
MINUS: '-'
NUMBER: '+0815.000'
COMPLEMENT: 'C'
UNION: 'u'
RPAREN1: ')'
RPAREN3: '}'
LPAREN2: '['
MINUS: '-'
INFINITY: 'inf'
EOS: ''