@tishlang/tish 2.12.0 → 2.16.13
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/Cargo.toml +3 -0
- package/bin/tish +0 -0
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/tests/integration_test.rs +80 -0
- package/crates/tish_ast/src/ast.rs +10 -0
- package/crates/tish_builtins/src/array.rs +529 -56
- package/crates/tish_builtins/src/collections.rs +114 -40
- package/crates/tish_builtins/src/string.rs +95 -8
- package/crates/tish_bytecode/src/chunk.rs +7 -0
- package/crates/tish_bytecode/src/compiler.rs +560 -64
- package/crates/tish_bytecode/src/lib.rs +1 -1
- package/crates/tish_bytecode/src/opcode.rs +154 -2
- package/crates/tish_bytecode/src/serialize.rs +2 -0
- package/crates/tish_bytecode/tests/append_local_string_builder.rs +63 -0
- package/crates/tish_bytecode/tests/math_unary_intrinsic.rs +47 -0
- package/crates/tish_compile/src/codegen.rs +17736 -5269
- package/crates/tish_compile/src/infer.rs +1707 -190
- package/crates/tish_compile/src/lib.rs +29 -4
- package/crates/tish_compile/src/resolve.rs +66 -5
- package/crates/tish_compile/src/types.rs +224 -28
- package/crates/tish_compile/tests/dump_codegen.rs +21 -0
- package/crates/tish_compile/tests/perf_codegen_169_173.rs +8 -17
- package/crates/tish_compile/tests/perf_codegen_173_part3.rs +61 -0
- package/crates/tish_compile/tests/perf_codegen_174.rs +160 -0
- package/crates/tish_compile/tests/perf_codegen_175.rs +190 -0
- package/crates/tish_compile/tests/perf_codegen_176.rs +60 -0
- package/crates/tish_compile/tests/perf_codegen_177.rs +167 -0
- package/crates/tish_compile/tests/perf_codegen_178.rs +114 -0
- package/crates/tish_compile/tests/perf_codegen_178_rec.rs +124 -0
- package/crates/tish_compile/tests/perf_codegen_181.rs +36 -0
- package/crates/tish_compile/tests/perf_codegen_320.rs +211 -0
- package/crates/tish_compile/tests/perf_codegen_module_const_forof.rs +40 -0
- package/crates/tish_compile_js/src/codegen.rs +33 -0
- package/crates/tish_compiler_wasm/src/resolve_virtual.rs +100 -2
- package/crates/tish_core/Cargo.toml +4 -0
- package/crates/tish_core/src/json.rs +104 -5
- package/crates/tish_core/src/lib.rs +174 -0
- package/crates/tish_core/src/shape.rs +4 -2
- package/crates/tish_core/src/value.rs +565 -35
- package/crates/tish_core/src/vmref.rs +14 -0
- package/crates/tish_eval/src/eval.rs +675 -76
- package/crates/tish_eval/src/natives.rs +19 -0
- package/crates/tish_eval/src/value.rs +76 -21
- package/crates/tish_ffi/src/lib.rs +11 -1
- package/crates/tish_ffi/tests/double_free.rs +35 -0
- package/crates/tish_fmt/src/lib.rs +75 -1
- package/crates/tish_lexer/src/lib.rs +76 -0
- package/crates/tish_lexer/src/token.rs +4 -0
- package/crates/tish_lint/src/lib.rs +126 -0
- package/crates/tish_lsp/README.md +2 -1
- package/crates/tish_lsp/src/main.rs +378 -28
- package/crates/tish_native/src/build.rs +41 -0
- package/crates/tish_parser/Cargo.toml +4 -0
- package/crates/tish_parser/src/lib.rs +27 -0
- package/crates/tish_parser/src/parser.rs +302 -20
- package/crates/tish_resolve/src/lib.rs +9 -0
- package/crates/tish_runtime/Cargo.toml +5 -0
- package/crates/tish_runtime/src/http.rs +28 -10
- package/crates/tish_runtime/src/http_fetch.rs +150 -1
- package/crates/tish_runtime/src/http_prefork.rs +72 -11
- package/crates/tish_runtime/src/lib.rs +523 -42
- package/crates/tish_runtime/src/timers.rs +49 -2
- package/crates/tish_ui/src/jsx.rs +3 -0
- package/crates/tish_vm/src/jit.rs +2514 -117
- package/crates/tish_vm/src/vm.rs +1227 -182
- package/package.json +1 -1
- package/platform/darwin-arm64/tish +0 -0
- package/platform/darwin-x64/tish +0 -0
- package/platform/linux-arm64/tish +0 -0
- package/platform/linux-x64/tish +0 -0
- package/platform/win32-x64/tish.exe +0 -0
|
@@ -33,6 +33,33 @@ mod tests {
|
|
|
33
33
|
use super::*;
|
|
34
34
|
use tishlang_ast::{CallArg, Expr, ObjectProp, Statement};
|
|
35
35
|
|
|
36
|
+
/// #381: pathologically nested source must return a catchable `Err`, NOT overflow the native
|
|
37
|
+
/// stack and abort the process. Exercises expression nesting (`((((…`), array nesting (`[[[[…`),
|
|
38
|
+
/// and prefix-op nesting (`-!-!…`) — all funnel through the depth-guarded `parse_unary`.
|
|
39
|
+
#[test]
|
|
40
|
+
fn deeply_nested_source_errors_instead_of_aborting() {
|
|
41
|
+
let n = 50_000;
|
|
42
|
+
for (open, close) in [("(", ")"), ("[", "]")] {
|
|
43
|
+
let src = format!("let x = {}1{}", open.repeat(n), close.repeat(n));
|
|
44
|
+
let r = parse(&src);
|
|
45
|
+
assert!(r.is_err(), "{n}× nested {open} should be a catchable Err, not an abort");
|
|
46
|
+
assert!(
|
|
47
|
+
r.unwrap_err().contains("nesting too deep"),
|
|
48
|
+
"expected a depth-limit error"
|
|
49
|
+
);
|
|
50
|
+
}
|
|
51
|
+
let prefix = format!("let x = {}1", "-".repeat(n));
|
|
52
|
+
assert!(parse(&prefix).is_err(), "deep prefix-op chain should error, not abort");
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/// The depth guard must not reject ordinary, modestly-nested code.
|
|
56
|
+
#[test]
|
|
57
|
+
fn normal_nesting_still_parses() {
|
|
58
|
+
assert!(parse("let x = ((((1 + 2))))").is_ok());
|
|
59
|
+
assert!(parse("let y = [[1, 2], [3, [4, [5]]]]").is_ok());
|
|
60
|
+
assert!(parse("fn f() { if (a) { if (b) { return 1 } } }").is_ok());
|
|
61
|
+
}
|
|
62
|
+
|
|
36
63
|
#[test]
|
|
37
64
|
fn test_async_fn_parse() {
|
|
38
65
|
let program = parse("async fn foo() { }").expect("parse async fn");
|
|
@@ -123,8 +123,17 @@ pub struct Parser<'a> {
|
|
|
123
123
|
generic_specializations: Vec<Statement>,
|
|
124
124
|
/// Names of specializations already generated (dedup).
|
|
125
125
|
generic_done: HashSet<String>,
|
|
126
|
+
/// Current recursion depth of the expression/statement descent, bounded by [`MAX_PARSE_DEPTH`] so
|
|
127
|
+
/// pathologically nested untrusted source (`((((…`, `[[[[…`, `{{{{…`, `-!-!…`) returns a CATCHABLE
|
|
128
|
+
/// parse error instead of overflowing the native stack and aborting the whole process (#381).
|
|
129
|
+
depth: usize,
|
|
126
130
|
}
|
|
127
131
|
|
|
132
|
+
/// Maximum expression/statement nesting the parser will descend before returning a catchable error.
|
|
133
|
+
/// Deep enough for any realistic hand-written or generated source; shallow enough that the descent
|
|
134
|
+
/// stays well within the OS thread stack. Mirrors `MAX_JSON_DEPTH` in the JSON parser.
|
|
135
|
+
const MAX_PARSE_DEPTH: usize = 1024;
|
|
136
|
+
|
|
128
137
|
impl<'a> Parser<'a> {
|
|
129
138
|
pub fn new(tokens: &'a [Token]) -> Self {
|
|
130
139
|
Self {
|
|
@@ -134,9 +143,25 @@ impl<'a> Parser<'a> {
|
|
|
134
143
|
generic_aliases: HashMap::new(),
|
|
135
144
|
generic_specializations: Vec::new(),
|
|
136
145
|
generic_done: HashSet::new(),
|
|
146
|
+
depth: 0,
|
|
137
147
|
}
|
|
138
148
|
}
|
|
139
149
|
|
|
150
|
+
/// Enter one level of recursive descent. Returns a catchable `Err` (never a panic/abort) once the
|
|
151
|
+
/// nesting exceeds [`MAX_PARSE_DEPTH`]. Pair every `Ok(())` return with a matching `self.depth -=
|
|
152
|
+
/// 1` on the way out (the `parse_unary`/`parse_statement` wrappers do this unconditionally). #381
|
|
153
|
+
fn enter_depth(&mut self) -> Result<(), String> {
|
|
154
|
+
self.depth += 1;
|
|
155
|
+
if self.depth > MAX_PARSE_DEPTH {
|
|
156
|
+
self.depth -= 1;
|
|
157
|
+
return Err(format!(
|
|
158
|
+
"parse error: nesting too deep (exceeded {} levels)",
|
|
159
|
+
MAX_PARSE_DEPTH
|
|
160
|
+
));
|
|
161
|
+
}
|
|
162
|
+
Ok(())
|
|
163
|
+
}
|
|
164
|
+
|
|
140
165
|
/// Close one generic-arg `>`: use a previously-split `>>` debt, consume a `>`, or split a
|
|
141
166
|
/// `>>` (consuming it and owing one `>` to the parent). Returns false if there's no closer.
|
|
142
167
|
fn try_close_angle(&mut self) -> bool {
|
|
@@ -259,7 +284,16 @@ impl<'a> Parser<'a> {
|
|
|
259
284
|
Ok(Program { statements })
|
|
260
285
|
}
|
|
261
286
|
|
|
287
|
+
/// Depth-guarded wrapper (#381): bounds block/statement nesting (`{{{{…`, `if(x){if(x){…`) so a
|
|
288
|
+
/// crafted deeply-nested program returns a catchable error instead of a stack-overflow abort.
|
|
262
289
|
fn parse_statement(&mut self) -> Result<Statement, String> {
|
|
290
|
+
self.enter_depth()?;
|
|
291
|
+
let r = stacker::maybe_grow(64 * 1024, 512 * 1024, || self.parse_statement_inner());
|
|
292
|
+
self.depth -= 1;
|
|
293
|
+
r
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
fn parse_statement_inner(&mut self) -> Result<Statement, String> {
|
|
263
297
|
let kind = self.peek_kind().ok_or("Unexpected EOF")?;
|
|
264
298
|
let span_start = self.peek().map(|t| t.span.start).unwrap_or((0, 0));
|
|
265
299
|
|
|
@@ -1579,9 +1613,78 @@ impl<'a> Parser<'a> {
|
|
|
1579
1613
|
})
|
|
1580
1614
|
}
|
|
1581
1615
|
|
|
1616
|
+
/// #305 — consume a `from "..."` clause (`from` is a soft keyword: an `Ident` whose spelling is
|
|
1617
|
+
/// "from"), returning the module path string.
|
|
1618
|
+
fn parse_from_clause(&mut self) -> Result<Arc<str>, String> {
|
|
1619
|
+
let from_tok = self.expect(TokenKind::Ident)?;
|
|
1620
|
+
if from_tok.literal.as_deref() != Some("from") {
|
|
1621
|
+
return Err("Expected 'from' in re-export statement".to_string());
|
|
1622
|
+
}
|
|
1623
|
+
self.expect(TokenKind::String)?
|
|
1624
|
+
.literal
|
|
1625
|
+
.clone()
|
|
1626
|
+
.ok_or_else(|| "Expected string path in re-export".to_string())
|
|
1627
|
+
}
|
|
1628
|
+
|
|
1582
1629
|
fn parse_export(&mut self) -> Result<Statement, String> {
|
|
1583
1630
|
let span_start = self.expect(TokenKind::Export)?.span.start;
|
|
1584
|
-
let declaration = if matches!(self.peek_kind(), Some(TokenKind::
|
|
1631
|
+
let declaration = if matches!(self.peek_kind(), Some(TokenKind::LBrace)) {
|
|
1632
|
+
// #305: re-export named — `export { foo, bar as inc } from "./m"`
|
|
1633
|
+
self.advance(); // {
|
|
1634
|
+
let mut specifiers = Vec::new();
|
|
1635
|
+
while !matches!(self.peek_kind(), Some(TokenKind::RBrace)) {
|
|
1636
|
+
let name_tok = self.expect(TokenKind::Ident)?;
|
|
1637
|
+
let name_span = Span {
|
|
1638
|
+
start: name_tok.span.start,
|
|
1639
|
+
end: name_tok.span.end,
|
|
1640
|
+
};
|
|
1641
|
+
let name = name_tok
|
|
1642
|
+
.literal
|
|
1643
|
+
.clone()
|
|
1644
|
+
.ok_or("Expected identifier in export")?;
|
|
1645
|
+
let (alias, alias_span) = if matches!(self.peek_kind(), Some(TokenKind::As)) {
|
|
1646
|
+
self.advance(); // as
|
|
1647
|
+
let alias_tok = self.expect(TokenKind::Ident)?;
|
|
1648
|
+
let asp = Span {
|
|
1649
|
+
start: alias_tok.span.start,
|
|
1650
|
+
end: alias_tok.span.end,
|
|
1651
|
+
};
|
|
1652
|
+
(
|
|
1653
|
+
Some(alias_tok.literal.clone().ok_or("Expected alias after 'as'")?),
|
|
1654
|
+
Some(asp),
|
|
1655
|
+
)
|
|
1656
|
+
} else {
|
|
1657
|
+
(None, None)
|
|
1658
|
+
};
|
|
1659
|
+
specifiers.push(ImportSpecifier::Named {
|
|
1660
|
+
name,
|
|
1661
|
+
name_span,
|
|
1662
|
+
alias,
|
|
1663
|
+
alias_span,
|
|
1664
|
+
});
|
|
1665
|
+
if !matches!(self.peek_kind(), Some(TokenKind::RBrace)) {
|
|
1666
|
+
self.expect(TokenKind::Comma)?;
|
|
1667
|
+
}
|
|
1668
|
+
}
|
|
1669
|
+
self.expect(TokenKind::RBrace)?;
|
|
1670
|
+
let from = self.parse_from_clause()?;
|
|
1671
|
+
ExportDeclaration::ReExport {
|
|
1672
|
+
specifiers,
|
|
1673
|
+
all: false,
|
|
1674
|
+
from,
|
|
1675
|
+
span: self.span_end(span_start),
|
|
1676
|
+
}
|
|
1677
|
+
} else if matches!(self.peek_kind(), Some(TokenKind::Star)) {
|
|
1678
|
+
// #305: re-export all — `export * from "./m"`
|
|
1679
|
+
self.advance(); // *
|
|
1680
|
+
let from = self.parse_from_clause()?;
|
|
1681
|
+
ExportDeclaration::ReExport {
|
|
1682
|
+
specifiers: Vec::new(),
|
|
1683
|
+
all: true,
|
|
1684
|
+
from,
|
|
1685
|
+
span: self.span_end(span_start),
|
|
1686
|
+
}
|
|
1687
|
+
} else if matches!(self.peek_kind(), Some(TokenKind::Default)) {
|
|
1585
1688
|
self.advance();
|
|
1586
1689
|
let expr = self.parse_expr()?;
|
|
1587
1690
|
ExportDeclaration::Default(expr)
|
|
@@ -1617,6 +1720,73 @@ impl<'a> Parser<'a> {
|
|
|
1617
1720
|
self.parse_assign()
|
|
1618
1721
|
}
|
|
1619
1722
|
|
|
1723
|
+
/// #304 — is `e` a side-effect-free l-value whose object/index sub-expressions can be safely
|
|
1724
|
+
/// DUPLICATED by a compound/inc-dec desugar (no Call/New/assignment/inc-dec inside)? The desugar
|
|
1725
|
+
/// reads and writes the same target, so duplicating a side-effecting sub-expression would run it
|
|
1726
|
+
/// twice; those targets keep the existing parse error rather than mis-evaluate.
|
|
1727
|
+
fn is_simple_lvalue(e: &Expr) -> bool {
|
|
1728
|
+
match e {
|
|
1729
|
+
Expr::Ident { .. } | Expr::Literal { .. } => true,
|
|
1730
|
+
Expr::Member { object, prop, .. } => {
|
|
1731
|
+
Self::is_simple_lvalue(object)
|
|
1732
|
+
&& match prop {
|
|
1733
|
+
MemberProp::Name { .. } => true,
|
|
1734
|
+
MemberProp::Expr(pe) => Self::is_simple_lvalue(pe),
|
|
1735
|
+
}
|
|
1736
|
+
}
|
|
1737
|
+
Expr::Index { object, index, .. } => {
|
|
1738
|
+
Self::is_simple_lvalue(object) && Self::is_simple_lvalue(index)
|
|
1739
|
+
}
|
|
1740
|
+
_ => false,
|
|
1741
|
+
}
|
|
1742
|
+
}
|
|
1743
|
+
|
|
1744
|
+
/// #304 — does `e` name a member (`obj.x`) or index (`arr[i]`) target the read-modify-write
|
|
1745
|
+
/// desugar can handle (a simple l-value)?
|
|
1746
|
+
fn is_desugarable_member_target(e: &Expr) -> bool {
|
|
1747
|
+
Self::is_simple_lvalue(e)
|
|
1748
|
+
&& matches!(
|
|
1749
|
+
e,
|
|
1750
|
+
Expr::Member { prop: MemberProp::Name { .. }, .. } | Expr::Index { .. }
|
|
1751
|
+
)
|
|
1752
|
+
}
|
|
1753
|
+
|
|
1754
|
+
fn compound_op_to_binop(op: CompoundOp) -> BinOp {
|
|
1755
|
+
match op {
|
|
1756
|
+
CompoundOp::Add => BinOp::Add,
|
|
1757
|
+
CompoundOp::Sub => BinOp::Sub,
|
|
1758
|
+
CompoundOp::Mul => BinOp::Mul,
|
|
1759
|
+
CompoundOp::Div => BinOp::Div,
|
|
1760
|
+
CompoundOp::Mod => BinOp::Mod,
|
|
1761
|
+
}
|
|
1762
|
+
}
|
|
1763
|
+
|
|
1764
|
+
/// #304 — desugar a read-modify-write on a member/index target into the existing
|
|
1765
|
+
/// `MemberAssign`/`IndexAssign` node (so every backend handles it for free). `make_value(read)`
|
|
1766
|
+
/// builds the stored value from a clone of the target read (e.g. `read + 1`, `read || rhs`).
|
|
1767
|
+
/// Caller must have checked [`Self::is_desugarable_member_target`].
|
|
1768
|
+
fn desugar_member_rmw(left: &Expr, span: Span, make_value: impl FnOnce(Expr) -> Expr) -> Expr {
|
|
1769
|
+
match left {
|
|
1770
|
+
Expr::Member {
|
|
1771
|
+
object,
|
|
1772
|
+
prop: MemberProp::Name { name, .. },
|
|
1773
|
+
..
|
|
1774
|
+
} => Expr::MemberAssign {
|
|
1775
|
+
object: object.clone(),
|
|
1776
|
+
prop: Arc::clone(name),
|
|
1777
|
+
value: Box::new(make_value(left.clone())),
|
|
1778
|
+
span,
|
|
1779
|
+
},
|
|
1780
|
+
Expr::Index { object, index, .. } => Expr::IndexAssign {
|
|
1781
|
+
object: object.clone(),
|
|
1782
|
+
index: index.clone(),
|
|
1783
|
+
value: Box::new(make_value(left.clone())),
|
|
1784
|
+
span,
|
|
1785
|
+
},
|
|
1786
|
+
_ => unreachable!("desugar_member_rmw on non-member/index target"),
|
|
1787
|
+
}
|
|
1788
|
+
}
|
|
1789
|
+
|
|
1620
1790
|
fn parse_assign(&mut self) -> Result<Expr, String> {
|
|
1621
1791
|
let left = self.parse_conditional()?;
|
|
1622
1792
|
|
|
@@ -1686,18 +1856,29 @@ impl<'a> Parser<'a> {
|
|
|
1686
1856
|
};
|
|
1687
1857
|
|
|
1688
1858
|
if let Some(op) = compound_op {
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
let start = span.start;
|
|
1859
|
+
let is_ident = matches!(&left, Expr::Ident { .. });
|
|
1860
|
+
if is_ident || Self::is_desugarable_member_target(&left) {
|
|
1861
|
+
let start = left.span().start;
|
|
1692
1862
|
self.advance(); // consume the compound operator
|
|
1693
1863
|
let value = self.parse_assign()?;
|
|
1694
1864
|
let end = value.span().end;
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1865
|
+
let span = Span { start, end };
|
|
1866
|
+
if let Expr::Ident { name, .. } = &left {
|
|
1867
|
+
return Ok(Expr::CompoundAssign {
|
|
1868
|
+
name: Arc::clone(name),
|
|
1869
|
+
op,
|
|
1870
|
+
value: Box::new(value),
|
|
1871
|
+
span,
|
|
1872
|
+
});
|
|
1873
|
+
}
|
|
1874
|
+
// #304: `obj.x += e` / `arr[i] *= e` -> MemberAssign/IndexAssign(read <op> e).
|
|
1875
|
+
let binop = Self::compound_op_to_binop(op);
|
|
1876
|
+
return Ok(Self::desugar_member_rmw(&left, span, move |read| Expr::Binary {
|
|
1877
|
+
left: Box::new(read),
|
|
1878
|
+
op: binop,
|
|
1879
|
+
right: Box::new(value),
|
|
1880
|
+
span,
|
|
1881
|
+
}));
|
|
1701
1882
|
}
|
|
1702
1883
|
}
|
|
1703
1884
|
|
|
@@ -1710,18 +1891,42 @@ impl<'a> Parser<'a> {
|
|
|
1710
1891
|
};
|
|
1711
1892
|
|
|
1712
1893
|
if let Some(op) = logical_op {
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
let start = span.start;
|
|
1894
|
+
let is_ident = matches!(&left, Expr::Ident { .. });
|
|
1895
|
+
if is_ident || Self::is_desugarable_member_target(&left) {
|
|
1896
|
+
let start = left.span().start;
|
|
1716
1897
|
self.advance(); // consume the logical assign operator
|
|
1717
1898
|
let value = self.parse_assign()?;
|
|
1718
1899
|
let end = value.span().end;
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1900
|
+
let span = Span { start, end };
|
|
1901
|
+
if let Expr::Ident { name, .. } = &left {
|
|
1902
|
+
return Ok(Expr::LogicalAssign {
|
|
1903
|
+
name: Arc::clone(name),
|
|
1904
|
+
op,
|
|
1905
|
+
value: Box::new(value),
|
|
1906
|
+
span,
|
|
1907
|
+
});
|
|
1908
|
+
}
|
|
1909
|
+
// #304: `obj.x ||= e` -> MemberAssign/IndexAssign(read || e) (short-circuit preserved,
|
|
1910
|
+
// since `||`/`&&`/`??` evaluate the RHS lazily).
|
|
1911
|
+
return Ok(Self::desugar_member_rmw(&left, span, move |read| match op {
|
|
1912
|
+
LogicalAssignOp::OrOr => Expr::Binary {
|
|
1913
|
+
left: Box::new(read),
|
|
1914
|
+
op: BinOp::Or,
|
|
1915
|
+
right: Box::new(value),
|
|
1916
|
+
span,
|
|
1917
|
+
},
|
|
1918
|
+
LogicalAssignOp::AndAnd => Expr::Binary {
|
|
1919
|
+
left: Box::new(read),
|
|
1920
|
+
op: BinOp::And,
|
|
1921
|
+
right: Box::new(value),
|
|
1922
|
+
span,
|
|
1923
|
+
},
|
|
1924
|
+
LogicalAssignOp::Nullish => Expr::NullishCoalesce {
|
|
1925
|
+
left: Box::new(read),
|
|
1926
|
+
right: Box::new(value),
|
|
1927
|
+
span,
|
|
1928
|
+
},
|
|
1929
|
+
}));
|
|
1725
1930
|
}
|
|
1726
1931
|
}
|
|
1727
1932
|
|
|
@@ -1797,7 +2002,19 @@ impl<'a> Parser<'a> {
|
|
|
1797
2002
|
Ok(left)
|
|
1798
2003
|
}
|
|
1799
2004
|
|
|
2005
|
+
/// Depth-guarded wrapper (#381): every expression operand and every nested `(…)`/`[…]`/`{…}`/
|
|
2006
|
+
/// prefix-op re-enters `parse_unary`, so bounding it here bounds all expression nesting. The
|
|
2007
|
+
/// `-= 1` runs on both the `Ok` and `Err` paths, keeping the counter balanced.
|
|
1800
2008
|
fn parse_unary(&mut self) -> Result<Expr, String> {
|
|
2009
|
+
self.enter_depth()?;
|
|
2010
|
+
// Grow the stack on demand (each nesting level costs a whole precedence-chain of frames), so
|
|
2011
|
+
// we reach the MAX_PARSE_DEPTH bound and return a catchable error rather than overflowing.
|
|
2012
|
+
let r = stacker::maybe_grow(64 * 1024, 512 * 1024, || self.parse_unary_inner());
|
|
2013
|
+
self.depth -= 1;
|
|
2014
|
+
r
|
|
2015
|
+
}
|
|
2016
|
+
|
|
2017
|
+
fn parse_unary_inner(&mut self) -> Result<Expr, String> {
|
|
1801
2018
|
// Handle prefix ++/-- (consolidated)
|
|
1802
2019
|
if let Some(is_inc) = match self.peek_kind() {
|
|
1803
2020
|
Some(TokenKind::PlusPlus) => Some(true),
|
|
@@ -1819,6 +2036,24 @@ impl<'a> Parser<'a> {
|
|
|
1819
2036
|
Expr::PrefixDec { name, span }
|
|
1820
2037
|
});
|
|
1821
2038
|
}
|
|
2039
|
+
// #304: prefix `++obj.x` / `--arr[i]` on a member/index target. Desugars to a
|
|
2040
|
+
// read-modify-write, which yields the NEW value — exactly prefix semantics.
|
|
2041
|
+
if Self::is_desugarable_member_target(&operand) {
|
|
2042
|
+
let span = Span {
|
|
2043
|
+
start: span_start,
|
|
2044
|
+
end: operand.span().end,
|
|
2045
|
+
};
|
|
2046
|
+
let binop = if is_inc { BinOp::Add } else { BinOp::Sub };
|
|
2047
|
+
return Ok(Self::desugar_member_rmw(&operand, span, |read| Expr::Binary {
|
|
2048
|
+
left: Box::new(read),
|
|
2049
|
+
op: binop,
|
|
2050
|
+
right: Box::new(Expr::Literal {
|
|
2051
|
+
value: Literal::Number(1.0),
|
|
2052
|
+
span,
|
|
2053
|
+
}),
|
|
2054
|
+
span,
|
|
2055
|
+
}));
|
|
2056
|
+
}
|
|
1822
2057
|
return Err(format!(
|
|
1823
2058
|
"Prefix {} requires an identifier",
|
|
1824
2059
|
if is_inc { "++" } else { "--" }
|
|
@@ -2049,13 +2284,13 @@ impl<'a> Parser<'a> {
|
|
|
2049
2284
|
};
|
|
2050
2285
|
}
|
|
2051
2286
|
TokenKind::PlusPlus | TokenKind::MinusMinus => {
|
|
2287
|
+
let is_inc = kind == TokenKind::PlusPlus;
|
|
2052
2288
|
if let Expr::Ident {
|
|
2053
2289
|
name,
|
|
2054
2290
|
span: ident_span,
|
|
2055
2291
|
} = &expr
|
|
2056
2292
|
{
|
|
2057
2293
|
let name = Arc::clone(name);
|
|
2058
|
-
let is_inc = kind == TokenKind::PlusPlus;
|
|
2059
2294
|
let tok = self.advance().ok_or("Unexpected EOF")?;
|
|
2060
2295
|
let span = Span {
|
|
2061
2296
|
start: ident_span.start,
|
|
@@ -2066,6 +2301,26 @@ impl<'a> Parser<'a> {
|
|
|
2066
2301
|
} else {
|
|
2067
2302
|
Expr::PostfixDec { name, span }
|
|
2068
2303
|
};
|
|
2304
|
+
} else if Self::is_desugarable_member_target(&expr) {
|
|
2305
|
+
// #304: postfix `obj.x++` / `arr[i]--` on a member/index target. Desugars to a
|
|
2306
|
+
// read-modify-write (statement-position semantics: yields the NEW value; the
|
|
2307
|
+
// old-value result in expression position is a documented follow-up).
|
|
2308
|
+
let start = expr.span().start;
|
|
2309
|
+
let tok = self.advance().ok_or("Unexpected EOF")?;
|
|
2310
|
+
let span = Span {
|
|
2311
|
+
start,
|
|
2312
|
+
end: tok.span.end,
|
|
2313
|
+
};
|
|
2314
|
+
let binop = if is_inc { BinOp::Add } else { BinOp::Sub };
|
|
2315
|
+
expr = Self::desugar_member_rmw(&expr, span, |read| Expr::Binary {
|
|
2316
|
+
left: Box::new(read),
|
|
2317
|
+
op: binop,
|
|
2318
|
+
right: Box::new(Expr::Literal {
|
|
2319
|
+
value: Literal::Number(1.0),
|
|
2320
|
+
span,
|
|
2321
|
+
}),
|
|
2322
|
+
span,
|
|
2323
|
+
});
|
|
2069
2324
|
} else {
|
|
2070
2325
|
break;
|
|
2071
2326
|
}
|
|
@@ -2102,6 +2357,33 @@ impl<'a> Parser<'a> {
|
|
|
2102
2357
|
span,
|
|
2103
2358
|
})
|
|
2104
2359
|
}
|
|
2360
|
+
TokenKind::Regex => {
|
|
2361
|
+
// #299: `/pattern/flags` desugars to `new RegExp("pattern", "flags")`. Every backend
|
|
2362
|
+
// already lowers `new RegExp(str, str)`, so no codegen change is needed. The lexer
|
|
2363
|
+
// encodes the literal as `pattern\0flags`; the pattern is kept verbatim (e.g. `\d+`).
|
|
2364
|
+
let lit = t.literal.clone().ok_or("Expected regex literal")?;
|
|
2365
|
+
let (pat, flags) = match lit.split_once('\u{0}') {
|
|
2366
|
+
Some((p, f)) => (Arc::from(p), Arc::from(f)),
|
|
2367
|
+
None => (Arc::clone(&lit), Arc::from("")),
|
|
2368
|
+
};
|
|
2369
|
+
Ok(Expr::New {
|
|
2370
|
+
callee: Box::new(Expr::Ident {
|
|
2371
|
+
name: Arc::from("RegExp"),
|
|
2372
|
+
span,
|
|
2373
|
+
}),
|
|
2374
|
+
args: vec![
|
|
2375
|
+
CallArg::Expr(Expr::Literal {
|
|
2376
|
+
value: Literal::String(pat),
|
|
2377
|
+
span,
|
|
2378
|
+
}),
|
|
2379
|
+
CallArg::Expr(Expr::Literal {
|
|
2380
|
+
value: Literal::String(flags),
|
|
2381
|
+
span,
|
|
2382
|
+
}),
|
|
2383
|
+
],
|
|
2384
|
+
span,
|
|
2385
|
+
})
|
|
2386
|
+
}
|
|
2105
2387
|
TokenKind::True => Ok(Expr::Literal {
|
|
2106
2388
|
value: Literal::Bool(true),
|
|
2107
2389
|
span,
|
|
@@ -286,6 +286,7 @@ fn collect_stmt(
|
|
|
286
286
|
collect_stmt(inner, source, lsp_line, lsp_char, best)
|
|
287
287
|
}
|
|
288
288
|
ExportDeclaration::Default(e) => collect_expr(e, source, lsp_line, lsp_char, best),
|
|
289
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
289
290
|
},
|
|
290
291
|
Statement::TypeAlias {
|
|
291
292
|
name, name_span, ..
|
|
@@ -1009,6 +1010,7 @@ fn member_chain_collect_stmt(
|
|
|
1009
1010
|
ExportDeclaration::Default(e) => {
|
|
1010
1011
|
member_chain_collect_expr(e, source, lsp_line, lsp_char, best)
|
|
1011
1012
|
}
|
|
1013
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
1012
1014
|
},
|
|
1013
1015
|
Statement::Import { .. }
|
|
1014
1016
|
| Statement::Break { .. }
|
|
@@ -1598,6 +1600,7 @@ fn walk_stmt_resolve(
|
|
|
1598
1600
|
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
1599
1601
|
ExportDeclaration::Named(inner) => walk_stmt_resolve(inner, scopes, target),
|
|
1600
1602
|
ExportDeclaration::Default(e) => walk_expr_resolve(e, scopes, target),
|
|
1603
|
+
ExportDeclaration::ReExport { .. } => None,
|
|
1601
1604
|
},
|
|
1602
1605
|
Statement::TypeAlias {
|
|
1603
1606
|
name, name_span, ..
|
|
@@ -2156,6 +2159,7 @@ fn check_unresolved_stmt(
|
|
|
2156
2159
|
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
2157
2160
|
ExportDeclaration::Named(inner) => check_unresolved_stmt(inner, scopes, out),
|
|
2158
2161
|
ExportDeclaration::Default(e) => check_unresolved_expr(e, scopes, out),
|
|
2162
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
2159
2163
|
},
|
|
2160
2164
|
Statement::TypeAlias {
|
|
2161
2165
|
name, name_span, ..
|
|
@@ -2489,6 +2493,7 @@ fn enumerate_stmt(stmt: &Statement, exported: bool, out: &mut Vec<BindingSite>)
|
|
|
2489
2493
|
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
2490
2494
|
ExportDeclaration::Named(inner) => enumerate_stmt(inner, true, out),
|
|
2491
2495
|
ExportDeclaration::Default(e) => enumerate_expr(e, exported, out),
|
|
2496
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
2492
2497
|
},
|
|
2493
2498
|
Statement::VarDecl {
|
|
2494
2499
|
name,
|
|
@@ -2788,6 +2793,7 @@ fn jsx_tags_stmt(s: &Statement, out: &mut std::collections::HashSet<Arc<str>>) {
|
|
|
2788
2793
|
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
2789
2794
|
ExportDeclaration::Named(inner) => jsx_tags_stmt(inner, out),
|
|
2790
2795
|
ExportDeclaration::Default(e) => jsx_tags_expr(e, out),
|
|
2796
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
2791
2797
|
},
|
|
2792
2798
|
Statement::Import { .. }
|
|
2793
2799
|
| Statement::Break { .. }
|
|
@@ -3532,6 +3538,7 @@ fn walk_stmt_completion(
|
|
|
3532
3538
|
ExportDeclaration::Default(e) => {
|
|
3533
3539
|
walk_expr_completion(e, source, lsp_line, lsp_char, stack, best);
|
|
3534
3540
|
}
|
|
3541
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
3535
3542
|
},
|
|
3536
3543
|
}
|
|
3537
3544
|
}
|
|
@@ -3945,6 +3952,7 @@ fn tref_stmt(stmt: &Statement, out: &mut Vec<TypeOcc>) {
|
|
|
3945
3952
|
Statement::Export { declaration, .. } => match declaration.as_ref() {
|
|
3946
3953
|
ExportDeclaration::Named(inner) => tref_stmt(inner, out),
|
|
3947
3954
|
ExportDeclaration::Default(e) => tref_expr(e, out),
|
|
3955
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
3948
3956
|
},
|
|
3949
3957
|
_ => {}
|
|
3950
3958
|
}
|
|
@@ -4132,6 +4140,7 @@ fn refs_stmt(
|
|
|
4132
4140
|
refs_stmt(inner, program, source, name, def_span, out)
|
|
4133
4141
|
}
|
|
4134
4142
|
ExportDeclaration::Default(e) => refs_expr(e, program, source, name, def_span, out),
|
|
4143
|
+
ExportDeclaration::ReExport { .. } => {}
|
|
4135
4144
|
},
|
|
4136
4145
|
Statement::Import { .. }
|
|
4137
4146
|
| Statement::Break { .. }
|
|
@@ -94,3 +94,8 @@ core_affinity = { version = "0.8", optional = true }
|
|
|
94
94
|
tokio-tungstenite = { version = "0.29", optional = true }
|
|
95
95
|
futures-util = { version = "0.3", optional = true }
|
|
96
96
|
lazy_static = { version = "1", optional = true }
|
|
97
|
+
|
|
98
|
+
# #381 native-codegen recursion guard: stack-pressure probe for guarded self-recursive fns.
|
|
99
|
+
# Non-wasm only — wasm traps on overflow inside its own sandbox, so the guard is a no-op there.
|
|
100
|
+
[target.'cfg(not(target_family = "wasm"))'.dependencies]
|
|
101
|
+
stacker = "0.1"
|
|
@@ -1110,16 +1110,30 @@ where
|
|
|
1110
1110
|
drop(tx);
|
|
1111
1111
|
|
|
1112
1112
|
let mut count = 0usize;
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1113
|
+
// #384: `recv_timeout` (not a blocking `recv`) so a handler's scheduled timers still fire when the
|
|
1114
|
+
// server is idle. Handler calls all run on this single VM thread, so the timer registry is here.
|
|
1115
|
+
loop {
|
|
1116
|
+
match rx.recv_timeout(Duration::from_millis(250)) {
|
|
1117
|
+
Ok((req_prim, resp_tx)) => {
|
|
1118
|
+
let req_value = req_prim.into_value();
|
|
1119
|
+
let response_value = handler.call(&[req_value]);
|
|
1120
|
+
crate::timers::drain_timers();
|
|
1121
|
+
let resp_prim = ResponsePrimitive::from_value(&response_value);
|
|
1122
|
+
let _ = resp_tx.send(resp_prim);
|
|
1123
|
+
|
|
1124
|
+
count += 1;
|
|
1125
|
+
if max_requests.map(|m| count >= m).unwrap_or(false) {
|
|
1126
|
+
stop.store(true, Ordering::Relaxed);
|
|
1127
|
+
break;
|
|
1128
|
+
}
|
|
1129
|
+
}
|
|
1130
|
+
Err(mpsc::RecvTimeoutError::Timeout) => {
|
|
1131
|
+
crate::timers::drain_timers();
|
|
1132
|
+
if stop.load(Ordering::Relaxed) {
|
|
1133
|
+
break;
|
|
1134
|
+
}
|
|
1135
|
+
}
|
|
1136
|
+
Err(mpsc::RecvTimeoutError::Disconnected) => break,
|
|
1123
1137
|
}
|
|
1124
1138
|
}
|
|
1125
1139
|
|
|
@@ -1181,6 +1195,10 @@ fn worker_loop_direct(
|
|
|
1181
1195
|
Ok(None) => {} // timeout; re-check stop flag
|
|
1182
1196
|
Err(_) => break,
|
|
1183
1197
|
}
|
|
1198
|
+
// #384: fire any timers a handler scheduled. The handler runs on this accept thread, so its
|
|
1199
|
+
// `setTimeout`/`setInterval` callbacks live in this thread's registry; the ≤250ms `recv_timeout`
|
|
1200
|
+
// above means they fire within that window even when no new requests arrive.
|
|
1201
|
+
crate::timers::drain_timers();
|
|
1184
1202
|
}
|
|
1185
1203
|
}
|
|
1186
1204
|
|