const std = @import("std"); pub const NumeralTag = enum { Integer, Float, }; pub fn isFloatExactInt(value: f64) bool { return value == 0 or (std.math.isNormal(value) and @floor(value) == value); } pub const Numeral = union(NumeralTag) { Integer: i64, Float: f64, pub fn rawEqual(self: Numeral, other: Numeral) bool { if(@as(NumeralTag, self) == @as(NumeralTag, other)) { switch(self) { .Float => |value| return value == other.Float, .Integer => |value| return value == other.Integer, } } // Other is the respective other type switch(self) { .Float => |value| { return isFloatExactInt(value) and @as(u64, @intFromFloat(value)) == other.Integer; }, .Integer => |value| { return isFloatExactInt(other.Float) and @as(u64, @intFromFloat(other.Float)) == value; } } } }; test "float int equality" { const a = Numeral { .Float = 12.0 }; const b = Numeral { .Integer = 12 }; try std.testing.expect(a.rawEqual(b)); try std.testing.expect(b.rawEqual(a)); try std.testing.expect(a.rawEqual(a)); try std.testing.expect(b.rawEqual(b)); const c = Numeral { .Float = (0.2 + 0.1) * 10.0 }; const d = Numeral { .Integer = 3 }; try std.testing.expect(c.rawEqual(d)); try std.testing.expect(d.rawEqual(c)); try std.testing.expect(c.rawEqual(c)); try std.testing.expect(d.rawEqual(d)); const e = Numeral { .Float = 3.2 }; try std.testing.expect(!a.rawEqual(e)); try std.testing.expect(!b.rawEqual(e)); try std.testing.expect(!c.rawEqual(e)); try std.testing.expect(!d.rawEqual(e)); try std.testing.expect(!e.rawEqual(a)); try std.testing.expect(!e.rawEqual(b)); try std.testing.expect(!e.rawEqual(c)); try std.testing.expect(!e.rawEqual(d)); } pub const TableEntry = struct { key: Value, value: Value, }; pub const Table = struct { entries: std.ArrayList(TableEntry), pub fn get(self: Table, key: Value) Value { if(@as(ValueTag, key) == ValueTag.Nil) { return Value.Nil; } for (self.entries.items) |entry| { if(entry.key.rawEqual(key)) { return entry.value; } } return Value.Nil; } }; pub const ValueTag = enum { Nil, Bool, Numeral, String, Table, }; pub const Value = union(ValueTag) { Nil, Bool: bool, Numeral: Numeral, String: []const u8, Table: *Table, pub fn rawEqual(self: Value, other: Value) bool { if(@as(ValueTag, self) != @as(ValueTag, other)) { return false; } switch(self) { .Nil => return true, .Bool => |value| return value == other.Bool, .Numeral => |value| return value.rawEqual(other.Numeral), .String => |value| return std.mem.eql(u8, value, other.String), .Table => |value| return value == other.Table, } } }; test "Value equalities" { const a = Value { .Bool = true }; const b = Value { .Numeral = Numeral { .Integer = 1 } }; // true != 1 try std.testing.expect(!a.rawEqual(b)); // 1 != true try std.testing.expect(!b.rawEqual(a)); const c = Value { .Bool = false }; // true != false try std.testing.expect(!a.rawEqual(c)); // false!= true try std.testing.expect(!c.rawEqual(a)); const d = Value { .Bool = true }; // true == true try std.testing.expect(a.rawEqual(d)); // true == true try std.testing.expect(d.rawEqual(a)); const e = Value { .String = "foo" }; const f = Value { .String = "bar" }; // foo != bar try std.testing.expect(!e.rawEqual(f)); // bar != foo try std.testing.expect(!f.rawEqual(e)); const g = Value { .String = "foo" }; // foo != foo try std.testing.expect(e.rawEqual(g)); // foo != foo try std.testing.expect(g.rawEqual(e)); var table = Table { .entries = std.ArrayList(TableEntry).init(std.testing.allocator) }; const h = Value { .Table = &table }; var table2 = Table { .entries = std.ArrayList(TableEntry).init(std.testing.allocator) }; const i = Value { .Table = &table2 }; const j = Value { .Table = &table2 }; try std.testing.expect(h.rawEqual(h)); try std.testing.expect(i.rawEqual(i)); try std.testing.expect(!h.rawEqual(i)); try std.testing.expect(!i.rawEqual(h)); try std.testing.expect(i.rawEqual(j)); try std.testing.expect(!h.rawEqual(j)); } pub const CodeRegion = struct { start: ?CodeLocation, length: usize, }; pub const CodeLocation = struct { line: usize, col: usize, };