Home meiner WebpräsenzStartseite
Das Menü auf einem Blick!Sitemap
Downloads vorliegender WebSiteDownloads
Hilfeseite vorliegender HomepageHilfe
Erscheinungsvermerk vorliegender HomepageImpressum
Zauberwürfel mit Povray (Rubik)POV-Ray-Zauberwürfel










Setty-Scanner
Ein verhältnismäßig einfacher Scanner zur Quellsprache Setty.
1 Setty-Scanner
2 Implementierung
3 Test
Setty−Scanner

Setty's Scanner
Dieser Scanner kann eine Zeichenkette, in der alle Terminalsymole T folgender Grammatik SG in beliebiger Reihenfolge vorkommen dürfen, tokenweise lesen.
Die Grammatik der Setty−Sprache
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
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.
Nur die Menge der Terminalsymbole!
Zur Entwicklung eines Scanners benötigt man generell nur die Definition der textuellen Zusammensetzung aller Terminalsymbole T einer Grammatik, nichts weiter!
Die anderen Elemente einer Grammatik N, P und S haben nichts mit dem Scanner zu tun. Der Scanner „konzentriert” sich lediglich auf das tokenweise Lesen. Beim Lesen einer unbekannten Fremdsprache, verhält sich der Mensch wie ein Scanner, er liest wortweise, ohne etwas von der Grammatik zu wissen.
Tokens sind fast wie Hieroglyphen, jede Hieroglyphe besitzt eine Bedeutung, die textuell ausgedrückt werden kann.
Implementierung

Zeichenstrom −> Tokenstrom
Da die Hauptarbeit eines Scanners darin liegt, einen Zeichenstrom in einen Tokenstrom umzuwandeln, benötigt man Tokens; es sind zumindest die Terminalsymbole der Grammatik. Hier werden die Tokens als Integer−Konstanten repräsentiert.
Die Setty−Tokens mit Java
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
32
33
34
35
36
37
/**
 * 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"
}
Hierbei kommt es vor allem darauf an, den Tokens sprechende Bezeichnungen (mnemonisch) zu geben. Welche Wertigkeit sich nun hinter einem Tokenbezeichner verbirgt, ist eigentlich egal. Letztendlich müssen die Tokens paarweise unterscheidbar sein.
Um sicherzustellen, dass sich alle aufgeführten Tokens paarweise unterscheiden, können die Tokens auch nach dem Prinzip der Aufzählung deklariert werden:
...
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;
...
Der Scanner würde somit leichter zu warten sein. Neue Tokens können einfach in die Aufzählung eingegliedert werden. Einen Tokenwert nun irrtumlicherweise mehrfach zu verwenden, würde hiermit unmöglich gemacht werden.
Seit der Java-Version 1.5 gibt es erstmals die Möglichkeit mit enum einen Aufzähungstyp in Java zu definieren, was die Deklaration von Token-Typen extrem erleichtert:
public enum TokenType {
UNKNOWN,
EOS,
LPAREN1,
RPAREN1,
...
}
Dank des neuen Enum−Typen muss man sich als Programmierer, nicht mehr um die eindeutige Wertigkeit einzelner Tokens kümmern. Was unter vielen anderen Programmiersprachen schon lange möglich war, ist nun endlich auch mit Java 1.5 möglich.
Sehen Sie hierzu die Tokenklasse von Tinyray−Scanner.
Der Setty−Scanner
Der Setty−Scanner wird dazu verwendet, Setty−Tokens vom gegebenen String source zu lesen.
Folgender Scanner muss zwar nicht viele Token−Typen erkennnen können, dennoch erreicht er eine kaum überschaubare Größe.
Warum das so ist, liegt in der Natur eines jeden Scanners, der zwischen vielen Token−Typen unterscheiden muss, besonders wenn manche Tokens den selben Wortanfang haben wie beispielsweise in und inf (Regel des längsten Musters benötigt viel Programmcode).
Tipp: Der Lesende sollte sich sofort auf die Methode nextToken() stürtzen, da alles andere in der Klasse Scanner nur Detailkram ist und deshalb auch ein bisschen abschreckend wirken könnte.
Der Setty−Scanner mit Java
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
/**
 * 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;
  }
}
Scanner−Generator
Wie Sie sehen, ist die händische Implementierung eines Scanners sehr umfangreich. Fehler kann man bei dieser Vorgehensweise quasi nie ausschließen, deshalb sei zur Entwicklung eines Scanners die Verwendung eines Scannergenerators (JLex oder JFlex) als weitaus sicherere Variante erwähnt.
Sie definieren die Tokenmenge in der Regel mit regulären Ausdrücken, damit der Scannergenerator eine Scannerklasse (hier: eine Java-Klasse) automatisch generieren kann, d. h. die Scannerklasse müssen Sie somit nicht selbst schreiben, weil dies der Scannergenerator für Sie erledigt.
Test

Arbeitsweise des Scanners
Folgende Testklasse zeigt die einfache Handhabung des Setty−Scanners.
Der Setty−Scanner wird getestet
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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
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);

  }
}
java TestScanner "Is 4711 not in C(]−inf;+inf[)?"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
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: ''
java TestScanner "Is +5.0 in {30.0;−1}?"
1
2
3
4
5
6
7
8
9
10
11
12
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: ''
Der Setty-Scanner erkennt aber auch Fehler, wenn die gegebene Zeichenkette ungültige Zeichenfolgen enthält.
Das Gute hierbei ist, dass der Scanner keine Exception wirft, was den Lesefluss unterbrechen würde, sondern das definierte Ausnahmetoken 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.
Genauer gesagt, handelt es sich beim vorliegenden Scanner um einen totalen, deterministischen, endlichen Automaten.
java TestScanner "Is dee 3 a drinn?"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
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: ''
Im Folgenden kommen ausnahmslos gültige Tokens vor, jedoch mit falscher Grammatik — kein Problem für den Setty−Scanner. Der Parser allerdings würde schon beim ersten Token eine ParseException werfen, weil ein Setty−Programm nicht mit IN, sondern gemäß der Grammatik mit IS beginnt.
java TestScanner "innotnun?Is+−+0815.000Cu)}[−inf"
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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: ''
Allgemein

Diese Seite ist Bestandteil der Domäne www.stefan−baur.de und heißt Setty−Scanner. Die letzte Änderung erfuhr diese Seite am Freitag, den 21. August 2009 von Stefan K. Baur. Zuletzt wurde sie von Ihnen am Mittwoch, den 8. September 2010 um 3:41 Uhr im Internet besucht. Sie gelangen zur vorliegenden Seite, wenn Sie ausgehend von der Startseite über dem Hauptmenü wie folgt navigieren:
Startseite
Informatik
Programmiersprachen
Interpreter
Setty
Setty−Scanner
Wenn Sie die vorliegende Seite ausdrucken möchten, nutzen Sie dazu die Printversion.


Autoren

Werden Sie Autor vorliegender Homepage, informieren Sie sich unter Autoren.
Look & Feel

Das aktuelle Design trägt den Namen „Design 2007”.
Falls Ihnen jedoch das aktuelle Design nicht zusagen sollte, so stehen Ihnen noch andere, alternative Designs zur Verfügung:
Design 2010
Design 2009
Design 2008
Design 2006
Design 2005
Design 2004
Copyright

Vorliegender Webauftritt wurde in mühevoller Kleinarbeit von Stefan K. Baur entwickelt.
Copyright (c) 2004-2010 Stefan K. Baur
Home meiner WebpräsenzStartseite
Das Menü auf einem Blick!Sitemap
Downloads vorliegender WebSiteDownloads
Hilfeseite vorliegender HomepageHilfe
Erscheinungsvermerk vorliegender HomepageImpressum
Zauberwürfel mit Povray (Rubik)POV-Ray-Zauberwürfel