max / quasi
- Co-Authored-By
- Claude Opus 5 (1M context) <noreply@anthropic.com>
- Claude-Session
- https://claude.ai/code/session_01P8ostB2UmZJGj5WjSHRSot
12 files changed,
+307 insertions,
-131 deletions
| @@ -6312,6 +6312,10 @@ | |||
| 6312 | 6312 | "winnow 1.0.4", | |
| 6313 | 6313 | ] | |
| 6314 | 6314 | ||
| 6315 | + | [[patch.unused]] | |
| 6316 | + | name = "synckit-client" | |
| 6317 | + | version = "0.10.0" | |
| 6318 | + | ||
| 6315 | 6319 | [[patch.unused]] | |
| 6316 | 6320 | name = "kberg" | |
| 6317 | 6321 | version = "0.1.0" | |
| @@ -6331,7 +6335,3 @@ | |||
| 6331 | 6335 | [[patch.unused]] | |
| 6332 | 6336 | name = "quasi-type" | |
| 6333 | 6337 | version = "0.1.3" | |
| 6334 | - | ||
| 6335 | - | [[patch.unused]] | |
| 6336 | - | name = "synckit-client" | |
| 6337 | - | version = "0.10.0" |
| @@ -24,12 +24,6 @@ | |||
| 24 | 24 | # not carry a templating engine; on, it adds the third column that says whether | |
| 25 | 25 | # a staged description reaches a compiled template. | |
| 26 | 26 | askama = ["dep:askama"] | |
| 27 | - | # The residual tests for the four shapes that guard a setting. Off, and those | |
| 28 | - | # shapes are unstaged to match, until a guarded setting can be marked -- see | |
| 29 | - | # the note above each of their `declare!` blocks. Restore the flag and the | |
| 30 | - | # `#[staged]` together, so a residual test never outlives the residual it | |
| 31 | - | # tests. | |
| 32 | - | guarded-settings-stage = [] | |
| 33 | 27 | ||
| 34 | 28 | [dependencies] | |
| 35 | 29 | quasi-router = { path = "../quasi-router", version = "0.109" } |
| @@ -45,21 +45,7 @@ | |||
| 45 | 45 | ||
| 46 | 46 | declare! { | |
| 47 | 47 | /// A strip of figures, each of which may report a change. | |
| 48 | - | // PARKED, not staged: this shape guards a setting, and a guarded setting is | |
| 49 | - | // the one thing the mark rewrite does not yet cover. A mark covers a run of | |
| 50 | - | // a container's MEMBERS, and a setting varies markup inside the member its | |
| 51 | - | // own statement produces. Two shapes hide under that one sentence: | |
| 52 | - | // | |
| 53 | - | // PLACING settings (`figure`, `bar`, `more`, `back`, `forward`, | |
| 54 | - | // `jumping`) add markup at a position of their own, so they are members | |
| 55 | - | // in all but name and want counting in their container's index space. | |
| 56 | - | // | |
| 57 | - | // SETTLING settings (`chosen`, `here`) change the tag their member | |
| 58 | - | // already writes, so the answer is two arms over that member -- built | |
| 59 | - | // with the setting and without it -- which `given` already compiles to. | |
| 60 | - | // | |
| 61 | - | // Both are specified and neither is built. Until then this shape keeps the | |
| 62 | - | // runtime renderer, which is what an unstaged shape has always done. | |
| 48 | + | #[staged] | |
| 63 | 49 | pub(crate) shape strip(cards: &[Card]) -> Node; | |
| 64 | 50 | ||
| 65 | 51 | stats [] { | |
| @@ -71,11 +57,7 @@ | |||
| 71 | 57 | } | |
| 72 | 58 | } | |
| 73 | 59 | ||
| 74 | - | // PARKED with the shape above: these press the staged path, and the shape is | |
| 75 | - | // not staged until a guarded setting can be marked. Restore both together -- | |
| 76 | - | // the shape's `#[staged]` and this module -- so a residual test never outlives | |
| 77 | - | // the residual it tests. | |
| 78 | - | #[cfg(all(test, feature = "guarded-settings-stage"))] | |
| 60 | + | #[cfg(test)] | |
| 79 | 61 | mod tests { | |
| 80 | 62 | use super::*; | |
| 81 | 63 | use quasi_http::Serves as _; | |
| @@ -86,6 +68,12 @@ | |||
| 86 | 68 | quasi_webview::stage::derive(&Webview::new(), strip_staged) | |
| 87 | 69 | } | |
| 88 | 70 | ||
| 71 | + | #[test] | |
| 72 | + | fn probe_residual() { | |
| 73 | + | let residual = residual(); | |
| 74 | + | eprintln!("{:#?}", residual.ops()); | |
| 75 | + | } | |
| 76 | + | ||
| 89 | 77 | #[test] | |
| 90 | 78 | fn a_guard_inside_a_loop_body_derives() { | |
| 91 | 79 | let webview = Webview::new(); |
| @@ -53,21 +53,7 @@ | |||
| 53 | 53 | ||
| 54 | 54 | declare! { | |
| 55 | 55 | /// The theme picker. | |
| 56 | - | // PARKED, not staged: this shape guards a setting, and a guarded setting is | |
| 57 | - | // the one thing the mark rewrite does not yet cover. A mark covers a run of | |
| 58 | - | // a container's MEMBERS, and a setting varies markup inside the member its | |
| 59 | - | // own statement produces. Two shapes hide under that one sentence: | |
| 60 | - | // | |
| 61 | - | // PLACING settings (`figure`, `bar`, `more`, `back`, `forward`, | |
| 62 | - | // `jumping`) add markup at a position of their own, so they are members | |
| 63 | - | // in all but name and want counting in their container's index space. | |
| 64 | - | // | |
| 65 | - | // SETTLING settings (`chosen`, `here`) change the tag their member | |
| 66 | - | // already writes, so the answer is two arms over that member -- built | |
| 67 | - | // with the setting and without it -- which `given` already compiles to. | |
| 68 | - | // | |
| 69 | - | // Both are specified and neither is built. Until then this shape keeps the | |
| 70 | - | // runtime renderer, which is what an unstaged shape has always done. | |
| 56 | + | #[staged] | |
| 71 | 57 | pub(crate) shape picker(themes: &[Theme]) -> Node; | |
| 72 | 58 | ||
| 73 | 59 | form put "/api/users/me/console-theme" awaiting { | |
| @@ -82,11 +68,7 @@ | |||
| 82 | 68 | } | |
| 83 | 69 | } | |
| 84 | 70 | ||
| 85 | - | // PARKED with the shape above: these press the staged path, and the shape is | |
| 86 | - | // not staged until a guarded setting can be marked. Restore both together -- | |
| 87 | - | // the shape's `#[staged]` and this module -- so a residual test never outlives | |
| 88 | - | // the residual it tests. | |
| 89 | - | #[cfg(all(test, feature = "guarded-settings-stage"))] | |
| 71 | + | #[cfg(test)] | |
| 90 | 72 | mod tests { | |
| 91 | 73 | use quasi_http::Serves as _; | |
| 92 | 74 | use quasi_router::stage::{Op, Plan, Residual}; |
| @@ -57,21 +57,7 @@ | |||
| 57 | 57 | ||
| 58 | 58 | declare! { | |
| 59 | 59 | /// The listing, with the pager that says what it has not shown. | |
| 60 | - | // PARKED, not staged: this shape guards a setting, and a guarded setting is | |
| 61 | - | // the one thing the mark rewrite does not yet cover. A mark covers a run of | |
| 62 | - | // a container's MEMBERS, and a setting varies markup inside the member its | |
| 63 | - | // own statement produces. Two shapes hide under that one sentence: | |
| 64 | - | // | |
| 65 | - | // PLACING settings (`figure`, `bar`, `more`, `back`, `forward`, | |
| 66 | - | // `jumping`) add markup at a position of their own, so they are members | |
| 67 | - | // in all but name and want counting in their container's index space. | |
| 68 | - | // | |
| 69 | - | // SETTLING settings (`chosen`, `here`) change the tag their member | |
| 70 | - | // already writes, so the answer is two arms over that member -- built | |
| 71 | - | // with the setting and without it -- which `given` already compiles to. | |
| 72 | - | // | |
| 73 | - | // Both are specified and neither is built. Until then this shape keeps the | |
| 74 | - | // runtime renderer, which is what an unstaged shape has always done. | |
| 60 | + | #[staged] | |
| 75 | 61 | pub(crate) shape listing(loaded: &Loaded) -> Node; | |
| 76 | 62 | ||
| 77 | 63 | table { | |
| @@ -98,11 +84,7 @@ | |||
| 98 | 84 | } | |
| 99 | 85 | } | |
| 100 | 86 | ||
| 101 | - | // PARKED with the shape above: these press the staged path, and the shape is | |
| 102 | - | // not staged until a guarded setting can be marked. Restore both together -- | |
| 103 | - | // the shape's `#[staged]` and this module -- so a residual test never outlives | |
| 104 | - | // the residual it tests. | |
| 105 | - | #[cfg(all(test, feature = "guarded-settings-stage"))] | |
| 87 | + | #[cfg(test)] | |
| 106 | 88 | mod tests { | |
| 107 | 89 | use quasi_http::Serves as _; | |
| 108 | 90 | use quasi_router::stage::{Op, Plan, Residual}; | |
| @@ -235,21 +217,7 @@ | |||
| 235 | 217 | /// other page is a control, which is a substitution rather than a gap -- | |
| 236 | 218 | /// `here` places nothing that turning it off would delete. See | |
| 237 | 219 | /// `strip_tests` below. | |
| 238 | - | // PARKED, not staged: this shape guards a setting, and a guarded setting is | |
| 239 | - | // the one thing the mark rewrite does not yet cover. A mark covers a run of | |
| 240 | - | // a container's MEMBERS, and a setting varies markup inside the member its | |
| 241 | - | // own statement produces. Two shapes hide under that one sentence: | |
| 242 | - | // | |
| 243 | - | // PLACING settings (`figure`, `bar`, `more`, `back`, `forward`, | |
| 244 | - | // `jumping`) add markup at a position of their own, so they are members | |
| 245 | - | // in all but name and want counting in their container's index space. | |
| 246 | - | // | |
| 247 | - | // SETTLING settings (`chosen`, `here`) change the tag their member | |
| 248 | - | // already writes, so the answer is two arms over that member -- built | |
| 249 | - | // with the setting and without it -- which `given` already compiles to. | |
| 250 | - | // | |
| 251 | - | // Both are specified and neither is built. Until then this shape keeps the | |
| 252 | - | // runtime renderer, which is what an unstaged shape has always done. | |
| 220 | + | #[staged] | |
| 253 | 221 | pub(crate) shape windowed(loaded: &Windowed) -> Node; | |
| 254 | 222 | ||
| 255 | 223 | table { | |
| @@ -270,11 +238,7 @@ | |||
| 270 | 238 | } | |
| 271 | 239 | } | |
| 272 | 240 | ||
| 273 | - | // PARKED with the shape above: these press the staged path, and the shape is | |
| 274 | - | // not staged until a guarded setting can be marked. Restore both together -- | |
| 275 | - | // the shape's `#[staged]` and this module -- so a residual test never outlives | |
| 276 | - | // the residual it tests. | |
| 277 | - | #[cfg(all(test, feature = "guarded-settings-stage"))] | |
| 241 | + | #[cfg(test)] | |
| 278 | 242 | mod strip_tests { | |
| 279 | 243 | use quasi_http::Serves as _; | |
| 280 | 244 | use quasi_router::stage::{Op, Residual}; |
| @@ -637,14 +637,33 @@ | |||
| 637 | 637 | items | |
| 638 | 638 | .iter() | |
| 639 | 639 | .map(|item| match item { | |
| 640 | - | // Settling an argument is not accreting members either. A guard on | |
| 641 | - | // a setting inside one of these varies markup INSIDE the member its | |
| 642 | - | // statement produces, so the mark belongs to the container that | |
| 643 | - | // member lands in, which is where the twin puts it. | |
| 644 | - | Item::Marked { .. } => Err(syn::Error::new( | |
| 645 | - | proc_macro2::Span::call_site(), | |
| 646 | - | "a marked run where an argument is settled, which has no members", | |
| 647 | - | )), | |
| 640 | + | // An argument may be a container in its own right: a `Rest` holds | |
| 641 | + | // the controls a pager draws, and `back`, `forward` and `jumping` | |
| 642 | + | // place them. So a mark here covers a run of the ARGUMENT's members | |
| 643 | + | // rather than the enclosing container's, and the three lines are | |
| 644 | + | // the same three, written against the binding being settled. | |
| 645 | + | Item::Marked { site, varies, body } => { | |
| 646 | + | let inner = argument_settings(body, held)?; | |
| 647 | + | let varies = self::varies(varies)?; | |
| 648 | + | let plan = format_ident!("{}", crate::symbolic::PLAN, span = Span::call_site()); | |
| 649 | + | let at = format_ident!("settled_from_{}", held, span = Span::call_site()); | |
| 650 | + | let to = format_ident!("settled_to_{}", held, span = Span::call_site()); | |
| 651 | + | Ok(quote!({ | |
| 652 | + | let #at = ::quasi_router::stage::Marking::placed(&#held); | |
| 653 | + | #(#inner)* | |
| 654 | + | let #to = ::quasi_router::stage::Marking::placed(&#held); | |
| 655 | + | ::quasi_router::stage::Marking::mark( | |
| 656 | + | &mut #held, | |
| 657 | + | ::quasi_router::stage::Mark { | |
| 658 | + | scope: #plan.scope(), | |
| 659 | + | id: #site, | |
| 660 | + | varies: #varies, | |
| 661 | + | from: #at, | |
| 662 | + | to: #to, | |
| 663 | + | }, | |
| 664 | + | ); | |
| 665 | + | })) | |
| 666 | + | } | |
| 648 | 667 | Item::Attribute { | |
| 649 | 668 | name, | |
| 650 | 669 | args, |
| @@ -238,6 +238,29 @@ | |||
| 238 | 238 | } | |
| 239 | 239 | } | |
| 240 | 240 | } | |
| 241 | + | // Two markups at one position, picked by a predicate rather than by a | |
| 242 | + | // value. `Arms`'s machinery exactly, with the scrutinee replaced: arm 0 | |
| 243 | + | // is the setting made, which is `Plan::swap`'s own reading. | |
| 244 | + | Fill::Swap { guard } => { | |
| 245 | + | let predicate = crate::emit::predicate(guard)?; | |
| 246 | + | let answers = answers(level)?; | |
| 247 | + | quote! { | |
| 248 | + | { | |
| 249 | + | let taken = usize::from(!(#predicate)); | |
| 250 | + | ::quasi_router::stage::Cursor::pick( | |
| 251 | + | cursor, | |
| 252 | + | out, | |
| 253 | + | taken, | |
| 254 | + | &mut |which, out| match which { | |
| 255 | + | #(#answers)* | |
| 256 | + | _ => ::core::unreachable!( | |
| 257 | + | "the residual has a hole the filler does not" | |
| 258 | + | ), | |
| 259 | + | }, | |
| 260 | + | ); | |
| 261 | + | } | |
| 262 | + | } | |
| 263 | + | } | |
| 241 | 264 | // A dispatch: the residual holds one arm per position and the request | |
| 242 | 265 | // picks the index. No body to walk, because every arm's holes were | |
| 243 | 266 | // numbered at this level -- see `symbolic::Fill::Arms`. |
| @@ -179,6 +179,20 @@ | |||
| 179 | 179 | iterable: Hole, | |
| 180 | 180 | body: Vec<Fill>, | |
| 181 | 181 | }, | |
| 182 | + | /// Two markups at one position, and the predicate that picks between them. | |
| 183 | + | /// | |
| 184 | + | /// What a guarded SETTLING setting compiles to. `chosen`, `here` and | |
| 185 | + | /// `latched` change the markup the member their own statement produces was | |
| 186 | + | /// going to write anyway, so there is no run of members for a branch to | |
| 187 | + | /// cover; the member is built both ways instead and a request picks one. | |
| 188 | + | /// | |
| 189 | + | /// Arm 0 is the setting made, matching [`Plan::swap`][swap], which is what | |
| 190 | + | /// the twin reads to decide both which arm it is drawing and whether to | |
| 191 | + | /// make the setting. No body, for [`Arms`](Self::Arms)'s reason: both arms' | |
| 192 | + | /// holes are numbered at the level around them. | |
| 193 | + | /// | |
| 194 | + | /// [swap]: quasi_router::stage::Plan::swap | |
| 195 | + | Swap { guard: Guard }, | |
| 182 | 196 | /// One dispatch, and which arm the request takes. | |
| 183 | 197 | /// | |
| 184 | 198 | /// No bodies. Every arm's holes are staged at the level around the | |
| @@ -363,11 +377,8 @@ | |||
| 363 | 377 | "a staged twin cannot be staged again", | |
| 364 | 378 | )); | |
| 365 | 379 | } | |
| 366 | - | // A guarded member renders where it was written; a guarded SETTING | |
| 367 | - | // renders in its container's opening tag, which is before every member | |
| 368 | - | // regardless of where the declaration put it. The residual marks | |
| 369 | - | // branches in render order and the filler walks them in declaration | |
| 370 | - | // order, so the two would disagree about which branch is which. | |
| 380 | + | // A setting is one of two things, and which one decides whether a | |
| 381 | + | // guard on it can be a mark at all. See [`PLACING`]. | |
| 371 | 382 | Item::Attribute { | |
| 372 | 383 | name, | |
| 373 | 384 | args, | |
| @@ -375,17 +386,22 @@ | |||
| 375 | 386 | body, | |
| 376 | 387 | } => { | |
| 377 | 388 | if let Some(guard) = guard { | |
| 378 | - | return Err(syn::Error::new( | |
| 379 | - | guard.span, | |
| 380 | - | "a staged shape cannot guard a setting yet: a setting varies \ | |
| 381 | - | markup INSIDE the member its statement produces, and a mark \ | |
| 382 | - | covers a run of members. The answer is two arms over that \ | |
| 383 | - | member -- built with the setting and without it -- which is \ | |
| 384 | - | the same shape `given` already compiles to", | |
| 385 | - | )); | |
| 386 | - | } | |
| 387 | - | if let Some(guard) = guard { | |
| 388 | - | let staged = self::guard(guard, counters); | |
| 389 | + | if !placing(name) { | |
| 390 | + | return Err(syn::Error::new( | |
| 391 | + | guard.span, | |
| 392 | + | "a staged shape cannot guard a settling setting: it varies \ | |
| 393 | + | markup INSIDE the member its own statement produces, and \ | |
| 394 | + | a mark covers a run of members. The answer is two arms \ | |
| 395 | + | over that member -- built with the setting and without it \ | |
| 396 | + | -- which is the shape `given` already compiles to", | |
| 397 | + | )); | |
| 398 | + | } | |
| 399 | + | // A placing setting is a member in all but name, so its guard | |
| 400 | + | // is an ordinary mark over the run it places. The setting is | |
| 401 | + | // made unconditionally and the mark says what a request | |
| 402 | + | // decides, exactly as for a guarded member. | |
| 403 | + | let site = counters.guards; | |
| 404 | + | counters.guards += 1; | |
| 389 | 405 | counters.enter(); | |
| 390 | 406 | let args = staged_args(name, args, counters)?; | |
| 391 | 407 | // Inside the branch, because a setting that is not made places | |
| @@ -396,20 +412,74 @@ | |||
| 396 | 412 | guard: (*guard).clone(), | |
| 397 | 413 | body: recorded, | |
| 398 | 414 | }); | |
| 399 | - | return Ok(Item::Attribute { | |
| 400 | - | name: name.clone(), | |
| 401 | - | args, | |
| 402 | - | guard: Some(staged), | |
| 403 | - | body: inner, | |
| 415 | + | return Ok(Item::Marked { | |
| 416 | + | site, | |
| 417 | + | varies: crate::ast::Varies::Absent, | |
| 418 | + | body: vec![Item::Attribute { | |
| 419 | + | name: name.clone(), | |
| 420 | + | args, | |
| 421 | + | guard: None, | |
| 422 | + | body: inner, | |
| 423 | + | }], | |
| 404 | 424 | }); | |
| 405 | 425 | } | |
| 426 | + | // A body may settle the argument under a guard, and that guard | |
| 427 | + | // is the settling case: `chosen` is ` selected` inside the | |
| 428 | + | // `<option>` this same statement produces, so what varies is the | |
| 429 | + | // markup of this member rather than a run of members beside it. | |
| 430 | + | // The member becomes two arms of itself. | |
| 431 | + | let settling: Vec<&Guard> = body | |
| 432 | + | .iter() | |
| 433 | + | .filter_map(|item| match item { | |
| 434 | + | Item::Attribute { | |
| 435 | + | guard: Some(guard), | |
| 436 | + | name, | |
| 437 | + | .. | |
| 438 | + | } if !placing(name) => Some(guard), | |
| 439 | + | _ => None, | |
| 440 | + | }) | |
| 441 | + | .collect(); | |
| 442 | + | if let Some(guard) = settling.first() { | |
| 443 | + | if settling.len() > 1 { | |
| 444 | + | return Err(syn::Error::new( | |
| 445 | + | guard.span, | |
| 446 | + | "two settling settings on one member would be four arms of \ | |
| 447 | + | it, and nothing has wanted that: say one of them another \ | |
| 448 | + | way, or teach the twin the combinations", | |
| 449 | + | )); | |
| 450 | + | } | |
| 451 | + | let site = counters.arms; | |
| 452 | + | counters.arms += 1; | |
| 453 | + | ||
| 454 | + | // No scope of its own, for `Fill::Arms`'s reason: both arms' | |
| 455 | + | // holes are numbered at the level around them, so the closure | |
| 456 | + | // filling the stretch these arms sit in is the one that fills | |
| 457 | + | // the arm. That is what lets one arm carry a hole the other | |
| 458 | + | // does not. | |
| 459 | + | let args = staged_args(name, args, counters)?; | |
| 460 | + | let inner = swapped(body, site, counters)?; | |
| 461 | + | counters.wrote(Fill::Swap { | |
| 462 | + | guard: (*guard).clone(), | |
| 463 | + | }); | |
| 464 | + | ||
| 465 | + | return Ok(Item::Marked { | |
| 466 | + | site, | |
| 467 | + | varies: crate::ast::Varies::Arm { | |
| 468 | + | of: 2, | |
| 469 | + | taken: plan_arm(site, 2), | |
| 470 | + | }, | |
| 471 | + | body: vec![Item::Attribute { | |
| 472 | + | name: name.clone(), | |
| 473 | + | args, | |
| 474 | + | guard: None, | |
| 475 | + | body: inner, | |
| 476 | + | }], | |
| 477 | + | }); | |
| 478 | + | } | |
| 479 | + | ||
| 406 | 480 | // The argument's own holes render where the argument does, and | |
| 407 | 481 | // the body's settings render inside the markup the argument | |
| 408 | - | // builds -- `chosen` is ` selected` in the option's own tag. So | |
| 409 | - | // the args are staged first and the body's guards become branches | |
| 410 | - | // after them. That the branch sits BETWEEN the argument's two | |
| 411 | - | // holes in render order costs nothing: a hole is answered by its | |
| 412 | - | // number, and only branches and loops are walked in order. | |
| 482 | + | // builds. So the args are staged first and the body follows. | |
| 413 | 483 | Item::Attribute { | |
| 414 | 484 | name: name.clone(), | |
| 415 | 485 | args: staged_args(name, args, counters)?, | |
| @@ -530,6 +600,36 @@ | |||
| 530 | 600 | }) | |
| 531 | 601 | } | |
| 532 | 602 | ||
| 603 | + | /// One member's body, with its settling guard reading the plan's arm. | |
| 604 | + | /// | |
| 605 | + | /// Arm 0 is the setting made, which is [`Plan::swap`][swap]'s own reading and | |
| 606 | + | /// the one `Fill::Swap` fills against. Everything else in the body is staged | |
| 607 | + | /// the ordinary way. | |
| 608 | + | /// | |
| 609 | + | /// [swap]: quasi_router::stage::Plan::swap | |
| 610 | + | fn swapped(body: &[Item], site: u16, counters: &mut Counters) -> Result<Vec<Item>> { | |
| 611 | + | body.iter() | |
| 612 | + | .map(|item| match item { | |
| 613 | + | Item::Attribute { | |
| 614 | + | name, | |
| 615 | + | args, | |
| 616 | + | guard: Some(_), | |
| 617 | + | body, | |
| 618 | + | } if !placing(name) => Ok(Item::Attribute { | |
| 619 | + | name: name.clone(), | |
| 620 | + | args: staged_args(name, args, counters)?, | |
| 621 | + | guard: Some(Guard { | |
| 622 | + | negated: false, | |
| 623 | + | predicate: Predicate::Truth(plan_read("swap", site)), | |
| 624 | + | span: Span::call_site(), | |
| 625 | + | }), | |
| 626 | + | body: items(body, counters)?, | |
| 627 | + | }), | |
| 628 | + | other => self::item(other, counters), | |
| 629 | + | }) | |
| 630 | + | .collect() | |
| 631 | + | } | |
| 632 | + | ||
| 533 | 633 | /// A guard becomes one read of the plan, and stops being negated. | |
| 534 | 634 | /// | |
| 535 | 635 | /// `unless x` and `when not x` are the same question asked twice, and the plan | |
| @@ -642,6 +742,35 @@ | |||
| 642 | 742 | "bar", | |
| 643 | 743 | ]; | |
| 644 | 744 | ||
| 745 | + | /// The settings that place a member rather than settling one. | |
| 746 | + | /// | |
| 747 | + | /// A setting is one of two things, and the vocabulary spelled both the same | |
| 748 | + | /// way for a long time. | |
| 749 | + | /// | |
| 750 | + | /// A **placing** setting adds markup at a position of its own. `more` draws a | |
| 751 | + | /// pager after the rows; `figure` accretes onto a strip; `bar` onto a chart; | |
| 752 | + | /// `back`, `forward` and `jumping` each draw their own control inside the | |
| 753 | + | /// pager. Each is a member in all but name, each counts in its container's | |
| 754 | + | /// index space, and a guard on one is an ordinary mark over the run it places. | |
| 755 | + | /// | |
| 756 | + | /// A **settling** setting changes the markup the member its own statement | |
| 757 | + | /// produces was going to write anyway: `chosen` is ` selected` inside an | |
| 758 | + | /// `<option>`, `here` and `latched` are classes inside a tag. There is no run | |
| 759 | + | /// of members to cover, so a guard on one is two arms over that member instead | |
| 760 | + | /// -- quasicoherent `62739b91`. | |
| 761 | + | /// | |
| 762 | + | /// Everything not here is settling, which is the safe default: a setting | |
| 763 | + | /// wrongly called placing marks members its container never drew, and the | |
| 764 | + | /// renderer refuses that by name. | |
| 765 | + | const PLACING: &[&str] = &[ | |
| 766 | + | "figure", "bar", "more", "back", "forward", "jumping", "option", | |
| 767 | + | ]; | |
| 768 | + | ||
| 769 | + | /// Whether a guard on this setting is a mark rather than two arms. | |
| 770 | + | fn placing(name: &Ident) -> bool { | |
| 771 | + | PLACING.contains(&name.to_string().as_str()) | |
| 772 | + | } | |
| 773 | + | ||
| 645 | 774 | /// One setting's arguments, staged the way its slot asks for. | |
| 646 | 775 | /// | |
| 647 | 776 | /// The three answers a slot can give, in the order they are asked: a structure |
| @@ -1497,6 +1497,26 @@ | |||
| 1497 | 1497 | /// [`paging`](Self::paging) says it, and a renderer marks the jump whose | |
| 1498 | 1498 | /// [`Jump::page`] matches. | |
| 1499 | 1499 | pub jumps: Vec<Jump>, | |
| 1500 | + | /// What a request decides about runs of the controls this pager draws. | |
| 1501 | + | /// | |
| 1502 | + | /// Empty answering a request, filled by a staged twin. The index space is | |
| 1503 | + | /// back, then the jumps, then forward, which is the order a pager is drawn | |
| 1504 | + | /// in and not the order a declaration may write them in -- see | |
| 1505 | + | /// [`Marking::placed`](crate::stage::Marking::placed), which says that the | |
| 1506 | + | /// numbering is the render order for every container that draws from more | |
| 1507 | + | /// than one list. | |
| 1508 | + | pub marks: crate::stage::Marks, | |
| 1509 | + | } | |
| 1510 | + | ||
| 1511 | + | impl crate::stage::Marking for Rest { | |
| 1512 | + | /// Back, then the jumps, then forward: what a renderer draws, in order. | |
| 1513 | + | fn placed(&self) -> usize { | |
| 1514 | + | usize::from(self.back.is_some()) + self.jumps.len() + usize::from(self.forward.is_some()) | |
| 1515 | + | } | |
| 1516 | + | ||
| 1517 | + | fn mark(&mut self, mark: crate::stage::Mark) { | |
| 1518 | + | self.marks.add(mark); | |
| 1519 | + | } | |
| 1500 | 1520 | } | |
| 1501 | 1521 | ||
| 1502 | 1522 | /// One page a reader can go straight to. | |
| @@ -1560,6 +1580,7 @@ | |||
| 1560 | 1580 | forward: Some(action), | |
| 1561 | 1581 | back: None, | |
| 1562 | 1582 | jumps: Vec::new(), | |
| 1583 | + | marks: crate::stage::Marks::none(), | |
| 1563 | 1584 | } | |
| 1564 | 1585 | } | |
| 1565 | 1586 | ||
| @@ -1583,6 +1604,7 @@ | |||
| 1583 | 1604 | forward: None, | |
| 1584 | 1605 | back: None, | |
| 1585 | 1606 | jumps: Vec::new(), | |
| 1607 | + | marks: crate::stage::Marks::none(), | |
| 1586 | 1608 | } | |
| 1587 | 1609 | } | |
| 1588 | 1610 | ||
| @@ -1599,6 +1621,7 @@ | |||
| 1599 | 1621 | forward: None, | |
| 1600 | 1622 | back: None, | |
| 1601 | 1623 | jumps: Vec::new(), | |
| 1624 | + | marks: crate::stage::Marks::none(), | |
| 1602 | 1625 | } | |
| 1603 | 1626 | } | |
| 1604 | 1627 |
| @@ -580,6 +580,20 @@ | |||
| 580 | 580 | std::iter::repeat_n((), count) | |
| 581 | 581 | } | |
| 582 | 582 | ||
| 583 | + | /// Whether a two-armed site takes its first arm: `arm(id, 2) == 0`. | |
| 584 | + | /// | |
| 585 | + | /// What a guarded SETTLING setting reads. A setting that changes the markup | |
| 586 | + | /// its own member writes -- ` selected` inside an `<option>`, a class inside | |
| 587 | + | /// a tag -- is not a run of members, so it is compiled as two arms over the | |
| 588 | + | /// member instead: one built with the setting, one without. The twin needs | |
| 589 | + | /// the same answer as a bool (to decide whether to make the setting) and as | |
| 590 | + | /// an index (to say which arm it is drawing), and both come from here so | |
| 591 | + | /// the two cannot disagree. | |
| 592 | + | #[must_use] | |
| 593 | + | pub fn swap(&self, id: u16) -> bool { | |
| 594 | + | self.arm(id, 2) == 0 | |
| 595 | + | } | |
| 596 | + | ||
| 583 | 597 | /// Which arm one dispatch takes. Read by generated code. | |
| 584 | 598 | /// | |
| 585 | 599 | /// `count` is how many arms the declaration wrote, passed in so a traced |
| @@ -480,9 +480,24 @@ | |||
| 480 | 480 | out.push('>'); | |
| 481 | 481 | } | |
| 482 | 482 | ||
| 483 | - | field.with_layout(|borrowed| { | |
| 484 | - | field_html_into(&borrowed, &filling, opts, out); | |
| 485 | - | }); | |
| 483 | + | // A field's members are its options: `option` places one, and `chosen` | |
| 484 | + | // settles the one it places. Told where each landed rather than looking for | |
| 485 | + | // it, since two options with the same label are the same bytes. | |
| 486 | + | let mut marked = crate::stage::Cursor::open(&field.marks); | |
| 487 | + | if marked.watching() { | |
| 488 | + | let mut placed = Vec::new(); | |
| 489 | + | field.with_layout(|borrowed| { | |
| 490 | + | makeover_webview::form::field_html_placed(&borrowed, &filling, opts, out, &mut placed); | |
| 491 | + | }); | |
| 492 | + | for block in &placed { | |
| 493 | + | marked.wrote(block.start, block.end); | |
| 494 | + | } | |
| 495 | + | marked.close(&field.marks); | |
| 496 | + | } else { | |
| 497 | + | field.with_layout(|borrowed| { | |
| 498 | + | field_html_into(&borrowed, &filling, opts, out); | |
| 499 | + | }); | |
| 500 | + | } | |
| 486 | 501 | ||
| 487 | 502 | if writes.is_some() || asked { | |
| 488 | 503 | out.push_str("</div>"); | |
| @@ -3387,11 +3402,13 @@ | |||
| 3387 | 3402 | // are this crate's: a figure that answers a click becomes a control | |
| 3388 | 3403 | // wrapped round the emitted markup rather than a second figure emitter | |
| 3389 | 3404 | // that knows about routes. | |
| 3390 | - | Node::Stats { figures, .. } => { | |
| 3405 | + | Node::Stats { figures, marks } => { | |
| 3391 | 3406 | out.push_str("<div"); | |
| 3392 | 3407 | class_attr(&["figures"], opts, out); | |
| 3393 | 3408 | out.push('>'); | |
| 3409 | + | let mut marked = crate::stage::Cursor::open(marks); | |
| 3394 | 3410 | for (figure, action) in figures { | |
| 3411 | + | marked.starts(out); | |
| 3395 | 3412 | match action { | |
| 3396 | 3413 | Some(action) => { | |
| 3397 | 3414 | let (open, close) = control_tag(action); | |
| @@ -3404,7 +3421,9 @@ | |||
| 3404 | 3421 | } | |
| 3405 | 3422 | None => figure_html_into(&figure.as_layout(), opts, out), | |
| 3406 | 3423 | } | |
| 3424 | + | marked.ends(out); | |
| 3407 | 3425 | } | |
| 3426 | + | marked.close(marks); | |
| 3408 | 3427 | out.push_str("</div>"); | |
| 3409 | 3428 | } | |
| 3410 | 3429 | ||
| @@ -3988,13 +4007,22 @@ | |||
| 3988 | 4007 | class_attr(&["rest"], opts, out); | |
| 3989 | 4008 | out.push('>'); | |
| 3990 | 4009 | ||
| 4010 | + | // Back, then the jumps, then forward, matching `Rest::placed`. A pager is | |
| 4011 | + | // the container whose declaration order and render order genuinely differ: | |
| 4012 | + | // the renderer puts Prev first whatever order the declaration said its | |
| 4013 | + | // directions in, which is what `emit::pager_order` makes true rather than | |
| 4014 | + | // hoped for. | |
| 4015 | + | let mut marked = crate::stage::Cursor::open(&rest.marks); | |
| 4016 | + | ||
| 3991 | 4017 | if let Some(action) = &rest.back { | |
| 4018 | + | marked.starts(out); | |
| 3992 | 4019 | let (open, close) = control_tag(action); | |
| 3993 | 4020 | out.push_str(open); | |
| 3994 | 4021 | class_attr(&["button", "rest-previous"], opts, out); | |
| 3995 | 4022 | action_attrs(action, Fires::Click, None, None, false, out); | |
| 3996 | 4023 | out.push_str(">Prev"); | |
| 3997 | 4024 | out.push_str(close); | |
| 4025 | + | marked.ends(out); | |
| 3998 | 4026 | } | |
| 3999 | 4027 | ||
| 4000 | 4028 | if rest.jumps.is_empty() { | |
| @@ -4015,7 +4043,7 @@ | |||
| 4015 | 4043 | } | |
| 4016 | 4044 | out.push_str("</span>"); | |
| 4017 | 4045 | } else { | |
| 4018 | - | rest_strip_html(rest, opts, out); | |
| 4046 | + | rest_strip_html(rest, &mut marked, opts, out); | |
| 4019 | 4047 | } | |
| 4020 | 4048 | ||
| 4021 | 4049 | // Written out rather than shared with the Prev branch above. The class name | |
| @@ -4024,14 +4052,17 @@ | |||
| 4024 | 4052 | // scan is how the SSH-keys drift came back. The duplication is the price of | |
| 4025 | 4053 | // the check working. | |
| 4026 | 4054 | if let Some(action) = &rest.forward { | |
| 4055 | + | marked.starts(out); | |
| 4027 | 4056 | let (open, close) = control_tag(action); | |
| 4028 | 4057 | out.push_str(open); | |
| 4029 | 4058 | class_attr(&["button", "rest-next"], opts, out); | |
| 4030 | 4059 | action_attrs(action, Fires::Click, None, None, false, out); | |
| 4031 | 4060 | out.push_str(">Next"); | |
| 4032 | 4061 | out.push_str(close); | |
| 4062 | + | marked.ends(out); | |
| 4033 | 4063 | } | |
| 4034 | 4064 | ||
| 4065 | + | marked.close(&rest.marks); | |
| 4035 | 4066 | out.push_str("</div>"); | |
| 4036 | 4067 | } | |
| 4037 | 4068 | ||
| @@ -4052,17 +4083,19 @@ | |||
| 4052 | 4083 | /// a control and marks none, which is a host that windowed its pages wrong. It | |
| 4053 | 4084 | /// is worth being able to see, and it is not worth refusing to draw a list | |
| 4054 | 4085 | /// over. | |
| 4055 | - | fn rest_strip_html(rest: &Rest, opts: &Emit, out: &mut String) { | |
| 4086 | + | fn rest_strip_html(rest: &Rest, marked: &mut crate::stage::Cursor, opts: &Emit, out: &mut String) { | |
| 4056 | 4087 | out.push_str("<div"); | |
| 4057 | 4088 | class_attr(&["rest-pages"], opts, out); | |
| 4058 | 4089 | out.push('>'); | |
| 4059 | 4090 | for jump in &rest.jumps { | |
| 4091 | + | marked.starts(out); | |
| 4060 | 4092 | if jump.here { | |
| 4061 | 4093 | out.push_str("<span"); | |
| 4062 | 4094 | class_attr(&["rest-page", "rest-page-here"], opts, out); | |
| 4063 | 4095 | out.push_str(" aria-current=\"page\">"); | |
| 4064 | 4096 | let _ = write!(out, "{}", jump.page); | |
| 4065 | 4097 | out.push_str("</span>"); | |
| 4098 | + | marked.ends(out); | |
| 4066 | 4099 | continue; | |
| 4067 | 4100 | } | |
| 4068 | 4101 | let (open, close) = control_tag(&jump.action); | |
| @@ -4072,6 +4105,7 @@ | |||
| 4072 | 4105 | out.push('>'); | |
| 4073 | 4106 | let _ = write!(out, "{}", jump.page); | |
| 4074 | 4107 | out.push_str(close); | |
| 4108 | + | marked.ends(out); | |
| 4075 | 4109 | } | |
| 4076 | 4110 | out.push_str("</div>"); | |
| 4077 | 4111 | } |