From fa3cd03fc6392b9254596e2a995cd637f3ad82e0 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 21:15:51 +0900 Subject: [PATCH 01/15] =?UTF-8?q?docs(readme):=20=EB=A6=AC=EB=93=9C?= =?UTF-8?q?=EB=AF=B8=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 13420b29..aaf3363c 100644 --- a/README.md +++ b/README.md @@ -1 +1,18 @@ -# javascript-calculator-precourse \ No newline at end of file +## ➕ 문자열 덧셈 계산기 체크리스트 + +### 기능 구현 + +- [ ] 빈 문자열 입력 시 `0` 반환 +- [ ] 기본 구분자 (`,` `:`) 로 숫자 분리 +- [ ] 커스텀 구분자 `"//<구분자>\n"` 형식 지원 +- [ ] 잘못된 입력 시 `[ERROR]` 메시지 출력 +- [ ] 양수만 허용 +- [ ] `Console.readLineAsync`, `Console.print` 사용 +- [ ] 출력 형식: `결과 : <합계>` + +### 예외 처리 + +- [ ] 음수 입력 시 에러 발생 +- [ ] 숫자 아닌 값 입력 시 에러 발생 +- [ ] 잘못된 포맷 입력 시 에러 발생 +- [ ] 연속 구분자 처리 정책 반영 From ad16d2f9ab8560d30dbfb1a5859e966ea403a427 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:03:23 +0900 Subject: [PATCH 02/15] feat(app): add InputHandler logic --- src/App.js | 7 ++++++- src/io/InputHandler.js | 7 +++++++ 2 files changed, 13 insertions(+), 1 deletion(-) create mode 100644 src/io/InputHandler.js diff --git a/src/App.js b/src/App.js index 091aa0a5..64ccda86 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ +import { readInput, printResult, printError } from "./io/InputHandler.js"; + class App { - async run() {} + async run() { + const input = await readInput(); + const sum = calculateSum(input); + } } export default App; diff --git a/src/io/InputHandler.js b/src/io/InputHandler.js new file mode 100644 index 00000000..8c46cd1b --- /dev/null +++ b/src/io/InputHandler.js @@ -0,0 +1,7 @@ +import { Console } from "@woowacourse/mission-utils"; + +export async function readInput() { + Console.print("덧셈할 문자열을 입력해 주세요."); + const input = await Console.readLineAsync(""); + return input; +} From f54e4281014e0bea62841af8a7615ed491074d91 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:04:13 +0900 Subject: [PATCH 03/15] feat(app): add print result logic --- src/App.js | 1 + src/io/InputHandler.js | 3 +++ 2 files changed, 4 insertions(+) diff --git a/src/App.js b/src/App.js index 64ccda86..5b9ffbd9 100644 --- a/src/App.js +++ b/src/App.js @@ -4,6 +4,7 @@ class App { async run() { const input = await readInput(); const sum = calculateSum(input); + printResult(sum); } } diff --git a/src/io/InputHandler.js b/src/io/InputHandler.js index 8c46cd1b..fc0808e1 100644 --- a/src/io/InputHandler.js +++ b/src/io/InputHandler.js @@ -5,3 +5,6 @@ export async function readInput() { const input = await Console.readLineAsync(""); return input; } +export function printResult(result) { + Console.print(`결과 : <${result}>`); +} From 7f15d94ca68beccf8252d5c43532238981d82703 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:04:50 +0900 Subject: [PATCH 04/15] feat(app):add error handler logic --- src/App.js | 10 +++++++--- src/io/InputHandler.js | 4 ++++ 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/src/App.js b/src/App.js index 5b9ffbd9..ab4dedd2 100644 --- a/src/App.js +++ b/src/App.js @@ -2,9 +2,13 @@ import { readInput, printResult, printError } from "./io/InputHandler.js"; class App { async run() { - const input = await readInput(); - const sum = calculateSum(input); - printResult(sum); + try { + const input = await readInput(); + const sum = calculateSum(input); + printResult(sum); + } catch (err) { + printError(err); + } } } diff --git a/src/io/InputHandler.js b/src/io/InputHandler.js index fc0808e1..815c3e90 100644 --- a/src/io/InputHandler.js +++ b/src/io/InputHandler.js @@ -8,3 +8,7 @@ export async function readInput() { export function printResult(result) { Console.print(`결과 : <${result}>`); } +export function printError(err) { + const m = err?.message ?? "알 수 없는 오류가 발생했습니다."; + Console.print(`[ERROR] ${m}`); +} From 85ad5130b76908eaa6877ed3ab82badffc9bd0bb Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:12:05 +0900 Subject: [PATCH 05/15] feat(Parser): return empty array when input string is empty --- src/calculator/Parser.js | 7 +++++++ 1 file changed, 7 insertions(+) create mode 100644 src/calculator/Parser.js diff --git a/src/calculator/Parser.js b/src/calculator/Parser.js new file mode 100644 index 00000000..dda3bb3c --- /dev/null +++ b/src/calculator/Parser.js @@ -0,0 +1,7 @@ +class Parser { + static parse(input) { + if (!input) return []; + } +} + +export default Parser; From 35fdad9bbfa7b466563baaf0f46690c77997571a Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:22:48 +0900 Subject: [PATCH 06/15] feat(parser): add base Parser class with default delimiters (, :) --- src/calculator/Parser.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/calculator/Parser.js b/src/calculator/Parser.js index dda3bb3c..fa0b6ffe 100644 --- a/src/calculator/Parser.js +++ b/src/calculator/Parser.js @@ -1,6 +1,10 @@ class Parser { static parse(input) { if (!input) return []; + + const delimiters = [",", ":"]; + const pattern = new RegExp(`[${delimiters.join("")}]`); + return input.split(pattern); } } From a8d89a6f4eab16e4db8bdbb78e6cf18a8d0b9cd7 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:23:51 +0900 Subject: [PATCH 07/15] feat(parser): support custom delimiter syntax --- src/calculator/Parser.js | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/calculator/Parser.js b/src/calculator/Parser.js index fa0b6ffe..01a2726e 100644 --- a/src/calculator/Parser.js +++ b/src/calculator/Parser.js @@ -2,9 +2,17 @@ class Parser { static parse(input) { if (!input) return []; - const delimiters = [",", ":"]; + let delimiters = [",", ":"]; + let expression = input; + + const match = input.match(/^\/\/(.)\n/); + if (match) { + delimiters.push(match[1]); + expression = input.slice(match[0].length); + } + const pattern = new RegExp(`[${delimiters.join("")}]`); - return input.split(pattern); + return expression.split(pattern); } } From e32b2821189e1f3c43e90aaea47e76f70f37fdb7 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:24:25 +0900 Subject: [PATCH 08/15] feat(parser): add escapeForRegExp to safely handle special characters in delimiters --- src/calculator/Parser.js | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/src/calculator/Parser.js b/src/calculator/Parser.js index 01a2726e..8f91afc2 100644 --- a/src/calculator/Parser.js +++ b/src/calculator/Parser.js @@ -1,17 +1,23 @@ class Parser { + static escapeForRegExp(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); + } + static parse(input) { if (!input) return []; let delimiters = [",", ":"]; let expression = input; - const match = input.match(/^\/\/(.)\n/); - if (match) { - delimiters.push(match[1]); - expression = input.slice(match[0].length); + const m = input.match(/^\/\/(.)\n/); + if (m) { + delimiters.push(m[1]); + expression = input.slice(m[0].length); } - const pattern = new RegExp(`[${delimiters.join("")}]`); + const pattern = new RegExp( + `[${Parser.escapeForRegExp(delimiters.join(""))}]` + ); return expression.split(pattern); } } From f05086b0a3a339c91e23eebc1d17eac9c474aa18 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:25:31 +0900 Subject: [PATCH 09/15] feat(calculator): add base calculateSum function with empty input handling --- src/calculator/Calculator.js | 6 ++++++ 1 file changed, 6 insertions(+) create mode 100644 src/calculator/Calculator.js diff --git a/src/calculator/Calculator.js b/src/calculator/Calculator.js new file mode 100644 index 00000000..0235f9ec --- /dev/null +++ b/src/calculator/Calculator.js @@ -0,0 +1,6 @@ +export function calculateSum(input) { + if (input == null) throw new Error("입력이 필요합니다."); + if (input === "") return 0; + + return 0; +} From 84f1ebda0d74bda9c3f88a4f9164aebfa8dc0ac9 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:25:47 +0900 Subject: [PATCH 10/15] feat(calculator): integrate Parser to compute sum of parsed tokens --- src/calculator/Calculator.js | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/calculator/Calculator.js b/src/calculator/Calculator.js index 0235f9ec..f669cbc5 100644 --- a/src/calculator/Calculator.js +++ b/src/calculator/Calculator.js @@ -1,6 +1,9 @@ +import Parser from "./Parser.js"; + export function calculateSum(input) { if (input == null) throw new Error("입력이 필요합니다."); if (input === "") return 0; - return 0; + const tokens = Parser.parse(input); + return tokens.reduce((sum, t) => sum + Number(t), 0); } From 33071f8f880d8a6bd4b3c99933b348d266598986 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:27:06 +0900 Subject: [PATCH 11/15] feat(validator): add validator --- src/calculator/Validator.js | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/calculator/Validator.js diff --git a/src/calculator/Validator.js b/src/calculator/Validator.js new file mode 100644 index 00000000..cdf5c65a --- /dev/null +++ b/src/calculator/Validator.js @@ -0,0 +1,17 @@ +export function validateTokens(tokens) { + if (!Array.isArray(tokens)) { + throw new Error("유효하지 않은 입력입니다."); + } + + for (const t of tokens) { + if (t === "") throw new Error("유효하지 않은 구분자 사용입니다."); + + if (!/^-?\d+$/.test(t)) throw new Error("숫자만 입력할 수 있습니다."); + + const n = Number(t); + + if (n < 0) throw new Error("음수는 허용되지 않습니다."); + + if (n === 0) throw new Error("양수만 입력할 수 있습니다."); + } +} From 23408c1942a055e09431d8eaf227ff11a83b4fad Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:27:19 +0900 Subject: [PATCH 12/15] feat(calculator): add Validator to enforce input validation before summation --- src/calculator/Calculator.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/calculator/Calculator.js b/src/calculator/Calculator.js index f669cbc5..18d2fd87 100644 --- a/src/calculator/Calculator.js +++ b/src/calculator/Calculator.js @@ -1,9 +1,14 @@ import Parser from "./Parser.js"; +import { validateTokens } from "./Validator.js"; export function calculateSum(input) { if (input == null) throw new Error("입력이 필요합니다."); if (input === "") return 0; - const tokens = Parser.parse(input); + const normalized = String(input).replace(/\\n/g, "\n"); + + const tokens = Parser.parse(normalized); + validateTokens(tokens); + return tokens.reduce((sum, t) => sum + Number(t), 0); } From d26de08483a31a7a5922bb2003f8d0fae9bc0eb3 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:28:10 +0900 Subject: [PATCH 13/15] feat(app): add calculator --- src/App.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/App.js b/src/App.js index ab4dedd2..1bb02d75 100644 --- a/src/App.js +++ b/src/App.js @@ -1,4 +1,5 @@ import { readInput, printResult, printError } from "./io/InputHandler.js"; +import { calculateSum } from "./calculator/Calculator.js"; class App { async run() { From 084a8dfbf8b30df39ee8b0965514de078bb50905 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:31:52 +0900 Subject: [PATCH 14/15] fix(io): print exact result format --- src/io/InputHandler.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/io/InputHandler.js b/src/io/InputHandler.js index 815c3e90..8ef10629 100644 --- a/src/io/InputHandler.js +++ b/src/io/InputHandler.js @@ -6,7 +6,7 @@ export async function readInput() { return input; } export function printResult(result) { - Console.print(`결과 : <${result}>`); + Console.print(`결과 : ${result}`); } export function printError(err) { const m = err?.message ?? "알 수 없는 오류가 발생했습니다."; From 7cfb2735ace8a93f771516c0d3ef2975e4e4a874 Mon Sep 17 00:00:00 2001 From: kwonjeong Date: Mon, 20 Oct 2025 22:36:53 +0900 Subject: [PATCH 15/15] fix(app): simplify error handling and unify [ERROR] message formatting --- src/App.js | 6 +++++- src/io/InputHandler.js | 5 ++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index 1bb02d75..eee77a1f 100644 --- a/src/App.js +++ b/src/App.js @@ -8,7 +8,11 @@ class App { const sum = calculateSum(input); printResult(sum); } catch (err) { - printError(err); + const message = `[ERROR] ${( + err?.message ?? "알 수 없는 오류가 발생했습니다." + ).replace(/^\[ERROR\]\s*/, "")}`; + printError(message); + throw new Error(message); } } } diff --git a/src/io/InputHandler.js b/src/io/InputHandler.js index 8ef10629..ee3c99c3 100644 --- a/src/io/InputHandler.js +++ b/src/io/InputHandler.js @@ -8,7 +8,6 @@ export async function readInput() { export function printResult(result) { Console.print(`결과 : ${result}`); } -export function printError(err) { - const m = err?.message ?? "알 수 없는 오류가 발생했습니다."; - Console.print(`[ERROR] ${m}`); +export function printError(message) { + Console.print(message); }