Osatehtävä 2: Syötteen tokenisointi

Osatehtävässä ohjelmoidaan syötteen tokenisoija (engl. tokenizer; valitettavasti kunnollista suomenkielistä termiä ei ole käytössä). Tokenisointi on keskeinen merkkiimuotoisen syötteen käsittelyyn liittyvä tehtävä. Tokenisoija käy läpi merkkimuotoista syötettä ja tunnistaa siitä suurempia sanatason kokonaisuuksia. Näitä kokonaisuuksia sanotaan tokeneiksi. Tyypillisiä tokeneita ovat numerot, tunnukset, varatut sanat, merkkijonot sekä erotinmerkit. Tokeniin voi liittyä arvo. Esimerkiksi numerotokeneihin liittyy vastaava numeerinen arvo. Merkityksettömät merkit, kuten välilyönnit ja rivinvaihdot ohitetaan (paitsi merkkijonojen sisällä).

Muodostettavan tokenisoijan tulee tunnistaa seuraavat tokenit syötteestä:

Tunnus eli identifier
Tunnus koostuu yhdestä tai useammasta kirjaimesta tai numerosta, joista ensimmäisen on oltava kirjain.
Merkkijono
Merkkijono on kaksinkertaisten lainausmerkkien " " väliin jäävä jono merkkejä.
Vasen sulje '('
Oikea sulje ')'
Pilkku ','
Puolipiste ';'
Sijoitusmerkki '='

Jos syöte on esimerkiksi:

aux = f(a, d, c);
d = aux;
g(d, e);
      

tuottaa tokenisoija seuraavan jonon tokeneita.

IDENTIFIER[aux]
ASSIGN
IDENTIFIER[f]
LEFT_PAREN
IDENTIFIER[a]
COMMA
IDENTIFIER[d]
COMMA
IDENTIFIER[c]
RIGHT_PAREN
SEMICOLON
IDENTIFIER[d]
ASSIGN
IDENTIFIER[aux]
SEMICOLON
IDENTIFIER[g]
LEFT_PAREN
IDENTIFIER[d]
COMMA
IDENTIFIER[e]
RIGHT_PAREN
SEMICOLON
      

Tehtävässä ei käytetä Java API:n valmiita StreamTokenizer- tai StringTokenizer-luokkia vaan syötteen käsittely tehdään kokonaan itse. Kyseisillä luokilla pystyy tekemään ainoastaan hyvin yksinkertaisia tokenisoijia, joten on hyvä oppia toteuttamaan tokenisoija itse. Tehtävässä laadittavaa tokenisoijaa on helppo laajentaa.

Tokenien esittämistä varten on tarjolla valmis luokka Token. Sen voit kopioida itsellesi täältä. Luokan JavaDoc-muotoinen dokumentaatio kertoo tarjolla olevat operaatiot.

Huom! Osatehtävissä laadittavat metodit ovat lyhyitä. Ainoastaan metodi readToken on hieman pitempi (noin 30 riviä). Jos ratkaisusi muodostuvat kovin pitkiksi, mieti voisitko totetuttaa ne toisin.

Osatehtävä 2.1: Luokan Tokenizer runko

Muodosta luokka Tokenizer, joka käy läpi syötettä ja tokenisoi sitä. Määritä luokalle seuraavat ominaisuusmuuttujat:

java.io.Reader reader
Syötteen lukija.
char c
Syötteestä viimeksi luettu merkki.
boolean eof
Tosi, jos syöte on kokonaan luettu.
StringBuffer tokenData
Sisältää tokenia vastaavan arvon.

Tee luontimetodi Tokenizer(Reader r), joka luo uuden tokenisoijan ja alustaa ominaisuusmuuttujat.

Osatehtävä 2.2: Metodi void read()

Tee luokalle Tokenizer metodi read(), joka lukee seuraavan merkin reader-oliolta ja asettaa sen ominaisuusmuuttujan c arvoksi. Jos syöte on loppu, eikä seuraavaa merkkiä saatu luettua, asetetaan ominaisuusmuuttujan eof arvoksi true.

Lisää luokkaasi oheinen testimetodi:

public void testRead() {
    while (!eof) {
        System.out.println("Char: " + c + " = " + (int)c);
        read();
    }
    System.out.flush();
}
      

Testaa sitten read metodi oheisella pääohjelmalla ja syötetiedostolla. Sen tulisi tulostaa seuraavaa:

Char:   = 32
Char: x = 120
Char:   = 32
Char: s = 115
Char:   = 32
Char: s = 115
Char: k = 107
Char:   = 32
Char:
 = 10
Char: l = 108
Char: k = 107
Char: s = 115
Char:   = 32
Char: f = 102
Char: j = 106
Char: s = 115
Char: l = 108
Char: d = 100
Char: k = 107
Char: j = 106
Char: f = 102
Char:   = 32
Char:
 = 10
      

Osatehtävä 2.3: Metodi void skipWhitespace()

Tee luokalle Tokenizer metodi skipWhitespace() joka ohittaa välilyönnit, tabulaattorimerkit ja rivinvaihdot syötteestä. Käytä merkkien lukemiseen luokan omaa metodia read. Toteuta metodi siten, että se ei lue yhtään uutta merkkiä, jos ominaisuusmuuttujassa c on aluksi jokin muu merkki kuin välilyönti, tabulaattori tai rivinvaihto.

Lisää luokkaasi oheinen testimetodi:

public void testSkipWhitespace() {
    while (!eof) {
        skipWhitespace();
        if (eof) {
            break;
        }
        System.out.println("Char: " + c + " = " + (int)c);
        read();
    }
    System.out.flush();
}
      

Testaa sitten skipWhitespace metodi oheisella pääohjelmalla ja syötetiedostolla. Sen tulisi tulostaa seuraavaa:

Char: a = 97
Char: b = 98
Char: c = 99
Char: d = 100
Char: f = 102
Char: g = 103
      

Osatehtävä 2.4: Metodi void readIdentifier()

Tee luokalle Tokenizer metodi readIdentifier(). Metodi olettaa, että edellinen luettu merkki c on ollut kirjain. Metodi lukee syötettä read-metodilla niin kauan, kun seuraava merkki on kirjain tai numero. Kaikki tunnuksen merkit asetetaan ominaisuusmuuttujaan tokenData.

Lisää luokkaasi oheinen testimetodi:

public void testReadIdentifier() {
    while (!eof) {
        skipWhitespace();
        if (eof) {
            break;
        }
        readIdentifier();
        System.out.println("Identifier: " + tokenData);
    }
    System.out.flush();
}
      

Testaa sitten readIdentifier-metodi oheisella pääohjelmalla ja syötetiedostolla. Sen tulisi tulostaa seuraavaa:

Identifier: aapo
Identifier: eero
Identifier: juhani
Identifier: tuomas
      

Osatehtävä 2.5: Metodi void readString()

Tee luokalle Tokenizer metodi readString(). Metodi olettaa, että edellinen luettu merkki c on ollut lainausmerkki ". Metodi lukee syötettä read-metodilla niin kauan, kunnes vastaan tulee toinen lainausmerkki " tai eof. Kaikki lainausmerkkien välissä olevat merkit sijoitetaan ominaisuusmuuttujaan tokenData. Metodin on luettava myös lainausmerkkiä seuraava merkki read-metodilla. Jos syöte loppuu kesken merkkijonon lukemisen, annetaan virheilmoitus.

Lisää luokkaasi oheinen testimetodi:

public void testReadString() {
    while (!eof) {
        skipWhitespace();
        if (eof) {
            break;
        }
        if (c == '"') {
            readString();
            System.out.println("String: " + tokenData);
        } else {
            System.err.println("Internal error in test");
            System.exit(1);
        }
    }
    System.out.flush();
}
      

Testaa sitten readString-metodi oheisella pääohjelmalla ja syötetiedostolla. Sen tulisi tulostaa seuraavaa:

String: aapo   eero
String:  juhani
String: tuomas
      

Osatehtävä 2.6: Metodi Token readToken()

Tee luokalle Tokenizer metodi readToken(). Metodi olettaa, että ominaisuusmuuttujassa c on valmiiksi seuraavaksi käsiteltävä merkki (eli merkki, joka ei kuulunut edelliseen tokeniin). Metodi ohittaa ensiksi välilyönnit, tabulaattorit ja rivinvaihdot metodin skipWhitespace avulla. Tämän jälkeen:

Lisää luokkaasi oheinen testimetodi:

public void testReadToken() {
    while (!eof) {
        Token t = readToken();
        System.out.println("Token: " + t);
    }
    System.out.flush();
}
      

Testaa sitten readToken-metodi oheisella pääohjelmalla ja syötetiedostolla. Sen tulisi tulostaa seuraavaa:

Token: IDENTIFIER[a]
Token: ASSIGN
Token: IDENTIFIER[f]
Token: LEFT_PAREN
Token: STRING[a b c]
Token: COMMA
Token: UNKNOWN
Token: UNKNOWN
Token: RIGHT_PAREN
Token: SEMICOLON
Token: null
      

Osatehtävä 2.7: Dokumentointi

Laadi luokalle Tokenizer JavaDoc-tyyppinen dokumentaatio. Dokumentaatio kirjoitetaan luokan sisään kommenteilla. Esimerkki dokumentaation laatimisesta löytyy tiedostosta Token.java. Dokumentoi luokka Tokenizer ja kaikki sen metodit ja ominaisuusmuuttujat. Riittää, että käytät @param- ja @return-tageja.

Kun olet kirjoittanut dokumentaatioon liittyviä kommentteja luokkaan, saat vastaavat HTML-tiedostot JDE-valikon operaatiolla Dokumentation -> Generate. Jos haluat linkkien toimivan myös käyttämiesi Javan API:n luokkien dokumentaatioon, on sinun asetettava JDE-valikosta kohdasta Project -> Options -> Javadoc muuttujalle Gen Link Online arvo on. Vastaavasti on samasta paikasta löytyvälle muuttujalle Gen Link Url asetettava arvo http://java.sun.com/j2se/1.3/docs/api/. Muista tallettaa arvot operaatiolla State -> Save for future sessions.


Last modified: Mon Apr 29 10:51:32 EEST 2002