7 unstable releases (3 breaking)

0.6.1 Feb 13, 2024
0.6.0 Jan 15, 2024
0.5.1 Dec 29, 2023
0.4.1 Nov 8, 2023
0.3.0 Sep 10, 2023

#327 in Parser implementations

MIT license

10K SLoC

crates.io github workflow dependency status codecov


csv++ is a superset of CSV which allows you to author spreadsheets in a text file then compile that to your target spreadsheet (Excel, Google Sheets or even back to CSV).

Since csv++ is a superset of CSV any CSV document is valid:

Sum,Column 1,Column 2,

However you can extract reusable variables and functions by making a code section at the top, separated from the cells with a ---. Here's the same spreadsheet but extracted out into variables and functions:

# you can define variables with `:=`
multiplier := 5

# functions look like this and have a single expression as their body
fn my_fn(a, b)
  multiplier * SUM(a, b)

Sum                 , Column 1, Column 2,
"=my_fn(B2, C2)"    ,         ,         ,

One more useful feature is the ability to bind variables to a cell. You can use the [[/]] syntax on the cell you want to bind it on.

fn my_complex_fn(a, b)
  a * a + SQRT(b)

Complex function      , Column 1  , Column 2 ,
"=my_complex_fn(a, b)", [[var=a]] , [[var=b]],


Another useful feature is to define a range of rows which fill out (either infinitely or by a finite amount) in the compiled spreadsheet. To specify one you use the row-option syntax which is similar to above, you just prefix it with !: ![[/]].

Product Name  , Quantity          , Price per Unit  , =SUM(D2:D12)
![[fill=10]]  , [[var=quantity]]  , [[var=price]]   , =quantity * price

This will take the second row and repeat it 10 times in the final spreadsheet. If you wanted it to be repeated until the end of the spreadsheet just leave off the =10 and specify it as ![[fill]].

Variable Scoping

The variable scoping semantics are pretty unique because every function call is evaluated relative to the cell where it is used. As you've seen above you can use [[var=...]] to bind a variable name to a given cell. As an example of scoping semantics we'll use this csv++ template:

foo_from_code_section := 42
[[var=bar_outside_fill]] ,                         ,                 ,                     ,                       ,
![[fill=2]]bar           , [[var=bar_in_fill]]     , =bar_in_fill    , =bar_outside_fill   , =foo_from_code_section,

which will compile to:

     ,     ,     ,     ,
bar  ,     , =B2 , =A1 , =42
bar  ,     , =B3 , =A1 , =42

Breaking this down:

  • foo_from_code_section - Is always 42 no matter where it is used.
  • bar_in_fill - Since it is defined within an ![[fill]], it's value depends on the final row, which will be B2 or B3
  • bar_outside_fill - Will always be A1, pointing to the cell where it was defined. There is no relative aspect to it since it's not defined in an fill.

Importing Code

csv++ allows you to import and re-use functions and variables with the use statement:


pi := 3.14159
e := 2.7182818284


use my_math_constants

fn circumference_from_diameter(r)
    r * 2 * pi

fn radius_to_diameter(r)
    2 * r

fn circumference_from_radius(r)

Radius          ,Circumference                           ,
[[var=radius]]  ,"=circumference_from_radius(radius)"    ,


You can apply basic cell formatting which will either apply for the entire row or just for individual cells. To apply formatting to individual cells use the [[/]] syntax:


and here is the same thing using short-hand:


To format the entire row you can use ![[/]] at the beginning of the line


For a full list of formatting features, take a look at the language reference

Additional Reading


~1M SLoC