summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/exec/context.rs211
-rw-r--r--src/layout/par.rs3
-rw-r--r--src/library/align.rs2
-rw-r--r--src/library/image.rs2
-rw-r--r--src/library/lang.rs2
-rw-r--r--src/library/markup.rs10
-rw-r--r--src/library/pad.rs2
-rw-r--r--src/library/page.rs6
-rw-r--r--src/library/par.rs2
-rw-r--r--src/library/shapes.rs8
-rw-r--r--src/library/spacing.rs2
-rw-r--r--tests/ref/control/for.pngbin2715 -> 2700 bytes
-rw-r--r--tests/ref/markup/heading.pngbin5177 -> 5187 bytes
-rw-r--r--tests/ref/markup/linebreak.pngbin4292 -> 3574 bytes
-rw-r--r--tests/ref/markup/nbsp.pngbin1769 -> 1664 bytes
-rw-r--r--tests/typ/control/for.typ4
-rw-r--r--tests/typ/expand.typ2
-rw-r--r--tests/typ/expr/call.typ2
-rw-r--r--tests/typ/library/font.typ2
-rw-r--r--tests/typ/markup/linebreak.typ14
-rw-r--r--tests/typ/markup/nbsp.typ3
-rw-r--r--tests/typ/repr.typ12
-rw-r--r--tests/typ/spacing.typ8
23 files changed, 147 insertions, 150 deletions
diff --git a/src/exec/context.rs b/src/exec/context.rs
index 333ad3ba..6101047e 100644
--- a/src/exec/context.rs
+++ b/src/exec/context.rs
@@ -65,87 +65,66 @@ impl<'a> ExecContext<'a> {
mem::replace(&mut self.stack, stack).build()
}
+ /// Push any node into the active paragraph.
+ pub fn push(&mut self, node: impl Into<AnyNode>) {
+ let align = self.state.aligns.cross;
+ self.stack.par.push(ParChild::Any(node.into(), align));
+ }
+
+ /// Push a word space into the active paragraph.
+ pub fn push_word_space(&mut self) {
+ let em = self.state.font.resolve_size();
+ let amount = self.state.par.word_spacing.resolve(em);
+ self.stack.par.push_soft(ParChild::Spacing(amount));
+ }
+
/// Push text into the active paragraph.
///
/// The text is split into lines at newlines.
pub fn push_text(&mut self, text: &str) {
let mut scanner = Scanner::new(text);
- let mut line = String::new();
- let push = |this: &mut Self, text| {
- let props = this.state.font.resolve_props();
- let node = TextNode { text, props };
- let align = this.state.aligns.cross;
- this.stack.par.folder.push(ParChild::Text(node, align))
- };
+ let mut text = String::new();
while let Some(c) = scanner.eat_merging_crlf() {
if is_newline(c) {
- push(self, mem::take(&mut line));
- self.push_linebreak();
+ self.stack.par.push_text(mem::take(&mut text), &self.state);
+ self.linebreak();
} else {
- line.push(c);
+ text.push(c);
}
}
- push(self, line);
- }
-
- /// Push a word space.
- pub fn push_word_space(&mut self) {
- let em = self.state.font.resolve_size();
- let amount = self.state.par.word_spacing.resolve(em);
- self.push_spacing(GenAxis::Cross, amount, 1);
- }
-
- /// Apply a forced line break.
- pub fn push_linebreak(&mut self) {
- let em = self.state.font.resolve_size();
- let amount = self.state.par.leading.resolve(em);
- self.push_spacing(GenAxis::Main, amount, 2);
- }
-
- /// Apply a forced paragraph break.
- pub fn push_parbreak(&mut self) {
- let em = self.state.font.resolve_size();
- let amount = self.state.par.spacing.resolve(em);
- self.push_spacing(GenAxis::Main, amount, 1);
+ self.stack.par.push_text(text, &self.state);
}
/// Push spacing into paragraph or stack depending on `axis`.
- ///
- /// The `softness` configures how the spacing interacts with surrounding
- /// spacing.
- pub fn push_spacing(&mut self, axis: GenAxis, amount: Length, softness: u8) {
+ pub fn push_spacing(&mut self, axis: GenAxis, amount: Length) {
match axis {
GenAxis::Main => {
- let spacing = StackChild::Spacing(amount);
- self.stack.finish_par(&self.state);
- self.stack.folder.push_soft(spacing, softness);
+ self.stack.parbreak(&self.state);
+ self.stack.push_hard(StackChild::Spacing(amount));
}
GenAxis::Cross => {
- let spacing = ParChild::Spacing(amount);
- self.stack.par.folder.push_soft(spacing, softness);
+ self.stack.par.push_hard(ParChild::Spacing(amount));
}
}
}
- /// Push any node into the active paragraph.
- pub fn push_into_par(&mut self, node: impl Into<AnyNode>) {
- let align = self.state.aligns.cross;
- self.stack.par.folder.push(ParChild::Any(node.into(), align));
+ /// Apply a forced line break.
+ pub fn linebreak(&mut self) {
+ self.stack.par.push_hard(ParChild::Linebreak);
}
- /// Push any node directly into the stack of paragraphs.
- ///
- /// This finishes the active paragraph and starts a new one.
- pub fn push_into_stack(&mut self, node: impl Into<AnyNode>) {
- let aligns = self.state.aligns;
- self.stack.finish_par(&self.state);
- self.stack.folder.push(StackChild::Any(node.into(), aligns));
+ /// Apply a forced paragraph break.
+ pub fn parbreak(&mut self) {
+ let em = self.state.font.resolve_size();
+ let amount = self.state.par.spacing.resolve(em);
+ self.stack.parbreak(&self.state);
+ self.stack.push_soft(StackChild::Spacing(amount));
}
- /// Finish the active page.
- pub fn finish_page(&mut self, keep: bool, hard: bool, source: Span) {
+ /// Apply a forced page break.
+ pub fn pagebreak(&mut self, keep: bool, hard: bool, source: Span) {
if let Some(builder) = &mut self.page {
let page = mem::replace(builder, PageBuilder::new(&self.state, hard));
let stack = mem::replace(&mut self.stack, StackBuilder::new(&self.state));
@@ -158,7 +137,7 @@ impl<'a> ExecContext<'a> {
/// Finish execution and return the created layout tree.
pub fn finish(mut self) -> Pass<Tree> {
assert!(self.page.is_some());
- self.finish_page(true, false, Span::default());
+ self.pagebreak(true, false, Span::default());
Pass::new(self.tree, self.diags)
}
}
@@ -189,7 +168,8 @@ impl PageBuilder {
struct StackBuilder {
dirs: Gen<Dir>,
- folder: SoftFolder<StackChild>,
+ children: Vec<StackChild>,
+ last: Last<StackChild>,
par: ParBuilder,
}
@@ -197,20 +177,36 @@ impl StackBuilder {
fn new(state: &State) -> Self {
Self {
dirs: Gen::new(Dir::TTB, state.lang.dir),
- folder: SoftFolder::new(),
+ children: vec![],
+ last: Last::None,
par: ParBuilder::new(state),
}
}
- fn finish_par(&mut self, state: &State) {
+ fn push_soft(&mut self, child: StackChild) {
+ self.last.soft(child);
+ }
+
+ fn push_hard(&mut self, child: StackChild) {
+ self.last.hard();
+ self.children.push(child);
+ }
+
+ fn parbreak(&mut self, state: &State) {
let par = mem::replace(&mut self.par, ParBuilder::new(state));
- self.folder.extend(par.build());
+ if let Some(par) = par.build() {
+ self.children.extend(self.last.any());
+ self.children.push(par);
+ }
}
fn build(self) -> StackNode {
- let Self { dirs, mut folder, par } = self;
- folder.extend(par.build());
- StackNode { dirs, children: folder.finish() }
+ let Self { dirs, mut children, par, mut last } = self;
+ if let Some(par) = par.build() {
+ children.extend(last.any());
+ children.push(par);
+ }
+ StackNode { dirs, children }
}
}
@@ -218,7 +214,8 @@ struct ParBuilder {
aligns: Gen<Align>,
dir: Dir,
line_spacing: Length,
- folder: SoftFolder<ParChild>,
+ children: Vec<ParChild>,
+ last: Last<ParChild>,
}
impl ParBuilder {
@@ -228,13 +225,43 @@ impl ParBuilder {
aligns: state.aligns,
dir: state.lang.dir,
line_spacing: state.par.leading.resolve(em),
- folder: SoftFolder::new(),
+ children: vec![],
+ last: Last::None,
+ }
+ }
+
+ fn push(&mut self, child: ParChild) {
+ self.children.extend(self.last.any());
+ self.children.push(child);
+ }
+
+ fn push_text(&mut self, text: String, state: &State) {
+ self.children.extend(self.last.any());
+
+ let align = state.aligns.cross;
+ let props = state.font.resolve_props();
+
+ if let Some(ParChild::Text(prev, prev_align)) = self.children.last_mut() {
+ if *prev_align == align && prev.props == props {
+ prev.text.push_str(&text);
+ return;
+ }
}
+
+ self.children.push(ParChild::Text(TextNode { text, props }, align));
+ }
+
+ fn push_soft(&mut self, child: ParChild) {
+ self.last.soft(child);
+ }
+
+ fn push_hard(&mut self, child: ParChild) {
+ self.last.hard();
+ self.children.push(child);
}
fn build(self) -> Option<StackChild> {
- let Self { aligns, dir, line_spacing, folder } = self;
- let children = folder.finish();
+ let Self { aligns, dir, line_spacing, children, .. } = self;
(!children.is_empty()).then(|| {
let node = ParNode { dir, line_spacing, children };
StackChild::Any(node.into(), aligns)
@@ -242,54 +269,28 @@ impl ParBuilder {
}
}
-/// This is used to remove leading and trailing word/line/paragraph spacing
-/// as well as collapse sequences of spacings into just one.
-struct SoftFolder<N> {
- nodes: Vec<N>,
- last: Last<N>,
-}
-
+/// Finite state machine for spacing coalescing.
enum Last<N> {
None,
- Hard,
- Soft(N, u8),
+ Any,
+ Soft(N),
}
-impl<N> SoftFolder<N> {
- fn new() -> Self {
- Self { nodes: vec![], last: Last::Hard }
- }
-
- fn push(&mut self, node: N) {
- let last = mem::replace(&mut self.last, Last::None);
- if let Last::Soft(soft, _) = last {
- self.nodes.push(soft);
+impl<N> Last<N> {
+ fn any(&mut self) -> Option<N> {
+ match mem::replace(self, Self::Any) {
+ Self::Soft(soft) => Some(soft),
+ _ => None,
}
- self.nodes.push(node);
}
- fn push_soft(&mut self, node: N, softness: u8) {
- if softness == 0 {
- self.last = Last::Hard;
- self.nodes.push(node);
- } else {
- match self.last {
- Last::Hard => {}
- Last::Soft(_, other) if softness >= other => {}
- _ => self.last = Last::Soft(node, softness),
- }
+ fn soft(&mut self, soft: N) {
+ if let Self::Any = self {
+ *self = Self::Soft(soft);
}
}
- fn finish(self) -> Vec<N> {
- self.nodes
- }
-}
-
-impl<N> Extend<N> for SoftFolder<N> {
- fn extend<T: IntoIterator<Item = N>>(&mut self, iter: T) {
- for elem in iter {
- self.push(elem);
- }
+ fn hard(&mut self) {
+ *self = Self::None;
}
}
diff --git a/src/layout/par.rs b/src/layout/par.rs
index 02e27cbd..e0b42821 100644
--- a/src/layout/par.rs
+++ b/src/layout/par.rs
@@ -23,6 +23,8 @@ pub enum ParChild {
Text(TextNode, Align),
/// Any child node and how to align it in its line.
Any(AnyNode, Align),
+ /// A forced linebreak.
+ Linebreak,
}
/// A consecutive, styled run of text.
@@ -55,6 +57,7 @@ impl Layout for ParNode {
layouter.push_frame(frame, align);
}
}
+ ParChild::Linebreak => layouter.finish_line(),
}
}
layouter.finish()
diff --git a/src/library/align.rs b/src/library/align.rs
index d5811bf4..ccb25b34 100644
--- a/src/library/align.rs
+++ b/src/library/align.rs
@@ -52,7 +52,7 @@ pub fn align(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
if let Some(vertical) = vertical {
ctx.state.aligns.main = vertical.to_align(Dir::TTB);
if ctx.state.aligns.main != snapshot.aligns.main {
- ctx.push_linebreak();
+ ctx.parbreak();
}
}
diff --git a/src/library/image.rs b/src/library/image.rs
index 020f7d50..ed7b2268 100644
--- a/src/library/image.rs
+++ b/src/library/image.rs
@@ -23,7 +23,7 @@ pub fn image(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
let loaded = ctx.env.resources.load(&path.v, ImageResource::parse);
if let Some((res, img)) = loaded {
let dimensions = img.buf.dimensions();
- ctx.push_into_par(ImageNode { res, dimensions, width, height });
+ ctx.push(ImageNode { res, dimensions, width, height });
} else {
ctx.diag(error!(path.span, "failed to load image"));
}
diff --git a/src/library/lang.rs b/src/library/lang.rs
index 79015c7d..1248b707 100644
--- a/src/library/lang.rs
+++ b/src/library/lang.rs
@@ -32,7 +32,7 @@ pub fn lang(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
}
}
- ctx.push_parbreak();
+ ctx.parbreak();
})
}
diff --git a/src/library/markup.rs b/src/library/markup.rs
index d7d750ea..ac2356a9 100644
--- a/src/library/markup.rs
+++ b/src/library/markup.rs
@@ -14,7 +14,7 @@ use crate::syntax::{HeadingNode, RawNode};
/// A template that inserts a line break.
pub fn linebreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
Value::template(Node::LINEBREAK, move |ctx| {
- ctx.push_linebreak();
+ ctx.linebreak();
})
}
@@ -24,7 +24,7 @@ pub fn linebreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
/// A template that inserts a paragraph break.
pub fn parbreak(_: &mut EvalContext, _: &mut FuncArgs) -> Value {
Value::template(Node::PARBREAK, move |ctx| {
- ctx.push_parbreak();
+ ctx.parbreak();
})
}
@@ -118,7 +118,7 @@ pub fn heading(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
body.exec(ctx);
ctx.state = snapshot;
- ctx.push_parbreak();
+ ctx.parbreak();
})
}
@@ -155,7 +155,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
Value::template(Node::RAW, move |ctx| {
if block {
- ctx.push_parbreak();
+ ctx.parbreak();
}
let snapshot = ctx.state.clone();
@@ -164,7 +164,7 @@ pub fn raw(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
ctx.state = snapshot;
if block {
- ctx.push_parbreak();
+ ctx.parbreak();
}
})
}
diff --git a/src/library/pad.rs b/src/library/pad.rs
index d6b69007..7c422239 100644
--- a/src/library/pad.rs
+++ b/src/library/pad.rs
@@ -32,6 +32,6 @@ pub fn pad(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
Value::template("pad", move |ctx| {
let child = ctx.exec_group(&body).into();
- ctx.push_into_par(PadNode { padding, child });
+ ctx.push(PadNode { padding, child });
})
}
diff --git a/src/library/page.rs b/src/library/page.rs
index fb3542ed..7b8557be 100644
--- a/src/library/page.rs
+++ b/src/library/page.rs
@@ -83,13 +83,13 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
std::mem::swap(&mut page.size.width, &mut page.size.height);
}
- ctx.finish_page(false, true, span);
+ ctx.pagebreak(false, true, span);
if let Some(body) = &body {
// TODO: Restrict body to a single page?
body.exec(ctx);
ctx.state = snapshot;
- ctx.finish_page(true, false, span);
+ ctx.pagebreak(true, false, span);
}
})
}
@@ -101,6 +101,6 @@ pub fn page(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
pub fn pagebreak(_: &mut EvalContext, args: &mut FuncArgs) -> Value {
let span = args.span;
Value::template("pagebreak", move |ctx| {
- ctx.finish_page(true, true, span);
+ ctx.pagebreak(true, true, span);
})
}
diff --git a/src/library/par.rs b/src/library/par.rs
index cf2549bf..92fc20e8 100644
--- a/src/library/par.rs
+++ b/src/library/par.rs
@@ -27,6 +27,6 @@ pub fn par(ctx: &mut EvalContext, args: &mut FuncArgs) -> Value {
ctx.state.par.word_spacing = word_spacing;
}
- ctx.push_parbreak();
+ ctx.parbreak();
})
}
diff --git a/src/library/shapes.rs b/src/library/shapes.rs
index 6f9e6677..6c6a2f0b 100644
--- a/src/library/shapes.rs
+++ b/src/library/shapes.rs
@@ -63,13 +63,13 @@ fn rect_impl(
let node = FixedNode { width, height, aspect, child };
if let Some(color) = fill {
- ctx.push_into_par(BackgroundNode {
+ ctx.push(BackgroundNode {
shape: BackgroundShape::Rect,
fill: Fill::Color(color),
child: node.into(),
});
} else {
- ctx.push_into_par(node);
+ ctx.push(node);
}
})
}
@@ -146,13 +146,13 @@ fn ellipse_impl(
};
if let Some(color) = fill {
- ctx.push_into_par(BackgroundNode {
+ ctx.push(BackgroundNode {
shape: BackgroundShape::Ellipse,
fill: Fill::Color(color),
child: node.into(),
});
} else {
- ctx.push_into_par(node);
+ ctx.push(node);
}
})
}
diff --git a/src/library/spacing.rs b/src/library/spacing.rs
index 6a67a653..36cd88fb 100644
--- a/src/library/spacing.rs
+++ b/src/library/spacing.rs
@@ -32,7 +32,7 @@ fn spacing_impl(
Value::template(name, move |ctx| {
if let Some(linear) = spacing {
let amount = linear.resolve(ctx.state.font.resolve_size());
- ctx.push_spacing(axis, amount, 0);
+ ctx.push_spacing(axis, amount);
}
})
}
diff --git a/tests/ref/control/for.png b/tests/ref/control/for.png
index 1c7dfd42..bf6b535c 100644
--- a/tests/ref/control/for.png
+++ b/tests/ref/control/for.png
Binary files differ
diff --git a/tests/ref/markup/heading.png b/tests/ref/markup/heading.png
index a32229e7..a7de742d 100644
--- a/tests/ref/markup/heading.png
+++ b/tests/ref/markup/heading.png
Binary files differ
diff --git a/tests/ref/markup/linebreak.png b/tests/ref/markup/linebreak.png
index 512fa0f5..32236e74 100644
--- a/tests/ref/markup/linebreak.png
+++ b/tests/ref/markup/linebreak.png
Binary files differ
diff --git a/tests/ref/markup/nbsp.png b/tests/ref/markup/nbsp.png
index cc920776..8834bb2b 100644
--- a/tests/ref/markup/nbsp.png
+++ b/tests/ref/markup/nbsp.png
Binary files differ
diff --git a/tests/typ/control/for.typ b/tests/typ/control/for.typ
index 5eaa6ae8..bca1af46 100644
--- a/tests/typ/control/for.typ
+++ b/tests/typ/control/for.typ
@@ -14,8 +14,8 @@
// Dictionary is not traversed in insertion order.
// Should output `age: 1, name: Typst,`.
-#for k, v in (name: "Typst", age: 2) [
- {k}: {v}, \
+#for k, v in (Name: "Typst", Age: 2) [
+ {k}: {v}.
]
// String.
diff --git a/tests/typ/expand.typ b/tests/typ/expand.typ
index 23044744..8b858a4e 100644
--- a/tests/typ/expand.typ
+++ b/tests/typ/expand.typ
@@ -7,7 +7,7 @@
// Top-level paragraph fills page, boxed paragraph only when width is fixed.
L #right[R] \
#rect(width: 50pt)[L #right[R]] \
-#rect[L #right[R]] \
+#rect[L #right[R]]
// Pad inherits expansion behaviour.
#pad[PL #right[PR]] \
diff --git a/tests/typ/expr/call.typ b/tests/typ/expr/call.typ
index 0e941124..dcf11806 100644
--- a/tests/typ/expr/call.typ
+++ b/tests/typ/expr/call.typ
@@ -60,7 +60,7 @@
#args[a] \
#args(a) \
#args(a, [b]) \
-#args(a)[b] \
+#args(a)[b]
// Template can be argument or body depending on whitespace.
#if "template" == type[b] [Sure ]
diff --git a/tests/typ/library/font.typ b/tests/typ/library/font.typ
index 13f71125..397452e3 100644
--- a/tests/typ/library/font.typ
+++ b/tests/typ/library/font.typ
@@ -53,7 +53,7 @@ Emoji: 🐪, 🌋, 🏞
#font(sans-serif: "PT Sans")
#font(sans-serif)[Sans-serif.] \
#font(monospace)[Monospace.] \
-#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.] \
+#font(monospace, monospace: ("Nope", "Latin Modern Math"))[Math.]
---
// Ref: false
diff --git a/tests/typ/markup/linebreak.typ b/tests/typ/markup/linebreak.typ
index a0a501b6..b1b83dcc 100644
--- a/tests/typ/markup/linebreak.typ
+++ b/tests/typ/markup/linebreak.typ
@@ -10,17 +10,11 @@ Line \ Break
// Directly before word does not work.
No \Break
----
-// Leading line break.
-\ Leading
-
-// Trailing before paragraph break.
-Trailing 1 \
+\ Before
-Trailing 2
+Multiple \ \ \
-// Trailing before end of document.
-Trailing 3 \
+Times
---
#let linebreak() = [
@@ -28,4 +22,4 @@ Trailing 3 \
#circle(radius: 2pt, fill: #000) \
]
-A \ B \ C \
+A \ B \ C
diff --git a/tests/typ/markup/nbsp.typ b/tests/typ/markup/nbsp.typ
index 5af6c84f..372268eb 100644
--- a/tests/typ/markup/nbsp.typ
+++ b/tests/typ/markup/nbsp.typ
@@ -1,5 +1,4 @@
// Test the non breaking space.
---
-// Parsed correctly, but not actually doing anything at the moment.
-The non-breaking~space does not work.
+The non-breaking~space does work.
diff --git a/tests/typ/repr.typ b/tests/typ/repr.typ
index cf137745..f2404510 100644
--- a/tests/typ/repr.typ
+++ b/tests/typ/repr.typ
@@ -9,7 +9,7 @@
{name} \
{ke-bab} \
-{α} \
+{α}
// Error: 2-3 unknown variable
{_}
@@ -18,7 +18,7 @@
// Literal values.
{none} (empty) \
{true} \
-{false} \
+{false}
---
// Numerical values.
@@ -33,16 +33,16 @@
{2.5rad} \
{45deg} \
// Not in monospace via repr.
-#repr(45deg) \
+#repr(45deg)
---
// Colors.
-{#f7a20500} \
+{#f7a20500}
---
// Strings and escaping.
{"hi"} \
-{"a\n[]\"\u{1F680}string"} \
+{"a\n[]\"\u{1F680}string"}
---
// Templates.
@@ -54,4 +54,4 @@
{rect} \
{f} \
-{() => none} \
+{() => none}
diff --git a/tests/typ/spacing.typ b/tests/typ/spacing.typ
index ccaf084a..bb86a59c 100644
--- a/tests/typ/spacing.typ
+++ b/tests/typ/spacing.typ
@@ -7,7 +7,7 @@
A#let;B \
A#let x = 1;B #test(x, 1) \
A #let x = 2;B #test(x, 2) \
-A#let x = 3; B #test(x, 3) \
+A#let x = 3; B #test(x, 3)
---
// Spacing around if-else.
@@ -17,7 +17,7 @@ A#if true [B] C \
A #if true{"B"}C \
A #if true{"B"} C \
A#if false [] #else [B]C \
-A#if true [B] #else [] C \
+A#if true [B] #else [] C
---
// Spacing around while loop.
@@ -25,11 +25,11 @@ A#if true [B] #else [] C \
#let c = true; A#while c [{c = false}B]C \
#let c = true; A#while c [{c = false}B] C \
#let c = true; A #while c { c = false; "B" }C \
-#let c = true; A #while c { c = false; "B" } C \
+#let c = true; A #while c { c = false; "B" } C
---
// Spacing around for loop.
A#for _ in (none,) [B]C \
A#for _ in (none,) [B] C \
-A #for _ in (none,) {"B"}C \
+A #for _ in (none,) {"B"}C