Skip to content

Instantly share code, notes, and snippets.

@dzaima
Last active October 20, 2022 16:27
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save dzaima/35ca0ce12b5e215a62460f00e693984f to your computer and use it in GitHub Desktop.
Save dzaima/35ca0ce12b5e215a62460f00e693984f to your computer and use it in GitHub Desktop.
// ==UserScript==
// @name BQN highlighting
// @namespace https://github.com/dzaima
// @version 0.2
// @description Syntax highlighting for BQN code on GitHub
// @author dzaima
// @match https://github.com/*
// @grant none
// ==/UserScript==
let dark = true;
let prevLink = "";
document.body.classList.add(dark? 'dt' : 'lt');
function upd() {
'use strict';
let newLink = window.location.href;
if (newLink.includes('#')) newLink = newLink.substring(0, newLink.indexOf('#'));
if (newLink==prevLink || !newLink.endsWith(".bqn")) return;
if (document.getElementsByClassName("highlight tab-size js-file-line-container").length==0) return;
prevLink = newLink;
let fnames = document.getElementsByClassName('final-path');
if (fnames.length!=1 || !fnames[0].innerText.endsWith('.bqn')) return;
let els = [...document.getElementsByClassName("blob-code blob-code-inner js-file-line")];
if (els.length) {
let table = document.querySelector("table.highlight.js-file-line-container");
let tableP = table.parentElement;
table.remove();
els.forEach(c=>{
let txt = [...c.innerText];
c.innerHTML = colorCode(txt, parseBQN(txt), 'B');
});
tableP.appendChild(table);
}
}
setTimeout(() => {
setInterval(upd, 1000);
upd();
}, 1000);
// copy-pasted from paste:
let htmlMap = {};
function html(str) {
let res = "";
for (let chr of str) {
if (chr>='0'&chr<='9' | chr>='a'&chr<='z' | chr>='A'&chr<='Z' | chr==' ' | chr=='_') res+= chr;
else if (chr=='\n') res+= '<br>';
else {
let m = htmlMap[chr];
if (!m) m = htmlMap[chr] = new Option(chr).innerHTML;
res+= m;
}
}
return res;
}
function colorCode(str, cols, prefix) {
const wrap = (sub,col) => `<span class=${prefix+col}>${html(sub)}</span>`;
let code = "";
let pcol = cols[0];
let li = 0;
for (let i = 0; i < str.length; i++) {
let ncol = cols[i];
if (ncol && pcol!=ncol) {
code+= wrap(str.slice(li,i), pcol);
li = i;
pcol = ncol;
}
}
if (pcol) code+= wrap(str.slice(li), pcol);
return code;
}
function parseBQN(str) {
str = str;
const regC = '0';
const fnsC = '1'; let fns = "!+-×÷⋆*√⌊⌈∧∨¬|=≠≤<>≥≡≢⊣⊢⥊∾≍⋈↑↓↕⌽⍉/⍋⍒⊏⊑⊐⊒∊⍷⊔«»⍎⍕";
const mopC = '2'; let mop = "`˜˘¨⁼⌜´˝˙";
const dopC = '3'; const dop = "∘⊸⟜○⌾⎉⚇⍟⊘◶⎊";
const namC = '4'; const nam = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz_";
const digC = '5'; const dig = "0123456789π∞"; const digS = dig+"¯."; const digM = "eEiI";
const arrC = '6'; const arr = "·⍬‿⦃⦄⟨⟩[]@";
const dfnC = '7'; const dfn = [..."𝕨𝕩𝔽𝔾𝕎𝕏𝕗𝕘𝕣ℝ𝕤𝕊{}:"]; // double-strucks are 2-byters
const strC = '8'; // '' ""
const dmdC = 'D'; const dmd = "←↩,⋄→⇐";
const comC = 'C'; // #
if (!window.BQNStyle) {
const s = document.createElement("style");
s.id = "BQNStyle";
s.innerText=`
body.dt .B${regC} { color: #D2D2D2; } body.lt .B${regC} { color: #000000; }
body.dt .B${namC} { color: #D2D2D2; } body.lt .B${namC} { color: #000000; }
body.dt .B${comC} { color: #898989; } body.lt .B${comC} { color: #6A737D; }
body.dt .B${digC} { color: #ff6E6E; } body.lt .B${digC} { color: #005CC5; }
body.dt .B${arrC} { color: #DD99FF; } body.lt .B${arrC} { color: #005CC5; }
body.dt .B${dmdC} { color: #FFFF00; } body.lt .B${dmdC} { color: #0000FF; }
body.dt .B${strC} { color: #6A9FFB; } body.lt .B${strC} { color: #032F62; }
body.dt .B${fnsC} { color: #57d657; } body.lt .B${fnsC} { color: #D73A49; }
body.dt .B${mopC} { color: #EB60DB; } body.lt .B${mopC} { color: #ED5F00; }
body.dt .B${dopC} { color: #FFDD66; } body.lt .B${dopC} { color: #C82C00; }
body.dt .B${dfnC} { color: #AA77BB; } body.lt .B${dfnC} { color: #A906D4; }
`;
document.body.appendChild(s);
}
const res = new Array(str.length).fill();
res[0] = regC;
for (let i = 0; i < str.length; ) {
const p = str[i-1]||'\0';
const c = str[i ];
const n = str[i+1]||'\0';
if (digS.includes(c)) {
res[i] = digC; i++;
while(dig.includes(str[i]) || str[i]=='.' || digM.includes(str[i])&&digS.includes(str[i+1])) i++;
continue;
}
else if (fns.includes(c)) res[i] = fnsC;
else if (mop.includes(c)) res[i] = mopC;
else if (dop.includes(c)) res[i] = dopC;
else if (dfn.includes(c)) res[i] = dfnC;
else if (arr.includes(c)) res[i] = arrC;
else if (dmd.includes(c)) res[i] = dmdC;
else if (nam.includes(c) || c=='•') {
let fst = i;
if (str[i] == '•') i++;
let cs = str[i];
while(nam.includes(str[i]) || dig.includes(str[i])) i++;
let ce = str[i-1];
res[fst] = cs=='_'? (ce=='_'? dopC : mopC) : (cs>='A'&&cs<='Z'?fnsC : namC);
continue;
}
else if (c=="'" || c=='"') {
res[i] = strC; i++;
let q = c;
while(str[i] && str[i]!=q && str[i]!='\n') i++;
}
else if (c=='#') {
res[i] = comC;
while(str[i] && str[i]!='\n') i++;
}
else if (!' \n\t'.includes(c)) res[i] = regC;
i++;
}
return res;
}
@yewscion
Copy link

yewscion commented Aug 5, 2022

Hello,

Just wanted You to be aware: Based on the above script (thank You for providing this, by the way!) and the already-existing APL support in the project, I have made a pull request to add BQN support to PrismJS (mostly because I want to post BQN code in my blog).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment