7
\$\begingroup\$

JavaScript has no direct support for keyword arguments, but it supports argument object destructuring. It is standard to use a destructed final argument for keyword arguments. Here is an example:

function f(x, { a = 7, b = 2}){
    return [x, a, b];
}

You would then call this function like:

f(9, {}); // ==> [9, 7, 2]
f(9, {a : 3}); // ==> [9, 3, 2]
f(9, {b : 3, a : 1}); // ==> [9, 1, 3]

The object may also supply a separate default if you omit the object entirely:

function g({ a = 7, b = 2} = {b : 3} ){
    return [a, b];
}
g() // => [7,3]
g({}) // => [7,2]

Goal

Given an argument specification for a JavaScript function, determine whether it "supports key word arguments". The input must be a valid argument specification for a JavaScript function. It "supports key word arguments" if the last argument does object destructuring. See many examples below.

You may assume that no strings appear in the input (so no single quote ' double quote " or uptick `). You may also assume that the spread operator ... does not appear.

(It would be nice to have the grammar for JavaScript argument specifications here but on a quick google I didn't find a readable reference. This section of the ECMAScript specification looks relevant, but I can't make sense of the grammar they provide. If anyone can suggest a readable link, I will add it.)

Scoring

This is code golf, and the shortest code wins.

Examples

"" // empty params ==> false
"x" // single normal argument ==> false
"x     " // test is space insensitive ==> false
"{x}" // ==> true
"x, y, z" // multiple normal arguments ==> false
"x, y, {z}" // ==>  true
"x, {y}, z" // ==> false
"x, {y}, {z}" // ==> true

"{}" // ==> true
"{} = {}" ==> true
"[] = {}" ==> false
"{} = []" ==> true
"[] = []" ==> false
"{} = null" ==> true

"[{x}]" // ==> false
"[x, y, z]" // ==> false
"x," // ==> false
"{x}," // ==> true

"x, { y = 2 }" // ==> true
"{ y = 2 }, x" // ==> false, not last argument
"{ x = 2 }, { y = 2 }" // ==> true

"{ a = 7, b = 2}" // ==> true, this is f above
"{ a = 7, b = 2} = {b : 3}" // ==> true this is g above
"{ a = [7, 1], b = { c : 2} } = {}" // ==> true
"{ a = 7, b = 2} = {}" // ==> true
"{ a = 7, b = 2} = null" // ==> true.

"{ x = { y : 2 }}" // ==> true

"x, [y, { z }]" // ==> false
"[x] = {[Symbol.iterator]: [][Symbol.iterator].bind([42])}" // ==> false
"x, y = function ({z}) {}" // ==> false
`x, y = ({z}) => {z}` // ==> false

Unspecified inputs:

","
"x y"
"x = '2, {y}' " // no syntax error but default is a string
...[x, y, { keyword },] // no syntax error but contains illegal ...
"{x"
"{{x}}"
"{[x]}"
"{1}"
"x,,"
"x, { x = 2 }"
"{ x = { y = 2 }}"
"{ x : 2 }"

Here are the tests as a JSON string:

"[[\"\",false],[\"x\",false],[\"x     \",false],[\"{x}\",true],[\"x, y, z\",false],[\"x, y, {z}\",true],[\"x, {y}, z\",false],[\"x, {y}, {z}\",true],[\"{}\",true],[\"{} = {}\",true],[\"[] = {}\",false],[\"{} = []\",true],[\"[] = []\",false],[\"{} = null\",true],[\"[{x}]\",false],[\"[x, y, z]\",false],[\"x,\",false],[\"{x},\",true],[\"x, { y = 2 }\",true],[\"{ y = 2 }, x\",false],[\"{ x = 2 }, { y = 2 }\",true],[\"{ a = 7, b = 2}\",true],[\"{ a = 7, b = 2} = {b : 3}\",true],[\"{ a = [7, 1], b = { c : 2} } = {}\",true],[\"{ a = 7, b = 2} = {}\",true],[\"{ a = 7, b = 2} = null\",true],[\"{ x = { y : 2 }}\",true], [\"x, [y, { z }]\", false], [\"[x] = {[Symbol.iterator]: [][Symbol.iterator].bind([42])}\", false], [\"x, y = function ({z}) {}\", false], [\"x, y = ({z}) => {z}\", false] ]"

A reference implementation

Here is an ungolfed reference implementation in JavaScript (it uses nothing specific to JavaScript and could be easily ported to other languages).

function supportsKwargs (funcstr) {
    let START_ARG = 1;
    let ARG = 2;
    let paren_depth = 0;
    let arg_is_obj_dest = false;
    let state = START_ARG;
    for (let i = 0; i < funcstr.length; i++) {
      let x = funcstr[i];
      // Skip whitespace.
      if(x === " " || x === "\n" || x === "\t"){
        continue;
      }
      if(paren_depth === 0){
        if(x === ","){
          state = START_ARG;
          continue;
        }
      }
      if(state === START_ARG){
        // Nonwhitespace character in START_ARG so now we're in state arg.
        state = ARG;
        arg_is_obj_dest = x === "{";
      }
      switch(x){
        case "[": case "{": case "(":
          paren_depth ++;
          continue;
        case "]": case "}": case ")":
          paren_depth--;
          continue;
      }
    }
    return arg_is_obj_dest;
}
\$\endgroup\$
14
  • 3
    \$\begingroup\$ I would remove all the test cases which have "unspecified" behaviour (or at the very least, move them to the end), as they make the test cases harder to read \$\endgroup\$ – caird coinheringaahing Jan 11 at 17:38
  • \$\begingroup\$ I was thinking it might be helpful to fix the test cases to make it easier for a computer to read too. \$\endgroup\$ – Hood Jan 11 at 17:39
  • \$\begingroup\$ Maybe... what about: ...[x, y, { keyword },]; x, y, { keyword },;x, [y, { z }]; [x] = {[Symbol.iterator]: [][Symbol.iterator].bind([42])}; x, y = function ({z}) {}; x, y = ({z}) => {z}? \$\endgroup\$ – tsh Jan 12 at 6:18
  • \$\begingroup\$ @tsh What do you mean? Are these extra suggested test cases? Or are you responding to someone else's deleted comments? \$\endgroup\$ – Hood Jan 12 at 6:19
  • \$\begingroup\$ @Hood Are these valid inputs? If so, what expected outputs are. If not, what exactly required to be a valid input? \$\endgroup\$ – tsh Jan 12 at 6:20
3
\$\begingroup\$

05AB1E, 44 bytes

… 
    м©¼¬ˆε',Q¾*i®N>èˆ}…[{(yå½…]})yåi.¼]¯θ'{Q

Simply a port of the reference implementation.

Try it online or verify all test cases.

Explanation:

… \n\tм      # Remove all spaces, tabs, and newlines from the (implicit) input
©            # Store this new string in variable `®` (without popping)
¼            # Start the counter variable at 1 (it's 0 by default)
¬            # Get the first character of the string (without popping)
 ˆ           # And pop and add it to the global array
ε            # For-each `y` over the characters in the string:
 ',Q        '#  If the current character is a ","
    ¾*i      #  and the counter variable is still 1:
       ®     #   Push the string from variable `®`
        N>   #   Get the for-each index, and increment it by 1
          è  #   Index that into the string, to get the next character
           ˆ #   Pop and add that to the global array
      }      #  Close the if-statement
 …[{(yå      #  If the current character is in the string "[{(":
       ½     #   Increment the counter variable by 1
 …]})yåi     #  If the current character is in the string "]})":
        .¼   #   Decrement the counter variable by 1
]            # Close both the if-statement and for-each
 ¯           # Push the global array
  θ          # Only leave its last item
   '{Q      '# And check if this is equal to "{"
             # (after which the result is output implicitly)
\$\endgroup\$
0
3
\$\begingroup\$

05AB1E, 36 35 bytes

Some parts are taken from Kevin Cruijsen's answer
-1 byte thanks to Kevin Cruijssen!

… 
	мS©…[{(®å…]})®å-.¥_®',Q1š*Ïθ'{Q

Try it online!

Commented:

…\s\n\tм    # remove all whitespace from the input
        S   # split into a list of characters
         ©  # store this list in the register (without popping)
…[{(        # push string literal "[{("
    ®       # push the list from the register
     å      # for each character check if it is in the string "[{("
            # this is now a list with 1's at the indices of opening brackets
…]})        # push string literal "]})"
    ®       # push the list from the register
     å      # for each character check if it is in the string "]})"
            # this is now a list with 1's at the indices of closing brackets
-           # subtract element-wise
 .¥         # take the cumulative sum and prepend a 0
            # this a list of the parentheses depths
   _        # for each element: does it equal 0?
®           # push the list from the register
 ‛,Q        # for each element: does it equal ","?
    1š      # prepend a 1, this list is 1 at index N if N==0 or ®[N-1]==","
      *     # multiply element-wise with the previous list
Ï           # take the elements in the input where a 1 is in this list
 θ          # take the last element of this
  ‛{Q       # is this equal to "{"?
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Nice approach! I knew when I posted my semi-straightforward port as answer it could be golfed, so it's not unexpected to see this answer. You can golf the 0Q to _ for -1. \$\endgroup\$ – Kevin Cruijssen Jan 12 at 13:48
3
\$\begingroup\$

JavaScript (SpiderMonkey), 69 bytes

x=>Reflect.parse(`function f(${x}){}`).body[0].params.pop()?.type>'I'

Try it online!

Certainly you need right tools for this question...

SpiderMonkey on TIO is a bit old and won't work correctly for above code. You may download newest version of jsshell from https://archive.mozilla.org/pub/firefox/nightly/latest-mozilla-central/ .

It is strange that, using function f(){} instead of ()=>{} in JavaScript may save bytes.

\$\endgroup\$
3
  • \$\begingroup\$ I have a reference implementation that passes the test suite and doesn't use any built-in javascript parsing tools. Maybe you can tell me if I'm missing some cases? \$\endgroup\$ – Hood Jan 12 at 6:43
  • \$\begingroup\$ I guess I could just add it to the question. \$\endgroup\$ – Hood Jan 12 at 6:44
  • \$\begingroup\$ I wish Reflect.parse were part of the web standard, it would be really useful. \$\endgroup\$ – Hood Jan 12 at 7:14
2
\$\begingroup\$

JavaScript (ES6), 96 bytes

A minimal parser that expects a list of characters and returns 0 or 1.

a=>a.map(c=>n=(i=",{}[]() ".indexOf(c))<7?~i?i?(o|=n/(d+=i&1||-1)/i)&0:!d:n?o=0:0:n,n=1,d=o=0)|o

Try it online!

Commented

Variables:

  • d is the current depth (0 = root)
  • n is a flag telling whether we're at the beginning of a new block at the root level
  • o is a flag telling whether the last block is an object

Source:

a =>                     // a[] = array of characters
a.map(c =>               // for each character c in a[]:
  n =                    //   update n:
    ( i = ",{}[]() "     //     i = index of c in this string
          .indexOf(c)    //
    ) < 7 ?              //     if c is not a space:
      ~i ?               //       if c is one of the characters defined above:
        i ?              //         if c is not a comma:
          ( o |=         //           set the flag o if:
            n /          //             n = 1
            ( d += i & 1 //             and the updated depth is 1
                   || -1 //             (incremented if i is odd, decremented if even)
            ) / i        //             and i = 1 (i.e. c is "{")
          ) & 0          //           clear n
        :                //         else (comma):
          !d             //           set n if we are at the root level
      :                  //       else (other character):
        n ?              //         if n is set:
          o = 0          //           clear o and clear n
        :                //         else:
          0              //           clear n
    :                    //     else (space):
      n,                 //       leave n unchanged
  n = 1,                 //     start with n = 1
  d = o = 0              //     start with d = o = 0
) | o                    // end of map(); return o
\$\endgroup\$
2
\$\begingroup\$

Retina 0.8.2, 38 bytes

+`{([^{}]|{})+}
{}
\s

,$

=[^,]*$

}$

Try it online! Link includes test suite that assumes all tests are on separate lines and enclosed in quotes. Explanation:

+`{([^{}]|{})+}
{}

Collapse all objects recursively to empty objects.

\s

Delete all whitespace.

,$

Delete any trailing comma.

=[^,]*$

Delete the last parameter's default value, if any.

}$

Check whether the last parameter is a destructured object.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.