Skip to content
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ Just like S7 Scheme, [src/goldfish.hpp](src/goldfish.hpp) and [src/goldfish.cpp]
| [(liii uuid)](goldfish/liii/uuid.scm) | UUID generation | `uuid4` |
| [(liii http)](goldfish/liii/http.scm) | HTTP client library | `http-get`, `http-post`, `http-head` |
| [(liii json)](goldfish/liii/json.scm) | JSON parsing and manipulation | `string->json`, `json->string` |
| [(liii ini)](goldfish/liii/ini.scm) | INI file parsing (SRFI 233) | `make-ini-file-generator`, `make-ini-file-accumulator` |


### SRFI
Expand All @@ -55,6 +56,7 @@ Just like S7 Scheme, [src/goldfish.hpp](src/goldfish.hpp) and [src/goldfish.cpp]
| `(srfi srfi-133)` | Part | Vector |
| `(srfi srfi-151)` | Part | Bitwise Operations |
| `(srfi srfi-196)` | Complete | Range Library |
| `(srfi srfi-233)` | Complete | INI files |
| `(srfi srfi-216)` | Part | SICP |

### R7RS Standard Libraries
Expand Down
2 changes: 2 additions & 0 deletions README_ZH.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
| [(liii uuid)](goldfish/liii/uuid.scm) | UUID 生成 | `uuid4` |
| [(liii http)](goldfish/liii/http.scm) | HTTP 客户端库 | `http-get`, `http-post`, `http-head` |
| [(liii json)](goldfish/liii/json.scm) | JSON 解析和操作 | `string->json`, `json->string` |
| [(liii ini)](goldfish/liii/ini.scm) | INI 文件解析 (SRFI 233) | `make-ini-file-generator`, `make-ini-file-accumulator` |

### SRFI

Expand All @@ -54,6 +55,7 @@
| `(srfi srfi-133)` | 部分 | 向量 |
| `(srfi srfi-151)` | 部分 | 位运算 |
| `(srfi srfi-196)` | 完整 | Range 库 |
| `(srfi srfi-233)` | 完整 | INI 文件 |
| `(srfi srfi-216)` | 部分 | SICP |


Expand Down
69 changes: 69 additions & 0 deletions devel/0039.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
# [0039] 实现 SRFI 233: INI Files

## 任务相关的代码文件
- `goldfish/srfi/srfi-233.scm`
- `goldfish/liii/ini.scm`
- `tests/liii/ini-file-test.scm`
- `README.md`
- `README_ZH.md`

## 如何测试
```bash
xmake b goldfish
bin/gf fmt goldfish/srfi/srfi-233.scm
bin/gf fmt goldfish/liii/ini.scm
bin/gf fmt tests/liii/ini-file-test.scm
bin/gf tests/liii/ini-file-test.scm
```

## 2026/05/06 PR 审查反馈修改

### What
1. 将实现从 `(liii ini-file)` 迁移到 `(srfi srfi-233)`,符合项目库命名规范
2. 新建 `goldfish/liii/ini.scm` 作为用户接口层,re-export `(srfi srfi-233)` 的导出
3. 修复 accumulator 在 value 为 `#f` 时写入字面量 `#f` 的 bug
4. 新增 standalone key 往返测试,断言数从 46 增至 47
5. 更新 README.md 和 README_ZH.md 的标准库表格和 SRFI 表格

### Why
PR 审查人 da-liii 指出:
- 库名应为 `(liii ini)` 而非 `(liii ini-file)`,与 `(liii json)`、`(liii csv)` 命名一致
- 接口放在 `(liii ini)` 中,实现应放在 `(srfi srfi-233)` 中,保证兼容性

### How
参照 `(liii range)` re-export `(srfi srfi-196)` 的模式:
- `goldfish/srfi/srfi-233.scm` 包含完整实现
- `goldfish/liii/ini.scm` 仅 `import` + `export`,无额外逻辑

## 2026/05/05 实现 (srfi srfi-233) 库

### What
1. 创建 `goldfish/srfi/srfi-233.scm`,实现 SRFI 233 规范中的两个核心函数:
- `make-ini-file-generator`:从文本输入端口创建生成器,逐行解析 INI 格式,返回 `(section key value)` 三元素列表
- `make-ini-file-accumulator`:向文本输出端口写入 INI 格式数据
2. 创建 `tests/liii/ini-file-test.scm`,包含 23 个测试场景、47 个断言
3. 在 `README.md` 和 `README_ZH.md` 的标准库表格中加入 `(liii ini)` 条目

### Why
SRFI 233 是 Scheme 社区标准化的 INI 文件访问接口。INI 文件虽然简单,但在配置管理场景中广泛使用。作为 Goldfish Scheme 标准库的补充,提供开箱即用的 INI 文件解析和生成能力。

### How
**解析逻辑(Generator):**
- 逐行 `read-line` 读取输入端口
- 用 `char-index` 找到首个注释分隔符,截断注释部分
- 用 `ini-trim` 去除首尾空白(仅空格和 Tab,符合 SRFI 233 定义)
- `[...]` 模式匹配节名,转为 symbol
- 含分隔符的行用 `split-at-first` 按第一个分隔符拆分为键值对
- 不含分隔符的行视为键,值为 `#f`
- 通过闭包维护 `current-section` 状态

**写入逻辑(Accumulator):**
- 通过闭包维护 `current-section` 状态,仅在 section 变化时写入 `[section]` 行
- `#f` section 不写入 section 头
- value 为 `#f` 时只写 key,不写分隔符
- 字符串参数以注释格式写入(`comment-delim` + 空格 + 内容)
- EOF 对象触发关闭状态,后续调用报错

**关键设计决策:**
- 使用纯整数索引的辅助函数(`char-index`、`ini-trim`),避免 `string-cursor` 库的 cursor/index 转换复杂性
- 用 `define*` 实现可选参数,支持自定义 `key-value-sep` 和 `comment-delim`
4 changes: 4 additions & 0 deletions goldfish/liii/ini.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
(define-library (liii ini)
(import (srfi srfi-233))
(export make-ini-file-generator make-ini-file-accumulator)
) ;define-library
174 changes: 174 additions & 0 deletions goldfish/srfi/srfi-233.scm
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
;;
;; Copyright (C) 2024 The Goldfish Scheme Authors
Comment thread
Kabukimono-Sakura marked this conversation as resolved.
Outdated
;;
;; 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
;;
;; http://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.
;;
;; SRFI 233: INI files
;; Authors: John Cowan (spec), Arvydas Silanskas (reference implementation)

(define-library (srfi srfi-233)
(import (scheme base) (liii base))
(export make-ini-file-generator make-ini-file-accumulator)
(begin

;; Whitespace for SRFI 233: space and tab only
(define (ini-whitespace? ch)
(or (char=? ch #\space) (char=? ch #\tab))
) ;define

;; Find first occurrence of char in string, returns integer index or #f
(define (char-index str ch)
(let ((len (string-length str)))
(let loop
((i 0))
(cond ((= i len) #f)
((char=? (string-ref str i) ch) i)
(else (loop (+ i 1)))
) ;cond
) ;let
) ;let
) ;define

;; Trim leading and trailing whitespace (space/tab)
(define (ini-trim str)
Comment thread
Kabukimono-Sakura marked this conversation as resolved.
Outdated
(let ((len (string-length str)))
(let loop-start
((i 0))
(if (and (< i len) (ini-whitespace? (string-ref str i)))
(loop-start (+ i 1))
(let loop-end
((j (- len 1)))
(if (and (>= j i) (ini-whitespace? (string-ref str j)))
(loop-end (- j 1))
(substring str i (+ j 1))
) ;if
) ;let
) ;if
) ;let
) ;let
) ;define

;; Remove comment from a line: find first comment-delim and truncate
(define (remove-comment line comment-delim)
(let ((idx (char-index line comment-delim)))
(if idx (substring line 0 idx) line)
) ;let
) ;define

;; Split string at first occurrence of a character
;; Returns (before . after) or #f if not found
(define (split-at-first str sep-char)
(let ((idx (char-index str sep-char)))
(if idx
(cons (substring str 0 idx) (substring str (+ idx 1) (string-length str)))
#f
) ;if
) ;let
) ;define

;; Parse a section header line like "[name]"
;; Returns section name string, or #f if not a section header
(define (parse-section-name line)
(let* ((trimmed (ini-trim line)) (len (string-length trimmed)))
(if (and (> len 1)
(char=? (string-ref trimmed 0) #\[)
(char=? (string-ref trimmed (- len 1)) #\])
) ;and
(ini-trim (substring trimmed 1 (- len 1)))
#f
) ;if
) ;let*
) ;define

;; make-ini-file-generator
;; Returns a thunk that yields (section key value) lists from the INI port
(define* (make-ini-file-generator inport (key-value-sep #\=) (comment-delim #\;))
(let ((current-section #f))
(lambda ()
(let loop
()
(let ((line (read-line inport)))
(cond ((eof-object? line) (eof-object))
(else (let* ((no-comment (remove-comment line comment-delim))
(trimmed (ini-trim no-comment))
) ;
(if (string=? trimmed "")
(loop)
(let ((section-name (parse-section-name trimmed)))
(cond (section-name (set! current-section (string->symbol section-name)) (loop))
(else (let ((kv (split-at-first trimmed key-value-sep)))
(if kv
(let ((key-str (ini-trim (car kv))) (val-str (ini-trim (cdr kv))))
(list current-section (string->symbol key-str) val-str)
) ;let
(list current-section (string->symbol trimmed) #f)
) ;if
) ;let
) ;else
) ;cond
) ;let
) ;if
) ;let*
) ;else
) ;cond
) ;let
) ;let
) ;lambda
) ;let
) ;define*

;; make-ini-file-accumulator
;; Returns a procedure that writes INI data to outport
(define* (make-ini-file-accumulator outport (key-value-sep #\=) (comment-delim #\;))
(let ((current-section '*ini-none*) (closed #f))
(lambda (item)
(cond (closed (error 'ini-file "accumulator called after EOF"))
((eof-object? item) (set! closed #t) (eof-object))
((string? item)
(when (char-index item #\newline)
(error 'ini-file "comment string contains newline")
) ;when
(display comment-delim outport)
(display #\space outport)
(display item outport)
(newline outport)
) ;
((and (list? item) (= (length item) 3))
(let ((section (car item)) (key (cadr item)) (value (caddr item)))
;; Write section header if changed
(when (not (eq? section current-section))
(when section
(display #\[ outport)
(display (symbol->string section) outport)
(display #\] outport)
(newline outport)
) ;when
(set! current-section section)
) ;when
;; Write key-value line
(display (symbol->string key) outport)
(when value
(display key-value-sep outport)
(display value outport)
) ;when
(newline outport)
) ;let
) ;
(else (error 'ini-file "invalid argument to accumulator"))
) ;cond
) ;lambda
) ;let
) ;define*

) ;begin
) ;define-library
Loading
Loading