Merge bea97e1f8b3e20bb5ffd82780eea09c0f4c7d4dc into 82da96ed957a68017e092e2606226b45c34324f1

This commit is contained in:
Malo 2025-06-10 11:27:15 +02:00 committed by GitHub
commit c747c2f9db
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 159 additions and 141 deletions

View File

@ -142,12 +142,10 @@ pub struct BibliographyElem {
pub style: Derived<CslSource, CslStyle>, pub style: Derived<CslSource, CslStyle>,
/// The language setting where the bibliography is. /// The language setting where the bibliography is.
#[internal]
#[synthesized] #[synthesized]
pub lang: Lang, pub lang: Lang,
/// The region setting where the bibliography is. /// The region setting where the bibliography is.
#[internal]
#[synthesized] #[synthesized]
pub region: Option<Region>, pub region: Option<Region>,
} }

View File

@ -110,12 +110,10 @@ pub struct CiteElem {
pub style: Smart<Derived<CslSource, CslStyle>>, pub style: Smart<Derived<CslSource, CslStyle>>,
/// The text language setting where the citation is. /// The text language setting where the citation is.
#[internal]
#[synthesized] #[synthesized]
pub lang: Lang, pub lang: Lang,
/// The text region setting where the citation is. /// The text region setting where the citation is.
#[internal]
#[synthesized] #[synthesized]
pub region: Option<Region>, pub region: Option<Region>,
} }

View File

@ -7,6 +7,7 @@ use typst_utils::NonZeroExt;
use crate::diag::{bail, SourceResult}; use crate::diag::{bail, SourceResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::func;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector, cast, elem, scope, select_where, Content, Element, NativeElement, Packed, Selector,
Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem,
@ -241,7 +242,7 @@ pub struct FigureElem {
/// These are the counters you'll need to modify if you want to skip a /// These are the counters you'll need to modify if you want to skip a
/// number or reset the counter. /// number or reset the counter.
#[synthesized] #[synthesized]
pub counter: Option<Counter>, pub counter: Counter,
} }
#[scope] #[scope]
@ -315,15 +316,21 @@ impl Synthesize for Packed<FigureElem> {
if let Some(caption) = &mut caption { if let Some(caption) = &mut caption {
caption.synthesize(engine, styles)?; caption.synthesize(engine, styles)?;
caption.push_kind(kind.clone()); caption.push_kind(kind.clone());
caption.push_supplement(supplement.clone()); if let Some(supplement) = supplement.clone() {
caption.push_numbering(numbering.clone()); caption.push_supplement(supplement);
caption.push_counter(Some(counter.clone())); }
if let Some(numbering) = numbering.clone() {
caption.push_numbering(numbering);
}
caption.push_counter(counter.clone());
if let Some(location) = location {
caption.push_figure_location(location); caption.push_figure_location(location);
} }
}
elem.push_kind(Smart::Custom(kind)); elem.push_kind(Smart::Custom(kind));
elem.push_supplement(Smart::Custom(supplement.map(Supplement::Content))); elem.push_supplement(Smart::Custom(supplement.map(Supplement::Content)));
elem.push_counter(Some(counter)); elem.push_counter(counter);
elem.push_caption(caption); elem.push_caption(caption);
Ok(()) Ok(())
@ -423,7 +430,6 @@ impl Refable for Packed<FigureElem> {
(**self) (**self)
.counter() .counter()
.cloned() .cloned()
.flatten()
.unwrap_or_else(|| Counter::of(FigureElem::elem())) .unwrap_or_else(|| Counter::of(FigureElem::elem()))
} }
@ -460,20 +466,24 @@ impl Outlinable for Packed<FigureElem> {
/// customize the appearance of captions for all figures or figures of a /// customize the appearance of captions for all figures or figures of a
/// specific kind. /// specific kind.
/// ///
/// In addition to its `position` and `body`, the `caption` also provides the /// In addition to its `position` and `body`, it is possible to retrieve the
/// figure's `kind`, `supplement`, `counter`, and `numbering` as fields. These /// corresponding figure's `kind`, `supplement`, `counter`, and `numbering` by
/// parts can be used in [`where`]($function.where) selectors and show rules to /// using the corresponding methods. These parts can be used in show rules to
/// build a completely custom caption. /// build a completely custom caption.
/// ///
/// ```example /// ```example
/// #show figure.caption: emph /// #show figure.caption: it => [
/// #underline(it.body) |
/// #it.supplement()
/// #context it.counter().display(it.numbering())
/// ]
/// ///
/// #figure( /// #figure(
/// rect[Hello], /// rect[Hello],
/// caption: [A rectangle], /// caption: [A rectangle],
/// ) /// )
/// ``` /// ```
#[elem(name = "caption", Synthesize, Show)] #[elem(scope, name = "caption", Synthesize, Show)]
pub struct FigureCaption { pub struct FigureCaption {
/// The caption's position in the figure. Either `{top}` or `{bottom}`. /// The caption's position in the figure. Either `{top}` or `{bottom}`.
/// ///
@ -519,22 +529,6 @@ pub struct FigureCaption {
pub separator: Smart<Content>, pub separator: Smart<Content>,
/// The caption's body. /// The caption's body.
///
/// Can be used alongside `kind`, `supplement`, `counter`, `numbering`, and
/// `location` to completely customize the caption.
///
/// ```example
/// #show figure.caption: it => [
/// #underline(it.body) |
/// #it.supplement
/// #context it.counter.display(it.numbering)
/// ]
///
/// #figure(
/// rect[Hello],
/// caption: [A rectangle],
/// )
/// ```
#[required] #[required]
pub body: Content, pub body: Content,
@ -544,20 +538,71 @@ pub struct FigureCaption {
/// The figure's supplement. /// The figure's supplement.
#[synthesized] #[synthesized]
pub supplement: Option<Content>, pub supplement: Content,
/// How to number the figure. /// How to number the figure.
#[synthesized] #[synthesized]
pub numbering: Option<Numbering>, pub numbering: Numbering,
/// The counter for the figure. /// The counter for the figure.
#[synthesized] #[synthesized]
pub counter: Option<Counter>, pub counter: Counter,
/// The figure's location. /// The figure's location.
#[internal]
#[synthesized] #[synthesized]
pub figure_location: Option<Location>, pub figure_location: Location,
}
#[scope]
impl FigureCaption {
/// The kind of the corresponding figure.
///
/// This returns `{none}` in case the corresponding figure does not appear
/// in the document.
#[func]
pub fn kind_(self) -> Option<FigureKind> {
self.kind
}
/// The supplement of the corresponding figure.
///
/// This returns `{none}` in case the corresponding figure does not appear
/// in the document.
#[func]
pub fn supplement_(self) -> Option<Content> {
self.supplement
}
/// The numbering of the corresponding figure.
///
/// This returns `{none}` in case the corresponding figure does not appear
/// in the document.
#[func]
pub fn numbering_(self) -> Option<Numbering> {
self.numbering
}
/// The counter for the corresponding figure:
/// `{counter(figure.where(kind: kind))}` for a figure of kind `{kind}`.
///
/// For example, for a table figure, this returns
/// `{counter(figure.where(kind: table))}`.
///
/// This returns `{none}` in case the corresponding figure does not appear
/// in the document, or it is not numbered.
#[func]
pub fn counter_(self) -> Option<Counter> {
self.counter
}
/// The location of the corresponding figure.
///
/// This returns `{none}` in case the corresponding figure does not appear
/// in the document.
#[func]
pub fn figure_location_(self) -> Option<Location> {
self.figure_location
}
} }
impl FigureCaption { impl FigureCaption {
@ -595,12 +640,7 @@ impl Show for Packed<FigureCaption> {
fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, engine: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let mut realized = self.body.clone(); let mut realized = self.body.clone();
if let ( if let (Some(mut supplement), Some(numbering), Some(counter), Some(location)) = (
Some(Some(mut supplement)),
Some(Some(numbering)),
Some(Some(counter)),
Some(Some(location)),
) = (
self.supplement().cloned(), self.supplement().cloned(),
self.numbering(), self.numbering(),
self.counter(), self.counter(),

View File

@ -1,8 +1,9 @@
use comemo::Track; use comemo::Track;
use ecow::eco_format; use ecow::eco_format;
use crate::diag::{bail, At, Hint, SourceResult}; use crate::diag::{bail, At, Hint, SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::func;
use crate::foundations::{ use crate::foundations::{
cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed, cast, elem, Cast, Content, Context, Func, IntoValue, Label, NativeElement, Packed,
Show, Smart, StyleChain, Synthesize, Show, Smart, StyleChain, Synthesize,
@ -13,6 +14,7 @@ use crate::model::{
BibliographyElem, CiteElem, Destination, Figurable, FootnoteElem, Numbering, BibliographyElem, CiteElem, Destination, Figurable, FootnoteElem, Numbering,
}; };
use crate::text::TextElem; use crate::text::TextElem;
use typst_macros::scope;
/// A reference to a label or bibliography. /// A reference to a label or bibliography.
/// ///
@ -90,7 +92,7 @@ use crate::text::TextElem;
/// ///
/// #show ref: it => { /// #show ref: it => {
/// let eq = math.equation /// let eq = math.equation
/// let el = it.element /// let el = it.element()
/// if el != none and el.func() == eq { /// if el != none and el.func() == eq {
/// // Override equation references. /// // Override equation references.
/// link(el.location(),numbering( /// link(el.location(),numbering(
@ -107,7 +109,7 @@ use crate::text::TextElem;
/// In @beginning we prove @pythagoras. /// In @beginning we prove @pythagoras.
/// $ a^2 + b^2 = c^2 $ <pythagoras> /// $ a^2 + b^2 = c^2 $ <pythagoras>
/// ``` /// ```
#[elem(title = "Reference", Synthesize, Locatable, Show)] #[elem(scope, title = "Reference", Locatable, Show)]
pub struct RefElem { pub struct RefElem {
/// The target label that should be referenced. /// The target label that should be referenced.
/// ///
@ -161,37 +163,6 @@ pub struct RefElem {
/// ``` /// ```
#[default(RefForm::Normal)] #[default(RefForm::Normal)]
pub form: RefForm, pub form: RefForm,
/// A synthesized citation.
#[synthesized]
pub citation: Option<Packed<CiteElem>>,
/// The referenced element.
#[synthesized]
pub element: Option<Content>,
}
impl Synthesize for Packed<RefElem> {
fn synthesize(
&mut self,
engine: &mut Engine,
styles: StyleChain,
) -> SourceResult<()> {
let citation = to_citation(self, engine, styles)?;
let elem = self.as_mut();
elem.push_citation(Some(citation));
elem.push_element(None);
if !BibliographyElem::has(engine, elem.target) {
if let Ok(found) = engine.introspector.query_label(elem.target).cloned() {
elem.push_element(Some(found));
return Ok(());
}
}
Ok(())
}
} }
impl Show for Packed<RefElem> { impl Show for Packed<RefElem> {
@ -334,6 +305,25 @@ fn to_citation(
Ok(elem) Ok(elem)
} }
#[scope]
impl RefElem {
/// The referenced element.
///
/// If this is a reference to a bibliography entry, this returns `{none}`.
#[func]
pub fn element(self, engine: &mut Engine) -> StrResult<Option<Content>> {
if BibliographyElem::has(engine, self.target) {
return Ok(None);
}
engine.introspector.query_label(self.target).cloned().map(Some)
}
}
cast! {
RefElem,
v: Content => v.unpack::<Self>().map_err(|_| "expected reference")?
}
/// Additional content for a reference. /// Additional content for a reference.
#[derive(Debug, Clone, PartialEq, Hash)] #[derive(Debug, Clone, PartialEq, Hash)]
pub enum Supplement { pub enum Supplement {

View File

@ -6,6 +6,7 @@ use comemo::Tracked;
use ecow::{eco_format, EcoString, EcoVec}; use ecow::{eco_format, EcoString, EcoVec};
use syntect::highlighting as synt; use syntect::highlighting as synt;
use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder}; use syntect::parsing::{SyntaxDefinition, SyntaxSet, SyntaxSetBuilder};
use typst_macros::func;
use typst_syntax::{split_newlines, LinkedNode, Span, Spanned}; use typst_syntax::{split_newlines, LinkedNode, Span, Spanned};
use typst_utils::ManuallyHash; use typst_utils::ManuallyHash;
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
@ -14,8 +15,8 @@ use super::Lang;
use crate::diag::{At, FileError, SourceResult, StrResult}; use crate::diag::{At, FileError, SourceResult, StrResult};
use crate::engine::Engine; use crate::engine::Engine;
use crate::foundations::{ use crate::foundations::{
cast, elem, scope, Bytes, Content, Derived, NativeElement, OneOrMultiple, Packed, cast, elem, scope, Bytes, Content, Context, Derived, NativeElement, OneOrMultiple,
PlainText, Show, ShowSet, Smart, StyleChain, Styles, Synthesize, TargetElem, Packed, PlainText, Show, ShowSet, Smart, StyleChain, Styles, TargetElem,
}; };
use crate::html::{tag, HtmlElem}; use crate::html::{tag, HtmlElem};
use crate::layout::{BlockBody, BlockElem, Em, HAlignment}; use crate::layout::{BlockBody, BlockElem, Em, HAlignment};
@ -72,16 +73,7 @@ use crate::World;
/// needed, start the text with a single space (which will be trimmed) or use /// needed, start the text with a single space (which will be trimmed) or use
/// the single backtick syntax. If your text should start or end with a /// the single backtick syntax. If your text should start or end with a
/// backtick, put a space before or after it (it will be trimmed). /// backtick, put a space before or after it (it will be trimmed).
#[elem( #[elem(scope, title = "Raw Text / Code", Show, ShowSet, LocalName, Figurable, PlainText)]
scope,
title = "Raw Text / Code",
Synthesize,
Show,
ShowSet,
LocalName,
Figurable,
PlainText
)]
pub struct RawElem { pub struct RawElem {
/// The raw text. /// The raw text.
/// ///
@ -265,19 +257,27 @@ pub struct RawElem {
/// ```` /// ````
#[default(2)] #[default(2)]
pub tab_size: usize, pub tab_size: usize,
/// The stylized lines of raw text.
///
/// Made accessible for the [`raw.line` element]($raw.line).
/// Allows more styling control in `show` rules.
#[synthesized]
pub lines: Vec<Packed<RawLine>>,
} }
#[scope] #[scope]
impl RawElem { impl RawElem {
#[elem] #[elem]
type RawLine; type RawLine;
/// The stylized lines of raw text.
#[func(contextual)]
pub fn lines(
self,
context: Tracked<Context>,
span: Span,
) -> SourceResult<Vec<Packed<RawLine>>> {
Ok(self.highlight(context.styles().at(span)?, span))
}
}
cast! {
RawElem,
v: Content => v.unpack::<Self>().map_err(|_| "expected raw text / code")?
} }
impl RawElem { impl RawElem {
@ -299,24 +299,13 @@ impl RawElem {
]) ])
.collect() .collect()
} }
}
impl Synthesize for Packed<RawElem> {
fn synthesize(&mut self, _: &mut Engine, styles: StyleChain) -> SourceResult<()> {
let seq = self.highlight(styles);
self.push_lines(seq);
Ok(())
}
}
impl Packed<RawElem> {
#[comemo::memoize] #[comemo::memoize]
fn highlight(&self, styles: StyleChain) -> Vec<Packed<RawLine>> { fn highlight(&self, styles: StyleChain, span: Span) -> Vec<Packed<RawLine>> {
let elem = self.as_ref(); let lines = preprocess(&self.text, styles, span);
let lines = preprocess(&elem.text, styles, self.span());
let count = lines.len() as i64; let count = lines.len() as i64;
let lang = elem let lang = self
.lang(styles) .lang(styles)
.as_ref() .as_ref()
.as_ref() .as_ref()
@ -335,8 +324,8 @@ impl Packed<RawElem> {
}) })
}; };
let syntaxes = LazyCell::new(|| elem.syntaxes(styles)); let syntaxes = LazyCell::new(|| self.syntaxes(styles));
let theme: &synt::Theme = match elem.theme(styles) { let theme: &synt::Theme = match self.theme(styles) {
Smart::Auto => &RAW_THEME, Smart::Auto => &RAW_THEME,
Smart::Custom(Some(theme)) => theme.derived.get(), Smart::Custom(Some(theme)) => theme.derived.get(),
Smart::Custom(None) => return non_highlighted_result(lines).collect(), Smart::Custom(None) => return non_highlighted_result(lines).collect(),
@ -432,7 +421,7 @@ impl Packed<RawElem> {
impl Show for Packed<RawElem> { impl Show for Packed<RawElem> {
#[typst_macros::time(name = "raw", span = self.span())] #[typst_macros::time(name = "raw", span = self.span())]
fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> { fn show(&self, _: &mut Engine, styles: StyleChain) -> SourceResult<Content> {
let lines = self.lines().map(|v| v.as_slice()).unwrap_or_default(); let lines = self.as_ref().highlight(styles, self.span());
let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1)); let mut seq = EcoVec::with_capacity((2 * lines.len()).saturating_sub(1));
for (i, line) in lines.iter().enumerate() { for (i, line) in lines.iter().enumerate() {

View File

@ -78,9 +78,7 @@ impl Elem {
/// Fields that show up in the documentation. /// Fields that show up in the documentation.
fn doc_fields(&self) -> impl Iterator<Item = &Field> + Clone { fn doc_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.fields self.fields.iter().filter(|field| !field.internal)
.iter()
.filter(|field| !field.internal && !field.synthesized)
} }
/// Fields that are relevant for `Construct` impl. /// Fields that are relevant for `Construct` impl.
@ -89,9 +87,8 @@ impl Elem {
/// because it's a pattern used a lot for parsing data from the input and /// because it's a pattern used a lot for parsing data from the input and
/// then storing it in a field. /// then storing it in a field.
fn construct_fields(&self) -> impl Iterator<Item = &Field> + Clone { fn construct_fields(&self) -> impl Iterator<Item = &Field> + Clone {
self.real_fields().filter(|field| { self.real_fields()
field.parse.is_some() || (!field.synthesized && !field.internal) .filter(|field| field.parse.is_some() || !field.internal)
})
} }
/// Fields that can be configured with set rules. /// Fields that can be configured with set rules.
@ -242,6 +239,8 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
let variadic = has_attr(&mut attrs, "variadic"); let variadic = has_attr(&mut attrs, "variadic");
let required = has_attr(&mut attrs, "required") || variadic; let required = has_attr(&mut attrs, "required") || variadic;
let positional = has_attr(&mut attrs, "positional") || required; let positional = has_attr(&mut attrs, "positional") || required;
let synthesized = has_attr(&mut attrs, "synthesized");
let internal = has_attr(&mut attrs, "internal") || synthesized;
let mut field = Field { let mut field = Field {
ident: ident.clone(), ident: ident.clone(),
@ -261,11 +260,11 @@ fn parse_field(field: &syn::Field) -> Result<Field> {
variadic, variadic,
resolve: has_attr(&mut attrs, "resolve"), resolve: has_attr(&mut attrs, "resolve"),
fold: has_attr(&mut attrs, "fold"), fold: has_attr(&mut attrs, "fold"),
internal: has_attr(&mut attrs, "internal"), internal,
external: has_attr(&mut attrs, "external"), external: has_attr(&mut attrs, "external"),
borrowed: has_attr(&mut attrs, "borrowed"), borrowed: has_attr(&mut attrs, "borrowed"),
ghost: has_attr(&mut attrs, "ghost"), ghost: has_attr(&mut attrs, "ghost"),
synthesized: has_attr(&mut attrs, "synthesized"), synthesized,
parse: parse_attr(&mut attrs, "parse")?.flatten(), parse: parse_attr(&mut attrs, "parse")?.flatten(),
default: parse_attr::<syn::Expr>(&mut attrs, "default")?.flatten(), default: parse_attr::<syn::Expr>(&mut attrs, "default")?.flatten(),
}; };

View File

@ -201,7 +201,9 @@ pub fn ty(stream: BoundaryStream, item: BoundaryStream) -> BoundaryStream {
/// flexibility. /// flexibility.
/// - `#[synthesized]`: The field cannot be specified in a constructor or set /// - `#[synthesized]`: The field cannot be specified in a constructor or set
/// rule. Instead, it is added to an element before its show rule runs /// rule. Instead, it is added to an element before its show rule runs
/// through the `Synthesize` trait. /// through the `Synthesize` trait. This implies `#[internal]`. If a
/// synthesized field needs to be exposed to the user, that should be done via
/// a getter method.
/// - `#[ghost]`: Allows creating fields that are only present in the style chain, /// - `#[ghost]`: Allows creating fields that are only present in the style chain,
/// this means that they *cannot* be accessed by the user, they cannot be set /// this means that they *cannot* be accessed by the user, they cannot be set
/// on an individual instantiated element, and must be set via the style chain. /// on an individual instantiated element, and must be set via the style chain.

View File

@ -58,8 +58,7 @@ $x$$y$
--- issue-2821-missing-fields --- --- issue-2821-missing-fields ---
// Issue #2821: Setting a figure's supplement to none removes the field // Issue #2821: Setting a figure's supplement to none removes the field
#show figure.caption: it => { #show figure.caption: it => {
assert(it.has("supplement")) assert(it.supplement() == none)
assert(it.supplement == none)
} }
#figure([], caption: [], supplement: none) #figure([], caption: [], supplement: none)

View File

@ -97,7 +97,7 @@ We can clearly see that @fig-cylinder and
if not it.numbering == none { if not it.numbering == none {
title = it.supplement title = it.supplement
if not it.numbering == none { if not it.numbering == none {
title += " " + it.counter.display(it.numbering) title += " " + counter(figure.where(kind: it.kind)).display(it.numbering)
} }
} }
title = strong(title) title = strong(title)
@ -168,7 +168,10 @@ We can clearly see that @fig-cylinder and
--- figure-caption-where-selector --- --- figure-caption-where-selector ---
// Test figure.caption element for specific figure kinds // Test figure.caption element for specific figure kinds
#show figure.caption.where(kind: table): underline #show figure.where(kind: table): it => {
show figure.caption: underline
it
}
#figure( #figure(
[Not a table], [Not a table],
@ -212,8 +215,8 @@ We can clearly see that @fig-cylinder and
#show figure.caption: it => emph[ #show figure.caption: it => emph[
#it.body #it.body
(#it.supplement (#it.supplement()
#context it.counter.display(it.numbering)) #context it.counter().display(it.numbering()))
] ]
#figure( #figure(

View File

@ -513,7 +513,7 @@ fn main() {
--- raw-line-alternating-fill --- --- raw-line-alternating-fill ---
#set page(width: 200pt) #set page(width: 200pt)
#show raw: it => stack(dir: ttb, ..it.lines) #show raw: it => stack(dir: ttb, ..it.lines())
#show raw.line: it => { #show raw.line: it => {
box( box(
width: 100%, width: 100%,
@ -564,21 +564,21 @@ print(y)
// Test line extraction works. // Test line extraction works.
#show raw: code => { #show raw: code => {
for i in code.lines { for i in code.lines() {
test(i.count, 10) test(i.count, 10)
} }
test(code.lines.at(0).text, "import numpy as np") test(code.lines().at(0).text, "import numpy as np")
test(code.lines.at(1).text, "") test(code.lines().at(1).text, "")
test(code.lines.at(2).text, "def f(x):") test(code.lines().at(2).text, "def f(x):")
test(code.lines.at(3).text, " return x**2") test(code.lines().at(3).text, " return x**2")
test(code.lines.at(4).text, "") test(code.lines().at(4).text, "")
test(code.lines.at(5).text, "x = np.linspace(0, 10, 100)") test(code.lines().at(5).text, "x = np.linspace(0, 10, 100)")
test(code.lines.at(6).text, "y = f(x)") test(code.lines().at(6).text, "y = f(x)")
test(code.lines.at(7).text, "") test(code.lines().at(7).text, "")
test(code.lines.at(8).text, "print(x)") test(code.lines().at(8).text, "print(x)")
test(code.lines.at(9).text, "print(y)") test(code.lines().at(9).text, "print(y)")
test(code.lines.at(10, default: none), none) test(code.lines().at(10, default: none), none)
} }
```py ```py