Files
coollang/samples/app/config.cool
T
coolguyandClaude Opus 5 6c0d08b6b0 std: F5 해결(List.enumerate), F4·F8은 넣지 않는 것으로 닫음
F5 — "튜플이 없어서 enumerate가 불가능하다"고 적었는데 틀렸다. 제네릭
struct 하나면 된다. fold_indexed보다 이쪽이 낫다: enumerate 하나가 기존
each/map/filter/fold 전부와 조합되고, fold_indexed를 만들면 map_indexed,
each_indexed가 따라와 "한 개념 한 방식"을 깬다.
samples/app에서 Numbered struct가 사라졌다 (244줄 → 232줄).

F2에 이어 두 번째로 관찰자가 틀린 사례다. 마찰 8건 중 2건이 "언어가 못
한다"고 적었다가 확인해 보니 되는 것이었다.

F4 — 넣지 않는다. 전체 나열이 귀찮은 것은 맞지만 그 귀찮음이 값을 한다.
필드를 추가하면 모든 생성 지점이 컴파일 오류를 내고, 컴파일러가 전부
방문하도록 강제한다. ..base는 그것을 없앤다. "오류를 더 빨리 잡는다"가
1번 목표인데 F4는 정확히 그것을 깎는 거래다.

F8 — 넣지 않는다. 진짜 질문은 "미사용 지역 변수를 잡을 것인가"가 아니라
"경고 등급을 만들 것인가"였다. 오류로 넣으면 성가시고, 경고로 넣으면 경고
등급의 첫 입주자가 된다. 경고가 없다는 것은 이 언어의 좋은 성질이고 죽은
지역 변수 하나 때문에 팔 것이 아니다.

둘 다 "보류"가 아니라 "닫음"으로 적는다. 근거를 적어두지 않으면 다음에
같은 논의를 처음부터 다시 한다. 다시 열 조건도 함께 적었다.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019ZVDeU6KLuUVL3gs18Hm3E
2026-08-30 16:30:56 +09:00

137 lines
4.1 KiB
Plaintext

// 설정 파일 파서.
//
// 이 파일은 검사기를 시험하려고 쓴 것이 아니라 일을 하려고 쓴 것이다.
// 그것이 이 파일의 목적이다 — 언어가 실제로 쓸 만한지는 이런 코드에서만
// 드러난다.
import "cool.dev/std/list" as List
import "cool.dev/std/string" as String
import "cool.dev/std/int" as Int
import "cool.dev/std/option" as Option
import "cool.dev/std/result" as Result
// 설정 값. 타입이 셋뿐이므로 열거형이 맞다.
pub enum Value {
Text(String),
Number(Int),
Flag(Bool),
}
pub copyable struct Entry {
line: Int,
key: String,
value: Value,
}
// 오류에 줄 번호가 있어야 사람이 고칠 수 있다.
pub copyable struct Problem {
line: Int,
message: String,
}
pub copyable struct Config {
entries: List[Entry],
problems: List[Problem],
}
// 값 하나를 읽는다. 따옴표도 타입 표기도 없다 — 모양으로 정한다.
// true/false는 Flag, 정수로 읽히면 Number, 나머지는 Text.
pub fn parse_value(raw: String) -> Value {
if raw == "true" {
Flag(true)
} else if raw == "false" {
Flag(false)
} else {
match Int.parse(raw) {
Ok(n) => Number(n),
Err(_) => Text(raw),
}
}
}
pub fn show_value(v: Value) -> String {
match v {
Text(s) => String.concat("\"", String.concat(s, "\"")),
Number(n) => Int.show(n),
Flag(b) => if b { "true" } else { "false" },
}
}
pub fn type_name(v: Value) -> String {
match v {
Text(_) => "text",
Number(_) => "number",
Flag(_) => "flag",
}
}
// 한 줄을 읽는다. 빈 줄과 주석은 값이 없고, 그것은 오류가 아니다.
// 그래서 Result가 아니라 Option[Result[...]]가 필요해 보이지만, 그러면
// 부르는 쪽이 두 겹을 벗겨야 한다. Config 하나에 둘 다 모으는 편이 낫다.
pub fn parse_line(cfg: Config, no: Int, raw: String) -> Config {
let line = String.trim(raw)
let parts = String.split(line, "=")
if String.is_empty(line) {
cfg
} else if String.starts_with(line, "#") {
cfg
} else if List.len(parts) != 2 {
add_problem(cfg, no, String.concat("= 가 하나여야 합니다: ", line))
} else {
let key = String.trim(Option.unwrap_or(List.nth(parts, 0), ""))
let val = String.trim(Option.unwrap_or(List.nth(parts, 1), ""))
if String.is_empty(key) {
add_problem(cfg, no, "이름이 비어 있습니다")
} else {
add_entry(cfg, Entry { line: no, key: key, value: parse_value(val) })
}
}
}
pub fn add_entry(cfg: Config, e: Entry) -> Config {
Config {
entries: List.push(cfg.entries, e),
problems: cfg.problems,
}
}
pub fn add_problem(cfg: Config, no: Int, msg: String) -> Config {
Config {
entries: cfg.entries,
problems: List.push(cfg.problems, Problem { line: no, message: msg }),
}
}
pub fn parse(text: String) -> Config {
let lines = List.enumerate(String.split(text, "\n"))
let empty = Config { entries: [], problems: [] }
List.fold(lines, empty, fn(cfg, l) {
parse_line(cfg, l.i + 1, l.value)
})
}
// 이름으로 찾는다. 없으면 Err — 못 찾은 것은 오류지 빈 값이 아니다.
pub fn lookup(cfg: Config, key: String) -> Result[Value, String] {
let hit = List.filter(cfg.entries, fn(e) { e.key == key })
Result.map(
Option.ok_or(List.first(hit), String.concat("설정에 없습니다: ", key)),
fn(e) { e.value },
)
}
pub fn get_int(cfg: Config, key: String) -> Result[Int, String] {
match lookup(cfg, key)? {
Number(n) => Ok(n),
Text(_) => Err(String.concat(key, "은(는) 정수가 아닙니다")),
Flag(_) => Err(String.concat(key, "은(는) 정수가 아닙니다")),
}
}
pub fn get_flag(cfg: Config, key: String) -> Result[Bool, String] {
match lookup(cfg, key)? {
Flag(b) => Ok(b),
Text(_) => Err(String.concat(key, "은(는) 참거짓이 아닙니다")),
Number(_) => Err(String.concat(key, "은(는) 참거짓이 아닙니다")),
}
}