import { capitalize } from "../../../prelude/string"; function escape(text: string) { return text .replace(/>/g, '>') .replace(/ k.toUpperCase())) .sort((a, b) => b.length - a.length); const symbols = [ '=', '+', '-', '*', '/', '%', '~', '^', '&', '|', '>', '<', '!', '?' ]; type Token = { html: string next: number }; type Element = (code: string, i: number, source: string) => (Token | null); const elements: Element[] = [ // comment code => { if (code.substr(0, 2) != '//') return null; const match = code.match(/^\/\/(.+?)(\n|$)/); if (!match) return null; const comment = match[0]; return { html: `${escape(comment)}`, next: comment.length }; }, // block comment code => { const match = code.match(/^\/\*([\s\S]+?)\*\//); if (!match) return null; return { html: `${escape(match[0])}`, next: match[0].length }; }, // string code => { if (!/^['"`]/.test(code)) return null; const begin = code[0]; let str = begin; let thisIsNotAString = false; for (let i = 1; i < code.length; i++) { const char = code[i]; if (char == '\\') { str += char; str += code[i + 1] || ''; i++; continue; } else if (char == begin) { str += char; break; } else if (char == '\n' || i == (code.length - 1)) { thisIsNotAString = true; break; } else { str += char; } } if (thisIsNotAString) { return null; } else { return { html: `${escape(str)}`, next: str.length }; } }, // regexp code => { if (code[0] != '/') return null; let regexp = ''; let thisIsNotARegexp = false; for (let i = 1; i < code.length; i++) { const char = code[i]; if (char == '\\') { regexp += char; regexp += code[i + 1] || ''; i++; continue; } else if (char == '/') { break; } else if (char == '\n' || i == (code.length - 1)) { thisIsNotARegexp = true; break; } else { regexp += char; } } if (thisIsNotARegexp) return null; if (regexp == '') return null; if (regexp.startsWith(' ') && regexp.endsWith(' ')) return null; return { html: `/${escape(regexp)}/`, next: regexp.length + 2 }; }, // label code => { if (code[0] != '@') return null; const match = code.match(/^@([a-zA-Z_-]+?)\n/); if (!match) return null; const label = match[0]; return { html: `${label}`, next: label.length }; }, // number (code, i, source) => { const prev = source[i - 1]; if (prev && /[a-zA-Z]/.test(prev)) return null; if (!/^[\-\+]?[0-9\.]+/.test(code)) return null; const match = code.match(/^[\-\+]?[0-9\.]+/)[0]; if (match) { return { html: `${match}`, next: match.length }; } else { return null; } }, // nan (code, i, source) => { const prev = source[i - 1]; if (prev && /[a-zA-Z]/.test(prev)) return null; if (code.substr(0, 3) == 'NaN') { return { html: `NaN`, next: 3 }; } else { return null; } }, // method code => { const match = code.match(/^([a-zA-Z_-]+?)\(/); if (!match) return null; if (match[1] == '-') return null; return { html: `${match[1]}`, next: match[1].length }; }, // property (code, i, source) => { const prev = source[i - 1]; if (prev != '.') return null; const match = code.match(/^[a-zA-Z0-9_-]+/); if (!match) return null; return { html: `${match[0]}`, next: match[0].length }; }, // keyword (code, i, source) => { const prev = source[i - 1]; if (prev && /[a-zA-Z]/.test(prev)) return null; const match = keywords.filter(k => code.substr(0, k.length) == k)[0]; if (match) { if (/^[a-zA-Z]/.test(code.substr(match.length))) return null; return { html: `${match}`, next: match.length }; } else { return null; } }, // symbol code => { const match = symbols.filter(s => code[0] == s)[0]; if (match) { return { html: `${match}`, next: 1 }; } else { return null; } } ]; // specify lang is todo export default (source: string, lang?: string) => { let code = source; let html = ''; let i = 0; function push(token: Token) { html += token.html; code = code.substr(token.next); i += token.next; } while (code != '') { const parsed = elements.some(el => { const e = el(code, i, source); if (e) { push(e); return true; } else { return false; } }); if (!parsed) { push({ html: escape(code[0]), next: 1 }); } } return html; };