future-lang 0.4.1 → 0.4.2

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 CHANGED
@@ -15,22 +15,25 @@ future-lang/
15
15
  │ ├── index.js # Public API — compile(source, options)
16
16
  │ ├── lexer.js # Phase 1: source text → token list
17
17
  │ ├── parser.js # Phase 2: token list → AST
18
- │ ├── ast.js # AST node types and factory functions (23 types)
18
+ │ ├── ast.js # AST node types and factory functions (24 types)
19
19
  │ ├── generator.js # Phase 3: AST → JavaScript source
20
20
  │ ├── errors.js # FutureError with line/column tracking
21
- └── cli.js # CLI binary: future run / future compile
21
+ ├── formatter.js # Line-based auto-formatter (future fmt)
22
+ │ ├── sourcemap.js # VLQ encoder + Source Map v3 builder
23
+ │ └── cli.js # CLI binary: 9 commands including future test
22
24
 
23
25
  ├── runtime/ # Capability modules (imported by generated JS)
24
26
  │ ├── index.js # Aggregator: runtime object + manifest + introspection
25
27
  │ │
26
- │ ├── ai.js # Text generation — pluggable provider architecture
27
- │ ├── http.js # REST API consumption (fetch-based)
28
+ │ ├── ai.js # Text generation — ask/chat/stream accept opts {temperature, max_tokens, model}
29
+ │ ├── http.js # REST API HttpError class, configure() for global headers/timeout
28
30
  │ ├── mqtt.js # Pub/sub messaging (real broker or in-process loopback)
29
31
  │ ├── tts.js # Text-to-speech (system engine)
30
32
  │ ├── rag.js # RAG — chunk → embed → store → retrieve → answer
31
33
  │ ├── vision.js # Vision AI — describe, detect, ocr, classify, compare
32
34
  │ ├── home.js # Home automation (composes over MQTT)
33
35
  │ │
36
+ │ ├── assert.js # Test assertions — ok/equal/notEqual/deepEqual/fail
34
37
  │ ├── memory.js # In-process key-value store (set/get/search/delete/forget)
35
38
  │ ├── schedule.js # Recurring / one-shot / cron scheduling
36
39
  │ ├── system.js # OS utilities: exec, open, notify, read, write
@@ -41,7 +44,7 @@ future-lang/
41
44
  │ │
42
45
  │ ├── providers/ # AI provider implementations
43
46
  │ │ ├── index.js # Provider factory + env-var resolution
44
- │ │ ├── anthropic.js # Anthropic Messages API (native format)
47
+ │ │ ├── anthropic.js # Anthropic Messages API AiError class, opts forwarding
45
48
  │ │ ├── openai-compat.js # OpenAI-compatible (OpenAI, Ollama, Gemini, OpenRouter, …)
46
49
  │ │ └── util.js # SSE parser, keyword vector, cosine similarity
47
50
  │ │
@@ -69,6 +72,7 @@ future-lang/
69
72
 
70
73
  ├── future-browser.js # Browser entry point: window.Future + <script type="future">
71
74
  ├── future-playground.html # In-browser editor (11 examples, live compile)
75
+ ├── FUTURE_FOR_LLMS.md # BNF grammar + all APIs — quick-reference for AI assistants
72
76
  ├── package.json
73
77
  ├── ARCHITECTURE.md # This file
74
78
  ├── ROADMAP.md # Feature roadmap
@@ -114,7 +118,7 @@ In **browser mode** (`browserMode: true` option), the `import` statement is omit
114
118
 
115
119
  ## Lexer
116
120
 
117
- ### Keywords (24)
121
+ ### Keywords (26)
118
122
 
119
123
  | Keyword | Token | Purpose |
120
124
  |---------|-------|---------|
@@ -136,7 +140,8 @@ In **browser mode** (`browserMode: true` option), the `import` statement is omit
136
140
  | `null` / `none` | NULL | Null literal (two spellings) |
137
141
  | `stream` | STREAM | Streaming statement |
138
142
  | `agent` | AGENT | Agent declaration |
139
- | `use` | USE | Capability declaration inside `agent` |
143
+ | `use` | USE | Import statement / capability declaration inside `agent` |
144
+ | `as` | AS | Alias in `use "..." as alias` |
140
145
 
141
146
  ### String escape
142
147
 
@@ -144,12 +149,13 @@ In **browser mode** (`browserMode: true` option), the `import` statement is omit
144
149
 
145
150
  ---
146
151
 
147
- ## AST Node Types (23)
152
+ ## AST Node Types (24)
148
153
 
149
154
  | Category | Nodes |
150
155
  |----------|-------|
151
156
  | Program | `Program` |
152
157
  | Statements | `PrintStatement`, `Assignment`, `IfStatement`, `FunctionDeclaration`, `ReturnStatement`, `ExpressionStatement` |
158
+ | Import | `UseStatement` — `use "path"` / `use "path" as alias` |
153
159
  | Control flow | `ForStatement`, `WhileStatement`, `TryStatement` |
154
160
  | Event statements | `OnStatement`, `EveryStatement` |
155
161
  | AI/IoT statements | `AgentDeclaration`, `StreamStatement` |
@@ -191,12 +197,37 @@ export const NAMESPACES = new Set([
191
197
  'ai', 'http', 'mqtt', 'tts',
192
198
  'rag', 'vision', 'home',
193
199
  'memory', 'schedule', 'system', 'device',
194
- 'math',
200
+ 'math', 'assert',
195
201
  ]);
196
202
  ```
197
203
 
198
204
  Any identifier in this set, when used as the object of a `MemberExpression` or `CallExpression`, is routed through `__rt` in ASYNC mode. Adding a new capability is just a name in this set plus a matching runtime module — no grammar change.
199
205
 
206
+ `use ... as alias` imports are tracked in a `useAliases` Set and explicitly excluded from namespace routing — `m.add()` never becomes `__rt.m.add()`.
207
+
208
+ ### Source maps (`sourceMaps` option)
209
+
210
+ When `compile(source, { sourceMaps: true })` is used, the generator prefixes each top-level statement line with `/*@FL:N*/` (where N is the original `.future` line number). `src/sourcemap.js` post-processes this output:
211
+
212
+ 1. Scans the generated JS line by line.
213
+ 2. Strips `/*@FL:N*/` markers.
214
+ 3. Builds VLQ-encoded `mappings` in Source Map v3 format.
215
+ 4. Returns `{ code: cleanJS, map: { version: 3, sources, sourcesContent, mappings } }`.
216
+
217
+ The CLI appends `//# sourceMappingURL=file.js.map` to the clean JS and writes the map as JSON.
218
+
219
+ ### Import system (UseStatement)
220
+
221
+ `use "./file.future"` statements are emitted before all other code. At compile time, when `resolveSource` is provided, the compiler reads the imported file, parses it, and extracts top-level `FunctionDeclaration` names. This enables named imports:
222
+
223
+ ```js
224
+ import { formatName, greet } from "./utils.js"; // named import (default)
225
+ import * as m from "./math.js"; // namespace import (alias)
226
+ import * as df from "date-fns"; // npm package (alias)
227
+ ```
228
+
229
+ The `pathMap` option (a `Map<futurePath, fileURL>`) allows `future run` to redirect imports to temp `.mjs` files compiled in `tmpdir`.
230
+
200
231
  ### String interpolation
201
232
 
202
233
  String literals containing `{identifier}` or `{identifier.prop}` are emitted as JS template literals. Namespace references inside strings are correctly prefixed with `__rt.` in ASYNC mode:
@@ -227,7 +258,7 @@ In ASYNC mode, ALL call expressions where the callee is a `MemberExpression` are
227
258
  ```
228
259
  runtime/index.js
229
260
 
230
- ├── Imports 12 capability modules (+ readline for input())
261
+ ├── Imports 13 capability modules (+ readline for input())
231
262
  ├── Exports `runtime` object → used as `__rt` in generated JS
232
263
  ├── Exports `manifest` → structured metadata for every function
233
264
  └── Attaches introspection:
@@ -237,6 +268,8 @@ runtime/index.js
237
268
  runtime.input(prompt) → Promise<string> (stdin)
238
269
  ```
239
270
 
271
+ When `FUTURE_DEBUG=1` (`future run --debug`), the runtime is wrapped by `wrapDebug()` which proxies every namespace method to log timing and arguments to stderr with ANSI colours.
272
+
240
273
  ### Manifest shape (per function)
241
274
 
242
275
  ```js
@@ -402,14 +435,15 @@ await __rt.ai.stream("Tell me a story", async (chunk) => {
402
435
 
403
436
  ---
404
437
 
405
- ## Adding a New Capability (4 steps)
438
+ ## Adding a New Capability (5 steps)
406
439
 
407
440
  1. Create `runtime/mymodule.js` with named exports.
408
441
  2. Import it in `runtime/index.js`, add to `runtime` object, `MODULE_NAMES`, and `manifest`.
409
442
  3. Add the namespace name to `NAMESPACES` in `src/generator.js`.
410
- 4. Add the module to `browserRuntime` in `runtime/browser.js` if browser support is wanted.
443
+ 4. Add the namespace name to `RESERVED_NAMESPACES` in `src/parser.js`.
444
+ 5. Add the module to `browserRuntime` in `runtime/browser.js` if browser support is wanted.
411
445
 
412
- No grammar, lexer, parser, or AST changes needed.
446
+ No grammar, lexer, or AST changes needed.
413
447
 
414
448
  ---
415
449
 
@@ -4,6 +4,19 @@ Future is a small language that compiles to JavaScript. It does NOT exist in you
4
4
 
5
5
  ---
6
6
 
7
+ ## Capability layers
8
+
9
+ | Layer | Namespaces | Notes |
10
+ |-------|-----------|-------|
11
+ | **Core language** | *(none)* | variables, if/end, for/end, while/end, try/catch/end, functions, lists, objects, strings |
12
+ | **Standard** | `math` `http` `memory` `system` `schedule` | General-purpose I/O; triggers async mode |
13
+ | **Extended** | `ai` `rag` `vision` `mqtt` `tts` `home` `device` `agent` | AI, IoT, automation; triggers async mode |
14
+ | **Testing** | `assert` | Use only in `*.test.future` files |
15
+
16
+ Any call from Standard or Extended triggers ASYNC mode automatically.
17
+
18
+ ---
19
+
7
20
  ## Reserved words
8
21
 
9
22
  ```
@@ -176,10 +189,17 @@ embed = ai.embed("text to embed")
176
189
  ai.configure("openai", "sk-...")
177
190
  ai.configure("ollama")
178
191
 
179
- # With options: temperature and max_tokens
192
+ # With inference options
180
193
  answer = ai.ask("Explain quantum physics", { temperature: 0.2 max_tokens: 200 })
181
194
  reply = ai.chat(messages, { model: "gpt-4o" temperature: 0.7 })
182
195
 
196
+ # Structured response: text + token counts + model + provider
197
+ result = ai.complete("Summarise this in one line.")
198
+ print result.text
199
+ print result.tokens.total # total tokens used
200
+ print result.model # e.g. "claude-sonnet-4-6"
201
+ print result.provider # e.g. "anthropic"
202
+
183
203
  stream ai.ask("Tell me a story")
184
204
  print chunk
185
205
  end
package/MIGRATION.md CHANGED
@@ -4,7 +4,215 @@ All releases are **additive only**. No existing Future program has ever required
4
4
 
5
5
  ---
6
6
 
7
- ## v0.3.1 → v0.3.2 (current — Phase 7: General-Purpose Programming)
7
+ ## v0.4.0 → v0.4.1 (current — Gemini improvements)
8
+
9
+ **No breaking changes.**
10
+
11
+ ### AI inference options
12
+
13
+ `ai.ask`, `ai.chat`, and `ai.stream` now accept an optional second argument with inference parameters:
14
+
15
+ ```future
16
+ # temperature controls creativity (0.0 = deterministic, 1.0 = creative)
17
+ precise = ai.ask("List the planets.", { temperature: 0.1 max_tokens: 100 })
18
+ creative = ai.chat(messages, { temperature: 0.9 model: "gpt-4o" })
19
+ ai.stream(prompt, { temperature: 0.7 })
20
+ ```
21
+
22
+ Supported fields: `temperature`, `max_tokens`, `model`, `system` (Anthropic only).
23
+ Both providers (Anthropic native, OpenAI-compat) forward all opts to the API.
24
+
25
+ ### Structured errors
26
+
27
+ `HttpError` and `AiError` replace generic `Error` objects. Catchable with `try/catch err`:
28
+
29
+ ```future
30
+ try
31
+ data = http.get("https://api.example.com/private")
32
+ catch err
33
+ print "{err.status}" # 401
34
+ print "{err.code}" # HTTP_401
35
+ print "{err.url}" # https://api.example.com/private
36
+ end
37
+
38
+ try
39
+ reply = ai.ask("hello")
40
+ catch err
41
+ print "{err.provider}" # anthropic
42
+ print "{err.status}" # 429
43
+ print "{err.code}" # AI_HTTP_429
44
+ end
45
+ ```
46
+
47
+ Error classes are exported from `runtime/providers/anthropic.js` (`AiError`) and `runtime/http.js` (`HttpError`) for use in JS integrations.
48
+
49
+ ### `http.configure()` — global request defaults
50
+
51
+ ```future
52
+ # Call once at the top of your program
53
+ http.configure({ headers: { Authorization: "Bearer {token}" } timeout: 5000 })
54
+
55
+ # All subsequent http.get / http.post calls include the header and timeout automatically
56
+ data = http.get("https://api.example.com/me")
57
+ ```
58
+
59
+ Supported fields: `headers` (merged with per-call headers), `timeout` (ms, uses `AbortSignal.timeout`).
60
+
61
+ ### Source maps (`future compile --sourcemap`)
62
+
63
+ ```bash
64
+ future compile --sourcemap program.future
65
+ # Produces: program.js + program.js.map
66
+ ```
67
+
68
+ The `.js.map` is a standard Source Map v3 file. Stack traces in Node.js, VS Code, and browser DevTools automatically map back to the original `.future` line numbers.
69
+
70
+ The generator embeds `/*@FL:N*/` markers at each statement and `src/sourcemap.js` post-processes them into VLQ-encoded mappings.
71
+
72
+ ### `future test` command + `assert` namespace
73
+
74
+ ```bash
75
+ future test # finds and runs all *.test.future / test/**/*.future files
76
+ future test myfeature # filter by filename substring
77
+ ```
78
+
79
+ Test files use the reserved `assert` namespace:
80
+
81
+ ```future
82
+ # calculator.test.future
83
+ assert.equal(1 + 1, 2)
84
+ assert.ok(math.sqrt(16) == 4)
85
+ assert.notEqual("hello", "world")
86
+ assert.deepEqual([1, 2, 3], [1, 2, 3])
87
+ ```
88
+
89
+ Exit code 0 when all pass, 1 when any fail. `assert.*` calls throw `AssertionError` on failure — the test runner catches them and reports per-file results.
90
+
91
+ `assert` is a reserved namespace — it cannot be redefined by user code.
92
+
93
+ ### Internal changes
94
+
95
+ - `runtime/assert.js` — new module, wraps `node:assert/strict`
96
+ - `src/sourcemap.js` — new module, VLQ encoder + Source Map v3 builder
97
+ - `runtime/providers/anthropic.js` — exports `AiError` class; `chat`/`stream` accept `opts`
98
+ - `runtime/providers/openai-compat.js` — imports `AiError`; `chat`/`stream` accept `opts`
99
+ - `runtime/http.js` — exports `HttpError` class; `get`/`post` throw it; adds `configure()`; uses `AbortSignal.timeout`
100
+ - `runtime/index.js` — `assert` added to module list, `_base`, and `manifest`
101
+ - `src/generator.js` — `assert` added to `NAMESPACES`; `sourceMaps` option emits `/*@FL:N*/` markers
102
+ - `src/parser.js` — `assert` added to `RESERVED_NAMESPACES`
103
+ - `src/cli.js` — `future test` command; `--sourcemap` flag in `future compile`; calls `buildSourceMap()` to strip markers and write `.js.map`
104
+
105
+ ---
106
+
107
+ ## v0.3.2 → v0.4.0 (CLI + Import system + Language improvements)
108
+
109
+ **No breaking changes.**
110
+
111
+ ### CLI commands
112
+
113
+ ```bash
114
+ future run <file.future> # compile + run
115
+ future compile <file.future> # compile to .js next to source
116
+ future new <name> # scaffold a new project directory
117
+ future check <file.future> # syntax-check only, no output
118
+ future fmt <file.future> # auto-format source in-place
119
+ future playground # launch the interactive playground
120
+ future doctor # check Node.js version, runtime, AI env, MQTT, etc.
121
+ future --version # show version
122
+ future run --debug <file> # print per-call timing to stderr (FUTURE_DEBUG=1)
123
+ ```
124
+
125
+ ### Import system (`use`)
126
+
127
+ ```future
128
+ # Import all top-level functions by name
129
+ use "./utils.future"
130
+ print formatName("Alice")
131
+
132
+ # Import as a namespace
133
+ use "./math.future" as m
134
+ result = m.add(10, 20)
135
+
136
+ # Import an npm package as a namespace
137
+ use "date-fns" as df
138
+ ```
139
+
140
+ Compiles to standard ES module imports:
141
+
142
+ ```js
143
+ import { formatName } from "./utils.js";
144
+ import * as m from "./math.js";
145
+ import * as df from "date-fns";
146
+ ```
147
+
148
+ The compiler reads imported `.future` files at compile time, parses them, and extracts top-level function names to generate named imports instead of wildcard imports. Dependencies are compiled recursively.
149
+
150
+ `use ... as alias` aliases are excluded from `__rt` routing — `m.add()` does not become `__rt.m.add()`.
151
+
152
+ ### `else if` chains
153
+
154
+ ```future
155
+ if score >= 90
156
+ print "A"
157
+ else if score >= 80
158
+ print "B"
159
+ else if score >= 70
160
+ print "C"
161
+ else
162
+ print "F"
163
+ end
164
+ ```
165
+
166
+ One `end` closes the entire chain. Previously required nesting.
167
+
168
+ ### Reserved namespace protection
169
+
170
+ Trying to reassign a namespace name now raises a compile-time error:
171
+
172
+ ```future
173
+ math = 42 # error[parse]: 'math' is a reserved namespace
174
+ http = {} # error[parse]: 'http' is a reserved namespace
175
+ ```
176
+
177
+ ### Async handler error safety (`__safe`)
178
+
179
+ Programs that use `on`, `every`, or `stream` now wrap their handlers in `__safe`. Errors inside handlers are logged to `stderr` with `[future:ns]` prefix instead of crashing the process silently:
180
+
181
+ ```js
182
+ const __safe = (ns, fn) => async (...a) => {
183
+ try { return await fn(...a); }
184
+ catch (e) { console.error(`[future:${ns}]`, e.message); }
185
+ };
186
+ ```
187
+
188
+ ### Better error messages
189
+
190
+ Parse errors now show the source line and a `^` pointer:
191
+
192
+ ```
193
+ error[parse]: Expected 'end' to close 'if' — did you forget 'end'?
194
+ --> hello.future:5:1
195
+ 5 | x = 1
196
+ ^
197
+ ```
198
+
199
+ ### `FUTURE_FOR_LLMS.md`
200
+
201
+ A complete quick-reference for AI assistants generating Future code: BNF grammar, all keywords, all namespace APIs, every construct with an example, common mistakes, and what-compiles-to-what pairs.
202
+
203
+ ### Internal changes
204
+
205
+ - `src/lexer.js` — `as` keyword added (`AS` token)
206
+ - `src/ast.js` — `UseStatement` node type and factory added
207
+ - `src/parser.js` — `parseUse()`, `parseIf(isChained)` updated; `RESERVED_NAMESPACES` set added
208
+ - `src/generator.js` — `genUseStatement()`, `useAliases` Set, `isModule` and `pathMap` options, `__safe` emission, `else if` chain detection
209
+ - `src/index.js` — `compile()` accepts `resolveSource`, `isModule`, `pathMap`, `importedNames` options
210
+ - `src/formatter.js` — new module: line-based indentation formatter
211
+ - `src/cli.js` — full rewrite; `compileDepsToTemp()` for `future run`; all CLI commands implemented
212
+
213
+ ---
214
+
215
+ ## v0.3.1 → v0.3.2 (Phase 7: General-Purpose Programming)
8
216
 
9
217
  **No breaking changes.**
10
218
 
package/README.md CHANGED
@@ -164,6 +164,18 @@ In the browser this uses `window.prompt()`. In a Node.js CLI program it reads fr
164
164
 
165
165
  Future programs talk to the outside world through **namespace calls**. The compiler detects them and automatically switches the program to async mode — you never write `async` or `await`.
166
166
 
167
+ ### Capability layers
168
+
169
+ Future organises its built-ins into three layers. You only need to know the layer you're using.
170
+
171
+ | Layer | What it includes | Typical use |
172
+ |-------|-----------------|-------------|
173
+ | **Core language** | variables, functions, `if/else/end`, `for`, `while`, `try/catch`, lists, objects, string interpolation | Any program — no external calls |
174
+ | **Standard capabilities** | `math`, `http`, `memory`, `system`, `schedule` | General-purpose programs with I/O |
175
+ | **Extended modules** | `ai`, `rag`, `vision`, `mqtt`, `tts`, `home`, `device`, `agent`, `assert` | AI, IoT, and automation programs |
176
+
177
+ Any call from Standard or Extended triggers async mode — the compiler handles it automatically.
178
+
167
179
  ```future
168
180
  # HTTP
169
181
  todo = http.get("https://jsonplaceholder.typicode.com/todos/1")
@@ -182,8 +194,8 @@ mqtt.publish("home/livingroom/light", "on")
182
194
 
183
195
  | Namespace | Functions | Notes |
184
196
  |------------|-----------|-------|
185
- | `http` | `get(url)`, `post(url, body)` | Parses JSON automatically |
186
- | `ai` | `ask(prompt)`, `chat(messages)`, `embed(text)`, `stream(prompt, cb)`, `configure(provider, key)` | Pluggable providers |
197
+ | `http` | `get(url)`, `post(url, body)`, `configure(opts)` | Parses JSON automatically; throws `HttpError` with `.status`, `.code`, `.body` |
198
+ | `ai` | `ask(prompt, opts?)`, `chat(messages, opts?)`, `embed(text)`, `stream(prompt, cb, opts?)`, `configure(provider, key)` | opts: `{ temperature, max_tokens, model }`; throws `AiError` |
187
199
  | `tts` | `speak(text)` | System engine (`say` / SAPI / `espeak-ng`) |
188
200
  | `mqtt` | `publish(topic, msg)`, `subscribe(topic, handler)` | Real broker or in-process loopback |
189
201
  | `memory` | `set(key, v)`, `get(key)`, `delete(key)`, `search(q)`, `forget(pattern?)` | In-process key-value store |
@@ -194,6 +206,7 @@ mqtt.publish("home/livingroom/light", "on")
194
206
  | `home` | `turnOn(device)`, `turnOff(device)`, `set(device, value)` | Home automation via MQTT |
195
207
  | `math` | `round`, `floor`, `ceil`, `abs`, `sqrt`, `pow`, `log`, `random`, `min`, `max`, `pi`, `e` | Full Math wrapper |
196
208
  | `device` | `register(config)`, `get(name)`, `list()` | IoT device registry |
209
+ | `assert` | `ok(val)`, `equal(a, b)`, `notEqual(a, b)`, `deepEqual(a, b)`, `fail(msg?)` | Use in `*.test.future` files |
197
210
 
198
211
  ### Configuration (environment variables)
199
212
 
@@ -211,12 +224,56 @@ FUTURE_VECTOR_DB=memory # memory | file | qdrant
211
224
  ## AI configuration
212
225
 
213
226
  ```future
214
- # From code
227
+ # Provider selection
215
228
  ai.configure("openai", "sk-...")
216
229
  ai.configure("ollama") # local, no key needed
217
230
 
231
+ # Basic usage
218
232
  answer = ai.ask("What is 2 + 2?")
219
233
  print answer
234
+
235
+ # With inference options
236
+ precise = ai.ask("List the planets.", { temperature: 0.1 max_tokens: 150 })
237
+ creative = ai.chat(messages, { temperature: 0.9 model: "gpt-4o" })
238
+
239
+ # Structured response — text + token counts + model + provider
240
+ result = ai.complete("Explain recursion in one sentence.")
241
+ print result.text
242
+ print "Tokens used: {result.tokens.total}"
243
+ print "Model: {result.model}"
244
+ print "Provider: {result.provider}"
245
+ ```
246
+
247
+ `ai.ask()` returns a plain string. `ai.complete()` returns `{ text, model, provider, tokens: { input, output, total } }` — useful when you need to track costs or log which model answered.
248
+
249
+ ## HTTP configuration
250
+
251
+ ```future
252
+ # Set global headers and timeout once at the top of your program
253
+ http.configure({ headers: { Authorization: "Bearer {token}" } timeout: 5000 })
254
+
255
+ data = http.get("https://api.example.com/me") # Authorization header included automatically
256
+ ```
257
+
258
+ ## Structured errors
259
+
260
+ HTTP and AI errors now carry structured data you can inspect:
261
+
262
+ ```future
263
+ try
264
+ data = http.get("https://api.example.com/private")
265
+ catch err
266
+ print "Status: {err.status}" # e.g. 401
267
+ print "Code: {err.code}" # e.g. HTTP_401
268
+ print "URL: {err.url}"
269
+ end
270
+
271
+ try
272
+ reply = ai.ask("hello")
273
+ catch err
274
+ print "Provider: {err.provider}" # e.g. anthropic
275
+ print "Status: {err.status}" # e.g. 429
276
+ end
220
277
  ```
221
278
 
222
279
  ---
@@ -333,14 +390,49 @@ Open `future-playground.html` in any browser for a live editor with 11 built-in
333
390
  ```bash
334
391
  npm install -g future-lang
335
392
 
336
- future --version # show version
337
- future new myapp # create a new project
338
- future run program.future # compile + run
339
- future compile program.future # compile to JavaScript
340
- future check program.future # syntax check only
341
- future fmt program.future # format source in-place
342
- future playground # launch the interactive playground
343
- future doctor # check your environment
393
+ future --version # show version
394
+ future new myapp # create a new project
395
+ future run program.future # compile + run
396
+ future run --debug program.future # run with per-call timing logs
397
+ future compile program.future # compile to JavaScript
398
+ future compile --sourcemap program.future # compile + emit .js.map
399
+ future test # run all *.test.future files
400
+ future test myfeature # run matching test files
401
+ future ast program.future # print the AST as JSON
402
+ future ast --pretty program.future # indented JSON
403
+ future check program.future # syntax check only
404
+ future fmt program.future # format source in-place
405
+ future playground # launch the interactive playground
406
+ future doctor # check your environment
407
+ ```
408
+
409
+ ---
410
+
411
+ ## Testing
412
+
413
+ Name your test files `*.test.future` (or put them in a `test/` folder) and use the `assert` namespace:
414
+
415
+ ```future
416
+ # math.test.future
417
+
418
+ assert.equal(math.round(3.7), 4)
419
+ assert.equal(math.sqrt(16), 4)
420
+ assert.ok(math.pi > 3.14)
421
+ assert.notEqual(math.random(), math.random())
422
+ print "math tests passed"
423
+ ```
424
+
425
+ ```bash
426
+ future test # runs all *.test.future files
427
+ future test math # runs files matching "math"
428
+ ```
429
+
430
+ Output:
431
+ ```
432
+ math tests passed
433
+ ✓ math.test.future
434
+
435
+ 1/1 tests passed
344
436
  ```
345
437
 
346
438
  ---
package/ROADMAP.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Future — Roadmap
2
2
 
3
- **Version:** 0.3.0 · **Last updated:** 2026-06-13
3
+ **Version:** 0.4.1 · **Last updated:** 2026-06-13
4
4
 
5
5
  Status legend: ✅ Done · 🔄 In progress · 📋 Planned · 💡 Idea
6
6
 
@@ -18,7 +18,9 @@ Status legend: ✅ Done · 🔄 In progress · 📋 Planned · 💡 Idea
18
18
  | Multi-line strings | 📋 | Strings must be single-line today |
19
19
  | String `+` concatenation | ✅ | Works via binary `+` operator |
20
20
  | Integer division / modulo | 📋 | `math.trunc(a / b)` workaround for now |
21
- | `use "other.future"` imports | 💡 | No cross-file composition yet |
21
+ | `use "other.future"` imports | | Named + namespace imports; npm packages; recursive deps |
22
+ | `else if` chains | ✅ | One `end` for the whole chain |
23
+ | Reserved namespace protection | ✅ | Compile-time error if reserved name is reassigned |
22
24
  | REPL (`future repl`) | 💡 | Interactive shell with introspection |
23
25
 
24
26
  ---
@@ -28,11 +30,14 @@ Status legend: ✅ Done · 🔄 In progress · 📋 Planned · 💡 Idea
28
30
  | Feature | Status | Notes |
29
31
  |---------|--------|-------|
30
32
  | `ai.ask(prompt)` | ✅ | Single-turn Q&A |
33
+ | `ai.ask(prompt, opts)` | ✅ | `opts`: `temperature`, `max_tokens`, `model`, `system` |
31
34
  | `ai.chat(messages)` | ✅ | Multi-turn conversation |
35
+ | `ai.chat(messages, opts)` | ✅ | Inference options forwarded to provider |
32
36
  | `ai.embed(text)` | ✅ | Real embeddings (OpenAI/Ollama) + keyword fallback |
33
- | `ai.stream(prompt, callback)` | ✅ | Streaming via SSE runtime implemented |
37
+ | `ai.stream(prompt, cb, opts?)` | ✅ | Streaming via SSE; opts forwarded |
34
38
  | `stream ai.ask() ... end` syntax | ✅ | Language-level streaming with implicit `chunk` variable |
35
39
  | `ai.configure(provider, key, model)` | ✅ | Pluggable provider from Future code |
40
+ | Structured `AiError` (status, code, provider) | ✅ | Catchable with rich properties |
36
41
  | Provider: Anthropic | ✅ | Native Messages API |
37
42
  | Provider: OpenAI | ✅ | Via OpenAI-compat layer |
38
43
  | Provider: Ollama | ✅ | Local models, no key needed |
@@ -205,8 +210,11 @@ Status legend: ✅ Done · 🔄 In progress · 📋 Planned · 💡 Idea
205
210
  | `len(x)` built-in | ✅ | Arrays, strings, objects — sync, no runtime needed |
206
211
  | `math.*` module | ✅ | Full JS Math wrapper |
207
212
  | `input(prompt)` built-in | ✅ | stdin (Node.js) / `window.prompt` (browser) |
213
+ | `else if` chains | ✅ | One `end` closes the whole chain |
214
+ | `use "./file.future"` imports | ✅ | Named or namespace imports, recursive deps |
215
+ | `use "npm-pkg" as alias` | ✅ | Import npm packages as namespaces |
216
+ | Reserved namespace protection | ✅ | Compile-time error on redefinition |
208
217
  | Multi-line strings | 📋 | Strings must be single-line |
209
- | `use "other.future"` — module imports | 💡 | No cross-file composition |
210
218
  | REPL (`future repl`) | 💡 | Interactive shell with introspection |
211
219
 
212
220
  ---
@@ -230,21 +238,57 @@ Status legend: ✅ Done · 🔄 In progress · 📋 Planned · 💡 Idea
230
238
 
231
239
  ---
232
240
 
241
+ ## HTTP
242
+
243
+ | Feature | Status | Notes |
244
+ |---------|--------|-------|
245
+ | `http.get(url, headers?)` | ✅ | Parses JSON or returns text |
246
+ | `http.post(url, body, headers?)` | ✅ | JSON body; parses response |
247
+ | `http.configure({ headers, timeout })` | ✅ | Global defaults for all requests |
248
+ | Structured `HttpError` (status, code, url, body) | ✅ | Catchable with rich properties |
249
+ | `http.put / patch / delete` | 📋 | Additional HTTP verbs |
250
+ | Response headers access | 📋 | `res.headers.get("content-type")` |
251
+
252
+ ---
253
+
254
+ ## Testing
255
+
256
+ | Feature | Status | Notes |
257
+ |---------|--------|-------|
258
+ | `future test` command | ✅ | Finds `*.test.future` / `test/**/*.future`, runs all |
259
+ | `future test <pattern>` | ✅ | Filter by filename substring |
260
+ | `assert` namespace | ✅ | `ok`, `equal`, `notEqual`, `deepEqual`, `fail` |
261
+ | Per-file pass/fail reporting | ✅ | `✓` / `✗` with error message |
262
+ | Exit code 1 on failure | ✅ | Integrates with CI |
263
+ | Test isolation (separate process) | 📋 | Currently runs in same Node process |
264
+ | `assert.throws(fn)` | 📋 | Assert that a function throws |
265
+ | Coverage reporting | 💡 | Line coverage for `.future` files |
266
+
267
+ ---
268
+
233
269
  ## Tooling
234
270
 
235
271
  | Feature | Status | Notes |
236
272
  |---------|--------|-------|
237
273
  | CLI: `future run` | ✅ | |
238
274
  | CLI: `future compile` | ✅ | |
239
- | Structured manifest | ✅ | All 12 modules, 50+ functions fully described |
275
+ | CLI: `future compile --sourcemap` | ✅ | Emits Source Map v3 `.js.map` |
276
+ | CLI: `future run --debug` | ✅ | Per-call timing via `FUTURE_DEBUG=1` |
277
+ | CLI: `future test` | ✅ | Test runner for `*.test.future` files |
278
+ | CLI: `future new` | ✅ | Project scaffold |
279
+ | CLI: `future check` | ✅ | Syntax-check without running |
280
+ | CLI: `future fmt` | ✅ | Auto-formatter |
281
+ | CLI: `future doctor` | ✅ | Environment health check |
282
+ | CLI: `future playground` | ✅ | Launches browser playground server |
283
+ | Source maps (`.js.map`) | ✅ | VLQ-encoded Source Map v3 |
284
+ | Structured manifest | ✅ | All 13 modules, 50+ functions fully described |
240
285
  | Runtime introspection API | ✅ | `runtime.describe()` / `listModules()` / `listFunctions()` |
241
286
  | LSP metadata module | ✅ | Completions, hover, signatures |
242
287
  | Browser playground | ✅ | `future-playground.html` — 11 examples |
288
+ | `FUTURE_FOR_LLMS.md` | ✅ | BNF grammar + all APIs for AI code assistants |
289
+ | npm publish (`future-lang`) | ✅ | Public — `npm install -g future-lang` |
243
290
  | VSCode extension | 📋 | Syntax highlighting, completions, hover |
244
291
  | Language Server (LSP) | 📋 | Full editor integration |
245
- | `future fmt` | 📋 | Auto-formatter |
246
- | `future check` | 📋 | Lint / type check without running |
247
- | npm publish (`future-lang`) | 📋 | Public package registry |
248
292
 
249
293
  ---
250
294
 
@@ -252,12 +296,15 @@ Status legend: ✅ Done · 🔄 In progress · 📋 Planned · 💡 Idea
252
296
 
253
297
  | Priority | Item | Why it matters |
254
298
  |----------|------|----------------|
255
- | 🔴 Critical | npm publish (`future-lang`) | Required to ship publicly |
256
299
  | 🔴 Critical | VSCode extension (syntax highlighting) | First impression for new users |
257
- | 🟠 High | `use "other.future"` imports | Cross-file composition for real projects |
258
- | 🟠 High | `system.env(name)` | Read env vars from Future code |
300
+ | 🔴 Critical | Language Server (LSP) | Completions and hover for all IDEs |
301
+ | 🟠 High | `ai.extract(text, schema)` | Structured output is the #1 AI use case |
302
+ | 🟠 High | Test isolation (separate process) | Prevents test state leakage |
303
+ | 🟠 High | `assert.throws(fn)` | Needed for error-handling tests |
259
304
  | 🟡 Medium | Home Assistant REST API | Most HA users don't run MQTT |
260
305
  | 🟡 Medium | Persistent memory / device registry | Most programs are stateless today |
261
306
  | 🟡 Medium | Agent tool-calling loop (ReAct) | True autonomous agents |
307
+ | 🟡 Medium | `http.put / patch / delete` | Needed for full REST APIs |
262
308
  | 🟢 Low | `rag.delete(id)` | Selective document removal |
263
309
  | 🟢 Low | REPL | Nice-to-have for exploration |
310
+ | 🟢 Low | Coverage reporting | `.future` line coverage |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "future-lang",
3
- "version": "0.4.1",
3
+ "version": "0.4.2",
4
4
  "description": "Future — a small programming language that transpiles to JavaScript, with a capability runtime (HTTP/AI/MQTT/TTS).",
5
5
  "type": "module",
6
6
  "bin": {
package/runtime/ai.js CHANGED
@@ -72,6 +72,40 @@ export async function stream(promptOrMessages, onChunk, opts = {}) {
72
72
  return provider.stream(messages, onChunk, opts);
73
73
  }
74
74
 
75
+ /**
76
+ * Like ask(), but returns a structured result object instead of a plain string.
77
+ * Includes the generated text, model used, provider name, and token counts.
78
+ *
79
+ * @param {string|Array} prompt String prompt or messages array
80
+ * @param {{ temperature?: number, max_tokens?: number, model?: string, system?: string }} [opts]
81
+ * @returns {Promise<{ text: string, model: string, provider: string, tokens: { input: number, output: number, total: number } }>}
82
+ */
83
+ export async function complete(prompt, opts = {}) {
84
+ const messages = Array.isArray(prompt)
85
+ ? prompt
86
+ : [{ role: 'user', content: String(prompt) }];
87
+ const provider = resolveProvider();
88
+ if (!provider) {
89
+ return {
90
+ text: offlineStub(messages),
91
+ model: 'none',
92
+ provider: 'offline',
93
+ tokens: { input: 0, output: 0, total: 0 },
94
+ };
95
+ }
96
+ if (typeof provider.complete === 'function') {
97
+ return provider.complete(messages, opts);
98
+ }
99
+ // Fallback for providers that don't implement complete() yet.
100
+ const text = await provider.chat(messages, opts);
101
+ return {
102
+ text,
103
+ model: opts.model ?? 'unknown',
104
+ provider: provider.name,
105
+ tokens: { input: 0, output: 0, total: 0 },
106
+ };
107
+ }
108
+
75
109
  /**
76
110
  * Generate a vector embedding for a piece of text.
77
111
  * With OpenAI/Ollama providers, returns real semantic embeddings.
package/runtime/index.js CHANGED
@@ -83,10 +83,16 @@ export const manifest = {
83
83
  },
84
84
  ask: {
85
85
  description: 'Ask an AI model a question and get a text response',
86
- params: [{ name: 'prompt', type: 'string' }],
86
+ params: [{ name: 'prompt', type: 'string' }, { name: 'opts', type: 'object', optional: true }],
87
87
  returns: 'string',
88
88
  async: true,
89
89
  },
90
+ complete: {
91
+ description: 'Like ask(), but returns a structured object: { text, model, provider, tokens: { input, output, total } }',
92
+ params: [{ name: 'prompt', type: 'string|array' }, { name: 'opts', type: 'object', optional: true }],
93
+ returns: '{ text: string, model: string, provider: string, tokens: { input: number, output: number, total: number } }',
94
+ async: true,
95
+ },
90
96
  chat: {
91
97
  description: 'Send a multi-turn message list to an AI model',
92
98
  params: [{ name: 'messages', type: 'array' }],
@@ -434,7 +440,7 @@ runtime.listFunctions = (mod) => {
434
440
  * Suitable for AI agent discovery or documentation generation.
435
441
  */
436
442
  runtime.describe = () => ({
437
- version: '0.4.1',
443
+ version: '0.4.2',
438
444
  modules: [...MODULE_NAMES],
439
445
  manifest,
440
446
  });
@@ -77,11 +77,42 @@ export function create(config) {
77
77
  }
78
78
  }
79
79
 
80
+ async function complete(messages, opts = {}) {
81
+ const model = opts.model ?? defaultModel;
82
+ const max_tokens = opts.max_tokens ?? 1024;
83
+ const body = { model, max_tokens, messages };
84
+ if (opts.temperature != null) body.temperature = opts.temperature;
85
+ if (opts.system) body.system = opts.system;
86
+
87
+ const res = await fetch(`${BASE}/messages`, {
88
+ method: 'POST',
89
+ headers,
90
+ body: JSON.stringify(body),
91
+ });
92
+ if (!res.ok) {
93
+ let errBody;
94
+ try { errBody = await res.json(); } catch { errBody = await res.text(); }
95
+ throw new AiError(res.status, 'anthropic', errBody);
96
+ }
97
+ const data = await res.json();
98
+ const text = (data.content ?? []).map((b) => b.text ?? '').join('').trim();
99
+ return {
100
+ text,
101
+ model: data.model ?? model,
102
+ provider: 'anthropic',
103
+ tokens: {
104
+ input: data.usage?.input_tokens ?? 0,
105
+ output: data.usage?.output_tokens ?? 0,
106
+ total: (data.usage?.input_tokens ?? 0) + (data.usage?.output_tokens ?? 0),
107
+ },
108
+ };
109
+ }
110
+
80
111
  async function embed(text) {
81
112
  // Anthropic has no public embeddings endpoint — use keyword vector fallback.
82
113
  // For production semantic search, configure an OpenAI-compatible provider with an embed model.
83
114
  return keywordVector(String(text));
84
115
  }
85
116
 
86
- return { name: 'anthropic', ask, chat, stream, embed };
117
+ return { name: 'anthropic', ask, chat, complete, stream, embed };
87
118
  }
@@ -81,6 +81,36 @@ export function create(config) {
81
81
  }
82
82
  }
83
83
 
84
+ async function complete(messages, opts = {}) {
85
+ const model = opts.model ?? defaultModel;
86
+ const max_tokens = opts.max_tokens ?? 1024;
87
+ const body = { model, messages, max_tokens };
88
+ if (opts.temperature != null) body.temperature = opts.temperature;
89
+
90
+ const res = await fetch(`${baseUrl}/chat/completions`, {
91
+ method: 'POST',
92
+ headers,
93
+ body: JSON.stringify(body),
94
+ });
95
+ if (!res.ok) {
96
+ let errBody;
97
+ try { errBody = await res.json(); } catch { errBody = await res.text(); }
98
+ throw new AiError(res.status, providerTag, errBody);
99
+ }
100
+ const data = await res.json();
101
+ const text = data.choices?.[0]?.message?.content?.trim() ?? '';
102
+ return {
103
+ text,
104
+ model: data.model ?? model,
105
+ provider: providerTag,
106
+ tokens: {
107
+ input: data.usage?.prompt_tokens ?? 0,
108
+ output: data.usage?.completion_tokens ?? 0,
109
+ total: data.usage?.total_tokens ?? 0,
110
+ },
111
+ };
112
+ }
113
+
84
114
  async function embed(text) {
85
115
  if (!embedModel) return keywordVector(String(text));
86
116
  try {
@@ -97,5 +127,5 @@ export function create(config) {
97
127
  }
98
128
  }
99
129
 
100
- return { name: providerTag, ask, chat, stream, embed };
130
+ return { name: providerTag, ask, chat, complete, stream, embed };
101
131
  }
package/src/cli.js CHANGED
@@ -22,7 +22,7 @@ import { format } from './formatter.js';
22
22
  import { FutureError } from './errors.js';
23
23
  import { buildSourceMap } from './sourcemap.js';
24
24
 
25
- const VERSION = '0.4.1';
25
+ const VERSION = '0.4.2';
26
26
  const PROJECT_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
27
27
  const RUNTIME_INDEX = join(PROJECT_ROOT, 'runtime', 'index.js');
28
28
 
@@ -32,13 +32,14 @@ Usage:
32
32
  future run <file.future> Compile and run a program
33
33
  future compile <file.future> Compile to JavaScript (<file>.js)
34
34
  future test [pattern] Run *.test.future files
35
- future new <name> Create a new project
35
+ future ast <file.future> Print the AST as JSON
36
+ future new <name> Create a new project
36
37
  future check <file.future> Check for syntax errors
37
38
  future fmt <file.future> Format source code in-place
38
- future playground Launch the interactive playground
39
- future doctor Check your environment
40
- future help Show this help
41
- future --version Show the version
39
+ future playground Launch the interactive playground
40
+ future doctor Check your environment
41
+ future help Show this help
42
+ future --version Show the version
42
43
 
43
44
  Import system:
44
45
  use "./utils.future" Import all functions from a file
@@ -48,18 +49,21 @@ Import system:
48
49
  Flags:
49
50
  future run --debug <file> Show timing for every namespace call
50
51
  future compile --sourcemap <file> Also emit a .js.map source map
52
+ future ast --pretty <file> Indented JSON output
51
53
  `;
52
54
 
53
55
  async function main(argv) {
54
56
  const debug = argv.includes('--debug');
55
57
  const sourcemap = argv.includes('--sourcemap');
58
+ const pretty = argv.includes('--pretty');
56
59
  if (debug) process.env.FUTURE_DEBUG = '1';
57
- const rest = argv.filter((a) => a !== '--debug' && a !== '--sourcemap');
60
+ const rest = argv.filter((a) => a !== '--debug' && a !== '--sourcemap' && a !== '--pretty');
58
61
  const [command, arg] = rest;
59
62
  switch (command) {
60
63
  case 'run': return cmdRun(arg);
61
64
  case 'compile': return cmdCompile(arg, { sourcemap });
62
65
  case 'test': return cmdTest(arg);
66
+ case 'ast': return cmdAst(arg, { pretty });
63
67
  case 'new': return cmdNew(arg);
64
68
  case 'check': return cmdCheck(arg);
65
69
  case 'fmt': return cmdFmt(arg);
@@ -345,6 +349,23 @@ function cmdFmt(file) {
345
349
  return 0;
346
350
  }
347
351
 
352
+ /** Output the AST of a .future file as JSON. */
353
+ function cmdAst(file, { pretty = false } = {}) {
354
+ let path, source;
355
+ try { ({ path, source } = readSource(file)); }
356
+ catch (err) { return fail(err, file); }
357
+
358
+ try {
359
+ const tokens = tokenize(source);
360
+ const ast = parse(tokens);
361
+ process.stdout.write((pretty ? JSON.stringify(ast, null, 2) : JSON.stringify(ast)) + '\n');
362
+ return 0;
363
+ } catch (err) {
364
+ if (err instanceof FutureError) { reportFutureError(err, source, file); return 1; }
365
+ throw err;
366
+ }
367
+ }
368
+
348
369
  /** Recursively collect .future files matching a pattern or default test globs. */
349
370
  function findTestFiles(pattern) {
350
371
  const cwd = process.cwd();