{"id":15038579,"url":"https://github.com/bradhowes/swift-math-parser","last_synced_at":"2025-04-10T00:07:00.864Z","repository":{"id":38053860,"uuid":"432476632","full_name":"bradhowes/swift-math-parser","owner":"bradhowes","description":"Math expression parser built with Point•Free's swift-parsing package","archived":false,"fork":false,"pushed_at":"2024-08-15T20:03:48.000Z","size":662,"stargazers_count":61,"open_issues_count":0,"forks_count":8,"subscribers_count":2,"default_branch":"main","last_synced_at":"2025-04-10T00:06:51.795Z","etag":null,"topics":["math","parser","parser-combinators","pointfree","swift","wolfram-alpha"],"latest_commit_sha":null,"homepage":"https://bradhowes.github.io/swift-math-parser/documentation/mathparser/","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/bradhowes.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null},"funding":{"github":"bradhowes"}},"created_at":"2021-11-27T14:02:34.000Z","updated_at":"2024-09-09T05:02:17.000Z","dependencies_parsed_at":"2024-02-23T20:44:23.011Z","dependency_job_id":null,"html_url":"https://github.com/bradhowes/swift-math-parser","commit_stats":{"total_commits":86,"total_committers":1,"mean_commits":86.0,"dds":0.0,"last_synced_commit":"6065e3e40251bcfd65c78a0cef299a4ac0c1c7d5"},"previous_names":[],"tags_count":29,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2Fswift-math-parser","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2Fswift-math-parser/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2Fswift-math-parser/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/bradhowes%2Fswift-math-parser/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/bradhowes","download_url":"https://codeload.github.com/bradhowes/swift-math-parser/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248131316,"owners_count":21052819,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["math","parser","parser-combinators","pointfree","swift","wolfram-alpha"],"created_at":"2024-09-24T20:39:02.065Z","updated_at":"2025-04-10T00:07:00.822Z","avatar_url":"https://github.com/bradhowes.png","language":"Swift","readme":"[![CI](https://github.com/bradhowes/swift-math-parser/workflows/CI/badge.svg)](https://github.com/bradhowes/swift-math-parser)\n[![COV](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/bradhowes/ad941184ed256708952a2057fc5d7bb4/raw/swift-math-parser-coverage.json)](https://github.com/bradhowes/swift-math-parser/blob/main/.github/workflows/CI.yml)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbradhowes%2Fswift-math-parser%2Fbadge%3Ftype%3Dswift-versions)](https://swiftpackageindex.com/bradhowes/swift-math-parser)\n[![](https://img.shields.io/endpoint?url=https%3A%2F%2Fswiftpackageindex.com%2Fapi%2Fpackages%2Fbradhowes%2Fswift-math-parser%2Fbadge%3Ftype%3Dplatforms)](https://swiftpackageindex.com/bradhowes/swift-math-parser)\n[![License: MIT](https://img.shields.io/badge/License-MIT-A31F34.svg)](https://opensource.org/licenses/MIT)\n\n# swift-math-parser\n\nBasic math expression parser built with [Point•Free's](https://www.pointfree.co/)\n[swift-parsing](https://github.com/pointfreeco/swift-parsing) package (v0.12.0). See the [API documentation](https://bradhowes.github.io/swift-math-parser/documentation/mathparser/) for developer info.\n\nNOTE: v3.1.0 uses swift-parsing v0.12 which requires Xcode 14 and ideally Swift 5.8 \n(see their [What's Changed](https://github.com/pointfreeco/swift-parsing/releases/tag/0.12.0) doc for additional details).\nIf you need to use an older version, use the tagged 3.0.1 release instead.\n\n# Usage Example\n\n```swift\nlet parser = MathParser()\nlet evaluator = parser.parse(\"4 × sin(t × π) + 2 × sin(t × π)\")\nevaluator.eval(\"t\", value: 0.0) // =\u003e 0.0\nevaluator.eval(\"t\", value: 0.25) // =\u003e 4.2426406871192848\nevaluator.eval(\"t\", value: 0.5) // =\u003e 6\nevaluator.eval(\"t\", value: 1.0) // =\u003e 0\n```\n\nThe parser will return `nil` if it is unable to completely parse the expression. Alternatively, you can call the\n`parseResult` to obtain a Swift `Result` enum that will have a `MathParserError` value when parsing fails. This\nwill contain a description of the parsing failure that comes from the swift-parsing library.\n\n```swift\nlet evaluator = parser.parseResult(\"4 × sin(t × π\")\nprint(evaluator)\nfailure(error: unexpected input\n --\u003e input:1:8\n1 | 4 × sin(t × π\n  |        ^ expected end of input)\n\n```\n\nBy default, the expression parser and evaluator handle the following symbols and functions:\n\n* Standard math operations: addition (`+`), subtraction (`-`), multiplication (`*`), division (`/`), \nand exponentiation (`^`)\n* The factorial operator (`!`) [^2]\n* Constants: `pi` (`π`) and `e`\n* 1-argument functions: \n  * trigonometric functions: `sin`, `asin`, `cos`, `acos`, `tan`, `atan`, `sec`, `csc`, `ctn`\n  * hyperbolic functions: `sinh`, `asinh`, `cosh`, `acosh`, `tanh`, `atanh`\n  * logarithmic and exponential functions: `log10`, `ln` (`loge`), `log2`, `exp`\n  * others: `ceil`, `floor`, `round`, `sqrt` (`√`), `cbrt` (cube root), `abs`, and `sgn`\n* 2-argument functions: `atan2`, `hypot`, `pow` [^1]\n* alternative math operator symbols: `×` for multiplication and `÷` for division (see example above for use of `×`)\n\nYou can reference additional symbols or variables and functions by providing your own mapping functions. There are two\nplaces where this can be done:\n\n* `MathParser.init`\n* `Evaluator.eval`\n\nIf a symbol or function does not exist during an `eval` call, the final result will be `NaN`. If a symbol is resolved\nduring parsing, it will be replaced with the symbol's value. Otherwise, it will be resolved during a future `eval` call.\nSame for function calls -- if the function is known during parsing _and_ all arguments have a known value, then it will\nbe replaced with the function result. Otherwise, the function call will take place during an `eval` call.\n\nYou can get the unresolved symbol names from the `Evaluator.unresolved` attribute. It returns three collections for\nunresolved variables, unary functions, and binary function names. You can also use the `evalResult` to attempt an\nevaluation but also obtain a description of the failure when the evaluation fails.\n\n# Custom Symbols\n\nBelow is an example that provides a custom unary function that returns the twice the value it receives. There is also a\ncustom variable called `foo` which holds the constant `123.4`.\n\n```swift\nlet myVariables = [\"foo\": 123.4]\nlet myFuncs: [String:(Double)-\u003eDouble] = [\"twice\": {$0 + $0}]\nlet parser = MathParser(variables: myVariables.producer, unaryFunctions: myFuncs.producer)\nlet evaluator = parser.parse(\"power(twice(foo))\")\n\n# Expression parsed and `twice(foo)` resolved to `246.8` but `power` is still unknown\nevaluator?.value // =\u003e nan\nevaluator?.unresolved.unaryFunctions // =\u003e ['power']'\n# Give evaluator way to resolve `power(246.8)`\nlet myEvalFuncs: [String:(Double)-\u003eDouble] = [\"power\": {$0 * $0}]\nevaluator?.eval(unaryFunctions: myEvalFuncs.producer) // =\u003e 60910.240000000005\n```\n\nInstead of passing a closure to access the dictionary of symbols, you can pass the dictionary itself:\n\n```\nlet parser = MathParser(variableDict: myVariables, unaryFunctionDict: myFuncs)\nevaluator?.eval(unaryFunctionDict: myEvalFuncs) // =\u003e 60910.240000000005\n```\n\n# Precedence\n\nThe usual math operations follow the traditional precedence hierarchy: multiplication and division operations happen\nbefore addition and subtraction, so `1 + 2 * 3 - 4 / 5 + 6` evaluates the same as `1 + (2 * 3) - (4 / 5) + 6`. \nThere are three additional operators, one for exponentiations (^) which is higher than the previous ones, \nso `2 * 3 ^ 4 + 5` is the same as `2 * (3 ^ 4) + 5`. It is also right-associative, so `2 ^ 3 ^ 4` is evaluated as \n`2 ^ (3 ^ 4)` instead of `(2 ^ 3) ^ 4`.\n\nThere are two other operations that are even higher in precedence than exponentiation:\n\n* negation (`-`) -- `-3.4`\n* factorial (`!`) -- `12!`\n\nNote that factorial of a negative number is undefined, so negation and factorial cannot be combined. In other words,\nparsing `-3!` returns `nil`. Also, factorial is only done on the integral portion of a number, so `12.3!` will parse but\nthe resulting value will be the same as `12!`. In effect, factorial always operates as `floor(x)!` or `!(floor(x))`.\n\n# Implied Multiplication\n\nOne of the original goals of this parser was to be able to accept a Wolfram Alpha math expression more or less as-is\n-- for instance the definition https://www.wolframalpha.com/input/?i=Sawsbuck+Winter+Form%E2%80%90like+curve -- without\nany editing. Here is the start of the textual representation from the above link:\n\n```\nx(t) = ((-2/9 sin(11/7 - 4 t) + 78/11 sin(t + 11/7) + 2/7 sin(2 t + 8/5) ...\n```\n\nSkipping over the assignment one can readily see that the representation includes implied multiplication between terms\nwhen there are no explicit math operators present (eg `-2/9` __x__ `sin(11/7 - 4` __x__ `t)`). There is support for this\nsort of operation in the parser that can be enabled by setting `enableImpliedMultiplication` when creating a new\n`MathParser` instance (it defaults to `false`). Note that when enabled, an expression such as `2^3 2^4` would be\nconsidered a valid expression, resolving to `2^3 * 2^4 = 128`, and `4sin(t(pi))` would become `4 * sin(t * pi)`.\n\nYou can see the entire Wolfram example in the [TestWolfram](Tests/MathParserTests/TestWolfram.swift) test case.\n\nHere is the original example expression from the start of this README file with implied multiplication in use (all of \nthe muliplication symbols have been removed):\n\n```swift\nlet parser = MathParser(enableImpliedMultiplication: true)\nlet evaluator = parser.parse(\"4sin(t π) + 2sin(t π)\")\nevaluator.eval(\"t\", value: 0.0) // =\u003e 0.0\nevaluator.eval(\"t\", value: 0.25) // =\u003e 4.2426406871192848\nevaluator.eval(\"t\", value: 0.5) // =\u003e 6\nevaluator.eval(\"t\", value: 1.0) // =\u003e 0\n```\n\nBe aware that with implied multiplication enabled, you could encounter strange parsing if you do not use spaces between\nthe \"-\" operator:\n\n* `2-3` =\u003e -6\n* `2 -3` -\u003e -6\n* `2 - 3` =\u003e -1\n\nHowever, for \"+\" all is well:\n\n* `2+3` =\u003e 5\n* `2 +3` -\u003e 5\n* `2 + 3` =\u003e 5\n\nUnfortunately, there is no way to handle this ambiguity between implied multiplication, subtraction and negation when \nspaces are not used to signify intent. \n\n## Symbol Splitting\n\nWhen implied multiplication mode is active and the name of a variable or a 1-parameter (unary) function is not found in\ntheir corresponding map, the token evaluation routine will attempt to resolve them by splitting the names into two or\nmore pieces that all resolve to known variables and/or functions. For example, using the default variable map and \nunary function map from `MathParser`:\n\n* `pie` =\u003e `pi * e`\n* `esin(2π)` =\u003e `e * sin(2 * pi)`\n* `eeesgn(-1)` =\u003e `e * e * e * -1`\n\nAs you can see, this could lead to erroneous resolution of variable names and functions, but this behavior is only used\nwhen the initial lookup of the name fails, and it is never performed when the symbol names are separated by a space.\nHowever, if you make a mistake and forget to provide the definition of a custom variable or function, it could provide\na value instead of an error. For instance, consider evaluating `tabs(-3)` where `t` is a custom variable set to `1.2`\nand `tabs` is a custom function but it is not provided for in the custom unary function map:\n\n* `tabs(-3)` =\u003e `1.2 * abs(-3)` =\u003e `3.6`\n\nIf implied multiplication had not been active, the evaluator would have correctly reported an issue -- either returning\nNaN or a `Result.failure` describing the missing function.\n\n[^1]: Redundant since there is already the `^` operator.\n[^2]: Exact up to 20! -- larger numbers are approximations\n","funding_links":["https://github.com/sponsors/bradhowes"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbradhowes%2Fswift-math-parser","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbradhowes%2Fswift-math-parser","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbradhowes%2Fswift-math-parser/lists"}