noshift.js 0.0.1
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 -0
- package/bin/cli.js +10 -0
- package/commands/build.js +45 -0
- package/commands/create.js +170 -0
- package/commands/dev.js +35 -0
- package/package.json +18 -0
- package/src/convert.js +362 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 [otoneko.]
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/bin/cli.js
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import convert from "../src/convert.js";
|
|
4
|
+
|
|
5
|
+
// ✅ ユーザープロジェクトの ./src, ./build を参照
|
|
6
|
+
const srcDir = path.join(process.cwd(), "src");
|
|
7
|
+
const buildDir = path.join(process.cwd(), "build");
|
|
8
|
+
|
|
9
|
+
async function findNsjsFiles(dir) {
|
|
10
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
11
|
+
const files = [];
|
|
12
|
+
|
|
13
|
+
for (const entry of entries) {
|
|
14
|
+
const fullPath = path.join(dir, entry.name);
|
|
15
|
+
|
|
16
|
+
if (entry.isDirectory()) {
|
|
17
|
+
files.push(...(await findNsjsFiles(fullPath)));
|
|
18
|
+
} else if (
|
|
19
|
+
entry.name.endsWith(".nsjs") &&
|
|
20
|
+
!entry.name.startsWith("_")
|
|
21
|
+
) {
|
|
22
|
+
files.push(fullPath);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
return files;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
async function writeToBuild(originalPath, jsCode) {
|
|
30
|
+
const relative = path.relative(srcDir, originalPath);
|
|
31
|
+
const destPath = path.join(buildDir, relative).replace(/\.nsjs$/, ".js");
|
|
32
|
+
|
|
33
|
+
await fs.mkdir(path.dirname(destPath), { recursive: true });
|
|
34
|
+
await fs.writeFile(destPath, jsCode, "utf-8");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const files = await findNsjsFiles(srcDir);
|
|
38
|
+
await fs.mkdir(buildDir, { recursive: true });
|
|
39
|
+
|
|
40
|
+
for (const file of files) {
|
|
41
|
+
const code = await fs.readFile(file, "utf-8");
|
|
42
|
+
const js = convert(code);
|
|
43
|
+
await writeToBuild(file, js);
|
|
44
|
+
console.log(`✅ Converted: ${file}`);
|
|
45
|
+
}
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
import { execSync } from "child_process";
|
|
2
|
+
import fs from "fs/promises";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import { fileURLToPath } from "url";
|
|
5
|
+
import inquirer from "inquirer";
|
|
6
|
+
|
|
7
|
+
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
8
|
+
const DEFAULT_BUILD_DIR = "build";
|
|
9
|
+
|
|
10
|
+
export default async function create(projectNameArg) {
|
|
11
|
+
const cwd = process.cwd();
|
|
12
|
+
|
|
13
|
+
// 🌐 言語選択
|
|
14
|
+
const { lang } = await inquirer.prompt([
|
|
15
|
+
{
|
|
16
|
+
type: "list",
|
|
17
|
+
name: "lang",
|
|
18
|
+
message: "Select language / 言語を選んでください",
|
|
19
|
+
choices: [
|
|
20
|
+
{ name: "English", value: "en" },
|
|
21
|
+
{ name: "日本語", value: "ja" },
|
|
22
|
+
],
|
|
23
|
+
},
|
|
24
|
+
]);
|
|
25
|
+
|
|
26
|
+
const t = (key) => {
|
|
27
|
+
const messages = {
|
|
28
|
+
projectName: {
|
|
29
|
+
en: "Enter project name:",
|
|
30
|
+
ja: "プロジェクト名を入力してください:",
|
|
31
|
+
},
|
|
32
|
+
creatingDir: {
|
|
33
|
+
en: "📁 Creating project directory:",
|
|
34
|
+
ja: "📁 プロジェクトディレクトリを作成:",
|
|
35
|
+
},
|
|
36
|
+
initializingNpm: {
|
|
37
|
+
en: "📦 Initializing npm...",
|
|
38
|
+
ja: "📦 npm 初期化中...",
|
|
39
|
+
},
|
|
40
|
+
creatingConfig: {
|
|
41
|
+
en: "⚙️ Creating nsjs.config.js",
|
|
42
|
+
ja: "⚙️ nsjs.config.js を作成",
|
|
43
|
+
},
|
|
44
|
+
usePrettier: {
|
|
45
|
+
en: "Do you want to format compiled code with Prettier?",
|
|
46
|
+
ja: "コンパイル後のコードを Prettier で整形しますか?",
|
|
47
|
+
},
|
|
48
|
+
installingPrettier: {
|
|
49
|
+
en: "📦 Installing Prettier...",
|
|
50
|
+
ja: "📦 Prettier をインストール中...",
|
|
51
|
+
},
|
|
52
|
+
installingNoshift: {
|
|
53
|
+
en: "📦 Installing noshift.js...",
|
|
54
|
+
ja: "📦 noshift.js をインストール中...",
|
|
55
|
+
},
|
|
56
|
+
success: {
|
|
57
|
+
en: "🎉 Project created successfully!",
|
|
58
|
+
ja: "🎉 プロジェクト作成が完了しました!",
|
|
59
|
+
},
|
|
60
|
+
nextSteps: {
|
|
61
|
+
en: "👉 cd {name} && npm run dev",
|
|
62
|
+
ja: "👉 cd {name} && npm run dev",
|
|
63
|
+
},
|
|
64
|
+
};
|
|
65
|
+
return messages[key][lang];
|
|
66
|
+
};
|
|
67
|
+
|
|
68
|
+
// プロジェクト名取得
|
|
69
|
+
let projectName = projectNameArg;
|
|
70
|
+
if (!projectName) {
|
|
71
|
+
const answer = await inquirer.prompt([
|
|
72
|
+
{
|
|
73
|
+
type: "input",
|
|
74
|
+
name: "projectName",
|
|
75
|
+
message: t("projectName"),
|
|
76
|
+
default: "my-noshift-app",
|
|
77
|
+
},
|
|
78
|
+
]);
|
|
79
|
+
projectName = answer.projectName;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const projectPath = path.join(cwd, projectName);
|
|
83
|
+
await fs.mkdir(projectPath, { recursive: true });
|
|
84
|
+
|
|
85
|
+
console.log(`\n${t("creatingDir")} ${projectPath}`);
|
|
86
|
+
|
|
87
|
+
process.chdir(projectPath);
|
|
88
|
+
|
|
89
|
+
console.log(t("initializingNpm"));
|
|
90
|
+
execSync("npm init -y", { stdio: "inherit" });
|
|
91
|
+
|
|
92
|
+
// nsjs.config.js を作成
|
|
93
|
+
const configContent = `export default {
|
|
94
|
+
build: "", // 空なら ./build にビルドされます
|
|
95
|
+
};
|
|
96
|
+
`;
|
|
97
|
+
await fs.writeFile("nsjs.config.js", configContent);
|
|
98
|
+
console.log(t("creatingConfig"));
|
|
99
|
+
|
|
100
|
+
// Prettier の使用
|
|
101
|
+
const { usePrettier } = await inquirer.prompt([
|
|
102
|
+
{
|
|
103
|
+
type: "confirm",
|
|
104
|
+
name: "usePrettier",
|
|
105
|
+
message: t("usePrettier"),
|
|
106
|
+
default: true,
|
|
107
|
+
},
|
|
108
|
+
]);
|
|
109
|
+
|
|
110
|
+
if (usePrettier) {
|
|
111
|
+
console.log(t("installingPrettier"));
|
|
112
|
+
execSync("npm install --save-dev prettier", { stdio: "inherit" });
|
|
113
|
+
|
|
114
|
+
const prettierIgnore = `build/\nnode_modules/\n`;
|
|
115
|
+
await fs.writeFile(".prettierignore", prettierIgnore);
|
|
116
|
+
|
|
117
|
+
const prettierConfig = `{
|
|
118
|
+
"semi": true,
|
|
119
|
+
"singleQuote": false,
|
|
120
|
+
"trailingComma": "es5"
|
|
121
|
+
}`;
|
|
122
|
+
await fs.writeFile(".prettierrc", prettierConfig);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
console.log(t("installingNoshift"));
|
|
126
|
+
execSync("npm install noshift.js", { stdio: "inherit" });
|
|
127
|
+
|
|
128
|
+
await fs.mkdir("src", { recursive: true });
|
|
129
|
+
await fs.writeFile("src/hello.nsjs", "println(123);");
|
|
130
|
+
|
|
131
|
+
// README.md
|
|
132
|
+
const readme = lang === "ja"
|
|
133
|
+
? `# ${projectName}
|
|
134
|
+
|
|
135
|
+
これは [noshift.js](https://github.com/otoneko1102/NoShift.js) プロジェクトです。
|
|
136
|
+
|
|
137
|
+
## 開発
|
|
138
|
+
|
|
139
|
+
\`\`\`bash
|
|
140
|
+
npm run dev
|
|
141
|
+
\`\`\`
|
|
142
|
+
|
|
143
|
+
## ビルド
|
|
144
|
+
|
|
145
|
+
\`\`\`bash
|
|
146
|
+
npm run build
|
|
147
|
+
\`\`\`
|
|
148
|
+
`
|
|
149
|
+
: `# ${projectName}
|
|
150
|
+
|
|
151
|
+
This is a [NoShift.js](https://github.com/otoneko1102/NoShift.js) project.
|
|
152
|
+
|
|
153
|
+
## Development
|
|
154
|
+
|
|
155
|
+
\`\`\`bash
|
|
156
|
+
npm run dev
|
|
157
|
+
\`\`\`
|
|
158
|
+
|
|
159
|
+
## Build
|
|
160
|
+
|
|
161
|
+
\`\`\`bash
|
|
162
|
+
npm run build
|
|
163
|
+
\`\`\`
|
|
164
|
+
`;
|
|
165
|
+
|
|
166
|
+
await fs.writeFile("README.md", readme);
|
|
167
|
+
|
|
168
|
+
console.log(`\n${t("success")}`);
|
|
169
|
+
console.log("\n" + t("nextSteps").replace("{name}", projectName));
|
|
170
|
+
}
|
package/commands/dev.js
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { promises as fs } from "fs";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import convert from "../src/convert.js";
|
|
4
|
+
|
|
5
|
+
// ✅ ユーザープロジェクトの ./src を参照
|
|
6
|
+
const srcDir = path.join(process.cwd(), "src");
|
|
7
|
+
|
|
8
|
+
async function findNsjsFiles(dir) {
|
|
9
|
+
const entries = await fs.readdir(dir, { withFileTypes: true });
|
|
10
|
+
const files = [];
|
|
11
|
+
|
|
12
|
+
for (const entry of entries) {
|
|
13
|
+
const fullPath = path.join(dir, entry.name);
|
|
14
|
+
|
|
15
|
+
if (entry.isDirectory()) {
|
|
16
|
+
files.push(...(await findNsjsFiles(fullPath)));
|
|
17
|
+
} else if (
|
|
18
|
+
entry.name.endsWith(".nsjs") &&
|
|
19
|
+
!entry.name.startsWith("_")
|
|
20
|
+
) {
|
|
21
|
+
files.push(fullPath);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
return files;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
const files = await findNsjsFiles(srcDir);
|
|
29
|
+
for (const file of files) {
|
|
30
|
+
const code = await fs.readFile(file, "utf-8");
|
|
31
|
+
const js = convert(code);
|
|
32
|
+
console.log(`\n--- ${file} ---\n`);
|
|
33
|
+
console.log(js);
|
|
34
|
+
console.log("\n");
|
|
35
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "noshift.js",
|
|
3
|
+
"version": "0.0.1",
|
|
4
|
+
"description": "Joke language.",
|
|
5
|
+
"bin": {
|
|
6
|
+
"nsjs": "./bin/cli.js"
|
|
7
|
+
},
|
|
8
|
+
"type": "module",
|
|
9
|
+
"scripts": {
|
|
10
|
+
"dev": "node commands/dev.js",
|
|
11
|
+
"build": "node commands/build.js"
|
|
12
|
+
},
|
|
13
|
+
"keywords": [
|
|
14
|
+
"noshift"
|
|
15
|
+
],
|
|
16
|
+
"author": "otoneko.",
|
|
17
|
+
"license": "MIT"
|
|
18
|
+
}
|
package/src/convert.js
ADDED
|
@@ -0,0 +1,362 @@
|
|
|
1
|
+
function convertNsjsToJs(nsjsCode) {
|
|
2
|
+
let jsCode = "";
|
|
3
|
+
let i = 0;
|
|
4
|
+
const len_ns = nsjsCode.length;
|
|
5
|
+
|
|
6
|
+
// ======
|
|
7
|
+
// 各種状態 (State) を定義
|
|
8
|
+
// ======
|
|
9
|
+
const STATE = {
|
|
10
|
+
NORMAL: "NORMAL", // 普通のコード
|
|
11
|
+
IN_DQ_STRING: "IN_DQ_STRING", // " … " の中
|
|
12
|
+
IN_SQ_STRING: "IN_SQ_STRING", // ' … ' の中
|
|
13
|
+
IN_BT_SINGLE_STRING: "IN_BT_SINGLE_STRING", // ` … ` の中
|
|
14
|
+
IN_BT_MULTI_STRING: "IN_BT_MULTI_STRING", // ``` … ``` の中
|
|
15
|
+
IN_TEMPLATE_EXPRESSION: "IN_TEMPLATE_EXPRESSION", // ${ … } の中
|
|
16
|
+
RAW_DQ_IN_EXPR: "RAW_DQ_IN_EXPR", // テンプレート式内の " … " の中 (NoShift 変換なし)
|
|
17
|
+
RAW_SQ_IN_EXPR: "RAW_SQ_IN_EXPR", // テンプレート式内の ' … ' の中 (NoShift 変換なし)
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
let currentState = STATE.NORMAL;
|
|
21
|
+
const stateStack = [];
|
|
22
|
+
|
|
23
|
+
// ======
|
|
24
|
+
// NoShift.js → JavaScript 置換用マップ
|
|
25
|
+
// ======
|
|
26
|
+
const noShiftMap = {
|
|
27
|
+
// "^@^@^@": "```", // マルチバックチック
|
|
28
|
+
"^4^[": "${", // テンプレート式展開開始
|
|
29
|
+
"^1": "!",
|
|
30
|
+
"^2": '"',
|
|
31
|
+
"^4": "$",
|
|
32
|
+
"^5": "%",
|
|
33
|
+
"^6": "&",
|
|
34
|
+
"^7": "'",
|
|
35
|
+
"^8": "(",
|
|
36
|
+
"^9": ")",
|
|
37
|
+
"^-": "=",
|
|
38
|
+
"^^": "~",
|
|
39
|
+
"^\\": "|",
|
|
40
|
+
"^@": "`",
|
|
41
|
+
"^[": "{",
|
|
42
|
+
"^]": "}",
|
|
43
|
+
"^;": "+",
|
|
44
|
+
"^:": "*",
|
|
45
|
+
"^,": "<",
|
|
46
|
+
"^.": ">",
|
|
47
|
+
"^/": "?",
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
// マッピングキーは長いものからマッチさせる
|
|
51
|
+
const sortedNsKeys = Object.keys(noShiftMap).sort(
|
|
52
|
+
(a, b) => b.length - a.length
|
|
53
|
+
);
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* tryConsumeNsjsSequence:
|
|
57
|
+
* - カーソル i の位置に noShiftMap のキーまたは特殊シーケンスがあれば消費し、
|
|
58
|
+
* jsCode に出力して true を返す。なければ false を返す。
|
|
59
|
+
*
|
|
60
|
+
* 優先度:
|
|
61
|
+
* 1) テンプレートリテラル中の式展開開始 (^4^[ / ^4[)
|
|
62
|
+
* 2) テンプレート式中の式閉じ (^])
|
|
63
|
+
* 3) 通常コード/テンプレート式外の文字列開閉 (^2, ^7, ^@^@^@, ^@)
|
|
64
|
+
* 4) テンプレート式中の JS 文字列開閉 (^2, ^7 → RAW_DQ_IN_EXPR / RAW_SQ_IN_EXPR)
|
|
65
|
+
* 5) RAW_DQ_IN_EXPR/RAW_SQ_IN_EXPR 内の終端 (^2 / ^7 → 戻る)
|
|
66
|
+
* 6) 通常の NoShift 置換 (NORMAL または IN_TEMPLATE_EXPRESSION のとき)
|
|
67
|
+
*/
|
|
68
|
+
function tryConsumeNsjsSequence() {
|
|
69
|
+
let allowGeneral = false;
|
|
70
|
+
if (
|
|
71
|
+
currentState === STATE.NORMAL ||
|
|
72
|
+
currentState === STATE.IN_TEMPLATE_EXPRESSION
|
|
73
|
+
) {
|
|
74
|
+
allowGeneral = true;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
for (const nsKey of sortedNsKeys) {
|
|
78
|
+
if (!nsjsCode.startsWith(nsKey, i)) continue;
|
|
79
|
+
|
|
80
|
+
// ======
|
|
81
|
+
// 1) テンプレートリテラル中の式展開開始
|
|
82
|
+
// ======
|
|
83
|
+
if (
|
|
84
|
+
(nsKey === "^4^[" || nsjsCode.startsWith("^4[", i)) &&
|
|
85
|
+
(currentState === STATE.IN_BT_SINGLE_STRING ||
|
|
86
|
+
currentState === STATE.IN_BT_MULTI_STRING)
|
|
87
|
+
) {
|
|
88
|
+
jsCode += "${";
|
|
89
|
+
i += nsKey === "^4^[" ? nsKey.length : 3;
|
|
90
|
+
stateStack.push(currentState);
|
|
91
|
+
currentState = STATE.IN_TEMPLATE_EXPRESSION;
|
|
92
|
+
return true;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ======
|
|
96
|
+
// 2) テンプレート式中の式閉じ
|
|
97
|
+
// ======
|
|
98
|
+
if (nsKey === "^]" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
|
|
99
|
+
jsCode += "}";
|
|
100
|
+
i += nsKey.length;
|
|
101
|
+
currentState = stateStack.pop();
|
|
102
|
+
return true;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// ======
|
|
106
|
+
// 3) 通常コード/テンプレート式外の文字列開閉
|
|
107
|
+
// ======
|
|
108
|
+
|
|
109
|
+
// 3-1) "^2": ダブルクォート開閉
|
|
110
|
+
if (nsKey === "^2" && currentState === STATE.NORMAL) {
|
|
111
|
+
jsCode += '"';
|
|
112
|
+
i += nsKey.length;
|
|
113
|
+
stateStack.push(currentState);
|
|
114
|
+
currentState = STATE.IN_DQ_STRING;
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (nsKey === "^2" && currentState === STATE.IN_DQ_STRING) {
|
|
118
|
+
jsCode += '"';
|
|
119
|
+
i += nsKey.length;
|
|
120
|
+
currentState = stateStack.pop();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
// 3-2) "^7": シングルクォート開閉
|
|
125
|
+
if (nsKey === "^7" && currentState === STATE.NORMAL) {
|
|
126
|
+
jsCode += "'";
|
|
127
|
+
i += nsKey.length;
|
|
128
|
+
stateStack.push(currentState);
|
|
129
|
+
currentState = STATE.IN_SQ_STRING;
|
|
130
|
+
return true;
|
|
131
|
+
}
|
|
132
|
+
if (nsKey === "^7" && currentState === STATE.IN_SQ_STRING) {
|
|
133
|
+
jsCode += "'";
|
|
134
|
+
i += nsKey.length;
|
|
135
|
+
currentState = stateStack.pop();
|
|
136
|
+
return true;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// このブロックを削除
|
|
140
|
+
// 3-3) "^@^@^@": マルチバックチック開閉
|
|
141
|
+
/*
|
|
142
|
+
if (
|
|
143
|
+
nsKey === "^@^@^@" &&
|
|
144
|
+
(currentState === STATE.NORMAL ||
|
|
145
|
+
currentState === STATE.IN_TEMPLATE_EXPRESSION)
|
|
146
|
+
) {
|
|
147
|
+
jsCode += "```";
|
|
148
|
+
i += nsKey.length;
|
|
149
|
+
stateStack.push(currentState);
|
|
150
|
+
currentState = STATE.IN_BT_MULTI_STRING;
|
|
151
|
+
return true;
|
|
152
|
+
}
|
|
153
|
+
if (nsKey === "^@^@^@" && currentState === STATE.IN_BT_MULTI_STRING) {
|
|
154
|
+
jsCode += "```";
|
|
155
|
+
i += nsKey.length;
|
|
156
|
+
currentState = stateStack.pop();
|
|
157
|
+
return true;
|
|
158
|
+
}
|
|
159
|
+
*/
|
|
160
|
+
|
|
161
|
+
// 3-4) "^@": シングルバックチック開閉
|
|
162
|
+
if (
|
|
163
|
+
nsKey === "^@" &&
|
|
164
|
+
(currentState === STATE.NORMAL ||
|
|
165
|
+
currentState === STATE.IN_TEMPLATE_EXPRESSION)
|
|
166
|
+
) {
|
|
167
|
+
jsCode += "`";
|
|
168
|
+
i += nsKey.length;
|
|
169
|
+
stateStack.push(currentState);
|
|
170
|
+
currentState = STATE.IN_BT_SINGLE_STRING;
|
|
171
|
+
return true;
|
|
172
|
+
}
|
|
173
|
+
if (nsKey === "^@" && currentState === STATE.IN_BT_SINGLE_STRING) {
|
|
174
|
+
jsCode += "`";
|
|
175
|
+
i += nsKey.length;
|
|
176
|
+
currentState = stateStack.pop();
|
|
177
|
+
return true;
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
// ======
|
|
181
|
+
// 4) テンプレート式中の JS 文字列開閉
|
|
182
|
+
// ======
|
|
183
|
+
if (nsKey === "^2" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
|
|
184
|
+
jsCode += '"';
|
|
185
|
+
i += nsKey.length;
|
|
186
|
+
stateStack.push(currentState);
|
|
187
|
+
currentState = STATE.RAW_DQ_IN_EXPR;
|
|
188
|
+
return true;
|
|
189
|
+
}
|
|
190
|
+
if (nsKey === "^7" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
|
|
191
|
+
jsCode += "'";
|
|
192
|
+
i += nsKey.length;
|
|
193
|
+
stateStack.push(currentState);
|
|
194
|
+
currentState = STATE.RAW_SQ_IN_EXPR;
|
|
195
|
+
return true;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
// ======
|
|
199
|
+
// 5) RAW_DQ_IN_EXPR / RAW_SQ_IN_EXPR 内の終端
|
|
200
|
+
// ======
|
|
201
|
+
if (nsKey === "^2" && currentState === STATE.RAW_DQ_IN_EXPR) {
|
|
202
|
+
jsCode += '"';
|
|
203
|
+
i += nsKey.length;
|
|
204
|
+
currentState = stateStack.pop();
|
|
205
|
+
return true;
|
|
206
|
+
}
|
|
207
|
+
if (nsKey === "^7" && currentState === STATE.RAW_SQ_IN_EXPR) {
|
|
208
|
+
jsCode += "'";
|
|
209
|
+
i += nsKey.length;
|
|
210
|
+
currentState = stateStack.pop();
|
|
211
|
+
return true;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// ======
|
|
215
|
+
// 6) 通常の NoShift 置換 (NORMAL or IN_TEMPLATE_EXPRESSION のとき)
|
|
216
|
+
// ======
|
|
217
|
+
if (allowGeneral) {
|
|
218
|
+
jsCode += noShiftMap[nsKey];
|
|
219
|
+
i += nsKey.length;
|
|
220
|
+
return true;
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
return false;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
// ======
|
|
228
|
+
// メインループ
|
|
229
|
+
// ======
|
|
230
|
+
while (i < len_ns) {
|
|
231
|
+
let consumed = false;
|
|
232
|
+
|
|
233
|
+
// ======
|
|
234
|
+
// ステップ1: 文字列内でのエスケープ処理
|
|
235
|
+
// ======
|
|
236
|
+
|
|
237
|
+
// (A) IN_DQ_STRING 内 (" … ")
|
|
238
|
+
if (currentState === STATE.IN_DQ_STRING) {
|
|
239
|
+
if (nsjsCode.startsWith("\\^2", i)) {
|
|
240
|
+
jsCode += "^2"; // "\^2" を文字列中の "^2" として出力
|
|
241
|
+
i += 3;
|
|
242
|
+
consumed = true;
|
|
243
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
244
|
+
jsCode += "\\\\\\\\"; // "\\" を JSコード内で "\\\\" に出力
|
|
245
|
+
i += 2;
|
|
246
|
+
consumed = true;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
// (B) IN_SQ_STRING 内 (' … ')
|
|
250
|
+
else if (currentState === STATE.IN_SQ_STRING) {
|
|
251
|
+
if (nsjsCode.startsWith("\\^7", i)) {
|
|
252
|
+
jsCode += "^7";
|
|
253
|
+
i += 3;
|
|
254
|
+
consumed = true;
|
|
255
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
256
|
+
jsCode += "\\\\\\\\";
|
|
257
|
+
i += 2;
|
|
258
|
+
consumed = true;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
// (C) RAW_DQ_IN_EXPR 内 (テンプレート式中の " … ")
|
|
262
|
+
else if (currentState === STATE.RAW_DQ_IN_EXPR) {
|
|
263
|
+
if (nsjsCode.startsWith("\\^2", i)) {
|
|
264
|
+
jsCode += "^2";
|
|
265
|
+
i += 3;
|
|
266
|
+
consumed = true;
|
|
267
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
268
|
+
jsCode += "\\\\\\\\";
|
|
269
|
+
i += 2;
|
|
270
|
+
consumed = true;
|
|
271
|
+
} else if (nsjsCode.startsWith("^2", i)) {
|
|
272
|
+
jsCode += '"'; // 終端
|
|
273
|
+
i += 2;
|
|
274
|
+
currentState = stateStack.pop();
|
|
275
|
+
consumed = true;
|
|
276
|
+
}
|
|
277
|
+
if (consumed) {
|
|
278
|
+
continue; // 生文字扱いなので NoShift 変換せず次へ
|
|
279
|
+
} else {
|
|
280
|
+
jsCode += nsjsCode[i];
|
|
281
|
+
i += 1;
|
|
282
|
+
continue;
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// (D) RAW_SQ_IN_EXPR 内 (テンプレート式中の ' … ')
|
|
286
|
+
else if (currentState === STATE.RAW_SQ_IN_EXPR) {
|
|
287
|
+
if (nsjsCode.startsWith("\\^7", i)) {
|
|
288
|
+
jsCode += "^7";
|
|
289
|
+
i += 3;
|
|
290
|
+
consumed = true;
|
|
291
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
292
|
+
jsCode += "\\\\\\\\";
|
|
293
|
+
i += 2;
|
|
294
|
+
consumed = true;
|
|
295
|
+
} else if (nsjsCode.startsWith("^7", i)) {
|
|
296
|
+
jsCode += "'";
|
|
297
|
+
i += 2;
|
|
298
|
+
currentState = stateStack.pop();
|
|
299
|
+
consumed = true;
|
|
300
|
+
}
|
|
301
|
+
if (consumed) {
|
|
302
|
+
continue;
|
|
303
|
+
} else {
|
|
304
|
+
jsCode += nsjsCode[i];
|
|
305
|
+
i += 1;
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
// (E) IN_BT_SINGLE_STRING 内 (` … `)
|
|
310
|
+
else if (currentState === STATE.IN_BT_SINGLE_STRING) {
|
|
311
|
+
if (nsjsCode.startsWith("\\^@", i)) {
|
|
312
|
+
jsCode += "^@";
|
|
313
|
+
i += 3;
|
|
314
|
+
consumed = true;
|
|
315
|
+
} else if (nsjsCode.startsWith("\\\\", i)) {
|
|
316
|
+
jsCode += "\\\\\\\\";
|
|
317
|
+
i += 2;
|
|
318
|
+
consumed = true;
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
// (F) IN_BT_MULTI_STRING 内 (``` … ```)
|
|
322
|
+
else if (currentState === STATE.IN_BT_MULTI_STRING) {
|
|
323
|
+
/* if (nsjsCode.startsWith("\\^@^@^@", i)) {
|
|
324
|
+
jsCode += "^@^@^@";
|
|
325
|
+
i += 7;
|
|
326
|
+
consumed = true;
|
|
327
|
+
} else */
|
|
328
|
+
if (nsjsCode.startsWith("\\\\", i)) {
|
|
329
|
+
jsCode += "\\\\\\\\";
|
|
330
|
+
i += 2;
|
|
331
|
+
consumed = true;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
// ======
|
|
336
|
+
// ステップ2: NoShift シーケンスや文字列/テンプレートの開閉、式展開を試す
|
|
337
|
+
// ======
|
|
338
|
+
if (!consumed) {
|
|
339
|
+
consumed = tryConsumeNsjsSequence();
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ======
|
|
343
|
+
// ステップ3: 何も消費しなかったら文字をそのまま出力
|
|
344
|
+
// ======
|
|
345
|
+
if (!consumed) {
|
|
346
|
+
jsCode += nsjsCode[i];
|
|
347
|
+
i += 1;
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
// スタックに残りがあればリテラル/テンプレートの閉じ忘れ
|
|
352
|
+
if (stateStack.length > 0) {
|
|
353
|
+
console.warn(
|
|
354
|
+
`Warning: Unmatched literal/templating states. Final state: ${currentState}, Remaining stack: ${stateStack.join(
|
|
355
|
+
", "
|
|
356
|
+
)}`
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
return jsCode;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export default convertNsjsToJs;
|