UNPKG

nadesiko3

Version:
320 lines (319 loc) 10.8 kB
/** インデント構文を処理するモジュール */ import { NewEmptyToken } from './nako_types.mjs'; import { NakoIndentError } from '../src/nako_errors.mjs'; import { debugTokens, newToken } from './nako_tools.mjs'; const IS_DEBUG = false; function isSkipWord(t) { if (t.type === '違えば') { return true; } if (t.type === 'word' && t.value === 'エラー' && t.josi === 'ならば') { return true; } return false; } /** インラインインデント構文 --- 末尾の":"をインデントを考慮して"ここまで"を挿入 (#1215) */ export function convertInlineIndent(tokens) { // // 0:もし、A=0ならば: // 2: もし、B=0ならば: // 4: 「A=0,B=0」を表示。 // 2: 違えば: // 4: 「A=0,B!=0」を表示。 // 5:違えば: // 6: 「A!=0」を表示。 // const lines = splitTokens(tokens, 'eol'); const blockIndents = []; let checkICount = -1; let jsonObjLevel = 0; let jsonArrayLevel = 0; const checkJsonSyntax = (line) => { // JSONのオブジェクトがあるか? line.forEach((t) => { if (t.type === '{') { jsonObjLevel++; } if (t.type === '}') { jsonObjLevel--; } if (t.type === '[') { jsonArrayLevel++; } if (t.type === ']') { jsonArrayLevel--; } }); }; for (let i = 0; i < lines.length; i++) { const line = lines[i]; // 空行は飛ばす || コメント行だけの行も飛ばす if (IsEmptyLine(line)) { continue; } const leftToken = GetLeftTokens(line); // JSONの途中であればブロックの変更は行わない if (jsonObjLevel > 0 || jsonArrayLevel > 0) { checkJsonSyntax(line); continue; } // インデントの終了を確認する必要があるか? if (checkICount >= 0) { const lineICount = leftToken.indent; while (checkICount >= lineICount) { const tFirst = leftToken; // console.log('@@', lineICount, '>>', checkICount, tFirst.type) if (isSkipWord(tFirst) && (checkICount === lineICount)) { // 「違えば」や「エラーならば」 // 「ここまで」の挿入不要 / ただしネストした際の「違えば」(上記の5の状態なら必要) } else { // ここまでを挿入する lines[i - 1].push(newToken('ここまで', 'ここまで', tFirst)); lines[i - 1].push(newToken('eol', '\n', tFirst)); } blockIndents.pop(); if (blockIndents.length > 0) { checkICount = blockIndents[blockIndents.length - 1]; } else { checkICount = -1; break; } } } // JSONの途中であればブロックの変更は行わない checkJsonSyntax(line); if (jsonObjLevel > 0 || jsonArrayLevel > 0) { continue; } // 末尾の「:」をチェック const tLast = getLastTokenWithoutEOL(line); if (tLast.type === ':') { // 末尾の「:」を削除 lines[i] = lines[i].filter(t => t !== tLast); checkICount = tLast.indent; blockIndents.push(checkICount); } } if (lines.length > 0 && blockIndents.length > 0) { // トークン情報を得るため、直近のトークンを得る let t = tokens[0]; for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i]; if (line.length > 0) { t = line[line.length - 1]; break; } } // ここまでを差し込む for (let i = 0; i < blockIndents.length; i++) { lines[lines.length - 1].push(newToken('ここまで', 'ここまで', t)); lines[lines.length - 1].push(newToken('eol', '\n', t)); } } const result = joinTokenLines(lines); if (IS_DEBUG) { console.log('###', debugTokens(result)); } return result; } /** 行ごとに分割していたトークンをくっつける */ export function joinTokenLines(lines) { const r = []; for (const line of lines) { for (const t of line) { r.push(t); } } return r; } function getLastTokenWithoutEOL(line) { const len = line.length; if (len === 0) { return NewEmptyToken('?'); } let res = line[len - 1]; if (res.type === 'eol') { if (len >= 2) { res = line[len - 2]; } } return res; } export function splitTokens(tokens, delimiter) { const result = []; let line = []; let kakko = 0; for (const t of tokens) { line.push(t); if (t.type === '{') { kakko++; } else if (t.type === '}') { kakko--; } else if (kakko === 0 && t.type === delimiter) { result.push(line); line = []; } } if (line.length > 0) { result.push(line); } return result; } /** トークン行が空かどうか調べる */ function IsEmptyLine(line) { if (line.length === 0) { return true; } for (let j = 0; j < line.length; j++) { const ty = line[j].type; if (ty === 'eol' || ty === 'line_comment' || ty === 'range_comment') { continue; } return false; } return true; } /** コメントを除去した最初のトークンを返す */ function GetLeftTokens(line) { for (let i = 0; i < line.length; i++) { const t = line[i].type; if (t === 'eol' || t === 'line_comment' || t === 'range_comment') { continue; } return line[i]; } return line[0]; } // インデント構文のキーワード const INDENT_MODE_KEYWORDS = ['!インデント構文', '!ここまでだるい', '💡インデント構文', '💡ここまでだるい']; /** インデント構文 --- インデントを見て"ここまで"を自動挿入 (#596) */ export function convertIndentSyntax(tokens) { // インデント構文の変換が必要か? if (!useIndentSynax(tokens)) { return tokens; } // 『ここまで』があったらエラーを出す for (const t of tokens) { if (t.type === 'ここまで') { // エラーを出す throw new NakoIndentError('インデント構文が有効化されているときに『ここまで』を使うことはできません。', t.line, t.file); } } // JSON構文のチェック let jsonObjLevel = 0; let jsonArrayLevel = 0; const checkJsonSyntax = (line) => { // JSONのオブジェクトがあるか? line.forEach((t) => { if (t.type === '{') { jsonObjLevel++; } if (t.type === '}') { jsonObjLevel--; } if (t.type === '[') { jsonArrayLevel++; } if (t.type === ']') { jsonArrayLevel--; } }); }; // 行ごとにトークンを分割 const blockIndents = []; const lines = splitTokens(tokens, 'eol'); let lastI = 0; // 各行を確認する for (let i = 0; i < lines.length; i++) { const line = lines[i]; // 空行は飛ばす || コメント行だけの行も飛ばす if (IsEmptyLine(line)) { continue; } // JSON構文のチェック if (jsonArrayLevel > 0 || jsonObjLevel > 0) { checkJsonSyntax(line); continue; } const leftToken = GetLeftTokens(line); const curI = leftToken.indent; if (curI === lastI) { continue; } // ブロックの終了? // 0: 3回 // 2: もし、1 > 1ならば // 4: 1を表示 // 2: 違えば // 4: 2を表示 // 0: // ブロックの終了? if (lastI >= 0) { while (lastI > curI) { const blockIndentTopLast = blockIndents[blockIndents.length - 1][1]; // console.log('@@[', i, ']', lastI, '>', curI, '@', blockIndentTopLast, leftToken.type) if (isSkipWord(leftToken) && blockIndentTopLast === curI) { // 「違えば」などなら不要 (ただし、違えばがネストしている場合は必要) } else { const t = lines[i - 1][0]; lines[i - 1].push(newToken('ここまで', 'ここまで', t)); lines[i - 1].push(newToken('eol', '\n', t)); } blockIndents.pop(); if (blockIndents.length > 0) { lastI = blockIndents[blockIndents.length - 1][0]; } else { lastI = 0; break; } } } if (jsonArrayLevel > 0 || jsonObjLevel > 0) { continue; } // JSON構文のチェック checkJsonSyntax(line); // ブロックの開始? if (curI > lastI) { blockIndents.push([curI, lastI]); // console.log('@@@push', curI) lastI = curI; continue; } } // 末尾に「ここまで」を追加する for (let i = 0; i < blockIndents.length; i++) { // トークン情報を得るため、直近のトークンを得る let t = tokens[0]; for (let i = lines.length - 1; i >= 0; i--) { const line = lines[i]; if (line.length > 0) { t = line[line.length - 1]; break; } } lines[lines.length - 1].push(newToken('ここまで', 'ここまで', t)); lines[lines.length - 1].push(newToken('eol', '\n', t)); } const result = joinTokenLines(lines); // console.log('###', debugTokens(result)) return result; } function useIndentSynax(tokens) { // インデント構文が必要かチェック (最初の100個をチェック) for (let i = 0; i < tokens.length; i++) { if (i > 100) { break; } const t = tokens[i]; if (t.type === 'line_comment' && (INDENT_MODE_KEYWORDS.indexOf(t.value) >= 0)) { return true; } } return false; }