Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Basic numeric expresion handling #86

Closed
wants to merge 2 commits into from

Conversation

djbe
Copy link
Contributor

@djbe djbe commented Dec 20, 2016

Stencil has boolean expression handling, but numeric ones. Instead of writing my own ridiculously complex expression parser, there exists this little gem of a Foundation class called NSExpression, so I just used that.

Small problem is that, while it accepts an object parameter, this is quite limited, as it won't know how to handle myArray.count because myArray isn't Key-Value compliant for count, and all object leaves must be number values. That's why I rolled my own resolver using Stencil's existing system.

There is a small gotcha with this whole system: if NSExpression fails parsing an expression, it throws an Objective-C exception, which Swift can't catch. There are solutions for this, such as http://stackoverflow.com/questions/32758811/catching-nsexception-in-swift, but they would require a small bit of objective-c code.

I've just made a quick implementation to throw the idea out there. It currently works for examples such as {{ (myArray.count + 5) / 3 - 1 }}, which is already really cool.

@krzysztofzablocki
Copy link
Contributor

fwiw. I use catching objective-c exceptions in Sourcery to recover from Stencil KVC resolution failures (in daemon mode)

@djbe
Copy link
Contributor Author

djbe commented Dec 20, 2016

@krzysztofzablocki
Copy link
Contributor

krzysztofzablocki commented Dec 20, 2016

@kylef kylef self-assigned this Jan 5, 2017
@kylef kylef self-requested a review January 5, 2017 21:22
@kylef
Copy link
Collaborator

kylef commented Feb 18, 2017

@djbe not having much luck with testing this. Am I doing something wrong?

 CompoundVariable can resolve a literal expression
  Tests/StencilTests/VariableSpec.swift:125 given value is nil

  ``
  try expect(result) == "3"
  ``
 CompoundVariable can resolve a variable expression
  Tests/StencilTests/VariableSpec.swift:131 given value is nil

  ``
  try expect(result) == "15"
  ``
diff --git a/Tests/StencilTests/VariableSpec.swift b/Tests/StencilTests/VariableSpec.swift
index d66dc1a..0723c40 100644
--- a/Tests/StencilTests/VariableSpec.swift
+++ b/Tests/StencilTests/VariableSpec.swift
@@ -19,16 +19,18 @@ fileprivate struct Article {
 
 
 func testVariable() {
-  describe("Variable") {
-    let context = Context(dictionary: [
-      "name": "Kyle",
-      "contacts": ["Katie", "Carlton"],
-      "profiles": [
-        "github": "kylef",
-      ],
-      "article": Article(author: Person(name: "Kyle"))
-    ])
+  let context = Context(dictionary: [
+    "name": "Kyle",
+    "contacts": ["Katie", "Carlton"],
+    "profiles": [
+      "github": "kylef",
+    ],
+    "article": Article(author: Person(name: "Kyle")),
+    "x": 5,
+    "y": 10,
+  ])
 
+  describe("Variable") {
 #if os(OSX)
     context["object"] = Object()
 #endif
@@ -115,4 +117,18 @@ func testVariable() {
     }
 #endif
   }
+
+  describe("CompoundVariable") {
+    $0.it("can resolve a literal expression") {
+      let variable = CompoundVariable("1 + 2")
+      let result = try variable.resolve(context) as? String
+      try expect(result) == "3"
+    }
+
+    $0.it("can resolve a variable expression") {
+      let variable = CompoundVariable("x + y")
+      let result = try variable.resolve(context) as? String
+      try expect(result) == "15"
+    }
+  }
 }

@djbe djbe force-pushed the feature/arithmetic-operators branch from 052fba2 to 5c241d9 Compare February 22, 2017 14:18
@djbe
Copy link
Contributor Author

djbe commented Feb 22, 2017

@kylef I've added some tests that should show how it works.
Small issue with your test code you posted is that the result of numeric expressions will be a number, not a string.

Edit: Apparently, the linux Foundation port does not implement NSExpression yet. We could use a typealias to only enable CompoundVariable on macOS:

#if os(Linux)
typealias VariableType = Variable
#else
typealias VariableType = CompoundVariable
#endif

@DivineDominion
Copy link

Would like to hereby +1 this :) 👍

@ilyapuchka
Copy link
Collaborator

ilyapuchka commented Sep 8, 2018

@djbe can we close this in favour of #183 ?

@djbe
Copy link
Contributor Author

djbe commented Sep 8, 2018

I don't know, I'm still not sure which is better.

  • NSExpression is much, much more flexible in what it allows a user to do, we get all the operators for "free" (see here https://nshipster.com/nsexpression/), but doesn't work on Linux yet.
  • With Arithmetic operations #183 we have Linux support, but only very limited expression support. Every other operator we may want to add, we'll have to write, test and maintain ourselves.

@ilyapuchka
Copy link
Collaborator

I feel Linux support is a better thing to have for project like Stencil than having a very powerful expressions engine under the hood 🤷‍♂️

@kylef kylef removed their request for review December 17, 2019 11:20
@kylef kylef removed their assignment Dec 17, 2019
@djbe
Copy link
Contributor Author

djbe commented Jul 29, 2022

If we ever implement this, we should use a library such as Expression.

The mechanism for triggering an expression "parse" could be taken from this PR, and we'd need to provide expression with our own symbol lookup function (which it supports).

@djbe djbe closed this Jul 29, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants