1

I want to determine how many parameters (top level) a function has, given the String representation of the function. As an example:

(1) assertEquals(verify, actual);
(2) assertEquals("message", actual, verify);
(3) assertEquals(account.addMoney(19, Delivery.fast()).toString(), bank.getAccount(person.getCredentials("basic")).tally(), mock.getData().verify());

Answers should be (1) = 2, (2) = 3, (3) = 3. You may assume I have the function name, e.g. regex on assert.

Basically I can count commas, but I need to take into account function parameters for inner function calls. So

function(param1, param2.data(param3, param4, param5.getData(param6, param7));

Of course the nesting can be arbitrary deep.

I think a regex might do the trick, but my experience with them is insufficient to solve this question.

Please note that I have considered using a lever/parser, but this seems a bit overkill for my intentions. I have a set of functions as strings and my target method invocations are assertion methods.

2 Answers2

0

For this kind of problem you need a lexical analyzer and a parser.

The lexical analyzer split the string in tokens

The parse take the tokens and build a data structure, often a tree, that better represents what the tokens means.

Navigating that data structure you can easily found how many parameters there are.


Note: this is the same process followed to transform a program written by a human, that is a text file (like a .java file for example) in a different form that can be interpreted by a computer (the process of compiling).


Using a lexical analyzer and a parser is necessary to correctly handle strings like the followings:

  • function("a,b", c)
  • function(new String[]{"a", "b"})
  • function(a(b, c))
  • function(',')

and any composition of them


A lexical analyzer using java can be the following JFlex.

I think that once that you have tokens you can manually count commas and parenthesis (that now are really commas and parenthesis, not part of strings or something other)

Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
  • This is indeed an option to solve the problem, but this is a bit overkill for my intentions. I simply have a set of functions of which I have the source (string). My target is assertion method calls, so this simplifies the analysis considerably ;) – Kevin van den Bekerom Jun 15 '16 at 08:21
  • You need to consider that you can have also strings containing the commas, arrays wich elements can be separated by commas. It is not something that can be solved without a parser + lexical analyzer. – Davide Lorenzo MARINO Jun 15 '16 at 08:24
  • Any suggestions for a light weight Java lexer/parser that takes a String as input? @Davide: hadn't considered all those border cases! Problem seemed simpler at first glance. – Kevin van den Bekerom Jun 15 '16 at 08:34
0

Based on possible cases I defined a recursive algorithm to find out the number of function parameters. Please post any corner-cases that break the code below :) Code works for cases (also recursive) as suggested by Davide

/*
 * returns the number of parameters in this function
 */
private int functionParameters(String function) {
    String fnStripped = stripTill('(', function);
    String fnStripped2 = fnStripped.substring(1);
    // find function with 0 parameters, i.e. ()
    if (removeWhiteSpace(fnStripped).length() == 2) {
        return 0;
    }
    // find function with >= 1 parameters
    return countFunctionParameters(fnStripped2);
}

/*
 * @pre: function is a string, starting from the arguments of a function, excluding the first parenthesis.
 * @pre: function has at least one parameter.
 * @post: the number of parameters of the original (top level) function.
 */
private int countFunctionParameters(String function) {
    // every function has at least one parameter
    if (function.length() == 0) {
        return 1;
    } 
    char next = function.charAt(0);
    String newString = function.substring(1);

    switch(next) {
        case ',' : return 1 + countFunctionParameters(newString);
        case '(' : newString = removeUntil(')', newString); return 0 + countFunctionParameters(newString);
        case '"' : newString = removeUntil('"', newString); return 0 + countFunctionParameters(newString);
        case '\'' : newString = removeUntil('\'', newString); return 0 + countFunctionParameters(newString);
        case '{' : newString = removeUntil('}', newString); return 0 + countFunctionParameters(newString);
        default : return 0 + countFunctionParameters(newString);
    }
}

private String removeUntil(char c, String str) {
    System.out.println("CURRENT: " + str);
    if (str.equals("")) {
        return str;
    }
    String newString = str.substring(1);
    if (str.charAt(0) == c) {
        return newString;
    }

    switch (str.charAt(0)) {
        case '(' : return removeUntil(c, removeUntil('(', newString));
        case '"' : return removeUntil(c, removeUntil('"', newString));
        case '\'' : return removeUntil(c, removeUntil('\'', newString));
        case '{' : return removeUntil(c, removeUntil('}', newString));
        case ')' : return newString;
        case '}' : return newString;
        case '\\' : return removeUntil(c, newString.substring(1)); // disregard the escaped character!
        default : return removeUntil(c, newString);
    }
}

private String removeWhiteSpace(String str) {
    return str.replaceAll("\\s+","");
}

private String stripTill(char c, String str) {
    while (str.charAt(0) != c) {
        str = str.substring(1);
    }
    return str;
}
Community
  • 1
  • 1
  • 1
    this is not so simple. If you have for example myFunction("\"", 3); you have some problem for the first string containing a \" – Davide Lorenzo MARINO Jun 15 '16 at 10:20
  • Good point. I did not yet model escaping characters. They are indeed a bit trickier. For my purpose an approximation might suffice if it captures most cases, which I have to find out. A simple solution here could be to strip away any single character after '\'. – Kevin van den Bekerom Jun 15 '16 at 11:30