Karen E. Petrie

23 papers A* 1A 9B 3Misc 1Journal 4Unranked 3
YearRankTypeTitle / Venue / Authors
2017 J jnl
Educ. Inf. Technol.
Rachel Menzies, Karen E. Petrie, Mark Zarb
2016 B conf
SOCS
David A. Cohen, Christopher Jefferson, Karen E. Petrie
2016 conf
IEEE BigData
Christopher Hillman, Karen E. Petrie, Andrew Cobley, Mark Whitehorn
2015 A conf
SIGCSE
Rachel Menzies, Michael Crabb, Daniel Herron, Karen E. Petrie, Craig D. Stewart, Mark Zarb
2014 J jnl
CoRR
Sylwester Arabas, Michael R. Bareford, Ian P. Gent, Benjamin M. Gorman, Masih Hajiarabderkani, Tristan Henderson, Luke Hutton, Alexander Konovalov, Lars Kotthoff, Ciaran McCreesh, Ruma R. Paul, Karen E. Petrie, Abdul Razaq, Daniël Reijsbergen
2013 conf
ICCSW
Hu Xu, Karen E. Petrie, Iain R. Murray
2012 conf
ICCSW
Hu Xu, Karen E. Petrie
2011 A conf
CP
Christopher Jefferson, Karen E. Petrie
2011 Misc conf
SAC
Christopher Jefferson, Wendy Moncur, Karen E. Petrie
2010 J jnl
Artif. Intell.
Christopher Jefferson, Neil C. A. Moore, Peter Nightingale, Karen E. Petrie
2010 A conf
ECAI
Ian P. Gent, Christopher Jefferson, Lars Kotthoff, Ian Miguel, Neil C. A. Moore, Peter Nightingale, Karen E. Petrie
2009 A conf
CP
Christopher Jefferson, Serdar Kadioglu, Karen E. Petrie, Meinolf Sellmann, Stanislav Zivný
2009 B conf
ITiCSE
Hannah M. Dee, Karen E. Petrie, Roger D. Boyle, Reena Pau
2008 A conf
CP
Karen E. Petrie, Christopher Jefferson
2006 A* conf
AAAI
David A. Cohen, Peter Jeavons, Christopher Jefferson, Karen E. Petrie, Barbara M. Smith
2006 J jnl
Constraints An Int. J.
David A. Cohen, Peter Jeavons, Christopher Jefferson, Karen E. Petrie, Barbara M. Smith
2006 ch.
Handbook of Constraint Programming
Ian P. Gent, Karen E. Petrie, Jean-François Puget
2005
Karen E. Petrie
2005 A conf
CP
David A. Cohen, Peter Jeavons, Christopher Jefferson, Karen E. Petrie, Barbara M. Smith
2004 B conf
CPAIOR
Barbara M. Smith, Karen E. Petrie, Ian P. Gent
2003 A conf
CP
Karen E. Petrie
2003 A conf
CP
Karen E. Petrie, Barbara M. Smith
2002 A conf
CP
Karen E. Petrie
src/highlight.rs
← Index src/highlight.rs rust
use once_cell::sync::Lazy;
use regex::Regex;
use std::collections::HashMap;

/// Highlighting rules for a "family" of languages.
struct LangRule {
    line_comments: &'static [&'static str],
    block_comments: &'static [(&'static str, &'static str)],
    /// If true, enables strings delimited by " and '
    strings: bool,
    /// If true, also enables triple-quoted strings (Python)
    triple_quote: bool,
    /// If true, also enables backtick template strings (JS/TS)
    backtick: bool,
    /// If true, keywords are matched case-insensitively (e.g. SQL)
    case_insensitive_keywords: bool,
    keywords: &'static [&'static str],
}

const EMPTY_KW: &[&str] = &[];

fn rule_for(lang: &str) -> LangRule {
    match lang {
        "rust" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "as", "break", "const", "continue", "crate", "dyn", "else", "enum", "extern",
                "false", "fn", "for", "if", "impl", "in", "let", "loop", "match", "mod", "move",
                "mut", "pub", "ref", "return", "self", "Self", "static", "struct", "super",
                "trait", "true", "type", "unsafe", "use", "where", "while", "async", "await",
                "union",
            ],
        },
        "python" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: true,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "False", "None", "True", "and", "as", "assert", "async", "await", "break",
                "class", "continue", "def", "del", "elif", "else", "except", "finally", "for",
                "from", "global", "if", "import", "in", "is", "lambda", "nonlocal", "not", "or",
                "pass", "raise", "return", "try", "while", "with", "yield", "self",
            ],
        },
        "javascript" | "typescript" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: true,
            case_insensitive_keywords: false,
            keywords: &[
                "break", "case", "catch", "class", "const", "continue", "debugger", "default",
                "delete", "do", "else", "export", "extends", "finally", "for", "function", "if",
                "import", "in", "instanceof", "new", "return", "super", "switch", "this", "throw",
                "try", "typeof", "var", "void", "while", "with", "yield", "let", "static",
                "async", "await", "of", "null", "true", "false", "undefined", "interface",
                "type", "enum", "implements", "namespace", "readonly", "public", "private",
                "protected", "abstract", "as", "declare", "from", "get", "set",
            ],
        },
        "java" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "abstract", "assert", "boolean", "break", "byte", "case", "catch", "char",
                "class", "const", "continue", "default", "do", "double", "else", "enum",
                "extends", "final", "finally", "float", "for", "goto", "if", "implements",
                "import", "instanceof", "int", "interface", "long", "native", "new", "package",
                "private", "protected", "public", "return", "short", "static", "strictfp",
                "super", "switch", "synchronized", "this", "throw", "throws", "transient", "try",
                "void", "volatile", "while", "true", "false", "null", "var", "record", "yield",
                "sealed", "permits",
            ],
        },
        "c" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "auto", "break", "case", "char", "const", "continue", "default", "do", "double",
                "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long",
                "register", "restrict", "return", "short", "signed", "sizeof", "static",
                "struct", "switch", "typedef", "union", "unsigned", "void", "volatile", "while",
            ],
        },
        "cpp" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "auto", "break", "case", "char", "const", "continue", "default", "do", "double",
                "else", "enum", "extern", "float", "for", "goto", "if", "inline", "int", "long",
                "register", "return", "short", "signed", "sizeof", "static", "struct", "switch",
                "typedef", "union", "unsigned", "void", "volatile", "while", "class",
                "namespace", "template", "typename", "public", "private", "protected", "virtual",
                "friend", "this", "new", "delete", "try", "catch", "throw", "using", "operator",
                "explicit", "mutable", "bool", "true", "false", "nullptr", "constexpr",
                "decltype", "noexcept", "override", "final",
            ],
        },
        "csharp" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "abstract", "as", "base", "bool", "break", "byte", "case", "catch", "char",
                "checked", "class", "const", "continue", "decimal", "default", "delegate", "do",
                "double", "else", "enum", "event", "explicit", "extern", "false", "finally",
                "fixed", "float", "for", "foreach", "goto", "if", "implicit", "in", "int",
                "interface", "internal", "is", "lock", "long", "namespace", "new", "null",
                "object", "operator", "out", "override", "params", "private", "protected",
                "public", "readonly", "ref", "return", "sbyte", "sealed", "short", "sizeof",
                "stackalloc", "static", "string", "struct", "switch", "this", "throw", "true",
                "try", "typeof", "uint", "ulong", "unchecked", "unsafe", "ushort", "using",
                "virtual", "void", "volatile", "while", "var", "async", "await", "yield",
                "nameof",
            ],
        },
        "go" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: true,
            case_insensitive_keywords: false,
            keywords: &[
                "break", "case", "chan", "const", "continue", "default", "defer", "else",
                "fallthrough", "for", "func", "go", "goto", "if", "import", "interface", "map",
                "package", "range", "return", "select", "struct", "switch", "type", "var",
                "true", "false", "nil", "iota",
            ],
        },
        "ruby" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "def", "end", "if", "elsif", "else", "unless", "while", "until", "for", "in",
                "do", "begin", "rescue", "ensure", "raise", "class", "module", "self", "nil",
                "true", "false", "and", "or", "not", "then", "yield", "return", "break", "next",
                "redo", "retry", "super", "require", "require_relative", "attr_accessor",
                "attr_reader", "attr_writer", "private", "public", "protected",
            ],
        },
        "php" => LangRule {
            line_comments: &["//", "#"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "abstract", "and", "array", "as", "break", "callable", "case", "catch", "class",
                "clone", "const", "continue", "declare", "default", "do", "echo", "else",
                "elseif", "empty", "extends", "final", "finally", "fn", "for", "foreach",
                "function", "global", "goto", "if", "implements", "include", "include_once",
                "instanceof", "interface", "isset", "list", "match", "namespace", "new", "or",
                "print", "private", "protected", "public", "require", "require_once", "return",
                "static", "switch", "throw", "trait", "try", "unset", "use", "var", "while",
                "xor", "yield", "true", "false", "null", "self", "parent",
            ],
        },
        "bash" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "if", "then", "else", "elif", "fi", "for", "while", "until", "do", "done",
                "case", "esac", "function", "in", "return", "break", "continue", "local",
                "export", "readonly", "shift", "exit", "echo", "source",
            ],
        },
        "sql" => LangRule {
            line_comments: &["--"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: true,
            keywords: &[
                "select", "from", "where", "insert", "into", "values", "update", "set",
                "delete", "create", "table", "alter", "drop", "join", "inner", "left", "right",
                "outer", "on", "group", "by", "order", "having", "as", "and", "or", "not",
                "null", "is", "in", "like", "limit", "offset", "distinct", "union", "all",
                "exists", "between", "case", "when", "then", "end", "primary", "key", "foreign",
                "references", "default", "index", "view", "trigger", "procedure", "function",
                "begin", "commit", "rollback",
            ],
        },
        "lua" => LangRule {
            line_comments: &["--"],
            block_comments: &[("--[[", "]]")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "and", "break", "do", "else", "elseif", "end", "false", "for", "function", "if",
                "in", "local", "nil", "not", "or", "repeat", "return", "then", "true", "until",
                "while",
            ],
        },
        "kotlin" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "as", "break", "class", "continue", "do", "else", "false", "for", "fun", "if",
                "in", "interface", "is", "null", "object", "package", "return", "super", "this",
                "throw", "true", "try", "typealias", "val", "var", "when", "while", "by",
                "companion", "constructor", "data", "enum", "import", "init", "override",
                "private", "protected", "public", "sealed", "suspend",
            ],
        },
        "swift" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &[
                "class", "deinit", "enum", "extension", "func", "import", "init", "internal",
                "let", "operator", "private", "protocol", "public", "static", "struct",
                "subscript", "typealias", "var", "break", "case", "continue", "default", "do",
                "else", "fallthrough", "if", "in", "for", "return", "switch", "where", "while",
                "as", "false", "true", "guard", "nil", "self", "super", "throw", "try", "async",
                "await",
            ],
        },
        "css" | "scss" | "less" => LangRule {
            line_comments: &[],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
        "xml" => LangRule {
            line_comments: &[],
            block_comments: &[("<!--", "-->")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
        "json" => LangRule {
            line_comments: &[],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &["true", "false", "null"],
        },
        "yaml" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &["true", "false", "null", "yes", "no"],
        },
        "ini" => LangRule {
            line_comments: &["#", ";"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
        "dockerfile" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: true,
            keywords: &[
                "from", "run", "cmd", "label", "expose", "env", "add", "copy", "entrypoint",
                "volume", "user", "workdir", "arg", "onbuild", "stopsignal", "healthcheck",
                "shell",
            ],
        },
        "makefile" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
        "graphql" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &["query", "mutation", "subscription", "type", "input", "enum", "interface", "fragment", "on"],
        },
        // Language families without detailed specific rules: still
        // highlight comments/strings/numbers generically.
        "perl" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &["my", "our", "sub", "if", "elsif", "else", "unless", "while", "for", "foreach", "return", "use", "package"],
        },
        "groovy" => LangRule {
            line_comments: &["//"],
            block_comments: &[("/*", "*/")],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
        "r" => LangRule {
            line_comments: &["#"],
            block_comments: &[],
            strings: true,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: &["function", "if", "else", "for", "while", "repeat", "break", "next", "return", "TRUE", "FALSE", "NULL", "NA"],
        },
        "diff" => LangRule {
            line_comments: &[],
            block_comments: &[],
            strings: false,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
        // markdown, plaintext, cmake, vbnet, dos, powershell, etc:
        // no specific highlighting, plain but formatted text.
        _ => LangRule {
            line_comments: &[],
            block_comments: &[],
            strings: false,
            triple_quote: false,
            backtick: false,
            case_insensitive_keywords: false,
            keywords: EMPTY_KW,
        },
    }
}

/// Cache of compiled regexes (one per language actually encountered).
static REGEX_CACHE: Lazy<std::sync::Mutex<HashMap<String, Option<Regex>>>> =
    Lazy::new(|| std::sync::Mutex::new(HashMap::new()));

fn build_regex(rule: &LangRule) -> Option<Regex> {
    let mut comment_alts: Vec<String> = Vec::new();
    for (start, end) in rule.block_comments {
        comment_alts.push(format!(
            "(?s:{}.*?{})",
            regex::escape(start),
            regex::escape(end)
        ));
    }
    for prefix in rule.line_comments {
        comment_alts.push(format!("{}[^\n]*", regex::escape(prefix)));
    }

    let mut string_alts: Vec<String> = Vec::new();
    if rule.triple_quote {
        string_alts.push(r#"(?s:"""(?:[^"\\]|\\.)*?""")"#.to_string());
        string_alts.push(r#"(?s:'''(?:[^'\\]|\\.)*?''')"#.to_string());
    }
    if rule.strings {
        string_alts.push(r#""(?:[^"\\\n]|\\.)*""#.to_string());
        string_alts.push(r#"'(?:[^'\\\n]|\\.)*'"#.to_string());
    }
    if rule.backtick {
        string_alts.push(r"(?s:`(?:[^`\\]|\\.)*`)".to_string());
    }

    let number_pat = r"\b0[xX][0-9a-fA-F]+\b|\b\d+\.\d+(?:[eE][+-]?\d+)?\b|\b\d+\b".to_string();

    let mut keyword_alt = String::new();
    if !rule.keywords.is_empty() {
        let escaped: Vec<String> = rule.keywords.iter().map(|k| regex::escape(k)).collect();
        let joined = escaped.join("|");
        keyword_alt = if rule.case_insensitive_keywords {
            format!("(?i:\\b(?:{})\\b)", joined)
        } else {
            format!("\\b(?:{})\\b", joined)
        };
    }

    let mut parts: Vec<String> = Vec::new();
    if !comment_alts.is_empty() {
        parts.push(format!("(?P<comment>{})", comment_alts.join("|")));
    }
    if !string_alts.is_empty() {
        parts.push(format!("(?P<string>{})", string_alts.join("|")));
    }
    parts.push(format!("(?P<number>{})", number_pat));
    if !keyword_alt.is_empty() {
        parts.push(format!("(?P<keyword>{})", keyword_alt));
    }

    if parts.is_empty() {
        return None;
    }

    Regex::new(&parts.join("|")).ok()
}

fn escape_html(s: &str) -> String {
    let mut out = String::with_capacity(s.len());
    for c in s.chars() {
        match c {
            '&' => out.push_str("&amp;"),
            '<' => out.push_str("&lt;"),
            '>' => out.push_str("&gt;"),
            '"' => out.push_str("&quot;"),
            '\'' => out.push_str("&#39;"),
            _ => out.push(c),
        }
    }
    out
}

/// Highlights the source code and returns HTML that's already escaped
/// and ready to insert inside a `<pre><code>...</code></pre>`.
/// No JavaScript involved: all the work happens here, in Rust,
/// at file-generation time.
pub fn highlight_code(content: &str, lang: &str) -> String {
    let rule = rule_for(lang);

    let regex_opt = {
        let mut cache = REGEX_CACHE.lock().unwrap();
        cache
            .entry(lang.to_string())
            .or_insert_with(|| build_regex(&rule))
            .clone()
    };

    let Some(re) = regex_opt else {
        return escape_html(content);
    };

    let mut out = String::with_capacity(content.len() + content.len() / 4);
    let mut last = 0;

    for caps in re.captures_iter(content) {
        let m = caps.get(0).unwrap();
        out.push_str(&escape_html(&content[last..m.start()]));

        let class = if caps.name("comment").is_some() {
            "c"
        } else if caps.name("string").is_some() {
            "s"
        } else if caps.name("number").is_some() {
            "n"
        } else if caps.name("keyword").is_some() {
            "k"
        } else {
            ""
        };

        let escaped_match = escape_html(m.as_str());
        if class.is_empty() {
            out.push_str(&escaped_match);
        } else {
            out.push_str(&format!("<span class=\"{}\">{}</span>", class, escaped_match));
        }
        last = m.end();
    }
    out.push_str(&escape_html(&content[last..]));
    out
}