nadesiko3
Version:
Japanese Programming Language
320 lines (319 loc) • 13.5 kB
JavaScript
/* eslint-disable @typescript-eslint/no-explicit-any */
/**
* なでしこ3字句解析のためのルール
*/
import { josiRE, removeJosiMap } from './nako_josi_list.mjs';
const kanakanji = /^[\u3005\u4E00-\u9FCF_a-zA-Z0-9ァ-ヶー\u2460-\u24FF\u2776-\u277F\u3251-\u32BF]+/;
const hira = /^[ぁ-ん]/;
const allHiragana = /^[ぁ-ん]+$/;
const wordHasIjoIka = /^.+(以上|以下|超|未満)$/;
const wordSpecial = /^(かつ|または)/;
const errorRead = (ch) => {
return function () { throw new Error('突然の『' + ch + '』があります。'); };
};
// 数値の後の単位は自動的に省略されるルール (#994)
export const unitRE = /^(円|ドル|元|歩|㎡|坪|度|℃|°|個|つ|本|冊|才|歳|匹|枚|皿|セット|羽|人|件|行|列|機|品|m|mm|cm|km|g|kg|t|b|mb|kb|gb)/;
// CSSの単位であれば自動的に文字列に変換するルール (#1811)
export const cssUnitRE = /^(px|em|ex|rem|vw|vh|vmin|vmax)/;
/** トークンに区切るルールの一覧 */
export const rules = [
// 上から順にマッチさせていく
{ name: 'ここまで', pattern: /^;;;/ }, // #925
{ name: 'eol', pattern: /^\n/ },
{ name: 'eol', pattern: /^;/ },
// eslint-disable-next-line no-control-regex
{ name: 'space', pattern: /^(\x20|\x09|・|⎿ |└||)+/ }, // #877,#1015
{ name: 'comma', pattern: /^,/ },
{ name: 'line_comment', pattern: /^#[^\n]*/ },
{ name: 'line_comment', pattern: /^\/\/[^\n]*/ },
{ name: 'range_comment', pattern: /^\/\*/, cbParser: cbRangeComment },
{ name: 'def_test', pattern: /^●テスト:/ },
{ name: 'def_func', pattern: /^●/ },
{ name: '…', pattern: /^…/ }, // 範囲オブジェクト(#1704)
{ name: '…', pattern: /^\.{2,3}/ }, // 範囲オブジェクト(#1704)
// 多倍長整数リテラルの判定。整数の末尾に「n」がついているだけな為、数値判定より上に書かないとただの整数にされる
{ name: 'bigint', pattern: /^0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*n/, readJosi: true },
{ name: 'bigint', pattern: /^0[oO][0-7]+(_[0-7]+)*n/, readJosi: true },
{ name: 'bigint', pattern: /^0[bB][0-1]+(_[0-1]+)*n/, readJosi: true },
{ name: 'bigint', pattern: /^\d+(_\d+)*?n/, readJosi: true },
// 16進/8進/2進法の数値判定 --- この後nako_lexerにて単位を読む処理が入る(#994)
{ name: 'number', pattern: /^0[xX][0-9a-fA-F]+(_[0-9a-fA-F]+)*/, readJosi: true, cb: parseNumber },
{ name: 'number', pattern: /^0[oO][0-7]+(_[0-7]+)*/, readJosi: true, cb: parseNumber },
{ name: 'number', pattern: /^0[bB][0-1]+(_[0-1]+)*/, readJosi: true, cb: parseNumber },
// 下の三つは小数点が挟まっている場合、小数点から始まっている場合、小数点がない場合の十進法の数値にマッチする
{ name: 'number', pattern: /^\d+(_\d+)*\.(\d+(_\d+)*)?([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
{ name: 'number', pattern: /^\.\d+(_\d+)*([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
{ name: 'number', pattern: /^\d+(_\d+)*([eE][+|-]?\d+(_\d+)*)?/, readJosi: true, cb: parseNumber },
{ name: 'ここから', pattern: /^(ここから),?/ },
{ name: 'ここまで', pattern: /^(ここまで|💧)/ },
{ name: 'もし', pattern: /^もしも?/ },
// 「ならば」は助詞として定義している
{ name: '違えば', pattern: /^違(えば)?/ },
// 「回」「間」「繰返」「反復」「抜」「続」「戻」「代入」「条件分岐」などは NakoLexer._replaceWord で word から変換
// @see nako_reserved_words.js
{ name: 'shift_r0', pattern: /^>>>/ },
{ name: 'shift_r', pattern: /^>>/ },
{ name: 'shift_l', pattern: /^<</ },
{ name: '===', pattern: /^===/ }, // #999
{ name: '!==', pattern: /^!==/ }, // #999
{ name: 'gteq', pattern: /^(≧|>=|=>)/ },
{ name: 'lteq', pattern: /^(≦|<=|=<)/ },
{ name: 'noteq', pattern: /^(≠|<>|!=)/ },
{ name: '←', pattern: /^(←|<--)/ }, // 矢印 --- ただし(core#140)で廃止された演算子(#891,#899)
{ name: 'eq', pattern: /^(==|🟰🟰)/ },
{ name: 'eq', pattern: /^(=|🟰)/ },
{ name: 'line_comment', pattern: /^(!|💡)(インデント構文|ここまでだるい|DNCLモード|DNCL2モード|DNCL2)[^\n]*/ }, // #1184
{ name: 'not', pattern: /^(!|💡)/ }, // #1184 #1457
{ name: 'gt', pattern: /^>/ },
{ name: 'lt', pattern: /^</ },
{ name: 'and', pattern: /^(かつ|&&|and\s)/ },
{ name: 'or', pattern: /^(または|或いは|あるいは|or\s|\|\|)/ },
{ name: '@', pattern: /^@/ },
{ name: '+', pattern: /^\+/ },
{ name: '-', pattern: /^-/ },
{ name: '**', pattern: /^(××|\*\*)/ }, // Python風べき乗演算子
{ name: '*', pattern: /^(×|\*)/ },
{ name: '÷÷', pattern: /^÷÷/ }, // 整数の割り算
{ name: '÷', pattern: /^(÷|\/)/ }, // 普通の割り算
{ name: '%', pattern: /^%/ },
{ name: '^', pattern: /^\^/ },
{ name: '&', pattern: /^&/ },
{ name: '[', pattern: /^\[/ },
{ name: ']', pattern: /^]/, readJosi: true },
{ name: '(', pattern: /^\(/ },
{ name: ')', pattern: /^\)/, readJosi: true },
{ name: '|', pattern: /^\|/ },
{ name: '??', pattern: /^\?\?/ }, // 「表示」のエイリアス #1745
{ name: 'word', pattern: /^\$\{.+?\}/, cbParser: src => cbExtWord(src) }, // 特別名前トークン(#1836)(#672)
{ name: '$', pattern: /^(\$|\.)/ }, // プロパティアクセス (#1793)(#1807)
{ name: 'string', pattern: /^🌿/, cbParser: src => cbString('🌿', '🌿', src) },
{ name: 'string_ex', pattern: /^🌴/, cbParser: src => cbString('🌴', '🌴', src) },
{ name: 'string_ex', pattern: /^「/, cbParser: src => cbString('「', '」', src) },
{ name: 'string', pattern: /^『/, cbParser: src => cbString('『', '』', src) },
{ name: 'string_ex', pattern: /^“/, cbParser: src => cbString('“', '”', src) },
{ name: 'string_ex', pattern: /^"/, cbParser: src => cbString('"', '"', src) },
{ name: 'string', pattern: /^'/, cbParser: src => cbString('\'', '\'', src) },
{ name: '」', pattern: /^」/, cbParser: errorRead('」') }, // error
{ name: '』', pattern: /^』/, cbParser: errorRead('』') }, // error
{ name: 'func', pattern: /^\{関数\},?/ },
{ name: '{', pattern: /^\{/ },
{ name: '}', pattern: /^\}/, readJosi: true },
{ name: ':', pattern: /^:/ },
{ name: '_eol', pattern: /^_\s*\n/ },
{ name: 'dec_lineno', pattern: /^‰/ },
// 絵文字変数 = (絵文字)英数字*
{ name: 'word', pattern: /^[\uD800-\uDBFF][\uDC00-\uDFFF][_a-zA-Z0-9]*/, readJosi: true },
{ name: 'word', pattern: /^[\u1F60-\u1F6F][_a-zA-Z0-9]*/, readJosi: true }, // 絵文字
{ name: 'word', pattern: /^《.+?》/, readJosi: true }, // 《特別名前トークン》(#672)
// 単語句
{
name: 'word',
pattern: /^[_a-zA-Z\u3005\u4E00-\u9FCFぁ-んァ-ヶ\u2460-\u24FF\u2776-\u277F\u3251-\u32BF]/,
cbParser: cbWordParser
}
];
export function trimOkurigana(s) {
// ひらがなから始まらない場合、送り仮名を削除。(例)置換する
if (!hira.test(s)) {
return s.replace(/[ぁ-ん]+/g, '');
}
// 全てひらがな? (例) どうぞ
if (allHiragana.test(s)) {
return s;
}
// 末尾のひらがなのみ (例)お願いします →お願
return s.replace(/[ぁ-ん]+$/g, '');
}
// Utility for Rule
function cbRangeComment(src) {
let res = '';
const josi = '';
let numEOL = 0;
src = src.substring(2); // skip /*
const i = src.indexOf('*/');
if (i < 0) { // not found
res = src;
src = '';
}
else {
res = src.substring(0, i);
src = src.substring(i + 2);
}
// 改行を数える
for (let i = 0; i < res.length; i++) {
if (res.charAt(i) === '\n') {
numEOL++;
}
}
res = res.replace(/(^\s+|\s+$)/, ''); // trim
return { src, res, josi, numEOL };
}
/**
* @param {string} src
*/
function cbWordParser(src, isTrimOkurigana = true) {
/*
kanji = [\u3005\u4E00-\u9FCF]
hiragana = [ぁ-ん]
katakana = [ァ-ヶー]
emoji = [\u1F60-\u1F6F]
uni_extra = [\uD800-\uDBFF] [\uDC00-\uDFFF]
alphabet = [_a-zA-Z]
numchars = [0-9]
*/
let res = '';
let josi = '';
while (src !== '') {
// 1文字以上のとき
if (res.length > 0) {
// 「かつ」「または」なら分割する (#1379 core#84)
const jsw = wordSpecial.exec(src);
if (jsw) {
break;
}
// 助詞の判定
const j = josiRE.exec(src);
if (j) {
josi = j[0].replace(/^\s+/, '');
src = src.substring(j[0].length);
// 助詞の直後にある「,」を飛ばす #877
if (src.charAt(0) === ',') {
src = src.substring(1);
}
break;
}
}
// カタカナ漢字英数字か?
const m = kanakanji.exec(src);
if (m) {
res += m[0];
src = src.substring(m[0].length);
continue;
}
// ひらがな?
const h = hira.test(src);
if (h) {
res += src.charAt(0);
src = src.substring(1);
continue;
}
break; // other chars
}
// --- 単語分割における特殊ルール ---
// 「間」の特殊ルール (#831)
// 「等しい間」や「一致する間」なら「間」をsrcに戻す。ただし「システム時間」はそのままにする。
if (/[ぁ-ん]間$/.test(res)) {
src = res.charAt(res.length - 1) + src;
res = res.slice(0, -1);
}
// 「以上」「以下」「超」「未満」 #918
const ii = wordHasIjoIka.exec(res);
if (ii) {
src = ii[1] + josi + src;
josi = '';
res = res.substring(0, res.length - ii[1].length);
}
// 「もの」構文 #1614
if (josi.substring(0, 2) === 'もの') {
josi = josi.substring(2);
}
// 助詞「こと」「である」「です」などは「**すること」のように使うので削除 #936 #939 #974
if (removeJosiMap[josi]) {
josi = '';
}
// 送り仮名の省略ルール
// 漢字カタカナ英語から始まる語句 --- 送り仮名を省略
if (isTrimOkurigana) {
res = trimOkurigana(res);
}
// 助詞だけの語句の場合
if (res === '' && josi !== '') {
res = josi;
josi = '';
}
return { src, res, josi, numEOL: 0 };
}
function cbString(beginTag, closeTag, src) {
let res = '';
let josi = '';
let numEOL = 0;
src = src.substring(beginTag.length); // skip beginTag
const i = src.indexOf(closeTag);
if (i < 0) { // not found
res = src;
src = '';
}
else {
res = src.substring(0, i);
src = src.substring(i + closeTag.length);
// res の中に beginTag があればエラーにする #953
if (res.indexOf(beginTag) >= 0) {
if (beginTag === '『') {
throw new Error('「『」で始めた文字列の中に「『」を含めることはできません。');
}
else {
throw new Error(`『${beginTag}』で始めた文字列の中に『${beginTag}』を含めることはできません。`);
}
}
}
// 文字列直後の助詞を取得
const j = josiRE.exec(src);
if (j) {
josi = j[0].replace(/^\s+/, '');
src = src.substring(j[0].length);
// 助詞の後のカンマ #877
if (src.charAt(0) === ',') {
src = src.substring(1);
}
}
// 助詞「こと」「である」「です」などは「**すること」のように使うので削除 #936 #939 #974
if (removeJosiMap[josi]) {
josi = '';
}
// 「もの」構文 (#1614)
if (josi.substring(0, 2) === 'もの') {
josi = josi.substring(2);
}
// 改行を数える
for (let i = 0; i < res.length; i++) {
if (res.charAt(i) === '\n') {
numEOL++;
}
}
return { src, res, josi, numEOL };
}
function cbExtWord(src) {
let res = '';
let josi = '';
let numEOL = 0;
src = src.substring(2); // skip '${'
const i = src.indexOf('}');
if (i < 0) { // not found
throw new Error('変数名の終わりが見つかりません。');
}
res = src.substring(0, i);
src = src.substring(i + 1);
// 文字列直後の助詞を取得
const j = josiRE.exec(src);
if (j) {
josi = j[0].replace(/^\s+/, '');
src = src.substring(j[0].length);
// 助詞の後のカンマ #877
if (src.charAt(0) === ',') {
src = src.substring(1);
}
}
// 改行を数える(あり得ないけど)
for (let i = 0; i < res.length; i++) {
if (res.charAt(i) === '\n') {
numEOL++;
}
}
if (numEOL > 0) {
throw new Error('変数名に改行を含めることはできません。');
}
return { src, res, josi, numEOL };
}
function parseNumber(n) {
return Number(n.replace(/_/g, ''));
}