Building a Command-Line Calculator

Learn how to create a simple calculator application using ShitRust's basic features.

Introduction

In this tutorial, we'll build a simple command-line calculator that can perform basic arithmetic operations. This project will demonstrate:

  • Reading user input
  • String manipulation and parsing
  • Basic error handling with the result type
  • Control flow with match expressions
  • Basic arithmetic operations

By the end of this tutorial, you'll have a fully functional calculator program that can add, subtract, multiply, and divide numbers.

Setup

First, make sure you have ShitRust installed on your system. If not, follow the installation instructions.

Create a new file called calculator.sr. We'll start by importing the standard I/O library to handle user input and output:

// Import standard input/output library
import { stdin, stdout } from "std/io";

Basic Structure

Let's start by defining a function to display welcome messages and instructions to the user:

// Function to display welcome message and instructions
fn show_welcome() -> void {
    println("===== ShitRust Calculator =====");
    println("Enter an expression with two numbers and an operator.");
    println("Example: 5 + 3");
    println("Supported operators: +, -, *, /");
    println("Type 'exit' to quit.");
    println("==============================");
}

This function uses the println function to display text to the console. The -> void return type indicates that this function doesn't return a value.

Input Parsing

Next, we'll create a function to parse the user's input. This function will:

  1. Split the input string into parts
  2. Extract the first number, operator, and second number
  3. Return a result type with either the parsed values or an error message
// Function to parse user input and extract operation components
fn parse_input(input: string) -> result<(float, string, float), string> {
    // Trim input and split by spaces
    let trimmed = input.trim();
    let parts = trimmed.split(" ");
    
    if parts.length() != 3 {
        return err("Invalid input format. Please use: number operator number");
    }
    
    // Parse the first number
    let first_num = parts[0].parse_float();
    if first_num.is_err() {
        return err("Invalid first number");
    }
    
    // Get the operator
    let operator = parts[1];
    if operator != "+" && operator != "-" && operator != "*" && operator != "/" {
        return err("Invalid operator. Use +, -, *, or /");
    }
    
    // Parse the second number
    let second_num = parts[2].parse_float();
    if second_num.is_err() {
        return err("Invalid second number");
    }
    
    return ok((first_num.unwrap(), operator, second_num.unwrap()));
}

This function returns a result type, which is ShitRust's way of handling operations that might fail. The result contains either:

  • An ok value with a tuple containing the two numbers and the operator
  • An err value with an error message

Calculation Logic

Now we'll implement the function that performs the actual calculation:

// Function to perform the calculation
fn calculate(a: float, op: string, b: float) -> result {
    match op {
        "+" => return ok(a + b),
        "-" => return ok(a - b),
        "*" => return ok(a * b),
        "/" => {
            if b == 0.0 {
                return err("Division by zero is not allowed");
            }
            return ok(a / b);
        },
        _ => return err("Unsupported operator")
    }
}

This function uses pattern matching with a match expression to handle different operators. It also returns a result type, allowing us to handle errors like division by zero.

Main Program Loop

Finally, let's implement the main function that ties everything together:

// Main function
fn main() -> void {
    show_welcome();
    
    // Main program loop
    let mut running = true;
    while (running) {
        // Prompt for input
        print("> ");
        let input = stdin.read_line();
        
        // Check for exit command
        if input.trim().to_lowercase() == "exit" {
            println("Goodbye!");
            running = false;
            continue;
        }
        
        // Parse input and calculate
        let parse_result = parse_input(input);
        
        match parse_result {
            ok((a, op, b)) => {
                let calc_result = calculate(a, op, b);
                
                match calc_result {
                    ok(result) => {
                        println("Result: " + result.to_string());
                    },
                    err(error_msg) => {
                        println("Error: " + error_msg);
                    }
                }
            },
            err(error_msg) => {
                println("Error: " + error_msg);
            }
        }
        
        println(""); // Empty line for better readability
    }
}

The main function:

  1. Displays the welcome message
  2. Enters a loop that continues until the user types "exit"
  3. Reads user input, parses it, and performs the calculation
  4. Handles errors at each step and displays appropriate messages

Complete Code

Here's the complete code for our calculator program:

// ShitRust Command-Line Calculator
// This program demonstrates basic input/output and arithmetic operations

// Import standard input/output library
import { stdin, stdout } from "std/io";

// Function to display welcome message and instructions
fn show_welcome() -> void {
    println("===== ShitRust Calculator =====");
    println("Enter an expression with two numbers and an operator.");
    println("Example: 5 + 3");
    println("Supported operators: +, -, *, /");
    println("Type 'exit' to quit.");
    println("==============================");
}

// Function to parse user input and extract operation components
fn parse_input(input: string) -> result<(float, string, float), string> {
    // Trim input and split by spaces
    let trimmed = input.trim();
    let parts = trimmed.split(" ");
    
    if parts.length() != 3 {
        return err("Invalid input format. Please use: number operator number");
    }
    
    // Parse the first number
    let first_num = parts[0].parse_float();
    if first_num.is_err() {
        return err("Invalid first number");
    }
    
    // Get the operator
    let operator = parts[1];
    if operator != "+" && operator != "-" && operator != "*" && operator != "/" {
        return err("Invalid operator. Use +, -, *, or /");
    }
    
    // Parse the second number
    let second_num = parts[2].parse_float();
    if second_num.is_err() {
        return err("Invalid second number");
    }
    
    return ok((first_num.unwrap(), operator, second_num.unwrap()));
}

// Function to perform the calculation
fn calculate(a: float, op: string, b: float) -> result {
    match op {
        "+" => return ok(a + b),
        "-" => return ok(a - b),
        "*" => return ok(a * b),
        "/" => {
            if b == 0.0 {
                return err("Division by zero is not allowed");
            }
            return ok(a / b);
        },
        _ => return err("Unsupported operator")
    }
}

// Main function
fn main() -> void {
    show_welcome();
    
    // Main program loop
    let mut running = true;
    while (running) {
        // Prompt for input
        print("> ");
        let input = stdin.read_line();
        
        // Check for exit command
        if input.trim().to_lowercase() == "exit" {
            println("Goodbye!");
            running = false;
            continue;
        }
        
        // Parse input and calculate
        let parse_result = parse_input(input);
        
        match parse_result {
            ok((a, op, b)) => {
                let calc_result = calculate(a, op, b);
                
                match calc_result {
                    ok(result) => {
                        println("Result: " + result.to_string());
                    },
                    err(error_msg) => {
                        println("Error: " + error_msg);
                    }
                }
            },
            err(error_msg) => {
                println("Error: " + error_msg);
            }
        }
        
        println(""); // Empty line for better readability
    }
}

Running the Program

To run the calculator program:

  1. Save the code in a file named calculator.sr
  2. Open a terminal and navigate to the directory containing your file
  3. Compile and run the program with the following command:
shitrust run calculator.sr

You should see the welcome message, and you can start entering calculations. For example:

> 5 + 3
Result: 8

> 10 / 2
Result: 5

> 7 * 6
Result: 42

> 10 / 0
Error: Division by zero is not allowed

> exit
Goodbye!

Extensions and Challenges

Now that you've built a basic calculator, here are some ideas to extend and improve it:

  1. Add support for more operations (modulo, exponentiation, etc.)
  2. Implement memory functions (store values, recall them later)
  3. Support more complex expressions with multiple operations
  4. Add a history feature to recall previous calculations
  5. Implement unit conversions (temperature, length, etc.)

These extensions will give you more practice with ShitRust and help you build a more powerful calculator application.

Next Steps

Congratulations on completing your first ShitRust tutorial! You've built a functional calculator application and learned about several key features of the language.

Check out our other tutorials to continue your ShitRust journey:

Or explore the Language Reference to learn more about ShitRust's features.