🎓 A1Learner
🔍
0 of 46 lessons completed

Zig Project: Config Parser

A config parser is code that reads settings written as lines of text and turns them into values a program can use. Each line holds one key, an equals sign, and one value.

Reading host and port

Two lines set the host and the port. Each line is split on the equals sign, and the port text is parsed into a number.

const std = @import("std");
pub fn main() !void {
    const text = "host=localhost\nport=8080\n";
    var host: []const u8 = "";
    var port: u16 = 0;
    var lines = std.mem.splitScalar(u8, text, '\n');
    while (lines.next()) |line| {
        if (line.len == 0) continue;
        var parts = std.mem.splitScalar(u8, line, '=');
        const key = parts.next().?;
        const value = parts.next().?;
        if (std.mem.eql(u8, key, "host")) {
            host = value;
        } else if (std.mem.eql(u8, key, "port")) {
            port = try std.fmt.parseInt(u16, value, 10);
        }
    }
    const out = std.io.getStdOut().writer();
    try out.print("{s}:{d}\n", .{ host, port });
}

Reading a true or false setting

A debug setting is text until it is compared. Comparing the value against the word true turns it into a real boolean.

const std = @import("std");
pub fn main() !void {
    const text = "debug=true\n";
    var debug = false;
    var lines = std.mem.splitScalar(u8, text, '\n');
    while (lines.next()) |line| {
        if (line.len == 0) continue;
        var parts = std.mem.splitScalar(u8, line, '=');
        const key = parts.next().?;
        const value = parts.next().?;
        if (std.mem.eql(u8, key, "debug")) {
            debug = std.mem.eql(u8, value, "true");
        }
    }
    const out = std.io.getStdOut().writer();
    try out.print("debug: {}\n", .{debug});
}

Defaults and unknown keys

This file sets only the host and an unknown key. The port keeps its default of 80, and the unknown key is ignored.

const std = @import("std");
pub fn main() !void {
    const text = "host=example.com\nmode=fast\n";
    var host: []const u8 = "localhost";
    var port: u16 = 80;
    var lines = std.mem.splitScalar(u8, text, '\n');
    while (lines.next()) |line| {
        if (line.len == 0) continue;
        var parts = std.mem.splitScalar(u8, line, '=');
        const key = parts.next().?;
        const value = parts.next().?;
        if (std.mem.eql(u8, key, "host")) {
            host = value;
        } else if (std.mem.eql(u8, key, "port")) {
            port = try std.fmt.parseInt(u16, value, 10);
        }
    }
    const out = std.io.getStdOut().writer();
    try out.print("{s}:{d}\n", .{ host, port });
}

Example

const std = @import("std");
pub fn main() !void {
    const text = "# server settings\nhost=db.local\nport=5432\ndebug=false\n\n";
    var host: []const u8 = "localhost";
    var port: u16 = 80;
    var debug = false;
    var lines = std.mem.splitScalar(u8, text, '\n');
    while (lines.next()) |line| {
        if (line.len == 0) continue;
        if (line[0] == '#') continue;
        var parts = std.mem.splitScalar(u8, line, '=');
        const key = parts.next().?;
        const value = parts.next().?;
        if (std.mem.eql(u8, key, "host")) {
            host = value;
        } else if (std.mem.eql(u8, key, "port")) {
            port = try std.fmt.parseInt(u16, value, 10);
        } else if (std.mem.eql(u8, key, "debug")) {
            debug = std.mem.eql(u8, value, "true");
        }
    }
    const out = std.io.getStdOut().writer();
    try out.print("host: {s}\nport: {d}\ndebug: {}\n", .{ host, port, debug });
}

Key points

  • Config parser: a config parser is code that turns lines of key and value text into program settings.
  • Key: a key is the name on the left of the equals sign, and value is the text on the right.
  • Splitting the text on newlines gives one line at a time, and splitting a line on the equals sign gives its key and value.
  • Blank lines carry no setting, so the parser skips them instead of failing.
  • A value that should be a number is parsed into a number, and a value that should be true or false is compared against the word true.
  • A default is the value used when a key is missing, so a config file may leave settings out.
  • An unknown key is ignored, so older files still work when new settings are added later.

The full parser starts from defaults and then reads each line in turn. Comment lines starting with a hash and blank lines are skipped. The host, port, and debug lines each fill one setting, and the final print shows all three resolved values.

Try it Yourself

Edit the code, then press Run.

Running is simulated

Exercise

A config file sets host but not port, and the parser defaults port to 80. What port does the program use?