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,101 @@
1
+ // runtime/system.js — OS-level utilities: run commands, open files/URLs, notify, read/write files.
2
+
3
+ import { execFile, spawn } from 'node:child_process';
4
+ import { promisify } from 'node:util';
5
+ import { readFile, writeFile } from 'node:fs/promises';
6
+ import process from 'node:process';
7
+
8
+ const execFileAsync = promisify(execFile);
9
+
10
+ /**
11
+ * Run a shell command. Uses execFile (no shell expansion) for safety.
12
+ * The command string is split on spaces; use exec(['cmd', 'arg with spaces']) for complex calls.
13
+ * @param {string|string[]} command Command + args as a string or array.
14
+ * @returns {Promise<string>} stdout
15
+ */
16
+ export async function exec(command) {
17
+ const parts = Array.isArray(command) ? command : String(command).trim().split(/\s+/);
18
+ const [cmd, ...args] = parts;
19
+ const { stdout, stderr } = await execFileAsync(cmd, args);
20
+ if (stderr) console.warn('[system.exec]', stderr.trim());
21
+ return stdout.trim();
22
+ }
23
+
24
+ /**
25
+ * Open a file path or URL with the OS default handler.
26
+ * Uses the platform's native launcher (start / open / xdg-open).
27
+ * @returns {Promise<string>} the target that was opened.
28
+ */
29
+ export async function open(target) {
30
+ const str = String(target);
31
+ let cmd, args;
32
+ if (process.platform === 'win32') {
33
+ // `start` is a cmd built-in; spawn via cmd /c.
34
+ cmd = 'cmd'; args = ['/c', 'start', '', str];
35
+ } else if (process.platform === 'darwin') {
36
+ cmd = 'open'; args = [str];
37
+ } else {
38
+ cmd = 'xdg-open'; args = [str];
39
+ }
40
+ await new Promise((resolve, reject) => {
41
+ const proc = spawn(cmd, args, { detached: true, stdio: 'ignore' });
42
+ proc.once('error', reject);
43
+ proc.unref();
44
+ // Resolve immediately — the opened app runs independently.
45
+ resolve();
46
+ });
47
+ return str;
48
+ }
49
+
50
+ /**
51
+ * Send a desktop notification.
52
+ * macOS: AppleScript. Linux: notify-send. Windows: falls back to console.log.
53
+ * @returns {Promise<string>} the message text.
54
+ */
55
+ export async function notify(message) {
56
+ const msg = String(message);
57
+ try {
58
+ if (process.platform === 'darwin') {
59
+ // Sanitise for AppleScript: remove quotes.
60
+ const safe = msg.replace(/['"\\]/g, '').slice(0, 200);
61
+ await execFileAsync('osascript', ['-e', `display notification "${safe}" with title "Future"`]);
62
+ } else if (process.platform === 'linux') {
63
+ await execFileAsync('notify-send', ['Future', msg]);
64
+ } else {
65
+ // Windows: log clearly; a real toast would need a packaged app identity.
66
+ console.log(`[notify] ${msg}`);
67
+ }
68
+ } catch {
69
+ console.log(`[notify] ${msg}`);
70
+ }
71
+ return msg;
72
+ }
73
+
74
+ /**
75
+ * Read a file and return its content as a string.
76
+ * @param {string} path Absolute or relative file path.
77
+ * @returns {Promise<string>}
78
+ */
79
+ export async function read(path) {
80
+ return readFile(String(path), 'utf8');
81
+ }
82
+
83
+ /**
84
+ * Write a string to a file (creates or overwrites).
85
+ * @param {string} path Absolute or relative file path.
86
+ * @param {string} content Content to write.
87
+ * @returns {Promise<string>} The path that was written.
88
+ */
89
+ export async function write(path, content) {
90
+ await writeFile(String(path), String(content), 'utf8');
91
+ return String(path);
92
+ }
93
+
94
+ /**
95
+ * Read an environment variable. Returns null if not set.
96
+ * @param {string} name Variable name, e.g. "VENICE_API_KEY".
97
+ * @returns {string|null}
98
+ */
99
+ export function env(name) {
100
+ return process.env[String(name)] ?? null;
101
+ }
package/runtime/tts.js ADDED
@@ -0,0 +1,38 @@
1
+ // runtime/tts.js — text-to-speech.
2
+ // Uses a system engine when present (macOS `say`, Windows SAPI, Linux espeak-ng)
3
+ // and falls back to printing `[tts] ...` so it never crashes a program.
4
+
5
+ import { spawn } from 'node:child_process';
6
+ import process from 'node:process';
7
+
8
+ /** Speak text aloud. @returns {Promise<string>} the spoken text. */
9
+ export async function speak(text) {
10
+ const str = String(text);
11
+ const ok = await trySpeak(str);
12
+ if (!ok) console.log(`[tts] ${str}`);
13
+ return str;
14
+ }
15
+
16
+ function trySpeak(str) {
17
+ return new Promise((resolve) => {
18
+ let cmd; let args;
19
+ if (process.platform === 'darwin') {
20
+ cmd = 'say'; args = [str];
21
+ } else if (process.platform === 'win32') {
22
+ const safe = str.replace(/'/g, "''");
23
+ cmd = 'powershell';
24
+ args = ['-NoProfile', '-Command',
25
+ `Add-Type -AssemblyName System.Speech; ` +
26
+ `(New-Object System.Speech.Synthesis.SpeechSynthesizer).Speak('${safe}')`];
27
+ } else {
28
+ cmd = 'espeak-ng'; args = [str];
29
+ }
30
+ try {
31
+ const p = spawn(cmd, args, { stdio: 'ignore' });
32
+ p.on('error', () => resolve(false)); // engine not installed
33
+ p.on('close', (code) => resolve(code === 0));
34
+ } catch {
35
+ resolve(false);
36
+ }
37
+ });
38
+ }
@@ -0,0 +1,85 @@
1
+ // runtime/vision.js — Vision AI with pluggable provider.
2
+ //
3
+ // Uses the same AI provider configured for the rest of the runtime (FUTURE_AI_PROVIDER).
4
+ // Both Anthropic (claude-3+) and OpenAI (gpt-4o) support vision natively.
5
+ //
6
+ // All functions accept:
7
+ // image — a URL (https://...) or base64 data-URI (data:image/jpeg;base64,...)
8
+
9
+ import { resolveProvider } from './providers/index.js';
10
+
11
+ /**
12
+ * Build the message payload for a vision request.
13
+ * Supports URLs and base64 data-URIs.
14
+ */
15
+ function visionMessage(prompt, image) {
16
+ const img = String(image);
17
+ const isDataUri = img.startsWith('data:');
18
+ const content = [
19
+ {
20
+ type: 'image_url',
21
+ image_url: { url: isDataUri ? img : img },
22
+ },
23
+ { type: 'text', text: prompt },
24
+ ];
25
+ // Anthropic format differs slightly — provider.chat() receives the same
26
+ // OpenAI-style structure; the Anthropic provider does NOT reformat it
27
+ // because the Messages API v2 accepts this layout too.
28
+ return [{ role: 'user', content }];
29
+ }
30
+
31
+ async function callVision(prompt, image) {
32
+ const provider = resolveProvider();
33
+ if (!provider) {
34
+ return `[vision offline] Configure an AI provider to process: ${image}`;
35
+ }
36
+ try {
37
+ return await provider.chat(visionMessage(prompt, image));
38
+ } catch (e) {
39
+ return `[vision error] ${e.message}`;
40
+ }
41
+ }
42
+
43
+ /** Describe the contents of an image. @returns {Promise<string>} */
44
+ export async function describe(image) {
45
+ return callVision('Describe this image in detail.', image);
46
+ }
47
+
48
+ /** Detect and list objects, people, or labels visible in the image. @returns {Promise<string>} */
49
+ export async function detect(image) {
50
+ return callVision('List all objects, people, and notable elements you can detect in this image. Be concise.', image);
51
+ }
52
+
53
+ /** Extract all readable text from the image (OCR). @returns {Promise<string>} */
54
+ export async function ocr(image) {
55
+ return callVision('Extract all text visible in this image exactly as it appears. Return only the text, nothing else.', image);
56
+ }
57
+
58
+ /** Classify the image into a category. @returns {Promise<string>} */
59
+ export async function classify(image) {
60
+ return callVision('What is the primary category or type of this image? Reply with one or two words.', image);
61
+ }
62
+
63
+ /**
64
+ * Compare two images and describe the differences.
65
+ * @param {string} imageA
66
+ * @param {string} imageB
67
+ * @returns {Promise<string>}
68
+ */
69
+ export async function compare(imageA, imageB) {
70
+ const provider = resolveProvider();
71
+ if (!provider) return '[vision offline] Configure an AI provider to compare images.';
72
+ try {
73
+ const messages = [{
74
+ role: 'user',
75
+ content: [
76
+ { type: 'image_url', image_url: { url: String(imageA) } },
77
+ { type: 'image_url', image_url: { url: String(imageB) } },
78
+ { type: 'text', text: 'Compare these two images. What are the key similarities and differences?' },
79
+ ],
80
+ }];
81
+ return await provider.chat(messages);
82
+ } catch (e) {
83
+ return `[vision error] ${e.message}`;
84
+ }
85
+ }
package/server.js ADDED
@@ -0,0 +1,42 @@
1
+ // server.js
2
+
3
+ import http from 'http';
4
+ import fs from 'fs';
5
+ import path from 'path';
6
+
7
+ const PORT = 3000;
8
+
9
+ http.createServer((req, res) => {
10
+
11
+ let file = req.url === '/'
12
+ ? './examples/mini-app.html'
13
+ : '.' + req.url;
14
+
15
+ try {
16
+
17
+ const data = fs.readFileSync(file);
18
+
19
+ const ext = path.extname(file);
20
+
21
+ const types = {
22
+ '.html': 'text/html',
23
+ '.js': 'application/javascript',
24
+ '.css': 'text/css'
25
+ };
26
+
27
+ res.writeHead(200, {
28
+ 'Content-Type': types[ext] || 'text/plain'
29
+ });
30
+
31
+ res.end(data);
32
+
33
+ } catch {
34
+
35
+ res.writeHead(404);
36
+ res.end('Not Found');
37
+
38
+ }
39
+
40
+ }).listen(PORT);
41
+
42
+ console.log(`http://localhost:${PORT}`);
package/src/ast.js ADDED
@@ -0,0 +1,202 @@
1
+ // ast.js
2
+ // AST node types and factory functions for Future.
3
+ //
4
+ // Design note: capabilities like HTTP/AI/MQTT/TTS are NOT special node types.
5
+ // They are ordinary calls whose callee is a `MemberExpression` (e.g. http.get).
6
+ // This keeps the AST tiny and means new capabilities (RAG, Vision AI, Home
7
+ // Automation, ...) need ZERO grammar/AST changes — only a new runtime module.
8
+
9
+ export const NodeType = Object.freeze({
10
+ Program: 'Program',
11
+ UseStatement: 'UseStatement', // use "./file.future" [as alias]
12
+ PrintStatement: 'PrintStatement',
13
+ Assignment: 'Assignment',
14
+ IfStatement: 'IfStatement',
15
+ FunctionDeclaration: 'FunctionDeclaration',
16
+ ReturnStatement: 'ReturnStatement',
17
+ ExpressionStatement: 'ExpressionStatement',
18
+ // Event-oriented statements.
19
+ OnStatement: 'OnStatement', // on <source> <channel> ... end
20
+ EveryStatement: 'EveryStatement', // every <interval> ... end
21
+ // Iteration.
22
+ ForStatement: 'ForStatement', // for <var> in <expr> ... end
23
+ WhileStatement: 'WhileStatement', // while <condition> ... end
24
+ // Error handling.
25
+ TryStatement: 'TryStatement', // try ... catch <var> ... end
26
+ // Streaming.
27
+ StreamStatement: 'StreamStatement', // stream <call> ... end
28
+ // Agent declaration — architecture support (parser implementation pending).
29
+ AgentDeclaration: 'AgentDeclaration', // agent <name> use <cap> ... end
30
+ // Literals.
31
+ ArrayLiteral: 'ArrayLiteral', // [expr, expr, ...]
32
+ ObjectLiteral: 'ObjectLiteral', // { key: expr key: expr }
33
+ BinaryExpression: 'BinaryExpression',
34
+ UnaryExpression: 'UnaryExpression',
35
+ CallExpression: 'CallExpression',
36
+ MemberExpression: 'MemberExpression',
37
+ Identifier: 'Identifier',
38
+ NumberLiteral: 'NumberLiteral',
39
+ StringLiteral: 'StringLiteral',
40
+ BooleanLiteral: 'BooleanLiteral',
41
+ NullLiteral: 'NullLiteral',
42
+ });
43
+
44
+ // --- Statements ---
45
+ export const Program = (body) => ({ type: NodeType.Program, body });
46
+
47
+ /**
48
+ * `use "./file.future"` or `use "./file.future" as alias`
49
+ * Top-level file import. Compiles to an ES `import` statement.
50
+ * @param {string} path Relative path to the imported .future file.
51
+ * @param {string|null} alias Namespace alias, or null for named imports.
52
+ */
53
+ export const UseStatement = (path, alias, line, column) => ({
54
+ type: NodeType.UseStatement, path, alias, line, column,
55
+ });
56
+
57
+ export const PrintStatement = (expression, line, column) => ({
58
+ type: NodeType.PrintStatement, expression, line, column,
59
+ });
60
+
61
+ export const Assignment = (name, value, line, column) => ({
62
+ type: NodeType.Assignment, name, value, line, column,
63
+ });
64
+
65
+ export const IfStatement = (condition, consequent, alternate, line, column) => ({
66
+ type: NodeType.IfStatement, condition, consequent, alternate, line, column,
67
+ });
68
+
69
+ export const FunctionDeclaration = (name, params, body, line, column) => ({
70
+ type: NodeType.FunctionDeclaration, name, params, body, line, column,
71
+ });
72
+
73
+ export const ReturnStatement = (argument, line, column) => ({
74
+ type: NodeType.ReturnStatement, argument, line, column,
75
+ });
76
+
77
+ export const ExpressionStatement = (expression, line, column) => ({
78
+ type: NodeType.ExpressionStatement, expression, line, column,
79
+ });
80
+
81
+ /**
82
+ * `on <source> <channel> ... end`
83
+ * Subscribes to an event source (e.g. mqtt) on a channel. The body receives an
84
+ * implicit `message` binding containing the event payload.
85
+ * @param {string} source Runtime module name, e.g. "mqtt".
86
+ * @param {object} channel Expression node for the channel/topic string.
87
+ * @param {object[]} body Statement list (callback body).
88
+ */
89
+ export const OnStatement = (source, channel, body, line, column) => ({
90
+ type: NodeType.OnStatement, source, channel, body, line, column,
91
+ });
92
+
93
+ /**
94
+ * `every <interval> ... end`
95
+ * Runs the body on a recurring schedule. Compiles to schedule.every(interval, callback).
96
+ * @param {object} interval Expression node (string or number literal).
97
+ * @param {object[]} body Statement list (callback body).
98
+ */
99
+ export const EveryStatement = (interval, body, line, column) => ({
100
+ type: NodeType.EveryStatement, interval, body, line, column,
101
+ });
102
+
103
+ /**
104
+ * `for <variable> in <iterable> ... end`
105
+ * Compiles to `for (const <variable> of <iterable>) { ... }`
106
+ */
107
+ export const ForStatement = (variable, iterable, body, line, column) => ({
108
+ type: NodeType.ForStatement, variable, iterable, body, line, column,
109
+ });
110
+
111
+ /**
112
+ * `try ... catch <errorVar> ... end`
113
+ * Compiles to `try { ... } catch (<errorVar>) { ... }`
114
+ */
115
+ export const TryStatement = (body, catchVar, catchBody, line, column) => ({
116
+ type: NodeType.TryStatement, body, catchVar, catchBody, line, column,
117
+ });
118
+
119
+ /**
120
+ * `while <condition> ... end`
121
+ * Compiles to `while (condition) { ... }`
122
+ */
123
+ export const WhileStatement = (condition, body, line, column) => ({
124
+ type: NodeType.WhileStatement, condition, body, line, column,
125
+ });
126
+
127
+ /**
128
+ * `agent <name> ... end`
129
+ * Declares an agent with a set of capabilities and an optional body.
130
+ * Parser implementation is pending; this node supports architecture and tooling.
131
+ * @param {string} name Agent name identifier.
132
+ * @param {string[]} capabilities List of runtime modules the agent uses (e.g. ['rag','ai']).
133
+ * @param {object[]} body Statement list (agent body).
134
+ */
135
+ export const AgentDeclaration = (name, capabilities, body, line, column) => ({
136
+ type: NodeType.AgentDeclaration, name, capabilities, body, line, column,
137
+ });
138
+
139
+ /**
140
+ * `stream <callExpr> ... end`
141
+ * Streams a response and binds each chunk to the implicit `chunk` variable in the body.
142
+ * Compiles to: await __rt.ai.stream(args, async (chunk) => { ... })
143
+ * Parser implementation is pending.
144
+ * @param {object} call CallExpression node representing the streaming call.
145
+ * @param {object[]} body Statement list executed for each chunk.
146
+ */
147
+ export const StreamStatement = (call, body, line, column) => ({
148
+ type: NodeType.StreamStatement, call, body, line, column,
149
+ });
150
+
151
+ // --- Expressions ---
152
+ export const BinaryExpression = (operator, left, right, line, column) => ({
153
+ type: NodeType.BinaryExpression, operator, left, right, line, column,
154
+ });
155
+
156
+ export const UnaryExpression = (operator, argument, line, column) => ({
157
+ type: NodeType.UnaryExpression, operator, argument, line, column,
158
+ });
159
+
160
+ /** `callee` is any expression (an Identifier or a MemberExpression). */
161
+ export const CallExpression = (callee, args, line, column) => ({
162
+ type: NodeType.CallExpression, callee, arguments: args, line, column,
163
+ });
164
+
165
+ /** `object.property` — e.g. http.get or todo.title. `property` is a string. */
166
+ export const MemberExpression = (object, property, line, column) => ({
167
+ type: NodeType.MemberExpression, object, property, line, column,
168
+ });
169
+
170
+ export const Identifier = (name, line, column) => ({
171
+ type: NodeType.Identifier, name, line, column,
172
+ });
173
+
174
+ export const NumberLiteral = (value, line, column) => ({
175
+ type: NodeType.NumberLiteral, value, line, column,
176
+ });
177
+
178
+ export const StringLiteral = (value, line, column) => ({
179
+ type: NodeType.StringLiteral, value, line, column,
180
+ });
181
+
182
+ export const BooleanLiteral = (value, line, column) => ({
183
+ type: NodeType.BooleanLiteral, value, line, column,
184
+ });
185
+
186
+ export const NullLiteral = (line, column) => ({
187
+ type: NodeType.NullLiteral, line, column,
188
+ });
189
+
190
+ /** `[expr, expr, ...]` — array literal. */
191
+ export const ArrayLiteral = (elements, line, column) => ({
192
+ type: NodeType.ArrayLiteral, elements, line, column,
193
+ });
194
+
195
+ /**
196
+ * `{ key: expr key: expr }` — object literal.
197
+ * `properties` is an array of `{ key: string, value: node }`.
198
+ * No commas between properties — separator is whitespace/newline.
199
+ */
200
+ export const ObjectLiteral = (properties, line, column) => ({
201
+ type: NodeType.ObjectLiteral, properties, line, column,
202
+ });