Skip to content

Adding a language

phil/highlight

Highlighter::with() adds a language under its name and aliases, or replaces the language that had them. Names are lower-case letters, digits and _ + # . -, 32 at most; anything else throws InvalidLanguage.

A programming language: a Syntax

Most programming languages are a Syntax read by Code:

use Phil\Highlight\Highlighter;
use Phil\Highlight\Language\Code;
use Phil\Highlight\Language\Quote;
use Phil\Highlight\Language\Syntax;

$lua = new Code(new Syntax(
    'lua',
    aliases: ['luau'],
    keywords: ['and', 'break', 'do', 'else', 'elseif', 'end', 'for', 'function', 'if', 'in', 'local', 'not', 'or', 'repeat', 'return', 'then', 'until', 'while'],
    constants: ['true', 'false', 'nil'],
    builtins: ['print', 'pairs', 'ipairs', 'require'],
    lineComments: ['--'],
    blockComments: ['--[[' => ']]'],
    quotes: [new Quote('[[', ']]', escape: '', multiline: true), new Quote('"'), new Quote("'")],
));

$highlighter = Highlighter::default()->with($lua);
FieldWhat it says
name, aliasesThe names the language is asked for by, in lower case
keywords, types, constants, builtinsWord lists; a word in two lists takes the first of keyword, constant, type, built-in
lineCommentsMarks that open a comment to the end of the line
blockCommentsEach opening mark, to its closing one
quotesQuotes, tried in order — the longer marks first: opening and closing marks, escape character (empty for none), whether it spans lines, whether it holds one character
stringPrefixesLetters or signs that may stand before a quote: rbfu in Python
variablePrefixes, specialVariablesSigils ($), and the characters that make a one-character variable after one (? in $?)
annotationThe sign of decorators and annotations: @
metaMarksMarks read as meta wherever they stand, before comments: #[, <?php
preprocessorWhether # opening a line opens a preprocessor line, continued by a backslash
caseInsensitiveWhether words are matched in any case
capitalizedTypesWhether a name that starts with a capital is a type
regexWhether / opens a regular expression where a value is expected
propertyKeysWhether a quoted text followed by : is a key
shebangWhether a first line opening with #! is meta
commentAfterSpaceWhether a line comment opens only at the start of a word, as # in shell
symbolsWhether : before a word makes a symbol
macrosWhether a word followed by ! is a macro call
callAfterSigns after which a word is a call: `
wordChars, wordSuffixesCharacters a word may hold ($ in JavaScript), and that may end it (? and ! in Ruby)
operators, punctuationThe characters of each

Any other language: a Language

A language that is not a programming language implements Language: name(), aliases() and tokenize(), which returns Tokens whose texts, joined, are the source — never throwing, whatever the source. The shipped Ini and Diff are short models. A tokenizer that mixes languages takes the others in its constructor, as Dockerfile takes the shell and Twig the markup and expression languages.

use Phil\Highlight\Language;
use Phil\Highlight\Token;
use Phil\Highlight\TokenType;

final readonly class Csv implements Language
{
    public function name(): string { return 'csv'; }

    public function aliases(): array { return ['tsv']; }

    public function tokenize(string $code): array
    {
        $tokens = [];

        foreach (preg_split('/([,\t\n])/', $code, -1, PREG_SPLIT_DELIM_CAPTURE) ?: [] as $part) {
            if ('' !== $part) {
                $tokens[] = new Token($part, str_contains(",\t\n", $part) ? TokenType::Punctuation : TokenType::String);
            }
        }

        return $tokens;
    }
}

Replacing a shipped language

A language added under a shipped name replaces it, aliases included when it claims them: with(new Code(new Syntax('sql', aliases: ['postgresql'], …))) reads SQL and PostgreSQL its way, while the other SQL aliases keep the shipped tokenizer.