// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2021 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
const std = @import("std.zig");
const tokenizer = @import("zig/tokenizer.zig");

pub const Token = tokenizer.Token;
pub const Tokenizer = tokenizer.Tokenizer;
pub const fmtId = @import("zig/fmt.zig").fmtId;
pub const fmtEscapes = @import("zig/fmt.zig").fmtEscapes;
pub const parse = @import("zig/parse.zig").parse;
pub const string_literal = @import("zig/string_literal.zig");
pub const ast = @import("zig/ast.zig");
pub const system = @import("zig/system.zig");
pub const CrossTarget = @import("zig/cross_target.zig").CrossTarget;

pub const SrcHash = [16]u8;

pub fn hashSrc(src: []const u8) SrcHash {
    var out: SrcHash = undefined;
    std.crypto.hash.Blake3.hash(src, &out, .{});
    return out;
}

pub fn srcHashEql(a: SrcHash, b: SrcHash) bool {
    return @bitCast(u128, a) == @bitCast(u128, b);
}

pub fn hashName(parent_hash: SrcHash, sep: []const u8, name: []const u8) SrcHash {
    var out: SrcHash = undefined;
    var hasher = std.crypto.hash.Blake3.init(.{});
    hasher.update(&parent_hash);
    hasher.update(sep);
    hasher.update(name);
    hasher.final(&out);
    return out;
}

pub const Loc = struct {
    line: usize,
    column: usize,
    /// Does not include the trailing newline.
    source_line: []const u8,
};

pub fn findLineColumn(source: []const u8, byte_offset: usize) Loc {
    var line: usize = 0;
    var column: usize = 0;
    var line_start: usize = 0;
    var i: usize = 0;
    while (i < byte_offset) : (i += 1) {
        switch (source[i]) {
            '\n' => {
                line += 1;
                column = 0;
                line_start = i + 1;
            },
            else => {
                column += 1;
            },
        }
    }
    while (i < source.len and source[i] != '\n') {
        i += 1;
    }
    return .{
        .line = line,
        .column = column,
        .source_line = source[line_start..i],
    };
}

pub fn lineDelta(source: []const u8, start: usize, end: usize) isize {
    var line: isize = 0;
    if (end >= start) {
        for (source[start..end]) |byte| switch (byte) {
            '\n' => line += 1,
            else => continue,
        };
    } else {
        for (source[end..start]) |byte| switch (byte) {
            '\n' => line -= 1,
            else => continue,
        };
    }
    return line;
}

pub const BinNameOptions = struct {
    root_name: []const u8,
    target: std.Target,
    output_mode: std.builtin.OutputMode,
    link_mode: ?std.builtin.LinkMode = null,
    object_format: ?std.Target.ObjectFormat = null,
    version: ?std.builtin.Version = null,
};

/// Returns the standard file system basename of a binary generated by the Zig compiler.
pub fn binNameAlloc(allocator: *std.mem.Allocator, options: BinNameOptions) error{OutOfMemory}![]u8 {
    const root_name = options.root_name;
    const target = options.target;
    switch (options.object_format orelse target.getObjectFormat()) {
        .coff, .pe => switch (options.output_mode) {
            .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }),
            .Lib => {
                const suffix = switch (options.link_mode orelse .Static) {
                    .Static => ".lib",
                    .Dynamic => ".dll",
                };
                return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, suffix });
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
        },
        .elf => switch (options.output_mode) {
            .Exe => return allocator.dupe(u8, root_name),
            .Lib => {
                switch (options.link_mode orelse .Static) {
                    .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                        target.libPrefix(), root_name,
                    }),
                    .Dynamic => {
                        if (options.version) |ver| {
                            return std.fmt.allocPrint(allocator, "{s}{s}.so.{d}.{d}.{d}", .{
                                target.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
                            });
                        } else {
                            return std.fmt.allocPrint(allocator, "{s}{s}.so", .{
                                target.libPrefix(), root_name,
                            });
                        }
                    },
                }
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
        },
        .macho => switch (options.output_mode) {
            .Exe => return allocator.dupe(u8, root_name),
            .Lib => {
                switch (options.link_mode orelse .Static) {
                    .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                        target.libPrefix(), root_name,
                    }),
                    .Dynamic => {
                        if (options.version) |ver| {
                            return std.fmt.allocPrint(allocator, "{s}{s}.{d}.{d}.{d}.dylib", .{
                                target.libPrefix(), root_name, ver.major, ver.minor, ver.patch,
                            });
                        } else {
                            return std.fmt.allocPrint(allocator, "{s}{s}.dylib", .{
                                target.libPrefix(), root_name,
                            });
                        }
                    },
                }
                return std.fmt.allocPrint(allocator, "{s}{s}{s}", .{ target.libPrefix(), root_name, suffix });
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
        },
        .wasm => switch (options.output_mode) {
            .Exe => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.exeFileExt() }),
            .Lib => {
                switch (options.link_mode orelse .Static) {
                    .Static => return std.fmt.allocPrint(allocator, "{s}{s}.a", .{
                        target.libPrefix(), root_name,
                    }),
                    .Dynamic => return std.fmt.allocPrint(allocator, "{s}.wasm", .{root_name}),
                }
            },
            .Obj => return std.fmt.allocPrint(allocator, "{s}{s}", .{ root_name, target.oFileExt() }),
        },
        .c => return std.fmt.allocPrint(allocator, "{s}.c", .{root_name}),
        .spirv => return std.fmt.allocPrint(allocator, "{s}.spv", .{root_name}),
        .hex => return std.fmt.allocPrint(allocator, "{s}.ihex", .{root_name}),
        .raw => return std.fmt.allocPrint(allocator, "{s}.bin", .{root_name}),
    }
}

/// Only validates escape sequence characters.
/// Slice must be valid utf8 starting and ending with "'" and exactly one codepoint in between.
pub fn parseCharLiteral(
    slice: []const u8,
    bad_index: *usize, // populated if error.InvalidCharacter is returned
) error{InvalidCharacter}!u32 {
    std.debug.assert(slice.len >= 3 and slice[0] == '\'' and slice[slice.len - 1] == '\'');

    if (slice[1] == '\\') {
        switch (slice[2]) {
            'n' => return '\n',
            'r' => return '\r',
            '\\' => return '\\',
            't' => return '\t',
            '\'' => return '\'',
            '"' => return '"',
            'x' => {
                if (slice.len != 6) {
                    bad_index.* = slice.len - 2;
                    return error.InvalidCharacter;
                }
                var value: u32 = 0;
                for (slice[3..5]) |c, i| {
                    switch (c) {
                        '0'...'9' => {
                            value *= 16;
                            value += c - '0';
                        },
                        'a'...'f' => {
                            value *= 16;
                            value += c - 'a' + 10;
                        },
                        'A'...'F' => {
                            value *= 16;
                            value += c - 'A' + 10;
                        },
                        else => {
                            bad_index.* = 3 + i;
                            return error.InvalidCharacter;
                        },
                    }
                }
                return value;
            },
            'u' => {
                if (slice.len < "'\\u{0}'".len or slice[3] != '{' or slice[slice.len - 2] != '}') {
                    bad_index.* = 2;
                    return error.InvalidCharacter;
                }
                var value: u32 = 0;
                for (slice[4 .. slice.len - 2]) |c, i| {
                    switch (c) {
                        '0'...'9' => {
                            value *= 16;
                            value += c - '0';
                        },
                        'a'...'f' => {
                            value *= 16;
                            value += c - 'a' + 10;
                        },
                        'A'...'F' => {
                            value *= 16;
                            value += c - 'A' + 10;
                        },
                        else => {
                            bad_index.* = 4 + i;
                            return error.InvalidCharacter;
                        },
                    }
                    if (value > 0x10ffff) {
                        bad_index.* = 4 + i;
                        return error.InvalidCharacter;
                    }
                }
                return value;
            },
            else => {
                bad_index.* = 2;
                return error.InvalidCharacter;
            },
        }
    }
    return std.unicode.utf8Decode(slice[1 .. slice.len - 1]) catch unreachable;
}

test "parseCharLiteral" {
    var bad_index: usize = undefined;
    try std.testing.expectEqual(try parseCharLiteral("'a'", &bad_index), 'a');
    try std.testing.expectEqual(try parseCharLiteral("'ä'", &bad_index), 'ä');
    try std.testing.expectEqual(try parseCharLiteral("'\\x00'", &bad_index), 0);
    try std.testing.expectEqual(try parseCharLiteral("'\\x4f'", &bad_index), 0x4f);
    try std.testing.expectEqual(try parseCharLiteral("'\\x4F'", &bad_index), 0x4f);
    try std.testing.expectEqual(try parseCharLiteral("'ぁ'", &bad_index), 0x3041);
    try std.testing.expectEqual(try parseCharLiteral("'\\u{0}'", &bad_index), 0);
    try std.testing.expectEqual(try parseCharLiteral("'\\u{3041}'", &bad_index), 0x3041);
    try std.testing.expectEqual(try parseCharLiteral("'\\u{7f}'", &bad_index), 0x7f);
    try std.testing.expectEqual(try parseCharLiteral("'\\u{7FFF}'", &bad_index), 0x7FFF);

    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\x0'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\x000'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\y'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\uFFFF'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u{}'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u{FFFFFF}'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u{FFFF'", &bad_index));
    try std.testing.expectError(error.InvalidCharacter, parseCharLiteral("'\\u{FFFF}x'", &bad_index));
}

test {
    @import("std").testing.refAllDecls(@This());
}
