Expose tokenizer for web highlighting

This commit is contained in:
2026-07-08 19:50:04 +09:00
parent fe127428f9
commit a07f4c48aa
10 changed files with 415 additions and 6 deletions
+60
View File
@@ -25,6 +25,66 @@ export function highlightCode(code, language = '') {
return escapeHtml(code);
}
export function highlightDecodalTokens(source, tokens) {
let html = '';
let cursor = 0;
for (const token of tokens) {
if (token.start > cursor) html += escapeHtml(source.slice(cursor, token.start));
const text = source.slice(token.start, token.end);
const kind = tokenClass(token.kind, text);
html += kind ? `<span class="${kind}">${escapeHtml(text)}</span>` : escapeHtml(text);
cursor = token.end;
}
if (cursor < source.length) html += escapeHtml(source.slice(cursor));
return html;
}
function tokenClass(kind, text) {
if (['let', 'in', 'fn', 'match', 'import', 'default'].includes(kind)) return 'tok-keyword';
if (kind === 'ident' && ['String', 'Int', 'Float', 'Bool', 'Array'].includes(text)) return 'tok-type';
if (kind === 'ident') return '';
if (['true', 'false'].includes(kind)) return 'tok-literal';
if (['int', 'float'].includes(kind)) return 'tok-number';
if (kind === 'string') return 'tok-string';
if (kind === 'regex') return 'tok-regex';
if (kind === 'comment') return 'tok-comment';
if (
[
'equal',
'equal_equal',
'bang',
'bang_equal',
'arrow',
'amp',
'amp_amp',
'pipe_pipe',
'plus',
'plus_plus',
'minus',
'star',
'slash',
'slash_slash',
'gt',
'gte',
'lt',
'lte',
'dot',
'colon',
'semicolon',
'comma',
'l_brace',
'r_brace',
'l_bracket',
'r_bracket',
'l_paren',
'r_paren',
].includes(kind)
) {
return 'tok-operator';
}
return '';
}
export function highlightDecodal(source) {
let html = '';
let index = 0;
+17 -3
View File
@@ -1,5 +1,5 @@
import init, { evaluateProject } from '../wasm/decodal_wasm.js';
import { highlightDecodal } from '../lib/highlight.js';
import init, { evaluateProject, tokenizeSource } from '../wasm/decodal_wasm.js';
import { highlightDecodal, highlightDecodalTokens } from '../lib/highlight.js';
import { playgroundExamples } from './playground-examples.js';
const STORAGE_KEY = 'decodal-playground-project-v1';
@@ -18,6 +18,7 @@ const exampleSelect = document.getElementById('example-select');
const loadExample = document.getElementById('load-example');
const project = loadProject();
let wasmReady = false;
for (const example of playgroundExamples) {
const option = document.createElement('option');
@@ -97,7 +98,18 @@ function setActiveFile(path) {
}
function updateHighlight() {
sourceHighlight.innerHTML = `${highlightDecodal(source.value)}\n`;
sourceHighlight.innerHTML = `${highlightSource(source.value)}\n`;
}
function highlightSource(value) {
if (!wasmReady) return highlightDecodal(value);
try {
const result = JSON.parse(tokenizeSource(value));
if (result.ok) return highlightDecodalTokens(value, result.tokens);
} catch (_error) {
// Fall back to the lightweight JavaScript highlighter.
}
return highlightDecodal(value);
}
function syncHighlightScroll() {
@@ -165,6 +177,8 @@ function compareNodes(a, b) {
try {
await init();
wasmReady = true;
updateHighlight();
run.disabled = false;
status.textContent = '';
execute();
+3
View File
@@ -5,12 +5,15 @@ export function evaluate(source: string): string;
export function evaluateProject(entry: string, files_json: string): string;
export function tokenizeSource(source: string): string;
export type InitInput = RequestInfo | URL | Response | BufferSource | WebAssembly.Module;
export interface InitOutput {
readonly memory: WebAssembly.Memory;
readonly evaluate: (a: number, b: number) => [number, number];
readonly evaluateProject: (a: number, b: number, c: number, d: number) => [number, number];
readonly tokenizeSource: (a: number, b: number) => [number, number];
readonly __wbindgen_externrefs: WebAssembly.Table;
readonly __wbindgen_malloc: (a: number, b: number) => number;
readonly __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;
@@ -40,6 +40,25 @@ export function evaluateProject(entry, files_json) {
wasm.__wbindgen_free(deferred3_0, deferred3_1, 1);
}
}
/**
* @param {string} source
* @returns {string}
*/
export function tokenizeSource(source) {
let deferred2_0;
let deferred2_1;
try {
const ptr0 = passStringToWasm0(source, wasm.__wbindgen_malloc, wasm.__wbindgen_realloc);
const len0 = WASM_VECTOR_LEN;
const ret = wasm.tokenizeSource(ptr0, len0);
deferred2_0 = ret[0];
deferred2_1 = ret[1];
return getStringFromWasm0(ret[0], ret[1]);
} finally {
wasm.__wbindgen_free(deferred2_0, deferred2_1, 1);
}
}
function __wbg_get_imports() {
const import0 = {
__proto__: null,
Binary file not shown.
+1
View File
@@ -3,6 +3,7 @@
export const memory: WebAssembly.Memory;
export const evaluate: (a: number, b: number) => [number, number];
export const evaluateProject: (a: number, b: number, c: number, d: number) => [number, number];
export const tokenizeSource: (a: number, b: number) => [number, number];
export const __wbindgen_externrefs: WebAssembly.Table;
export const __wbindgen_malloc: (a: number, b: number) => number;
export const __wbindgen_realloc: (a: number, b: number, c: number, d: number) => number;