1use crate::{
26 flags::{HelperCollector, PatchFlag, RuntimeHelper, SlotFlag, StaticLevel},
27 parser::{SourceNode, TextNode},
28 util::{find_dir, get_core_component, VStr},
29 Name,
30};
31
32pub use crate::error::{CompilationError, ErrorHandler};
33pub use crate::parser::{AstNode, AstRoot, Directive, Element};
34use rustc_hash::{FxHashMap, FxHashSet};
35use smallvec::{smallvec, SmallVec};
36use std::hash::Hash;
37use std::{marker::PhantomData, ops::Deref, rc::Rc};
38
39#[cfg(feature = "serde")]
40use serde::Serialize;
41
42mod build_props;
43mod cache_dir;
44mod convert_element;
45mod convert_slot_outlet;
46mod v_bind;
47mod v_for;
48mod v_if;
49mod v_slot;
50
51use cache_dir::{pre_convert_memo, pre_convert_once};
52use v_for::pre_convert_for;
53use v_if::{pre_group_v_if, PreGroup};
54
55pub trait Converter<'a>: Sized {
60 type IR;
61 fn convert_ir(&self, ast: AstRoot<'a>) -> Self::IR;
62}
63
64#[cfg(feature = "serde")]
65pub trait ConvertInfo {
66 type TopType: Default + Serialize;
67 type TextType: AsMut<[Self::JsExpression]> + Serialize;
69 type IfBranchType: Serialize;
70 type CommentType: Serialize;
71 type JsExpression: Default + Serialize;
72 type StrType: Serialize + Eq + Hash;
73}
74#[cfg(not(feature = "serde"))]
75pub trait ConvertInfo {
76 type TopType: Default;
77 type TextType: AsMut<[Self::JsExpression]>;
79 type IfBranchType;
80 type CommentType;
81 type JsExpression: Default;
82 type StrType: Eq + Hash;
83}
84
85#[cfg_attr(feature = "serde", derive(Serialize))]
86pub enum IRNode<T: ConvertInfo> {
87 TextCall(TextIR<T>),
89 If(IfNodeIR<T>),
91 For(ForNodeIR<T>),
93 VNodeCall(VNodeIR<T>),
95 RenderSlotCall(RenderSlotIR<T>),
97 VSlotUse(VSlotIR<T>),
99 AlterableSlot(Slot<T>),
101 CommentCall(T::CommentType),
103}
104
105#[cfg_attr(feature = "serde", derive(Serialize))]
106pub struct TextIR<T: ConvertInfo> {
107 pub fast_path: bool, pub need_patch: bool, pub texts: T::TextType,
110}
111#[cfg_attr(feature = "serde", derive(Serialize))]
112pub struct IfNodeIR<T: ConvertInfo> {
113 pub branches: Vec<IfBranch<T>>,
114}
115#[cfg_attr(feature = "serde", derive(Serialize))]
116pub struct IfBranch<T: ConvertInfo> {
117 pub condition: Option<T::JsExpression>,
118 pub child: Box<IRNode<T>>,
119 pub info: T::IfBranchType,
120}
121#[cfg_attr(feature = "serde", derive(Serialize))]
122pub struct ForNodeIR<T: ConvertInfo> {
123 pub source: T::JsExpression,
124 pub parse_result: ForParseResult<T>,
125 pub child: Box<IRNode<T>>,
126 pub is_stable: bool,
127 pub fragment_flag: PatchFlag,
128 pub key: Option<T::JsExpression>,
129}
130#[cfg_attr(feature = "serde", derive(Serialize))]
133pub struct ForParseResult<T: ConvertInfo> {
134 pub value: T::JsExpression,
135 pub key: Option<T::JsExpression>,
136 pub index: Option<T::JsExpression>,
137}
138#[cfg_attr(feature = "serde", derive(Serialize))]
139pub struct RenderSlotIR<T: ConvertInfo> {
140 pub slot_obj: T::JsExpression,
141 pub slot_name: T::JsExpression,
142 pub slot_props: Option<T::JsExpression>,
143 pub fallbacks: Vec<IRNode<T>>,
144 pub no_slotted: bool,
145}
146#[cfg_attr(feature = "serde", derive(Serialize))]
147pub struct RuntimeDir<T: ConvertInfo> {
148 pub name: T::JsExpression,
149 pub expr: Option<T::JsExpression>,
150 pub arg: Option<T::JsExpression>,
151 pub mods: Option<T::JsExpression>,
152}
153#[derive(Default)]
154#[cfg_attr(feature = "serde", derive(Serialize))]
155pub struct VNodeIR<T: ConvertInfo> {
156 pub tag: T::JsExpression,
157 pub props: Option<T::JsExpression>,
158 pub children: Vec<IRNode<T>>,
159 pub patch_flag: PatchFlag,
160 pub dynamic_props: FxHashSet<T::StrType>,
161 pub directives: Vec<RuntimeDir<T>>,
162 pub is_block: bool,
163 pub disable_tracking: bool,
164 pub is_component: bool,
165}
166#[cfg_attr(feature = "serde", derive(Serialize))]
167pub struct Slot<T: ConvertInfo> {
168 pub name: T::JsExpression,
169 pub param: Option<T::JsExpression>,
170 pub body: Vec<IRNode<T>>,
171}
172#[cfg_attr(feature = "serde", derive(Serialize))]
176pub struct VSlotIR<T: ConvertInfo> {
177 pub stable_slots: Vec<Slot<T>>,
179 pub alterable_slots: Vec<IRNode<T>>,
181 pub slot_flag: SlotFlag,
182}
183
184pub type Prop<'a> = (JsExpr<'a>, JsExpr<'a>);
185#[derive(Clone)]
186#[cfg_attr(feature = "serde", derive(Serialize))]
187pub enum JsExpr<'a> {
188 Src(&'a str),
190 Num(usize),
192 StrLit(VStr<'a>),
195 Simple(VStr<'a>, StaticLevel),
197 Param(Name<'a>),
199 Compound(Vec<JsExpr<'a>>),
201 Props(Vec<Prop<'a>>),
202 Call(RuntimeHelper, Vec<JsExpr<'a>>),
204 Symbol(RuntimeHelper),
206 Array(Vec<JsExpr<'a>>),
208}
209
210impl<'a> Default for JsExpr<'a> {
211 fn default() -> Self {
212 Self::Src("")
213 }
214}
215
216impl<'a> JsExpr<'a> {
217 pub fn simple<V: Into<VStr<'a>>>(v: V) -> Self {
219 JsExpr::Simple(v.into(), StaticLevel::NotStatic)
220 }
221 pub fn str_lit<V: Into<VStr<'a>>>(v: V) -> Self {
222 JsExpr::StrLit(v.into())
223 }
224 pub fn static_level(&self) -> StaticLevel {
225 use JsExpr::*;
226 use StaticLevel as S;
227 match self {
228 Src(_) | StrLit(_) => S::CanStringify,
229 Simple(_, level) => *level,
230 Compound(v) | Array(v) | Call(_, v) => v
231 .iter()
232 .map(Self::static_level)
233 .min()
234 .unwrap_or(S::CanHoist),
235 _ => S::NotStatic,
236 }
237 }
238}
239
240#[derive(PartialEq, Eq)]
241pub enum BindingTypes {
242 Data,
244 Props,
246 SetupLet,
248 SetupConst,
252 SetupMaybeRef,
254 SetupRef,
256 Options,
258}
259
260impl BindingTypes {
261 pub fn get_js_prop<'a>(&self, name: VStr<'a>, lvl: StaticLevel) -> JsExpr<'a> {
262 use BindingTypes::*;
263 let obj_dot = JsExpr::Src(match self {
264 Data => "$data.",
265 Props => "$props.",
266 Options => "$options.",
267 _ => "$setup.",
268 });
269 let prop = JsExpr::Simple(name, lvl);
270 JsExpr::Compound(vec![obj_dot, prop])
271 }
272}
273
274#[cfg_attr(feature = "serde", derive(Serialize))]
275pub struct IRRoot<T: ConvertInfo> {
276 pub body: Vec<IRNode<T>>,
277 pub top_scope: T::TopType,
279}
280
281pub trait CoreConverter<'a, T: ConvertInfo> {
284 fn convert_core_ir(&self, ast: AstRoot<'a>) -> IRRoot<T> {
285 let body = self.convert_children(ast.children);
286 IRRoot {
287 body,
288 top_scope: T::TopType::default(),
289 }
290 }
291 fn convert_children(&self, children: Vec<AstNode<'a>>) -> Vec<IRNode<T>> {
292 let mut key = 0;
293 pre_group_v_if(children)
295 .map(|pre| match pre {
296 PreGroup::VIfGroup(to_convert) => {
297 let len = to_convert.len();
298 let converted = self.convert_if(to_convert, key);
299 key += len;
300 converted
301 }
302 PreGroup::StandAlone(n) => self.dispatch_ast(n),
303 })
304 .collect()
305 }
306
307 fn dispatch_ast(&self, n: AstNode<'a>) -> IRNode<T> {
308 match n {
309 AstNode::Text(t) => self.convert_text(t),
310 AstNode::Interpolation(i) => self.convert_interpolation(i),
311 AstNode::Comment(c) => self.convert_comment(c),
312 AstNode::Element(e) => self.pre_convert_element(e),
314 }
315 }
316 fn pre_convert_element(&self, mut e: Element<'a>) -> IRNode<T> {
317 let once = pre_convert_once(&mut e);
319 let memo = pre_convert_memo(&mut e);
320 let vfor = pre_convert_for(&mut e);
321 let mut n = self.dispatch_element(e);
322 if let Some(d) = vfor {
323 n = self.convert_for(d, n);
324 }
325 if let Some(d) = memo {
326 n = self.convert_memo(d, n);
327 }
328 if let Some(d) = once {
329 n = self.convert_once(d, n);
330 }
331 n
333 }
334 fn dispatch_element(&self, e: Element<'a>) -> IRNode<T> {
335 use super::parser::ElementType::{SlotOutlet, Template};
336 match e.tag_type {
337 Template => self.convert_template(e),
338 SlotOutlet => self.convert_slot_outlet(e),
339 _ => self.convert_element(e),
340 }
341 }
342
343 fn emit_error(&self, error: CompilationError);
345 fn get_builtin_component(&self, tag: &str) -> Option<RuntimeHelper>;
347
348 fn convert_directive(
350 &self,
351 dir: &mut Directive<'a>,
352 e: &mut Element<'a>,
353 ) -> DirectiveConvertResult<T::JsExpression>;
354 fn convert_if(&self, elems: Vec<Element<'a>>, key: usize) -> IRNode<T>;
355 fn convert_for(&self, d: Directive<'a>, n: IRNode<T>) -> IRNode<T>;
356 fn convert_memo(&self, d: Directive<'a>, n: IRNode<T>) -> IRNode<T>;
357 fn convert_once(&self, d: Directive<'a>, n: IRNode<T>) -> IRNode<T>;
358 fn convert_slot_outlet(&self, e: Element<'a>) -> IRNode<T>;
359 fn convert_element(&self, e: Element<'a>) -> IRNode<T>;
360 fn convert_text(&self, t: TextNode<'a>) -> IRNode<T>;
361 fn convert_interpolation(&self, i: SourceNode<'a>) -> IRNode<T>;
362 fn convert_template(&self, e: Element<'a>) -> IRNode<T>;
363 fn convert_comment(&self, c: SourceNode<'a>) -> IRNode<T>;
364}
365
366pub enum DirectiveConvertResult<Expr> {
371 Converted {
372 value: Expr,
373 runtime: Result<RuntimeHelper, bool>,
376 },
377 Preserve,
378 Dropped,
379}
380
381pub fn no_op_directive_convert<'a>(
382 _: &mut Directive<'a>,
383 _: &Element<'a>,
384 _: &dyn ErrorHandler,
385) -> DirectiveConvertResult<JsExpr<'a>> {
386 DirectiveConvertResult::Dropped
387}
388
389#[derive(Default)]
391#[cfg_attr(feature = "serde", derive(Serialize))]
392pub struct BaseConvertInfo<'a>(PhantomData<&'a ()>);
393
394#[derive(Default)]
395#[cfg_attr(feature = "serde", derive(Serialize))]
396pub struct TopScope<'a> {
397 pub helpers: HelperCollector,
399 pub components: FxHashSet<VStr<'a>>,
401 pub directives: FxHashSet<VStr<'a>>,
403 pub hoists: Vec<BaseIR<'a>>,
405 pub cached: usize,
407 pub temps: usize,
409}
410
411impl<'a> ConvertInfo for BaseConvertInfo<'a> {
412 type TopType = TopScope<'a>;
413 type TextType = SmallVec<[JsExpr<'a>; 1]>;
414 type IfBranchType = usize;
415 type CommentType = &'a str;
416 type JsExpression = JsExpr<'a>;
417 type StrType = VStr<'a>;
418}
419
420pub type CoreDirConvRet<'a> = DirectiveConvertResult<JsExpr<'a>>;
421pub type DirConvertFn =
426 for<'a> fn(&mut Directive<'a>, &Element<'a>, &dyn ErrorHandler) -> CoreDirConvRet<'a>;
427pub type DirectiveConverter = (&'static str, DirConvertFn);
428
429#[derive(Default)]
432pub struct BindingMetadata<'a>(FxHashMap<&'a str, BindingTypes>, bool);
433impl<'a> BindingMetadata<'a> {
434 pub fn is_setup(&self) -> bool {
435 self.1
436 }
437}
438impl<'a> Deref for BindingMetadata<'a> {
439 type Target = FxHashMap<&'a str, BindingTypes>;
440 fn deref(&self) -> &Self::Target {
441 &self.0
442 }
443}
444
445#[derive(Clone)]
446pub struct ConvertOption {
447 pub get_builtin_component: fn(&str) -> Option<RuntimeHelper>,
450 pub is_dev: bool,
451 pub directive_converters: FxHashMap<&'static str, DirConvertFn>,
452}
453
454impl Default for ConvertOption {
455 fn default() -> Self {
456 Self {
457 get_builtin_component: get_core_component,
458 is_dev: true,
459 directive_converters: FxHashMap::default(),
460 }
461 }
462}
463
464pub struct SFCInfo<'a> {
466 pub inline: bool,
469 pub slotted: bool,
473 pub scope_id: Option<String>,
474 pub binding_metadata: Rc<BindingMetadata<'a>>,
477 pub self_name: String,
479}
480
481impl<'a> Default for SFCInfo<'a> {
482 fn default() -> Self {
483 Self {
484 scope_id: None,
485 inline: false,
486 slotted: true,
487 binding_metadata: Rc::new(BindingMetadata::default()),
488 self_name: "".into(),
489 }
490 }
491}
492
493pub struct BaseConverter<'a> {
494 pub err_handle: Box<dyn ErrorHandler>,
495 pub sfc_info: SFCInfo<'a>,
496 pub option: ConvertOption,
497}
498pub type BaseRoot<'a> = IRRoot<BaseConvertInfo<'a>>;
499pub type BaseIR<'a> = IRNode<BaseConvertInfo<'a>>;
500impl<'a> Converter<'a> for BaseConverter<'a> {
501 type IR = BaseRoot<'a>;
502 fn convert_ir(&self, ast: AstRoot<'a>) -> Self::IR {
503 self.convert_core_ir(ast)
504 }
505}
506impl<'a> CoreConverter<'a, BaseConvertInfo<'a>> for BaseConverter<'a> {
507 fn emit_error(&self, error: CompilationError) {
508 self.err_handle.on_error(error)
509 }
510
511 fn get_builtin_component(&self, tag: &str) -> Option<RuntimeHelper> {
513 (self.option.get_builtin_component)(tag)
514 }
515
516 fn convert_directive(
518 &self,
519 dir: &mut Directive<'a>,
520 e: &mut Element<'a>,
521 ) -> CoreDirConvRet<'a> {
522 if let Some(convert) = self.option.directive_converters.get(dir.name) {
523 convert(dir, e, self.err_handle.as_ref())
524 } else {
525 DirectiveConvertResult::Preserve
526 }
527 }
528 fn convert_if(&self, elems: Vec<Element<'a>>, key: usize) -> BaseIR<'a> {
529 v_if::convert_if(self, elems, key)
530 }
531 fn convert_for(&self, d: Directive<'a>, e: BaseIR<'a>) -> BaseIR<'a> {
532 v_for::convert_for(self, d, e)
533 }
534 fn convert_memo(&self, _: Directive<'a>, n: BaseIR<'a>) -> BaseIR<'a> {
536 n
537 }
538 fn convert_once(&self, _: Directive<'a>, n: BaseIR<'a>) -> BaseIR<'a> {
539 n
540 }
541 fn convert_slot_outlet(&self, e: Element<'a>) -> BaseIR<'a> {
542 convert_slot_outlet::convert_slot_outlet(self, e)
543 }
544 fn convert_element(&self, e: Element<'a>) -> BaseIR<'a> {
545 convert_element::convert_element(self, e)
546 }
547 fn convert_text(&self, text: TextNode<'a>) -> BaseIR<'a> {
548 let texts = text.text.into_iter().map(JsExpr::StrLit).collect();
550 IRNode::TextCall(TextIR {
551 fast_path: false,
552 need_patch: false,
553 texts,
554 })
555 }
556 fn convert_interpolation(&self, interp: SourceNode<'a>) -> BaseIR<'a> {
557 let expr = JsExpr::simple(interp.source);
558 let call = JsExpr::Call(RuntimeHelper::ToDisplayString, vec![expr]);
559 IRNode::TextCall(TextIR {
560 fast_path: false,
561 need_patch: false,
562 texts: smallvec![call],
563 })
564 }
565 fn convert_template(&self, e: Element<'a>) -> BaseIR<'a> {
566 convert_element::convert_template(self, e, false)
567 }
568 fn convert_comment(&self, c: SourceNode<'a>) -> BaseIR<'a> {
569 IRNode::CommentCall(c.source)
570 }
571}
572
573impl<'a> BaseConverter<'a> {
574 fn no_slotted(&self) -> bool {
575 self.sfc_info.scope_id.is_some() && !self.sfc_info.slotted
576 }
577}
578
579#[cfg(test)]
580pub mod test {
581 use super::*;
582 use crate::{cast, error::test::TestErrorHandler, parser::test::base_parse};
583 use BaseConverter as BC;
584 use JsExpr as Js;
585
586 pub fn assert_str_lit(expr: &Js, s: &str) {
587 let v = cast!(expr, Js::StrLit);
588 assert_eq!(v.raw, s);
589 }
590
591 #[test]
592 fn test_simplest() {
593 let body = base_convert("<p/>").body;
594 assert_eq!(body.len(), 1);
595 if let IRNode::VNodeCall(VNodeIR { tag, .. }) = &body[0] {
596 assert_str_lit(tag, "p");
597 } else {
598 panic!("wrong parsing");
599 }
600 let body = base_convert("hello world").body;
601 let t = cast!(&body[0], IRNode::TextCall);
602 assert_str_lit(&t.texts[0], "hello world");
603 }
604
605 #[test]
606 fn test_abort() {
607 base_convert("hello <p/> {{world}}");
608 }
609
610 pub fn base_convert(s: &str) -> BaseRoot {
611 let mut convs = FxHashMap::default();
612 for (n, f) in [v_bind::V_BIND, ("on", no_op_directive_convert)] {
613 convs.insert(n, f);
614 }
615 let option = ConvertOption {
616 directive_converters: convs,
617 ..Default::default()
618 };
619 let bc = BC {
620 err_handle: Box::new(TestErrorHandler),
621 sfc_info: Default::default(),
622 option,
623 };
624 let ast = base_parse(s);
625 bc.convert_ir(ast)
626 }
627}