future-lang 0.3.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.
Files changed (47) hide show
  1. package/ARCHITECTURE.md +424 -0
  2. package/MIGRATION.md +365 -0
  3. package/README.md +370 -0
  4. package/ROADMAP.md +263 -0
  5. package/examples/adult.future +8 -0
  6. package/examples/api.future +11 -0
  7. package/examples/assistant.future +8 -0
  8. package/examples/browser-demo.html +164 -0
  9. package/examples/greet.future +7 -0
  10. package/examples/hello.future +1 -0
  11. package/examples/math.future +8 -0
  12. package/examples/mini-app.html +301 -0
  13. package/examples/smarthome.future +10 -0
  14. package/future-browser.js +102 -0
  15. package/future-playground.html +650 -0
  16. package/package.json +27 -0
  17. package/runtime/ai.js +92 -0
  18. package/runtime/browser.js +458 -0
  19. package/runtime/device.js +36 -0
  20. package/runtime/home.js +19 -0
  21. package/runtime/http.js +32 -0
  22. package/runtime/index.js +403 -0
  23. package/runtime/lsp-metadata.js +104 -0
  24. package/runtime/math.js +16 -0
  25. package/runtime/memory.js +61 -0
  26. package/runtime/mqtt.js +49 -0
  27. package/runtime/providers/anthropic.js +59 -0
  28. package/runtime/providers/index.js +93 -0
  29. package/runtime/providers/openai-compat.js +85 -0
  30. package/runtime/providers/util.js +70 -0
  31. package/runtime/rag/chunker.js +65 -0
  32. package/runtime/rag/pipeline.js +86 -0
  33. package/runtime/rag/vector-store.js +119 -0
  34. package/runtime/rag.js +94 -0
  35. package/runtime/schedule.js +77 -0
  36. package/runtime/system.js +101 -0
  37. package/runtime/tts.js +38 -0
  38. package/runtime/vision.js +85 -0
  39. package/server.js +42 -0
  40. package/src/ast.js +202 -0
  41. package/src/cli.js +391 -0
  42. package/src/errors.js +21 -0
  43. package/src/formatter.js +48 -0
  44. package/src/generator.js +457 -0
  45. package/src/index.js +48 -0
  46. package/src/lexer.js +248 -0
  47. package/src/parser.js +469 -0
@@ -0,0 +1,650 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8" />
5
+ <meta name="viewport" content="width=device-width, initial-scale=1" />
6
+ <title>Future — Playground</title>
7
+ <link rel="preconnect" href="https://fonts.googleapis.com" />
8
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
9
+ <link href="https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
10
+
11
+ <style>
12
+ :root {
13
+ --bg: #14121d;
14
+ --panel: #1b1830;
15
+ --border: #322c4c;
16
+ --ink: #ece9f6;
17
+ --muted: #968fb4;
18
+ --amber: #f6b250;
19
+ --amber-s: rgba(246,178,80,.14);
20
+ --teal: #54d3c4;
21
+ --violet: #8b7bf0;
22
+ --red: #ff6b81;
23
+ --r: 12px;
24
+ --mono: "JetBrains Mono", ui-monospace, Menlo, monospace;
25
+ --display: "Space Grotesk", ui-sans-serif, system-ui, sans-serif;
26
+ }
27
+ *, *::before, *::after { box-sizing: border-box; }
28
+ html, body { height: 100%; margin: 0; }
29
+
30
+ body {
31
+ color: var(--ink);
32
+ font-family: var(--display);
33
+ -webkit-font-smoothing: antialiased;
34
+ display: flex;
35
+ flex-direction: column;
36
+ min-height: 100vh;
37
+ background:
38
+ radial-gradient(900px 500px at 85% -10%, rgba(139,123,240,.16), transparent 60%),
39
+ radial-gradient(700px 500px at -5% 110%, rgba(84,211,196,.10), transparent 55%),
40
+ var(--bg);
41
+ }
42
+
43
+ /* ── Header ────────────────────────────────────────────────────────────── */
44
+ header {
45
+ display: flex;
46
+ align-items: baseline;
47
+ gap: 14px;
48
+ padding: 22px 26px 14px;
49
+ flex-wrap: wrap;
50
+ }
51
+ .wordmark { font-size: 26px; font-weight: 700; letter-spacing: -.02em; }
52
+ .wordmark b { color: var(--amber); }
53
+ .wordmark .sep { color: var(--violet); margin: 0 6px; font-weight: 500; }
54
+ .wordmark .sub { color: var(--muted); font-weight: 500; font-size: 19px; }
55
+ .tagline {
56
+ margin-left: auto;
57
+ color: var(--muted);
58
+ font-size: 13px;
59
+ font-family: var(--mono);
60
+ }
61
+ .tagline b { color: var(--teal); font-weight: 500; }
62
+
63
+ /* ── Toolbar ───────────────────────────────────────────────────────────── */
64
+ .toolbar {
65
+ display: flex;
66
+ align-items: center;
67
+ gap: 10px;
68
+ padding: 0 26px 10px;
69
+ flex-wrap: wrap;
70
+ }
71
+ button.run {
72
+ font-family: var(--display);
73
+ font-weight: 600;
74
+ font-size: 14px;
75
+ color: #1a1304;
76
+ background: var(--amber);
77
+ border: none;
78
+ border-radius: 9px;
79
+ padding: 9px 18px;
80
+ cursor: pointer;
81
+ transition: transform .08s, filter .15s;
82
+ }
83
+ button.run:hover { filter: brightness(1.06); }
84
+ button.run:active { transform: translateY(1px); }
85
+ button.run:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
86
+
87
+ .ex-group { display: flex; gap: 6px; align-items: center; flex-wrap: wrap; margin-left: 6px; }
88
+ .ex-label { color: var(--muted); font-size: 12px; font-family: var(--mono); margin-right: 2px; }
89
+
90
+ .chip {
91
+ font-family: var(--mono);
92
+ font-size: 12.5px;
93
+ color: var(--ink);
94
+ background: transparent;
95
+ border: 1px solid var(--border);
96
+ border-radius: 7px;
97
+ padding: 6px 11px;
98
+ cursor: pointer;
99
+ transition: border-color .15s, background .15s, color .15s;
100
+ }
101
+ .chip:hover { border-color: var(--violet); color: #fff; }
102
+ .chip[aria-pressed="true"] { background: var(--amber-s); border-color: var(--amber); color: var(--amber); }
103
+ .chip:focus-visible { outline: 2px solid var(--violet); outline-offset: 2px; }
104
+
105
+ /* ── Capabilities strip ────────────────────────────────────────────────── */
106
+ .caps {
107
+ padding: 0 26px 12px;
108
+ font-family: var(--mono);
109
+ font-size: 11.5px;
110
+ color: var(--muted);
111
+ }
112
+ .caps b { color: var(--ink); font-weight: 500; }
113
+ .caps .ok { color: var(--teal); }
114
+ .caps .key { color: var(--amber); }
115
+
116
+ /* ── Main layout ───────────────────────────────────────────────────────── */
117
+ main {
118
+ flex: 1;
119
+ display: grid;
120
+ grid-template-columns: 1fr 1fr;
121
+ gap: 14px;
122
+ padding: 0 26px 18px;
123
+ min-height: 0;
124
+ }
125
+ .col { display: flex; flex-direction: column; gap: 14px; min-height: 0; }
126
+
127
+ .pane {
128
+ background: var(--panel);
129
+ border: 1px solid var(--border);
130
+ border-radius: var(--r);
131
+ display: flex;
132
+ flex-direction: column;
133
+ min-height: 0;
134
+ overflow: hidden;
135
+ }
136
+ .pane.grow { flex: 1; }
137
+
138
+ .pane-head {
139
+ display: flex;
140
+ align-items: center;
141
+ justify-content: space-between;
142
+ padding: 11px 15px;
143
+ border-bottom: 1px solid var(--border);
144
+ font-size: 12px;
145
+ font-family: var(--mono);
146
+ color: var(--muted);
147
+ flex-shrink: 0;
148
+ }
149
+ .dot {
150
+ display: inline-block;
151
+ width: 8px; height: 8px;
152
+ border-radius: 50%;
153
+ margin-right: 8px;
154
+ vertical-align: middle;
155
+ }
156
+ .dot-src { background: var(--amber); }
157
+ .dot-js { background: var(--violet); }
158
+ .dot-out { background: var(--teal); }
159
+ .badge { color: var(--muted); font-size: 11px; }
160
+
161
+ textarea, pre.code {
162
+ font-family: var(--mono);
163
+ font-size: 13.5px;
164
+ line-height: 1.65;
165
+ color: var(--ink);
166
+ background: transparent;
167
+ border: none;
168
+ margin: 0;
169
+ padding: 15px;
170
+ flex: 1;
171
+ width: 100%;
172
+ resize: none;
173
+ overflow: auto;
174
+ tab-size: 2;
175
+ }
176
+ textarea { outline: none; }
177
+ textarea:focus { background: rgba(139,123,240,.04); }
178
+ pre.code { white-space: pre; color: #d8d3ee; }
179
+
180
+ .output {
181
+ font-family: var(--mono);
182
+ font-size: 13.5px;
183
+ line-height: 1.7;
184
+ padding: 15px;
185
+ flex: 1;
186
+ overflow: auto;
187
+ white-space: pre-wrap;
188
+ word-break: break-word;
189
+ }
190
+ .out-line { color: var(--teal); }
191
+ .out-tts { color: var(--amber); }
192
+ .out-empty { color: var(--muted); font-style: italic; }
193
+ .out-err {
194
+ color: var(--red);
195
+ border-left: 2px solid var(--red);
196
+ padding-left: 10px;
197
+ margin: 2px 0;
198
+ }
199
+ .out-err .where { color: var(--muted); }
200
+
201
+ /* ── Status bar ────────────────────────────────────────────────────────── */
202
+ .status {
203
+ border-top: 1px solid var(--border);
204
+ padding: 9px 26px;
205
+ font-family: var(--mono);
206
+ font-size: 12px;
207
+ color: var(--muted);
208
+ display: flex;
209
+ gap: 10px;
210
+ align-items: center;
211
+ flex-shrink: 0;
212
+ }
213
+ .status .ok { color: var(--teal); }
214
+ .status .bad { color: var(--red); }
215
+ .status .sep { color: var(--border); }
216
+
217
+ @media (max-width: 820px) {
218
+ main { grid-template-columns: 1fr; }
219
+ .pane.editor { min-height: 240px; }
220
+ .tagline { margin-left: 0; width: 100%; }
221
+ }
222
+ @media (prefers-reduced-motion: reduce) { * { transition: none !important; } }
223
+ </style>
224
+ </head>
225
+ <body>
226
+
227
+ <header>
228
+ <div class="wordmark">
229
+ <b>Future</b><span class="sep">/</span><span class="sub">playground</span>
230
+ </div>
231
+ <div class="tagline">compiles &amp; runs in the browser · <b>real capabilities</b></div>
232
+ </header>
233
+
234
+ <div class="toolbar">
235
+ <button class="run" id="btn-run">▸ Run</button>
236
+ <div class="ex-group" id="examples">
237
+ <span class="ex-label">examples:</span>
238
+ <button class="chip" data-ex="hello" aria-pressed="true">hello</button>
239
+ <button class="chip" data-ex="lists" aria-pressed="false">lists</button>
240
+ <button class="chip" data-ex="objects" aria-pressed="false">objects</button>
241
+ <button class="chip" data-ex="errors" aria-pressed="false">errors</button>
242
+ <button class="chip" data-ex="math" aria-pressed="false">math</button>
243
+ <button class="chip" data-ex="input" aria-pressed="false">input</button>
244
+ <button class="chip" data-ex="http" aria-pressed="false">http</button>
245
+ <button class="chip" data-ex="memory" aria-pressed="false">memory</button>
246
+ <button class="chip" data-ex="tts" aria-pressed="false">tts</button>
247
+ <button class="chip" data-ex="ai" aria-pressed="false">ai</button>
248
+ <button class="chip" data-ex="agent" aria-pressed="false">agent</button>
249
+ </div>
250
+ </div>
251
+
252
+ <div class="caps">
253
+ browser capabilities:
254
+ <b>http</b> <span class="ok">fetch ✓</span> ·
255
+ <b>memory</b> <span class="ok">✓</span> ·
256
+ <b>math</b> <span class="ok">✓</span> ·
257
+ <b>len()</b> <span class="ok">built-in ✓</span> ·
258
+ <b>input()</b> <span class="ok">window.prompt ✓</span> ·
259
+ <b>tts</b> <span class="ok">Web Speech ✓</span> ·
260
+ <b>schedule</b> <span class="ok">✓</span> ·
261
+ <b>ai</b> / <b>rag</b> / <b>vision</b> <span class="key">requires key or proxy</span>
262
+ </div>
263
+
264
+ <main>
265
+ <div class="col">
266
+ <div class="pane editor grow">
267
+ <div class="pane-head">
268
+ <span><span class="dot dot-src"></span>Future source</span>
269
+ <span class="badge">.future</span>
270
+ </div>
271
+ <textarea id="src" spellcheck="false" autocomplete="off"></textarea>
272
+ </div>
273
+ </div>
274
+ <div class="col">
275
+ <div class="pane grow">
276
+ <div class="pane-head">
277
+ <span><span class="dot dot-js"></span>Generated JavaScript</span>
278
+ <span class="badge">live</span>
279
+ </div>
280
+ <pre class="code" id="js"></pre>
281
+ </div>
282
+ <div class="pane grow">
283
+ <div class="pane-head">
284
+ <span><span class="dot dot-out"></span>Output</span>
285
+ <span class="badge">print + capabilities</span>
286
+ </div>
287
+ <div class="output" id="out">
288
+ <span class="out-empty">Press "Run" or Ctrl+Enter to execute.</span>
289
+ </div>
290
+ </div>
291
+ </div>
292
+ </main>
293
+
294
+ <div class="status" id="status"><span>ready</span></div>
295
+
296
+ <script type="module" src="./future-browser.js"></script>
297
+
298
+ <script type="module">
299
+ import Future from './future-browser.js';
300
+
301
+ // ── Examples ────────────────────────────────────────────────────────────────
302
+
303
+ const EXAMPLES = {
304
+ hello: `\
305
+ # String interpolation + arithmetic
306
+ name = "World"
307
+ year = 2025
308
+ print "Hello, {name}!"
309
+ print "The year is {year}"
310
+
311
+ x = 10
312
+ y = 3
313
+ print "Sum: {x} + {y} = " + (x + y)
314
+ `,
315
+
316
+ lists: `\
317
+ # Arrays + for loop + while loop
318
+ fruits = ["apple", "banana", "cherry"]
319
+
320
+ for fruit in fruits
321
+ print "I like {fruit}"
322
+ end
323
+
324
+ # while loop
325
+ count = 1
326
+ while count <= 3
327
+ print "Count: {count}"
328
+ count = count + 1
329
+ end
330
+ `,
331
+
332
+ objects: `\
333
+ # Object literals + null
334
+ user = {
335
+ name: "Alice"
336
+ age: 28
337
+ city: "Lisbon"
338
+ }
339
+
340
+ print "Name: {user.name}"
341
+ print "Age: {user.age}"
342
+
343
+ # Null handling
344
+ session = null
345
+ if session == null
346
+ print "No active session"
347
+ end
348
+
349
+ # Nested data
350
+ team = ["Alice", "Bob", "Carlos"]
351
+ for member in team
352
+ print "{member} is on the team"
353
+ end
354
+ `,
355
+
356
+ errors: `\
357
+ # try / catch error handling
358
+ function divide(a, b)
359
+ if b == 0
360
+ return null
361
+ end
362
+ return a / b
363
+ end
364
+
365
+ result = divide(10, 2)
366
+ print "10 / 2 = {result}"
367
+
368
+ # Catch a real runtime error
369
+ try
370
+ bad = http.get("https://this-domain-does-not-exist-xyz.com/api")
371
+ print bad
372
+ catch err
373
+ print "Caught error: request failed (expected)"
374
+ end
375
+
376
+ print "Program continues after error"
377
+ `,
378
+
379
+ math: `\
380
+ # math module + len() built-in
381
+ scores = [85, 92, 78, 95, 88, 61, 73]
382
+ n = len(scores)
383
+ print "Scores: {scores}"
384
+ print "Count: {n}"
385
+
386
+ total = 0
387
+ for s in scores
388
+ total = total + s
389
+ end
390
+
391
+ avg = math.round(total / n)
392
+ print "Average: {avg}"
393
+
394
+ best = math.max(85, 92, 78, 95, 88, 61, 73)
395
+ worst = math.min(85, 92, 78, 95, 88, 61, 73)
396
+ print "Best: {best}"
397
+ print "Worst: {worst}"
398
+
399
+ # Constants and functions
400
+ print "π ≈ {math.pi}"
401
+ diagonal = math.sqrt(math.pow(3, 2) + math.pow(4, 2))
402
+ print "Hypotenuse of 3-4-5 triangle: {diagonal}"
403
+ `,
404
+
405
+ input: `\
406
+ # input() — read a line from the user
407
+ # In the browser this uses window.prompt().
408
+ # In Node.js CLI it reads from stdin.
409
+
410
+ name = input("What is your name? ")
411
+ print "Hello, {name}!"
412
+
413
+ age = input("How old are you? ")
414
+ future_age = age + 10
415
+ print "In 10 years you will be {future_age}"
416
+
417
+ # Works in the browser (window.prompt) and CLI (stdin)
418
+ size = math.round(math.pi * math.pow(10, 2))
419
+ print "Area of circle r=10: {size}"
420
+ `,
421
+
422
+ http: `\
423
+ # Real HTTP request via fetch (live)
424
+ todo = http.get("https://jsonplaceholder.typicode.com/todos/1")
425
+ print "Title: {todo.title}"
426
+
427
+ if todo.completed == true
428
+ print "Status: done"
429
+ else
430
+ print "Status: pending"
431
+ end
432
+
433
+ # Fetch a user
434
+ user = http.get("https://jsonplaceholder.typicode.com/users/1")
435
+ print "User: {user.name} ({user.email})"
436
+ `,
437
+
438
+ memory: `\
439
+ # In-memory key-value store
440
+ memory.set("language", "Future")
441
+ memory.set("version", "0.1")
442
+ memory.set("author", "unknown")
443
+
444
+ lang = memory.get("language")
445
+ ver = memory.get("version")
446
+ print "Running {lang} v{ver}"
447
+
448
+ # Search
449
+ results = memory.search("ver")
450
+ print "Found {results.length} key(s) matching 'ver'"
451
+
452
+ # Forget a specific key
453
+ memory.forget("author")
454
+ check = memory.get("author")
455
+ if check == null
456
+ print "author key was removed"
457
+ end
458
+ `,
459
+
460
+ tts: `\
461
+ # Text-to-speech via Web Speech API
462
+ print "Speaking out loud..."
463
+ tts.speak("Hello! I am the Future programming language running in your browser.")
464
+
465
+ names = ["Alice", "Bob", "Carlos"]
466
+ for name in names
467
+ print "Greeting {name}..."
468
+ tts.speak("Hello, {name}!")
469
+ end
470
+ `,
471
+
472
+ ai: `\
473
+ # AI requires a key or proxy.
474
+ # Uncomment and configure in your own page:
475
+ #
476
+ # Future.configure({ provider: 'openai', apiKey: 'sk-...' })
477
+ # Future.configure({ proxy: '/api/ai' })
478
+
479
+ try
480
+ answer = ai.ask("What is the capital of Portugal? One word only.")
481
+ print "Answer: {answer}"
482
+ catch err
483
+ print "AI not configured."
484
+ print "Add Future.configure({ provider: 'openai', apiKey: 'sk-...' })"
485
+ print "or Future.configure({ proxy: '/api/ai' })"
486
+ end
487
+ `,
488
+
489
+ agent: `\
490
+ # Agents are named async tasks with an implicit "goal" parameter
491
+ # They need AI to be configured for AI calls.
492
+
493
+ agent greeter
494
+ msg = "Hello, {goal}! Welcome to Future."
495
+ return msg
496
+ end
497
+
498
+ # This agent works without AI
499
+ reply = greeter("World")
500
+ print reply
501
+
502
+ # An AI-powered agent (needs key/proxy)
503
+ agent translator
504
+ use ai
505
+ try
506
+ result = ai.ask("Translate to Portuguese in one short sentence: {goal}")
507
+ return result
508
+ catch err
509
+ return "AI not configured — set a key or proxy"
510
+ end
511
+ end
512
+
513
+ translation = translator("The weather is beautiful today.")
514
+ print translation
515
+ `,
516
+ };
517
+
518
+ // ── DOM refs ────────────────────────────────────────────────────────────────
519
+
520
+ const $src = document.getElementById('src');
521
+ const $js = document.getElementById('js');
522
+ const $out = document.getElementById('out');
523
+ const $status = document.getElementById('status');
524
+ const $run = document.getElementById('btn-run');
525
+ const $chips = document.getElementById('examples');
526
+
527
+ // ── Helpers ─────────────────────────────────────────────────────────────────
528
+
529
+ const esc = (s) =>
530
+ String(s).replace(/[&<>]/g, (c) => ({ '&': '&amp;', '<': '&lt;', '>': '&gt;' }[c]));
531
+
532
+ function setStatus(html) {
533
+ $status.innerHTML = html;
534
+ }
535
+
536
+ function showLogs(logs) {
537
+ if (logs.length === 0) {
538
+ $out.innerHTML = '<span class="out-empty">(ran without printing anything)</span>';
539
+ return;
540
+ }
541
+ $out.innerHTML = logs
542
+ .map(({ kind, text }) => `<div class="${kind === 'tts' ? 'out-tts' : 'out-line'}">${esc(text)}</div>`)
543
+ .join('');
544
+ }
545
+
546
+ function showError(e, prior = []) {
547
+ const priorHtml = prior
548
+ .map(({ kind, text }) => `<div class="${kind === 'tts' ? 'out-tts' : 'out-line'}">${esc(text)}</div>`)
549
+ .join('');
550
+ const where = (e.line != null)
551
+ ? ` <span class="where">— line ${e.line}, column ${e.column}</span>`
552
+ : '';
553
+ const phase = e.phase ?? 'error';
554
+ $out.innerHTML = priorHtml +
555
+ `<div class="out-err">error[${esc(phase)}]: ${esc(e.message)}${where}</div>`;
556
+ setStatus(`<span class="bad">● ${esc(e.phase === 'parse' || e.phase === 'lex' ? 'compile error' : 'runtime error')}</span>`);
557
+ }
558
+
559
+ // ── Live compile (JS preview while typing) ───────────────────────────────────
560
+
561
+ let liveTimer;
562
+ function liveCompile() {
563
+ clearTimeout(liveTimer);
564
+ liveTimer = setTimeout(() => {
565
+ try {
566
+ $js.textContent = Future.compile($src.value);
567
+ setStatus('<span class="ok">● compiled</span><span class="sep">·</span><span>Future → JavaScript · live</span>');
568
+ } catch (e) {
569
+ const where = e.line != null ? ` (line ${e.line})` : '';
570
+ $js.textContent = `// compile error\n// ${e.message}`;
571
+ setStatus(`<span class="bad">● error[${esc(e.phase ?? 'compile')}]</span><span class="sep">·</span><span>${esc(e.message)}${where}</span>`);
572
+ }
573
+ }, 180);
574
+ }
575
+
576
+ // ── Run ──────────────────────────────────────────────────────────────────────
577
+
578
+ async function run() {
579
+ // Compile first so JS preview is up to date
580
+ let compiled;
581
+ try {
582
+ compiled = Future.compile($src.value);
583
+ $js.textContent = compiled;
584
+ } catch (e) {
585
+ showError(e);
586
+ return;
587
+ }
588
+
589
+ // Capture print + tts output
590
+ const logs = [];
591
+ Future.runtime.print = (...args) => {
592
+ logs.push({ kind: 'line', text: args.join(' ') });
593
+ };
594
+ Future.runtime.tts.speak = async (text) => {
595
+ logs.push({ kind: 'tts', text: `🔊 ${text}` });
596
+ if (window.speechSynthesis) {
597
+ speechSynthesis.cancel();
598
+ speechSynthesis.speak(new SpeechSynthesisUtterance(String(text)));
599
+ }
600
+ return String(text);
601
+ };
602
+
603
+ setStatus('<span>● running…</span>');
604
+ try {
605
+ await Future.run($src.value);
606
+ } catch (e) {
607
+ showError(e, logs);
608
+ return;
609
+ }
610
+ showLogs(logs);
611
+ setStatus(
612
+ `<span class="ok">● done</span><span class="sep">·</span>` +
613
+ `<span>${logs.length} line${logs.length !== 1 ? 's' : ''} of output</span>`
614
+ );
615
+ }
616
+
617
+ // ── Load example ─────────────────────────────────────────────────────────────
618
+
619
+ function loadExample(name) {
620
+ if (!EXAMPLES[name]) return;
621
+ $src.value = EXAMPLES[name];
622
+ [...$chips.querySelectorAll('.chip')].forEach((c) =>
623
+ c.setAttribute('aria-pressed', String(c.dataset.ex === name))
624
+ );
625
+ liveCompile();
626
+ $out.innerHTML = '<span class="out-empty">Press "Run" or Ctrl+Enter to execute.</span>';
627
+ }
628
+
629
+ // ── Event listeners ──────────────────────────────────────────────────────────
630
+
631
+ $run.addEventListener('click', run);
632
+ $src.addEventListener('input', liveCompile);
633
+ $chips.addEventListener('click', (e) => {
634
+ const chip = e.target.closest('.chip');
635
+ if (chip) loadExample(chip.dataset.ex);
636
+ });
637
+ $src.addEventListener('keydown', (e) => {
638
+ if ((e.ctrlKey || e.metaKey) && e.key === 'Enter') {
639
+ e.preventDefault();
640
+ run();
641
+ }
642
+ });
643
+
644
+ // ── Boot ──────────────────────────────────────────────────────────────────────
645
+
646
+ loadExample('hello');
647
+ </script>
648
+
649
+ </body>
650
+ </html>
package/package.json ADDED
@@ -0,0 +1,27 @@
1
+ {
2
+ "name": "future-lang",
3
+ "version": "0.3.0",
4
+ "description": "Future — a small programming language that transpiles to JavaScript, with a capability runtime (HTTP/AI/MQTT/TTS).",
5
+ "type": "module",
6
+ "bin": {
7
+ "future": "src/cli.js"
8
+ },
9
+ "exports": {
10
+ ".": "./src/index.js",
11
+ "./runtime": "./runtime/index.js",
12
+ "./runtime/lsp-metadata": "./runtime/lsp-metadata.js"
13
+ },
14
+ "scripts": {
15
+ "start": "node src/cli.js",
16
+ "test": "node --test"
17
+ },
18
+ "engines": {
19
+ "node": ">=22"
20
+ },
21
+ "optionalDependencies": {
22
+ "mqtt": "^5.0.0",
23
+ "node-cron": "^3.0.0"
24
+ },
25
+ "keywords": ["language", "compiler", "transpiler", "future", "ai", "mqtt", "tts"],
26
+ "license": "MIT"
27
+ }