Last active
October 18, 2024 14:14
-
-
Save AndrewKraevskii/413f293630650f10df030edda4d55081 to your computer and use it in GitHub Desktop.
Simple calculator with precedence and parentheses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
const std = @import("std"); | |
const Presedence = u8; | |
const Operator = enum { | |
divide, | |
add, | |
subtract, | |
multiply, | |
pub fn fromChar(char: u8) ?Operator { | |
return switch (char) { | |
'+' => .add, | |
'-' => .subtract, | |
'*' => .multiply, | |
'/' => .divide, | |
else => null, | |
}; | |
} | |
pub fn presedence(op: Operator) Presedence { | |
return switch (op) { | |
.add => 1, | |
.subtract => 1, | |
.multiply => 2, | |
.divide => 2, | |
}; | |
} | |
pub fn execute(op: Operator, a: f64, b: f64) f64 { | |
return switch (op) { | |
.add => a + b, | |
.divide => a / b, | |
.subtract => a - b, | |
.multiply => a * b, | |
}; | |
} | |
}; | |
const Token = union(enum) { | |
number: f64, | |
operator: Operator, | |
@"(", | |
@")", | |
}; | |
const base_ten_digits = "0123456789"; | |
pub fn getToken(text: []const u8) error{ InvalidToken, Eof }!struct { Token, []const u8 } { | |
const trimmed = std.mem.trimLeft(u8, text, &std.ascii.whitespace); | |
if (trimmed.len == 0) return error.Eof; | |
switch (trimmed[0]) { | |
'0'...'9' => { | |
const token_len = std.mem.indexOfNone(u8, trimmed, base_ten_digits) orelse trimmed.len; | |
const num = std.fmt.parseFloat(f64, trimmed[0..token_len]) catch | |
return error.InvalidToken; | |
return .{ .{ .number = num }, trimmed[token_len..] }; | |
}, | |
'+', '-', '*', '/' => |op| { | |
return .{ .{ .operator = Operator.fromChar(op).? }, trimmed[1..] }; | |
}, | |
')', '(' => |paren| { | |
return .{ | |
switch (paren) { | |
'(' => .@"(", | |
')' => .@")", | |
else => unreachable, | |
}, | |
trimmed[1..], | |
}; | |
}, | |
else => { | |
return error.InvalidToken; | |
}, | |
} | |
} | |
const TokenizeError = std.mem.Allocator.Error || error{InvalidToken}; | |
pub fn tokenize(alloc: std.mem.Allocator, text: []const u8) TokenizeError![]const Token { | |
var tokens = std.ArrayListUnmanaged(Token){}; | |
defer tokens.deinit(alloc); | |
var part_of_expression: []const u8 = text; | |
while (true) { | |
const token, part_of_expression = getToken(part_of_expression) catch |e| switch (e) { | |
error.Eof => return tokens.toOwnedSlice(alloc), | |
error.InvalidToken => return error.InvalidToken, | |
}; | |
try tokens.append(alloc, token); | |
} | |
} | |
pub fn evaluate(expression: []const Token) !f64 { | |
if (expression.len == 0) return error.Empty; | |
var indent_level: u32 = 0; | |
for (expression) |token| { | |
if (token == .@"(") { | |
indent_level += 1; | |
} else if (token == .@")") { | |
indent_level = std.math.sub(u32, indent_level, 1) catch return error.ToManyClosingParenthesis; | |
} | |
} | |
if (indent_level != 0) return error.NotEnoughClosingParenthesis; | |
const res, const not_consumed_tokens = try evaluateInner(expression, 0); | |
std.debug.assert(not_consumed_tokens.len == 0); | |
return res; | |
} | |
fn evaluateInner(expression: []const Token, prev_presedence: Presedence) !struct { f64, []const Token } { | |
if (expression.len == 0) return error.Empty; | |
var tokens = expression; | |
var left = blk: switch (tokens[0]) { | |
.number => |n| { | |
tokens = tokens[1..]; | |
break :blk n; | |
}, | |
.operator => return error.EncounteredOperatorWhenNumberExpected, | |
.@"(" => { | |
const res, tokens = try evaluateInner(expression[1..], 0); | |
break :blk res; | |
}, | |
.@")" => return error.@"Got ) when number expected", | |
}; | |
while (true) { | |
if (tokens.len == 0) return .{ left, &.{} }; | |
const op = switch (tokens[0]) { | |
.number => return error.GotNumberButExpectedOperator, | |
.operator => |op| op, | |
.@"(" => return error.@"Got ( when operator expected", | |
.@")" => return .{ left, tokens[1..] }, | |
}; | |
if (prev_presedence >= op.presedence()) { | |
return .{ left, tokens }; | |
} | |
tokens = tokens[1..]; | |
const right, tokens = try evaluateInner(tokens, op.presedence()); | |
left = op.execute(left, right); | |
} | |
return .{ left, tokens }; | |
} | |
pub fn main() !void { | |
var gpa = std.heap.GeneralPurposeAllocator(.{}){}; | |
defer _ = gpa.deinit(); | |
const alloc = gpa.allocator(); | |
const tests = [_][]const u8{ | |
"1", | |
"1 + 1", | |
"4 * 5 + 6", | |
"4 + 5 * 6", | |
"4 + 5 / 0", | |
"(1)", | |
"(1", | |
"1))", | |
")", | |
"(((2*((1+1+1+1)+10)))", | |
"(1 + 2) * 2", | |
"1 + (2 * 2)", | |
"4 + 5 / 0 + ", | |
"* 4 + 5 / 0 + ", | |
"4 - 5 - 10 * 2", | |
"(1 + 2) / 3", | |
"* ", | |
"", | |
}; | |
var max_len: usize = 0; | |
for (tests) |@"test"| { | |
max_len = @max(max_len, @"test".len); | |
} | |
const out = std.io.getStdOut(); | |
for (&tests) |@"test"| { | |
const tokens = try tokenize(alloc, @"test"); | |
defer alloc.free(tokens); | |
const res = evaluate(tokens) catch |e| { | |
try out.writer().print("\"{s}\"", .{@"test"}); | |
try out.writer().writeByteNTimes(' ', max_len - @"test".len); | |
try out.writer().print(" = {s}\n", .{@errorName(e)}); | |
continue; | |
}; | |
try out.writer().print("\"{s}\"", .{@"test"}); | |
try out.writer().writeByteNTimes(' ', max_len - @"test".len); | |
try out.writer().print(" = {d}\n", .{res}); | |
} | |
} | |
// Prints: | |
// "1" = 1 | |
// "1 + 1" = 2 | |
// "4 * 5 + 6" = 26 | |
// "4 + 5 * 6" = 34 | |
// "4 + 5 / 0" = inf | |
// "(1)" = 1 | |
// "(1" = NotEnoughClosingParenthesis | |
// "1))" = ToManyClosingParenthesis | |
// ")" = ToManyClosingParenthesis | |
// "(((2*((1+1+1+1)+10)))" = NotEnoughClosingParenthesis | |
// "(1 + 2) * 2" = 6 | |
// "1 + (2 * 2)" = 5 | |
// "4 + 5 / 0 + " = Empty | |
// "* 4 + 5 / 0 + " = EncounteredOperatorWhenNumberExpected | |
// "4 - 5 - 10 * 2" = -21 | |
// "(1 + 2) / 3" = 1 | |
// "* " = EncounteredOperatorWhenNumberExpected | |
// "" = Empty |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment