1use super::render::LowerTrail;
2use super::*;
3use crate::ast::component::{ComponentDef, ComponentKind, RouteBody};
4use crate::ast::stmt::{BinaryOpKind, Expr, Stmt, TypeExpr};
5use crate::ast::view::ViewNode;
6use logicaffeine_base::Symbol;
7
8pub const ROUTE_HANDLER: &str = "__lgx_route_handler";
12
13pub const ROUTE_BODY_WALK: &str = "__lgx_route_for";
17
18pub const ROUTE_STATUS_WALK: &str = "__lgx_route_status_for";
19
20pub const ROUTE_CTYPE_WALK: &str = "__lgx_route_ctype_for";
21
22pub const ROUTE_HEADERS_WALK: &str = "__lgx_route_headers_for";
31
32pub const ROUTE_RESPONSE_WALK: &str = "__lgx_route_response_for";
46
47pub const SESSION_VAR: &str = "session";
50
51pub const SESSION_SECRET_VAR: &str = "__lgx_secret";
52
53pub const SESSION_LOADED_VAR: &str = "__lgx_session_loaded";
54
55pub const SESSION_PAYLOAD_FN: &str = "__lgx_session_payload";
56
57pub const SESSION_LOAD_FN: &str = "__lgx_session_load";
58
59pub const SESSION_SAVE_FN: &str = "__lgx_session_save";
60
61pub const SESSION_COOKIE: &str = "lgxsession";
63
64pub const SESSION_NONCE_FIELD: &str = "__csrfnonce";
73
74pub const CSRF_FIELD: &str = "_csrf";
77
78pub const CSRF_HEADER: &str = "x-csrf-token";
79
80pub const CSRF_FN: &str = "csrf";
82
83pub const CSRF_TOKEN_PATH: &str = "/__logos/csrf";
87
88pub fn declares_session(stmts: &[Stmt<'_>], interner: &logicaffeine_base::Interner) -> bool {
93 stmts.iter().any(|s| {
94 matches!(s, Stmt::FunctionDef { name, .. } if interner.resolve(*name) == SESSION_LOAD_FN)
95 })
96}
97
98#[derive(Clone, Copy, PartialEq, Eq, Debug)]
116pub enum SessionFieldKind {
117 Text,
118 Int,
119 Bool,
120}
121
122#[derive(Clone, Copy, PartialEq, Eq, Debug)]
130pub enum ParamFrom {
131 Path,
133 Extra,
135 Request,
137}
138
139impl<'a, 'ctx, 'int> super::super::Parser<'a, 'ctx, 'int> {
140 pub(crate) fn lower_session(&mut self) -> crate::parser::ParseResult<()> {
173 use crate::analysis::registry::{FieldType, TypeDef};
174 if !self.session_declared {
175 return Ok(());
176 }
177 let session_ty = self.interner.intern("Session");
178 let declared: Vec<(Symbol, FieldType)> = match self.type_registry.as_ref().and_then(|r| {
179 r.iter_types()
180 .find(|(n, _)| **n == session_ty)
181 .map(|(_, d)| d.clone())
182 }) {
183 Some(TypeDef::Struct {
184 fields, generics, ..
185 }) if generics.is_empty() => fields.iter().map(|f| (f.name, f.ty.clone())).collect(),
186 _ => return Ok(()),
189 };
190 let mut fields: Vec<(Symbol, SessionFieldKind)> = Vec::new();
191 for (name, ty) in &declared {
192 let named = match ty {
193 FieldType::Primitive(s) | FieldType::Named(s) => self.interner.resolve(*s),
194 _ => "",
195 };
196 let kind = match named.to_ascii_lowercase().as_str() {
197 "text" => SessionFieldKind::Text,
198 "int" | "nat" => SessionFieldKind::Int,
199 "bool" => SessionFieldKind::Bool,
200 _ => {
201 let field = self.interner.resolve(*name).to_string();
202 let span = self
203 .type_registry
204 .as_ref()
205 .and_then(|r| r.field_span(session_ty, *name))
206 .unwrap_or_default();
207 return Err(crate::error::ParseError {
208 kind: crate::error::ParseErrorKind::Custom(format!(
209 "`## Session` carries `{field}`, which a cookie cannot hold. A session \
210 travels in a few hundred bytes the visitor keeps and the browser \
211 sends with every single request, so it holds Text, Int and Bool and \
212 nothing larger. What does the NEXT request actually need to know? \
213 Usually that is an id — keep the id in the session and look the rest \
214 up from it."
215 )),
216 span,
217 });
218 }
219 };
220 fields.push((*name, kind));
221 }
222 if fields.is_empty() {
223 return Ok(());
224 }
225
226 let text_ty: &'a TypeExpr<'a> = self
227 .ctx
228 .alloc_type_expr(TypeExpr::Primitive(self.interner.intern("Text")));
229 let session_named: &'a TypeExpr<'a> = self.ctx.alloc_type_expr(TypeExpr::Named(session_ty));
230 let response_named: &'a TypeExpr<'a> = self
231 .ctx
232 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Response")));
233 let request_named: &'a TypeExpr<'a> = self
234 .ctx
235 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Request")));
236 let blank = self.interner.intern("");
237 let session_var = self.interner.intern(SESSION_VAR);
238 let secret_var = self.interner.intern(SESSION_SECRET_VAR);
239 let loaded_var = self.interner.intern(SESSION_LOADED_VAR);
240
241 let secret_call = {
244 let f = self.interner.intern("sessionSecret");
245 self.te_call(f, Vec::new())
246 };
247 self.deferred_functions.push(Stmt::Let {
248 var: secret_var,
249 ty: Some(text_ty),
250 value: secret_call,
251 mutable: true,
252 });
253 let fresh = self.fresh_session(session_ty, &fields);
254 self.deferred_functions.push(Stmt::Let {
255 var: session_var,
256 ty: Some(session_named),
257 value: fresh,
258 mutable: true,
259 });
260 self.deferred_functions.push(Stmt::Let {
261 var: loaded_var,
262 ty: Some(text_ty),
263 value: self.te_text(blank),
264 mutable: true,
265 });
266
267 let dot = self.interner.intern(".");
269 let hex = self.interner.intern("hexText");
270 let mut payload: Option<&'a Expr<'a>> = None;
271 for (name, _) in &fields {
272 let read = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
273 object: self.te_id(session_var),
274 field: *name,
275 });
276 let shown = self.te_bin(BinaryOpKind::Add, self.te_text(blank), read);
278 let piece = self.te_call(hex, vec![shown]);
279 payload = Some(match payload {
280 None => piece,
281 Some(acc) => {
282 let with_dot = self.te_bin(BinaryOpKind::Add, acc, self.te_text(dot));
283 self.te_bin(BinaryOpKind::Add, with_dot, piece)
284 }
285 });
286 }
287 let payload = payload.expect("at least one field");
288 let payload_fn = self.interner.intern(SESSION_PAYLOAD_FN);
289 self.deferred_functions.push(Stmt::FunctionDef {
290 name: payload_fn,
291 generics: Vec::new(),
292 params: Vec::new(),
293 body: self.alloc_block(vec![Stmt::Return {
294 value: Some(payload),
295 }]),
296 return_type: Some(text_ty),
297 is_native: false,
298 native_path: None,
299 is_exported: false,
300 export_target: None,
301 opt_flags: crate::optimization::OptimizationConfig::all_on(),
302 });
303
304 let req_p = self.interner.intern("__lgx_sreq");
308 let mut load: Vec<Stmt<'a>> = Vec::new();
309 let fresh = self.fresh_session(session_ty, &fields);
310 load.push(Stmt::Set {
311 target: session_var,
312 value: fresh,
313 });
314 let cookie_name = self.interner.intern(SESSION_COOKIE);
315 let cookie_of = self.interner.intern("cookieOf");
316 let raw = self.te_call(
317 cookie_of,
318 vec![self.te_id(req_p), self.te_text(cookie_name)],
319 );
320 let verify = self.interner.intern("verifySession");
321 let carried = self.interner.intern("__lgx_scarried");
322 let mut filled: Vec<Stmt<'a>> = Vec::new();
323 for (i, (name, kind)) in fields.iter().enumerate() {
324 let reader = match kind {
325 SessionFieldKind::Text => "sessionField",
326 SessionFieldKind::Int => "sessionInt",
327 SessionFieldKind::Bool => "sessionBool",
328 };
329 let f = self.interner.intern(reader);
330 let idx = self.te_int(i as i64 + 1);
332 let value = self.te_call(f, vec![self.te_id(carried), idx]);
333 filled.push(Stmt::SetField {
334 object: self.te_id(session_var),
335 field: *name,
336 value,
337 });
338 }
339 let some_arm = crate::ast::stmt::MatchArm {
340 enum_name: None,
341 variant: Some(self.interner.intern("Some")),
342 bindings: vec![(carried, carried)],
343 body: self.alloc_block(filled),
344 };
345 let none_arm = crate::ast::stmt::MatchArm {
346 enum_name: None,
347 variant: Some(self.interner.intern("None")),
348 bindings: Vec::new(),
349 body: self.alloc_block(Vec::new()),
350 };
351 load.push(Stmt::Inspect {
352 target: self.te_call(verify, vec![self.te_id(secret_var), raw]),
353 arms: vec![some_arm, none_arm],
354 has_otherwise: false,
355 });
356 let now = self.te_call(payload_fn, Vec::new());
362 load.push(Stmt::Set {
363 target: loaded_var,
364 value: now,
365 });
366 let load_fn = self.interner.intern(SESSION_LOAD_FN);
367 self.deferred_functions.push(Stmt::FunctionDef {
368 name: load_fn,
369 generics: Vec::new(),
370 params: vec![(req_p, request_named)],
371 body: self.alloc_block(load),
372 return_type: None,
373 is_native: false,
374 native_path: None,
375 is_exported: false,
376 export_target: None,
377 opt_flags: crate::optimization::OptimizationConfig::all_on(),
378 });
379
380 let nonce_field = self.interner.intern(SESSION_NONCE_FIELD);
387 let read_nonce = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
388 object: self.te_id(session_var),
389 field: nonce_field,
390 });
391 let unminted = self.te_bin(BinaryOpKind::Eq, read_nonce, self.te_text(blank));
392 let fresh_nonce = {
393 let f = self.interner.intern("randomSecret");
394 self.te_call(f, Vec::new())
395 };
396 let mint = self.alloc_block(vec![Stmt::SetField {
397 object: self.te_id(session_var),
398 field: nonce_field,
399 value: fresh_nonce,
400 }]);
401 let read_nonce = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
402 object: self.te_id(session_var),
403 field: nonce_field,
404 });
405 let token = {
406 let f = self.interner.intern("csrfToken");
407 self.te_call(f, vec![self.te_id(secret_var), read_nonce])
408 };
409 let csrf_fn = self.interner.intern(CSRF_FN);
410 self.deferred_functions.push(Stmt::FunctionDef {
411 name: csrf_fn,
412 generics: Vec::new(),
413 params: Vec::new(),
414 body: self.alloc_block(vec![
415 Stmt::If {
416 cond: unminted,
417 then_block: mint,
418 else_block: None,
419 },
420 Stmt::Return { value: Some(token) },
421 ]),
422 return_type: Some(text_ty),
423 is_native: false,
424 native_path: None,
425 is_exported: false,
426 export_target: None,
427 opt_flags: crate::optimization::OptimizationConfig::all_on(),
428 });
429
430 let resp_p = self.interner.intern("__lgx_sresp");
432 let now_var = self.interner.intern("__lgx_snow");
433 let mut save: Vec<Stmt<'a>> = Vec::new();
434 let now = self.te_call(payload_fn, Vec::new());
435 save.push(Stmt::Let {
436 var: now_var,
437 ty: Some(text_ty),
438 value: now,
439 mutable: false,
440 });
441 let same = self.te_bin(
442 BinaryOpKind::Eq,
443 self.te_id(now_var),
444 self.te_id(loaded_var),
445 );
446 let unchanged = self.alloc_block(vec![Stmt::Return {
447 value: Some(self.te_id(resp_p)),
448 }]);
449 save.push(Stmt::If {
450 cond: same,
451 then_block: unchanged,
452 else_block: None,
453 });
454 let sign = self.interner.intern("signSession");
455 let signed = self.te_call(sign, vec![self.te_id(secret_var), self.te_id(now_var)]);
456 let with_cookie = self.interner.intern("withCookie");
457 let set = self.te_call(
458 with_cookie,
459 vec![self.te_id(resp_p), self.te_text(cookie_name), signed],
460 );
461 save.push(Stmt::Return { value: Some(set) });
462 let save_fn = self.interner.intern(SESSION_SAVE_FN);
463 self.deferred_functions.push(Stmt::FunctionDef {
464 name: save_fn,
465 generics: Vec::new(),
466 params: vec![(resp_p, response_named)],
467 body: self.alloc_block(save),
468 return_type: Some(response_named),
469 is_native: false,
470 native_path: None,
471 is_exported: false,
472 export_target: None,
473 opt_flags: crate::optimization::OptimizationConfig::all_on(),
474 });
475 Ok(())
476 }
477
478 pub(crate) fn fresh_session(
480 &mut self,
481 session_ty: Symbol,
482 fields: &[(Symbol, SessionFieldKind)],
483 ) -> &'a Expr<'a> {
484 let init: Vec<(Symbol, &'a Expr<'a>)> = fields
485 .iter()
486 .map(|(name, kind)| {
487 let zero = match kind {
488 SessionFieldKind::Text => {
489 let blank = self.interner.intern("");
490 self.te_text(blank)
491 }
492 SessionFieldKind::Int => self.te_int(0),
493 SessionFieldKind::Bool => self.te_bool(false),
494 };
495 (*name, zero)
496 })
497 .collect();
498 self.ctx.alloc_imperative_expr(Expr::New {
499 type_name: session_ty,
500 type_args: Vec::new(),
501 init_fields: init,
502 })
503 }
504
505 pub(crate) fn empty_header_map(&mut self) -> &'a Expr<'a> {
507 let map_ty = self.interner.intern("Map");
508 let text_arg = TypeExpr::Primitive(self.interner.intern("Text"));
509 self.ctx.alloc_imperative_expr(Expr::New {
510 type_name: map_ty,
511 type_args: vec![text_arg.clone(), text_arg],
512 init_fields: Vec::new(),
513 })
514 }
515
516 pub(crate) fn response_value(
518 &mut self,
519 status: &'a Expr<'a>,
520 ctype: &'a Expr<'a>,
521 body: &'a Expr<'a>,
522 ) -> &'a Expr<'a> {
523 let headers = self.empty_header_map();
524 self.ctx.alloc_imperative_expr(Expr::New {
525 type_name: self.interner.intern("Response"),
526 type_args: Vec::new(),
527 init_fields: vec![
528 (self.interner.intern("status"), status),
529 (self.interner.intern("contentType"), ctype),
530 (self.interner.intern("body"), body),
531 (self.interner.intern("headers"), headers),
532 ],
533 })
534 }
535
536 pub(crate) fn plain_response(&mut self, code: i64, body: &'a Expr<'a>) -> &'a Expr<'a> {
539 let plain = self.interner.intern("text/plain; charset=utf-8");
540 let ctype = self.te_text(plain);
541 let status = self.te_int(code);
542 self.response_value(status, ctype, body)
543 }
544
545 pub(crate) fn header_map_type(&mut self) -> &'a TypeExpr<'a> {
547 let text = TypeExpr::Primitive(self.interner.intern("Text"));
548 let params = self.ctx.alloc_type_exprs(vec![text.clone(), text]);
549 let base = self.interner.intern("Map");
550 self.ctx.alloc_type_expr(TypeExpr::Generic { base, params })
551 }
552
553 pub(crate) fn route_param_value(
554 &mut self,
555 from: ParamFrom,
556 pattern: Symbol,
557 name: Symbol,
558 path_p: Symbol,
559 query_p: Symbol,
560 body_p: Option<Symbol>,
561 req_p: Symbol,
562 ) -> &'a Expr<'a> {
563 if from == ParamFrom::Request {
566 return self.te_id(req_p);
567 }
568 let name_text = self.te_text(name);
569 if from == ParamFrom::Path {
570 let get = self.interner.intern("routeParam");
571 let pat = self.te_text(pattern);
572 return self.te_call(get, vec![pat, name_text, self.te_id(path_p)]);
573 }
574 match body_p {
579 Some(body) => {
580 let get = self.interner.intern("bodyParam");
581 self.te_call(get, vec![self.te_id(body), name_text])
582 }
583 None => {
584 let get = self.interner.intern("queryParam");
585 self.te_call(get, vec![self.te_id(query_p), name_text])
586 }
587 }
588 }
589
590 pub(crate) fn lower_view_function_twins(&mut self, statements: &[Stmt<'a>]) {
603 use crate::ast_depth::{for_each_node, Visited};
604 if self.lgx_views.functions.is_empty()
605 || !(self.renders_on_a_server(statements) || no_page_places_a_view(statements))
606 {
607 return;
608 }
609 let fns: Vec<(Symbol, Vec<(Symbol, &'a TypeExpr<'a>)>, &'a [Stmt<'a>])> = statements
610 .iter()
611 .filter_map(|s| match s {
612 Stmt::FunctionDef {
613 name, params, body, ..
614 } if self.lgx_views.functions.contains(name) => {
615 Some((*name, params.clone(), *body))
616 }
617 _ => None,
618 })
619 .collect();
620 let names: Vec<Symbol> = fns.iter().map(|(n, ..)| *n).collect();
621 let defs: Vec<ComponentDef<'a>> = statements
622 .iter()
623 .filter_map(|s| match s {
624 Stmt::ComponentDef(d) => Some((**d).clone()),
625 _ => None,
626 })
627 .collect();
628 let reads = |stmts: &[Stmt<'a>]| -> Vec<Symbol> {
630 let mut out: Vec<Symbol> = Vec::new();
631 for_each_node(stmts, |node| {
632 let named = match node {
633 Visited::Expr(Expr::Call { function, .. })
634 | Visited::Stmt(Stmt::Call { function, .. }) => *function,
635 Visited::Expr(Expr::Identifier(sym)) => *sym,
636 _ => return,
637 };
638 if names.contains(&named) && !out.contains(&named) {
639 out.push(named);
640 }
641 });
642 out
643 };
644 let templates = self.lgx_views.templates.clone();
645 let calls: Vec<Vec<Symbol>> = fns
646 .iter()
647 .map(
648 |(name, _, body)| match templates.iter().find(|t| t.name == *name) {
649 Some(t) => {
650 let mut held = Vec::new();
651 super::views::view_holes(&t.view.nodes, &mut |hole| {
652 if let Expr::Call { function, .. } = hole {
653 if names.contains(function) && !held.contains(function) {
654 held.push(*function);
655 }
656 }
657 });
658 held
659 }
660 None => reads(body),
661 },
662 )
663 .collect();
664 let mut twinned: Vec<Symbol> = fns
665 .iter()
666 .filter(
667 |(name, ..)| match templates.iter().find(|t| t.name == *name) {
668 Some(t) => {
669 view_html_lowerable_with(&t.view.nodes, &defs, self.interner).is_ok()
670 }
671 None => true,
672 },
673 )
674 .map(|(name, ..)| *name)
675 .collect();
676 loop {
677 let before = twinned.len();
678 let keep: Vec<Symbol> = fns
679 .iter()
680 .zip(&calls)
681 .filter(|((name, ..), called)| {
682 twinned.contains(name) && called.iter().all(|c| twinned.contains(c))
683 })
684 .map(|((name, ..), _)| *name)
685 .collect();
686 twinned = keep;
687 if twinned.len() == before {
688 break;
689 }
690 }
691 let text_ty = self.text_type();
692 for (name, params, _) in fns.iter().filter(|(name, ..)| twinned.contains(name)) {
693 let twin = self
694 .interner
695 .intern(&view_html_fn_name(self.interner.resolve(*name)));
696 let view = self.interner.intern("__lgx_answered");
697 let args: Vec<&'a Expr<'a>> = params.iter().map(|(p, _)| self.te_id(*p)).collect();
698 let answered = self.te_call(*name, args);
699 let mut body = vec![Stmt::Let {
700 var: view,
701 ty: None,
702 value: answered,
703 mutable: false,
704 }];
705 let blank = self.interner.intern("");
706 let place = Stmt::Call {
707 function: self.interner.intern(place_view_fn_name()),
708 args: vec![self.te_id(view), self.te_text(blank), self.te_int(1)],
709 };
710 let out = self.interner.intern("__lgx_html");
711 self.serve_markup(vec![place], out, &mut body);
712 body.push(Stmt::Return {
713 value: Some(self.te_id(out)),
714 });
715 self.deferred_functions.push(Stmt::FunctionDef {
716 name: twin,
717 generics: Vec::new(),
718 params: params.clone(),
719 body: self.alloc_block(body),
720 return_type: Some(text_ty),
721 is_native: false,
722 native_path: None,
723 is_exported: false,
724 export_target: None,
725 opt_flags: crate::optimization::OptimizationConfig::all_on(),
726 });
727 }
728 }
729
730 pub(crate) fn read_views_as_markup(&mut self, statements: &mut [Stmt<'a>]) {
733 if !no_page_places_a_view(statements) {
734 return;
735 }
736 let twins = self.served_view_twins(statements);
737 if twins.is_empty() {
738 return;
739 }
740 for stmt in statements.iter_mut() {
741 match stmt {
742 Stmt::FunctionDef { name, body, .. } => {
743 if !self.lgx_views.functions.contains(name) {
744 *body = self.serve_views_through_twins(body, &twins);
745 }
746 }
747 other => {
748 let alone = self.alloc_block(vec![other.clone()]);
749 let mapped = self.serve_views_through_twins(alone, &twins);
750 if let Some(first) = mapped.first() {
751 *other = first.clone();
752 }
753 }
754 }
755 }
756 }
757
758 pub(crate) fn view_html_twin(&self, function: Symbol) -> Option<Symbol> {
760 let twin = self
761 .interner
762 .lookup(&view_html_fn_name(self.interner.resolve(function)))?;
763 self.deferred_functions
764 .iter()
765 .any(|s| matches!(s, Stmt::FunctionDef { name, .. } if *name == twin))
766 .then_some(twin)
767 }
768
769 pub(crate) fn serve_views_through_twins(
771 &mut self,
772 body: &'a [Stmt<'a>],
773 twins: &[(Symbol, Symbol)],
774 ) -> &'a [Stmt<'a>] {
775 if twins.is_empty() {
776 return body;
777 }
778 let arenas = crate::ast_map::Arenas {
779 exprs: self.ctx.imperative_exprs.expect("imperative arenas"),
780 stmts: self.ctx.stmts.expect("imperative arenas"),
781 types: self.ctx.type_exprs.expect("imperative arenas"),
782 };
783 crate::ast_map::AstMap::map_block(&mut TwinCalls { arenas, twins }, body)
784 }
785
786 pub(crate) fn served_view_twins(&self, statements: &[Stmt<'a>]) -> Vec<(Symbol, Symbol)> {
788 statements
789 .iter()
790 .filter_map(|s| match s {
791 Stmt::FunctionDef {
792 name, return_type, ..
793 } if self.is_view_function(*name, *return_type) => {
794 self.view_html_twin(*name).map(|twin| (*name, twin))
795 }
796 _ => None,
797 })
798 .collect()
799 }
800
801 pub(crate) fn lower_lgx_routes(&mut self, statements: &[Stmt<'a>]) {
802 struct Route {
803 method: String,
804 path: Symbol,
805 fname: Symbol,
806 params: Vec<(Symbol, bool, ParamFrom)>,
811 literal: bool,
812 encode: Option<(Symbol, bool)>,
817 has_body: bool,
821 content_type: &'static str,
826 bare_scalar: bool,
829 is_response: bool,
841 }
842 if !statements.iter().any(|s| {
847 matches!(
848 s,
849 Stmt::RouteDef { .. } | Stmt::MiddlewareDef { .. } | Stmt::RedirectDef { .. }
850 )
851 }) {
852 return;
853 }
854 let text_ty: &'a TypeExpr<'a> = self
855 .ctx
856 .alloc_type_expr(TypeExpr::Primitive(self.interner.intern("Text")));
857 let int_ty: &'a TypeExpr<'a> = self
858 .ctx
859 .alloc_type_expr(TypeExpr::Primitive(self.interner.intern("Int")));
860 let view_twins = self.served_view_twins(statements);
861 let record_names: Vec<Symbol> = self
862 .type_registry
863 .as_ref()
864 .map(|r| {
865 r.iter_types()
866 .filter(|(_, d)| {
867 matches!(d, crate::analysis::registry::TypeDef::Struct { generics, .. }
868 if generics.is_empty())
869 })
870 .map(|(n, _)| *n)
871 .collect()
872 })
873 .unwrap_or_default();
874 let is_record = |s: Symbol| record_names.contains(&s);
875 let defs: Vec<ComponentDef<'a>> = statements
879 .iter()
880 .filter_map(|s| match s {
881 Stmt::ComponentDef(d) => Some((**d).clone()),
882 _ => None,
883 })
884 .collect();
885 let mut routes: Vec<Route> = Vec::new();
886 for stmt in statements {
887 let Stmt::RouteDef {
888 method,
889 path,
890 params,
891 return_type,
892 body,
893 span,
894 } = stmt
895 else {
896 continue;
897 };
898 if route_lowerable(
899 params,
900 *return_type,
901 body,
902 self.interner,
903 &is_record,
904 &defs,
905 *span,
906 )
907 .is_err()
908 {
909 continue;
910 }
911 let m = self.interner.resolve(*method).to_ascii_uppercase();
912 let p = self.interner.resolve(*path).to_string();
913 let fname = self.interner.intern(&route_fn_name(&m, &p));
914 let segments: Vec<&str> = p.split('/').collect();
915 let bound: Vec<(Symbol, bool, ParamFrom)> = params
916 .iter()
917 .map(|(n, ty)| {
918 let named = |k: &str| {
919 matches!(ty, TypeExpr::Primitive(s) | TypeExpr::Named(s)
920 if self.interner.resolve(*s).eq_ignore_ascii_case(k))
921 };
922 let slot = format!(":{}", self.interner.resolve(*n));
923 let from = if named("Request") {
924 ParamFrom::Request
925 } else if segments.iter().any(|seg| *seg == slot) {
926 ParamFrom::Path
927 } else {
928 ParamFrom::Extra
929 };
930 (*n, named("Int"), from)
931 })
932 .collect();
933 let serves_view =
936 matches!(**body, RouteBody::View(_)) || self.returns_a_view(*return_type);
937 let (fn_body, fn_ret) = match &**body {
941 RouteBody::Statements(block) => {
948 let block = self.serve_views_through_twins(block, &view_twins);
949 (
950 block,
951 if serves_view {
952 Some(text_ty)
953 } else {
954 return_type.or(Some(text_ty))
955 },
956 )
957 }
958 RouteBody::View(view) => {
959 let out = self.interner.intern("__lgx_html");
960 let owner = self.template_owner(fname, params.to_vec(), view);
961 let mut draw = Vec::new();
962 let mut sheets_of: Vec<Symbol> = Vec::new();
963 for child in placed_components(&view.nodes) {
964 for styled in self.styled_reachable(child, &defs) {
965 if !sheets_of.contains(&styled) {
966 sheets_of.push(styled);
967 }
968 }
969 }
970 for styled in sheets_of {
971 for sheet in self.served_sheets(styled, &defs) {
972 draw.push(sheet);
973 }
974 }
975 draw.extend(self.serving_draw(&view.nodes, &owner, &defs));
976 let mut html = Vec::new();
977 self.serve_markup(draw, out, &mut html);
978 html.push(Stmt::Return {
979 value: Some(self.te_id(out)),
980 });
981 (self.alloc_block(html), Some(text_ty))
982 }
983 };
984 self.deferred_functions.push(Stmt::FunctionDef {
985 name: fname,
986 generics: Vec::new(),
987 params: params.iter().map(|(n, ty)| (*n, *ty)).collect(),
988 body: fn_body,
989 return_type: fn_ret,
990 is_native: false,
991 native_path: None,
992 is_exported: false,
993 export_target: None,
994 opt_flags: crate::optimization::OptimizationConfig::all_on(),
995 });
996 let encode = match (&**body, return_type) {
997 _ if serves_view => None,
1000 (_, Some(TypeExpr::Primitive(sym) | TypeExpr::Named(sym)))
1004 if self.interner.resolve(*sym).eq_ignore_ascii_case("Response") =>
1005 {
1006 None
1007 }
1008 (_, Some(TypeExpr::Primitive(sym) | TypeExpr::Named(sym))) if is_record(*sym) => {
1009 Some((*sym, false))
1010 }
1011 (_, Some(TypeExpr::Generic { base, params }))
1014 if self.interner.resolve(*base).eq_ignore_ascii_case("Seq")
1015 && params.len() == 1 =>
1016 {
1017 match ¶ms[0] {
1018 TypeExpr::Primitive(s) | TypeExpr::Named(s) if is_record(*s) => {
1019 Some((*s, true))
1020 }
1021 _ => None,
1022 }
1023 }
1024 _ => None,
1025 };
1026 if let Some((rec, _)) = encode {
1029 if !self.json_encoders_needed.contains(&rec) {
1030 self.json_encoders_needed.push(rec);
1031 }
1032 }
1033 let has_body = matches!(m.as_str(), "POST" | "PUT" | "PATCH");
1034 let is_response = !serves_view
1035 && matches!(return_type, Some(TypeExpr::Primitive(sym) | TypeExpr::Named(sym))
1036 if self.interner.resolve(*sym).eq_ignore_ascii_case("Response"));
1037 let bare_scalar = !serves_view
1038 && matches!(return_type, Some(TypeExpr::Primitive(sym) | TypeExpr::Named(sym))
1039 if json_bare_scalar_name(self.interner.resolve(*sym)));
1040 let content_type = if serves_view {
1043 "text/html; charset=utf-8"
1044 } else if encode.is_some() || bare_scalar {
1045 "application/json"
1046 } else {
1047 "text/plain; charset=utf-8"
1048 };
1049 routes.push(Route {
1050 method: m,
1051 path: *path,
1052 fname,
1053 params: bound,
1054 literal: !p.contains(':'),
1055 encode,
1056 has_body,
1057 content_type,
1058 bare_scalar,
1059 is_response,
1060 });
1061 }
1062 if routes.is_empty()
1065 && !statements
1066 .iter()
1067 .any(|s| matches!(s, Stmt::MiddlewareDef { .. } | Stmt::RedirectDef { .. }))
1068 {
1069 return;
1070 }
1071 let method_p = self.interner.intern("__lgx_method");
1072 let url_p = self.interner.intern("__lgx_url");
1077 let path_p = self.interner.intern("__lgx_path");
1078 let query_p = self.interner.intern("__lgx_query");
1079 let body_p = self.interner.intern("__lgx_body");
1085 let req_p = self.interner.intern("__lgx_req");
1090 let has_session = self.session_declared;
1095 let split_url = |me: &mut Self| -> Vec<Stmt<'a>> {
1096 let path_of = me.interner.intern("pathOf");
1097 let query_string = me.interner.intern("queryString");
1098 let field = |me: &mut Self, f: &str| -> &'a Expr<'a> {
1099 let field = me.interner.intern(f);
1100 let object = me.te_id(req_p);
1101 me.ctx
1102 .alloc_imperative_expr(Expr::FieldAccess { object, field })
1103 };
1104 let m = field(me, "method");
1105 let u = field(me, "path");
1106 let b = field(me, "body");
1107 let mut out = vec![
1108 Stmt::Let {
1109 var: method_p,
1110 ty: Some(text_ty),
1111 value: m,
1112 mutable: false,
1113 },
1114 Stmt::Let {
1115 var: url_p,
1116 ty: Some(text_ty),
1117 value: u,
1118 mutable: false,
1119 },
1120 Stmt::Let {
1121 var: body_p,
1122 ty: Some(text_ty),
1123 value: b,
1124 mutable: false,
1125 },
1126 Stmt::Let {
1127 var: path_p,
1128 ty: Some(text_ty),
1129 value: me.te_call(path_of, vec![me.te_id(url_p)]),
1130 mutable: false,
1131 },
1132 Stmt::Let {
1133 var: query_p,
1134 ty: Some(text_ty),
1135 value: me.te_call(query_string, vec![me.te_id(url_p)]),
1136 mutable: false,
1137 },
1138 ];
1139 if has_session {
1140 let load = me.interner.intern(SESSION_LOAD_FN);
1141 let req = me.te_id(req_p);
1142 out.push(Stmt::Call {
1143 function: load,
1144 args: vec![req],
1145 });
1146 }
1147 out
1148 };
1149 let mut arms: Vec<Stmt<'a>> = split_url(self);
1150 let mut status_arms: Vec<Stmt<'a>> = split_url(self);
1152 let mut ctype_arms: Vec<Stmt<'a>> = split_url(self);
1155 let mut header_arms: Vec<Stmt<'a>> = split_url(self);
1157 let mut response_arms: Vec<Stmt<'a>> = split_url(self);
1159 let mut next = 0usize;
1160
1161 let csrf_guard = |me: &mut Self, as_status: bool| -> Vec<Stmt<'a>> {
1172 let refusal = |me: &mut Self| -> &'a Expr<'a> {
1173 if as_status {
1174 me.te_int(403)
1175 } else {
1176 let code = me.interner.intern("403");
1177 let body_text = me.te_text(code);
1178 me.plain_response(403, body_text)
1179 }
1180 };
1181 let m_upper = me.interner.intern("__lgx_method_upper");
1182 let upper = {
1183 let f = me.interner.intern("upper");
1184 me.te_call(f, vec![me.te_id(method_p)])
1185 };
1186 let mut out = vec![Stmt::Let {
1187 var: m_upper,
1188 ty: Some(text_ty),
1189 value: upper,
1190 mutable: false,
1191 }];
1192 let blank = me.interner.intern("");
1193 let secret_var = me.interner.intern(SESSION_SECRET_VAR);
1194 let session_var = me.interner.intern(SESSION_VAR);
1195 let nonce_field = me.interner.intern(SESSION_NONCE_FIELD);
1196 let sent = me.interner.intern("__lgx_csrf_sent");
1197 let mut guard: Vec<Stmt<'a>> = Vec::new();
1198 let from_body = {
1199 let f = me.interner.intern("bodyParam");
1200 let name = me.interner.intern(CSRF_FIELD);
1201 me.te_call(f, vec![me.te_id(body_p), me.te_text(name)])
1202 };
1203 guard.push(Stmt::Let {
1204 var: sent,
1205 ty: Some(text_ty),
1206 value: from_body,
1207 mutable: true,
1208 });
1209 let from_header = {
1211 let f = me.interner.intern("headerOf");
1212 let name = me.interner.intern(CSRF_HEADER);
1213 me.te_call(f, vec![me.te_id(req_p), me.te_text(name)])
1214 };
1215 let none_sent = me.te_bin(BinaryOpKind::Eq, me.te_id(sent), me.te_text(blank));
1216 guard.push(Stmt::If {
1217 cond: none_sent,
1218 then_block: me.alloc_block(vec![Stmt::Set {
1219 target: sent,
1220 value: from_header,
1221 }]),
1222 else_block: None,
1223 });
1224 let read_nonce = me.ctx.alloc_imperative_expr(Expr::FieldAccess {
1228 object: me.te_id(session_var),
1229 field: nonce_field,
1230 });
1231 let unknown = me.te_bin(BinaryOpKind::Eq, read_nonce, me.te_text(blank));
1232 let refused = refusal(me);
1233 guard.push(Stmt::If {
1234 cond: unknown,
1235 then_block: me.alloc_block(vec![Stmt::Return {
1236 value: Some(refused),
1237 }]),
1238 else_block: None,
1239 });
1240 let read_nonce = me.ctx.alloc_imperative_expr(Expr::FieldAccess {
1241 object: me.te_id(session_var),
1242 field: nonce_field,
1243 });
1244 let valid = {
1245 let f = me.interner.intern("csrfValid");
1246 me.te_call(f, vec![me.te_id(secret_var), read_nonce, me.te_id(sent)])
1247 };
1248 let bad = me.te_bin(BinaryOpKind::Eq, valid, me.te_bool(false));
1249 let refused = refusal(me);
1250 guard.push(Stmt::If {
1251 cond: bad,
1252 then_block: me.alloc_block(vec![Stmt::Return {
1253 value: Some(refused),
1254 }]),
1255 else_block: None,
1256 });
1257 let mut changes: Option<&'a Expr<'a>> = None;
1258 for m in ["POST", "PUT", "PATCH", "DELETE"] {
1259 let sym = me.interner.intern(m);
1260 let is = me.te_bin(BinaryOpKind::Eq, me.te_id(m_upper), me.te_text(sym));
1261 changes = Some(match changes {
1262 None => is,
1263 Some(acc) => me.te_bin(BinaryOpKind::Or, acc, is),
1264 });
1265 }
1266 let changes = changes.expect("four methods");
1267 out.push(Stmt::If {
1268 cond: changes,
1269 then_block: me.alloc_block(guard),
1270 else_block: None,
1271 });
1272 out
1273 };
1274
1275 let middleware: Vec<&Stmt<'a>> = statements
1281 .iter()
1282 .filter(|s| matches!(s, Stmt::MiddlewareDef { .. }))
1283 .collect();
1284 if !middleware.is_empty() {
1285 let req = self.interner.intern("__lgx_guard_request");
1301 let derived = {
1302 let read = |me: &mut Self, f: &str| -> &'a Expr<'a> {
1303 let field = me.interner.intern(f);
1304 let object = me.te_id(req_p);
1305 me.ctx
1306 .alloc_imperative_expr(Expr::FieldAccess { object, field })
1307 };
1308 let headers = read(self, "headers");
1309 let query = read(self, "query");
1310 self.ctx.alloc_imperative_expr(Expr::New {
1311 type_name: self.interner.intern("Request"),
1312 type_args: Vec::new(),
1313 init_fields: vec![
1314 (self.interner.intern("method"), self.te_id(method_p)),
1315 (self.interner.intern("path"), self.te_id(path_p)),
1316 (self.interner.intern("body"), self.te_id(body_p)),
1317 (self.interner.intern("headers"), headers),
1318 (self.interner.intern("query"), query),
1319 ],
1320 })
1321 };
1322 for dest in [
1326 &mut arms,
1327 &mut status_arms,
1328 &mut header_arms,
1329 &mut response_arms,
1330 ] {
1331 dest.push(Stmt::Let {
1332 var: req,
1333 ty: None,
1334 value: derived,
1335 mutable: false,
1336 });
1337 }
1338 for (i, m) in middleware.iter().enumerate() {
1339 let Stmt::MiddlewareDef { param, body } = m else {
1340 continue;
1341 };
1342 let fname = self.interner.intern(&format!("__middleware_{i}"));
1343 let body = self.serve_views_through_twins(body, &view_twins);
1344 let answers = {
1346 let response = TypeExpr::Named(self.interner.intern("Response"));
1347 let params = self.ctx.alloc_type_exprs(vec![response]);
1348 self.ctx.alloc_type_expr(TypeExpr::Generic {
1349 base: self.interner.intern("Option"),
1350 params,
1351 })
1352 };
1353 self.deferred_functions.push(Stmt::FunctionDef {
1354 name: fname,
1355 generics: Vec::new(),
1356 params: vec![*param],
1357 body,
1358 return_type: Some(answers),
1359 is_native: false,
1360 native_path: None,
1361 is_exported: false,
1362 export_target: None,
1363 opt_flags: crate::optimization::OptimizationConfig::all_on(),
1364 });
1365 let answered = self.interner.intern("__lgx_answer");
1366 let body_field = self.interner.intern("body");
1367 let taken = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
1368 object: self.te_id(answered),
1369 field: body_field,
1370 });
1371 let some_arm = crate::ast::stmt::MatchArm {
1372 enum_name: None,
1373 variant: Some(self.interner.intern("Some")),
1374 bindings: vec![(answered, answered)],
1375 body: self.alloc_block(vec![Stmt::Return { value: Some(taken) }]),
1376 };
1377 let none_arm = crate::ast::stmt::MatchArm {
1381 enum_name: None,
1382 variant: Some(self.interner.intern("None")),
1383 bindings: Vec::new(),
1384 body: self.alloc_block(Vec::new()),
1385 };
1386 arms.push(Stmt::Inspect {
1387 target: self.te_call(fname, vec![self.te_id(req)]),
1388 arms: vec![some_arm, none_arm],
1389 has_otherwise: false,
1390 });
1391 let status_field = self.interner.intern("status");
1394 let its_status = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
1395 object: self.te_id(answered),
1396 field: status_field,
1397 });
1398 let some_status = crate::ast::stmt::MatchArm {
1399 enum_name: None,
1400 variant: Some(self.interner.intern("Some")),
1401 bindings: vec![(answered, answered)],
1402 body: self.alloc_block(vec![Stmt::Return {
1403 value: Some(its_status),
1404 }]),
1405 };
1406 let none_status = crate::ast::stmt::MatchArm {
1407 enum_name: None,
1408 variant: Some(self.interner.intern("None")),
1409 bindings: Vec::new(),
1410 body: self.alloc_block(Vec::new()),
1411 };
1412 status_arms.push(Stmt::Inspect {
1413 target: self.te_call(fname, vec![self.te_id(req)]),
1414 arms: vec![some_status, none_status],
1415 has_otherwise: false,
1416 });
1417 let headers_field = self.interner.intern("headers");
1422 let its_headers = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
1423 object: self.te_id(answered),
1424 field: headers_field,
1425 });
1426 let some_headers = crate::ast::stmt::MatchArm {
1427 enum_name: None,
1428 variant: Some(self.interner.intern("Some")),
1429 bindings: vec![(answered, answered)],
1430 body: self.alloc_block(vec![Stmt::Return {
1431 value: Some(its_headers),
1432 }]),
1433 };
1434 let none_headers = crate::ast::stmt::MatchArm {
1435 enum_name: None,
1436 variant: Some(self.interner.intern("None")),
1437 bindings: Vec::new(),
1438 body: self.alloc_block(Vec::new()),
1439 };
1440 header_arms.push(Stmt::Inspect {
1441 target: self.te_call(fname, vec![self.te_id(req)]),
1442 arms: vec![some_headers, none_headers],
1443 has_otherwise: false,
1444 });
1445 let some_whole = crate::ast::stmt::MatchArm {
1451 enum_name: None,
1452 variant: Some(self.interner.intern("Some")),
1453 bindings: vec![(answered, answered)],
1454 body: self.alloc_block(vec![Stmt::Return {
1455 value: Some(self.te_id(answered)),
1456 }]),
1457 };
1458 let none_whole = crate::ast::stmt::MatchArm {
1459 enum_name: None,
1460 variant: Some(self.interner.intern("None")),
1461 bindings: Vec::new(),
1462 body: self.alloc_block(Vec::new()),
1463 };
1464 response_arms.push(Stmt::Inspect {
1465 target: self.te_call(fname, vec![self.te_id(req)]),
1466 arms: vec![some_whole, none_whole],
1467 has_otherwise: false,
1468 });
1469 }
1470 }
1471
1472 if has_session {
1487 status_arms.extend(csrf_guard(self, true));
1488 response_arms.extend(csrf_guard(self, false));
1489 }
1490
1491 if has_session {
1498 let get = self.interner.intern("GET");
1499 let reserved = self.interner.intern(CSRF_TOKEN_PATH);
1500 let plain = self.interner.intern("text/plain; charset=utf-8");
1501 let is_get = self.te_bin(BinaryOpKind::Eq, self.te_id(method_p), self.te_text(get));
1502 let at_path = self.te_bin(BinaryOpKind::Eq, self.te_id(path_p), self.te_text(reserved));
1503 let cond = self.te_bin(BinaryOpKind::And, is_get, at_path);
1504 let csrf_fn = self.interner.intern(CSRF_FN);
1505 let no_store = |me: &mut Self, headers: Symbol| -> Stmt<'a> {
1506 let name = me.interner.intern("cache-control");
1507 let value = me.interner.intern("no-store");
1508 Stmt::SetIndex {
1509 collection: me.te_id(headers),
1510 index: me.te_text(name),
1511 value: me.te_text(value),
1512 }
1513 };
1514 let token = self.te_call(csrf_fn, Vec::new());
1515 let body_then = self.alloc_block(vec![Stmt::Return { value: Some(token) }]);
1516 arms.push(Stmt::If {
1517 cond,
1518 then_block: body_then,
1519 else_block: None,
1520 });
1521 let status_then = self.alloc_block(vec![Stmt::Return {
1522 value: Some(self.te_int(200)),
1523 }]);
1524 status_arms.push(Stmt::If {
1525 cond,
1526 then_block: status_then,
1527 else_block: None,
1528 });
1529 let ct_then = self.alloc_block(vec![Stmt::Return {
1530 value: Some(self.te_text(plain)),
1531 }]);
1532 ctype_arms.push(Stmt::If {
1533 cond,
1534 then_block: ct_then,
1535 else_block: None,
1536 });
1537 let hmap = self.interner.intern("__lgx_csrf_headers");
1538 let fresh = self.empty_header_map();
1539 let mark = no_store(self, hmap);
1540 let back = Stmt::Return {
1541 value: Some(self.te_id(hmap)),
1542 };
1543 let header_then = self.alloc_block(vec![
1544 Stmt::Let {
1545 var: hmap,
1546 ty: None,
1547 value: fresh,
1548 mutable: true,
1549 },
1550 mark,
1551 back,
1552 ]);
1553 header_arms.push(Stmt::If {
1554 cond,
1555 then_block: header_then,
1556 else_block: None,
1557 });
1558 let answer = self.interner.intern("__lgx_csrf_answer");
1559 let token = self.te_call(csrf_fn, Vec::new());
1560 let served = self.response_value(self.te_int(200), self.te_text(plain), token);
1561 let served_headers = self.ctx.alloc_imperative_expr(Expr::FieldAccess {
1562 object: self.te_id(answer),
1563 field: self.interner.intern("headers"),
1564 });
1565 let cache = self.interner.intern("cache-control");
1566 let store = self.interner.intern("no-store");
1567 let mark = Stmt::SetIndex {
1568 collection: served_headers,
1569 index: self.te_text(cache),
1570 value: self.te_text(store),
1571 };
1572 let back = Stmt::Return {
1573 value: Some(self.te_id(answer)),
1574 };
1575 let response_then = self.alloc_block(vec![
1576 Stmt::Let {
1577 var: answer,
1578 ty: None,
1579 value: served,
1580 mutable: true,
1581 },
1582 mark,
1583 back,
1584 ]);
1585 response_arms.push(Stmt::If {
1586 cond,
1587 then_block: response_then,
1588 else_block: None,
1589 });
1590 }
1591
1592 for pass_literal in [true, false] {
1594 for r in routes.iter().filter(|r| r.literal == pass_literal) {
1595 let m_sym = self.interner.intern(&r.method);
1596 let same_method =
1597 self.te_bin(BinaryOpKind::Eq, self.te_id(method_p), self.te_text(m_sym));
1598 let matches_path = if r.literal {
1599 self.te_bin(BinaryOpKind::Eq, self.te_id(path_p), self.te_text(r.path))
1600 } else {
1601 let f = self.interner.intern("matchRoute");
1602 self.te_call(f, vec![self.te_text(r.path), self.te_id(path_p)])
1603 };
1604 let cond = self.te_bin(BinaryOpKind::And, same_method, matches_path);
1605
1606 {
1613 let mut rbody: Vec<Stmt<'a>> = Vec::new();
1614 let mut rargs: Vec<&'a Expr<'a>> = Vec::new();
1615 for (name, is_int, from) in &r.params {
1616 let body_src = r.has_body.then_some(body_p);
1617 let raw = self.route_param_value(
1618 *from, r.path, *name, path_p, query_p, body_src, req_p,
1619 );
1620 if *is_int {
1621 let is_int_fn = self.interner.intern("isIntText");
1622 let ok = self.te_call(is_int_fn, vec![raw]);
1623 let bad = self.te_bin(BinaryOpKind::Eq, ok, self.te_bool(false));
1624 let four = self.interner.intern("400");
1625 let text = self.te_text(four);
1626 let refused = self.plain_response(400, text);
1627 let then = self.alloc_block(vec![Stmt::Return {
1628 value: Some(refused),
1629 }]);
1630 rbody.push(Stmt::If {
1631 cond: bad,
1632 then_block: then,
1633 else_block: None,
1634 });
1635 let parse = self.interner.intern("parseInt");
1636 rargs.push(self.te_call(parse, vec![raw]));
1637 } else {
1638 rargs.push(raw);
1639 }
1640 }
1641 let rcall = self.te_call(r.fname, rargs);
1642 let served = if r.is_response {
1643 rcall
1644 } else {
1645 let ranswer = match r.encode {
1646 Some((record, as_list)) => {
1647 let prefix = if as_list { "toJsonSeq_" } else { "toJson_" };
1648 let enc = self
1649 .interner
1650 .intern(&format!("{prefix}{}", self.interner.resolve(record)));
1651 self.te_call(enc, vec![rcall])
1652 }
1653 None if r.bare_scalar => {
1654 let empty = self.interner.intern("");
1655 self.te_bin(BinaryOpKind::Add, self.te_text(empty), rcall)
1656 }
1657 None => rcall,
1658 };
1659 let ct = self.interner.intern(r.content_type);
1660 let ctype = self.te_text(ct);
1661 let ok = self.te_int(200);
1662 self.response_value(ok, ctype, ranswer)
1663 };
1664 rbody.push(Stmt::Return {
1665 value: Some(served),
1666 });
1667 let rthen = self.alloc_block(rbody);
1668 response_arms.push(Stmt::If {
1669 cond,
1670 then_block: rthen,
1671 else_block: None,
1672 });
1673 }
1674
1675 let mut body: Vec<Stmt<'a>> = Vec::new();
1676 let mut args: Vec<&'a Expr<'a>> = Vec::new();
1677 for (name, is_int, from) in &r.params {
1678 let raw = self.lgx_temp(&mut next);
1679 let body_src = r.has_body.then_some(body_p);
1680 let value = self
1681 .route_param_value(*from, r.path, *name, path_p, query_p, body_src, req_p);
1682 let param_ty = if *from == ParamFrom::Request {
1686 self.ctx
1687 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Request")))
1688 } else {
1689 text_ty
1690 };
1691 body.push(Stmt::Let {
1692 var: raw,
1693 ty: Some(param_ty),
1694 value,
1695 mutable: false,
1696 });
1697 if *is_int {
1698 let parse = self.interner.intern("parseInt");
1708 let is_int = self.interner.intern("isIntText");
1709 let ok = self.te_call(is_int, vec![self.te_id(raw)]);
1710 let bad = self.te_bin(BinaryOpKind::Eq, ok, self.te_bool(false));
1711 let four = self.interner.intern("400");
1712 let then_block = self.alloc_block(vec![Stmt::Return {
1713 value: Some(self.te_text(four)),
1714 }]);
1715 body.push(Stmt::If {
1716 cond: bad,
1717 then_block,
1718 else_block: None,
1719 });
1720 let conv = self.lgx_temp(&mut next);
1721 body.push(Stmt::Let {
1722 var: conv,
1723 ty: Some(int_ty),
1724 value: self.te_call(parse, vec![self.te_id(raw)]),
1725 mutable: false,
1726 });
1727 args.push(self.te_id(conv));
1728 } else {
1729 args.push(self.te_id(raw));
1730 }
1731 }
1732 let call = self.te_call(r.fname, args);
1733 if r.is_response {
1736 let field_of = |me: &mut Self, call: &'a Expr<'a>, f: &str| {
1748 let field = me.interner.intern(f);
1749 me.ctx.alloc_imperative_expr(Expr::FieldAccess {
1750 object: call,
1751 field,
1752 })
1753 };
1754 let taken = field_of(self, call, "body");
1755 body.push(Stmt::Return { value: Some(taken) });
1756 let then_block = self.alloc_block(body);
1757 arms.push(Stmt::If {
1758 cond,
1759 then_block,
1760 else_block: None,
1761 });
1762
1763 for (field, dest) in [("status", 0u8), ("contentType", 1u8), ("headers", 2u8)] {
1766 let mut cbody: Vec<Stmt<'a>> = Vec::new();
1767 let mut cargs: Vec<&'a Expr<'a>> = Vec::new();
1768 for (name, is_int, from) in &r.params {
1769 let body_src = r.has_body.then_some(body_p);
1770 let raw = self.route_param_value(
1771 *from, r.path, *name, path_p, query_p, body_src, req_p,
1772 );
1773 if *is_int {
1774 let is_int_fn = self.interner.intern("isIntText");
1775 let ok = self.te_call(is_int_fn, vec![raw]);
1776 let bad = self.te_bin(BinaryOpKind::Eq, ok, self.te_bool(false));
1777 let four: &'a Expr<'a> = match dest {
1778 0 => self.te_int(400),
1779 1 => {
1780 let plain =
1781 self.interner.intern("text/plain; charset=utf-8");
1782 self.te_text(plain)
1783 }
1784 _ => self.empty_header_map(),
1785 };
1786 let then =
1787 self.alloc_block(vec![Stmt::Return { value: Some(four) }]);
1788 cbody.push(Stmt::If {
1789 cond: bad,
1790 then_block: then,
1791 else_block: None,
1792 });
1793 let parse = self.interner.intern("parseInt");
1794 cargs.push(self.te_call(parse, vec![raw]));
1795 } else {
1796 cargs.push(raw);
1797 }
1798 }
1799 let ccall = self.te_call(r.fname, cargs);
1800 let read = field_of(self, ccall, field);
1801 cbody.push(Stmt::Return { value: Some(read) });
1802 let cthen = self.alloc_block(cbody);
1803 let arm = Stmt::If {
1804 cond,
1805 then_block: cthen,
1806 else_block: None,
1807 };
1808 match dest {
1809 0 => status_arms.push(arm),
1810 1 => ctype_arms.push(arm),
1811 _ => header_arms.push(arm),
1812 }
1813 }
1814 continue;
1815 }
1816 let answer = match r.encode {
1817 Some((record, as_list)) => {
1818 let prefix = if as_list { "toJsonSeq_" } else { "toJson_" };
1819 let enc = self
1820 .interner
1821 .intern(&format!("{prefix}{}", self.interner.resolve(record)));
1822 self.te_call(enc, vec![call])
1823 }
1824 None if r.bare_scalar => {
1827 let empty = self.interner.intern("");
1828 self.te_bin(BinaryOpKind::Add, self.te_text(empty), call)
1829 }
1830 None => call,
1831 };
1832 body.push(Stmt::Return {
1833 value: Some(answer),
1834 });
1835 let then_block = self.alloc_block(body);
1836 arms.push(Stmt::If {
1837 cond,
1838 then_block,
1839 else_block: None,
1840 });
1841 let mut status_body: Vec<Stmt<'a>> = Vec::new();
1846 for (name, is_int, from) in &r.params {
1847 if !*is_int {
1848 continue;
1849 }
1850 let body_src = r.has_body.then_some(body_p);
1851 let raw = self
1852 .route_param_value(*from, r.path, *name, path_p, query_p, body_src, req_p);
1853 let is_int_fn = self.interner.intern("isIntText");
1854 let ok = self.te_call(is_int_fn, vec![raw]);
1855 let bad = self.te_bin(BinaryOpKind::Eq, ok, self.te_bool(false));
1856 let four = self.te_int(400);
1857 let then = self.alloc_block(vec![Stmt::Return { value: Some(four) }]);
1858 status_body.push(Stmt::If {
1859 cond: bad,
1860 then_block: then,
1861 else_block: None,
1862 });
1863 }
1864 status_body.push(Stmt::Return {
1865 value: Some(self.te_int(200)),
1866 });
1867 let status_then = self.alloc_block(status_body);
1868 status_arms.push(Stmt::If {
1869 cond,
1870 then_block: status_then,
1871 else_block: None,
1872 });
1873 let ct = self.interner.intern(r.content_type);
1874 let ct_then = self.alloc_block(vec![Stmt::Return {
1875 value: Some(self.te_text(ct)),
1876 }]);
1877 ctype_arms.push(Stmt::If {
1878 cond,
1879 then_block: ct_then,
1880 else_block: None,
1881 });
1882 }
1883 }
1884
1885 for stmt in statements {
1890 let Stmt::RedirectDef { from, to } = stmt else {
1891 continue;
1892 };
1893 let same = self.te_bin(BinaryOpKind::Eq, self.te_id(path_p), self.te_text(*from));
1894 let answer = self
1895 .interner
1896 .intern(&format!("302 {}", self.interner.resolve(*to)));
1897 let then_block = self.alloc_block(vec![Stmt::Return {
1898 value: Some(self.te_text(answer)),
1899 }]);
1900 arms.push(Stmt::If {
1901 cond: same,
1902 then_block,
1903 else_block: None,
1904 });
1905 let moved = self.alloc_block(vec![Stmt::Return {
1906 value: Some(self.te_int(302)),
1907 }]);
1908 status_arms.push(Stmt::If {
1909 cond: same,
1910 then_block: moved,
1911 else_block: None,
1912 });
1913 let moved_body = self.te_text(answer);
1918 let moved_res = self.plain_response(302, moved_body);
1919 let rthen = self.alloc_block(vec![Stmt::Return {
1920 value: Some(moved_res),
1921 }]);
1922 response_arms.push(Stmt::If {
1923 cond: same,
1924 then_block: rthen,
1925 else_block: None,
1926 });
1927 }
1928
1929 let mut error_pages: Vec<(u16, Symbol)> = Vec::new();
1934 for stmt in statements {
1935 let Stmt::ComponentDef(def) = stmt else {
1936 continue;
1937 };
1938 let ComponentKind::ErrorPage { status } = def.kind else {
1939 continue;
1940 };
1941 let Some(view) = &def.view else { continue };
1942 if view_html_lowerable(&view.nodes, self.interner).is_err() {
1943 continue;
1944 }
1945 let fname = self.interner.intern(&format!("__error_page_{status}"));
1946 let out = self.interner.intern("__lgx_html");
1947 let mut html: Vec<Stmt<'a>> = Vec::new();
1948 for field in def.state.iter().filter(|f| !f.derived) {
1951 let bound = self.state_field_let(field, field.init, true);
1952 html.push(bound);
1953 }
1954 let owner = self.template_owner(fname, Vec::new(), view);
1955 let draw = self.serving_draw(&view.nodes, &owner, &defs);
1956 self.serve_markup(draw, out, &mut html);
1957 html.push(Stmt::Return {
1958 value: Some(self.te_id(out)),
1959 });
1960 self.deferred_functions.push(Stmt::FunctionDef {
1961 name: fname,
1962 generics: Vec::new(),
1963 params: Vec::new(),
1964 body: self.alloc_block(html),
1965 return_type: Some(text_ty),
1966 is_native: false,
1967 native_path: None,
1968 is_exported: false,
1969 export_target: None,
1970 opt_flags: crate::optimization::OptimizationConfig::all_on(),
1971 });
1972 error_pages.push((status, fname));
1973 }
1974 let has_page = |code: u16| error_pages.iter().any(|(s, _)| *s == code);
1980 let answer_ctype = |me: &mut Self, code: u16| -> &'a Expr<'a> {
1981 let ct = if has_page(code) {
1982 me.interner.intern("text/html; charset=utf-8")
1983 } else {
1984 me.interner.intern("text/plain; charset=utf-8")
1985 };
1986 me.te_text(ct)
1987 };
1988 let status_answer = |me: &mut Self, code: u16| -> &'a Expr<'a> {
1989 match error_pages.iter().find(|(s, _)| *s == code) {
1990 Some((_, f)) => me.te_call(*f, Vec::new()),
1991 None => {
1992 let bare = me.interner.intern(&code.to_string());
1993 me.te_text(bare)
1994 }
1995 }
1996 };
1997
1998 let mut seen: Vec<Symbol> = Vec::new();
2000 for r in &routes {
2001 if seen.contains(&r.path) {
2002 continue;
2003 }
2004 seen.push(r.path);
2005 let cond = if r.literal {
2006 self.te_bin(BinaryOpKind::Eq, self.te_id(path_p), self.te_text(r.path))
2007 } else {
2008 let f = self.interner.intern("matchRoute");
2009 self.te_call(f, vec![self.te_text(r.path), self.te_id(path_p)])
2010 };
2011 let answer = status_answer(self, 405);
2012 let then_block = self.alloc_block(vec![Stmt::Return {
2013 value: Some(answer),
2014 }]);
2015 arms.push(Stmt::If {
2016 cond,
2017 then_block,
2018 else_block: None,
2019 });
2020 let wrong = self.alloc_block(vec![Stmt::Return {
2021 value: Some(self.te_int(405)),
2022 }]);
2023 status_arms.push(Stmt::If {
2024 cond,
2025 then_block: wrong,
2026 else_block: None,
2027 });
2028 let served = status_answer(self, 405);
2029 let ct = answer_ctype(self, 405);
2030 let code = self.te_int(405);
2031 let refused = self.response_value(code, ct, served);
2032 let rthen = self.alloc_block(vec![Stmt::Return {
2033 value: Some(refused),
2034 }]);
2035 response_arms.push(Stmt::If {
2036 cond,
2037 then_block: rthen,
2038 else_block: None,
2039 });
2040 let ct = answer_ctype(self, 405);
2045 let ct_then = self.alloc_block(vec![Stmt::Return { value: Some(ct) }]);
2046 ctype_arms.push(Stmt::If {
2047 cond,
2048 then_block: ct_then,
2049 else_block: None,
2050 });
2051 }
2052
2053 let not_found = status_answer(self, 404);
2055 arms.push(Stmt::Return {
2056 value: Some(not_found),
2057 });
2058 status_arms.push(Stmt::Return {
2059 value: Some(self.te_int(404)),
2060 });
2061 let missing = status_answer(self, 404);
2062 let ct = answer_ctype(self, 404);
2063 let code = self.te_int(404);
2064 let missing = self.response_value(code, ct, missing);
2065 response_arms.push(Stmt::Return {
2066 value: Some(missing),
2067 });
2068 let tail_ct = answer_ctype(self, 404);
2078 ctype_arms.push(Stmt::Return {
2079 value: Some(tail_ct),
2080 });
2081 let no_headers = self.empty_header_map();
2082 header_arms.push(Stmt::Return {
2083 value: Some(no_headers),
2084 });
2085 let request_walk_ty: &'a TypeExpr<'a> = self
2086 .ctx
2087 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Request")));
2088 let ctype_name = self.interner.intern(ROUTE_CTYPE_WALK);
2089 self.deferred_functions.push(Stmt::FunctionDef {
2090 name: ctype_name,
2091 generics: Vec::new(),
2092 params: vec![(req_p, request_walk_ty)],
2093 body: self.alloc_block(ctype_arms),
2094 return_type: Some(text_ty),
2095 is_native: false,
2096 native_path: None,
2097 is_exported: false,
2098 export_target: None,
2099 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2100 });
2101 let response_named: &'a TypeExpr<'a> = self
2102 .ctx
2103 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Response")));
2104 let response_walk = self.interner.intern(ROUTE_RESPONSE_WALK);
2105 self.deferred_functions.push(Stmt::FunctionDef {
2106 name: response_walk,
2107 generics: Vec::new(),
2108 params: vec![(req_p, request_walk_ty)],
2109 body: self.alloc_block(response_arms),
2110 return_type: Some(response_named),
2111 is_native: false,
2112 native_path: None,
2113 is_exported: false,
2114 export_target: None,
2115 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2116 });
2117 let headers_ty = self.header_map_type();
2118 let headers_name = self.interner.intern(ROUTE_HEADERS_WALK);
2119 self.deferred_functions.push(Stmt::FunctionDef {
2120 name: headers_name,
2121 generics: Vec::new(),
2122 params: vec![(req_p, request_walk_ty)],
2123 body: self.alloc_block(header_arms),
2124 return_type: Some(headers_ty),
2125 is_native: false,
2126 native_path: None,
2127 is_exported: false,
2128 export_target: None,
2129 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2130 });
2131 let status_name = self.interner.intern(ROUTE_STATUS_WALK);
2132 self.deferred_functions.push(Stmt::FunctionDef {
2133 name: status_name,
2134 generics: Vec::new(),
2135 params: vec![(req_p, request_walk_ty)],
2136 body: self.alloc_block(status_arms),
2137 return_type: Some(int_ty),
2138 is_native: false,
2139 native_path: None,
2140 is_exported: false,
2141 export_target: None,
2142 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2143 });
2144 for (short, long) in [
2148 ("route", "routeWith"),
2149 ("routeStatus", "routeStatusWith"),
2150 ("routeContentType", "routeContentTypeWith"),
2151 ] {
2152 let short_sym = self.interner.intern(short);
2153 let long_sym = self.interner.intern(long);
2154 let blank = self.interner.intern("");
2155 let ret: &'a TypeExpr<'a> = if short == "routeStatus" {
2156 int_ty
2157 } else {
2158 text_ty
2159 };
2160 let call = self.te_call(
2161 long_sym,
2162 vec![self.te_id(method_p), self.te_id(url_p), self.te_text(blank)],
2163 );
2164 self.deferred_functions.push(Stmt::FunctionDef {
2165 name: short_sym,
2166 generics: Vec::new(),
2167 params: vec![(method_p, text_ty), (url_p, text_ty)],
2168 body: self.alloc_block(vec![Stmt::Return { value: Some(call) }]),
2169 return_type: Some(ret),
2170 is_native: false,
2171 native_path: None,
2172 is_exported: false,
2173 export_target: None,
2174 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2175 });
2176 }
2177
2178 let handler_name = self.interner.intern(ROUTE_HANDLER);
2188 let request_p = self.interner.intern("__lgx_request_in");
2189 let s_var = self.interner.intern("__lgx_hs");
2190 let b_var = self.interner.intern("__lgx_hb");
2191 let c_var = self.interner.intern("__lgx_hc");
2192 let mut hbody: Vec<Stmt<'a>> = Vec::new();
2193 let h_var = self.interner.intern("__lgx_hh");
2201 let served_var = self.interner.intern("__lgx_hres");
2202 let response_named: &'a TypeExpr<'a> = self
2203 .ctx
2204 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Response")));
2205 let serve_call = {
2206 let f = self.interner.intern(ROUTE_RESPONSE_WALK);
2207 self.te_call(f, vec![self.te_id(request_p)])
2208 };
2209 hbody.push(Stmt::Let {
2210 var: served_var,
2211 ty: Some(response_named),
2212 value: serve_call,
2213 mutable: false,
2214 });
2215 let field_of = |me: &mut Self, f: &str| -> &'a Expr<'a> {
2216 let field = me.interner.intern(f);
2217 let object = me.te_id(served_var);
2218 me.ctx
2219 .alloc_imperative_expr(Expr::FieldAccess { object, field })
2220 };
2221 let read_status = field_of(self, "status");
2222 let read_body = field_of(self, "body");
2223 let read_ctype = field_of(self, "contentType");
2224 let read_headers = field_of(self, "headers");
2225 let headers_ty = self.header_map_type();
2226 hbody.push(Stmt::Let {
2227 var: s_var,
2228 ty: Some(int_ty),
2229 value: read_status,
2230 mutable: false,
2231 });
2232 hbody.push(Stmt::Let {
2233 var: b_var,
2234 ty: Some(text_ty),
2235 value: read_body,
2236 mutable: false,
2237 });
2238 hbody.push(Stmt::Let {
2239 var: c_var,
2240 ty: Some(text_ty),
2241 value: read_ctype,
2242 mutable: false,
2243 });
2244 hbody.push(Stmt::Let {
2245 var: h_var,
2246 ty: Some(headers_ty),
2247 value: read_headers,
2248 mutable: false,
2249 });
2250 let loc_var = self.interner.intern("__lgx_hloc");
2251 let header_in = self.interner.intern("headerIn");
2252 let location = self.interner.intern("location");
2253 let read_loc = self.te_call(header_in, vec![self.te_id(h_var), self.te_text(location)]);
2254 hbody.push(Stmt::Let {
2255 var: loc_var,
2256 ty: Some(text_ty),
2257 value: read_loc,
2258 mutable: false,
2259 });
2260 let saved = |me: &mut Self, value: &'a Expr<'a>| -> &'a Expr<'a> {
2269 if !has_session {
2270 return value;
2271 }
2272 let f = me.interner.intern(SESSION_SAVE_FN);
2273 me.te_call(f, vec![value])
2274 };
2275 let redirect_to = self.interner.intern("redirectTo");
2276 let permanent_to = self.interner.intern("permanentRedirectTo");
2277 let cut_after = self.interner.intern("cutAfter");
2278 let space = self.interner.intern(" ");
2279 let where_to = self.te_call(cut_after, vec![self.te_id(b_var), self.te_text(space)]);
2280 let blank_sym = self.interner.intern("");
2287 let no_location = self.te_bin(
2288 BinaryOpKind::Eq,
2289 self.te_id(loc_var),
2290 self.te_text(blank_sym),
2291 );
2292 let moved_302 = self.te_bin(BinaryOpKind::Eq, self.te_id(s_var), self.te_int(302));
2293 let moved_301 = self.te_bin(BinaryOpKind::Eq, self.te_id(s_var), self.te_int(301));
2294 let is_302 = self.te_bin(BinaryOpKind::And, moved_302, no_location);
2295 let is_301 = self.te_bin(BinaryOpKind::And, moved_301, no_location);
2296 hbody.push(Stmt::If {
2297 cond: is_302,
2298 then_block: {
2299 let moved = self.te_call(redirect_to, vec![where_to]);
2300 let moved = saved(self, moved);
2301 self.alloc_block(vec![Stmt::Return { value: Some(moved) }])
2302 },
2303 else_block: None,
2304 });
2305 hbody.push(Stmt::If {
2306 cond: is_301,
2307 then_block: {
2308 let moved = self.te_call(permanent_to, vec![where_to]);
2309 let moved = saved(self, moved);
2310 self.alloc_block(vec![Stmt::Return { value: Some(moved) }])
2311 },
2312 else_block: None,
2313 });
2314 let response_ty = self.interner.intern("Response");
2315 let answer = self.ctx.alloc_imperative_expr(Expr::New {
2316 type_name: response_ty,
2317 type_args: Vec::new(),
2318 init_fields: vec![
2319 (self.interner.intern("status"), self.te_id(s_var)),
2320 (self.interner.intern("contentType"), self.te_id(c_var)),
2321 (self.interner.intern("body"), self.te_id(b_var)),
2322 (self.interner.intern("headers"), self.te_id(h_var)),
2323 ],
2324 });
2325 let answer = saved(self, answer);
2326 hbody.push(Stmt::Return {
2327 value: Some(answer),
2328 });
2329 let request_ty: &'a TypeExpr<'a> = self
2330 .ctx
2331 .alloc_type_expr(TypeExpr::Named(self.interner.intern("Request")));
2332 let response_ty_expr: &'a TypeExpr<'a> =
2333 self.ctx.alloc_type_expr(TypeExpr::Named(response_ty));
2334 self.deferred_functions.push(Stmt::FunctionDef {
2335 name: handler_name,
2336 generics: Vec::new(),
2337 params: vec![(request_p, request_ty)],
2338 body: self.alloc_block(hbody),
2339 return_type: Some(response_ty_expr),
2340 is_native: false,
2341 native_path: None,
2342 is_exported: false,
2343 export_target: None,
2344 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2345 });
2346 let name = self.interner.intern(ROUTE_BODY_WALK);
2347 self.deferred_functions.push(Stmt::FunctionDef {
2348 name,
2349 generics: Vec::new(),
2350 params: vec![(req_p, request_walk_ty)],
2351 body: self.alloc_block(arms),
2352 return_type: Some(text_ty),
2353 is_native: false,
2354 native_path: None,
2355 is_exported: false,
2356 export_target: None,
2357 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2358 });
2359
2360 for (wrapper, walk, ret) in [
2370 ("routeWith", ROUTE_BODY_WALK, text_ty),
2371 ("routeStatusWith", ROUTE_STATUS_WALK, int_ty),
2372 ("routeContentTypeWith", ROUTE_CTYPE_WALK, text_ty),
2373 ] {
2374 let map_ty = self.interner.intern("Map");
2375 let text_arg = TypeExpr::Primitive(self.interner.intern("Text"));
2376 let empty_map = self.ctx.alloc_imperative_expr(Expr::New {
2377 type_name: map_ty,
2378 type_args: vec![text_arg.clone(), text_arg],
2379 init_fields: Vec::new(),
2380 });
2381 let built = self.ctx.alloc_imperative_expr(Expr::New {
2382 type_name: self.interner.intern("Request"),
2383 type_args: Vec::new(),
2384 init_fields: vec![
2385 (self.interner.intern("method"), self.te_id(method_p)),
2386 (self.interner.intern("path"), self.te_id(url_p)),
2387 (self.interner.intern("body"), self.te_id(body_p)),
2388 (self.interner.intern("headers"), empty_map),
2389 (self.interner.intern("query"), empty_map),
2390 ],
2391 });
2392 let walk_sym = self.interner.intern(walk);
2393 let call = self.te_call(walk_sym, vec![built]);
2394 let wrapper_sym = self.interner.intern(wrapper);
2395 self.deferred_functions.push(Stmt::FunctionDef {
2396 name: wrapper_sym,
2397 generics: Vec::new(),
2398 params: vec![(method_p, text_ty), (url_p, text_ty), (body_p, text_ty)],
2399 body: self.alloc_block(vec![Stmt::Return { value: Some(call) }]),
2400 return_type: Some(ret),
2401 is_native: false,
2402 native_path: None,
2403 is_exported: false,
2404 export_target: None,
2405 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2406 });
2407 }
2408
2409 let name_p = self.interner.intern("__lgx_hname");
2415 for (wrapper, with_body) in [("routeHeaderWith", true), ("routeHeader", false)] {
2416 let body_expr = if with_body {
2417 let text_arg = TypeExpr::Primitive(self.interner.intern("Text"));
2418 let empty_map = self.ctx.alloc_imperative_expr(Expr::New {
2419 type_name: self.interner.intern("Map"),
2420 type_args: vec![text_arg.clone(), text_arg],
2421 init_fields: Vec::new(),
2422 });
2423 let built = self.ctx.alloc_imperative_expr(Expr::New {
2424 type_name: self.interner.intern("Request"),
2425 type_args: Vec::new(),
2426 init_fields: vec![
2427 (self.interner.intern("method"), self.te_id(method_p)),
2428 (self.interner.intern("path"), self.te_id(url_p)),
2429 (self.interner.intern("body"), self.te_id(body_p)),
2430 (self.interner.intern("headers"), empty_map),
2431 (self.interner.intern("query"), empty_map),
2432 ],
2433 });
2434 let walk = self.interner.intern(ROUTE_HEADERS_WALK);
2435 let table = self.te_call(walk, vec![built]);
2436 let read = self.interner.intern("headerIn");
2437 self.te_call(read, vec![table, self.te_id(name_p)])
2438 } else {
2439 let blank = self.interner.intern("");
2440 let long = self.interner.intern("routeHeaderWith");
2441 self.te_call(
2442 long,
2443 vec![
2444 self.te_id(method_p),
2445 self.te_id(url_p),
2446 self.te_text(blank),
2447 self.te_id(name_p),
2448 ],
2449 )
2450 };
2451 let params = if with_body {
2452 vec![
2453 (method_p, text_ty),
2454 (url_p, text_ty),
2455 (body_p, text_ty),
2456 (name_p, text_ty),
2457 ]
2458 } else {
2459 vec![(method_p, text_ty), (url_p, text_ty), (name_p, text_ty)]
2460 };
2461 let wrapper_sym = self.interner.intern(wrapper);
2462 self.deferred_functions.push(Stmt::FunctionDef {
2463 name: wrapper_sym,
2464 generics: Vec::new(),
2465 params,
2466 body: self.alloc_block(vec![Stmt::Return {
2467 value: Some(body_expr),
2468 }]),
2469 return_type: Some(text_ty),
2470 is_native: false,
2471 native_path: None,
2472 is_exported: false,
2473 export_target: None,
2474 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2475 });
2476 }
2477 }
2478
2479 pub(crate) fn styled_reachable(&self, root: Symbol, all: &[ComponentDef<'a>]) -> Vec<Symbol> {
2485 let mut seen = vec![root];
2486 let mut queue = vec![root];
2487 while let Some(cur) = queue.pop() {
2488 let Some(def) = all.iter().find(|d| d.name == cur) else {
2489 continue;
2490 };
2491 let Some(view) = def.view.as_ref() else {
2492 continue;
2493 };
2494 for child in placed_components(&view.nodes) {
2495 if !seen.contains(&child) {
2496 seen.push(child);
2497 queue.push(child);
2498 }
2499 }
2500 }
2501 seen.retain(|n| all.iter().any(|d| d.name == *n && d.style.is_some()));
2502 seen
2503 }
2504
2505 pub(crate) fn html_reachable(
2514 &self,
2515 name: Symbol,
2516 all: &[ComponentDef<'a>],
2517 routed: &[Symbol],
2518 ) -> bool {
2519 if self.names_html_of(name) || routed.contains(&name) {
2520 return true;
2521 }
2522 let mut seeds: Vec<Symbol> = all
2527 .iter()
2528 .filter(|d| self.names_html_of(d.name))
2529 .map(|d| d.name)
2530 .collect();
2531 seeds.extend(routed.iter().copied());
2532 let mut seen: Vec<Symbol> = seeds.clone();
2533 while let Some(cur) = seeds.pop() {
2534 let Some(def) = all.iter().find(|d| d.name == cur) else {
2535 continue;
2536 };
2537 let Some(view) = def.view.as_ref() else {
2538 continue;
2539 };
2540 for child in placed_components(&view.nodes) {
2541 if child == name {
2542 return true;
2543 }
2544 if !seen.contains(&child) {
2545 seen.push(child);
2546 seeds.push(child);
2547 }
2548 }
2549 }
2550 false
2551 }
2552
2553 pub(crate) fn names_html_of(&self, name: Symbol) -> bool {
2558 self.tokens.windows(3).any(|w| {
2559 self.interner
2560 .resolve(w[0].lexeme)
2561 .eq_ignore_ascii_case("html")
2562 && self
2563 .interner
2564 .resolve(w[1].lexeme)
2565 .eq_ignore_ascii_case("of")
2566 && w[2].lexeme == name
2567 })
2568 }
2569
2570 pub(crate) fn renders_on_a_server(&self, statements: &[Stmt<'a>]) -> bool {
2572 statements
2573 .iter()
2574 .any(|s| matches!(s, Stmt::RouteDef { .. }))
2575 || self.tokens.windows(2).any(|w| {
2576 self.interner
2577 .resolve(w[0].lexeme)
2578 .eq_ignore_ascii_case("html")
2579 && self
2580 .interner
2581 .resolve(w[1].lexeme)
2582 .eq_ignore_ascii_case("of")
2583 })
2584 }
2585
2586 pub(crate) fn template_owner(
2589 &self,
2590 name: Symbol,
2591 params: Vec<(Symbol, &'a TypeExpr<'a>)>,
2592 view: &crate::ast::view::ViewDef<'a>,
2593 ) -> ComponentDef<'a> {
2594 ComponentDef {
2595 kind: crate::ast::component::ComponentKind::Component,
2596 name,
2597 props: params,
2598 uses: Vec::new(),
2599 state: Vec::new(),
2600 events: Vec::new(),
2601 actions: Vec::new(),
2602 view: Some(view.clone()),
2603 head: None,
2604 style: None,
2605 span: view.span,
2606 }
2607 }
2608
2609 pub(crate) fn serve_markup(
2617 &mut self,
2618 draw: Vec<Stmt<'a>>,
2619 out: Symbol,
2620 body: &mut Vec<Stmt<'a>>,
2621 ) {
2622 let root = self.interner.intern("__lgx_served");
2623 let problem = self.interner.intern("__lgx_problem");
2624 let stash = self.interner.intern("__lgx_outer_tape");
2625 let stashed = self.vd_expr("vdTapeStash", Vec::new());
2629 body.push(Stmt::Let {
2630 var: stash,
2631 ty: None,
2632 value: stashed,
2633 mutable: false,
2634 });
2635 let detached = self.vd_expr("vdNewRoot", vec![self.te_int(-1)]);
2636 body.push(Stmt::Let {
2637 var: root,
2638 ty: None,
2639 value: detached,
2640 mutable: false,
2641 });
2642 body.extend(draw);
2643 let committed = self.vd_expr("vdCommit", vec![self.te_id(root)]);
2644 body.push(Stmt::Let {
2645 var: problem,
2646 ty: None,
2647 value: committed,
2648 mutable: false,
2649 });
2650 let blank = self.interner.intern("");
2651 let refused = self.te_bin(
2652 BinaryOpKind::NotEq,
2653 self.te_id(problem),
2654 self.te_text(blank),
2655 );
2656 let give_up = vec![
2657 self.vd_call("vdDiscardRoot", vec![self.te_id(root)]),
2658 self.vd_call("vdTapeUnstash", vec![self.te_id(stash)]),
2659 Stmt::Fail {
2660 reason: self.te_id(problem),
2661 },
2662 ];
2663 body.push(Stmt::If {
2664 cond: refused,
2665 then_block: self.alloc_block(give_up),
2666 else_block: None,
2667 });
2668 let markup = self.vd_expr("vdSerializeChildren", vec![self.te_id(root)]);
2669 let text_ty = self.text_type();
2670 body.push(Stmt::Let {
2671 var: out,
2672 ty: Some(text_ty),
2673 value: markup,
2674 mutable: false,
2675 });
2676 body.push(self.vd_call("vdDiscardRoot", vec![self.te_id(root)]));
2677 body.push(self.vd_call("vdTapeUnstash", vec![self.te_id(stash)]));
2678 }
2679
2680 pub(crate) fn served_sheets(
2683 &mut self,
2684 root: Symbol,
2685 all: &[ComponentDef<'a>],
2686 ) -> Vec<Stmt<'a>> {
2687 let mut out = Vec::new();
2688 for styled in self.styled_reachable(root, all) {
2689 let Some(sheet) = all
2690 .iter()
2691 .find(|d| d.name == styled)
2692 .and_then(|d| d.style.clone())
2693 else {
2694 continue;
2695 };
2696 let class = scope_class(self.interner.resolve(styled));
2697 let css = self.interner.resolve(sheet.css).to_string();
2698 let Ok(scoped) = crate::lgx::css::scope(&css, &class) else {
2699 continue;
2700 };
2701 let tag = self.interner.intern("style");
2702 let css_sym = self.interner.intern(&scoped);
2703 out.push(self.vd_call("vdOpen", vec![self.te_text(tag)]));
2704 out.push(self.vd_call("vdText", vec![self.te_text(css_sym)]));
2705 out.push(self.vd_call("vdClose", Vec::new()));
2706 }
2707 out
2708 }
2709
2710 pub(crate) fn serving_draw(
2713 &mut self,
2714 nodes: &[ViewNode<'a>],
2715 owner: &ComponentDef<'a>,
2716 all: &[ComponentDef<'a>],
2717 ) -> Vec<Stmt<'a>> {
2718 let mut draw = Vec::new();
2719 let mut next = 0usize;
2720 let outer_locals = std::mem::take(&mut self.lgx_views.locals);
2721 self.lower_lgx_nodes(
2722 nodes,
2723 &mut next,
2724 &mut draw,
2725 all,
2726 owner,
2727 &LowerTrail::serving(),
2728 );
2729 self.lgx_views.locals = outer_locals;
2730 draw
2731 }
2732
2733 fn refuse_serving(
2736 &self,
2737 def: &ComponentDef<'a>,
2738 all: &[ComponentDef<'a>],
2739 ) -> crate::parser::ParseResult<()> {
2740 let Some(view) = &def.view else { return Ok(()) };
2741 if let Err(why) = view_html_lowerable_with(&view.nodes, all, self.interner) {
2742 let name = self.interner.resolve(def.name).to_string();
2743 return Err(crate::error::ParseError {
2744 kind: crate::error::ParseErrorKind::Custom(format!(
2745 "`html of {name}` asks for this component as server-rendered markup, and {}. \
2746 A server has no document, so the server-side walk covers a smaller set of the \
2747 view tree than a browser does — and rendering the page SHORT would be worse \
2748 than saying so, because the reader would see a plausible document with a \
2749 piece missing and nothing anywhere would say which. In a browser this view is \
2750 ordinary: `Mount {name}.` renders it in full. So either render it there, or \
2751 replace the construct in `{name}`'s view that the server cannot draw.",
2752 why.message
2753 )),
2754 span: why.span,
2755 });
2756 }
2757 Ok(())
2758 }
2759
2760 pub(crate) fn lower_component_serve(
2774 &mut self,
2775 def: &ComponentDef<'a>,
2776 all: &[ComponentDef<'a>],
2777 routed: &[Symbol],
2778 ) -> crate::parser::ParseResult<()> {
2779 let Some(view) = &def.view else { return Ok(()) };
2780 if !self.html_reachable(def.name, all, routed) {
2781 return Ok(());
2782 }
2783 self.refuse_serving(def, all)?;
2784 let component = self.interner.resolve(def.name).to_string();
2785 let depth = self.interner.intern(draw_depth_param());
2786 let mut body: Vec<Stmt<'a>> = Vec::new();
2787 if on_placement_cycle(def, all) {
2788 body.push(self.refuse_past_render_depth(depth, def.name));
2789 }
2790 for field in def.state.iter().filter(|f| !f.derived) {
2791 let bound = self.state_field_let(field, field.init, true);
2792 body.push(bound);
2793 }
2794 for field in def.state.iter().filter(|f| f.derived) {
2795 let bound = self.state_field_let(field, field.init, false);
2796 body.push(bound);
2797 }
2798 let mut next = 0usize;
2799 let outer_locals = std::mem::take(&mut self.lgx_views.locals);
2800 let trail = LowerTrail::serving_at(depth);
2801 match super::render::extended_layout(&view.nodes)
2802 .and_then(|l| all.iter().find(|d| d.name == l))
2803 {
2804 Some(layout) => {
2808 let mut args =
2809 vec![self.te_bin(BinaryOpKind::Add, self.te_id(depth), self.te_int(1))];
2810 let slot = self.interner.intern(render_slot_param());
2811 args.push(self.te_id(slot));
2812 for block in slot_blocks(layout) {
2813 let fill = view.nodes.iter().enumerate().find_map(|(at, n)| match n {
2814 ViewNode::Block {
2815 name,
2816 default: Some(b),
2817 ..
2818 } if *name == block => Some((at, &b[..])),
2819 _ => None,
2820 });
2821 args.push(match fill {
2822 Some((at, b)) => {
2823 let branch = trail.branch(&format!("{at}."));
2824 self.slot_range(b, &mut next, &mut body, all, def, &branch)
2825 }
2826 None => self.te_int(0),
2827 });
2828 }
2829 let serve = self
2830 .interner
2831 .intern(&serve_fn_name(self.interner.resolve(layout.name)));
2832 body.push(Stmt::Call {
2833 function: serve,
2834 args,
2835 });
2836 }
2837 None => self.lower_lgx_nodes(&view.nodes, &mut next, &mut body, all, def, &trail),
2838 }
2839 self.lgx_views.locals = outer_locals;
2840 let int_ty = self.int_type();
2841 let mut params = vec![(depth, int_ty)];
2842 for (prop, ty) in &def.props {
2843 if is_callback_type(ty, self.interner) {
2844 continue;
2845 }
2846 let ty = self.lowered_type(ty);
2847 params.push((*prop, ty));
2848 }
2849 params.push((self.interner.intern(render_slot_param()), int_ty));
2850 for block in slot_blocks(def) {
2851 params.push((
2852 self.interner
2853 .intern(&block_slot_param(self.interner.resolve(block))),
2854 int_ty,
2855 ));
2856 }
2857 self.deferred_functions.push(Stmt::FunctionDef {
2858 name: self.interner.intern(&serve_fn_name(&component)),
2859 generics: Vec::new(),
2860 params,
2861 body: self.alloc_block(body),
2862 return_type: None,
2863 is_native: false,
2864 native_path: None,
2865 is_exported: false,
2866 export_target: None,
2867 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2868 });
2869 if !self.names_html_of(def.name) {
2870 return Ok(());
2871 }
2872 if let Some((prop, _)) = def.props.first() {
2874 let prop = self.interner.resolve(*prop).to_string();
2875 return Err(crate::error::ParseError {
2876 kind: crate::error::ParseErrorKind::Custom(format!(
2877 "`html of {component}` draws `{component}` as a page nobody placed, and `{component}` \
2878 takes `{prop}`, which only a placement hands it. Place `<{component} {prop}={{…}}/>` in \
2879 a component `html of` draws, or in a `## Route … -> View:` template."
2880 )),
2881 span: def.span,
2882 });
2883 }
2884 let text_ty = self.text_type();
2885 let out = self.interner.intern("__lgx_html");
2886 let mut draw = self.served_sheets(def.name, all);
2887 let mut args = vec![self.te_int(1)];
2888 args.push(self.te_int(0));
2889 args.extend(self.block_slot_zeros(def));
2890 draw.push(Stmt::Call {
2891 function: self.interner.intern(&serve_fn_name(&component)),
2892 args,
2893 });
2894 let mut body = Vec::new();
2895 self.serve_markup(draw, out, &mut body);
2896 body.push(Stmt::Return {
2897 value: Some(self.te_id(out)),
2898 });
2899 self.deferred_functions.push(Stmt::FunctionDef {
2900 name: self.interner.intern(&html_fn_name(&component)),
2901 generics: Vec::new(),
2902 params: Vec::new(),
2903 body: self.alloc_block(body),
2904 return_type: Some(text_ty),
2905 is_native: false,
2906 native_path: None,
2907 is_exported: false,
2908 export_target: None,
2909 opt_flags: crate::optimization::OptimizationConfig::all_on(),
2910 });
2911 Ok(())
2912 }
2913}
2914
2915pub(crate) fn no_page_places_a_view(statements: &[Stmt<'_>]) -> bool {
2918 statements
2919 .iter()
2920 .all(|s| !matches!(s, Stmt::ComponentDef(d) if d.view.is_some()))
2921}
2922
2923pub fn view_html_fn_name(function: &str) -> String {
2928 format!("__lgx_viewhtml_{function}")
2929}
2930
2931struct TwinCalls<'a, 't> {
2934 arenas: crate::ast_map::Arenas<'a>,
2935 twins: &'t [(Symbol, Symbol)],
2936}
2937
2938impl<'a, 't> TwinCalls<'a, 't> {
2939 fn twin_of(&self, function: Symbol) -> Option<Symbol> {
2940 self.twins
2941 .iter()
2942 .find(|(f, _)| *f == function)
2943 .map(|(_, twin)| *twin)
2944 }
2945}
2946
2947impl<'a, 't> crate::ast_map::AstMap<'a> for TwinCalls<'a, 't> {
2948 fn arenas(&self) -> crate::ast_map::Arenas<'a> {
2949 self.arenas
2950 }
2951
2952 fn map_expr(&mut self, e: &'a Expr<'a>) -> &'a Expr<'a> {
2953 if let Expr::Call { function, args } = e {
2954 if let Some(twin) = self.twin_of(*function) {
2955 let args = args.iter().map(|a| self.map_expr(a)).collect();
2956 return self.arenas.exprs.alloc(Expr::Call {
2957 function: twin,
2958 args,
2959 });
2960 }
2961 }
2962 crate::ast_map::walk_expr(self, e)
2963 }
2964
2965 fn map_stmt(&mut self, s: &Stmt<'a>) -> Stmt<'a> {
2966 if let Stmt::Call { function, args } = s {
2967 if let Some(twin) = self.twin_of(*function) {
2968 let args = args.iter().map(|a| self.map_expr(a)).collect();
2969 return Stmt::Call {
2970 function: twin,
2971 args,
2972 };
2973 }
2974 }
2975 crate::ast_map::walk_stmt(self, s)
2976 }
2977}