swift-math-parser

3.0.0

Math expression parser built with Point•Free's swift-parsing package
bradhowes/swift-math-parser

What's New

Everything, Everywhere, All at Once

2022-12-12T21:57:05Z

Built with latest version swift-parser library (0.11.0).
Better detection of implied multiplication involving combine symbols.

CI COV License: MIT

swift-math-parser

Basic math expression parser built with Point•Free's swift-parsing package (v0.11.0).

let parser = MathParser()
let evaluator = parser.parse('4 × sin(t × π) + 2 × sin(t × π)')
evaluator.eval("t", value: 0.0) // => 0.0
evaluator.eval("t", value: 0.25) // => 4.2426406871192848
evaluator.eval("t", value: 0.5) // => 6
evaluator.eval("t", value: 1.0) // => 0

The parser will return nil if it is unable to completely parse the expression.

By default, the expression parser and evaluator handle the following symbols and functions:

  • Constants: pi (π) and e
  • 1-argument functions: sin, cos, tan, log10, ln (loge), log2, exp, ceil, floor, round, sqrt ()
  • 2-argument functions: atan, hypot, pow 1
  • alternative math operator symbols: × for multiplication and ÷ for division (see example above for use of ×)

You can reference additional symbols or variables and functions by providing your own mapping functions. There are two places where this can be done:

  • MathParser.init
  • MathParser.Evaluator.eval

If a symbol or function does not exist during an eval call, the final result will be NaN. If a symbol is resolved during parsing, it will be replaced with the symbol's value. Otherwise, it will be resolved during a future eval call. Same for function calls -- if the function is known during parsing and all arguments have a known value, then it will be replaced with the function result. Otherwise, the function call will take place during an eval call.

Example:

let mySymbols = ["foo": 123.4]
let myFuncs: [String:(Double)->Double] = ["twice": {$0 + $0}]
let parser = MathParser(symbols: mySymbols.producer, unaryFunctions: myFuncs.producer)
let myEvalFuncs: [String:(Double)->Double] = ["power": {$0 * $0}]
let evaluator = parser.parse("power(twice(foo))")

# Expression parsed and `twice(foo)` resolved to `246.8` but `power` is still unknown
evaluator?.value // => nan

# Give evaluator way to resolve `power(246.8)`
evaluator?.eval(unaryFunctions: myEvalFuncs.producer) // => 60910.240000000005

Implied Multiplication

One of the original goals of this parser was to be able to accept a Wolfram Alpha math expression more or less as-is -- for instance the definition https://www.wolframalpha.com/input/?i=Sawsbuck+Winter+Form%E2%80%90like+curve -- without any editing. Here is the start of the textual representation from the above link:

x(t) = ((-2/9 sin(11/7 - 4 t) + 78/11 sin(t + 11/7) + 2/7 sin(2 t + 8/5) ...

Skipping over the assignment one can readily see that the representation includes implied multiplication between terms when there are no explicit math operators present (eg -2/9 x sin(11/7 - 4 x t)). There is support for this sort of operation in the parser that can be enabled by setting enableImpliedMultiplication when creating a new MathParser instance (it defaults to false). Note that when enabled, an expression such as 2^3 2^4 would be considered a valid expression, resolving to 2^3 * 2^4 = 128, and 4sin(t(pi)) would become 4 * sin(t * pi).

Here is the original example expression with implied multiplication:

let parser = MathParser(enableImpliedMultiplication: true)
let evaluator = parser.parse("4sin(t π) + 2sin(t π)")
evaluator.eval("t", value: 0.0) // => 0.0
evaluator.eval("t", value: 0.25) // => 4.2426406871192848
evaluator.eval("t", value: 0.5) // => 6
evaluator.eval("t", value: 1.0) // => 0

Note that with enableImpliedMultiplication enabled, will be broken into t and π even though one could have a symbol called . This eager splitting of a symbol may cause unexpected multiplication. For instance, if you have a symbol defined with the name pin, this will be split into pi and n because pi / π is a known symbol. The best way to protect from this happening is to not enable enabledImpliedMultiplication but an alternative is to always separate individual symbols with a space -- or place one inside a pair of parentheses.

Footnotes

  1. Redundant since there is already the ^ operator.

Description

  • Swift Tools 5.5.0
View More Packages from this Author

Dependencies

Last updated: Sun Jan 22 2023 18:24:57 GMT-0500 (GMT-05:00)