tova 0.3.4 → 0.3.6
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/bin/tova.js +438 -58
- package/package.json +1 -1
- package/src/analyzer/analyzer.js +172 -32
- package/src/analyzer/client-analyzer.js +21 -5
- package/src/analyzer/scope.js +78 -3
- package/src/codegen/base-codegen.js +754 -45
- package/src/codegen/client-codegen.js +293 -36
- package/src/codegen/codegen.js +10 -15
- package/src/codegen/server-codegen.js +189 -40
- package/src/codegen/wasm-codegen.js +610 -0
- package/src/lexer/lexer.js +157 -109
- package/src/lexer/tokens.js +3 -0
- package/src/lsp/server.js +148 -12
- package/src/parser/ast.js +2 -1
- package/src/parser/client-parser.js +10 -3
- package/src/parser/parser.js +144 -150
- package/src/runtime/embedded.js +1 -1
- package/src/runtime/reactivity.js +307 -59
- package/src/runtime/ssr.js +101 -34
- package/src/stdlib/inline.js +333 -24
- package/src/stdlib/native-bridge.js +150 -0
- package/src/version.js +1 -1
package/src/parser/parser.js
CHANGED
|
@@ -1,37 +1,29 @@
|
|
|
1
1
|
import { TokenType } from '../lexer/tokens.js';
|
|
2
2
|
import * as AST from './ast.js';
|
|
3
|
+
import { installServerParser } from './server-parser.js';
|
|
4
|
+
import { installClientParser } from './client-parser.js';
|
|
3
5
|
|
|
4
6
|
export class Parser {
|
|
5
7
|
static MAX_EXPRESSION_DEPTH = 200;
|
|
8
|
+
static COMPARISON_OPS = null; // initialized after class definition
|
|
6
9
|
|
|
7
10
|
constructor(tokens, filename = '<stdin>') {
|
|
8
|
-
|
|
11
|
+
// Pre-filter: build array of significant tokens for O(1) peek
|
|
12
|
+
const significant = [];
|
|
13
|
+
const docs = [];
|
|
14
|
+
for (const t of tokens) {
|
|
15
|
+
const type = t.type;
|
|
16
|
+
if (type === TokenType.NEWLINE || type === TokenType.SEMICOLON) continue;
|
|
17
|
+
if (type === TokenType.DOCSTRING) { docs.push(t); continue; }
|
|
18
|
+
significant.push(t);
|
|
19
|
+
}
|
|
20
|
+
this.tokens = significant;
|
|
21
|
+
this._eof = significant[significant.length - 1]; // cache EOF for hot-path methods
|
|
9
22
|
this.filename = filename;
|
|
10
23
|
this.pos = 0;
|
|
11
24
|
this.errors = [];
|
|
12
25
|
this._expressionDepth = 0;
|
|
13
|
-
this.docstrings =
|
|
14
|
-
this._skipInsignificant();
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
_isInsignificant(type) {
|
|
18
|
-
return type === TokenType.NEWLINE || type === TokenType.DOCSTRING || type === TokenType.SEMICOLON;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
_skipInsignificant() {
|
|
22
|
-
while (this.pos < this.tokens.length && this._isInsignificant(this.tokens[this.pos].type)) {
|
|
23
|
-
this.pos++;
|
|
24
|
-
}
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
extractDocstrings(tokens) {
|
|
28
|
-
const docs = [];
|
|
29
|
-
for (const t of tokens) {
|
|
30
|
-
if (t.type === TokenType.DOCSTRING) {
|
|
31
|
-
docs.push(t);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
return docs;
|
|
26
|
+
this.docstrings = docs;
|
|
35
27
|
}
|
|
36
28
|
|
|
37
29
|
// ─── Helpers ───────────────────────────────────────────────
|
|
@@ -47,28 +39,16 @@ export class Parser {
|
|
|
47
39
|
}
|
|
48
40
|
|
|
49
41
|
current() {
|
|
50
|
-
return this.tokens[this.pos] || this.
|
|
42
|
+
return this.tokens[this.pos] || this._eof;
|
|
51
43
|
}
|
|
52
44
|
|
|
53
45
|
peek(offset = 0) {
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
// General path: skip over insignificant tokens
|
|
57
|
-
let count = 0;
|
|
58
|
-
for (let idx = this.pos + 1; idx < this.tokens.length; idx++) {
|
|
59
|
-
if (!this._isInsignificant(this.tokens[idx].type)) {
|
|
60
|
-
count++;
|
|
61
|
-
if (count === offset) return this.tokens[idx];
|
|
62
|
-
}
|
|
63
|
-
}
|
|
64
|
-
return this.tokens[this.tokens.length - 1];
|
|
46
|
+
const idx = this.pos + offset;
|
|
47
|
+
return idx < this.tokens.length ? this.tokens[idx] : this._eof;
|
|
65
48
|
}
|
|
66
49
|
|
|
67
50
|
advance() {
|
|
68
|
-
|
|
69
|
-
this.pos++;
|
|
70
|
-
this._skipInsignificant();
|
|
71
|
-
return tok;
|
|
51
|
+
return this.tokens[this.pos++] || this._eof;
|
|
72
52
|
}
|
|
73
53
|
|
|
74
54
|
check(type) {
|
|
@@ -248,8 +228,8 @@ export class Parser {
|
|
|
248
228
|
}
|
|
249
229
|
|
|
250
230
|
_attachDocstrings(program) {
|
|
251
|
-
//
|
|
252
|
-
const docTokens = this.
|
|
231
|
+
// Use pre-extracted docstring tokens
|
|
232
|
+
const docTokens = this.docstrings;
|
|
253
233
|
if (docTokens.length === 0) return;
|
|
254
234
|
|
|
255
235
|
// Group consecutive docstring lines
|
|
@@ -295,14 +275,12 @@ export class Parser {
|
|
|
295
275
|
parseTopLevel() {
|
|
296
276
|
if (this.check(TokenType.SERVER)) {
|
|
297
277
|
if (!Parser.prototype._serverParserInstalled) {
|
|
298
|
-
const { installServerParser } = import.meta.require('./server-parser.js');
|
|
299
278
|
installServerParser(Parser);
|
|
300
279
|
}
|
|
301
280
|
return this.parseServerBlock();
|
|
302
281
|
}
|
|
303
282
|
if (this.check(TokenType.CLIENT)) {
|
|
304
283
|
if (!Parser.prototype._clientParserInstalled) {
|
|
305
|
-
const { installClientParser } = import.meta.require('./client-parser.js');
|
|
306
284
|
installClientParser(Parser);
|
|
307
285
|
}
|
|
308
286
|
return this.parseClientBlock();
|
|
@@ -563,6 +541,7 @@ export class Parser {
|
|
|
563
541
|
this.advance(); // consume async
|
|
564
542
|
return this.parseForStatement(null, true);
|
|
565
543
|
}
|
|
544
|
+
if (this.check(TokenType.AT)) return this.parseDecoratedDeclaration();
|
|
566
545
|
if (this.check(TokenType.ASYNC) && this.peek(1).type === TokenType.FN) return this.parseAsyncFunctionDeclaration();
|
|
567
546
|
if (this.check(TokenType.FN) && (this.peek(1).type === TokenType.IDENTIFIER || this._isContextualKeywordToken(this.peek(1)))) return this.parseFunctionDeclaration();
|
|
568
547
|
if (this.check(TokenType.TYPE)) return this.parseTypeDeclaration();
|
|
@@ -723,7 +702,35 @@ export class Parser {
|
|
|
723
702
|
return new AST.ExternDeclaration(name, params, returnType, l, isAsync);
|
|
724
703
|
}
|
|
725
704
|
|
|
726
|
-
|
|
705
|
+
parseDecoratedDeclaration() {
|
|
706
|
+
const decorators = [];
|
|
707
|
+
while (this.check(TokenType.AT)) {
|
|
708
|
+
this.advance(); // consume @
|
|
709
|
+
const decName = this.expect(TokenType.IDENTIFIER, "Expected decorator name after '@'").value;
|
|
710
|
+
let decArgs = [];
|
|
711
|
+
if (this.check(TokenType.LPAREN)) {
|
|
712
|
+
this.advance(); // consume (
|
|
713
|
+
while (!this.check(TokenType.RPAREN) && !this.isAtEnd()) {
|
|
714
|
+
decArgs.push(this.parseExpression());
|
|
715
|
+
if (!this.match(TokenType.COMMA)) break;
|
|
716
|
+
}
|
|
717
|
+
this.expect(TokenType.RPAREN, "Expected ')' after decorator arguments");
|
|
718
|
+
}
|
|
719
|
+
decorators.push({ name: decName, args: decArgs });
|
|
720
|
+
}
|
|
721
|
+
// After decorators, expect fn or async fn
|
|
722
|
+
if (this.check(TokenType.ASYNC) && this.peek(1).type === TokenType.FN) {
|
|
723
|
+
const node = this.parseAsyncFunctionDeclaration(decorators);
|
|
724
|
+
return node;
|
|
725
|
+
}
|
|
726
|
+
if (this.check(TokenType.FN)) {
|
|
727
|
+
const node = this.parseFunctionDeclaration(decorators);
|
|
728
|
+
return node;
|
|
729
|
+
}
|
|
730
|
+
this.error("Expected 'fn' or 'async fn' after decorator");
|
|
731
|
+
}
|
|
732
|
+
|
|
733
|
+
parseFunctionDeclaration(decorators = []) {
|
|
727
734
|
const l = this.loc();
|
|
728
735
|
this.expect(TokenType.FN);
|
|
729
736
|
let name;
|
|
@@ -754,10 +761,10 @@ export class Parser {
|
|
|
754
761
|
}
|
|
755
762
|
|
|
756
763
|
const body = this.parseBlock();
|
|
757
|
-
return new AST.FunctionDeclaration(name, params, body, returnType, l, false, typeParams);
|
|
764
|
+
return new AST.FunctionDeclaration(name, params, body, returnType, l, false, typeParams, decorators);
|
|
758
765
|
}
|
|
759
766
|
|
|
760
|
-
parseAsyncFunctionDeclaration() {
|
|
767
|
+
parseAsyncFunctionDeclaration(decorators = []) {
|
|
761
768
|
const l = this.loc();
|
|
762
769
|
this.expect(TokenType.ASYNC);
|
|
763
770
|
this.expect(TokenType.FN);
|
|
@@ -789,7 +796,7 @@ export class Parser {
|
|
|
789
796
|
}
|
|
790
797
|
|
|
791
798
|
const body = this.parseBlock();
|
|
792
|
-
return new AST.FunctionDeclaration(name, params, body, returnType, l, true, typeParams);
|
|
799
|
+
return new AST.FunctionDeclaration(name, params, body, returnType, l, true, typeParams, decorators);
|
|
793
800
|
}
|
|
794
801
|
|
|
795
802
|
parseBreakStatement() {
|
|
@@ -1495,10 +1502,10 @@ export class Parser {
|
|
|
1495
1502
|
// ─── Expressions (precedence climbing) ────────────────────
|
|
1496
1503
|
|
|
1497
1504
|
parseExpression() {
|
|
1498
|
-
if (
|
|
1499
|
-
this._expressionDepth--;
|
|
1505
|
+
if (this._expressionDepth >= Parser.MAX_EXPRESSION_DEPTH) {
|
|
1500
1506
|
this.error('Expression nested too deeply (max ' + Parser.MAX_EXPRESSION_DEPTH + ' levels)');
|
|
1501
1507
|
}
|
|
1508
|
+
this._expressionDepth++;
|
|
1502
1509
|
try {
|
|
1503
1510
|
return this.parsePipe();
|
|
1504
1511
|
} finally {
|
|
@@ -1578,9 +1585,7 @@ export class Parser {
|
|
|
1578
1585
|
let left = this.parseMembership();
|
|
1579
1586
|
|
|
1580
1587
|
// Check for chained comparisons: a < b < c
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
if (compOps.some(op => this.check(op))) {
|
|
1588
|
+
if (Parser.COMPARISON_OPS.has(this.current().type)) {
|
|
1584
1589
|
// Don't parse < as comparison if it looks like JSX
|
|
1585
1590
|
if (this.check(TokenType.LESS) && this._looksLikeJSX()) {
|
|
1586
1591
|
return left;
|
|
@@ -1589,9 +1594,8 @@ export class Parser {
|
|
|
1589
1594
|
const operands = [left];
|
|
1590
1595
|
const operators = [];
|
|
1591
1596
|
|
|
1592
|
-
while (
|
|
1593
|
-
const op = this.
|
|
1594
|
-
if (!op) break;
|
|
1597
|
+
while (Parser.COMPARISON_OPS.has(this.current().type)) {
|
|
1598
|
+
const op = this.advance();
|
|
1595
1599
|
operators.push(op.value);
|
|
1596
1600
|
operands.push(this.parseMembership());
|
|
1597
1601
|
}
|
|
@@ -1885,117 +1889,100 @@ export class Parser {
|
|
|
1885
1889
|
|
|
1886
1890
|
parsePrimary() {
|
|
1887
1891
|
const l = this.loc();
|
|
1892
|
+
const tokenType = this.current().type;
|
|
1888
1893
|
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
}
|
|
1894
|
+
switch (tokenType) {
|
|
1895
|
+
case TokenType.NUMBER:
|
|
1896
|
+
return new AST.NumberLiteral(this.advance().value, l);
|
|
1893
1897
|
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
}
|
|
1898
|
-
|
|
1899
|
-
// Regex literal
|
|
1900
|
-
if (this.check(TokenType.REGEX)) {
|
|
1901
|
-
const token = this.advance();
|
|
1902
|
-
return new AST.RegexLiteral(token.value.pattern, token.value.flags, l);
|
|
1903
|
-
}
|
|
1898
|
+
case TokenType.STRING:
|
|
1899
|
+
case TokenType.STRING_TEMPLATE:
|
|
1900
|
+
return this.parseStringLiteral();
|
|
1904
1901
|
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
}
|
|
1910
|
-
if (this.check(TokenType.FALSE)) {
|
|
1911
|
-
this.advance();
|
|
1912
|
-
return new AST.BooleanLiteral(false, l);
|
|
1913
|
-
}
|
|
1902
|
+
case TokenType.REGEX: {
|
|
1903
|
+
const token = this.advance();
|
|
1904
|
+
return new AST.RegexLiteral(token.value.pattern, token.value.flags, l);
|
|
1905
|
+
}
|
|
1914
1906
|
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
return new AST.NilLiteral(l);
|
|
1919
|
-
}
|
|
1907
|
+
case TokenType.TRUE:
|
|
1908
|
+
this.advance();
|
|
1909
|
+
return new AST.BooleanLiteral(true, l);
|
|
1920
1910
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
}
|
|
1911
|
+
case TokenType.FALSE:
|
|
1912
|
+
this.advance();
|
|
1913
|
+
return new AST.BooleanLiteral(false, l);
|
|
1925
1914
|
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
}
|
|
1915
|
+
case TokenType.NIL:
|
|
1916
|
+
this.advance();
|
|
1917
|
+
return new AST.NilLiteral(l);
|
|
1930
1918
|
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
return this.parseAsyncLambda();
|
|
1934
|
-
}
|
|
1919
|
+
case TokenType.MATCH:
|
|
1920
|
+
return this.parseMatchExpression();
|
|
1935
1921
|
|
|
1936
|
-
|
|
1937
|
-
|
|
1938
|
-
return this.parseLambda();
|
|
1939
|
-
}
|
|
1922
|
+
case TokenType.IF:
|
|
1923
|
+
return this.parseIfExpression();
|
|
1940
1924
|
|
|
1941
|
-
|
|
1942
|
-
|
|
1925
|
+
case TokenType.ASYNC:
|
|
1926
|
+
if (this.peek(1).type === TokenType.FN) {
|
|
1927
|
+
return this.parseAsyncLambda();
|
|
1928
|
+
}
|
|
1929
|
+
break;
|
|
1943
1930
|
|
|
1944
|
-
|
|
1945
|
-
|
|
1946
|
-
|
|
1947
|
-
|
|
1931
|
+
case TokenType.FN:
|
|
1932
|
+
if (this.peek(1).type === TokenType.LPAREN) {
|
|
1933
|
+
return this.parseLambda();
|
|
1934
|
+
}
|
|
1935
|
+
break;
|
|
1936
|
+
|
|
1937
|
+
case TokenType.LBRACKET:
|
|
1938
|
+
return this.parseArrayOrComprehension();
|
|
1939
|
+
|
|
1940
|
+
case TokenType.LBRACE:
|
|
1941
|
+
return this.parseObjectOrDictComprehension();
|
|
1942
|
+
|
|
1943
|
+
case TokenType.DOT:
|
|
1944
|
+
// Column expression: .column (for table operations)
|
|
1945
|
+
if (this.peek(1).type === TokenType.IDENTIFIER) {
|
|
1946
|
+
this.advance(); // consume .
|
|
1947
|
+
const name = this.advance().value; // consume identifier
|
|
1948
|
+
// Check for column assignment: .col = expr (used in derive)
|
|
1949
|
+
if (this.check(TokenType.ASSIGN)) {
|
|
1950
|
+
this.advance(); // consume =
|
|
1951
|
+
const expr = this.parseExpression();
|
|
1952
|
+
return new AST.ColumnAssignment(name, expr, l);
|
|
1953
|
+
}
|
|
1954
|
+
return new AST.ColumnExpression(name, l);
|
|
1955
|
+
}
|
|
1956
|
+
break;
|
|
1948
1957
|
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
return this.parseObjectOrDictComprehension();
|
|
1952
|
-
}
|
|
1958
|
+
case TokenType.LPAREN:
|
|
1959
|
+
return this.parseParenOrArrowLambda();
|
|
1953
1960
|
|
|
1954
|
-
|
|
1955
|
-
|
|
1956
|
-
|
|
1957
|
-
|
|
1958
|
-
|
|
1959
|
-
// Column expressions appear in function arguments and assignments
|
|
1960
|
-
this.advance(); // consume .
|
|
1961
|
-
const name = this.advance().value; // consume identifier
|
|
1961
|
+
case TokenType.SERVER:
|
|
1962
|
+
case TokenType.CLIENT:
|
|
1963
|
+
case TokenType.SHARED:
|
|
1964
|
+
case TokenType.DERIVE:
|
|
1965
|
+
return new AST.Identifier(this.advance().value, l);
|
|
1962
1966
|
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1967
|
+
case TokenType.IDENTIFIER: {
|
|
1968
|
+
const name = this.advance().value;
|
|
1969
|
+
// Check for arrow lambda: x => expr or x -> expr
|
|
1970
|
+
if (this.check(TokenType.ARROW) || this.check(TokenType.THIN_ARROW)) {
|
|
1971
|
+
this.advance();
|
|
1972
|
+
const body = this.parseExpression();
|
|
1973
|
+
return new AST.LambdaExpression(
|
|
1974
|
+
[new AST.Parameter(name, null, null, l)],
|
|
1975
|
+
body,
|
|
1976
|
+
l
|
|
1977
|
+
);
|
|
1978
|
+
}
|
|
1979
|
+
return new AST.Identifier(name, l);
|
|
1968
1980
|
}
|
|
1969
|
-
|
|
1970
|
-
return new AST.ColumnExpression(name, l);
|
|
1971
|
-
}
|
|
1972
|
-
|
|
1973
|
-
// Parenthesized expression or arrow lambda
|
|
1974
|
-
if (this.check(TokenType.LPAREN)) {
|
|
1975
|
-
return this.parseParenOrArrowLambda();
|
|
1976
|
-
}
|
|
1977
|
-
|
|
1978
|
-
// Keywords that can appear as identifiers in expression position
|
|
1979
|
-
if (this.check(TokenType.SERVER) || this.check(TokenType.CLIENT) || this.check(TokenType.SHARED) || this.check(TokenType.DERIVE) ||
|
|
1980
|
-
this._isContextualKeyword()) {
|
|
1981
|
-
const name = this.advance().value;
|
|
1982
|
-
return new AST.Identifier(name, l);
|
|
1983
1981
|
}
|
|
1984
1982
|
|
|
1985
|
-
//
|
|
1986
|
-
if (this.
|
|
1987
|
-
|
|
1988
|
-
// Check for arrow lambda: x => expr or x -> expr
|
|
1989
|
-
if (this.check(TokenType.ARROW) || this.check(TokenType.THIN_ARROW)) {
|
|
1990
|
-
this.advance();
|
|
1991
|
-
const body = this.parseExpression();
|
|
1992
|
-
return new AST.LambdaExpression(
|
|
1993
|
-
[new AST.Parameter(name, null, null, l)],
|
|
1994
|
-
body,
|
|
1995
|
-
l
|
|
1996
|
-
);
|
|
1997
|
-
}
|
|
1998
|
-
return new AST.Identifier(name, l);
|
|
1983
|
+
// Contextual keywords that can appear as identifiers in expression position
|
|
1984
|
+
if (this._isContextualKeyword()) {
|
|
1985
|
+
return new AST.Identifier(this.advance().value, l);
|
|
1999
1986
|
}
|
|
2000
1987
|
|
|
2001
1988
|
this.error(`Unexpected token: ${this.current().type}`);
|
|
@@ -2486,3 +2473,10 @@ export class Parser {
|
|
|
2486
2473
|
return node;
|
|
2487
2474
|
}
|
|
2488
2475
|
}
|
|
2476
|
+
|
|
2477
|
+
// Initialize static Set after class definition (depends on TokenType)
|
|
2478
|
+
Parser.COMPARISON_OPS = new Set([
|
|
2479
|
+
TokenType.LESS, TokenType.LESS_EQUAL,
|
|
2480
|
+
TokenType.GREATER, TokenType.GREATER_EQUAL,
|
|
2481
|
+
TokenType.EQUAL, TokenType.NOT_EQUAL
|
|
2482
|
+
]);
|