///|
/// A deliberately fixed numeric table rather than a general CSV dialect.
/// Columns: x,rho,velocity,pressure; CRLF and a single terminal newline accepted.
pub fn Simulation::profile_csv(self : Simulation) -> String raise GasError {
  let lines = ["x,rho,velocity,pressure"]
  for i in 0.. Double raise GasError {
  if text.length() == 0 || text.trim() != text {
    raise Invalid("empty or padded CSV field")
  }
  let n = @string.parse_double(text) catch {
    _ => raise Invalid("invalid numeric CSV field")
  }
  if !finite(n) {
    raise Invalid("CSV fields must be finite")
  }
  n
}

///|
/// Import primitive cell averages on a supplied mesh. Coordinates must match centers.
/// Quoting, comments, extra columns/rows and blank interior rows are rejected.
pub fn Gas::import_csv(
  self : Gas,
  mesh : Mesh,
  text : String,
) -> Array[Conserved] raise GasError {
  if text.length() > 256000000 {
    raise Invalid("CSV exceeds input limit")
  }
  let rows = text.split("\n").collect()
  if rows.length() > 0 && rows[rows.length() - 1] == "" {
    ignore(rows.pop())
  }
  if rows.length() != mesh.cells + 1 {
    raise Invalid("CSV must have exactly one header and one row per cell")
  }
  let header = rows[0].trim_end(chars="\r")
  if header != "x,rho,velocity,pressure" {
    raise Invalid("unsupported CSV header")
  }
  let cells = []
  for i in 0.. tolerance {
      raise Invalid("CSV coordinate does not match mesh center")
    }
    cells.push(
      self.conserved(
        Primitive::new(
          csv_number(fields[1]),
          csv_number(fields[2]),
          csv_number(fields[3]),
        ),
      ),
    )
  }
  self.validate_cells(mesh, cells)
  cells
}