lua-users home
lua-l archive

[Date Prev][Date Next][Thread Prev][Thread Next] [Date Index] [Thread Index]


Rationale

Consistency with table constructors. Especially when implementing DSLs it is inconsistent that you can do

f{
	1,
	2,
}

but not

f(
	1,
	2,
)

For documentation purposes, I like to split parameters across lines:

function f(
	a, -- a comment for a
	b  -- a comment for b
)
	...
end

which would benefit from a trailing comma (which should also be added for consistency with the trailing comma in arglists):

function f(
	a, -- a comment for a
	b, -- a comment for b
)
	...
end

This can not be extended to explists in general, since doing so would introduce an ambiguity with function calls:

a, b = 1,
print("hello world")

However, it should be possible to extend this to everything else (return statements, for explists) since those are delimited. Additionally, the left hand side of assignments could also get trailing comma support; I don't think these extensions would be worthwhile though.

Usage

$ ./lua
Lua 5.4.5  Copyright (C) 1994-2022 Lua.org, PUC-Rio
> function f(a,b,c,) print(a,b,c,) end
> f(1,2,3)
1    2    3

Patch

The diff got somewhat messy due to the addition of the arglist parameter to explist, which is somewhat dirty.

diff --git a/lparser.c b/lparser.c
index 24668c24..5837f9d6 100644
--- a/lparser.c
+++ b/lparser.c
@@ -957,28 +957,31 @@ static void setvararg (FuncState *fs, int nparams) {


 static void parlist (LexState *ls) {
-  /* parlist -> [ {NAME ','} (NAME | '...') ] */
+  /* parlist -> [ {NAME ','} [ (NAME | '...') ',' ] ] */
   FuncState *fs = ls->fs;
   Proto *f = fs->f;
   int nparams = 0;
   int isvararg = 0;
-  if (ls->t.token != ')') {  /* is 'parlist' not empty? */
-    do {
-      switch (ls->t.token) {
-        case TK_NAME: {
-          new_localvar(ls, str_checkname(ls));
-          nparams++;
-          break;
-        }
-        case TK_DOTS: {
-          luaX_next(ls);
-          isvararg = 1;
-          break;
-        }
-        default: luaX_syntaxerror(ls, "<name> or '...' expected");
+  int closed = 0;
+  do {
+    switch (ls->t.token) {
+      case TK_NAME: {
+        new_localvar(ls, str_checkname(ls));
+        nparams++;
+        break;
       }
-    } while (!isvararg && testnext(ls, ','));
-  }
+      case TK_DOTS: {
+        luaX_next(ls);
+        isvararg = 1;
+        break;
+      }
+      case ')': {
+        closed = 1;
+        break;
+      }
+      default: luaX_syntaxerror(ls, "<name> or '...' expected");
+    }
+  } while (!closed && !isvararg && testnext(ls, ','));
   adjustlocalvars(ls, nparams);
   f->numparams = cast_byte(fs->nactvar);
   if (isvararg)
@@ -1009,11 +1012,13 @@ static void body (LexState *ls, expdesc *e, int ismethod, int line) {
 }


-static int explist (LexState *ls, expdesc *v) {
+static int explist (LexState *ls, expdesc *v, int arglist) {
   /* explist -> expr { ',' expr } */
   int n = 1;  /* at least one _expression_ */
   expr(ls, v);
   while (testnext(ls, ',')) {
+    if (arglist && ls->t.token == ')')  /* allow trailing comma in an arglist */
+      return n;
     luaK_exp2nextreg(ls->fs, v);
     expr(ls, v);
     n++;
@@ -1027,12 +1032,12 @@ static void funcargs (LexState *ls, expdesc *f, int line) {
   expdesc args;
   int base, nparams;
   switch (ls->t.token) {
-    case '(': {  /* funcargs -> '(' [ explist ] ')' */
+    case '(': {  /* funcargs -> '(' [ explist ] [ ',' ] ')' */
       luaX_next(ls);
       if (ls->t.token == ')')  /* arg list is empty? */
         args.k = VVOID;
       else {
-        explist(ls, &args);
+        explist(ls, &args, 1);
         if (hasmultret(args.k))
           luaK_setmultret(fs, &args);
       }
@@ -1389,7 +1394,7 @@ static void restassign (LexState *ls, struct LHS_assign *lh, int nvars) {
   else {  /* restassign -> '=' explist */
     int nexps;
     checknext(ls, '=');
-    nexps = explist(ls, &e);
+    nexps = explist(ls, &e, 0);
     if (nexps != nvars)
       adjust_assign(ls, nvars, nexps, &e);
     else {
@@ -1609,7 +1614,7 @@ static void forlist (LexState *ls, TString *indexname) {
   }
   checknext(ls, TK_IN);
   line = ls->linenumber;
-  adjust_assign(ls, 4, explist(ls, &e), &e);
+  adjust_assign(ls, 4, explist(ls, &e, 0), &e);
   adjustlocalvars(ls, 4);  /* control variables */
   marktobeclosed(fs);  /* last control var. must be closed */
   luaK_checkstack(fs, 3);  /* extra space to call generator */
@@ -1744,7 +1749,7 @@ static void localstat (LexState *ls) {
     nvars++;
   } while (testnext(ls, ','));
   if (testnext(ls, '='))
-    nexps = explist(ls, &e);
+    nexps = explist(ls, &e, 0);
   else {
     e.k = VVOID;
     nexps = 0;
@@ -1819,7 +1824,7 @@ static void retstat (LexState *ls) {
   if (block_follow(ls, 1) || ls->t.token == ';')
     nret = 0;  /* return no values */
   else {
-    nret = explist(ls, &e);  /* optional return values */
+    nret = explist(ls, &e, 0);  /* optional return values */
     if (hasmultret(e.k)) {
       luaK_setmultret(fs, &e);
       if (e.k == VCALL && nret == 1 && !fs->bl->insidetbc) {  /* tail call? */