DEV Community

Cover image for Building a Command-Line Calculator in Zig - Tutorial
augusthottie
augusthottie

Posted on

Building a Command-Line Calculator in Zig - Tutorial

This tutorial will guide you through creating a simple command-line calculator using Zig programming language. We'll cover installation, project setup, implementation, and testing.

Table of Contents

  1. Installing Zig
  2. Project Setup
  3. Understanding the Code
  4. Building and Running
  5. Testing
  6. Common Issues

Installing Zig

  1. Visit ziglang.org/download
  2. Download the appropriate version for your operating system (0.11.0 or later)
  3. Extract the archive to a location of your choice
  4. Add Zig to your system's PATH:

    • Windows: Edit system environment variables and add the path to Zig
    • Linux/MacOS: Add to ~/.bashrc or ~/.zshrc:
     export PATH=$PATH:/path/to/zig
    
  5. Verify installation:

   zig version
Enter fullscreen mode Exit fullscreen mode

Project Setup

  1. Create a new project directory:
   mkdir calculator
   cd calculator
Enter fullscreen mode Exit fullscreen mode
  1. Initialize the project structure:
   mkdir src
   touch src/calculator.zig
   touch src/calculator_test.zig
Enter fullscreen mode Exit fullscreen mode
  1. Create a build.zig file:
   const std = @import("std");

   pub fn build(b: *std.Build) void {
       const target = b.standardTargetOptions(.{});
       const optimize = b.standardOptimizeOption(.{});

       const exe = b.addExecutable(.{
           .name = "calculator",
           .root_source_file = b.path("src/calculator.zig"),
           .target = target,
           .optimize = optimize,
       });

       b.installArtifact(exe);

       const run_cmd = b.addRunArtifact(exe);
       run_cmd.step.dependOn(b.getInstallStep());

       const run_step = b.step("run", "Run the app");
       run_step.dependOn(&run_cmd.step);

       const exe_unit_tests = b.addTest(.{
           .root_source_file = b.path("src/calculator_test.zig"),
           .target = target,
           .optimize = optimize,
       });

       const run_exe_unit_tests = b.addRunArtifact(exe_unit_tests);
       const test_step = b.step("test", "Run unit tests");
       test_step.dependOn(&run_exe_unit_tests.step);
   }
Enter fullscreen mode Exit fullscreen mode

Understanding the Code

1. Calculator Implementation (src/calculator.zig)

The calculator consists of two main parts:

a) The calculate function:

pub fn calculate(num1: f64, num2: f64, op: u8) !f64 {
    return switch (op) {
        '+' => num1 + num2,
        '-' => num1 - num2,
        '*' => num1 * num2,
        '/' => if (num2 == 0) {
            return error.DivisionByZero;
        } else num1 / num2,
        '%' => @mod(num1, num2),
        else => error.InvalidOperation,
    };
}
Enter fullscreen mode Exit fullscreen mode

This function:

  • Takes two numbers and an operation as input
  • Uses a switch statement to perform the calculation
  • Handles errors like division by zero
  • Returns the result as a f64 (double-precision float)
pub fn main() !void {
    const stdout = std.io.getStdOut().writer();
    const stdin = std.io.getStdIn().reader();

    while (true) {
        try stdout.writeAll("\nCalculator (enter 'q' to quit)\n");
        try stdout.writeAll("Enter first number: ");

        var first_input_buffer: [16]u8 = undefined;
        const first_input = try stdin.readUntilDelimiter(&first_input_buffer, '\n');
        if (first_input.len == 1 and first_input[0] == 'q') break;

        const num1 = try std.fmt.parseFloat(f64, first_input);

        try stdout.writeAll("Enter operation (+, -, *, /): ");

        var op_buffer: [16]u8 = undefined;
        const op = try stdin.readUntilDelimiter(&op_buffer, '\n');

        if (op.len == 0) {
            try stdout.writeAll("Error: No operation entered!\n");
            continue;
        }

        try stdout.writeAll("Enter second number: ");

        var second_input_buffer: [16]u8 = undefined;
        const num2 = try std.fmt.parseFloat(f64, try stdin.readUntilDelimiter(&second_input_buffer, '\n'));

        const result = calculate(num1, num2, op[0]) catch |err| {
            switch (err) {
                error.DivisionByZero => try stdout.writeAll("Error: Division by zero!\n"),
                error.InvalidOperation => try stdout.print("Error: Invalid operation '{s}'!\n", .{op}),
                else => return err,
            }
            continue;
        };

        try stdout.print("Result: {d}\n", .{result});
    }
}
Enter fullscreen mode Exit fullscreen mode

b) The main function:

  • Creates an interactive command-line interface
  • Reads user input for numbers and operations
  • Handles errors and displays results
  • Provides a quit option ('q')

2. Testing (src/calculator_test.zig)

Tests are written using Zig's built-in testing framework:

test "basic addition" {
    const result = try calculator.calculate(5, 3, '+');
    try testing.expectEqual(@as(f64, 8), result);
}
Enter fullscreen mode Exit fullscreen mode

Key testing concepts:

  • Each test is a function marked with the test keyword
  • Use try for error handling
  • Use testing.expectEqual() for assertions

Building and Running

  1. Build the project:
   zig build
Enter fullscreen mode Exit fullscreen mode
  1. Run the calculator:
   zig build run
Enter fullscreen mode Exit fullscreen mode
  1. Using the calculator:
   Calculator (enter 'q' to quit)
   Enter first number: 10
   Enter operation (+, -, *, /): +
   Enter second number: 5
   Result: 15
Enter fullscreen mode Exit fullscreen mode

Testing

Run the test suite:

zig build test
Enter fullscreen mode Exit fullscreen mode

The tests will verify:

  • Basic arithmetic operations
  • Error handling for division by zero
  • Invalid operation handling
  • Edge cases

Common Issues

  1. Compilation Errors
  • Ensure Zig version 0.11.0 or later
  • Check file paths in build.zig
  • Verify syntax, especially in error handling
  1. Runtime Errors
  • Division by zero is handled with error.DivisionByZero
  • Invalid operations return error.InvalidOperation
  • Input parsing errors are caught and handled
  1. Build Issues
    • Make sure build.zig is in the root directory
    • Verify project structure matches the tutorial
    • Check that all source files are present

Next Steps

To enhance the calculator, consider:

  1. Adding more operations (power, square root)
  2. Implementing memory functions
  3. Supporting complex numbers
  4. Adding a graphical user interface
  5. Implementing scientific calculator features

Resources

Conclusion

You've now built a functional command-line calculator in Zig! This project demonstrates:

  • Basic Zig syntax and features
  • Error handling
  • Testing
  • Command-line I/O
  • Project organization

Keep exploring Zig's features and build upon this foundation to create more complex applications!

Find me on X and Discord @augusthottie

Top comments (1)

Collapse
 
jovialcore profile image
Chidiebere Chukwudi

In future when If I will be doing stuff with Zig, I will make sure to follow through properly. Weldone!