Skip to content

Add trigonometric functions & more #2

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

Open
wants to merge 27 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
154 changes: 154 additions & 0 deletions Sources/jsonlogic/Parser.swift
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,64 @@ struct Comparison: Expression {
}
}

struct Round: Expression {
let arg: Expression

func evalWithData(_ data: JSON?) throws -> JSON {
let result = try arg.evalWithData(data)
switch result {
case let .Array(array) where array.count == 2:
guard let numberToRound = array[0].double,
let numberOfPlaces = array[1].int
else { fallthrough }

let divisor = pow(10.0, Double(numberOfPlaces))
let result = (numberToRound * divisor).rounded() / divisor
return .Double(result)
default:
return result.toNumber()
}
}
}

struct CastToNumber: Expression {
let arg: Expression

func evalWithData(_ data: JSON?) throws -> JSON {
let result = try arg.evalWithData(data)

switch result {
case let .Array(array):
let stringArray = array.compactMap { element -> String? in
switch element {
case .String(let value):
return value
default:
return nil
}
}

guard stringArray.isEmpty == false else { return .Null }

let doubleArray = stringArray.compactMap(Double.init).map(JSON.Double)

guard doubleArray.isEmpty == false else { return .Null }

if doubleArray.count == 1 {
return doubleArray[0]
}

return .Array(doubleArray)
case let .String(string):
guard let value = Double(string) else { return .Null }

return .Double(value)
default:
return .Null
}
}
}

//swiftlint:disable:next type_name
struct If: Expression {
let arg: ArrayOfExpressions
Expand Down Expand Up @@ -216,6 +274,92 @@ struct Not: Expression {
}
}

struct Sin: Expression {
let arg: Expression

func evalWithData(_ data: JSON?) throws -> JSON {
let jsonValue = try arg.evalWithData(data)

if case let JSON.Array(array) = try arg.evalWithData(data) {
guard array.count > 1 else {
return array.first?.double.flatMap(sin).flatMap(JSON.Double) ??
array.first?.int.flatMap(Double.init).flatMap(sin).flatMap(JSON.Double) ??
JSON.Null
}

return JSON.Array(array.compactMap { value in
return value.double.flatMap(sin).flatMap(JSON.Double) ??
value.int.flatMap(Double.init).flatMap(sin).flatMap(JSON.Double)
})
} else if case let JSON.Int(number) = jsonValue {
let sinValue = sin(Double(number))
return JSON.Double(sinValue)
} else if case let JSON.Double(number) = jsonValue {
return JSON.Double(sin(number))
} else {
return JSON.Null
}
}
}

struct Cos: Expression {
let arg: Expression

func evalWithData(_ data: JSON?) throws -> JSON {
let jsonValue = try arg.evalWithData(data)

if case let JSON.Array(array) = try arg.evalWithData(data) {
guard array.count > 1 else {
return array.first?.double.flatMap(cos).flatMap(JSON.Double) ??
array.first?.int.flatMap(Double.init).flatMap(cos).flatMap(JSON.Double) ??
JSON.Null
}

return JSON.Array(array.compactMap { value in
return value.double.flatMap(cos).flatMap(JSON.Double) ??
value.int.flatMap(Double.init).flatMap(cos).flatMap(JSON.Double)
})
} else if case let JSON.Int(number) = jsonValue {
let sinValue = cos(Double(number))
return JSON.Double(sinValue)
} else if case let JSON.Double(number) = jsonValue {
return JSON.Double(cos(number))
} else {
return JSON.Null
}
}

}

struct Tan: Expression {
let arg: Expression

func evalWithData(_ data: JSON?) throws -> JSON {
let jsonValue = try arg.evalWithData(data)

if case let JSON.Array(array) = try arg.evalWithData(data) {
guard array.count > 1 else {
return array.first?.double.flatMap(tan).flatMap(JSON.Double) ??
array.first?.int.flatMap(Double.init).flatMap(tan).flatMap(JSON.Double) ??
JSON.Null
}

return JSON.Array(array.compactMap { value in
return value.double.flatMap(tan).flatMap(JSON.Double) ??
value.int.flatMap(Double.init).flatMap(tan).flatMap(JSON.Double)
})
} else if case let JSON.Int(number) = jsonValue {
let sinValue = tan(Double(number))
return JSON.Double(sinValue)
} else if case let JSON.Double(number) = jsonValue {
return JSON.Double(tan(number))
} else {
return JSON.Null
}
}

}

struct Max: Expression {
let arg: Expression

Expand Down Expand Up @@ -660,6 +804,10 @@ class Parser {
return Comparison(arg: try self.parse(json: value), operation: >=)
case "<=":
return Comparison(arg: try self.parse(json: value), operation: <=)
case "rnd":
return Round(arg: try self.parse(json: value))
case "num":
return CastToNumber(arg: try self.parse(json: value))
case "if", "?:":
guard let array = try self.parse(json: value) as? ArrayOfExpressions else {
throw ParseError.GenericError("\(key) statement be followed by an array")
Expand All @@ -676,6 +824,12 @@ class Parser {
return Max(arg: try self.parse(json: value))
case "min":
return Min(arg: try self.parse(json: value))
case "sin":
return Sin(arg: try self.parse(json: value))
case "cos":
return Cos(arg: try self.parse(json: value))
case "tan":
return Tan(arg: try self.parse(json: value))
case "substr":
guard let array = try self.parse(json: value) as? ArrayOfExpressions else {
throw ParseError.GenericError("\(key) statement be followed by an array")
Expand Down
78 changes: 78 additions & 0 deletions Tests/jsonlogicTests/ArithmeticTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -167,4 +167,82 @@ class Arithmetic: XCTestCase {
"""
XCTAssertEqual(2, try applyRule(rule, to: nil))
}

func testSin() {
var rule =
"""
{ "sin": [\(Double.pi / 2)] }
"""

XCTAssertEqual(1, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "sin": [\(Double.pi / 6)] }
"""

XCTAssertEqual(0.5, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "sin": [\(Double.pi)] }
"""

XCTAssertEqual(0, try applyRule(rule, to: nil), accuracy: 0.002)
}

func testCos() {
var rule =
"""
{ "cos": [\(Double.pi / 2)] }
"""

XCTAssertEqual(0, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "cos": [\(Double.pi / 3)] }
"""

XCTAssertEqual(0.5, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "cos": [\(2 * Double.pi)] }
"""

XCTAssertEqual(1, try applyRule(rule, to: nil), accuracy: 0.002)
}

func testTan() {
var rule =
"""
{ "tan": [0] }
"""

XCTAssertEqual(0, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "tan": [\(Double.pi / 3)] }
"""

XCTAssertEqual(sqrt(3), try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "tan": [\(Double.pi / 4)] }
"""

XCTAssertEqual(1, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "tan": [\(Double.pi / 2)] }
"""

// In mathematics, this would be undefined
// In a programming language using IEEE 754, this is "a very large number"
XCTAssertGreaterThan(try applyRule(rule, to: nil), 1.5e16)
}
}
82 changes: 82 additions & 0 deletions Tests/jsonlogicTests/NumberCastTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// NumberCastTests.swift
// jsonlogicTests
//
// Created by Dino on 22/07/2019.
//

import XCTest
import JSON
@testable import jsonlogic

class NumberCastTests: XCTestCase {

func testCastNumericalString() {
var rule =
"""
{ "num": ["0"] }
"""

XCTAssertEqual(0, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "num": ["0.145"] }
"""

XCTAssertEqual(0.145, try applyRule(rule, to: nil), accuracy: 0.002)

rule =
"""
{ "num": [".145"] }
"""

XCTAssertEqual(0.145, try applyRule(rule, to: nil), accuracy: 0.002)


rule =
"""
{ "num": ["3.14159"] }
"""

XCTAssertEqual(Double.pi, try applyRule(rule, to: nil), accuracy: 0.002)
}

func testCastInvalidString() {
var rule =
"""
{ "num": ["Hello World"] }
"""

XCTAssertThrowsError(try applyRule(rule, to: nil) as Optional<Any>)

rule =
"""
{ "num": ["0.14AF"] }
"""

XCTAssertThrowsError(try applyRule(rule, to: nil) as Optional<Any>)

rule =
"""
{ "num": ["F0.14"] }
"""

XCTAssertThrowsError(try applyRule(rule, to: nil) as Optional<Any>)

rule =
"""
{ "num": ["0...14"] }
"""

XCTAssertThrowsError(try applyRule(rule, to: nil) as Optional<Any>)

rule =
"""
{ "num": ["2.1.4"] }
"""

XCTAssertThrowsError(try applyRule(rule, to: nil) as Optional<Any>)
}

}
Loading