Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
79 changes: 79 additions & 0 deletions devel/0046.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
# [0046] 修复 gf fmt 将 quasiquote 内的注释输出为 (*comment*) 代码

## 相关文档
- [dddd.md](dddd.md) - 任务文档模板

## 任务相关的代码文件
- `tools/fmt/liii/goldfmt-format.scm` - 格式化输出核心逻辑
- `tools/fmt/liii/goldfmt-scan.scm` - 扫描和规范化逻辑
- `tools/fmt/liii/goldfmt-tokenize.scm` - 词法分析(将 `;;` 转为 `(*comment*)`)
- `tools/fmt/tests/liii/goldfmt-format/format-datum-test.scm` - 相关测试

## 如何测试

### 确定性测试(单元测试)
```bash
xmake b goldfish
bin/gf tests/liii/goldfmt-format/format-datum-test.scm
bin/gf tests/liii/goldfmt-format/format-inline-test.scm
bin/gf tests/liii/goldfmt-format/format-string-test.scm
```

### 非确定性测试(文档验证)
```bash
bin/gf fmt --dry-run ~/git/mogan/TeXmacs/progs/kernel/texmacs/tm-define.scm
```
预期:quasiquote 内的 `;;` 注释应保持为 `;;` 注释,不应输出为 `(*comment* "...")`。

## 2026-05-17 定位并修复 gf fmt 对 quasiquote 内注释的错误输出

### What
`gf fmt` 在处理包含 `;;` 行注释的 Scheme 文件时,会将注释转换为 `(*comment* "...")` 伪表达式以保留注释信息。然而,当这些注释位于 quasiquote(反引号)或宏体内时,格式化输出会将 `(*comment* "...")` 作为实际代码输出,而非还原为 `;;` 注释。

以 `tm-define.scm` 为例,原始代码:
```scheme
`(let ((former ,var))
;;(if (== (length (ahash-ref tm-defined-table ',var)) 1)
;; (display* "Overloaded " ',var "\n"))
(set! ,var ,nval)
...)
```

被错误格式化为:
```scheme
`(let ((former ,var))
(*comment* "(if (== (length (ahash-ref tm-defined-table ',var)) 1)")
(*comment* " (display* \"Overloaded \" ',var \"\\n\"))")
(set! ,var ,nval)
...)
```

### Why
这是一个严重的语义错误:
1. `(*comment* ...)` 不是标准 Scheme 形式,在 Goldfish Scheme 中未定义,会导致运行时错误。
2. 注释被转换为可执行代码,完全改变了原程序的语义。
3. quasiquote 和宏体内的代码往往是动态生成的,注释此处尤为重要。

### How
问题的根本原因在于 `goldfmt-format.scm` 中的 `reader-prefix-env?` 判断。quasiquote、unquote、unquote-splicing 节点被归类为 `reader-prefix-env?`,此时 `walk-env!` 直接通过 `format-inline-atom-or-quote-at` 格式化原始 `:value` 字段,绕过了扫描阶段生成的 `:children` 节点树。

由于 `:value` 中保留了原始的 `(*comment* "...")` 伪表达式,而 `format-reader-datum-at` / `format-reader-datum-inline` 没有识别并还原 `*comment*` 形式,导致注释被当作普通列表输出。

修复方案:在 `format-reader-datum-inline` 和 `format-reader-datum-at` 中增加对 `*comment*` 形式的识别,将其还原为 `;;` 注释行。

具体修改:
1. 添加 `comment-datum?` 辅助谓词(与已有的 `newline-marker-datum?` 类似)。
2. 在 `format-reader-datum-inline` 和 `format-reader-datum-at` 的 `cond` 分支中,于 `raw-string-datum?` 之后添加 `comment-datum?` 处理,调用 `format-comment-content` 还原 `;;` 注释。
3. 确保 `reader-datum-contains-newline-marker?` 也考虑 `*comment*`(避免内联时被误判为可内联的纯文本)。

### 修改的文件
- `tools/fmt/liii/goldfmt-format.scm`:添加 `comment-datum?` 谓词,更新 `reader-datum-contains-newline-marker?`、`format-reader-datum-inline`、`format-reader-datum-at`。
- `tools/fmt/tests/liii/goldfmt-format/format-string-test.scm`:添加回归测试,覆盖 quasiquote 和 quote 内的 `*comment*` 还原。

### 测试结果
- `tools/fmt/tests/liii/goldfmt-format/*.scm`:全部通过(189 checks)。
- `tools/fmt/tests/liii/goldfmt-scan/*.scm`:全部通过(235 checks)。
- `tools/fmt/tests/liii/goldfmt-tokenize/*.scm`:全部通过(117 checks)。
- `tools/fmt/tests/liii/goldfmt-record/*.scm`:全部通过(48 checks)。
- `tools/fmt/tests/liii/goldfmt-rule/*.scm`:全部通过(20 checks)。
- `gf fmt --dry-run ~/git/mogan/TeXmacs/progs/kernel/texmacs/tm-define.scm`:输出中不再包含 `*comment*`,所有注释正确还原为 `;;`。
12 changes: 12 additions & 0 deletions tools/fmt/liii/goldfmt-format.scm
Original file line number Diff line number Diff line change
Expand Up @@ -228,6 +228,15 @@
) ;and
) ;define

(define (comment-datum? datum)
(and (pair? datum)
(eq? (car datum) '*comment*)
(pair? (cdr datum))
(string? (cadr datum))
(null? (cddr datum))
) ;and
) ;define

(define (reader-newlines count)
(let loop
((i count) (result ""))
Expand All @@ -237,6 +246,7 @@

(define (reader-datum-contains-newline-marker? datum)
(cond ((newline-marker-datum? datum) #t)
((comment-datum? datum) #t)
((pair? datum)
(or (reader-datum-contains-newline-marker? (car datum))
(reader-datum-contains-newline-marker? (cdr datum))
Expand Down Expand Up @@ -587,6 +597,7 @@
(cond ((raw-string-literal? datum) (raw-string-literal-source datum))
((char-literal? datum) (char-literal-source datum))
((raw-string-datum? datum) (cadr datum))
((comment-datum? datum) (format-comment-content (cadr datum)))
((single-arg-symbol-form? datum 'quasiquote)
(string-append "`" (format-reader-datum-inline (cadr datum)))
) ;
Expand All @@ -609,6 +620,7 @@
(cond ((raw-string-literal? datum) (raw-string-literal-source datum))
((char-literal? datum) (char-literal-source datum))
((raw-string-datum? datum) (cadr datum))
((comment-datum? datum) (format-comment-content (cadr datum)))
((single-arg-symbol-form? datum 'quasiquote)
(string-append "`" (format-reader-datum-at (cadr datum) (+ indent 1)))
) ;
Expand Down
24 changes: 11 additions & 13 deletions tools/fmt/liii/goldfmt-scan.scm
Original file line number Diff line number Diff line change
Expand Up @@ -698,8 +698,8 @@
(define (scan-file path)
(let* ((raw-content (path-read-text path))
(scanned (source-tokenize raw-content))
;; 处理文件开头空行
(leading-blanks (let loop ((i 0) (count 0))
(leading-blanks (let loop
((i 0) (count 0))
(if (>= i (string-length raw-content))
count
(let ((c (string-ref raw-content i)))
Expand All @@ -711,18 +711,16 @@
) ;let
) ;if
) ;let
)
(tokens-with-leading (if (> leading-blanks 0)
(cons (cons 'newline leading-blanks) scanned)
scanned))
;; 处理文件末尾换行符
) ;leading-blanks
(tokens-with-leading (if (> leading-blanks 0) (cons (cons 'newline leading-blanks) scanned) scanned)
) ;tokens-with-leading
(tokens (if (and (not (null? tokens-with-leading))
(> (string-length raw-content) 0)
(char=? (string-ref raw-content (- (string-length raw-content) 1)) #\newline))
;and
(append tokens-with-leading (list (cons 'newline 1)))
tokens-with-leading)
;if
(> (string-length raw-content) 0)
(char=? (string-ref raw-content (- (string-length raw-content) 1)) #\newline)
) ;and
(append tokens-with-leading (list (cons 'newline 1)))
tokens-with-leading
) ;if
) ;tokens
(processed-content (source-tokens->string tokens))
) ;
Expand Down
4 changes: 1 addition & 3 deletions tools/fmt/liii/goldfmt.scm
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,7 @@

;; ; 格式化单个文件(dry-run 模式,输出到终端)
(define (format-file-dry-run path-str)
(let* ((nodes (scan-file path-str))
(formatted (format-nodes nodes))
) ;
(let* ((nodes (scan-file path-str)) (formatted (format-nodes nodes)))
(display formatted)
) ;let*
) ;define
Expand Down
26 changes: 26 additions & 0 deletions tools/fmt/tests/liii/goldfmt-format/format-string-test.scm
Original file line number Diff line number Diff line change
Expand Up @@ -260,4 +260,30 @@
"\n;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;\n"
) ;check

;; 测试 quasiquote 内的 (*comment*) 被还原为 ;; 注释
;; 注意:format-string 使用 read 读取输入,raw ;; 注释会被 read 丢弃,
;; 因此测试输入需直接使用 tokenize 后的 (*comment*) 形式。
(check (format-string #"IN"`(let ((x ,y))
(*comment* " comment inside quasiquote")
(display x))
"IN")
=>
#"OUT"`(let ((x ,y))
;; comment inside quasiquote
(display x))
"OUT"
) ;check

;; 测试 quote 内的 (*comment*) 被还原为 ;; 注释
(check (format-string #"IN"'(let ((x 1))
(*comment* " comment inside quote")
(display x))
"IN")
=>
#"OUT"'(let ((x 1))
;; comment inside quote
(display x))
"OUT"
) ;check

(check-report)
Loading