0

I'm trying to combine NSExpression + NSPredicate to create a simple feature where the user will type something like size + 1 > 3 and it will evaluate the expression.

First, to make sure I can get NSExpression to work with a variable, I tried the following:

NSExpression(format: "2 + 1")
  .expressionValue(with: nil, context: nil)
// == 3

NSExpression(format: "myInt + 1")
  .expressionValue(with: ["myInt": 2], context: nil)
// == 3

Next, to make sure I can evaluate a NSPredicate, I tried the following:

NSPredicate(format: "2 + 1 == 3")
  .evaluate(with: nil)
// == true

Now when I try to combine the two, I get error: Execution was interrupted, reason: signal SIGABRT., no matter which combination I try:

NSPredicate(format: "size + 1 == 3")
  .evaluate(with: ["size": 2])
// error: Execution was interrupted, reason: signal SIGABRT.

NSPredicate(format: "$size + 1 == 3")
  .evaluate(with: ["size": 2])
// error: Execution was interrupted, reason: signal SIGABRT.

NSPredicate(format: "size + 1 == 3")
  .withSubstitutionVariables(["size": 2])
  .evaluate(with: nil)
// error: Execution was interrupted, reason: signal SIGABRT.

NSPredicate(format: "$size + 1 == 3")
  .withSubstitutionVariables(["size": 2])
  .evaluate(with: nil)
// error: Execution was interrupted, reason: signal SIGABRT.

I know that most NSPredicate's are used to filter lists, which make me wonder if a use-case such as the above could even work.

Is it possible to use a variable in an NSPredicate that is evaluated one-time?

Senseful
  • 86,719
  • 67
  • 308
  • 465

1 Answers1

0

There are several things in the code above that can lead to the SIGABRT:

  1. Using a protected variable name (e.g. size) on either NSExpression or NSPredicate (you should use mySize instead).

    (In my NSExpression example, it succeeded because I used the variable name myVar, had I tried to just change the variable to size, it would have produced the same SIGABRT.

  2. Trying to use myVar with NSPredicate.withSubstitutionVariables(:) (you should use $myVar instead).

  3. Trying to use $myVar with NSPredicate.evaluate(with:) (you should use myVar instead).

  4. Trying to use $myVar with NSExpression.evaluate(with:context:) (you should use myVar instead).

Both of these will work:

NSPredicate(format: "mySize + 1 == 3")
  .evaluate(with: ["mySize": 2])
// == true

NSPredicate(format: "$mySize + 1 == 3")
  .withSubstitutionVariables(["mySize": 2])
  .evaluate(with: nil)
// == true

You could even have the same variable defined in both:

NSPredicate(format: "mySize == $mySize")
  .withSubstitutionVariables(["mySize": 2])
  .evaluate(with: ["mySize": 2])
// == true

However, you likely only want to stick to one or the other.

Update: As far as avoiding the reserved keywords, you could compare the dictionary supplied to the following list that Martin linked to:

/// https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html
let reservedKeywords = ["AND", "OR", "IN", "NOT", "ALL", "ANY", "SOME", "NONE", "LIKE", "CASEINSENSITIVE", "CI", "MATCHES", "CONTAINS", "BEGINSWITH", "ENDSWITH", "BETWEEN", "NULL", "NIL", "SELF", "TRUE", "YES", "FALSE", "NO", "FIRST", "LAST", "SIZE", "ANYKEY", "SUBQUERY", "FETCH", "CAST", "TRUEPREDICATE", "FALSEPREDICATE", "UTI-CONFORMS-TO", "UTI-EQUALS"]

You'll want to do a case-insensitive comparison.

Senseful
  • 86,719
  • 67
  • 308
  • 465
  • The reserved words are listed at the end of this page: [Predicate Format String Syntax](https://developer.apple.com/library/archive/documentation/Cocoa/Conceptual/Predicates/Articles/pSyntax.html). They are case-insensitive. – Martin R Nov 09 '20 at 20:41
  • 1
    A safe method is to use key path substitution, e.g. `NSPredicate(format: "%K + 1 == 3", "size")`. – Martin R Nov 09 '20 at 20:48
  • Good point! However, the user is supplying the full expression, so I can't use `%K`. E.g. they are typing the entire string: `mySize + 1 == 3`. – Senseful Nov 11 '20 at 03:39