Нет описания
Вы не можете выбрать более 25 тем Темы должны начинаться с буквы или цифры, могут содержать дефисы(-) и должны содержать не более 35 символов.
Joshua Rutschmann 1142a7c6c5 Merge branch 'hw2' of github.com:htwg-syslab-bsys-ws17/bsys-ws17-template into hw2 8 лет назад
..
src hw2/task2 complete, but tests cannot see function *run()* 8 лет назад
tests adding bats file 8 лет назад
Cargo.toml Finished task2 until Restructuring 8 лет назад
README.md fixes https://github.com/htwg-syslab-bsys-ws17/bsys-ws17-template/issues/21 8 лет назад

README.md

Homework hw2 task2

Überblick

In dieser Aufgabe sollen Sie ein Programm entwickeln, welches 2 Parameter übergeben bekommt:

  • 1. übergebene Parameter: Zu suchendes Zeichen
  • 2. übergebene Parameter: String in dem die Zeichenkette gesucht werden soll.

Das Programm gibt die Anzahl gefundener Treffer aus.

You asked me to count all 'e' in "♥ The quick brown fox jumps over the lazy dog. ♥"
Found 3 'e' in "♥ The quick brown fox jumps over the lazy dog. ♥"

Neben einer klaren Struktur für das Programm soll eine kompetente Fehlerbehandlung implementiert werden, sodass das Programm robust gegenüber Eingebefehlern wird. Benutzen Sie keine weiteren Module aus der Standardbibliothek, ausser:

  • std::env
  • std::process

Ziele:

  • Parameter einlesen können
  • Weitere String-Funktionen kennen lernen
  • Fehlerbehandlung

Vorbereitung

Erstellen Sie im task2/ Ordner mittels cargo ein Binary Projekt.

Struktur

Das Programm soll aus 3 Funktionen bestehen:

  • main()
  • run()
  • parse_arguments()

Die main() Funktion ruft zuerst die parse_arguments() auf, um danach geeignet die run() Funktion zum Suchen der Zeichenkette zu starten.

Funktionen

Programmargumente in Rust

Bisher waren in Ihren Programmen die Parameter fest codiert. Wie kann man in Rust die Parameter beim Aufruf des Programms auswerten?

Dazu existiert das std::env Modul, welches Strukturen und Funktionen für die Prozessumgebung (process environment) bereit stellt.

Die Funktion, die die Parameter zur weiteren Auswertung liefert heißt args(). Diese Funktion liefert einen Iterator mit Namen Arc zurück. Was Iteratoren genau sind und was damit alles anzustellen ist werden wir später im Semester kennen lernen. Zunächst kann man sich einen Iterator einfach als eine ‘Datenquelle’ vorstellen, die - wenn wir darauf zugreifen - eine Serie von Werten liefert. In unserem Fall liefert ein Zugriff auf args() die dem Programm übergebenen Parameter als Strings, sofern welche übergeben wurden.

Und damit sind wir auch schon mitten in der Fehlerbehandlung. Auf mögliche Fehler wird in der Dokumentation bereits hingewiesen. Wir werden im weiteren von UNIX Systemen und korrektem unicode ausgehen, sodass uns ein paar Fehlerbehandlungen erspart bleiben.

Um in unserem Programm auf die Parameter zugreifen zu können, werden wir diese in einer geeigneten Datentyp speichern. Es bieten sich Arrays oder Vektoren an.

Warmup Übung

  1. Warum sollten Sie für die Argumente den Datentyp Vector vorziehen?
  2. Schreiben Sie die Funktion:
/// Prints elements of Vec
///
///
/// This function will print all elements of Vec with "args found: <elem>" in
/// each line
///
/// Returns nothing
fn print_arguments(args: &Vec<String>)

Die Funktion gibt den Inhalt des übergeben Vektors zeilenweise aus.

In der Main Funktion lesen Sie die bei Programmstart übergebenen Parameter ein und erstellen einen Vektor aus den eingelesenen Parametern, den Sie als Referenz übergeben.

Die Methode collect() ist Teil des Iterator Traits und bietet sich an um den Vector zu füllen. Überlegen Sie sich auch, warum es sinnvoll ist den Vector an die Funktion print_arguments nur zu leihen (’&‘)?

Wenn Sie nun Ihr Projekt mit cargo run a b cstarten, so erhalten Sie folgende Ausgabe:

    Finished dev [unoptimized + debuginfo] target(s) in 0.0 secs
     Running `target/debug/task1 a b c`
args found: target/debug/task1
args found: a
args found: b
args found: c

Argumente parsen

Unsere Argumente speichern wir am Besten in einer eigenen Datenstruktur Config. Dann sind unsere Parameter als entsprechende Datentypen einfach verfügbar.

/// a struct to hold all of our configuration
#[derive(Debug,PartialEq)]
struct Config{
    search: char,
    line: String,
}

Mit der derive Anweisung lassen wir den Rust Compiler etwas Magie anwenden. Denn durch diese Makro Anweisung erhält unsere Datenstruktur automatisch 2 Traits wodurch wiederum automatisch die nötigen Methoden für uns erzeugt werden um unsere Struct zu printen und deren Inhalt vergleichen zu können (“==” Abfrage). Letzteres brauchen wir für die Tests.

Parsen ohne Fehlerbehandlung

  1. Implementieren Sie die Funktion
/// Parses relevant arguments, returning the filled Config
///
///
/// This function will parse the relevant arguments from the
/// given <Strings>.
/// Returns Config
fn parse_arguments_simple(args: &Vec<String>) -> Config

Sie müssen nun in der Funktion:

  • den 1. String in einen char verwandeln
  • den 2. String der Config Struktur zuweisen

Beachten Sie, das die Strings, die die Argumente repräsentieren in der Funktion geliehen sind und somit jemand anderen gehören. Ihre Config Struktur benötigt somit EIGENE Strings.

In main() geben Sie die geparsten Parameter aus, sodass Sie bei Aufruf Ihres Programms “cargo run a b c” folgende Ausgabe erhalten:

    Finished dev [unoptimized + debuginfo] target(s) in 0.27 secs
     Running `target/debug/task1 a b c`
args found: target/debug/task1
args found: a
args found: b
args found: c
Config { search: 'a', line: "b" }

Parsen mit Fehlerbehandlung

  1. Unter Umständen ruft ein Benutzer Ihr Programm ohne die benötigten Parameter auf. Welche Fehlermeldung erhalten Sie bei dem Aufruf Ihres Programms ohne Parameter und warum?

  2. Ein Programmabsturz wäre die falsche Antwort darauf. Besser ist beim Auftreten von Eingabefehler ein entsprechender Hinweis, wie das Programm zu verwenden ist. Implementieren Sie die Funktion

/// Parses relevant arguments, returning the filled Config in Result
///
///
/// This function will parse the relevant arguments from the
/// given <Strings>.
/// Returns Config or Error Message in Result
fn parse_arguments(args: &Vec<String>) -> Result<Config, xx >

Implementieren Sie die folgende Fehlerbehandlung:

  • Werden vom Benutzer zuwenig übergeben, so gibt die Funktion den Fehlerstring “not enough arguments” zurück. Werden vom Benutzer zu viele Parameter übergeben, so wertet die Funktion nur die ersten beiden aus. In der API der Funktion oben ist als Typ xx für den ‘String’ angedeutet und muss entsprechend von Ihnen mit dem richtigen Typen versehen werden.

  • Tritt ein Fehler beim Wandeln des 1. Parameter in einen char auf, so geben Sie den String “char mismatch” als Fehlerstring in Result zurück. Wird statt eines chars als 1. Parameter ein String übergeben, so werten Sie nur den ersten Buchstaben davon aus.

Die main() Funktion kann keine Return Werte zurückliefern. Um aber der aufrufenden Shell den Abbruch des Programms mitteilen zu können steht die Funktion process::exit() aus der Standardbibliothek zu Verfügung. Durch die Rückgabe von ‘1’ signalisieren Sie der Shell, dass das Programm abgebrochen wurde.

Zeichen im String suchen

In Ihrer vorherigen Homework haben Sie bereits eine ähnliche Funktionalität implementiert, die Sie nun in der run() Funktion übernehmen können.

Ablauf des Programms

Zur Abgabe des Programms soll die main() Funktion nur:

  • parse_arguments() und
  • run()

benutzen. In diesen beiden Funktionen darf kein unwrap() mehr verwendet werden.

Da die Tests jedoch auch die Funktion parse_arguments_simple() benutzt, darf diese nicht auskommentiert werden.

Dead Code

Um die Compiler Warnungen vor nicht benutzen Funktionen auszuschalten, fügen Sie direkt in der Zeile vor der Funktion folgende Anweisung hinzu

#[allow(dead_code)]
fn parse_arguments_simple(args: &Vec<String>) -> Config {
...

Restructuring

Bisher ist alles in unserm src/main.rs Modul, wodurch dieses im Laufe der Zeit immer unübersichtlicher werden würde. Ziel ist es nun, das meiste aus der main.rs auszulagern, sodass nur noch die main() Funktion in main.rs steht.

Ausgelagert wird der Code in eine Bibliothek, genauer gesagt in die Datei src/lib.rs. Um den Code aus lib.rs in main.rs verwenden zu können, müssen Sie ihre derzeitige ‘Create’ mit den entsprechenden ‘extern’ und ‘use’ Anweisungen in main.rs, erreichbar machen. Ergänzen Sie dazu in der src/main.rs:

extern crate task2;
use task2::Config;

Alle use Anweisungen stehen in der main.rs. Es müssen keine weiteren use Anweisungen in der lib.rs stehen

Parsen der Config als Methode

Die parse_arguments() Methode steht noch in keinem Zusammenhang mit der Struktur, die die Methode mit Daten füllt.

Schreiben Sie einen Konstruktor new() für die Datenstruktur Config und ersetzten Sie damit die parse_arguments() Funktion. Verwenden Sie nun Config geeignet in Ihrer main() Funktion.

Tests

Die Test sind ausgelagert in die Datei tests/task2.rs. Der Aufruf von cargo test sucht nach Unit Tests in der src/main.rs und im Verzeichnis tests/. Die Tests in tests/task2.rs funktionieren erst, wenn eine src/lib.rs existiert (siehe Aufgabenstellung Restructuring oben).

Dokumentation

Dokumentieren Sie die Funktionen in Ihrer lib.rs, sodass cargo doc eine aussagekräftige Dokumentation erstellt.

Da Sie im src/Verzeichnis eine main.rs und eine lib.rs vorliegen haben, beschwert sich cargo doc, dass es nicht weiss wie es die Dokumentation erstellen soll:

error: cannot document a package where a library and a binary have the same name. Consider renaming one or marking the target as `doc = false`

Damit cargo doc Ihre Dokumentation für die Funktionen in lib.rs erstellt, benötigen Sie folgende Erweiterung in Ihrer Cargo.toml Datei.

[[bin]]
doc = false
name = "task2"

Damit weiß cargo, dass es die Dokumentation aus lib.rs erstellen soll und nicht aus main.rs.