jm object NAME --preset reader — reader (external source → output)¶
A reader opens an external source — file, socket, pipe, mmap'd region — and yields data on demand. Unlike a generator (which produces from internal state), a reader has a side input it must acquire, position within, and release.
Concrete examples: a binary file reader for a custom format, a CSV row reader, a WAV / PNG / Parquet loader, a TCP socket consumer, a mmap'd shared-memory channel reader, or any source where the data lives outside the process.
--preset reader bundles --no-step (the standard step() interface
doesn't fit I/O) with a filepath:const char * init-param. The scaffold
builds and tests green. Add the read() / seek() / close() methods
yourself with jm method; this preset formalises the asymmetry
init-params solve — init_params for the user-facing ctor (filepath),
state for internal bookkeeping (fd, position).
Command¶
jm object NAME --preset reader \
--init-param header_bytes:size_t:0 \
--state fd:int:-1 \
--state file_size:size_t:0 \
--state position:size_t:0
The filepath:const char * init-param comes from the preset; the rest
are yours. Then add the I/O verbs:
jm method NAME read --param n:size_t --out-type "float _Complex"
jm method NAME seek --param sample_index:size_t --return-type int
jm method NAME close
What you get¶
native/inc/NAME/NAME_core.h¶
typedef struct {
int fd;
size_t file_size;
size_t position;
} NAME_state_t;
/* Ctor takes init_params; state stays internal. */
NAME_state_t *NAME_create(const char *filepath, size_t header_bytes);
void NAME_destroy(NAME_state_t *state);
void NAME_reset(NAME_state_t *state);
size_t NAME_read(NAME_state_t *state, float complex *out, size_t n);
int NAME_seek(NAME_state_t *state, size_t sample_index);
void NAME_close(NAME_state_t *state);
native/src/NAME/NAME_core.c¶
NAME_state_t *
NAME_create(const char *filepath, size_t header_bytes)
{
NAME_state_t *obj = calloc(1, sizeof(*obj));
if (!obj) return NULL;
obj->fd = open(filepath, O_RDONLY);
if (obj->fd < 0) { free(obj); return NULL; }
struct stat st;
if (fstat(obj->fd, &st) < 0) { close(obj->fd); free(obj); return NULL; }
obj->file_size = (size_t)st.st_size;
obj->position = header_bytes;
return obj;
}
void
NAME_destroy(NAME_state_t *state)
{
if (!state) return;
if (state->fd >= 0) close(state->fd);
free(state);
}
size_t
NAME_read(NAME_state_t *state, float complex *out, size_t n)
{
/* TODO: read up to n complex samples from state->fd into out[].
Return the number actually read. The default body does a raw
read() of n * sizeof(float complex) bytes. */
ssize_t bytes = read(state->fd, out, n * sizeof(*out));
if (bytes <= 0) return 0;
state->position += (size_t)bytes;
return (size_t)bytes / sizeof(*out);
}
int
NAME_seek(NAME_state_t *state, size_t sample_index)
{
/* TODO: translate sample_index to byte offset and lseek. */
off_t off = (off_t)(sample_index * sizeof(float complex));
if (lseek(state->fd, off, SEEK_SET) == (off_t)-1) return -1;
state->position = (size_t)off;
return 0;
}
void
NAME_close(NAME_state_t *state)
{
if (state->fd >= 0) { close(state->fd); state->fd = -1; }
}
What you fill in¶
Replace the raw-read default with your format. Common shapes:
- Wire-format demultiplexing (separate I and Q from interleaved bytes).
- Header parsing (use
header_bytesto skip a file header). - Type conversion (read int16 from disk, return float complex).
- Endianness swap on read.
Python usage¶
from <pkg> import NAME
rdr = NAME(filepath="capture.iq", header_bytes=0)
chunk = rdr.read(4096) # → (4096,) complex64
rdr.seek(0)
rdr.close()
Concrete types¶
| Slot | Accepts | Rejects | Default |
|---|---|---|---|
--init-param name:T:D |
Path/filename strings use const char *. Any scalar, T[], T[][], string_enum:a,b,c. |
T[N] (fixed length — that's --state territory). |
filepath:"const char *", header_bytes:size_t:0 |
--state field:T:D |
Any scalar. The file descriptor pattern is fd:int:-1. |
const char * (use an --init-param to receive the path, then store the parsed fd / size). |
fd:int:-1, file_size:size_t:0, position:size_t:0 |
Method return / output (out_type = "T") |
Any array element type. read() returns a T[] ndarray sized from the requested sample count. |
bool, int, const char *, long double _Complex. |
float _Complex |
const char * is the load-bearing type here. It is a valid init-param
(PyArg parses the Python str; lifetime is managed on the Python side)
but is not a valid state field — persist the parsed result (the
fd, a size_t) instead. This is the asymmetry the reader preset
exists to formalise.