commit 13b74b03f38fb2d4571091193438d8e3d6df391d Author: Mahdi Dibaiee Date: Thu Nov 3 01:42:22 2016 +0330 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..35bf8ec --- /dev/null +++ b/.gitignore @@ -0,0 +1,23 @@ +#### joe made this: http://goel.io/joe +#### haskell #### +dist +dist-* +cabal-dev +*.o +*.hi +*.chi +*.chs.h +*.dyn_o +*.dyn_hi +.hpc +.hsenv +.cabal-sandbox/ +cabal.sandbox.config +*.prof +*.aux +*.hp +*.eventlog +.stack-work/ +cabal.project.local +.HTF/ + diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..f44d204 --- /dev/null +++ b/LICENSE @@ -0,0 +1,101 @@ + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 + + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, diff --git a/README.md b/README.md new file mode 100644 index 0000000..5718275 --- /dev/null +++ b/README.md @@ -0,0 +1,41 @@ +mathexpr +======== +Ever wanted to take as input a mathematical expression to fill in values and use it? `mathexpr` is exactly what you need. + +_I also wrote this in JavaScript some time ago: [Equation](https://github.com/mdibaiee/Equation)_ + +Examples +-------- +Simple evaluation of expressions: + +```haskell +import Data.MathExpr + +main = do + expr <- getLine -- a mathematical expression, e.g. sin x + y ^ 2 + -- replace x and y with the specified values and evaluate: sin 2 + 5 ^ 2 = 25.909.. + print $ evaluate def expr [("x", 2), ("y", 5)] +``` + +Using custom operators and functions: + +```haskell +import Data.MathExpr + + +-- operators are in the form (character, precedence, function) +-- example: ('+', 0, (+)), ('*', 1, (*)) +-- the function should have the type (Double -> Double -> Double) +-- the higher the precedence, the sooner the operator operates + +-- functions are in the form (name, function) +-- example: ("ln", log) +-- the function should have the type (Double -> Double) + +main = + let avg a b = (a + b) / 2 + let settings = Settings { operators = defaultOperators ++ [('~', 0, avg)] + , functions = defaultFunctions ++ [("trunc", fromIntegral . truncate)] + evaluate settings "3 ~ 5" [] -- 4 + evaluate settings "trunc 1.1" [] -- 1 +``` diff --git a/Setup.hs b/Setup.hs new file mode 100644 index 0000000..9a994af --- /dev/null +++ b/Setup.hs @@ -0,0 +1,2 @@ +import Distribution.Simple +main = defaultMain diff --git a/mathexpr.cabal b/mathexpr.cabal new file mode 100644 index 0000000..b4ca758 --- /dev/null +++ b/mathexpr.cabal @@ -0,0 +1,25 @@ +name: mathexpr +version: 0.1.0.0 +synopsis: Parse and evaluate math expressions with variables and functions +description: A simple tool to evaluate math expressions as strings with support for custom functions and operators +homepage: https://github.com/mdibaiee/mathexpr +license: GPL-3 +license-file: LICENSE +author: Mahdi Dibaiee +maintainer: mdibaiee@aol.com +copyright: 2016 Mahdi Dibaiee +category: Math +build-type: Simple +-- extra-source-files: +cabal-version: >=1.10 + +library + hs-source-dirs: src + exposed-modules: Data.MathExpr + build-depends: base >= 4.7 && < 5, + data-default-class + default-language: Haskell2010 + +source-repository head + type: git + location: https://github.com/mdibaiee/mathexpr diff --git a/src/Data/MathExpr.hs b/src/Data/MathExpr.hs new file mode 100644 index 0000000..405db19 --- /dev/null +++ b/src/Data/MathExpr.hs @@ -0,0 +1,108 @@ +module Data.MathExpr + ( evaluate + , Settings (..) + , defaultFunctions + , defaultOperators + ) where + import Data.Default.Class + import Data.Maybe (isJust, fromJust) + import Debug.Trace + import Data.List (find) + + data Settings = Settings { operators :: [(Char, Int, Double -> Double -> Double)] + , functions :: [(String, Double -> Double)] + } + + defaultOperators = [ + ('+', 0, (+)), ('-', 0, (-)), + ('*', 1, (*)), ('/', 1, (/)), + ('^', 2, (**)) + ] + defaultFunctions = [("ln", log), ("sin", sin), ("cos", cos)] + + instance Default Settings where + def = Settings { operators = defaultOperators + , functions = defaultFunctions + } + toPostfix :: Settings -> String -> String + toPostfix settings s = helper (tokenize s) [] [] + where + ops = operators settings + fns = functions settings + + helper :: [String] -> [String] -> String -> String + helper [] os out = out ++ concat os + helper (c:cs) os out + | head c == '(' = helper cs (c:os) out + | head c == ')' && head os == "(" = helper cs (tail os) out + | head c == ')' = helper (c:cs) (tail os) (out ++ pad (head os)) + | isOperator c && (null os || precedence c > precedence (head os)) = helper cs (c:os) out + | isOperator c = helper (c:cs) (tail os) (out ++ pad (head os)) + | otherwise = helper cs os (out ++ pad c) + + isOperator cs = isOp cs || isFunction cs + isOp cs = isJust $ (head cs) `triLookup` ops + isFunction cs = isJust $ cs `lookup` fns + precedence cs + | isFunction cs = Just 999 + | otherwise = (head cs) `triLookup` ops + + tokenize :: String -> [String] + tokenize str = words $ helper str + where + helper :: String -> String + helper [] = [] + helper (c:cs) + | isAlphanumeric c = c : helper cs + | isSymbol c = pad [c] ++ helper cs + + replaceVariables :: String -> [(String, Double)] -> String + replaceVariables str [] = str + replaceVariables str vars = concatMap replace (tokenize str) + where + replace c + | isVariable c = pad $ show $ fromJust $ c `lookup` vars + | otherwise = c + + isVariable c = isJust $ c `lookup` vars + + -- | Evaluate an expression + -- Example: `evaluate def "x + y ^ 2" [("x", 1), ("y", 2)] + evaluate :: Settings -> String -> [(String, Double)] -> Double + evaluate settings expr vars = + let postfix = toPostfix settings expr + replaced = replaceVariables postfix vars + in helper (tokenize replaced) [] + where + ops = operators settings + fns = functions settings + + helper :: [String] -> [String] -> Double + helper [] [o] = read o + helper (c:cs) os + | isOperator c = + let result = (operatorFunction c) (read . head . tail $ os) (read . head $ os) + in helper cs $ (show result) : drop 2 os + | isFunction c = + let result = (function c) (read . head $ os) + in helper cs $ (show result) : tail os + | otherwise = helper cs (c:os) + + isOperator cs = isJust $ (head cs) `triLookup` ops + isFunction cs = isJust $ cs `lookup` fns + + function cs = fromJust $ cs `lookup` fns + operatorFunction cs = case find (\(a, _, _) -> a == head cs) ops of + Just (_, _, c) -> c + Nothing -> const (const 0) + isParen cs = head cs `elem` ['(', ')'] + + alphanumeric = '.' : ['a'..'z'] ++ ['0'..'9'] + isAlphanumeric = (`elem` alphanumeric) + isSymbol = not . (`elem` alphanumeric) + + triLookup :: (Eq a) => a -> [(a, b, c)] -> Maybe b + triLookup a x = lookup a $ map (\(a, b, _) -> (a, b)) x + + pad :: String -> String + pad x = ' ' : x ++ [' '] diff --git a/stack.yaml b/stack.yaml new file mode 100644 index 0000000..9493ffe --- /dev/null +++ b/stack.yaml @@ -0,0 +1,66 @@ +# This file was automatically generated by 'stack init' +# +# Some commonly used options have been documented as comments in this file. +# For advanced use and comprehensive documentation of the format, please see: +# http://docs.haskellstack.org/en/stable/yaml_configuration/ + +# Resolver to choose a 'specific' stackage snapshot or a compiler version. +# A snapshot resolver dictates the compiler version and the set of packages +# to be used for project dependencies. For example: +# +# resolver: lts-3.5 +# resolver: nightly-2015-09-21 +# resolver: ghc-7.10.2 +# resolver: ghcjs-0.1.0_ghc-7.10.2 +# resolver: +# name: custom-snapshot +# location: "./custom-snapshot.yaml" +resolver: lts-7.7 + +# User packages to be built. +# Various formats can be used as shown in the example below. +# +# packages: +# - some-directory +# - https://example.com/foo/bar/baz-0.0.2.tar.gz +# - location: +# git: https://github.com/commercialhaskell/stack.git +# commit: e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# - location: https://github.com/commercialhaskell/stack/commit/e7b331f14bcffb8367cd58fbfc8b40ec7642100a +# extra-dep: true +# subdirs: +# - auto-update +# - wai +# +# A package marked 'extra-dep: true' will only be built if demanded by a +# non-dependency (i.e. a user package), and its test suites and benchmarks +# will not be run. This is useful for tweaking upstream packages. +packages: +- '.' +# Dependency packages to be pulled from upstream that are not in the resolver +# (e.g., acme-missiles-0.3) +extra-deps: [] + +# Override default flag values for local packages and extra-deps +flags: {} + +# Extra package databases containing global packages +extra-package-dbs: [] + +# Control whether we use the GHC we find on the path +# system-ghc: true +# +# Require a specific version of stack, using version ranges +# require-stack-version: -any # Default +# require-stack-version: ">=1.1" +# +# Override the architecture used by stack, especially useful on Windows +# arch: i386 +# arch: x86_64 +# +# Extra directories used by stack for building +# extra-include-dirs: [/path/to/dir] +# extra-lib-dirs: [/path/to/dir] +# +# Allow a newer minor version of GHC than the snapshot specifies +# compiler-check: newer-minor \ No newline at end of file