94 lines
2.4 KiB
Plaintext
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.
|
|
defmacro(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
|
|
: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
|