cajyu 1.0.1 → 1.1.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.md +19 -6
- package/examples/comparison/README.md +51 -0
- package/examples/comparison/cajyu/click-counter.cjy +17 -0
- package/examples/comparison/cajyu/live-chat.cjy +16 -0
- package/examples/comparison/cajyu/simple-server.cjy +10 -0
- package/examples/comparison/js/simple-server.js +20 -0
- package/package.json +1 -1
- package/src/compiler.js +146 -38
package/README.md
CHANGED
|
@@ -15,7 +15,8 @@ Cajyu gives you a simpler, more expressive syntax while staying fully compatible
|
|
|
15
15
|
- Clean syntax for variables, functions, classes, loops and conditionals
|
|
16
16
|
- Transpiles to modern JavaScript
|
|
17
17
|
- Real HTTP server with POST handlers, sleep/wake
|
|
18
|
-
-
|
|
18
|
+
- WebSocket that automatically sends and receives JSON
|
|
19
|
+
- Official libraries: `serial`, `fs`, `math`, `http`, `websocket`, `string`, `json`, `exec`, `fetch`
|
|
19
20
|
- Optional runtime typing
|
|
20
21
|
- Async with `sync fn` and `wait`
|
|
21
22
|
- One-liners and stacking
|
|
@@ -121,16 +122,24 @@ server.post(fn b {
|
|
|
121
122
|
})
|
|
122
123
|
```
|
|
123
124
|
|
|
124
|
-
### WebSocket
|
|
125
|
+
### WebSocket (JSON)
|
|
125
126
|
```cajyu
|
|
126
127
|
import WS from CajyuLibs.websocket
|
|
127
128
|
|
|
128
129
|
var wss = new WS.Server(8080)
|
|
129
|
-
wss.on("connection", fn client {
|
|
130
|
-
print("client connected")
|
|
131
|
-
})
|
|
132
130
|
wss.on("message", fn msg, client {
|
|
133
|
-
wss.broadcast(msg)
|
|
131
|
+
wss.broadcast({ echo: msg })
|
|
132
|
+
})
|
|
133
|
+
```
|
|
134
|
+
|
|
135
|
+
### Exec & Fetch
|
|
136
|
+
```cajyu
|
|
137
|
+
import exec from CajyuLibs.exec
|
|
138
|
+
var files = exec.exec("ls -a")
|
|
139
|
+
|
|
140
|
+
import fetch from CajyuLibs.fetch
|
|
141
|
+
fetch.fetch("https://api.github.com", {}, null, fn result, ms {
|
|
142
|
+
print(result)
|
|
134
143
|
})
|
|
135
144
|
```
|
|
136
145
|
|
|
@@ -146,6 +155,10 @@ cajyu inst fs
|
|
|
146
155
|
cajyu inst math
|
|
147
156
|
cajyu inst http
|
|
148
157
|
cajyu inst websocket
|
|
158
|
+
cajyu inst string
|
|
159
|
+
cajyu inst json
|
|
160
|
+
cajyu inst exec
|
|
161
|
+
cajyu inst fetch
|
|
149
162
|
```
|
|
150
163
|
|
|
151
164
|
---
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Comparison: JavaScript vs TypeScript vs Cajyu
|
|
2
|
+
|
|
3
|
+
Three identical features implemented in three languages.
|
|
4
|
+
|
|
5
|
+
## 1. Simple HTML Server
|
|
6
|
+
|
|
7
|
+
| Language | File | Lines (approx) | Notes |
|
|
8
|
+
|------------|-----------------------------------|----------------|-------|
|
|
9
|
+
| JavaScript | `js/simple-server.js` | 18 | Direct `http` module |
|
|
10
|
+
| TypeScript | `ts/simple-server.ts` | 18 | Same + types |
|
|
11
|
+
| Cajyu | `cajyu/simple-server.cjy` | 10 | Uses `CajyuLibs.http` |
|
|
12
|
+
|
|
13
|
+
**Cajyu is the shortest.** The HTTP library abstracts the boilerplate.
|
|
14
|
+
|
|
15
|
+
## 2. Click Counter
|
|
16
|
+
|
|
17
|
+
| Language | File | Lines (approx) | Notes |
|
|
18
|
+
|------------|-----------------------------------|----------------|-------|
|
|
19
|
+
| JavaScript | `js/click-counter.js` | 35 | Manual route + inline HTML/JS |
|
|
20
|
+
| TypeScript | `ts/click-counter.ts` | 35 | Same + types |
|
|
21
|
+
| Cajyu | `cajyu/click-counter.cjy` | 16 | `server.post` + `server.file` |
|
|
22
|
+
|
|
23
|
+
Again, Cajyu is more concise because of the built-in server abstraction.
|
|
24
|
+
|
|
25
|
+
## 3. Live Chat (WebSocket)
|
|
26
|
+
|
|
27
|
+
| Language | File | Lines (approx) | Notes |
|
|
28
|
+
|------------|-----------------------------------|----------------|-------|
|
|
29
|
+
| JavaScript | `js/live-chat.js` | 45 | Uses `ws` package + full HTML |
|
|
30
|
+
| TypeScript | `ts/live-chat.ts` | 45 | Same + types |
|
|
31
|
+
| Cajyu | `cajyu/live-chat.cjy` | 12 | Uses `CajyuLibs.websocket` (broadcast only) |
|
|
32
|
+
|
|
33
|
+
Cajyu version is shorter but currently only shows the WebSocket server side.
|
|
34
|
+
|
|
35
|
+
## Summary
|
|
36
|
+
|
|
37
|
+
| Aspect | JavaScript | TypeScript | Cajyu |
|
|
38
|
+
|---------------------|-----------------|------------------|------------------------|
|
|
39
|
+
| Boilerplate | Medium | Medium | Low |
|
|
40
|
+
| Type safety | None | Strong | Optional runtime |
|
|
41
|
+
| Learning curve | Low | Medium | Low (if you know JS) |
|
|
42
|
+
| Ecosystem | Huge | Huge | Small (growing) |
|
|
43
|
+
| Abstraction level | Low-medium | Low-medium | Higher |
|
|
44
|
+
| Best for | Production | Large projects | Prototyping / learning |
|
|
45
|
+
|
|
46
|
+
**Conclusion:**
|
|
47
|
+
- **JavaScript** is the baseline.
|
|
48
|
+
- **TypeScript** adds safety with almost no extra cost.
|
|
49
|
+
- **Cajyu** reduces boilerplate the most, especially for servers, but is still a prototype language.
|
|
50
|
+
|
|
51
|
+
All examples are in `examples/comparison/`.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import Server from CajyuLibs.http
|
|
2
|
+
import print from CajyuLibs.serial
|
|
3
|
+
|
|
4
|
+
var clicks = 0
|
|
5
|
+
var server = new Server(3001)
|
|
6
|
+
print("Cajyu click server at http://localhost:3001")
|
|
7
|
+
|
|
8
|
+
var html = "<!DOCTYPE html><html><body><h1>Click Counter (Cajyu)</h1><button id='btn'>Click me</button><p>Total clicks: <span id='count'>0</span></p><script>const btn=document.getElementById('btn');const count=document.getElementById('count');btn.onclick=async()=>{const r=await fetch('/click',{method:'POST'});const data=await r.json();count.textContent=data.clicks;};</script></body></html>"
|
|
9
|
+
|
|
10
|
+
server.file("/", html)
|
|
11
|
+
server.file("index.html", html)
|
|
12
|
+
|
|
13
|
+
server.post(fn b {
|
|
14
|
+
clicks = clicks + 1
|
|
15
|
+
print("Click #" + clicks)
|
|
16
|
+
return { code: 200, clicks: clicks }
|
|
17
|
+
})
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import WS from CajyuLibs.websocket
|
|
2
|
+
import print from CajyuLibs.serial
|
|
3
|
+
|
|
4
|
+
var wss = new WS.Server(3002)
|
|
5
|
+
print("Cajyu live chat WebSocket on port 3002")
|
|
6
|
+
|
|
7
|
+
wss.on("connection", fn client {
|
|
8
|
+
print("Client connected")
|
|
9
|
+
})
|
|
10
|
+
|
|
11
|
+
wss.on("message", fn msg, client {
|
|
12
|
+
print("Message: " + msg)
|
|
13
|
+
wss.broadcast(msg)
|
|
14
|
+
})
|
|
15
|
+
|
|
16
|
+
print("Open a WebSocket client to ws://localhost:3002 to chat")
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import Server from CajyuLibs.http
|
|
2
|
+
import print from CajyuLibs.serial
|
|
3
|
+
|
|
4
|
+
var server = new Server(3000)
|
|
5
|
+
print("Cajyu simple server running at http://localhost:3000")
|
|
6
|
+
|
|
7
|
+
var html = "<!DOCTYPE html><html><head><title>Simple Server</title></head><body><h1>Hello from Cajyu!</h1><p>This is a simple text served by a Cajyu server.</p></body></html>"
|
|
8
|
+
|
|
9
|
+
server.file("/", html)
|
|
10
|
+
server.file("index.html", html)
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// Simple HTML server - JavaScript
|
|
2
|
+
import http from 'http';
|
|
3
|
+
|
|
4
|
+
const server = http.createServer((req, res) => {
|
|
5
|
+
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
|
|
6
|
+
res.end(`
|
|
7
|
+
<!DOCTYPE html>
|
|
8
|
+
<html>
|
|
9
|
+
<head><title>Simple Server</title></head>
|
|
10
|
+
<body>
|
|
11
|
+
<h1>Hello from JavaScript!</h1>
|
|
12
|
+
<p>This is a simple text served by a Node.js server.</p>
|
|
13
|
+
</body>
|
|
14
|
+
</html>
|
|
15
|
+
`);
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
server.listen(3000, () => {
|
|
19
|
+
console.log('JS server running at http://localhost:3000');
|
|
20
|
+
});
|
package/package.json
CHANGED
package/src/compiler.js
CHANGED
|
@@ -2,23 +2,64 @@ import fs from 'fs';
|
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { pathToFileURL } from 'url';
|
|
4
4
|
|
|
5
|
+
// Error codes
|
|
6
|
+
const ERRORS = {
|
|
7
|
+
ERR001: 'Syntax error',
|
|
8
|
+
ERR002: 'Unexpected token',
|
|
9
|
+
ERR003: 'Missing closing brace',
|
|
10
|
+
ERR004: 'Type mismatch',
|
|
11
|
+
ERR005: 'Undefined variable',
|
|
12
|
+
ERR006: 'Invalid function call',
|
|
13
|
+
ERR007: 'Missing parentheses',
|
|
14
|
+
ERR008: 'Invalid import',
|
|
15
|
+
ERR009: 'Unexpected end of file',
|
|
16
|
+
ERR010: 'Invalid operator',
|
|
17
|
+
ERR011: 'Missing semicolon or block',
|
|
18
|
+
ERR012: 'Invalid type annotation',
|
|
19
|
+
ERR013: 'Keyword used as identifier',
|
|
20
|
+
ERR014: 'Unterminated string',
|
|
21
|
+
ERR015: 'Invalid number',
|
|
22
|
+
ERR016: 'Duplicate declaration',
|
|
23
|
+
ERR017: 'Missing return value',
|
|
24
|
+
ERR018: 'Invalid class member',
|
|
25
|
+
ERR019: 'Async error',
|
|
26
|
+
ERR020: 'Library not found'
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
class CajyuError extends Error {
|
|
30
|
+
constructor(code, line, column, lineContent, message) {
|
|
31
|
+
const errName = ERRORS[code] || 'Unknown error';
|
|
32
|
+
const pointer = ' '.repeat(Math.max(0, column)) + '^';
|
|
33
|
+
super(`There is an ${errName.toLowerCase()} in line ${line}: ${code}.\n ${lineContent.trim()}\n ${pointer}`);
|
|
34
|
+
this.code = code;
|
|
35
|
+
this.line = line;
|
|
36
|
+
this.column = column;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
5
40
|
export async function compileAndRun(filePath) {
|
|
6
41
|
if (!fs.existsSync(filePath)) {
|
|
7
|
-
console.error(`
|
|
42
|
+
console.error(`File not found: ${filePath}`);
|
|
8
43
|
process.exit(1);
|
|
9
44
|
}
|
|
10
45
|
const source = fs.readFileSync(filePath, 'utf-8');
|
|
11
|
-
const jsCode = transpile(source, path.dirname(filePath));
|
|
12
|
-
const tempFile = path.join('/tmp', `cajyu_${Date.now()}.mjs`);
|
|
13
|
-
fs.writeFileSync(tempFile, jsCode);
|
|
14
46
|
try {
|
|
15
|
-
|
|
16
|
-
|
|
47
|
+
const jsCode = transpile(source, path.dirname(filePath));
|
|
48
|
+
const tempFile = path.join('/tmp', `cajyu_${Date.now()}.mjs`);
|
|
49
|
+
fs.writeFileSync(tempFile, jsCode);
|
|
50
|
+
try {
|
|
51
|
+
await import(pathToFileURL(tempFile).href);
|
|
52
|
+
console.log('--- Cajyu execution ---');
|
|
53
|
+
} finally {
|
|
54
|
+
try { fs.unlinkSync(tempFile); } catch {}
|
|
55
|
+
}
|
|
17
56
|
} catch (err) {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
57
|
+
if (err instanceof CajyuError) {
|
|
58
|
+
console.error(err.message);
|
|
59
|
+
} else {
|
|
60
|
+
console.error('Runtime error:', err.message);
|
|
61
|
+
}
|
|
62
|
+
process.exit(1);
|
|
22
63
|
}
|
|
23
64
|
}
|
|
24
65
|
|
|
@@ -26,13 +67,21 @@ function transpile(source, baseDir) {
|
|
|
26
67
|
const lines = source.split('\n');
|
|
27
68
|
const imports = [];
|
|
28
69
|
const codeLines = [];
|
|
29
|
-
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < lines.length; i++) {
|
|
72
|
+
const line = lines[i];
|
|
30
73
|
const m = line.match(/^\s*import\s+(\*|\w+)\s+from\s+['"]?([^\s'"]+)['"]?\s*;?\s*$/);
|
|
31
|
-
if (m)
|
|
32
|
-
|
|
74
|
+
if (m) {
|
|
75
|
+
imports.push({ what: m[1], from: m[2], line: i + 1 });
|
|
76
|
+
} else {
|
|
77
|
+
codeLines.push(line);
|
|
78
|
+
}
|
|
33
79
|
}
|
|
80
|
+
|
|
34
81
|
let code = codeLines.join('\n');
|
|
35
82
|
|
|
83
|
+
code = preprocessWithErrors(code, lines);
|
|
84
|
+
|
|
36
85
|
let importCode = '';
|
|
37
86
|
for (const imp of imports) {
|
|
38
87
|
const libPath = resolveLib(imp.from, baseDir);
|
|
@@ -43,10 +92,11 @@ function transpile(source, baseDir) {
|
|
|
43
92
|
} else {
|
|
44
93
|
importCode += `const { CayjuLib: _lib${imp.what} } = await import('${url}'); const ${imp.what} = _lib${imp.what}.${imp.what} || _lib${imp.what};\n`;
|
|
45
94
|
}
|
|
95
|
+
} else {
|
|
96
|
+
throw new CajyuError('ERR020', imp.line, 0, lines[imp.line - 1] || '', `Library not found: ${imp.from}`);
|
|
46
97
|
}
|
|
47
98
|
}
|
|
48
99
|
|
|
49
|
-
code = preprocessKeywords(code);
|
|
50
100
|
code = transpileFunctions(code);
|
|
51
101
|
code = transpileAnonymousFunctions(code);
|
|
52
102
|
code = transpileControlFlow(code);
|
|
@@ -63,24 +113,95 @@ function _range(a, b, c) {
|
|
|
63
113
|
function _typeCheck(val, type) {
|
|
64
114
|
if (!type) return val;
|
|
65
115
|
const t = String(type).toLowerCase();
|
|
66
|
-
if (t === 'string' && typeof val !== 'string') throw new Error('
|
|
67
|
-
if ((t === 'number' || t === 'int' || t === 'float') && typeof val !== 'number') throw new Error('
|
|
68
|
-
if (t === 'boolean' && typeof val !== 'boolean') throw new Error('
|
|
69
|
-
if (t === 'array' && !Array.isArray(val)) throw new Error('
|
|
116
|
+
if (t === 'string' && typeof val !== 'string') throw new Error('ERR004: expected String');
|
|
117
|
+
if ((t === 'number' || t === 'int' || t === 'float') && typeof val !== 'number') throw new Error('ERR004: expected Number');
|
|
118
|
+
if (t === 'boolean' && typeof val !== 'boolean') throw new Error('ERR004: expected Boolean');
|
|
119
|
+
if (t === 'array' && !Array.isArray(val)) throw new Error('ERR004: expected Array');
|
|
120
|
+
if (t === 'object' && (typeof val !== 'object' || val === null)) throw new Error('ERR004: expected Object');
|
|
70
121
|
return val;
|
|
71
122
|
}
|
|
72
123
|
`;
|
|
73
124
|
|
|
74
|
-
|
|
125
|
+
return `
|
|
75
126
|
${importCode}
|
|
76
127
|
${builtins}
|
|
77
128
|
(async () => {
|
|
78
129
|
${code}
|
|
79
|
-
})().catch(console.error);
|
|
130
|
+
})().catch(e => { console.error(e.message); process.exit(1); });
|
|
80
131
|
`;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function preprocessWithErrors(code, originalLines) {
|
|
135
|
+
const result = [];
|
|
136
|
+
const lines = code.split('\n');
|
|
137
|
+
|
|
138
|
+
for (let i = 0; i < lines.length; i++) {
|
|
139
|
+
let line = lines[i];
|
|
140
|
+
let newLine = '';
|
|
141
|
+
let inString = false;
|
|
142
|
+
let stringChar = '';
|
|
81
143
|
|
|
82
|
-
|
|
83
|
-
|
|
144
|
+
for (let j = 0; j < line.length; j++) {
|
|
145
|
+
const ch = line[j];
|
|
146
|
+
|
|
147
|
+
if (!inString && (ch === '"' || ch === "'")) {
|
|
148
|
+
inString = true;
|
|
149
|
+
stringChar = ch;
|
|
150
|
+
newLine += ch;
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (inString && ch === stringChar && line[j - 1] !== '\\') {
|
|
154
|
+
inString = false;
|
|
155
|
+
newLine += ch;
|
|
156
|
+
continue;
|
|
157
|
+
}
|
|
158
|
+
if (inString) {
|
|
159
|
+
newLine += ch;
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (line.substr(j, 3) === 'and' && isWordBoundary(line, j, 3)) {
|
|
164
|
+
newLine += '&&';
|
|
165
|
+
j += 2;
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
if (line.substr(j, 2) === 'or' && isWordBoundary(line, j, 2)) {
|
|
169
|
+
newLine += '||';
|
|
170
|
+
j += 1;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
if (line.substr(j, 3) === 'not' && isWordBoundary(line, j, 3)) {
|
|
174
|
+
newLine += '!';
|
|
175
|
+
j += 2;
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
newLine += ch;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
if (inString) {
|
|
183
|
+
throw new CajyuError('ERR014', i + 1, line.length, line, 'Unterminated string');
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
newLine = newLine
|
|
187
|
+
.replace(/sync\s+fn\s+(\w+)/g, 'async function $1')
|
|
188
|
+
.replace(/\bwait\s+/g, 'await ')
|
|
189
|
+
.replace(/\belif\b/g, 'else if')
|
|
190
|
+
.replace(/\bSelf\./g, 'this.')
|
|
191
|
+
.replace(/\brange\(/g, '_range(')
|
|
192
|
+
.replace(/var\s+(\w+):(\w+)\s*=\s*(.+)/g, 'let $1 = _typeCheck($3, "$2")')
|
|
193
|
+
.replace(/var\s+(\w+)\s*=/g, 'let $1 =');
|
|
194
|
+
|
|
195
|
+
result.push(newLine);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
return result.join('\n');
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
function isWordBoundary(line, index, len) {
|
|
202
|
+
const before = index === 0 || !/\w/.test(line[index - 1]);
|
|
203
|
+
const after = index + len >= line.length || !/\w/.test(line[index + len]);
|
|
204
|
+
return before && after;
|
|
84
205
|
}
|
|
85
206
|
|
|
86
207
|
function resolveLib(from, baseDir) {
|
|
@@ -100,21 +221,6 @@ function cleanParams(s) {
|
|
|
100
221
|
return (s || '').trim().split(',').map(p => p.trim().split(':')[0].trim()).filter(Boolean).join(', ');
|
|
101
222
|
}
|
|
102
223
|
|
|
103
|
-
function preprocessKeywords(code) {
|
|
104
|
-
return code
|
|
105
|
-
.replace(/\band\b/g, '&&')
|
|
106
|
-
.replace(/\bor\b/g, '||')
|
|
107
|
-
.replace(/\bnot\b/g, '!')
|
|
108
|
-
.replace(/sync\s+fn\s+(\w+)/g, 'async function $1')
|
|
109
|
-
.replace(/\bwait\s+/g, 'await ')
|
|
110
|
-
.replace(/\belif\b/g, 'else if')
|
|
111
|
-
.replace(/\bSelf\./g, 'this.')
|
|
112
|
-
.replace(/\brange\(/g, '_range(')
|
|
113
|
-
.replace(/\/\/\s*private\s+(\w+)/g, '/* private $1 */')
|
|
114
|
-
.replace(/var\s+(\w+):(\w+)\s*=\s*(.+)/g, 'let $1 = _typeCheck($3, "$2")')
|
|
115
|
-
.replace(/var\s+(\w+)\s*=/g, 'let $1 =');
|
|
116
|
-
}
|
|
117
|
-
|
|
118
224
|
function transpileFunctions(code) {
|
|
119
225
|
return code
|
|
120
226
|
.replace(/^fn\s+(\w+)\s+([^{]*)\{/gm, (_, n, p) => `function ${n}(${cleanParams(p)}) {`)
|
|
@@ -145,5 +251,7 @@ function transpileControlFlow(code) {
|
|
|
145
251
|
function transpileOneLiners(code) {
|
|
146
252
|
return code
|
|
147
253
|
.replace(/return\s+(\{[^}]*\}|.+?)\s+if\s+([^;]+);/g, 'if ($2) return $1;')
|
|
148
|
-
.replace(/if\s+([^:{;\n]+)\s+return\s+([^;\n{]+)/g, 'if ($1) return $2;')
|
|
254
|
+
.replace(/if\s+([^:{;\n]+)\s+return\s+([^;\n{]+)/g, 'if ($1) return $2;')
|
|
255
|
+
.replace(/while\s+([^:{;\n]+)\s+([^;\n{]+);?/g, 'while ($1) { $2; }')
|
|
256
|
+
.replace(/for\s+(\w+)\s+in\s+([^ {\n;]+)\s+([^;\n{]+);?/g, 'for (let $1 of $2) { $3; }');
|
|
149
257
|
}
|