-
Notifications
You must be signed in to change notification settings - Fork 0
/
CLIMath.sc
115 lines (94 loc) · 3.36 KB
/
CLIMath.sc
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
import $ivy.`com.lihaoyi::fastparse:2.2.2`, fastparse._
import SingleLineWhitespace._
import fastparse.Parsed.Failure
import fastparse.Parsed.Success
val help =
"""CLIMath - No Non-sense Arithmetic Operations on the CLI
Usage:
$ amm --silent CliMath.sc 10 + 4 + 2
> 16
$ amm --silent CliMath.sc "10 * (2 + 1.24)"
> 30.24
$ amm --silent CliMath.sc 100 000. 01 + 345,545.12 + 456.678.213,39
> 457123758.52
"""
@main
def main(expressions: String*): Unit = expressions.toList match {
case Nil | "-h" :: _ | "-help" :: _ | "help" :: _ | "--h" :: _ |
"--help" :: _ =>
println(help)
case other =>
calculate(other.mkString(" ")) match {
case failure: Failure =>
println("Couldn't parse expression")
println(failure.trace().failure.label)
case Success(value, _) =>
println(value.bigDecimal.toPlainString())
}
}
def unsignedInteger[_: P]: P[String] =
P((" ".rep(0) ~ CharIn("0-9").! ~~ (" ".rep(0) ~ CharIn("0-9").!).rep(0)))
.map { case (first, others) =>
first + others.mkString
}
def signedInteger[_: P]: P[String] =
("-" ~ unsignedInteger).map(unsigned => s"-$unsigned")
def anyInteger[_: P]: P[String] =
P(unsignedInteger | signedInteger)
def singleCommaDecimal[_: P]: P[String] =
P(anyInteger ~ "," ~ unsignedInteger).map { case (nonDecimal, decimal) =>
s"$nonDecimal.$decimal"
}
def singleDottedDecimal[_: P]: P[String] =
P(anyInteger ~ "." ~ unsignedInteger).map { case (nonDecimal, decimal) =>
s"$nonDecimal.$decimal"
}
def unsignedIntegerLimitedAtThree[_: P]: P[String] =
CharIn("0-9").rep(min = 1, max = 3).!
// Numbers shaped like: -120,000.99
def usNotationDecimal[_: P]: P[String] =
P(
"-".!.? ~
unsignedIntegerLimitedAtThree ~
("," ~ unsignedIntegerLimitedAtThree).rep(1) ~
"." ~ unsignedInteger
).map { case (maybeSign, firstNonDecimal, otherNonDecimal, decimal) =>
s"${maybeSign.getOrElse("")}$firstNonDecimal${otherNonDecimal.mkString}.$decimal"
}
// Numbers shaped like: -120.000,99
def euNotationDecimal[_: P]: P[String] =
P(
"-".!.? ~
unsignedIntegerLimitedAtThree ~
("." ~ unsignedIntegerLimitedAtThree).rep(1) ~
"," ~ unsignedInteger
).map { case (maybeSign, firstNonDecimal, otherNonDecimal, decimal) =>
s"${maybeSign.getOrElse("")}$firstNonDecimal${otherNonDecimal.mkString}.$decimal"
}
def number[_: P]: P[BigDecimal] = P(
euNotationDecimal |
usNotationDecimal |
singleDottedDecimal |
singleCommaDecimal |
anyInteger
).map(BigDecimal.exact)
def operate(operations: (BigDecimal, Seq[(String, BigDecimal)])) =
operations match {
case (firstNumber, otherOperations) =>
otherOperations.foldLeft(firstNumber) {
case (accumulator, ("+", number)) => accumulator + number
case (accumulator, ("-", number)) => accumulator - number
case (accumulator, ("*", number)) => accumulator * number
case (accumulator, ("/", number)) => accumulator / number
}
}
def factor[_: P]: P[BigDecimal] =
P(number | ("(" ~ addAndSub ~ ")"))
def mulAndDiv[_: P]: P[BigDecimal] =
P(factor ~ (("*" | "/").! ~ factor).rep).map(operate)
def addAndSub[_: P]: P[BigDecimal] =
P(mulAndDiv ~ (("+" | "-").! ~ mulAndDiv).rep).map(operate)
def parser[_: P]: P[BigDecimal] =
P(" ".rep ~ addAndSub ~ " ".rep ~ End)
def calculate(mathExpression: String): Parsed[BigDecimal] =
parse(mathExpression, parser(_))