Rename sheet folders to assure proper sorting

This commit is contained in:
Lukas Kalbertodt
2017-02-16 15:12:56 +01:00
parent aaf0332117
commit b3664c6ffd
74 changed files with 0 additions and 0 deletions

10
aufgaben/sheet04/README.md Executable file
View File

@ -0,0 +1,10 @@
Blatt 4
=======
Wie immer soll dieses Blatt in einem eigenen Branch, `sheet4`, bearbeitet
werden.
Eine weitere generelle Aufgabe: Euer erster Commit soll *nur* die Dateien
beinhalten, die euch schon (in diesem Repository) gegeben werden. So fällt es
den Tutoren und mir deutlich leichter, den vorgegebenen Teil und eure eigenen
Änderungen zu unterscheiden!

57
aufgaben/sheet04/sol1/good.rs Executable file
View File

@ -0,0 +1,57 @@
/// Prints all happy primes between 1 and 20.
fn main() {
for i in 1..21 {
if is_happy_prime(i) {
println!("{} is a happy prime!", i);
}
}
}
/// Determines whether the given number is a happy number AND a prime number.
fn is_happy_prime(n: u64) -> bool {
is_happy(n) && is_prime(n)
}
/// Determines whether the given, positive number is a [happy number][1].
///
/// [1]: https://en.wikipedia.org/wiki/Happy_number
fn is_happy(mut number: u64) -> bool {
// Either we end as "1" or in a cycle
while number > 1 {
number = {
// Here we compute the sum of squares of all digits. The trick is that
// the last digit is `number % 10` and that we can remove the last
// digit by dividing number by 10.
let mut sum = 0;
while number > 0 {
let digit = number % 10;
sum += digit * digit;
number /= 10;
}
sum
};
// We ended up in a cycle -> not happy
if number == 4 {
return false;
}
}
true
}
/// Determines whether the given, positive, non-zero number is a prime number.
fn is_prime(n: u64) -> bool {
if n == 1 {
return false;
}
for divisor in 2..n {
if n % divisor == 0 {
return false;
}
}
true
}

View File

@ -0,0 +1,450 @@
//! A simple command-line calculator
//!
/// The integer type we use internally to hold the numbers.
type Integer = i64;
/// Represents an operation our calculator can carry out.
#[derive(Debug, Clone, Copy, PartialEq)]
enum Op {
Add,
Subtract,
}
impl Op {
/// The operation is applied to the given `lhs` and `rhs`. If an overflow
/// occured during the operation, None is returned.
fn apply(&self, lhs: Integer, rhs: Integer) -> Option<Integer> {
match *self {
Op::Add => lhs.checked_add(rhs),
Op::Subtract => lhs.checked_sub(rhs),
}
}
}
/// Represents a (part of a) calculation.
#[derive(Debug, PartialEq)]
enum Expr {
/// Just a literal number (no calculation needed)
Literal(Integer),
/// An operation with two operands
Op {
/// Kind of operation
operator: Op,
/// The Operands.
///
/// The task said the operands should be saved as `Vec<Expr>`
/// and it would be fine to hand in such a solution, but a vector
/// is actually the wrong type here. We know that we always have
/// exactly two operands, so a type that can hold any number of items
/// is not correct.
///
/// The correct type is a pair or an array with two elements. But this
/// will lead to a problem: fields are saved inside of the type and
/// therefore it's not possible to have a recursive definition. The
/// only way to do it, is to introduce one layer of indirection. A
/// vector is such a layer, because it saves its contents on the heap.
///
/// A better choice is `Box<T>`. As a vector, it stores its content
/// on the heap and also owns the content. But unlike the vector, a box
/// always saves one element only.
operands: Box<[Expr; 2]>,
}
}
/// Stuff that can go wrong during parsing. Of course, it's desirable to have
/// even more information about an error, but this is not in the scope of this
/// task. Apart from that, good error reporting is hard. That's why so many
/// compiler (especially a few years ago) suck at it.
#[derive(Debug, Clone, Copy)]
enum ParseError {
/// Empty input
Empty,
/// A token that was not expected at that position in the input
UnexpectedToken(Token),
/// More tokens were expected
UnexpectedEof,
/// There are unmatched parenthesis
UnmatchedParens,
/// More tokens in the input found, although none were expected
UnexpectedAdditionalTokens,
}
impl Expr {
/// This function parses a list of tokens and returns the corresponding
/// expression tree, if the parse was successful. The grammar for our
/// expressions is:
///
/// ```
/// expr := ⟨operand⟩ [⟨op⟩ ⟨operand⟩]
/// operand := ⟨num⟩ | "(" ⟨expr⟩ ")"
/// op := "+" | "-"
/// num := "0" | "1" | ... | "9"
/// ```
///
/// Parsing is difficult. Parsing mathematical infix notation like this can
/// be done with the Shunting-yard algorithm [1]. But more general parsing
/// algorithms to parse determinstic context-free grammars can be used,
/// too. Recursive descent parsers are probably rather similar to the way
/// humans think about a grammar.
///
/// This implementation does not implement a concrete algorithm (as far as
/// I know).
///
/// [1]: https://en.wikipedia.org/wiki/Shunting-yard_algorithm
fn parse(tokens: &[Token]) -> Result<Self, ParseError> {
/// Parses an operand:
///
/// ```
/// operand := ⟨num⟩ | "(" ⟨expr⟩ ")"
/// ```
///
/// Note that it's usually nicer to build subslices of a slice instead
/// of passing indices manually. But in this case it makes sense to
/// work with "global" indices, because this function also returns an
/// index into the slice.
fn parse_operand(tokens: &[Token], start: usize)
-> Result<(Expr, usize), ParseError>
{
// At this point, we expect an operand, so there need to be some
// tokens left
if start >= tokens.len() {
return Err(ParseError::UnexpectedEof);
}
match tokens[start] {
// The operand is just a literal number
Token::Number(n) => Ok((Expr::Literal(n), start + 1)),
// The operand is some operation
Token::ParenOpen => {
match parse_expr(tokens, start + 1) {
Err(e) => Err(e),
Ok((expr, after_expr)) => {
// We expect the closing paren after the parsed
// expression
if tokens.get(after_expr) != Some(&Token::ParenClose) {
return Err(ParseError::UnmatchedParens);
}
Ok((expr, after_expr + 1))
}
}
}
// An operand starts either with a number or an opening paren
tok => Err(ParseError::UnexpectedToken(tok)),
}
}
/// Parses an expression:
///
/// ```
/// expr := ⟨operand⟩ [⟨op⟩ ⟨operand⟩]
/// ```
///
fn parse_expr(tokens: &[Token], start: usize)
-> Result<(Expr, usize), ParseError>
{
// First: parse the left hand side (lhs).
let (lhs, after_lhs) = match parse_operand(tokens, start) {
Err(e) => return Err(e),
Ok(tuple) => tuple,
};
// If the lhs consumed all of our tokens, the LHS is the only
// expression -> return it. Otherwise we expect an operator.
let op = match tokens.get(after_lhs) {
Some(&Token::Plus) => Op::Add,
Some(&Token::Minus) => Op::Subtract,
_ => return Ok((lhs, after_lhs)),
};
// Parse the rhs.
let (rhs, after_rhs) = match parse_operand(tokens, after_lhs + 1) {
Err(e) => return Err(e),
Ok(tuple) => tuple,
};
Ok((
Expr::Op {
operator: op,
operands: Box::new([lhs, rhs]),
},
after_rhs,
))
}
// Check for special case: no tokens.
if tokens.is_empty() {
return Err(ParseError::Empty);
}
// Start parsing the token stream as ⟨expr⟩
let (expr, after_expr) = match parse_expr(&tokens, 0){
Err(e) => return Err(e),
Ok(t) => t,
};
// Check if tokens are left (shouldn't happen) and return an error
// if that is the case.
if after_expr < tokens.len() {
return Err(ParseError::UnexpectedAdditionalTokens);
}
Ok(expr)
}
/// Evaluates the expression tree to calculate the final result. If an
/// overflow occured, None is returned.
fn evaluate(&self) -> Option<Integer> {
match *self {
Expr::Literal(value) => Some(value),
Expr::Op { operator, ref operands } => {
let lhs = match operands[0].evaluate() {
None => return None,
Some(lhs) => lhs,
};
let rhs = match operands[1].evaluate() {
None => return None,
Some(rhs) => rhs,
};
operator.apply(lhs, rhs)
}
}
}
}
/// A token in the input stream.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum Token {
Plus,
Minus,
ParenOpen,
ParenClose,
Number(Integer),
}
#[derive(Debug, Clone, Copy)]
enum LexError {
InvalidChar(char),
Overflow,
}
/// Tokenizes the string and returns a list of tokens, if the input is valid.
fn tokenize(input: &str) -> Result<Vec<Token>, LexError> {
let mut out = Vec::new();
let mut current_number = String::new();
for c in input.chars() {
// Check if we were reading a number and reached the end of it
if !current_number.is_empty() && !(c >= '0' && c <= '9') {
let num: Integer = match current_number.parse() {
// The string contains valid digits only, so "overflow" is the
// only reason `parse()` would error.
Err(_) => return Err(LexError::Overflow),
Ok(v) => v,
};
out.push(Token::Number(num));
current_number.clear();
}
let token = match c {
'+' => Token::Plus,
'-' => Token::Minus,
'(' => Token::ParenOpen,
')' => Token::ParenClose,
c @ '0' ... '9' => {
// We do not yet generate a token, but only push the char on
// the number buffer
current_number.push(c);
continue;
},
c if c.is_whitespace() => continue,
c => return Err(LexError::InvalidChar(c)),
};
out.push(token);
}
// If the last token is a number, we still have to push it
if !current_number.is_empty() {
let num: Integer = match current_number.parse() {
// The string contains valid digits only, so "overflow" is the
// only reason `parse()` would error.
Err(_) => return Err(LexError::Overflow),
Ok(v) => v,
};
out.push(Token::Number(num));
}
Ok(out)
}
fn main() {
loop {
// Read input from the user and just do nothing when the input is empty
let input = read_string();
if input.is_empty() {
continue;
}
// Try to tokenize the input. If it fails, print error message and
// start anew.
let tokens = match tokenize(&input) {
Err(e) => {
println!("{:?}", e);
continue;
}
Ok(tokens) => tokens,
};
// Debug output
// println!("{:?}", tokens);
// Try to parse the tokenstream into an expression. If it fails,
// print the error and start anew.
let expr = match Expr::parse(&tokens) {
Err(e) => {
println!("error: {:?}", e);
continue;
}
Ok(expr) => expr,
};
// Debug output
// println!("{:?}", expr);
// Evaluate the expression and print the result
match expr.evaluate() {
None => println!("Overflow occured!"),
Some(res) => println!("{}", res),
}
}
}
/// Reads a string from the user (with a nice prompt).
fn read_string() -> String {
use std::io::Write;
// Print prompt
print!("calc > ");
std::io::stdout().flush().unwrap();
// Read line
let mut buffer = String::new();
std::io::stdin()
.read_line(&mut buffer)
.expect("something went horribly wrong...");
// Discard trailing newline
let new_len = buffer.trim_right().len();
buffer.truncate(new_len);
buffer
}
#[test]
fn parser_valid() {
use Token::*;
// "3"
assert_eq!(
Expr::parse(&[Number(3)]).unwrap(),
Expr::Literal(3)
);
// "(3)"
assert_eq!(
Expr::parse(&[ParenOpen, Number(3), ParenClose]).unwrap(),
Expr::Literal(3)
);
// "3 + 4"
assert_eq!(
Expr::parse(&[Number(3), Plus, Number(4)]).unwrap(),
Expr::Op {
operator: Op::Add,
operands: Box::new([
Expr::Literal(3),
Expr::Literal(4),
]),
}
);
// "(3 + 4)"
assert_eq!(
Expr::parse(&[ParenOpen, Number(3), Plus, Number(4), ParenClose]).unwrap(),
Expr::Op {
operator: Op::Add,
operands: Box::new([
Expr::Literal(3),
Expr::Literal(4),
]),
}
);
// "(3) + 4"
assert_eq!(
Expr::parse(&[ParenOpen, Number(3), ParenClose, Plus, Number(4)]).unwrap(),
Expr::Op {
operator: Op::Add,
operands: Box::new([
Expr::Literal(3),
Expr::Literal(4),
]),
}
);
// "(3 - 7) + 4"
assert_eq!(
Expr::parse(&[
ParenOpen, Number(3), Minus, Number(7), ParenClose, Plus, Number(4)
]).unwrap(),
Expr::Op {
operator: Op::Add,
operands: Box::new([
Expr::Op {
operator: Op::Subtract,
operands: Box::new([
Expr::Literal(3),
Expr::Literal(7),
]),
},
Expr::Literal(4),
]),
}
);
}
#[test]
fn parser_invalid() {
use Token::*;
// ""
assert!(Expr::parse(&[]).is_err());
// "-"
assert!(Expr::parse(&[Minus]).is_err());
// "1 +"
assert!(Expr::parse(&[Number(1), Plus]).is_err());
// "1 + 2 + 3"
assert!(Expr::parse(&[Number(1), Plus, Number(2), Plus, Number(3)]).is_err());
// "(1"
assert!(Expr::parse(&[ParenOpen, Number(1)]).is_err());
// "1)"
assert!(Expr::parse(&[Number(1), ParenClose]).is_err());
}

View File

@ -0,0 +1,15 @@
Aufgabe 4.1: Programm schöner machen
====================================
In dieser Aufgabe dürft *ihr* mal die Aufgabe der Tutoren übernehmen. Gegeben
ist ein Programm, das funktioniert und das tut, was es soll. Es gibt alle
Zahlen zwischen 1 und 20, die sowohl eine Primzahl als auch eine ["fröhliche"
Zahl](https://en.wikipedia.org/wiki/Happy_number) sind, aus.
Jedoch ist der Quellcode alles andere als hübsch. Eure Aufgabe ist es, den
Quellcode zu verbessern. D.h., ihr sollt die Funktionsweise des Programms gar
nicht verändern, sondern nur den Quellcode.
Versucht, den Code möglichst kurz, gut lesbar und idiomatisch zu gestalten.
So viel sei gesagt: Es gibt *sehr viele* Stellen, an denen etwas verbessert
werden kann! Versucht möglichst, alle dieser Stellen zu finden.

69
aufgaben/sheet04/task1/bad.rs Executable file
View File

@ -0,0 +1,69 @@
// print if they are both
fn main() -> () {
for iterationnumber in &[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20] {
let iterationnumber = *iterationnumber;
if !happy_prime(iterationnumber) {} else {
print!("{}", iterationnumber);
println!(" is a happy prime!");
}
}
}
// is it botH?
fn happy_prime(n: i32) -> bool {
match check_if_number_is_happy(n) {
false => return false,
_ => {}
}
if check_if_number_is_prime(n) == true {
return true;
} else {
return false;
}
}
// Is it a happy number? https://en.wikipedia.org/wiki/Happy_number
fn check_if_number_is_happy(number: i32) -> bool {
let mut number: i32= number;
while number > 1 {
let mut tmp = 0;
while number > 0 {
tmp = tmp + (number %10) * (number%10);
number = number / 10;
}
number = tmp;
// We ended up in a cycle -> not happy
if (number == 4) {
return false;
}
}
return true;
}
// is it priem?
fn check_if_number_is_prime(n: i32) -> bool {
if n == 1 {
return false;
}
if n == 2 {
return false;
}
let mut teilerGefunden:bool = false;
let mut teiler:i32= 2;
while (teiler < n) {
if (n % teiler == 0) {
teilerGefunden = true;
}
teiler = teiler + 1;
}
return !teilerGefunden;
}

219
aufgaben/sheet04/task2/README.md Executable file
View File

@ -0,0 +1,219 @@
Aufgaben 4.2: Taschenrechner
============================
In dieser Aufgabe sollt ihr ein Programm schreiben, welches einfache
Rechenaufgaben lösen kann.
Zwar kann man sich einen sehr einfachen Rechner schnell manuell
zusammenschreiben.
Das Programm dieser Aufgabe soll jedoch nicht quick'n'dirty geschrieben werden,
sondern später einfach zu erweitern sein.
Ein Teil des Codes ist bereits in `calculator.rs` vorgegeben.
Das gegebene Programm liest in einer Endlosschleife Eingaben vom Nutzer ein,
tut damit aber bislang noch nichts, außer sie auszugeben.
Das Programm kann man bekanntlich mit Strg+C beenden.
Das Programm soll später ungefähr folgende Aufgaben lösen können:
```
calc > 3 + 3
6
calc > 2-3
-1
calc > 9 + (7 - 2)
14
calc > 2
2
calc > (3 - 2) + ((3 - 2) + 9)
11
```
a) Tokenization
---------------
Die ersten beiden Schritte fast aller Compiler jeglicher Art sind:
*Tokenization* (auch *Lexing*) und *Parsing*.
Gerade Compiler haben einen extrem komplizierten und komplexen Job.
Um dieser Komplexität Herr zu werden, werden eine Reihe von unterschiedlichen
Abstraktionsstufen genutzt.
Der Quellcode wird permanent von einer Darstellung in die nächste überführt;
Analysen, wie Typechecking ("werden alle Typen korrekt benutzt?"), werden nie
direkt auf dem Quellcode-String, sondern auf abstrakteren Darstellungen
ausgeführt.
Glücklicherweise müsst ihr keinen Compiler schreiben, sondern nur einen
Taschenrechner.
Aber auch dabei müssen Nutzereingaben mit einer bestimmten Syntax analysiert
werden.
Daher werden wir die Funktionalität des Rechners ebenfalls in drei Schritte
unterteilen:
1. Tokenization/Lexing
2. Parsing
3. Errechnen des Ergebnisses
Im ersten Schritt wird der Eingabestring in eine Liste von sog. *Tokens*
umgewandelt. Ein Token kann ein Zeichen repräsentieren (wie z.B. `"+"`) oder
auch mehrere (wie z.B. `"127"`).
Anstatt also eine Liste von Zeichen zu betrachten, betrachtet man eine Liste
von atomaren Elementen im Code.
Um mal ein Beispiel zu geben:
```
Eingabe: "23 + 7"
| | |
Tokens: [Number(23), Plus, Number(7)]
```
Eure erste Aufgabe ist es, einen Typen `Token `zu definieren, der einen Token
darstellt.
Der Tokenization-Schritt produziert nämlich dann `Vec<Token>`.
Wie oben schon gezeigt, muss der Rechner in der Lage sein, Addition und
Subtraktion von beliebigen positiven Ganzzahlen auszuführen (das Ergebnis kann
negativ sein, nur die einzelnen Zahlen in der Eingabe nicht).
Außerdem müssen Klammerausdrücke behandelt werden.
Entwerft entsprechend den `Token` Typ.
---
Nachdem der Typ definiert ist, müsst ihr jetzt eine Funktion `tokenize()`
schreiben.
Diese Funktion bekommt einen String und erzeugt daraus eine Liste von Tokens.
Natürlich können schon bei der Tokenization Fehler im Input auffallen.
Diese Fehler sollten nicht einfach ignoriert werden.
Die Funktion soll außerdem Whitespaces in der Eingabe komplett ignorieren, dafür
also keinen Token erstellen (das würde in späteren Schritten nur ablenken).
*Wichtig*: Ihr müsst lediglich mit Zahlen umgehen können, die nur
*eine* Ziffer besitzen.
Zahlen bestehend aus mehreren Ziffern zu lexen, ist -- selbst mit Hilfe von
der `parse()` Funktion aus der Standardbibliothek -- nicht ganz trivial.
Wer das trotzdem schafft, wird natürlich vom Tutor gelobt ;-)
Schließlich soll die Nutzereingabe an `tokenize()` gegeben werden und das
Ergebnis zu Debugzwecken in `main()` ausgegeben werden.
Folgende Eingaben können getestet werden:
- `3`
- `3 + 4`
- `3 - (7 - 7)`
- `3 + ) - (` -> (in diesem Schritt führt das *noch nicht* zum Fehler!)
- `peter` -> Hier sollte ein Fehler ausgegeben werden (nicht panic!)
**Tipps**:
- Es lohnt sich, die Dokumentation von
[`char`](https://doc.rust-lang.org/std/primitive.char.html) durchzulesen
- Um Instanzen selbstdefinierter Typen miteinander vergleichen zu können, kann
man `#[derive(PartialEq)]` über die Typdefinition schreiben. Dann kann der
Typ per `==` und `!=` verglichen werden.
b) Definition des Parsetrees
----------------------------
Bisher haben wir aus einem String einen Tokenstream gemacht.
Im Parsing-Schritt wird aus dem Tokenstream ein sog. Parsetree erzeugt.
Doch bevor wir uns an das Parsing wagen, müssen wir erstmal Typen definieren,
um den Parsetree darzustellen.
Hier ist mal ein Beispiel eines Parsetrees zu der Eingabe `3 + (6 - 1)` zu
sehen:
```
+---------+
| Summe |
+---------+
/ \
/ \
+-----+ +-----------+
| 3 | | Differenz |
+-----+ +-----------+
/ \
/ \
+-----+ +-----+
| 6 | | 1 |
+-----+ +-----+
```
Wir sehen also:
- Blätter des Baumes stellen die Literale/eingegebenen Zahlen dar
- Innere Knoten des Baumes wissen, welche Art von Rechenoperation sie
darstellen und welche Kinder sie besitzen
Erstellt zunächst einen Typ `Op`, welcher eine Rechenoperation darstellt.
Dieser Typ soll dann eine Methode `apply()` bekommen.
Diese Methode bekommt zwei Zahlen und gibt das Ergebnis der Operation
angewendet auf diese beiden Zahlen zurück.
Danach soll der Typ `Expr` erstellt werden, der letztendlich den Parsetree
darstellt.
*Tipp*: Auch wenn wir immer nur zwei Kinder pro Knoten erwarten, könnt ihr hier
gerne `Vec<_>` benutzen!
Dieser Typ soll eine Methode `evaluate()` bekommen, welche den ganzen Baum
auswertet (also das Ergebnis ausrechnet).
Die Methode würde also auf dem oben gezeigten Baum "8" zurückgeben.
Das ist also Schritt 3 aus der oben genannten Liste von Schritten.
Baut euch in einer kleinen Hilfsfunktion manuell eine Instanz (z.B. den oben
gezeigten Baum) von `Expr` zusammen und testet `evaluate()` auf dieser Instanz.
Ruft diese Hilfsfunktion in `main()` auf, um das Ergebnis zu prüfen.
Löscht diese Hilfsfunktion auch nicht, sodass euer Tutor sie später nutzen
kann.
c) Parsing
----------
Dieser zweite Schritt ist der komplizierteste.
Ihr müsst diese Teilaufgabe nicht komplett lösen, da die komplette Lösung
nicht gerade trivial ist.
Mindestanforderung ist jedoch, dass ihr die folgenden Eingaben parsen könnt:
- `3`
- `1 + 2`
- `9 - 6`
Das Parsing soll in einer Konstruktorfunktion von `Expr` geschehen.
Nennen wir diese Funktion einfach `parse()`.
Die Funktion bekommt natürlich den vorher generierten Tokenstream und soll
bei Erfolg eine `Expr`-Instanz zurückgeben, welche den Parsetree darstellt.
Aber natürlich kann das Parsing auch auf Fehler in der Eingabe stoßen.
Zum Beispiel sollten Fehler wie eine leere Eingabe und eine falsche Syntax
gemeldet werden.
Ein Hinweis zur Syntax: Wir gehen davon aus, dass jeder Knoten im Baum nur zwei
Kinder hat und Rechnungen immer geklammert sind.
Eine Eingabe von `3 + 4 + 5` ist also ungültig und müsste als `3 + (4 + 5)`
oder `(3 + 4) + 5` eingegeben werden.
Das vereinfacht das Parsing stark.
Unsere Grammatik ist also:
```
expr := ⟨operand⟩ [⟨op⟩ ⟨operand⟩]
operand := ⟨num⟩ | "(" ⟨expr⟩ ")"
op := "+" | "-"
num := "0" | "1" | ... | "9"
```
*Tipps*:
- Wenn ihr versucht, den kompletten Parser zu implementieren, bietet
sich natürlich eine Art von Rekursion an.
- Es gibt recht viele Wege, diesen Parser zu implementieren. Ein Weg, den ich
recht bequem finde: Man speichert sich den aktuellen Index im Tokenstream
und erhöht diesen immer, wenn man weitere Tokens verarbeitet hat.
- Kleine Hilfsfunktionen können doppelten Code vermeiden.
---
Zuletzt (unabhängig davon, ob ihr nur den halben oder ganzen Parser
implementiert habt) soll alles in `main()` benutzt werden, um den
Taschenrechner funktionsfähig zu machen.

View File

@ -0,0 +1,35 @@
fn main() {
loop {
// Read input from the user and just do nothing when the input is empty
let input = read_string();
if input.is_empty() {
continue;
}
// Debug output
println!("{}", input);
}
}
/// Reads a string from the user (with a nice prompt).
fn read_string() -> String {
use std::io::Write;
// Print prompt
print!("calc > ");
std::io::stdout().flush().unwrap();
// Read line
let mut buffer = String::new();
std::io::stdin()
.read_line(&mut buffer)
.expect("something went horribly wrong...");
// Discard trailing newline
let new_len = buffer.trim_right().len();
buffer.truncate(new_len);
buffer
}