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.
- package/ARCHITECTURE.md +424 -0
- package/MIGRATION.md +365 -0
- package/README.md +370 -0
- package/ROADMAP.md +263 -0
- package/examples/adult.future +8 -0
- package/examples/api.future +11 -0
- package/examples/assistant.future +8 -0
- package/examples/browser-demo.html +164 -0
- package/examples/greet.future +7 -0
- package/examples/hello.future +1 -0
- package/examples/math.future +8 -0
- package/examples/mini-app.html +301 -0
- package/examples/smarthome.future +10 -0
- package/future-browser.js +102 -0
- package/future-playground.html +650 -0
- package/package.json +27 -0
- package/runtime/ai.js +92 -0
- package/runtime/browser.js +458 -0
- package/runtime/device.js +36 -0
- package/runtime/home.js +19 -0
- package/runtime/http.js +32 -0
- package/runtime/index.js +403 -0
- package/runtime/lsp-metadata.js +104 -0
- package/runtime/math.js +16 -0
- package/runtime/memory.js +61 -0
- package/runtime/mqtt.js +49 -0
- package/runtime/providers/anthropic.js +59 -0
- package/runtime/providers/index.js +93 -0
- package/runtime/providers/openai-compat.js +85 -0
- package/runtime/providers/util.js +70 -0
- package/runtime/rag/chunker.js +65 -0
- package/runtime/rag/pipeline.js +86 -0
- package/runtime/rag/vector-store.js +119 -0
- package/runtime/rag.js +94 -0
- package/runtime/schedule.js +77 -0
- package/runtime/system.js +101 -0
- package/runtime/tts.js +38 -0
- package/runtime/vision.js +85 -0
- package/server.js +42 -0
- package/src/ast.js +202 -0
- package/src/cli.js +391 -0
- package/src/errors.js +21 -0
- package/src/formatter.js +48 -0
- package/src/generator.js +457 -0
- package/src/index.js +48 -0
- package/src/lexer.js +248 -0
- 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 & 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) => ({ '&': '&', '<': '<', '>': '>' }[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
|
+
}
|