~/bend-docscommunity

lib.bend source

lib.bend on the hub · documented module

# Csv — small, Base-only comma-separated values.## Rows are List<&2, String>; a document is List<&2, List<&2, String>>.# The parser is deliberately a small line-oriented CSV subset. See README.import Base# A field is quoted when it contains syntax which would otherwise be data.def Csv.is_quote(+c: Char) -> Bool:  match c:    case Chr{+x}:      U32.is_eq(x, 34)def Csv.is_comma(+c: Char) -> Bool:  match c:    case Chr{+x}:      U32.is_eq(x, 44)# Finish a field. fields is stored backwards while scanning.def Csv.parse_line.finish(  +quoted: Bool, +field: String, +fields: List<&2, String>) -> List<&2, String>:  List.reverse(&2, String, String.reverse(field) <> fields)# The scanner has an explicit state so doubled quotes need no mutual# recursion. AfterQuote re-processes the next non-quote character as plain.type Csv.Scan is Data:  CPlain{}  CQuoted{}  CAfterQuote{}def Csv.parse_line.go(  s: String, state: Csv.Scan, +field: String, +fields: List<&2, String>) -> List<&2, String>:  match s state:    case SNil{} _:      Csv.parse_line.finish(False{}, field, fields)    case SCon{+h, +t} CPlain{}:      Bool.pick(        List<&2, String>,        Csv.is_quote(h),        Bool.pick(          List<&2, String>,          String.is_empty(field),          Csv.parse_line.go(t, CQuoted{}, field, fields),          Csv.parse_line.go(t, CPlain{}, SCon{h, field}, fields)        ),        Bool.pick(          List<&2, String>,          Csv.is_comma(h),          Csv.parse_line.go(t, CPlain{}, SNil{}, String.reverse(field) <> fields),          Csv.parse_line.go(t, CPlain{}, SCon{h, field}, fields)        )      )    case SCon{+h, +t} CQuoted{}:      Bool.pick(        List<&2, String>,        Csv.is_quote(h),        Csv.parse_line.go(t, CAfterQuote{}, field, fields),        Csv.parse_line.go(t, CQuoted{}, SCon{h, field}, fields)      )    case SCon{+h, +t} CAfterQuote{}:      Bool.pick(        List<&2, String>,        Csv.is_quote(h),        Csv.parse_line.go(t, CQuoted{}, SCon{h, field}, fields),        Csv.parse_line.go(t, CPlain{}, SCon{h, field}, fields)      )# Parse one physical line. Empty lines are one empty field.def Csv.parse_line(s: String) -> List<&2, String>:  Csv.parse_line.go(s, CPlain{}, SNil{}, Nil{})def Csv.parse.go(  lines: List<&2, String>) -> List<&2, List<&2, String>>:  match lines:    case Nil{}:      Nil{}    case h <> t:      Csv.parse_line(h) <> Csv.parse.go(t)# Empty input is no document; a non-empty input is split on LF. CR is not# stripped, so callers wanting CRLF should trim it before parsing.def Csv.parse(s: String) -> List<&2, List<&2, String>>:  match s:    case SNil{}:      Nil{}    case SCon{h, t}:      Csv.parse.go(String.lines(SCon{h, t}))# Double quotes in a quoted field are represented by two quotes.def Csv.encode_field.escaped(s: String) -> String:  match s:    case SNil{}:      SNil{}    case SCon{+h, +t}:      Bool.pick(        String,        Csv.is_quote(h),        String.append("\"\"", Csv.encode_field.escaped(t)),        String.append(SCon{h, SNil{}}, Csv.encode_field.escaped(t))      )def Csv.encode_field(+s: String) -> String:  Bool.pick(    String,    Bool.or(String.contains(s, ","), String.contains(s, "\"")),    String.append("\"", String.append(Csv.encode_field.escaped(s), "\"")),    s  )def Csv.encode_row.go(  cells: List<&2, String>, first: Bool) -> String:  match cells:    case Nil{}:      SNil{}    case +h <> +t:      Bool.pick(        String,        first,        String.append(Csv.encode_field(h), Csv.encode_row.go(t, False{})),        String.append(",", String.append(Csv.encode_field(h), Csv.encode_row.go(t, False{})))      )def Csv.encode_row(cells: List<&2, String>) -> String:  Csv.encode_row.go(cells, True{})def Csv.encode.go(  rows: List<&2, List<&2, String>>, first: Bool) -> String:  match rows:    case Nil{}:      SNil{}    case +h <> +t:      Bool.pick(        String,        first,        String.append(Csv.encode_row(h), Csv.encode.go(t, False{})),        String.append("\n", String.append(Csv.encode_row(h), Csv.encode.go(t, False{})))      )def Csv.encode(rows: List<&2, List<&2, String>>) -> String:  Csv.encode.go(rows, True{})