Skip to content

Implement multi-statement ${...}#1857

Draft
mjf wants to merge 5 commits intosilverbulletmd:mainfrom
mjf:implement-multi-statement-blocks
Draft

Implement multi-statement ${...}#1857
mjf wants to merge 5 commits intosilverbulletmd:mainfrom
mjf:implement-multi-statement-blocks

Conversation

@mjf
Copy link
Contributor

@mjf mjf commented Feb 25, 2026

Implements multi-statement ${...} expressions. The following works:

${a = 1; a}
${local a = 1; a}

but not (and should not):

${a = 1; a;}
${local a = 1; a;}

Also return works now:

${a = 1; return a}
${a = 1; return a;}

Also parenthesized expressions work:

${a = 1; local b = 2; (a + b)}

And anonymous function call works:

${(function()return 1;end)()}

And even global and local function definitions work:

${a = function(b)return 10*b;end; a(3)}
${a(5)}
${local b = function(c)return 10*a(c)end; b(3)}

Multi-line works:

${
  local t = {1,2,3}
  -- test
  table
    .includes(t, 3)
      and 'yes'
      or 'no'
}

or (non-sensical, just to demostrate):

${
  local t = {1,2,3}
  -- test
  ('!-' .. (
    (
      table
        .includes(t, 3)
          and 'yes'
          or  'no'
    )
    and 'sey'
    or  'on'
  ) .. '-!')
}

mjf added 3 commits February 25, 2026 12:22
Implements multi-statement `${...}` expressions. The following works:

```
${a = 1; a}
${local a = 1; a}
```

but not (and should not):

```
${a = 1; a;}
${local a = 1; a;}
```

Also `return` works now:

```
${a = 1; return a}
${a = 1; return a;}
```

Also parenthesized expressions work:

```
${a = 1; local b = 2; (a + b)}
```

And anonymous function call works:

```
${(function()return 1;end)()}
```

And even global and local function definitions work:

```
${a = function(b)return 10*b;end; a(3)}
${a(5)}
${local b = function(c)return 10*a(c)end; b(3)}
```

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Interpolation blocks split across multiple lines (e.g.,
`table\n.includes(...)`) failed because only the last separator was
tried as a split point.  Now all `;` and `\n` separators are collected
and tried RTL until a valid parse is found.

Additionally, trailing semicolons (e.g.  `${a(5);}`) caused implicit
return to be skipped because the last AST node was a `Semicolon` rather
than the preceding `FunctionCallStatement`. The return logic now skips
trailing semicolons to find the last statement.

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
@zefhemel
Copy link
Collaborator

What I don't understand is why this would work (haven't checked it myself, but you claim it does):

${a = function(b)return 10*b;end; a(3)}
${a(5)}`

What is the scope of a? How does it transfer between the first ${} and second one?

The reason I'm asking is that I was toying with the idea of "page variables" specifically for these Lua expressions, so you could do things like this:

${multiplier = 3}
Bladiedah: ${7 * multiplier} and ${2 * multiplier}

Then when you change multiplier to something else, it would propagate, basically give you a type of spreadsheet-like behavior.

Does this do that? Somehow?

@mjf
Copy link
Contributor Author

mjf commented Feb 25, 2026

${a = function(b)return 10*b;end; a(3)}
${a(5)}`

What is the scope of a? How does it transfer between the first ${} and second one?

a is obviously global. I didn't touch environments. local a = ... will be local (and indeed is) and without the local it's global.

The reason I'm asking is that I was toying with the idea of "page variables" specifically for these Lua expressions, so you could do things like this:

${multiplier = 3}
Bladiedah: ${7 * multiplier} and ${2 * multiplier}

Then when you change multiplier to something else, it would propagate, basically give you a type of spreadsheet-like behavior.

Does this do that? Somehow?

That's how it is supposed to work. The first assignment will return nil (which it is, but ugly, but we could allow ${...} also in frontmatter and then it won't be as ugly). And the latter expressions yield correct results.

Note: not yet, it works like that but the values don't get updated instantly, you have "run over it" to get updated values, or do reload or something. Not sure, this is not my domain, you may know. I also think there may be some issues with ordering (and I am quite sure there are). If these things were fixed then it would be splendid. I dunno how myself atm. Many things and layers are involved.

@zefhemel
Copy link
Collaborator

Right, so by default the behavior will indeed seem kind of random based on the ordering that CodeMirror decides on. To make what I envisision (page scope) work, when you live preview something, have to find all other lua directives on the page as well, and evaluate them in sequence (with a "page environment" as env, so not to leak into the global space lua global environemnt) up to the point where the currently live-previewing expression is being rendered...

@mjf
Copy link
Contributor Author

mjf commented Mar 2, 2026

I agree with the "page-scope".

mjf added 2 commits March 5, 2026 14:43
- non-local writes stop at page boundary
- reads chain to global

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
…hanges

Signed-off-by: Matouš Jan Fialka <mjf@mjf.cz>
@mjf mjf marked this pull request as draft March 5, 2026 14:42
@Baudogit
Copy link

Baudogit commented Mar 9, 2026

Not a solution. Just an illustration on what we can do with functions within a widget (you can see a more complete example here: https://github.com/Baudogit/silverbullet-libraries/blob/main/TCexport.md). But i didn't find how update instantly.

${widgets.button("Click", function() (function() if nbClics == nil then nbClics = 1 else nbClics = nbClics + 1 end multiplier = 5 * nbClics end) () js.log('nbClics = ' .. nbClics .. ' and multiplier = ' .. multiplier) end)}
${multiplier *2} = multiplier x 2
${7 * multiplier} = multiplier x 7
${widget.html("<span class='class-attribute'> <span>multiplier x 2 = " .. multiplier *2 .. " </span></span>")}

@mjf
Copy link
Contributor Author

mjf commented Mar 13, 2026

But i didn't find how update instantly.

@Baudogit Working on it. It will take it's time, sorry. The current solution is an ugly "hack" only. The part with multiline statements seems quite ok, the instant updating needs some more work and perhaps more changes elswhere.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants