noshift.js
Version:
Joke language.
641 lines (638 loc) • 18.3 kB
JavaScript
// src/convert.ts
var noShiftMap = {
// "^@^@^@": "```", // マルチバックチック
"^4^[": "${",
// テンプレート式展開開始
"^0": "^",
"^1": "!",
"^2": '"',
"^3": "#",
"^4": "$",
"^5": "%",
"^7": "'",
"^8": "(",
"^9": ")",
"^-": "=",
"^^": "~",
"^\\": "_",
"^@": "`",
"^[": "{",
"^]": "}",
"^;": "+",
"^:": "*",
"^,": "<",
"^.": ">",
"^/": "?"
};
var symbolToNoshift = Object.fromEntries(
Object.entries(noShiftMap).filter(([key, value]) => key.length === 2 && value.length === 1).map(([key, value]) => [value, key])
);
var keywordReplacements = [
["@or^-", "|="],
["@and^-", "&="],
["or^-", "||="],
["and^-", "&&="],
["@or", "|"],
["@and", "&"]
];
var wordKeywordReplacements = [
["or", "||"],
["and", "&&"]
];
function convertNsjsToJs(nsjsCode, options = {}) {
const capitalizeInStrings = options.capitalizeInStrings !== false;
let jsCode = "";
let i = 0;
const len_ns = nsjsCode.length;
const STATE = {
NORMAL: "NORMAL",
// 普通のコード
IN_DQ_STRING: "IN_DQ_STRING",
// " … " の中
IN_SQ_STRING: "IN_SQ_STRING",
// ' … ' の中
IN_BT_SINGLE_STRING: "IN_BT_SINGLE_STRING",
// ` … ` の中
IN_BT_MULTI_STRING: "IN_BT_MULTI_STRING",
// ``` … ``` の中
IN_TEMPLATE_EXPRESSION: "IN_TEMPLATE_EXPRESSION",
// ${ … } の中
RAW_DQ_IN_EXPR: "RAW_DQ_IN_EXPR",
// テンプレート式内の " … " の中 (NoShift 変換なし)
RAW_SQ_IN_EXPR: "RAW_SQ_IN_EXPR",
// テンプレート式内の ' … ' の中 (NoShift 変換なし)
IN_LINE_COMMENT: "IN_LINE_COMMENT",
// // … の中
IN_BLOCK_COMMENT: "IN_BLOCK_COMMENT"
// /^: … ^:/ の中
};
let currentState = STATE.NORMAL;
const stateStack = [];
const sortedNsKeys = Object.keys(noShiftMap).sort(
(a, b) => b.length - a.length
);
function tryConsumeNsjsSequence() {
let allowGeneral = false;
if (currentState === STATE.NORMAL || currentState === STATE.IN_TEMPLATE_EXPRESSION) {
allowGeneral = true;
}
for (const nsKey of sortedNsKeys) {
if (!nsjsCode.startsWith(nsKey, i)) continue;
if ((nsKey === "^4^[" || nsjsCode.startsWith("^4[", i)) && (currentState === STATE.IN_BT_SINGLE_STRING || currentState === STATE.IN_BT_MULTI_STRING)) {
jsCode += "${";
i += nsKey === "^4^[" ? nsKey.length : 3;
stateStack.push(currentState);
currentState = STATE.IN_TEMPLATE_EXPRESSION;
return true;
}
if (nsKey === "^]" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
jsCode += "}";
i += nsKey.length;
currentState = stateStack.pop();
return true;
}
if (nsKey === "^2" && currentState === STATE.NORMAL) {
jsCode += '"';
i += nsKey.length;
stateStack.push(currentState);
currentState = STATE.IN_DQ_STRING;
return true;
}
if (nsKey === "^2" && currentState === STATE.IN_DQ_STRING) {
jsCode += '"';
i += nsKey.length;
currentState = stateStack.pop();
return true;
}
if (nsKey === "^7" && currentState === STATE.NORMAL) {
jsCode += "'";
i += nsKey.length;
stateStack.push(currentState);
currentState = STATE.IN_SQ_STRING;
return true;
}
if (nsKey === "^7" && currentState === STATE.IN_SQ_STRING) {
jsCode += "'";
i += nsKey.length;
currentState = stateStack.pop();
return true;
}
if (nsKey === "^@" && (currentState === STATE.NORMAL || currentState === STATE.IN_TEMPLATE_EXPRESSION)) {
jsCode += "`";
i += nsKey.length;
stateStack.push(currentState);
currentState = STATE.IN_BT_SINGLE_STRING;
return true;
}
if (nsKey === "^@" && currentState === STATE.IN_BT_SINGLE_STRING) {
jsCode += "`";
i += nsKey.length;
currentState = stateStack.pop();
return true;
}
if (nsKey === "^2" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
jsCode += '"';
i += nsKey.length;
stateStack.push(currentState);
currentState = STATE.RAW_DQ_IN_EXPR;
return true;
}
if (nsKey === "^7" && currentState === STATE.IN_TEMPLATE_EXPRESSION) {
jsCode += "'";
i += nsKey.length;
stateStack.push(currentState);
currentState = STATE.RAW_SQ_IN_EXPR;
return true;
}
if (nsKey === "^2" && currentState === STATE.RAW_DQ_IN_EXPR) {
jsCode += '"';
i += nsKey.length;
currentState = stateStack.pop();
return true;
}
if (nsKey === "^7" && currentState === STATE.RAW_SQ_IN_EXPR) {
jsCode += "'";
i += nsKey.length;
currentState = stateStack.pop();
return true;
}
if (allowGeneral) {
jsCode += noShiftMap[nsKey];
i += nsKey.length;
return true;
}
}
return false;
}
while (i < len_ns) {
let consumed = false;
if (currentState === STATE.IN_DQ_STRING) {
if (nsjsCode.startsWith("\\^6", i)) {
jsCode += "^6";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\^2", i)) {
jsCode += "^2";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\\\", i)) {
jsCode += "\\\\\\\\";
i += 2;
consumed = true;
}
} else if (currentState === STATE.IN_SQ_STRING) {
if (nsjsCode.startsWith("\\^6", i)) {
jsCode += "^6";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\^7", i)) {
jsCode += "^7";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\\\", i)) {
jsCode += "\\\\\\\\";
i += 2;
consumed = true;
}
} else if (currentState === STATE.RAW_DQ_IN_EXPR) {
if (nsjsCode.startsWith("\\^6", i)) {
jsCode += "^6";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\^2", i)) {
jsCode += "^2";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\\\", i)) {
jsCode += "\\\\\\\\";
i += 2;
consumed = true;
} else if (nsjsCode.startsWith("^2", i)) {
jsCode += '"';
i += 2;
currentState = stateStack.pop();
consumed = true;
}
if (consumed) {
continue;
} else {
jsCode += nsjsCode[i];
i += 1;
continue;
}
} else if (currentState === STATE.RAW_SQ_IN_EXPR) {
if (nsjsCode.startsWith("\\^6", i)) {
jsCode += "^6";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\^7", i)) {
jsCode += "^7";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\\\", i)) {
jsCode += "\\\\\\\\";
i += 2;
consumed = true;
} else if (nsjsCode.startsWith("^7", i)) {
jsCode += "'";
i += 2;
currentState = stateStack.pop();
consumed = true;
}
if (consumed) {
continue;
} else {
jsCode += nsjsCode[i];
i += 1;
continue;
}
} else if (currentState === STATE.IN_BT_SINGLE_STRING) {
if (nsjsCode.startsWith("\\^6", i)) {
jsCode += "^6";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\^@", i)) {
jsCode += "^@";
i += 3;
consumed = true;
} else if (nsjsCode.startsWith("\\\\", i)) {
jsCode += "\\\\\\\\";
i += 2;
consumed = true;
}
} else if (currentState === STATE.IN_BT_MULTI_STRING) {
if (nsjsCode.startsWith("\\\\", i)) {
jsCode += "\\\\\\\\";
i += 2;
consumed = true;
}
}
if (!consumed && currentState !== STATE.IN_LINE_COMMENT && currentState !== STATE.IN_BLOCK_COMMENT) {
if (nsjsCode.startsWith("^6", i)) {
const inString = currentState === STATE.IN_DQ_STRING || currentState === STATE.IN_SQ_STRING || currentState === STATE.IN_BT_SINGLE_STRING || currentState === STATE.IN_BT_MULTI_STRING;
if (!inString || capitalizeInStrings) {
i += 2;
if (i < len_ns) {
jsCode += nsjsCode[i].toUpperCase();
i += 1;
}
consumed = true;
}
}
}
if (!consumed) {
if (currentState === STATE.NORMAL && nsjsCode.startsWith("//", i)) {
jsCode += "//";
i += 2;
stateStack.push(currentState);
currentState = STATE.IN_LINE_COMMENT;
consumed = true;
} else if (currentState === STATE.IN_LINE_COMMENT) {
if (nsjsCode[i] === "\n") {
jsCode += "\n";
i += 1;
currentState = stateStack.pop();
} else {
jsCode += nsjsCode[i];
i += 1;
}
consumed = true;
} else if (currentState === STATE.NORMAL && nsjsCode.startsWith("/^:", i)) {
jsCode += "/*";
i += 3;
stateStack.push(currentState);
currentState = STATE.IN_BLOCK_COMMENT;
consumed = true;
} else if (currentState === STATE.IN_BLOCK_COMMENT && nsjsCode.startsWith("^:/", i)) {
jsCode += "*/";
i += 3;
currentState = stateStack.pop();
consumed = true;
} else if (currentState === STATE.IN_BLOCK_COMMENT) {
jsCode += nsjsCode[i];
i += 1;
consumed = true;
}
}
if (!consumed && (currentState === STATE.NORMAL || currentState === STATE.IN_TEMPLATE_EXPRESSION)) {
for (const [kw, replacement] of keywordReplacements) {
if (nsjsCode.startsWith(kw, i)) {
jsCode += replacement;
i += kw.length;
consumed = true;
break;
}
}
if (!consumed) {
for (const [kw, replacement] of wordKeywordReplacements) {
if (nsjsCode.startsWith(kw, i)) {
const prevChar = i > 0 ? nsjsCode[i - 1] : " ";
const prevIsBoundary = !/[a-zA-Z0-9_$]/.test(prevChar);
const afterPos = i + kw.length;
const nextChar = afterPos < len_ns ? nsjsCode[afterPos] : " ";
const nextIsBoundary = !/[a-zA-Z0-9_$]/.test(nextChar);
if (prevIsBoundary && nextIsBoundary) {
jsCode += replacement;
i += kw.length;
consumed = true;
break;
}
}
}
}
}
if (!consumed) {
consumed = tryConsumeNsjsSequence();
}
if (!consumed) {
jsCode += nsjsCode[i];
i += 1;
}
}
if (stateStack.length > 0) {
if (!options._silent) {
console.warn(
`Warning: Unmatched literal/templating states. Final state: ${currentState}, Remaining stack: ${stateStack.join(
", "
)}`
);
}
}
return jsCode;
}
var validCaretKeys = new Set(Object.keys(noShiftMap).map((k) => k[1]));
validCaretKeys.add("6");
function diagnose(nsjsCode) {
const errors = [];
const lines = nsjsCode.split("\n");
let state = "NORMAL";
const stateStack = [];
const openPositions = [];
for (let lineNum = 0; lineNum < lines.length; lineNum++) {
const line = lines[lineNum];
if (state === "LINE_COMMENT") {
state = stateStack.pop() || "NORMAL";
}
for (let col = 0; col < line.length; col++) {
const ch = line[col];
const next = col + 1 < line.length ? line[col + 1] : void 0;
const next2 = col + 2 < line.length ? line[col + 2] : void 0;
if (ch === "\\" && next === "^") {
col += 2;
continue;
}
if (ch === "\\" && next === "\\") {
col += 1;
continue;
}
if (state === "BLOCK_COMMENT") {
if (ch === "^" && next === ":" && next2 === "/") {
state = stateStack.pop() || "NORMAL";
openPositions.pop();
col += 2;
}
continue;
}
if (state === "LINE_COMMENT") {
continue;
}
if (state === "DQ") {
if (ch === "^" && next === "2") {
state = stateStack.pop() || "NORMAL";
openPositions.pop();
col += 1;
}
continue;
}
if (state === "SQ") {
if (ch === "^" && next === "7") {
state = stateStack.pop() || "NORMAL";
openPositions.pop();
col += 1;
}
continue;
}
if (state === "BT") {
if (ch === "^" && next === "4" && (next2 === "^" || next2 === "[")) {
if (next2 === "^" && col + 3 < line.length && line[col + 3] === "[") {
stateStack.push(state);
openPositions.push({
line: lineNum + 1,
column: col + 1,
type: "TEMPLATE_EXPR"
});
state = "TEMPLATE_EXPR";
col += 3;
} else if (next2 === "[") {
stateStack.push(state);
openPositions.push({
line: lineNum + 1,
column: col + 1,
type: "TEMPLATE_EXPR"
});
state = "TEMPLATE_EXPR";
col += 2;
}
continue;
}
if (ch === "^" && next === "@") {
state = stateStack.pop() || "NORMAL";
openPositions.pop();
col += 1;
}
continue;
}
if (state === "TEMPLATE_EXPR") {
if (ch === "^" && next === "]") {
state = stateStack.pop() || "NORMAL";
openPositions.pop();
col += 1;
continue;
}
}
if (ch === "/" && next === "/") {
stateStack.push(state);
state = "LINE_COMMENT";
break;
}
if (ch === "/" && next === "^" && next2 === ":") {
stateStack.push(state);
openPositions.push({
line: lineNum + 1,
column: col + 1,
type: "BLOCK_COMMENT"
});
state = "BLOCK_COMMENT";
col += 2;
continue;
}
if (ch === "^" && next === "2") {
stateStack.push(state);
openPositions.push({ line: lineNum + 1, column: col + 1, type: "DQ" });
state = "DQ";
col += 1;
continue;
}
if (ch === "^" && next === "7") {
stateStack.push(state);
openPositions.push({ line: lineNum + 1, column: col + 1, type: "SQ" });
state = "SQ";
col += 1;
continue;
}
if (ch === "^" && next === "@") {
stateStack.push(state);
openPositions.push({ line: lineNum + 1, column: col + 1, type: "BT" });
state = "BT";
col += 1;
continue;
}
if (ch === "^" && next === "6") {
if (col + 2 >= line.length && lineNum === lines.length - 1) {
errors.push({
line: lineNum + 1,
column: col + 1,
message: "^6 at end of file with no following character to capitalize."
});
}
col += 2;
continue;
}
if (ch === "^" && next !== void 0) {
if (!validCaretKeys.has(next)) {
errors.push({
line: lineNum + 1,
column: col + 1,
message: `Unknown sequence '^${next}'.`
});
}
col += 1;
continue;
}
if (ch === "^" && next === void 0 && lineNum === lines.length - 1) {
errors.push({
line: lineNum + 1,
column: col + 1,
message: "Lone '^' at end of file."
});
}
}
}
while (openPositions.length > 0) {
const pos = openPositions.pop();
const labels = {
DQ: "string literal (^2...^2)",
SQ: "string literal (^7...^7)",
BT: "template literal (^@...^@)",
BLOCK_COMMENT: "block comment (/^:...^:/)",
TEMPLATE_EXPR: "template expression (^4^[...^])"
};
errors.push({
line: pos.line,
column: pos.column,
message: `Unclosed ${labels[pos.type] || pos.type} opened here.`
});
}
return errors;
}
// package.json
var package_default = {
name: "noshift.js",
version: "0.15.1",
description: "Joke language.",
bin: {
nsc: "./dist/cli.cjs"
},
main: "./dist/index.cjs",
module: "./dist/index.mjs",
types: "./dist/index.d.ts",
exports: {
".": {
import: {
types: "./dist/index.d.mts",
default: "./dist/index.mjs"
},
require: {
types: "./dist/index.d.cts",
default: "./dist/index.cjs"
}
}
},
type: "module",
scripts: {
build: "tsup",
test: "vitest run",
"test:watch": "vitest",
typecheck: "tsc --noEmit",
format: "prettier --write ./src/"
},
repository: {
type: "git",
url: "git+https://github.com/otoneko1102/NoShift.js.git"
},
keywords: [
"noshift",
"nsjs",
"joke",
"language",
"lang"
],
author: "otoneko.",
license: "MIT",
bugs: {
url: "https://github.com/otoneko1102/NoShift.js/issues"
},
homepage: "https://noshift.js.org",
dependencies: {
commander: "^14.0.3"
},
devDependencies: {
"@types/node": "^25.3.0",
prettier: "^3.8.1",
tsup: "^8.5.1",
tsx: "^4.21.0",
typescript: "^5.9.3",
vitest: "^4.0.18"
},
engines: {
node: ">=22.12.0"
}
};
// src/header.ts
function getVersion() {
return package_default.version;
}
function addHeader(js, version) {
const ver = version ?? getVersion();
const header = `// Generated by NoShift.js ${ver}`;
if (js.startsWith("#!")) {
const newlineIndex = js.indexOf("\n");
if (newlineIndex === -1) {
return js + "\n" + header + "\n";
}
const shebang = js.slice(0, newlineIndex + 1);
const rest = js.slice(newlineIndex + 1);
return shebang + header + "\n" + rest;
}
return header + "\n" + js;
}
// src/index.ts
function compile(source, options = {}) {
let outputText = convertNsjsToJs(source, {
capitalizeInStrings: options.capitalizeInStrings !== false
});
if (!options.noHeader && outputText.length > 0) {
outputText = addHeader(outputText);
}
return { outputText };
}
function diagnose2(source) {
return diagnose(source);
}
var index_default = compile;
export {
compile,
index_default as default,
diagnose2 as diagnose
};
//# sourceMappingURL=index.mjs.map