Skip to content

Commit 2d7d313

Browse files
author
Enrico Granata
committed
Embed highlight.js with a custom Aria grammar
1 parent 2dd68d0 commit 2d7d313

3 files changed

Lines changed: 185 additions & 0 deletions

File tree

_config.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,12 @@ titles_from_headings:
4141
include:
4242
- blog/
4343

44+
highlighter: none
45+
markdown: kramdown
46+
kramdown:
47+
syntax_highlighter_opts:
48+
disable: true
49+
4450
# Exclude from processing.
4551
# The following items will not be processed, by default.
4652
# Any item listed under the `exclude:` key here will be automatically added to

_includes/head-custom.html

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/highlight.js@11/styles/github.min.css">
2+
3+
<script type="module">
4+
import hljs from "https://cdn.jsdelivr.net/npm/highlight.js@11/es/core.min.js";
5+
import aria from "/assets/js/aria.js";
6+
7+
hljs.registerLanguage("aria", aria);
8+
9+
document.addEventListener("DOMContentLoaded", () => {
10+
document.querySelectorAll("pre code").forEach(block => {
11+
hljs.highlightElement(block);
12+
});
13+
});
14+
</script>

assets/js/aria.js

Lines changed: 165 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,165 @@
1+
// assets/js/aria.js
2+
/** @typedef {import('highlight.js').HLJSApi} HLJSApi */
3+
/** @param {HLJSApi} hljs */
4+
export default function (hljs) {
5+
// ---------- Lexical ----------
6+
7+
const LINE_COMMENT = hljs.COMMENT('#', '$');
8+
const SHEBANG = hljs.SHEBANG({ binary: '' });
9+
10+
// escapes: \n \t \\ \xNN \u{...}
11+
const ESCAPE = {
12+
className: 'subst',
13+
begin: /\\(?:[nt\\]|x[0-9A-Fa-f]{2}|u\{[0-9A-Fa-f]+\})/
14+
};
15+
const SQ_STRING = { className: 'string', begin: /'/, end: /'/, contains: [ESCAPE] };
16+
const DQ_STRING = { className: 'string', begin: /"/, end: /"/, contains: [ESCAPE] };
17+
18+
// numbers
19+
const HEX_INT = { className: 'number', begin: /0x[0-9A-Fa-f]+(?:_[0-9A-Fa-f]+)*/ };
20+
const OCT_INT = { className: 'number', begin: /0o[0-7]+(?:_[0-7]+)*/ };
21+
const BIN_INT = { className: 'number', begin: /0b[01]+(?:_[01]+)*/ };
22+
const DEC_INT = { className: 'number', begin: /-?(?:0|[1-9][0-9]*)(?:_[0-9]+)*/ };
23+
const DEC_FLOAT = {
24+
className: 'number',
25+
// -? DIGITS '.' DIGITS (exp)?
26+
begin: /-?(?:0|[1-9][0-9]*)\.[0-9]+(?:[eE][+-]?[0-9]+)?/,
27+
illegal: /_/ // no underscores in floats
28+
};
29+
30+
// identifiers (approx)
31+
const IDENT = /[A-Za-z_$][\w$]*/u;
32+
33+
// ---------- Keywords ----------
34+
35+
const KEYWORDS = {
36+
keyword: [
37+
'assert', 'else', 'elsif', 'extension', 'if', 'include', 'match', 'return', 'throw', 'while',
38+
'val', 'var', 'type', 'func', 'operator', 'struct', 'enum', 'mixin', 'import', 'from', 'in',
39+
'and', 'case', 'isa', 'instance', 'reverse', 'try', 'catch', 'break', 'continue', 'for'
40+
].join(' '),
41+
literal: 'true false',
42+
built_in: 'println assert',
43+
type: 'Int Float String Bool Result Maybe Any'
44+
};
45+
46+
// ---------- Declarations & titles ----------
47+
48+
const FUNC_DEF = {
49+
className: 'function',
50+
begin: new RegExp(`\\bfunc\\s+(${IDENT.source})\\s*\\(`),
51+
beginScope: { 1: 'title.function' },
52+
end: /\)/,
53+
excludeEnd: true,
54+
keywords: KEYWORDS,
55+
contains: [LINE_COMMENT, DQ_STRING, SQ_STRING, DEC_FLOAT, HEX_INT, OCT_INT, BIN_INT, DEC_INT]
56+
};
57+
58+
const METHOD_DEF = {
59+
className: 'function',
60+
begin: new RegExp(`\\b(?:instance|type)\\s+func\\s+(${IDENT.source})\\s*\\(`),
61+
beginScope: { 1: 'title.function' },
62+
end: /\)/,
63+
excludeEnd: true,
64+
keywords: KEYWORDS,
65+
contains: [LINE_COMMENT, DQ_STRING, SQ_STRING, DEC_FLOAT, HEX_INT, OCT_INT, BIN_INT, DEC_INT]
66+
};
67+
68+
// operator symbols from grammar
69+
const OP_SYM = /\+\-|u\-|\+|\-|\*|\/|%|<<|>>|==|<=|>=|<|>|\&|\||\^|\(\)|\[\]=|\[\]/;
70+
const OP_DEF = {
71+
className: 'function',
72+
begin: new RegExp(`\\b(?:reverse\\s+)?operator\\s+(${OP_SYM.source})\\s*\\(`),
73+
beginScope: { 1: 'title.function' },
74+
end: /\)/,
75+
excludeEnd: true,
76+
keywords: KEYWORDS,
77+
contains: [LINE_COMMENT]
78+
};
79+
80+
const TYPE_LIKE_DEF = {
81+
className: 'class',
82+
variants: [
83+
{ begin: new RegExp(`\\bstruct\\s+(${IDENT.source})\\b`) },
84+
{ begin: new RegExp(`\\benum\\s+(${IDENT.source})\\b`) },
85+
{ begin: new RegExp(`\\bmixin\\s+(${IDENT.source})\\b`) }
86+
],
87+
beginScope: { 1: 'title.class' }
88+
};
89+
90+
const EXTENSION_DEF = {
91+
begin: new RegExp(`\\bextension\\s+(${IDENT.source})\\b`),
92+
beginScope: { 1: 'type' }
93+
};
94+
95+
const ENUM_CASE_DECL = {
96+
className: 'constant',
97+
begin: new RegExp(`\\bcase\\s+(${IDENT.source})\\b`),
98+
beginScope: { 1: 'constant' }
99+
};
100+
101+
const ENUM_CASE_USE = {
102+
className: 'constant',
103+
begin: new RegExp(`::\\s*(${IDENT.source})`),
104+
beginScope: { 1: 'constant' }
105+
};
106+
107+
// lambdas: |args| => ...
108+
const LAMBDA_HEAD = {
109+
begin: /\|/,
110+
end: /\|\s*=>/,
111+
excludeEnd: true,
112+
contains: [LINE_COMMENT, { className: 'params', begin: /[^|]+/ }],
113+
relevance: 1
114+
};
115+
116+
// postfix try-protocol
117+
const TRY_PROTOCOL = { className: 'operator', begin: /\?\?|\!\!/ };
118+
119+
// compound assign ops used in statements
120+
const ASSIGN_OP = { className: 'operator', begin: /\+=|-=|\*=|\/=|%=/, relevance: 0 };
121+
122+
const INDEXING = {
123+
begin: /\[/, end: /\]/,
124+
contains: [LINE_COMMENT, DQ_STRING, SQ_STRING, DEC_FLOAT, HEX_INT, OCT_INT, BIN_INT, DEC_INT],
125+
relevance: 0
126+
};
127+
128+
const OBJECT_WRITE = {
129+
begin: /\{/, end: /\}/,
130+
contains: [LINE_COMMENT, DQ_STRING, SQ_STRING, INDEXING],
131+
relevance: 0
132+
};
133+
134+
const IMPORTS = {
135+
begin: /\bimport\b/, end: /;/,
136+
keywords: KEYWORDS,
137+
contains: [LINE_COMMENT, { begin: /\bfrom\b/, className: 'keyword' }],
138+
relevance: 0
139+
};
140+
141+
// ---------- REPL transcript support ----------
142+
// Prompt line: 〉...
143+
const REPL_PROMPT = { className: 'meta', begin: /^\s?/m, relevance: 0 };
144+
// Continuation lines: ::: ...
145+
const REPL_CONT = { className: 'meta', begin: /^:::\s?/m, relevance: 0 };
146+
147+
return {
148+
name: 'Aria',
149+
aliases: ['aria'],
150+
keywords: KEYWORDS,
151+
contains: [
152+
SHEBANG,
153+
REPL_PROMPT, REPL_CONT,
154+
LINE_COMMENT,
155+
DQ_STRING, SQ_STRING,
156+
DEC_FLOAT, HEX_INT, OCT_INT, BIN_INT, DEC_INT,
157+
FUNC_DEF, METHOD_DEF, OP_DEF,
158+
TYPE_LIKE_DEF, EXTENSION_DEF,
159+
ENUM_CASE_DECL, ENUM_CASE_USE,
160+
LAMBDA_HEAD, TRY_PROTOCOL, ASSIGN_OP,
161+
INDEXING, OBJECT_WRITE,
162+
IMPORTS
163+
]
164+
};
165+
}

0 commit comments

Comments
 (0)