Why am I getting an error when using a for loop to expand table arguments?

Hi,

I have created a function that I call and it works.

Now I want to create a function that takes an argument a vec of some data like this :

(
  (
key1:"value1",
key2:"value2",
...
),
//...
)

And then use typst code like that :

// Define a function
#let display_content(data) = [
  #table(
    columns: (2fr, 1fr, 2fr, 1fr),
    align: left,
    ..for elem in #data{
      (table.cell(elem.key1),
       table.cell(elem.key2),
       // ...
      )
    }
  )
]

// Then call the function in a for
#for elem in my_content {
[#call_other_function[#elem.title]] // title is a string
[#display_content[#elem.data]]  // data is a vec like in the beginning of the example.
}

But as I take my precedent function and just add a parameter, I think the problem comes with this line :

 ..for elem in #data{

I got this error using a typst-as-lib in rust :

TypstSource(
        [
            SourceDiagnostic {
                severity: Error,
                span: Span(
                    323974196639343,
                ),
                message: "expected expression",
                trace: [],
                hints: [],
            },
            SourceDiagnostic {
                severity: Error,
                span: Span(
                    323974717241437,
                ),
                message: "the character `#` is not valid in code",
                trace: [],
                hints: [],
            },
            SourceDiagnostic {
                severity: Error,
                span: Span(
                    323974849457841,
                ),
                message: "expected comma",
                trace: [],
                hints: [],
            },
        ],
    ),

As I am a beginner in typst, I try a lot of thing but really I am lost now…

Hi,

Thanks for your explanation — it sounds interesting, but it’s currently quite hard to follow what’s actually causing the issue. To better understand the problem, it would be really helpful if you could provide a Minimal Working Example (MWE): a small but complete piece of code that leads to the exact error messages you’re getting.

Ideally, include:
• the function definitions,
• the data structures,
• and the exact function calls that trigger the error.

All in one self-contained snippet, without any unrelated code or dependencies. That way, others can easily test it and give more precise feedback.

Thank you. I prepare that…

Can you please try to remove the hash in front of data? Since the for loop is written in code mode, the hash might just be the issue here?

I already test without # but I got error.

I create this example of code you can test to see the problem.
If I uncomment the part commented, it does not work anymore…

#let content = (
   ( 
     title:"my title",
     data:(
            ( key1:"my_title_key1", 
              key2:"1",
            ),
            ( key1:"my_title_key2", 
              key2:"2",
            )
          )
   ),
   ( 
     title:"my title 2",
     data:(
            ( key1:"my_title_2_key1", 
              key2:"3",
            ),
            ( key1:"my_title_2_key2", 
              key2:"4",
            )
          )
   ),
)

#let display_content(data) = [
  [#data]
  #table(
    columns: (2fr, 1fr),
    align: left,
    table.cell(
      [header1]
    ),
    table.cell(
      [header2]
    ),
    /*..for elem in #data {
      (table.cell(elem.key1),
       table.cell(elem.key2),
      )
    }*/
  )
]

#for elem in content {
 [#elem.title]
 [#display_content[#elem.data]]
}

The following code will fix your issue

#let display_content(data) = [
  [#data]
  #table(
    columns: (2fr, 1fr),
    align: left,
    table.cell(
      [header1]
    ),
    table.cell(
      [header2]
    ),
    ..for elem in data {
      (table.cell(elem.key1),
       table.cell(elem.key2),
      )
    }
  )
]

#for elem in content {
 [#elem.title]
 [#display_content(elem.data)]
}

When calling the function #display_content[#elem.data], the argument was automatically converted to a code block. Unfortunately, I have no idea why that happened. Hopefully someone with more technical knowledge about Typst can help here?

2 Likes

Thank you very much. It works now.

I am so confused.
I was thinking #call_function[arg] was the same that #call_function(arg). And I need #arg sometimes in the function to have its content and in your solution not.

I really am interessed to understand the difference.

My only guess is that this is related to the .data. You would like to pass the argument #(elem.data) but the compiler actually sees this as #(elem).data.

I was wrong here, .data has nothing to do with the conversion to a code block. See the correct explanation by @Mathemensch below.

Using

results in #elem.data being passed as content.

Functions are typically called with (), so #display_content(elem.data).

However, it’s also possible to call them (additionally) with [] - in this case, everything between the brackets is passed as content. The key difference:

  • #display_content(elem.data) passes the value of elem.data directly
  • #display_content[#elem.data] passes #elem.data as content (markup that gets evaluated)

This is for example useful when you want to style a box and then pass the body content:

#box(stroke: 1pt)[
Box content
]

This is cleaner than writing it inside the parentheses:

#box(stroke: 1pt, [
Box content
])

This trailing content block syntax is an official Typst feature. As explained in the scripting documentation, “An arbitrary number of content blocks can be passed as trailing arguments to functions. That is, list([A], [B]) is equivalent to list[A][B].” The formatting tutorial notes that since “passing content to a function is such a common thing to do in Typst, there is special syntax for it” - allowing you to write content in square brackets directly after the normal arguments for improved readability.

4 Likes

Thank you. Yes, it was this part that I read and makes me confused finally. Now it is more clear.

Thank you very much to all of you for your time.

1 Like