/** * Code */ const regexp = /```([\s\S]+?)```/; module.exports = { test: x => new RegExp('^' + regexp.source).test(x), parse: text => { const code = text.match(new RegExp('^' + regexp.source))[0]; return { type: 'code', content: code, code: code.substr(3, code.length - 6).trim(), codeHtml: genHtml(code.substr(3, code.length - 6).trim()) }; } }; function escape(text) { return text .replace(/>/g, '>') .replace(/ b.length - a.length); const symbols = [ '=', '+', '-', '*', '/', '%', '~', '^', '&', '|', '>', '<', '!', '?' ]; // 変数宣言 const varDef = [ 'var', 'const', 'let', 'mut' ]; const elements = [ // comment code => { if (code.substr(0, 2) != '//') return null; const comment = code.match(/^\/\/(.+?)\n/)[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[0] == ' ' && regexp[regexp.length - 1] == ' ') return null; return { html: `/${escape(regexp)}/`, next: regexp.length + 2 }; }, // label code => { if (code[0] != '@') return null; const label = code.match(/^@([a-zA-Z_-]+?)\n/)[0]; return { html: `${label}`, next: label.length }; }, // extract vars (code, i, source, vars) => { const prev = source[i - 1]; if (prev && /[a-zA-Z]/.test(prev)) return null; const match = varDef.filter(v => code.substr(0, v.length + 1) == v + ' ')[0]; if (match) { const bars = code.substr(match.length + 1).match(/^[a-zA-Z0-9_\-,\s]+/); if (bars) { bars[0].replace(/,/g, ' ').split(' ').filter(x => x != '').forEach(bar => { if (!keywords.some(k => k == bar)) { vars.push(bar); } }); } } return null; }, // vars (code, i, source, vars) => { const prev = source[i - 1]; // プロパティは変数と認識させないために、 // 前に . や > (PHPなどではプロパティに -> でアクセスするため) が無いかチェック if (prev && /[a-zA-Z\.>]/.test(prev)) return null; const match = vars.sort((a, b) => b.length - a.length) .filter(v => code.substr(0, v.length) == v)[0]; if (match) { if (/^[a-zA-Z]/.test(code.substr(match.length))) return null; return { html: `${match}`, next: match.length }; } else { return null; } }, // 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 function genHtml(source, lang) { let code = source; let html = ''; let vars = []; let i = 0; function push(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, vars); if (e) { push(e); return true; } }); if (!parsed) { push({ html: escape(code[0]), next: 1 }); } } return html; }