purus 0.0.3 → 0.2.0
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/README-ja.md +2 -2
- package/README.md +1 -1
- package/bin/purus.js +50 -1
- package/lib/build-wrapper.js +142 -0
- package/lib/config.js +67 -0
- package/lib/create.js +195 -0
- package/lib/purus-compiler.js +278 -268
- package/lib/run-wrapper.js +111 -0
- package/package.json +1 -1
package/README-ja.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
<div align="center">
|
|
2
2
|
|
|
3
|
-
[](https://purus.work)
|
|
4
4
|
|
|
5
5
|
[English](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/README.md) | **日本語**
|
|
6
6
|
|
|
@@ -55,4 +55,4 @@ otoneko. https://github.com/otoneko1102
|
|
|
55
55
|
|
|
56
56
|
## ライセンス
|
|
57
57
|
|
|
58
|
-
Apache 2.0 ライセンスに基づいて配布されます。詳細については、[LICENSE](
|
|
58
|
+
Apache 2.0 ライセンスに基づいて配布されます。詳細については、[LICENSE](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/LICENSE) を参照してください。
|
package/README.md
CHANGED
|
@@ -55,4 +55,4 @@ otoneko. https://github.com/otoneko1102
|
|
|
55
55
|
|
|
56
56
|
## License
|
|
57
57
|
|
|
58
|
-
Distributed under the Apache 2.0 License. See [LICENSE](
|
|
58
|
+
Distributed under the Apache 2.0 License. See [LICENSE](https://raw.githubusercontent.com/otoneko1102/purus/refs/heads/main/LICENSE) for more information.
|
package/bin/purus.js
CHANGED
|
@@ -1,3 +1,52 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
"use strict";
|
|
3
|
-
|
|
3
|
+
|
|
4
|
+
const VERSION = require("../package.json").version;
|
|
5
|
+
const cmd = process.argv[2];
|
|
6
|
+
|
|
7
|
+
function printHelp() {
|
|
8
|
+
console.log(`purus v${VERSION} - A language that compiles to JavaScript`);
|
|
9
|
+
console.log("");
|
|
10
|
+
console.log("Usage:");
|
|
11
|
+
console.log(" purus build [file] Compile to JavaScript");
|
|
12
|
+
console.log(" purus build --directory <dir> Compile all files in directory");
|
|
13
|
+
console.log(" purus build --output <dir> Specify output directory");
|
|
14
|
+
console.log(" purus build Compile using config.purus");
|
|
15
|
+
console.log(" .purus -> .js");
|
|
16
|
+
console.log(" .cpurus -> .cjs (CommonJS)");
|
|
17
|
+
console.log(" .mpurus -> .mjs (ES Module)");
|
|
18
|
+
console.log(" purus build --no-header [file] Compile without header comment");
|
|
19
|
+
console.log(" purus run [file] Run without generating files");
|
|
20
|
+
console.log(" purus run --directory <dir> Run all files in directory");
|
|
21
|
+
console.log(" purus run Run using config.purus");
|
|
22
|
+
console.log(" purus check <file> Syntax check only");
|
|
23
|
+
console.log(" purus new [name] [-y] Create a new project");
|
|
24
|
+
console.log(" purus init Initialize project in current directory");
|
|
25
|
+
console.log(" purus version Show version");
|
|
26
|
+
console.log(" purus help Show this help");
|
|
27
|
+
console.log("");
|
|
28
|
+
console.log("Aliases: compile = build, create = new");
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
switch (cmd) {
|
|
32
|
+
case "new":
|
|
33
|
+
case "create":
|
|
34
|
+
require("../lib/create.js");
|
|
35
|
+
break;
|
|
36
|
+
case "build":
|
|
37
|
+
case "compile":
|
|
38
|
+
require("../lib/build-wrapper.js");
|
|
39
|
+
break;
|
|
40
|
+
case "run":
|
|
41
|
+
require("../lib/run-wrapper.js");
|
|
42
|
+
break;
|
|
43
|
+
case "help":
|
|
44
|
+
case "--help":
|
|
45
|
+
case "-h":
|
|
46
|
+
case undefined:
|
|
47
|
+
printHelp();
|
|
48
|
+
break;
|
|
49
|
+
default:
|
|
50
|
+
require("../lib/purus-compiler.js");
|
|
51
|
+
break;
|
|
52
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const { loadConfig } = require("./config.js");
|
|
6
|
+
const { compile } = require("./purus-core.js");
|
|
7
|
+
|
|
8
|
+
const args = process.argv.slice(3);
|
|
9
|
+
|
|
10
|
+
let file = null;
|
|
11
|
+
let directory = null;
|
|
12
|
+
let output = null;
|
|
13
|
+
let noHeader = false;
|
|
14
|
+
let toStdout = false;
|
|
15
|
+
|
|
16
|
+
for (let i = 0; i < args.length; i++) {
|
|
17
|
+
if (args[i] === "--no-header") {
|
|
18
|
+
noHeader = true;
|
|
19
|
+
} else if (args[i] === "--stdout") {
|
|
20
|
+
toStdout = true;
|
|
21
|
+
} else if (args[i] === "--directory" || args[i] === "-d") {
|
|
22
|
+
directory = args[++i];
|
|
23
|
+
} else if (args[i] === "--output" || args[i] === "-o") {
|
|
24
|
+
output = args[++i];
|
|
25
|
+
} else if (!args[i].startsWith("-")) {
|
|
26
|
+
file = args[i];
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (file) {
|
|
31
|
+
// Single file - delegate to MoonBit compiler
|
|
32
|
+
require("./purus-compiler.js");
|
|
33
|
+
} else {
|
|
34
|
+
let entryDir;
|
|
35
|
+
let outputDir;
|
|
36
|
+
let useHeader;
|
|
37
|
+
|
|
38
|
+
if (directory) {
|
|
39
|
+
entryDir = path.resolve(directory);
|
|
40
|
+
outputDir = output ? path.resolve(output) : path.resolve("dist");
|
|
41
|
+
useHeader = !noHeader;
|
|
42
|
+
|
|
43
|
+
const result = loadConfig();
|
|
44
|
+
if (result) {
|
|
45
|
+
if (!output) {
|
|
46
|
+
outputDir = path.resolve(
|
|
47
|
+
result.configDir,
|
|
48
|
+
result.config.output || "dist"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
useHeader = result.config.header !== false && !noHeader;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
const result = loadConfig();
|
|
55
|
+
if (!result) {
|
|
56
|
+
console.log("Error: no input file specified and no config.purus found");
|
|
57
|
+
console.log("");
|
|
58
|
+
console.log("Usage:");
|
|
59
|
+
console.log(" purus build <file> Compile a single file");
|
|
60
|
+
console.log(
|
|
61
|
+
" purus build --directory <dir> Compile all files in directory"
|
|
62
|
+
);
|
|
63
|
+
console.log(
|
|
64
|
+
" purus build Compile using config.purus"
|
|
65
|
+
);
|
|
66
|
+
process.exit(1);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const { config, configDir } = result;
|
|
70
|
+
entryDir = path.resolve(configDir, config.entry || "src");
|
|
71
|
+
outputDir = output
|
|
72
|
+
? path.resolve(output)
|
|
73
|
+
: path.resolve(configDir, config.output || "dist");
|
|
74
|
+
useHeader = config.header !== false && !noHeader;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (!fs.existsSync(entryDir)) {
|
|
78
|
+
console.log(`Error: entry directory '${entryDir}' not found`);
|
|
79
|
+
process.exit(1);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
const stat = fs.statSync(entryDir);
|
|
83
|
+
let files;
|
|
84
|
+
|
|
85
|
+
if (stat.isFile()) {
|
|
86
|
+
files = [entryDir];
|
|
87
|
+
// For single file entry, output is a file too
|
|
88
|
+
if (!fs.existsSync(path.dirname(outputDir))) {
|
|
89
|
+
fs.mkdirSync(path.dirname(outputDir), { recursive: true });
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
files = findPurusFiles(entryDir);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
if (files.length === 0) {
|
|
96
|
+
console.log(`No .purus files found in ${entryDir}`);
|
|
97
|
+
process.exit(0);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
let count = 0;
|
|
101
|
+
for (const f of files) {
|
|
102
|
+
const source = fs.readFileSync(f, "utf8");
|
|
103
|
+
const js = compile(source, { header: useHeader });
|
|
104
|
+
let outputPath;
|
|
105
|
+
|
|
106
|
+
if (stat.isFile()) {
|
|
107
|
+
outputPath = outputDir;
|
|
108
|
+
} else {
|
|
109
|
+
outputPath = getOutputPath(f, entryDir, outputDir);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
fs.mkdirSync(path.dirname(outputPath), { recursive: true });
|
|
113
|
+
fs.writeFileSync(outputPath, js);
|
|
114
|
+
console.log(
|
|
115
|
+
`Compiled ${path.relative(process.cwd(), f)} -> ${path.relative(process.cwd(), outputPath)}`
|
|
116
|
+
);
|
|
117
|
+
count++;
|
|
118
|
+
}
|
|
119
|
+
console.log(`\n${count} file${count === 1 ? "" : "s"} compiled.`);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function findPurusFiles(dir) {
|
|
123
|
+
const results = [];
|
|
124
|
+
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
|
125
|
+
const fullPath = path.join(dir, entry.name);
|
|
126
|
+
if (entry.isDirectory()) {
|
|
127
|
+
results.push(...findPurusFiles(fullPath));
|
|
128
|
+
} else if (/\.(c|m)?purus$/.test(entry.name)) {
|
|
129
|
+
results.push(fullPath);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
return results;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function getOutputPath(inputFile, inputBase, outputBase) {
|
|
136
|
+
const relative = path.relative(inputBase, inputFile);
|
|
137
|
+
let ext = ".js";
|
|
138
|
+
if (inputFile.endsWith(".cpurus")) ext = ".cjs";
|
|
139
|
+
else if (inputFile.endsWith(".mpurus")) ext = ".mjs";
|
|
140
|
+
const base = relative.replace(/\.(c|m)?purus$/, "");
|
|
141
|
+
return path.join(outputBase, base + ext);
|
|
142
|
+
}
|
package/lib/config.js
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
|
|
6
|
+
function parseConfig(configPath) {
|
|
7
|
+
const content = fs.readFileSync(configPath, "utf8");
|
|
8
|
+
const config = {};
|
|
9
|
+
|
|
10
|
+
for (const line of content.split("\n")) {
|
|
11
|
+
const trimmed = line.trim();
|
|
12
|
+
if (trimmed === "" || trimmed.startsWith("--")) continue;
|
|
13
|
+
|
|
14
|
+
const match = trimmed.match(/^const\s+([\w.-]+)\s+be\s+(.+)$/);
|
|
15
|
+
if (!match) continue;
|
|
16
|
+
|
|
17
|
+
const key = match[1];
|
|
18
|
+
let value = match[2].trim();
|
|
19
|
+
|
|
20
|
+
if (value.startsWith("///") && value.endsWith("///")) {
|
|
21
|
+
value = value.slice(3, -3);
|
|
22
|
+
} else if (value === "true") {
|
|
23
|
+
value = true;
|
|
24
|
+
} else if (value === "false") {
|
|
25
|
+
value = false;
|
|
26
|
+
} else if (/^\d+$/.test(value)) {
|
|
27
|
+
value = parseInt(value, 10);
|
|
28
|
+
} else if (/^\d+\.\d+$/.test(value)) {
|
|
29
|
+
value = parseFloat(value);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const keys = key.split(".");
|
|
33
|
+
let target = config;
|
|
34
|
+
for (let i = 0; i < keys.length - 1; i++) {
|
|
35
|
+
if (!target[keys[i]] || typeof target[keys[i]] !== "object") {
|
|
36
|
+
target[keys[i]] = {};
|
|
37
|
+
}
|
|
38
|
+
target = target[keys[i]];
|
|
39
|
+
}
|
|
40
|
+
target[keys[keys.length - 1]] = value;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return config;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function findConfig(startDir) {
|
|
47
|
+
let dir = startDir || process.cwd();
|
|
48
|
+
while (true) {
|
|
49
|
+
const configPath = path.join(dir, "config.purus");
|
|
50
|
+
if (fs.existsSync(configPath)) return configPath;
|
|
51
|
+
const parent = path.dirname(dir);
|
|
52
|
+
if (parent === dir) return null;
|
|
53
|
+
dir = parent;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function loadConfig(startDir) {
|
|
58
|
+
const configPath = findConfig(startDir);
|
|
59
|
+
if (!configPath) return null;
|
|
60
|
+
return {
|
|
61
|
+
config: parseConfig(configPath),
|
|
62
|
+
configPath,
|
|
63
|
+
configDir: path.dirname(configPath),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
module.exports = { parseConfig, findConfig, loadConfig };
|
package/lib/create.js
ADDED
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
const fs = require("fs");
|
|
4
|
+
const path = require("path");
|
|
5
|
+
const readline = require("readline");
|
|
6
|
+
const { spawnSync } = require("child_process");
|
|
7
|
+
|
|
8
|
+
function question(rl, text) {
|
|
9
|
+
return new Promise((resolve) => rl.question(text, (a) => resolve(a.trim())));
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function isDirEmpty(dir) {
|
|
13
|
+
return fs.readdirSync(dir).length === 0;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
async function run() {
|
|
17
|
+
const args = process.argv.slice(3);
|
|
18
|
+
const yesFlag = args.includes("-y") || args.includes("--yes");
|
|
19
|
+
const positionalArgs = args.filter((a) => !a.startsWith("-"));
|
|
20
|
+
|
|
21
|
+
let projectName = positionalArgs[0] || "";
|
|
22
|
+
|
|
23
|
+
const rl = readline.createInterface({
|
|
24
|
+
input: process.stdin,
|
|
25
|
+
output: process.stdout,
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
try {
|
|
29
|
+
if (!projectName) {
|
|
30
|
+
projectName = await question(rl, "Project name: ");
|
|
31
|
+
if (!projectName) {
|
|
32
|
+
console.log("Error: project name required");
|
|
33
|
+
process.exit(1);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const projectDir = path.resolve(projectName);
|
|
38
|
+
|
|
39
|
+
if (fs.existsSync(projectDir) && !isDirEmpty(projectDir)) {
|
|
40
|
+
console.log(`Error: directory '${projectName}' already exists and is not empty`);
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
console.log(`\nCreating project in ./${projectName}...`);
|
|
45
|
+
fs.mkdirSync(path.join(projectDir, "src"), { recursive: true });
|
|
46
|
+
|
|
47
|
+
// config.purus
|
|
48
|
+
const configPurus = `-- Purus Configuration
|
|
49
|
+
|
|
50
|
+
const entry be ///src///
|
|
51
|
+
const output be ///dist///
|
|
52
|
+
const header be true
|
|
53
|
+
|
|
54
|
+
-- Linter settings
|
|
55
|
+
const lint.no-var be ///warn///
|
|
56
|
+
const lint.indent-size be 2
|
|
57
|
+
const lint.max-line-length be ///off///
|
|
58
|
+
`;
|
|
59
|
+
fs.writeFileSync(path.join(projectDir, "config.purus"), configPurus);
|
|
60
|
+
|
|
61
|
+
// .prettierrc
|
|
62
|
+
const prettierrc =
|
|
63
|
+
JSON.stringify(
|
|
64
|
+
{
|
|
65
|
+
tabWidth: 2,
|
|
66
|
+
semi: false,
|
|
67
|
+
plugins: ["@puruslang/prettier-plugin-purus"],
|
|
68
|
+
},
|
|
69
|
+
null,
|
|
70
|
+
2
|
|
71
|
+
) + "\n";
|
|
72
|
+
fs.writeFileSync(path.join(projectDir, ".prettierrc"), prettierrc);
|
|
73
|
+
|
|
74
|
+
// src/main.purus
|
|
75
|
+
const mainPurus = `-- main.purus
|
|
76
|
+
|
|
77
|
+
const message be ///Hello, World///
|
|
78
|
+
console.log[message]
|
|
79
|
+
`;
|
|
80
|
+
fs.writeFileSync(path.join(projectDir, "src/main.purus"), mainPurus);
|
|
81
|
+
|
|
82
|
+
// README.md
|
|
83
|
+
const readme = `# ${projectName}
|
|
84
|
+
|
|
85
|
+
A [Purus](https://purus.work) project.
|
|
86
|
+
|
|
87
|
+
## Getting Started
|
|
88
|
+
|
|
89
|
+
\`\`\`sh
|
|
90
|
+
npm install
|
|
91
|
+
purus build
|
|
92
|
+
\`\`\`
|
|
93
|
+
|
|
94
|
+
## Scripts
|
|
95
|
+
|
|
96
|
+
| Script | Description |
|
|
97
|
+
|---|---|
|
|
98
|
+
| \`npm run build\` | Compile Purus to JavaScript |
|
|
99
|
+
| \`npm run exec\` | Run without compiling to files |
|
|
100
|
+
| \`npm run format\` | Format with Prettier |
|
|
101
|
+
| \`npm run lint\` | Lint with purus-lint |
|
|
102
|
+
`;
|
|
103
|
+
fs.writeFileSync(path.join(projectDir, "README.md"), readme);
|
|
104
|
+
|
|
105
|
+
// .gitignore
|
|
106
|
+
const gitignore = `dist/
|
|
107
|
+
node_modules/
|
|
108
|
+
`;
|
|
109
|
+
fs.writeFileSync(path.join(projectDir, ".gitignore"), gitignore);
|
|
110
|
+
|
|
111
|
+
console.log(" config.purus");
|
|
112
|
+
console.log(" .prettierrc");
|
|
113
|
+
console.log(" .gitignore");
|
|
114
|
+
console.log(" README.md");
|
|
115
|
+
console.log(" src/main.purus");
|
|
116
|
+
|
|
117
|
+
// npm init
|
|
118
|
+
console.log("");
|
|
119
|
+
if (yesFlag) {
|
|
120
|
+
console.log("Running npm init -y...");
|
|
121
|
+
spawnSync("npm", ["init", "-y"], {
|
|
122
|
+
cwd: projectDir,
|
|
123
|
+
stdio: "inherit",
|
|
124
|
+
shell: true,
|
|
125
|
+
});
|
|
126
|
+
} else {
|
|
127
|
+
console.log("Running npm init...");
|
|
128
|
+
spawnSync("npm", ["init"], {
|
|
129
|
+
cwd: projectDir,
|
|
130
|
+
stdio: "inherit",
|
|
131
|
+
shell: true,
|
|
132
|
+
});
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Add scripts to package.json
|
|
136
|
+
const pkgPath = path.join(projectDir, "package.json");
|
|
137
|
+
if (fs.existsSync(pkgPath)) {
|
|
138
|
+
const pkg = JSON.parse(fs.readFileSync(pkgPath, "utf8"));
|
|
139
|
+
pkg.scripts = {
|
|
140
|
+
...(pkg.scripts || {}),
|
|
141
|
+
purus: "purus",
|
|
142
|
+
build: "purus build",
|
|
143
|
+
compile: "purus compile",
|
|
144
|
+
exec: "purus run",
|
|
145
|
+
format: "prettier --write ./src",
|
|
146
|
+
lint: "purus-lint",
|
|
147
|
+
};
|
|
148
|
+
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2) + "\n");
|
|
149
|
+
console.log("\nAdded scripts to package.json");
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Ask about dependencies
|
|
153
|
+
let installDeps;
|
|
154
|
+
if (yesFlag) {
|
|
155
|
+
installDeps = true;
|
|
156
|
+
} else {
|
|
157
|
+
const answer = await question(rl, "\nInstall dependencies? (y/N) ");
|
|
158
|
+
installDeps =
|
|
159
|
+
answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (installDeps) {
|
|
163
|
+
const devDeps = [
|
|
164
|
+
"@puruslang/linter",
|
|
165
|
+
"@puruslang/prettier-plugin-purus",
|
|
166
|
+
"prettier",
|
|
167
|
+
"purus",
|
|
168
|
+
];
|
|
169
|
+
console.log("\nInstalling devDependencies...");
|
|
170
|
+
for (const dep of devDeps) {
|
|
171
|
+
console.log(` ${dep}`);
|
|
172
|
+
}
|
|
173
|
+
console.log("");
|
|
174
|
+
spawnSync("npm", ["install", "--save-dev", ...devDeps], {
|
|
175
|
+
cwd: projectDir,
|
|
176
|
+
stdio: "inherit",
|
|
177
|
+
shell: true,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
console.log("\nDone! To get started:");
|
|
182
|
+
console.log(` cd ${projectName}`);
|
|
183
|
+
if (!installDeps) {
|
|
184
|
+
console.log(" npm install");
|
|
185
|
+
}
|
|
186
|
+
console.log(" purus build");
|
|
187
|
+
} finally {
|
|
188
|
+
rl.close();
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
run().catch((err) => {
|
|
193
|
+
console.error(err.message);
|
|
194
|
+
process.exit(1);
|
|
195
|
+
});
|