bakpakin / Fennel

Lua Lisp Language
https://fennel-lang.org
MIT License
2.42k stars 124 forks source link

Creating macro which unpacks arbitrary number of arguments yields incorrect result #473

Closed jaggedblades closed 6 months ago

jaggedblades commented 6 months ago
(macro compile-concat [& strs] 
  (.. (table.unpack strs)))

(print (compile-concat "Hello " "World!")) ; expected to print "Hello World!", prints "Hello " instead.
alexjgriffith commented 6 months ago

This issue arises from how fennel handles multiple values being passed to an operator via any multi value return.

From the Fennel Lang Reference [Operators] take any number of arguments, as long as that number is fixed at compile-time. For instance, (= 2 2 (unpack [2 5])) will evaluate to true because the compile-time number of values being compared is 3. Multiple values at runtime will not be taken into account.

In this example two-values returns the strings just as unpack would from a table, however the .. operation doesn't work as expected.

(fn two-values []
  (values "Hello " "World!"))
(.. (two-values))
;;> "Hello "    "World!"
(.. (two-values) :1)
;;> "Hello 1"

Only the first return value is passed into the function. As the reference guide says, this behaviour applies to all built in operations (+, -, .. etc).

So in your case where your are trying to unpack string into the .. operator, the total count of arguments known at compile time is only 1 so it only takes the first argument.

(macro concat-1 [& str]
  (.. (unpack str)))
(print (concat-1 "Hello " "World!"))
print("Hello ")

The simplest way to get concat to work the way you want is to unpack at macro expand time and then concat at runtime. This means that at compile time (which falls between macro expand and runtime) the the argument count is 2 and the operation should work as expected.

(macro concat-2 [& str]
  `(.. ,(unpack str)))
(print (concat-2 "Hello " "World!"))
print(("Hello " .. "World!"))

I assume you want to avoid the overhead of concating at runtime. As an alternative you can use the fennel folding function accumulate to build up your string.

(macro concat-3 [& str]
  (accumulate [acc "" _i s (ipairs str)] (.. acc s)))
(print (concat-3 "Hello " "World!"))
print("Hello World!")
jaggedblades commented 6 months ago

Ok, thank you for the solutions.