94 lines
2.4 KiB
Plaintext

; A CSV reader written as a state machine over bytes: quoted fields, doubled
; quotes inside them, and a count of rows kept in globals.
enum State
start
bare
quoted
quote-in-quoted
once rows-seen: i32
def fields-seen: i32 = 0
const separator = \,
; Frame the body's output with a title line and a closing rule.
macro with-section(title, & body)
quote
println("--", ~title, "--")
~@body
println("-----")
fn flush(field: Ptr(Vec(u8)), row: Ptr(Vec(string))) -> ()
push(deref(row), string(slice(deref(field))))
fields-seen += 1
deref(field) = vec-new(u8)
fn parse-line(line: [const u8]) -> Vec(string)
let row = vec-new(string)
let field = vec-new(u8)
let state = State.start
for i in range(length(line))
let c = line[i]
match state
:start ->
if c == \"
state = :quoted
elif c == separator
flush(addr(field), addr(row))
else
push(field, c)
state = :bare
State.bare ->
if c == separator
flush(addr(field), addr(row))
state = :start
else
push(field, c)
:quoted ->
if c == \" then state = :quote-in-quoted else push(field, c)
:quote-in-quoted ->
if c == \"
push(field, c)
state = :quoted
else
flush(addr(field), addr(row))
state = :start
flush(addr(field), addr(row))
rows-seen += 1
row
fn widest(rows: [Vec(string)]) -> i32
let best = 0
for r in range(length(rows))
for f in range(length(rows[r]))
best = max(best, i32(length(rows[r][f])))
best
fn pad(s: string, width: i32) -> ()
print(s)
let n = width - i32(length(s))
until n <= 0
print(" ")
n -= 1
fn main() -> i32
let text = "name,qty,note\npear,3,\"ripe, soft\"\nfig,12,\"said \"\"hi\"\"\"\n,0,none"
let frame = arena-new(65536)
defer arena-destroy(frame)
with-allocator(frame):
let lines = split(bytes-view(text), \newline)
let rows = vec-new(Vec(string))
for i in range(length(lines))
push(rows, parse-line(lines[i]))
let w = widest(slice(rows)) + 1
with-section("table"):
for r in range(length(rows))
for f in range(length(rows[r]))
let last = f + 1 == length(rows[r])
pad(rows[r][f], if last then 0 else w)
println("")
println("rows", rows-seen, "fields", fields-seen)
comment:
parse-line(bytes-view("a,\"b\",c"))
0