skynovel
Version:
webgl novelgame framework
148 lines (124 loc) • 5.49 kB
text/typescript
/* ***** BEGIN LICENSE BLOCK *****
Copyright (c) 2019-2020 Famibee (famibee.blog38.fc2.com)
This software is released under the MIT License.
http://opensource.org/licenses/mit-license.php
** ***** END LICENSE BLOCK ***** */
import {Script, HArg} from './CmnInterface';
import {RubySpliter} from './RubySpliter';
export class Grammar {
constructor() {this.setEscape('');}
REG_TOKEN : RegExp;
setEscape(ce: string) {
if (this.hC2M && (ce in this.hC2M)) throw '[エスケープ文字] char【'+ ce +'】が登録済みの括弧マクロまたは一文字マクロです';
// 1059 match 13935 step (8ms) https://regex101.com/r/ygXx16/6
this.REG_TOKEN = new RegExp(
(ce ?`\\${ce}\\S|`:'')+ // エスケープシーケンス
'\\n+'+ // 改行
'|\\t+'+ // タブ
`|\\[let_ml\\s+[^\\]]+\\]`+
`.+?`+ // [let_ml]〜[endlet_ml]間のテキスト
`(?=\\[endlet_ml[\\]\\s])`+
`|\\[(?:[^"'#;\\]]+|(["'#]).*?\\1|;[^\\n]*)*?]`+ // タグ
'|;[^\\n]*'+ // コメント
'|&[^&\\n]+&'+ // &表示&
'|&&?[^;\\n\\t&]+'+ // &代入
'|^\\*\\w+'+ // ラベル
`|[^\\n\\t\\[;${ce?`\\${ce}`:''}]+`, // 本文
'gs');
RubySpliter.setEscape(ce);
this.REG_CANTC2M = new RegExp(`[\\w\\s;[\\]*=&|《》${ce ?`\\${ce}` :''}]`);
this.REG_TOKEN_NOTXT = new RegExp(`[\\n\\t;\\[*&${ce ?`\\${ce}` :''}]`);
}
// 括弧マクロの定義
bracket2macro(hArg: HArg, script: Script, idxToken: number) {
const name = hArg.name;
if (! name) throw '[bracket2macro] nameは必須です';
const text = hArg.text;
if (! text) throw '[bracket2macro] textは必須です';
if (text.length !== 2) throw '[bracket2macro] textは括弧の前後を示す二文字を指定してください';
this.hC2M = this.hC2M ?? {};
const op = text.charAt(0);
const cl = text.charAt(1);
if (op in this.hC2M) throw '[bracket2macro] text【'+ op +'】が登録済みの括弧マクロまたは一文字マクロです';
if (cl in this.hC2M) throw '[bracket2macro] text【'+ cl +'】が登録済みの括弧マクロまたは一文字マクロです';
this.REG_CANTC2M.lastIndex = 0;
if (this.REG_CANTC2M.test(op)) throw '[bracket2macro] text【'+ op +'】は括弧マクロに使用できない文字です';
this.REG_CANTC2M.lastIndex = 0;
if (this.REG_CANTC2M.test(cl)) throw '[bracket2macro] text【'+ cl +'】は括弧マクロに使用できない文字です';
this.hC2M[cl] = '0'; // チェック用ダミー
this.hC2M[op] = `[${name} text=`;
this.addC2M(`\\${op}[^\\${cl}]*\\${cl}`, `\\${op}\\${cl}`);
this.replaceScr_C2M_And_let_ml(script, idxToken);
}
// 一文字マクロの定義
char2macro(hArg: HArg, hTag: HArg, script: Script, idxToken: number) {
const char = hArg.char;
if (! char) throw '[char2macro] charは必須です';
this.hC2M = this.hC2M ?? {};
if (char in this.hC2M) throw '[char2macro] char【'+ char +'】が登録済みの括弧マクロまたは一文字マクロです';
this.REG_CANTC2M.lastIndex = 0;
if (this.REG_CANTC2M.test(char)) throw '[char2macro] char【'+ char +'】は一文字マクロに使用できない文字です';
const name = hArg.name;
if (! name) throw '[char2macro] nameは必須です';
if (! (name in hTag)) throw `[char2macro] 未定義のタグ又はマクロ[${name}]です`;
this.hC2M[char] = `[${name}]`;
this.addC2M(`\\${char}`, `\\${char}`);
this.replaceScr_C2M_And_let_ml(script, idxToken);
}
private REG_CANTC2M : RegExp;
private regC2M = new RegExp('');
private regStrC2M = '';
private regStrC2M4not = '';
addC2M(a: string, b: string) {
this.regStrC2M += `${a}|`;
this.regStrC2M4not += `${b}`;
this.regC2M = new RegExp(
`(${this.regStrC2M}[^${this.regStrC2M4not}]+)`, 'g');
}
private hC2M : {[char: string]: string};
private REG_TOKEN_NOTXT : RegExp;
replaceScr_C2M_And_let_ml = (scr: Script, start_idx = 0)=> {
if (! this.hC2M) return;
for (let i=scr.len- 1; i >= start_idx; --i) {
const token = scr.aToken[i];
this.REG_TOKEN_NOTXT.lastIndex = 0;
if (this.REG_TOKEN_NOTXT.test(token.charAt(0))) continue;
const lnum = scr.aLNum[i];
const a = token.match(this.regC2M);
if (! a) continue;
let del = 1;
for (let j=a.length -1; j>=0; --j) {
let ch = a[j];
const macro = this.hC2M[ch.charAt(0)];
if (macro) {
ch = macro +((macro.slice(-1) === ']')
? ''
: (`'${ch.slice(1, -1)}']`));
// 文字列は半角空白を意識して''で囲むが、いずれ変えたい場合がある?
}
scr.aToken.splice(i, del, ch);
scr.aLNum.splice(i, del, lnum);
del = 0;
}
}
scr.len = scr.aToken.length;
}
static splitAmpersand(token: string): {
name: string;
text: string;
cast: string | null;
} { // テスト用にpublic
const equa = token.replace(/==/g, '=').replace(/!=/g, '≠').split('=');
// != を弾けないので中途半端ではある
const cnt_equa = equa.length;
if (cnt_equa < 2 || cnt_equa > 3) throw '「&計算」書式では「=」指定が一つか二つ必要です';
if (equa[1].charAt(0) === '&') throw '「&計算」書式では「&」指定が不要です';
return {
name: equa[0].replace(/=/g, '==').replace(/≠/g, '!='),
text: equa[1].replace(/=/g, '==').replace(/≠/g, '!='),
cast: ((cnt_equa === 3) ?equa[2].trim() :null)
};
}
// 47 match 959 step (1ms) https://regex101.com/r/TKk1Iz/4
static readonly REG_TAG = /\[(?<name>[^\s;\]]+)\s*(?<args>(?:[^"'#\]]+|(["'#]).*?\3)*?)]/;
}