Viewing:
const std = @import("std"); const CalcError = error{ BadChar, NotEnoughOperators, TooLong, Overflow, TooManyOperators, BadFloat, }; const inputSize = 1024; // rules of rpn dictate there must be 1 more operand than operators // so we can never need a bigger stack than that. const stackLength = (inputSize / 2) + 1; fn calculateRpn(stream: []const u8) !f64 { var stack: [inputSize:0]f64 = undefined; var stackI: usize = 0; var numberI: usize = 0; var numberBuilder: [stackLength:0]u8 = undefined; var i: usize = 0; const streamLen = stream.len; for (stream) |token| { if (token == 0 or token == '\n') { break; } switch (token) { '0'...'9', '.' => { numberBuilder[numberI] = token; numberI += 1; }, '_', ',' => {}, ' ' => { if (numberI == 0) { continue; } const myFloat = std.fmt.parseFloat(f64, numberBuilder[0..numberI]) catch { return CalcError.BadFloat; // std.debug.print("bad float {s}, {any}", .{ numberBuilder[0..numberI], err }); // std.process.exit(1); }; stack[stackI] = myFloat; stackI += 1; numberI = 0; }, '-', '+', '*', '/' => { if (token == '-') { if (i < streamLen - 1 and (stream[i + 1] >= '0' and stream[i + 1] <= '9')) { numberBuilder[numberI] = token; numberI += 1; continue; } } if (numberI != 0) { const myFloat = std.fmt.parseFloat(f64, numberBuilder[0..numberI]) catch { return CalcError.BadFloat; // std.debug.print("bad float {s}, {any}", .{ numberBuilder[0..numberI], err }); // std.process.exit(1); }; stack[stackI] = myFloat; stackI += 1; numberI = 0; } if (stackI < 2) { return CalcError.TooManyOperators; // std.debug.print("not enough operands {c}", .{token}); // std.process.exit(1); } var result: f64 = undefined; // std.debug.print("\n{d} {c} {d}:\n", .{ stack[stackI - 2], token, stack[stackI - 1] }); const left = stack[stackI - 2]; const right = stack[stackI - 1]; if (token == '+') { result = left + right; } if (token == '-') { result = left - right; } if (token == '*') { result = left * right; } if (token == '/') { result = left / right; } stack[stackI - 2] = result; stackI -= 1; }, else => { return CalcError.BadChar; }, } i += 1; } if (stackI < 1) { return CalcError.BadChar; } if (stackI > 1) { return CalcError.NotEnoughOperators; } if (numberI != 0) { return CalcError.NotEnoughOperators; } return stack[0]; } const usage = \\Enter the math you would like to perform, up to 1024 characters \\valid characters: [0-9] - + * / . " " \\This is a reverse polish notation calculator \\e.g. 5 4 + 9 * 6 3 - / \\NOT ((5+4)*9)/(6-3) \\input: \\ ; pub fn main() !void { var args_it = std.process.args(); _ = args_it.next(); const inputVal = args_it.next(); var result: f64 = undefined; if (inputVal != null) { result = calculateRpn(inputVal.?) catch |err| { std.debug.print("error processing input: {any}\n", .{err}); std.process.exit(1); }; } else { var input: [inputSize]u8 = undefined; std.debug.print(usage, .{}); const stdin = std.io.getStdIn().reader(); _ = stdin.readUntilDelimiter(&input, '\n') catch |err| { std.debug.print("error reading input: {any}\n", .{err}); std.process.exit(1); }; result = calculateRpn(&input) catch |err| { std.debug.print("error processing input: {any}\n", .{err}); std.process.exit(1); }; } const stdout_file = std.io.getStdOut().writer(); var bw = std.io.bufferedWriter(stdout_file); const stdout = bw.writer(); defer { bw.flush() catch |err| switch (err) { else => {}, }; } var bufA: [64]u8 = undefined; var bufB: [64]u8 = undefined; const twoDecimals = try std.fmt.formatFloat(&bufA, result, .{ .mode = .decimal, .precision = 3 }); const allDecimals = try std.fmt.formatFloat(&bufB, result, .{ .mode = .decimal }); if (twoDecimals.len < allDecimals.len) { try stdout.print("{s}\n", .{twoDecimals}); } else { try stdout.print("{s}\n", .{allDecimals}); } } test "happy paths" { const expectEqual = std.testing.expectEqual; const secondsInWeek = 60 * 60 * 24 * 7; try expectEqual(6.0, try calculateRpn("3 3 +")); try expectEqual(2.0, try calculateRpn("3 1.5 /")); try expectEqual(3.142857142857143e0, try calculateRpn("22 7 /")); try expectEqual(secondsInWeek, try calculateRpn("60 60 24 7 * * *")); try expectEqual(secondsInWeek, try calculateRpn("60 60 * 24 * 7 *")); } test "supports commas" { const expectEqual = std.testing.expectEqual; try expectEqual(5044.1, calculateRpn("5,000 44.1 +")); } test "supports underscores" { const expectEqual = std.testing.expectEqual; try expectEqual(5044.1, calculateRpn("5_000 44.1 +")); } test "supports no spaces as long as not a minus" { const expectEqual = std.testing.expectEqual; try expectEqual(-1, calculateRpn("4 3 2 +-")); try expectEqual(8, calculateRpn("2 2 2 **")); } test "invalid cases" { const expectError = std.testing.expectError; try expectError(CalcError.TooManyOperators, calculateRpn("60 60 * 24 * 7 * +")); try expectError(CalcError.NotEnoughOperators, calculateRpn("60 60 * 24 * 7")); try expectError(CalcError.BadChar, calculateRpn("oops text 3 3 +")); } test "non RPN not supported" { const expectError = std.testing.expectError; try expectError(CalcError.BadChar, calculateRpn("((5+4)*9)/(6-3)")); try expectError(CalcError.TooManyOperators, calculateRpn("5+4")); } test "scientific notation not supported" { const expectError = std.testing.expectError; try expectError(CalcError.BadChar, calculateRpn("3e10 10 *")); }