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
package/src/lexer.js
ADDED
|
@@ -0,0 +1,248 @@
|
|
|
1
|
+
// lexer.js
|
|
2
|
+
// Phase 1 — Tokenizer.
|
|
3
|
+
// Converts raw Future source text into a flat list of tokens that the parser
|
|
4
|
+
// consumes. Every token records its 1-based line and column for diagnostics.
|
|
5
|
+
|
|
6
|
+
import { FutureError } from './errors.js';
|
|
7
|
+
|
|
8
|
+
/** Reserved words mapped to their token type. */
|
|
9
|
+
const KEYWORDS = new Map([
|
|
10
|
+
['print', 'PRINT'],
|
|
11
|
+
['if', 'IF'],
|
|
12
|
+
['else', 'ELSE'],
|
|
13
|
+
['end', 'END'],
|
|
14
|
+
['function', 'FUNCTION'],
|
|
15
|
+
['return', 'RETURN'],
|
|
16
|
+
['true', 'TRUE'],
|
|
17
|
+
['false', 'FALSE'],
|
|
18
|
+
['and', 'AND'],
|
|
19
|
+
['or', 'OR'],
|
|
20
|
+
['not', 'NOT'],
|
|
21
|
+
// Event-oriented keywords.
|
|
22
|
+
['on', 'ON'],
|
|
23
|
+
['every', 'EVERY'],
|
|
24
|
+
// Iteration.
|
|
25
|
+
['for', 'FOR'],
|
|
26
|
+
['in', 'IN'],
|
|
27
|
+
// Error handling.
|
|
28
|
+
['try', 'TRY'],
|
|
29
|
+
['catch', 'CATCH'],
|
|
30
|
+
// Loop.
|
|
31
|
+
['while', 'WHILE'],
|
|
32
|
+
// Null literal (two spellings).
|
|
33
|
+
['null', 'NULL'],
|
|
34
|
+
['none', 'NULL'],
|
|
35
|
+
// Streaming.
|
|
36
|
+
['stream', 'STREAM'],
|
|
37
|
+
// Agent.
|
|
38
|
+
['agent', 'AGENT'],
|
|
39
|
+
['use', 'USE'],
|
|
40
|
+
['as', 'AS'],
|
|
41
|
+
]);
|
|
42
|
+
|
|
43
|
+
/** Two-character operators, checked before single-character ones. */
|
|
44
|
+
const TWO_CHAR_OPS = new Map([
|
|
45
|
+
['==', 'EQ'],
|
|
46
|
+
['!=', 'NEQ'],
|
|
47
|
+
['>=', 'GTE'],
|
|
48
|
+
['<=', 'LTE'],
|
|
49
|
+
]);
|
|
50
|
+
|
|
51
|
+
/** Single-character operators and punctuation. */
|
|
52
|
+
const ONE_CHAR_OPS = new Map([
|
|
53
|
+
['=', 'ASSIGN'],
|
|
54
|
+
['>', 'GT'],
|
|
55
|
+
['<', 'LT'],
|
|
56
|
+
['+', 'PLUS'],
|
|
57
|
+
['-', 'MINUS'],
|
|
58
|
+
['*', 'STAR'],
|
|
59
|
+
['/', 'SLASH'],
|
|
60
|
+
['.', 'DOT'],
|
|
61
|
+
['(', 'LPAREN'],
|
|
62
|
+
[')', 'RPAREN'],
|
|
63
|
+
[',', 'COMMA'],
|
|
64
|
+
['[', 'LBRACKET'],
|
|
65
|
+
[']', 'RBRACKET'],
|
|
66
|
+
['{', 'LBRACE'],
|
|
67
|
+
['}', 'RBRACE'],
|
|
68
|
+
[':', 'COLON'],
|
|
69
|
+
]);
|
|
70
|
+
|
|
71
|
+
export class Token {
|
|
72
|
+
/**
|
|
73
|
+
* @param {string} type e.g. 'IDENTIFIER', 'NUMBER', 'PRINT'.
|
|
74
|
+
* @param {*} value Literal value or raw lexeme.
|
|
75
|
+
* @param {number} line 1-based line.
|
|
76
|
+
* @param {number} column 1-based column.
|
|
77
|
+
*/
|
|
78
|
+
constructor(type, value, line, column) {
|
|
79
|
+
this.type = type;
|
|
80
|
+
this.value = value;
|
|
81
|
+
this.line = line;
|
|
82
|
+
this.column = column;
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export class Lexer {
|
|
87
|
+
/** @param {string} source */
|
|
88
|
+
constructor(source) {
|
|
89
|
+
this.source = source;
|
|
90
|
+
this.pos = 0;
|
|
91
|
+
this.line = 1;
|
|
92
|
+
this.column = 1;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
isAtEnd() {
|
|
96
|
+
return this.pos >= this.source.length;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/** Look at a character without consuming it. */
|
|
100
|
+
peek(offset = 0) {
|
|
101
|
+
return this.source[this.pos + offset];
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
/** Consume and return the current character, advancing line/column counters. */
|
|
105
|
+
advance() {
|
|
106
|
+
const ch = this.source[this.pos++];
|
|
107
|
+
if (ch === '\n') {
|
|
108
|
+
this.line++;
|
|
109
|
+
this.column = 1;
|
|
110
|
+
} else {
|
|
111
|
+
this.column++;
|
|
112
|
+
}
|
|
113
|
+
return ch;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Produce the full token list, terminated by an EOF token.
|
|
118
|
+
* @returns {Token[]}
|
|
119
|
+
*/
|
|
120
|
+
tokenize() {
|
|
121
|
+
const tokens = [];
|
|
122
|
+
while (true) {
|
|
123
|
+
this.skipTrivia();
|
|
124
|
+
if (this.isAtEnd()) break;
|
|
125
|
+
|
|
126
|
+
const line = this.line;
|
|
127
|
+
const column = this.column;
|
|
128
|
+
const ch = this.peek();
|
|
129
|
+
|
|
130
|
+
let token;
|
|
131
|
+
if (isDigit(ch)) {
|
|
132
|
+
token = this.readNumber(line, column);
|
|
133
|
+
} else if (ch === '"' || ch === "'") {
|
|
134
|
+
token = this.readString(line, column);
|
|
135
|
+
} else if (isIdentStart(ch)) {
|
|
136
|
+
token = this.readIdentifier(line, column);
|
|
137
|
+
} else {
|
|
138
|
+
token = this.readOperator(line, column);
|
|
139
|
+
}
|
|
140
|
+
tokens.push(token);
|
|
141
|
+
}
|
|
142
|
+
tokens.push(new Token('EOF', null, this.line, this.column));
|
|
143
|
+
return tokens;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/** Skip whitespace and `#` line comments. */
|
|
147
|
+
skipTrivia() {
|
|
148
|
+
while (!this.isAtEnd()) {
|
|
149
|
+
const ch = this.peek();
|
|
150
|
+
if (ch === ' ' || ch === '\t' || ch === '\r' || ch === '\n') {
|
|
151
|
+
this.advance();
|
|
152
|
+
} else if (ch === '#') {
|
|
153
|
+
while (!this.isAtEnd() && this.peek() !== '\n') this.advance();
|
|
154
|
+
} else {
|
|
155
|
+
break;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
readNumber(line, column) {
|
|
161
|
+
let text = '';
|
|
162
|
+
while (!this.isAtEnd() && isDigit(this.peek())) text += this.advance();
|
|
163
|
+
// Optional fractional part — only if followed by a digit.
|
|
164
|
+
if (this.peek() === '.' && isDigit(this.peek(1))) {
|
|
165
|
+
text += this.advance(); // the '.'
|
|
166
|
+
while (!this.isAtEnd() && isDigit(this.peek())) text += this.advance();
|
|
167
|
+
}
|
|
168
|
+
return new Token('NUMBER', Number(text), line, column);
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
readString(line, column) {
|
|
172
|
+
const quote = this.advance(); // opening quote
|
|
173
|
+
let value = '';
|
|
174
|
+
while (!this.isAtEnd() && this.peek() !== quote) {
|
|
175
|
+
let ch = this.advance();
|
|
176
|
+
if (ch === '\n') {
|
|
177
|
+
throw new FutureError('Unterminated string literal', line, column, 'lex');
|
|
178
|
+
}
|
|
179
|
+
if (ch === '\\') {
|
|
180
|
+
ch = unescape(this.advance());
|
|
181
|
+
}
|
|
182
|
+
value += ch;
|
|
183
|
+
}
|
|
184
|
+
if (this.isAtEnd()) {
|
|
185
|
+
throw new FutureError('Unterminated string literal', line, column, 'lex');
|
|
186
|
+
}
|
|
187
|
+
this.advance(); // closing quote
|
|
188
|
+
return new Token('STRING', value, line, column);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
readIdentifier(line, column) {
|
|
192
|
+
let text = '';
|
|
193
|
+
while (!this.isAtEnd() && isIdentPart(this.peek())) text += this.advance();
|
|
194
|
+
const type = KEYWORDS.get(text) ?? 'IDENTIFIER';
|
|
195
|
+
return new Token(type, text, line, column);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
readOperator(line, column) {
|
|
199
|
+
const ch = this.advance();
|
|
200
|
+
const two = ch + (this.peek() ?? '');
|
|
201
|
+
if (TWO_CHAR_OPS.has(two)) {
|
|
202
|
+
this.advance();
|
|
203
|
+
return new Token(TWO_CHAR_OPS.get(two), two, line, column);
|
|
204
|
+
}
|
|
205
|
+
if (ONE_CHAR_OPS.has(ch)) {
|
|
206
|
+
return new Token(ONE_CHAR_OPS.get(ch), ch, line, column);
|
|
207
|
+
}
|
|
208
|
+
throw new FutureError(`Unexpected character '${ch}'`, line, column, 'lex');
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// --- character helpers --------------------------------------------------------
|
|
213
|
+
|
|
214
|
+
function isDigit(ch) {
|
|
215
|
+
return ch >= '0' && ch <= '9';
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
function isIdentStart(ch) {
|
|
219
|
+
return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || ch === '_';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function isIdentPart(ch) {
|
|
223
|
+
return isIdentStart(ch) || isDigit(ch);
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
/** Translate a backslash escape into its character. Unknown escapes pass through. */
|
|
227
|
+
function unescape(ch) {
|
|
228
|
+
switch (ch) {
|
|
229
|
+
case 'n': return '\n';
|
|
230
|
+
case 't': return '\t';
|
|
231
|
+
case 'r': return '\r';
|
|
232
|
+
case '0': return '\0';
|
|
233
|
+
case '\\': return '\\';
|
|
234
|
+
case '"': return '"';
|
|
235
|
+
case "'": return "'";
|
|
236
|
+
case '{': return '\x01'; // placeholder — generator converts back to literal {
|
|
237
|
+
default: return ch ?? '';
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Convenience wrapper used by the rest of the pipeline and the tests.
|
|
243
|
+
* @param {string} source
|
|
244
|
+
* @returns {Token[]}
|
|
245
|
+
*/
|
|
246
|
+
export function tokenize(source) {
|
|
247
|
+
return new Lexer(source).tokenize();
|
|
248
|
+
}
|