@tishlang/tish 1.0.29 → 1.0.33

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 (60) hide show
  1. package/Cargo.toml +1 -0
  2. package/crates/js_to_tish/src/transform/expr.rs +15 -6
  3. package/crates/tish/Cargo.toml +1 -1
  4. package/crates/tish/src/main.rs +1 -1
  5. package/crates/tish/tests/integration_test.rs +4 -3
  6. package/crates/tish_ast/src/ast.rs +65 -2
  7. package/crates/tish_build_utils/src/lib.rs +10 -2
  8. package/crates/tish_builtins/src/construct.rs +177 -0
  9. package/crates/tish_builtins/src/globals.rs +3 -5
  10. package/crates/tish_builtins/src/helpers.rs +2 -3
  11. package/crates/tish_builtins/src/lib.rs +1 -0
  12. package/crates/tish_builtins/src/object.rs +3 -4
  13. package/crates/tish_bytecode/src/compiler.rs +85 -11
  14. package/crates/tish_bytecode/src/opcode.rs +7 -3
  15. package/crates/tish_compile/Cargo.toml +1 -0
  16. package/crates/tish_compile/src/codegen.rs +233 -71
  17. package/crates/tish_compile/src/lib.rs +35 -0
  18. package/crates/tish_compile_js/Cargo.toml +1 -0
  19. package/crates/tish_compile_js/src/codegen.rs +38 -94
  20. package/crates/tish_compile_js/src/lib.rs +0 -1
  21. package/crates/tish_compile_js/src/tests_jsx.rs +68 -0
  22. package/crates/tish_core/Cargo.toml +4 -0
  23. package/crates/tish_core/src/console_style.rs +7 -1
  24. package/crates/tish_core/src/json.rs +1 -2
  25. package/crates/tish_core/src/macros.rs +2 -3
  26. package/crates/tish_core/src/value.rs +10 -5
  27. package/crates/tish_eval/Cargo.toml +2 -0
  28. package/crates/tish_eval/src/eval.rs +149 -72
  29. package/crates/tish_eval/src/http.rs +3 -4
  30. package/crates/tish_eval/src/regex.rs +3 -2
  31. package/crates/tish_eval/src/value.rs +11 -13
  32. package/crates/tish_eval/src/value_convert.rs +4 -8
  33. package/crates/tish_fmt/src/lib.rs +49 -10
  34. package/crates/tish_lexer/src/token.rs +2 -0
  35. package/crates/tish_lint/src/lib.rs +9 -0
  36. package/crates/tish_lsp/README.md +1 -1
  37. package/crates/tish_native/src/build.rs +16 -2
  38. package/crates/tish_opt/src/lib.rs +15 -0
  39. package/crates/tish_parser/src/lib.rs +101 -1
  40. package/crates/tish_parser/src/parser.rs +161 -50
  41. package/crates/tish_runtime/src/http.rs +4 -5
  42. package/crates/tish_runtime/src/http_fetch.rs +9 -10
  43. package/crates/tish_runtime/src/lib.rs +9 -2
  44. package/crates/tish_runtime/src/promise.rs +2 -3
  45. package/crates/tish_runtime/src/promise_io.rs +2 -3
  46. package/crates/tish_runtime/src/ws.rs +7 -7
  47. package/crates/tish_ui/Cargo.toml +17 -0
  48. package/crates/tish_ui/src/jsx.rs +390 -0
  49. package/crates/tish_ui/src/lib.rs +16 -0
  50. package/crates/tish_ui/src/runtime/hooks.rs +122 -0
  51. package/crates/tish_ui/src/runtime/mod.rs +173 -0
  52. package/crates/tish_vm/src/vm.rs +121 -27
  53. package/justfile +3 -3
  54. package/package.json +1 -1
  55. package/platform/darwin-arm64/tish +0 -0
  56. package/platform/darwin-x64/tish +0 -0
  57. package/platform/linux-arm64/tish +0 -0
  58. package/platform/linux-x64/tish +0 -0
  59. package/platform/win32-x64/tish.exe +0 -0
  60. package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
@@ -1,13 +1,12 @@
1
1
  //! Conversion between tishlang_eval::Value and tishlang_core::Value for opaque method calls.
2
2
 
3
3
  use std::cell::RefCell;
4
- use std::collections::HashMap;
5
4
  use std::rc::Rc;
6
5
  use std::sync::Arc;
7
6
 
8
- use tishlang_core::Value as CoreValue;
7
+ use tishlang_core::{ObjectMap, Value as CoreValue};
9
8
 
10
- use crate::value::Value;
9
+ use crate::value::{PropMap, Value};
11
10
 
12
11
  /// Convert interpreter Value to core Value. Fails for interpreter-only variants.
13
12
  pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
@@ -24,7 +23,7 @@ pub fn eval_to_core(v: &Value) -> Result<CoreValue, String> {
24
23
  Ok(CoreValue::Array(Rc::new(RefCell::new(out))))
25
24
  }
26
25
  Value::Object(map) => {
27
- let mut out = HashMap::new();
26
+ let mut out = ObjectMap::default();
28
27
  for (k, v) in map.borrow().iter() {
29
28
  out.insert(Arc::clone(k), eval_to_core(v)?);
30
29
  }
@@ -53,7 +52,7 @@ pub fn core_to_eval(v: CoreValue) -> Value {
53
52
  Value::Array(Rc::new(RefCell::new(out)))
54
53
  }
55
54
  CoreValue::Object(map) => {
56
- let mut out = HashMap::new();
55
+ let mut out = PropMap::default();
57
56
  for (k, v) in map.borrow().iter() {
58
57
  out.insert(Arc::clone(k), core_to_eval(v.clone()));
59
58
  }
@@ -64,10 +63,7 @@ pub fn core_to_eval(v: CoreValue) -> Value {
64
63
  CoreValue::Promise(p) => Value::CorePromise(Arc::clone(&p)),
65
64
  #[cfg(not(feature = "http"))]
66
65
  CoreValue::Promise(_) => Value::Null,
67
- #[cfg(any(feature = "http", feature = "ws"))]
68
66
  CoreValue::Function(f) => Value::CoreFn(Rc::clone(&f)),
69
- #[cfg(not(any(feature = "http", feature = "ws")))]
70
- CoreValue::Function(_) => Value::Null,
71
67
  // tishlang_core gets regex from http or regex features; handle RegExp when it exists
72
68
  #[cfg(any(feature = "http", feature = "regex"))]
73
69
  CoreValue::RegExp(re) => {
@@ -2,7 +2,7 @@
2
2
 
3
3
  use tishlang_ast::{
4
4
  ArrayElement, ArrowBody, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern,
5
- ExportDeclaration, Expr, ImportSpecifier, JsxAttrValue, JsxChild, JsxProp,
5
+ ExportDeclaration, Expr, FunParam, ImportSpecifier, JsxAttrValue, JsxChild, JsxProp,
6
6
  Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Statement, TypeAnnotation,
7
7
  TypedParam, UnaryOp,
8
8
  };
@@ -385,19 +385,38 @@ impl Printer {
385
385
  self.buf.push_str(" }");
386
386
  }
387
387
 
388
- fn param_list(&mut self, params: &[TypedParam], rest: &Option<TypedParam>) {
388
+ fn param_list(&mut self, params: &[FunParam], rest: &Option<TypedParam>) {
389
389
  for (i, p) in params.iter().enumerate() {
390
390
  if i > 0 {
391
391
  self.buf.push_str(", ");
392
392
  }
393
- self.buf.push_str(p.name.as_ref());
394
- if let Some(t) = &p.type_ann {
395
- self.buf.push_str(": ");
396
- self.type_ann(t);
397
- }
398
- if let Some(e) = &p.default {
399
- self.buf.push_str(" = ");
400
- self.expr(e);
393
+ match p {
394
+ FunParam::Simple(tp) => {
395
+ self.buf.push_str(tp.name.as_ref());
396
+ if let Some(t) = &tp.type_ann {
397
+ self.buf.push_str(": ");
398
+ self.type_ann(t);
399
+ }
400
+ if let Some(e) = &tp.default {
401
+ self.buf.push_str(" = ");
402
+ self.expr(e);
403
+ }
404
+ }
405
+ FunParam::Destructure {
406
+ pattern,
407
+ type_ann,
408
+ default,
409
+ } => {
410
+ self.destruct_pat(pattern);
411
+ if let Some(t) = type_ann {
412
+ self.buf.push_str(": ");
413
+ self.type_ann(t);
414
+ }
415
+ if let Some(e) = default {
416
+ self.buf.push_str(" = ");
417
+ self.expr(e);
418
+ }
419
+ }
401
420
  }
402
421
  }
403
422
  if let Some(r) = rest {
@@ -551,6 +570,26 @@ impl Printer {
551
570
  }
552
571
  self.buf.push(')');
553
572
  }
573
+ Expr::New { callee, args, .. } => {
574
+ self.buf.push_str("new ");
575
+ self.expr(callee);
576
+ if !args.is_empty() {
577
+ self.buf.push('(');
578
+ for (i, a) in args.iter().enumerate() {
579
+ if i > 0 {
580
+ self.buf.push_str(", ");
581
+ }
582
+ match a {
583
+ CallArg::Expr(ex) => self.expr(ex),
584
+ CallArg::Spread(ex) => {
585
+ self.buf.push_str("...");
586
+ self.expr(ex);
587
+ }
588
+ }
589
+ }
590
+ self.buf.push(')');
591
+ }
592
+ }
554
593
  Expr::Member {
555
594
  object,
556
595
  prop,
@@ -54,6 +54,7 @@ pub enum TokenKind {
54
54
  In,
55
55
  Async,
56
56
  Await,
57
+ New,
57
58
  Import,
58
59
  Export,
59
60
 
@@ -149,6 +150,7 @@ impl TokenKind {
149
150
  "in" => TokenKind::In,
150
151
  "async" => TokenKind::Async,
151
152
  "await" => TokenKind::Await,
153
+ "new" => TokenKind::New,
152
154
  "import" => TokenKind::Import,
153
155
  "export" => TokenKind::Export,
154
156
  _ => TokenKind::Ident,
@@ -180,6 +180,15 @@ fn lint_expr(e: &Expr, out: &mut Vec<LintDiagnostic>) {
180
180
  }
181
181
  }
182
182
  }
183
+ Expr::New { callee, args, .. } => {
184
+ lint_expr(callee, out);
185
+ for a in args {
186
+ match a {
187
+ tishlang_ast::CallArg::Expr(x) => lint_expr(x, out),
188
+ tishlang_ast::CallArg::Spread(x) => lint_expr(x, out),
189
+ }
190
+ }
191
+ }
183
192
  Expr::Member { object, .. } => {
184
193
  lint_expr(object, out);
185
194
  }
@@ -5,7 +5,7 @@ Language Server Protocol implementation for [Tish](https://github.com/tishlang/t
5
5
  ## Build
6
6
 
7
7
  ```bash
8
- cargo build --release -p tish_lsp
8
+ cargo build --release -p tishlang_lsp
9
9
  ```
10
10
 
11
11
  Binary: `target/release/tish-lsp` (stdio LSP).
@@ -45,6 +45,19 @@ pub fn build_via_cargo(
45
45
  })
46
46
  .collect();
47
47
 
48
+ let tish_ui_path = std::path::Path::new(&runtime_path)
49
+ .parent()
50
+ .ok_or_else(|| "invalid tishlang_runtime path (no parent)".to_string())?
51
+ .join("tish_ui");
52
+ let ui_dep = if rust_code.contains("tishlang_ui") {
53
+ format!(
54
+ "\ntishlang_ui = {{ path = {:?}, default-features = false, features = [\"runtime\"] }}\n",
55
+ tish_ui_path.display().to_string().replace('\\', "/")
56
+ )
57
+ } else {
58
+ String::new()
59
+ };
60
+
48
61
  let cargo_toml = format!(
49
62
  r#"[package]
50
63
  name = "tish_output"
@@ -63,7 +76,7 @@ codegen-units = 1
63
76
  lto = "thin"
64
77
 
65
78
  [dependencies]
66
- tishlang_runtime = {{ path = {:?}{} }}{}{}
79
+ tishlang_runtime = {{ path = {:?}{} }}{}{}{}
67
80
  "#,
68
81
  out_name,
69
82
  runtime_path,
@@ -73,7 +86,8 @@ tishlang_runtime = {{ path = {:?}{} }}{}{}
73
86
  String::new()
74
87
  } else {
75
88
  format!("\n{}", native_deps)
76
- }
89
+ },
90
+ ui_dep
77
91
  );
78
92
 
79
93
  fs::write(build_dir.join("Cargo.toml"), cargo_toml)
@@ -339,6 +339,21 @@ fn optimize_expr(expr: &Expr) -> Expr {
339
339
  .collect(),
340
340
  span: *span,
341
341
  },
342
+ Expr::New {
343
+ callee,
344
+ args,
345
+ span,
346
+ } => Expr::New {
347
+ callee: Box::new(optimize_expr(callee)),
348
+ args: args
349
+ .iter()
350
+ .map(|a| match a {
351
+ tishlang_ast::CallArg::Expr(e) => tishlang_ast::CallArg::Expr(optimize_expr(e)),
352
+ tishlang_ast::CallArg::Spread(e) => tishlang_ast::CallArg::Spread(optimize_expr(e)),
353
+ })
354
+ .collect(),
355
+ span: *span,
356
+ },
342
357
  Expr::Member {
343
358
  object,
344
359
  prop,
@@ -18,7 +18,7 @@ pub fn parse(source: &str) -> Result<Program, String> {
18
18
  #[cfg(test)]
19
19
  mod tests {
20
20
  use super::*;
21
- use tishlang_ast::{Expr, ObjectProp, Statement};
21
+ use tishlang_ast::{CallArg, Expr, ObjectProp, Statement};
22
22
 
23
23
  #[test]
24
24
  fn test_async_fn_parse() {
@@ -120,4 +120,104 @@ mod tests {
120
120
  _ => panic!("expected KeyValue prop"),
121
121
  }
122
122
  }
123
+
124
+ fn unwrap_expr_stmt(program: &tishlang_ast::Program) -> &Expr {
125
+ match program.statements.first() {
126
+ Some(Statement::ExprStmt { expr, .. }) => expr,
127
+ _ => panic!("expected expression statement"),
128
+ }
129
+ }
130
+
131
+ #[test]
132
+ fn new_expression_simple_call() {
133
+ let program = parse("new Foo()").expect("parse");
134
+ let e = unwrap_expr_stmt(&program);
135
+ match e {
136
+ Expr::New { callee, args, .. } => {
137
+ assert!(matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Foo"));
138
+ assert!(args.is_empty());
139
+ }
140
+ _ => panic!("expected New, got {:?}", e),
141
+ }
142
+ }
143
+
144
+ #[test]
145
+ fn new_expression_with_args() {
146
+ let program = parse("new Uint8Array(16)").expect("parse");
147
+ let e = unwrap_expr_stmt(&program);
148
+ match e {
149
+ Expr::New { callee, args, .. } => {
150
+ assert!(matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Uint8Array"));
151
+ assert_eq!(args.len(), 1);
152
+ assert!(matches!(&args[0], CallArg::Expr(Expr::Literal { .. })));
153
+ }
154
+ _ => panic!("expected New"),
155
+ }
156
+ }
157
+
158
+ #[test]
159
+ fn new_expression_member_callee() {
160
+ let program = parse("new ns.AudioContext()").expect("parse");
161
+ let e = unwrap_expr_stmt(&program);
162
+ match e {
163
+ Expr::New { callee, args, .. } => {
164
+ assert!(matches!(
165
+ callee.as_ref(),
166
+ Expr::Member { prop: tishlang_ast::MemberProp::Name(p), .. } if p.as_ref() == "AudioContext"
167
+ ));
168
+ assert!(args.is_empty());
169
+ }
170
+ _ => panic!("expected New"),
171
+ }
172
+ }
173
+
174
+ #[test]
175
+ fn new_expression_chained_new() {
176
+ let program = parse("new new Date()").expect("parse");
177
+ let e = unwrap_expr_stmt(&program);
178
+ match e {
179
+ Expr::New { callee, args, .. } => {
180
+ assert!(args.is_empty());
181
+ match callee.as_ref() {
182
+ Expr::New { callee: inner, args: inner_args, .. } => {
183
+ assert!(matches!(inner.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Date"));
184
+ assert!(inner_args.is_empty());
185
+ }
186
+ _ => panic!("expected nested New"),
187
+ }
188
+ }
189
+ _ => panic!("expected New"),
190
+ }
191
+ }
192
+
193
+ #[test]
194
+ fn new_then_member_access() {
195
+ let program = parse("new Foo().bar").expect("parse");
196
+ let e = unwrap_expr_stmt(&program);
197
+ match e {
198
+ Expr::Member { object, prop: tishlang_ast::MemberProp::Name(p), .. } => {
199
+ assert_eq!(p.as_ref(), "bar");
200
+ match object.as_ref() {
201
+ Expr::New { callee, args, .. } => {
202
+ assert!(matches!(callee.as_ref(), Expr::Ident { name, .. } if name.as_ref() == "Foo"));
203
+ assert!(args.is_empty());
204
+ }
205
+ _ => panic!("expected New object"),
206
+ }
207
+ }
208
+ _ => panic!("expected Member"),
209
+ }
210
+ }
211
+
212
+ #[test]
213
+ fn new_with_spread_arg() {
214
+ let program = parse("new Foo(...xs)").expect("parse");
215
+ let e = unwrap_expr_stmt(&program);
216
+ match e {
217
+ Expr::New { args, .. } => {
218
+ assert!(matches!(&args[0], CallArg::Spread(Expr::Ident { name, .. }) if name.as_ref() == "xs"));
219
+ }
220
+ _ => panic!("expected New"),
221
+ }
222
+ }
123
223
  }
@@ -53,9 +53,9 @@ macro_rules! binary_multi_op {
53
53
 
54
54
  use tishlang_ast::{
55
55
  ArrowBody, ArrayElement, BinOp, CallArg, CompoundOp, DestructElement, DestructPattern,
56
- DestructProp, ExportDeclaration, Expr, ImportSpecifier, JsxAttrValue, JsxChild, JsxProp,
57
- Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement, TypeAnnotation,
58
- TypedParam, UnaryOp,
56
+ DestructProp, ExportDeclaration, Expr, FunParam, ImportSpecifier, JsxAttrValue, JsxChild,
57
+ JsxProp, Literal, LogicalAssignOp, MemberProp, ObjectProp, Program, Span, Statement,
58
+ TypeAnnotation, TypedParam, UnaryOp,
59
59
  };
60
60
  use tishlang_lexer::{Token, TokenKind};
61
61
 
@@ -363,6 +363,55 @@ impl<'a> Parser<'a> {
363
363
  self.expect(TokenKind::RBrace)?;
364
364
  Ok(DestructPattern::Object(props))
365
365
  }
366
+
367
+ /// One formal parameter: `name`, `name: T`, `name = expr`, or a destructuring pattern.
368
+ fn parse_fun_param(&mut self) -> Result<FunParam, String> {
369
+ if matches!(
370
+ self.peek_kind(),
371
+ Some(TokenKind::LBracket | TokenKind::LBrace)
372
+ ) {
373
+ let pattern = self.parse_destruct_pattern()?;
374
+ let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
375
+ self.advance();
376
+ Some(self.parse_type_annotation()?)
377
+ } else {
378
+ None
379
+ };
380
+ let default = if matches!(self.peek_kind(), Some(TokenKind::Assign)) {
381
+ self.advance();
382
+ Some(self.parse_expr()?)
383
+ } else {
384
+ None
385
+ };
386
+ return Ok(FunParam::Destructure {
387
+ pattern,
388
+ type_ann,
389
+ default,
390
+ });
391
+ }
392
+ let param_name = self
393
+ .expect(TokenKind::Ident)?
394
+ .literal
395
+ .clone()
396
+ .ok_or("Expected param name")?;
397
+ let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
398
+ self.advance();
399
+ Some(self.parse_type_annotation()?)
400
+ } else {
401
+ None
402
+ };
403
+ let default = if matches!(self.peek_kind(), Some(TokenKind::Assign)) {
404
+ self.advance();
405
+ Some(self.parse_expr()?)
406
+ } else {
407
+ None
408
+ };
409
+ Ok(FunParam::Simple(TypedParam {
410
+ name: param_name,
411
+ type_ann,
412
+ default,
413
+ }))
414
+ }
366
415
 
367
416
  /// Parse a type annotation (number, string, T[], {a: T}, etc.)
368
417
  fn parse_type_annotation(&mut self) -> Result<TypeAnnotation, String> {
@@ -480,26 +529,7 @@ impl<'a> Parser<'a> {
480
529
  }
481
530
  break;
482
531
  }
483
- let param_name = self
484
- .expect(TokenKind::Ident)?
485
- .literal
486
- .clone()
487
- .ok_or("Expected param name")?;
488
- // Optional type annotation
489
- let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
490
- self.advance();
491
- Some(self.parse_type_annotation()?)
492
- } else {
493
- None
494
- };
495
- // Optional default value
496
- let default = if matches!(self.peek_kind(), Some(TokenKind::Assign)) {
497
- self.advance();
498
- Some(self.parse_expr()?)
499
- } else {
500
- None
501
- };
502
- params.push(TypedParam { name: param_name, type_ann, default });
532
+ params.push(self.parse_fun_param()?);
503
533
  if !matches!(self.peek_kind(), Some(TokenKind::RParen)) {
504
534
  self.expect(TokenKind::Comma)?;
505
535
  }
@@ -1117,8 +1147,97 @@ impl<'a> Parser<'a> {
1117
1147
  })
1118
1148
  }
1119
1149
 
1120
- fn parse_postfix(&mut self) -> Result<Expr, String> {
1150
+ /// Member chain (`.`, `?.`, `[]`) without consuming a call `(...)`.
1151
+ fn parse_member_expression_no_call(&mut self) -> Result<Expr, String> {
1121
1152
  let mut expr = self.parse_primary()?;
1153
+ while let Some(kind) = self.peek_kind() {
1154
+ match kind {
1155
+ TokenKind::Dot | TokenKind::OptionalChain => {
1156
+ let optional = kind == TokenKind::OptionalChain;
1157
+ self.advance();
1158
+ let prop = self
1159
+ .expect(TokenKind::Ident)?
1160
+ .literal
1161
+ .clone()
1162
+ .ok_or("Expected property name")?;
1163
+ let start = expr.span().start;
1164
+ let end = self.peek().map(|x| x.span.start).unwrap_or(start);
1165
+ expr = Expr::Member {
1166
+ object: Box::new(expr),
1167
+ prop: MemberProp::Name(prop),
1168
+ optional,
1169
+ span: Span { start, end },
1170
+ };
1171
+ }
1172
+ TokenKind::LBracket => {
1173
+ self.advance();
1174
+ let index = self.parse_expr()?;
1175
+ self.expect(TokenKind::RBracket)?;
1176
+ let start = expr.span().start;
1177
+ let end = self.peek().map(|x| x.span.start).unwrap_or(start);
1178
+ expr = Expr::Index {
1179
+ object: Box::new(expr),
1180
+ index: Box::new(index),
1181
+ optional: false,
1182
+ span: Span { start, end },
1183
+ };
1184
+ }
1185
+ _ => break,
1186
+ }
1187
+ }
1188
+ Ok(expr)
1189
+ }
1190
+
1191
+ /// ECMAScript `NewExpression`: `new` chains, then member expression without call, optional `(...)`.
1192
+ fn parse_new_expression(&mut self) -> Result<Expr, String> {
1193
+ if matches!(self.peek_kind(), Some(TokenKind::New)) {
1194
+ let span_start = self.peek().map(|t| t.span.start).unwrap_or((0, 0));
1195
+ self.advance();
1196
+ let callee = Box::new(self.parse_new_expression()?);
1197
+ let args = if matches!(self.peek_kind(), Some(TokenKind::LParen)) {
1198
+ self.advance();
1199
+ let mut args = Vec::new();
1200
+ while !matches!(self.peek_kind(), Some(TokenKind::RParen)) {
1201
+ if matches!(self.peek_kind(), Some(TokenKind::Spread)) {
1202
+ self.advance();
1203
+ let arg_expr = self.parse_expr()?;
1204
+ args.push(CallArg::Spread(arg_expr));
1205
+ } else {
1206
+ let arg_expr = self.parse_expr()?;
1207
+ args.push(CallArg::Expr(arg_expr));
1208
+ }
1209
+ if !matches!(self.peek_kind(), Some(TokenKind::RParen)) {
1210
+ self.expect(TokenKind::Comma)?;
1211
+ }
1212
+ }
1213
+ self.expect(TokenKind::RParen)?;
1214
+ args
1215
+ } else {
1216
+ Vec::new()
1217
+ };
1218
+ let end = self
1219
+ .peek()
1220
+ .map(|x| x.span.start)
1221
+ .unwrap_or(callee.as_ref().span().end);
1222
+ Ok(Expr::New {
1223
+ callee,
1224
+ args,
1225
+ span: Span {
1226
+ start: span_start,
1227
+ end,
1228
+ },
1229
+ })
1230
+ } else {
1231
+ self.parse_member_expression_no_call()
1232
+ }
1233
+ }
1234
+
1235
+ fn parse_postfix(&mut self) -> Result<Expr, String> {
1236
+ let mut expr = if matches!(self.peek_kind(), Some(TokenKind::New)) {
1237
+ self.parse_new_expression()?
1238
+ } else {
1239
+ self.parse_primary()?
1240
+ };
1122
1241
  while let Some(kind) = self.peek_kind() {
1123
1242
  match kind {
1124
1243
  TokenKind::LParen => {
@@ -1243,7 +1362,11 @@ impl<'a> Parser<'a> {
1243
1362
  let body = self.parse_arrow_body()?;
1244
1363
  let end = self.previous_span_end();
1245
1364
  return Ok(Expr::ArrowFunction {
1246
- params: vec![TypedParam { name: name.clone(), type_ann: None, default: None }],
1365
+ params: vec![FunParam::Simple(TypedParam {
1366
+ name: name.clone(),
1367
+ type_ann: None,
1368
+ default: None,
1369
+ })],
1247
1370
  body,
1248
1371
  span: Span { start: span.start, end },
1249
1372
  });
@@ -1418,39 +1541,26 @@ impl<'a> Parser<'a> {
1418
1541
  is_arrow = true;
1419
1542
  }
1420
1543
  } else {
1421
- // Try to parse params: (x, y, z) or (x: Type, y: Type)
1544
+ // Try to parse params: (x, y), ({ a }), ([a, b]), with optional types/defaults
1545
+ let mut params_ok = true;
1422
1546
  loop {
1423
- if !matches!(self.peek_kind(), Some(TokenKind::Ident)) {
1424
- break; // Not a valid arrow function param list
1547
+ if matches!(self.peek_kind(), Some(TokenKind::RParen)) {
1548
+ break;
1549
+ }
1550
+ match self.parse_fun_param() {
1551
+ Ok(param) => params.push(param),
1552
+ Err(_) => {
1553
+ params_ok = false;
1554
+ break;
1555
+ }
1425
1556
  }
1426
- let name = self.advance().unwrap().literal.clone().ok_or("Expected param name")?;
1427
-
1428
- // Optional type annotation
1429
- let type_ann = if matches!(self.peek_kind(), Some(TokenKind::Colon)) {
1430
- self.advance();
1431
- Some(self.parse_type_annotation()?)
1432
- } else {
1433
- None
1434
- };
1435
-
1436
- // Optional default value
1437
- let default = if matches!(self.peek_kind(), Some(TokenKind::Assign)) {
1438
- self.advance();
1439
- Some(self.parse_expr()?)
1440
- } else {
1441
- None
1442
- };
1443
-
1444
- params.push(TypedParam { name, type_ann, default });
1445
-
1446
1557
  if matches!(self.peek_kind(), Some(TokenKind::Comma)) {
1447
1558
  self.advance();
1448
1559
  } else {
1449
1560
  break;
1450
1561
  }
1451
1562
  }
1452
-
1453
- if matches!(self.peek_kind(), Some(TokenKind::RParen)) {
1563
+ if params_ok && matches!(self.peek_kind(), Some(TokenKind::RParen)) {
1454
1564
  self.advance(); // consume )
1455
1565
  if matches!(self.peek_kind(), Some(TokenKind::Arrow)) {
1456
1566
  self.advance(); // consume =>
@@ -1787,6 +1897,7 @@ impl ExprSpan for Expr {
1787
1897
  Expr::Binary { span, .. } => *span,
1788
1898
  Expr::Unary { span, .. } => *span,
1789
1899
  Expr::Call { span, .. } => *span,
1900
+ Expr::New { span, .. } => *span,
1790
1901
  Expr::Member { span, .. } => *span,
1791
1902
  Expr::Index { span, .. } => *span,
1792
1903
  Expr::Conditional { span, .. } => *span,
@@ -1,12 +1,11 @@
1
1
  //! HTTP server + shared request parsing. Client `fetch` lives in `http_fetch.rs`.
2
2
 
3
3
  use std::cell::RefCell;
4
- use std::collections::HashMap;
5
4
  use std::fs::File;
6
5
  use std::io::Write;
7
6
  use std::rc::Rc;
8
7
  use std::sync::Arc;
9
- use tishlang_core::Value;
8
+ use tishlang_core::{ObjectMap, Value};
10
9
  use tokio::runtime::Runtime;
11
10
 
12
11
  thread_local! {
@@ -80,7 +79,7 @@ pub(crate) fn extract_body(options: Option<&Value>) -> Option<String> {
80
79
  }
81
80
 
82
81
  pub(crate) fn build_error_response(error: &str) -> Value {
83
- let mut obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(2);
82
+ let mut obj: ObjectMap = ObjectMap::with_capacity(2);
84
83
  obj.insert(Arc::from("error"), Value::String(error.into()));
85
84
  obj.insert(Arc::from("ok"), Value::Bool(false));
86
85
  Value::Object(Rc::new(RefCell::new(obj)))
@@ -147,7 +146,7 @@ pub fn create_server(port: u16) -> Result<tiny_http::Server, String> {
147
146
  }
148
147
 
149
148
  pub fn request_to_value(request: &mut tiny_http::Request) -> Value {
150
- let mut obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(6);
149
+ let mut obj: ObjectMap = ObjectMap::with_capacity(6);
151
150
 
152
151
  obj.insert(
153
152
  Arc::from("method"),
@@ -164,7 +163,7 @@ pub fn request_to_value(request: &mut tiny_http::Request) -> Value {
164
163
  let query_string = request.url().split('?').nth(1).unwrap_or("");
165
164
  obj.insert(Arc::from("query"), Value::String(query_string.into()));
166
165
 
167
- let mut headers_obj: HashMap<Arc<str>, Value> = HashMap::with_capacity(request.headers().len());
166
+ let mut headers_obj: ObjectMap = ObjectMap::with_capacity(request.headers().len());
168
167
  for header in request.headers() {
169
168
  headers_obj.insert(
170
169
  Arc::from(header.field.as_str().as_str()),