diff --git a/README.md b/README.md index 13420b29..e1c318fb 100644 --- a/README.md +++ b/README.md @@ -1 +1,70 @@ -# javascript-calculator-precourse \ No newline at end of file +# javascript-calculator-precourse + +# ๐Ÿงฎ ๋ฌธ์ž์—ด ๋ง์…ˆ ๊ณ„์‚ฐ๊ธฐ + +## ๊ตฌํ˜„ ๊ธฐ๋Šฅ ๋ชฉ๋ก + +> ๊ฐ ๊ธฐ๋Šฅ ๊ตฌํ˜„์ด ์™„๋ฃŒ๋  ๋•Œ๋งˆ๋‹ค AngularJS ์ปค๋ฐ‹ ์ปจ๋ฒค์…˜์„ ์‚ฌ์šฉํ•˜์—ฌ ์ปค๋ฐ‹. +> ์ปค๋ฐ‹ ๋‹จ์œ„๋Š” โ€œํ•œ ๊ธฐ๋Šฅ = ํ•œ ์ปค๋ฐ‹โ€ + +--- + +### 1. ์ž…๋ ฅ ์•ˆ๋‚ด ์ถœ๋ ฅ + +- [x] `Console.print('๋ง์…ˆํ•  ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.')` ์ถœ๋ ฅ + +--- + +### 2. ๋ฌธ์ž์—ด ์ž…๋ ฅ ๋ฐ›๊ธฐ + +- [x] `Console.readLineAsync()`๋กœ ์‚ฌ์šฉ์ž๋กœ๋ถ€ํ„ฐ ๋ฌธ์ž์—ด ํ•œ ์ค„์„ ์ž…๋ ฅ๋ฐ›์Œ + +--- + +### 3. ๋นˆ ๋ฌธ์ž์—ด ์ฒ˜๋ฆฌ + +- [x] ๋นˆ ๋ฌธ์ž์—ด(`""` ๋˜๋Š” ๊ณต๋ฐฑ๋งŒ ํฌํ•จ) ์ž…๋ ฅ ์‹œ ๊ฒฐ๊ณผ `0` ๋ฐ˜ํ™˜ + +--- + +### 4. ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž ํŒŒ์‹ฑ + +- [x] ์ž…๋ ฅ ๋ฌธ์ž์—ด์ด "//"๋กœ ์‹œ์ž‘ํ•˜๊ณ  "\n"์„ ํฌํ•จํ•˜๋Š” ๊ฒฝ์šฐ, "//"์™€ "\n" ์‚ฌ์ด์— ์žˆ๋Š” ๋ฌธ์ž๋ฅผ ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋กœ ์‚ฌ์šฉ +- [x] ๊ทธ๋ ‡์ง€ ์•Š์œผ๋ฉด ๊ธฐ๋ณธ ๊ตฌ๋ถ„์ž `,`์™€ `:` ์‚ฌ์šฉ + +--- + +### 5. ์ˆซ์ž ๋ถ„๋ฆฌ ๋กœ์ง + +- [x] ๊ตฌ๋ถ„์ž(๊ธฐ๋ณธ/์ปค์Šคํ…€)๋กœ ๋ฌธ์ž์—ด์„ ๋‚˜๋ˆ„์–ด ๊ฐ ์ˆซ์ž๋ฅผ ์–ป๊ธฐ +- [x] ๊ฐ ์ˆซ์ž ์•ž๋’ค ๊ณต๋ฐฑ(`trim()`) ์ œ๊ฑฐ + +--- + +### 6. ์œ ํšจ์„ฑ ๊ฒ€์ฆ + +- [x] ๋ถ„๋ฆฌ๋œ ๋ชจ๋“  ๊ฐ’์ด ์ •์ˆ˜ ํ˜•ํƒœ์˜ ๋ฌธ์ž์—ด์ธ์ง€ ํ™•์ธ +- [x] ์–‘์˜ ์ •์ˆ˜๋งŒ ํ—ˆ์šฉ (0 ๋ฐ ์Œ์ˆ˜ ๊ธˆ์ง€) +- [x] ์—ฐ์† ๊ตฌ๋ถ„์ž(`"1,,2"`, `"1:"`) ๋“ฑ ์ž˜๋ชป๋œ ํ˜•์‹์˜ ์ž…๋ ฅ์„ ํ™•์ธ +- [x] ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ `[ERROR]`๋กœ ์‹œ์ž‘ํ•˜๋Š” ๋ฉ”์‹œ์ง€์™€ ํ•จ๊ป˜ `Error` ๋ฐœ์ƒ์‹œํ‚จ ํ›„ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ์ข…๋ฃŒ + +--- + +### 7. ํ•ฉ๊ณ„ ๊ณ„์‚ฐ + +- [x] ๊ฒ€์ฆ ํ†ต๊ณผํ•œ ์ •์ˆ˜๋“ค์„ ํ•ฉ์‚ฐํ•˜์—ฌ `Number`๋กœ ๋ฐ˜ํ™˜ + +--- + +### 8. ์ถœ๋ ฅ ํ˜•์‹ ์ค€์ˆ˜ + +- [x] ๊ฒฐ๊ณผ ์ถœ๋ ฅ ์‹œ ํ˜•์‹์„ ์ •ํ™•ํžˆ ๋งž์ถค +- [x] ๊ณต๋ฐฑ ๋ฐ ์ฝœ๋ก (`:`) ์œ„์น˜ ์ •ํ™•ํžˆ ์œ ์ง€ + +--- + +### 9. ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ๋ฐ ์ข…๋ฃŒ ํ๋ฆ„ + +- [x] `run()` ๋‚ด๋ถ€์—์„œ ์˜ค๋ฅ˜๋ฅผ `try/catch`๋กœ ์ฒ˜๋ฆฌ +- [x] `[ERROR]` ๋ฉ”์‹œ์ง€๋ฅผ `Console.print()`๋กœ ์ถœ๋ ฅ +- [x] `process.exit()` ํ˜ธ์ถœ ์—†์ด ์ž์—ฐ์Šค๋Ÿฝ๊ฒŒ ์ข…๋ฃŒ diff --git a/src/App.js b/src/App.js index 091aa0a5..4aec972d 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,20 @@ +import { Console } from "@woowacourse/mission-utils"; +import { add } from "./StringCalculator.js"; class App { - async run() {} + async run() { + Console.print("๋ง์…ˆํ•  ๋ฌธ์ž์—ด์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”."); + try { + const input = await Console.readLineAsync(""); + const result = add(input); + Console.print(`๊ฒฐ๊ณผ : ${result}`); + } catch (error) { + const message = + error?.message ?? "[ERROR] ์•Œ ์ˆ˜ ์—†๋Š” ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค."; + Console.print(message); + + throw error; + } + } } export default App; diff --git a/src/StringCalculator.js b/src/StringCalculator.js new file mode 100644 index 00000000..b1fa3406 --- /dev/null +++ b/src/StringCalculator.js @@ -0,0 +1,81 @@ +const DEFAULT_DELIMS = [",", ":"]; + +function escapeRegexChar(ch) { + return ch.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function parseDelimiterAndBody(input) { + if (input.startsWith("//")) { + const realNewlineIdx = input.indexOf("\n"); + const literalNewlineIdx = input.indexOf("\\n"); + + let idx = -1; + let useLiteral = false; + + if (realNewlineIdx !== -1) { + idx = realNewlineIdx; + } else if (literalNewlineIdx !== -1) { + idx = literalNewlineIdx; + useLiteral = true; + } + + if (idx === -1) { + throw new Error("[ERROR] ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž ๋’ค์— ์ค„๋ฐ”๊ฟˆ(\\n)์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค."); + } + const delim = input.slice(2, idx); + if (!delim || delim.length !== 1) { + throw new Error( + "[ERROR] ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๋Š” '//'์™€ '\\n' ์‚ฌ์ด์˜ ๋ฌธ์ž 1๊ฐœ๋งŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค." + ); + } + + const body = useLiteral ? input.slice(idx + 2) : input.slice(idx + 1); + + // ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๊ฐ€ ์กด์žฌํ•  ๋•Œ + return { delims: [delim], body }; + } + // ์ปค์Šคํ…€ ๊ตฌ๋ถ„์ž๊ฐ€ ์—†์„ ๋•Œ + return { delims: DEFAULT_DELIMS, body: input }; +} + +function splitTokens(body, delims) { + if (body === "") return []; + const escaped = delims.map(escapeRegexChar).join(""); + const re = new RegExp(`[${escaped}]`, "g"); + const raw = body.split(re).map((t) => t.trim()); + + // split์œผ๋กœ ๋‚˜๋ˆˆ ๊ฒฐ๊ณผ๊ฐ€ ์œ ํšจํ•œ์ง€ ๊ฒ€์‚ฌ + if (raw.some((t) => t === "")) { + throw new Error("[ERROR] ๊ตฌ๋ถ„์ž ์‚ฌ์ด์— ์ˆซ์ž๊ฐ€ ๋น„์–ด์žˆ์Šต๋‹ˆ๋‹ค."); + } + return raw; +} + +function toPositiveIntegers(tokens) { + return tokens.map((t) => { + // ์ •๊ทœ์‹ ๊ฒ€์‚ฌ + if (!/^\d+$/.test(t)) { + throw new Error("[ERROR] ์ˆซ์ž ์ด์™ธ์˜ ๊ฐ’์ด ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค."); + } + // ์ˆซ์ž ๋ณ€ํ™˜ ๋ฐ ์Œ์ˆ˜/0 ๊ฒ€์‚ฌ + const num = Number(t); + if (num <= 0) { + throw new Error("[ERROR] 0 ๋˜๋Š” ์Œ์ˆ˜๋Š” ํ—ˆ์šฉ๋˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค."); + } + return num; + }); +} + +export function add(input) { + const trimmed = (input ?? "").trim(); + if (trimmed === "") return 0; + + const { delims, body } = parseDelimiterAndBody(trimmed); + + const tokens = splitTokens(body, delims); + + const numbers = toPositiveIntegers(tokens); + + // ํ•ฉ์‚ฐ + return numbers.reduce((acc, n) => acc + n, 0); +} diff --git a/types/woowacourse__mission-utils.d.ts b/types/woowacourse__mission-utils.d.ts new file mode 100644 index 00000000..00574645 --- /dev/null +++ b/types/woowacourse__mission-utils.d.ts @@ -0,0 +1,6 @@ +declare module "@woowacourse/mission-utils" { + export const Console: { + readLineAsync(prompt?: string): Promise; + print(message: string): void; + }; +}