@tishlang/tish 1.0.27 → 1.0.29

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.
@@ -1,6 +1,6 @@
1
1
  [package]
2
2
  name = "tishlang"
3
- version = "1.0.27"
3
+ version = "1.0.29"
4
4
  edition = "2021"
5
5
  description = "Tish CLI - run, REPL, compile to native"
6
6
  license-file = { workspace = true }
@@ -56,9 +56,6 @@ struct CompileArgs {
56
56
  features: Vec<String>,
57
57
  #[arg(long)]
58
58
  no_optimize: bool,
59
- /// JS target only: `lattish` (default), `vdom` (vnode + patch; use with Lattish createRoot).
60
- #[arg(long = "jsx", value_name = "MODE", default_value = "lattish")]
61
- jsx_mode: String,
62
59
  #[arg(required = true)]
63
60
  file: String,
64
61
  }
@@ -94,7 +91,6 @@ fn main() {
94
91
  &a.native_backend,
95
92
  &a.features,
96
93
  a.no_optimize || no_opt_env,
97
- &a.jsx_mode,
98
94
  ),
99
95
  Some(Commands::DumpAst { file }) => dump_ast(&file),
100
96
  None => run_repl("vm", false), // No args = REPL
@@ -339,29 +335,7 @@ fn tish_history_path() -> Option<PathBuf> {
339
335
  home.map(|h| PathBuf::from(h).join(".tish_history"))
340
336
  }
341
337
 
342
- fn parse_jsx_mode(s: &str) -> Result<tishlang_compile_js::JsxMode, String> {
343
- match s {
344
- "legacy" => Err(
345
- "--jsx legacy was removed. Use --jsx lattish (default) with lattish merged into your \
346
- bundle, or --jsx vdom with Lattish's createRoot."
347
- .to_string(),
348
- ),
349
- "vdom" => Ok(tishlang_compile_js::JsxMode::Vdom),
350
- "lattish" => Ok(tishlang_compile_js::JsxMode::LattishH),
351
- other => Err(format!(
352
- "Unknown --jsx {:?}: use lattish (default) or vdom.",
353
- other
354
- )),
355
- }
356
- }
357
-
358
- fn compile_to_js(
359
- input_path: &Path,
360
- output_path: &str,
361
- optimize: bool,
362
- jsx: &str,
363
- ) -> Result<(), String> {
364
- let jsx_mode = parse_jsx_mode(jsx)?;
338
+ fn compile_to_js(input_path: &Path, output_path: &str, optimize: bool) -> Result<(), String> {
365
339
  let project_root = input_path.parent().and_then(|p| {
366
340
  if p.file_name().and_then(|n| n.to_str()) == Some("src") {
367
341
  p.parent()
@@ -382,13 +356,13 @@ fn compile_to_js(
382
356
  } else {
383
357
  program
384
358
  };
385
- tishlang_compile_js::compile_with_jsx(&p, optimize, jsx_mode).map_err(|e| format!("{}", e))?
359
+ tishlang_compile_js::compile_with_jsx(&p, optimize).map_err(|e| format!("{}", e))?
386
360
  } else if input_path.extension().map(|e| e == "js") == Some(true) {
387
361
  let source = fs::read_to_string(input_path).map_err(|e| format!("{}", e))?;
388
362
  let program = tishlang_js_to_tish::convert(&source).map_err(|e| format!("{}", e))?;
389
- tishlang_compile_js::compile_with_jsx(&program, optimize, jsx_mode).map_err(|e| format!("{}", e))?
363
+ tishlang_compile_js::compile_with_jsx(&program, optimize).map_err(|e| format!("{}", e))?
390
364
  } else {
391
- tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize, jsx_mode)
365
+ tishlang_compile_js::compile_project_with_jsx(input_path, project_root, optimize)
392
366
  .map_err(|e| format!("{}", e))?
393
367
  };
394
368
 
@@ -418,7 +392,6 @@ fn compile_file(
418
392
  native_backend: &str,
419
393
  cli_features: &[String],
420
394
  no_optimize: bool,
421
- jsx: &str,
422
395
  ) -> Result<(), String> {
423
396
  let optimize = !no_optimize;
424
397
  let input_path =
@@ -427,7 +400,7 @@ fn compile_file(
427
400
  let is_js = input_path.extension().map(|e| e == "js") == Some(true);
428
401
 
429
402
  if target == "js" {
430
- return compile_to_js(&input_path, output_path, optimize, jsx.trim());
403
+ return compile_to_js(&input_path, output_path, optimize);
431
404
  }
432
405
 
433
406
  if target == "wasm" && is_js {
@@ -539,7 +512,7 @@ mod cli_tests {
539
512
  use super::{Cli, Commands};
540
513
 
541
514
  #[test]
542
- fn compile_jsx_defaults_to_lattish() {
515
+ fn compile_js_target_parses() {
543
516
  let cli = Cli::try_parse_from([
544
517
  "tish",
545
518
  "compile",
@@ -551,27 +524,7 @@ mod cli_tests {
551
524
  ])
552
525
  .unwrap();
553
526
  match cli.command {
554
- Some(Commands::Compile(a)) => assert_eq!(a.jsx_mode.as_str(), "lattish"),
555
- _ => panic!("expected Compile"),
556
- }
557
- }
558
-
559
- #[test]
560
- fn compile_jsx_flag_vdom() {
561
- let cli = Cli::try_parse_from([
562
- "tish",
563
- "compile",
564
- "a.tish",
565
- "--target",
566
- "js",
567
- "--jsx",
568
- "vdom",
569
- "-o",
570
- "x.js",
571
- ])
572
- .unwrap();
573
- match cli.command {
574
- Some(Commands::Compile(a)) => assert_eq!(a.jsx_mode.as_str(), "vdom"),
527
+ Some(Commands::Compile(a)) => assert_eq!(a.file, "m.tish"),
575
528
  _ => panic!("expected Compile"),
576
529
  }
577
530
  }
@@ -10,7 +10,6 @@ repository = { workspace = true }
10
10
  default = []
11
11
 
12
12
  [dependencies]
13
- tishlang_jsx_web = { path = "../tish_jsx_web", version = ">=0.1" }
14
13
  tishlang_ast = { path = "../tish_ast", version = ">=0.1" }
15
14
  tishlang_compile = { path = "../tish_compile", version = ">=0.1" }
16
15
  tishlang_opt = { path = "../tish_opt", version = ">=0.1" }
@@ -9,23 +9,11 @@ use tishlang_ast::{
9
9
  use crate::error::CompileError;
10
10
  use crate::js_intrinsics::{JsIntrinsic, JsIntrinsics};
11
11
 
12
- /// How JSX lowers for `--target js`. Native targets never use this.
13
- #[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
14
- pub enum JsxMode {
15
- /// `h(tag, props, [children])` + `Fragment` (Lattish.tish). Import `h` / `Fragment` from lattish.
16
- #[default]
17
- LattishH,
18
- /// Vnode `__vdom_h` + `window.__lattishVdomPatch` (Lattish createRoot must use patch).
19
- Vdom,
20
- }
21
-
22
12
  struct Codegen {
23
13
  output: String,
24
14
  indent: usize,
25
15
  in_async: bool,
26
- uses_jsx: bool,
27
16
  intrinsics: JsIntrinsics,
28
- jsx_mode: JsxMode,
29
17
  }
30
18
 
31
19
  fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
@@ -36,14 +24,12 @@ fn stmt_terminates_switch(stmt: Option<&Statement>) -> bool {
36
24
  }
37
25
 
38
26
  impl Codegen {
39
- fn new(jsx_mode: JsxMode) -> Self {
27
+ fn new() -> Self {
40
28
  Self {
41
29
  output: String::new(),
42
30
  indent: 0,
43
31
  in_async: false,
44
- uses_jsx: false,
45
32
  intrinsics: JsIntrinsics::new(),
46
- jsx_mode,
47
33
  }
48
34
  }
49
35
 
@@ -72,25 +58,12 @@ impl Codegen {
72
58
 
73
59
  fn emit_program(&mut self, program: &Program) -> Result<(), CompileError> {
74
60
  self.write("// Generated by tishlang_compile_js\n");
75
- // First pass: check if JSX is used (we'll set uses_jsx during emit)
76
61
  for stmt in &program.statements {
77
62
  self.emit_statement(stmt)?;
78
63
  }
79
64
  self.output = self
80
65
  .intrinsics
81
66
  .prepend_runtime_preamble(std::mem::take(&mut self.output));
82
- if self.uses_jsx {
83
- match self.jsx_mode {
84
- JsxMode::Vdom => {
85
- self.output = format!(
86
- "{}\n{}",
87
- tishlang_jsx_web::VDOM_PRELUDE,
88
- self.output
89
- );
90
- }
91
- JsxMode::LattishH => {}
92
- }
93
- }
94
67
  Ok(())
95
68
  }
96
69
 
@@ -653,7 +626,6 @@ impl Codegen {
653
626
  format!("(await {})", o)
654
627
  }
655
628
  Expr::JsxElement { tag, props, children, .. } => {
656
- self.uses_jsx = true;
657
629
  let tag_str = if tag.chars().next().map(|c| c.is_uppercase()).unwrap_or(false) {
658
630
  tag.as_ref().to_string()
659
631
  } else {
@@ -663,28 +635,13 @@ impl Codegen {
663
635
  let children_strs: Result<Vec<_>, _> =
664
636
  children.iter().map(|c| self.emit_jsx_child(c)).collect();
665
637
  let children_str = children_strs?.join(", ");
666
- match self.jsx_mode {
667
- JsxMode::LattishH => {
668
- format!("h({}, {}, [{}])", tag_str, props_str, children_str)
669
- }
670
- JsxMode::Vdom => {
671
- format!("__vdom_h({}, {}, [{}])", tag_str, props_str, children_str)
672
- }
673
- }
638
+ format!("h({}, {}, [{}])", tag_str, props_str, children_str)
674
639
  }
675
640
  Expr::JsxFragment { children, .. } => {
676
- self.uses_jsx = true;
677
641
  let children_strs: Result<Vec<_>, _> =
678
642
  children.iter().map(|c| self.emit_jsx_child(c)).collect();
679
643
  let children_str = children_strs?.join(", ");
680
- match self.jsx_mode {
681
- JsxMode::LattishH => {
682
- format!("h(Fragment, null, [{}])", children_str)
683
- }
684
- JsxMode::Vdom => {
685
- format!("__vdom_h(__Fragment, null, [{}])", children_str)
686
- }
687
- }
644
+ format!("h(Fragment, null, [{}])", children_str)
688
645
  }
689
646
  Expr::NativeModuleLoad { spec, .. } => {
690
647
  return Err(CompileError {
@@ -754,18 +711,14 @@ impl Codegen {
754
711
  }
755
712
  }
756
713
 
757
- /// Compile a single program (no imports) to JavaScript.
758
- pub fn compile_with_jsx(
759
- program: &Program,
760
- optimize: bool,
761
- jsx_mode: JsxMode,
762
- ) -> Result<String, CompileError> {
714
+ /// Compile a single program (no imports) to JavaScript. JSX lowers to `h` / `Fragment` (Lattish).
715
+ pub fn compile_with_jsx(program: &Program, optimize: bool) -> Result<String, CompileError> {
763
716
  let program = if optimize {
764
717
  tishlang_opt::optimize(program)
765
718
  } else {
766
719
  program.clone()
767
720
  };
768
- let mut g = Codegen::new(jsx_mode);
721
+ let mut g = Codegen::new();
769
722
  g.emit_program(&program)?;
770
723
  Ok(g.output)
771
724
  }
@@ -776,7 +729,6 @@ pub fn compile_project_with_jsx(
776
729
  entry_path: &std::path::Path,
777
730
  project_root: Option<&std::path::Path>,
778
731
  optimize: bool,
779
- jsx_mode: JsxMode,
780
732
  ) -> Result<String, CompileError> {
781
733
  use tishlang_ast::Statement;
782
734
  let modules = tishlang_compile::resolve_project(entry_path, project_root)
@@ -802,7 +754,7 @@ pub fn compile_project_with_jsx(
802
754
  None
803
755
  }
804
756
  });
805
- let mut js = compile_with_jsx(&program, optimize, jsx_mode)?;
757
+ let mut js = compile_with_jsx(&program, optimize)?;
806
758
  if let Some(name) = default_export {
807
759
  js.push_str(&format!("\nexport default {};\n", name));
808
760
  }
@@ -8,14 +8,12 @@ mod js_intrinsics;
8
8
  #[cfg(test)]
9
9
  mod tests_jsx;
10
10
 
11
- pub use codegen::{compile_project_with_jsx, compile_with_jsx, JsxMode};
11
+ pub use codegen::{compile_project_with_jsx, compile_with_jsx};
12
12
  pub use error::CompileError;
13
13
 
14
- /// Default JSX mode lowers to Lattish-style calls (implementation detail). Import what you use from
15
- /// `lattish` (e.g. `useState`, `createRoot`); the merged bundle includes the JSX runtime from that
16
- /// module. JSX-only files can use `import {} from "lattish"` to pull it in without bindings.
14
+ /// JSX lowers to `h` / `Fragment`; merge the `lattish` runtime for hooks and DOM.
17
15
  pub fn compile(program: &tishlang_ast::Program, optimize: bool) -> Result<String, CompileError> {
18
- compile_with_jsx(program, optimize, JsxMode::LattishH)
16
+ compile_with_jsx(program, optimize)
19
17
  }
20
18
 
21
19
  pub fn compile_project(
@@ -23,5 +21,5 @@ pub fn compile_project(
23
21
  project_root: Option<&std::path::Path>,
24
22
  optimize: bool,
25
23
  ) -> Result<String, CompileError> {
26
- compile_project_with_jsx(entry_path, project_root, optimize, JsxMode::LattishH)
24
+ compile_project_with_jsx(entry_path, project_root, optimize)
27
25
  }
@@ -4,13 +4,13 @@ mod tests {
4
4
 
5
5
  use tishlang_parser::parse;
6
6
 
7
- use crate::{compile_project_with_jsx, compile_with_jsx, JsxMode};
7
+ use crate::{compile_project_with_jsx, compile_with_jsx};
8
8
 
9
9
  #[test]
10
10
  fn lattish_jsx_emits_h_with_children_array() {
11
11
  let src = r#"fn X() { return <div class="a">{"hi"}</div> }"#;
12
12
  let program = parse(src).unwrap();
13
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
13
+ let js = compile_with_jsx(&program, false).unwrap();
14
14
  assert!(js.contains("h(\"div\", { class: \"a\" }, [\"hi\"])"), "{}", js);
15
15
  assert!(!js.contains("function __h("));
16
16
  }
@@ -19,7 +19,7 @@ mod tests {
19
19
  fn fragment_lattish_uses_fragment_symbol() {
20
20
  let src = "fn X() { return <><b>{\"1\"}</b></> }";
21
21
  let program = parse(src).unwrap();
22
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
22
+ let js = compile_with_jsx(&program, false).unwrap();
23
23
  assert!(js.contains("h(Fragment, null, ["));
24
24
  }
25
25
 
@@ -27,7 +27,7 @@ mod tests {
27
27
  fn jsx_text_whitespace_coalesced() {
28
28
  let src = r#"fn X() { return <p>First paragraph</p> }"#;
29
29
  let program = parse(src).unwrap();
30
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
30
+ let js = compile_with_jsx(&program, false).unwrap();
31
31
  assert!(
32
32
  js.contains("\"First paragraph\""),
33
33
  "expected \"First paragraph\" in output, got: {}",
@@ -43,7 +43,7 @@ mod tests {
43
43
  fn jsx_text_whitespace_coalesced_multiline() {
44
44
  let src = "fn App() {\n return <p>First paragraph</p>\n}";
45
45
  let program = parse(src).unwrap();
46
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
46
+ let js = compile_with_jsx(&program, false).unwrap();
47
47
  assert!(
48
48
  js.contains("\"First paragraph\""),
49
49
  "multiline: expected \"First paragraph\", got: {}",
@@ -56,7 +56,7 @@ mod tests {
56
56
  // Punctuation (e.g. !) concatenates without space: "work!" not "work !"
57
57
  let src = r#"fn X() { return <p>work!</p> }"#;
58
58
  let program = parse(src).unwrap();
59
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
59
+ let js = compile_with_jsx(&program, false).unwrap();
60
60
  assert!(js.contains(r#""work!""#), "expected 'work!', got: {}", &js[..400.min(js.len())]);
61
61
  }
62
62
 
@@ -64,7 +64,7 @@ mod tests {
64
64
  fn jsx_text_emojis() {
65
65
  let src = r#"fn X() { return <p>hello 😔</p> }"#;
66
66
  let program = parse(src).unwrap();
67
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
67
+ let js = compile_with_jsx(&program, false).unwrap();
68
68
  assert!(js.contains("😔"), "expected emoji, got: {}", &js[..400.min(js.len())]);
69
69
  }
70
70
 
@@ -78,7 +78,7 @@ mod tests {
78
78
  f.write_all(src.as_bytes()).unwrap();
79
79
  f.sync_all().unwrap();
80
80
  drop(f);
81
- let js = compile_project_with_jsx(&path, Some(&dir), false, JsxMode::LattishH)
81
+ let js = compile_project_with_jsx(&path, Some(&dir), false)
82
82
  .expect("compile_project_with_jsx failed");
83
83
  assert!(
84
84
  js.contains("\"First paragraph\""),
@@ -89,12 +89,14 @@ mod tests {
89
89
  }
90
90
 
91
91
  #[test]
92
- fn vdom_emits_vdom_h() {
93
- let src = r#"fn X() { return <p/> }"#;
92
+ fn jsx_never_emits_vdom_helpers_or_prelude_flags() {
93
+ let src = r#"fn X() { return <div class="x">{"a"}</div> }"#;
94
94
  let program = parse(src).unwrap();
95
- let js = compile_with_jsx(&program, false, JsxMode::Vdom).unwrap();
96
- assert!(js.contains("__vdom_h(\"p\", null, [])"), "{}", &js[..600.min(js.len())]);
97
- assert!(js.contains("__lattishVdomPatch"));
95
+ let js = compile_with_jsx(&program, false).unwrap();
96
+ assert!(js.contains("h(\"div\", { class: \"x\" }"), "{}", &js[..500.min(js.len())]);
97
+ assert!(!js.contains("__vdom_h"), "{}", &js[..600.min(js.len())]);
98
+ assert!(!js.contains("window.__LATTISH_JSX_VDOM"), "{}", &js[..600.min(js.len())]);
99
+ assert!(!js.contains("__lattishVdomPatch"), "{}", &js[..600.min(js.len())]);
98
100
  }
99
101
 
100
102
  /// Component calls like {Panel()} return DOM elements. Wrapping in String() produces [object HTMLDivElement].
@@ -105,7 +107,7 @@ fn Panel() { return <div class="p">content</div> }
105
107
  fn App() { return <div>{Panel()}</div> }
106
108
  "#;
107
109
  let program = parse(src).unwrap();
108
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
110
+ let js = compile_with_jsx(&program, false).unwrap();
109
111
  assert!(
110
112
  js.contains("Panel()"),
111
113
  "component call should appear as Panel(), got: {}",
@@ -123,7 +125,7 @@ fn App() { return <div>{Panel()}</div> }
123
125
  fn jsx_nested_element_not_wrapped_in_string() {
124
126
  let src = r#"fn X() { return <div><span>inner</span></div> }"#;
125
127
  let program = parse(src).unwrap();
126
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
128
+ let js = compile_with_jsx(&program, false).unwrap();
127
129
  assert!(
128
130
  !js.contains("String(h("),
129
131
  "nested JSX elements must NOT be wrapped in String(). got: {}",
@@ -136,7 +138,7 @@ fn App() { return <div>{Panel()}</div> }
136
138
  fn jsx_literal_number_wrapped_in_string() {
137
139
  let src = r#"fn X() { return <span>{42}</span> }"#;
138
140
  let program = parse(src).unwrap();
139
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
141
+ let js = compile_with_jsx(&program, false).unwrap();
140
142
  assert!(
141
143
  js.contains("String(42)"),
142
144
  "literal number in JSX should be wrapped in String(). got: {}",
@@ -156,11 +158,57 @@ fn FileList() {
156
158
  }
157
159
  "#;
158
160
  let program = parse(src).unwrap();
159
- let js = compile_with_jsx(&program, false, JsxMode::LattishH).unwrap();
161
+ let js = compile_with_jsx(&program, false).unwrap();
160
162
  assert!(
161
163
  !js.contains("String(items)"),
162
164
  "array/ident in JSX must NOT be wrapped in String() - causes [object HTMLButtonElement]. got: {}",
163
165
  &js[..600.min(js.len())]
164
166
  );
165
167
  }
168
+
169
+ /// `>` inside `{ ... }` attribute values must be a comparison operator, not end of opening tag.
170
+ #[test]
171
+ fn jsx_gt_comparison_inside_attribute_expression() {
172
+ let src = r#"fn X() {
173
+ return <button
174
+ type="button"
175
+ onclick={() => {
176
+ let nm = "a"
177
+ if (nm && nm.length > 0) { print(nm) }
178
+ }}
179
+ >{"ok"}</button>
180
+ }"#;
181
+ let program = parse(src).expect("parse multi-line JSX with > comparison in attr");
182
+ let js = compile_with_jsx(&program, false).expect("compile");
183
+ assert!(
184
+ js.contains("length > 0") || js.contains("length>0"),
185
+ "expected compiled JS to preserve greater-than comparison, got: {}",
186
+ &js[..800.min(js.len())]
187
+ );
188
+ }
189
+
190
+ /// Nested JSX inside an attribute callback must still close inner `<tag>` correctly.
191
+ #[test]
192
+ fn jsx_nested_element_inside_attribute_expression() {
193
+ let src = r#"fn X() {
194
+ return <button
195
+ onclick={() => {
196
+ let x = <span>{"inner"}</span>
197
+ print(x)
198
+ }}
199
+ >{"outer"}</button>
200
+ }"#;
201
+ let program = parse(src).expect("parse nested JSX inside onclick");
202
+ let js = compile_with_jsx(&program, false).expect("compile");
203
+ assert!(
204
+ js.contains("\"inner\""),
205
+ "expected nested span text in output, got: {}",
206
+ &js[..900.min(js.len())]
207
+ );
208
+ assert!(
209
+ js.contains("\"outer\""),
210
+ "expected button child text in output, got: {}",
211
+ &js[..900.min(js.len())]
212
+ );
213
+ }
166
214
  }
@@ -10,7 +10,6 @@ mod resolve_virtual;
10
10
  use base64::Engine;
11
11
  use resolve_virtual::{detect_cycles_virtual, merge_modules_virtual, resolve_virtual};
12
12
  use std::collections::HashMap;
13
- use tishlang_compile_js::JsxMode;
14
13
  use wasm_bindgen::prelude::*;
15
14
 
16
15
  #[wasm_bindgen]
@@ -24,7 +23,7 @@ pub fn compile_to_bytecode(source: &str) -> Result<String, JsValue> {
24
23
  #[wasm_bindgen]
25
24
  pub fn compile_to_js(source: &str) -> Result<String, JsValue> {
26
25
  let program = tishlang_parser::parse(source.trim()).map_err(|e| JsValue::from_str(&e.to_string()))?;
27
- tishlang_compile_js::compile_with_jsx(&program, true, JsxMode::LattishH)
26
+ tishlang_compile_js::compile_with_jsx(&program, true)
28
27
  .map_err(|e| JsValue::from_str(&e.message))
29
28
  }
30
29
 
@@ -50,6 +49,6 @@ pub fn compile_to_js_with_imports(entry_path: &str, files_json: &str) -> Result<
50
49
  detect_cycles_virtual(&modules).map_err(|e| JsValue::from_str(&e))?;
51
50
  let program = merge_modules_virtual(modules).map_err(|e| JsValue::from_str(&e))?;
52
51
  let program = tishlang_opt::optimize(&program);
53
- tishlang_compile_js::compile_with_jsx(&program, true, JsxMode::LattishH)
52
+ tishlang_compile_js::compile_with_jsx(&program, true)
54
53
  .map_err(|e| JsValue::from_str(&e.message))
55
54
  }
@@ -2,7 +2,7 @@
2
2
  name = "tishlang_jsx_web"
3
3
  version = "0.1.0"
4
4
  edition = "2021"
5
- description = "JSX / DOM runtime snippets for Tish JS target only (not used by native)"
5
+ description = "Workspace placeholder; Lattish runtime is the lattish npm/git repo (not vendored here)"
6
6
 
7
7
  license-file = { workspace = true }
8
8
  repository = { workspace = true }
@@ -1,18 +1,5 @@
1
- # tish_jsx_web
1
+ # tishlang_jsx_web
2
2
 
3
- Web-only crate for the Tish JS backend:
3
+ Workspace placeholder. **Lattish** (`h`, `Fragment`, hooks, reconciler) lives in the [lattish](https://github.com/tishlang/lattish) repository — edit **`lattish/src/Lattish.tish` only**.
4
4
 
5
- - **`VDOM_PRELUDE`** vnode helpers + `window.__lattishVdomPatch` + `window.__LATTISH_JSX_VDOM` for `--jsx vdom`.
6
-
7
- Native / non-JS compiler targets must not depend on this crate; only `tish_compile_js` pulls it in.
8
-
9
- ## Vendor runtime
10
-
11
- `vendor/Lattish.tish` is a copy refreshed from the **lattish** npm package. Run `just refresh-lattish` to update from the sibling lattish package.
12
-
13
- ## JSX modes (`--jsx`)
14
-
15
- | Mode | JSX lowers to | Preamble |
16
- |------|----------------|----------|
17
- | `lattish` (default) | Lattish-style JSX lowering | none — merge `Lattish.tish` by importing any export you need (or `import {} from "lattish"` for JSX-only) |
18
- | `vdom` | `__vdom_h(...)` | VDOM prelude; Lattish `createRoot` patches the tree when `window.__LATTISH_JSX_VDOM` is set |
5
+ Tish lowers JSX to `h` / `Fragment` via `tishlang_compile_js`; apps merge a compiled `Lattish.js` from npm or a path dependency.
@@ -1,157 +1,2 @@
1
- //! Web-only JSX helpers for `tish compile --target js`.
2
- //! Native and WASM native paths must not depend on this crate.
3
-
4
- /// VDOM: vnode `__vdom_h` + `window.__lattishVdomPatch` for Lattish batched flush.
5
- pub const VDOM_PRELUDE: &str = r#"window.__LATTISH_JSX_VDOM = true;
6
- const __Fragment = Symbol('Fragment');
7
- function __vdom_h(tag, props, children) {
8
- if (children === undefined || children === null) children = [];
9
- if (!Array.isArray(children)) children = [children];
10
- return { tag: tag, props: props || null, children: children, _el: null };
11
- }
12
- function __vdom_flatten(ch) {
13
- const out = [];
14
- function w(c) {
15
- if (c == null) return;
16
- if (Array.isArray(c)) { for (let i = 0; i < c.length; i++) w(c[i]); return; }
17
- if (typeof c === 'object' && c && c.tag === __Fragment) {
18
- const inner = c.children;
19
- if (Array.isArray(inner)) for (let i = 0; i < inner.length; i++) w(inner[i]);
20
- return;
21
- }
22
- out.push(c);
23
- }
24
- if (Array.isArray(ch)) for (let i = 0; i < ch.length; i++) w(ch[i]);
25
- return out;
26
- }
27
- function __vdom_mount(v) {
28
- if (typeof v === 'string') return document.createTextNode(v);
29
- if (v.tag === __Fragment) {
30
- const f = document.createDocumentFragment();
31
- const ch = __vdom_flatten(v.children);
32
- for (let i = 0; i < ch.length; i++) f.appendChild(__vdom_mount(ch[i]));
33
- return f;
34
- }
35
- const el = document.createElement(v.tag);
36
- const p = v.props || {};
37
- for (const k of Object.keys(p)) {
38
- const val = p[k];
39
- if (val === true) el.setAttribute(k, k);
40
- else if (val !== false && val != null) {
41
- if (k === 'class' || k === 'className') el.className = val;
42
- else if (k.startsWith('on') && typeof val === 'function') el[k.toLowerCase()] = val;
43
- else if (k === 'value' && (v.tag === 'input' || v.tag === 'textarea' || v.tag === 'select')) el.value = val;
44
- else el.setAttribute(k, String(val));
45
- }
46
- }
47
- const ch = __vdom_flatten(v.children);
48
- for (let i = 0; i < ch.length; i++) el.appendChild(__vdom_mount(ch[i]));
49
- v._el = el;
50
- return el;
51
- }
52
- function __vdomPatchEl(el, ov, nv) {
53
- if (!el || !ov || !nv) return false;
54
- if (typeof nv === 'string') {
55
- if (el.nodeType === 3) el.textContent = nv;
56
- return true;
57
- }
58
- if (ov.tag !== nv.tag) return false;
59
- nv._el = el;
60
- const p = nv.props || {};
61
- const op = ov.props || {};
62
- for (const k of Object.keys(p)) {
63
- if (p[k] !== op[k]) {
64
- const val = p[k];
65
- if (k === 'class' || k === 'className') el.className = val || '';
66
- else if (k.startsWith('on')) el[k.toLowerCase()] = val || null;
67
- else if (k === 'value' && (nv.tag === 'input' || nv.tag === 'textarea' || nv.tag === 'select')) {
68
- if (el !== document.activeElement) el.value = val;
69
- } else el.setAttribute(k, String(val));
70
- }
71
- }
72
- const ocx = __vdom_flatten(ov.children);
73
- const ncx = __vdom_flatten(nv.children);
74
- let fullCh = ocx.length !== ncx.length;
75
- if (!fullCh) {
76
- for (let j = 0; j < ncx.length; j++) {
77
- const o = ocx[j], n = ncx[j];
78
- if (typeof n === 'string') { if (typeof o !== 'string') fullCh = true; }
79
- else if (typeof o === 'string') fullCh = true;
80
- else if (!o || o.tag !== n.tag) fullCh = true;
81
- if (fullCh) break;
82
- }
83
- }
84
- if (fullCh) {
85
- const nodes = [];
86
- for (let j = 0; j < ncx.length; j++) nodes.push(__vdom_mount(ncx[j]));
87
- el.replaceChildren(...nodes);
88
- return true;
89
- }
90
- let i = 0;
91
- while (i < ncx.length) {
92
- const o = ocx[i], n = ncx[i];
93
- const childEl = el.childNodes[i];
94
- if (n == null) {
95
- if (childEl) el.removeChild(childEl);
96
- i++;
97
- continue;
98
- }
99
- if (o == null) {
100
- el.appendChild(__vdom_mount(n));
101
- i++;
102
- continue;
103
- }
104
- if (typeof n === 'string') {
105
- if (childEl && childEl.nodeType === 3) childEl.textContent = n;
106
- else {
107
- const m = __vdom_mount(n);
108
- if (childEl) el.replaceChild(m, childEl);
109
- else el.appendChild(m);
110
- }
111
- i++;
112
- continue;
113
- }
114
- if (typeof o === 'string' || o.tag !== n.tag) {
115
- const m = __vdom_mount(n);
116
- if (childEl) el.replaceChild(m, childEl);
117
- else el.appendChild(m);
118
- i++;
119
- continue;
120
- }
121
- if (!childEl || !__vdomPatchEl(childEl, o, n)) {
122
- const m = __vdom_mount(n);
123
- if (childEl) el.replaceChild(m, childEl);
124
- else el.appendChild(m);
125
- }
126
- i++;
127
- }
128
- return true;
129
- }
130
- window.__lattishVdomPatch = function(container, oldTree, newTree) {
131
- try {
132
- if (oldTree == null) {
133
- container.replaceChildren(__vdom_mount(newTree));
134
- return;
135
- }
136
- const el = container.firstChild;
137
- if (!el) {
138
- container.appendChild(__vdom_mount(newTree));
139
- return;
140
- }
141
- if (typeof newTree === 'string' || oldTree.tag !== newTree.tag) {
142
- container.replaceChildren(__vdom_mount(newTree));
143
- return;
144
- }
145
- if (!__vdomPatchEl(el, oldTree, newTree)) {
146
- container.replaceChildren(__vdom_mount(newTree));
147
- }
148
- } catch (err) {
149
- console.warn('Lattish VDOM patch failed, full remount', err);
150
- try {
151
- container.replaceChildren(__vdom_mount(newTree));
152
- } catch (e2) {
153
- console.error('Lattish VDOM remount failed', e2);
154
- }
155
- }
156
- };
157
- "#;
1
+ //! Placeholder crate kept for workspace / crates.io ordering. The JSX runtime is maintained in the
2
+ //! [lattish](https://github.com/tishlang/lattish) repo (`src/Lattish.tish` only do not vendor a copy here).
@@ -14,6 +14,16 @@ use std::str::Chars;
14
14
  const INDENT_WIDTH: usize = 2;
15
15
  const TAB_AS_LEVELS: usize = 1;
16
16
 
17
+ /// One JSX element on the stack: tracks whether we are still in its opening tag (`<Tag ...`)
18
+ /// and how many `{` are open inside that element's **attribute values** (embedded JS).
19
+ /// This lets `>` be a comparison operator inside `{...}` while still closing `<span>` when
20
+ /// `attr_value_braces == 0` for the innermost element (React-like).
21
+ #[derive(Debug, Clone)]
22
+ struct JsxEl {
23
+ in_opener: bool,
24
+ attr_value_braces: i32,
25
+ }
26
+
17
27
  #[derive(Debug, Clone)]
18
28
  pub struct Lexer<'a> {
19
29
  chars: Peekable<Chars<'a>>,
@@ -27,7 +37,7 @@ pub struct Lexer<'a> {
27
37
  jsx_after_gt: bool,
28
38
  jsx_in_opening_tag: bool,
29
39
  jsx_saw_slash_before_gt: bool,
30
- jsx_brace_depth: i32,
40
+ jsx_stack: Vec<JsxEl>,
31
41
  jsx_depth: i32,
32
42
  jsx_child_brace_depth: i32,
33
43
  jsx_in_closing_tag: bool,
@@ -47,13 +57,18 @@ impl<'a> Lexer<'a> {
47
57
  jsx_after_gt: false,
48
58
  jsx_in_opening_tag: false,
49
59
  jsx_saw_slash_before_gt: false,
50
- jsx_brace_depth: 0,
60
+ jsx_stack: Vec::new(),
51
61
  jsx_depth: 0,
52
62
  jsx_child_brace_depth: 0,
53
63
  jsx_in_closing_tag: false,
54
64
  }
55
65
  }
56
66
 
67
+ #[inline]
68
+ fn jsx_sync_in_opening_tag(&mut self) {
69
+ self.jsx_in_opening_tag = self.jsx_stack.last().map(|e| e.in_opener).unwrap_or(false);
70
+ }
71
+
57
72
  fn read_jsx_text(&mut self, start: (usize, usize)) -> Result<Option<Token>, String> {
58
73
  let mut s = String::new();
59
74
  loop {
@@ -305,18 +320,33 @@ impl<'a> Lexer<'a> {
305
320
  '(' => TokenKind::LParen,
306
321
  ')' => TokenKind::RParen,
307
322
  '{' => {
308
- if self.jsx_in_opening_tag { self.jsx_brace_depth += 1; }
309
- else if self.jsx_depth > 0 { self.jsx_child_brace_depth += 1; }
323
+ if self.jsx_in_opening_tag {
324
+ if let Some(top) = self.jsx_stack.last_mut() {
325
+ top.attr_value_braces += 1;
326
+ }
327
+ } else if self.jsx_depth > 0 {
328
+ self.jsx_child_brace_depth += 1;
329
+ }
310
330
  if let Some(depth) = self.template_brace_stack.last_mut() {
311
331
  *depth += 1;
312
332
  }
313
333
  TokenKind::LBrace
314
334
  }
315
335
  '}' => {
316
- if self.jsx_brace_depth > 0 { self.jsx_brace_depth -= 1; }
317
- else if self.jsx_child_brace_depth > 0 {
336
+ let mut handled = false;
337
+ if let Some(top) = self.jsx_stack.last() {
338
+ if top.in_opener && top.attr_value_braces > 0 {
339
+ if let Some(top) = self.jsx_stack.last_mut() {
340
+ top.attr_value_braces -= 1;
341
+ }
342
+ handled = true;
343
+ }
344
+ }
345
+ if !handled && self.jsx_child_brace_depth > 0 {
318
346
  self.jsx_child_brace_depth -= 1;
319
- if self.jsx_child_brace_depth == 0 { self.jsx_after_gt = true; }
347
+ if self.jsx_child_brace_depth == 0 {
348
+ self.jsx_after_gt = true;
349
+ }
320
350
  }
321
351
  if let Some(depth) = self.template_brace_stack.last_mut() {
322
352
  *depth -= 1;
@@ -358,6 +388,10 @@ impl<'a> Lexer<'a> {
358
388
  else if self.peek() == Some('/') { self.jsx_in_closing_tag = true; TokenKind::Lt }
359
389
  else if self.peek() == Some('>') || self.peek().map(|c| c.is_ascii_alphabetic() || c == '_').unwrap_or(false) {
360
390
  self.jsx_depth += 1;
391
+ self.jsx_stack.push(JsxEl {
392
+ in_opener: true,
393
+ attr_value_braces: 0,
394
+ });
361
395
  self.jsx_in_opening_tag = true;
362
396
  TokenKind::Lt
363
397
  } else { TokenKind::Lt }
@@ -366,13 +400,23 @@ impl<'a> Lexer<'a> {
366
400
  if self.peek() == Some('=') { self.advance(); TokenKind::Ge }
367
401
  else if self.peek() == Some('>') { self.advance(); TokenKind::Shr }
368
402
  else {
369
- if self.jsx_in_opening_tag && self.jsx_brace_depth == 0 && !self.jsx_saw_slash_before_gt {
370
- self.jsx_after_gt = true;
371
- }
372
- if self.jsx_in_closing_tag || (self.jsx_in_opening_tag && self.jsx_saw_slash_before_gt) {
403
+ if self.jsx_in_closing_tag {
404
+ self.jsx_depth = (self.jsx_depth - 1).max(0);
405
+ self.jsx_stack.pop();
406
+ self.jsx_sync_in_opening_tag();
407
+ } else if self.jsx_in_opening_tag && self.jsx_saw_slash_before_gt {
373
408
  self.jsx_depth = (self.jsx_depth - 1).max(0);
409
+ self.jsx_stack.pop();
410
+ self.jsx_sync_in_opening_tag();
411
+ } else if let Some(top) = self.jsx_stack.last_mut() {
412
+ if top.in_opener && top.attr_value_braces > 0 {
413
+ // `>` is a comparison (or shift) token inside `{ ... }`, not end of opening tag.
414
+ } else if top.in_opener && !self.jsx_saw_slash_before_gt {
415
+ top.in_opener = false;
416
+ self.jsx_after_gt = true;
417
+ self.jsx_sync_in_opening_tag();
418
+ }
374
419
  }
375
- self.jsx_in_opening_tag = false;
376
420
  self.jsx_in_closing_tag = false;
377
421
  self.jsx_saw_slash_before_gt = false;
378
422
  TokenKind::Gt
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tishlang/tish",
3
- "version": "1.0.27",
3
+ "version": "1.0.29",
4
4
  "description": "Tish - minimal TS/JS-compatible language. Run, REPL, compile to native.",
5
5
  "license": "SEE LICENSE IN LICENSE",
6
6
  "repository": {
Binary file
Binary file
Binary file
Binary file
Binary file
@@ -1,362 +0,0 @@
1
- // Lattish: Lattish runtime for compiled JSX. Publish to npm; consumed by tish and tish-playground.
2
- // With `tish compile --target js --jsx vdom`, the compiler injects a prelude that sets
3
- // window.__LATTISH_JSX_VDOM and __lattishVdomPatch; createRoot then reconciles vnodes.
4
-
5
- export let Fragment = Symbol("Lattish.Fragment")
6
-
7
- fn __lattishAppend(parent, c) {
8
- if (c === null || c === undefined) {
9
- return
10
- }
11
- if (Array.isArray(c)) {
12
- let i = 0
13
- while (i < c.length) {
14
- __lattishAppend(parent, c[i])
15
- i = i + 1
16
- }
17
- return
18
- }
19
- if (typeof c === "string") {
20
- parent.appendChild(document.createTextNode(c))
21
- } else {
22
- parent.appendChild(c)
23
- }
24
- }
25
-
26
- export fn h(tag, props, children) {
27
- if (children === undefined || children === null) {
28
- children = []
29
- }
30
- if (typeof tag === "function") {
31
- let p = props && props !== null ? props : {}
32
- if (children.length > 0) {
33
- let newProps = {}
34
- let keys = Object.keys(p)
35
- let i = 0
36
- while (i < keys.length) {
37
- newProps[keys[i]] = p[keys[i]]
38
- i = i + 1
39
- }
40
- newProps.children = children
41
- p = newProps
42
- }
43
- return tag(p)
44
- }
45
- if (tag === Fragment) {
46
- let f = document.createDocumentFragment()
47
- let i = 0
48
- while (i < children.length) {
49
- __lattishAppend(f, children[i])
50
- i = i + 1
51
- }
52
- return f
53
- }
54
- let el = document.createElement(tag)
55
- let refVal = null
56
- if (props && props !== null) {
57
- let keys = Object.keys(props)
58
- let ki = 0
59
- while (ki < keys.length) {
60
- let k = keys[ki]
61
- let v = props[k]
62
- if (k === "ref") {
63
- refVal = v
64
- } else {
65
- if (k === "disabled") {
66
- el.disabled = !!v
67
- } else {
68
- if (v === true) {
69
- el.setAttribute(k, k)
70
- } else {
71
- if (v !== false && v !== null && v !== undefined) {
72
- if (k === "class" || k === "className") {
73
- el.className = v
74
- } else {
75
- if (k.length >= 3 && k[0] === "o" && k[1] === "n" && typeof v === "function") {
76
- let eventType = k.slice(2).toLowerCase()
77
- el.addEventListener(eventType, v)
78
- } else {
79
- if (k === "value" && (tag === "input" || tag === "textarea")) {
80
- el.value = v
81
- } else {
82
- if (k !== "ref") {
83
- el.setAttribute(k, String(v))
84
- }
85
- }
86
- }
87
- }
88
- }
89
- }
90
- }
91
- }
92
- ki = ki + 1
93
- }
94
- }
95
- let ci = 0
96
- while (ci < children.length) {
97
- __lattishAppend(el, children[ci])
98
- ci = ci + 1
99
- }
100
- if (refVal !== null && refVal !== undefined && typeof refVal === "object") {
101
- refVal.current = el
102
- }
103
- return el
104
- }
105
-
106
- export fn text(s) {
107
- return s
108
- }
109
-
110
- let __state = []
111
- let __cursor = 0
112
- let __container = null
113
- let __App = null
114
- let __rootVnode = null
115
-
116
- let __memoSlots = []
117
- let __refSlots = []
118
- let __layoutEffectSlots = []
119
- let __effectSlots = []
120
-
121
- let __flushScheduled = false
122
- let __batchDepth = 0
123
- let __dirtyFromBatch = false
124
- let __effectsMicrotaskScheduled = false
125
-
126
- fn __depsChanged(prev, next) {
127
- if (prev === null) {
128
- return true
129
- }
130
- if (next === null || prev === null) {
131
- return true
132
- }
133
- if (prev.length !== next.length) {
134
- return true
135
- }
136
- let j = 0
137
- while (j < next.length) {
138
- if (prev[j] !== next[j]) {
139
- return true
140
- }
141
- j = j + 1
142
- }
143
- return false
144
- }
145
-
146
- fn __runLayoutEffects() {
147
- let i = 0
148
- while (i < __layoutEffectSlots.length) {
149
- let s = __layoutEffectSlots[i]
150
- if (s && s.pending) {
151
- s.pending = false
152
- if (s.cleanup) {
153
- s.cleanup()
154
- s.cleanup = null
155
- }
156
- let c = s.effect()
157
- if (typeof c === "function") {
158
- s.cleanup = c
159
- }
160
- }
161
- i = i + 1
162
- }
163
- }
164
-
165
- fn __scheduleEffectsFlush() {
166
- if (__effectsMicrotaskScheduled) {
167
- return
168
- }
169
- __effectsMicrotaskScheduled = true
170
- queueMicrotask(() => {
171
- __effectsMicrotaskScheduled = false
172
- let i = 0
173
- while (i < __effectSlots.length) {
174
- let s = __effectSlots[i]
175
- if (s && s.pending) {
176
- s.pending = false
177
- if (s.cleanup) {
178
- s.cleanup()
179
- s.cleanup = null
180
- }
181
- let c = s.effect()
182
- if (typeof c === "function") {
183
- s.cleanup = c
184
- }
185
- }
186
- i = i + 1
187
- }
188
- })
189
- }
190
-
191
- /// Runs synchronously after DOM is updated (before paint in typical browsers).
192
- export fn useLayoutEffect(effect, deps) {
193
- let i = __cursor
194
- __cursor = __cursor + 1
195
- while (i >= __layoutEffectSlots.length) {
196
- __layoutEffectSlots.push(null)
197
- }
198
- let slot = __layoutEffectSlots[i]
199
- if (slot === null) {
200
- slot = { deps: null, effect: null, cleanup: null, pending: false }
201
- __layoutEffectSlots[i] = slot
202
- }
203
- if (__depsChanged(slot.deps, deps)) {
204
- slot.deps = deps
205
- slot.effect = effect
206
- slot.pending = true
207
- }
208
- }
209
-
210
- /// Runs after paint (microtask after flush).
211
- export fn useEffect(effect, deps) {
212
- let i = __cursor
213
- __cursor = __cursor + 1
214
- while (i >= __effectSlots.length) {
215
- __effectSlots.push(null)
216
- }
217
- let slot = __effectSlots[i]
218
- if (slot === null) {
219
- slot = { deps: null, effect: null, cleanup: null, pending: false }
220
- __effectSlots[i] = slot
221
- }
222
- if (__depsChanged(slot.deps, deps)) {
223
- slot.deps = deps
224
- slot.effect = effect
225
- slot.pending = true
226
- __scheduleEffectsFlush()
227
- }
228
- }
229
-
230
- fn __usingVdom() {
231
- return typeof window !== "undefined" && window.__LATTISH_JSX_VDOM && window.__lattishVdomPatch
232
- }
233
-
234
- fn __flushRender() {
235
- __cursor = 0
236
- if (__container && __App) {
237
- if (__usingVdom()) {
238
- let newTree = __App()
239
- window.__lattishVdomPatch(__container, __rootVnode, newTree)
240
- __rootVnode = newTree
241
- } else {
242
- let el = __App()
243
- __container.replaceChildren(el)
244
- __rootVnode = null
245
- }
246
- __runLayoutEffects()
247
- __scheduleEffectsFlush()
248
- }
249
- }
250
-
251
- fn __scheduleFlush() {
252
- if (__batchDepth > 0) {
253
- __dirtyFromBatch = true
254
- return
255
- }
256
- if (__flushScheduled) {
257
- return
258
- }
259
- __flushScheduled = true
260
- queueMicrotask(() => {
261
- __flushScheduled = false
262
- __flushRender()
263
- })
264
- }
265
-
266
- export fn unstable_batchedUpdates(run) {
267
- __batchDepth = __batchDepth + 1
268
- run()
269
- __batchDepth = __batchDepth - 1
270
- if (__batchDepth === 0 && __dirtyFromBatch) {
271
- __dirtyFromBatch = false
272
- __flushScheduled = false
273
- __flushRender()
274
- }
275
- }
276
-
277
- export fn useState(init) {
278
- let i = __cursor
279
- __cursor = __cursor + 1
280
- if (i >= __state.length) {
281
- __state.push(init)
282
- }
283
- fn setState(v) {
284
- if (typeof v === "function") {
285
- __state[i] = v(__state[i])
286
- } else {
287
- __state[i] = v
288
- }
289
- __scheduleFlush()
290
- }
291
- return [__state[i], setState]
292
- }
293
-
294
- export fn useMemo(deps, factory) {
295
- let i = __cursor
296
- __cursor = __cursor + 1
297
- while (i >= __memoSlots.length) {
298
- __memoSlots.push(null)
299
- }
300
- let slot = __memoSlots[i]
301
- let stale = true
302
- if (slot) {
303
- stale = false
304
- if (slot.deps.length !== deps.length) {
305
- stale = true
306
- } else {
307
- let j = 0
308
- while (j < deps.length) {
309
- if (slot.deps[j] !== deps[j]) {
310
- stale = true
311
- break
312
- }
313
- j = j + 1
314
- }
315
- }
316
- }
317
- if (stale) {
318
- __memoSlots[i] = { deps: deps, value: factory() }
319
- }
320
- return __memoSlots[i].value
321
- }
322
-
323
- export fn useRef(initial) {
324
- let i = __cursor
325
- __cursor = __cursor + 1
326
- while (i >= __refSlots.length) {
327
- __refSlots.push(null)
328
- }
329
- if (__refSlots[i] === null) {
330
- __refSlots[i] = { current: initial }
331
- }
332
- return __refSlots[i]
333
- }
334
-
335
- export fn createRoot(container) {
336
- fn render(App) {
337
- __container = container
338
- __App = App
339
- __state = []
340
- __memoSlots = []
341
- __refSlots = []
342
- __layoutEffectSlots = []
343
- __effectSlots = []
344
- __cursor = 0
345
- __flushScheduled = false
346
- __batchDepth = 0
347
- __dirtyFromBatch = false
348
- __effectsMicrotaskScheduled = false
349
- __rootVnode = null
350
- if (__usingVdom()) {
351
- __rootVnode = App()
352
- window.__lattishVdomPatch(container, null, __rootVnode)
353
- } else {
354
- let el = App()
355
- container.replaceChildren(el)
356
- }
357
- __cursor = 0
358
- __runLayoutEffects()
359
- __scheduleEffectsFlush()
360
- }
361
- return { render: render }
362
- }