Yuescript icon indicating copy to clipboard operation
Yuescript copied to clipboard

[feature request] Implementing '...' for Variable Declaration within Scope using Anonymous Functions

Open Propagram opened this issue 2 years ago • 5 comments

Hi, In the Lua language, the ellipsis ... can only be used in the last function argument.

local function a(...)
    print(...)
end

It is not possible to declare it in the scope (e.g., as local ... = 1, 2).

local ok, ... = true, true -- syntax error! ('<name>' expected near '...')

My suggestion is to implement the use of ... to declare it in the scope, using anonymous functions for that purpose. During parsing, if there is ... in the variable declaration, Yuescript can wrap that scope inside a function and call it with the values. This way, ... will be defined within the same scope.

list = {1, 2, 3, 4, 5}
fn = (ok) ->
  ok, table.unpack list
ok, ... = fn true
print ok, ...
ok, ...

compiles to:

local list = {
  1, 
  2,
  3,
  4,
  5
}
local fn
fn = function(ok)
  return ok, table.unpack(list)
end
return (function(ok, ...)
    print(ok, ...) -- "ok" and "..." is available here without errors!
    return ok, ...
end)(fn(true))
Notes

In my scripts, I use varargs (...) returns frequently. Usually, they contain 'nil' (holes), so I cannot obtain the correct length if I wrap them in tables '{...}'. It is necessary to return the length and then use 'table.unpack'

local function fn_many_args()
    return 10, nil, 20, nil, 30
end

local args = {fn_many_args()}
print(#args) --> 1

local _ = (function(...)
    print(select("#", ...)) --> 5
    print(...) --> 10, nil, 20, nil, 30
end)(fn_many_args())


-- I can pass the function to select:
print(select("#", fn_many_args())) --> 5
-- But, How can I get the items? I will need call the function twice!
print(select(1, fn_many_args())) --> 10, nil, 20, nil, 30
-- First to get length, and second to catch the items

Regards

Propagram avatar Jul 31 '23 22:07 Propagram

Tried to add this feature, if you use vararg mutiple times in a same scope. You will get you're code block actually split in multiple anonymous functions.

list = {1, 2, 3, 4, 5}
fn = (ok) ->
  ok, table.unpack list
ok, ... = fn true
print ok, ...

fn_many_args = ->
  10, nil, 20, nil, 30

... = fn_many_args!
print select "#", ...
print ...

now compiles to:

local list = {
  1,
  2,
  3,
  4,
  5
}
local fn
fn = function(ok)
  return ok, table.unpack(list)
end
return (function(_arg_0, ...)
  local ok = _arg_0
  print(ok, ...)
  local fn_many_args
  fn_many_args = function()
    return 10, nil, 20, nil, 30
  end
  return (function(...)
    print(select("#", ...))
    return print(...)
  end)(fn_many_args())
end)(fn(true))

pigpigyyy avatar Aug 07 '23 03:08 pigpigyyy

Thank you! Congratulations on the effort put into this project.

I found some inline expressions that don't compile or report errors:

... = 1, 2 if a

compiles to

return (function(...) end)(1, 2) -- 1

In this case, the inline expression should be placed within the anonymous function, or alternatively, report an error in inline expressions with varargs.

Propagram avatar Aug 08 '23 00:08 Propagram

Fixed the behavior of varargs with inline expression to be the same as assignment statement with 33260af2175004347a3b9345b67727e596c6fffd.

a = 1 if true
... = 1, 2 if a
print ...

compiles to:

local a
if true then
  a = 1
end
return (function(...)
  return print(...)
end)((function()
  if a then
    return 1, 2
  end
end)())

pigpigyyy avatar Aug 09 '23 01:08 pigpigyyy

After running some tests, I realized that it's better if the compiler indicates an error when using all line decorators in varargs assignments.

It will print 2 when if true (ok), but it will print nil when if false (expected output was 1).

a = 1
a, ... = 2, ... if true
print a --> 2 (ok)
a = 1
a, ... = 2, ... if false
print a --> nil (1?)

I believe that if a line decorator is used in a vararg assignment, an error should be displayed because vararg assignment modifies the scope, and an explicit scoped if assignment makes more sense.

Propagram avatar Aug 14 '23 12:08 Propagram

I agree with you for raising errors when using vararg assignment with line decorator. There is no way to tell the difference between:

a = 1
cond = false
value = 2
a, ... = value, ... if cond
print a --> expecting 1
a = 1
cond = true
value = nil
a, ... = value, ... if cond
print a --> expecting nil

So just raising error should be the best idea. Got this fixed until commit b0c461305cfc0dd1cf8235197224130a9bd78d68.

pigpigyyy avatar Aug 17 '23 02:08 pigpigyyy