PranitInitial commit439f83c3 months agoHistory
453 lines · 20.3 KB · Zig
RawDownload
const std = @import("std"); const Allocator = std.mem.Allocator; pub const KeywordCase = enum {    sensitive,    ascii_insensitive,}; pub const BlockComment = struct {    start: []const u8,    end: []const u8,}; pub const Detection = enum {    none,    typescript_assertion,    json,    shell_shebang,    python_header,    sql_select,    dockerfile_from,    go_package,    rust_function,}; pub const Profile = struct {    label: []const u8,    aliases: []const []const u8,    line_comments: []const []const u8 = &.{},    block_comment: ?BlockComment = null,    quotes: []const u8 = &.{},    keywords: []const []const u8 = &.{},    literals: []const []const u8 = &.{},    keyword_case: KeywordCase = .sensitive,    detection: Detection = .none,}; const double_quote = &[_]u8{'"'};const double_single_quotes = &[_]u8{ '"', '\'' };const shell_quotes = &[_]u8{ '"', '\'', '`' }; const profiles = [_]Profile{    .{        .label = "zig",        .aliases = &.{"zig"},        .line_comments = &.{"//"},        .quotes = double_quote,        .keywords = &.{ "const", "var", "fn", "pub", "return", "if", "else", "while", "for", "struct", "enum", "union", "try", "catch", "comptime", "defer", "errdefer", "async", "await", "anytype", "void" },    },    .{        .label = "ts",        .aliases = &.{ "js", "jsx", "javascript", "ts", "tsx", "typescript" },        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = shell_quotes,        .keywords = &.{ "const", "let", "var", "function", "class", "interface", "type", "export", "import", "from", "return", "if", "else", "for", "while", "async", "await", "new", "extends", "implements", "public", "private", "readonly" },        .literals = &.{ "true", "false", "null", "undefined" },        .detection = .typescript_assertion,    },    .{        .label = "json",        .aliases = &.{"json"},        .quotes = double_quote,        .literals = &.{ "true", "false", "null" },        .detection = .json,    },    .{        .label = "sh",        .aliases = &.{ "sh", "bash", "zsh", "shell" },        .line_comments = &.{"#"},        .quotes = shell_quotes,        .keywords = &.{ "if", "then", "fi", "for", "do", "done", "in", "case", "esac", "function", "local", "export", "readonly", "return" },        .literals = &.{ "true", "false", "null" },        .detection = .shell_shebang,    },    .{        .label = "python",        .aliases = &.{ "python", "py" },        .line_comments = &.{"#"},        .quotes = double_single_quotes,        .keywords = &.{ "def", "class", "return", "if", "elif", "else", "for", "while", "in", "import", "from", "as", "try", "except", "with", "lambda", "async", "await", "pass", "raise", "yield", "match", "case" },        .literals = &.{ "True", "False", "None" },        .detection = .python_header,    },    .{        .label = "yaml",        .aliases = &.{ "yaml", "yml" },        .line_comments = &.{"#"},        .quotes = double_single_quotes,        .literals = &.{ "true", "false", "null", "yes", "no", "on", "off" },    },    .{        .label = "toml",        .aliases = &.{"toml"},        .line_comments = &.{"#"},        .quotes = double_single_quotes,        .literals = &.{ "true", "false" },    },    .{        .label = "sql",        .aliases = &.{"sql"},        .line_comments = &.{"--"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "select", "from", "where", "join", "left", "right", "inner", "outer", "on", "insert", "into", "values", "update", "set", "delete", "create", "alter", "drop", "table", "index", "group", "by", "order", "having", "limit", "as", "and", "or", "not", "distinct", "union" },        .literals = &.{ "true", "false", "null" },        .keyword_case = .ascii_insensitive,        .detection = .sql_select,    },    .{        .label = "dockerfile",        .aliases = &.{ "dockerfile", "docker" },        .line_comments = &.{"#"},        .quotes = double_single_quotes,        .keywords = &.{ "from", "run", "cmd", "entrypoint", "copy", "add", "workdir", "env", "arg", "expose", "volume", "user", "label", "onbuild", "stopsignal", "healthcheck", "shell", "maintainer" },        .keyword_case = .ascii_insensitive,        .detection = .dockerfile_from,    },    .{        .label = "rust",        .aliases = &.{ "rust", "rs" },        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "fn", "let", "mut", "pub", "struct", "enum", "impl", "trait", "use", "mod", "crate", "return", "if", "else", "match", "for", "while", "loop", "async", "await", "move", "where", "self", "super" },        .literals = &.{ "true", "false", "None", "Some" },        .detection = .rust_function,    },    .{        .label = "go",        .aliases = &.{"go"},        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = &[_]u8{ '"', '`' },        .keywords = &.{ "package", "import", "func", "var", "const", "type", "struct", "interface", "return", "if", "else", "for", "range", "switch", "case", "go", "defer", "select", "chan", "map" },        .literals = &.{ "true", "false", "nil" },        .detection = .go_package,    },    .{        .label = "c",        .aliases = &.{ "c", "h" },        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "auto", "break", "case", "char", "const", "continue", "default", "do", "double", "else", "enum", "extern", "float", "for", "goto", "if", "int", "long", "return", "short", "signed", "sizeof", "static", "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while" },        .literals = &.{ "true", "false", "NULL" },    },    .{        .label = "cpp",        .aliases = &.{ "cpp", "c++", "cc", "cxx", "hpp" },        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "auto", "bool", "class", "const", "constexpr", "decltype", "delete", "enum", "explicit", "friend", "inline", "namespace", "new", "nullptr", "private", "protected", "public", "template", "this", "typename", "using", "virtual", "void" },        .literals = &.{ "true", "false", "nullptr", "NULL" },    },    .{        .label = "csharp",        .aliases = &.{ "csharp", "cs" },        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "class", "namespace", "using", "public", "private", "protected", "internal", "static", "void", "string", "int", "var", "new", "return", "if", "else", "for", "foreach", "while", "async", "await", "interface", "record", "get", "set" },        .literals = &.{ "true", "false", "null" },    },    .{        .label = "java",        .aliases = &.{"java"},        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "class", "interface", "package", "import", "public", "private", "protected", "static", "final", "void", "new", "return", "if", "else", "for", "while", "try", "catch", "throws", "extends", "implements", "record", "var" },        .literals = &.{ "true", "false", "null" },    },    .{        .label = "kotlin",        .aliases = &.{ "kotlin", "kt", "kts" },        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "fun", "val", "var", "class", "object", "interface", "package", "import", "public", "private", "return", "if", "else", "when", "for", "while", "try", "catch", "data", "sealed", "suspend" },        .literals = &.{ "true", "false", "null" },    },    .{        .label = "php",        .aliases = &.{"php"},        .line_comments = &.{ "//", "#" },        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "function", "class", "public", "private", "protected", "namespace", "use", "return", "if", "else", "foreach", "for", "while", "try", "catch", "new", "static", "const", "echo", "yield" },        .literals = &.{ "true", "false", "null" },    },    .{        .label = "ruby",        .aliases = &.{ "ruby", "rb" },        .line_comments = &.{"#"},        .quotes = double_single_quotes,        .keywords = &.{ "def", "class", "module", "end", "return", "if", "elsif", "else", "unless", "case", "when", "do", "while", "for", "in", "begin", "rescue", "require", "attr_reader" },        .literals = &.{ "true", "false", "nil" },    },    .{        .label = "swift",        .aliases = &.{"swift"},        .line_comments = &.{"//"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "func", "let", "var", "class", "struct", "enum", "protocol", "extension", "import", "public", "private", "return", "if", "else", "guard", "for", "while", "switch", "case", "async", "await", "throws", "try" },        .literals = &.{ "true", "false", "nil" },    },    .{        .label = "powershell",        .aliases = &.{ "powershell", "ps1", "pwsh", "ps" },        .line_comments = &.{"#"},        .block_comment = .{ .start = "<#", .end = "#>" },        .quotes = double_single_quotes,        .keywords = &.{ "function", "param", "if", "else", "elseif", "foreach", "for", "while", "switch", "return", "throw", "try", "catch", "finally", "begin", "process", "end", "filter", "class", "enum" },        .literals = &.{ "true", "false", "null" },        .keyword_case = .ascii_insensitive,    },    .{        .label = "lua",        .aliases = &.{"lua"},        .line_comments = &.{"--"},        .block_comment = .{ .start = "--[[", .end = "]]" },        .quotes = double_single_quotes,        .keywords = &.{ "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "goto", "if", "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until", "while" },        .literals = &.{ "true", "false", "nil" },    },    .{        .label = "html",        .aliases = &.{ "html", "htm" },        .block_comment = .{ .start = "<!--", .end = "-->" },        .quotes = double_single_quotes,        .keywords = &.{ "html", "head", "body", "main", "header", "footer", "section", "article", "div", "span", "a", "p", "script", "style", "link", "meta", "title", "button", "input", "form", "img", "ul", "li" },    },    .{        .label = "xml",        .aliases = &.{"xml"},        .block_comment = .{ .start = "<!--", .end = "-->" },        .quotes = double_single_quotes,        .keywords = &.{ "xml", "version", "encoding", "DOCTYPE", "CDATA" },    },    .{        .label = "css",        .aliases = &.{"css"},        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "color", "background", "display", "position", "margin", "padding", "border", "font", "width", "height", "flex", "grid", "align", "justify", "transition", "transform", "animation", "media" },    },    .{        .label = "hcl",        .aliases = &.{ "hcl", "terraform", "tf" },        .line_comments = &.{ "#", "//" },        .block_comment = .{ .start = "/*", .end = "*/" },        .quotes = double_single_quotes,        .keywords = &.{ "resource", "module", "variable", "output", "provider", "terraform", "locals", "data", "dynamic", "for_each", "count" },        .literals = &.{ "true", "false", "null" },    },}; pub fn resolve(label: []const u8) ?*const Profile {    for (&profiles) |*profile| {        for (profile.aliases) |alias| {            if (std.ascii.eqlIgnoreCase(label, alias)) return profile;        }    }    return null;} pub fn infer(alloc: Allocator, source: []const u8) ?*const Profile {    for (&profiles) |*profile| {        if (matchesDetection(alloc, profile.detection, source)) return profile;    }    return null;} fn matchesDetection(alloc: Allocator, detection: Detection, source: []const u8) bool {    return switch (detection) {        .none => false,        .typescript_assertion => matchesTypeScriptAssertion(source),        .json => isValidJson(alloc, source),        .shell_shebang => matchesShellShebang(source),        .python_header => matchesPythonHeader(source),        .sql_select => matchesSqlSelect(source),        .dockerfile_from => startsWithIgnoreCase(firstNonblankLine(source), "from "),        .go_package => startsWith(firstNonblankLine(source), "package ") and containsLineStart(source, "func "),        .rust_function => matchesRustFunction(source),    };} fn matchesTypeScriptAssertion(source: []const u8) bool {    var start: usize = 0;    while (std.mem.indexOfPos(u8, source, start, "} as ")) |assertion_start| {        const type_start = assertion_start + "} as ".len;        if (type_start < source.len and std.ascii.isUpper(source[type_start])) return true;        start = type_start;    }    return false;} fn isValidJson(alloc: Allocator, source: []const u8) bool {    const trimmed = std.mem.trim(u8, source, " \t\r\n");    if (trimmed.len == 0 or (trimmed[0] != '{' and trimmed[0] != '[')) return false;    var parsed = std.json.parseFromSlice(std.json.Value, alloc, trimmed, .{}) catch return false;    defer parsed.deinit();    return parsed.value == .object or parsed.value == .array;} fn matchesShellShebang(source: []const u8) bool {    const line = firstNonblankLine(source);    return std.mem.startsWith(u8, line, "#!") and        (std.mem.indexOf(u8, line, "bash") != null or std.mem.indexOf(u8, line, "zsh") != null or std.mem.indexOf(u8, line, "/sh") != null);} fn matchesPythonHeader(source: []const u8) bool {    const line = firstNonblankLine(source);    return (std.mem.startsWith(u8, line, "def ") or std.mem.startsWith(u8, line, "class ")) and std.mem.endsWith(u8, line, ":");} fn matchesSqlSelect(source: []const u8) bool {    const line = firstNonblankLine(source);    return startsWithIgnoreCase(line, "select ") and containsWordIgnoreCase(source, "from");} fn matchesRustFunction(source: []const u8) bool {    const line = firstNonblankLine(source);    if (!std.mem.startsWith(u8, line, "fn ") and !std.mem.startsWith(u8, line, "pub fn ")) return false;    return std.mem.indexOf(u8, source, "let ") != null or std.mem.indexOf(u8, source, "println!") != null or std.mem.indexOf(u8, line, "->") != null;} fn firstNonblankLine(source: []const u8) []const u8 {    var lines = std.mem.splitScalar(u8, source, '\n');    while (lines.next()) |line| {        const trimmed = std.mem.trim(u8, line, " \t\r");        if (trimmed.len > 0) return trimmed;    }    return "";} fn containsLineStart(source: []const u8, prefix: []const u8) bool {    if (std.mem.startsWith(u8, source, prefix)) return true;    var start: usize = 0;    while (std.mem.indexOfPos(u8, source, start, "\n")) |newline| {        const line_start = newline + 1;        if (std.mem.startsWith(u8, source[line_start..], prefix)) return true;        start = line_start;    }    return false;} fn startsWith(text: []const u8, prefix: []const u8) bool {    return text.len >= prefix.len and std.mem.eql(u8, text[0..prefix.len], prefix);} fn startsWithIgnoreCase(text: []const u8, prefix: []const u8) bool {    return text.len >= prefix.len and std.ascii.eqlIgnoreCase(text[0..prefix.len], prefix);} fn containsWordIgnoreCase(source: []const u8, word: []const u8) bool {    if (word.len > source.len) return false;    var index: usize = 0;    while (index + word.len <= source.len) : (index += 1) {        const end = index + word.len;        if (std.ascii.eqlIgnoreCase(source[index..end], word) and            (index == 0 or !isWordByte(source[index - 1])) and            (end == source.len or !isWordByte(source[end]))) return true;    }    return false;} fn isWordByte(byte: u8) bool {    return std.ascii.isAlphanumeric(byte) or byte == '_';} test "TypeScript assertions infer the canonical TypeScript label" {    const source =        "const hook = await resumeHook(token, { cleanup: true } as CleanupSignal);";     try std.testing.expectEqualStrings("ts", infer(std.testing.allocator, source).?.label);    try std.testing.expect(infer(std.testing.allocator, "const value = 1;") == null);    try std.testing.expect(infer(std.testing.allocator, "const value = {} as cleanupSignal;") == null);} test "supported code fence labels resolve case insensitively" {    const cases = [_]struct { label: []const u8, profile: []const u8 }{        .{ .label = "Zig", .profile = "zig" },        .{ .label = "js", .profile = "ts" },        .{ .label = "JSX", .profile = "ts" },        .{ .label = "javascript", .profile = "ts" },        .{ .label = "TS", .profile = "ts" },        .{ .label = "tsx", .profile = "ts" },        .{ .label = "TypeScript", .profile = "ts" },        .{ .label = "JSON", .profile = "json" },        .{ .label = "sh", .profile = "sh" },        .{ .label = "BASH", .profile = "sh" },        .{ .label = "zsh", .profile = "sh" },        .{ .label = "Shell", .profile = "sh" },    };    for (cases) |case| try std.testing.expectEqualStrings(case.profile, resolve(case.label).?.label);    try std.testing.expect(resolve("") == null);    try std.testing.expect(resolve("text") == null);} test "expanded code fence labels resolve through the language registry" {    const cases = [_]struct { label: []const u8, profile: []const u8 }{        .{ .label = "python", .profile = "python" },         .{ .label = "py", .profile = "python" },        .{ .label = "yaml", .profile = "yaml" },             .{ .label = "yml", .profile = "yaml" },        .{ .label = "toml", .profile = "toml" },             .{ .label = "sql", .profile = "sql" },        .{ .label = "dockerfile", .profile = "dockerfile" }, .{ .label = "rust", .profile = "rust" },        .{ .label = "rs", .profile = "rust" },               .{ .label = "go", .profile = "go" },        .{ .label = "c", .profile = "c" },                   .{ .label = "cpp", .profile = "cpp" },        .{ .label = "c++", .profile = "cpp" },               .{ .label = "csharp", .profile = "csharp" },        .{ .label = "cs", .profile = "csharp" },             .{ .label = "java", .profile = "java" },        .{ .label = "kotlin", .profile = "kotlin" },         .{ .label = "php", .profile = "php" },        .{ .label = "ruby", .profile = "ruby" },             .{ .label = "swift", .profile = "swift" },        .{ .label = "powershell", .profile = "powershell" }, .{ .label = "ps1", .profile = "powershell" },        .{ .label = "lua", .profile = "lua" },               .{ .label = "html", .profile = "html" },        .{ .label = "xml", .profile = "xml" },               .{ .label = "css", .profile = "css" },        .{ .label = "hcl", .profile = "hcl" },               .{ .label = "terraform", .profile = "hcl" },        .{ .label = "tf", .profile = "hcl" },    };    for (cases) |case| try std.testing.expectEqualStrings(case.profile, resolve(case.label).?.label);} test "high-confidence source shapes infer registered profiles" {    const alloc = std.testing.allocator;    const cases = [_]struct { source: []const u8, profile: []const u8 }{        .{ .source = "{\"ready\": true}", .profile = "json" },        .{ .source = "#!/usr/bin/env bash\necho ready", .profile = "sh" },        .{ .source = "def render(value):\n    return value", .profile = "python" },        .{ .source = "SELECT id FROM users", .profile = "sql" },        .{ .source = "FROM alpine:3.20\nRUN echo ready", .profile = "dockerfile" },        .{ .source = "package main\nfunc main() {}", .profile = "go" },        .{ .source = "fn main() { println!(\"ready\"); }", .profile = "rust" },    };     for (cases) |case| try std.testing.expectEqualStrings(case.profile, infer(alloc, case.source).?.label);    try std.testing.expect(infer(alloc, "const value = 1;") == null);    try std.testing.expect(infer(alloc, "title: ready") == null);} test "aliases do not collide across profiles" {    for (profiles, 0..) |profile, profile_index| {        for (profile.aliases) |alias| {            for (profiles[profile_index + 1 ..]) |other| {                for (other.aliases) |other_alias| {                    try std.testing.expect(!std.ascii.eqlIgnoreCase(alias, other_alias));                }            }        }    }}