New Syntax `function{...}` for script mode?

I have created several sites using Typst. Ultimately I think Typst is a good SSG tool. There is just one thing that has been bugging me, even when I write ordinary documents.

Consider this example:

#page[
  #title[hakimi]
  #foo #bar #baz
  Aay bee see dee ee eff gi, ache I jay kay el em en oh pea
]

#page([
  #title([hakimi])
  #foo #bar #baz
  Aay bee see dee ee eff gi, ache I jay kay el em en oh pea
])

Here I am using [] directly after each function call, and the content inserted in the [] block becomes normal markup. Though sometimes I do this

#html.html({
  import html: *
  head({
    meta(...)
    meta(...)
    link(...)
    title(...)
  })
  body({
    article({
      include "foo.typ"
    })
  })
})

This is valid yet verbose. Every single function call needs to have two layers of brackets, round and curly. Though in theory I could’ve just used html[] and # to address functions (and that is not a problem for most html typed elems since most of them accept content anyways), but sometimes I wonder if there could be a syntax looking like this to eliminate most of the verbosity:

#html.html {
  import html: *
  head {
    meta(...)
    meta(...)
    link(...)
    title(...)
  } 
  body {
    article {
      include "foo.typ"
    }
  }
}

I imagined that this might also be useful in other scenarios. Right now I could only think of it being used in html generation.

I like this idea. It extends the concept that exists with []s. I guess it could be extended further so that all modes are included:

  • Markup: #strong[content]
  • Code: #html.html{/*code*/}
  • Math: #math.display$a + b$

Now that I’ve written it out math mode seems odd. Maybe because $ is not a bracket so I’m not used to it encapsulating things.

This would be nice but is impossible for the parser to cope with in the case of the condition of if/while or the collection of `for

for i in range{1}{(a) => a}{
   some content
}

should equal the some content, but hopefully this makes the point that it’s utterly impossible for the parser to cope.

I think you’re right about parsing, I can’t figure out what the example is supposed to do :rofl:.

This doesn’t compile - range doesn’t take a function like this

#for i in range(1, (a) => a){
   str(i)
}

This does compile but I think could be represented in the proposed syntax

//Current
#for i in range(1).map((a) => a){
   str(i)
}
//New
#for i in range{1}.map(a => a){
   str(i)
}

Parsing with so many bracket options would certainly be more difficult (impossible included as “more difficult”). Not only for compiling the document but also for human readability and for syntax highlighting.

How about this?

#html.html(
  import html: *
  head(
    meta(...)
    meta(...)
    link(...)
    title(...)
  )
  body(
    article(
      include "foo.typ"
    )
  )
)
(for i in (range{1}) {
  (a) => a
}){
   some content
}

is what I had in mind, but you get the point

Assuming the remaining brace is a mistake, you could do this — giving it the same syntax/linebreak semantics as blocks already have.
But this is still very likely a breaking change.
It would also lead to confusing errors where a missing comma between arguments on different lines is misinterpreted as a single argument.

On the other hand, there’s been talk of fully dropping use of + as the join operator in favor of always using ;. If that were done then ; would need to be valid in more contexts because there should be a join operator that can be used in expressions not just blocks.
But this wouldn’t really solve the problem since you still probably wouldn’t be allowed to use let and import.

1 Like