cajyu 1.0.1 → 1.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.md +23 -6
- package/bin/cajyu.js +1 -1
- package/logos/README.md +8 -0
- package/logos/logo-128.png +0 -0
- package/logos/logo-256.png +0 -0
- package/logos/logo-64.png +0 -0
- package/package.json +1 -1
- package/src/compiler-ast-note.md +16 -0
- package/src/compiler.js +146 -38
package/README.md
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
# Cajyu
|
|
2
2
|
|
|
3
|
+
<p align="center">
|
|
4
|
+
<img src="https://raw.githubusercontent.com/abs-js/Cajyu/main/logos/logo-128.png" alt="Cajyu Logo" width="128">
|
|
5
|
+
</p>
|
|
6
|
+
|
|
3
7
|
**A clean, practical programming language that transpiles to JavaScript.**
|
|
4
8
|
|
|
5
9
|
Cajyu gives you a simpler, more expressive syntax while staying fully compatible with the JavaScript ecosystem.
|
|
@@ -15,7 +19,8 @@ Cajyu gives you a simpler, more expressive syntax while staying fully compatible
|
|
|
15
19
|
- Clean syntax for variables, functions, classes, loops and conditionals
|
|
16
20
|
- Transpiles to modern JavaScript
|
|
17
21
|
- Real HTTP server with POST handlers, sleep/wake
|
|
18
|
-
-
|
|
22
|
+
- WebSocket that automatically sends and receives JSON
|
|
23
|
+
- Official libraries: `serial`, `fs`, `math`, `http`, `websocket`, `string`, `json`, `exec`, `fetch`
|
|
19
24
|
- Optional runtime typing
|
|
20
25
|
- Async with `sync fn` and `wait`
|
|
21
26
|
- One-liners and stacking
|
|
@@ -121,16 +126,24 @@ server.post(fn b {
|
|
|
121
126
|
})
|
|
122
127
|
```
|
|
123
128
|
|
|
124
|
-
### WebSocket
|
|
129
|
+
### WebSocket (JSON)
|
|
125
130
|
```cajyu
|
|
126
131
|
import WS from CajyuLibs.websocket
|
|
127
132
|
|
|
128
133
|
var wss = new WS.Server(8080)
|
|
129
|
-
wss.on("connection", fn client {
|
|
130
|
-
print("client connected")
|
|
131
|
-
})
|
|
132
134
|
wss.on("message", fn msg, client {
|
|
133
|
-
wss.broadcast(msg)
|
|
135
|
+
wss.broadcast({ echo: msg })
|
|
136
|
+
})
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### Exec & Fetch
|
|
140
|
+
```cajyu
|
|
141
|
+
import exec from CajyuLibs.exec
|
|
142
|
+
var files = exec.exec("ls -a")
|
|
143
|
+
|
|
144
|
+
import fetch from CajyuLibs.fetch
|
|
145
|
+
fetch.fetch("https://api.github.com", {}, null, fn result, ms {
|
|
146
|
+
print(result)
|
|
134
147
|
})
|
|
135
148
|
```
|
|
136
149
|
|
|
@@ -146,6 +159,10 @@ cajyu inst fs
|
|
|
146
159
|
cajyu inst math
|
|
147
160
|
cajyu inst http
|
|
148
161
|
cajyu inst websocket
|
|
162
|
+
cajyu inst string
|
|
163
|
+
cajyu inst json
|
|
164
|
+
cajyu inst exec
|
|
165
|
+
cajyu inst fetch
|
|
149
166
|
```
|
|
150
167
|
|
|
151
168
|
---
|
package/bin/cajyu.js
CHANGED
|
@@ -9,7 +9,7 @@ const __filename = fileURLToPath(import.meta.url);
|
|
|
9
9
|
const __dirname = path.dirname(__filename);
|
|
10
10
|
|
|
11
11
|
const CAJYULIBS_REPO = 'https://github.com/abs-js/CajyuLibs.git';
|
|
12
|
-
const VERSION = '1.
|
|
12
|
+
const VERSION = '1.2.0';
|
|
13
13
|
|
|
14
14
|
const args = process.argv.slice(2);
|
|
15
15
|
const command = args[0];
|
package/logos/README.md
ADDED
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/package.json
CHANGED
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# AST Migration in progress
|
|
2
|
+
|
|
3
|
+
The compiler now has a real Tokenizer + recursive descent Parser that builds an AST.
|
|
4
|
+
|
|
5
|
+
Current status:
|
|
6
|
+
- Tokenizer handles strings, numbers, identifiers, keywords correctly
|
|
7
|
+
- Parser builds AST for var, fn, if/elif/else, while, for, class, return, expressions
|
|
8
|
+
- Code generator emits JavaScript from the AST
|
|
9
|
+
|
|
10
|
+
Still being completed:
|
|
11
|
+
- Full import resolution
|
|
12
|
+
- Builtins (range, typeCheck)
|
|
13
|
+
- Async (sync/wait)
|
|
14
|
+
- One-liners and more edge cases
|
|
15
|
+
|
|
16
|
+
This is the foundation for a robust parser.
|
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
|
}
|