diff --git a/.gitignore b/.gitignore
new file mode 100644
index 000000000..c0e01feb8
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,53 @@
+HELP.md
+.gradle
+build/
+!gradle/wrapper/gradle-wrapper.jar
+!src/main/**
+!src/test/**
+
+### macOS ###
+.DS_Store
+
+### STS ###
+.apt_generated
+.classpath
+.factorypath
+.project
+.settings
+.springBeans
+.sts4-cache
+
+### IntelliJ IDEA ###
+.idea
+*.iws
+*.iml
+*.ipr
+out/
+
+### NetBeans ###
+/nbproject/private/
+/nbbuild/
+/dist/
+/nbdist/
+/.nb-gradle/
+
+### VS Code ###
+.vscode/
+# Ignore Gradle build output directory
+build
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Avoid ignore Gradle wrappper properties
+!gradle-wrapper.properties
+
+# Cache of project
+.gradletasknamecache
+
+### Gradle Patch ###
+# Java heap dump
+*.hprof
\ No newline at end of file
diff --git a/build.gradle b/build.gradle
new file mode 100644
index 000000000..2d6c6dc89
--- /dev/null
+++ b/build.gradle
@@ -0,0 +1,20 @@
+plugins {
+ id 'java'
+}
+
+group = 'com.programmers.calculator'
+version = '1.0-SNAPSHOT'
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ testImplementation platform('org.junit:junit-bom:5.9.1')
+ testImplementation 'org.junit.jupiter:junit-jupiter'
+ testImplementation 'org.assertj:assertj-core:3.22.0'
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/docs/README.md b/docs/README.md
new file mode 100644
index 000000000..9835fef44
--- /dev/null
+++ b/docs/README.md
@@ -0,0 +1,97 @@
+## ๐ ๊ณผ์ ์ค๋ช
+
+- [x] ์ฝ์๋ก ๊ตฌํ
+- [x] OOP ๊ณ์ฐ๊ธฐ ๊ตฌํ
+ - [x] ๋ํ๊ธฐ
+ - [x] ๋นผ๊ธฐ
+ - [x] ๊ณฑํ๊ธฐ
+ - [x] ๋๋๊ธฐ
+ - [x] ์ฐ์ ์์ (์ฌ์น์ฐ์ฐ)
+- [x] ํ
์คํธ ์ฝ๋ ๊ตฌํ
+- [x] ๊ณ์ฐ ์ด๋ ฅ์ ๋งต์ผ๋ก ๋ฐ์ดํฐ ์ ์ฅ ๊ธฐ๋ฅ
+ - ์ ํ๋ฆฌ์ผ์ด์
์ด ๋์ํ๋ ๋์ DB ์ธ์ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ ์ ์๋ ๋ฐฉ๋ฒ ๊ณ ๋ฏผ
+- [x] ์ ๊ท์ ์ฌ์ฉ
+
+
+
+## ๐ฉโ๐ป ์๊ตฌ ์ฌํญ๊ณผ ๊ตฌํ ๋ด์ฉ
+
+1. ์
๋ ฅ๊ณผ ์ถ๋ ฅ์ด ํ์ฌ๋ ์ฝ์์ด์ง๋ง, ๊ฐ๊ธฐ ๋ค๋ฅธ ํํ๋ก ๋ฐ๋ ์ ์์ด์ ์ธํฐํ์ด์ค๋ก ์ถ์ํํ๊ณ ์ด๋ฅผ ๊ตฌํํ InputConsole๊ณผ OutputConsole์ด ์๊ฒผ์ต๋๋ค.
+2. Calculator ํด๋์ค์์ ์
๋ ฅ์ ๋ฐ๊ฒ ํ๋ค๊ฐ ๊ณ์ฐ์ด๋ผ๋ ์ฑ
์์์ ๋ฉ์ด์ง๋ ๊ฒ ๊ฐ์์ Controller๋ฅผ ์ถ๊ฐํ์ฌ ์ฐ๊ฒฐ์์ผฐ์ต๋๋ค.
+3. Enum ํด๋์ค๋ฅผ ํ์ฉํ์ฌ ์ ํ ๊ฐ๋ฅํ ๋ฉ๋ด๋ฅผ ์ ์ํ์๊ณ ์์ธ์ฒ๋ฆฌ๋ฅผ ์งํํ์ต๋๋ค.
+4. Calculator ํด๋์ค์์๋ ๊ณ์ฐ์ด๋ผ๋ ๋์๋ง ์ํํ๊ณ Expression์ด๋ผ๋ ํํ์ ํด๋์ค์์ ํํ์๊ณผ ๊ด๋ จ๋ ๋ชจ๋ ์ฑ
์์ ๊ฐ์ต๋๋ค.
+ - ์ฐ์ฐ์์ ๋ํ ์ฑ
์์ Enum ํด๋์ค๋ฅผ ํ์ฉํ Operator์์ ๊ฐ์ง๊ณ ์์ต๋๋ค.
+5. ์ ์ฅ์๋ฅผ ๊ตฌํํ ๋, HistoryRepository๋ก ์ถ์ํํ์๊ณ ์ด๋ฅผ ๊ตฌํํ์ต๋๋ค. ์ถ๊ฐ๋ก, CalculationHistory ๊ฐ์ฒด๋ฅผ ์ด์ฉํ์ฌ ์ ์ฅ์ํค๋๋ก ๊ตฌํํ์ต๋๋ค.
+6. ๊ธฐ์กด์ ์ ๊ท์์ ๋ค๋ฃจ๋ ๊ณณ์ Expression ์ด์์ผ๋, ์์ ๊ณ์ฐ๋ง๋ค Pattern์ ์์ฑํ๋ ๋น์ฉ์ด ํฌ๋ค๊ณ ์๊ฐํ์ฌ, ๋ฐ๋ก util๋ก ๋บ์ต๋๋ค.
+ - Pattern ์์ฑ ๋น์ฉ์ ๊ดํ ๊ทผ๊ฑฐ๋, ํด๋์ค๋ฅผ ์ดํด๋ณด๋ฉด `new Pattern()` ํ๋ ๋ถ๋ถ์
๋๋ค.
+
+
+
+## ๐ญ 1์ฐจ ์๊ธฐ์ฃผ๋์ ๋ฆฌ๋ทฐ ํผ๋๋ฐฑ
+
+**์์๋ ํผ๋๋ฐฑ**
+1. ๊ฐ์ ๊ฒ์ฆ์๋ ์์ธ์ฒ๋ฆฌ๋ฅผ ํ์ง ์๋ ๊ฒ์ด ์ข์ต๋๋ค. (`Expression` ํด๋์ค์ `isNumber()` ๋ฉ์๋)
+2. `Calculator` ํด๋์ค์์ ํ๋๋ก `expression`์ ๊ฐ์ง๋ ๊ฒ์ ์๋ฌธ์ด ๋ญ๋๋ค.
+3. `Expression` ํด๋์ค๊ฐ ๋๋ฌด ๋น๋ํ๊ณ ํนํ `converToPostfix()` ๋ฉ์๋๋ฅผ ์ต๋ํ ์ชผ๊ฐ๋ด
์๋ค.
+4. `if-else` ๋ณด๋จ `switch`๋ฅผ ์ด์ฉํ์ฌ ๋ฆฌํฉํ ๋ง ํด๋ด
์๋ค.
+5. ์ต๋ํ 3 depth๋ฅผ ๋์ง ์๊ฒ ๋ฉ์๋๋ฅผ ๋ฆฌํฉํ ๋ง ํด๋ด
์๋ค.
+6. ์ฌ๋ฌ ๊ฐ์ฒด๊ฐ ์ฐธ์กฐํ๋ ํํ๊ฐ ์๋๋ผ๋ฉด, static ์ฌ์ฉ์ ๊ดํ์ฌ ๊ณ ๋ฏผํด๋ด
์๋ค.
+ - OutOfMemory์ ๊ดํด์๋ ๊ณ ๋ คํด๋ด
์๋ค.
+ - ๊ฐ๋ฅํ static์ ์ฌ์ฉํ์ง ์๊ณ ์ฝ๋๋ฅผ ์ง๋ด
์๋ค.
+7. Regex๋ ์์๋ฅผ ๋ชจ์๋๊ธฐ ์ํ ์ฅ์๊ฐ์๋ณด์ด๋๋ฐ, ์ด๋ค ๊ณ ๋ฏผ์ ํด๋ด์ผํ ๊น์? ์๊ฐํด๋ด
์๋ค.
+
+
+
+**ํ๊ตฌ๋ ํผ๋๋ฐฑ**
+1. ํ์ฌ ๋ชจ๋ ๊ตฌํ์ธ ๊ฒ ๊ฐ๊ณ , ํ์ฅ์ ๋๋ฌด ๋ซํ ์์ต๋๋ค.
+2. ๊ถ๊ธํ ๊ฒ์ ์ค๋ช
ํ๊ณ ์ถ์ ์๋๋ ์๊ฒ ์ผ๋, ์ฝ๋๋ฆฌ๋ทฐ ์์ฒญํ๋ ์
์ฅ์์ ์ฝ๋๋ฅผ ์ฃผ์์ฒ๋ฆฌํ ๊ฒ์ ๋ฐ๋์ ์์ ๋ด
์๋ค.
+3. ์ธ์คํด์ค๋ฅผ ์์ฑํ์ง ๋ชปํ๊ฒ ํ๊ณ ์ถ์ ๋, ๋ฐฉ์ด์ ์ผ๋ก ์ฝ๋ ์์ฑํ๋ ๊ฒ์ ์๊ฐํฉ์๋ค.
+4. 1๊ฐ์ ์ฝ์๋ก ์
์ถ๋ ฅ ํ๋ ์ํฉ์ธ๋ฐ, `InputConsole`๊ณผ `OutputConsole`์ด ๋ง์น 2๊ฐ์ ์ฝ์์ฒ๋ผ ๋ณด์
๋๋ค.
+5. ๊ฐ์ฒด์งํฅ์ ์ค๊ณ๊ฐ ์ด๋ ต๋ค๋ฉด, ์ ๋ง์ ๋ฐฉ์์ธ๋ฐ, logic โ behavior โ data ๋ฅผ ์๊ฐํด๋ด
์๋ค.
+ - logic : data๋ฅผ ์ด์ฉํ behavior์ ์ ์ฒด์ ์ธ ํ๋ฆ
+ - behavior : data๋ฅผ ์ด์ฉํ๋ ์ด๋ ํ ํ์
+ - data : ๋ง ๊ทธ๋๋ก ๋ฐ์ดํฐ, ๊ฐ์ฒด
+ - logic์ behavior์, behavior์ data์ ์์กด(์ฌ์ฉ)
+ - ๊ฐ๊ฐ interaction(์ํธ์์ฉ)์ ๊ดํด์๋ ์๊ฐํด๋ณผ ๊ฒ
+6. ๋ฌด์กฐ๊ฑด ๋ฉ์๋๋ก ํํํ ํ์๋ ์์ต๋๋ค. ๊ฐ์ฒด๊ฐ ์ํธ์์ฉ์ด ๋ถ์กฑํ ์ง ์๊ฐํด๋ด
์๋ค.
+7. ๋ฌด์กฐ๊ฑด ์ ์ธ set ์ง์์ ํ์ง๋ง๊ณ , ์ด๋ค ๋ฌธ์ ๊ฐ ์๋์ง, ์ด๋ค ์ด์ ๋๋ฌธ์ธ์ง ์๊ณ set์ ์ง์ํด๋ด
์๋ค. ๋๋ฌด ๋ฌด์์ํ์ง ๋ง์ธ์.
+
+
+
+## ๐ค ํผ๋๋ฐฑ ๊ธฐ๋ฐ ๋ฆฌํฉํ ๋ง
+
+- [x] Console ๋จ์ผํ ๋ฐ ๊ตฌํ์ฒด๊ฐ ์๋ ์ถ์์ฒด์ ์์กดํ๋๋ก
+- [x] Controller ํด๋์ค ๋ฆฌํฉํ ๋ง
+ - [x] logic โ behavior โ data ๊ณ ๋ คํ๋๊ฐ?
+ - [x] ๊ฐ์ฒด๊ฐ ์ํธ์์ฉ์ ๊ณ ๋ คํ๋๊ฐ?
+- [x] Expression ํด๋์ค ๋ฆฌํฉํ ๋ง
+ - [x] 3 depth๋ฅผ ๋์ง ์๋๊ฐ?
+ - [x] ์ถ์ํ๋ฅผ ๊ณ ๋ คํ์๋๊ฐ?
+- [x] Regex์ ๋ฐฉ์ด ์ฝ๋ฉ์ ์ ์ฉํ๋๊ฐ?
+
+
+## โ
PR ํฌ์ธํธ & ๊ถ๊ธํ ์
+
+**PR ํฌ์ธํธ**
+
+- Expression ํด๋์ค์ ๊ธฐ๋ฅ์ด ๋น๋ํ ๊ฒ ๊ฐ์์ ๋๋ด์ต๋๋ค.
+ - Converter, Evaluator ์ถ์์ฒด๋ฅผ ๋ง๋ค๊ณ ๊ทธ๋ฅผ ๊ตฌํํ ๊ตฌํ์ฒด๋ฅผ ํ์ฉํ์ต๋๋ค.
+ - Calculator ์ถ์์ฒด๋ฅผ ๊ตฌํํ ๊ตฌํ์ฒด์์ ์กฐ๋ฆฝํ์ฌ ์ฌ์ฉํ์ต๋๋ค.
+ - Parser๋ ์ถ๊ฐํ์๋๋ฐ, ๋งก๋ ์ฑ
์์ด Regex์์ ์ญํ ๊ณผ ๋น์ทํ์ฌ ์์ ๊ณ ์ด๊ดํ์ต๋๋ค.
+- ๊ณ์ฐ ๊ฒฐ๊ณผ๋ ์์์ ๊ฐ์ธ๊ธฐ ์ํ์ฌ CalculationResult์ Expression vo๋ฅผ ๋ง๋ค์์ต๋๋ค.
+- Regex์ ์ญํ ์ด ์์๋ง ์ ์ฅํ๋ ๊ฒ์ด๋ผ์ util๊ณผ ๋ง์ง ์์์ enum์ผ๋ก ๋ฐ๋ก ๋บ์ต๋๋ค.
+
+
+
+**๊ถ๊ธํ ์ **
+
+1. logic behavior data๋ฅผ ์ต๋ํ ๊ณ ๋ คํด๋ดค๋๋ฐ, ์ฌ์ค ์์ง๋ ๊ฐ์ด ์ ์กํ์ง ์์ต๋๋ค. ์ต๋ํ ์ถ์ํํด๋ณด๋ ค๊ณ ํ๋๋ฐ, ์ญํ ์ด ๋๋ฌด ์๋? ๊ณผํ ์ถ์ํ์ธ๊ฐ? ๋ผ๋ ๊ณ ๋ฏผ์ด ๊ณ์ ์๊ฒผ์ต๋๋ค.
+2. ํ
์คํธ ์ฝ๋๊ฐ ๋ฏธํกํ์ฌ, ์์๋ ๊ฐ์๋ฅผ ๋ฃ๊ณ ๊ฐ์ด ์กฐ๊ธ ์กํ์ ์์ฑํด๋ดค๋๋ฐ, ๋ฐฉ์์ด ๋ง๋์ง ๋ชจ๋ฅด๊ฒ ์ต๋๋ค.
+3. ํ์ญ๋ ํผ๋๋ฐฑ์ ๋ดค์ ๋, ๊ณตํ์ฉ ๊ณ์ฐ๊ธฐ์ ์ฌ์น์ฐ์ฐ๋ง ๊ฐ๋ฅํ ๊ณ์ฐ๊ธฐ๋ฅผ ๊ณ ๋ คํด๋ณด๋ ค ํ์ต๋๋ค.
+ - ํ์ฌ, Operator enum์ ์ฌ์น์ฐ์ฐ์ด ๋ชจ๋ ๋ชฐ์๋๊ณ enum์ values()๋ฅผ ๋ฐ๋ณต๋๋ฆฌ๋ ๋ฉ์๋๋ฅผ ์ ์ํด๋์ enum์ ์ ์๋์ด ์๋ค๋ฉด ๋ฐํํด์ฃผ๊ณ ์์ผ๋ฉด ์์ธ๋ฅผ ๋์ง๋ ์ํ์
๋๋ค.
+ - enum์ ๊ธฐ๋ฅ๋ง ์ถ๊ฐํ๋ฉด ๋๊ธฐ ๋๋ฌธ์ ๊ณตํ์ฉ ๊ณ์ฐ๊ธฐ๊ฐ ์ถ๊ฐ๋๋๊ฑด ๋ฌธ์ ๊ฐ ์์ต๋๋ค.
+ - enum์ ๋ค๋ฅธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๋ฉด ์ฌ์น์ฐ์ฐ๋ง ๊ฐ๋ฅํ ๊ณ์ฐ๊ธฐ๊ฐ ๋ค๋ฅธ ๊ธฐ๋ฅ๋ ์ฌ์ฉํ ์ ์๋ ์ํฉ์ด ๋์ด๋ฒ๋ฆฝ๋๋ค.
+ - ์ด์ ๋ํ์ฌ ์๊ฐํ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ์๋์ ๊ฐ์ต๋๋ค.
+ - ๊ณตํ์ฉ ๊ณ์ฐ๊ธฐ enum์ ํ๋ ๋ ๋ง๋ค ๋, ์ธํฐํ์ด์ค๋ฅผ ์ด์ฉํด ํ์ฅ ๊ฐ๋ฅํ Enum Type์ด ๋๋๋ก ๋ฆฌํฉํ ๋ง ํ๋ ๊ฒ ์
๋๋ค. [์์ดํ
38 - ์ธํฐํ์ด์ค ํ์ฉ Enum Type ํ์ฅ](https://medium.com/lucky-sonnie/item-38-%ED%99%95%EC%9E%A5%ED%95%A0-%EC%88%98-%EC%9E%88%EB%8A%94-enum-type%EC%9D%B4-%ED%95%84%EC%9A%94%ED%95%98%EB%A9%B4-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%9D%BC-9c9f78334a5e)๋ฅผ ์ฐธ๊ณ ํ์์ต๋๋ค.
+ - enum์ ์์ ๊ณ FourArithmeticStrategy ๋ผ๋ ์ธํฐํ์ด์ค๋ฅผ ๊ตฌํํ๋ ๊ฐ๊ฐ ์ฌ์น์ฐ์ฐ์ด ์๊ณ , ๊ณตํ์ฉ ์ ๋ต ์ธํฐํ์ด์ค๋ฅผ ํ๋ ๋ง๋ค์ด์ ๊ฑฐ๊ธฐ์ ์ฌํ์ ์ธ ๊ธฐ๋ฅ์ ์ถ๊ฐํ๊ณ , ๊ธฐ๋ณธ ๊ณ์ฐ๊ธฐ์๋ ์ฌ์น์ฐ์ฐ ์ ๋ต๋ง ์ ์ฉ, ๊ณตํ์ฉ ๊ณ์ฐ๊ธฐ์๋ ์ฌ์น์ฐ์ฐ์ ๋ต + ์ฌํ์ ๋ต ์ ์ฉ ์ด๋ ๊ฒ ๊ฐ๋ณด๋ ค๊ณ ํฉ๋๋ค.
+ - ์ด๋ ํ ๋ฐฉ๋ฒ์ด ๋ ๊ด์ฐฎ์์ง ๊ถ๊ธํฉ๋๋ค.
\ No newline at end of file
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
new file mode 100644
index 000000000..249e5832f
Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
new file mode 100644
index 000000000..2d4d62871
--- /dev/null
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -0,0 +1,6 @@
+#Thu Jun 08 01:00:05 KST 2023
+distributionBase=GRADLE_USER_HOME
+distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.0-bin.zip
+zipStoreBase=GRADLE_USER_HOME
+zipStorePath=wrapper/dists
diff --git a/gradlew b/gradlew
new file mode 100755
index 000000000..1b6c78733
--- /dev/null
+++ b/gradlew
@@ -0,0 +1,234 @@
+#!/bin/sh
+
+#
+# Copyright ยฉ 2015-2021 the original authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+##############################################################################
+#
+# Gradle start up script for POSIX generated by Gradle.
+#
+# Important for running:
+#
+# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
+# noncompliant, but you have some other compliant shell such as ksh or
+# bash, then to run this script, type that shell name before the whole
+# command line, like:
+#
+# ksh Gradle
+#
+# Busybox and similar reduced shells will NOT work, because this script
+# requires all of these POSIX shell features:
+# * functions;
+# * expansions ยซ$varยป, ยซ${var}ยป, ยซ${var:-default}ยป, ยซ${var+SET}ยป,
+# ยซ${var#prefix}ยป, ยซ${var%suffix}ยป, and ยซ$( cmd )ยป;
+# * compound commands having a testable exit status, especially ยซcaseยป;
+# * various built-in commands including ยซcommandยป, ยซsetยป, and ยซulimitยป.
+#
+# Important for patching:
+#
+# (2) This script targets any POSIX shell, so it avoids extensions provided
+# by Bash, Ksh, etc; in particular arrays are avoided.
+#
+# The "traditional" practice of packing multiple parameters into a
+# space-separated string is a well documented source of bugs and security
+# problems, so this is (mostly) avoided, by progressively accumulating
+# options in "$@", and eventually passing that to Java.
+#
+# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
+# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
+# see the in-line comments for details.
+#
+# There are tweaks for specific operating systems such as AIX, CygWin,
+# Darwin, MinGW, and NonStop.
+#
+# (3) This script is generated from the Groovy template
+# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
+# within the Gradle project.
+#
+# You can find Gradle at https://github.com/gradle/gradle/.
+#
+##############################################################################
+
+# Attempt to set APP_HOME
+
+# Resolve links: $0 may be a link
+app_path=$0
+
+# Need this for daisy-chained symlinks.
+while
+ APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
+ [ -h "$app_path" ]
+do
+ ls=$( ls -ld "$app_path" )
+ link=${ls#*' -> '}
+ case $link in #(
+ /*) app_path=$link ;; #(
+ *) app_path=$APP_HOME$link ;;
+ esac
+done
+
+APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
+
+APP_NAME="Gradle"
+APP_BASE_NAME=${0##*/}
+
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
+# Use the maximum available, or set MAX_FD != -1 to use that value.
+MAX_FD=maximum
+
+warn () {
+ echo "$*"
+} >&2
+
+die () {
+ echo
+ echo "$*"
+ echo
+ exit 1
+} >&2
+
+# OS specific support (must be 'true' or 'false').
+cygwin=false
+msys=false
+darwin=false
+nonstop=false
+case "$( uname )" in #(
+ CYGWIN* ) cygwin=true ;; #(
+ Darwin* ) darwin=true ;; #(
+ MSYS* | MINGW* ) msys=true ;; #(
+ NONSTOP* ) nonstop=true ;;
+esac
+
+CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
+
+# Determine the Java command to use to start the JVM.
+if [ -n "$JAVA_HOME" ] ; then
+ if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
+ # IBM's JDK on AIX uses strange locations for the executables
+ JAVACMD=$JAVA_HOME/jre/sh/java
+ else
+ JAVACMD=$JAVA_HOME/bin/java
+ fi
+ if [ ! -x "$JAVACMD" ] ; then
+ die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+ fi
+else
+ JAVACMD=java
+ which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+
+Please set the JAVA_HOME variable in your environment to match the
+location of your Java installation."
+fi
+
+# Increase the maximum file descriptors if we can.
+if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
+ case $MAX_FD in #(
+ max*)
+ MAX_FD=$( ulimit -H -n ) ||
+ warn "Could not query maximum file descriptor limit"
+ esac
+ case $MAX_FD in #(
+ '' | soft) :;; #(
+ *)
+ ulimit -n "$MAX_FD" ||
+ warn "Could not set maximum file descriptor limit to $MAX_FD"
+ esac
+fi
+
+# Collect all arguments for the java command, stacking in reverse order:
+# * args from the command line
+# * the main class name
+# * -classpath
+# * -D...appname settings
+# * --module-path (only if needed)
+# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
+
+# For Cygwin or MSYS, switch paths to Windows format before running java
+if "$cygwin" || "$msys" ; then
+ APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
+ CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
+
+ JAVACMD=$( cygpath --unix "$JAVACMD" )
+
+ # Now convert the arguments - kludge to limit ourselves to /bin/sh
+ for arg do
+ if
+ case $arg in #(
+ -*) false ;; # don't mess with options #(
+ /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
+ [ -e "$t" ] ;; #(
+ *) false ;;
+ esac
+ then
+ arg=$( cygpath --path --ignore --mixed "$arg" )
+ fi
+ # Roll the args list around exactly as many times as the number of
+ # args, so each arg winds up back in the position where it started, but
+ # possibly modified.
+ #
+ # NB: a `for` loop captures its iteration list before it begins, so
+ # changing the positional parameters here affects neither the number of
+ # iterations, nor the values presented in `arg`.
+ shift # remove old arg
+ set -- "$@" "$arg" # push replacement arg
+ done
+fi
+
+# Collect all arguments for the java command;
+# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
+# shell script including quotes and variable substitutions, so put them in
+# double quotes to make sure that they get re-expanded; and
+# * put everything else in single quotes, so that it's not re-expanded.
+
+set -- \
+ "-Dorg.gradle.appname=$APP_BASE_NAME" \
+ -classpath "$CLASSPATH" \
+ org.gradle.wrapper.GradleWrapperMain \
+ "$@"
+
+# Use "xargs" to parse quoted args.
+#
+# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
+#
+# In Bash we could simply go:
+#
+# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
+# set -- "${ARGS[@]}" "$@"
+#
+# but POSIX shell has neither arrays nor command substitution, so instead we
+# post-process each arg (as a line of input to sed) to backslash-escape any
+# character that might be a shell metacharacter, then use eval to reverse
+# that process (while maintaining the separation between arguments), and wrap
+# the whole thing up as a single "set" statement.
+#
+# This will of course break if any of these variables contains a newline or
+# an unmatched quote.
+#
+
+eval "set -- $(
+ printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
+ xargs -n1 |
+ sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
+ tr '\n' ' '
+ )" '"$@"'
+
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
new file mode 100644
index 000000000..107acd32c
--- /dev/null
+++ b/gradlew.bat
@@ -0,0 +1,89 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
+@if "%DEBUG%" == "" @echo off
+@rem ##########################################################################
+@rem
+@rem Gradle startup script for Windows
+@rem
+@rem ##########################################################################
+
+@rem Set local scope for the variables with windows NT shell
+if "%OS%"=="Windows_NT" setlocal
+
+set DIRNAME=%~dp0
+if "%DIRNAME%" == "" set DIRNAME=.
+set APP_BASE_NAME=%~n0
+set APP_HOME=%DIRNAME%
+
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
+@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
+
+@rem Find java.exe
+if defined JAVA_HOME goto findJavaFromJavaHome
+
+set JAVA_EXE=java.exe
+%JAVA_EXE% -version >NUL 2>&1
+if "%ERRORLEVEL%" == "0" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:findJavaFromJavaHome
+set JAVA_HOME=%JAVA_HOME:"=%
+set JAVA_EXE=%JAVA_HOME%/bin/java.exe
+
+if exist "%JAVA_EXE%" goto execute
+
+echo.
+echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
+echo.
+echo Please set the JAVA_HOME variable in your environment to match the
+echo location of your Java installation.
+
+goto fail
+
+:execute
+@rem Setup the command line
+
+set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
+
+@rem Execute Gradle
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
+
+:end
+@rem End local scope for the variables with windows NT shell
+if "%ERRORLEVEL%"=="0" goto mainEnd
+
+:fail
+rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
+rem the _cmd.exe /c_ return code!
+if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
+exit /b 1
+
+:mainEnd
+if "%OS%"=="Windows_NT" endlocal
+
+:omega
diff --git a/settings.gradle b/settings.gradle
new file mode 100644
index 000000000..bcf30bf6c
--- /dev/null
+++ b/settings.gradle
@@ -0,0 +1,2 @@
+rootProject.name = 'java-calculator'
+
diff --git a/src/main/java/com/programmers/Application.java b/src/main/java/com/programmers/Application.java
new file mode 100644
index 000000000..ef743155c
--- /dev/null
+++ b/src/main/java/com/programmers/Application.java
@@ -0,0 +1,39 @@
+package com.programmers;
+
+import com.programmers.calculator.controller.CalculatorController;
+import com.programmers.calculator.domain.component.Evaluator;
+import com.programmers.calculator.domain.component.NotationConverter;
+import com.programmers.calculator.domain.component.PostfixConverter;
+import com.programmers.calculator.domain.component.PostfixEvaluator;
+import com.programmers.calculator.domain.core.Calculator;
+import com.programmers.calculator.domain.core.FourArithmeticCalculator;
+import com.programmers.calculator.repository.HistoryMapRepository;
+import com.programmers.calculator.repository.HistoryRepository;
+import com.programmers.calculator.view.Console;
+import com.programmers.calculator.view.Input;
+import com.programmers.calculator.view.InputReader;
+import com.programmers.calculator.view.Output;
+import com.programmers.calculator.view.OutputWriter;
+
+public class Application {
+ public static void main(String[] args) {
+
+ // view
+ Input input = new InputReader();
+ Output output = new OutputWriter();
+ Console console = new Console(input, output);
+
+ // domain
+ NotationConverter converter = new PostfixConverter();
+ Evaluator evaluator = new PostfixEvaluator();
+ Calculator calculator = new FourArithmeticCalculator(converter, evaluator);
+
+ // repository
+ HistoryRepository repository = new HistoryMapRepository();
+
+ // controller
+ CalculatorController calculatorController = new CalculatorController(console, calculator, repository);
+ calculatorController.run();
+
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/constant/OptionType.java b/src/main/java/com/programmers/calculator/constant/OptionType.java
new file mode 100644
index 000000000..99fe7dbc7
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/constant/OptionType.java
@@ -0,0 +1,30 @@
+package com.programmers.calculator.constant;
+
+import java.util.Arrays;
+
+public enum OptionType {
+ EXIT("0"),
+ HISTORY("1"),
+ CALCULATION("2");
+
+ private static final OptionType[] OPTION_TYPES = OptionType.values();
+
+ private final String inputOption;
+
+ OptionType(String inputOption) {
+ this.inputOption = inputOption;
+ }
+
+ public String getInputOption() {
+ return inputOption;
+ }
+
+ public static OptionType of(String inputOption) {
+ return Arrays.stream(OPTION_TYPES)
+ .filter(optionType -> optionType.inputOption.equals(inputOption))
+ .findFirst()
+ .orElseThrow(() -> new IllegalArgumentException("์ ํจํ์ง ์์ ๋ฉ๋ด์
๋๋ค."));
+ }
+
+}
+
diff --git a/src/main/java/com/programmers/calculator/constant/RegexEnum.java b/src/main/java/com/programmers/calculator/constant/RegexEnum.java
new file mode 100644
index 000000000..6d1dd913e
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/constant/RegexEnum.java
@@ -0,0 +1,46 @@
+package com.programmers.calculator.constant;
+
+import com.programmers.calculator.domain.vo.Expression;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+public enum RegexEnum {
+
+ FOUR_ARITHMETIC("\\d+|[-+*/]"),
+ NUMERIC("\\d+");
+
+ private final String regex;
+ private final Pattern pattern;
+
+ RegexEnum(String regex) {
+ this.regex = regex;
+ this.pattern = Pattern.compile(regex);
+ }
+
+ public String getRegex() {
+ return regex;
+ }
+
+ public Pattern getPattern() {
+ return pattern;
+ }
+
+ public static boolean isNumeric(String token) {
+ return token.matches(RegexEnum.NUMERIC.getRegex());
+ }
+
+ public static List parseToTokens(Expression expression) {
+ List tokens = new ArrayList<>();
+ Matcher matcher = RegexEnum.FOUR_ARITHMETIC.getPattern().matcher(expression.toString());
+
+ while (matcher.find()) {
+ tokens.add(matcher.group());
+ }
+
+ return tokens;
+ }
+}
+
diff --git a/src/main/java/com/programmers/calculator/controller/CalculatorController.java b/src/main/java/com/programmers/calculator/controller/CalculatorController.java
new file mode 100644
index 000000000..ac3fbb292
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/controller/CalculatorController.java
@@ -0,0 +1,84 @@
+package com.programmers.calculator.controller;
+
+import com.programmers.calculator.constant.OptionType;
+import com.programmers.calculator.domain.core.Calculator;
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.domain.vo.Expression;
+import com.programmers.calculator.repository.CalculationHistory;
+import com.programmers.calculator.repository.HistoryRepository;
+import com.programmers.calculator.view.Console;
+
+import java.util.List;
+
+public class CalculatorController {
+
+ private final Console console;
+ private final Calculator calculator;
+ private final HistoryRepository repository;
+
+ public CalculatorController(Console console, Calculator calculator, HistoryRepository repository) {
+ this.console = console;
+ this.calculator = calculator;
+ this.repository = repository;
+ }
+
+ public void run() {
+
+ while (true) {
+
+ try {
+ console.outputOption();
+ OptionType inputValue = OptionType.of(console.inputOption());
+
+ switch (inputValue) {
+ case EXIT:
+ exitCalculator();
+ break;
+ case HISTORY:
+ loadCalculationHistory();
+ break;
+ case CALCULATION:
+ processCalculation();
+ break;
+ default:
+ errorDetection();
+ }
+ } catch (IllegalArgumentException | ArithmeticException e) {
+ e.printStackTrace();
+ }
+
+ }
+ }
+
+ private void exitCalculator() {
+ console.outputExit();
+ System.exit(0);
+ }
+
+ private void loadCalculationHistory() {
+ List calculationHistoryList = repository.findAll();
+ console.outputHistory(calculationHistoryList);
+ }
+
+ private void processCalculation() {
+ Expression expression = new Expression(console.inputExpression());
+ CalculationResult calculationResult = calculator.calculate(expression);
+
+ printCalculationResult(calculationResult);
+ saveCalculationResult(expression, calculationResult);
+ }
+
+ private void printCalculationResult(CalculationResult calculationResult) {
+ console.outputCalculation(calculationResult);
+ }
+
+ private void saveCalculationResult(Expression expression, CalculationResult calculationResult) {
+ CalculationHistory calculationHistory = new CalculationHistory(expression, calculationResult);
+ repository.save(calculationHistory);
+ }
+
+ private void errorDetection() {
+ System.exit(0);
+ }
+
+}
diff --git a/src/main/java/com/programmers/calculator/domain/component/Evaluator.java b/src/main/java/com/programmers/calculator/domain/component/Evaluator.java
new file mode 100644
index 000000000..b20e5eb7e
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/component/Evaluator.java
@@ -0,0 +1,9 @@
+package com.programmers.calculator.domain.component;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+
+import java.util.List;
+
+public interface Evaluator {
+ CalculationResult evaluate(List convertedExpression);
+}
diff --git a/src/main/java/com/programmers/calculator/domain/component/NotationConverter.java b/src/main/java/com/programmers/calculator/domain/component/NotationConverter.java
new file mode 100644
index 000000000..bd7389011
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/component/NotationConverter.java
@@ -0,0 +1,7 @@
+package com.programmers.calculator.domain.component;
+
+import java.util.List;
+
+public interface NotationConverter {
+ List convert(List tokens);
+}
diff --git a/src/main/java/com/programmers/calculator/domain/component/PostfixConverter.java b/src/main/java/com/programmers/calculator/domain/component/PostfixConverter.java
new file mode 100644
index 000000000..82c24a8fa
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/component/PostfixConverter.java
@@ -0,0 +1,45 @@
+package com.programmers.calculator.domain.component;
+
+import com.programmers.calculator.constant.RegexEnum;
+import com.programmers.calculator.domain.core.Operator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Stack;
+
+public class PostfixConverter implements NotationConverter {
+
+ @Override
+ public List convert(List tokens) {
+ List postfix = new ArrayList<>();
+ Stack operatorStack = new Stack<>();
+
+ for (String token : tokens) {
+ if (RegexEnum.isNumeric(token)) {
+ postfix.add(token);
+ continue;
+ }
+
+ compareOperatorPriority(postfix, operatorStack, token.charAt(0));
+ operatorStack.push(token.charAt(0));
+ }
+
+ pushUntilEmptyStack(postfix, operatorStack);
+
+ return postfix;
+ }
+
+ private void compareOperatorPriority(List postfix, Stack operatorStack, char tokenChar) {
+ while (!operatorStack.isEmpty() && (Operator.of(tokenChar).getPriority() <= Operator.of(operatorStack.peek()).getPriority())) {
+ char popOperator = operatorStack.pop();
+ postfix.add(String.valueOf(popOperator));
+ }
+ }
+
+ private void pushUntilEmptyStack(List postfix, Stack operatorStack) {
+ while (!operatorStack.isEmpty()) {
+ postfix.add(operatorStack.pop().toString());
+ }
+ }
+
+}
diff --git a/src/main/java/com/programmers/calculator/domain/component/PostfixEvaluator.java b/src/main/java/com/programmers/calculator/domain/component/PostfixEvaluator.java
new file mode 100644
index 000000000..b491e11bb
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/component/PostfixEvaluator.java
@@ -0,0 +1,39 @@
+package com.programmers.calculator.domain.component;
+
+import com.programmers.calculator.constant.RegexEnum;
+import com.programmers.calculator.domain.core.Operator;
+import com.programmers.calculator.domain.vo.CalculationResult;
+
+import java.math.BigDecimal;
+import java.util.List;
+import java.util.Stack;
+
+public class PostfixEvaluator implements Evaluator {
+
+ @Override
+ public CalculationResult evaluate(List convertedExpression) {
+ Stack operandStack = new Stack<>();
+
+ for (String token : convertedExpression) {
+ if (RegexEnum.isNumeric(token)) {
+ operandStack.push(new CalculationResult(new BigDecimal(token)));
+ continue;
+ }
+
+ if (operandStack.size() < 2) {
+ throw new IllegalArgumentException("์๋ชป๋ ์์์
๋๋ค.");
+ }
+
+ CalculationResult operand2 = operandStack.pop();
+ CalculationResult operand1 = operandStack.pop();
+ CalculationResult result = Operator.of(token.charAt(0)).getFunction().apply(operand1, operand2);
+ operandStack.push(result);
+ }
+
+ if (operandStack.size() != 1) {
+ throw new IllegalArgumentException("์๋ชป๋ ์์์
๋๋ค.");
+ }
+
+ return operandStack.pop();
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/domain/core/Calculator.java b/src/main/java/com/programmers/calculator/domain/core/Calculator.java
new file mode 100644
index 000000000..0ce5c0daa
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/core/Calculator.java
@@ -0,0 +1,8 @@
+package com.programmers.calculator.domain.core;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.domain.vo.Expression;
+
+public interface Calculator {
+ CalculationResult calculate(Expression expression);
+}
diff --git a/src/main/java/com/programmers/calculator/domain/core/FourArithmeticCalculator.java b/src/main/java/com/programmers/calculator/domain/core/FourArithmeticCalculator.java
new file mode 100644
index 000000000..b6e6b15b5
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/core/FourArithmeticCalculator.java
@@ -0,0 +1,28 @@
+package com.programmers.calculator.domain.core;
+
+import com.programmers.calculator.constant.RegexEnum;
+import com.programmers.calculator.domain.component.Evaluator;
+import com.programmers.calculator.domain.component.NotationConverter;
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.domain.vo.Expression;
+
+import java.util.List;
+
+public class FourArithmeticCalculator implements Calculator {
+
+ private final NotationConverter converter;
+ private final Evaluator evaluator;
+
+ public FourArithmeticCalculator(NotationConverter converter, Evaluator evaluator) {
+ this.converter = converter;
+ this.evaluator = evaluator;
+ }
+
+ @Override
+ public CalculationResult calculate(Expression expression) {
+ List tokens = RegexEnum.parseToTokens(expression);
+ List postfix = converter.convert(tokens);
+ return evaluator.evaluate(postfix);
+ }
+
+}
diff --git a/src/main/java/com/programmers/calculator/domain/core/Operator.java b/src/main/java/com/programmers/calculator/domain/core/Operator.java
new file mode 100644
index 000000000..764be377b
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/core/Operator.java
@@ -0,0 +1,52 @@
+package com.programmers.calculator.domain.core;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+
+import java.math.BigDecimal;
+import java.util.Objects;
+import java.util.function.BiFunction;
+
+public enum Operator {
+ ADDITION('+', 10, CalculationResult::add),
+ SUBTRACTION('-', 10, CalculationResult::subtract),
+ MULTIPLICATION('*', 100, CalculationResult::multiply),
+ DIVISION('/', 100, (o1, o2) -> {
+ if (Objects.equals(o2.getValue(), BigDecimal.ZERO)) {
+ throw new ArithmeticException("0์ผ๋ก ๋๋ ์ ์์ต๋๋ค.");
+ }
+ return o1.divide(o2);
+ });
+
+ private static final Operator[] OPERATORS = Operator.values();
+
+ private final char symbol;
+ private final int priority;
+ private final BiFunction function;
+
+ Operator(char symbol, int priority, BiFunction function) {
+ this.symbol = symbol;
+ this.priority = priority;
+ this.function = function;
+ }
+
+ public char getSymbol() {
+ return symbol;
+ }
+
+ public int getPriority() {
+ return priority;
+ }
+
+ public BiFunction getFunction() {
+ return function;
+ }
+
+ public static Operator of(char inputOperator) {
+ for (Operator operator : OPERATORS) {
+ if (operator.symbol == inputOperator) {
+ return operator;
+ }
+ }
+ throw new IllegalArgumentException("์ ํจํ์ง ์์ ์ฐ์ฐ์์
๋๋ค.");
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/domain/vo/CalculationResult.java b/src/main/java/com/programmers/calculator/domain/vo/CalculationResult.java
new file mode 100644
index 000000000..3acbc3200
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/vo/CalculationResult.java
@@ -0,0 +1,37 @@
+package com.programmers.calculator.domain.vo;
+
+import java.math.BigDecimal;
+import java.math.RoundingMode;
+
+public class CalculationResult {
+ private final BigDecimal value;
+
+ public CalculationResult(BigDecimal value) {
+ this.value = value;
+ }
+
+ public BigDecimal getValue() {
+ return value;
+ }
+
+ public CalculationResult add(CalculationResult otherValue) {
+ return new CalculationResult(value.add(otherValue.getValue()));
+ }
+
+ public CalculationResult subtract(CalculationResult otherValue) {
+ return new CalculationResult(value.subtract(otherValue.getValue()));
+ }
+
+ public CalculationResult multiply(CalculationResult otherValue) {
+ return new CalculationResult(value.multiply(otherValue.getValue()));
+ }
+
+ public CalculationResult divide(CalculationResult otherValue) {
+ return new CalculationResult(value.divide(otherValue.getValue(), 5, RoundingMode.HALF_UP));
+ }
+
+ @Override
+ public String toString() {
+ return value.toString();
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/domain/vo/Expression.java b/src/main/java/com/programmers/calculator/domain/vo/Expression.java
new file mode 100644
index 000000000..56eeea513
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/domain/vo/Expression.java
@@ -0,0 +1,18 @@
+package com.programmers.calculator.domain.vo;
+
+public class Expression {
+ private final String expression;
+
+ public Expression(String expression) {
+ this.expression = expression;
+ }
+
+ public String getExpression() {
+ return expression;
+ }
+
+ @Override
+ public String toString() {
+ return expression;
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/repository/CalculationHistory.java b/src/main/java/com/programmers/calculator/repository/CalculationHistory.java
new file mode 100644
index 000000000..8cba608e1
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/repository/CalculationHistory.java
@@ -0,0 +1,28 @@
+package com.programmers.calculator.repository;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.domain.vo.Expression;
+
+public class CalculationHistory {
+
+ private Long id = 0L;
+ private Expression expression;
+ private CalculationResult result;
+
+ public CalculationHistory(Expression expression, CalculationResult result) {
+ this.expression = expression;
+ this.result = result;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public CalculationHistory saveHistory() {
+ return this;
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/repository/HistoryMapRepository.java b/src/main/java/com/programmers/calculator/repository/HistoryMapRepository.java
new file mode 100644
index 000000000..47b05e231
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/repository/HistoryMapRepository.java
@@ -0,0 +1,23 @@
+package com.programmers.calculator.repository;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class HistoryMapRepository implements HistoryRepository {
+
+ private final Map store = new HashMap<>();
+ private long sequence = 0L;
+
+ @Override
+ public void save(CalculationHistory calculationHistory) {
+ calculationHistory.setId(++sequence);
+ store.put(calculationHistory.getId(), calculationHistory.saveHistory());
+ }
+
+ @Override
+ public List findAll() {
+ return new ArrayList<>(store.values());
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/repository/HistoryRepository.java b/src/main/java/com/programmers/calculator/repository/HistoryRepository.java
new file mode 100644
index 000000000..2af208ab8
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/repository/HistoryRepository.java
@@ -0,0 +1,8 @@
+package com.programmers.calculator.repository;
+
+import java.util.List;
+
+public interface HistoryRepository {
+ void save(CalculationHistory calculationHistory);
+ List findAll();
+}
diff --git a/src/main/java/com/programmers/calculator/view/Console.java b/src/main/java/com/programmers/calculator/view/Console.java
new file mode 100644
index 000000000..c9a53a5e4
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/view/Console.java
@@ -0,0 +1,45 @@
+package com.programmers.calculator.view;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.repository.CalculationHistory;
+
+import java.util.List;
+
+public class Console {
+
+ private final Input input;
+ private final Output output;
+ private final String newLine = System.getProperty("line.separator");
+
+ public Console(Input input, Output output) {
+ this.input = input;
+ this.output = output;
+ }
+
+ public String inputOption() {
+ output.write(newLine + newLine + "์ ํ : ");
+ return input.read();
+ }
+
+ public String inputExpression() {
+ return input.read();
+ }
+
+ public void outputOption() {
+ output.write(newLine + "0. ์ข
๋ฃ");
+ output.write(newLine + "1. ์กฐํ");
+ output.write(newLine + "2. ๊ณ์ฐ");
+ }
+
+ public void outputExit() {
+ output.write("ํ๋ก๊ทธ๋จ์ด ์ข
๋ฃ๋ฉ๋๋ค.");
+ }
+
+ public void outputHistory(List calculationHistories) {
+ output.write(calculationHistories);
+ }
+
+ public void outputCalculation(CalculationResult result) {
+ output.write(result);
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/view/Input.java b/src/main/java/com/programmers/calculator/view/Input.java
new file mode 100644
index 000000000..03abada4e
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/view/Input.java
@@ -0,0 +1,5 @@
+package com.programmers.calculator.view;
+
+public interface Input {
+ String read();
+}
diff --git a/src/main/java/com/programmers/calculator/view/InputReader.java b/src/main/java/com/programmers/calculator/view/InputReader.java
new file mode 100644
index 000000000..a209d84ed
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/view/InputReader.java
@@ -0,0 +1,17 @@
+package com.programmers.calculator.view;
+
+import java.util.Scanner;
+
+public class InputReader implements Input {
+
+ private final Scanner scanner;
+
+ public InputReader() {
+ this.scanner = new Scanner(System.in);
+ }
+
+ @Override
+ public String read() {
+ return scanner.nextLine();
+ }
+}
diff --git a/src/main/java/com/programmers/calculator/view/Output.java b/src/main/java/com/programmers/calculator/view/Output.java
new file mode 100644
index 000000000..73dc10759
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/view/Output.java
@@ -0,0 +1,12 @@
+package com.programmers.calculator.view;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.repository.CalculationHistory;
+
+import java.util.List;
+
+public interface Output {
+ void write(String message);
+ void write(List calculationHistories);
+ void write(CalculationResult result);
+}
\ No newline at end of file
diff --git a/src/main/java/com/programmers/calculator/view/OutputWriter.java b/src/main/java/com/programmers/calculator/view/OutputWriter.java
new file mode 100644
index 000000000..8950422d3
--- /dev/null
+++ b/src/main/java/com/programmers/calculator/view/OutputWriter.java
@@ -0,0 +1,25 @@
+package com.programmers.calculator.view;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.repository.CalculationHistory;
+
+import java.util.List;
+
+public class OutputWriter implements Output {
+
+ @Override
+ public void write(String message) {
+ System.out.print(message);
+ }
+
+ @Override
+ public void write(List calculationHistories) {
+ calculationHistories.forEach(System.out::println);
+ }
+
+ @Override
+ public void write(CalculationResult result) {
+ System.out.println(result);
+ }
+
+}
diff --git a/src/test/java/com/programmers/calculator/constant/OptionTypeTest.java b/src/test/java/com/programmers/calculator/constant/OptionTypeTest.java
new file mode 100644
index 000000000..1cb7f4f00
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/constant/OptionTypeTest.java
@@ -0,0 +1,36 @@
+package com.programmers.calculator.constant;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+class OptionTypeTest {
+
+ @DisplayName("๋ฉ๋ด ๋ฒํธ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ๋ฉด, ์ฌ๋ฐ๋ฅธ Option ๋ฐํ๋๋ค.")
+ @ParameterizedTest
+ @ValueSource(strings = {"0", "1", "2"})
+ void right_option_menu(String inputOption) {
+
+ // given
+ OptionType optionType = OptionType.of(inputOption);
+
+ // then
+ assertThat(optionType.getInputOption()).isEqualTo(inputOption);
+
+ }
+
+ @DisplayName("์๋ชป๋ ๋ฉ๋ด ๋ฒํธ๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ๋ฉด, ์์ธ๊ฐ ๋ฐ์ํ๋ค.")
+ @ParameterizedTest
+ @ValueSource(strings = {"3", "a", "+"})
+ void wrong_option_menu(String notInputOption) {
+
+ // then
+ assertThatThrownBy(() -> OptionType.of(notInputOption))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("์ ํจํ์ง ์์ ๋ฉ๋ด์
๋๋ค.");
+
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/programmers/calculator/constant/RegexEnumTest.java b/src/test/java/com/programmers/calculator/constant/RegexEnumTest.java
new file mode 100644
index 000000000..6209ad6c6
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/constant/RegexEnumTest.java
@@ -0,0 +1,46 @@
+package com.programmers.calculator.constant;
+
+import com.programmers.calculator.domain.vo.Expression;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class RegexEnumTest {
+
+ @DisplayName("์ ๊ท์์ ๋ง๊ฒ ํ ํฐ์ด ์ ์ชผ๊ฐ์ง๋์ง ํ์ธ")
+ @ParameterizedTest
+ @CsvSource({
+ "1 + 2 * 4 / 6",
+ "1+2*4/6"
+ })
+ void expression_regex_parsing (String inputExpression) {
+
+ // given
+ Expression expression = new Expression(inputExpression);
+
+ // when
+ List result = RegexEnum.parseToTokens(expression);
+ List expectedTokens = List.of("1", "+", "2", "*", "4", "/", "6");
+
+ // then
+ assertThat(result).isEqualTo(expectedTokens);
+ }
+
+ @DisplayName("์ซ์๊ฐ ์ซ์ ์ ๊ท์์ ์ฒดํฌ ๋๋์ง ํ์ธ")
+ @Test
+ void numeric_regex() {
+ assertThat(RegexEnum.isNumeric("1")).isTrue();
+ }
+
+ @DisplayName("์ซ์ ์๋ ๊ฒ์ด ์ซ์ ์ ๊ท์์ ์ฒดํฌ๊ฐ ์๋๋์ง ํ์ธ")
+ @Test
+ void not_numeric_regex() {
+ assertThat(RegexEnum.isNumeric("a")).isFalse();
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/programmers/calculator/domain/component/PostfixConverterTest.java b/src/test/java/com/programmers/calculator/domain/component/PostfixConverterTest.java
new file mode 100644
index 000000000..f11b6fc0c
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/domain/component/PostfixConverterTest.java
@@ -0,0 +1,28 @@
+package com.programmers.calculator.domain.component;
+
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class PostfixConverterTest {
+
+ PostfixConverter postfixConverter = new PostfixConverter();
+
+ @DisplayName("ํ์ํ๊ธฐ์์ผ๋ก ๋ณํ ์ ๋๋์ง ํ์ธ")
+ @Test
+ void right_convert_postfix () {
+ // given
+ List tokens = List.of("1", "+", "2", "*", "4", "/", "6");
+ List expected = List.of("1", "2", "4", "*", "6", "/", "+");
+
+ // when
+ List result = postfixConverter.convert(tokens);
+
+ // then
+ assertThat(result).isEqualTo(expected);
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/programmers/calculator/domain/component/PostfixEvaluatorTest.java b/src/test/java/com/programmers/calculator/domain/component/PostfixEvaluatorTest.java
new file mode 100644
index 000000000..b887f3756
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/domain/component/PostfixEvaluatorTest.java
@@ -0,0 +1,45 @@
+package com.programmers.calculator.domain.component;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Test;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+class PostfixEvaluatorTest {
+
+ PostfixEvaluator postfixEvaluator = new PostfixEvaluator();
+
+ @DisplayName("ํ์ํ๊ธฐ์์ ๊ณ์ฐ์ด ์ ๋๋์ง ํ์ธ")
+ @Test
+ void right_convert_postfix () {
+
+ // given
+ List postfix = List.of("1", "2", "4", "*", "6", "*", "+");
+
+ // when
+ CalculationResult result = postfixEvaluator.evaluate(postfix);
+
+ // then
+ assertThat(result.getValue()).isEqualTo(new BigDecimal(49));
+ }
+
+ @DisplayName("์๋ชป๋ ํ์ํ๊ธฐ์์ด ์์ธ๋ฅผ ๋์ง๋์ง ํ์ธ")
+ @Test
+ void throw_wrong_evaluate_postfix () {
+
+ // given
+ List postfix = List.of("1", "+", "+");
+
+ // then
+ assertThatThrownBy(() -> postfixEvaluator.evaluate(postfix))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("์๋ชป๋ ์์์
๋๋ค.");
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/programmers/calculator/domain/core/FourArithmeticCalculatorTest.java b/src/test/java/com/programmers/calculator/domain/core/FourArithmeticCalculatorTest.java
new file mode 100644
index 000000000..fb4e382c5
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/domain/core/FourArithmeticCalculatorTest.java
@@ -0,0 +1,42 @@
+package com.programmers.calculator.domain.core;
+
+import com.programmers.calculator.domain.component.Evaluator;
+import com.programmers.calculator.domain.component.NotationConverter;
+import com.programmers.calculator.domain.component.PostfixConverter;
+import com.programmers.calculator.domain.component.PostfixEvaluator;
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.domain.vo.Expression;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.math.BigDecimal;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class FourArithmeticCalculatorTest {
+
+ NotationConverter converter = new PostfixConverter();
+ Evaluator evaluator = new PostfixEvaluator();
+ FourArithmeticCalculator calculator = new FourArithmeticCalculator(converter, evaluator);
+
+ @DisplayName("์ฌ์น์ฐ์ฐ ๊ณ์ฐ๊ธฐ์ ๊ณ์ฐ์ด ์ ๋๋์ง ํ์ธ")
+ @ParameterizedTest
+ @CsvSource({
+ "1 + 5 * 3 - 8, 8",
+ "1 + 2 * 9 - 6, 13"
+ })
+ void right_calculate(String inputExpression, BigDecimal expectedResult) {
+
+ // given
+ Expression expression = new Expression(inputExpression);
+
+ // when
+ CalculationResult result = calculator.calculate(expression);
+
+ // then
+ assertThat(result.getValue()).isEqualTo(expectedResult);
+
+ }
+
+}
\ No newline at end of file
diff --git a/src/test/java/com/programmers/calculator/domain/core/OperatorTest.java b/src/test/java/com/programmers/calculator/domain/core/OperatorTest.java
new file mode 100644
index 000000000..2fd895160
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/domain/core/OperatorTest.java
@@ -0,0 +1,57 @@
+package com.programmers.calculator.domain.core;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+import org.junit.jupiter.params.provider.ValueSource;
+
+import java.math.BigDecimal;
+
+import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
+import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy;
+
+class OperatorTest {
+
+ @DisplayName("0์ผ๋ก ๋๋ด์ ๋ ์์ธ๋ฅผ ๋์ง๋์ง ํ์ธ")
+ @ParameterizedTest
+ @CsvSource({"5, 0", "13, 0"})
+ void division_by_zero_throw_exception(BigDecimal testOperand1, BigDecimal testOperand2) {
+
+ // given
+ Operator division = Operator.DIVISION;
+
+ CalculationResult operand1 = new CalculationResult(testOperand1);
+ CalculationResult operand2 = new CalculationResult(testOperand2);
+
+ // when
+ assertThatThrownBy(() -> division.getFunction()
+ .apply(operand1, operand2))
+ .isInstanceOf(ArithmeticException.class)
+ .hasMessage("0์ผ๋ก ๋๋ ์ ์์ต๋๋ค.");
+
+ }
+
+ @DisplayName("์ฐ์ฐ์๋ฅผ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ๋ฉด, ๊ทธ์ ๋ง๋ Operator๊ฐ ๋ฐํ")
+ @ParameterizedTest
+ @ValueSource(chars = {'+', '-', '*', '/'})
+ void operator_of_success(char operatorChar) {
+
+ // given
+ Operator operator = Operator.of(operatorChar);
+
+ // then
+ assertThat(operator.getSymbol()).isEqualTo(operatorChar);
+ }
+
+ @DisplayName("์ฐ์ฐ์๊ฐ ์๋ ๊ฐ๋ค์ ํ๋ผ๋ฏธํฐ๋ก ๋๊ฒจ์ฃผ๋ฉด, ์์ธ๊ฐ ๋ฐ์")
+ @ParameterizedTest
+ @ValueSource(chars = {'|', '`', 'z', '1', '3'})
+ void of_throws_exception(char notOperatorChar) {
+
+ // then
+ assertThatThrownBy(() -> Operator.of(notOperatorChar))
+ .isInstanceOf(IllegalArgumentException.class)
+ .hasMessage("์ ํจํ์ง ์์ ์ฐ์ฐ์์
๋๋ค.");
+ }
+}
\ No newline at end of file
diff --git a/src/test/java/com/programmers/calculator/repository/HistoryMapRepositoryTest.java b/src/test/java/com/programmers/calculator/repository/HistoryMapRepositoryTest.java
new file mode 100644
index 000000000..f96974d58
--- /dev/null
+++ b/src/test/java/com/programmers/calculator/repository/HistoryMapRepositoryTest.java
@@ -0,0 +1,43 @@
+package com.programmers.calculator.repository;
+
+import com.programmers.calculator.domain.vo.CalculationResult;
+import com.programmers.calculator.domain.vo.Expression;
+import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.CsvSource;
+
+import java.math.BigDecimal;
+import java.util.List;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+class HistoryMapRepositoryTest {
+
+ HistoryRepository repository = new HistoryMapRepository();
+
+ @DisplayName("repository ์ ์์ ์ผ๋ก ์ ์ฅ๋๋์ง ํ์ธ")
+ @ParameterizedTest
+ @CsvSource({
+ "1 + 2 * 3, 7",
+ "3 + 2 * 3 / 3, 5"
+ })
+ void repository_right_work(String inputExpression, BigDecimal expectedResult) {
+
+ // given
+ Expression expression = new Expression(inputExpression);
+ CalculationResult calculationResult = new CalculationResult(expectedResult);
+ CalculationHistory result = new CalculationHistory(expression, calculationResult);
+
+ // when
+ repository.save(result);
+
+ // then
+ List all = repository.findAll();
+
+ assertThat(all.size()).isEqualTo(1);
+ assertThat(all).contains(result);
+ assertThat(all.get(0)).isEqualTo(result);
+
+ }
+
+}
\ No newline at end of file