Split operator issue in macro and passing $let defined variable to macro issue

Hey there!

I’m running into multiple issues that I can’t seem to wrap my head around, and would love a fresh set of eyes on them.

I have a field called casting_time in tiddlers describing magic spells.

This field holds something that usually contains a time described using syntax of the form “1s” for 1 second, “1m” for one minute, “10h” for 10 hours, etc.

Sometimes, the field also has extra information, in which case it would be written like “1s, extra information”. This extra information can have <<macro>> calls in it.

I’m writing a macro called timeparse to parse the field, in order to display it with extra styling on the portions that actually are times, and this macro uses another macro called time.

The time macro below:

\define time(value)
<$macrocall $name=entity icon=time value="$value$"/>
\end

which calls for the entity macro below to do some styling:

\define entity(icon, label, value)
<div class="mark" title="$icon$ $value$"><span class="label entity clean"><span class="icon-$icon$">$label$</span></span><span class="data entity clean">$value$</span></div>
\end

The timeparse macro I’ve written like this for now:

\define timeparse(value)
<$list filter="[[$value$]split[ ]]" variable="seg">
	<% if [<seg>regexp[\d+[smhj],]] %>
		<$let segm={{{ [<seg>removesuffix[,]] }}}>
			<$macrocall $name="time" value=<segm>/>, 
		</$let>
	<% elseif [<seg>regexp[\d+[smhj]$]] %>
		<$macrocall $name="time" value="{{{ [<seg>] }}}"/>
	<% else %>
		<<seg>>
	<% endif %>
</$list>
\end

When I call <<timeparse {{!!casting_time}}>>, there are a couple of issues in that timeparse macro:

  1. The list split[ ] operator at the very beginning of the timeparse macro doesn’t work; it keeps the whole content of the casting_time field as is, rather than splitting it by space characters.
  2. I’ve tried so many different things to zero in on bugs (using debug lines I haven’t reproduced above) that I’ve written the value parameter in different ways that are bound to be incorrect.

I won’t be able to wrap my head around the second issue until I figure out the first one, but clearer heads could very well prevail on it ;p

The first thing I see is that the value passed to regexp is wrong. You can’t use [ / ] inline that way. What happens if you externalize it like this:

\define periods() [smhj]

\define timeparse(value)
<$list filter="[[$value$]split[ ]]" variable="seg">
	<% if [<seg>regexp[\d+<periods>,]] %>

and again a few lines later

	<% elseif [<seg>regexp[\d+<periods>$]] %>

?

Also, out of curiosity: seconds, minutes, hours, and j???.

Is there any reason to use macros instead of procedures? The recommended good scripting style is to use procedures.

It doesn’t seem to work either; but that’s probably the split[ ] not working.

Before trying your substitution, I had a version of the macro with debug text inside:

<$list filter="[[$value$]split[ ]]" variable="seg">
	''(list seg:'' <<seg>>'')''
	<% if [<seg>regexp[\d+[smhd],]] %>
		''(1st if seg:'' <<seg>>'')''
		<$let segm={{{ [<seg>removesuffix[,]] }}}>
			<$macrocall $name="time" value=<<segm>>/>, ''(macro segm:'' <<segm>>'')''
		</$let>
	<% elseif [<seg>regexp[\d+[smhd]$]] %>
		<$macrocall $name="time" value="{{{ [<seg>] }}}"/> ''(macro seg:'' <<seg>>'')''
	<% else %>
		''(else seg:'' <<seg>>'')''
	<% endif %>
</$list>

And it rendered like this:


… which seemed to indicate that it entered the first conditional (which it should from the original regexp).

With your susbstitution in the regexp occurrences, the macro becomes:

\define timeparse(value)
<$list filter="[[$value$]split[ ]]" variable="seg">
	''(list seg:'' <<seg>>'')''
	<% if [<seg>regexp[\d+<periods>,]] %>
		''(1st if seg:'' <<seg>>'')''
		<$let segm={{{ [<seg>removesuffix[,]] }}}>
			<$macrocall $name="time" value=<<segm>>/>, ''(macro segm:'' <<segm>>'')''
		</$let>
	<% elseif [<seg>regexp[\d+<periods>$]] %>
		<$macrocall $name="time" value="{{{ [<seg>] }}}"/> ''(macro seg:'' <<seg>>'')''
	<% else %>
		''(else seg:'' <<seg>>'')''
	<% endif %>
</$list>
\end

… end it renders like this:

…which seems to indicated it didn’t enter any of the conditions.

Also, the split[ ] still doesn’t work, which remains as puzzling to me as ever.

That’s a mistake. It’s supposed to be a d :stuck_out_tongue_winking_eye: (I’m french and “days” is said “jours” in french, sometimes my brain mixes langages ;P)

Not really, no, aside from the fact that I don’t really know how to use procedures correctly, but been doing good so far with macros (and also the fact that I have macros everywhere, sometimes quite complex ones, by now).

I’ve read about procedures, but for some reason the documentation is confusing to me in relation to my macros habits.

Sorry, I really shouldn’t post such things without testing them.

This might work instead:

\define re1() \d+[smhd,]
\define re2() \d+[smhd]$

\procedure timeparse(value)
<$list filter="[<value>split[ ]]" variable="seg">
	<% if [<seg>regexp<re1>] %>
		<$let segm={{{ [<seg>removesuffix[,]] }}}>
			<$macrocall $name="time" value=<segm>/>, 
		</$let>
	<% elseif [<seg>regexp<re2>] %>
      <$macrocall $name="time" value="{{{ [<seg>] }}}"/>
    <% else %>
      <<seg>>
    <% endif %>
</$list>
\end

I never was good at the macro parameter handling. I find it much easier with procedures. But I’m quite sure that there is something wrong with this:

filter="[[$value$]split[ ]]

even before the split call.

The first problem is that when using the <<macroname ...>> form of macrocall you can’t transclude a field value as a parameter. It will just pass the literal text {{!!casting_time}} as the parameter value. Use this instead:

<$macrocall $name=timeparse value={{!!casting_time}}/>

The next problem is that your regexp[...] filter syntax contains square brackets as part of the regexp operand, and filter syntax doesn’t permit nested [...] sequences.

Here’s my suggested alternative that completely avoids using regexp:

\define timeparse(value)
<$let seg={{{ [<__value__>split[,]first[]] }}}>
<$let extra={{{ [<__value__>split[,]rest[]join[,]] }}}>
<$macrocall $name="time" value=<<seg>>/>
<%if [<extra>!is[blank]] %>, <<extra>><%endif%>
\end

Notes:

  • When referencing the value parameter, use <__value__> instead of [$value$]. This permits handling of values that contain literal square brackets, without breaking the filter syntax. Note that if you were to use a procedure definition (\procedure timeparse(value)), then the reference to the value parameter would be just <value> rather than <__value__>
  • To get the seg value, just split the input on the commas, and take the first piece. Note that the split[,] filter operator removes the commas from the results. If there is no comma in the input, it will get the entire input value.
  • To get the extra content, split the input on the commas, and take all but the first piece. Then join those pieces with commas to restore them in the result.
  • Next, invoke your time formatting macro, passing in the <<seg>> value. Note the use of DOUBLED angle brackets when referencing the seg variable as a macro parameter. This is in contrast to the SINGLE angle brackets used when referencing a variable within filter syntax.
  • Then, check to see if the extra variable value is not blank. If it is, then output a comma, a space, and then the extra value. Again, note the use of SINGLE angle brackets (<extra>) within filter syntax, but DOUBLED angle brackets within a normal rendered “wikitext” context.

enjoy,
-e

1 Like

Thanks @EricShulman (and @Scott_Sauyet and @Mohammad).

So if I follow your answer correctly, I could define:

\procedure timeparse(value)
<$let seg={{{ [<value>split[,]first[]] }}}>
<$let extra={{{ [<value>split[,]rest[]join[,]] }}}>
<$macrocall $name="time" value=<<seg>>/>
<%if [<extra>!is[blank]] %>, <<extra>><%endif%>
\end

… and it would work exactly the same when calling this procedure version rather than the macro version? (from what I read, and the part that confused me, procedures and macros don’t behave exactly the same, as evidenced by @etardiff in this post; the substitution part being what makes me irrationally afraid to use procedures)

Thanks for the clever suggestion. I realize I wasn’t clear enough in detailing the form of what may be in the field. It would work fine with the example provided here, however, if I have more than one time segment in the field (for ex.: "1s for the first effect, 2m afterwards), it will break down. That’s why I originally try and favor regexp to detect the segments that have to be styled within “a sentence”. But I see now why I can’t use the brackets in a regexp, thanks to you and @Scott_Sauyet.

The only other approach I see to make sure it captures any occurrence of a time, is to craft the conditional in 4 parts, one for each letter “s”, “m”. “h”, “d”, and go with four separate regexps \d+s$, \d+m$, \d+h$, \d+d$.

Do you have a better idea?

Does this do what you want?

\procedure re() \d+[smhd]$

\procedure timeparse(value)
  <$list filter="[<value>split[,]splitregexp[\s+]] :filter[regexp<re>]" variable="seg">
    <$transclude $variable="time" value=<<seg>>/>
  </$list>
\end

I think it’s a bit simpler than you’re expecting.

Here we first split on ,, then on any set of spaces, then filter the results for anything that looks like digits followed by one of our time period abbreviations.

You can download this and drag it to a wiki to test:

timeparse.json (1.1 KB)

Nope, returns blank :stuck_out_tongue:

Also, shouldn’t there be a join to get back anything that doesn’t fit the last regexp?

(just to be clear, I called your procedure like so: <$transclude $variable="timeparse" param={{!!casting_time}}/> instead of <$macrocall $name=timeparse value={{!!casting_time}}/>)

Don’t use “param”. Use the actual parameter name, “value”.

timeparse.json (1.5 KB)

I don’t know. Perhaps you can show a few inputs and their associated expected outputs.?

How come you don’t need to close your declarations with </$let> in this code?

By default, any “unclosed” widgets are automatically closed when the end of the macro, procedure, or tiddler in which they are declared in reached, so we can leave off the matching </$let>'s in the timeparse(...) code.

Note that this is only true for widgets at the “top level” of the code. If the widget is nested within another widget, then it must be properly matched before the containing widget is closed.

For example:

\define mymacro()
<$list filter="[somefilterhere]">
   <$let somevar="somevalue">
   ...wikitext code here...
   </$let> <!-- This closing $/let syntax is REQUIRED before the `/$list` -->
</$list>
\end

However, this “omit the closing syntax” trick CAN be used in nested syntax if ALL the containing widgets are also to be closed at the end of the macro. For example, I often have macros that only produce output if a certain condition is satisfied, so they start with a $list widget as a test to see if the rest of the macro code should be invoked. In that case, the closing /$list as well as the “nested” $let widgets can be omitted, so the following is valid:

\define mymacro()
<$list filter="[<somevar>!match[]]">
<$let someothervar="somevalue">
... wikitext code here ...
\end

-e

1 Like

So this would ensure to catch any part of the field that contains a number followed by “s”, “m”, “h”, “d”, “s,”, “m,”, “h,”, or “d,”, and not catch anything else by mistake, then style it as I need:

\procedure timeparse(value)
<$list filter="[<value>split[ ]]" variable="seg" join=" ">
<% if [<seg>regexp[\d+s$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+s,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+m$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+m,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+h$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+h,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+d$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+d,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% else %>
<<seg>> 
<% endif %>
</$list>
\end

However, if there is any shortcut macro call inside the field (like “<<macro value>>”), those calls won’t work anymore and become litteral strings because they would be split at the space character, then reassembled by the list widget join operator, which would also be an issue for me.

Edit: the idea below wouldn’t work because the angled brackets in the regexp will break it like brackets do, right? :stuck_out_tongue_winking_eye:

So my feeling is that before entering the list split, I have to test for the presence of any such macro shortcut, which could be achieved with <% if [<value>regexp[<<\w+\s.+>>$]] %> (I’m pretty sure the field will never contain macros that have more than one parameter). Then I’d have to:

  1. replace the space in all the macro calls of the field string with a detectable but inoccuous character (like ? perhaps), and pass that new string to the list widget
  2. in the last condition of the list widget, test the <seg> for the presence of double angle brackets with <% if [<seg>regexp[<<\w+.+>>$]] %>
  3. output the full macro again by substituting the now missing space with the “?”

The first one gives me pause; I have a feeling I could use search-replace in regexp-mode to grab the macro shortcuts and replace the spaces with “#”. Then it becomes a bit humongous:

\procedure timeparse2(value)
<% if [<value>regexp[<<\w+\s.+>>$]]] %>
<$list filter="[search:<value>:regexp[<<\w+\s.+>>$]]" variable="macroshortcut">
<$let tempvalue={{{ [<macroshortcut>search-replace[ ],[?]] }}}>
<$list filter="[<tempvalue>split[ ]]" variable="seg" join=" ">
<% if [<seg>regexp[\d+s$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+s,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+m$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+m,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+h$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+h,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+d$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+d,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<%elseif [<seg>regexp[<<\w+.+>>$]] %>
{{{ [<seg>search-replace[?],[ ]] }}}
<% else %>
<<seg>> 
<% endif %>
</$list>
</$let>
</$list>
<% else %>
<$list filter="[<value>split[ ]]" variable="seg" join=" ">
<% if [<seg>regexp[\d+s$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+s,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+m$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+m,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+h$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+h,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% elseif [<seg>regexp[\d+d$]] %>
<$macrocall $name="time" value=<<seg>>/>
<% elseif [<seg>regexp[\d+d,$]] %>
<$let segm={{{ [<seg>split[,]first[]] }}}>
<$macrocall $name="time" value=<<segm>>/>, </$let>
<% else %>
<<seg>> 
<% endif %>
</$list>
<% endif %>
\end

I have yet to test this, but I’m opened to better ideas if you already have them.

Edit: the idea above wouldn’t work because the angled brackets in the regexp will break it like brackets do, right? :stuck_out_tongue_winking_eye: