naider 1.24.5 → 2.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/README.md +133 -112
- package/SPEC.naide +8 -6
- package/bin/naide.js +509 -124
- package/bin/postinstall.js +5 -15
- package/examples/model.naide +1 -1
- package/lsp/server.js +210 -101
- package/mcp/server.js +247 -22
- package/package.json +8 -5
- package/src/gen.js +853 -324
- package/src/generator-c.js +34 -14
- package/src/generator-go.js +26 -0
- package/src/generator.js +5 -1
- package/src/index.js +34 -14
- package/src/parser.js +15 -14
- package/src/project-tools.js +785 -0
- package/src/typechecker.js +147 -18
- package/vscode-naide/README.md +4 -6
- package/vscode-naide/extension.js +1 -2
- package/vscode-naide/package-lock.json +2 -2
- package/vscode-naide/package.json +3 -15
- package/SPEC-X.nx +0 -276
- package/assets/nx.ico +0 -0
- package/assets/nx_check.png +0 -0
- package/examples/ai-app.nx +0 -21
- package/examples/async.nx +0 -40
- package/examples/crud.nx +0 -64
- package/examples/fullapp.nx +0 -74
- package/examples/hello.nx +0 -29
- package/examples/server.nx +0 -27
- package/examples/sql-app.nx +0 -23
- package/examples/todo-api.nx +0 -31
- package/src/preprocess.js +0 -308
- package/vscode-naide/language-configuration-nx.json +0 -32
- package/vscode-naide/naide-lang-1.1.0.vsix +0 -0
- package/vscode-naide/syntaxes/naidex.tmLanguage.json +0 -287
package/src/typechecker.js
CHANGED
|
@@ -1,9 +1,19 @@
|
|
|
1
|
+
const EXPRESSION_TYPES = new Set([
|
|
2
|
+
'Number', 'String', 'InterpolatedString', 'Bool', 'Null', 'Array', 'Object',
|
|
3
|
+
'Identifier', 'Binary', 'Unary', 'Call', 'MemberAccess', 'Ternary', 'Await',
|
|
4
|
+
'ArrowFunction', 'Spread', 'New', 'IndexAccess',
|
|
5
|
+
]);
|
|
6
|
+
|
|
1
7
|
export class TypeChecker {
|
|
2
8
|
constructor() {
|
|
3
9
|
this.scopes = [new Map()];
|
|
4
10
|
this.errors = [];
|
|
5
11
|
this.warnings = [];
|
|
12
|
+
this.errorKeys = new Set();
|
|
13
|
+
this.warningKeys = new Set();
|
|
14
|
+
this.visitedStatements = new WeakSet();
|
|
6
15
|
this.functions = new Map();
|
|
16
|
+
this.returnTypes = [];
|
|
7
17
|
}
|
|
8
18
|
|
|
9
19
|
pushScope() { this.scopes.push(new Map()); }
|
|
@@ -21,18 +31,37 @@ export class TypeChecker {
|
|
|
21
31
|
}
|
|
22
32
|
|
|
23
33
|
error(msg, line) {
|
|
24
|
-
|
|
34
|
+
const diagnostic = { message: msg, line: line || 0, severity: 'error' };
|
|
35
|
+
const key = `${diagnostic.line}:${diagnostic.message}`;
|
|
36
|
+
if (this.errorKeys.has(key)) return;
|
|
37
|
+
this.errorKeys.add(key);
|
|
38
|
+
this.errors.push(diagnostic);
|
|
25
39
|
}
|
|
26
40
|
|
|
27
41
|
warn(msg, line) {
|
|
28
|
-
|
|
42
|
+
const diagnostic = { message: msg, line: line || 0, severity: 'warning' };
|
|
43
|
+
const key = `${diagnostic.line}:${diagnostic.message}`;
|
|
44
|
+
if (this.warningKeys.has(key)) return;
|
|
45
|
+
this.warningKeys.add(key);
|
|
46
|
+
this.warnings.push(diagnostic);
|
|
29
47
|
}
|
|
30
48
|
|
|
31
49
|
check(ast) {
|
|
50
|
+
for (const stmt of ast.body) {
|
|
51
|
+
if (stmt.type === 'Function') this.registerFunction(stmt);
|
|
52
|
+
}
|
|
32
53
|
this.visitProgram(ast);
|
|
33
54
|
return { errors: this.errors, warnings: this.warnings };
|
|
34
55
|
}
|
|
35
56
|
|
|
57
|
+
registerFunction(node) {
|
|
58
|
+
this.functions.set(node.name, {
|
|
59
|
+
params: node.params.map(p => ({ name: p.name, type: p.type || 'any', hasDefault: p.defaultValue !== null, spread: p.spread })),
|
|
60
|
+
returnType: node.returnType || 'any',
|
|
61
|
+
isAsync: node.isAsync || false,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
|
|
36
65
|
visitProgram(node) {
|
|
37
66
|
for (const stmt of node.body) {
|
|
38
67
|
this.visitStatement(stmt);
|
|
@@ -40,7 +69,10 @@ export class TypeChecker {
|
|
|
40
69
|
}
|
|
41
70
|
|
|
42
71
|
visitStatement(node) {
|
|
72
|
+
if (this.visitedStatements.has(node)) return;
|
|
73
|
+
this.visitedStatements.add(node);
|
|
43
74
|
const line = node._line || 0;
|
|
75
|
+
this.checkExpressions(node);
|
|
44
76
|
switch (node.type) {
|
|
45
77
|
case 'TypedVar': return this.visitTypedVar(node, line);
|
|
46
78
|
case 'Function': return this.visitFunction(node, line);
|
|
@@ -51,11 +83,59 @@ export class TypeChecker {
|
|
|
51
83
|
case 'For': return this.visitFor(node, line);
|
|
52
84
|
case 'While': return this.visitWhile(node, line);
|
|
53
85
|
case 'Try': return this.visitTry(node, line);
|
|
54
|
-
case 'Server': return this.
|
|
55
|
-
default: return;
|
|
86
|
+
case 'Server': return this.visitServer(node, line);
|
|
87
|
+
default: return this.visitNestedStatements(node);
|
|
56
88
|
}
|
|
57
89
|
}
|
|
58
90
|
|
|
91
|
+
checkExpressions(node) {
|
|
92
|
+
const seen = new WeakSet();
|
|
93
|
+
const visit = value => {
|
|
94
|
+
if (Array.isArray(value)) {
|
|
95
|
+
if (seen.has(value)) return;
|
|
96
|
+
seen.add(value);
|
|
97
|
+
for (const item of value) visit(item);
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (!value || typeof value !== 'object') return;
|
|
101
|
+
if (seen.has(value)) return;
|
|
102
|
+
seen.add(value);
|
|
103
|
+
|
|
104
|
+
if (typeof value.type === 'string') {
|
|
105
|
+
if (!EXPRESSION_TYPES.has(value.type)) return;
|
|
106
|
+
this.inferType(value);
|
|
107
|
+
for (const [key, child] of Object.entries(value)) {
|
|
108
|
+
if (key !== 'type' && key !== '_line') visit(child);
|
|
109
|
+
}
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
for (const child of Object.values(value)) visit(child);
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
for (const [key, value] of Object.entries(node)) {
|
|
117
|
+
if (key !== 'type' && key !== '_line') visit(value);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
visitNestedStatements(value, seen = new WeakSet()) {
|
|
122
|
+
if (Array.isArray(value)) {
|
|
123
|
+
if (seen.has(value)) return;
|
|
124
|
+
seen.add(value);
|
|
125
|
+
for (const item of value) this.visitNestedStatements(item, seen);
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (!value || typeof value !== 'object') return;
|
|
129
|
+
if (seen.has(value)) return;
|
|
130
|
+
seen.add(value);
|
|
131
|
+
if (typeof value.type === 'string') {
|
|
132
|
+
if (EXPRESSION_TYPES.has(value.type)) return;
|
|
133
|
+
this.visitStatement(value);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
for (const child of Object.values(value)) this.visitNestedStatements(child, seen);
|
|
137
|
+
}
|
|
138
|
+
|
|
59
139
|
visitBlock(body, _line) {
|
|
60
140
|
if (!body) return;
|
|
61
141
|
this.pushScope();
|
|
@@ -80,26 +160,20 @@ export class TypeChecker {
|
|
|
80
160
|
}
|
|
81
161
|
|
|
82
162
|
visitFunction(node, line) {
|
|
83
|
-
|
|
84
|
-
for (const p of node.params) {
|
|
85
|
-
paramTypes[p.name] = p.varType || 'any';
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
this.functions.set(node.name, {
|
|
89
|
-
params: node.params.map(p => ({ name: p.name, type: p.varType || 'any' })),
|
|
90
|
-
returnType: node.returnType || 'any',
|
|
91
|
-
isAsync: node.isAsync || false,
|
|
92
|
-
});
|
|
163
|
+
this.registerFunction(node);
|
|
93
164
|
|
|
94
165
|
this.setType(node.name, 'function');
|
|
95
166
|
|
|
96
167
|
this.pushScope();
|
|
97
168
|
for (const p of node.params) {
|
|
98
|
-
this.setType(p.name, p.
|
|
169
|
+
this.setType(p.name, p.type || 'any');
|
|
170
|
+
if (p.defaultValue) this.inferType(p.defaultValue);
|
|
99
171
|
}
|
|
172
|
+
this.returnTypes.push(node.returnType || 'any');
|
|
100
173
|
for (const stmt of node.body) {
|
|
101
174
|
this.visitStatement(stmt);
|
|
102
175
|
}
|
|
176
|
+
this.returnTypes.pop();
|
|
103
177
|
this.popScope();
|
|
104
178
|
}
|
|
105
179
|
|
|
@@ -118,7 +192,22 @@ export class TypeChecker {
|
|
|
118
192
|
}
|
|
119
193
|
}
|
|
120
194
|
|
|
121
|
-
visitReturn(node, line) {
|
|
195
|
+
visitReturn(node, line) {
|
|
196
|
+
const expectedType = this.returnTypes[this.returnTypes.length - 1];
|
|
197
|
+
if (!expectedType || expectedType === 'any') return;
|
|
198
|
+
|
|
199
|
+
if (!node.value) {
|
|
200
|
+
if (expectedType !== 'void') {
|
|
201
|
+
this.error(`Return type mismatch: expected ${expectedType} but got void`, line);
|
|
202
|
+
}
|
|
203
|
+
return;
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const actualType = this.inferType(node.value);
|
|
207
|
+
if (actualType !== 'any' && !this.typesCompatible(expectedType, actualType)) {
|
|
208
|
+
this.error(`Return type mismatch: expected ${expectedType} but got ${actualType}`, line);
|
|
209
|
+
}
|
|
210
|
+
}
|
|
122
211
|
|
|
123
212
|
visitIf(node, line) {
|
|
124
213
|
this.visitBlock(node.body, line);
|
|
@@ -168,6 +257,19 @@ export class TypeChecker {
|
|
|
168
257
|
}
|
|
169
258
|
}
|
|
170
259
|
|
|
260
|
+
visitServer(node, line) {
|
|
261
|
+
for (const route of node.routes || []) {
|
|
262
|
+
this.pushScope();
|
|
263
|
+
for (const param of route.params || []) {
|
|
264
|
+
this.setType(typeof param === 'string' ? param : param.name, 'any');
|
|
265
|
+
}
|
|
266
|
+
this.visitBlock(route.body, line);
|
|
267
|
+
this.popScope();
|
|
268
|
+
}
|
|
269
|
+
this.visitNestedStatements(node.middleware || []);
|
|
270
|
+
this.visitNestedStatements(node.body || []);
|
|
271
|
+
}
|
|
272
|
+
|
|
171
273
|
inferType(node) {
|
|
172
274
|
if (!node) return 'any';
|
|
173
275
|
switch (node.type) {
|
|
@@ -177,7 +279,7 @@ export class TypeChecker {
|
|
|
177
279
|
}
|
|
178
280
|
case 'String': return 'str';
|
|
179
281
|
case 'InterpolatedString': return 'str';
|
|
180
|
-
case '
|
|
282
|
+
case 'Bool': return 'bool';
|
|
181
283
|
case 'Null': return 'any';
|
|
182
284
|
case 'Array': return 'list';
|
|
183
285
|
case 'Object': return 'map';
|
|
@@ -220,7 +322,10 @@ export class TypeChecker {
|
|
|
220
322
|
inferCallType(node) {
|
|
221
323
|
if (node.callee.type === 'Identifier') {
|
|
222
324
|
const fn = this.functions.get(node.callee.name);
|
|
223
|
-
if (fn)
|
|
325
|
+
if (fn) {
|
|
326
|
+
this.checkCallArguments(node, fn);
|
|
327
|
+
return fn.returnType;
|
|
328
|
+
}
|
|
224
329
|
|
|
225
330
|
const builtins = {
|
|
226
331
|
'uuid': 'str', 'hash': 'str', 'verify': 'bool',
|
|
@@ -251,6 +356,30 @@ export class TypeChecker {
|
|
|
251
356
|
return 'any';
|
|
252
357
|
}
|
|
253
358
|
|
|
359
|
+
checkCallArguments(node, fn) {
|
|
360
|
+
const params = fn.params;
|
|
361
|
+
const line = node._line || 0;
|
|
362
|
+
const requiredCount = params.filter(p => !p.hasDefault && !p.spread).length;
|
|
363
|
+
const hasSpread = params.some(p => p.spread);
|
|
364
|
+
|
|
365
|
+
if (node.args.length < requiredCount || (!hasSpread && node.args.length > params.length)) {
|
|
366
|
+
const expected = hasSpread ? `at least ${requiredCount}` : `${requiredCount}${params.length === requiredCount ? '' : ` to ${params.length}`}`;
|
|
367
|
+
this.error(`Argument count mismatch for '${node.callee.name}': expected ${expected}, got ${node.args.length}`, line);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const spreadIndex = params.findIndex(param => param.spread);
|
|
371
|
+
for (let i = 0; i < node.args.length; i++) {
|
|
372
|
+
const paramIndex = spreadIndex >= 0 && i >= spreadIndex ? spreadIndex : i;
|
|
373
|
+
const param = params[paramIndex];
|
|
374
|
+
if (!param) continue;
|
|
375
|
+
const expectedType = param.type;
|
|
376
|
+
const actualType = this.inferType(node.args[i]);
|
|
377
|
+
if (expectedType !== 'any' && actualType !== 'any' && !this.typesCompatible(expectedType, actualType)) {
|
|
378
|
+
this.error(`Argument '${param.name}' of '${node.callee.name}' expects ${expectedType} but got ${actualType}`, line);
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
254
383
|
typesCompatible(declared, actual) {
|
|
255
384
|
if (declared === actual) return true;
|
|
256
385
|
if (declared === 'any' || actual === 'any') return true;
|
package/vscode-naide/README.md
CHANGED
|
@@ -1,22 +1,20 @@
|
|
|
1
1
|
# NAIDE Language Support for VS Code
|
|
2
2
|
|
|
3
|
-
Syntax highlighting and language support for **NAIDE** (`.naide`)
|
|
3
|
+
Syntax highlighting and language support for **NAIDE** (`.naide`) files.
|
|
4
4
|
|
|
5
5
|
## Features
|
|
6
6
|
|
|
7
7
|
- Syntax highlighting for all NAIDE keywords, types, and operators
|
|
8
|
-
- Syntax highlighting for NAIDE-X shorthand prefixes (`>`, `?`, `!`, `$`, `^`, `@`, etc.)
|
|
9
8
|
- String interpolation highlighting (`"Hello {name}"`)
|
|
10
9
|
- Auto-indentation for block structures
|
|
11
10
|
- Bracket matching and auto-closing
|
|
12
|
-
- Comment toggling (`#`
|
|
11
|
+
- Comment toggling (`#`)
|
|
13
12
|
|
|
14
13
|
## File Associations
|
|
15
14
|
|
|
16
15
|
| Extension | Language |
|
|
17
16
|
|-----------|----------|
|
|
18
17
|
| `.naide` | NAIDE |
|
|
19
|
-
| `.nx` | NAIDE-X |
|
|
20
18
|
|
|
21
19
|
## Install
|
|
22
20
|
|
|
@@ -26,7 +24,7 @@ Syntax highlighting and language support for **NAIDE** (`.naide`) and **NAIDE-X*
|
|
|
26
24
|
cd vscode-naide
|
|
27
25
|
npm install -g @vscode/vsce
|
|
28
26
|
vsce package
|
|
29
|
-
code --install-extension naide-lang-
|
|
27
|
+
code --install-extension naide-lang-2.0.1.vsix
|
|
30
28
|
```
|
|
31
29
|
|
|
32
30
|
### Manual
|
|
@@ -35,4 +33,4 @@ Copy `vscode-naide/` to `~/.vscode/extensions/naide-lang/` and restart VS Code.
|
|
|
35
33
|
|
|
36
34
|
## About NAIDE
|
|
37
35
|
|
|
38
|
-
NAIDE is
|
|
36
|
+
NAIDE is a programming language with one readable syntax, built-in diagnostics, and compiler targets. See [github.com/irxk/naide](https://github.com/irxk/naide).
|
|
@@ -18,10 +18,9 @@ function activate(context) {
|
|
|
18
18
|
const clientOptions = {
|
|
19
19
|
documentSelector: [
|
|
20
20
|
{ scheme: 'file', language: 'naide' },
|
|
21
|
-
{ scheme: 'file', language: 'naidex' },
|
|
22
21
|
],
|
|
23
22
|
synchronize: {
|
|
24
|
-
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.
|
|
23
|
+
fileEvents: vscode.workspace.createFileSystemWatcher('**/*.naide'),
|
|
25
24
|
},
|
|
26
25
|
};
|
|
27
26
|
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naide-lang",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.1",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "naide-lang",
|
|
9
|
-
"version": "
|
|
9
|
+
"version": "2.0.1",
|
|
10
10
|
"dependencies": {
|
|
11
11
|
"vscode-languageclient": "^9.0.0"
|
|
12
12
|
},
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naide-lang",
|
|
3
3
|
"displayName": "NAIDE Language",
|
|
4
|
-
"description": "Syntax highlighting, diagnostics, and autocomplete for NAIDE (.naide)
|
|
5
|
-
"version": "
|
|
4
|
+
"description": "Syntax highlighting, diagnostics, and autocomplete for NAIDE (.naide)",
|
|
5
|
+
"version": "2.0.1",
|
|
6
6
|
"publisher": "irxk",
|
|
7
7
|
"engines": { "vscode": "^1.75.0" },
|
|
8
8
|
"categories": ["Programming Languages"],
|
|
9
9
|
"main": "./extension.js",
|
|
10
10
|
"activationEvents": [
|
|
11
|
-
"onLanguage:naide"
|
|
12
|
-
"onLanguage:naidex"
|
|
11
|
+
"onLanguage:naide"
|
|
13
12
|
],
|
|
14
13
|
"repository": {
|
|
15
14
|
"type": "git",
|
|
@@ -22,12 +21,6 @@
|
|
|
22
21
|
"aliases": ["NAIDE", "naide"],
|
|
23
22
|
"extensions": [".naide"],
|
|
24
23
|
"configuration": "./language-configuration-naide.json"
|
|
25
|
-
},
|
|
26
|
-
{
|
|
27
|
-
"id": "naidex",
|
|
28
|
-
"aliases": ["NAIDE-X", "naidex", "nx"],
|
|
29
|
-
"extensions": [".nx"],
|
|
30
|
-
"configuration": "./language-configuration-nx.json"
|
|
31
24
|
}
|
|
32
25
|
],
|
|
33
26
|
"grammars": [
|
|
@@ -35,11 +28,6 @@
|
|
|
35
28
|
"language": "naide",
|
|
36
29
|
"scopeName": "source.naide",
|
|
37
30
|
"path": "./syntaxes/naide.tmLanguage.json"
|
|
38
|
-
},
|
|
39
|
-
{
|
|
40
|
-
"language": "naidex",
|
|
41
|
-
"scopeName": "source.naidex",
|
|
42
|
-
"path": "./syntaxes/naidex.tmLanguage.json"
|
|
43
31
|
}
|
|
44
32
|
],
|
|
45
33
|
"configuration": {
|
package/SPEC-X.nx
DELETED
|
@@ -1,276 +0,0 @@
|
|
|
1
|
-
-- ============================================
|
|
2
|
-
-- NAIDE-X Language Specification
|
|
3
|
-
-- ~80% fewer tokens than JavaScript
|
|
4
|
-
-- AI-only readability. Maximum generation speed.
|
|
5
|
-
-- ============================================
|
|
6
|
-
-- Rule: first char of line = intent
|
|
7
|
-
-- > return ? if | elif : else
|
|
8
|
-
-- @ loop * while ! try !! catch !!! ensure
|
|
9
|
-
-- $ server ^ model < import % match X patch
|
|
10
|
-
-- f func ~f async + export
|
|
11
|
-
-- s:i:n:b:l:m:a: typed vars
|
|
12
|
-
-- ~ await ~~ await.all
|
|
13
|
-
-- ============================================
|
|
14
|
-
|
|
15
|
-
-- Types: s=str i=int n=num b=bool l=list m=map a=any
|
|
16
|
-
s:name="hello"
|
|
17
|
-
i:count=42
|
|
18
|
-
n:price=9.99
|
|
19
|
-
b:active=true
|
|
20
|
-
l:items=[1,2,3]
|
|
21
|
-
m:cfg={host:"localhost",port:3000}
|
|
22
|
-
a:data=null
|
|
23
|
-
|
|
24
|
-
-- Mutable: ~ prefix
|
|
25
|
-
~i:counter=0
|
|
26
|
-
~s:label="init"
|
|
27
|
-
|
|
28
|
-
-- String interpolation: "{expr}" (double quotes)
|
|
29
|
-
s:greet="Hello {name}, count is {count}"
|
|
30
|
-
|
|
31
|
-
-- Functions: f name(TYPE:param,...)RETTYPE
|
|
32
|
-
f add(i:a,i:b)i
|
|
33
|
-
>a+b
|
|
34
|
-
|
|
35
|
-
f sayHi(s:who)
|
|
36
|
-
log"Hi {who}"
|
|
37
|
-
|
|
38
|
-
f greet2(s:name,s:prefix="Hello")
|
|
39
|
-
log"{prefix}, {name}!"
|
|
40
|
-
|
|
41
|
-
-- Async: ~f
|
|
42
|
-
~f fetchUser(s:id)m
|
|
43
|
-
a:res=~fetch("/api/users/{id}")
|
|
44
|
-
a:data=~res.json()
|
|
45
|
-
>data
|
|
46
|
-
|
|
47
|
-
-- Return: >
|
|
48
|
-
-- Return with status: >.s CODE BODY
|
|
49
|
-
|
|
50
|
-
-- If/elif/else: ? | :
|
|
51
|
-
?count>10
|
|
52
|
-
log"many"
|
|
53
|
-
|count>5
|
|
54
|
-
log"some"
|
|
55
|
-
:
|
|
56
|
-
log"few"
|
|
57
|
-
|
|
58
|
-
-- Inline ternary (same as NAIDE v1)
|
|
59
|
-
-- s:size=if count>10 then "big" else "small"
|
|
60
|
-
|
|
61
|
-
-- Each: @var<collection
|
|
62
|
-
@item<items
|
|
63
|
-
log item
|
|
64
|
-
|
|
65
|
-
-- Each with key: @key,val<collection
|
|
66
|
-
@k,v<cfg
|
|
67
|
-
log"{k}: {v}"
|
|
68
|
-
|
|
69
|
-
-- For range: @var<start..end
|
|
70
|
-
@i<0..10
|
|
71
|
-
log i
|
|
72
|
-
|
|
73
|
-
-- While: *condition
|
|
74
|
-
*active
|
|
75
|
-
log"running"
|
|
76
|
-
active=false
|
|
77
|
-
|
|
78
|
-
-- Match: %value
|
|
79
|
-
s:status="ok"
|
|
80
|
-
%status
|
|
81
|
-
"ok": log "success"
|
|
82
|
-
"error": log "failed"
|
|
83
|
-
_: log "unknown"
|
|
84
|
-
|
|
85
|
-
-- Try/catch/ensure: ! / !!var / !!!
|
|
86
|
-
~f safe(s:url)m
|
|
87
|
-
!
|
|
88
|
-
a:data=~fetch(url)
|
|
89
|
-
>{ok:true,data:data}
|
|
90
|
-
!!e
|
|
91
|
-
log.e"fail: {e.message}"
|
|
92
|
-
>{ok:false,error:e.message}
|
|
93
|
-
!!!
|
|
94
|
-
log"request done"
|
|
95
|
-
|
|
96
|
-
-- typeof / instanceof
|
|
97
|
-
?typeof x=="string"
|
|
98
|
-
log"is string"
|
|
99
|
-
?err instanceof TypeError
|
|
100
|
-
log"type error"
|
|
101
|
-
|
|
102
|
-
-- Pipe: |> (same)
|
|
103
|
-
l:result=[1,2,3,4,5]
|
|
104
|
-
|>filter((x)=>x>2)
|
|
105
|
-
|>map((x)=>x*10)
|
|
106
|
-
|
|
107
|
-
-- Model: ^Name
|
|
108
|
-
^User
|
|
109
|
-
s:name
|
|
110
|
-
s:email
|
|
111
|
-
i:age=0
|
|
112
|
-
f greet()s
|
|
113
|
-
>"Hi, I'm {self.name}"
|
|
114
|
-
~f save()
|
|
115
|
-
log"Saving {self.name}..."
|
|
116
|
-
|
|
117
|
-
-- Inheritance: ^Child<Parent
|
|
118
|
-
^Admin<User
|
|
119
|
-
s:role="admin"
|
|
120
|
-
f perms()l
|
|
121
|
-
>["read","write","delete"]
|
|
122
|
-
|
|
123
|
-
-- DB persistence: db "path/"
|
|
124
|
-
db "data/"
|
|
125
|
-
|
|
126
|
-
-- SQL database: db.sql "driver" "connection"
|
|
127
|
-
-- db.sql "sqlite" "app.db"
|
|
128
|
-
-- db.sql "postgres" "postgresql://localhost/mydb"
|
|
129
|
-
|
|
130
|
-
-- Environment variables
|
|
131
|
-
env:
|
|
132
|
-
PORT int default(3000)
|
|
133
|
-
JWT_SECRET str required
|
|
134
|
-
|
|
135
|
-
-- Schema: types + modifiers
|
|
136
|
-
schema User:
|
|
137
|
-
id auto
|
|
138
|
-
name str required min(2) max(50)
|
|
139
|
-
email str required email unique
|
|
140
|
-
role enum("admin","user") default("user")
|
|
141
|
-
joined timestamp auto
|
|
142
|
-
|
|
143
|
-
-- Server with high-level features
|
|
144
|
-
$app:PORT
|
|
145
|
-
cors "*"
|
|
146
|
-
auth JWT_SECRET:
|
|
147
|
-
protect "/api/*"
|
|
148
|
-
public "/api/auth/*"
|
|
149
|
-
limit "/api/*" 100 "1m"
|
|
150
|
-
static "/public"
|
|
151
|
-
crud "/api/users" User
|
|
152
|
-
cookie
|
|
153
|
-
session JWT_SECRET
|
|
154
|
-
cache "/api/*" "5m"
|
|
155
|
-
view "./views"
|
|
156
|
-
sse "/events"
|
|
157
|
-
upload "/api/upload" "avatar" (req,res):
|
|
158
|
-
>{file:req.file.filename}
|
|
159
|
-
-- Route middleware: [mid1, mid2]
|
|
160
|
-
G"/admin"[authCheck](req,res)
|
|
161
|
-
>{admin:true}
|
|
162
|
-
P"/api/secure"[auth,logger](req,res)
|
|
163
|
-
>req.body
|
|
164
|
-
group "/api/v1":
|
|
165
|
-
G"/status"
|
|
166
|
-
>{version:"1.0"}
|
|
167
|
-
G"/"
|
|
168
|
-
>.h "<h1>Welcome</h1>"
|
|
169
|
-
P"/api/auth/register"(req,res)
|
|
170
|
-
s:hashed=hash(req.body.password)
|
|
171
|
-
user=UserStore.create({...req.body,password:hashed})
|
|
172
|
-
token=auth.sign({id:user.id})
|
|
173
|
-
>{token,user}
|
|
174
|
-
P"/api/auth/login"(req,res)
|
|
175
|
-
user=UserStore.where({email:req.body.email})[0]
|
|
176
|
-
?not user
|
|
177
|
-
>.s 401 {error:"Invalid credentials"}
|
|
178
|
-
?not verify(req.body.password,user.password)
|
|
179
|
-
>.s 401 {error:"Invalid credentials"}
|
|
180
|
-
token=sign({id:user.id})
|
|
181
|
-
>{token}
|
|
182
|
-
ws "/chat":
|
|
183
|
-
on "connect":
|
|
184
|
-
send({type:"welcome"})
|
|
185
|
-
on "message" (data):
|
|
186
|
-
broadcast(data)
|
|
187
|
-
|
|
188
|
-
-- Error handler
|
|
189
|
-
error (err, req, res):
|
|
190
|
-
log.e err.message
|
|
191
|
-
>.s 500 {error:"Internal error"}
|
|
192
|
-
|
|
193
|
-
-- HTTP client (auto-imported)
|
|
194
|
-
-- api.get(url) api.post(url,body) api.put(url,body) api.del(url) api.raw(url,opts)
|
|
195
|
-
|
|
196
|
-
-- Return helpers
|
|
197
|
-
-- >expr → JSON
|
|
198
|
-
-- >.s CODE expr → status code
|
|
199
|
-
-- >.r "/path" → redirect
|
|
200
|
-
-- >.h "<html>" → HTML
|
|
201
|
-
-- >.t "text" → plain text
|
|
202
|
-
-- >.f "/file" → send file
|
|
203
|
-
-- >.d "/file" → download
|
|
204
|
-
-- >.v "tmpl" {} → render template
|
|
205
|
-
|
|
206
|
-
-- Built-in functions (auto-imported)
|
|
207
|
-
-- uuid() → UUID
|
|
208
|
-
-- hash("password") → scrypt hash
|
|
209
|
-
-- verify("pw",hashed) → verify
|
|
210
|
-
-- sign({id:1}) → JWT (uses auth secret)
|
|
211
|
-
-- sign({id:1},"secret") → JWT (explicit)
|
|
212
|
-
|
|
213
|
-
-- Validate: auto-validate POST/PUT/PATCH bodies
|
|
214
|
-
-- validate "/api/users" User
|
|
215
|
-
|
|
216
|
-
-- Test (node:test)
|
|
217
|
-
test "math":
|
|
218
|
-
assert 1+1==2
|
|
219
|
-
assert 2*3==6
|
|
220
|
-
|
|
221
|
-
-- Queue (async job queue)
|
|
222
|
-
queue jobs:
|
|
223
|
-
job "notify" (data):
|
|
224
|
-
log data.msg
|
|
225
|
-
|
|
226
|
-
-- OpenAPI: auto-generate spec from schemas
|
|
227
|
-
-- openapi "/docs"
|
|
228
|
-
|
|
229
|
-
-- AI / LLM integration (auto-imported, zero-dep)
|
|
230
|
-
-- s:answer=~ai.ask("Explain NAIDE")
|
|
231
|
-
-- m:result=~ai.json("List 3 colors",{colors:["string"]})
|
|
232
|
-
-- s:reply=~ai.chat(msgs)
|
|
233
|
-
-- s:a=~ai.ask("hi",{model:"gpt-4o",maxTokens:100})
|
|
234
|
-
-- Streaming: ~f main(): @chunk<~ai.stream("poem") ...
|
|
235
|
-
-- Embed: l:vec=~ai.embed("hello")
|
|
236
|
-
-- Similarity: n:score=ai.similarity(a,b)
|
|
237
|
-
|
|
238
|
-
-- Prompt template
|
|
239
|
-
-- prompt summarize {lang:"en"}:
|
|
240
|
-
-- "Summarize in {lang}: {text}"
|
|
241
|
-
|
|
242
|
-
-- Inter-file import (.naide/.nx → .mjs auto-rewrite)
|
|
243
|
-
-- <{handler}"./routes.naide"
|
|
244
|
-
|
|
245
|
-
-- Scheduled tasks
|
|
246
|
-
every "5m":
|
|
247
|
-
log"cleanup"
|
|
248
|
-
|
|
249
|
-
-- Event watch
|
|
250
|
-
watch User.create (event):
|
|
251
|
-
log"new: {event.data.name}"
|
|
252
|
-
|
|
253
|
-
-- Import: <module or <{names}"module"
|
|
254
|
-
-- <express
|
|
255
|
-
-- <{readFile,writeFile}"fs/promises"
|
|
256
|
-
|
|
257
|
-
-- Export: + prefix
|
|
258
|
-
+f helper()s
|
|
259
|
-
>"exported"
|
|
260
|
-
+s:VER="1.0.0"
|
|
261
|
-
|
|
262
|
-
-- Log: log, log.e, log.w, log.i, log.d
|
|
263
|
-
log"info"
|
|
264
|
-
log.e"error"
|
|
265
|
-
log.w"warning"
|
|
266
|
-
log.i"info level"
|
|
267
|
-
log.d"debug level"
|
|
268
|
-
|
|
269
|
-
-- Null safe: ?. and ?? (same)
|
|
270
|
-
a:val=data?.nested?.value??"default"
|
|
271
|
-
|
|
272
|
-
-- Await all: ~~[expr1,expr2]
|
|
273
|
-
|
|
274
|
-
-- Spread: ...
|
|
275
|
-
l:combined=[...items,4,5,6]
|
|
276
|
-
m:merged={...cfg,debug:true}
|
package/assets/nx.ico
DELETED
|
Binary file
|
package/assets/nx_check.png
DELETED
|
Binary file
|
package/examples/ai-app.nx
DELETED
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
-- AI-powered API (NAIDE-X)
|
|
2
|
-
-- Set AI_KEY or OPENAI_API_KEY env var
|
|
3
|
-
|
|
4
|
-
$app:3000
|
|
5
|
-
cors "*"
|
|
6
|
-
|
|
7
|
-
P"/api/ask"(req,res)
|
|
8
|
-
s:answer=~ai.ask(req.body.prompt)
|
|
9
|
-
>{answer}
|
|
10
|
-
|
|
11
|
-
P"/api/summarize"(req,res)
|
|
12
|
-
s:summary=~ai.ask("Summarize: {req.body.text}")
|
|
13
|
-
>{summary}
|
|
14
|
-
|
|
15
|
-
P"/api/extract"(req,res)
|
|
16
|
-
m:data=~ai.json("Extract name, email, phone: {req.body.text}",{name:"string",email:"string",phone:"string"})
|
|
17
|
-
>data
|
|
18
|
-
|
|
19
|
-
P"/api/chat"(req,res)
|
|
20
|
-
s:reply=~ai.chat(req.body.messages)
|
|
21
|
-
>{reply}
|