nadesiko3 3.3.49 → 3.3.50
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -21
- package/README.md +143 -143
- package/batch/.DS_Store +0 -0
- package/batch/browsers.template.md +3 -3
- package/batch/build_browsers.nako3 +72 -72
- package/batch/build_command.nako3 +44 -44
- package/batch/build_nako_version.nako3 +42 -42
- package/batch/calc_hash.nako3 +29 -29
- package/batch/cmd_txt2json.nako3 +74 -74
- package/batch/command.txt +79 -0
- package/batch/command_nakopad.txt +69 -0
- package/batch/download-extlib.nako3 +43 -43
- package/batch/gen_command_nakopad.nako3 +57 -57
- package/batch/jsplugin2text.nako3 +285 -285
- package/batch/pickup_command.nako3 +110 -110
- package/batch/pickup_reserved_words.nako3 +11 -11
- package/batch/publish_version.nako3 +46 -46
- package/batch/show_agents.js +14 -14
- package/batch/turtle2js.nako3 +21 -21
- package/bin/cnako3 +10 -10
- package/core/LICENSE +21 -21
- package/core/README.md +66 -66
- package/core/batch/build_nako_version.nako3 +42 -42
- package/core/command/snako.mjs +105 -105
- package/core/command/snako.mts +116 -116
- package/core/index.mjs +21 -21
- package/core/package.json +47 -47
- package/core/sample/hello.nako3 +7 -7
- package/core/sample/hoge.mjs +4 -4
- package/core/sample/hoge.mts +6 -6
- package/core/src/nako3.mjs +864 -858
- package/core/src/nako3.mts +976 -967
- package/core/src/nako_colors.mjs +78 -78
- package/core/src/nako_colors.mts +86 -86
- package/core/src/nako_core_version.mjs +8 -8
- package/core/src/nako_core_version.mts +19 -19
- package/core/src/nako_csv.mjs +185 -185
- package/core/src/nako_csv.mts +188 -188
- package/core/src/nako_errors.mjs +173 -173
- package/core/src/nako_errors.mts +197 -197
- package/core/src/nako_from_dncl.mjs +255 -255
- package/core/src/nako_from_dncl.mts +250 -250
- package/core/src/nako_gen.mjs +1647 -1648
- package/core/src/nako_gen.mts +1718 -1719
- package/core/src/nako_gen_async.mjs +1659 -1659
- package/core/src/nako_gen_async.mts +1732 -1732
- package/core/src/nako_global.mjs +107 -107
- package/core/src/nako_global.mts +138 -138
- package/core/src/nako_indent.mjs +445 -445
- package/core/src/nako_indent.mts +492 -492
- package/core/src/nako_josi_list.mjs +38 -38
- package/core/src/nako_josi_list.mts +45 -45
- package/core/src/nako_lex_rules.mjs +253 -253
- package/core/src/nako_lex_rules.mts +260 -260
- package/core/src/nako_lexer.mjs +609 -609
- package/core/src/nako_lexer.mts +612 -612
- package/core/src/nako_logger.mjs +199 -199
- package/core/src/nako_logger.mts +232 -232
- package/core/src/nako_parser3.mjs +2439 -2439
- package/core/src/nako_parser3.mts +2195 -2195
- package/core/src/nako_parser_base.mjs +370 -370
- package/core/src/nako_parser_base.mts +370 -370
- package/core/src/nako_parser_const.mjs +37 -37
- package/core/src/nako_parser_const.mts +37 -37
- package/core/src/nako_prepare.mjs +304 -304
- package/core/src/nako_prepare.mts +315 -315
- package/core/src/nako_reserved_words.mjs +38 -38
- package/core/src/nako_reserved_words.mts +38 -38
- package/core/src/nako_source_mapping.mjs +207 -207
- package/core/src/nako_source_mapping.mts +262 -262
- package/core/src/nako_test.mjs +37 -37
- package/core/src/nako_types.mjs +25 -25
- package/core/src/nako_types.mts +151 -151
- package/core/src/plugin_csv.mjs +49 -49
- package/core/src/plugin_csv.mts +50 -50
- package/core/src/plugin_math.mjs +328 -328
- package/core/src/plugin_math.mts +326 -326
- package/core/src/plugin_promise.mjs +91 -91
- package/core/src/plugin_promise.mts +91 -91
- package/core/src/plugin_system.mjs +2832 -2832
- package/core/src/plugin_system.mts +2690 -2690
- package/core/src/plugin_test.mjs +34 -34
- package/core/src/plugin_test.mts +34 -34
- package/demo/.DS_Store +0 -0
- package/demo/ace_editor.html +89 -89
- package/demo/ace_editor_tabs.html +161 -161
- package/demo/basic.html +71 -71
- package/demo/browsers.html +10 -9
- package/demo/css/basic.css +3 -3
- package/demo/css/common.css +157 -157
- package/demo/css/editor.css +8 -8
- package/demo/css/flow.css +3 -3
- package/demo/css/index.css +3 -3
- package/demo/extlib/.DS_Store +0 -0
- package/demo/flow.html +98 -98
- package/demo/graph.html +53 -53
- package/demo/image/nakopad-icon256.png +0 -0
- package/demo/index.html +133 -133
- package/demo/js/common.js +17 -17
- package/demo/js/turtle3d_test.js +44 -44
- package/demo/js/turtle_test.js +45 -45
- package/demo/nako3/calc.nako3 +4 -4
- package/demo/runscript.html +47 -47
- package/demo/runscript2.html +33 -33
- package/demo/runscript3.html +35 -35
- package/demo/runscript4.html +33 -33
- package/demo/turtle.html +58 -58
- package/demo/turtle2.html +141 -141
- package/demo/turtle3.html +279 -279
- package/demo/turtle3d.html +58 -58
- package/demo/turtle3d2.html +107 -107
- package/demo/version.html +24 -24
- package/doc/SETUP.md +157 -157
- package/doc/about.md +17 -17
- package/doc/browsers.md +26 -26
- package/doc/docgen.md +21 -21
- package/doc/editor.md +44 -44
- package/doc/files.md +39 -39
- package/doc/plugins.md +234 -234
- package/doc/release.md +79 -79
- package/doc/textlint.md +43 -43
- package/doc/win32.md +57 -57
- package/package.json +195 -195
- package/release/_hash.txt +28 -28
- package/release/_script-tags.txt +14 -14
- package/release/command.json +1 -1
- package/release/command.json.js +1 -1
- package/release/command_cnako3.json +1 -1
- package/release/command_list.json +1 -1
- package/release/editor.js +1 -1
- package/release/nako_gen_async.js +1 -1
- package/release/nako_gen_async.js.LICENSE.txt +35 -0
- package/release/plugin_caniuse.js.LICENSE.txt +11 -0
- package/release/plugin_csv.js +1 -1
- package/release/plugin_csv.js.LICENSE.txt +15 -0
- package/release/plugin_datetime.js.LICENSE.txt +15 -0
- package/release/plugin_kansuji.js.LICENSE.txt +3 -0
- package/release/plugin_markup.js.LICENSE.txt +11 -0
- package/release/plugin_turtle.js.LICENSE.txt +15 -0
- package/release/plugin_webworker.js.LICENSE.txt +3 -0
- package/release/plugin_weykturtle3d.js.LICENSE.txt +3 -0
- package/release/stats.json +1 -1
- package/release/version.js +1 -1
- package/release/wnako3.js +1 -1
- package/release/wnako3webworker.js +1 -1
- package/release/wnako3webworker.js.LICENSE.txt +131 -0
- package/src/.DS_Store +0 -0
- package/src/browsers.txt +12 -11
- package/src/browsers_agents.json +2 -2
- package/src/browsers_agents.mjs +1 -1
- package/src/cnako3.mjs +17 -17
- package/src/cnako3.mts +18 -18
- package/src/cnako3mod.mjs +707 -707
- package/src/cnako3mod.mts +712 -712
- package/src/commander_ja.mjs +164 -164
- package/src/commander_ja.mts +161 -161
- package/src/enako3.mjs +68 -68
- package/src/era.mjs +22 -22
- package/src/image_turtle-elephant.mjs +2 -2
- package/src/image_turtle-panda.mjs +2 -2
- package/src/image_turtle64.mjs +2 -2
- package/src/index.mjs +9 -9
- package/src/index.mts +10 -10
- package/src/nako3editorfix.sfd +106 -106
- package/src/nako_version.mjs +8 -8
- package/src/nako_version.mts +2 -2
- package/src/plugin_browser.mjs +213 -213
- package/src/plugin_browser.mts +206 -206
- package/src/plugin_browser_ajax.mjs +399 -399
- package/src/plugin_browser_audio.mjs +109 -109
- package/src/plugin_browser_canvas.mjs +449 -449
- package/src/plugin_browser_chart.mjs +294 -294
- package/src/plugin_browser_color.mjs +49 -49
- package/src/plugin_browser_crypto.mjs +26 -26
- package/src/plugin_browser_dialog.mjs +53 -53
- package/src/plugin_browser_dom_basic.mjs +336 -336
- package/src/plugin_browser_dom_event.mjs +193 -193
- package/src/plugin_browser_dom_parts.mjs +396 -396
- package/src/plugin_browser_geolocation.mjs +51 -51
- package/src/plugin_browser_hotkey.mjs +25 -25
- package/src/plugin_browser_html.mjs +59 -59
- package/src/plugin_browser_in_worker.mjs +45 -45
- package/src/plugin_browser_location.mjs +21 -21
- package/src/plugin_browser_speech.mjs +111 -111
- package/src/plugin_browser_storage.mjs +121 -121
- package/src/plugin_browser_system.mjs +31 -31
- package/src/plugin_browser_websocket.mjs +73 -73
- package/src/plugin_caniuse.mjs +29 -29
- package/src/plugin_datetime.mjs +394 -394
- package/src/plugin_httpserver.mjs +277 -277
- package/src/plugin_httpserver.mts +286 -286
- package/src/plugin_kansuji.mjs +224 -224
- package/src/plugin_keigo.mjs +55 -55
- package/src/plugin_markup.mjs +32 -32
- package/src/plugin_node.mjs +1047 -1047
- package/src/plugin_node.mts +980 -980
- package/src/plugin_turtle.mjs +647 -647
- package/src/plugin_webworker.mjs +334 -334
- package/src/plugin_weykturtle3d.mjs +1214 -1214
- package/src/plugin_worker.mjs +95 -95
- package/src/repl.nako3 +63 -63
- package/src/wnako3.mjs +12 -12
- package/src/wnako3.mts +11 -11
- package/src/wnako3_editor.css +215 -215
- package/src/wnako3_editor.mjs +1542 -1542
- package/src/wnako3_editor.mts +1658 -1657
- package/src/wnako3mod.mjs +213 -213
- package/src/wnako3mod.mts +214 -214
- package/src/wnako3webworker.mjs +69 -69
- package/test/.DS_Store +0 -0
- package/test/ace_editor/karma.config.js +94 -94
- package/test/ace_editor/test/.babelrc.json +3 -3
- package/test/ace_editor/test/ace_editor_test.js +178 -178
- package/test/ace_editor/test/html/custom_context.html +139 -139
- package/test/async/async_basic_test.mjs +122 -122
- package/test/browser/.DS_Store +0 -0
- package/test/browser/karma.config.js +221 -221
- package/test/browser/test/.babelrc.json +3 -3
- package/test/browser/test/compare_util.js +50 -50
- package/test/browser/test/html/div_basic.html +2 -2
- package/test/browser/test/html/event_dom_form.html +4 -4
- package/test/browser/test/html/event_dom_scrolldiv.html +5 -5
- package/test/browser/test/import_plugin_checker.js +24 -24
- package/test/browser/test/plugin_browser_test.js +51 -51
- package/test/browser/test/plugin_browser_test_ajax.js +123 -123
- package/test/browser/test/plugin_browser_test_color.js +18 -18
- package/test/browser/test/plugin_browser_test_dialog.js +72 -72
- package/test/browser/test/plugin_browser_test_dom_event.js +598 -598
- package/test/browser/test/plugin_browser_test_dom_parts.js +125 -125
- package/test/browser/test/plugin_browser_test_system.js +9 -9
- package/test/browser/test/plugin_turtle_test.js +817 -817
- package/test/browser/test/plugin_webworker_test.js +86 -86
- package/test/browser/test/require_test.js +68 -68
- package/test/bundled/.DS_Store +0 -0
- package/test/bundled/karma.config.base.js +117 -117
- package/test/bundled/karma.config.js +86 -86
- package/test/bundled/test/.babelrc.json +3 -3
- package/test/bundled/test/bundled_test.js +69 -69
- package/test/bundled/test/html/custom_context.html +65 -65
- package/test/bundled/test/html/custom_debug.html +66 -66
- package/test/bundled/test4b.cmd +52 -52
- package/test/bundled/test_base/.DS_Store +0 -0
- package/test/bundled/test_base/.babelrc.json +3 -3
- package/test/bundled/test_base/_checktool_test.js +25 -25
- package/test/bundled/test_base/basic_ajax_test.js +56 -56
- package/test/bundled/test_base/basic_async_test.js +18 -18
- package/test/bundled/test_base/basic_test.js +153 -153
- package/test/bundled/test_base/calc_test.js +132 -132
- package/test/bundled/test_base/css/browsers_box.css +114 -114
- package/test/bundled/test_base/html/custom_context.html +69 -69
- package/test/bundled/test_base/html/custom_debug.html +71 -71
- package/test/bundled/test_base/js/browsers_box.js +72 -72
- package/test/bundled/test_base/plugin_csv_test.js +37 -37
- package/test/bundled/test_base/plugin_datetime_test.js +115 -115
- package/test/bundled/test_base/plugin_kansuji_test.js +49 -49
- package/test/bundled/test_base/plugin_system_test.js +410 -410
- package/test/bundled/test_base/plugin_webworker_test.js +53 -53
- package/test/bundled/test_base/test_utils.js +191 -191
- package/test/common/.DS_Store +0 -0
- package/test/common/plugin_browser_test.mjs +22 -22
- package/test/common/plugin_browser_ut_audio_test.mjs +108 -108
- package/test/common/plugin_browser_ut_color_test.mjs +21 -21
- package/test/common/plugin_browser_ut_dialog_test.mjs +100 -100
- package/test/common/plugin_browser_ut_html_test.mjs +13 -13
- package/test/common/plugin_browser_ut_system_test.mjs +10 -10
- package/test/common/plugin_markup_test.mjs +23 -23
- package/test/jsconfig.json +19 -19
- package/test/karma.config.js +91 -91
- package/test/node/.DS_Store +0 -0
- package/test/node/async_test.mjs +96 -96
- package/test/node/commander_ja_test.mjs +89 -89
- package/test/node/error_message_test.mjs +243 -243
- package/test/node/kai_test.nako3 +6 -6
- package/test/node/node_test.mjs +60 -60
- package/test/node/plugin_broken.js.txt +3 -3
- package/test/node/plugin_browser_ut_ajax_test.mjs.todo +357 -357
- package/test/node/plugin_browser_ut_location_test.mjs +42 -42
- package/test/node/plugin_markup_test.mjs +47 -47
- package/test/node/plugin_math_test.mjs +45 -45
- package/test/node/plugin_node_test.mjs +98 -98
- package/test/node/plugin_test.mjs +44 -44
- package/test/node/relative_import_test_1.nako3 +1 -1
- package/test/node/relative_import_test_2.nako3 +2 -2
- package/test/node/require_nako3_test.mjs +67 -67
- package/test/node/requiretest.nako3 +4 -4
- package/test/node/requiretest_indirect.nako3 +1 -1
- package/test/node/requiretest_name.nako3 +5 -5
- package/test/node/runtime_error.nako3 +2 -2
- package/test/node/scope1.nako3 +10 -10
- package/test/node/scope2.nako3 +12 -12
- package/test/node/side_effects_test.mjs +39 -39
- package/test/node/sjis.txt +5 -5
- package/test/node/syntax_error.nako3 +1 -1
- package/test/node/wnako3_editor_test.mjs +384 -384
- package/tools/.DS_Store +0 -0
- package/tools/README.md +12 -12
- package/tools/check_new_version.nako3 +25 -25
- package/tools/nako3edit/.DS_Store +0 -0
- package/tools/nako3edit/a.sqlite3 +0 -0
- package/tools/nako3edit/html/.DS_Store +0 -0
- package/tools/nako3edit/html/daisyui/LICENSE +22 -22
- package/tools/nako3edit/html/daisyui/version_2.14.1 +1 -1
- package/tools/nako3edit/html/edit.html +170 -170
- package/tools/nako3edit/html/edit_plugin.js +6 -6
- package/tools/nako3edit/html/files.html +125 -125
- package/tools/nako3edit/html/nako3edit.css +65 -65
- package/tools/nako3edit/index.mjs +248 -248
- package/tools/nako3server/index.html +10 -10
- package/tools/nako3server/index.mjs +116 -116
- package/tools/nako3server/index.nako3 +34 -34
- package/core/.editorconfig +0 -6
- package/core/.eslintrc.cjs +0 -33
- package/core/.github/dependabot.yml +0 -7
- package/core/.github/workflows/nodejs.yml +0 -37
- package/core/.github/workflows/super-linter.yml +0 -61
- package/core/.github/workflows/textlint.yml +0 -199
- package/core/index.mts +0 -21
- package/core/test/array_test.mjs +0 -34
- package/core/test/basic_test.mjs +0 -344
- package/core/test/calc_test.mjs +0 -140
- package/core/test/core_module_test.mjs +0 -23
- package/core/test/debug_test.mjs +0 -16
- package/core/test/dncl_test.mjs +0 -94
- package/core/test/error_message_test.mjs +0 -210
- package/core/test/error_test.mjs +0 -16
- package/core/test/flow_test.mjs +0 -373
- package/core/test/func_call.mjs +0 -160
- package/core/test/func_test.mjs +0 -149
- package/core/test/indent_test.mjs +0 -364
- package/core/test/lex_test.mjs +0 -168
- package/core/test/literal_test.mjs +0 -73
- package/core/test/nako_lexer_test.mjs +0 -35
- package/core/test/nako_logger_test.mjs +0 -76
- package/core/test/nako_logger_test.mts +0 -78
- package/core/test/plugin_csv_test.mjs +0 -38
- package/core/test/plugin_promise_test.mjs +0 -18
- package/core/test/plugin_system_test.mjs +0 -630
- package/core/test/prepare_test.mjs +0 -96
- package/core/test/re_test.mjs +0 -22
- package/core/test/side_effects_test.mjs +0 -92
- package/core/test/variable_scope_test.mjs +0 -149
- package/core/tsconfig.json +0 -101
|
@@ -1,2195 +1,2195 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* nadesiko v3 parser
|
|
3
|
-
*/
|
|
4
|
-
import { opPriority, keizokuJosi, operatorList } from './nako_parser_const.mjs'
|
|
5
|
-
import { NakoParserBase } from './nako_parser_base.mjs'
|
|
6
|
-
import { NakoSyntaxError } from './nako_errors.mjs'
|
|
7
|
-
import { NakoLexer } from './nako_lexer.mjs'
|
|
8
|
-
import { Token, Ast, FuncListItem, FuncArgs, NewEmptyToken, SourceMap } from './nako_types.mjs'
|
|
9
|
-
|
|
10
|
-
/**
|
|
11
|
-
* 構文解析を行うクラス
|
|
12
|
-
*/
|
|
13
|
-
export class NakoParser extends NakoParserBase {
|
|
14
|
-
/**
|
|
15
|
-
* 構文解析を実行する
|
|
16
|
-
* @param {TokenWithSourceMap[]} tokens 字句解析済みのトークンの配列
|
|
17
|
-
* @param {string} filename 解析対象のモジュール名
|
|
18
|
-
* @return {Ast} AST(構文木)
|
|
19
|
-
*/
|
|
20
|
-
parse (tokens: Token[], filename: string): Ast {
|
|
21
|
-
this.reset()
|
|
22
|
-
this.tokens = tokens
|
|
23
|
-
this.modName = NakoLexer.filenameToModName(filename)
|
|
24
|
-
this.modList.push(this.modName)
|
|
25
|
-
// 解析開始
|
|
26
|
-
return this.startParser()
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
/** パーサーの一番最初に呼び出す構文規則 */
|
|
30
|
-
startParser (): Ast {
|
|
31
|
-
const b: Ast = this.ySentenceList()
|
|
32
|
-
const c: Token|null = this.get()
|
|
33
|
-
if (c && c.type !== 'eof') {
|
|
34
|
-
this.logger.debug(`構文解析でエラー。${this.nodeToStr(c, { depth: 1 }, true)}の使い方が間違っています。`, c)
|
|
35
|
-
throw NakoSyntaxError.fromNode(`構文解析でエラー。${this.nodeToStr(c, { depth: 1 }, false)}の使い方が間違っています。`, c)
|
|
36
|
-
}
|
|
37
|
-
return b
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** 複数文を返す */
|
|
41
|
-
ySentenceList (): Ast {
|
|
42
|
-
const blocks = []
|
|
43
|
-
let line = -1
|
|
44
|
-
const map = this.peekSourceMap()
|
|
45
|
-
while (!this.isEOF()) {
|
|
46
|
-
const n: Ast|null = this.ySentence()
|
|
47
|
-
if (!n) { break }
|
|
48
|
-
blocks.push(n)
|
|
49
|
-
if (line < 0) { line = n.line }
|
|
50
|
-
}
|
|
51
|
-
if (blocks.length === 0) {
|
|
52
|
-
const token = this.peek() || this.tokens[0]
|
|
53
|
-
this.logger.debug('構文解析に失敗:' + this.nodeToStr(this.peek(), { depth: 1 }, true), token)
|
|
54
|
-
throw NakoSyntaxError.fromNode('構文解析に失敗:' + this.nodeToStr(this.peek(), { depth: 1 }, false), token)
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
return { type: 'block', block: blocks, ...map, end: this.peekSourceMap(), genMode: this.genMode }
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
yEOL (): Ast | null {
|
|
61
|
-
// 行末のチェック #1009
|
|
62
|
-
const eol = this.get()
|
|
63
|
-
if (!eol) { return null }
|
|
64
|
-
// 余剰スタックの確認
|
|
65
|
-
if (this.stack.length > 0) {
|
|
66
|
-
/** 余剰スタックのレポートを作る */
|
|
67
|
-
const words: string[] = []
|
|
68
|
-
this.stack.forEach((t) => {
|
|
69
|
-
let w = this.nodeToStr(t, { depth: 1 }, false)
|
|
70
|
-
if (t.josi) { w += t.josi }
|
|
71
|
-
words.push(w)
|
|
72
|
-
})
|
|
73
|
-
const desc = words.join(',')
|
|
74
|
-
// 最近使った関数の使い方レポートを作る #1093
|
|
75
|
-
let descFunc = ''
|
|
76
|
-
const chA = 'A'.charCodeAt(0)
|
|
77
|
-
for (const f of this.recentlyCalledFunc) {
|
|
78
|
-
descFunc += ' - '
|
|
79
|
-
let no = 0
|
|
80
|
-
const josiA: FuncArgs | undefined = (f as FuncListItem).josi
|
|
81
|
-
if (josiA) {
|
|
82
|
-
for (const arg of josiA) {
|
|
83
|
-
const ch = String.fromCharCode(chA + no)
|
|
84
|
-
descFunc += ch
|
|
85
|
-
if (arg.length === 1) { descFunc += arg[0] } else { descFunc += `(${arg.join('|')})` }
|
|
86
|
-
no++
|
|
87
|
-
}
|
|
88
|
-
}
|
|
89
|
-
descFunc += f.name + '\n'
|
|
90
|
-
}
|
|
91
|
-
throw NakoSyntaxError.fromNode(
|
|
92
|
-
`未解決の単語があります: [${desc}]\n次の命令の可能性があります:\n${descFunc}`, eol)
|
|
93
|
-
}
|
|
94
|
-
this.recentlyCalledFunc = []
|
|
95
|
-
return eol as Ast
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
/** @returns {Ast | null} */
|
|
99
|
-
ySentence (): Ast | null {
|
|
100
|
-
const map: SourceMap = this.peekSourceMap()
|
|
101
|
-
|
|
102
|
-
// 最初の語句が決まっている構文
|
|
103
|
-
if (this.check('eol')) { return this.yEOL() }
|
|
104
|
-
if (this.check('もし')) { return this.yIF() }
|
|
105
|
-
if (this.check('後判定')) { return this.yAtohantei() }
|
|
106
|
-
if (this.check('エラー監視')) { return this.yTryExcept() }
|
|
107
|
-
if (this.check('逐次実行')) { return this.yTikuji() }
|
|
108
|
-
if (this.accept(['抜ける'])) { return { type: 'break', josi: '', ...map, end: this.peekSourceMap() } }
|
|
109
|
-
if (this.accept(['続ける'])) { return { type: 'continue', josi: '', ...map, end: this.peekSourceMap() } }
|
|
110
|
-
if (this.accept(['require', 'string', '取込'])) { return this.yRequire() }
|
|
111
|
-
if (this.accept(['not', '非同期モード'])) { return this.yASyncMode() }
|
|
112
|
-
if (this.accept(['not', 'DNCLモード'])) { return this.yDNCLMode() }
|
|
113
|
-
if (this.accept(['not', 'string', 'モード設定'])) { return this.ySetGenMode(this.y[1].value) }
|
|
114
|
-
|
|
115
|
-
// 関数呼び出し演算子
|
|
116
|
-
if (this.check2(['func', '←'])) { return this.yCallOp() }
|
|
117
|
-
if (this.check2(['func', 'eq'])) {
|
|
118
|
-
const word: Token = this.get() || NewEmptyToken('?', '?', map.line, map.file || '')
|
|
119
|
-
throw NakoSyntaxError.fromNode(`関数『${word.value}』に代入できません。『←』を使ってください。`, word)
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// 先読みして初めて確定する構文
|
|
123
|
-
if (this.accept([this.ySpeedMode])) { return this.y[0] }
|
|
124
|
-
if (this.accept([this.yPerformanceMonitor])) { return this.y[0] }
|
|
125
|
-
if (this.accept([this.yLet])) { return this.y[0] }
|
|
126
|
-
if (this.accept([this.yDefTest])) { return this.y[0] }
|
|
127
|
-
if (this.accept([this.yDefFunc])) { return this.y[0] }
|
|
128
|
-
|
|
129
|
-
// 関数呼び出しの他、各種構文の実装
|
|
130
|
-
if (this.accept([this.yCall])) {
|
|
131
|
-
const c1 = this.y[0]
|
|
132
|
-
if (c1.josi === 'して') { // 連文をblockとして接続する(もし構文、逐次実行構文などのため)
|
|
133
|
-
const c2 = this.ySentence()
|
|
134
|
-
if (c2 !== null) {
|
|
135
|
-
return {
|
|
136
|
-
type: 'block',
|
|
137
|
-
block: [c1, c2],
|
|
138
|
-
josi: c2.josi,
|
|
139
|
-
...map,
|
|
140
|
-
end: this.peekSourceMap()
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
return c1
|
|
145
|
-
}
|
|
146
|
-
return null
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
/** @returns {Ast} */
|
|
150
|
-
yASyncMode (): Ast {
|
|
151
|
-
const map = this.peekSourceMap()
|
|
152
|
-
this.genMode = '非同期モード'
|
|
153
|
-
return { type: 'eol', ...map, end: this.peekSourceMap() }
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
/** @returns {Ast} */
|
|
157
|
-
yDNCLMode (): Ast {
|
|
158
|
-
const map = this.peekSourceMap()
|
|
159
|
-
// 配列インデックスは1から
|
|
160
|
-
this.arrayIndexFrom = 1
|
|
161
|
-
// 配列アクセスをJSと逆順で指定する
|
|
162
|
-
this.flagReverseArrayIndex = true
|
|
163
|
-
// 配列代入時自動で初期化チェックする
|
|
164
|
-
this.flagCheckArrayInit = true
|
|
165
|
-
return { type: 'eol', ...map, end: this.peekSourceMap() }
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
/** @returns {Ast} */
|
|
169
|
-
ySetGenMode (mode: string): Ast {
|
|
170
|
-
const map = this.peekSourceMap()
|
|
171
|
-
this.genMode = mode
|
|
172
|
-
return { type: 'eol', ...map, end: this.peekSourceMap() }
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
/** @returns {Ast} */
|
|
176
|
-
yRequire (): Ast {
|
|
177
|
-
const nameToken = this.y[1]
|
|
178
|
-
const filename = nameToken.value
|
|
179
|
-
const modName = NakoLexer.filenameToModName(filename)
|
|
180
|
-
if (this.modList.indexOf(modName) < 0) {
|
|
181
|
-
// 優先度が最も高いのは modList[0]
|
|
182
|
-
// [memo] モジュールの検索優先度は、下に書くほど高くなる
|
|
183
|
-
const modSelf: string|undefined = this.modList.shift()
|
|
184
|
-
if (modSelf) {
|
|
185
|
-
this.modList.unshift(modName)
|
|
186
|
-
this.modList.unshift(modSelf)
|
|
187
|
-
}
|
|
188
|
-
}
|
|
189
|
-
return {
|
|
190
|
-
type: 'require',
|
|
191
|
-
value: filename,
|
|
192
|
-
josi: '',
|
|
193
|
-
...this.peekSourceMap(),
|
|
194
|
-
end: this.peekSourceMap()
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
/** @returns {Ast} */
|
|
199
|
-
yBlock (): Ast {
|
|
200
|
-
const map = this.peekSourceMap()
|
|
201
|
-
const blocks = []
|
|
202
|
-
if (this.check('ここから')) { this.get() }
|
|
203
|
-
while (!this.isEOF()) {
|
|
204
|
-
if (this.checkTypes(['違えば', 'ここまで', 'エラー'])) { break }
|
|
205
|
-
if (!this.accept([this.ySentence])) { break }
|
|
206
|
-
blocks.push(this.y[0])
|
|
207
|
-
}
|
|
208
|
-
return { type: 'block', block: blocks, ...map, end: this.peekSourceMap() }
|
|
209
|
-
}
|
|
210
|
-
|
|
211
|
-
yDefFuncReadArgs (): Ast[]|null {
|
|
212
|
-
if (!this.check('(')) { return null }
|
|
213
|
-
const a: Ast[] = []
|
|
214
|
-
this.get() // skip '('
|
|
215
|
-
while (!this.isEOF()) {
|
|
216
|
-
if (this.check(')')) {
|
|
217
|
-
this.get() // skip ''
|
|
218
|
-
break
|
|
219
|
-
}
|
|
220
|
-
const t = this.get()
|
|
221
|
-
if (t) { a.push(t as Ast) }
|
|
222
|
-
if (this.check('comma')) { this.get() }
|
|
223
|
-
}
|
|
224
|
-
return a
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
/** @returns {Ast | null} */
|
|
228
|
-
yDefTest (): Ast|null {
|
|
229
|
-
return this.yDef('def_test')
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
/** @returns {Ast | null} */
|
|
233
|
-
yDefFunc (): Ast|null {
|
|
234
|
-
return this.yDef('def_func')
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* @param {string} type
|
|
239
|
-
* @returns {Ast | null}
|
|
240
|
-
*/
|
|
241
|
-
yDef (type: string): Ast|null {
|
|
242
|
-
if (!this.check(type)) {
|
|
243
|
-
return null
|
|
244
|
-
}
|
|
245
|
-
const map = this.peekSourceMap()
|
|
246
|
-
const def = this.get() // ●
|
|
247
|
-
if (!def) { return null }
|
|
248
|
-
let defArgs: Ast[] = []
|
|
249
|
-
if (this.check('(')) { defArgs = this.yDefFuncReadArgs() || [] } // // lexerでも解析しているが再度詳しく
|
|
250
|
-
|
|
251
|
-
const funcName: Token|null = this.get()
|
|
252
|
-
if (!funcName || funcName.type !== 'func') {
|
|
253
|
-
this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の宣言でエラー。', funcName)
|
|
254
|
-
throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の宣言でエラー。', def)
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (this.check('(')) {
|
|
258
|
-
// 関数引数の二重定義
|
|
259
|
-
if (defArgs.length > 0) {
|
|
260
|
-
this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の宣言で、引数定義は名前の前か後に一度だけ可能です。', funcName)
|
|
261
|
-
throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の宣言で、引数定義は名前の前か後に一度だけ可能です。', funcName)
|
|
262
|
-
}
|
|
263
|
-
defArgs = this.yDefFuncReadArgs() || []
|
|
264
|
-
}
|
|
265
|
-
|
|
266
|
-
if (this.check('とは')) { this.get() }
|
|
267
|
-
let block: Ast|null = null
|
|
268
|
-
let multiline = false
|
|
269
|
-
let asyncFn = false
|
|
270
|
-
if (this.check('ここから')) { multiline = true }
|
|
271
|
-
if (this.check('eol')) { multiline = true }
|
|
272
|
-
try {
|
|
273
|
-
this.funcLevel++
|
|
274
|
-
this.usedAsyncFn = false
|
|
275
|
-
// ローカル変数を生成
|
|
276
|
-
const backupLocalvars = this.localvars
|
|
277
|
-
this.localvars = { 'それ': { type: 'var', value: '' } }
|
|
278
|
-
|
|
279
|
-
if (multiline) {
|
|
280
|
-
this.saveStack()
|
|
281
|
-
// 関数の引数をローカル変数として登録する
|
|
282
|
-
for (const arg of defArgs) {
|
|
283
|
-
const fnName: string = (arg.value) ? arg.value + '' : ''
|
|
284
|
-
this.localvars[fnName] = { 'type': 'var', 'value': '' }
|
|
285
|
-
}
|
|
286
|
-
block = this.yBlock()
|
|
287
|
-
if (this.check('ここまで')) { this.get() } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。関数定義の末尾に必要です。', def) }
|
|
288
|
-
this.loadStack()
|
|
289
|
-
} else {
|
|
290
|
-
this.saveStack()
|
|
291
|
-
block = this.ySentence()
|
|
292
|
-
this.loadStack()
|
|
293
|
-
}
|
|
294
|
-
this.funcLevel--
|
|
295
|
-
asyncFn = this.usedAsyncFn
|
|
296
|
-
this.localvars = backupLocalvars
|
|
297
|
-
} catch (err: any) {
|
|
298
|
-
this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) +
|
|
299
|
-
'の定義で以下のエラーがありました。\n' + err.message, def)
|
|
300
|
-
throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) +
|
|
301
|
-
'の定義で以下のエラーがありました。\n' + err.message, def)
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
return {
|
|
305
|
-
type,
|
|
306
|
-
name: funcName,
|
|
307
|
-
args: defArgs,
|
|
308
|
-
block: block || [],
|
|
309
|
-
asyncFn,
|
|
310
|
-
josi: '',
|
|
311
|
-
...map,
|
|
312
|
-
end: this.peekSourceMap()
|
|
313
|
-
}
|
|
314
|
-
}
|
|
315
|
-
|
|
316
|
-
/** @returns {Ast | null} */
|
|
317
|
-
yIFCond (): Ast | null { // もしの条件の取得
|
|
318
|
-
const map = this.peekSourceMap()
|
|
319
|
-
let a: Ast | null = this.yGetArg()
|
|
320
|
-
if (!a) { return null }
|
|
321
|
-
// console.log('yIFCond=', a, this.peek())
|
|
322
|
-
|
|
323
|
-
// チェック : AがBならば
|
|
324
|
-
if (a.josi === 'が') {
|
|
325
|
-
const tmpI = this.index
|
|
326
|
-
const b = this.yGetArg()
|
|
327
|
-
const naraba = this.get()
|
|
328
|
-
if ((b && b.type !== 'func') && (naraba && naraba.type === 'ならば')) {
|
|
329
|
-
return {
|
|
330
|
-
type: 'op',
|
|
331
|
-
operator: (naraba.value === 'でなければ') ? 'noteq' : 'eq',
|
|
332
|
-
left: a,
|
|
333
|
-
right: b,
|
|
334
|
-
josi: '',
|
|
335
|
-
...map,
|
|
336
|
-
end: this.peekSourceMap()
|
|
337
|
-
}
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
this.index = tmpI
|
|
341
|
-
}
|
|
342
|
-
if (a.josi !== '') {
|
|
343
|
-
// もし文で関数呼び出しがある場合
|
|
344
|
-
this.stack.push(a)
|
|
345
|
-
a = this.yCall()
|
|
346
|
-
}
|
|
347
|
-
// (ならば|でなければ)を確認
|
|
348
|
-
if (!this.check('ならば')) {
|
|
349
|
-
const smap: Ast = a || { type: '?', ...map }
|
|
350
|
-
this.logger.debug(
|
|
351
|
-
'もし文で『ならば』がないか、条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false) + 'の直前に『ならば』を書いてください。', smap)
|
|
352
|
-
throw NakoSyntaxError.fromNode(
|
|
353
|
-
'もし文で『ならば』がないか、条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false) + 'の直前に『ならば』を書いてください。', smap)
|
|
354
|
-
}
|
|
355
|
-
|
|
356
|
-
const naraba = this.get()
|
|
357
|
-
if (naraba && naraba.value === 'でなければ') {
|
|
358
|
-
a = {
|
|
359
|
-
type: 'not',
|
|
360
|
-
value: a,
|
|
361
|
-
josi: '',
|
|
362
|
-
...map,
|
|
363
|
-
end: this.peekSourceMap()
|
|
364
|
-
}
|
|
365
|
-
}
|
|
366
|
-
|
|
367
|
-
return a
|
|
368
|
-
}
|
|
369
|
-
|
|
370
|
-
/** @returns {Ast | null} */
|
|
371
|
-
yIF (): Ast | null {
|
|
372
|
-
const map = this.peekSourceMap()
|
|
373
|
-
if (!this.check('もし')) { return null }
|
|
374
|
-
const mosi:Token|null = this.get() // skip もし
|
|
375
|
-
if (mosi == null) { return null }
|
|
376
|
-
while (this.check('comma')) { this.get() } // skip comma
|
|
377
|
-
let cond: Ast|null = null
|
|
378
|
-
try {
|
|
379
|
-
cond = this.yIFCond()
|
|
380
|
-
} catch (err: any) {
|
|
381
|
-
throw NakoSyntaxError.fromNode('『もし』文の条件で次のエラーがあります。\n' + err.message, mosi)
|
|
382
|
-
}
|
|
383
|
-
if (cond === null) {
|
|
384
|
-
throw NakoSyntaxError.fromNode('『もし』文で条件の指定が空です。', mosi)
|
|
385
|
-
}
|
|
386
|
-
let trueBlock: Ast|null = null
|
|
387
|
-
let falseBlock: Ast|null = null
|
|
388
|
-
let tanbun = false
|
|
389
|
-
|
|
390
|
-
// True Block
|
|
391
|
-
if (this.check('eol')) {
|
|
392
|
-
trueBlock = this.yBlock()
|
|
393
|
-
} else {
|
|
394
|
-
trueBlock = this.ySentence()
|
|
395
|
-
tanbun = true
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
// skip EOL
|
|
399
|
-
while (this.check('eol')) { this.get() }
|
|
400
|
-
|
|
401
|
-
// Flase Block
|
|
402
|
-
if (this.check('違えば')) {
|
|
403
|
-
this.get() // skip 違えば
|
|
404
|
-
while (this.check('comma')) { this.get() }
|
|
405
|
-
if (this.check('eol')) {
|
|
406
|
-
falseBlock = this.yBlock()
|
|
407
|
-
} else {
|
|
408
|
-
falseBlock = this.ySentence()
|
|
409
|
-
tanbun = true
|
|
410
|
-
}
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
if (tanbun === false) {
|
|
414
|
-
if (this.check('ここまで')) {
|
|
415
|
-
this.get()
|
|
416
|
-
} else {
|
|
417
|
-
throw NakoSyntaxError.fromNode('『もし』文で『ここまで』がありません。', mosi)
|
|
418
|
-
}
|
|
419
|
-
}
|
|
420
|
-
return {
|
|
421
|
-
type: 'if',
|
|
422
|
-
expr: cond || [],
|
|
423
|
-
block: trueBlock || [],
|
|
424
|
-
false_block: falseBlock || [],
|
|
425
|
-
josi: '',
|
|
426
|
-
...map,
|
|
427
|
-
end: this.peekSourceMap()
|
|
428
|
-
}
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
ySpeedMode (): Ast | null {
|
|
432
|
-
const map: SourceMap = this.peekSourceMap()
|
|
433
|
-
if (!this.check2(['string', '実行速度優先'])) {
|
|
434
|
-
return null
|
|
435
|
-
}
|
|
436
|
-
const optionNode: Token|null = this.get()
|
|
437
|
-
this.get()
|
|
438
|
-
let val = ''
|
|
439
|
-
if (optionNode && optionNode.value) { val = optionNode.value } else { return null }
|
|
440
|
-
|
|
441
|
-
const options: {[key: string]: boolean} = { 行番号無し: false, 暗黙の型変換無し: false, 強制ピュア: false, それ無効: false }
|
|
442
|
-
for (const name of val.split('/')) {
|
|
443
|
-
// 全て有効化
|
|
444
|
-
if (name === '全て') {
|
|
445
|
-
for (const k of Object.keys(options)) {
|
|
446
|
-
options[k] = true
|
|
447
|
-
}
|
|
448
|
-
break
|
|
449
|
-
}
|
|
450
|
-
|
|
451
|
-
// 個別に有効化
|
|
452
|
-
if (Object.keys(options).includes(name)) {
|
|
453
|
-
options[name] = true
|
|
454
|
-
} else {
|
|
455
|
-
// 互換性を考えて、警告に留める。
|
|
456
|
-
this.logger.warn(`実行速度優先文のオプション『${name}』は存在しません。`, optionNode)
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
|
|
460
|
-
let multiline = false
|
|
461
|
-
if (this.check('ここから')) {
|
|
462
|
-
this.get()
|
|
463
|
-
multiline = true
|
|
464
|
-
} else if (this.check('eol')) {
|
|
465
|
-
multiline = true
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
let block = null
|
|
469
|
-
if (multiline) {
|
|
470
|
-
block = this.yBlock()
|
|
471
|
-
if (this.check('ここまで')) { this.get() }
|
|
472
|
-
} else {
|
|
473
|
-
block = this.ySentence()
|
|
474
|
-
}
|
|
475
|
-
|
|
476
|
-
return {
|
|
477
|
-
type: 'speed_mode',
|
|
478
|
-
options,
|
|
479
|
-
block: block || [],
|
|
480
|
-
josi: '',
|
|
481
|
-
...map
|
|
482
|
-
}
|
|
483
|
-
}
|
|
484
|
-
|
|
485
|
-
yPerformanceMonitor (): Ast|null {
|
|
486
|
-
const map = this.peekSourceMap()
|
|
487
|
-
if (!this.check2(['string', 'パフォーマンスモニタ適用'])) {
|
|
488
|
-
return null
|
|
489
|
-
}
|
|
490
|
-
const optionNode = this.get()
|
|
491
|
-
if (!optionNode) { return null }
|
|
492
|
-
this.get()
|
|
493
|
-
|
|
494
|
-
const options: {[key: string]: boolean} = { ユーザ関数: false, システム関数本体: false, システム関数: false }
|
|
495
|
-
for (const name of optionNode.value.split('/')) {
|
|
496
|
-
// 全て有効化
|
|
497
|
-
if (name === '全て') {
|
|
498
|
-
for (const k of Object.keys(options)) {
|
|
499
|
-
options[k] = true
|
|
500
|
-
}
|
|
501
|
-
break
|
|
502
|
-
}
|
|
503
|
-
|
|
504
|
-
// 個別に有効化
|
|
505
|
-
if (Object.keys(options).includes(name)) {
|
|
506
|
-
options[name] = true
|
|
507
|
-
} else {
|
|
508
|
-
// 互換性を考えて、警告に留める。
|
|
509
|
-
this.logger.warn(`パフォーマンスモニタ適用文のオプション『${name}』は存在しません。`, optionNode)
|
|
510
|
-
}
|
|
511
|
-
}
|
|
512
|
-
|
|
513
|
-
let multiline = false
|
|
514
|
-
if (this.check('ここから')) {
|
|
515
|
-
this.get()
|
|
516
|
-
multiline = true
|
|
517
|
-
} else if (this.check('eol')) {
|
|
518
|
-
multiline = true
|
|
519
|
-
}
|
|
520
|
-
|
|
521
|
-
let block = null
|
|
522
|
-
if (multiline) {
|
|
523
|
-
block = this.yBlock()
|
|
524
|
-
if (this.check('ここまで')) { this.get() }
|
|
525
|
-
} else {
|
|
526
|
-
block = this.ySentence()
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
return {
|
|
530
|
-
type: 'performance_monitor',
|
|
531
|
-
options,
|
|
532
|
-
block: block || [],
|
|
533
|
-
josi: '',
|
|
534
|
-
...map
|
|
535
|
-
}
|
|
536
|
-
}
|
|
537
|
-
|
|
538
|
-
/** (非推奨) 「逐次実行」構文 @returns {Ast | null} */
|
|
539
|
-
yTikuji (): Ast|null {
|
|
540
|
-
const map = this.peekSourceMap()
|
|
541
|
-
if (!this.check('逐次実行')) { return null }
|
|
542
|
-
const tikuji = this.getCur() // skip 逐次実行
|
|
543
|
-
this.logger.warn('『逐次実行』構文の使用は非推奨になりました(https://nadesi.com/v3/doc/go.php?944)。', tikuji)
|
|
544
|
-
const blocks: Ast[] = []
|
|
545
|
-
let errorBlock = null
|
|
546
|
-
if (!tikuji || !this.check('eol')) {
|
|
547
|
-
throw NakoSyntaxError.fromNode('『逐次実行』の直後は改行が必要です。', tikuji)
|
|
548
|
-
}
|
|
549
|
-
// ブロックを読む
|
|
550
|
-
for (;;) {
|
|
551
|
-
if (this.check('ここまで')) { break }
|
|
552
|
-
if (this.check('eol')) {
|
|
553
|
-
this.get() // skip EOL
|
|
554
|
-
continue
|
|
555
|
-
}
|
|
556
|
-
if (this.check2(['エラー', 'ならば'])) {
|
|
557
|
-
this.get() // skip エラー
|
|
558
|
-
this.get() // skip ならば
|
|
559
|
-
errorBlock = this.yBlock()
|
|
560
|
-
break
|
|
561
|
-
}
|
|
562
|
-
let block = null
|
|
563
|
-
// 「先に」「次に」句はブロック宣言 #717 (ただしブロック以外も可能)
|
|
564
|
-
if (this.check('先に') || this.check('次に')) {
|
|
565
|
-
const tugini = this.get() // skip 先に | 次に
|
|
566
|
-
if (this.check('comma')) { this.get() }
|
|
567
|
-
if (this.check('eol')) { // block
|
|
568
|
-
block = this.yBlock()
|
|
569
|
-
if (!this.check('ここまで')) {
|
|
570
|
-
let tuginiType = '次に'
|
|
571
|
-
if (tugini != null) { tuginiType = tugini.type }
|
|
572
|
-
throw NakoSyntaxError.fromNode(`『${tuginiType}』...『ここまで』を対応させてください。`, map)
|
|
573
|
-
}
|
|
574
|
-
this.get() // skip 'ここまで'
|
|
575
|
-
} else { // line
|
|
576
|
-
block = this.ySentence()
|
|
577
|
-
}
|
|
578
|
-
} else {
|
|
579
|
-
block = this.ySentence()
|
|
580
|
-
}
|
|
581
|
-
// add block
|
|
582
|
-
if (block != null) { blocks.push(block) }
|
|
583
|
-
}
|
|
584
|
-
if (!this.check('ここまで')) {
|
|
585
|
-
console.log(blocks, this.peek())
|
|
586
|
-
throw NakoSyntaxError.fromNode('『逐次実行』...『ここまで』を対応させてください。', tikuji)
|
|
587
|
-
}
|
|
588
|
-
this.get() // skip 'ここまで'
|
|
589
|
-
return {
|
|
590
|
-
type: 'tikuji',
|
|
591
|
-
blocks: blocks || [],
|
|
592
|
-
errorBlock: errorBlock || [],
|
|
593
|
-
josi: '',
|
|
594
|
-
...map,
|
|
595
|
-
end: this.peekSourceMap()
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
/**
|
|
600
|
-
* 1つ目の値を与え、その後に続く計算式を取得し、優先規則に沿って並び替えして戻す
|
|
601
|
-
* @param {Ast} firstValue
|
|
602
|
-
*/
|
|
603
|
-
yGetArgOperator (firstValue: Ast): Ast|null {
|
|
604
|
-
const args:Ast[] = [firstValue]
|
|
605
|
-
while (!this.isEOF()) {
|
|
606
|
-
// 演算子がある?
|
|
607
|
-
let op = this.peek()
|
|
608
|
-
if (op && opPriority[op.type]) {
|
|
609
|
-
op = this.getCur()
|
|
610
|
-
args.push(op as Ast)
|
|
611
|
-
// 演算子後の値を取得
|
|
612
|
-
const v = this.yValue()
|
|
613
|
-
if (v === null) {
|
|
614
|
-
throw NakoSyntaxError.fromNode(
|
|
615
|
-
`計算式で演算子『${op.value}』後に値がありません`,
|
|
616
|
-
firstValue)
|
|
617
|
-
}
|
|
618
|
-
args.push(v)
|
|
619
|
-
continue
|
|
620
|
-
}
|
|
621
|
-
break
|
|
622
|
-
}
|
|
623
|
-
if (args.length === 0) { return null }
|
|
624
|
-
if (args.length === 1) { return args[0] }
|
|
625
|
-
return this.infixToAST(args)
|
|
626
|
-
}
|
|
627
|
-
|
|
628
|
-
yGetArg (): Ast|null {
|
|
629
|
-
// 値を一つ読む
|
|
630
|
-
const value1 = this.yValue()
|
|
631
|
-
if (value1 === null) { return null }
|
|
632
|
-
// 計算式がある場合を考慮
|
|
633
|
-
return this.yGetArgOperator(value1)
|
|
634
|
-
}
|
|
635
|
-
|
|
636
|
-
infixToPolish (list: Ast[]): Ast[] {
|
|
637
|
-
// 中間記法から逆ポーランドに変換
|
|
638
|
-
const priority = (t: Ast) => {
|
|
639
|
-
if (opPriority[t.type]) { return opPriority[t.type] }
|
|
640
|
-
return 10
|
|
641
|
-
}
|
|
642
|
-
const stack: Ast[] = []
|
|
643
|
-
const polish: Ast[] = []
|
|
644
|
-
while (list.length > 0) {
|
|
645
|
-
const t = list.shift()
|
|
646
|
-
if (!t) { break }
|
|
647
|
-
while (stack.length > 0) { // 優先順位を見て移動する
|
|
648
|
-
const sTop = stack[stack.length - 1]
|
|
649
|
-
if (priority(t) > priority(sTop)) { break }
|
|
650
|
-
const tpop = stack.pop()
|
|
651
|
-
if (!tpop) {
|
|
652
|
-
this.logger.error('計算式に間違いがあります。', t)
|
|
653
|
-
break
|
|
654
|
-
}
|
|
655
|
-
polish.push(tpop)
|
|
656
|
-
}
|
|
657
|
-
stack.push(t)
|
|
658
|
-
}
|
|
659
|
-
// 残った要素を積み替える
|
|
660
|
-
while (stack.length > 0) {
|
|
661
|
-
const t = stack.pop()
|
|
662
|
-
if (t) { polish.push(t) }
|
|
663
|
-
}
|
|
664
|
-
return polish
|
|
665
|
-
}
|
|
666
|
-
|
|
667
|
-
/** @returns {Ast | null} */
|
|
668
|
-
infixToAST (list: Ast[]): Ast | null {
|
|
669
|
-
if (list.length === 0) { return null }
|
|
670
|
-
// 逆ポーランドを構文木に
|
|
671
|
-
const josi = list[list.length - 1].josi
|
|
672
|
-
const node = list[list.length - 1]
|
|
673
|
-
const polish = this.infixToPolish(list)
|
|
674
|
-
/** @type {Ast[]} */
|
|
675
|
-
const stack = []
|
|
676
|
-
for (const t of polish) {
|
|
677
|
-
if (!opPriority[t.type]) { // 演算子ではない
|
|
678
|
-
stack.push(t)
|
|
679
|
-
continue
|
|
680
|
-
}
|
|
681
|
-
const b:Ast|undefined = stack.pop()
|
|
682
|
-
const a:Ast|undefined = stack.pop()
|
|
683
|
-
if (a === undefined || b === undefined) {
|
|
684
|
-
this.logger.debug('--- 計算式(逆ポーランド) ---\n' + JSON.stringify(polish))
|
|
685
|
-
throw NakoSyntaxError.fromNode('計算式でエラー', node)
|
|
686
|
-
}
|
|
687
|
-
/** @type {Ast} */
|
|
688
|
-
const op:Ast = {
|
|
689
|
-
type: 'op',
|
|
690
|
-
operator: t.type,
|
|
691
|
-
left: a,
|
|
692
|
-
right: b,
|
|
693
|
-
josi,
|
|
694
|
-
startOffset: a.startOffset,
|
|
695
|
-
endOffset: a.endOffset,
|
|
696
|
-
line: a.line,
|
|
697
|
-
column: a.column,
|
|
698
|
-
file: a.file
|
|
699
|
-
}
|
|
700
|
-
stack.push(op)
|
|
701
|
-
}
|
|
702
|
-
const ans = stack.pop()
|
|
703
|
-
if (!ans) { return null }
|
|
704
|
-
return ans
|
|
705
|
-
}
|
|
706
|
-
|
|
707
|
-
yGetArgParen (y: Ast[]): Ast[] { // C言語風呼び出しでカッコの中を取得
|
|
708
|
-
let isClose = false
|
|
709
|
-
const si = this.stack.length
|
|
710
|
-
while (!this.isEOF()) {
|
|
711
|
-
if (this.check(')')) {
|
|
712
|
-
isClose = true
|
|
713
|
-
break
|
|
714
|
-
}
|
|
715
|
-
const v = this.yGetArg()
|
|
716
|
-
if (v) {
|
|
717
|
-
this.pushStack(v)
|
|
718
|
-
if (this.check('comma')) { this.get() }
|
|
719
|
-
continue
|
|
720
|
-
}
|
|
721
|
-
break
|
|
722
|
-
}
|
|
723
|
-
if (!isClose) {
|
|
724
|
-
throw NakoSyntaxError.fromNode(`C風関数『${y[0].value}』でカッコが閉じていません`, y[0])
|
|
725
|
-
}
|
|
726
|
-
const a: Ast[] = []
|
|
727
|
-
while (si < this.stack.length) {
|
|
728
|
-
const v = this.popStack()
|
|
729
|
-
if (v) { a.unshift(v) }
|
|
730
|
-
}
|
|
731
|
-
return a
|
|
732
|
-
}
|
|
733
|
-
|
|
734
|
-
/** @returns {Ast | null} */
|
|
735
|
-
yRepeatTime (): Ast|null {
|
|
736
|
-
const map = this.peekSourceMap()
|
|
737
|
-
if (!this.check('回')) { return null }
|
|
738
|
-
this.get() // skip '回'
|
|
739
|
-
if (this.check('comma')) { this.get() } // skip comma
|
|
740
|
-
if (this.check('繰返')) { this.get() } // skip 'N回、繰り返す' (#924)
|
|
741
|
-
let num = this.popStack([])
|
|
742
|
-
let multiline = false
|
|
743
|
-
let block = null
|
|
744
|
-
if (num === null) { num = { type: 'word', value: 'それ', josi: '', ...map, end: this.peekSourceMap() } }
|
|
745
|
-
if (this.check('comma')) { this.get() }
|
|
746
|
-
if (this.check('ここから')) {
|
|
747
|
-
this.get()
|
|
748
|
-
multiline = true
|
|
749
|
-
} else if (this.check('eol')) {
|
|
750
|
-
multiline = true
|
|
751
|
-
}
|
|
752
|
-
if (multiline) { // multiline
|
|
753
|
-
block = this.yBlock()
|
|
754
|
-
if (this.check('ここまで')) { this.get() } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。『回』...『ここまで』を対応させてください。', map) }
|
|
755
|
-
} else {
|
|
756
|
-
// singleline
|
|
757
|
-
block = this.ySentence()
|
|
758
|
-
}
|
|
759
|
-
|
|
760
|
-
return {
|
|
761
|
-
type: 'repeat_times',
|
|
762
|
-
value: num,
|
|
763
|
-
block: block || [],
|
|
764
|
-
josi: '',
|
|
765
|
-
...map,
|
|
766
|
-
end: this.peekSourceMap()
|
|
767
|
-
}
|
|
768
|
-
}
|
|
769
|
-
|
|
770
|
-
/** @returns {Ast | null} */
|
|
771
|
-
yWhile (): Ast | null {
|
|
772
|
-
const map = this.peekSourceMap()
|
|
773
|
-
if (!this.check('間')) { return null }
|
|
774
|
-
this.get() // skip '間'
|
|
775
|
-
while (this.check('comma')) { this.get() } // skip ','
|
|
776
|
-
if (this.check('繰返')) { this.get() } // skip '繰り返す' #927
|
|
777
|
-
const cond = this.popStack()
|
|
778
|
-
if (cond === null) {
|
|
779
|
-
throw NakoSyntaxError.fromNode('『間』で条件がありません。', map)
|
|
780
|
-
}
|
|
781
|
-
if (this.check('comma')) { this.get() }
|
|
782
|
-
if (!this.checkTypes(['ここから', 'eol'])) {
|
|
783
|
-
throw NakoSyntaxError.fromNode('『間』の直後は改行が必要です', map)
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
const block = this.yBlock()
|
|
787
|
-
if (this.check('ここまで')) { this.get() }
|
|
788
|
-
return {
|
|
789
|
-
type: 'while',
|
|
790
|
-
cond,
|
|
791
|
-
block,
|
|
792
|
-
josi: '',
|
|
793
|
-
...map,
|
|
794
|
-
end: this.peekSourceMap()
|
|
795
|
-
}
|
|
796
|
-
}
|
|
797
|
-
|
|
798
|
-
/** @returns {Ast | null} */
|
|
799
|
-
yAtohantei (): Ast|null {
|
|
800
|
-
const map = this.peekSourceMap()
|
|
801
|
-
if (this.check('後判定')) { this.get() } // skip 後判定
|
|
802
|
-
if (this.check('繰返')) { this.get() } // skip 繰り返す
|
|
803
|
-
if (this.check('ここから')) { this.get() }
|
|
804
|
-
const block = this.yBlock()
|
|
805
|
-
if (this.check('ここまで')) { this.get() }
|
|
806
|
-
if (this.check('comma')) { this.get() }
|
|
807
|
-
let cond = this.yGetArg() // 条件
|
|
808
|
-
let bUntil = false
|
|
809
|
-
const t = this.peek()
|
|
810
|
-
if (t && t.value === 'なる' && (t.josi === 'まで' || t.josi === 'までの')) {
|
|
811
|
-
this.get() // skip なるまで
|
|
812
|
-
bUntil = true
|
|
813
|
-
}
|
|
814
|
-
if (this.check('間')) { this.get() } // skip 間
|
|
815
|
-
if (bUntil) { // 条件を反転する
|
|
816
|
-
cond = {
|
|
817
|
-
type: 'not',
|
|
818
|
-
value: cond,
|
|
819
|
-
josi: '',
|
|
820
|
-
...map,
|
|
821
|
-
end: this.peekSourceMap()
|
|
822
|
-
}
|
|
823
|
-
}
|
|
824
|
-
return {
|
|
825
|
-
type: 'atohantei',
|
|
826
|
-
cond: cond || [],
|
|
827
|
-
block,
|
|
828
|
-
josi: '',
|
|
829
|
-
...map,
|
|
830
|
-
end: this.peekSourceMap()
|
|
831
|
-
}
|
|
832
|
-
}
|
|
833
|
-
|
|
834
|
-
/** @returns {Ast | null} */
|
|
835
|
-
yFor (): Ast|null {
|
|
836
|
-
const map = this.peekSourceMap()
|
|
837
|
-
if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) {
|
|
838
|
-
// pass
|
|
839
|
-
} else {
|
|
840
|
-
return null
|
|
841
|
-
}
|
|
842
|
-
const kurikaesu: Token = this.getCur() // skip 繰り返す
|
|
843
|
-
// スタックに(増や|減ら)してがある?
|
|
844
|
-
const incdec = this.stack.pop()
|
|
845
|
-
if (incdec) {
|
|
846
|
-
if (incdec.type === 'word' && (incdec.value === '増' || incdec.value === '減')) {
|
|
847
|
-
kurikaesu.type = incdec.value + kurikaesu.type
|
|
848
|
-
// ↑ typeを増繰返 | 減繰返 に変換
|
|
849
|
-
} else {
|
|
850
|
-
// 普通の繰り返しの場合
|
|
851
|
-
this.stack.push(incdec) // 違ったので改めて追加
|
|
852
|
-
}
|
|
853
|
-
}
|
|
854
|
-
let vInc = null
|
|
855
|
-
if (kurikaesu.type === '増繰返' || kurikaesu.type === '減繰返') {
|
|
856
|
-
vInc = this.popStack(['ずつ'])
|
|
857
|
-
}
|
|
858
|
-
const vTo = this.popStack(['まで'])
|
|
859
|
-
const vFrom = this.popStack(['から'])
|
|
860
|
-
const word = this.popStack(['を', 'で'])
|
|
861
|
-
if (vFrom === null || vTo === null) {
|
|
862
|
-
throw NakoSyntaxError.fromNode('『繰り返す』文でAからBまでの指定がありません。', kurikaesu)
|
|
863
|
-
}
|
|
864
|
-
if (this.check('comma')) { this.get() } // skip comma
|
|
865
|
-
let multiline = false
|
|
866
|
-
if (this.check('ここから')) {
|
|
867
|
-
multiline = true
|
|
868
|
-
this.get()
|
|
869
|
-
} else if (this.check('eol')) {
|
|
870
|
-
multiline = true
|
|
871
|
-
this.get()
|
|
872
|
-
}
|
|
873
|
-
let block = null
|
|
874
|
-
if (multiline) {
|
|
875
|
-
block = this.yBlock()
|
|
876
|
-
if (this.check('ここまで')) {
|
|
877
|
-
this.get()
|
|
878
|
-
} else {
|
|
879
|
-
throw NakoSyntaxError.fromNode('『ここまで』がありません。『繰り返す』...『ここまで』を対応させてください。', map)
|
|
880
|
-
}
|
|
881
|
-
} else { block = this.ySentence() }
|
|
882
|
-
|
|
883
|
-
return {
|
|
884
|
-
type: 'for',
|
|
885
|
-
from: vFrom,
|
|
886
|
-
to: vTo,
|
|
887
|
-
inc: vInc,
|
|
888
|
-
word,
|
|
889
|
-
block: block || [],
|
|
890
|
-
josi: '',
|
|
891
|
-
...map,
|
|
892
|
-
end: this.peekSourceMap()
|
|
893
|
-
}
|
|
894
|
-
}
|
|
895
|
-
|
|
896
|
-
/** @returns {Ast | null} */
|
|
897
|
-
yReturn (): Ast|null {
|
|
898
|
-
const map = this.peekSourceMap()
|
|
899
|
-
if (!this.check('戻る')) { return null }
|
|
900
|
-
this.get() // skip '戻る'
|
|
901
|
-
const v = this.popStack(['で', 'を'])
|
|
902
|
-
if (this.stack.length > 0) {
|
|
903
|
-
throw NakoSyntaxError.fromNode('『戻』文の直前に未解決の引数があります。『(式)を戻す』のように式をカッコで括ってください。', map)
|
|
904
|
-
}
|
|
905
|
-
return {
|
|
906
|
-
type: 'return',
|
|
907
|
-
value: v,
|
|
908
|
-
josi: '',
|
|
909
|
-
...map,
|
|
910
|
-
end: this.peekSourceMap()
|
|
911
|
-
}
|
|
912
|
-
}
|
|
913
|
-
|
|
914
|
-
/** @returns {Ast | null} */
|
|
915
|
-
yForEach (): Ast|null {
|
|
916
|
-
const map = this.peekSourceMap()
|
|
917
|
-
if (!this.check('反復')) { return null }
|
|
918
|
-
this.get() // skip '反復'
|
|
919
|
-
while (this.check('comma')) { this.get() } // skip ','
|
|
920
|
-
const target = this.popStack(['を'])
|
|
921
|
-
const name = this.popStack(['で'])
|
|
922
|
-
let block = null
|
|
923
|
-
let multiline = false
|
|
924
|
-
if (this.check('ここから')) {
|
|
925
|
-
multiline = true
|
|
926
|
-
this.get()
|
|
927
|
-
} else if (this.check('eol')) { multiline = true }
|
|
928
|
-
|
|
929
|
-
if (multiline) {
|
|
930
|
-
block = this.yBlock()
|
|
931
|
-
if (this.check('ここまで')) { this.get() }
|
|
932
|
-
} else { block = this.ySentence() }
|
|
933
|
-
|
|
934
|
-
return {
|
|
935
|
-
type: 'foreach',
|
|
936
|
-
name,
|
|
937
|
-
target,
|
|
938
|
-
block: block || [],
|
|
939
|
-
josi: '',
|
|
940
|
-
...map,
|
|
941
|
-
end: this.peekSourceMap()
|
|
942
|
-
}
|
|
943
|
-
}
|
|
944
|
-
|
|
945
|
-
/** 条件分岐構文 */
|
|
946
|
-
ySwitch (): Ast | null {
|
|
947
|
-
const map = this.peekSourceMap()
|
|
948
|
-
if (!this.check('条件分岐')) { return null }
|
|
949
|
-
const joukenbunki = this.get() // skip '条件分岐'
|
|
950
|
-
if (!joukenbunki) { return null }
|
|
951
|
-
const eol = this.get() // skip 'eol'
|
|
952
|
-
if (!eol) { return null }
|
|
953
|
-
const value = this.popStack(['で'])
|
|
954
|
-
if (!value) {
|
|
955
|
-
throw NakoSyntaxError.fromNode('『(値)で条件分岐』のように記述してください。', joukenbunki)
|
|
956
|
-
}
|
|
957
|
-
if (eol.type !== 'eol') {
|
|
958
|
-
throw NakoSyntaxError.fromNode('『条件分岐』の直後は改行してください。', joukenbunki)
|
|
959
|
-
}
|
|
960
|
-
let isDefaultClause = false // 「違えば」内かどうか
|
|
961
|
-
let skippedKokomade = false
|
|
962
|
-
const cases: any[] = []
|
|
963
|
-
while (!this.isEOF()) {
|
|
964
|
-
if (this.check('ここまで')) {
|
|
965
|
-
if (skippedKokomade) {
|
|
966
|
-
throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki)
|
|
967
|
-
}
|
|
968
|
-
this.get() // skip ここまで
|
|
969
|
-
break
|
|
970
|
-
}
|
|
971
|
-
if (this.check('eol')) {
|
|
972
|
-
this.get()
|
|
973
|
-
continue
|
|
974
|
-
}
|
|
975
|
-
if (isDefaultClause) {
|
|
976
|
-
throw NakoSyntaxError.fromNode('『条件分岐』で『違えば〜ここまで』の後に処理を続けることは出来ません。', joukenbunki)
|
|
977
|
-
}
|
|
978
|
-
// 違えば?
|
|
979
|
-
let cond: Ast|null = null
|
|
980
|
-
const condToken: Token|null = this.peek()
|
|
981
|
-
if (condToken && condToken.type === '違えば') {
|
|
982
|
-
// 違えば
|
|
983
|
-
skippedKokomade = false
|
|
984
|
-
isDefaultClause = true
|
|
985
|
-
cond = this.get() as Ast // skip 違えば
|
|
986
|
-
if (this.check('comma')) { this.get() } // skip ','
|
|
987
|
-
} else {
|
|
988
|
-
// ***ならば
|
|
989
|
-
if (skippedKokomade) {
|
|
990
|
-
throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki)
|
|
991
|
-
}
|
|
992
|
-
// 「**ならば」を得る
|
|
993
|
-
cond = this.yValue()
|
|
994
|
-
if (!cond) {
|
|
995
|
-
throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki)
|
|
996
|
-
}
|
|
997
|
-
const naraba = this.get() // skip ならば
|
|
998
|
-
if (!naraba || naraba.type !== 'ならば') {
|
|
999
|
-
throw NakoSyntaxError.fromNode('『条件分岐』で条件は**ならばと記述してください。', joukenbunki)
|
|
1000
|
-
}
|
|
1001
|
-
if (this.check('comma')) { this.get() } // skip ','
|
|
1002
|
-
}
|
|
1003
|
-
// 条件にあったときに実行すること
|
|
1004
|
-
const condBlock = this.yBlock()
|
|
1005
|
-
const kokomade = this.peek()
|
|
1006
|
-
if (kokomade && kokomade.type === 'ここまで') {
|
|
1007
|
-
this.get() // skip ここまで
|
|
1008
|
-
} else {
|
|
1009
|
-
if (isDefaultClause) {
|
|
1010
|
-
throw NakoSyntaxError.fromNode('『条件分岐』は『違えば〜ここまで』と記述してください。', joukenbunki)
|
|
1011
|
-
}
|
|
1012
|
-
// 次が「違えば」の場合に限り、「もし〜ここまで」の「ここまで」を省略できる
|
|
1013
|
-
skippedKokomade = true
|
|
1014
|
-
}
|
|
1015
|
-
cases.push([cond, condBlock])
|
|
1016
|
-
}
|
|
1017
|
-
|
|
1018
|
-
return {
|
|
1019
|
-
type: 'switch',
|
|
1020
|
-
value,
|
|
1021
|
-
cases: cases || [],
|
|
1022
|
-
josi: '',
|
|
1023
|
-
...map,
|
|
1024
|
-
end: this.peekSourceMap()
|
|
1025
|
-
}
|
|
1026
|
-
}
|
|
1027
|
-
|
|
1028
|
-
/** 無名関数 */
|
|
1029
|
-
yMumeiFunc (): Ast | null { // 無名関数の定義
|
|
1030
|
-
const map = this.peekSourceMap()
|
|
1031
|
-
if (!this.check('def_func')) { return null }
|
|
1032
|
-
const def = this.get()
|
|
1033
|
-
if (!def) { return null }
|
|
1034
|
-
let args: Ast[] = []
|
|
1035
|
-
// 「,」を飛ばす
|
|
1036
|
-
if (this.check('comma')) { this.get() }
|
|
1037
|
-
// 関数の引数定義は省略できる
|
|
1038
|
-
if (this.check('(')) { args = this.yDefFuncReadArgs() || [] }
|
|
1039
|
-
// 「,」を飛ばす
|
|
1040
|
-
if (this.check('comma')) { this.get() }
|
|
1041
|
-
// ブロックを読む
|
|
1042
|
-
this.funcLevel++
|
|
1043
|
-
this.saveStack()
|
|
1044
|
-
const block = this.yBlock()
|
|
1045
|
-
// 末尾の「ここまで」をチェック - もしなければエラーにする #1045
|
|
1046
|
-
if (!this.check('ここまで')) {
|
|
1047
|
-
throw NakoSyntaxError.fromNode('『ここまで』がありません。『には』構文か無名関数の末尾に『ここまで』が必要です。', map)
|
|
1048
|
-
}
|
|
1049
|
-
this.get() // skip ここまで
|
|
1050
|
-
this.loadStack()
|
|
1051
|
-
this.funcLevel--
|
|
1052
|
-
return {
|
|
1053
|
-
type: 'func_obj',
|
|
1054
|
-
args,
|
|
1055
|
-
block,
|
|
1056
|
-
meta: def.meta,
|
|
1057
|
-
josi: '',
|
|
1058
|
-
...map,
|
|
1059
|
-
end: this.peekSourceMap()
|
|
1060
|
-
}
|
|
1061
|
-
}
|
|
1062
|
-
|
|
1063
|
-
/** 代入構文 */
|
|
1064
|
-
yDainyu (): Ast | null {
|
|
1065
|
-
const map = this.peekSourceMap()
|
|
1066
|
-
const dainyu = this.get() // 代入
|
|
1067
|
-
if (dainyu === null) { return null }
|
|
1068
|
-
const value = this.popStack(['を'])
|
|
1069
|
-
const word: Ast|null = this.popStack(['へ', 'に'])
|
|
1070
|
-
if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== '配列参照')) {
|
|
1071
|
-
throw NakoSyntaxError.fromNode('代入文で代入先の変数が見当たりません。『(変数名)に(値)を代入』のように使います。', dainyu)
|
|
1072
|
-
}
|
|
1073
|
-
// 配列への代入
|
|
1074
|
-
if (word.type === '配列参照') {
|
|
1075
|
-
return {
|
|
1076
|
-
type: 'let_array',
|
|
1077
|
-
name: word.name,
|
|
1078
|
-
index: word.index,
|
|
1079
|
-
value,
|
|
1080
|
-
josi: '',
|
|
1081
|
-
checkInit: this.flagCheckArrayInit,
|
|
1082
|
-
...map,
|
|
1083
|
-
end: this.peekSourceMap()
|
|
1084
|
-
}
|
|
1085
|
-
}
|
|
1086
|
-
// 一般的な変数への代入
|
|
1087
|
-
const word2 = this.getVarName(word)
|
|
1088
|
-
return {
|
|
1089
|
-
type: 'let',
|
|
1090
|
-
name: word2,
|
|
1091
|
-
value,
|
|
1092
|
-
josi: '',
|
|
1093
|
-
...map,
|
|
1094
|
-
end: this.peekSourceMap()
|
|
1095
|
-
}
|
|
1096
|
-
}
|
|
1097
|
-
|
|
1098
|
-
/** 定める構文 */
|
|
1099
|
-
ySadameru (): Ast | null {
|
|
1100
|
-
const map = this.peekSourceMap()
|
|
1101
|
-
const sadameru = this.get() // 定める
|
|
1102
|
-
if (sadameru === null) { return null }
|
|
1103
|
-
const word = this.popStack(['を'])
|
|
1104
|
-
const value = this.popStack(['へ', 'に'])
|
|
1105
|
-
if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== '配列参照')) {
|
|
1106
|
-
throw NakoSyntaxError.fromNode('『定める』文で定数が見当たりません。『(定数名)を(値)に定める』のように使います。', sadameru)
|
|
1107
|
-
}
|
|
1108
|
-
// 変数を生成する
|
|
1109
|
-
const nameToken = this.getVarName(word)
|
|
1110
|
-
return {
|
|
1111
|
-
type: 'def_local_var',
|
|
1112
|
-
name: nameToken,
|
|
1113
|
-
vartype: '定数',
|
|
1114
|
-
value,
|
|
1115
|
-
josi: '',
|
|
1116
|
-
...map,
|
|
1117
|
-
end: this.peekSourceMap()
|
|
1118
|
-
}
|
|
1119
|
-
}
|
|
1120
|
-
|
|
1121
|
-
yIncDec (): Ast | null {
|
|
1122
|
-
const map = this.peekSourceMap()
|
|
1123
|
-
const action = this.get() // (増やす|減らす)
|
|
1124
|
-
if (action === null) { return null }
|
|
1125
|
-
|
|
1126
|
-
// 『Nずつ増やして繰り返す』文か?
|
|
1127
|
-
if (this.check('繰返')) {
|
|
1128
|
-
this.pushStack({ type: 'word', value: action.value, josi: action.josi, ...map, end: this.peekSourceMap() })
|
|
1129
|
-
return this.yFor()
|
|
1130
|
-
}
|
|
1131
|
-
|
|
1132
|
-
// スタックから引数をポップ
|
|
1133
|
-
let value = this.popStack(['だけ', ''])
|
|
1134
|
-
if (!value) {
|
|
1135
|
-
value = { type: 'number', value: 1, josi: 'だけ', ...map, end: this.peekSourceMap() }
|
|
1136
|
-
}
|
|
1137
|
-
const word = this.popStack(['を'])
|
|
1138
|
-
if (!word || (word.type !== 'word' && word.type !== '配列参照')) {
|
|
1139
|
-
throw NakoSyntaxError.fromNode(
|
|
1140
|
-
`『${action.type}』文で定数が見当たりません。『(変数名)を(値)だけ${action.type}』のように使います。`,
|
|
1141
|
-
action)
|
|
1142
|
-
}
|
|
1143
|
-
|
|
1144
|
-
// 減らすなら-1かける
|
|
1145
|
-
if (action.value === '減') {
|
|
1146
|
-
value = { type: 'op', operator: '*', left: value, right: { type: 'number', value: -1, line: action.line }, josi: '', ...map }
|
|
1147
|
-
}
|
|
1148
|
-
|
|
1149
|
-
return {
|
|
1150
|
-
type: 'inc',
|
|
1151
|
-
name: word,
|
|
1152
|
-
value,
|
|
1153
|
-
josi: action.josi,
|
|
1154
|
-
...map,
|
|
1155
|
-
end: this.peekSourceMap()
|
|
1156
|
-
}
|
|
1157
|
-
}
|
|
1158
|
-
|
|
1159
|
-
yCall (): Ast | null {
|
|
1160
|
-
if (this.isEOF()) { return null }
|
|
1161
|
-
|
|
1162
|
-
// スタックに積んでいく
|
|
1163
|
-
while (!this.isEOF()) {
|
|
1164
|
-
if (this.check('ここから')) { this.get() }
|
|
1165
|
-
// 代入
|
|
1166
|
-
if (this.check('代入')) { return this.yDainyu() }
|
|
1167
|
-
if (this.check('定める')) { return this.ySadameru() }
|
|
1168
|
-
// 制御構文
|
|
1169
|
-
if (this.check('回')) { return this.yRepeatTime() }
|
|
1170
|
-
if (this.check('間')) { return this.yWhile() }
|
|
1171
|
-
if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) { return this.yFor() }
|
|
1172
|
-
if (this.check('反復')) { return this.yForEach() }
|
|
1173
|
-
if (this.check('条件分岐')) { return this.ySwitch() }
|
|
1174
|
-
if (this.check('戻る')) { return this.yReturn() }
|
|
1175
|
-
if (this.check('増') || this.check('減')) { return this.yIncDec() }
|
|
1176
|
-
// C言語風関数
|
|
1177
|
-
if (this.check2([['func', 'word'], '('])) { // C言語風
|
|
1178
|
-
const cur = this.peek()
|
|
1179
|
-
if (cur && cur.josi === '') {
|
|
1180
|
-
const t: Ast|null = this.yValue()
|
|
1181
|
-
if (t) {
|
|
1182
|
-
const josi = t.josi || ''
|
|
1183
|
-
if (t.type === 'func' && (t.josi === '' || keizokuJosi.indexOf(josi) >= 0)) {
|
|
1184
|
-
t.josi = ''
|
|
1185
|
-
return t // 関数なら値とする
|
|
1186
|
-
}
|
|
1187
|
-
this.pushStack(t)
|
|
1188
|
-
}
|
|
1189
|
-
if (this.check('comma')) { this.get() }
|
|
1190
|
-
continue
|
|
1191
|
-
}
|
|
1192
|
-
}
|
|
1193
|
-
// なでしこ式関数
|
|
1194
|
-
if (this.check('func')) {
|
|
1195
|
-
const r = this.yCallFunc()
|
|
1196
|
-
if (r === null) { continue }
|
|
1197
|
-
// 「〜する間」の形ならスタックに積む。
|
|
1198
|
-
if (this.check('間')) {
|
|
1199
|
-
this.pushStack(r)
|
|
1200
|
-
continue
|
|
1201
|
-
}
|
|
1202
|
-
// 関数呼び出しの直後に、四則演算があるか?
|
|
1203
|
-
if (!this.checkTypes(operatorList)) { return r } // なければ関数呼び出しを戻す
|
|
1204
|
-
// 四則演算があった場合、計算してスタックに載せる
|
|
1205
|
-
this.pushStack(this.yGetArgOperator(r))
|
|
1206
|
-
continue
|
|
1207
|
-
}
|
|
1208
|
-
// 値のとき → スタックに載せる
|
|
1209
|
-
const t = this.yGetArg()
|
|
1210
|
-
if (t) {
|
|
1211
|
-
this.pushStack(t)
|
|
1212
|
-
continue
|
|
1213
|
-
}
|
|
1214
|
-
break
|
|
1215
|
-
} // end of while
|
|
1216
|
-
|
|
1217
|
-
// 助詞が余ってしまった場合
|
|
1218
|
-
if (this.stack.length > 0) {
|
|
1219
|
-
this.logger.debug('--- stack dump ---\n' + JSON.stringify(this.stack, null, 2) + '\npeek: ' + JSON.stringify(this.peek(), null, 2))
|
|
1220
|
-
let msgDebug = `不完全な文です。${this.stack.map((n) => this.nodeToStr(n, { depth: 0 }, true)).join('、')}が解決していません。`
|
|
1221
|
-
let msg = `不完全な文です。${this.stack.map((n) => this.nodeToStr(n, { depth: 0 }, false)).join('、')}が解決していません。`
|
|
1222
|
-
|
|
1223
|
-
// 各ノードについて、更に詳細な情報があるなら表示
|
|
1224
|
-
for (const n of this.stack) {
|
|
1225
|
-
const d0 = this.nodeToStr(n, { depth: 0 }, false)
|
|
1226
|
-
const d1 = this.nodeToStr(n, { depth: 1 }, false)
|
|
1227
|
-
if (d0 !== d1) {
|
|
1228
|
-
msgDebug += `${this.nodeToStr(n, { depth: 0 }, true)}は${this.nodeToStr(n, { depth: 1 }, true)}として使われています。`
|
|
1229
|
-
msg += `${d0}は${d1}として使われています。`
|
|
1230
|
-
}
|
|
1231
|
-
}
|
|
1232
|
-
|
|
1233
|
-
const first = this.stack[0]
|
|
1234
|
-
const last = this.stack[this.stack.length - 1]
|
|
1235
|
-
this.logger.debug(msgDebug, first)
|
|
1236
|
-
throw NakoSyntaxError.fromNode(msg, first, last)
|
|
1237
|
-
}
|
|
1238
|
-
return this.popStack([])
|
|
1239
|
-
}
|
|
1240
|
-
|
|
1241
|
-
/** @returns {Ast | null} */
|
|
1242
|
-
yCallFunc (): Ast | null {
|
|
1243
|
-
const map = this.peekSourceMap()
|
|
1244
|
-
const t = this.get()
|
|
1245
|
-
if (!t) { return null }
|
|
1246
|
-
const f = t.meta
|
|
1247
|
-
const funcName: string = t.value
|
|
1248
|
-
// (関数)には ... 構文 ... https://github.com/kujirahand/nadesiko3/issues/66
|
|
1249
|
-
let funcObj = null
|
|
1250
|
-
if (t.josi === 'には') {
|
|
1251
|
-
try {
|
|
1252
|
-
funcObj = this.yMumeiFunc()
|
|
1253
|
-
} catch (err: any) {
|
|
1254
|
-
throw NakoSyntaxError.fromNode(`『${t.value}には...』で無名関数の定義で以下の間違いがあります。\n${err.message}`, t)
|
|
1255
|
-
}
|
|
1256
|
-
if (funcObj === null) { throw NakoSyntaxError.fromNode('『Fには』構文がありましたが、関数定義が見当たりません。', t) }
|
|
1257
|
-
}
|
|
1258
|
-
if (!f || typeof f.josi === 'undefined') { throw NakoSyntaxError.fromNode('関数の定義でエラー。', t) }
|
|
1259
|
-
|
|
1260
|
-
// 最近使った関数を記録
|
|
1261
|
-
this.recentlyCalledFunc.push({ name: funcName, ...f })
|
|
1262
|
-
|
|
1263
|
-
// 呼び出す関数が非同期呼び出しが必要(asyncFn)ならマーク
|
|
1264
|
-
if (f && f.asyncFn) { this.usedAsyncFn = true }
|
|
1265
|
-
|
|
1266
|
-
// 関数の引数を取り出す処理
|
|
1267
|
-
const args: any[] = []
|
|
1268
|
-
let nullCount = 0
|
|
1269
|
-
let valueCount = 0
|
|
1270
|
-
for (let i = 0; i < f.josi.length; i++) {
|
|
1271
|
-
while (true) {
|
|
1272
|
-
// スタックから任意の助詞を持つ値を一つ取り出す、助詞がなければ末尾から得る
|
|
1273
|
-
let popArg = this.popStack(f.josi[i])
|
|
1274
|
-
if (popArg !== null) {
|
|
1275
|
-
valueCount++
|
|
1276
|
-
} else if (i < f.josi.length - 1 || !f.isVariableJosi) {
|
|
1277
|
-
nullCount++
|
|
1278
|
-
popArg = funcObj
|
|
1279
|
-
} else {
|
|
1280
|
-
break
|
|
1281
|
-
}
|
|
1282
|
-
|
|
1283
|
-
if (popArg !== null && f.funcPointers !== undefined && f.funcPointers[i] !== null) {
|
|
1284
|
-
if (popArg.type === 'func') { // 引数が関数の参照渡しに該当する場合、typeを『func_pointer』に変更
|
|
1285
|
-
popArg.type = 'func_pointer'
|
|
1286
|
-
} else {
|
|
1287
|
-
const varname = (f.varnames) ? f.varnames[i] : `${i + 1}番目の引数`
|
|
1288
|
-
throw NakoSyntaxError.fromNode(
|
|
1289
|
-
`関数『${t.value}』の引数『${varname}』には関数オブジェクトが必要です。`, t)
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
args.push(popArg)
|
|
1293
|
-
if (i < f.josi.length - 1 || !f.isVariableJosi) { break }
|
|
1294
|
-
}
|
|
1295
|
-
}
|
|
1296
|
-
// 1つだけなら、変数「それ」で補完される
|
|
1297
|
-
if (nullCount >= 2 && (valueCount > 0 || t.josi === '' || keizokuJosi.indexOf(t.josi) >= 0)) {
|
|
1298
|
-
throw NakoSyntaxError.fromNode(`関数『${t.value}』の引数が不足しています。`, t)
|
|
1299
|
-
}
|
|
1300
|
-
// 関数呼び出しのAstを構築
|
|
1301
|
-
const funcNode: Ast = {
|
|
1302
|
-
type: 'func',
|
|
1303
|
-
name: t.value,
|
|
1304
|
-
args,
|
|
1305
|
-
josi: t.josi,
|
|
1306
|
-
...map,
|
|
1307
|
-
end: this.peekSourceMap()
|
|
1308
|
-
}
|
|
1309
|
-
|
|
1310
|
-
// 「プラグイン名設定」ならば、そこでスコープを変更することを意味する
|
|
1311
|
-
if (funcNode.name === 'プラグイン名設定') {
|
|
1312
|
-
if (args.length > 0 && args[0]) {
|
|
1313
|
-
let fname: string = '' + args[0].value
|
|
1314
|
-
if (fname === 'メイン') { fname = '' + args[0].file }
|
|
1315
|
-
this.modName = NakoLexer.filenameToModName(fname)
|
|
1316
|
-
}
|
|
1317
|
-
}
|
|
1318
|
-
|
|
1319
|
-
// 言い切りならそこで一度切る
|
|
1320
|
-
if (t.josi === '') { return funcNode }
|
|
1321
|
-
|
|
1322
|
-
// 「**して、**」の場合も一度切る
|
|
1323
|
-
if (keizokuJosi.indexOf(t.josi) >= 0) {
|
|
1324
|
-
funcNode.josi = 'して'
|
|
1325
|
-
return funcNode
|
|
1326
|
-
}
|
|
1327
|
-
// 続き
|
|
1328
|
-
funcNode.meta = f
|
|
1329
|
-
this.pushStack(funcNode)
|
|
1330
|
-
return null
|
|
1331
|
-
}
|
|
1332
|
-
|
|
1333
|
-
/** 関数呼び出し演算子 #891
|
|
1334
|
-
* @returns {Ast | null} */
|
|
1335
|
-
yCallOp (): Ast | null {
|
|
1336
|
-
if (!this.check2(['func', '←'])) { return null }
|
|
1337
|
-
const map = this.peekSourceMap()
|
|
1338
|
-
// 関数名を得る
|
|
1339
|
-
const word = this.get()
|
|
1340
|
-
if (word == null) { throw new Error('関数が取得できません。') }
|
|
1341
|
-
try {
|
|
1342
|
-
const op = this.get()
|
|
1343
|
-
if (op == null) { throw new Error('関数呼び出し演算子が取得できません。') }
|
|
1344
|
-
const funcName = word.value
|
|
1345
|
-
// 関数の引数なしをチェック
|
|
1346
|
-
if (!word.meta) { throw new Error('関数本体を取得できません。') }
|
|
1347
|
-
if (!word.meta.josi) { throw new Error('関数の引数情報を取得できません。') }
|
|
1348
|
-
const argCount = word.meta.josi.length
|
|
1349
|
-
if (argCount === 0) {
|
|
1350
|
-
throw NakoSyntaxError.fromNode(`引数がない関数『${funcName}』を関数呼び出し演算子で呼び出すことはできません。`, word)
|
|
1351
|
-
}
|
|
1352
|
-
// 引数を順に取得
|
|
1353
|
-
const curStackPos = this.stack.length
|
|
1354
|
-
while (!this.isEOF()) {
|
|
1355
|
-
const t = this.yGetArg()
|
|
1356
|
-
if (t) {
|
|
1357
|
-
this.pushStack(t)
|
|
1358
|
-
if ((this.stack.length - curStackPos) === argCount) { break }
|
|
1359
|
-
continue
|
|
1360
|
-
}
|
|
1361
|
-
break
|
|
1362
|
-
}
|
|
1363
|
-
// この場合第一引数の省略は認めない
|
|
1364
|
-
const realArgCount = this.stack.length - curStackPos
|
|
1365
|
-
if (realArgCount !== argCount) {
|
|
1366
|
-
throw NakoSyntaxError.fromNode(`関数『${funcName}』呼び出しで引数の数(${realArgCount})が定義(${argCount})と違います。`, word)
|
|
1367
|
-
}
|
|
1368
|
-
// 引数を取り出す
|
|
1369
|
-
const tmpList = this.stack.splice(curStackPos, argCount)
|
|
1370
|
-
// 引数が1つなら助詞は省略が可能。ただし、引数が2つ以上の時、正しく助詞の順序を入れ替える
|
|
1371
|
-
let argList = tmpList
|
|
1372
|
-
if (argCount >= 2) {
|
|
1373
|
-
argList = []
|
|
1374
|
-
const defList = word.meta.josi
|
|
1375
|
-
defList.forEach((josiList, i) => {
|
|
1376
|
-
for (let j = 0; j < tmpList.length; j++) {
|
|
1377
|
-
const t = tmpList[j]
|
|
1378
|
-
if (josiList.indexOf(t.josi) >= 0) {
|
|
1379
|
-
argList[i] = t
|
|
1380
|
-
return
|
|
1381
|
-
}
|
|
1382
|
-
}
|
|
1383
|
-
const josiStr = josiList.join(',')
|
|
1384
|
-
throw new Error(`助詞『${josiStr}』が見当たりません。`)
|
|
1385
|
-
})
|
|
1386
|
-
}
|
|
1387
|
-
// funcノードを返す
|
|
1388
|
-
return {
|
|
1389
|
-
type: 'func',
|
|
1390
|
-
name: funcName,
|
|
1391
|
-
args: argList,
|
|
1392
|
-
setter: true, // 重要
|
|
1393
|
-
josi: '',
|
|
1394
|
-
...map,
|
|
1395
|
-
end: this.peekSourceMap()
|
|
1396
|
-
}
|
|
1397
|
-
} catch (err: any) {
|
|
1398
|
-
this.logger.debug(`${this.nodeToStr(word, { depth: 0 }, true)}の関数呼び出しで引数(『←』以降)が読み取れません。`, word)
|
|
1399
|
-
throw NakoSyntaxError.fromNode(
|
|
1400
|
-
`${this.nodeToStr(word, { depth: 0 }, false)}の関数呼び出しでエラーがあります。\n${err.message}`, word)
|
|
1401
|
-
}
|
|
1402
|
-
}
|
|
1403
|
-
|
|
1404
|
-
/** @returns {Ast | null} */
|
|
1405
|
-
yLet (): Ast | null {
|
|
1406
|
-
const map = this.peekSourceMap()
|
|
1407
|
-
// 通常の変数
|
|
1408
|
-
if (this.check2(['word', 'eq'])) {
|
|
1409
|
-
const word = this.peek()
|
|
1410
|
-
let threw = false
|
|
1411
|
-
try {
|
|
1412
|
-
if (this.accept(['word', 'eq', this.yCalc]) || this.accept(['word', 'eq', this.ySentence])) {
|
|
1413
|
-
if (this.y[2].type === 'eol') {
|
|
1414
|
-
throw new Error('値が空です。')
|
|
1415
|
-
}
|
|
1416
|
-
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1417
|
-
const nameToken = this.getVarName(this.y[0])
|
|
1418
|
-
const valueToken = this.y[2]
|
|
1419
|
-
return {
|
|
1420
|
-
type: 'let',
|
|
1421
|
-
name: nameToken,
|
|
1422
|
-
value: valueToken,
|
|
1423
|
-
...map,
|
|
1424
|
-
end: this.peekSourceMap()
|
|
1425
|
-
}
|
|
1426
|
-
} else {
|
|
1427
|
-
threw = true
|
|
1428
|
-
this.logger.debug(`${this.nodeToStr(word, { depth: 1 }, true)}への代入文で計算式に書き間違いがあります。`, word)
|
|
1429
|
-
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に書き間違いがあります。`, map)
|
|
1430
|
-
}
|
|
1431
|
-
} catch (err: any) {
|
|
1432
|
-
if (threw) {
|
|
1433
|
-
throw err
|
|
1434
|
-
}
|
|
1435
|
-
this.logger.debug(`${this.nodeToStr(word, { depth: 1 }, true)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, word)
|
|
1436
|
-
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, map)
|
|
1437
|
-
}
|
|
1438
|
-
}
|
|
1439
|
-
|
|
1440
|
-
// let_array ?
|
|
1441
|
-
if (this.check2(['word', '@'])) {
|
|
1442
|
-
const la = this.yLetArrayAt(map)
|
|
1443
|
-
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1444
|
-
if (la) {
|
|
1445
|
-
la.checkInit = this.flagCheckArrayInit
|
|
1446
|
-
return la
|
|
1447
|
-
}
|
|
1448
|
-
}
|
|
1449
|
-
if (this.check2(['word', '['])) {
|
|
1450
|
-
const lb = this.yLetArrayBracket(map)
|
|
1451
|
-
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1452
|
-
if (lb) {
|
|
1453
|
-
lb.checkInit = this.flagCheckArrayInit
|
|
1454
|
-
return lb
|
|
1455
|
-
}
|
|
1456
|
-
}
|
|
1457
|
-
|
|
1458
|
-
// ローカル変数定義
|
|
1459
|
-
if (this.accept(['word', 'とは'])) {
|
|
1460
|
-
const word = this.getVarName(this.y[0])
|
|
1461
|
-
if (!this.checkTypes(['変数', '定数'])) {
|
|
1462
|
-
throw NakoSyntaxError.fromNode('ローカル変数『' + word.value + '』の定義エラー', word)
|
|
1463
|
-
}
|
|
1464
|
-
|
|
1465
|
-
const vtype = this.getCur() // 変数
|
|
1466
|
-
// 初期値がある?
|
|
1467
|
-
let value = null
|
|
1468
|
-
if (this.check('eq')) {
|
|
1469
|
-
this.get()
|
|
1470
|
-
value = this.yCalc()
|
|
1471
|
-
}
|
|
1472
|
-
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1473
|
-
return {
|
|
1474
|
-
type: 'def_local_var',
|
|
1475
|
-
name: word,
|
|
1476
|
-
vartype: vtype.type,
|
|
1477
|
-
value,
|
|
1478
|
-
...map,
|
|
1479
|
-
end: this.peekSourceMap()
|
|
1480
|
-
}
|
|
1481
|
-
}
|
|
1482
|
-
// ローカル変数定義(その2)
|
|
1483
|
-
if (this.accept(['変数', 'word', 'eq', this.yCalc])) {
|
|
1484
|
-
const word = this.getVarName(this.y[1])
|
|
1485
|
-
return {
|
|
1486
|
-
type: 'def_local_var',
|
|
1487
|
-
name: word,
|
|
1488
|
-
vartype: '変数',
|
|
1489
|
-
value: this.y[3],
|
|
1490
|
-
...map,
|
|
1491
|
-
end: this.peekSourceMap()
|
|
1492
|
-
}
|
|
1493
|
-
}
|
|
1494
|
-
|
|
1495
|
-
if (this.accept(['定数', 'word', 'eq', this.yCalc])) {
|
|
1496
|
-
const word = this.getVarName(this.y[1])
|
|
1497
|
-
return {
|
|
1498
|
-
type: 'def_local_var',
|
|
1499
|
-
name: word,
|
|
1500
|
-
vartype: '定数',
|
|
1501
|
-
value: this.y[3],
|
|
1502
|
-
...map,
|
|
1503
|
-
end: this.peekSourceMap()
|
|
1504
|
-
}
|
|
1505
|
-
}
|
|
1506
|
-
|
|
1507
|
-
// 複数定数への代入 #563
|
|
1508
|
-
if (this.accept(['定数', this.yJSONArray, 'eq', this.yCalc])) {
|
|
1509
|
-
const names = this.y[1]
|
|
1510
|
-
// check array
|
|
1511
|
-
if (names && names.value instanceof Array) {
|
|
1512
|
-
for (const i in names.value) {
|
|
1513
|
-
if (names.value[i].type !== 'word') {
|
|
1514
|
-
throw NakoSyntaxError.fromNode(`複数定数の代入文${i + 1}番目でエラー。『定数[A,B,C]=[1,2,3]』の書式で記述してください。`, this.y[0])
|
|
1515
|
-
}
|
|
1516
|
-
}
|
|
1517
|
-
} else {
|
|
1518
|
-
throw NakoSyntaxError.fromNode('複数定数の代入文でエラー。『定数[A,B,C]=[1,2,3]』の書式で記述してください。', this.y[0])
|
|
1519
|
-
}
|
|
1520
|
-
names.value = this.getVarNameList(names.value)
|
|
1521
|
-
return {
|
|
1522
|
-
type: 'def_local_varlist',
|
|
1523
|
-
names: names.value,
|
|
1524
|
-
vartype: '定数',
|
|
1525
|
-
value: this.y[3],
|
|
1526
|
-
...map,
|
|
1527
|
-
end: this.peekSourceMap()
|
|
1528
|
-
}
|
|
1529
|
-
}
|
|
1530
|
-
// 複数変数への代入 #563
|
|
1531
|
-
if (this.accept(['変数', this.yJSONArray, 'eq', this.yCalc])) {
|
|
1532
|
-
const names = this.y[1]
|
|
1533
|
-
// check array
|
|
1534
|
-
if (names && names.value instanceof Array) {
|
|
1535
|
-
for (const i in names.value) {
|
|
1536
|
-
if (names.value[i].type !== 'word') {
|
|
1537
|
-
throw NakoSyntaxError.fromNode(`複数変数の代入文${i + 1}番目でエラー。『変数[A,B,C]=[1,2,3]』の書式で記述してください。`, this.y[0])
|
|
1538
|
-
}
|
|
1539
|
-
}
|
|
1540
|
-
} else {
|
|
1541
|
-
throw NakoSyntaxError.fromNode('複数変数の代入文でエラー。『変数[A,B,C]=[1,2,3]』の書式で記述してください。', this.y[0])
|
|
1542
|
-
}
|
|
1543
|
-
names.value = this.getVarNameList(names.value)
|
|
1544
|
-
return {
|
|
1545
|
-
type: 'def_local_varlist',
|
|
1546
|
-
names: names.value,
|
|
1547
|
-
vartype: '変数',
|
|
1548
|
-
value: this.y[3],
|
|
1549
|
-
...map,
|
|
1550
|
-
end: this.peekSourceMap()
|
|
1551
|
-
}
|
|
1552
|
-
}
|
|
1553
|
-
|
|
1554
|
-
// 複数変数への代入 #563
|
|
1555
|
-
if (this.check2(['word', 'comma', 'word'])) {
|
|
1556
|
-
// 2 word
|
|
1557
|
-
if (this.accept(['word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1558
|
-
let names = [this.y[0], this.y[2]]
|
|
1559
|
-
names = this.getVarNameList(names)
|
|
1560
|
-
return {
|
|
1561
|
-
type: 'def_local_varlist',
|
|
1562
|
-
names,
|
|
1563
|
-
vartype: '変数',
|
|
1564
|
-
value: this.y[4],
|
|
1565
|
-
...map,
|
|
1566
|
-
end: this.peekSourceMap()
|
|
1567
|
-
}
|
|
1568
|
-
}
|
|
1569
|
-
// 3 word
|
|
1570
|
-
if (this.accept(['word', 'comma', 'word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1571
|
-
let names = [this.y[0], this.y[2], this.y[4]]
|
|
1572
|
-
names = this.getVarNameList(names)
|
|
1573
|
-
return {
|
|
1574
|
-
type: 'def_local_varlist',
|
|
1575
|
-
names,
|
|
1576
|
-
vartype: '変数',
|
|
1577
|
-
value: this.y[6],
|
|
1578
|
-
...map,
|
|
1579
|
-
end: this.peekSourceMap()
|
|
1580
|
-
}
|
|
1581
|
-
}
|
|
1582
|
-
// 4 word
|
|
1583
|
-
if (this.accept(['word', 'comma', 'word', 'comma', 'word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1584
|
-
let names = [this.y[0], this.y[2], this.y[4], this.y[6]]
|
|
1585
|
-
names = this.getVarNameList(names)
|
|
1586
|
-
return {
|
|
1587
|
-
type: 'def_local_varlist',
|
|
1588
|
-
names,
|
|
1589
|
-
vartype: '変数',
|
|
1590
|
-
value: this.y[8],
|
|
1591
|
-
...map,
|
|
1592
|
-
end: this.peekSourceMap()
|
|
1593
|
-
}
|
|
1594
|
-
}
|
|
1595
|
-
// 5 word
|
|
1596
|
-
if (this.accept(['word', 'comma', 'word', 'comma', 'word', 'comma', 'word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1597
|
-
let names = [this.y[0], this.y[2], this.y[4], this.y[6], this.y[8]]
|
|
1598
|
-
names = this.getVarNameList(names)
|
|
1599
|
-
return {
|
|
1600
|
-
type: 'def_local_varlist',
|
|
1601
|
-
names,
|
|
1602
|
-
vartype: '変数',
|
|
1603
|
-
value: this.y[10],
|
|
1604
|
-
...map,
|
|
1605
|
-
end: this.peekSourceMap()
|
|
1606
|
-
}
|
|
1607
|
-
}
|
|
1608
|
-
}
|
|
1609
|
-
return null
|
|
1610
|
-
}
|
|
1611
|
-
|
|
1612
|
-
/**
|
|
1613
|
-
* 配列のインデックスが1から始まる場合を考慮するか
|
|
1614
|
-
* @param {Ast} node
|
|
1615
|
-
* @returns
|
|
1616
|
-
*/
|
|
1617
|
-
checkArrayIndex (node: Ast): Ast {
|
|
1618
|
-
// 配列が0から始まるのであればそのまま返す
|
|
1619
|
-
if (this.arrayIndexFrom === 0) { return node }
|
|
1620
|
-
// 配列が1から始まるのであれば演算を加えて返す
|
|
1621
|
-
return {
|
|
1622
|
-
...node,
|
|
1623
|
-
'type': 'op',
|
|
1624
|
-
'operator': '-',
|
|
1625
|
-
'left': node,
|
|
1626
|
-
'right': {
|
|
1627
|
-
...node,
|
|
1628
|
-
'type': 'number',
|
|
1629
|
-
'value': this.arrayIndexFrom
|
|
1630
|
-
}
|
|
1631
|
-
}
|
|
1632
|
-
}
|
|
1633
|
-
|
|
1634
|
-
/**
|
|
1635
|
-
* 配列のインデックスを逆順にするのを考慮するか
|
|
1636
|
-
* @param {Ast[]| null} ary
|
|
1637
|
-
*/
|
|
1638
|
-
checkArrayReverse (ary: Ast[] | null): Ast[] {
|
|
1639
|
-
if (!ary) { return [] }
|
|
1640
|
-
if (!this.flagReverseArrayIndex) { return ary }
|
|
1641
|
-
// 二次元以上の配列変数のアクセスを[y][x]ではなく[x][y]と順序を変更する
|
|
1642
|
-
if (ary.length <= 1) { return ary }
|
|
1643
|
-
return ary.reverse()
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
|
-
/** @returns {Ast | null} */
|
|
1647
|
-
yLetArrayAt (map: SourceMap): Ast|null {
|
|
1648
|
-
// 一次元配列
|
|
1649
|
-
if (this.accept(['word', '@', this.yValue, 'eq', this.yCalc])) {
|
|
1650
|
-
return {
|
|
1651
|
-
type: 'let_array',
|
|
1652
|
-
name: this.getVarName(this.y[0]),
|
|
1653
|
-
index: [this.checkArrayIndex(this.y[2])],
|
|
1654
|
-
value: this.y[4],
|
|
1655
|
-
...map,
|
|
1656
|
-
end: this.peekSourceMap()
|
|
1657
|
-
}
|
|
1658
|
-
}
|
|
1659
|
-
|
|
1660
|
-
// 二次元配列
|
|
1661
|
-
if (this.accept(['word', '@', this.yValue, '@', this.yValue, 'eq', this.yCalc])) {
|
|
1662
|
-
return {
|
|
1663
|
-
type: 'let_array',
|
|
1664
|
-
name: this.getVarName(this.y[0]),
|
|
1665
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])]),
|
|
1666
|
-
value: this.y[6],
|
|
1667
|
-
...map,
|
|
1668
|
-
end: this.peekSourceMap()
|
|
1669
|
-
}
|
|
1670
|
-
}
|
|
1671
|
-
|
|
1672
|
-
// 三次元配列
|
|
1673
|
-
if (this.accept(['word', '@', this.yValue, '@', this.yValue, '@', this.yValue, 'eq', this.yCalc])) {
|
|
1674
|
-
return {
|
|
1675
|
-
type: 'let_array',
|
|
1676
|
-
name: this.getVarName(this.y[0]),
|
|
1677
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4]), this.checkArrayIndex(this.y[6])]),
|
|
1678
|
-
value: this.y[8],
|
|
1679
|
-
...map,
|
|
1680
|
-
end: this.peekSourceMap()
|
|
1681
|
-
}
|
|
1682
|
-
}
|
|
1683
|
-
|
|
1684
|
-
// 二次元配列(カンマ指定)
|
|
1685
|
-
if (this.accept(['word', '@', this.yValue, 'comma', this.yValue, 'eq', this.yCalc])) {
|
|
1686
|
-
return {
|
|
1687
|
-
type: 'let_array',
|
|
1688
|
-
name: this.getVarName(this.y[0]),
|
|
1689
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])]),
|
|
1690
|
-
value: this.y[6],
|
|
1691
|
-
...map,
|
|
1692
|
-
end: this.peekSourceMap()
|
|
1693
|
-
}
|
|
1694
|
-
}
|
|
1695
|
-
|
|
1696
|
-
// 三次元配列(カンマ指定)
|
|
1697
|
-
if (this.accept(['word', '@', this.yValue, 'comma', this.yValue, 'comma', this.yValue, 'eq', this.yCalc])) {
|
|
1698
|
-
return {
|
|
1699
|
-
type: 'let_array',
|
|
1700
|
-
name: this.getVarName(this.y[0]),
|
|
1701
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4]), this.checkArrayIndex(this.y[6])]),
|
|
1702
|
-
value: this.y[8],
|
|
1703
|
-
...map,
|
|
1704
|
-
end: this.peekSourceMap()
|
|
1705
|
-
}
|
|
1706
|
-
}
|
|
1707
|
-
return null
|
|
1708
|
-
}
|
|
1709
|
-
|
|
1710
|
-
/** @returns {Ast | null} */
|
|
1711
|
-
yLetArrayBracket (map: SourceMap): Ast|null {
|
|
1712
|
-
// 一次元配列
|
|
1713
|
-
if (this.accept(['word', '[', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1714
|
-
return {
|
|
1715
|
-
type: 'let_array',
|
|
1716
|
-
name: this.getVarName(this.y[0]),
|
|
1717
|
-
index: [this.checkArrayIndex(this.y[2])],
|
|
1718
|
-
value: this.y[5],
|
|
1719
|
-
...map,
|
|
1720
|
-
end: this.peekSourceMap()
|
|
1721
|
-
}
|
|
1722
|
-
}
|
|
1723
|
-
|
|
1724
|
-
// 二次元配列
|
|
1725
|
-
if (this.accept(['word', '[', this.yCalc, ']', '[', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1726
|
-
return {
|
|
1727
|
-
type: 'let_array',
|
|
1728
|
-
name: this.getVarName(this.y[0]),
|
|
1729
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[5])]),
|
|
1730
|
-
value: this.y[8],
|
|
1731
|
-
tag: '2',
|
|
1732
|
-
...map,
|
|
1733
|
-
end: this.peekSourceMap()
|
|
1734
|
-
}
|
|
1735
|
-
}
|
|
1736
|
-
if (this.accept(['word', '[', this.yCalc, 'comma', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1737
|
-
return {
|
|
1738
|
-
type: 'let_array',
|
|
1739
|
-
name: this.getVarName(this.y[0]),
|
|
1740
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])]),
|
|
1741
|
-
value: this.y[7],
|
|
1742
|
-
tag: '2',
|
|
1743
|
-
...map,
|
|
1744
|
-
end: this.peekSourceMap()
|
|
1745
|
-
}
|
|
1746
|
-
}
|
|
1747
|
-
|
|
1748
|
-
// 三次元配列
|
|
1749
|
-
if (this.accept(['word', '[', this.yCalc, ']', '[', this.yCalc, ']', '[', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1750
|
-
return {
|
|
1751
|
-
type: 'let_array',
|
|
1752
|
-
name: this.getVarName(this.y[0]),
|
|
1753
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[5]), this.checkArrayIndex(this.y[8])]),
|
|
1754
|
-
value: this.y[11],
|
|
1755
|
-
...map,
|
|
1756
|
-
end: this.peekSourceMap()
|
|
1757
|
-
}
|
|
1758
|
-
}
|
|
1759
|
-
if (this.accept(['word', '[', this.yCalc, 'comma', this.yCalc, 'comma', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1760
|
-
return {
|
|
1761
|
-
type: 'let_array',
|
|
1762
|
-
name: this.getVarName(this.y[0]),
|
|
1763
|
-
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4]), this.checkArrayIndex(this.y[6])]),
|
|
1764
|
-
value: this.y[9],
|
|
1765
|
-
...map,
|
|
1766
|
-
end: this.peekSourceMap()
|
|
1767
|
-
}
|
|
1768
|
-
}
|
|
1769
|
-
return null
|
|
1770
|
-
}
|
|
1771
|
-
|
|
1772
|
-
/** @returns {Ast | null} */
|
|
1773
|
-
yCalc (): Ast|null {
|
|
1774
|
-
const map = this.peekSourceMap()
|
|
1775
|
-
if (this.check('eol')) { return null }
|
|
1776
|
-
// 値を一つ読む
|
|
1777
|
-
const t = this.yGetArg()
|
|
1778
|
-
if (!t) { return null }
|
|
1779
|
-
// 助詞がある? つまり、関数呼び出しがある?
|
|
1780
|
-
if (t.josi === '') { return t } // 値だけの場合
|
|
1781
|
-
// 関数の呼び出しがあるなら、スタックに載せて関数読み出しを呼ぶ
|
|
1782
|
-
this.pushStack(t)
|
|
1783
|
-
const t1 = this.yCall()
|
|
1784
|
-
if (!t1) {
|
|
1785
|
-
return this.popStack()
|
|
1786
|
-
}
|
|
1787
|
-
// それが連文か確認
|
|
1788
|
-
if (t1.josi !== 'して') { return t1 } // 連文ではない
|
|
1789
|
-
// 連文なら右側を読んで左側とくっつける
|
|
1790
|
-
const t2 = this.yCalc()
|
|
1791
|
-
if (!t2) { return t1 }
|
|
1792
|
-
return {
|
|
1793
|
-
type: 'renbun',
|
|
1794
|
-
left: t1,
|
|
1795
|
-
right: t2,
|
|
1796
|
-
josi: t2.josi,
|
|
1797
|
-
...map,
|
|
1798
|
-
end: this.peekSourceMap()
|
|
1799
|
-
}
|
|
1800
|
-
}
|
|
1801
|
-
|
|
1802
|
-
/** @returns {Ast | null} */
|
|
1803
|
-
yValueKakko (): Ast | null {
|
|
1804
|
-
if (!this.check('(')) { return null }
|
|
1805
|
-
const t = this.get() // skip '('
|
|
1806
|
-
if (!t) { throw new Error('[System Error] check したのに get できない') }
|
|
1807
|
-
this.saveStack()
|
|
1808
|
-
const v = this.yCalc() || this.ySentence()
|
|
1809
|
-
if (v === null) {
|
|
1810
|
-
const v2 = this.get()
|
|
1811
|
-
this.logger.debug('(...)の解析エラー。' + this.nodeToStr(v2, { depth: 1 }, true) + 'の近く', t)
|
|
1812
|
-
throw NakoSyntaxError.fromNode('(...)の解析エラー。' + this.nodeToStr(v2, { depth: 1 }, false) + 'の近く', t)
|
|
1813
|
-
}
|
|
1814
|
-
if (!this.check(')')) {
|
|
1815
|
-
this.logger.debug('(...)の解析エラー。' + this.nodeToStr(v, { depth: 1 }, true) + 'の近く', t)
|
|
1816
|
-
throw NakoSyntaxError.fromNode('(...)の解析エラー。' + this.nodeToStr(v, { depth: 1 }, false) + 'の近く', t)
|
|
1817
|
-
}
|
|
1818
|
-
|
|
1819
|
-
const closeParent = this.get() // skip ')'
|
|
1820
|
-
this.loadStack()
|
|
1821
|
-
if (closeParent) {
|
|
1822
|
-
v.josi = closeParent.josi
|
|
1823
|
-
}
|
|
1824
|
-
return v
|
|
1825
|
-
}
|
|
1826
|
-
|
|
1827
|
-
/** @returns {Ast | null} */
|
|
1828
|
-
yValue (): Ast | null {
|
|
1829
|
-
const map = this.peekSourceMap()
|
|
1830
|
-
|
|
1831
|
-
// カンマなら飛ばす #877
|
|
1832
|
-
if (this.check('comma')) { this.get() }
|
|
1833
|
-
|
|
1834
|
-
// プリミティブな値
|
|
1835
|
-
if (this.checkTypes(['number', 'string'])) { return this.getCur() as Ast }
|
|
1836
|
-
|
|
1837
|
-
// 丸括弧
|
|
1838
|
-
if (this.check('(')) { return this.yValueKakko() }
|
|
1839
|
-
|
|
1840
|
-
// マイナス記号
|
|
1841
|
-
if (this.check2(['-', 'number']) || this.check2(['-', 'word']) || this.check2(['-', 'func'])) {
|
|
1842
|
-
const m = this.get() // skip '-'
|
|
1843
|
-
const v = this.yValue()
|
|
1844
|
-
const josi = (v && v.josi) ? v.josi : ''
|
|
1845
|
-
const line = (m && m.line) ? m.line : 0
|
|
1846
|
-
return {
|
|
1847
|
-
type: 'op',
|
|
1848
|
-
operator: '*',
|
|
1849
|
-
left: { type: 'number', value: -1, line },
|
|
1850
|
-
right: v || [],
|
|
1851
|
-
josi,
|
|
1852
|
-
...map,
|
|
1853
|
-
end: this.peekSourceMap()
|
|
1854
|
-
}
|
|
1855
|
-
}
|
|
1856
|
-
// NOT
|
|
1857
|
-
if (this.check('not')) {
|
|
1858
|
-
this.get() // skip '!'
|
|
1859
|
-
const v = this.yValue()
|
|
1860
|
-
const josi = (v && v.josi) ? v.josi : ''
|
|
1861
|
-
return {
|
|
1862
|
-
type: 'not',
|
|
1863
|
-
value: v,
|
|
1864
|
-
josi,
|
|
1865
|
-
...map,
|
|
1866
|
-
end: this.peekSourceMap()
|
|
1867
|
-
}
|
|
1868
|
-
}
|
|
1869
|
-
// JSON object
|
|
1870
|
-
const a = this.yJSONArray()
|
|
1871
|
-
if (a) { return a }
|
|
1872
|
-
const o = this.yJSONObject()
|
|
1873
|
-
if (o) { return o }
|
|
1874
|
-
// 一語関数
|
|
1875
|
-
const splitType = operatorList.concat(['eol', ')', ']', 'ならば', '回', '間', '反復', '条件分岐'])
|
|
1876
|
-
if (this.check2(['func', splitType])) {
|
|
1877
|
-
const tt = this.get()
|
|
1878
|
-
if (!tt) { throw new Error('[System Error] 正しく値が取れませんでした。') }
|
|
1879
|
-
const f = this.getVarNameRef(tt)
|
|
1880
|
-
return {
|
|
1881
|
-
type: 'func',
|
|
1882
|
-
name: f.value,
|
|
1883
|
-
args: [],
|
|
1884
|
-
josi: f.josi,
|
|
1885
|
-
...map,
|
|
1886
|
-
end: this.peekSourceMap()
|
|
1887
|
-
}
|
|
1888
|
-
}
|
|
1889
|
-
// C風関数呼び出し FUNC(...)
|
|
1890
|
-
if (this.check2([['func', 'word'], '(']) && this.peekDef().josi === '') {
|
|
1891
|
-
const f = this.peek()
|
|
1892
|
-
if (this.accept([['func', 'word'], '(', this.yGetArgParen, ')'])) {
|
|
1893
|
-
return {
|
|
1894
|
-
type: 'func',
|
|
1895
|
-
name: this.getVarNameRef(this.y[0]).value,
|
|
1896
|
-
args: this.y[2],
|
|
1897
|
-
josi: this.y[3].josi,
|
|
1898
|
-
...map,
|
|
1899
|
-
end: this.peekSourceMap()
|
|
1900
|
-
}
|
|
1901
|
-
} else { throw NakoSyntaxError.fromNode('C風関数呼び出しのエラー', f || NewEmptyToken()) }
|
|
1902
|
-
}
|
|
1903
|
-
// 関数呼び出し演算子
|
|
1904
|
-
if (this.check2(['func', '←'])) { return this.yCallOp() }
|
|
1905
|
-
// 無名関数(関数オブジェクト)
|
|
1906
|
-
if (this.check('def_func')) { return this.yMumeiFunc() }
|
|
1907
|
-
// 変数
|
|
1908
|
-
const word = this.yValueWord()
|
|
1909
|
-
if (word) { return word }
|
|
1910
|
-
// その他
|
|
1911
|
-
return null
|
|
1912
|
-
}
|
|
1913
|
-
|
|
1914
|
-
yValueWordGetIndex (ast: Ast): boolean {
|
|
1915
|
-
if (!ast.index) { ast.index = [] }
|
|
1916
|
-
// word @ a, b, c
|
|
1917
|
-
if (this.check('@')) {
|
|
1918
|
-
if (this.accept(['@', this.yValue, 'comma', this.yValue, 'comma', this.yValue])) {
|
|
1919
|
-
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1920
|
-
ast.index.push(this.checkArrayIndex(this.y[3]))
|
|
1921
|
-
ast.index.push(this.checkArrayIndex(this.y[5]))
|
|
1922
|
-
ast.index = this.checkArrayReverse(ast.index)
|
|
1923
|
-
ast.josi = this.y[5].josi
|
|
1924
|
-
return true
|
|
1925
|
-
}
|
|
1926
|
-
if (this.accept(['@', this.yValue, 'comma', this.yValue])) {
|
|
1927
|
-
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1928
|
-
ast.index.push(this.checkArrayIndex(this.y[3]))
|
|
1929
|
-
ast.index = this.checkArrayReverse(ast.index)
|
|
1930
|
-
ast.josi = this.y[3].josi
|
|
1931
|
-
return true
|
|
1932
|
-
}
|
|
1933
|
-
if (this.accept(['@', this.yValue])) {
|
|
1934
|
-
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1935
|
-
ast.josi = this.y[1].josi
|
|
1936
|
-
return true
|
|
1937
|
-
}
|
|
1938
|
-
throw NakoSyntaxError.fromNode('変数の後ろの『@要素』の指定が不正です。', ast)
|
|
1939
|
-
}
|
|
1940
|
-
if (this.check('[')) {
|
|
1941
|
-
if (this.accept(['[', this.yCalc, ']'])) {
|
|
1942
|
-
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1943
|
-
ast.josi = this.y[2].josi
|
|
1944
|
-
return true
|
|
1945
|
-
}
|
|
1946
|
-
}
|
|
1947
|
-
if (this.check('[')) {
|
|
1948
|
-
if (this.accept(['[', this.yCalc, 'comma', this.yCalc, ']'])) {
|
|
1949
|
-
const index = [
|
|
1950
|
-
this.checkArrayIndex(this.y[1]),
|
|
1951
|
-
this.checkArrayIndex(this.y[3])
|
|
1952
|
-
]
|
|
1953
|
-
ast.index = this.checkArrayReverse(index)
|
|
1954
|
-
ast.josi = this.y[4].josi
|
|
1955
|
-
return true
|
|
1956
|
-
}
|
|
1957
|
-
}
|
|
1958
|
-
if (this.check('[')) {
|
|
1959
|
-
if (this.accept(['[', this.yCalc, 'comma', this.yCalc, 'comma', this.yCalc, ']'])) {
|
|
1960
|
-
const index = [
|
|
1961
|
-
this.checkArrayIndex(this.y[1]),
|
|
1962
|
-
this.checkArrayIndex(this.y[3]),
|
|
1963
|
-
this.checkArrayIndex(this.y[5])
|
|
1964
|
-
]
|
|
1965
|
-
ast.index = this.checkArrayReverse(index)
|
|
1966
|
-
ast.josi = this.y[6].josi
|
|
1967
|
-
return true
|
|
1968
|
-
}
|
|
1969
|
-
}
|
|
1970
|
-
return false
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
/** @returns {Ast | null} */
|
|
1974
|
-
yValueWord (): Ast|null {
|
|
1975
|
-
const map = this.peekSourceMap()
|
|
1976
|
-
if (this.check('word')) {
|
|
1977
|
-
const t = this.getCur()
|
|
1978
|
-
const word = this.getVarNameRef(t)
|
|
1979
|
-
|
|
1980
|
-
// word[n] || word@n
|
|
1981
|
-
if (word.josi === '' && this.checkTypes(['[', '@'])) {
|
|
1982
|
-
const ast:Ast = {
|
|
1983
|
-
type: '配列参照',
|
|
1984
|
-
name: word,
|
|
1985
|
-
index: [],
|
|
1986
|
-
josi: '',
|
|
1987
|
-
...map,
|
|
1988
|
-
end: this.peekSourceMap()
|
|
1989
|
-
}
|
|
1990
|
-
while (!this.isEOF()) {
|
|
1991
|
-
if (!this.yValueWordGetIndex(ast)) { break }
|
|
1992
|
-
}
|
|
1993
|
-
if (ast.index && ast.index.length === 0) { throw NakoSyntaxError.fromNode(`配列『${word.value}』アクセスで指定ミス`, word) }
|
|
1994
|
-
return ast
|
|
1995
|
-
}
|
|
1996
|
-
return word as Ast
|
|
1997
|
-
}
|
|
1998
|
-
return null
|
|
1999
|
-
}
|
|
2000
|
-
|
|
2001
|
-
/** 変数名を検索して解決する
|
|
2002
|
-
* @param {Ast|Token} word
|
|
2003
|
-
* @return {Ast|Token}
|
|
2004
|
-
*/
|
|
2005
|
-
getVarName (word: Token|Ast): Token|Ast {
|
|
2006
|
-
// check word name
|
|
2007
|
-
const f = this.findVar(word.value)
|
|
2008
|
-
if (!f) { // 変数が見つからない
|
|
2009
|
-
if (this.funcLevel === 0) { // global
|
|
2010
|
-
let gname = word.value
|
|
2011
|
-
if (gname.indexOf('__') < 0) { gname = this.modName + '__' + word.value }
|
|
2012
|
-
this.funclist[gname] = { type: 'var', value: '' }
|
|
2013
|
-
word.value = gname
|
|
2014
|
-
} else { // local
|
|
2015
|
-
this.localvars[word.value] = { type: 'var', value: '' }
|
|
2016
|
-
}
|
|
2017
|
-
} else if (f && f.scope === 'global') {
|
|
2018
|
-
word.value = f.name
|
|
2019
|
-
}
|
|
2020
|
-
return word
|
|
2021
|
-
}
|
|
2022
|
-
|
|
2023
|
-
/** 変数名を検索して解決する */
|
|
2024
|
-
getVarNameRef (word: Token): Token {
|
|
2025
|
-
// check word name
|
|
2026
|
-
const f = this.findVar(word.value)
|
|
2027
|
-
if (!f) { // 変数が見つからない
|
|
2028
|
-
if (this.funcLevel === 0 && word.value.indexOf('__') < 0) {
|
|
2029
|
-
word.value = this.modName + '__' + word.value
|
|
2030
|
-
}
|
|
2031
|
-
} else if (f && f.scope === 'global') {
|
|
2032
|
-
word.value = f.name
|
|
2033
|
-
}
|
|
2034
|
-
return word
|
|
2035
|
-
}
|
|
2036
|
-
|
|
2037
|
-
/** 複数の変数名を検索して解決する */
|
|
2038
|
-
getVarNameList (words: Token[]): Token[] {
|
|
2039
|
-
for (let i = 0; i < words.length; i++) {
|
|
2040
|
-
words[i] = this.getVarName(words[i]) as Token
|
|
2041
|
-
}
|
|
2042
|
-
return words
|
|
2043
|
-
}
|
|
2044
|
-
|
|
2045
|
-
yJSONObjectValue (): {key: Ast, value: Ast}[] |null {
|
|
2046
|
-
const a: {key: Ast, value: Ast}[] = []
|
|
2047
|
-
const firstToken = this.peek()
|
|
2048
|
-
if (!firstToken) { return null }
|
|
2049
|
-
while (!this.isEOF()) {
|
|
2050
|
-
while (this.check('eol')) { this.get() }
|
|
2051
|
-
if (this.check('}')) { break }
|
|
2052
|
-
if (this.accept(['word', ':', this.yCalc])) {
|
|
2053
|
-
this.y[0].type = 'string' // キー名の文字列記号省略の場合
|
|
2054
|
-
a.push({
|
|
2055
|
-
key: this.y[0],
|
|
2056
|
-
value: this.y[2]
|
|
2057
|
-
})
|
|
2058
|
-
} else if (this.accept(['string', ':', this.yCalc])) {
|
|
2059
|
-
a.push({
|
|
2060
|
-
key: this.y[0],
|
|
2061
|
-
value: this.y[2]
|
|
2062
|
-
})
|
|
2063
|
-
} else if (this.check('word')) {
|
|
2064
|
-
const w = this.getCur()
|
|
2065
|
-
w.type = 'string'
|
|
2066
|
-
a.push({
|
|
2067
|
-
key: w as Ast,
|
|
2068
|
-
value: w as Ast
|
|
2069
|
-
})
|
|
2070
|
-
} else if (this.checkTypes(['string', 'number'])) {
|
|
2071
|
-
const w = this.getCur()
|
|
2072
|
-
a.push({
|
|
2073
|
-
key: w as Ast,
|
|
2074
|
-
value: w as Ast
|
|
2075
|
-
})
|
|
2076
|
-
} else { throw NakoSyntaxError.fromNode('辞書オブジェクトの宣言で末尾の『}』がありません。', firstToken) }
|
|
2077
|
-
|
|
2078
|
-
if (this.check('comma')) { this.get() }
|
|
2079
|
-
}
|
|
2080
|
-
return a
|
|
2081
|
-
}
|
|
2082
|
-
|
|
2083
|
-
/** @returns {Ast | null} */
|
|
2084
|
-
yJSONObject (): Ast | null {
|
|
2085
|
-
const map = this.peekSourceMap()
|
|
2086
|
-
if (this.accept(['{', '}'])) {
|
|
2087
|
-
return {
|
|
2088
|
-
type: 'json_obj',
|
|
2089
|
-
value: [],
|
|
2090
|
-
josi: this.y[1].josi,
|
|
2091
|
-
...map,
|
|
2092
|
-
end: this.peekSourceMap()
|
|
2093
|
-
}
|
|
2094
|
-
}
|
|
2095
|
-
|
|
2096
|
-
if (this.accept(['{', this.yJSONObjectValue, '}'])) {
|
|
2097
|
-
return {
|
|
2098
|
-
type: 'json_obj',
|
|
2099
|
-
value: this.y[1],
|
|
2100
|
-
josi: this.y[2].josi,
|
|
2101
|
-
...map,
|
|
2102
|
-
end: this.peekSourceMap()
|
|
2103
|
-
}
|
|
2104
|
-
}
|
|
2105
|
-
|
|
2106
|
-
// 辞書初期化に終わりがなかった場合 (エラーチェックのため) #958
|
|
2107
|
-
if (this.accept(['{', this.yJSONObjectValue])) {
|
|
2108
|
-
throw NakoSyntaxError.fromNode(
|
|
2109
|
-
'辞書型変数の初期化が『}』で閉じられていません。',
|
|
2110
|
-
this.y[1])
|
|
2111
|
-
}
|
|
2112
|
-
|
|
2113
|
-
return null
|
|
2114
|
-
}
|
|
2115
|
-
|
|
2116
|
-
yJSONArrayValue (): Ast[] | null {
|
|
2117
|
-
if (this.check('eol')) { this.get() }
|
|
2118
|
-
const v1 = this.yCalc()
|
|
2119
|
-
if (v1 === null) { return null }
|
|
2120
|
-
if (this.check('comma')) { this.get() }
|
|
2121
|
-
const a: Ast[] = [v1]
|
|
2122
|
-
while (!this.isEOF()) {
|
|
2123
|
-
if (this.check('eol')) { this.get() }
|
|
2124
|
-
if (this.check(']')) { break }
|
|
2125
|
-
const v2 = this.yCalc()
|
|
2126
|
-
if (v2 === null) { break }
|
|
2127
|
-
if (this.check('comma')) { this.get() }
|
|
2128
|
-
a.push(v2)
|
|
2129
|
-
}
|
|
2130
|
-
return a
|
|
2131
|
-
}
|
|
2132
|
-
|
|
2133
|
-
/** @returns {Ast | null} */
|
|
2134
|
-
yJSONArray (): Ast | null {
|
|
2135
|
-
const map = this.peekSourceMap()
|
|
2136
|
-
if (this.accept(['[', ']'])) {
|
|
2137
|
-
return {
|
|
2138
|
-
type: 'json_array',
|
|
2139
|
-
value: [],
|
|
2140
|
-
josi: this.y[1].josi,
|
|
2141
|
-
...map,
|
|
2142
|
-
end: this.peekSourceMap()
|
|
2143
|
-
}
|
|
2144
|
-
}
|
|
2145
|
-
|
|
2146
|
-
if (this.accept(['[', this.yJSONArrayValue, ']'])) {
|
|
2147
|
-
return {
|
|
2148
|
-
type: 'json_array',
|
|
2149
|
-
value: this.y[1],
|
|
2150
|
-
josi: this.y[2].josi,
|
|
2151
|
-
...map,
|
|
2152
|
-
end: this.peekSourceMap()
|
|
2153
|
-
}
|
|
2154
|
-
}
|
|
2155
|
-
// 配列に終わりがなかった場合 (エラーチェックのため) #958
|
|
2156
|
-
if (this.accept(['[', this.yJSONArrayValue])) {
|
|
2157
|
-
throw NakoSyntaxError.fromNode(
|
|
2158
|
-
'配列変数の初期化が『]』で閉じられていません。',
|
|
2159
|
-
this.y[1])
|
|
2160
|
-
}
|
|
2161
|
-
|
|
2162
|
-
return null
|
|
2163
|
-
}
|
|
2164
|
-
|
|
2165
|
-
/** エラー監視構文 */
|
|
2166
|
-
yTryExcept (): Ast | null {
|
|
2167
|
-
const map = this.peekSourceMap()
|
|
2168
|
-
if (!this.check('エラー監視')) { return null }
|
|
2169
|
-
const kansi = this.getCur() // skip エラー監視
|
|
2170
|
-
const block = this.yBlock()
|
|
2171
|
-
if (!this.check2(['エラー', 'ならば'])) {
|
|
2172
|
-
throw NakoSyntaxError.fromNode(
|
|
2173
|
-
'エラー構文で『エラーならば』がありません。' +
|
|
2174
|
-
'『エラー監視..エラーならば..ここまで』を対で記述します。',
|
|
2175
|
-
kansi)
|
|
2176
|
-
}
|
|
2177
|
-
|
|
2178
|
-
this.get() // skip エラー
|
|
2179
|
-
this.get() // skip ならば
|
|
2180
|
-
const errBlock = this.yBlock()
|
|
2181
|
-
if (this.check('ここまで')) {
|
|
2182
|
-
this.get()
|
|
2183
|
-
} else {
|
|
2184
|
-
throw NakoSyntaxError.fromNode('『ここまで』がありません。『エラー監視』...『エラーならば』...『ここまで』を対応させてください。', map)
|
|
2185
|
-
}
|
|
2186
|
-
return {
|
|
2187
|
-
type: 'try_except',
|
|
2188
|
-
block,
|
|
2189
|
-
errBlock: errBlock || [],
|
|
2190
|
-
josi: '',
|
|
2191
|
-
...map,
|
|
2192
|
-
end: this.peekSourceMap()
|
|
2193
|
-
}
|
|
2194
|
-
}
|
|
2195
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* nadesiko v3 parser
|
|
3
|
+
*/
|
|
4
|
+
import { opPriority, keizokuJosi, operatorList } from './nako_parser_const.mjs'
|
|
5
|
+
import { NakoParserBase } from './nako_parser_base.mjs'
|
|
6
|
+
import { NakoSyntaxError } from './nako_errors.mjs'
|
|
7
|
+
import { NakoLexer } from './nako_lexer.mjs'
|
|
8
|
+
import { Token, Ast, FuncListItem, FuncArgs, NewEmptyToken, SourceMap } from './nako_types.mjs'
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* 構文解析を行うクラス
|
|
12
|
+
*/
|
|
13
|
+
export class NakoParser extends NakoParserBase {
|
|
14
|
+
/**
|
|
15
|
+
* 構文解析を実行する
|
|
16
|
+
* @param {TokenWithSourceMap[]} tokens 字句解析済みのトークンの配列
|
|
17
|
+
* @param {string} filename 解析対象のモジュール名
|
|
18
|
+
* @return {Ast} AST(構文木)
|
|
19
|
+
*/
|
|
20
|
+
parse (tokens: Token[], filename: string): Ast {
|
|
21
|
+
this.reset()
|
|
22
|
+
this.tokens = tokens
|
|
23
|
+
this.modName = NakoLexer.filenameToModName(filename)
|
|
24
|
+
this.modList.push(this.modName)
|
|
25
|
+
// 解析開始
|
|
26
|
+
return this.startParser()
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
/** パーサーの一番最初に呼び出す構文規則 */
|
|
30
|
+
startParser (): Ast {
|
|
31
|
+
const b: Ast = this.ySentenceList()
|
|
32
|
+
const c: Token|null = this.get()
|
|
33
|
+
if (c && c.type !== 'eof') {
|
|
34
|
+
this.logger.debug(`構文解析でエラー。${this.nodeToStr(c, { depth: 1 }, true)}の使い方が間違っています。`, c)
|
|
35
|
+
throw NakoSyntaxError.fromNode(`構文解析でエラー。${this.nodeToStr(c, { depth: 1 }, false)}の使い方が間違っています。`, c)
|
|
36
|
+
}
|
|
37
|
+
return b
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
/** 複数文を返す */
|
|
41
|
+
ySentenceList (): Ast {
|
|
42
|
+
const blocks = []
|
|
43
|
+
let line = -1
|
|
44
|
+
const map = this.peekSourceMap()
|
|
45
|
+
while (!this.isEOF()) {
|
|
46
|
+
const n: Ast|null = this.ySentence()
|
|
47
|
+
if (!n) { break }
|
|
48
|
+
blocks.push(n)
|
|
49
|
+
if (line < 0) { line = n.line }
|
|
50
|
+
}
|
|
51
|
+
if (blocks.length === 0) {
|
|
52
|
+
const token = this.peek() || this.tokens[0]
|
|
53
|
+
this.logger.debug('構文解析に失敗:' + this.nodeToStr(this.peek(), { depth: 1 }, true), token)
|
|
54
|
+
throw NakoSyntaxError.fromNode('構文解析に失敗:' + this.nodeToStr(this.peek(), { depth: 1 }, false), token)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
return { type: 'block', block: blocks, ...map, end: this.peekSourceMap(), genMode: this.genMode }
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
yEOL (): Ast | null {
|
|
61
|
+
// 行末のチェック #1009
|
|
62
|
+
const eol = this.get()
|
|
63
|
+
if (!eol) { return null }
|
|
64
|
+
// 余剰スタックの確認
|
|
65
|
+
if (this.stack.length > 0) {
|
|
66
|
+
/** 余剰スタックのレポートを作る */
|
|
67
|
+
const words: string[] = []
|
|
68
|
+
this.stack.forEach((t) => {
|
|
69
|
+
let w = this.nodeToStr(t, { depth: 1 }, false)
|
|
70
|
+
if (t.josi) { w += t.josi }
|
|
71
|
+
words.push(w)
|
|
72
|
+
})
|
|
73
|
+
const desc = words.join(',')
|
|
74
|
+
// 最近使った関数の使い方レポートを作る #1093
|
|
75
|
+
let descFunc = ''
|
|
76
|
+
const chA = 'A'.charCodeAt(0)
|
|
77
|
+
for (const f of this.recentlyCalledFunc) {
|
|
78
|
+
descFunc += ' - '
|
|
79
|
+
let no = 0
|
|
80
|
+
const josiA: FuncArgs | undefined = (f as FuncListItem).josi
|
|
81
|
+
if (josiA) {
|
|
82
|
+
for (const arg of josiA) {
|
|
83
|
+
const ch = String.fromCharCode(chA + no)
|
|
84
|
+
descFunc += ch
|
|
85
|
+
if (arg.length === 1) { descFunc += arg[0] } else { descFunc += `(${arg.join('|')})` }
|
|
86
|
+
no++
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
descFunc += f.name + '\n'
|
|
90
|
+
}
|
|
91
|
+
throw NakoSyntaxError.fromNode(
|
|
92
|
+
`未解決の単語があります: [${desc}]\n次の命令の可能性があります:\n${descFunc}`, eol)
|
|
93
|
+
}
|
|
94
|
+
this.recentlyCalledFunc = []
|
|
95
|
+
return eol as Ast
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
/** @returns {Ast | null} */
|
|
99
|
+
ySentence (): Ast | null {
|
|
100
|
+
const map: SourceMap = this.peekSourceMap()
|
|
101
|
+
|
|
102
|
+
// 最初の語句が決まっている構文
|
|
103
|
+
if (this.check('eol')) { return this.yEOL() }
|
|
104
|
+
if (this.check('もし')) { return this.yIF() }
|
|
105
|
+
if (this.check('後判定')) { return this.yAtohantei() }
|
|
106
|
+
if (this.check('エラー監視')) { return this.yTryExcept() }
|
|
107
|
+
if (this.check('逐次実行')) { return this.yTikuji() }
|
|
108
|
+
if (this.accept(['抜ける'])) { return { type: 'break', josi: '', ...map, end: this.peekSourceMap() } }
|
|
109
|
+
if (this.accept(['続ける'])) { return { type: 'continue', josi: '', ...map, end: this.peekSourceMap() } }
|
|
110
|
+
if (this.accept(['require', 'string', '取込'])) { return this.yRequire() }
|
|
111
|
+
if (this.accept(['not', '非同期モード'])) { return this.yASyncMode() }
|
|
112
|
+
if (this.accept(['not', 'DNCLモード'])) { return this.yDNCLMode() }
|
|
113
|
+
if (this.accept(['not', 'string', 'モード設定'])) { return this.ySetGenMode(this.y[1].value) }
|
|
114
|
+
|
|
115
|
+
// 関数呼び出し演算子
|
|
116
|
+
if (this.check2(['func', '←'])) { return this.yCallOp() }
|
|
117
|
+
if (this.check2(['func', 'eq'])) {
|
|
118
|
+
const word: Token = this.get() || NewEmptyToken('?', '?', map.line, map.file || '')
|
|
119
|
+
throw NakoSyntaxError.fromNode(`関数『${word.value}』に代入できません。『←』を使ってください。`, word)
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// 先読みして初めて確定する構文
|
|
123
|
+
if (this.accept([this.ySpeedMode])) { return this.y[0] }
|
|
124
|
+
if (this.accept([this.yPerformanceMonitor])) { return this.y[0] }
|
|
125
|
+
if (this.accept([this.yLet])) { return this.y[0] }
|
|
126
|
+
if (this.accept([this.yDefTest])) { return this.y[0] }
|
|
127
|
+
if (this.accept([this.yDefFunc])) { return this.y[0] }
|
|
128
|
+
|
|
129
|
+
// 関数呼び出しの他、各種構文の実装
|
|
130
|
+
if (this.accept([this.yCall])) {
|
|
131
|
+
const c1 = this.y[0]
|
|
132
|
+
if (c1.josi === 'して') { // 連文をblockとして接続する(もし構文、逐次実行構文などのため)
|
|
133
|
+
const c2 = this.ySentence()
|
|
134
|
+
if (c2 !== null) {
|
|
135
|
+
return {
|
|
136
|
+
type: 'block',
|
|
137
|
+
block: [c1, c2],
|
|
138
|
+
josi: c2.josi,
|
|
139
|
+
...map,
|
|
140
|
+
end: this.peekSourceMap()
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
return c1
|
|
145
|
+
}
|
|
146
|
+
return null
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** @returns {Ast} */
|
|
150
|
+
yASyncMode (): Ast {
|
|
151
|
+
const map = this.peekSourceMap()
|
|
152
|
+
this.genMode = '非同期モード'
|
|
153
|
+
return { type: 'eol', ...map, end: this.peekSourceMap() }
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/** @returns {Ast} */
|
|
157
|
+
yDNCLMode (): Ast {
|
|
158
|
+
const map = this.peekSourceMap()
|
|
159
|
+
// 配列インデックスは1から
|
|
160
|
+
this.arrayIndexFrom = 1
|
|
161
|
+
// 配列アクセスをJSと逆順で指定する
|
|
162
|
+
this.flagReverseArrayIndex = true
|
|
163
|
+
// 配列代入時自動で初期化チェックする
|
|
164
|
+
this.flagCheckArrayInit = true
|
|
165
|
+
return { type: 'eol', ...map, end: this.peekSourceMap() }
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/** @returns {Ast} */
|
|
169
|
+
ySetGenMode (mode: string): Ast {
|
|
170
|
+
const map = this.peekSourceMap()
|
|
171
|
+
this.genMode = mode
|
|
172
|
+
return { type: 'eol', ...map, end: this.peekSourceMap() }
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
/** @returns {Ast} */
|
|
176
|
+
yRequire (): Ast {
|
|
177
|
+
const nameToken = this.y[1]
|
|
178
|
+
const filename = nameToken.value
|
|
179
|
+
const modName = NakoLexer.filenameToModName(filename)
|
|
180
|
+
if (this.modList.indexOf(modName) < 0) {
|
|
181
|
+
// 優先度が最も高いのは modList[0]
|
|
182
|
+
// [memo] モジュールの検索優先度は、下に書くほど高くなる
|
|
183
|
+
const modSelf: string|undefined = this.modList.shift()
|
|
184
|
+
if (modSelf) {
|
|
185
|
+
this.modList.unshift(modName)
|
|
186
|
+
this.modList.unshift(modSelf)
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return {
|
|
190
|
+
type: 'require',
|
|
191
|
+
value: filename,
|
|
192
|
+
josi: '',
|
|
193
|
+
...this.peekSourceMap(),
|
|
194
|
+
end: this.peekSourceMap()
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** @returns {Ast} */
|
|
199
|
+
yBlock (): Ast {
|
|
200
|
+
const map = this.peekSourceMap()
|
|
201
|
+
const blocks = []
|
|
202
|
+
if (this.check('ここから')) { this.get() }
|
|
203
|
+
while (!this.isEOF()) {
|
|
204
|
+
if (this.checkTypes(['違えば', 'ここまで', 'エラー'])) { break }
|
|
205
|
+
if (!this.accept([this.ySentence])) { break }
|
|
206
|
+
blocks.push(this.y[0])
|
|
207
|
+
}
|
|
208
|
+
return { type: 'block', block: blocks, ...map, end: this.peekSourceMap() }
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
yDefFuncReadArgs (): Ast[]|null {
|
|
212
|
+
if (!this.check('(')) { return null }
|
|
213
|
+
const a: Ast[] = []
|
|
214
|
+
this.get() // skip '('
|
|
215
|
+
while (!this.isEOF()) {
|
|
216
|
+
if (this.check(')')) {
|
|
217
|
+
this.get() // skip ''
|
|
218
|
+
break
|
|
219
|
+
}
|
|
220
|
+
const t = this.get()
|
|
221
|
+
if (t) { a.push(t as Ast) }
|
|
222
|
+
if (this.check('comma')) { this.get() }
|
|
223
|
+
}
|
|
224
|
+
return a
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
/** @returns {Ast | null} */
|
|
228
|
+
yDefTest (): Ast|null {
|
|
229
|
+
return this.yDef('def_test')
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
/** @returns {Ast | null} */
|
|
233
|
+
yDefFunc (): Ast|null {
|
|
234
|
+
return this.yDef('def_func')
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* @param {string} type
|
|
239
|
+
* @returns {Ast | null}
|
|
240
|
+
*/
|
|
241
|
+
yDef (type: string): Ast|null {
|
|
242
|
+
if (!this.check(type)) {
|
|
243
|
+
return null
|
|
244
|
+
}
|
|
245
|
+
const map = this.peekSourceMap()
|
|
246
|
+
const def = this.get() // ●
|
|
247
|
+
if (!def) { return null }
|
|
248
|
+
let defArgs: Ast[] = []
|
|
249
|
+
if (this.check('(')) { defArgs = this.yDefFuncReadArgs() || [] } // // lexerでも解析しているが再度詳しく
|
|
250
|
+
|
|
251
|
+
const funcName: Token|null = this.get()
|
|
252
|
+
if (!funcName || funcName.type !== 'func') {
|
|
253
|
+
this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の宣言でエラー。', funcName)
|
|
254
|
+
throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の宣言でエラー。', def)
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (this.check('(')) {
|
|
258
|
+
// 関数引数の二重定義
|
|
259
|
+
if (defArgs.length > 0) {
|
|
260
|
+
this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) + 'の宣言で、引数定義は名前の前か後に一度だけ可能です。', funcName)
|
|
261
|
+
throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) + 'の宣言で、引数定義は名前の前か後に一度だけ可能です。', funcName)
|
|
262
|
+
}
|
|
263
|
+
defArgs = this.yDefFuncReadArgs() || []
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
if (this.check('とは')) { this.get() }
|
|
267
|
+
let block: Ast|null = null
|
|
268
|
+
let multiline = false
|
|
269
|
+
let asyncFn = false
|
|
270
|
+
if (this.check('ここから')) { multiline = true }
|
|
271
|
+
if (this.check('eol')) { multiline = true }
|
|
272
|
+
try {
|
|
273
|
+
this.funcLevel++
|
|
274
|
+
this.usedAsyncFn = false
|
|
275
|
+
// ローカル変数を生成
|
|
276
|
+
const backupLocalvars = this.localvars
|
|
277
|
+
this.localvars = { 'それ': { type: 'var', value: '' } }
|
|
278
|
+
|
|
279
|
+
if (multiline) {
|
|
280
|
+
this.saveStack()
|
|
281
|
+
// 関数の引数をローカル変数として登録する
|
|
282
|
+
for (const arg of defArgs) {
|
|
283
|
+
const fnName: string = (arg.value) ? arg.value + '' : ''
|
|
284
|
+
this.localvars[fnName] = { 'type': 'var', 'value': '' }
|
|
285
|
+
}
|
|
286
|
+
block = this.yBlock()
|
|
287
|
+
if (this.check('ここまで')) { this.get() } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。関数定義の末尾に必要です。', def) }
|
|
288
|
+
this.loadStack()
|
|
289
|
+
} else {
|
|
290
|
+
this.saveStack()
|
|
291
|
+
block = this.ySentence()
|
|
292
|
+
this.loadStack()
|
|
293
|
+
}
|
|
294
|
+
this.funcLevel--
|
|
295
|
+
asyncFn = this.usedAsyncFn
|
|
296
|
+
this.localvars = backupLocalvars
|
|
297
|
+
} catch (err: any) {
|
|
298
|
+
this.logger.debug(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, true) +
|
|
299
|
+
'の定義で以下のエラーがありました。\n' + err.message, def)
|
|
300
|
+
throw NakoSyntaxError.fromNode(this.nodeToStr(funcName, { depth: 0, typeName: '関数' }, false) +
|
|
301
|
+
'の定義で以下のエラーがありました。\n' + err.message, def)
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return {
|
|
305
|
+
type,
|
|
306
|
+
name: funcName,
|
|
307
|
+
args: defArgs,
|
|
308
|
+
block: block || [],
|
|
309
|
+
asyncFn,
|
|
310
|
+
josi: '',
|
|
311
|
+
...map,
|
|
312
|
+
end: this.peekSourceMap()
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/** @returns {Ast | null} */
|
|
317
|
+
yIFCond (): Ast | null { // もしの条件の取得
|
|
318
|
+
const map = this.peekSourceMap()
|
|
319
|
+
let a: Ast | null = this.yGetArg()
|
|
320
|
+
if (!a) { return null }
|
|
321
|
+
// console.log('yIFCond=', a, this.peek())
|
|
322
|
+
|
|
323
|
+
// チェック : AがBならば
|
|
324
|
+
if (a.josi === 'が') {
|
|
325
|
+
const tmpI = this.index
|
|
326
|
+
const b = this.yGetArg()
|
|
327
|
+
const naraba = this.get()
|
|
328
|
+
if ((b && b.type !== 'func') && (naraba && naraba.type === 'ならば')) {
|
|
329
|
+
return {
|
|
330
|
+
type: 'op',
|
|
331
|
+
operator: (naraba.value === 'でなければ') ? 'noteq' : 'eq',
|
|
332
|
+
left: a,
|
|
333
|
+
right: b,
|
|
334
|
+
josi: '',
|
|
335
|
+
...map,
|
|
336
|
+
end: this.peekSourceMap()
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
this.index = tmpI
|
|
341
|
+
}
|
|
342
|
+
if (a.josi !== '') {
|
|
343
|
+
// もし文で関数呼び出しがある場合
|
|
344
|
+
this.stack.push(a)
|
|
345
|
+
a = this.yCall()
|
|
346
|
+
}
|
|
347
|
+
// (ならば|でなければ)を確認
|
|
348
|
+
if (!this.check('ならば')) {
|
|
349
|
+
const smap: Ast = a || { type: '?', ...map }
|
|
350
|
+
this.logger.debug(
|
|
351
|
+
'もし文で『ならば』がないか、条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false) + 'の直前に『ならば』を書いてください。', smap)
|
|
352
|
+
throw NakoSyntaxError.fromNode(
|
|
353
|
+
'もし文で『ならば』がないか、条件が複雑過ぎます。' + this.nodeToStr(this.peek(), { depth: 1 }, false) + 'の直前に『ならば』を書いてください。', smap)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
const naraba = this.get()
|
|
357
|
+
if (naraba && naraba.value === 'でなければ') {
|
|
358
|
+
a = {
|
|
359
|
+
type: 'not',
|
|
360
|
+
value: a,
|
|
361
|
+
josi: '',
|
|
362
|
+
...map,
|
|
363
|
+
end: this.peekSourceMap()
|
|
364
|
+
}
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
return a
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/** @returns {Ast | null} */
|
|
371
|
+
yIF (): Ast | null {
|
|
372
|
+
const map = this.peekSourceMap()
|
|
373
|
+
if (!this.check('もし')) { return null }
|
|
374
|
+
const mosi:Token|null = this.get() // skip もし
|
|
375
|
+
if (mosi == null) { return null }
|
|
376
|
+
while (this.check('comma')) { this.get() } // skip comma
|
|
377
|
+
let cond: Ast|null = null
|
|
378
|
+
try {
|
|
379
|
+
cond = this.yIFCond()
|
|
380
|
+
} catch (err: any) {
|
|
381
|
+
throw NakoSyntaxError.fromNode('『もし』文の条件で次のエラーがあります。\n' + err.message, mosi)
|
|
382
|
+
}
|
|
383
|
+
if (cond === null) {
|
|
384
|
+
throw NakoSyntaxError.fromNode('『もし』文で条件の指定が空です。', mosi)
|
|
385
|
+
}
|
|
386
|
+
let trueBlock: Ast|null = null
|
|
387
|
+
let falseBlock: Ast|null = null
|
|
388
|
+
let tanbun = false
|
|
389
|
+
|
|
390
|
+
// True Block
|
|
391
|
+
if (this.check('eol')) {
|
|
392
|
+
trueBlock = this.yBlock()
|
|
393
|
+
} else {
|
|
394
|
+
trueBlock = this.ySentence()
|
|
395
|
+
tanbun = true
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
// skip EOL
|
|
399
|
+
while (this.check('eol')) { this.get() }
|
|
400
|
+
|
|
401
|
+
// Flase Block
|
|
402
|
+
if (this.check('違えば')) {
|
|
403
|
+
this.get() // skip 違えば
|
|
404
|
+
while (this.check('comma')) { this.get() }
|
|
405
|
+
if (this.check('eol')) {
|
|
406
|
+
falseBlock = this.yBlock()
|
|
407
|
+
} else {
|
|
408
|
+
falseBlock = this.ySentence()
|
|
409
|
+
tanbun = true
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
if (tanbun === false) {
|
|
414
|
+
if (this.check('ここまで')) {
|
|
415
|
+
this.get()
|
|
416
|
+
} else {
|
|
417
|
+
throw NakoSyntaxError.fromNode('『もし』文で『ここまで』がありません。', mosi)
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
return {
|
|
421
|
+
type: 'if',
|
|
422
|
+
expr: cond || [],
|
|
423
|
+
block: trueBlock || [],
|
|
424
|
+
false_block: falseBlock || [],
|
|
425
|
+
josi: '',
|
|
426
|
+
...map,
|
|
427
|
+
end: this.peekSourceMap()
|
|
428
|
+
}
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
ySpeedMode (): Ast | null {
|
|
432
|
+
const map: SourceMap = this.peekSourceMap()
|
|
433
|
+
if (!this.check2(['string', '実行速度優先'])) {
|
|
434
|
+
return null
|
|
435
|
+
}
|
|
436
|
+
const optionNode: Token|null = this.get()
|
|
437
|
+
this.get()
|
|
438
|
+
let val = ''
|
|
439
|
+
if (optionNode && optionNode.value) { val = optionNode.value } else { return null }
|
|
440
|
+
|
|
441
|
+
const options: {[key: string]: boolean} = { 行番号無し: false, 暗黙の型変換無し: false, 強制ピュア: false, それ無効: false }
|
|
442
|
+
for (const name of val.split('/')) {
|
|
443
|
+
// 全て有効化
|
|
444
|
+
if (name === '全て') {
|
|
445
|
+
for (const k of Object.keys(options)) {
|
|
446
|
+
options[k] = true
|
|
447
|
+
}
|
|
448
|
+
break
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// 個別に有効化
|
|
452
|
+
if (Object.keys(options).includes(name)) {
|
|
453
|
+
options[name] = true
|
|
454
|
+
} else {
|
|
455
|
+
// 互換性を考えて、警告に留める。
|
|
456
|
+
this.logger.warn(`実行速度優先文のオプション『${name}』は存在しません。`, optionNode)
|
|
457
|
+
}
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
let multiline = false
|
|
461
|
+
if (this.check('ここから')) {
|
|
462
|
+
this.get()
|
|
463
|
+
multiline = true
|
|
464
|
+
} else if (this.check('eol')) {
|
|
465
|
+
multiline = true
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
let block = null
|
|
469
|
+
if (multiline) {
|
|
470
|
+
block = this.yBlock()
|
|
471
|
+
if (this.check('ここまで')) { this.get() }
|
|
472
|
+
} else {
|
|
473
|
+
block = this.ySentence()
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return {
|
|
477
|
+
type: 'speed_mode',
|
|
478
|
+
options,
|
|
479
|
+
block: block || [],
|
|
480
|
+
josi: '',
|
|
481
|
+
...map
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
yPerformanceMonitor (): Ast|null {
|
|
486
|
+
const map = this.peekSourceMap()
|
|
487
|
+
if (!this.check2(['string', 'パフォーマンスモニタ適用'])) {
|
|
488
|
+
return null
|
|
489
|
+
}
|
|
490
|
+
const optionNode = this.get()
|
|
491
|
+
if (!optionNode) { return null }
|
|
492
|
+
this.get()
|
|
493
|
+
|
|
494
|
+
const options: {[key: string]: boolean} = { ユーザ関数: false, システム関数本体: false, システム関数: false }
|
|
495
|
+
for (const name of optionNode.value.split('/')) {
|
|
496
|
+
// 全て有効化
|
|
497
|
+
if (name === '全て') {
|
|
498
|
+
for (const k of Object.keys(options)) {
|
|
499
|
+
options[k] = true
|
|
500
|
+
}
|
|
501
|
+
break
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// 個別に有効化
|
|
505
|
+
if (Object.keys(options).includes(name)) {
|
|
506
|
+
options[name] = true
|
|
507
|
+
} else {
|
|
508
|
+
// 互換性を考えて、警告に留める。
|
|
509
|
+
this.logger.warn(`パフォーマンスモニタ適用文のオプション『${name}』は存在しません。`, optionNode)
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
let multiline = false
|
|
514
|
+
if (this.check('ここから')) {
|
|
515
|
+
this.get()
|
|
516
|
+
multiline = true
|
|
517
|
+
} else if (this.check('eol')) {
|
|
518
|
+
multiline = true
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
let block = null
|
|
522
|
+
if (multiline) {
|
|
523
|
+
block = this.yBlock()
|
|
524
|
+
if (this.check('ここまで')) { this.get() }
|
|
525
|
+
} else {
|
|
526
|
+
block = this.ySentence()
|
|
527
|
+
}
|
|
528
|
+
|
|
529
|
+
return {
|
|
530
|
+
type: 'performance_monitor',
|
|
531
|
+
options,
|
|
532
|
+
block: block || [],
|
|
533
|
+
josi: '',
|
|
534
|
+
...map
|
|
535
|
+
}
|
|
536
|
+
}
|
|
537
|
+
|
|
538
|
+
/** (非推奨) 「逐次実行」構文 @returns {Ast | null} */
|
|
539
|
+
yTikuji (): Ast|null {
|
|
540
|
+
const map = this.peekSourceMap()
|
|
541
|
+
if (!this.check('逐次実行')) { return null }
|
|
542
|
+
const tikuji = this.getCur() // skip 逐次実行
|
|
543
|
+
this.logger.warn('『逐次実行』構文の使用は非推奨になりました(https://nadesi.com/v3/doc/go.php?944)。', tikuji)
|
|
544
|
+
const blocks: Ast[] = []
|
|
545
|
+
let errorBlock = null
|
|
546
|
+
if (!tikuji || !this.check('eol')) {
|
|
547
|
+
throw NakoSyntaxError.fromNode('『逐次実行』の直後は改行が必要です。', tikuji)
|
|
548
|
+
}
|
|
549
|
+
// ブロックを読む
|
|
550
|
+
for (;;) {
|
|
551
|
+
if (this.check('ここまで')) { break }
|
|
552
|
+
if (this.check('eol')) {
|
|
553
|
+
this.get() // skip EOL
|
|
554
|
+
continue
|
|
555
|
+
}
|
|
556
|
+
if (this.check2(['エラー', 'ならば'])) {
|
|
557
|
+
this.get() // skip エラー
|
|
558
|
+
this.get() // skip ならば
|
|
559
|
+
errorBlock = this.yBlock()
|
|
560
|
+
break
|
|
561
|
+
}
|
|
562
|
+
let block = null
|
|
563
|
+
// 「先に」「次に」句はブロック宣言 #717 (ただしブロック以外も可能)
|
|
564
|
+
if (this.check('先に') || this.check('次に')) {
|
|
565
|
+
const tugini = this.get() // skip 先に | 次に
|
|
566
|
+
if (this.check('comma')) { this.get() }
|
|
567
|
+
if (this.check('eol')) { // block
|
|
568
|
+
block = this.yBlock()
|
|
569
|
+
if (!this.check('ここまで')) {
|
|
570
|
+
let tuginiType = '次に'
|
|
571
|
+
if (tugini != null) { tuginiType = tugini.type }
|
|
572
|
+
throw NakoSyntaxError.fromNode(`『${tuginiType}』...『ここまで』を対応させてください。`, map)
|
|
573
|
+
}
|
|
574
|
+
this.get() // skip 'ここまで'
|
|
575
|
+
} else { // line
|
|
576
|
+
block = this.ySentence()
|
|
577
|
+
}
|
|
578
|
+
} else {
|
|
579
|
+
block = this.ySentence()
|
|
580
|
+
}
|
|
581
|
+
// add block
|
|
582
|
+
if (block != null) { blocks.push(block) }
|
|
583
|
+
}
|
|
584
|
+
if (!this.check('ここまで')) {
|
|
585
|
+
console.log(blocks, this.peek())
|
|
586
|
+
throw NakoSyntaxError.fromNode('『逐次実行』...『ここまで』を対応させてください。', tikuji)
|
|
587
|
+
}
|
|
588
|
+
this.get() // skip 'ここまで'
|
|
589
|
+
return {
|
|
590
|
+
type: 'tikuji',
|
|
591
|
+
blocks: blocks || [],
|
|
592
|
+
errorBlock: errorBlock || [],
|
|
593
|
+
josi: '',
|
|
594
|
+
...map,
|
|
595
|
+
end: this.peekSourceMap()
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* 1つ目の値を与え、その後に続く計算式を取得し、優先規則に沿って並び替えして戻す
|
|
601
|
+
* @param {Ast} firstValue
|
|
602
|
+
*/
|
|
603
|
+
yGetArgOperator (firstValue: Ast): Ast|null {
|
|
604
|
+
const args:Ast[] = [firstValue]
|
|
605
|
+
while (!this.isEOF()) {
|
|
606
|
+
// 演算子がある?
|
|
607
|
+
let op = this.peek()
|
|
608
|
+
if (op && opPriority[op.type]) {
|
|
609
|
+
op = this.getCur()
|
|
610
|
+
args.push(op as Ast)
|
|
611
|
+
// 演算子後の値を取得
|
|
612
|
+
const v = this.yValue()
|
|
613
|
+
if (v === null) {
|
|
614
|
+
throw NakoSyntaxError.fromNode(
|
|
615
|
+
`計算式で演算子『${op.value}』後に値がありません`,
|
|
616
|
+
firstValue)
|
|
617
|
+
}
|
|
618
|
+
args.push(v)
|
|
619
|
+
continue
|
|
620
|
+
}
|
|
621
|
+
break
|
|
622
|
+
}
|
|
623
|
+
if (args.length === 0) { return null }
|
|
624
|
+
if (args.length === 1) { return args[0] }
|
|
625
|
+
return this.infixToAST(args)
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
yGetArg (): Ast|null {
|
|
629
|
+
// 値を一つ読む
|
|
630
|
+
const value1 = this.yValue()
|
|
631
|
+
if (value1 === null) { return null }
|
|
632
|
+
// 計算式がある場合を考慮
|
|
633
|
+
return this.yGetArgOperator(value1)
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
infixToPolish (list: Ast[]): Ast[] {
|
|
637
|
+
// 中間記法から逆ポーランドに変換
|
|
638
|
+
const priority = (t: Ast) => {
|
|
639
|
+
if (opPriority[t.type]) { return opPriority[t.type] }
|
|
640
|
+
return 10
|
|
641
|
+
}
|
|
642
|
+
const stack: Ast[] = []
|
|
643
|
+
const polish: Ast[] = []
|
|
644
|
+
while (list.length > 0) {
|
|
645
|
+
const t = list.shift()
|
|
646
|
+
if (!t) { break }
|
|
647
|
+
while (stack.length > 0) { // 優先順位を見て移動する
|
|
648
|
+
const sTop = stack[stack.length - 1]
|
|
649
|
+
if (priority(t) > priority(sTop)) { break }
|
|
650
|
+
const tpop = stack.pop()
|
|
651
|
+
if (!tpop) {
|
|
652
|
+
this.logger.error('計算式に間違いがあります。', t)
|
|
653
|
+
break
|
|
654
|
+
}
|
|
655
|
+
polish.push(tpop)
|
|
656
|
+
}
|
|
657
|
+
stack.push(t)
|
|
658
|
+
}
|
|
659
|
+
// 残った要素を積み替える
|
|
660
|
+
while (stack.length > 0) {
|
|
661
|
+
const t = stack.pop()
|
|
662
|
+
if (t) { polish.push(t) }
|
|
663
|
+
}
|
|
664
|
+
return polish
|
|
665
|
+
}
|
|
666
|
+
|
|
667
|
+
/** @returns {Ast | null} */
|
|
668
|
+
infixToAST (list: Ast[]): Ast | null {
|
|
669
|
+
if (list.length === 0) { return null }
|
|
670
|
+
// 逆ポーランドを構文木に
|
|
671
|
+
const josi = list[list.length - 1].josi
|
|
672
|
+
const node = list[list.length - 1]
|
|
673
|
+
const polish = this.infixToPolish(list)
|
|
674
|
+
/** @type {Ast[]} */
|
|
675
|
+
const stack = []
|
|
676
|
+
for (const t of polish) {
|
|
677
|
+
if (!opPriority[t.type]) { // 演算子ではない
|
|
678
|
+
stack.push(t)
|
|
679
|
+
continue
|
|
680
|
+
}
|
|
681
|
+
const b:Ast|undefined = stack.pop()
|
|
682
|
+
const a:Ast|undefined = stack.pop()
|
|
683
|
+
if (a === undefined || b === undefined) {
|
|
684
|
+
this.logger.debug('--- 計算式(逆ポーランド) ---\n' + JSON.stringify(polish))
|
|
685
|
+
throw NakoSyntaxError.fromNode('計算式でエラー', node)
|
|
686
|
+
}
|
|
687
|
+
/** @type {Ast} */
|
|
688
|
+
const op:Ast = {
|
|
689
|
+
type: 'op',
|
|
690
|
+
operator: t.type,
|
|
691
|
+
left: a,
|
|
692
|
+
right: b,
|
|
693
|
+
josi,
|
|
694
|
+
startOffset: a.startOffset,
|
|
695
|
+
endOffset: a.endOffset,
|
|
696
|
+
line: a.line,
|
|
697
|
+
column: a.column,
|
|
698
|
+
file: a.file
|
|
699
|
+
}
|
|
700
|
+
stack.push(op)
|
|
701
|
+
}
|
|
702
|
+
const ans = stack.pop()
|
|
703
|
+
if (!ans) { return null }
|
|
704
|
+
return ans
|
|
705
|
+
}
|
|
706
|
+
|
|
707
|
+
yGetArgParen (y: Ast[]): Ast[] { // C言語風呼び出しでカッコの中を取得
|
|
708
|
+
let isClose = false
|
|
709
|
+
const si = this.stack.length
|
|
710
|
+
while (!this.isEOF()) {
|
|
711
|
+
if (this.check(')')) {
|
|
712
|
+
isClose = true
|
|
713
|
+
break
|
|
714
|
+
}
|
|
715
|
+
const v = this.yGetArg()
|
|
716
|
+
if (v) {
|
|
717
|
+
this.pushStack(v)
|
|
718
|
+
if (this.check('comma')) { this.get() }
|
|
719
|
+
continue
|
|
720
|
+
}
|
|
721
|
+
break
|
|
722
|
+
}
|
|
723
|
+
if (!isClose) {
|
|
724
|
+
throw NakoSyntaxError.fromNode(`C風関数『${y[0].value}』でカッコが閉じていません`, y[0])
|
|
725
|
+
}
|
|
726
|
+
const a: Ast[] = []
|
|
727
|
+
while (si < this.stack.length) {
|
|
728
|
+
const v = this.popStack()
|
|
729
|
+
if (v) { a.unshift(v) }
|
|
730
|
+
}
|
|
731
|
+
return a
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/** @returns {Ast | null} */
|
|
735
|
+
yRepeatTime (): Ast|null {
|
|
736
|
+
const map = this.peekSourceMap()
|
|
737
|
+
if (!this.check('回')) { return null }
|
|
738
|
+
this.get() // skip '回'
|
|
739
|
+
if (this.check('comma')) { this.get() } // skip comma
|
|
740
|
+
if (this.check('繰返')) { this.get() } // skip 'N回、繰り返す' (#924)
|
|
741
|
+
let num = this.popStack([])
|
|
742
|
+
let multiline = false
|
|
743
|
+
let block = null
|
|
744
|
+
if (num === null) { num = { type: 'word', value: 'それ', josi: '', ...map, end: this.peekSourceMap() } }
|
|
745
|
+
if (this.check('comma')) { this.get() }
|
|
746
|
+
if (this.check('ここから')) {
|
|
747
|
+
this.get()
|
|
748
|
+
multiline = true
|
|
749
|
+
} else if (this.check('eol')) {
|
|
750
|
+
multiline = true
|
|
751
|
+
}
|
|
752
|
+
if (multiline) { // multiline
|
|
753
|
+
block = this.yBlock()
|
|
754
|
+
if (this.check('ここまで')) { this.get() } else { throw NakoSyntaxError.fromNode('『ここまで』がありません。『回』...『ここまで』を対応させてください。', map) }
|
|
755
|
+
} else {
|
|
756
|
+
// singleline
|
|
757
|
+
block = this.ySentence()
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
return {
|
|
761
|
+
type: 'repeat_times',
|
|
762
|
+
value: num,
|
|
763
|
+
block: block || [],
|
|
764
|
+
josi: '',
|
|
765
|
+
...map,
|
|
766
|
+
end: this.peekSourceMap()
|
|
767
|
+
}
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
/** @returns {Ast | null} */
|
|
771
|
+
yWhile (): Ast | null {
|
|
772
|
+
const map = this.peekSourceMap()
|
|
773
|
+
if (!this.check('間')) { return null }
|
|
774
|
+
this.get() // skip '間'
|
|
775
|
+
while (this.check('comma')) { this.get() } // skip ','
|
|
776
|
+
if (this.check('繰返')) { this.get() } // skip '繰り返す' #927
|
|
777
|
+
const cond = this.popStack()
|
|
778
|
+
if (cond === null) {
|
|
779
|
+
throw NakoSyntaxError.fromNode('『間』で条件がありません。', map)
|
|
780
|
+
}
|
|
781
|
+
if (this.check('comma')) { this.get() }
|
|
782
|
+
if (!this.checkTypes(['ここから', 'eol'])) {
|
|
783
|
+
throw NakoSyntaxError.fromNode('『間』の直後は改行が必要です', map)
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
const block = this.yBlock()
|
|
787
|
+
if (this.check('ここまで')) { this.get() }
|
|
788
|
+
return {
|
|
789
|
+
type: 'while',
|
|
790
|
+
cond,
|
|
791
|
+
block,
|
|
792
|
+
josi: '',
|
|
793
|
+
...map,
|
|
794
|
+
end: this.peekSourceMap()
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/** @returns {Ast | null} */
|
|
799
|
+
yAtohantei (): Ast|null {
|
|
800
|
+
const map = this.peekSourceMap()
|
|
801
|
+
if (this.check('後判定')) { this.get() } // skip 後判定
|
|
802
|
+
if (this.check('繰返')) { this.get() } // skip 繰り返す
|
|
803
|
+
if (this.check('ここから')) { this.get() }
|
|
804
|
+
const block = this.yBlock()
|
|
805
|
+
if (this.check('ここまで')) { this.get() }
|
|
806
|
+
if (this.check('comma')) { this.get() }
|
|
807
|
+
let cond = this.yGetArg() // 条件
|
|
808
|
+
let bUntil = false
|
|
809
|
+
const t = this.peek()
|
|
810
|
+
if (t && t.value === 'なる' && (t.josi === 'まで' || t.josi === 'までの')) {
|
|
811
|
+
this.get() // skip なるまで
|
|
812
|
+
bUntil = true
|
|
813
|
+
}
|
|
814
|
+
if (this.check('間')) { this.get() } // skip 間
|
|
815
|
+
if (bUntil) { // 条件を反転する
|
|
816
|
+
cond = {
|
|
817
|
+
type: 'not',
|
|
818
|
+
value: cond,
|
|
819
|
+
josi: '',
|
|
820
|
+
...map,
|
|
821
|
+
end: this.peekSourceMap()
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
return {
|
|
825
|
+
type: 'atohantei',
|
|
826
|
+
cond: cond || [],
|
|
827
|
+
block,
|
|
828
|
+
josi: '',
|
|
829
|
+
...map,
|
|
830
|
+
end: this.peekSourceMap()
|
|
831
|
+
}
|
|
832
|
+
}
|
|
833
|
+
|
|
834
|
+
/** @returns {Ast | null} */
|
|
835
|
+
yFor (): Ast|null {
|
|
836
|
+
const map = this.peekSourceMap()
|
|
837
|
+
if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) {
|
|
838
|
+
// pass
|
|
839
|
+
} else {
|
|
840
|
+
return null
|
|
841
|
+
}
|
|
842
|
+
const kurikaesu: Token = this.getCur() // skip 繰り返す
|
|
843
|
+
// スタックに(増や|減ら)してがある?
|
|
844
|
+
const incdec = this.stack.pop()
|
|
845
|
+
if (incdec) {
|
|
846
|
+
if (incdec.type === 'word' && (incdec.value === '増' || incdec.value === '減')) {
|
|
847
|
+
kurikaesu.type = incdec.value + kurikaesu.type
|
|
848
|
+
// ↑ typeを増繰返 | 減繰返 に変換
|
|
849
|
+
} else {
|
|
850
|
+
// 普通の繰り返しの場合
|
|
851
|
+
this.stack.push(incdec) // 違ったので改めて追加
|
|
852
|
+
}
|
|
853
|
+
}
|
|
854
|
+
let vInc = null
|
|
855
|
+
if (kurikaesu.type === '増繰返' || kurikaesu.type === '減繰返') {
|
|
856
|
+
vInc = this.popStack(['ずつ'])
|
|
857
|
+
}
|
|
858
|
+
const vTo = this.popStack(['まで'])
|
|
859
|
+
const vFrom = this.popStack(['から'])
|
|
860
|
+
const word = this.popStack(['を', 'で'])
|
|
861
|
+
if (vFrom === null || vTo === null) {
|
|
862
|
+
throw NakoSyntaxError.fromNode('『繰り返す』文でAからBまでの指定がありません。', kurikaesu)
|
|
863
|
+
}
|
|
864
|
+
if (this.check('comma')) { this.get() } // skip comma
|
|
865
|
+
let multiline = false
|
|
866
|
+
if (this.check('ここから')) {
|
|
867
|
+
multiline = true
|
|
868
|
+
this.get()
|
|
869
|
+
} else if (this.check('eol')) {
|
|
870
|
+
multiline = true
|
|
871
|
+
this.get()
|
|
872
|
+
}
|
|
873
|
+
let block = null
|
|
874
|
+
if (multiline) {
|
|
875
|
+
block = this.yBlock()
|
|
876
|
+
if (this.check('ここまで')) {
|
|
877
|
+
this.get()
|
|
878
|
+
} else {
|
|
879
|
+
throw NakoSyntaxError.fromNode('『ここまで』がありません。『繰り返す』...『ここまで』を対応させてください。', map)
|
|
880
|
+
}
|
|
881
|
+
} else { block = this.ySentence() }
|
|
882
|
+
|
|
883
|
+
return {
|
|
884
|
+
type: 'for',
|
|
885
|
+
from: vFrom,
|
|
886
|
+
to: vTo,
|
|
887
|
+
inc: vInc,
|
|
888
|
+
word,
|
|
889
|
+
block: block || [],
|
|
890
|
+
josi: '',
|
|
891
|
+
...map,
|
|
892
|
+
end: this.peekSourceMap()
|
|
893
|
+
}
|
|
894
|
+
}
|
|
895
|
+
|
|
896
|
+
/** @returns {Ast | null} */
|
|
897
|
+
yReturn (): Ast|null {
|
|
898
|
+
const map = this.peekSourceMap()
|
|
899
|
+
if (!this.check('戻る')) { return null }
|
|
900
|
+
this.get() // skip '戻る'
|
|
901
|
+
const v = this.popStack(['で', 'を'])
|
|
902
|
+
if (this.stack.length > 0) {
|
|
903
|
+
throw NakoSyntaxError.fromNode('『戻』文の直前に未解決の引数があります。『(式)を戻す』のように式をカッコで括ってください。', map)
|
|
904
|
+
}
|
|
905
|
+
return {
|
|
906
|
+
type: 'return',
|
|
907
|
+
value: v,
|
|
908
|
+
josi: '',
|
|
909
|
+
...map,
|
|
910
|
+
end: this.peekSourceMap()
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
/** @returns {Ast | null} */
|
|
915
|
+
yForEach (): Ast|null {
|
|
916
|
+
const map = this.peekSourceMap()
|
|
917
|
+
if (!this.check('反復')) { return null }
|
|
918
|
+
this.get() // skip '反復'
|
|
919
|
+
while (this.check('comma')) { this.get() } // skip ','
|
|
920
|
+
const target = this.popStack(['を'])
|
|
921
|
+
const name = this.popStack(['で'])
|
|
922
|
+
let block = null
|
|
923
|
+
let multiline = false
|
|
924
|
+
if (this.check('ここから')) {
|
|
925
|
+
multiline = true
|
|
926
|
+
this.get()
|
|
927
|
+
} else if (this.check('eol')) { multiline = true }
|
|
928
|
+
|
|
929
|
+
if (multiline) {
|
|
930
|
+
block = this.yBlock()
|
|
931
|
+
if (this.check('ここまで')) { this.get() }
|
|
932
|
+
} else { block = this.ySentence() }
|
|
933
|
+
|
|
934
|
+
return {
|
|
935
|
+
type: 'foreach',
|
|
936
|
+
name,
|
|
937
|
+
target,
|
|
938
|
+
block: block || [],
|
|
939
|
+
josi: '',
|
|
940
|
+
...map,
|
|
941
|
+
end: this.peekSourceMap()
|
|
942
|
+
}
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
/** 条件分岐構文 */
|
|
946
|
+
ySwitch (): Ast | null {
|
|
947
|
+
const map = this.peekSourceMap()
|
|
948
|
+
if (!this.check('条件分岐')) { return null }
|
|
949
|
+
const joukenbunki = this.get() // skip '条件分岐'
|
|
950
|
+
if (!joukenbunki) { return null }
|
|
951
|
+
const eol = this.get() // skip 'eol'
|
|
952
|
+
if (!eol) { return null }
|
|
953
|
+
const value = this.popStack(['で'])
|
|
954
|
+
if (!value) {
|
|
955
|
+
throw NakoSyntaxError.fromNode('『(値)で条件分岐』のように記述してください。', joukenbunki)
|
|
956
|
+
}
|
|
957
|
+
if (eol.type !== 'eol') {
|
|
958
|
+
throw NakoSyntaxError.fromNode('『条件分岐』の直後は改行してください。', joukenbunki)
|
|
959
|
+
}
|
|
960
|
+
let isDefaultClause = false // 「違えば」内かどうか
|
|
961
|
+
let skippedKokomade = false
|
|
962
|
+
const cases: any[] = []
|
|
963
|
+
while (!this.isEOF()) {
|
|
964
|
+
if (this.check('ここまで')) {
|
|
965
|
+
if (skippedKokomade) {
|
|
966
|
+
throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki)
|
|
967
|
+
}
|
|
968
|
+
this.get() // skip ここまで
|
|
969
|
+
break
|
|
970
|
+
}
|
|
971
|
+
if (this.check('eol')) {
|
|
972
|
+
this.get()
|
|
973
|
+
continue
|
|
974
|
+
}
|
|
975
|
+
if (isDefaultClause) {
|
|
976
|
+
throw NakoSyntaxError.fromNode('『条件分岐』で『違えば〜ここまで』の後に処理を続けることは出来ません。', joukenbunki)
|
|
977
|
+
}
|
|
978
|
+
// 違えば?
|
|
979
|
+
let cond: Ast|null = null
|
|
980
|
+
const condToken: Token|null = this.peek()
|
|
981
|
+
if (condToken && condToken.type === '違えば') {
|
|
982
|
+
// 違えば
|
|
983
|
+
skippedKokomade = false
|
|
984
|
+
isDefaultClause = true
|
|
985
|
+
cond = this.get() as Ast // skip 違えば
|
|
986
|
+
if (this.check('comma')) { this.get() } // skip ','
|
|
987
|
+
} else {
|
|
988
|
+
// ***ならば
|
|
989
|
+
if (skippedKokomade) {
|
|
990
|
+
throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki)
|
|
991
|
+
}
|
|
992
|
+
// 「**ならば」を得る
|
|
993
|
+
cond = this.yValue()
|
|
994
|
+
if (!cond) {
|
|
995
|
+
throw NakoSyntaxError.fromNode('『条件分岐』は『(条件)ならば〜ここまで』と記述してください。', joukenbunki)
|
|
996
|
+
}
|
|
997
|
+
const naraba = this.get() // skip ならば
|
|
998
|
+
if (!naraba || naraba.type !== 'ならば') {
|
|
999
|
+
throw NakoSyntaxError.fromNode('『条件分岐』で条件は**ならばと記述してください。', joukenbunki)
|
|
1000
|
+
}
|
|
1001
|
+
if (this.check('comma')) { this.get() } // skip ','
|
|
1002
|
+
}
|
|
1003
|
+
// 条件にあったときに実行すること
|
|
1004
|
+
const condBlock = this.yBlock()
|
|
1005
|
+
const kokomade = this.peek()
|
|
1006
|
+
if (kokomade && kokomade.type === 'ここまで') {
|
|
1007
|
+
this.get() // skip ここまで
|
|
1008
|
+
} else {
|
|
1009
|
+
if (isDefaultClause) {
|
|
1010
|
+
throw NakoSyntaxError.fromNode('『条件分岐』は『違えば〜ここまで』と記述してください。', joukenbunki)
|
|
1011
|
+
}
|
|
1012
|
+
// 次が「違えば」の場合に限り、「もし〜ここまで」の「ここまで」を省略できる
|
|
1013
|
+
skippedKokomade = true
|
|
1014
|
+
}
|
|
1015
|
+
cases.push([cond, condBlock])
|
|
1016
|
+
}
|
|
1017
|
+
|
|
1018
|
+
return {
|
|
1019
|
+
type: 'switch',
|
|
1020
|
+
value,
|
|
1021
|
+
cases: cases || [],
|
|
1022
|
+
josi: '',
|
|
1023
|
+
...map,
|
|
1024
|
+
end: this.peekSourceMap()
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
|
|
1028
|
+
/** 無名関数 */
|
|
1029
|
+
yMumeiFunc (): Ast | null { // 無名関数の定義
|
|
1030
|
+
const map = this.peekSourceMap()
|
|
1031
|
+
if (!this.check('def_func')) { return null }
|
|
1032
|
+
const def = this.get()
|
|
1033
|
+
if (!def) { return null }
|
|
1034
|
+
let args: Ast[] = []
|
|
1035
|
+
// 「,」を飛ばす
|
|
1036
|
+
if (this.check('comma')) { this.get() }
|
|
1037
|
+
// 関数の引数定義は省略できる
|
|
1038
|
+
if (this.check('(')) { args = this.yDefFuncReadArgs() || [] }
|
|
1039
|
+
// 「,」を飛ばす
|
|
1040
|
+
if (this.check('comma')) { this.get() }
|
|
1041
|
+
// ブロックを読む
|
|
1042
|
+
this.funcLevel++
|
|
1043
|
+
this.saveStack()
|
|
1044
|
+
const block = this.yBlock()
|
|
1045
|
+
// 末尾の「ここまで」をチェック - もしなければエラーにする #1045
|
|
1046
|
+
if (!this.check('ここまで')) {
|
|
1047
|
+
throw NakoSyntaxError.fromNode('『ここまで』がありません。『には』構文か無名関数の末尾に『ここまで』が必要です。', map)
|
|
1048
|
+
}
|
|
1049
|
+
this.get() // skip ここまで
|
|
1050
|
+
this.loadStack()
|
|
1051
|
+
this.funcLevel--
|
|
1052
|
+
return {
|
|
1053
|
+
type: 'func_obj',
|
|
1054
|
+
args,
|
|
1055
|
+
block,
|
|
1056
|
+
meta: def.meta,
|
|
1057
|
+
josi: '',
|
|
1058
|
+
...map,
|
|
1059
|
+
end: this.peekSourceMap()
|
|
1060
|
+
}
|
|
1061
|
+
}
|
|
1062
|
+
|
|
1063
|
+
/** 代入構文 */
|
|
1064
|
+
yDainyu (): Ast | null {
|
|
1065
|
+
const map = this.peekSourceMap()
|
|
1066
|
+
const dainyu = this.get() // 代入
|
|
1067
|
+
if (dainyu === null) { return null }
|
|
1068
|
+
const value = this.popStack(['を'])
|
|
1069
|
+
const word: Ast|null = this.popStack(['へ', 'に'])
|
|
1070
|
+
if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== '配列参照')) {
|
|
1071
|
+
throw NakoSyntaxError.fromNode('代入文で代入先の変数が見当たりません。『(変数名)に(値)を代入』のように使います。', dainyu)
|
|
1072
|
+
}
|
|
1073
|
+
// 配列への代入
|
|
1074
|
+
if (word.type === '配列参照') {
|
|
1075
|
+
return {
|
|
1076
|
+
type: 'let_array',
|
|
1077
|
+
name: word.name,
|
|
1078
|
+
index: word.index,
|
|
1079
|
+
value,
|
|
1080
|
+
josi: '',
|
|
1081
|
+
checkInit: this.flagCheckArrayInit,
|
|
1082
|
+
...map,
|
|
1083
|
+
end: this.peekSourceMap()
|
|
1084
|
+
}
|
|
1085
|
+
}
|
|
1086
|
+
// 一般的な変数への代入
|
|
1087
|
+
const word2 = this.getVarName(word)
|
|
1088
|
+
return {
|
|
1089
|
+
type: 'let',
|
|
1090
|
+
name: word2,
|
|
1091
|
+
value,
|
|
1092
|
+
josi: '',
|
|
1093
|
+
...map,
|
|
1094
|
+
end: this.peekSourceMap()
|
|
1095
|
+
}
|
|
1096
|
+
}
|
|
1097
|
+
|
|
1098
|
+
/** 定める構文 */
|
|
1099
|
+
ySadameru (): Ast | null {
|
|
1100
|
+
const map = this.peekSourceMap()
|
|
1101
|
+
const sadameru = this.get() // 定める
|
|
1102
|
+
if (sadameru === null) { return null }
|
|
1103
|
+
const word = this.popStack(['を'])
|
|
1104
|
+
const value = this.popStack(['へ', 'に'])
|
|
1105
|
+
if (!word || (word.type !== 'word' && word.type !== 'func' && word.type !== '配列参照')) {
|
|
1106
|
+
throw NakoSyntaxError.fromNode('『定める』文で定数が見当たりません。『(定数名)を(値)に定める』のように使います。', sadameru)
|
|
1107
|
+
}
|
|
1108
|
+
// 変数を生成する
|
|
1109
|
+
const nameToken = this.getVarName(word)
|
|
1110
|
+
return {
|
|
1111
|
+
type: 'def_local_var',
|
|
1112
|
+
name: nameToken,
|
|
1113
|
+
vartype: '定数',
|
|
1114
|
+
value,
|
|
1115
|
+
josi: '',
|
|
1116
|
+
...map,
|
|
1117
|
+
end: this.peekSourceMap()
|
|
1118
|
+
}
|
|
1119
|
+
}
|
|
1120
|
+
|
|
1121
|
+
yIncDec (): Ast | null {
|
|
1122
|
+
const map = this.peekSourceMap()
|
|
1123
|
+
const action = this.get() // (増やす|減らす)
|
|
1124
|
+
if (action === null) { return null }
|
|
1125
|
+
|
|
1126
|
+
// 『Nずつ増やして繰り返す』文か?
|
|
1127
|
+
if (this.check('繰返')) {
|
|
1128
|
+
this.pushStack({ type: 'word', value: action.value, josi: action.josi, ...map, end: this.peekSourceMap() })
|
|
1129
|
+
return this.yFor()
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
// スタックから引数をポップ
|
|
1133
|
+
let value = this.popStack(['だけ', ''])
|
|
1134
|
+
if (!value) {
|
|
1135
|
+
value = { type: 'number', value: 1, josi: 'だけ', ...map, end: this.peekSourceMap() }
|
|
1136
|
+
}
|
|
1137
|
+
const word = this.popStack(['を'])
|
|
1138
|
+
if (!word || (word.type !== 'word' && word.type !== '配列参照')) {
|
|
1139
|
+
throw NakoSyntaxError.fromNode(
|
|
1140
|
+
`『${action.type}』文で定数が見当たりません。『(変数名)を(値)だけ${action.type}』のように使います。`,
|
|
1141
|
+
action)
|
|
1142
|
+
}
|
|
1143
|
+
|
|
1144
|
+
// 減らすなら-1かける
|
|
1145
|
+
if (action.value === '減') {
|
|
1146
|
+
value = { type: 'op', operator: '*', left: value, right: { type: 'number', value: -1, line: action.line }, josi: '', ...map }
|
|
1147
|
+
}
|
|
1148
|
+
|
|
1149
|
+
return {
|
|
1150
|
+
type: 'inc',
|
|
1151
|
+
name: word,
|
|
1152
|
+
value,
|
|
1153
|
+
josi: action.josi,
|
|
1154
|
+
...map,
|
|
1155
|
+
end: this.peekSourceMap()
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
|
|
1159
|
+
yCall (): Ast | null {
|
|
1160
|
+
if (this.isEOF()) { return null }
|
|
1161
|
+
|
|
1162
|
+
// スタックに積んでいく
|
|
1163
|
+
while (!this.isEOF()) {
|
|
1164
|
+
if (this.check('ここから')) { this.get() }
|
|
1165
|
+
// 代入
|
|
1166
|
+
if (this.check('代入')) { return this.yDainyu() }
|
|
1167
|
+
if (this.check('定める')) { return this.ySadameru() }
|
|
1168
|
+
// 制御構文
|
|
1169
|
+
if (this.check('回')) { return this.yRepeatTime() }
|
|
1170
|
+
if (this.check('間')) { return this.yWhile() }
|
|
1171
|
+
if (this.check('繰返') || this.check('増繰返') || this.check('減繰返')) { return this.yFor() }
|
|
1172
|
+
if (this.check('反復')) { return this.yForEach() }
|
|
1173
|
+
if (this.check('条件分岐')) { return this.ySwitch() }
|
|
1174
|
+
if (this.check('戻る')) { return this.yReturn() }
|
|
1175
|
+
if (this.check('増') || this.check('減')) { return this.yIncDec() }
|
|
1176
|
+
// C言語風関数
|
|
1177
|
+
if (this.check2([['func', 'word'], '('])) { // C言語風
|
|
1178
|
+
const cur = this.peek()
|
|
1179
|
+
if (cur && cur.josi === '') {
|
|
1180
|
+
const t: Ast|null = this.yValue()
|
|
1181
|
+
if (t) {
|
|
1182
|
+
const josi = t.josi || ''
|
|
1183
|
+
if (t.type === 'func' && (t.josi === '' || keizokuJosi.indexOf(josi) >= 0)) {
|
|
1184
|
+
t.josi = ''
|
|
1185
|
+
return t // 関数なら値とする
|
|
1186
|
+
}
|
|
1187
|
+
this.pushStack(t)
|
|
1188
|
+
}
|
|
1189
|
+
if (this.check('comma')) { this.get() }
|
|
1190
|
+
continue
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
// なでしこ式関数
|
|
1194
|
+
if (this.check('func')) {
|
|
1195
|
+
const r = this.yCallFunc()
|
|
1196
|
+
if (r === null) { continue }
|
|
1197
|
+
// 「〜する間」の形ならスタックに積む。
|
|
1198
|
+
if (this.check('間')) {
|
|
1199
|
+
this.pushStack(r)
|
|
1200
|
+
continue
|
|
1201
|
+
}
|
|
1202
|
+
// 関数呼び出しの直後に、四則演算があるか?
|
|
1203
|
+
if (!this.checkTypes(operatorList)) { return r } // なければ関数呼び出しを戻す
|
|
1204
|
+
// 四則演算があった場合、計算してスタックに載せる
|
|
1205
|
+
this.pushStack(this.yGetArgOperator(r))
|
|
1206
|
+
continue
|
|
1207
|
+
}
|
|
1208
|
+
// 値のとき → スタックに載せる
|
|
1209
|
+
const t = this.yGetArg()
|
|
1210
|
+
if (t) {
|
|
1211
|
+
this.pushStack(t)
|
|
1212
|
+
continue
|
|
1213
|
+
}
|
|
1214
|
+
break
|
|
1215
|
+
} // end of while
|
|
1216
|
+
|
|
1217
|
+
// 助詞が余ってしまった場合
|
|
1218
|
+
if (this.stack.length > 0) {
|
|
1219
|
+
this.logger.debug('--- stack dump ---\n' + JSON.stringify(this.stack, null, 2) + '\npeek: ' + JSON.stringify(this.peek(), null, 2))
|
|
1220
|
+
let msgDebug = `不完全な文です。${this.stack.map((n) => this.nodeToStr(n, { depth: 0 }, true)).join('、')}が解決していません。`
|
|
1221
|
+
let msg = `不完全な文です。${this.stack.map((n) => this.nodeToStr(n, { depth: 0 }, false)).join('、')}が解決していません。`
|
|
1222
|
+
|
|
1223
|
+
// 各ノードについて、更に詳細な情報があるなら表示
|
|
1224
|
+
for (const n of this.stack) {
|
|
1225
|
+
const d0 = this.nodeToStr(n, { depth: 0 }, false)
|
|
1226
|
+
const d1 = this.nodeToStr(n, { depth: 1 }, false)
|
|
1227
|
+
if (d0 !== d1) {
|
|
1228
|
+
msgDebug += `${this.nodeToStr(n, { depth: 0 }, true)}は${this.nodeToStr(n, { depth: 1 }, true)}として使われています。`
|
|
1229
|
+
msg += `${d0}は${d1}として使われています。`
|
|
1230
|
+
}
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
const first = this.stack[0]
|
|
1234
|
+
const last = this.stack[this.stack.length - 1]
|
|
1235
|
+
this.logger.debug(msgDebug, first)
|
|
1236
|
+
throw NakoSyntaxError.fromNode(msg, first, last)
|
|
1237
|
+
}
|
|
1238
|
+
return this.popStack([])
|
|
1239
|
+
}
|
|
1240
|
+
|
|
1241
|
+
/** @returns {Ast | null} */
|
|
1242
|
+
yCallFunc (): Ast | null {
|
|
1243
|
+
const map = this.peekSourceMap()
|
|
1244
|
+
const t = this.get()
|
|
1245
|
+
if (!t) { return null }
|
|
1246
|
+
const f = t.meta
|
|
1247
|
+
const funcName: string = t.value
|
|
1248
|
+
// (関数)には ... 構文 ... https://github.com/kujirahand/nadesiko3/issues/66
|
|
1249
|
+
let funcObj = null
|
|
1250
|
+
if (t.josi === 'には') {
|
|
1251
|
+
try {
|
|
1252
|
+
funcObj = this.yMumeiFunc()
|
|
1253
|
+
} catch (err: any) {
|
|
1254
|
+
throw NakoSyntaxError.fromNode(`『${t.value}には...』で無名関数の定義で以下の間違いがあります。\n${err.message}`, t)
|
|
1255
|
+
}
|
|
1256
|
+
if (funcObj === null) { throw NakoSyntaxError.fromNode('『Fには』構文がありましたが、関数定義が見当たりません。', t) }
|
|
1257
|
+
}
|
|
1258
|
+
if (!f || typeof f.josi === 'undefined') { throw NakoSyntaxError.fromNode('関数の定義でエラー。', t) }
|
|
1259
|
+
|
|
1260
|
+
// 最近使った関数を記録
|
|
1261
|
+
this.recentlyCalledFunc.push({ name: funcName, ...f })
|
|
1262
|
+
|
|
1263
|
+
// 呼び出す関数が非同期呼び出しが必要(asyncFn)ならマーク
|
|
1264
|
+
if (f && f.asyncFn) { this.usedAsyncFn = true }
|
|
1265
|
+
|
|
1266
|
+
// 関数の引数を取り出す処理
|
|
1267
|
+
const args: any[] = []
|
|
1268
|
+
let nullCount = 0
|
|
1269
|
+
let valueCount = 0
|
|
1270
|
+
for (let i = 0; i < f.josi.length; i++) {
|
|
1271
|
+
while (true) {
|
|
1272
|
+
// スタックから任意の助詞を持つ値を一つ取り出す、助詞がなければ末尾から得る
|
|
1273
|
+
let popArg = this.popStack(f.josi[i])
|
|
1274
|
+
if (popArg !== null) {
|
|
1275
|
+
valueCount++
|
|
1276
|
+
} else if (i < f.josi.length - 1 || !f.isVariableJosi) {
|
|
1277
|
+
nullCount++
|
|
1278
|
+
popArg = funcObj
|
|
1279
|
+
} else {
|
|
1280
|
+
break
|
|
1281
|
+
}
|
|
1282
|
+
|
|
1283
|
+
if (popArg !== null && f.funcPointers !== undefined && f.funcPointers[i] !== null) {
|
|
1284
|
+
if (popArg.type === 'func') { // 引数が関数の参照渡しに該当する場合、typeを『func_pointer』に変更
|
|
1285
|
+
popArg.type = 'func_pointer'
|
|
1286
|
+
} else {
|
|
1287
|
+
const varname = (f.varnames) ? f.varnames[i] : `${i + 1}番目の引数`
|
|
1288
|
+
throw NakoSyntaxError.fromNode(
|
|
1289
|
+
`関数『${t.value}』の引数『${varname}』には関数オブジェクトが必要です。`, t)
|
|
1290
|
+
}
|
|
1291
|
+
}
|
|
1292
|
+
args.push(popArg)
|
|
1293
|
+
if (i < f.josi.length - 1 || !f.isVariableJosi) { break }
|
|
1294
|
+
}
|
|
1295
|
+
}
|
|
1296
|
+
// 1つだけなら、変数「それ」で補完される
|
|
1297
|
+
if (nullCount >= 2 && (valueCount > 0 || t.josi === '' || keizokuJosi.indexOf(t.josi) >= 0)) {
|
|
1298
|
+
throw NakoSyntaxError.fromNode(`関数『${t.value}』の引数が不足しています。`, t)
|
|
1299
|
+
}
|
|
1300
|
+
// 関数呼び出しのAstを構築
|
|
1301
|
+
const funcNode: Ast = {
|
|
1302
|
+
type: 'func',
|
|
1303
|
+
name: t.value,
|
|
1304
|
+
args,
|
|
1305
|
+
josi: t.josi,
|
|
1306
|
+
...map,
|
|
1307
|
+
end: this.peekSourceMap()
|
|
1308
|
+
}
|
|
1309
|
+
|
|
1310
|
+
// 「プラグイン名設定」ならば、そこでスコープを変更することを意味する
|
|
1311
|
+
if (funcNode.name === 'プラグイン名設定') {
|
|
1312
|
+
if (args.length > 0 && args[0]) {
|
|
1313
|
+
let fname: string = '' + args[0].value
|
|
1314
|
+
if (fname === 'メイン') { fname = '' + args[0].file }
|
|
1315
|
+
this.modName = NakoLexer.filenameToModName(fname)
|
|
1316
|
+
}
|
|
1317
|
+
}
|
|
1318
|
+
|
|
1319
|
+
// 言い切りならそこで一度切る
|
|
1320
|
+
if (t.josi === '') { return funcNode }
|
|
1321
|
+
|
|
1322
|
+
// 「**して、**」の場合も一度切る
|
|
1323
|
+
if (keizokuJosi.indexOf(t.josi) >= 0) {
|
|
1324
|
+
funcNode.josi = 'して'
|
|
1325
|
+
return funcNode
|
|
1326
|
+
}
|
|
1327
|
+
// 続き
|
|
1328
|
+
funcNode.meta = f
|
|
1329
|
+
this.pushStack(funcNode)
|
|
1330
|
+
return null
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
/** 関数呼び出し演算子 #891
|
|
1334
|
+
* @returns {Ast | null} */
|
|
1335
|
+
yCallOp (): Ast | null {
|
|
1336
|
+
if (!this.check2(['func', '←'])) { return null }
|
|
1337
|
+
const map = this.peekSourceMap()
|
|
1338
|
+
// 関数名を得る
|
|
1339
|
+
const word = this.get()
|
|
1340
|
+
if (word == null) { throw new Error('関数が取得できません。') }
|
|
1341
|
+
try {
|
|
1342
|
+
const op = this.get()
|
|
1343
|
+
if (op == null) { throw new Error('関数呼び出し演算子が取得できません。') }
|
|
1344
|
+
const funcName = word.value
|
|
1345
|
+
// 関数の引数なしをチェック
|
|
1346
|
+
if (!word.meta) { throw new Error('関数本体を取得できません。') }
|
|
1347
|
+
if (!word.meta.josi) { throw new Error('関数の引数情報を取得できません。') }
|
|
1348
|
+
const argCount = word.meta.josi.length
|
|
1349
|
+
if (argCount === 0) {
|
|
1350
|
+
throw NakoSyntaxError.fromNode(`引数がない関数『${funcName}』を関数呼び出し演算子で呼び出すことはできません。`, word)
|
|
1351
|
+
}
|
|
1352
|
+
// 引数を順に取得
|
|
1353
|
+
const curStackPos = this.stack.length
|
|
1354
|
+
while (!this.isEOF()) {
|
|
1355
|
+
const t = this.yGetArg()
|
|
1356
|
+
if (t) {
|
|
1357
|
+
this.pushStack(t)
|
|
1358
|
+
if ((this.stack.length - curStackPos) === argCount) { break }
|
|
1359
|
+
continue
|
|
1360
|
+
}
|
|
1361
|
+
break
|
|
1362
|
+
}
|
|
1363
|
+
// この場合第一引数の省略は認めない
|
|
1364
|
+
const realArgCount = this.stack.length - curStackPos
|
|
1365
|
+
if (realArgCount !== argCount) {
|
|
1366
|
+
throw NakoSyntaxError.fromNode(`関数『${funcName}』呼び出しで引数の数(${realArgCount})が定義(${argCount})と違います。`, word)
|
|
1367
|
+
}
|
|
1368
|
+
// 引数を取り出す
|
|
1369
|
+
const tmpList = this.stack.splice(curStackPos, argCount)
|
|
1370
|
+
// 引数が1つなら助詞は省略が可能。ただし、引数が2つ以上の時、正しく助詞の順序を入れ替える
|
|
1371
|
+
let argList = tmpList
|
|
1372
|
+
if (argCount >= 2) {
|
|
1373
|
+
argList = []
|
|
1374
|
+
const defList = word.meta.josi
|
|
1375
|
+
defList.forEach((josiList, i) => {
|
|
1376
|
+
for (let j = 0; j < tmpList.length; j++) {
|
|
1377
|
+
const t = tmpList[j]
|
|
1378
|
+
if (josiList.indexOf(t.josi) >= 0) {
|
|
1379
|
+
argList[i] = t
|
|
1380
|
+
return
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
const josiStr = josiList.join(',')
|
|
1384
|
+
throw new Error(`助詞『${josiStr}』が見当たりません。`)
|
|
1385
|
+
})
|
|
1386
|
+
}
|
|
1387
|
+
// funcノードを返す
|
|
1388
|
+
return {
|
|
1389
|
+
type: 'func',
|
|
1390
|
+
name: funcName,
|
|
1391
|
+
args: argList,
|
|
1392
|
+
setter: true, // 重要
|
|
1393
|
+
josi: '',
|
|
1394
|
+
...map,
|
|
1395
|
+
end: this.peekSourceMap()
|
|
1396
|
+
}
|
|
1397
|
+
} catch (err: any) {
|
|
1398
|
+
this.logger.debug(`${this.nodeToStr(word, { depth: 0 }, true)}の関数呼び出しで引数(『←』以降)が読み取れません。`, word)
|
|
1399
|
+
throw NakoSyntaxError.fromNode(
|
|
1400
|
+
`${this.nodeToStr(word, { depth: 0 }, false)}の関数呼び出しでエラーがあります。\n${err.message}`, word)
|
|
1401
|
+
}
|
|
1402
|
+
}
|
|
1403
|
+
|
|
1404
|
+
/** @returns {Ast | null} */
|
|
1405
|
+
yLet (): Ast | null {
|
|
1406
|
+
const map = this.peekSourceMap()
|
|
1407
|
+
// 通常の変数
|
|
1408
|
+
if (this.check2(['word', 'eq'])) {
|
|
1409
|
+
const word = this.peek()
|
|
1410
|
+
let threw = false
|
|
1411
|
+
try {
|
|
1412
|
+
if (this.accept(['word', 'eq', this.yCalc]) || this.accept(['word', 'eq', this.ySentence])) {
|
|
1413
|
+
if (this.y[2].type === 'eol') {
|
|
1414
|
+
throw new Error('値が空です。')
|
|
1415
|
+
}
|
|
1416
|
+
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1417
|
+
const nameToken = this.getVarName(this.y[0])
|
|
1418
|
+
const valueToken = this.y[2]
|
|
1419
|
+
return {
|
|
1420
|
+
type: 'let',
|
|
1421
|
+
name: nameToken,
|
|
1422
|
+
value: valueToken,
|
|
1423
|
+
...map,
|
|
1424
|
+
end: this.peekSourceMap()
|
|
1425
|
+
}
|
|
1426
|
+
} else {
|
|
1427
|
+
threw = true
|
|
1428
|
+
this.logger.debug(`${this.nodeToStr(word, { depth: 1 }, true)}への代入文で計算式に書き間違いがあります。`, word)
|
|
1429
|
+
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に書き間違いがあります。`, map)
|
|
1430
|
+
}
|
|
1431
|
+
} catch (err: any) {
|
|
1432
|
+
if (threw) {
|
|
1433
|
+
throw err
|
|
1434
|
+
}
|
|
1435
|
+
this.logger.debug(`${this.nodeToStr(word, { depth: 1 }, true)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, word)
|
|
1436
|
+
throw NakoSyntaxError.fromNode(`${this.nodeToStr(word, { depth: 1 }, false)}への代入文で計算式に以下の書き間違いがあります。\n${err.message}`, map)
|
|
1437
|
+
}
|
|
1438
|
+
}
|
|
1439
|
+
|
|
1440
|
+
// let_array ?
|
|
1441
|
+
if (this.check2(['word', '@'])) {
|
|
1442
|
+
const la = this.yLetArrayAt(map)
|
|
1443
|
+
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1444
|
+
if (la) {
|
|
1445
|
+
la.checkInit = this.flagCheckArrayInit
|
|
1446
|
+
return la
|
|
1447
|
+
}
|
|
1448
|
+
}
|
|
1449
|
+
if (this.check2(['word', '['])) {
|
|
1450
|
+
const lb = this.yLetArrayBracket(map)
|
|
1451
|
+
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1452
|
+
if (lb) {
|
|
1453
|
+
lb.checkInit = this.flagCheckArrayInit
|
|
1454
|
+
return lb
|
|
1455
|
+
}
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
// ローカル変数定義
|
|
1459
|
+
if (this.accept(['word', 'とは'])) {
|
|
1460
|
+
const word = this.getVarName(this.y[0])
|
|
1461
|
+
if (!this.checkTypes(['変数', '定数'])) {
|
|
1462
|
+
throw NakoSyntaxError.fromNode('ローカル変数『' + word.value + '』の定義エラー', word)
|
|
1463
|
+
}
|
|
1464
|
+
|
|
1465
|
+
const vtype = this.getCur() // 変数
|
|
1466
|
+
// 初期値がある?
|
|
1467
|
+
let value = null
|
|
1468
|
+
if (this.check('eq')) {
|
|
1469
|
+
this.get()
|
|
1470
|
+
value = this.yCalc()
|
|
1471
|
+
}
|
|
1472
|
+
if (this.check('comma')) { this.get() } // skip comma (ex) name1=val1, name2=val2
|
|
1473
|
+
return {
|
|
1474
|
+
type: 'def_local_var',
|
|
1475
|
+
name: word,
|
|
1476
|
+
vartype: vtype.type,
|
|
1477
|
+
value,
|
|
1478
|
+
...map,
|
|
1479
|
+
end: this.peekSourceMap()
|
|
1480
|
+
}
|
|
1481
|
+
}
|
|
1482
|
+
// ローカル変数定義(その2)
|
|
1483
|
+
if (this.accept(['変数', 'word', 'eq', this.yCalc])) {
|
|
1484
|
+
const word = this.getVarName(this.y[1])
|
|
1485
|
+
return {
|
|
1486
|
+
type: 'def_local_var',
|
|
1487
|
+
name: word,
|
|
1488
|
+
vartype: '変数',
|
|
1489
|
+
value: this.y[3],
|
|
1490
|
+
...map,
|
|
1491
|
+
end: this.peekSourceMap()
|
|
1492
|
+
}
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
if (this.accept(['定数', 'word', 'eq', this.yCalc])) {
|
|
1496
|
+
const word = this.getVarName(this.y[1])
|
|
1497
|
+
return {
|
|
1498
|
+
type: 'def_local_var',
|
|
1499
|
+
name: word,
|
|
1500
|
+
vartype: '定数',
|
|
1501
|
+
value: this.y[3],
|
|
1502
|
+
...map,
|
|
1503
|
+
end: this.peekSourceMap()
|
|
1504
|
+
}
|
|
1505
|
+
}
|
|
1506
|
+
|
|
1507
|
+
// 複数定数への代入 #563
|
|
1508
|
+
if (this.accept(['定数', this.yJSONArray, 'eq', this.yCalc])) {
|
|
1509
|
+
const names = this.y[1]
|
|
1510
|
+
// check array
|
|
1511
|
+
if (names && names.value instanceof Array) {
|
|
1512
|
+
for (const i in names.value) {
|
|
1513
|
+
if (names.value[i].type !== 'word') {
|
|
1514
|
+
throw NakoSyntaxError.fromNode(`複数定数の代入文${i + 1}番目でエラー。『定数[A,B,C]=[1,2,3]』の書式で記述してください。`, this.y[0])
|
|
1515
|
+
}
|
|
1516
|
+
}
|
|
1517
|
+
} else {
|
|
1518
|
+
throw NakoSyntaxError.fromNode('複数定数の代入文でエラー。『定数[A,B,C]=[1,2,3]』の書式で記述してください。', this.y[0])
|
|
1519
|
+
}
|
|
1520
|
+
names.value = this.getVarNameList(names.value)
|
|
1521
|
+
return {
|
|
1522
|
+
type: 'def_local_varlist',
|
|
1523
|
+
names: names.value,
|
|
1524
|
+
vartype: '定数',
|
|
1525
|
+
value: this.y[3],
|
|
1526
|
+
...map,
|
|
1527
|
+
end: this.peekSourceMap()
|
|
1528
|
+
}
|
|
1529
|
+
}
|
|
1530
|
+
// 複数変数への代入 #563
|
|
1531
|
+
if (this.accept(['変数', this.yJSONArray, 'eq', this.yCalc])) {
|
|
1532
|
+
const names = this.y[1]
|
|
1533
|
+
// check array
|
|
1534
|
+
if (names && names.value instanceof Array) {
|
|
1535
|
+
for (const i in names.value) {
|
|
1536
|
+
if (names.value[i].type !== 'word') {
|
|
1537
|
+
throw NakoSyntaxError.fromNode(`複数変数の代入文${i + 1}番目でエラー。『変数[A,B,C]=[1,2,3]』の書式で記述してください。`, this.y[0])
|
|
1538
|
+
}
|
|
1539
|
+
}
|
|
1540
|
+
} else {
|
|
1541
|
+
throw NakoSyntaxError.fromNode('複数変数の代入文でエラー。『変数[A,B,C]=[1,2,3]』の書式で記述してください。', this.y[0])
|
|
1542
|
+
}
|
|
1543
|
+
names.value = this.getVarNameList(names.value)
|
|
1544
|
+
return {
|
|
1545
|
+
type: 'def_local_varlist',
|
|
1546
|
+
names: names.value,
|
|
1547
|
+
vartype: '変数',
|
|
1548
|
+
value: this.y[3],
|
|
1549
|
+
...map,
|
|
1550
|
+
end: this.peekSourceMap()
|
|
1551
|
+
}
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// 複数変数への代入 #563
|
|
1555
|
+
if (this.check2(['word', 'comma', 'word'])) {
|
|
1556
|
+
// 2 word
|
|
1557
|
+
if (this.accept(['word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1558
|
+
let names = [this.y[0], this.y[2]]
|
|
1559
|
+
names = this.getVarNameList(names)
|
|
1560
|
+
return {
|
|
1561
|
+
type: 'def_local_varlist',
|
|
1562
|
+
names,
|
|
1563
|
+
vartype: '変数',
|
|
1564
|
+
value: this.y[4],
|
|
1565
|
+
...map,
|
|
1566
|
+
end: this.peekSourceMap()
|
|
1567
|
+
}
|
|
1568
|
+
}
|
|
1569
|
+
// 3 word
|
|
1570
|
+
if (this.accept(['word', 'comma', 'word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1571
|
+
let names = [this.y[0], this.y[2], this.y[4]]
|
|
1572
|
+
names = this.getVarNameList(names)
|
|
1573
|
+
return {
|
|
1574
|
+
type: 'def_local_varlist',
|
|
1575
|
+
names,
|
|
1576
|
+
vartype: '変数',
|
|
1577
|
+
value: this.y[6],
|
|
1578
|
+
...map,
|
|
1579
|
+
end: this.peekSourceMap()
|
|
1580
|
+
}
|
|
1581
|
+
}
|
|
1582
|
+
// 4 word
|
|
1583
|
+
if (this.accept(['word', 'comma', 'word', 'comma', 'word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1584
|
+
let names = [this.y[0], this.y[2], this.y[4], this.y[6]]
|
|
1585
|
+
names = this.getVarNameList(names)
|
|
1586
|
+
return {
|
|
1587
|
+
type: 'def_local_varlist',
|
|
1588
|
+
names,
|
|
1589
|
+
vartype: '変数',
|
|
1590
|
+
value: this.y[8],
|
|
1591
|
+
...map,
|
|
1592
|
+
end: this.peekSourceMap()
|
|
1593
|
+
}
|
|
1594
|
+
}
|
|
1595
|
+
// 5 word
|
|
1596
|
+
if (this.accept(['word', 'comma', 'word', 'comma', 'word', 'comma', 'word', 'comma', 'word', 'eq', this.yCalc])) {
|
|
1597
|
+
let names = [this.y[0], this.y[2], this.y[4], this.y[6], this.y[8]]
|
|
1598
|
+
names = this.getVarNameList(names)
|
|
1599
|
+
return {
|
|
1600
|
+
type: 'def_local_varlist',
|
|
1601
|
+
names,
|
|
1602
|
+
vartype: '変数',
|
|
1603
|
+
value: this.y[10],
|
|
1604
|
+
...map,
|
|
1605
|
+
end: this.peekSourceMap()
|
|
1606
|
+
}
|
|
1607
|
+
}
|
|
1608
|
+
}
|
|
1609
|
+
return null
|
|
1610
|
+
}
|
|
1611
|
+
|
|
1612
|
+
/**
|
|
1613
|
+
* 配列のインデックスが1から始まる場合を考慮するか
|
|
1614
|
+
* @param {Ast} node
|
|
1615
|
+
* @returns
|
|
1616
|
+
*/
|
|
1617
|
+
checkArrayIndex (node: Ast): Ast {
|
|
1618
|
+
// 配列が0から始まるのであればそのまま返す
|
|
1619
|
+
if (this.arrayIndexFrom === 0) { return node }
|
|
1620
|
+
// 配列が1から始まるのであれば演算を加えて返す
|
|
1621
|
+
return {
|
|
1622
|
+
...node,
|
|
1623
|
+
'type': 'op',
|
|
1624
|
+
'operator': '-',
|
|
1625
|
+
'left': node,
|
|
1626
|
+
'right': {
|
|
1627
|
+
...node,
|
|
1628
|
+
'type': 'number',
|
|
1629
|
+
'value': this.arrayIndexFrom
|
|
1630
|
+
}
|
|
1631
|
+
}
|
|
1632
|
+
}
|
|
1633
|
+
|
|
1634
|
+
/**
|
|
1635
|
+
* 配列のインデックスを逆順にするのを考慮するか
|
|
1636
|
+
* @param {Ast[]| null} ary
|
|
1637
|
+
*/
|
|
1638
|
+
checkArrayReverse (ary: Ast[] | null): Ast[] {
|
|
1639
|
+
if (!ary) { return [] }
|
|
1640
|
+
if (!this.flagReverseArrayIndex) { return ary }
|
|
1641
|
+
// 二次元以上の配列変数のアクセスを[y][x]ではなく[x][y]と順序を変更する
|
|
1642
|
+
if (ary.length <= 1) { return ary }
|
|
1643
|
+
return ary.reverse()
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
/** @returns {Ast | null} */
|
|
1647
|
+
yLetArrayAt (map: SourceMap): Ast|null {
|
|
1648
|
+
// 一次元配列
|
|
1649
|
+
if (this.accept(['word', '@', this.yValue, 'eq', this.yCalc])) {
|
|
1650
|
+
return {
|
|
1651
|
+
type: 'let_array',
|
|
1652
|
+
name: this.getVarName(this.y[0]),
|
|
1653
|
+
index: [this.checkArrayIndex(this.y[2])],
|
|
1654
|
+
value: this.y[4],
|
|
1655
|
+
...map,
|
|
1656
|
+
end: this.peekSourceMap()
|
|
1657
|
+
}
|
|
1658
|
+
}
|
|
1659
|
+
|
|
1660
|
+
// 二次元配列
|
|
1661
|
+
if (this.accept(['word', '@', this.yValue, '@', this.yValue, 'eq', this.yCalc])) {
|
|
1662
|
+
return {
|
|
1663
|
+
type: 'let_array',
|
|
1664
|
+
name: this.getVarName(this.y[0]),
|
|
1665
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])]),
|
|
1666
|
+
value: this.y[6],
|
|
1667
|
+
...map,
|
|
1668
|
+
end: this.peekSourceMap()
|
|
1669
|
+
}
|
|
1670
|
+
}
|
|
1671
|
+
|
|
1672
|
+
// 三次元配列
|
|
1673
|
+
if (this.accept(['word', '@', this.yValue, '@', this.yValue, '@', this.yValue, 'eq', this.yCalc])) {
|
|
1674
|
+
return {
|
|
1675
|
+
type: 'let_array',
|
|
1676
|
+
name: this.getVarName(this.y[0]),
|
|
1677
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4]), this.checkArrayIndex(this.y[6])]),
|
|
1678
|
+
value: this.y[8],
|
|
1679
|
+
...map,
|
|
1680
|
+
end: this.peekSourceMap()
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
|
|
1684
|
+
// 二次元配列(カンマ指定)
|
|
1685
|
+
if (this.accept(['word', '@', this.yValue, 'comma', this.yValue, 'eq', this.yCalc])) {
|
|
1686
|
+
return {
|
|
1687
|
+
type: 'let_array',
|
|
1688
|
+
name: this.getVarName(this.y[0]),
|
|
1689
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])]),
|
|
1690
|
+
value: this.y[6],
|
|
1691
|
+
...map,
|
|
1692
|
+
end: this.peekSourceMap()
|
|
1693
|
+
}
|
|
1694
|
+
}
|
|
1695
|
+
|
|
1696
|
+
// 三次元配列(カンマ指定)
|
|
1697
|
+
if (this.accept(['word', '@', this.yValue, 'comma', this.yValue, 'comma', this.yValue, 'eq', this.yCalc])) {
|
|
1698
|
+
return {
|
|
1699
|
+
type: 'let_array',
|
|
1700
|
+
name: this.getVarName(this.y[0]),
|
|
1701
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4]), this.checkArrayIndex(this.y[6])]),
|
|
1702
|
+
value: this.y[8],
|
|
1703
|
+
...map,
|
|
1704
|
+
end: this.peekSourceMap()
|
|
1705
|
+
}
|
|
1706
|
+
}
|
|
1707
|
+
return null
|
|
1708
|
+
}
|
|
1709
|
+
|
|
1710
|
+
/** @returns {Ast | null} */
|
|
1711
|
+
yLetArrayBracket (map: SourceMap): Ast|null {
|
|
1712
|
+
// 一次元配列
|
|
1713
|
+
if (this.accept(['word', '[', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1714
|
+
return {
|
|
1715
|
+
type: 'let_array',
|
|
1716
|
+
name: this.getVarName(this.y[0]),
|
|
1717
|
+
index: [this.checkArrayIndex(this.y[2])],
|
|
1718
|
+
value: this.y[5],
|
|
1719
|
+
...map,
|
|
1720
|
+
end: this.peekSourceMap()
|
|
1721
|
+
}
|
|
1722
|
+
}
|
|
1723
|
+
|
|
1724
|
+
// 二次元配列
|
|
1725
|
+
if (this.accept(['word', '[', this.yCalc, ']', '[', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1726
|
+
return {
|
|
1727
|
+
type: 'let_array',
|
|
1728
|
+
name: this.getVarName(this.y[0]),
|
|
1729
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[5])]),
|
|
1730
|
+
value: this.y[8],
|
|
1731
|
+
tag: '2',
|
|
1732
|
+
...map,
|
|
1733
|
+
end: this.peekSourceMap()
|
|
1734
|
+
}
|
|
1735
|
+
}
|
|
1736
|
+
if (this.accept(['word', '[', this.yCalc, 'comma', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1737
|
+
return {
|
|
1738
|
+
type: 'let_array',
|
|
1739
|
+
name: this.getVarName(this.y[0]),
|
|
1740
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4])]),
|
|
1741
|
+
value: this.y[7],
|
|
1742
|
+
tag: '2',
|
|
1743
|
+
...map,
|
|
1744
|
+
end: this.peekSourceMap()
|
|
1745
|
+
}
|
|
1746
|
+
}
|
|
1747
|
+
|
|
1748
|
+
// 三次元配列
|
|
1749
|
+
if (this.accept(['word', '[', this.yCalc, ']', '[', this.yCalc, ']', '[', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1750
|
+
return {
|
|
1751
|
+
type: 'let_array',
|
|
1752
|
+
name: this.getVarName(this.y[0]),
|
|
1753
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[5]), this.checkArrayIndex(this.y[8])]),
|
|
1754
|
+
value: this.y[11],
|
|
1755
|
+
...map,
|
|
1756
|
+
end: this.peekSourceMap()
|
|
1757
|
+
}
|
|
1758
|
+
}
|
|
1759
|
+
if (this.accept(['word', '[', this.yCalc, 'comma', this.yCalc, 'comma', this.yCalc, ']', 'eq', this.yCalc])) {
|
|
1760
|
+
return {
|
|
1761
|
+
type: 'let_array',
|
|
1762
|
+
name: this.getVarName(this.y[0]),
|
|
1763
|
+
index: this.checkArrayReverse([this.checkArrayIndex(this.y[2]), this.checkArrayIndex(this.y[4]), this.checkArrayIndex(this.y[6])]),
|
|
1764
|
+
value: this.y[9],
|
|
1765
|
+
...map,
|
|
1766
|
+
end: this.peekSourceMap()
|
|
1767
|
+
}
|
|
1768
|
+
}
|
|
1769
|
+
return null
|
|
1770
|
+
}
|
|
1771
|
+
|
|
1772
|
+
/** @returns {Ast | null} */
|
|
1773
|
+
yCalc (): Ast|null {
|
|
1774
|
+
const map = this.peekSourceMap()
|
|
1775
|
+
if (this.check('eol')) { return null }
|
|
1776
|
+
// 値を一つ読む
|
|
1777
|
+
const t = this.yGetArg()
|
|
1778
|
+
if (!t) { return null }
|
|
1779
|
+
// 助詞がある? つまり、関数呼び出しがある?
|
|
1780
|
+
if (t.josi === '') { return t } // 値だけの場合
|
|
1781
|
+
// 関数の呼び出しがあるなら、スタックに載せて関数読み出しを呼ぶ
|
|
1782
|
+
this.pushStack(t)
|
|
1783
|
+
const t1 = this.yCall()
|
|
1784
|
+
if (!t1) {
|
|
1785
|
+
return this.popStack()
|
|
1786
|
+
}
|
|
1787
|
+
// それが連文か確認
|
|
1788
|
+
if (t1.josi !== 'して') { return t1 } // 連文ではない
|
|
1789
|
+
// 連文なら右側を読んで左側とくっつける
|
|
1790
|
+
const t2 = this.yCalc()
|
|
1791
|
+
if (!t2) { return t1 }
|
|
1792
|
+
return {
|
|
1793
|
+
type: 'renbun',
|
|
1794
|
+
left: t1,
|
|
1795
|
+
right: t2,
|
|
1796
|
+
josi: t2.josi,
|
|
1797
|
+
...map,
|
|
1798
|
+
end: this.peekSourceMap()
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1801
|
+
|
|
1802
|
+
/** @returns {Ast | null} */
|
|
1803
|
+
yValueKakko (): Ast | null {
|
|
1804
|
+
if (!this.check('(')) { return null }
|
|
1805
|
+
const t = this.get() // skip '('
|
|
1806
|
+
if (!t) { throw new Error('[System Error] check したのに get できない') }
|
|
1807
|
+
this.saveStack()
|
|
1808
|
+
const v = this.yCalc() || this.ySentence()
|
|
1809
|
+
if (v === null) {
|
|
1810
|
+
const v2 = this.get()
|
|
1811
|
+
this.logger.debug('(...)の解析エラー。' + this.nodeToStr(v2, { depth: 1 }, true) + 'の近く', t)
|
|
1812
|
+
throw NakoSyntaxError.fromNode('(...)の解析エラー。' + this.nodeToStr(v2, { depth: 1 }, false) + 'の近く', t)
|
|
1813
|
+
}
|
|
1814
|
+
if (!this.check(')')) {
|
|
1815
|
+
this.logger.debug('(...)の解析エラー。' + this.nodeToStr(v, { depth: 1 }, true) + 'の近く', t)
|
|
1816
|
+
throw NakoSyntaxError.fromNode('(...)の解析エラー。' + this.nodeToStr(v, { depth: 1 }, false) + 'の近く', t)
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
const closeParent = this.get() // skip ')'
|
|
1820
|
+
this.loadStack()
|
|
1821
|
+
if (closeParent) {
|
|
1822
|
+
v.josi = closeParent.josi
|
|
1823
|
+
}
|
|
1824
|
+
return v
|
|
1825
|
+
}
|
|
1826
|
+
|
|
1827
|
+
/** @returns {Ast | null} */
|
|
1828
|
+
yValue (): Ast | null {
|
|
1829
|
+
const map = this.peekSourceMap()
|
|
1830
|
+
|
|
1831
|
+
// カンマなら飛ばす #877
|
|
1832
|
+
if (this.check('comma')) { this.get() }
|
|
1833
|
+
|
|
1834
|
+
// プリミティブな値
|
|
1835
|
+
if (this.checkTypes(['number', 'string'])) { return this.getCur() as Ast }
|
|
1836
|
+
|
|
1837
|
+
// 丸括弧
|
|
1838
|
+
if (this.check('(')) { return this.yValueKakko() }
|
|
1839
|
+
|
|
1840
|
+
// マイナス記号
|
|
1841
|
+
if (this.check2(['-', 'number']) || this.check2(['-', 'word']) || this.check2(['-', 'func'])) {
|
|
1842
|
+
const m = this.get() // skip '-'
|
|
1843
|
+
const v = this.yValue()
|
|
1844
|
+
const josi = (v && v.josi) ? v.josi : ''
|
|
1845
|
+
const line = (m && m.line) ? m.line : 0
|
|
1846
|
+
return {
|
|
1847
|
+
type: 'op',
|
|
1848
|
+
operator: '*',
|
|
1849
|
+
left: { type: 'number', value: -1, line },
|
|
1850
|
+
right: v || [],
|
|
1851
|
+
josi,
|
|
1852
|
+
...map,
|
|
1853
|
+
end: this.peekSourceMap()
|
|
1854
|
+
}
|
|
1855
|
+
}
|
|
1856
|
+
// NOT
|
|
1857
|
+
if (this.check('not')) {
|
|
1858
|
+
this.get() // skip '!'
|
|
1859
|
+
const v = this.yValue()
|
|
1860
|
+
const josi = (v && v.josi) ? v.josi : ''
|
|
1861
|
+
return {
|
|
1862
|
+
type: 'not',
|
|
1863
|
+
value: v,
|
|
1864
|
+
josi,
|
|
1865
|
+
...map,
|
|
1866
|
+
end: this.peekSourceMap()
|
|
1867
|
+
}
|
|
1868
|
+
}
|
|
1869
|
+
// JSON object
|
|
1870
|
+
const a = this.yJSONArray()
|
|
1871
|
+
if (a) { return a }
|
|
1872
|
+
const o = this.yJSONObject()
|
|
1873
|
+
if (o) { return o }
|
|
1874
|
+
// 一語関数
|
|
1875
|
+
const splitType = operatorList.concat(['eol', ')', ']', 'ならば', '回', '間', '反復', '条件分岐'])
|
|
1876
|
+
if (this.check2(['func', splitType])) {
|
|
1877
|
+
const tt = this.get()
|
|
1878
|
+
if (!tt) { throw new Error('[System Error] 正しく値が取れませんでした。') }
|
|
1879
|
+
const f = this.getVarNameRef(tt)
|
|
1880
|
+
return {
|
|
1881
|
+
type: 'func',
|
|
1882
|
+
name: f.value,
|
|
1883
|
+
args: [],
|
|
1884
|
+
josi: f.josi,
|
|
1885
|
+
...map,
|
|
1886
|
+
end: this.peekSourceMap()
|
|
1887
|
+
}
|
|
1888
|
+
}
|
|
1889
|
+
// C風関数呼び出し FUNC(...)
|
|
1890
|
+
if (this.check2([['func', 'word'], '(']) && this.peekDef().josi === '') {
|
|
1891
|
+
const f = this.peek()
|
|
1892
|
+
if (this.accept([['func', 'word'], '(', this.yGetArgParen, ')'])) {
|
|
1893
|
+
return {
|
|
1894
|
+
type: 'func',
|
|
1895
|
+
name: this.getVarNameRef(this.y[0]).value,
|
|
1896
|
+
args: this.y[2],
|
|
1897
|
+
josi: this.y[3].josi,
|
|
1898
|
+
...map,
|
|
1899
|
+
end: this.peekSourceMap()
|
|
1900
|
+
}
|
|
1901
|
+
} else { throw NakoSyntaxError.fromNode('C風関数呼び出しのエラー', f || NewEmptyToken()) }
|
|
1902
|
+
}
|
|
1903
|
+
// 関数呼び出し演算子
|
|
1904
|
+
if (this.check2(['func', '←'])) { return this.yCallOp() }
|
|
1905
|
+
// 無名関数(関数オブジェクト)
|
|
1906
|
+
if (this.check('def_func')) { return this.yMumeiFunc() }
|
|
1907
|
+
// 変数
|
|
1908
|
+
const word = this.yValueWord()
|
|
1909
|
+
if (word) { return word }
|
|
1910
|
+
// その他
|
|
1911
|
+
return null
|
|
1912
|
+
}
|
|
1913
|
+
|
|
1914
|
+
yValueWordGetIndex (ast: Ast): boolean {
|
|
1915
|
+
if (!ast.index) { ast.index = [] }
|
|
1916
|
+
// word @ a, b, c
|
|
1917
|
+
if (this.check('@')) {
|
|
1918
|
+
if (this.accept(['@', this.yValue, 'comma', this.yValue, 'comma', this.yValue])) {
|
|
1919
|
+
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1920
|
+
ast.index.push(this.checkArrayIndex(this.y[3]))
|
|
1921
|
+
ast.index.push(this.checkArrayIndex(this.y[5]))
|
|
1922
|
+
ast.index = this.checkArrayReverse(ast.index)
|
|
1923
|
+
ast.josi = this.y[5].josi
|
|
1924
|
+
return true
|
|
1925
|
+
}
|
|
1926
|
+
if (this.accept(['@', this.yValue, 'comma', this.yValue])) {
|
|
1927
|
+
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1928
|
+
ast.index.push(this.checkArrayIndex(this.y[3]))
|
|
1929
|
+
ast.index = this.checkArrayReverse(ast.index)
|
|
1930
|
+
ast.josi = this.y[3].josi
|
|
1931
|
+
return true
|
|
1932
|
+
}
|
|
1933
|
+
if (this.accept(['@', this.yValue])) {
|
|
1934
|
+
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1935
|
+
ast.josi = this.y[1].josi
|
|
1936
|
+
return true
|
|
1937
|
+
}
|
|
1938
|
+
throw NakoSyntaxError.fromNode('変数の後ろの『@要素』の指定が不正です。', ast)
|
|
1939
|
+
}
|
|
1940
|
+
if (this.check('[')) {
|
|
1941
|
+
if (this.accept(['[', this.yCalc, ']'])) {
|
|
1942
|
+
ast.index.push(this.checkArrayIndex(this.y[1]))
|
|
1943
|
+
ast.josi = this.y[2].josi
|
|
1944
|
+
return true
|
|
1945
|
+
}
|
|
1946
|
+
}
|
|
1947
|
+
if (this.check('[')) {
|
|
1948
|
+
if (this.accept(['[', this.yCalc, 'comma', this.yCalc, ']'])) {
|
|
1949
|
+
const index = [
|
|
1950
|
+
this.checkArrayIndex(this.y[1]),
|
|
1951
|
+
this.checkArrayIndex(this.y[3])
|
|
1952
|
+
]
|
|
1953
|
+
ast.index = this.checkArrayReverse(index)
|
|
1954
|
+
ast.josi = this.y[4].josi
|
|
1955
|
+
return true
|
|
1956
|
+
}
|
|
1957
|
+
}
|
|
1958
|
+
if (this.check('[')) {
|
|
1959
|
+
if (this.accept(['[', this.yCalc, 'comma', this.yCalc, 'comma', this.yCalc, ']'])) {
|
|
1960
|
+
const index = [
|
|
1961
|
+
this.checkArrayIndex(this.y[1]),
|
|
1962
|
+
this.checkArrayIndex(this.y[3]),
|
|
1963
|
+
this.checkArrayIndex(this.y[5])
|
|
1964
|
+
]
|
|
1965
|
+
ast.index = this.checkArrayReverse(index)
|
|
1966
|
+
ast.josi = this.y[6].josi
|
|
1967
|
+
return true
|
|
1968
|
+
}
|
|
1969
|
+
}
|
|
1970
|
+
return false
|
|
1971
|
+
}
|
|
1972
|
+
|
|
1973
|
+
/** @returns {Ast | null} */
|
|
1974
|
+
yValueWord (): Ast|null {
|
|
1975
|
+
const map = this.peekSourceMap()
|
|
1976
|
+
if (this.check('word')) {
|
|
1977
|
+
const t = this.getCur()
|
|
1978
|
+
const word = this.getVarNameRef(t)
|
|
1979
|
+
|
|
1980
|
+
// word[n] || word@n
|
|
1981
|
+
if (word.josi === '' && this.checkTypes(['[', '@'])) {
|
|
1982
|
+
const ast:Ast = {
|
|
1983
|
+
type: '配列参照',
|
|
1984
|
+
name: word,
|
|
1985
|
+
index: [],
|
|
1986
|
+
josi: '',
|
|
1987
|
+
...map,
|
|
1988
|
+
end: this.peekSourceMap()
|
|
1989
|
+
}
|
|
1990
|
+
while (!this.isEOF()) {
|
|
1991
|
+
if (!this.yValueWordGetIndex(ast)) { break }
|
|
1992
|
+
}
|
|
1993
|
+
if (ast.index && ast.index.length === 0) { throw NakoSyntaxError.fromNode(`配列『${word.value}』アクセスで指定ミス`, word) }
|
|
1994
|
+
return ast
|
|
1995
|
+
}
|
|
1996
|
+
return word as Ast
|
|
1997
|
+
}
|
|
1998
|
+
return null
|
|
1999
|
+
}
|
|
2000
|
+
|
|
2001
|
+
/** 変数名を検索して解決する
|
|
2002
|
+
* @param {Ast|Token} word
|
|
2003
|
+
* @return {Ast|Token}
|
|
2004
|
+
*/
|
|
2005
|
+
getVarName (word: Token|Ast): Token|Ast {
|
|
2006
|
+
// check word name
|
|
2007
|
+
const f = this.findVar(word.value)
|
|
2008
|
+
if (!f) { // 変数が見つからない
|
|
2009
|
+
if (this.funcLevel === 0) { // global
|
|
2010
|
+
let gname = word.value
|
|
2011
|
+
if (gname.indexOf('__') < 0) { gname = this.modName + '__' + word.value }
|
|
2012
|
+
this.funclist[gname] = { type: 'var', value: '' }
|
|
2013
|
+
word.value = gname
|
|
2014
|
+
} else { // local
|
|
2015
|
+
this.localvars[word.value] = { type: 'var', value: '' }
|
|
2016
|
+
}
|
|
2017
|
+
} else if (f && f.scope === 'global') {
|
|
2018
|
+
word.value = f.name
|
|
2019
|
+
}
|
|
2020
|
+
return word
|
|
2021
|
+
}
|
|
2022
|
+
|
|
2023
|
+
/** 変数名を検索して解決する */
|
|
2024
|
+
getVarNameRef (word: Token): Token {
|
|
2025
|
+
// check word name
|
|
2026
|
+
const f = this.findVar(word.value)
|
|
2027
|
+
if (!f) { // 変数が見つからない
|
|
2028
|
+
if (this.funcLevel === 0 && word.value.indexOf('__') < 0) {
|
|
2029
|
+
word.value = this.modName + '__' + word.value
|
|
2030
|
+
}
|
|
2031
|
+
} else if (f && f.scope === 'global') {
|
|
2032
|
+
word.value = f.name
|
|
2033
|
+
}
|
|
2034
|
+
return word
|
|
2035
|
+
}
|
|
2036
|
+
|
|
2037
|
+
/** 複数の変数名を検索して解決する */
|
|
2038
|
+
getVarNameList (words: Token[]): Token[] {
|
|
2039
|
+
for (let i = 0; i < words.length; i++) {
|
|
2040
|
+
words[i] = this.getVarName(words[i]) as Token
|
|
2041
|
+
}
|
|
2042
|
+
return words
|
|
2043
|
+
}
|
|
2044
|
+
|
|
2045
|
+
yJSONObjectValue (): {key: Ast, value: Ast}[] |null {
|
|
2046
|
+
const a: {key: Ast, value: Ast}[] = []
|
|
2047
|
+
const firstToken = this.peek()
|
|
2048
|
+
if (!firstToken) { return null }
|
|
2049
|
+
while (!this.isEOF()) {
|
|
2050
|
+
while (this.check('eol')) { this.get() }
|
|
2051
|
+
if (this.check('}')) { break }
|
|
2052
|
+
if (this.accept(['word', ':', this.yCalc])) {
|
|
2053
|
+
this.y[0].type = 'string' // キー名の文字列記号省略の場合
|
|
2054
|
+
a.push({
|
|
2055
|
+
key: this.y[0],
|
|
2056
|
+
value: this.y[2]
|
|
2057
|
+
})
|
|
2058
|
+
} else if (this.accept(['string', ':', this.yCalc])) {
|
|
2059
|
+
a.push({
|
|
2060
|
+
key: this.y[0],
|
|
2061
|
+
value: this.y[2]
|
|
2062
|
+
})
|
|
2063
|
+
} else if (this.check('word')) {
|
|
2064
|
+
const w = this.getCur()
|
|
2065
|
+
w.type = 'string'
|
|
2066
|
+
a.push({
|
|
2067
|
+
key: w as Ast,
|
|
2068
|
+
value: w as Ast
|
|
2069
|
+
})
|
|
2070
|
+
} else if (this.checkTypes(['string', 'number'])) {
|
|
2071
|
+
const w = this.getCur()
|
|
2072
|
+
a.push({
|
|
2073
|
+
key: w as Ast,
|
|
2074
|
+
value: w as Ast
|
|
2075
|
+
})
|
|
2076
|
+
} else { throw NakoSyntaxError.fromNode('辞書オブジェクトの宣言で末尾の『}』がありません。', firstToken) }
|
|
2077
|
+
|
|
2078
|
+
if (this.check('comma')) { this.get() }
|
|
2079
|
+
}
|
|
2080
|
+
return a
|
|
2081
|
+
}
|
|
2082
|
+
|
|
2083
|
+
/** @returns {Ast | null} */
|
|
2084
|
+
yJSONObject (): Ast | null {
|
|
2085
|
+
const map = this.peekSourceMap()
|
|
2086
|
+
if (this.accept(['{', '}'])) {
|
|
2087
|
+
return {
|
|
2088
|
+
type: 'json_obj',
|
|
2089
|
+
value: [],
|
|
2090
|
+
josi: this.y[1].josi,
|
|
2091
|
+
...map,
|
|
2092
|
+
end: this.peekSourceMap()
|
|
2093
|
+
}
|
|
2094
|
+
}
|
|
2095
|
+
|
|
2096
|
+
if (this.accept(['{', this.yJSONObjectValue, '}'])) {
|
|
2097
|
+
return {
|
|
2098
|
+
type: 'json_obj',
|
|
2099
|
+
value: this.y[1],
|
|
2100
|
+
josi: this.y[2].josi,
|
|
2101
|
+
...map,
|
|
2102
|
+
end: this.peekSourceMap()
|
|
2103
|
+
}
|
|
2104
|
+
}
|
|
2105
|
+
|
|
2106
|
+
// 辞書初期化に終わりがなかった場合 (エラーチェックのため) #958
|
|
2107
|
+
if (this.accept(['{', this.yJSONObjectValue])) {
|
|
2108
|
+
throw NakoSyntaxError.fromNode(
|
|
2109
|
+
'辞書型変数の初期化が『}』で閉じられていません。',
|
|
2110
|
+
this.y[1])
|
|
2111
|
+
}
|
|
2112
|
+
|
|
2113
|
+
return null
|
|
2114
|
+
}
|
|
2115
|
+
|
|
2116
|
+
yJSONArrayValue (): Ast[] | null {
|
|
2117
|
+
if (this.check('eol')) { this.get() }
|
|
2118
|
+
const v1 = this.yCalc()
|
|
2119
|
+
if (v1 === null) { return null }
|
|
2120
|
+
if (this.check('comma')) { this.get() }
|
|
2121
|
+
const a: Ast[] = [v1]
|
|
2122
|
+
while (!this.isEOF()) {
|
|
2123
|
+
if (this.check('eol')) { this.get() }
|
|
2124
|
+
if (this.check(']')) { break }
|
|
2125
|
+
const v2 = this.yCalc()
|
|
2126
|
+
if (v2 === null) { break }
|
|
2127
|
+
if (this.check('comma')) { this.get() }
|
|
2128
|
+
a.push(v2)
|
|
2129
|
+
}
|
|
2130
|
+
return a
|
|
2131
|
+
}
|
|
2132
|
+
|
|
2133
|
+
/** @returns {Ast | null} */
|
|
2134
|
+
yJSONArray (): Ast | null {
|
|
2135
|
+
const map = this.peekSourceMap()
|
|
2136
|
+
if (this.accept(['[', ']'])) {
|
|
2137
|
+
return {
|
|
2138
|
+
type: 'json_array',
|
|
2139
|
+
value: [],
|
|
2140
|
+
josi: this.y[1].josi,
|
|
2141
|
+
...map,
|
|
2142
|
+
end: this.peekSourceMap()
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
|
|
2146
|
+
if (this.accept(['[', this.yJSONArrayValue, ']'])) {
|
|
2147
|
+
return {
|
|
2148
|
+
type: 'json_array',
|
|
2149
|
+
value: this.y[1],
|
|
2150
|
+
josi: this.y[2].josi,
|
|
2151
|
+
...map,
|
|
2152
|
+
end: this.peekSourceMap()
|
|
2153
|
+
}
|
|
2154
|
+
}
|
|
2155
|
+
// 配列に終わりがなかった場合 (エラーチェックのため) #958
|
|
2156
|
+
if (this.accept(['[', this.yJSONArrayValue])) {
|
|
2157
|
+
throw NakoSyntaxError.fromNode(
|
|
2158
|
+
'配列変数の初期化が『]』で閉じられていません。',
|
|
2159
|
+
this.y[1])
|
|
2160
|
+
}
|
|
2161
|
+
|
|
2162
|
+
return null
|
|
2163
|
+
}
|
|
2164
|
+
|
|
2165
|
+
/** エラー監視構文 */
|
|
2166
|
+
yTryExcept (): Ast | null {
|
|
2167
|
+
const map = this.peekSourceMap()
|
|
2168
|
+
if (!this.check('エラー監視')) { return null }
|
|
2169
|
+
const kansi = this.getCur() // skip エラー監視
|
|
2170
|
+
const block = this.yBlock()
|
|
2171
|
+
if (!this.check2(['エラー', 'ならば'])) {
|
|
2172
|
+
throw NakoSyntaxError.fromNode(
|
|
2173
|
+
'エラー構文で『エラーならば』がありません。' +
|
|
2174
|
+
'『エラー監視..エラーならば..ここまで』を対で記述します。',
|
|
2175
|
+
kansi)
|
|
2176
|
+
}
|
|
2177
|
+
|
|
2178
|
+
this.get() // skip エラー
|
|
2179
|
+
this.get() // skip ならば
|
|
2180
|
+
const errBlock = this.yBlock()
|
|
2181
|
+
if (this.check('ここまで')) {
|
|
2182
|
+
this.get()
|
|
2183
|
+
} else {
|
|
2184
|
+
throw NakoSyntaxError.fromNode('『ここまで』がありません。『エラー監視』...『エラーならば』...『ここまで』を対応させてください。', map)
|
|
2185
|
+
}
|
|
2186
|
+
return {
|
|
2187
|
+
type: 'try_except',
|
|
2188
|
+
block,
|
|
2189
|
+
errBlock: errBlock || [],
|
|
2190
|
+
josi: '',
|
|
2191
|
+
...map,
|
|
2192
|
+
end: this.peekSourceMap()
|
|
2193
|
+
}
|
|
2194
|
+
}
|
|
2195
|
+
}
|