@tishlang/tish 1.0.28 → 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.
- package/Cargo.toml +1 -0
- package/crates/js_to_tish/src/transform/expr.rs +15 -6
- package/crates/tish/Cargo.toml +1 -1
- package/crates/tish/src/main.rs +8 -55
- package/crates/tish/tests/integration_test.rs +4 -3
- package/crates/tish_ast/src/ast.rs +65 -2
- package/crates/tish_build_utils/src/lib.rs +10 -2
- package/crates/tish_builtins/src/construct.rs +177 -0
- package/crates/tish_builtins/src/globals.rs +3 -5
- package/crates/tish_builtins/src/helpers.rs +2 -3
- package/crates/tish_builtins/src/lib.rs +1 -0
- package/crates/tish_builtins/src/object.rs +3 -4
- package/crates/tish_bytecode/src/compiler.rs +85 -11
- package/crates/tish_bytecode/src/opcode.rs +7 -3
- package/crates/tish_compile/Cargo.toml +1 -0
- package/crates/tish_compile/src/codegen.rs +233 -71
- package/crates/tish_compile/src/lib.rs +35 -0
- package/crates/tish_compile_js/Cargo.toml +1 -1
- package/crates/tish_compile_js/src/codegen.rs +43 -147
- package/crates/tish_compile_js/src/lib.rs +4 -7
- package/crates/tish_compile_js/src/tests_jsx.rs +89 -19
- package/crates/tish_compiler_wasm/src/lib.rs +2 -3
- package/crates/tish_core/Cargo.toml +4 -0
- package/crates/tish_core/src/console_style.rs +7 -1
- package/crates/tish_core/src/json.rs +1 -2
- package/crates/tish_core/src/macros.rs +2 -3
- package/crates/tish_core/src/value.rs +10 -5
- package/crates/tish_eval/Cargo.toml +2 -0
- package/crates/tish_eval/src/eval.rs +149 -72
- package/crates/tish_eval/src/http.rs +3 -4
- package/crates/tish_eval/src/regex.rs +3 -2
- package/crates/tish_eval/src/value.rs +11 -13
- package/crates/tish_eval/src/value_convert.rs +4 -8
- package/crates/tish_fmt/src/lib.rs +49 -10
- package/crates/tish_jsx_web/Cargo.toml +1 -1
- package/crates/tish_jsx_web/README.md +3 -16
- package/crates/tish_jsx_web/src/lib.rs +2 -157
- package/crates/tish_lexer/src/token.rs +2 -0
- package/crates/tish_lint/src/lib.rs +9 -0
- package/crates/tish_lsp/README.md +1 -1
- package/crates/tish_native/src/build.rs +16 -2
- package/crates/tish_opt/src/lib.rs +15 -0
- package/crates/tish_parser/src/lib.rs +101 -1
- package/crates/tish_parser/src/parser.rs +161 -50
- package/crates/tish_runtime/src/http.rs +4 -5
- package/crates/tish_runtime/src/http_fetch.rs +9 -10
- package/crates/tish_runtime/src/lib.rs +9 -2
- package/crates/tish_runtime/src/promise.rs +2 -3
- package/crates/tish_runtime/src/promise_io.rs +2 -3
- package/crates/tish_runtime/src/ws.rs +7 -7
- package/crates/tish_ui/Cargo.toml +17 -0
- package/crates/tish_ui/src/jsx.rs +390 -0
- package/crates/tish_ui/src/lib.rs +16 -0
- package/crates/tish_ui/src/runtime/hooks.rs +122 -0
- package/crates/tish_ui/src/runtime/mod.rs +173 -0
- package/crates/tish_vm/src/vm.rs +121 -27
- package/justfile +3 -3
- 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
- package/crates/tish_compile_js/src/js_intrinsics.rs +0 -82
- package/crates/tish_jsx_web/vendor/Lattish.tish +0 -362
|
@@ -6,13 +6,15 @@
|
|
|
6
6
|
//! different shape (no AST-carrying variants). The split is intentional.
|
|
7
7
|
|
|
8
8
|
use std::cell::RefCell;
|
|
9
|
-
use std::collections::HashMap;
|
|
10
9
|
use std::rc::Rc;
|
|
11
10
|
use std::sync::Arc;
|
|
12
11
|
|
|
13
|
-
use
|
|
14
|
-
|
|
12
|
+
use ahash::AHashMap;
|
|
13
|
+
use tishlang_ast::{FunParam, Statement};
|
|
15
14
|
use tishlang_core::NativeFn as CoreNativeFn;
|
|
15
|
+
|
|
16
|
+
/// Property map for interpreter `Value::Object` (uses `eval::Value`, not `tishlang_core::Value`).
|
|
17
|
+
pub type PropMap = AHashMap<Arc<str>, Value>;
|
|
16
18
|
#[cfg(feature = "http")]
|
|
17
19
|
use tishlang_core::TishPromise;
|
|
18
20
|
use tishlang_core::TishOpaque;
|
|
@@ -32,11 +34,10 @@ pub enum Value {
|
|
|
32
34
|
Bool(bool),
|
|
33
35
|
Null,
|
|
34
36
|
Array(Rc<RefCell<Vec<Value>>>),
|
|
35
|
-
Object(Rc<RefCell<
|
|
37
|
+
Object(Rc<RefCell<PropMap>>),
|
|
36
38
|
/// User-defined function with AST body
|
|
37
39
|
Function {
|
|
38
|
-
|
|
39
|
-
defaults: Arc<[Option<Expr>]>,
|
|
40
|
+
formals: Arc<[FunParam]>,
|
|
40
41
|
rest_param: Option<Arc<str>>,
|
|
41
42
|
body: Arc<Statement>,
|
|
42
43
|
},
|
|
@@ -65,8 +66,7 @@ pub enum Value {
|
|
|
65
66
|
/// Native `tishlang_core` Promise (fetch / reader.read / response.text).
|
|
66
67
|
#[cfg(feature = "http")]
|
|
67
68
|
CorePromise(Arc<dyn TishPromise>),
|
|
68
|
-
/// `tishlang_core::Value::Function` (
|
|
69
|
-
#[cfg(any(feature = "http", feature = "ws"))]
|
|
69
|
+
/// `tishlang_core::Value::Function` (native callbacks, `new` constructors, fetch/ws when enabled).
|
|
70
70
|
CoreFn(CoreNativeFn),
|
|
71
71
|
/// Opaque handle to a native Rust type (e.g. Polars DataFrame).
|
|
72
72
|
Opaque(Arc<dyn TishOpaque>),
|
|
@@ -99,7 +99,6 @@ impl std::fmt::Debug for Value {
|
|
|
99
99
|
Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
|
|
100
100
|
#[cfg(feature = "http")]
|
|
101
101
|
Value::CorePromise(_) => write!(f, "Promise"),
|
|
102
|
-
#[cfg(any(feature = "http", feature = "ws"))]
|
|
103
102
|
Value::CoreFn(_) => write!(f, "CoreFn"),
|
|
104
103
|
Value::Opaque(o) => write!(f, "{}(opaque)", o.type_name()),
|
|
105
104
|
Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
|
|
@@ -155,7 +154,6 @@ impl std::fmt::Display for Value {
|
|
|
155
154
|
Value::BoundPromiseMethod(_, _) | Value::TimerBuiltin(_) => write!(f, "[Function]"),
|
|
156
155
|
#[cfg(feature = "http")]
|
|
157
156
|
Value::CorePromise(_) => write!(f, "[Promise]"),
|
|
158
|
-
#[cfg(any(feature = "http", feature = "ws"))]
|
|
159
157
|
Value::CoreFn(_) => write!(f, "[Function]"),
|
|
160
158
|
Value::Opaque(o) => write!(f, "[object {}]", o.type_name()),
|
|
161
159
|
Value::OpaqueMethod(_, _) => write!(f, "[Function]"),
|
|
@@ -199,8 +197,8 @@ impl Value {
|
|
|
199
197
|
Value::Array(Rc::new(RefCell::new(items)))
|
|
200
198
|
}
|
|
201
199
|
|
|
202
|
-
/// Create a new object Value from a
|
|
203
|
-
pub fn object(map:
|
|
200
|
+
/// Create a new object Value from a property map.
|
|
201
|
+
pub fn object(map: PropMap) -> Self {
|
|
204
202
|
Value::Object(Rc::new(RefCell::new(map)))
|
|
205
203
|
}
|
|
206
204
|
|
|
@@ -211,7 +209,7 @@ impl Value {
|
|
|
211
209
|
|
|
212
210
|
/// Create an empty object Value.
|
|
213
211
|
pub fn empty_object() -> Self {
|
|
214
|
-
Value::Object(Rc::new(RefCell::new(
|
|
212
|
+
Value::Object(Rc::new(RefCell::new(PropMap::default())))
|
|
215
213
|
}
|
|
216
214
|
|
|
217
215
|
/// Extract the number value, if this is a Number.
|
|
@@ -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 =
|
|
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 =
|
|
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: &[
|
|
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
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
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,
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
name = "tishlang_jsx_web"
|
|
3
3
|
version = "0.1.0"
|
|
4
4
|
edition = "2021"
|
|
5
|
-
description = "
|
|
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
|
-
#
|
|
1
|
+
# tishlang_jsx_web
|
|
2
2
|
|
|
3
|
-
|
|
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
|
-
|
|
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
|
-
//!
|
|
2
|
-
//!
|
|
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).
|
|
@@ -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
|
}
|
|
@@ -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
|
}
|