179 lines
5.7 KiB
Zig
179 lines
5.7 KiB
Zig
const std = @import("std");
|
|
const Allocator = std.mem.Allocator;
|
|
|
|
const Number = f64;
|
|
|
|
const Operator = struct {
|
|
token: u8,
|
|
operation: *const fn (Number, Number) Number,
|
|
precedence: u8,
|
|
is_left_associative: bool,
|
|
|
|
fn apply(self: Operator, x: Number, y: Number) Number {
|
|
return self.operation(x, y);
|
|
}
|
|
};
|
|
|
|
const Token = union(enum) {
|
|
digit: Number,
|
|
operator: Operator,
|
|
left_paren,
|
|
right_paren,
|
|
};
|
|
|
|
fn opAdd(x: Number, y: Number) Number { return x + y; }
|
|
fn opSub(x: Number, y: Number) Number { return x - y; }
|
|
fn opMul(x: Number, y: Number) Number { return x * y; }
|
|
fn opDiv(x: Number, y: Number) Number { return x / y; }
|
|
fn opPow(x: Number, y: Number) Number { return std.math.pow(Number, x, y); }
|
|
|
|
fn makeOperator(
|
|
token: u8,
|
|
precedence: u8,
|
|
is_left_associative: bool,
|
|
operation: *const fn (Number, Number) Number,
|
|
) Token {
|
|
return .{ .operator = .{
|
|
.token = token,
|
|
.operation = operation,
|
|
.precedence = precedence,
|
|
.is_left_associative = is_left_associative,
|
|
} };
|
|
}
|
|
|
|
fn lexToken(c: u8) error{InvalidCharacter}!Token {
|
|
return switch (c) {
|
|
'0'...'9' => .{ .digit = @floatFromInt(c - '0') },
|
|
'+' => makeOperator('+', 1, true, &opAdd),
|
|
'-' => makeOperator('-', 1, true, &opSub),
|
|
'*' => makeOperator('*', 2, true, &opMul),
|
|
'/' => makeOperator('/', 2, true, &opDiv),
|
|
'^' => makeOperator('^', 3, false, &opPow),
|
|
'(' => .left_paren,
|
|
')' => .right_paren,
|
|
else => error.InvalidCharacter,
|
|
};
|
|
}
|
|
|
|
fn lex(allocator: Allocator, input: []const u8) !std.ArrayList(Token) {
|
|
var tokens = std.ArrayList(Token).init(allocator);
|
|
errdefer tokens.deinit();
|
|
for (input) |c| {
|
|
if (std.ascii.isWhitespace(c)) continue;
|
|
try tokens.append(try lexToken(c));
|
|
}
|
|
return tokens;
|
|
}
|
|
|
|
fn tiltUntil(
|
|
operators: *std.ArrayList(Token),
|
|
output: *std.ArrayList(Token),
|
|
stop: std.meta.Tag(Token),
|
|
) !bool {
|
|
while (operators.items.len > 0) {
|
|
const tok = operators.pop().?;
|
|
if (std.meta.activeTag(tok) == stop) return true;
|
|
try output.append(tok);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
fn shuntingYard(allocator: Allocator, tokens: []const Token) !std.ArrayList(Token) {
|
|
var output = std.ArrayList(Token).init(allocator);
|
|
errdefer output.deinit();
|
|
var operators = std.ArrayList(Token).init(allocator);
|
|
defer operators.deinit();
|
|
|
|
for (tokens) |token| {
|
|
switch (token) {
|
|
.digit => try output.append(token),
|
|
.left_paren => try operators.append(token),
|
|
.operator => |op| {
|
|
while (operators.items.len > 0) {
|
|
switch (operators.getLast()) {
|
|
.left_paren => break,
|
|
.operator => |top| {
|
|
const same_prec_left = (top.precedence == op.precedence and
|
|
op.is_left_associative);
|
|
if (top.precedence > op.precedence or same_prec_left) {
|
|
try output.append(operators.pop().?);
|
|
} else break;
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
try operators.append(token);
|
|
},
|
|
.right_paren => {
|
|
if (!try tiltUntil(&operators, &output, .left_paren))
|
|
return error.MismatchedRightParen;
|
|
},
|
|
}
|
|
}
|
|
|
|
if (try tiltUntil(&operators, &output, .left_paren))
|
|
return error.MismatchedLeftParen;
|
|
|
|
std.debug.assert(operators.items.len == 0);
|
|
return output;
|
|
}
|
|
|
|
fn calculate(allocator: Allocator, postfix: []const Token) !Number {
|
|
var stack = std.ArrayList(Number).init(allocator);
|
|
defer stack.deinit();
|
|
|
|
for (postfix) |token| {
|
|
switch (token) {
|
|
.digit => |n| try stack.append(n),
|
|
.operator => |op| {
|
|
if (stack.items.len < 2) return error.MissingOperand;
|
|
const y = stack.pop().?;
|
|
const x = stack.pop().?;
|
|
try stack.append(op.apply(x, y));
|
|
},
|
|
else => unreachable,
|
|
}
|
|
}
|
|
|
|
std.debug.assert(stack.items.len == 1);
|
|
return stack.pop().?;
|
|
}
|
|
|
|
// ── display helpers ───────────────────────────────────────────────────────────
|
|
|
|
fn printTokens(tokens: []const Token, writer: anytype) !void {
|
|
for (tokens, 0..) |tok, i| {
|
|
if (i != 0) try writer.writeByte(' ');
|
|
switch (tok) {
|
|
.digit => |n| try writer.print("{d}", .{n}),
|
|
.operator => |op| try writer.writeByte(op.token),
|
|
.left_paren => try writer.writeByte('('),
|
|
.right_paren => try writer.writeByte(')'),
|
|
}
|
|
}
|
|
}
|
|
|
|
// ── entry point ───────────────────────────────────────────────────────────────
|
|
|
|
pub fn main() !void {
|
|
var gpa = std.heap.GeneralPurposeAllocator(.{}){};
|
|
defer _ = gpa.deinit();
|
|
const ally = gpa.allocator();
|
|
const out = std.io.getStdOut().writer();
|
|
|
|
const input = "3 + 4 * 2 / ( 1 - 5 ) ^ 2 ^ 3";
|
|
|
|
var infix = try lex(ally, input);
|
|
defer infix.deinit();
|
|
|
|
var postfix = try shuntingYard(ally, infix.items);
|
|
defer postfix.deinit();
|
|
|
|
try out.writeAll("infix: ");
|
|
try printTokens(infix.items, out);
|
|
try out.writeByte('\n');
|
|
|
|
try out.writeAll("postfix: ");
|
|
try printTokens(postfix.items, out);
|
|
try out.writeByte('\n');
|
|
}
|