UNPKG

nadesiko3

Version:
282 lines (269 loc) 9.44 kB
/** インデント構文を処理するモジュール */ import { Token, 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: Token): boolean { if (t.type === '違えば') { return true } if (t.type === 'word' && t.value === 'エラー' && t.josi === 'ならば') { return true } return false } /** インラインインデント構文 --- 末尾の":"をインデントを考慮して"ここまで"を挿入 (#1215) */ export function convertInlineIndent (tokens: Token[]): Token[] { // // 0:もし、A=0ならば: // 2: もし、B=0ならば: // 4: 「A=0,B=0」を表示。 // 2: 違えば: // 4: 「A=0,B!=0」を表示。 // 5:違えば: // 6: 「A!=0」を表示。 // const lines: Token[][] = splitTokens(tokens, 'eol') const blockIndents: number[] = [] let checkICount = -1 let jsonObjLevel = 0 let jsonArrayLevel = 0 const checkJsonSyntax = (line: Token[]) => { // JSONのオブジェクトがあるか? line.forEach((t: Token) => { 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: number = leftToken.indent while (checkICount >= lineICount) { const tFirst: Token = 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: Token = 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: Token[][]): Token[] { const r: Token[] = [] for (const line of lines) { for (const t of line) { r.push(t) } } return r } function getLastTokenWithoutEOL (line: Token[]): Token { const len: number = line.length if (len === 0) { return NewEmptyToken('?') } let res: Token = line[len - 1] if (res.type === 'eol') { if (len >= 2) { res = line[len - 2] } } return res } export function splitTokens (tokens: Token[], delimiter: string): Token[][] { const result: Token[][] = [] let line: Token[] = [] 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: Token[]): boolean { 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: Token[]): Token { 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: Token[]): Token[] { // インデント構文の変換が必要か? 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: Token[]) => { // JSONのオブジェクトがあるか? line.forEach((t: Token) => { if (t.type === '{') { jsonObjLevel++ } if (t.type === '}') { jsonObjLevel-- } if (t.type === '[') { jsonArrayLevel++ } if (t.type === ']') { jsonArrayLevel-- } }) } // 行ごとにトークンを分割 const blockIndents: number[][] = [] 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: number = 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: Token[]) : boolean { // インデント構文が必要かチェック (最初の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 }