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 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 three principles: 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. 47 built-in features including servers, databases, authentication, bots, GraphQL, gRPC, WebRTC, blockchain, and more.
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
- str id = uuid() # UUID
892
- str hashed = hash("password") # scrypt hash
893
- bool ok = verify("password", hashed) # timing-safe verify
894
- str token = sign({id: 1}) # JWT (uses auth secret)
895
- str token = sign({id: 1}, "my-secret") # JWT (explicit secret)
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.15.0",
4
- "description": "NAIDE - Node AI Development Environment. AI-specialized language transpiling to 15 targets: Node.js, Python, Bun, TypeScript, C, C++, Java, Go, Rust, PHP, Ruby, Kotlin, Swift, Dart, C#. Built-in server, auth, DB, AI/LLM, bots, GraphQL, gRPC, WebRTC, blockchain, and 47 more features.",
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",
@@ -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
  },