/**
* 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: ``,
next: comment.length
};
},
// block comment
code => {
const match = code.match(/^\/\*([\s\S]+?)\*\//);
if (!match) return null;
return {
html: ``,
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;
}