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
44 changes: 35 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A growable string buffer for Carp.
## Installation

```clojure
(load "git@github.com:carpentry-org/strbuf@0.2.1")
(load "git@github.com:carpentry-org/strbuf@0.3.0")
```

## Usage
Expand All @@ -20,12 +20,36 @@ allocates a new string on every call.
(StringBuf.append-int &sb 200)
(StringBuf.append-str &sb " OK")
(StringBuf.append-crlf &sb)
(println* &(StringBuf.to-string &sb))
(StringBuf.delete sb)))
(println* &(StringBuf.str &sb))))
```

`to-string` returns the contents and resets the buffer for reuse.
`str` copies the contents without resetting.
## Ownership

Buffers follow Carp's memory model like any other value. An owned `StringBuf`
is deleted when it goes out of scope, no matter where it lives: in a `let`, in
an array, inside a struct or sum type, returned from a function, or moved into
one. `@` copies a buffer, and the copy is freed independently. You never call
`StringBuf.delete` yourself; it is there because it implements the `delete`
interface that the compiler calls for you.

Three functions hand you a `String`, and they differ in what happens to the
buffer:

- `str` copies the contents and leaves the buffer untouched
- `to-string` copies the contents and resets the buffer for reuse
- `into-string` consumes the buffer and hands its allocation to the `String`,
so nothing is copied and nothing is freed

Use `into-string` when you build a string once and are done with the buffer,
and `to-string` when you keep appending to the same buffer afterwards.

```clojure
(defn greeting [name]
(let-do [sb (StringBuf.create)]
(StringBuf.append-str &sb "hello, ")
(StringBuf.append-str &sb name)
(StringBuf.into-string sb)))
```

## API

Expand All @@ -34,13 +58,15 @@ allocates a new string on every call.
- `StringBuf.append-char` — append a single character
- `StringBuf.append-bytes` — append raw bytes
- `StringBuf.append-int` — append integer as decimal
- `StringBuf.append-double` — append double as string
- `StringBuf.append-long` — append long as decimal
- `StringBuf.append-double` / `StringBuf.append-float` — append as string
- `StringBuf.append-bool` — append `true` or `false`
- `StringBuf.append-crlf` — append `\r\n`
- `StringBuf.length` — current byte count
- `StringBuf.to-string` — extract as String, reset buffer
- `StringBuf.str` — copy as String, keep buffer
- `StringBuf.str` / `StringBuf.prn` — copy as String, keep the buffer as is
- `StringBuf.to-string` — copy as String, reset the buffer
- `StringBuf.into-string` — consume the buffer, no copy
- `StringBuf.clear` — reset without freeing
- `StringBuf.delete` — free the buffer

## Testing

Expand Down
14 changes: 8 additions & 6 deletions gendocs.carp
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,16 @@
(Project.config "docs-prelude" "A growable string buffer for Carp. Provides
amortized O(1) appends, unlike `String.append` which allocates on every call.

Buffers are managed by Carp: an owned `StringBuf` is deleted when it goes out
of scope, so `delete` is never called by hand.

### Usage
```
(let [sb (StringBuf.create)]
(do
(StringBuf.append-str &sb \"hello \")
(StringBuf.append-int &sb 42)
(println* &(StringBuf.to-string &sb))
(StringBuf.delete sb)))
(defn greeting [name]
(let-do [sb (StringBuf.create)]
(StringBuf.append-str &sb \"hello, \")
(StringBuf.append-str &sb name)
(StringBuf.into-string sb)))
```")

(save-docs StringBuf)
Expand Down
8 changes: 8 additions & 0 deletions src/strbuf.h
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,14 @@ String StringBuf_str(StringBuf* sb) {
return s;
}

/* Consumes the buffer and hands its allocation over to the returned String.
No copy is made and nothing is freed: the String owns the memory now. */
String StringBuf_into_MINUS_string(StringBuf sb) {
strbuf_grow(&sb, 1);
sb.data[sb.len] = '\0';
return sb.data;
}

void StringBuf_clear(StringBuf* sb) {
sb->len = 0;
}
Expand Down
32 changes: 27 additions & 5 deletions strbuf.carp
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ amortized O(1) appends.
## Installation

```
(load \"git@github.com:carpentry-org/strbuf@0.2.1\")
(load \"git@github.com:carpentry-org/strbuf@0.3.0\")
```

## Usage
Expand All @@ -17,9 +17,19 @@ amortized O(1) appends.
(StringBuf.append-str &sb \"hello\")
(StringBuf.append-char &sb \\space)
(StringBuf.append-str &sb \"world\")
(println* &(StringBuf.to-string &sb))
(StringBuf.delete sb)))
```")
(println* &(StringBuf.str &sb))))
```

## Ownership

Buffers are managed like any other Carp value: an owned `StringBuf` is
deleted when it goes out of scope, copied by `@`, and moved when it is
passed on. You never call `StringBuf.delete` yourself.

To get a `String` out of a buffer, pick by what you want to happen to the
buffer: `str` copies the contents and leaves the buffer alone, `to-string`
copies them and resets the buffer for reuse, and `into-string` consumes the
buffer and hands its allocation to the `String` without copying.")

(relative-include "src/strbuf.h")
(register-type StringBuf)
Expand Down Expand Up @@ -66,15 +76,27 @@ amortized O(1) appends.
the buffer to empty. The buffer's allocation is kept for reuse.")
(register to-string (Fn [&StringBuf] String))

(doc into-string "consumes the buffer and returns its contents as an owned
String. The buffer's allocation is handed over to the String, so nothing is
copied and nothing is freed.")
(register into-string (Fn [StringBuf] String))

(doc str "returns a copy of the buffer contents as a String without
modifying the buffer.")
(register str (Fn [&StringBuf] String))
(implements str StringBuf.str)

(doc prn "returns a copy of the buffer contents as a String without
modifying the buffer.")
(defn prn [sb] (StringBuf.str sb))
(implements prn StringBuf.prn)

(doc clear "resets the buffer to empty without freeing memory.")
(register clear (Fn [&StringBuf] ()))

(doc delete "frees the buffer.")
(doc delete "frees the buffer. This implements the `delete` interface, which
means Carp calls it for you when an owned buffer goes out of scope; you should
not need to call it directly.")
(register delete (Fn [StringBuf] ()))
(implements delete StringBuf.delete)

Expand Down
Loading