nadesiko3 3.3.2 → 3.3.3

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/src/cnako3mod.mjs CHANGED
@@ -89,7 +89,7 @@ export class CNako3 extends NakoCompiler {
89
89
  source: app.eval || '',
90
90
  man: app.man || '',
91
91
  one_liner: app.eval || false,
92
- debug: this.debug,
92
+ debug: this.debug || false,
93
93
  trace: app.trace,
94
94
  warn: app.warn,
95
95
  repl: app.repl || false,
@@ -124,7 +124,9 @@ export class CNako3 extends NakoCompiler {
124
124
 
125
125
  // 実行する
126
126
  async execCommand () {
127
+ // コマンドを解析
127
128
  const opt = this.checkArguments()
129
+ // 使い方の表示か?
128
130
  if (opt.man) {
129
131
  this.cnakoMan(opt.man)
130
132
  return
@@ -155,10 +157,11 @@ export class CNako3 extends NakoCompiler {
155
157
  }
156
158
  try {
157
159
  if (opt.test) {
158
- this.loadDependencies(src, opt.mainfile, '')
160
+ await this.loadDependencies(src, opt.mainfile, '')
159
161
  this.test(src, opt.mainfile)
160
162
  } else {
161
- this.run(src, opt.mainfile)
163
+ // run はコンパイルと実行を行うメソッド
164
+ await this.run(src, opt.mainfile)
162
165
  }
163
166
  if (opt.test && this.numFailures > 0) {
164
167
  process.exit(1)
@@ -308,9 +311,15 @@ export class CNako3 extends NakoCompiler {
308
311
  const log = []
309
312
  const tools = {
310
313
  resolvePath: (name, token) => {
314
+ // JSプラグインのパスを解決する
311
315
  if (/\.(js|mjs)(\.txt)?$/.test(name) || /^[^.]*$/.test(name)) {
312
- return { filePath: path.resolve(CNako3.findPluginFile(name, this.filename, __dirname, log)), type: 'js' }
316
+ const jspath = CNako3.findJSPluginFile(name, this.filename, __dirname, log)
317
+ if (jspath === '') {
318
+ throw new NakoImportError(`ファイル『${name}』が見つかりません。以下のパスを検索しました。\n${log.join('\n')}`, token.file, token.line)
319
+ }
320
+ return { filePath: jspath, type: 'js' }
313
321
  }
322
+ // なでしこプラグインのパスを解決する
314
323
  if (/\.nako3?(\.txt)?$/.test(name)) {
315
324
  if (path.isAbsolute(name)) {
316
325
  return { filePath: path.resolve(name), type: 'nako3' }
@@ -326,12 +335,17 @@ export class CNako3 extends NakoCompiler {
326
335
  },
327
336
  readNako3: (name, token) => {
328
337
  if (!fs.existsSync(name)) {
329
- throw new NakoImportError(`ファイル ${name} が存在しません。`, token.line, token.file)
338
+ throw new NakoImportError(`ファイル ${name} が存在しません。`, token.file, token.line)
330
339
  }
331
340
  return { sync: true, value: fs.readFileSync(name).toString() }
332
341
  },
333
342
  readJs: (filePath, token) => {
334
343
  const content = {sync: false, value: null}
344
+ if (process.platform === 'win32') {
345
+ if (filePath.substring(1, 3) === ':\\') {
346
+ filePath = 'file://' + filePath
347
+ }
348
+ }
335
349
  content.value = (
336
350
  new Promise((resolve, reject) => {
337
351
  import(filePath).then((mod) => {
@@ -339,7 +353,7 @@ export class CNako3 extends NakoCompiler {
339
353
  const obj = Object.assign({}, mod)
340
354
  resolve(() => { return obj.default })
341
355
  }).catch((err) => {
342
- const err2 = new NakoImportError(`ファイル ${filePath} が読み込めません。${err}`, token.line, token.file)
356
+ const err2 = new NakoImportError(`ファイル『${filePath}』が読み込めません。${err}`, token.file, token.line)
343
357
  reject(err2)
344
358
  })
345
359
  })
@@ -356,84 +370,95 @@ export class CNako3 extends NakoCompiler {
356
370
  * @param {string} [preCode]
357
371
  */
358
372
  async run (code, fname, preCode = '') {
359
- await this.loadDependencies(code, fname, preCode)
373
+ // 取り込む文の処理
374
+ await this.loadDependencies(code, fname, preCode).catch((err) => {
375
+ this.logger.error(err)
376
+ })
377
+ // 実行
360
378
  return this._runEx(code, fname, {}, preCode)
361
379
  }
362
380
 
363
381
  /**
364
382
  * プラグインファイルの検索を行う
365
- * @param {string} pname
366
- * @param {string} filename
383
+ * @param {string} pname プラグインの名前
384
+ * @param {string} filename 取り込み元ファイル名
367
385
  * @param {string} srcDir このファイルが存在するディレクトリ
368
386
  * @param {string[]} [log]
369
- * @return {string} フルパス
387
+ * @return {string} フルパス、失敗した時は、''を返す
370
388
  */
371
- static findPluginFile (pname, filename, srcDir, log = []) {
389
+ static findJSPluginFile (pname, filename, srcDir, log = []) {
372
390
  log.length = 0
391
+ const cachePath = {}
373
392
  /** @type {string[]} */
374
- // フルパス指定か?
375
- const p1 = pname.substr(0, 1)
376
- if (p1 === '/') {
377
- // フルパス指定なので何もしない
378
- return pname
379
- }
380
- // 各パスを調べる
381
- const exists = (f, desc) => {
382
- const result = fs.existsSync(f)
393
+ const exists = (f, _desc) => {
394
+ // 同じパスを何度も検索することがないように
395
+ if (cachePath[f]) { return false }
396
+ cachePath[f] = true
383
397
  log.push(f)
384
- // console.log(result, 'exists[', desc, '] =', f)
385
- return result
398
+ const stat = fs.statSync(f, {throwIfNoEntry: false})
399
+ if (!stat) { return false }
400
+ return stat.isFile()
386
401
  }
402
+ // 普通にファイルをチェック
387
403
  const fCheck = (pathTest) => {
388
- // 素直にチェック
404
+ // 素直に指定されたパスをチェック
389
405
  let fpath = path.join(pathTest, pname)
390
406
  if (exists(fpath, 'direct')) { return fpath }
391
-
392
- // プラグイン名を分解してチェック
393
- const m = pname.match(/^(plugin_|nadesiko3-)([a-zA-Z0-9_-]+)/)
394
- if (!m) { return false }
395
- const name = m[2]
396
- // plugin_xxx.js
397
- // eslint-disable-next-line camelcase
398
- const plugin_xxx_js = 'plugin_' + name + '.js'
399
- fpath = path.join(pathTest, plugin_xxx_js)
400
- if (exists(fpath, 'plugin_xxx.js')) { return fpath }
401
- fpath = path.join(pathTest, 'src', plugin_xxx_js)
402
- if (exists(fpath, 'src/plugin_xxx.mjs')) { return fpath }
403
- // nadesiko3-xxx
404
- // eslint-disable-next-line camelcase
405
- const nadesiko3_xxx = 'nadesiko3-' + name
406
- fpath = path.join(pathTest, nadesiko3_xxx)
407
- if (exists(fpath, 'nadesiko3-xxx')) { return fpath }
408
- fpath = path.join(pathTest, 'node_modules', nadesiko3_xxx)
409
- if (exists(fpath, 'node_modules/nadesiko3-xxx')) { return fpath }
410
407
  return false
411
408
  }
409
+ // ファイル および node_modules 以下を調べる
410
+ const fCheckEx = (pathTest) => {
411
+ const defPath = fCheck(pathTest)
412
+ if (defPath) { return defPath }
413
+ const fpath = path.join(pathTest, 'node_modules', pname)
414
+ const json = path.join(fpath, 'package.json')
415
+ if (exists(json)) {
416
+ // package.jsonを見つけたので、メインファイルを調べて取り込む (CommonJSモジュール対策)
417
+ const json_txt = fs.readFileSync(json, 'utf-8')
418
+ const obj = JSON.parse(json_txt)
419
+ if (!obj['main']) { return false }
420
+ const mainFile = path.join(pathTest, 'node_modules', pname, obj['main'])
421
+ return mainFile
422
+ }
423
+ return false
424
+ }
425
+ // 各パスを検索していく
426
+ const p1 = pname.substring(0, 1)
427
+ // フルパス指定か?
428
+ if (p1 === '/') {
429
+ if (exists(pname)) { return pname }
430
+ const fileFullpath = fCheckEx(pname)
431
+ if (fileFullpath) { return fileFullpath }
432
+ return '' // フルパスの場合別のフォルダは調べない
433
+ }
412
434
  // 相対パスか?
413
- if (p1 === '.') {
435
+ if (p1 === '.' || pname.indexOf('/') >= 0) {
414
436
  // 相対パス指定なので、なでしこのプログラムからの相対指定を調べる
415
437
  const pathRelative = path.resolve(path.dirname(filename))
416
- const fileRelative = fCheck(pathRelative)
438
+ const fileRelative = fCheckEx(pathRelative)
417
439
  if (fileRelative) { return fileRelative }
440
+ return '' // 相対パスの場合も別のフォルダは調べない
418
441
  }
419
- // nako3スクリプトパスか?
442
+ // plugin_xxx.mjs のようにファイル名のみが指定された場合のみ、いくつかのパスを調べる
443
+ // 母艦パス(元ファイルと同じフォルダ)か?
420
444
  const pathScript = path.resolve(path.dirname(filename))
421
- const fileScript = fCheck(pathScript)
445
+ const fileScript = fCheckEx(pathScript)
422
446
  if (fileScript) { return fileScript }
423
447
 
424
448
  // ランタイムパス/src
425
- const pathRuntimeSrc = path.resolve(srcDir)
449
+ const pathRuntimeSrc = path.resolve(srcDir) // cnako3mod.mjs は ランタイム/src に配置されていることが前提
426
450
  const fileRuntimeSrc = fCheck(pathRuntimeSrc)
427
451
  if (fileRuntimeSrc) { return fileRuntimeSrc }
452
+
428
453
  // ランタイムパス
429
- const pathRuntime = path.dirname(pathRuntimeSrc)
430
- const fileRuntime = fCheck(pathRuntime)
454
+ const pathRuntime = path.resolve(path.dirname(srcDir))
455
+ const fileRuntime = fCheckEx(pathRuntime)
431
456
  if (fileRuntime) { return fileRuntime }
432
457
 
433
458
  // 環境変数 NAKO_HOMEか?
434
459
  if (process.env.NAKO_HOME) {
435
460
  const NAKO_HOME = path.resolve(process.env.NAKO_HOME)
436
- const fileHome = fCheck(NAKO_HOME)
461
+ const fileHome = fCheckEx(NAKO_HOME)
437
462
  if (fileHome) { return fileHome }
438
463
  // NAKO_HOME/src ?
439
464
  const pathNakoHomeSrc = path.join(NAKO_HOME, 'src')
@@ -443,10 +468,10 @@ export class CNako3 extends NakoCompiler {
443
468
  // 環境変数 NODE_PATH (global) 以下にあるか?
444
469
  if (process.env.NODE_PATH) {
445
470
  const pathNode = path.resolve(process.env.NODE_PATH)
446
- const fileNode = fCheck(pathNode)
471
+ const fileNode = fCheckEx(pathNode)
447
472
  if (fileNode) { return fileNode }
448
473
  }
449
- // Nodeのパス検索に任せる
450
- return pname
474
+ // Nodeのパス検索には任せない(importで必ず失敗するので)
475
+ return ''
451
476
  }
452
477
  }
package/src/nako3.mjs CHANGED
@@ -113,7 +113,7 @@ export class NakoCompiler {
113
113
  this.logger = new NakoLogger()
114
114
 
115
115
  // 必要なオブジェクトを覚えておく
116
- this.prepare = new NakoPrepare(this.logger)
116
+ this.prepare = NakoPrepare.getInstance(this.logger)
117
117
  this.parser = new NakoParser(this.logger)
118
118
  this.lexer = new NakoLexer(this.logger)
119
119
 
@@ -201,7 +201,12 @@ export class NakoCompiler {
201
201
  const inner = (code, filename, preCode) => {
202
202
  /** @type {Promise<unknown>[]} */
203
203
  const tasks = []
204
- for (const item of NakoCompiler.listRequireStatements(compiler.rawtokenize(code, 0, filename, preCode)).map((v) => ({ ...v, ...tools.resolvePath(v.value, v.firstToken) }))) {
204
+ // 取り込みが必要な情報一覧を調べる(トークン分割して、取り込みタグを得る)
205
+ const tags = NakoCompiler.listRequireStatements(compiler.rawtokenize(code, 0, filename, preCode))
206
+ // パスを解決する
207
+ const tagsResolvePath = tags.map((v) => ({ ...v, ...tools.resolvePath(v.value, v.firstToken) }))
208
+ // 取り込み開始
209
+ for (const item of tagsResolvePath) {
205
210
  // 2回目以降の読み込み
206
211
  // eslint-disable-next-line no-prototype-builtins
207
212
  if (dependencies.hasOwnProperty(item.filePath)) {
@@ -233,7 +238,7 @@ export class NakoCompiler {
233
238
  // シンタックスハイライトの高速化のために、事前にファイルが定義する関数名のリストを取り出しておく。
234
239
  // preDefineFuncはトークン列に変更を加えるため、事前にクローンしておく。
235
240
  // 「プラグイン名設定」を行う (#956)
236
- code = `「${item.filePath}」にプラグイン名設定;` + code + ';『メイン』にプラグイン名設定;'
241
+ code = `『${item.filePath}』にプラグイン名設定;` + code + ';『メイン』にプラグイン名設定;'
237
242
  const tokens = this.rawtokenize(code, 0, item.filePath)
238
243
  dependencies[item.filePath].tokens = tokens
239
244
  /** @type {import('./nako_lexer').FuncList} */
@@ -250,7 +255,7 @@ export class NakoCompiler {
250
255
  tasks.push(content.value.then((res) => registerFile(res)))
251
256
  }
252
257
  } else {
253
- throw new NakoImportError(`ファイル ${item.value} を読み込めません。未対応の拡張子です。`, item.firstToken.line, item.firstToken.file)
258
+ throw new NakoImportError(`ファイル『${item.value}』を読み込めません。ファイルが存在しないか未対応の拡張子です。`, item.firstToken.file, item.firstToken.line)
254
259
  }
255
260
  }
256
261
 
@@ -264,15 +269,20 @@ export class NakoCompiler {
264
269
 
265
270
  // 非同期な場合のエラーハンドリング
266
271
  if (result !== undefined) {
267
- result.catch((err) => { this.logger.error(err); throw err })
272
+ result.catch((err) => {
273
+ // 読み込みに失敗しても処理は続ける方針なので、失敗しても例外は投げない
274
+ // たぶん、その後の構文解析でエラーになるため
275
+ this.logger.warn(err.msg)
276
+ })
268
277
  }
269
278
 
270
279
  // すべてが終わってからthis.dependenciesに代入する。そうしないと、「実行」ボタンを連打した場合など、
271
280
  // loadDependencies() が並列実行されるときに正しく動作しない。
272
281
  this.dependencies = dependencies
273
282
  return result
274
- } catch (err) { // 同期的な場合のエラーハンドリング
275
- this.logger.error(err)
283
+ } catch (err) {
284
+ // 同期処理では素直に例外を投げる
285
+ this.logger.warn(err.msg)
276
286
  throw err
277
287
  }
278
288
  }
@@ -443,9 +453,7 @@ export class NakoCompiler {
443
453
  }
444
454
  const filePath = Object.keys(this.dependencies).find((key) => this.dependencies[key].alias.has(r.value))
445
455
  if (filePath === undefined) {
446
- //console.log('@@@@=',r.value, this.dependencies)
447
- // TODO: エラーが出るが、無視しても
448
- // throw new NakoLexerError(`ファイル ${r.value} が読み込まれていません。`, r.firstToken.startOffset, r.firstToken.endOffset, r.firstToken.line, r.firstToken.file)
456
+ throw new NakoLexerError(`ファイル ${r.value} が読み込まれていません。`, r.firstToken.startOffset, r.firstToken.endOffset, r.firstToken.line, r.firstToken.file)
449
457
  }
450
458
  this.dependencies[filePath].addPluginFile()
451
459
  const children = cloneAsJSON(this.dependencies[filePath].tokens)
@@ -155,10 +155,10 @@ export class NakoRuntimeError extends NakoError {
155
155
  export class NakoImportError extends NakoError {
156
156
  /**
157
157
  * @param {string} msg
158
- * @param {number} line
159
158
  * @param {string} file
159
+ * @param {number} line
160
160
  */
161
- constructor (msg, line, file) {
161
+ constructor (msg, file, line) {
162
162
  super('取り込みエラー', msg, file, line)
163
163
  this.file = file
164
164
  this.line = line
@@ -2,8 +2,9 @@
2
2
  * DNCLに対応する構文
3
3
  */
4
4
  // import { NakoIndentError } from './nako_errors.mjs'
5
- import { NakoPrepare } from './nako_prepare.mjs'
5
+ import { NakoPrepare, checkNakoMode } from './nako_prepare.mjs'
6
6
 
7
+ // DNCLモードのキーワード
7
8
  const DNCL_KEYWORDS = ['!DNCLモード']
8
9
 
9
10
  /**
@@ -16,7 +17,7 @@ export function convertDNCL(src, filename) {
16
17
  // 改行を合わせる
17
18
  src = src.replace(/(\r\n|\r)/g, '\n')
18
19
  // 「!DNCLモード」を使うかチェック
19
- if (!isIndentSyntaxEnabled(src)) { return src }
20
+ if (!checkNakoMode(src, DNCL_KEYWORDS)) { return src }
20
21
  let result = dncl2nako(src, filename)
21
22
  // console.log("=====\n" + result)
22
23
  // process.exit()
@@ -218,7 +219,7 @@ function dncl2nako(src, filename) {
218
219
  * @returns {string} converted source
219
220
  */
220
221
  function conv2half(src) {
221
- const prepare = new NakoPrepare() // `※`, `//`, `/*` といったパターン全てに対応するために必要
222
+ const prepare = NakoPrepare.getInstance(null) // `※`, `//`, `/*` といったパターン全てに対応するために必要
222
223
  // 全角半角の統一
223
224
  let result = ''
224
225
  let flagStr = false
@@ -1,5 +1,8 @@
1
1
  import { NakoIndentError } from './nako_errors.mjs'
2
- import { NakoPrepare } from './nako_prepare.mjs'
2
+ import { NakoPrepare, checkNakoMode } from './nako_prepare.mjs'
3
+
4
+ // インデント構文のキーワード
5
+ const INDENT_MODE_KEYWORDS = ['!インデント構文', '!ここまでだるい']
3
6
 
4
7
  /**
5
8
  * インデント構文指定があればコードを変換する
@@ -8,34 +11,25 @@ import { NakoPrepare } from './nako_prepare.mjs'
8
11
  * @returns {{ code: string, insertedLines: number[], deletedLines: { lineNumber: number, len: number }[] }}
9
12
  */
10
13
  function convert (code, filename) {
11
- // 最初の30行をチェック
12
- if (isIndentSyntaxEnabled(code)) {
14
+ // インデント構文の適用が必要か?
15
+ if (checkNakoMode(code, INDENT_MODE_KEYWORDS)) {
13
16
  return convertGo(code, filename)
14
17
  }
15
18
  return { code, insertedLines: [], deletedLines: [] }
16
19
  }
17
20
 
18
- // ありえない改行マークを定義
19
- const SpecialRetMark = '🌟🌟改行🌟🌟s4j#WjcSb😀/FcX3🌟🌟'
20
-
21
21
  /**
22
+ * インデント構文指定があるかチェックする
22
23
  * @param {string} code
23
24
  * @returns {boolean}
24
25
  */
25
- function isIndentSyntaxEnabled (code) {
26
- // プログラム冒頭に「!インデント構文」があればインデント構文が有効
27
- const keywords = ['!インデント構文', '!ここまでだるい']
28
- const lines = code.split('\n', 30)
29
- for (const line of lines) {
30
- const sline = line.replace(/^(!|💡)/, '!')
31
- const s9 = sline.substring(0, 8)
32
- if (keywords.indexOf(s9) >= 0) {
33
- return true
34
- }
35
- }
36
- return false
26
+ function isIndentSyntaxEnabled(code) {
27
+ return checkNakoMode(code, INDENT_MODE_KEYWORDS)
37
28
  }
38
29
 
30
+ // ありえない改行マークを定義
31
+ const SpecialRetMark = '🌟🌟改行🌟🌟s4j#WjcSb😀/FcX3🌟🌟'
32
+
39
33
  /**
40
34
  * ソースコードのある1行の中のコメントを全て取り除く。
41
35
  * 事前にreplaceRetMarkによって文字列や範囲コメント内の改行文字が置換されている必要がある。
@@ -43,7 +37,7 @@ function isIndentSyntaxEnabled (code) {
43
37
  * @return {string}
44
38
  */
45
39
  function removeCommentsFromLine (src) {
46
- const prepare = new NakoPrepare() // `※`, `//`, `/*` といったパターン全てに対応するために必要
40
+ const prepare = NakoPrepare.getInstance(null) // `※`, `//`, `/*` といったパターン全てに対応するために必要
47
41
  const len = src.length
48
42
  let result = ''
49
43
  let eos = ''
@@ -211,9 +205,10 @@ function convertGo (code, filename) {
211
205
  // |
212
206
  lastIndent = indent
213
207
  while (indentStack.length > 0) {
208
+ /** @type {number} */
214
209
  const n = indentStack.pop()
215
210
  if (n === indent) {
216
- if (lineTrimed.substr(0, 3) !== '違えば') {
211
+ if (lineTrimed.substring(0, 3) !== '違えば') {
217
212
  insertedLines.push(lines2.length)
218
213
  lines2.push(makeIndent(n) + END)
219
214
  }
@@ -263,6 +258,11 @@ function convertGo (code, filename) {
263
258
  return { code: lines3.join('\n'), insertedLines, deletedLines }
264
259
  }
265
260
 
261
+ /**
262
+ * count分だけ字下げする
263
+ * @param {number} count
264
+ * @returns {string}
265
+ */
266
266
  function makeIndent (count) {
267
267
  let s = ''
268
268
  for (let i = 0; i < count; i++) {
@@ -311,8 +311,12 @@ function countIndent (line) {
311
311
  return cnt
312
312
  }
313
313
 
314
+ /**
315
+ * @param {string} src
316
+ * @returns {string}
317
+ */
314
318
  function replaceRetMark (src) {
315
- const prepare = new NakoPrepare() // `※`, `//`, `/*` といったパターン全てに対応するために必要
319
+ const prepare = NakoPrepare.getInstance(null) // `※`, `//`, `/*` といったパターン全てに対応するために必要
316
320
  const len = src.length
317
321
  let result = ''
318
322
  let eos = ''
@@ -477,5 +481,5 @@ export default {
477
481
  getBlockStructure,
478
482
  getIndent,
479
483
  countIndent,
480
- isIndentSyntaxEnabled
484
+ isIndentSyntaxEnabled,
481
485
  }
@@ -7,7 +7,7 @@
7
7
  /**
8
8
  * 置換後の位置から置換前の位置へマッピングできる文字列
9
9
  */
10
- class Replace {
10
+ export class Replace {
11
11
  /**
12
12
  * @param {string} code
13
13
  */
@@ -65,15 +65,33 @@ class Replace {
65
65
  }
66
66
  }
67
67
 
68
- // 字句解析を行う前に全角文字を半角に揃える
69
- // ただし、文字列部分だけは、そのまま全角で出力するようにする
70
- // for https://github.com/kujirahand/nadesiko3/issues/94
68
+ /** @type {NakoPrepare} */
69
+ let nakoPrepareObj = null
70
+ /**
71
+ * 字句解析を行う前に全角文字を半角に揃える
72
+ * [memo]
73
+ * ただし、文字列部分だけは、そのまま全角で出力するようにする
74
+ * for https://github.com/kujirahand/nadesiko3/issues/94
75
+ */
71
76
  export class NakoPrepare {
77
+
78
+ /**
79
+ * 唯一のインスタンスを返す
80
+ * @param {import("./nako_logger.mjs") | null} logger
81
+ * @returns {NakoPrepare}
82
+ */
83
+ static getInstance(logger) {
84
+ if (nakoPrepareObj === null) {
85
+ nakoPrepareObj = new NakoPrepare(logger)
86
+ }
87
+ return nakoPrepareObj
88
+ }
89
+
72
90
  /**
73
- * @param {import("./nako_logger")} logger
91
+ * @param {import("./nako_logger.mjs") | null} logger
74
92
  */
75
93
  constructor (logger) {
76
- this.logger = logger
94
+ if (logger !== null) { this.logger = logger }
77
95
 
78
96
  // 参考) https://hydrocul.github.io/wiki/blog/2014/1101-hyphen-minus-wave-tilde.html
79
97
  this.HYPHENS = { // ハイフン問題
@@ -294,3 +312,24 @@ export class NakoPrepare {
294
312
  }
295
313
  }
296
314
 
315
+ /**
316
+ * なでしこのソースコードのモード(!インデント構文など)が設定されているか調べる
317
+ * @param {string} code
318
+ * @param {Array<string>} modeNames
319
+ * @returns {boolean}
320
+ */
321
+ export function checkNakoMode(code, modeNames) {
322
+ // 先頭の256文字について調べる
323
+ code = code.substring(0, 256)
324
+ // 全角半角の揺れを吸収
325
+ code = code.replace(/(!|💡)/, '!')
326
+ // 範囲コメントを削除
327
+ code = code.replace(/\/\*.*?\*\//g, '')
328
+ // 毎文調べる
329
+ const lines = code.split(/[;。\n]/, 30)
330
+ for (let line of lines) {
331
+ line = line.replace(/^\s+/, '').replace(/\s+$/, '') // trim
332
+ if (modeNames.indexOf(line) >= 0) return true
333
+ }
334
+ return false
335
+ }
@@ -1,7 +1,7 @@
1
1
  // なでしこバージョン
2
2
  export default {
3
- version: '3.3.2',
3
+ version: '3.3.3',
4
4
  major: 3,
5
5
  minor: 3,
6
- patch: 2
6
+ patch: 3
7
7
  }
@@ -52,4 +52,4 @@ const PluginKeigo = {
52
52
 
53
53
  }
54
54
 
55
- module.exports = PluginKeigo
55
+ export default PluginKeigo
package/src/wnako3.mjs CHANGED
@@ -18,7 +18,7 @@ class WebNakoCompiler extends NakoCompiler {
18
18
  /**
19
19
  * ブラウザでtype="なでしこ"というスクリプトを得て実行する
20
20
  */
21
- runNakoScript () {
21
+ async runNakoScript () {
22
22
  // スクリプトタグの中身を得る
23
23
  let nakoScriptCount = 0
24
24
  const scripts = document.querySelectorAll('script')
@@ -26,7 +26,14 @@ class WebNakoCompiler extends NakoCompiler {
26
26
  const script = scripts[i]
27
27
  if (script.type.match(NAKO_SCRIPT_RE)) {
28
28
  nakoScriptCount++
29
- this.run(script.text, `script${i}.nako3`)
29
+ // URLからスクリプト名を見つける
30
+ const url = (typeof(window.location) == 'object') ? window.location.href : 'url_unknown'
31
+ const fname = `${url}#script${nakoScriptCount}.nako3`
32
+ const code = script.text
33
+ // 依存するライブラリをロード
34
+ await this.loadDependencies(code, fname)
35
+ // プログラムを実行
36
+ this.run(script.text, fname)
30
37
  }
31
38
  }
32
39
  console.log('実行したなでしこの個数=', nakoScriptCount)
@@ -59,11 +66,11 @@ class WebNakoCompiler extends NakoCompiler {
59
66
  value: (async () => {
60
67
  const res = await fetch(filePath)
61
68
  if (!res.ok) {
62
- throw new NakoImportError(`ファイル ${filePath} のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.line, token.file)
69
+ throw new NakoImportError(`ファイル『${filePath}』のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line)
63
70
  }
64
71
  const text = await res.text()
65
72
  if (!text.includes('navigator.nako3.addPluginObject')) {
66
- throw new NakoImportError(`ファイル ${filePath} の中に文字列 "navigator.nako3.addPluginObject" が存在しません。現在、ブラウザ版のなでしこ言語v3は自動登録するプラグインのみをサポートしています。`, token.line, token.file)
73
+ throw new NakoImportError(`ファイル ${filePath} の中に文字列 "navigator.nako3.addPluginObject" が存在しません。現在、ブラウザ版のなでしこ言語v3は自動登録するプラグインのみをサポートしています。`, token.file, token.line)
67
74
  }
68
75
  // textの例: `navigator.nako3.addPluginObject('PluginRequireTest', { requiretest: { type: 'var', value: 100 } })`
69
76
  return () => {
@@ -74,7 +81,7 @@ class WebNakoCompiler extends NakoCompiler {
74
81
  // eslint-disable-next-line no-new-func
75
82
  Function(text)()
76
83
  } catch (err) {
77
- throw new NakoImportError(`プラグイン ${filePath} の取り込みに失敗: ${err instanceof Error ? err.message : err + ''}`, token.line, token.file)
84
+ throw new NakoImportError(`プラグイン ${filePath} の取り込みに失敗: ${err instanceof Error ? err.message : err + ''}`, token.file, token.line)
78
85
  } finally {
79
86
  navigator.nako3 = globalNako3
80
87
  }
@@ -93,7 +100,7 @@ class WebNakoCompiler extends NakoCompiler {
93
100
  value: (async () => {
94
101
  const res = await fetch(filePath)
95
102
  if (!res.ok) {
96
- throw new NakoImportError(`ファイル ${filePath} のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.line, token.file)
103
+ throw new NakoImportError(`ファイル ${filePath} のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line)
97
104
  }
98
105
  return await res.text()
99
106
  })()
@@ -114,17 +121,20 @@ class WebNakoCompiler extends NakoCompiler {
114
121
  const href = href_dir + '/' + name
115
122
  pathname = new URL(href).pathname
116
123
  } catch (e) {
117
- throw new NakoImportError(`取り込み文の引数でパスが解決できません。https:// か http:// で始まるアドレスを指定してください。\n${e}`, token.line, token.file)
124
+ throw new NakoImportError(`取り込み文の引数でパスが解決できません。https:// か http:// で始まるアドレスを指定してください。\n${e}`, token.file, token.line)
118
125
  }
119
126
  }
120
127
  }
121
- if (pathname.endsWith('.js') || pathname.endsWith('.js.txt')) {
128
+ // .js および .mjs なら JSプラグイン
129
+ if (pathname.endsWith('.js') || pathname.endsWith('.js.txt') || pathname.endsWith('.mjs') || pathname.endsWith('.mjs.txt')) {
122
130
  return { filePath: name, type: 'js' }
123
131
  }
132
+ // .nako3 なら なでしこ3プラグイン
124
133
  if (pathname.endsWith('.nako3') || pathname.endsWith('.nako3.txt')) {
125
134
  return { filePath: name, type: 'nako3' }
126
135
  }
127
- return { filePath: name, type: 'invalid' }
136
+ // ファイル拡張子が未指定の場合
137
+ throw new NakoImportError(`ファイル『${name}』は拡張子が(.nako3|.js|.js.txt|.mjs|.mjs.txt)以外なので取り込めません。`, token.file, token.line)
128
138
  }
129
139
  })
130
140
  }
@@ -723,7 +723,7 @@ export class LanguageFeatures {
723
723
  * @param {number} endRow
724
724
  */
725
725
  static toggleCommentLines (state, { doc }, startRow, endRow) {
726
- const prepare = new NakoPrepare(new NakoLogger())
726
+ const prepare = NakoPrepare.getInstance(new NakoLogger())
727
727
  /**
728
728
  * @param {string} line
729
729
  * @returns {{ type: 'blank' | 'code' } | { type: 'comment', start: number, len: number }}
@@ -734,11 +734,11 @@ export class LanguageFeatures {
734
734
  if (indent === line) {
735
735
  return { type: 'blank' }
736
736
  }
737
- line = line.substr(indent.length)
737
+ line = line.substring(indent.length)
738
738
 
739
739
  // 先頭がコメントの開始文字かどうか確認する
740
- const ch2 = line.substr(0, 2).split('').map((c) => prepare.convert1ch(c)).join('')
741
- if (ch2.substr(0, 1) === '#') {
740
+ const ch2 = line.substring(0, 2).split('').map((c) => prepare.convert1ch(c)).join('')
741
+ if (ch2.substring(0, 1) === '#') {
742
742
  return { type: 'comment', start: indent.length, len: 1 + (line.charAt(1) === ' ' ? 1 : 0) }
743
743
  }
744
744
  if (ch2 === '//') {