UNPKG

nadesiko3

Version:
388 lines (387 loc) 13.7 kB
import { NewEmptyToken } from './nako_types.mjs'; /** * なでしこの構文解析のためのユーティリティクラス */ export class NakoParserBase { constructor(logger) { this.logger = logger; this.stackList = []; // 関数定義の際にスタックが混乱しないように整理する this.tokens = []; this.usedFuncs = new Set(); /** @type {import('./nako3.mjs').Ast[]} */ this.stack = []; this.index = 0; /** トークン出現チェック(accept関数)に利用する * @type {import('./nako3.mjs').Ast[]} */ this.y = []; /** モジュル名 @type {string} */ this.modName = 'inline'; this.namespaceStack = []; /** * 利用するモジュールの名前一覧 * @type {Array<string>} */ this.modList = []; /** グローバル変数・関数の確認用 */ this.funclist = new Map(); this.funcLevel = 0; this.usedAsyncFn = false; // asyncFnの呼び出しがあるかどうか /** * ローカル変数の確認用 * @type {Object.<string,Object>} */ this.localvars = new Map([['それ', { type: 'var', value: '' }]]); /** コード生成器の名前 @type {string} */ this.genMode = 'sync'; // #637 /** 配列のインデックスが先頭要素(#1140) @type {int} */ this.arrayIndexFrom = 0; /** 配列のインデックス順序を反対にするか(#1140) @type {boolean} */ this.flagReverseArrayIndex = false; /** 配列を自動的に初期化するか(#1140) @type {boolean} */ this.flagCheckArrayInit = false; /** 最近呼び出した関数(余剰エラーの報告に使う) */ this.recentlyCalledFunc = []; // 構文解析に利用する - 現在計算式を読んでいるかどうか this.isReadingCalc = false; // エクスポート設定が未設定の関数・変数に対する既定値 this.isExportDefault = true; this.isExportStack = []; this.moduleExport = new Map(); this.isModifiedNodes = false; this.init(); } init() { this.funclist = new Map(); // 関数の一覧 this.moduleExport = new Map(); this.reset(); } reset() { this.tokens = []; // 字句解析済みのトークンの一覧を保存 this.index = 0; // tokens[] のどこまで読んだかを管理する this.stack = []; // 計算用のスタック ... 直接は操作せず、pushStack() popStack() を介して使う this.y = []; // accept()で解析済みのトークンを配列で得るときに使う this.genMode = 'sync'; // #637, #1056 } setFuncList(funclist) { this.funclist = funclist; } setModuleExport(moduleexport) { this.moduleExport = moduleexport; } /** * 特定の助詞を持つ要素をスタックから一つ下ろす、指定がなければ末尾を下ろす * @param {string[]} josiList 下ろしたい助詞の配列 */ popStack(josiList = undefined) { if (!josiList) { const t = this.stack.pop(); if (t) { return t; } return null; } // josiList にマッチする助詞を探す for (let i = this.stack.length - 1; i >= 0; i--) { const t = this.stack[i]; if (josiList.length === 0 || josiList.indexOf(t.josi) >= 0) { this.stack.splice(i, 1); // remove stack this.logger.trace('POP :' + JSON.stringify(t)); return t; } } // 該当する助詞が見つからなかった場合 return null; } /** * saveStack と loadStack は対で使う。 * 関数定義などでスタックが混乱しないように配慮するためのもの */ saveStack() { this.stackList.push(this.stack); this.stack = []; } loadStack() { this.stack = this.stackList.pop(); } /** 変数名を探す * @param {string} name * @returns {any}変数名の情報 */ findVar(name) { // ローカル変数? if (this.localvars.get(name)) { return { name, scope: 'local', info: this.localvars.get(name) }; } // モジュール名を含んでいる? if (name.indexOf('__') >= 0) { if (this.funclist.get(name)) { return { name, scope: 'global', info: this.funclist.get(name) }; } else { return undefined; } } // グローバル変数(自身)? const gnameSelf = `${this.modName}__${name}`; if (this.funclist.get(gnameSelf)) { return { name: gnameSelf, scope: 'global', info: this.funclist.get(gnameSelf) }; } // グローバル変数(モジュールを検索)? for (const mod of this.modList) { const gname = `${mod}__${name}`; const exportDefault = this.moduleExport.get(mod); const funcObj = this.funclist.get(gname); if (funcObj && (funcObj.isExport === true || (funcObj.isExport !== false && exportDefault !== false))) { return { name: gname, scope: 'global', info: this.funclist.get(gname) }; } } // システム変数 (funclistを普通に検索) if (this.funclist.get(name)) { return { name, scope: 'system', info: this.funclist.get(name) }; } return undefined; } /** * 計算用に要素をスタックに積む */ pushStack(item) { this.logger.trace('PUSH:' + JSON.stringify(item)); this.stack.push(item); } /** * トークンの末尾に達したか */ isEOF() { return (this.index >= this.tokens.length); } getIndex() { return this.index; } /** * カーソル位置にある単語の型を確かめる */ check(ttype) { return (this.tokens[this.index].type === ttype); } /** * カーソル位置以降にある単語の型を確かめる 2単語以上に対応 * @param a [単語1の型, 単語2の型, ... ] */ check2(a) { for (let i = 0; i < a.length; i++) { const idx = i + this.index; if (this.tokens.length <= idx) { return false; } if (a[i] === '*') { continue; } // ワイルドカード(どんなタイプも許容) const t = this.tokens[idx]; if (a[i] instanceof Array) { if (a[i].indexOf(t.type) < 0) { return false; } continue; } if (t.type !== a[i]) { return false; } } return true; } /** * カーソル位置の型を確認するが、複数の種類を確かめられる */ checkTypes(a) { const type = this.tokens[this.index].type; return (a.indexOf(type) >= 0); } /** * check2の高度なやつ、型名の他にコールバック関数を指定できる * 型にマッチしなければ false を返し、カーソルを巻き戻す */ accept(types) { const y = []; const tmpIndex = this.index; const rollback = () => { this.index = tmpIndex; return false; }; for (let i = 0; i < types.length; i++) { if (this.isEOF()) { return rollback(); } const type = types[i]; if (type == null) { return rollback(); } if (typeof type === 'string') { const token = this.get(); if (token && token.type !== type) { return rollback(); } y[i] = token; continue; } if (typeof type === 'function') { const f = type.bind(this); const r = f(y); if (r === null) { return rollback(); } y[i] = r; continue; } if (type instanceof Array) { if (!this.checkTypes(type)) { return rollback(); } y[i] = this.get(); continue; } throw new Error('System Error : accept broken : ' + typeof type); } this.y = y; return true; } /** * カーソル語句を取得して、カーソルを後ろに移動する */ get() { if (this.isEOF()) { return null; } return this.tokens[this.index++]; } /** カーソル語句を取得してカーソルを進める、取得できなければエラーを出す */ getCur() { if (this.isEOF()) { throw new Error('トークンが取得できません。'); } const t = this.tokens[this.index++]; if (!t) { throw new Error('トークンが取得できません。'); } return t; } unget() { if (this.index > 0) { this.index--; } } /** 解析中のトークンを返す */ peek(i = 0) { if (this.isEOF()) { return null; } return this.tokens[this.index + i]; } /** 解析中のトークンを返す、無理なら def を返す */ peekDef(def = null) { if (this.isEOF()) { if (!def) { def = NewEmptyToken(); } return def; } return this.tokens[this.index]; } /** * 現在のカーソル語句のソースコード上の位置を取得する。 */ peekSourceMap(t = undefined) { const token = (t === undefined) ? this.peek() : t; if (token === null) { return { startOffset: undefined, endOffset: undefined, file: undefined, line: 0, column: 0 }; } return { startOffset: token.startOffset, endOffset: token.endOffset, file: token.file, line: token.line, column: token.column }; } /** * depth: 表示する深さ * typeName: 先頭のtypeの表示を上書きする場合に設定する * @param {{ depth: number, typeName?: string }} opts * @param {boolean} debugMode */ nodeToStr(node, opts, debugMode) { const depth = opts.depth - 1; const typeName = (name) => (opts.typeName !== undefined) ? opts.typeName : name; const debug = debugMode ? (' debug: ' + JSON.stringify(node, null, 2)) : ''; if (!node) { return '(NULL)'; } switch (node.type) { case 'not': if (depth >= 0) { const subNode = node.blocks[0]; return `${typeName('')}『${this.nodeToStr(subNode, { depth }, debugMode)}に演算子『not』を適用した式${debug}』`; } else { return `${typeName('演算子')}『not』`; } case 'op': { const node2 = node; let operator = node2.operator || ''; const table = { eq: '=', not: '!', gt: '>', lt: '<', and: 'かつ', or: 'または' }; if (operator in table) { operator = table[operator]; } if (depth >= 0) { const left = this.nodeToStr(node2.blocks[0], { depth }, debugMode); const right = this.nodeToStr(node2.blocks[1], { depth }, debugMode); if (node2.operator === 'eq') { return `${typeName('')}『${left}と${right}が等しいかどうかの比較${debug}』`; } return `${typeName('')}『${left}と${right}に演算子『${operator}』を適用した式${debug}』`; } else { return `${typeName('演算子')}『${operator}${debug}』`; } } case 'number': return `${typeName('数値')}${node.value}`; case 'bigint': return `${typeName('巨大整数')}${node.value}`; case 'string': return `${typeName('文字列')}『${node.value}${debug}』`; case 'word': return `${typeName('単語')}『${node.value}${debug}』`; case 'func': return `${typeName('関数')}『${node.name || node.value}${debug}』`; case 'eol': return '行の末尾'; case 'eof': return 'ファイルの末尾'; default: { let name = node.name; if (name) { name = node.value; } if (typeof name !== 'string') { name = node.type; } return `${typeName('')}『${name}${debug}』`; } } } }