Zig 0.16 学习笔记 - 实现一个简单的四则运算解析器

实现一个支持加减乘除、括号的四则运算解析器,可进行整数和浮点数运算。

实现代码

main.zig

zig
const std = @import("std");
const Parser = @import("Parser.zig");

pub fn main() void {
    var parser = Parser.init("(1 + 0.5) * 10 - 5 / 2 + (3 * (2 + 1))");

    const value = parser.exec() catch |e| {
        printError(&parser, e);
        return;
    };
    std.debug.print("{}\n", .{value});
}

fn printError(parser: *const Parser, err: Parser.ParseError) void {
    const err_pos = parser.lex.pos -| 1;

    const window_s = err_pos -| 20;
    const window_e = @min(err_pos +| 20, parser.lex.src.len);
    const window = parser.lex.src[window_s..window_e];

    std.debug.print("{s}\n", .{window});
    for (0..err_pos - window_s) |_| {
        std.debug.print(" ", .{});
    }
    std.debug.print("^ {}\n", .{err});
}

Parser.zig

zig
const std = @import("std");

lex: Lexer,
cur: Token,

const Self = @This();

pub const ParseError = error{
    Syntax,
    Lexical,
};

pub fn init(src: []const u8) Self {
    return .{
        .lex = .init(src),
        .cur = undefined,
    };
}

pub fn exec(self: *Self) ParseError!f64 {
    self.cur = try self.lex.nextToken();
    const num = try self.parseExpr();
    if (self.cur.kind() != .eof) {
        return ParseError.Syntax;
    }
    return num;
}

// 消费匹配的 Token
fn eat(self: *Self, kind: TokenKind) ParseError!void {
    if (self.cur.kind() != kind) {
        return ParseError.Syntax;
    }
    self.cur = try self.lex.nextToken();
}

// 解析因子
fn parseFactor(self: *Self) ParseError!f64 {
    switch (self.cur.data) {
        .num => |num| {
            try self.eat(.num);
            return num;
        },
        .lparen => {
            try self.eat(.lparen);
            const num = try self.parseExpr();
            try self.eat(.rparen);
            return num;
        },
        else => {
            return ParseError.Syntax;
        },
    }
}

// 解析乘除
fn parseTerm(self: *Self) ParseError!f64 {
    var num = try self.parseFactor();
    while (true) {
        switch (self.cur.kind()) {
            .mul => {
                try self.eat(.mul);
                num *= try self.parseFactor();
            },
            .div => {
                try self.eat(.div);
                num /= try self.parseFactor();
            },
            else => break,
        }
    }
    return num;
}

// 解析加减
fn parseExpr(self: *Self) ParseError!f64 {
    var num = try self.parseTerm();
    while (true) {
        switch (self.cur.kind()) {
            .add => {
                try self.eat(.add);
                num += try self.parseTerm();
            },
            .sub => {
                try self.eat(.sub);
                num -= try self.parseTerm();
            },
            else => break,
        }
    }
    return num;
}

const Token = struct {
    data: TokenData,

    fn kind(self: *const Token) TokenKind {
        return self.*.data;
    }
};

const TokenData = union(TokenKind) {
    /// 数字
    num: f64,
    /// `+`
    add,
    /// `-`
    sub,
    /// `*`
    mul,
    /// `/`
    div,
    /// `(`
    lparen,
    /// `)`
    rparen,
    /// 结束
    eof,
};

const TokenKind = enum {
    /// 数字
    num,
    /// `+`
    add,
    /// `-`
    sub,
    /// `*`
    mul,
    /// `/`
    div,
    /// `(`
    lparen,
    /// `)`
    rparen,
    /// 结束
    eof,
};

const Lexer = struct {
    src: []const u8,
    pos: usize = 0,

    fn init(src: []const u8) Lexer {
        return .{ .src = src };
    }

    // 获取当前字符
    fn peek(self: *Lexer) ?u8 {
        if (self.pos >= self.src.len) {
            return null;
        }
        return self.src[self.pos];
    }

    // 前进一格
    fn next(self: *Lexer) void {
        if (self.pos < self.src.len) {
            self.pos += 1;
        }
    }

    // 跳过空格
    fn skipSpace(self: *Lexer) void {
        while (self.peek()) |cur| : (self.next()) {
            if (!std.ascii.isWhitespace(cur)) {
                return;
            }
        }
    }

    // 读取一个数字
    fn readNum(self: *Lexer) ?f64 {
        const start = self.pos;

        while (self.peek()) |cur| : (self.next()) {
            if (!std.ascii.isDigit(cur) and cur != '.') {
                break;
            }
        }

        return std.fmt.parseFloat(f64, self.src[start..self.pos]) catch null;
    }

    // 取下一个 Token
    fn nextToken(self: *Lexer) ParseError!Token {
        self.skipSpace();

        if (self.peek()) |cur| {
            return switch (cur) {
                '+' => {
                    self.next();
                    return .{ .data = .add };
                },
                '-' => {
                    self.next();
                    return .{ .data = .sub };
                },
                '*' => {
                    self.next();
                    return .{ .data = .mul };
                },
                '/' => {
                    self.next();
                    return .{ .data = .div };
                },
                '(' => {
                    self.next();
                    return .{ .data = .lparen };
                },
                ')' => {
                    self.next();
                    return .{ .data = .rparen };
                },
                '0'...'9', '.' => {
                    const num = self.readNum() orelse return ParseError.Lexical;
                    return .{ .data = .{ .num = num } };
                },
                else => ParseError.Lexical,
            };
        } else {
            return .{ .data = .eof };
        }
    }
};