Implement our parser, . field access, comment preservation and line number tracking for source-level debugging #26

Merged
alex-eg merged 13 commits from our-parser into main 2026-09-15 19:43:14 +02:00
3 changed files with 72 additions and 8 deletions
Showing only changes of commit 7e6e32488b - Show all commits

View File

@@ -47,8 +47,8 @@ source location. Only statement positions may be walked this way: a
(if src (if src
(list (line-directive src) (walk-expr s)) (list (line-directive src) (walk-expr s))
(list (walk-expr s))))) (list (walk-expr s)))))
stmts) (pack-comments stmts))
(map walk-expr stmts))) (map walk-expr (pack-comments stmts))))
(define (walk-stmt s) (define (walk-stmt s)
"A statement in a slot that holds exactly one form -- an `if' arm. "A statement in a slot that holds exactly one form -- an `if' arm.
@@ -132,6 +132,40 @@ forms, and what remains."
(define (comment-form? f) (define (comment-form? f)
(and (pair? f) (eq? (car f) 'comment))) (and (pair? f) (eq? (car f) 'comment)))
;;; Strip ;s so ;;; Foo becomes /* Foo */ and not /* ;; Foo */
(define (strip-comment-marker text)
(string-trim-both (string-trim text #\;)))

Still only c-or / c-bit-or / c-bit-or=. After the new reader, (| B A R) and (|| 0 1) are those symbols, not these names. sexc -C of the #16 sample emitted \|\\||(B, A, R) and cc rejected it. c-bit-or still works (1 | 2 | 4).

Still only `c-or` / `c-bit-or` / `c-bit-or=`. After the new reader, `(| B A R)` and `(|| 0 1)` are those symbols, not these names. `sexc -C` of the #16 sample emitted `\|\\||(B, A, R)` and `cc` rejected it. `c-bit-or` still works (`1 | 2 | 4`).
(define (walk-comment texts)
(list '%comment
(string-append " "
(string-intersperse (map strip-comment-marker texts)
"\n ")
" ")))
;;; Merge multiple lines of /* */ into single block
(define (pack-comments forms)
(let loop ((fs forms) (acc (list)))
(cond

Same positional trap: a ; between init / test / step binds to the wrong slot. Observed:

for (int i = 0; /* between*/; i < 1) {
    ++i;
    puts("x");
}

c-apply also joins args with ", ", so (printf "%d" ; c\n x) becomes printf("%d", /* c */, x) — extra comma, and cc fails on the #16 sample.

Same positional trap: a `;` between `init` / `test` / `step` binds to the wrong slot. Observed: ```c for (int i = 0; /* between*/; i < 1) { ++i; puts("x"); } ``` `c-apply` also joins args with `", "`, so `(printf "%d" ; c\n x)` becomes `printf("%d", /* c */, x)` — extra comma, and `cc` fails on the #16 sample.
((null? fs) (reverse acc))
((comment-form? (car fs))
(let ((first (car fs)))
(let gather ((rest (cdr fs)) (texts (cdr first)) (line (form-line first)))
(if (and (pair? rest)
(comment-form? (car rest))
line
(equal? (form-file first) (form-file (car rest)))
(eqv? (form-line (car rest)) (+ line 1)))
(gather (cdr rest)
(append texts (cdr (car rest)))
(form-line (car rest)))
(loop rest
(cons (if (eq? texts (cdr first))
first ; a run of one, left alone
(copy-form-source! first (cons 'comment texts)))
acc))))))
(else (loop (cdr fs) (cons (car fs) acc))))))
(define (walk-generic-toplevel form) (define (walk-generic-toplevel form)
(cond ((atom? form) (atom-to-fmt-c form)) (cond ((atom? form) (atom-to-fmt-c form))
((list? form) (map walk-generic-toplevel form)) ((list? form) (map walk-generic-toplevel form))
@@ -145,7 +179,7 @@ forms, and what remains."
((? atom?) ((? atom?)
(atom-to-fmt-c form)) (atom-to-fmt-c form))
;; (comment "text") -> /* text */. `%comment' is the fmt-c directive. ;; (comment "text") -> /* text */. `%comment' is the fmt-c directive.
(('comment . text) (cons '%comment text)) (('comment . text) (walk-comment text))
;; (dot-access obj field ...) -> obj.field... member access. `%.' ;; (dot-access obj field ...) -> obj.field... member access. `%.'
;; is the fmt-c directive for the `.' operator (see sex-fmt-c). ;; is the fmt-c directive for the `.' operator (see sex-fmt-c).
(('dot-access . rest) (cons '%. (map walk-expr rest))) (('dot-access . rest) (cons '%. (map walk-expr rest)))
@@ -200,7 +234,8 @@ forms, and what remains."
(cons* 'case/fallthrough (walk-expr (car slots)) (walk-body body))))) (cons* 'case/fallthrough (walk-expr (car slots)) (walk-body body)))))
(('default . body) (cons 'default (walk-body body))) (('default . body) (cons 'default (walk-body body)))
(else (map walk-expr form)))) ;; Drop comments so they will not generate additional comma
(else (map walk-expr (remove comment-form? form)))))
(define (walk-var form) (define (walk-var form)
;; (var a int) -> (%var int a) ;; (var a int) -> (%var int a)
@@ -365,7 +400,7 @@ forms, and what remains."
(define (process-toplevel-form form) (define (process-toplevel-form form)
(match form (match form
(('comment . text) (cons '%comment text)) (('comment . text) (walk-comment text))
(('fn . _) (list 'static (walk-function form))) (('fn . _) (list 'static (walk-function form)))
(('var . _) (list 'static (walk-var form))) (('var . _) (list 'static (walk-var form)))
(('extern . rest) (walk-extern rest)) (('extern . rest) (walk-extern rest))
@@ -386,4 +421,4 @@ forms, and what remains."
(when src (when src
(fmt #t (c-expr (line-directive src))))) (fmt #t (c-expr (line-directive src)))))
(fmt #t (c-expr (process-toplevel-form form)) nl)) (fmt #t (c-expr (process-toplevel-form form)) nl))
sex-forms)) (pack-comments sex-forms)))

View File

@@ -42,5 +42,10 @@
(test '(%. a_b c) (walk-expr '(dot-access a-b c))) (test '(%. a_b c) (walk-expr '(dot-access a-b c)))
;; comment -> %comment directive (rendered as /* ... */) ;; comment -> %comment directive (rendered as /* ... */)
(test '(%comment " hi") (walk-expr '(comment " hi"))) ;; The reader eats only the `;' that introduced the line, so ";;; Foo"
(test '(%comment " hi") (process-toplevel-form '(comment " hi")))) ;; arrives as ";; Foo"; and c-comment puts nothing between /* */ and
;; the text. Both are handled on the way out.
(test '(%comment " hi ") (walk-expr '(comment " hi")))
(test '(%comment " hi ") (process-toplevel-form '(comment " hi")))
(test '(%comment " Foo ") (walk-expr '(comment ";; Foo")))
(test '(%comment " Foo ") (walk-expr '(comment ";;; Foo "))))

View File

@@ -74,6 +74,30 @@
(emits? (in-fn "(var n int (cast (sizeof int) int))") (emits? (in-fn "(var n int (cast (sizeof int) int))")
"(int)sizeof(int)"))) "(int)sizeof(int)")))
;; A comment among a call's arguments used to become an argument,
;; and c-apply put a comma on each side of it -- which does not
;; compile. It is dropped, as in any other expression context.
(test-group "comments among arguments"
Review

These tests are why the suite is green while the #16 sample still fails: they pin comments and casts, but not (| B A R) → B | A | R or (|| a b) → a || b. A couple of emits? cases here would have caught it.

These tests are why the suite is green while the #16 sample still fails: they pin comments and casts, but not `(| B A R)` → `B | A | R` or `(|| a b)` → `a || b`. A couple of `emits?` cases here would have caught it.
(test-assert "no stray comma"
(not (emits? (in-fn "(g 1 ;; c\n 2)") "*/,")))
(test-assert "the arguments survive"
(emits? (in-fn "(g 1 ;; c\n 2)") "g(1, 2)")))
;; The reader leaves the `;'s that introduced each line, and a run of
;; comment lines arrives as one form per line.
(test-group "comment rendering"
(test-assert "the markers are stripped"
(emits? "(fn f () void ;;; Foo\n (g))" "/* Foo */"))
(test-assert "so none survive into the C"
(not (emits? "(fn f () void ;;; Foo\n (g))" ";;")))
(test-assert "consecutive lines are packed into one comment"
(emits? "(fn f () void\n ;; first\n ;; second\n (g))"
"/* first\n second */"))
;; Packing compares locations rather than just looking for adjacent
;; comment forms, so a blank line still separates them.
(test-assert "a blank line keeps them apart"
(emits? "(fn f () void\n ;; first\n\n ;; second\n (g))" "/* first */")))
;; A `;' comment is a form, so one written inside a construct with ;; A `;' comment is a form, so one written inside a construct with
;; positional slots used to land in a slot and shift everything after ;; positional slots used to land in a slot and shift everything after
;; it -- silently. In an `if' the comment became the then-arm and the ;; it -- silently. In an `if' the comment became the then-arm and the