differentmatt / filbert

JavaScript parser of Python
Other
133 stars 27 forks source link

Empty list should be false in conditional #46

Open dideler opened 9 years ago

dideler commented 9 years ago

The "Pythonic" way to check for an empty list is if not my_list and for a non-empty list is if my_list.

if not []: # List is empty, should evaluate to true (does not work)
if not [1, 2]: # List is not empty, should evaluate to false (works)

if []: # List is empty, should evaluate to false (does not work)
if [1, 2]: # List is not empty, should evaluate to true (works)

l = []
if l :# List is empty, should evaluate to false (does not work)
if not l: # List is empty, should evaluate to true (does not work)

l.append(1)
if l: # List is not empty, should evaluate to true (works)
if not l: # List is not empty, should evaluate to false (works)

A temporary workaround for checking for empty lists is to check the length of the list:

if len([]) != 0:

Note that bool() evaluates lists correctly in filbert; the issue seems to just be with conditionals.

dideler commented 9 years ago

According to http://designpepper.com/blog/drips/truthy-and-falsy-values-in-javascript.html, there are only six falsy values in JS:

// Outputs: "Falsy."
logTruthiness(false);

// Outputs: "Falsy."
logTruthiness(null);

// Outputs: "Falsy."
logTruthiness(undefined);

// Outputs: "Falsy."
logTruthiness(NaN);

// Outputs: "Falsy."
logTruthiness(0);

// Outputs: "Falsy."
logTruthiness("");

The empty string is one of them, which explains why if "": works in filbert. But the Python notation for empty list [], tuple (), and dict {} are all truthy in JS (whereas they're falsy in Python), so that's causing some issues.

I'm not entirely sure what the best approach to fix this is, especially since I'm new to lexical analysis. Should there be a special class for a Python list that holds an attribute of its truthiness?

Currently there is this

var pythonRuntime = exports.pythonRuntime = {
  objects: {
    list: function () {
        var arr = [];
        arr.push.apply(arr, arguments);
        pythonRuntime.utils.convertToList(arr);
        return arr;
      }
  }
}

It seems like parseExpression() is called for any sequence in a conditional expression, so maybe the logic for handling the sequences should go in there? Or in parseMaybeAssign() since parseExpression() just calls that?

differentmatt commented 9 years ago

Good questions. I'm not sure offhand, but should have some time later this week to look into this.

differentmatt commented 9 years ago

@dideler Wrapping non-operator expressions in a bool() call should fix this.

I don't think we need a specific truthiness value to the Python list object, since bool() already evaluates correctly. The truthiness value would be read by bool() if we had one.

For an if test clause, you'd need to identify all the expressions that should be wrapped, and then generate the correct AST for a __pythonRuntime.functions.bool() call. Unfortunately, you can't just wrap the whole test clause in a bool() call.

If test clause: https://github.com/differentmatt/filbert/blob/master/filbert.js#L1842 Generating AST for bool() runtime call: https://github.com/differentmatt/filbert/blob/master/filbert.js#L2189

Given this Python code:

if not []:
  print('correct')
else:
  print('incorrect')

if []:
  print('incorrect')
else:
  print('correct')

We'd generate this JavaScript code:

if (!__pythonRuntime.functions.bool(new __pythonRuntime.objects.list())) {
    __pythonRuntime.functions.print('correct');
} else {
    __pythonRuntime.functions.print('incorrect');
}
if (__pythonRuntime.functions.bool(new __pythonRuntime.objects.list())) {
    __pythonRuntime.functions.print('incorrect');
} else {
    __pythonRuntime.functions.print('correct');
}