nadesiko3
Version:
Japanese Programming Language
255 lines (254 loc) • 11.1 kB
JavaScript
// wnako3mod - nadesiko for web browser
// - wnako3 から wnako3mod を取り込む。
// - このファイルはモジュールとして別のファイルから取り込み可能。
import { NakoCompiler } from '../core/src/nako3.mjs';
import { NakoImportError } from '../core/src/nako_errors.mjs';
import { setupEditor } from './wnako3_editor.mjs';
import nakoVersion from './nako_version.mjs';
import PluginBrowser from './plugin_browser.mjs';
const NAKO_SCRIPT_RE = /^(なでしこ|nako|nadesiko)3?$/;
export class WebNakoCompiler extends NakoCompiler {
constructor() {
super({ useBasicPlugin: true });
this.version = nakoVersion.version;
this.wnakoVersion = nakoVersion;
this.localFiles = {};
// プラグインを追加
this.addPluginObject('PluginBrowser', PluginBrowser);
// 必要な定数を設定
this.addListener('beforeRun', (g) => {
// バージョン情報を設定
g.__varslist[0].set('ナデシコ種類', 'wnako3');
g.__varslist[0].set('ナデシコバージョン', nakoVersion.version);
g.__varslist[0].set('WNAKOバージョン', nakoVersion.version);
});
}
/**
* ブラウザでtype="なでしこ"というスクリプトを得て実行する
*/
async runNakoScript() {
// スクリプトタグの中身を得る
let nakoScriptCount = 0;
const scripts = document.querySelectorAll('script');
for (let i = 0; i < scripts.length; i++) {
const script = scripts[i];
if (script.type.match(NAKO_SCRIPT_RE)) {
nakoScriptCount++;
// URLからスクリプト名を見つける
const url = (typeof (window.location) === 'object') ? window.location.href : 'url_unknown';
const fname = `${url}#script${nakoScriptCount}.nako3`;
const code = script.text;
// 依存するライブラリをロード
await this.loadDependencies(code, fname);
// プログラムを実行
await this.runAsync(script.text, fname);
}
}
if (nakoScriptCount > 1) {
console.log('実行したなでしこの個数=', nakoScriptCount);
}
}
/** 取り込む文を実行する */
async loadDependencies(code, filename, preCode = '', localFiles = {}) {
this.localFiles = localFiles || {};
return this._loadDependencies(code, filename, preCode, this.getLoaderTool());
}
/**
* type=なでしこ のスクリプトを自動実行するべきかどうかを返す
* @returns type=なでしこ のスクリプトを自動実行するべきかどうか
*/
checkScriptTagParam() {
const scripts = document.querySelectorAll('script');
for (let i = 0; i < scripts.length; i++) {
const script = scripts[i];
const src = script.src || '';
if (src.indexOf('wnako3.js?run') >= 0 ||
src.indexOf('wnako3.js&run') >= 0) {
return true;
}
}
return false;
}
/**
* 指定したidのHTML要素をなでしこ言語のエディタにする。
* @param {string | Element} idOrElement HTML要素
* @see {setupEditor}
*/
setupEditor(idOrElement) {
return setupEditor(idOrElement, this, window.ace);
}
/** なでしこ3の『取り込む』命令のための読み込みツール */
getLoaderTool() {
const tool = { readJs: this.readJs, readNako3: this.readNako3, resolvePath: this.resolvePath };
return tool;
}
/** JSプラグインの読み込み */
readJs(filePath, token) {
if (this.localFiles && this.localFiles[filePath]) {
return {
task: (async () => () => {
Function(this.localFiles[filePath])();
return {};
})()
};
}
return {
task: (async () => {
// もし動的インポートに対応していれば動的インポートを試す
const basename = ('/' + filePath).split('/').pop() || '?';
/*
try {
const obj = await import(filePath)
this.addPluginObject(basename, obj)
return {}
} catch (e) { }
*/
// WebPackの関係で動的インポートが失敗するので、テキストとしてJSソースを取り出して処理する
// JavaScriptをテキストとして取得
const res = await fetch(filePath);
if (!res.ok) {
throw new NakoImportError(`ファイル『${filePath}』のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line);
}
let jstext = await res.text();
// ESModuleっぽければ `default export`を置き換える
const r = Math.floor(Math.random() * 1000000);
const exportName = `exportObject${r}`;
if (jstext.includes('default export')) {
jstext = jstext.replace('default export', `;let ${exportName} = `);
jstext += `\n;navigator.nako3.addPluginObject('${basename}', ${exportName});\n`;
}
// addPluginObjectが存在する
if (!jstext.includes('navigator.nako3.addPluginObject')) {
throw new NakoImportError(`ファイル ${filePath} の中に文字列 "navigator.nako3.addPluginObject" が存在しません。現在、ブラウザ版のなでしこ言語v3は自動登録するプラグインのみをサポートしています。`, token.file, token.line);
}
const gNavigator = navigator;
const gSelf = gNavigator.nako3;
// textの例: `navigator.nako3.addPluginObject('PluginRequireTest', { requiretest: { type: 'var', value: 100 } })`
return () => {
// プラグインの自動登録は navigator.nako3 を参照するため、 navigator.nako3 を一時的に現在のインスタンスにする。
const globalNavigator = gNavigator;
const globalNako3 = globalNavigator.nako3;
globalNavigator.nako3 = gSelf;
try {
const f = Function(jstext);
f.apply(gSelf.__globalObj);
}
catch (err) {
throw new NakoImportError(`プラグイン ${filePath} の取り込みに失敗: ${err instanceof Error ? err.message : err + ''}`, token.file, token.line);
}
finally {
globalNavigator.nako3 = globalNako3;
}
return {};
};
})()
};
}
/** NAKO3プラグインの読み込み */
readNako3(filePath, token) {
if (this.localFiles && this.localFiles[filePath]) { // ローカルファイルを使う場合
return {
task: (() => {
return (new Promise((resolve, reject) => {
const s = this.localFiles[filePath];
resolve(s);
}));
})()
};
}
return {
task: (async () => {
const res = await fetch(filePath);
if (!res.ok) {
throw new NakoImportError(`ファイル ${filePath} のダウンロードに失敗しました: ${res.status} ${res.statusText}`, token.file, token.line);
}
return await res.text();
})()
};
}
/** 読み込みでパスを解決する */
resolvePath(name, token, fromFile) {
let pathname = name;
// http から始まっていれば解決は不要
if (pathname.match(/^https?:\/\//)) { // フルパスなら解決不要
}
else if (this.localFiles && this.localFiles[name]) {
pathname = this.localFiles[name];
}
else {
try {
pathname = new URL(name).pathname;
}
catch (e) {
// 単純にパスに変換できなければ、location.hrefを参考にパスを組み立てる
try {
let baseDir = dirname(fromFile);
if (baseDir === '') {
// https://2/3/4.html
const a = window.location.href.split('/');
baseDir = '/' + a.slice(3, a.length - 1).join('/');
}
pathname = resolveURL(baseDir, name);
}
catch (e) {
throw new NakoImportError(`取り込み文の引数でパスが解決できません。https:// か http:// で始まるアドレスを指定してください。\n${e}`, token.file, token.line);
}
}
}
// 拡張子によってプラグインタイプを分岐する
const checkExt = (name, extlist) => {
for (const ext of extlist) {
if (name.endsWith(ext)) {
return true;
}
if (name.endsWith(ext + '.txt')) {
return true;
} // '.mjs.txt' にもマッチ
}
return false;
};
// URLパラメータを考慮する
const aname = (pathname + '?').split('?')[0];
// JSプラグイン → (.js|.mjs)
// NAKO3プラグイン → (.nako3|.nako)
if (checkExt(aname, ['.js', '.mjs'])) {
return { filePath: pathname, type: 'js' };
}
if (checkExt(aname, ['.nako3', '.nako'])) {
return { filePath: pathname, type: 'nako3' };
}
if (checkExt(pathname, ['.js', '.mjs'])) {
return { filePath: pathname, type: 'js' };
}
if (checkExt(pathname, ['.nako3', '.nako'])) {
return { filePath: pathname, type: 'nako3' };
}
// ファイル拡張子が未指定の場合
throw new NakoImportError(`ファイル『${name}』は拡張子が(.nako3|.js|.js.txt|.mjs|.mjs.txt)以外なので取り込めません。`, token.file, token.line);
}
}
function dirname(s) {
const a = s.split('/');
if (a && a.length > 1) {
return a.slice(0, a.length - 1).join('/');
}
return '';
}
function resolveURL(base, s) {
const baseA = base.split('/');
const sA = s.split('/');
for (const p of sA) {
if (p === '') {
continue;
}
if (p === '.') {
continue;
}
if (p === '..') {
baseA.pop();
continue;
}
baseA.push(p);
}
return baseA.join('/');
}