naider 1.15.0 → 1.16.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 +90 -7
- package/SPEC.naide +102 -0
- package/lsp/server.js +48 -0
- package/package.json +2 -2
- package/src/generator-python.js +73 -0
- package/src/generator.js +73 -0
- package/src/parser.js +99 -3
- package/src/tokens.js +15 -0
- package/vscode-naide/syntaxes/naide.tmLanguage.json +6 -2
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
# NAIDE
|
|
2
2
|
|
|
3
|
-
**Node AI Development Environment** — A programming language designed for AI-speed code generation that transpiles to **15 languages**.
|
|
3
|
+
**Node AI Development Environment** — A programming language simpler than Python, designed for AI-speed code generation, that transpiles to **15 languages**.
|
|
4
4
|
|
|
5
|
-
NAIDE is built on
|
|
5
|
+
NAIDE is built on four principles: simpler than Python (built-in functions, syntax sugar, zero boilerplate), one way to write everything (zero ambiguity), keyword-driven intent (the first token decides meaning), and minimal token count (fewer tokens = faster AI generation). Write once, compile to any target. 35+ built-in functions, syntax sugar (`unless`, `until`, `repeat`, `swap`, `is`/`isnt`, one-line functions), and 47 built-in features including servers, databases, authentication, bots, GraphQL, gRPC, WebRTC, blockchain, and more.
|
|
6
6
|
|
|
7
7
|
### Compilation Targets
|
|
8
8
|
|
|
@@ -885,14 +885,97 @@ env:
|
|
|
885
885
|
|
|
886
886
|
Variables become constants. Missing `required` vars exit with an error.
|
|
887
887
|
|
|
888
|
+
## Syntax Sugar (Simpler than Python)
|
|
889
|
+
|
|
890
|
+
```python
|
|
891
|
+
# unless — negated if
|
|
892
|
+
unless x > 10:
|
|
893
|
+
log "small"
|
|
894
|
+
|
|
895
|
+
# until — negated while
|
|
896
|
+
until done:
|
|
897
|
+
process()
|
|
898
|
+
|
|
899
|
+
# repeat — simple counted loop
|
|
900
|
+
repeat 5:
|
|
901
|
+
log "hi"
|
|
902
|
+
repeat 10 as i:
|
|
903
|
+
log i
|
|
904
|
+
|
|
905
|
+
# enum
|
|
906
|
+
enum Color:
|
|
907
|
+
RED
|
|
908
|
+
GREEN
|
|
909
|
+
BLUE
|
|
910
|
+
|
|
911
|
+
# swap
|
|
912
|
+
swap a, b
|
|
913
|
+
|
|
914
|
+
# is / isnt — readable equality
|
|
915
|
+
if x is 5: log "five"
|
|
916
|
+
if y isnt null: log "exists"
|
|
917
|
+
|
|
918
|
+
# one-line functions
|
|
919
|
+
fn double(int x) -> int = x * 2
|
|
920
|
+
|
|
921
|
+
# print alias
|
|
922
|
+
print "hello world"
|
|
923
|
+
```
|
|
924
|
+
|
|
888
925
|
## Built-in Functions
|
|
889
926
|
|
|
890
927
|
```python
|
|
891
|
-
|
|
892
|
-
str
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
str token = sign({id: 1}
|
|
928
|
+
# Security
|
|
929
|
+
str id = uuid()
|
|
930
|
+
str hashed = hash("password")
|
|
931
|
+
bool ok = verify("password", hashed)
|
|
932
|
+
str token = sign({id: 1})
|
|
933
|
+
|
|
934
|
+
# Type conversions
|
|
935
|
+
str s = str(42)
|
|
936
|
+
int n = int("42")
|
|
937
|
+
num f = float("3.14")
|
|
938
|
+
|
|
939
|
+
# String ops
|
|
940
|
+
str u = upper("hello")
|
|
941
|
+
str l = lower("HELLO")
|
|
942
|
+
str t = trim(" hi ")
|
|
943
|
+
list parts = split("a,b,c", ",")
|
|
944
|
+
str joined = join(parts, "-")
|
|
945
|
+
bool has = contains("hello", "ell")
|
|
946
|
+
|
|
947
|
+
# Collection ops
|
|
948
|
+
int length = len(items)
|
|
949
|
+
list sorted = sort(items)
|
|
950
|
+
list r = range(10)
|
|
951
|
+
list k = keys(obj)
|
|
952
|
+
list v = values(obj)
|
|
953
|
+
list uniq = unique(items)
|
|
954
|
+
list flat_list = flat(nested)
|
|
955
|
+
list zipped = zip(a, b)
|
|
956
|
+
list chunks = chunk(items, 3)
|
|
957
|
+
|
|
958
|
+
# Math
|
|
959
|
+
num a = abs(-5)
|
|
960
|
+
num r = round(3.7)
|
|
961
|
+
num s = sqrt(16)
|
|
962
|
+
num p = pow(2, 10)
|
|
963
|
+
num total = sum(nums)
|
|
964
|
+
|
|
965
|
+
# JSON
|
|
966
|
+
any data = json_parse('{"a":1}')
|
|
967
|
+
str json = json_str(data)
|
|
968
|
+
|
|
969
|
+
# I/O
|
|
970
|
+
str answer = ask("Name?")
|
|
971
|
+
str content = read("file.txt")
|
|
972
|
+
write("out.txt", content)
|
|
973
|
+
|
|
974
|
+
# Time & misc
|
|
975
|
+
int ts = now()
|
|
976
|
+
str t = time()
|
|
977
|
+
sleep(1000)
|
|
978
|
+
exit(0)
|
|
896
979
|
```
|
|
897
980
|
|
|
898
981
|
Auto-imported from the runtime when used.
|
package/SPEC.naide
CHANGED
|
@@ -634,3 +634,105 @@ blockchain "eth":
|
|
|
634
634
|
contract "0x1234abcd..."
|
|
635
635
|
abi "contract-abi.json"
|
|
636
636
|
# usage: eth.getBalance("0x..."), eth.getBlock(), eth.sendTx(wallet, to, "0.1")
|
|
637
|
+
|
|
638
|
+
|
|
639
|
+
# ============================================
|
|
640
|
+
# シンプルシンタックス (Pythonより簡単)
|
|
641
|
+
# ============================================
|
|
642
|
+
|
|
643
|
+
# ---- unless (ifの逆) ----
|
|
644
|
+
unless x > 10:
|
|
645
|
+
log "xは10以下"
|
|
646
|
+
|
|
647
|
+
# ---- until (whileの逆) ----
|
|
648
|
+
mut int i = 0
|
|
649
|
+
until i > 10:
|
|
650
|
+
i = i + 1
|
|
651
|
+
|
|
652
|
+
# ---- repeat (簡単ループ) ----
|
|
653
|
+
repeat 5:
|
|
654
|
+
log "hello"
|
|
655
|
+
|
|
656
|
+
repeat 10 as i:
|
|
657
|
+
log i
|
|
658
|
+
|
|
659
|
+
# ---- enum (列挙型) ----
|
|
660
|
+
enum Color:
|
|
661
|
+
RED
|
|
662
|
+
GREEN
|
|
663
|
+
BLUE
|
|
664
|
+
|
|
665
|
+
# インライン形式
|
|
666
|
+
enum Status: ACTIVE, INACTIVE, PENDING
|
|
667
|
+
|
|
668
|
+
# ---- swap (値の交換) ----
|
|
669
|
+
mut int a = 1
|
|
670
|
+
mut int b = 2
|
|
671
|
+
swap a, b
|
|
672
|
+
|
|
673
|
+
# ---- is / isnt (比較演算子) ----
|
|
674
|
+
if x is 5:
|
|
675
|
+
log "xは5"
|
|
676
|
+
if y isnt null:
|
|
677
|
+
log "yはnullじゃない"
|
|
678
|
+
|
|
679
|
+
# ---- 一行関数 ----
|
|
680
|
+
fn double(int x) -> int = x * 2
|
|
681
|
+
fn greet(str name) -> str = "Hello, {name}!"
|
|
682
|
+
|
|
683
|
+
# ---- print (logのエイリアス) ----
|
|
684
|
+
print "hello world"
|
|
685
|
+
|
|
686
|
+
# ---- ビルトイン関数 ----
|
|
687
|
+
# 型変換
|
|
688
|
+
str s = str(42)
|
|
689
|
+
int n = int("42")
|
|
690
|
+
num f = float("3.14")
|
|
691
|
+
|
|
692
|
+
# 文字列操作
|
|
693
|
+
str u = upper("hello") # "HELLO"
|
|
694
|
+
str l = lower("HELLO") # "hello"
|
|
695
|
+
str t = trim(" hi ") # "hi"
|
|
696
|
+
list parts = split("a,b,c", ",") # ["a","b","c"]
|
|
697
|
+
str joined = join(parts, "-") # "a-b-c"
|
|
698
|
+
str replaced = replace("hello", "l", "r") # "herro"
|
|
699
|
+
bool has = contains("hello", "ell") # true
|
|
700
|
+
|
|
701
|
+
# コレクション操作
|
|
702
|
+
int length = len(items)
|
|
703
|
+
list sorted = sort(items)
|
|
704
|
+
list reversed = reverse(items)
|
|
705
|
+
list uniq = unique([1,1,2,3]) # [1,2,3]
|
|
706
|
+
list k = keys(obj)
|
|
707
|
+
list v = values(obj)
|
|
708
|
+
list e = entries(obj)
|
|
709
|
+
list r = range(10) # [0,1,...,9]
|
|
710
|
+
list chunks = chunk([1,2,3,4], 2) # [[1,2],[3,4]]
|
|
711
|
+
list zipped = zip([1,2], ["a","b"])
|
|
712
|
+
list flattened = flat([[1,2],[3,4]])
|
|
713
|
+
|
|
714
|
+
# 数学
|
|
715
|
+
num a2 = abs(-5)
|
|
716
|
+
num r2 = round(3.7)
|
|
717
|
+
num c = ceil(3.1)
|
|
718
|
+
num fl = floor(3.9)
|
|
719
|
+
num sq = sqrt(16)
|
|
720
|
+
num p = pow(2, 10)
|
|
721
|
+
num total = sum([1,2,3])
|
|
722
|
+
|
|
723
|
+
# JSON
|
|
724
|
+
any data2 = json_parse('{"a":1}')
|
|
725
|
+
str json = json_str(data2)
|
|
726
|
+
|
|
727
|
+
# 時間
|
|
728
|
+
int timestamp = now()
|
|
729
|
+
str timestr = time()
|
|
730
|
+
|
|
731
|
+
# I/O
|
|
732
|
+
str content = read("file.txt")
|
|
733
|
+
write("out.txt", content)
|
|
734
|
+
str answer = ask("名前は?")
|
|
735
|
+
|
|
736
|
+
# その他
|
|
737
|
+
sleep(1000)
|
|
738
|
+
exit(0)
|
package/lsp/server.js
CHANGED
|
@@ -291,6 +291,7 @@ function getCompletions() {
|
|
|
291
291
|
'page', 'cli', 'mail', 'graphql', 'desktop', 'screen',
|
|
292
292
|
'oauth', 'pay', 'storage', 'pdf', 'i18n', 'push', 'search', 'image',
|
|
293
293
|
'csv', 'logging', 'migrate', 'grpc', 'webrtc', 'blockchain',
|
|
294
|
+
'unless', 'until', 'repeat', 'enum', 'swap', 'is', 'isnt', 'print',
|
|
294
295
|
];
|
|
295
296
|
const builtins = [
|
|
296
297
|
{ label: 'uuid()', detail: 'Generate UUID v4', insertText: 'uuid()' },
|
|
@@ -315,6 +316,45 @@ function getCompletions() {
|
|
|
315
316
|
{ label: 'graphql "/path"', detail: 'GraphQL endpoint', insertText: 'graphql ' },
|
|
316
317
|
{ label: 'desktop name:', detail: 'Desktop app', insertText: 'desktop ' },
|
|
317
318
|
{ label: 'screen Name:', detail: 'Mobile screen', insertText: 'screen ' },
|
|
319
|
+
{ label: 'len(list)', detail: 'Get length', insertText: 'len(' },
|
|
320
|
+
{ label: 'sort(list)', detail: 'Sort a list', insertText: 'sort(' },
|
|
321
|
+
{ label: 'reverse(list)', detail: 'Reverse a list', insertText: 'reverse(' },
|
|
322
|
+
{ label: 'unique(list)', detail: 'Remove duplicates', insertText: 'unique(' },
|
|
323
|
+
{ label: 'upper(str)', detail: 'Uppercase string', insertText: 'upper(' },
|
|
324
|
+
{ label: 'lower(str)', detail: 'Lowercase string', insertText: 'lower(' },
|
|
325
|
+
{ label: 'trim(str)', detail: 'Trim whitespace', insertText: 'trim(' },
|
|
326
|
+
{ label: 'split(str, sep)', detail: 'Split string', insertText: 'split(' },
|
|
327
|
+
{ label: 'join(list, sep)', detail: 'Join list to string', insertText: 'join(' },
|
|
328
|
+
{ label: 'contains(list, item)', detail: 'Check if contains', insertText: 'contains(' },
|
|
329
|
+
{ label: 'replace(str, old, new)', detail: 'Replace in string', insertText: 'replace(' },
|
|
330
|
+
{ label: 'keys(map)', detail: 'Get map keys', insertText: 'keys(' },
|
|
331
|
+
{ label: 'values(map)', detail: 'Get map values', insertText: 'values(' },
|
|
332
|
+
{ label: 'entries(map)', detail: 'Get key-value pairs', insertText: 'entries(' },
|
|
333
|
+
{ label: 'range(n)', detail: 'Generate 0..n-1', insertText: 'range(' },
|
|
334
|
+
{ label: 'abs(num)', detail: 'Absolute value', insertText: 'abs(' },
|
|
335
|
+
{ label: 'round(num)', detail: 'Round number', insertText: 'round(' },
|
|
336
|
+
{ label: 'ceil(num)', detail: 'Round up', insertText: 'ceil(' },
|
|
337
|
+
{ label: 'floor(num)', detail: 'Round down', insertText: 'floor(' },
|
|
338
|
+
{ label: 'sqrt(num)', detail: 'Square root', insertText: 'sqrt(' },
|
|
339
|
+
{ label: 'pow(base, exp)', detail: 'Power', insertText: 'pow(' },
|
|
340
|
+
{ label: 'sum(list)', detail: 'Sum of list', insertText: 'sum(' },
|
|
341
|
+
{ label: 'flat(list)', detail: 'Flatten nested list', insertText: 'flat(' },
|
|
342
|
+
{ label: 'zip(a, b)', detail: 'Zip two lists', insertText: 'zip(' },
|
|
343
|
+
{ label: 'chunk(list, n)', detail: 'Split into chunks', insertText: 'chunk(' },
|
|
344
|
+
{ label: 'str(val)', detail: 'Convert to string', insertText: 'str(' },
|
|
345
|
+
{ label: 'int(val)', detail: 'Convert to integer', insertText: 'int(' },
|
|
346
|
+
{ label: 'float(val)', detail: 'Convert to float', insertText: 'float(' },
|
|
347
|
+
{ label: 'json_parse(str)', detail: 'Parse JSON string', insertText: 'json_parse(' },
|
|
348
|
+
{ label: 'json_str(val)', detail: 'Stringify to JSON', insertText: 'json_str(' },
|
|
349
|
+
{ label: 'now()', detail: 'Current timestamp ms', insertText: 'now()' },
|
|
350
|
+
{ label: 'time()', detail: 'Current ISO time', insertText: 'time()' },
|
|
351
|
+
{ label: 'ask(prompt)', detail: 'Read user input', insertText: 'ask(' },
|
|
352
|
+
{ label: 'sleep(ms)', detail: 'Wait milliseconds', insertText: 'sleep(' },
|
|
353
|
+
{ label: 'exit(code)', detail: 'Exit process', insertText: 'exit(' },
|
|
354
|
+
{ label: 'read(path)', detail: 'Read file contents', insertText: 'read(' },
|
|
355
|
+
{ label: 'write(path, data)', detail: 'Write to file', insertText: 'write(' },
|
|
356
|
+
{ label: 'fetch_json(url)', detail: 'Fetch JSON from URL', insertText: 'fetch_json(' },
|
|
357
|
+
{ label: 'random(min, max)', detail: 'Random number', insertText: 'random(' },
|
|
318
358
|
];
|
|
319
359
|
|
|
320
360
|
return [
|
|
@@ -361,6 +401,14 @@ const HOVER_DOCS = {
|
|
|
361
401
|
'grpc': '**grpc** — gRPC service\n```naide\ngrpc "users" port 50051:\n rpc getUser(id) -> user\n rpc createUser(data) -> user\n```',
|
|
362
402
|
'webrtc': '**webrtc** — WebRTC signaling\n```naide\nwebrtc "video":\n stun "stun:stun.l.google.com:19302"\n on offer(data):\n log "offer received"\n```',
|
|
363
403
|
'blockchain': '**blockchain** — Blockchain/Web3\n```naide\nblockchain "eth":\n network "ethereum"\n provider env.ETH_RPC\n contract "0x..."\n```\nUsage: `eth.getBalance(addr)`, `eth.getBlock()`',
|
|
404
|
+
'unless': '**unless** — Negated if (do X unless condition)\n```naide\nunless x > 10:\n log "small"\n```',
|
|
405
|
+
'until': '**until** — Negated while (loop until condition)\n```naide\nuntil done:\n process()\n```',
|
|
406
|
+
'repeat': '**repeat** — Simple counted loop\n```naide\nrepeat 5:\n log "hi"\nrepeat 10 as i:\n log i\n```',
|
|
407
|
+
'enum': '**enum** — Enumeration type\n```naide\nenum Color:\n RED\n GREEN\n BLUE\n```\nInline: `enum Status: ACTIVE, INACTIVE`',
|
|
408
|
+
'swap': '**swap** — Swap two variables\n```naide\nswap a, b\n```',
|
|
409
|
+
'is': '**is** — Equality comparison (===)\n```naide\nif x is 5: log "five"\n```',
|
|
410
|
+
'isnt': '**isnt** — Inequality comparison (!==)\n```naide\nif x isnt null: log "exists"\n```',
|
|
411
|
+
'print': '**print** — Alias for log\n```naide\nprint "hello world"\n```',
|
|
364
412
|
};
|
|
365
413
|
|
|
366
414
|
function getHover(params) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "naider",
|
|
3
|
-
"version": "1.
|
|
4
|
-
"description": "NAIDE -
|
|
3
|
+
"version": "1.16.0",
|
|
4
|
+
"description": "NAIDE - Simpler than Python, compiles to 15 targets. AI-specialized language with 35+ built-in functions, syntax sugar (unless/until/repeat/swap/is/isnt), and 47 features. Targets: Node.js, Python, TypeScript, C, C++, Java, Go, Rust, PHP, Ruby, Kotlin, Swift, Dart, C#, Bun.",
|
|
5
5
|
"main": "src/index.js",
|
|
6
6
|
"exports": {
|
|
7
7
|
".": "./src/index.js",
|
package/src/generator-python.js
CHANGED
|
@@ -162,6 +162,8 @@ export class PythonGenerator {
|
|
|
162
162
|
case 'GrpcDecl': return this.visitGrpc(node);
|
|
163
163
|
case 'WebrtcDecl': return this.visitWebrtc(node);
|
|
164
164
|
case 'BlockchainDecl': return this.visitBlockchain(node);
|
|
165
|
+
case 'EnumDecl': return this.visitEnum(node);
|
|
166
|
+
case 'Swap': return this.visitSwap(node);
|
|
165
167
|
default:
|
|
166
168
|
this.emit(`# unknown: ${node.type}`);
|
|
167
169
|
}
|
|
@@ -1068,6 +1070,12 @@ export class PythonGenerator {
|
|
|
1068
1070
|
generateCall(node) {
|
|
1069
1071
|
const args = node.args.map(a => this.expr(a)).join(', ');
|
|
1070
1072
|
|
|
1073
|
+
if (node.callee.type === 'Identifier') {
|
|
1074
|
+
const argList = node.args.map(a => this.expr(a));
|
|
1075
|
+
const b = this.generateBuiltin(node.callee.name, argList);
|
|
1076
|
+
if (b !== null) return b;
|
|
1077
|
+
}
|
|
1078
|
+
|
|
1071
1079
|
// Map common JS global functions to Python
|
|
1072
1080
|
if (node.callee.type === 'Identifier') {
|
|
1073
1081
|
const name = node.callee.name;
|
|
@@ -2466,4 +2474,69 @@ export class PythonGenerator {
|
|
|
2466
2474
|
this.emit(`${name.replace(/\W/g, '_')} = ${name.charAt(0).toUpperCase() + name.slice(1).replace(/\W/g, '_')}()`);
|
|
2467
2475
|
this.emitRaw('');
|
|
2468
2476
|
}
|
|
2477
|
+
|
|
2478
|
+
// ===== Enum =====
|
|
2479
|
+
visitEnum(node) {
|
|
2480
|
+
this.addFromImport('enum', 'Enum');
|
|
2481
|
+
this.emit(`class ${node.name}(Enum):`);
|
|
2482
|
+
this.indent++;
|
|
2483
|
+
node.values.forEach((v, i) => {
|
|
2484
|
+
this.emit(`${v} = ${i}`);
|
|
2485
|
+
});
|
|
2486
|
+
this.indent--;
|
|
2487
|
+
this.emitRaw('');
|
|
2488
|
+
}
|
|
2489
|
+
|
|
2490
|
+
// ===== Swap =====
|
|
2491
|
+
visitSwap(node) {
|
|
2492
|
+
const a = this.expr(node.a);
|
|
2493
|
+
const b = this.expr(node.b);
|
|
2494
|
+
this.emit(`${a}, ${b} = ${b}, ${a}`);
|
|
2495
|
+
}
|
|
2496
|
+
|
|
2497
|
+
// ===== Builtins =====
|
|
2498
|
+
generateBuiltin(name, args) {
|
|
2499
|
+
switch (name) {
|
|
2500
|
+
case 'ask': return `input(${args[0] || '""'})`;
|
|
2501
|
+
case 'sleep': { this.addImport('time'); return `time.sleep(${args[0]} / 1000)`; }
|
|
2502
|
+
case 'exit': { this.addImport('sys'); return `sys.exit(${args[0] || '0'})`; }
|
|
2503
|
+
case 'read': return `open(${args[0]}).read()`;
|
|
2504
|
+
case 'write': return `open(${args[0]}, 'w').write(${args[1]})`;
|
|
2505
|
+
case 'fetch_json': { this.addImport('requests'); return `requests.get(${args[0]}).json()`; }
|
|
2506
|
+
case 'random': { this.addImport('random'); return args.length >= 2 ? `random.randint(${args[0]}, ${args[1]})` : `random.random()`; }
|
|
2507
|
+
case 'sort': return `sorted(${args[0]})`;
|
|
2508
|
+
case 'reverse': return `list(reversed(${args[0]}))`;
|
|
2509
|
+
case 'unique': return `list(set(${args[0]}))`;
|
|
2510
|
+
case 'len': return `len(${args[0]})`;
|
|
2511
|
+
case 'upper': return `${args[0]}.upper()`;
|
|
2512
|
+
case 'lower': return `${args[0]}.lower()`;
|
|
2513
|
+
case 'trim': return `${args[0]}.strip()`;
|
|
2514
|
+
case 'split': return `${args[0]}.split(${args[1] || ''})`;
|
|
2515
|
+
case 'join': return `${args[1] || '","'}.join(${args[0]})`;
|
|
2516
|
+
case 'contains': return `(${args[1]} in ${args[0]})`;
|
|
2517
|
+
case 'replace': return `${args[0]}.replace(${args[1]}, ${args[2]})`;
|
|
2518
|
+
case 'keys': return `list(${args[0]}.keys())`;
|
|
2519
|
+
case 'values': return `list(${args[0]}.values())`;
|
|
2520
|
+
case 'entries': return `list(${args[0]}.items())`;
|
|
2521
|
+
case 'range': return args.length >= 2 ? `list(range(${args[0]}, ${args[1]}))` : `list(range(${args[0]}))`;
|
|
2522
|
+
case 'abs': return `abs(${args[0]})`;
|
|
2523
|
+
case 'round': return `round(${args[0]})`;
|
|
2524
|
+
case 'ceil': { this.addImport('math'); return `math.ceil(${args[0]})`; }
|
|
2525
|
+
case 'floor': { this.addImport('math'); return `math.floor(${args[0]})`; }
|
|
2526
|
+
case 'sqrt': { this.addImport('math'); return `math.sqrt(${args[0]})`; }
|
|
2527
|
+
case 'pow': return `${args[0]} ** ${args[1]}`;
|
|
2528
|
+
case 'sum': return `sum(${args[0]})`;
|
|
2529
|
+
case 'flat': return `[x for sub in ${args[0]} for x in sub]`;
|
|
2530
|
+
case 'zip': return `list(zip(${args[0]}, ${args[1]}))`;
|
|
2531
|
+
case 'str': return `str(${args[0]})`;
|
|
2532
|
+
case 'int': return `int(${args[0]})`;
|
|
2533
|
+
case 'float': return `float(${args[0]})`;
|
|
2534
|
+
case 'json_parse': { this.addImport('json'); return `json.loads(${args[0]})`; }
|
|
2535
|
+
case 'json_str': { this.addImport('json'); return `json.dumps(${args[0]})`; }
|
|
2536
|
+
case 'now': { this.addImport('time'); return `int(time.time() * 1000)`; }
|
|
2537
|
+
case 'time': { this.addFromImport('datetime', 'datetime'); return `datetime.now().isoformat()`; }
|
|
2538
|
+
case 'chunk': return `[${args[0]}[i:i+${args[1]}] for i in range(0, len(${args[0]}), ${args[1]})]`;
|
|
2539
|
+
default: return null;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2469
2542
|
}
|
package/src/generator.js
CHANGED
|
@@ -162,6 +162,8 @@ export class Generator {
|
|
|
162
162
|
case 'GrpcDecl': return this.visitGrpc(node);
|
|
163
163
|
case 'WebrtcDecl': return this.visitWebrtc(node);
|
|
164
164
|
case 'BlockchainDecl': return this.visitBlockchain(node);
|
|
165
|
+
case 'EnumDecl': return this.visitEnum(node);
|
|
166
|
+
case 'Swap': return this.visitSwap(node);
|
|
165
167
|
default:
|
|
166
168
|
this.emit(`/* unknown: ${node.type} */`);
|
|
167
169
|
}
|
|
@@ -1724,6 +1726,12 @@ export class Generator {
|
|
|
1724
1726
|
}
|
|
1725
1727
|
|
|
1726
1728
|
generateCall(node) {
|
|
1729
|
+
if (node.callee.type === 'Identifier') {
|
|
1730
|
+
const args = node.args.map(a => this.expr(a));
|
|
1731
|
+
const b = this.generateBuiltin(node.callee.name, args);
|
|
1732
|
+
if (b !== null) return b;
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1727
1735
|
const AUTO_IMPORT = { 'hash': 'hash', 'verify': 'verify', 'uuid': 'uuid',
|
|
1728
1736
|
'createMock': 'createMock', 'createSpy': 'createSpy',
|
|
1729
1737
|
'registerPlugin': 'registerPlugin', 'usePlugin': 'usePlugin' };
|
|
@@ -2394,4 +2402,69 @@ export class Generator {
|
|
|
2394
2402
|
this.emit(`};`);
|
|
2395
2403
|
this.emitRaw('');
|
|
2396
2404
|
}
|
|
2405
|
+
|
|
2406
|
+
// ===== Enum =====
|
|
2407
|
+
visitEnum(node) {
|
|
2408
|
+
this.emit(`const ${node.name} = Object.freeze({`);
|
|
2409
|
+
this.indent++;
|
|
2410
|
+
node.values.forEach((v, i) => {
|
|
2411
|
+
this.emit(`${v}: ${i},`);
|
|
2412
|
+
});
|
|
2413
|
+
this.indent--;
|
|
2414
|
+
this.emit(`});`);
|
|
2415
|
+
this.emitRaw('');
|
|
2416
|
+
}
|
|
2417
|
+
|
|
2418
|
+
// ===== Swap =====
|
|
2419
|
+
visitSwap(node) {
|
|
2420
|
+
const a = this.expr(node.a);
|
|
2421
|
+
const b = this.expr(node.b);
|
|
2422
|
+
this.emit(`[${a}, ${b}] = [${b}, ${a}];`);
|
|
2423
|
+
}
|
|
2424
|
+
|
|
2425
|
+
// ===== Builtins =====
|
|
2426
|
+
generateBuiltin(name, args) {
|
|
2427
|
+
switch (name) {
|
|
2428
|
+
case 'ask': return `await new Promise(__r => { const __rl = (await import('readline')).createInterface({input:process.stdin,output:process.stdout}); __rl.question(${args[0] || '""'}, __a => { __rl.close(); __r(__a); }); })`;
|
|
2429
|
+
case 'sleep': return `await new Promise(__r => setTimeout(__r, ${args[0] || '0'}))`;
|
|
2430
|
+
case 'exit': return `process.exit(${args[0] || '0'})`;
|
|
2431
|
+
case 'read': return `(await import('fs')).readFileSync(${args[0]}, 'utf-8')`;
|
|
2432
|
+
case 'write': return `(await import('fs')).writeFileSync(${args[0]}, ${args[1]})`;
|
|
2433
|
+
case 'fetch_json': return `await fetch(${args[0]}).then(r => r.json())`;
|
|
2434
|
+
case 'random': return args.length >= 2 ? `(Math.floor(Math.random() * (${args[1]} - ${args[0]} + 1)) + ${args[0]})` : `Math.random()`;
|
|
2435
|
+
case 'sort': return `[...${args[0]}].sort()`;
|
|
2436
|
+
case 'reverse': return `[...${args[0]}].reverse()`;
|
|
2437
|
+
case 'unique': return `[...new Set(${args[0]})]`;
|
|
2438
|
+
case 'len': return `${args[0]}.length`;
|
|
2439
|
+
case 'upper': return `${args[0]}.toUpperCase()`;
|
|
2440
|
+
case 'lower': return `${args[0]}.toLowerCase()`;
|
|
2441
|
+
case 'trim': return `${args[0]}.trim()`;
|
|
2442
|
+
case 'split': return `${args[0]}.split(${args[1] || '""'})`;
|
|
2443
|
+
case 'join': return `${args[0]}.join(${args[1] || '","'})`;
|
|
2444
|
+
case 'contains': return `${args[0]}.includes(${args[1]})`;
|
|
2445
|
+
case 'replace': return `${args[0]}.replace(${args[1]}, ${args[2]})`;
|
|
2446
|
+
case 'keys': return `Object.keys(${args[0]})`;
|
|
2447
|
+
case 'values': return `Object.values(${args[0]})`;
|
|
2448
|
+
case 'entries': return `Object.entries(${args[0]})`;
|
|
2449
|
+
case 'range': return args.length >= 2 ? `Array.from({length: ${args[1]} - ${args[0]}}, (_, i) => i + ${args[0]})` : `Array.from({length: ${args[0]}}, (_, i) => i)`;
|
|
2450
|
+
case 'abs': return `Math.abs(${args[0]})`;
|
|
2451
|
+
case 'round': return `Math.round(${args[0]})`;
|
|
2452
|
+
case 'ceil': return `Math.ceil(${args[0]})`;
|
|
2453
|
+
case 'floor': return `Math.floor(${args[0]})`;
|
|
2454
|
+
case 'sqrt': return `Math.sqrt(${args[0]})`;
|
|
2455
|
+
case 'pow': return `Math.pow(${args[0]}, ${args[1]})`;
|
|
2456
|
+
case 'sum': return `${args[0]}.reduce((a, b) => a + b, 0)`;
|
|
2457
|
+
case 'flat': return `${args[0]}.flat()`;
|
|
2458
|
+
case 'zip': return `${args[0]}.map((v, i) => [v, ${args[1]}[i]])`;
|
|
2459
|
+
case 'str': return `String(${args[0]})`;
|
|
2460
|
+
case 'int': return `parseInt(${args[0]})`;
|
|
2461
|
+
case 'float': return `parseFloat(${args[0]})`;
|
|
2462
|
+
case 'json_parse': return `JSON.parse(${args[0]})`;
|
|
2463
|
+
case 'json_str': return `JSON.stringify(${args[0]})`;
|
|
2464
|
+
case 'now': return `Date.now()`;
|
|
2465
|
+
case 'time': return `new Date().toISOString()`;
|
|
2466
|
+
case 'chunk': return `Array.from({length: Math.ceil(${args[0]}.length / ${args[1]})}, (_, i) => ${args[0]}.slice(i * ${args[1]}, (i + 1) * ${args[1]}))`;
|
|
2467
|
+
default: return null;
|
|
2468
|
+
}
|
|
2469
|
+
}
|
|
2397
2470
|
}
|
package/src/parser.js
CHANGED
|
@@ -116,7 +116,9 @@ export class Parser {
|
|
|
116
116
|
type === T.PDF || type === T.I18N ||
|
|
117
117
|
type === T.PUSH || type === T.SEARCH || type === T.IMAGE ||
|
|
118
118
|
type === T.CSV || type === T.LOGGING || type === T.MIGRATE ||
|
|
119
|
-
type === T.GRPC || type === T.WEBRTC || type === T.BLOCKCHAIN
|
|
119
|
+
type === T.GRPC || type === T.WEBRTC || type === T.BLOCKCHAIN ||
|
|
120
|
+
type === T.UNLESS || type === T.UNTIL || type === T.REPEAT ||
|
|
121
|
+
type === T.ENUM_DECL || type === T.SWAP || type === T.IS || type === T.ISNT;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
expectPropertyName() {
|
|
@@ -166,9 +168,14 @@ export class Parser {
|
|
|
166
168
|
case T.PUB: return this.parsePub();
|
|
167
169
|
case T.RET: return this.parseReturn();
|
|
168
170
|
case T.IF: return this.parseIf();
|
|
171
|
+
case T.UNLESS: return this.parseUnless();
|
|
169
172
|
case T.EACH: return this.parseEach();
|
|
170
173
|
case T.FOR: return this.parseFor();
|
|
171
174
|
case T.WHILE: return this.parseWhile();
|
|
175
|
+
case T.UNTIL: return this.parseUntil();
|
|
176
|
+
case T.REPEAT: return this.parseRepeat();
|
|
177
|
+
case T.ENUM_DECL: return this.parseEnum();
|
|
178
|
+
case T.SWAP: return this.parseSwap();
|
|
172
179
|
case T.MATCH: return this.parseMatch();
|
|
173
180
|
case T.TRY: return this.parseTry();
|
|
174
181
|
case T.SERVER: return this.parseServer();
|
|
@@ -325,6 +332,11 @@ export class Parser {
|
|
|
325
332
|
returnType = this.parseTypeAnnotation();
|
|
326
333
|
}
|
|
327
334
|
|
|
335
|
+
if (this.match(T.ASSIGN)) {
|
|
336
|
+
const expr = this.parseExpression();
|
|
337
|
+
return new ASTNode('Function', { name, params, returnType, body: [new ASTNode('Return', { value: expr })], isAsync, isPublic });
|
|
338
|
+
}
|
|
339
|
+
|
|
328
340
|
this.expect(T.COLON);
|
|
329
341
|
const body = this.parseBlock();
|
|
330
342
|
|
|
@@ -1016,11 +1028,15 @@ export class Parser {
|
|
|
1016
1028
|
|
|
1017
1029
|
parseComparison() {
|
|
1018
1030
|
let left = this.parseAddition();
|
|
1019
|
-
while (this.atAny(T.EQ, T.NEQ, T.GT, T.LT, T.GTE, T.LTE, T.INSTANCEOF)) {
|
|
1031
|
+
while (this.atAny(T.EQ, T.NEQ, T.GT, T.LT, T.GTE, T.LTE, T.INSTANCEOF, T.IS, T.ISNT)) {
|
|
1020
1032
|
const tok = this.advance();
|
|
1021
1033
|
const right = this.parseAddition();
|
|
1022
1034
|
if (tok.type === T.INSTANCEOF) {
|
|
1023
1035
|
left = new ASTNode('Binary', { op: 'instanceof', left, right });
|
|
1036
|
+
} else if (tok.type === T.IS) {
|
|
1037
|
+
left = new ASTNode('Binary', { op: '===', left, right });
|
|
1038
|
+
} else if (tok.type === T.ISNT) {
|
|
1039
|
+
left = new ASTNode('Binary', { op: '!==', left, right });
|
|
1024
1040
|
} else {
|
|
1025
1041
|
left = new ASTNode('Binary', { op: tok.value === '==' ? '===' : tok.value === '!=' ? '!==' : tok.value, left, right });
|
|
1026
1042
|
}
|
|
@@ -1171,6 +1187,8 @@ export class Parser {
|
|
|
1171
1187
|
case T.PUSH: case T.SEARCH: case T.IMAGE:
|
|
1172
1188
|
case T.CSV: case T.LOGGING: case T.MIGRATE:
|
|
1173
1189
|
case T.GRPC: case T.WEBRTC: case T.BLOCKCHAIN:
|
|
1190
|
+
case T.UNLESS: case T.UNTIL: case T.REPEAT:
|
|
1191
|
+
case T.ENUM_DECL: case T.SWAP: case T.IS: case T.ISNT:
|
|
1174
1192
|
case T.FROM: case T.AS: case T.IN:
|
|
1175
1193
|
this.advance();
|
|
1176
1194
|
return new ASTNode('Identifier', { name: tok.value });
|
|
@@ -1389,7 +1407,7 @@ export class Parser {
|
|
|
1389
1407
|
|
|
1390
1408
|
if (TYPE_TOKENS.has(this.peek().type)) {
|
|
1391
1409
|
fieldType = this.advance().value;
|
|
1392
|
-
} else if (this.at(T.IDENT)) {
|
|
1410
|
+
} else if (this.at(T.IDENT) || this.at(T.ENUM_DECL)) {
|
|
1393
1411
|
fieldType = this.advance().value;
|
|
1394
1412
|
if (fieldType === 'enum' && this.at(T.LPAREN)) {
|
|
1395
1413
|
this.advance();
|
|
@@ -2357,4 +2375,82 @@ export class Parser {
|
|
|
2357
2375
|
if (this.at(T.DEDENT)) this.advance();
|
|
2358
2376
|
return new ASTNode('BlockchainDecl', { name, network, provider, contract, abi });
|
|
2359
2377
|
}
|
|
2378
|
+
|
|
2379
|
+
// unless cond: → desugars to if (!cond)
|
|
2380
|
+
parseUnless() {
|
|
2381
|
+
this.advance();
|
|
2382
|
+
const condition = this.parseExpression();
|
|
2383
|
+
this.expect(T.COLON);
|
|
2384
|
+
const body = this.parseBlock();
|
|
2385
|
+
return new ASTNode('If', {
|
|
2386
|
+
condition: new ASTNode('Unary', { op: '!', expr: condition }),
|
|
2387
|
+
elifs: [], elseBody: null, body
|
|
2388
|
+
});
|
|
2389
|
+
}
|
|
2390
|
+
|
|
2391
|
+
// until cond: → desugars to while (!cond)
|
|
2392
|
+
parseUntil() {
|
|
2393
|
+
this.advance();
|
|
2394
|
+
const condition = this.parseExpression();
|
|
2395
|
+
this.expect(T.COLON);
|
|
2396
|
+
const body = this.parseBlock();
|
|
2397
|
+
return new ASTNode('While', {
|
|
2398
|
+
condition: new ASTNode('Unary', { op: '!', expr: condition }),
|
|
2399
|
+
body
|
|
2400
|
+
});
|
|
2401
|
+
}
|
|
2402
|
+
|
|
2403
|
+
// repeat 5: or repeat 5 as i:
|
|
2404
|
+
parseRepeat() {
|
|
2405
|
+
this.advance();
|
|
2406
|
+
const count = this.parseExpression();
|
|
2407
|
+
let varName = 'it';
|
|
2408
|
+
if (this.peek().value === 'as') {
|
|
2409
|
+
this.advance();
|
|
2410
|
+
varName = this.advance().value;
|
|
2411
|
+
}
|
|
2412
|
+
this.expect(T.COLON);
|
|
2413
|
+
const body = this.parseBlock();
|
|
2414
|
+
return new ASTNode('For', {
|
|
2415
|
+
varName,
|
|
2416
|
+
start: new ASTNode('Number', { value: '0' }),
|
|
2417
|
+
end: count,
|
|
2418
|
+
body
|
|
2419
|
+
});
|
|
2420
|
+
}
|
|
2421
|
+
|
|
2422
|
+
// enum Color: red, green, blue
|
|
2423
|
+
parseEnum() {
|
|
2424
|
+
this.advance();
|
|
2425
|
+
const name = this.advance().value;
|
|
2426
|
+
this.expect(T.COLON);
|
|
2427
|
+
const values = [];
|
|
2428
|
+
if (this.at(T.INDENT) || this.at(T.NEWLINE)) {
|
|
2429
|
+
this.skipNewlines();
|
|
2430
|
+
this.expect(T.INDENT);
|
|
2431
|
+
while (!this.at(T.DEDENT) && !this.at(T.EOF)) {
|
|
2432
|
+
this.skipNewlines();
|
|
2433
|
+
if (this.at(T.DEDENT) || this.at(T.EOF)) break;
|
|
2434
|
+
values.push(this.advance().value);
|
|
2435
|
+
this.match(T.COMMA);
|
|
2436
|
+
this.skipNewlines();
|
|
2437
|
+
}
|
|
2438
|
+
if (this.at(T.DEDENT)) this.advance();
|
|
2439
|
+
} else {
|
|
2440
|
+
while (!this.at(T.NEWLINE) && !this.at(T.EOF)) {
|
|
2441
|
+
values.push(this.advance().value);
|
|
2442
|
+
this.match(T.COMMA);
|
|
2443
|
+
}
|
|
2444
|
+
}
|
|
2445
|
+
return new ASTNode('EnumDecl', { name, values });
|
|
2446
|
+
}
|
|
2447
|
+
|
|
2448
|
+
// swap a, b
|
|
2449
|
+
parseSwap() {
|
|
2450
|
+
this.advance();
|
|
2451
|
+
const a = this.parseExpression();
|
|
2452
|
+
this.expect(T.COMMA);
|
|
2453
|
+
const b = this.parseExpression();
|
|
2454
|
+
return new ASTNode('Swap', { a, b });
|
|
2455
|
+
}
|
|
2360
2456
|
}
|
package/src/tokens.js
CHANGED
|
@@ -107,6 +107,13 @@ export const T = {
|
|
|
107
107
|
GRPC: 'GRPC',
|
|
108
108
|
WEBRTC: 'WEBRTC',
|
|
109
109
|
BLOCKCHAIN: 'BLOCKCHAIN',
|
|
110
|
+
UNLESS: 'UNLESS',
|
|
111
|
+
UNTIL: 'UNTIL',
|
|
112
|
+
REPEAT: 'REPEAT',
|
|
113
|
+
ENUM_DECL: 'ENUM_DECL',
|
|
114
|
+
SWAP: 'SWAP',
|
|
115
|
+
IS: 'IS',
|
|
116
|
+
ISNT: 'ISNT',
|
|
110
117
|
|
|
111
118
|
// Operators
|
|
112
119
|
ASSIGN: 'ASSIGN',
|
|
@@ -242,6 +249,14 @@ export const KEYWORDS = {
|
|
|
242
249
|
'grpc': T.GRPC,
|
|
243
250
|
'webrtc': T.WEBRTC,
|
|
244
251
|
'blockchain': T.BLOCKCHAIN,
|
|
252
|
+
'unless': T.UNLESS,
|
|
253
|
+
'until': T.UNTIL,
|
|
254
|
+
'repeat': T.REPEAT,
|
|
255
|
+
'enum': T.ENUM_DECL,
|
|
256
|
+
'swap': T.SWAP,
|
|
257
|
+
'is': T.IS,
|
|
258
|
+
'isnt': T.ISNT,
|
|
259
|
+
'print': T.LOG,
|
|
245
260
|
'true': T.BOOL,
|
|
246
261
|
'false': T.BOOL,
|
|
247
262
|
'null': T.NULL,
|
|
@@ -83,7 +83,7 @@
|
|
|
83
83
|
"name": "support.type.naide"
|
|
84
84
|
},
|
|
85
85
|
"keywords-control": {
|
|
86
|
-
"match": "\\b(if|elif|else|each|for|while|in|match|try|fail|ensure|ret|break|continue|throw|then|not|and|or|typeof|instanceof)\\b",
|
|
86
|
+
"match": "\\b(if|elif|else|each|for|while|in|match|try|fail|ensure|ret|break|continue|throw|then|not|and|or|typeof|instanceof|unless|until|repeat|is|isnt)\\b",
|
|
87
87
|
"name": "keyword.control.naide"
|
|
88
88
|
},
|
|
89
89
|
"keywords-server": {
|
|
@@ -91,7 +91,7 @@
|
|
|
91
91
|
"name": "keyword.other.naide"
|
|
92
92
|
},
|
|
93
93
|
"keywords-declaration": {
|
|
94
|
-
"match": "\\b(fn|fn\\.async|use|from|as|model|extends|schema|env|db|every|watch|test|assert|queue|job|new)\\b",
|
|
94
|
+
"match": "\\b(fn|fn\\.async|use|from|as|model|extends|schema|env|db|every|watch|test|assert|queue|job|new|enum|swap)\\b",
|
|
95
95
|
"name": "keyword.declaration.naide"
|
|
96
96
|
},
|
|
97
97
|
"keywords-operator": {
|
|
@@ -127,6 +127,10 @@
|
|
|
127
127
|
{
|
|
128
128
|
"match": "\\b(send|broadcast|next)\\b(?=\\s*\\()",
|
|
129
129
|
"name": "support.function.naide"
|
|
130
|
+
},
|
|
131
|
+
{
|
|
132
|
+
"match": "\\b(ask|sleep|exit|read|write|fetch_json|random|sort|reverse|unique|len|upper|lower|trim|split|join|contains|replace|keys|values|entries|range|abs|round|ceil|floor|sqrt|pow|sum|flat|zip|chunk|str|int|float|json_parse|json_str|now|time|print)\\b(?=\\s*[\\(])",
|
|
133
|
+
"name": "support.function.naide"
|
|
130
134
|
}
|
|
131
135
|
]
|
|
132
136
|
},
|