UNPKG

nadesiko3

Version:
1,594 lines 112 kB
/* eslint-disable @typescript-eslint/no-explicit-any */ /** * nadesiko v3 parser */ import { opPriority, RenbunJosi, operatorList } from './nako_parser_const.mjs'; import { NakoParserBase } from './nako_parser_base.mjs'; import { NakoSyntaxError } from './nako_errors.mjs'; import { NakoLexer } from './nako_lexer.mjs'; import { NewEmptyToken } from './nako_types.mjs'; /** * 構文解析を行うクラス */ export class NakoParser extends NakoParserBase { /** * 構文解析を実行する */ parse(tokens, filename) { this.reset(); this.tokens = tokens; this.modName = NakoLexer.filenameToModName(filename); this.modList.push(this.modName); // 解析処理 - 先頭から解析開始 const result = this.startParser(); // 関数毎に非同期処理が必要かどうかを判定する this.isModifiedNodes = false; this._checkAsyncFn(result); while (this.isModifiedNodes) { this.isModifiedNodes = false; this._checkAsyncFn(result); } return result; } /** パーサーの一番最初に呼び出す構文規則 */ startParser() { const b = this.ySentenceList(); const c = this.get(); if (c && c.type !== 'eof') { this.logger.debug(`構文解析でエラー。${this.nodeToStr(c, { depth: 1 }, true)}の使い方が間違っています。`, c); throw NakoSyntaxError.fromNode(`構文解析でエラー。${this.nodeToStr(c, { depth: 1 }, false)}の使い方が間違っています。`, c); } return b; } /** 何もしない * @returns {Ast} */ yNop() { return { type: 'nop', josi: '', ...this.peekSourceMap(), end: this.peekSourceMap() }; } /** 複数文を返す */ ySentenceList() { const blocks = []; let line = -1; const map = this.peekSourceMap(); while (!this.isEOF()) { const n = this.ySentence(); if (!n) { break; } blocks.push(n); if (line < 0) { line = n.line; } } if (blocks.length === 0) { const token = this.peek() || this.tokens[0]; this.logger.debug('構文解析に失敗:' + this.nodeToStr(this.peek(), { depth: 1 }, true), token); throw NakoSyntaxError.fromNode('構文解析に失敗:' + this.nodeToStr(this.peek(), { depth: 1 }, false), token); } return { type: 'block', blocks: blocks, josi: '', ...map, end: this.peekSourceMap() }; } /** 余剰スタックのレポートを作る */ makeStackBalanceReport() { const words = []; this.stack.forEach((t) => { let w = this.nodeToStr(t, { depth: 1 }, false); if (t.josi) { w += t.josi; } words.push(w); }); const desc = words.join(','); // 最近使った関数の使い方レポートを作る #1093 let descFunc = ''; const chA = 'A'.charCodeAt(0); for (const f of this.recentlyCalledFunc) { descFunc += ' - '; let no = 0; const josiA = f.josi; if (josiA) { for (const arg of josiA) { const ch = String.fromCharCode(chA + no); descFunc += ch; if (arg.length === 1) { descFunc += arg[0]; } else { descFunc += `(${arg.join('|')})`; } no++; } } descFunc += f.name + '\n'; } this.recentlyCalledFunc = []; return `未解決の単語があります: [${desc}]\n次の命令の可能性があります:\n${descFunc}`; } yEOL() { // 行末のチェック #1009 const eol = this.get(); if (!eol) { return null; } // 余剰スタックの確認 if (this.stack.length > 0) { const report = this.makeStackBalanceReport(); throw NakoSyntaxError.fromNode(report, eol); } this.recentlyCalledFunc = []; return { type: 'eol', comment: eol.value, line: eol.line, column: eol.column, file: eol.file, }; } /** @returns {Ast | null} */ ySentence() { const map = this.peekSourceMap(); // 最初の語句が決まっている構文 if (this.check('eol')) { return this.yEOL(); } if (this.check('もし')) { return this.yIF(); } if (this.check('後判定')) { return this.yAtohantei(); } if (this.check('エラー監視')) { return this.yTryExcept(); } if (this.accept(['抜ける'])) { return { type: 'break', josi: '', ...map, end: this.peekSourceMap() }; } if (this.accept(['続ける'])) { return { type: 'continue', josi: '', ...map, end: this.peekSourceMap() }; } if (this.check('??')) { return this.yDebugPrint(); } // 実行モードの指定 if (this.accept(['DNCLモード'])) { return this.yDNCLMode(1); } if (this.accept(['DNCL2モード'])) { return this.yDNCLMode(2); } if (this.accept(['not', 'string', 'モード設定'])) { return this.ySetGenMode(this.y[1].value); } if (this.accept(['not', 'モジュール公開既定値', 'eq', 'string'])) { return this.yExportDefault(this.y[3].value); } if (this.accept(['not', '厳チェック'])) { return this.ySetMode('厳しくチェック'); } // (#1698) // (memo) 現状「取込」はプリプロセス段階(NakoCompiler.listRequireStatements)で処理される // if (this.accept(['require', 'string', '取込'])) { return this.yRequire() } // <廃止された構文> if (this.check('逐次実行')) { return this.yTikuji(); } // 廃止 #1611 if (this.accept(['not', '非同期モード'])) { return this.yASyncMode(); } // </廃止された構文> if (this.check2(['func', 'eq'])) { const word = this.get() || NewEmptyToken(); throw NakoSyntaxError.fromNode(`関数『${word.value}』に代入できません。`, word); } // 先読みして初めて確定する構文 if (this.accept([this.ySpeedMode])) { return this.y[0]; } if (this.accept([this.yPerformanceMonitor])) { return this.y[0]; } if (this.accept([this.yLet])) { return this.y[0]; } if (this.accept([this.yDefTest])) { return this.y[0]; } if (this.accept([this.yDefFunc])) { return this.y[0]; } // 関数呼び出しの他、各種構文の実装 if (this.accept([this.yCall])) { const c1 = this.y[0]; const nextToken = this.peek(); if (nextToken && nextToken.type === 'ならば') { const map = this.peekSourceMap(); const cond = c1; this.get(); // skip ならば // もし文の条件として関数呼び出しがある場合 return this.yIfThen(cond, map); } else if (RenbunJosi.indexOf(c1.josi || '') >= 0) { // 連文をblockとして接続する(もし構文などのため) if (this.stack.length >= 1) { // スタックの余剰をチェック const report = this.makeStackBalanceReport(); throw NakoSyntaxError.fromNode(report, c1); } const c2 = this.ySentence(); if (c2 !== null) { return { type: 'block', blocks: [c1, c2], josi: c2.josi, ...map, end: this.peekSourceMap() }; } } return c1; } return null; } /** [廃止] 非同期モード #11 @returns {Ast} */ yASyncMode() { this.logger.error('『非同期モード』構文は廃止されました(https://nadesi.com/v3/doc/go.php?1028)。', this.peek()); const map = this.peekSourceMap(); return { type: 'eol', ...map, end: this.peekSourceMap() }; } /** set DNCL mode */ yDNCLMode(ver) { const map = this.peekSourceMap(); if (ver === 1) { // 配列インデックスは1から this.arrayIndexFrom = 1; // 配列アクセスをJSと逆順で指定する this.flagReverseArrayIndex = true; } else { // ver2はPythonに近いとのこと } // 配列代入時自動で初期化チェックする this.flagCheckArrayInit = true; return { type: 'eol', ...map, end: this.peekSourceMap() }; } /** @returns {Ast} */ ySetGenMode(mode) { const map = this.peekSourceMap(); this.genMode = mode; return { type: 'eol', ...map, end: this.peekSourceMap() }; } /** @returns {Ast} */ yExportDefault(mode) { const map = this.peekSourceMap(); this.isExportDefault = mode === '公開'; this.moduleExport.set(this.modName, this.isExportDefault); return { type: 'eol', ...map, end: this.peekSourceMap() }; } /** @returns {AstStrValue} */ ySetMode(mode) { const map = this.peekSourceMap(); return { type: 'run_mode', value: mode, ...map, end: this.peekSourceMap() }; } /** @returns {AstBlocks} */ yBlock() { const map = this.peekSourceMap(); const blocks = []; if (this.check('ここから')) { this.get(); } while (!this.isEOF()) { if (this.checkTypes(['違えば', 'ここまで', 'エラー'])) { break; } if (!this.accept([this.ySentence])) { break; } blocks.push(this.y[0]); } return { type: 'block', blocks: blocks, josi: '', ...map, end: this.peekSourceMap() }; } yDefFuncReadArgs() { if (!this.check('(')) { return null; } const a = []; this.get(); // skip '(' while (!this.isEOF()) { if (this.check(')')) { this.get(); // skip '' break; } const t = this.get(); if (t) { a.push(t); } // Token to Ast if (this.check('comma')) { this.get(); } } return a; } yDefTest() { return this.yDefFuncCommon('def_test'); } yDefFunc() { return this.yDefFuncCommon('def_func'); } /** ユーザー関数の定義 * @returns {AstDefFunc | null} */ yDefFuncCommon(type) { if (!this.check(type)) { // yDefFuncから呼ばれれば def_func なのかをチェックする return null; } const map = this.peekSourceMap(); // 関数定義トークンを取得(このmetaに先読みした関数の型などが入っている) // (ref) NakoLexer.preDefineFunc const defToken = this.get(); // 'def_func' or 'def_test' if (!defToken) { return null; } const def = defToken; let isExport = this.isExportDefault; if (this.check('{')) { this.get(); const funcAttribute = this.get(); if (this.check('}')) { this.get(); } else { throw NakoSyntaxError.fromNode('関数の属性の指定が正しくありません。『{』と『}』で囲む必要があります。', def); } if (funcAttribute != null) { if (funcAttribute.value === '公開') { isExport = true; } if (funcAttribute.value === '非公開') { isExport = false; } if (funcAttribute.value === 'エクスポート') { isExport = true; } } } let defArgs = []; if (this.check('(')) { defArgs = this.yDefFuncReadArgs() || []; } // // lexerでも解析しているが再度詳しく const funcName = this.get(); if (!funcName || funcName.type !== 'func') { this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の宣言でエラー。', funcName); throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の宣言でエラー。', def); } if (this.check('(')) { // 関数引数の二重定義 if (defArgs.length > 0) { this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の宣言で、引数定義は名前の前か後に一度だけ可能です。', funcName); throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の宣言で、引数定義は名前の前か後に一度だけ可能です。', funcName); } defArgs = this.yDefFuncReadArgs() || []; } if (this.check('とは')) { this.get(); } let block = this.yNop(); let multiline = false; let asyncFn = false; if (this.check('ここから')) { multiline = true; } if (this.check('eol')) { multiline = true; } try { this.funcLevel++; this.usedAsyncFn = false; // ローカル変数を生成 const backupLocalvars = this.localvars; this.localvars = new Map([['それ', { type: 'var', value: '' }]]); if (multiline) { this.saveStack(); // 関数の引数をローカル変数として登録する for (const arg of defArgs) { if (!arg) { continue; } if (!arg.value) { continue; } const fnName = arg.value; this.localvars.set(fnName, { 'type': 'var', 'value': '' }); } block = this.yBlock(); if (this.check('ここまで')) { this.get(); } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。関数定義の末尾に必要です。', def); } this.loadStack(); } else { this.saveStack(); block = this.ySentence() || this.yNop(); this.loadStack(); } this.funcLevel--; asyncFn = this.usedAsyncFn; this.localvars = backupLocalvars; } catch (err) { this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の定義で以下のエラーがありました。\n' + err.message, def); throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の定義で以下のエラーがありました。\n' + err.message, def); } const func = this.funclist.get(funcName.value); if (func && !func.asyncFn && asyncFn) { func.asyncFn = asyncFn; } return { type, name: funcName.value, args: defArgs, blocks: [block], asyncFn, isExport, josi: '', meta: def.meta, ...map, end: this.peekSourceMap() }; } /** 「もし」文の条件を取得 */ yIFCond() { const map = this.peekSourceMap(); let a = this.yGetArg(); if (!a) { throw NakoSyntaxError.fromNode('「もし」文の条件式に間違いがあります。' + this.nodeToStr(this.peek(), { depth: 1 }, false), map); } // console.log('@@yIFCond=', a) // チェック : Aならば if (a.josi === 'ならば') { return a; } if (a.josi === 'でなければ') { a = { type: 'not', operator: 'not', blocks: [a], josi: '', ...map, end: this.peekSourceMap() }; return a; } // チェック : AがBならば --- 「関数B(A)」のとき if ((a.josi !== '') && (this.check('func'))) { // もし文で関数呼び出しがある場合 this.stack.push(a); a = this.yCall(); } else // チェック : AがBならば --- 「A = B」のとき if (a.josi === 'が') { const tmpI = this.index; const b = this.yGetArg(); if (!b) { throw NakoSyntaxError.fromNode('もし文の条件「AがBならば」でBがないか条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false), map); } if (this.check('ならば')) { const naraba = this.get() || { 'value': 'ならば' }; b.josi = naraba.value; } if (b && (b.josi === 'ならば' || b.josi === 'でなければ')) { return { type: 'op', operator: (b.josi === 'でなければ') ? 'noteq' : 'eq', blocks: [a, b], josi: '', ...map, end: this.peekSourceMap() }; } this.index = tmpI; } // もし文で追加の関数呼び出しがある場合 if (!this.check('ならば')) { this.stack.push(a); a = this.yCall(); } // (ならば|でなければ)を確認 if (!this.check('ならば')) { const smap = a || this.yNop(); this.logger.debug('もし文で『ならば』がないか、条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false) + 'の直前に『ならば』を書いてください。', smap); throw NakoSyntaxError.fromNode('もし文で『ならば』がないか、条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false) + 'の直前に『ならば』を書いてください。', smap); } const naraba = this.get(); // 否定形のチェック if (naraba && naraba.value === 'でなければ') { a = { type: 'not', operator: 'not', blocks: [a], josi: '', ...map, end: this.peekSourceMap() }; } if (!a) { throw NakoSyntaxError.fromNode('「もし」文の条件式に間違いがあります。' + this.nodeToStr(this.peek(), { depth: 1 }, false), map); } return a; } /** もし文 * @returns {AstIf | null} */ yIF() { const map = this.peekSourceMap(); // 「もし」があれば「もし」文である if (!this.check('もし')) { return null; } const mosi = this.get(); // skip もし if (mosi == null) { return null; } while (this.check('comma')) { this.get(); } // skip comma // 「もし」文の条件を取得 let expr = null; try { expr = this.yIFCond(); } catch (err) { throw NakoSyntaxError.fromNode('『もし』文の条件で次のエラーがあります。\n' + err.message, mosi); } return this.yIfThen(expr, map); } /** 「もし」文の「もし」以降の判定 ... 「もし」がなくても条件分岐は動くようになっている * @returns {AstIf | null} */ yIfThen(expr, map) { // 「もし」文の 真偽のブロックを取得 let trueBlock = this.yNop(); let falseBlock = this.yNop(); let tanbun = false; // True Block if (this.check('eol')) { trueBlock = this.yBlock(); } else { const block = this.ySentence(); if (block) { trueBlock = block; } tanbun = true; } // skip EOL while (this.check('eol')) { this.get(); } // Flase Block if (this.check('違えば')) { this.get(); // skip 違えば while (this.check('comma')) { this.get(); } if (this.check('eol')) { falseBlock = this.yBlock(); } else { const block = this.ySentence(); if (block) { falseBlock = block; } tanbun = true; } } if (tanbun === false) { if (this.check('ここまで')) { this.get(); } else { throw NakoSyntaxError.fromNode('『もし』文で『ここまで』がありません。', map); } } return { type: 'if', blocks: [expr, trueBlock, falseBlock], josi: '', ...map, end: this.peekSourceMap() }; } ySpeedMode() { const map = this.peekSourceMap(); if (!this.check2(['string', '実行速度優先'])) { return null; } const optionNode = this.get(); this.get(); let val = ''; if (optionNode && optionNode.value) { val = optionNode.value; } else { return null; } const options = { 行番号無し: false, 暗黙の型変換無し: false, 強制ピュア: false, それ無効: false }; for (const name of val.split('/')) { // 全て有効化 if (name === '全て') { for (const k of Object.keys(options)) { options[k] = true; } break; } // 個別に有効化 if (Object.keys(options).includes(name)) { options[name] = true; } else { // 互換性を考えて、警告に留める。 this.logger.warn(`実行速度優先文のオプション『${name}』は存在しません。`, optionNode); } } let multiline = false; if (this.check('ここから')) { this.get(); multiline = true; } else if (this.check('eol')) { multiline = true; } let block = this.yNop(); if (multiline) { block = this.yBlock(); if (this.check('ここまで')) { this.get(); } } else { block = this.ySentence() || block; } return { type: 'speed_mode', options, blocks: [block], josi: '', ...map }; } yPerformanceMonitor() { const map = this.peekSourceMap(); if (!this.check2(['string', 'パフォーマンスモニタ適用'])) { return null; } const optionNode = this.get(); if (!optionNode) { return null; } this.get(); const options = { ユーザ関数: false, システム関数本体: false, システム関数: false }; for (const name of optionNode.value.split('/')) { // 全て有効化 if (name === '全て') { for (const k of Object.keys(options)) { options[k] = true; } break; } // 個別に有効化 if (Object.keys(options).includes(name)) { options[name] = true; } else { // 互換性を考えて、警告に留める。 this.logger.warn(`パフォーマンスモニタ適用文のオプション『${name}』は存在しません。`, optionNode); } } let multiline = false; if (this.check('ここから')) { this.get(); multiline = true; } else if (this.check('eol')) { multiline = true; } let block = this.yNop(); if (multiline) { block = this.yBlock(); if (this.check('ここまで')) { this.get(); } } else { block = this.ySentence() || block; } return { type: 'performance_monitor', options, blocks: [block], josi: '', ...map }; } /** [廃止] #1611 「逐次実行」構文 @returns {Ast | null} */ yTikuji() { if (!this.check('逐次実行')) { return null; } const tikuji = this.getCur(); // skip this.logger.error('『逐次実行』構文は廃止されました(https://nadesi.com/v3/doc/go.php?944)。', tikuji); return { type: 'eol', ...this.peekSourceMap(), end: this.peekSourceMap() }; } /** * 1つ目の値を与え、その後に続く計算式を取得し、優先規則に沿って並び替えして戻す * @param {Ast} firstValue */ yGetArgOperator(firstValue) { const args = [firstValue]; while (!this.isEOF()) { // 演算子がある? let op = this.peek(); if (op && opPriority[op.type]) { op = this.getCur(); args.push(op); // Token to Ast // 演算子後の値を取得 const v = this.yValue(); if (v === null) { throw NakoSyntaxError.fromNode(`計算式で演算子『${op.value}』後に値がありません`, firstValue); } args.push(v); continue; } break; } if (args.length === 0) { return null; } if (args.length === 1) { return args[0]; } return this.infixToAST(args); } /** * 範囲(関数)を返す * @param kara * @returns {AstCallFunc | null} */ yRange(kara) { // 範囲オブジェクト? if (!this.check('…')) { return null; } const map = this.peekSourceMap(); this.get(); // skip '…' const made = this.yValue(); if (!kara || !made) { throw NakoSyntaxError.fromNode('範囲オブジェクトの指定エラー。『A…B』の書式で指定してください。', map); } const meta = this.funclist.get('範囲'); if (!meta) { throw new Error('関数『範囲』が見つかりません。plugin_systemをシステムに追加してください。'); } return { type: 'func', name: '範囲', blocks: [kara, made], josi: made.josi, meta, asyncFn: false, ...map, end: this.peekSourceMap() }; } /** * 表示(関数)を返す 「??」のエイリアスで利用 (#1745) */ yDebugPrint() { const map = this.peekSourceMap(); const t = this.get(); // skip '??' if (!t || t.value !== '??') { throw NakoSyntaxError.fromNode('『??』で指定してください。', map); } const arg = this.yGetArg(); if (!arg) { throw NakoSyntaxError.fromNode('『??(計算式)』で指定してください。', map); } const meta = this.funclist.get('ハテナ関数実行'); if (!meta) { throw new Error('関数『ハテナ関数実行』が見つかりません。plugin_systemをシステムに追加してください。'); } return { type: 'func', name: 'ハテナ関数実行', blocks: [arg], josi: '', meta, asyncFn: false, ...map, end: this.peekSourceMap() }; } yGetArg() { // 値を一つ読む const value1 = this.yValue(); if (value1 === null) { return null; } // 範囲オブジェクト? if (this.check('…')) { return this.yRange(value1); } // 計算式がある場合を考慮 return this.yGetArgOperator(value1); } infixToPolish(list) { // 中間記法から逆ポーランドに変換 const priority = (t) => { if (opPriority[t.type]) { return opPriority[t.type]; } return 10; }; const stack = []; const polish = []; while (list.length > 0) { const t = list.shift(); if (!t) { break; } while (stack.length > 0) { // 優先順位を見て移動する const sTop = stack[stack.length - 1]; if (priority(t) > priority(sTop)) { break; } const tpop = stack.pop(); if (!tpop) { this.logger.error('計算式に間違いがあります。', t); break; } polish.push(tpop); } stack.push(t); } // 残った要素を積み替える while (stack.length > 0) { const t = stack.pop(); if (t) { polish.push(t); } } return polish; } /** @returns {Ast | null} */ infixToAST(list) { if (list.length === 0) { return null; } // 逆ポーランドを構文木に const josi = list[list.length - 1].josi; const node = list[list.length - 1]; const polish = this.infixToPolish(list); /** @type {Ast[]} */ const stack = []; for (const t of polish) { if (!opPriority[t.type]) { // 演算子ではない stack.push(t); continue; } const b = stack.pop(); const a = stack.pop(); if (a === undefined || b === undefined) { this.logger.debug('--- 計算式(逆ポーランド) ---\n' + JSON.stringify(polish)); throw NakoSyntaxError.fromNode('計算式でエラー', node); } /** @type {AstOperator} */ const op = { type: 'op', operator: t.type, blocks: [a, b], josi, startOffset: a.startOffset, endOffset: a.endOffset, line: a.line, column: a.column, file: a.file }; stack.push(op); } const ans = stack.pop(); if (!ans) { return null; } return ans; } yGetArgParen(y) { let isClose = false; const si = this.stack.length; while (!this.isEOF()) { if (this.check(')')) { isClose = true; break; } const v = this.yGetArg(); if (v) { this.pushStack(v); if (this.check('comma')) { this.get(); } continue; } break; } if (!isClose) { throw NakoSyntaxError.fromNode(`C風関数『${y[0].value}』でカッコが閉じていません`, y[0]); } const a = []; while (si < this.stack.length) { const v = this.popStack(); if (v) { a.unshift(v); } } return a; } /** @returns {AstRepeatTimes | null} */ yRepeatTime() { const map = this.peekSourceMap(); if (!this.check('回')) { return null; } this.get(); // skip '回' if (this.check('comma')) { this.get(); } // skip comma if (this.check('繰返')) { this.get(); } // skip 'N回、繰り返す' (#924) const num = this.popStack([]) || { type: 'word', value: 'それ', josi: '', ...map, end: this.peekSourceMap() }; let multiline = false; let block = this.yNop(); if (this.check('comma')) { this.get(); } if (this.check('ここから')) { this.get(); multiline = true; } else if (this.check('eol')) { multiline = true; } if (multiline) { // multiline block = this.yBlock(); if (this.check('ここまで')) { this.get(); } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。『回』...『ここまで』を対応させてください。', map); } } else { // singleline const b = this.ySentence(); if (b) { block = b; } } return { type: 'repeat_times', blocks: [num, block], josi: '', ...map, end: this.peekSourceMap() }; } /** @returns {AstWhile | null} */ yWhile() { const map = this.peekSourceMap(); if (!this.check('間')) { return null; } this.get(); // skip '間' while (this.check('comma')) { this.get(); } // skip ',' if (this.check('繰返')) { this.get(); } // skip '繰り返す' #927 const expr = this.popStack(); if (expr === null) { throw NakoSyntaxError.fromNode('『間』で条件がありません。', map); } if (this.check('comma')) { this.get(); } if (!this.checkTypes(['ここから', 'eol'])) { throw NakoSyntaxError.fromNode('『間』の直後は改行が必要です', map); } const block = this.yBlock(); if (this.check('ここまで')) { this.get(); } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。『間』...『ここまで』を対応させてください。', map); } return { type: 'while', blocks: [expr, block], josi: '', ...map, end: this.peekSourceMap() }; } /** @returns {AstAtohantei | null} */ yAtohantei() { const map = this.peekSourceMap(); if (this.check('後判定')) { this.get(); } // skip 後判定 if (this.check('繰返')) { this.get(); } // skip 繰り返す if (this.check('ここから')) { this.get(); } const block = this.yBlock(); if (this.check('ここまで')) { this.get(); } if (this.check('comma')) { this.get(); } let cond = this.yGetArg(); // 条件 let bUntil = false; const t = this.peek(); if (t && t.value === 'なる' && (t.josi === 'まで' || t.josi === 'までの')) { this.get(); // skip なるまで bUntil = true; } if (this.check('間')) { this.get(); } // skip 間 if (bUntil) { // 条件を反転する cond = { type: 'not', operator: 'not', blocks: [cond], josi: '', ...map, end: this.peekSourceMap() }; } if (!cond) { cond = { type: 'number', value: 1, josi: '', ...map, end: this.peekSourceMap() }; } return { type: 'atohantei', blocks: [cond, block], josi: '', ...map, end: this.peekSourceMap() }; } /** @returns {AstFor | null} */ yFor() { const errorForArguments = '『繰り返す』文でAからBまでの指定がありません。'; let flagDown = true; // AからBまでの時、A>=Bを許容するかどうか let flagUp = true; // AからBまでの時、A<=Bを許容するかどうか let loopDirection = null; // ループの方向を一方向に限定する const map = this.peekSourceMap(); if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) { // pass } else { return null; } const kurikaesu = this.getCur(); // skip 繰り返す // スタックに(増や|減ら)してがある? const incdec = this.stack.pop(); if (incdec) { if (incdec.type === 'word' && (incdec.value === '増' || incdec.value === '減')) { if (incdec.value === '増') { flagDown = false; } else { flagUp = false; } const w = incdec.value + kurikaesu.type; if (w == '増繰返') { kurikaesu.type = '増繰返'; } else if (w == '減繰返') { kurikaesu.type = '減繰返'; } else { throw Error('[System Error] 増繰り返し | 減繰り返しのエラー。'); } } else { // 普通の繰り返しの場合 this.stack.push(incdec); // 違ったので改めて追加 } } let vInc = this.yNop(); if (kurikaesu.type === '増繰返' || kurikaesu.type === '減繰返') { vInc = this.popStack(['ずつ']) || this.yNop(); if (kurikaesu.type === '増繰返') { flagDown = false; } else { flagUp = false; } loopDirection = kurikaesu.type === '増繰返' ? 'up' : 'down'; } const vTo = this.popStack(['まで', 'を']); // 範囲オブジェクトの場合もあり const vFrom = this.popStack(['から']) || this.yNop(); const vWord = this.popStack(['を', 'で']); let wordStr = ''; if (vWord !== null) { // 変数 if (vWord.type !== 'word') { throw NakoSyntaxError.fromNode('『(変数名)をAからBまで繰り返す』で指定してください。', vWord); } wordStr = vWord.value; } if (vFrom === null || vTo === null) { // 『AからBの範囲を繰り返す』構文のとき (#1704) if (vFrom == null && vTo && (vTo.type === 'func' && vTo.name === '範囲')) { // ok } else { throw NakoSyntaxError.fromNode(errorForArguments, kurikaesu); } } if (this.check('comma')) { this.get(); } // skip comma let multiline = false; if (this.check('ここから')) { multiline = true; this.get(); } else if (this.check('eol')) { multiline = true; this.get(); } let block = this.yNop(); if (multiline) { block = this.yBlock(); if (this.check('ここまで')) { this.get(); } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。『繰り返す』...『ここまで』を対応させてください。', map); } } else { const b = this.ySentence(); if (b) { block = b; } } if (!block) { block = this.yNop(); } return { type: 'for', blocks: [vFrom, vTo, vInc, block], flagDown, flagUp, loopDirection, word: wordStr, josi: '', ...map, end: this.peekSourceMap() }; } /** @returns {AstBlocks | null} */ yReturn() { const map = this.peekSourceMap(); if (!this.check('戻る')) { return null; } this.get(); // skip '戻る' const v = this.popStack(['で', 'を']) || this.yNop(); if (this.stack.length > 0) { throw NakoSyntaxError.fromNode('『戻』文の直前に未解決の引数があります。『(式)を戻す』のように式をカッコで括ってください。', map); } return { type: 'return', blocks: [v], josi: '', ...map, end: this.peekSourceMap() }; } /** @returns {AstForeach | null} */ yForEach() { const map = this.peekSourceMap(); if (!this.check('反復')) { return null; } this.get(); // skip '反復' while (this.check('comma')) { this.get(); } // skip ',' const target = this.popStack(['を']) || this.yNop(); // target == null なら「それ」の値が使われる const name = this.popStack(['で']); let wordStr = ''; if (name !== null) { if (name.type !== 'word') { throw NakoSyntaxError.fromNode('『(変数名)で(配列)を反復』で指定してください。', map); } wordStr = name.value; } let block = this.yNop(); let multiline = false; if (this.check('ここから')) { multiline = true; this.get(); } else if (this.check('eol')) { multiline = true; } if (multiline) { block = this.yBlock(); if (this.check('ここまで')) { this.get(); } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。『反復』...『ここまで』を対応させてください。', map); } } else { const b = this.ySentence(); if (b) { block = b; } } return { type: 'foreach', word: wordStr, blocks: [target, block], josi: '', ...map, end: this.peekSourceMap() }; } /** 条件分岐構文 * @returns {AstSwitch | null} */ ySwitch() { const map = this.peekSourceMap(); if (!this.check('条件分岐')) { return null; } const joukenbunki = this.get(); // skip '条件分岐' if (!joukenbunki) { return null; } const eol = this.get(); // skip 'eol' if (!eol) { return null; } const expr = this.popStack(['で']); if (!expr) { throw NakoSyntaxError.fromNode('『(値)で条件分岐』のように記述してください。', joukenbunki); } if (eol.type !== 'eol') { throw NakoSyntaxError.fromNode('『条件分岐』の直後は改行してください。', joukenbunki); } // const blocks = []; blocks[0] = expr; blocks[1] = this.yNop(); // 後で default のAstを再設定するため // while (!this.isEOF()) { if (this.check('eol')) { this.get(); continue; } // ここまで? if (this.check('ここまで')) { this.get(); // skip ここまで break; } // 違えば? const condToken = this.peek(); if (condToken && condToken.type === '違えば') { this.get(); // skip 違えば if (this.check('comma')) { this.get(); } // skip ',' const defaultBlock = this.yBlock(); if (this.check('ここまで')) { this.get(); // skip ここまで (違えばとペア) } while (this.check('eol')) { this.get(); } // skip eol if (this.check('ここまで')) { this.get(); // skip ここまで (条件分岐:ここまで) } blocks[1] = defaultBlock; break; } // 通常の条件 const cond = this.yValue(); if (!cond) { throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki); } const naraba = this.get(); // skip ならば if (!naraba || naraba.type !== 'ならば') { throw NakoSyntaxError.fromNode('『条件分岐』で条件は**ならばと記述してください。', joukenbunki); } if (this.check('comma')) { this.get(); } // skip ',' // 条件にあったときに実行すること const condBlock = this.yBlock(); const kokomade = this.peek(); if (kokomade && kokomade.type === 'ここまで') { this.get(); // skip ここまで } blocks.push(cond); blocks.push(condBlock); } const ast = { type: 'switch', blocks, case_count: blocks.length / 2 - 1, josi: '', ...map, end: this.peekSourceMap() }; return ast; } /** 無名関数 * @returns {AstDefFunc|null} */ yMumeiFunc() { const map = this.peekSourceMap(); if (!this.check('def_func')) { return null; } const defToken = this.get(); if (!defToken) { return null; } const def = defToken; let args = []; // 「,」を飛ばす if (this.check('comma')) { this.get(); } // 関数の引数定義は省略できる if (this.check('(')) { args = this.yDefFuncReadArgs() || []; } // 「,」を飛ばす if (this.check('comma')) { this.get(); } // ブロックを読む this.funcLevel++; this.saveStack(); const backupAsyncFn = this.usedAsyncFn; this.usedAsyncFn = false; const block = this.yBlock(); const isAsyncFn = this.usedAsyncFn; // 末尾の「ここまで」をチェック - もしなければエラーにする #1045 if (!this.check('ここまで')) { throw NakoSyntaxError.fromNode('『ここまで』がありません。『には』構文か無名関数の末尾に『ここまで』が必要です。', map); } this.get(); // skip ここまで this.loadStack(); this.usedAsyncFn = backupAsyncFn; this.funcLevel--; return { type: 'func_obj', name: '', args, blocks: [block], meta: def.meta, josi: '', isExport: false, // 無名関数は外部公開しない asyncFn: isAsyncFn, // asyncFnかどうか ...map, end: this.peekSourceMap() }; } /** 代入構文 */ yDainyu() { const map = this.peekSourceMap(); const dainyu = this.get(); // 代入 if (dainyu === null) { return null; } const value = this.popStack(['を']) || { type: 'word', value: 'それ', josi: 'を', ...map }; const word = this.popStack(['へ', 'に']); if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== 'ref_array')) { throw NakoSyntaxError.fromNode('代入文で代入先の変数が見当たりません。『(変数名)に(値)を代入』のように使います。', dainyu); } // 配列への代入 if (word.type === 'ref_array') { const indexArray = word.index || []; const blocks = [value, ...indexArray]; return { type: 'let_array', name: word.name.value, indexes: word.index, blocks, josi: '', checkInit: this.flagCheckArrayInit, ...map, end: this.peekSourceMap() }; } // 一般的な変数への代入 const word2 = this.getVarName(word); return { type: 'let', name: word2.value, blocks: [value], josi: '', ...map, end: this.peekSourceMap() }; } /** 定める構文 */ ySadameru() { const map = this.peekSourceMap(); const sadameru = this.get(); // 定める if (sadameru === null) { return null; } // 引数(定数名)を取得 const word = this.popStack(['を']) || { type: 'word', value: 'それ', josi: 'を', ...map, end: this.peekSourceMap() }; if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== 'ref_array')) { throw NakoSyntaxError.fromNode('『定める』文で定数が見当たりません。『(定数名)を(値)に定める』のように使います。', sadameru); } // 引数(値)を取得 const value = this.popStack(['へ', 'に', 'と']) || this.yNop(); // 公開設定 let isExport = this.isExportDefault; if (this.check2(['{', 'word', '}'])) { this.get(); const attrNode = this.get(); if (attrNode === null) { throw NakoSyntaxError.fromNode('定める『' + word.value + '』の定義エラー', word); } const attr = attrNode.value; if (attr === '公開') { isExport = true; } else if (attr === '非公開') { isExport = false; } else if (attr === 'エクスポート') { isExport = true; } else { this.logger.warn(`不明な変数属性『${attr}』が指定されています。`); } this.get(); } // 変数を生成する const nameToken = this.createVar(word, true, isExport); return { type: 'def_local_var', name: nameToken.value, vartype: '定数', isExport, blocks: [value], josi: '', ...map, end: this.peekSourceMap() }; } yIncDec() { const map = this.peekSourceMap(); const action = this.get(); // (増やす|減らす) if (action === null) { return null; } // 『Nずつ増やして繰り返す』文か? if (this.check('繰返')) { this.pushStack({ type: 'word', value: action.value, josi: action.josi, ...map, end: this.peekSourceMap() }); return this.yFor(); } // スタックから引数をポップ let value = this.popStack(['だけ', '']); if (!value) { value = { type: 'number', value: 1, josi: 'だけ', ...map, end: this.peekSourceMap() }; } const word = this.popStack(['を']); if (!word || (word.type !== 'word' && word.type !== 'ref_array')) { throw NakoSyntaxError.fromNode(`『${action.type}』文で定数が見当たりません。『(変数名)を(値)だけ${action.type}』のように使います。`, action); } // 減らすなら-1かける if (action.value === '減') { const minus_one = { type: 'number', value: -1, line: action.line }; value = { type: 'op', operator: '*', blocks: [value, minus_one], josi: '', ...map }; } return { type: 'inc', name: word, blocks: [value], josi: action.josi, ...map, end: this.peekSourceMap() }; } yCall() { if (this.isEOF()) { return null; } // スタックに積んでいく while (!this.isEOF()) { if (this.check('ここから')) { this.get(); } // 代入 if (this.check('代入')) { return this.yDainyu(); } if (this.check('定める')) { return this.ySadameru(); } // 制御構文 if (this.check('回')) { return this.yRepeatTime(); } if (this.check('間')) { return this.yWhile(); } if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) { return this.yFor(); } if (this.check('反復')) { return this.yForEach(); } if (this.check('条件分岐')) { return this.ySwitch(); } if (this.check('戻る')) { return this.yReturn(); } if (this.check('増') || this.check('減')) { return this.yIncDec(); } // C言語風関数 if (this.check2([['func', 'word'], '('])) { // C言語風 const cur = this.peek(); if (cur && cur.josi === '') { const t = this.yValue(); // yValueにてC言語風呼び出しをパース if (t) { const josi = t.josi || ''; if (t.type === 'func' && (t.josi === '' || RenbunJosi.indexOf(josi) >= 0)) { t.josi = ''; return t; // 関数なら値とする } this.pushStack(t); } if (this.check('comma')) { this.get(); } continue; } } // なでしこ式関数 if (this.check('func')) { const r = this.yCallFunc(); if (r === null) { continue; } // 「〜する間」の形ならスタックに積む。 if (this.check('間')) { this.pushStack(r); continue; } // 関数呼び出しの直後に、四則演算があるか? if (!this.checkTypes(operatorList)) { return r; // 関数呼び出しの後に演算子がないのでそのまま関数呼び出しを戻す } // 四則