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ä:
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.
Tokenizer runko
Muodosta luokka Tokenizer, joka käy läpi syötettä
ja tokenisoi sitä. Määritä luokalle seuraavat ominaisuusmuuttujat:
Tee luontimetodi Tokenizer(Reader r), joka luo
uuden tokenisoijan ja alustaa ominaisuusmuuttujat.
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
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
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
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
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:
'(',
')', ',', ';' tai
'=' palautetaan uusi Token, jonka
tyyppi on vastaavasti LEFT_PAREN, RIGHT_PAREN,
COMMA, SEMICOLON tai
ASSIGN.
readIdentifier ja palautetaan uusi
Token, jonka tyyppi on IDENTIFIER ja
arvo ominaisuusmuuttujan tokenData sisältö.
readString ja palautetaan
uusi Token, jonka tyyppi on STRING
ja arvo ominaisuusmuuttujan tokenData sisältö.
Token, jonka tyyppi on UNKNOWN ja
arvo kyseinen merkki.
Token on saatu
luettua, palautetaan null.
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
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.