From 23012fef7ba9e7de749cf0ed52266029a8ef6b8d Mon Sep 17 00:00:00 2001
From: "carpentry-heartbeat[bot]"
Date: Mon, 20 Jul 2026 18:17:17 +0200
Subject: [PATCH] Fix documented parse return type and the broken README
example
The docs claimed `Reader.parse` returns `(Result (Array (Box Form))
ParseErr)`; it has returned `(Result (Array (Box Located)) ParseErr)`
since 0.2.0. The example did not compile: `Box.peek` yields a
`&Located` where `Form.str` wants a `&Form`, and bare `println` is
not a symbol.
`Located` appeared nowhere in the README, so `Located.form` -- needed
to get any value out of the parser -- was undiscoverable. Adds a
section covering it and the position fields.
---
README.md | 43 +++++++++++++++++++++++++++++++------
carp-reader.carp | 9 ++++----
docs/Reader.html | 9 ++++----
docs/carp-reader_index.html | 8 ++++---
docs/index.html | 8 ++++---
gendocs.carp | 8 ++++---
6 files changed, 62 insertions(+), 23 deletions(-)
diff --git a/README.md b/README.md
index 2179728..3d66e13 100644
--- a/README.md
+++ b/README.md
@@ -15,12 +15,43 @@ in which Carp gets parsed into a structured `Form` AST. Built on
(match (Reader.parse "; greeting\n(defn hello [name] (println &name))")
(Result.Success forms)
(for [i 0 (Array.length &forms)]
- (println &(Form.str (Box.peek (Array.unsafe-nth &forms i)))))
+ (IO.println &(Form.str (Located.form (Box.peek (Array.unsafe-nth &forms i))))))
(Result.Error e)
(IO.errorln &(Parser.format-error &e)))
```
-`Reader.parse` returns `(Result (Array (Box Form)) ParseErr)`.
+`Reader.parse` returns `(Result (Array (Box Located)) ParseErr)`.
+
+## Located
+
+Every node the parser emits — top-level and nested alike — is a `Located`,
+not a bare `Form`:
+
+```clojure
+(deftype Located
+ [form Form
+ info Info ; position of the form's first byte
+ end Info]) ; position just past its last byte
+
+(deftype Info
+ [pos Int ; byte offset
+ line Int
+ col Int])
+```
+
+`Located.form` unwraps it, so it is the accessor you reach for in almost any
+use of this library:
+
+```clojure
+(let [l (Box.peek (Array.unsafe-nth &forms 0))]
+ (println* (Form.str (Located.form l))
+ " at line " @(Info.line (Located.info l))
+ ", column " @(Info.col (Located.info l))))
+```
+
+`Located.str` is shorthand for `(Form.str (Located.form l))` when you do not
+need the position. Nodes that reader macros synthesize rather than read from
+source carry `Info.synthetic`, whose fields are all `0`.
## Form shape
@@ -36,10 +67,10 @@ in which Carp gets parsed into a structured `Form` AST. Built on
(Str [String])
(Pat [String]) ; #"..." pattern literal
(Sym [(Array String)]) ; ["Foo" "Bar" "baz"]
- (Lst [(Array (Box Form))]) ; (...)
- (Arr [(Array (Box Form))]) ; [...]
- (StaticArr [(Array (Box Form))]) ; $[...]
- (Dict [(Array (Box Form))]) ; {...}
+ (Lst [(Array (Box Located))]) ; (...)
+ (Arr [(Array (Box Located))]) ; [...]
+ (StaticArr [(Array (Box Located))]) ; $[...]
+ (Dict [(Array (Box Located))]) ; {...}
(Cmt [String])) ; ; line comment
```
diff --git a/carp-reader.carp b/carp-reader.carp
index 3856de3..9357fa9 100644
--- a/carp-reader.carp
+++ b/carp-reader.carp
@@ -1025,12 +1025,13 @@ you need it.")
0))
(doc parse "parses a Carp source string into an array of top-level
- `Form` forms. Comments are preserved as `Form.Cmt` nodes interspersed
- with real forms.")
+ nodes, yielding `(Result (Array (Box Located)) ParseErr)`. Use
+ `Located.form` to reach the `Form` inside a `Located`. Comments are
+ preserved as `Form.Cmt` nodes interspersed with real forms.")
(defn parse [src] (Parser.parse (many-forms) src))
- (doc parse-form "parses exactly one top-level `Form` form. Use
- `parse` for whole-file inputs.")
+ (doc parse-form "parses exactly one top-level form, yielding
+ `(Result Located ParseErr)`. Use `parse` for whole-file inputs.")
(defn parse-form [src]
(Parser.parse
(Parser.then (skip-ws) (Parser.before (Parser.recurse &*form*) (skip-ws)))
diff --git a/docs/Reader.html b/docs/Reader.html
index 8a63789..684339a 100644
--- a/docs/Reader.html
+++ b/docs/Reader.html
@@ -434,8 +434,9 @@
parses a Carp source string into an array of top-level
-Form forms. Comments are preserved as Form.Cmt nodes interspersed
-with real forms.
+nodes, yielding (Result (Array (Box Located)) ParseErr). Use
+Located.form to reach the Form inside a Located. Comments are
+preserved as Form.Cmt nodes interspersed with real forms.
@@ -455,8 +456,8 @@
-
parses exactly one top-level Form form. Use
-parse for whole-file inputs.
+ parses exactly one top-level form, yielding
+(Result Located ParseErr). Use parse for whole-file inputs.
diff --git a/docs/carp-reader_index.html b/docs/carp-reader_index.html
index 8f16e3c..4875018 100644
--- a/docs/carp-reader_index.html
+++ b/docs/carp-reader_index.html
@@ -34,9 +34,11 @@
(load "git@github.com:carpentry-org/carp-reader@0.3.7")
(match (Reader.parse "(defn hello [] (println \"hi\"))")
- (Result.Success forms) (for [i 0 (Array.length &forms)]
- (println &(Form.str (Box.peek (Array.unsafe-nth &forms i)))))
- (Result.Error e) (IO.errorln &(Parser.format-error &e)))
+ (Result.Success forms)
+ (for [i 0 (Array.length &forms)]
+ (IO.println &(Form.str (Located.form (Box.peek (Array.unsafe-nth &forms i))))))
+ (Result.Error e)
+ (IO.errorln &(Parser.format-error &e)))
Modules