Programming lesson
Scanner und CSVParser in Java: Schritt-für-Schritt-Anleitung für ECS140A
Lerne, wie du einen eigenen Scanner und CSVParser in Java implementierst – basierend auf den Anforderungen der ECS140A Projekte 1–4. Mit praktischen Beispielen und Tipps für den CSIF.
Einleitung: Warum Scanner und CSVParser wichtig sind
In der Programmiersprachenverarbeitung sind Scanner und Parser grundlegende Werkzeuge. Ein Scanner zerlegt einen Zeichenstrom in Tokens, die dann von einem Parser weiterverarbeitet werden. In diesem Tutorial zeige ich dir, wie du für die ECS140A Projekte 1–4 einen eigenen Scanner und einen CSVParser in Java implementierst. Diese Konzepte sind nicht nur für dein Studium relevant, sondern auch in der Praxis – etwa beim Parsen von Konfigurationsdateien oder beim Verarbeiten von CSV-Daten aus Umfragen.
Aktuell (Juni 2026) sind CSV-Dateien immer noch das Standardformat für Datenaustausch in vielen Bereichen, von der Finanzanalyse bis zu KI-Trainingsdaten. Ein solider Parser ist daher eine wertvolle Fähigkeit.
Projektübersicht: Was du implementieren wirst
Deine Aufgaben umfassen:
- PeekableCharacterStream: Eine Klasse, die einen FileInputStream kapselt und Methoden zum Vorausschauen und Lesen von Zeichen bietet.
- Scanner: Zerlegt den Zeichenstrom in Tokens gemäß den vorgegebenen Token-Regeln.
- CSVParser: Parst CSV-Dateien mit Header-Zeile und gibt jede Zeile als Map zurück.
Alle Klassen müssen auf dem CSIF (Computer Science Instructional Facility) kompilieren und laufen. Du wirst eine Makefile und eine README.txt einreichen.
Schritt 1: PeekableCharacterStream implementieren
Die Schnittstelle PeekableCharacterStream erlaubt es, Zeichen vorauszuschauen, ohne sie zu konsumieren. Das ist essentiell für den Scanner, um Tokens korrekt zu erkennen.
Wichtige Methoden
moreAvailable(): Gibt true zurück, wenn noch Zeichen vorhanden sind.peekNextChar(): Gibt das nächste Zeichen zurück, ohne es zu entfernen.peekAheadChar(int ahead): Gibt das Zeichen an Positionaheadzurück (0 = nächstes).getNextChar(): Gibt das nächste Zeichen zurück und entfernt es.close(): Schließt den Stream.
Implementiere eine Klasse FilePeekableCharacterStream, die einen FileInputStream verwendet. Verwende einen Puffer (z.B. ArrayList<Integer>), um die vorausgeschauten Zeichen zu speichern. Achte darauf, dass -1 für das Ende des Streams steht.
public class FilePeekableCharacterStream implements PeekableCharacterStream {
private FileInputStream fis;
private List<Integer> buffer = new ArrayList<>();
private boolean endReached = false;
public FilePeekableCharacterStream(FileInputStream fis) {
this.fis = fis;
}
@Override
public boolean moreAvailable() {
if (!buffer.isEmpty()) return true;
if (endReached) return false;
try {
int next = fis.read();
if (next == -1) {
endReached = true;
return false;
}
buffer.add(next);
return true;
} catch (IOException e) {
return false;
}
}
// ... weitere Methoden
}Schritt 2: Scanner – Tokenisierung nach Regeln
Der Scanner nimmt einen PeekableCharacterStream und eine Liste von Keywords entgegen. Er muss Tokens wie Identifier, IntConstant, FloatConstant, StringConstant, Operator und Invalid erkennen.
Token-Regeln im Überblick
- Identifier: Beginnt mit Unterstrich oder Buchstabe, gefolgt von Buchstaben, Ziffern oder Unterstrich.
- IntConstant: Optional negatives Vorzeichen, dann eine oder mehrere Ziffern.
- FloatConstant: Wie IntConstant, aber mit optionalem Dezimalpunkt und Nachkommastellen.
- StringConstant: In doppelten Anführungszeichen, mit Escape-Sequenzen wie
\noder\". - Operator: Einzelzeichen oder zweistellige Operatoren wie
==,!=,<=. - Invalid: Alles, was nicht in die obigen Kategorien fällt.
Besonderheiten
- Whitespace wird übersprungen.
- Ein negatives Vorzeichen vor einer Zahl wird als Operator behandelt, wenn das vorherige Token ein Identifier oder eine Konstante war.
- Wenn ein Identifier oder eine Konstante direkt von einem Buchstaben oder Unterstrich gefolgt wird, gilt das Token als Invalid.
Ein Beispiel: Der Code int x = -5; wird tokenisiert als: Identifier "int", Identifier "x", Operator "=", Operator "-", IntConstant "5", Operator ";".
Schritt 3: CSVParser – CSV-Dateien parsen
Der CSVParser liest eine CSV-Datei mit einer Header-Zeile und gibt jede Datenzeile als Map<String, String> zurück. Die Header-Spalten müssen eindeutig und nicht leer sein.
CSV-Formatregeln
- Jede Zeile wird durch einen Zeilenumbruch abgeschlossen.
- Spalten werden durch Kommas getrennt.
- Wenn eine Spalte Whitespace (Leerzeichen, Tabs, Zeilenumbrüche) enthält, muss sie in doppelten Anführungszeichen stehen.
- Ein doppeltes Anführungszeichen innerhalb einer solchen Spalte wird durch zwei aufeinanderfolgende Anführungszeichen escaped.
- Leere Spalten oder fehlende Spalten liefern
nullin der Map.
Der CSVParser kann auf dem PeekableCharacterStream aufbauen, den du bereits implementiert hast. Du musst die Zeichen lesen und die Felder entsprechend der Regeln extrahieren.
Häufige Fehler und Tipps
- Vergiss nicht, den Stream zu schließen: In der
close()-Methode solltest du auch den zugrunde liegenden FileInputStream schließen. - Beachte die Token-Reihenfolge: Beim Scanner musst du zuerst nach mehrstelligen Operatoren wie
==suchen, bevor du einzelne Zeichen wie=als Operator erkennst. - Teste mit verschiedenen Dateien: Nutze die Beispiele aus dem Projektverzeichnis auf dem CSIF, um sicherzustellen, dass dein Parser korrekt funktioniert.
Abschluss und Ausblick
Mit diesen Implementierungen hast du die Grundlage für die weiteren Projekte gelegt. Der Scanner und CSVParser werden in späteren Aufgaben für die semantische Analyse und Codegenerierung verwendet. Denke daran, deine Lösung mit make clean zu bereinigen und als .tgz-Archiv abzugeben. Viel Erfolg!