nadesiko3
Version:
Japanese Programming Language
1,619 lines (1,526 loc) • 95.4 kB
text/typescript
/* 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 { FuncListItem, FuncListItemType, FuncArgs, NewEmptyToken, SourceMap } from './nako_types.mjs'
import { NodeType, Ast, AstEol, AstBlocks, AstOperator, AstConst, AstLet, AstLetArray, AstIf, AstWhile, AstAtohantei, AstFor, AstForeach, AstSwitch, AstRepeatTimes, AstDefFunc, AstCallFunc, AstStrValue, AstDefVar, AstDefVarList } from './nako_ast.mjs'
import { Token, TokenDefFunc, TokenCallFunc } from './nako_token.mjs'
/**
* 構文解析を行うクラス
*/
export class NakoParser extends NakoParserBase {
/**
* 構文解析を実行する
*/
parse (tokens: Token[], filename: string): Ast {
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 (): Ast {
const b: Ast = this.ySentenceList()
const c: Token|null = 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 (): Ast {
return {
type: 'nop',
josi: '',
...this.peekSourceMap(),
end: this.peekSourceMap()
}
}
/** 複数文を返す */
ySentenceList(): AstBlocks {
const blocks = []
let line = -1
const map = this.peekSourceMap()
while (!this.isEOF()) {
const n: Ast|null = 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 (): string {
const words: string[] = []
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: FuncArgs | undefined = (f as FuncListItem).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(): AstEol | null {
// 行末のチェック #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 (): Ast | null {
const map: SourceMap = 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: Token = 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()
} as AstBlocks
}
}
return c1
}
return null
}
/** [廃止] 非同期モード #11 @returns {Ast} */
yASyncMode (): Ast {
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: number): Ast {
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: string): Ast {
const map = this.peekSourceMap()
this.genMode = mode
return { type: 'eol', ...map, end: this.peekSourceMap() }
}
/** @returns {Ast} */
yExportDefault (mode: string): Ast {
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: string): AstStrValue {
const map = this.peekSourceMap()
return { type: 'run_mode', value: mode, ...map, end: this.peekSourceMap() }
}
/** @returns {AstBlocks} */
yBlock(): AstBlocks {
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 (): Ast[]|null {
if (!this.check('(')) { return null }
const a: Ast[] = []
this.get() // skip '('
while (!this.isEOF()) {
if (this.check(')')) {
this.get() // skip ''
break
}
const t = this.get()
if (t) { a.push(t as any) } // Token to Ast
if (this.check('comma')) { this.get() }
}
return a
}
yDefTest (): Ast|null {
return this.yDefFuncCommon('def_test')
}
yDefFunc (): Ast|null {
return this.yDefFuncCommon('def_func')
}
/** ユーザー関数の定義
* @returns {AstDefFunc | null}
*/
yDefFuncCommon(type: NodeType): AstDefFunc | null {
if (!this.check(type)) { // yDefFuncから呼ばれれば def_func なのかをチェックする
return null
}
const map = this.peekSourceMap()
// 関数定義トークンを取得(このmetaに先読みした関数の型などが入っている)
// (ref) NakoLexer.preDefineFunc
const defToken: Token|null = this.get() // 'def_func' or 'def_test'
if (!defToken) { return null }
const def = defToken as TokenDefFunc
let isExport: boolean = this.isExportDefault
if (this.check('{')) {
this.get()
const funcAttribute: Token|null = 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: Ast[] = []
if (this.check('(')) { defArgs = this.yDefFuncReadArgs() || [] } // // lexerでも解析しているが再度詳しく
const funcName: Token|null = 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: Ast = 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 as AstStrValue).value) { continue }
const fnName: string = (arg as AstStrValue).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: any) {
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 (): Ast {
const map = this.peekSourceMap()
let a: Ast | null = 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() } as AstOperator
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()
} as AstOperator
}
this.index = tmpI
}
// もし文で追加の関数呼び出しがある場合
if (!this.check('ならば')) {
this.stack.push(a)
a = this.yCall()
}
// (ならば|でなければ)を確認
if (!this.check('ならば')) {
const smap: Ast = 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()
} as AstOperator
}
if (!a) {
throw NakoSyntaxError.fromNode(
'「もし」文の条件式に間違いがあります。' + this.nodeToStr(this.peek(), { depth: 1 }, false), map)
}
return a
}
/** もし文
* @returns {AstIf | null} */
yIF (): AstIf | null {
const map = this.peekSourceMap()
// 「もし」があれば「もし」文である
if (!this.check('もし')) { return null }
const mosi:Token|null = this.get() // skip もし
if (mosi == null) { return null }
while (this.check('comma')) { this.get() } // skip comma
// 「もし」文の条件を取得
let expr: Ast | null = null
try {
expr = this.yIFCond()
} catch (err: any) {
throw NakoSyntaxError.fromNode('『もし』文の条件で次のエラーがあります。\n' + err.message, mosi)
}
return this.yIfThen(expr, map)
}
/** 「もし」文の「もし」以降の判定 ... 「もし」がなくても条件分岐は動くようになっている
* @returns {AstIf | null}
*/
yIfThen (expr: Ast, map: SourceMap): AstIf | null {
// 「もし」文の 真偽のブロックを取得
let trueBlock: Ast = this.yNop()
let falseBlock: Ast = this.yNop()
let tanbun = false
// True Block
if (this.check('eol')) {
trueBlock = this.yBlock()
} else {
const block: Ast|null = 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: Ast|null = 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 (): AstBlocks | null {
const map: SourceMap = this.peekSourceMap()
if (!this.check2(['string', '実行速度優先'])) {
return null
}
const optionNode: Token|null = this.get()
this.get()
let val = ''
if (optionNode && optionNode.value) { val = optionNode.value } else { return null }
const options: {[key: string]: boolean} = { 行番号無し: 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: Ast = 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 (): AstBlocks | null {
const map = this.peekSourceMap()
if (!this.check2(['string', 'パフォーマンスモニタ適用'])) {
return null
}
const optionNode = this.get()
if (!optionNode) { return null }
this.get()
const options: {[key: string]: boolean} = { ユーザ関数: 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: Ast = 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 (): Ast|null {
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: Ast): Ast|null {
const args:Ast[] = [firstValue]
while (!this.isEOF()) {
// 演算子がある?
let op = this.peek()
if (op && opPriority[op.type]) {
op = this.getCur()
args.push(op as any) // 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: Ast): AstCallFunc | null {
// 範囲オブジェクト?
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 (): AstCallFunc | null {
const map = this.peekSourceMap()
const t = this.get() // skip '??'
if (!t || t.value !== '??') {
throw NakoSyntaxError.fromNode('『??』で指定してください。', map)
}
const arg: Ast|null = 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 (): Ast|null {
// 値を一つ読む
const value1 = this.yValue()
if (value1 === null) { return null }
// 範囲オブジェクト?
if (this.check('…')) { return this.yRange(value1) }
// 計算式がある場合を考慮
return this.yGetArgOperator(value1)
}
infixToPolish (list: Ast[]): Ast[] {
// 中間記法から逆ポーランドに変換
const priority = (t: Ast) => {
if (opPriority[t.type]) { return opPriority[t.type] }
return 10
}
const stack: Ast[] = []
const polish: Ast[] = []
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: Ast[]): Ast | null {
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:Ast|undefined = stack.pop()
const a:Ast|undefined = stack.pop()
if (a === undefined || b === undefined) {
this.logger.debug('--- 計算式(逆ポーランド) ---\n' + JSON.stringify(polish))
throw NakoSyntaxError.fromNode('計算式でエラー', node)
}
/** @type {AstOperator} */
const op: AstOperator = {
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: Ast[]): Ast[] { // C言語風呼び出しでカッコの中を取得
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] as AstStrValue).value}』でカッコが閉じていません`, y[0])
}
const a: Ast[] = []
while (si < this.stack.length) {
const v = this.popStack()
if (v) { a.unshift(v) }
}
return a
}
/** @returns {AstRepeatTimes | null} */
yRepeatTime(): AstRepeatTimes | null {
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() } as Ast
let multiline = false
let block: Ast = 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(): AstWhile | null { // 「*の間」文
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(): AstAtohantei |null {
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()
} as AstOperator
}
if (!cond) { cond = {type: 'number', value: 1, josi: '', ...map, end: this.peekSourceMap()} as AstConst }
return {
type: 'atohantei',
blocks: [cond, block],
josi: '',
...map,
end: this.peekSourceMap()
}
}
/** @returns {AstFor | null} */
yFor (): AstFor | null {
const errorForArguments = '『繰り返す』文でAからBまでの指定がありません。'
let flagDown = true // AからBまでの時、A>=Bを許容するかどうか
let flagUp = true // AからBまでの時、A<=Bを許容するかどうか
let loopDirection : null | 'up' | 'down' = null // ループの方向を一方向に限定する
const map = this.peekSourceMap()
if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) {
// pass
} else {
return null
}
const kurikaesu: Token = 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: Ast = 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: Ast|null = this.popStack(['を', 'で'])
let wordStr: string = ''
if (vWord !== null) { // 変数
if (vWord.type !== 'word') {
throw NakoSyntaxError.fromNode('『(変数名)をAからBまで繰り返す』で指定してください。', vWord)
}
wordStr = (vWord as AstStrValue).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: Ast = 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(): AstBlocks | null {
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(): AstForeach |null {
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: string = ''
if (name !== null) {
if (name.type !== 'word') {
throw NakoSyntaxError.fromNode('『(変数名)で(配列)を反復』で指定してください。', map)
}
wordStr = (name as AstStrValue).value
}
let block: Ast = 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 (): AstSwitch | null {
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: Ast[] = []
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: Token|null = 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: Ast | null = 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: AstSwitch = {
type: 'switch',
blocks,
case_count: blocks.length / 2 - 1,
josi: '',
...map,
end: this.peekSourceMap()
}
return ast
}
/** 無名関数
* @returns {AstDefFunc|null}
*/
yMumeiFunc (): AstDefFunc | null { // 無名関数の定義
const map = this.peekSourceMap()
if (!this.check('def_func')) { return null }
const defToken = this.get()
if (!defToken) { return null }
const def = defToken as TokenDefFunc
let args: Ast[] = []
// 「,」を飛ばす
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 (): AstBlocks | null {
const map = this.peekSourceMap()
const dainyu = this.get() // 代入
if (dainyu === null) { return null }
const value = this.popStack(['を']) || {type: 'word', value: 'それ', josi: 'を', ...map} as AstStrValue
const word: Ast|null = 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 as AstStrValue).value,
indexes: word.index,
blocks,
josi: '',
checkInit: this.flagCheckArrayInit,
...map,
end: this.peekSourceMap()
} as AstLetArray
}
// 一般的な変数への代入
const word2 = this.getVarName(word)
return {
type: 'let',
name: (word2 as AstStrValue).value,
blocks: [value],
josi: '',
...map,
end: this.peekSourceMap()
} as AstLet
}
/** 定める構文 */
ySadameru (): AstBlocks | null {
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() } as AstStrValue
if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== 'ref_array')) {
throw NakoSyntaxError.fromNode('『定める』文で定数が見当たりません。『(定数名)を(値)に定める』のように使います。', sadameru)
}
// 引数(値)を取得
const value = this.popStack(['へ', 'に', 'と']) || this.yNop()
// 公開設定
let isExport: boolean = this.isExportDefault
if (this.check2(['{', 'word', '}'])) {
this.get()
const attrNode = this.get()
if (attrNode === null) {
throw NakoSyntaxError.fromNode('定める『' + (word as AstStrValue).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 as AstStrValue, true, isExport)
return {
type: 'def_local_var',
name: (nameToken as AstStrValue).value,
vartype: '定数',
isExport,
blocks: [value],
josi: '',
...map,
end: this.peekSourceMap()
} as AstDefVar
}
yIncDec (): AstBlocks | null {
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() } as AstConst
}
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 } as AstConst
value = { type: 'op', operator: '*', blocks: [value, minus_one], josi: '', ...map } as AstOperator
}
return {
type: 'inc',
name: word,
blocks: [value],
josi: action.josi,
...map,
end: this.peekSourceMap()
}
}
yCall (): Ast | null {
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: Ast|null = 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 // 関数呼び出しの後に演算子がないのでそのまま関数呼び出しを戻す
}
// 四則演算があった場合、計算してスタックに載せる
const s = this.yGetArgOperator(r)
this.pushStack(s)
continue
}
// 値のとき → スタックに載せる
const t = this.yGetArg()
if (t) {
this.pushStack(t)
continue
}
break
} // end of while
// 助詞が余ってしまった場合
if (this.stack.length > 0) {
if (this.isReadingCalc) {
return this.popStack()
}
this.logger.debug('--- stack dump ---\n' + JSON.stringify(this.stack, null, 2) + '\npeek: ' + JSON.stringify(this.peek(), null, 2))
let msgDebug = `不完全な文です。${this.stack.map((n) => this.nodeToStr(n, { depth: 0 }, true)).join('、')}が解決していません。`
let msg = `不完全な文です。${this.stack.map((n) => this.nodeToStr(n, { depth: 0 }, false)).join('、')}が解決していません。`
// 各ノードについて、更に詳細な情報があるなら表示
for (const n of this.stack) {
const d0 = this.nodeToStr(n, { depth: 0 }, false)
const d1 = this.nodeToStr(n, { depth: 1 }, false)
if (d0 !== d1) {
msgDebug += `${this.nodeToStr(n, { depth: 0 }, true)}は${this.nodeToStr(n, { depth: 1 }, true)}として使われています。`
msg += `${d0}は${d1}として使われています。`
}
}
const first = this.stack[0]
const last = this.stack[this.stack.length - 1]
this.logger.debug(msgDebug, first)
throw NakoSyntaxError.fromNode(msg, first, last)
}
return this.popStack([])
}
/** @returns {Ast | null} */
yCallFunc (): Ast | null {
const map = this.peekSourceMap()
const callToken = this.get()
if (!callToken) { return null }
const t = callToken as TokenCallFunc
const f = t.meta
const funcName: string = t.value
// (関数)には ... 構文 ... https://github.com/kujirahand/nadesiko3/issues/66
let funcObj = null
if (t.josi === 'には') {
try {
funcObj = this.yMumeiFunc()
} catch (err: any) {
throw NakoSyntaxError.fromNode(`『${t.value}には...』で無名関数の定義で以下の間違いがあります。\n${err.message}`, t)
}
if (funcObj === null) { throw NakoSyntaxError.fromNode('『Fには』構文がありましたが、関数定義が見当たりません。', t) }
}
if (!f || typeof f.josi === 'undefined') { throw NakoSyntaxError.fromNode('関数の定義でエラー。', t) }
// 最近使った関数を記録
this.recentlyCalledFunc.push({ name: funcName, ...f })
// 呼び出す関数が非同期呼び出しが必要(asyncFn)ならマーク
if (f && f.asyncFn) { this.usedAsyncFn = true }
// 関数の引数を取り出す処理
const args: any[] = []
let nullCount = 0
let valueCount = 0
for (let i = f.josi.length - 1; i >= 0; i--) {
for (;;) {
// スタックから任意の助詞を持つ値を一つ取り出す、助詞がなければ末尾から得る
let popArg = this.popStack(f.josi[i])
if (popArg !== null) {
valueCount++
} else if (i < f.josi.length - 1 || !f.isVariableJosi) {
nullCount++
popArg = funcObj
} else {
break
}
// 参照渡しの場合、引数が関数の参照渡しに該当する場合、typeを『func_pointer』に変更
if (popArg !== null && f.funcPointers !== undefined && f.funcPointers[i] !== null) {
if (popArg.type === 'func') { // 引数が関数の参照渡しに該当する場合
popArg.type = 'func_pointer'
} else {
const varname = (f.varnames) ? f.varnames[i] : `${i + 1}番目の引数`
throw NakoSyntaxError.fromNode(
`関数『${t.value}』の引数『${varname}』には関数オブジェクトが必要です。`, t)
}
}
// 引数がnullであれば、自動的に『変数「それ」』で補完する
if (popArg === null) {
popArg = { type: 'word', value: 'それ', josi: '', ...map, end: map } as AstStrValue
}
args.unshift(popArg) // 先頭に追加
if (i < f.josi.length - 1 || !f.isVariableJosi) { break }
}
}
// 引数が不足しているとき(つまり、引数にnullがあるとき)、自動的に『変数「それ」』で補完される。
// ただし、nullが1つだけなら、変数「それ」で補完されるが、2つ以上あるときは、エラーにする
if (nullCount >= 2 && (valueCount > 0 || t.josi === '' || RenbunJosi.indexOf(t.josi) >= 0)) {
throw NakoSyntaxError.fromNode(`関数『${t.value}』の引数が不足しています。`, t)
}
this.usedFuncs.add(t.value)
// 関数呼び出しのAstを構築
const funcNode: AstCallFunc = {
type: 'func',
name: t.value,
blocks: args,
meta: f,
josi: t.josi,
asyncFn: f.asyncFn ? true : false,
...map,
end: this.peekSourceMap()
}
// 「プラグイン名設定」ならば、そこでスコープを変更することを意味する (#1112)
if (funcNode.name === 'プラグイン名設定') {
if (args.length > 0 && args[0]) {
let fname: string = '' + args[0].value
if (fname === 'メイン') { fname = '' + args[0].file }
this.namespaceStack.push(this.modName)
this.isExportStack.push(this.isExportDefault)
this.modName = NakoLexer.filenameToModName(fname)
this.modList.push(this.modName)
}
} else if (funcNode.name === '名前空間ポップ') { // (#1409)
const space = this.namespaceStack.pop()
if (space) { this.modName = space }
const isexport = this.isExportStack.pop()
if (isexport != null) { this.isExportDefault = isexport }
}
// 言い切りならそこで一度切る
if (t.josi === '') { return funcNode }
// 「**して、**」の場合も一度切る
if (RenbunJosi.indexOf(t.josi) >= 0) {
funcNode.josi = 'して'
return funcNode
}
// 続き
funcNode.meta = f
this.pushStack(funcNode)
return null
}
/** @returns {Ast | null} */
yLet (): AstBlocks | null {
const map = this.peekSourceMap()
// 通常の変数
if (this.check2(['word', 'eq'])) {
const word = this.peek()
let threw = false
try {
if (this.accept(['word', 'eq', this.yCalc]) || this.accept(['word', 'eq', this.ySentence])) {
if (this.y[2].type === 'eol') {
throw new Error('値が空です。')
}
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
const nameToken = this.getVarName(this.y[0])
const valueToken = this.y[2]
return {
type: 'let',
name: (nameToken as AstStrValue).value,
blocks: [valueToken],
josi: '',
...map,
end: this.peekSourceMap()
} as AstLet
} else {
threw = true
this.logger.debug(`${this.nodeToStr(word, { depth: 1 }, true)}への代入文で計算式に書き間違いがあります。`, word)
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に書き間違いがあります。`, map)
}
} catch (err: any) {
if (threw) {
throw err
}
this.logger.debug(`${this.nodeToStr(word, { depth: 1 }, true)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, word)
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, map)
}
}
// オブジェクトプロパティ構文 代入文 (#1793)
if (this.check2(['word', '$', '*', '$', '*', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', '$', '*', 'eq']) || this.check2(['word', '$', '*', 'eq'])) {
const propList = []
const word = this.getVarName(this.get() as Token)
for (;;) {
const flag = this.peek()
if (flag === null || flag.type !== '$') { break }
this.get() // skip $
propList.push(this.get() as Ast) // property
}
this.get() // skip eq
const valueToken = this.yCalc() // calc
if (valueToken === null) {
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文の計算式に書き間違いがあります。`, map)
}
return {
type: 'let_prop',
name: (word as AstStrValue).value,
index: propList,
blocks: [valueToken],
josi: '',
...map,
end: this.peekSourceMap()
} as AstLet
}
// オブジェクトプロパティ構文 ここまで
// let_array ?
if (this.check2(['word', '@'])) {
const la = this.yLetArrayAt(map)
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
if (la) {
la.checkInit = this.flagCheckArrayInit
return la
}
}
if (this.check2(['word', '['])) {
const lb = this.yLetArrayBracket(map) as AstLetArray
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
if (lb) {
lb.checkInit = this.flagCheckArrayInit
return lb
}
}
// ローカル変数定義
if (this.accept(['word', 'とは'])) {
const wordToken = this.y[0]
if (!this.checkTypes(['変数', '定数'])) {
throw NakoSyntaxError.fromNode('ローカル変数『' + wordToken.value + '』の定義エラー', wordToken)
}
const vtype = this.getCur() // 変数 or 定数
let isExport : boolean = this.isExportDefault
if (this.check2(['{', 'word', '}'])) {
this.get()
const attrNode = this.get()
if (attrNode === null) {
throw Na