// The Benchmarks Game's `k-nucleotide`: reads DNA in FASTA form from standard // input and reports how often each short fragment occurs in one section. // // cat dna.fasta | nikaia run // A 33.333 // T 30.000 // … // 3 GGT // 3 GGTA // 0 GGTATT // … // // The input is FASTA text: a header line starting with `>`, then lines of DNA. // The section read is the one whose header starts with `THREE` (the // benchmark's choice); `cat dna.fasta | nikaia run -- ONE` picks another. // // Shows: reading standard input, a `grammar` for a line-based format, map // keys that are views into a string rather than copies, and `?.` with `??`. // // A package: `nikaia run` in this directory runs it, and `nikaia test` checks // what it prints against `tests/` (ADR-269 D13). use std::io use std::cli use std::collections // --------------------------------------------------------------------------- // 1. FASTA // --------------------------------------------------------------------------- grammar Fasta { // In FASTA a newline is the structure, not a blank to skip. Defining `WS` // as empty turns off the whitespace skipping of lowercase rules, which // would otherwise eat the line endings this grammar is made of. rule WS = "" -> { } entry rule file -> Vec[Section] = sections:section* -> { sections } // A header, then every line that does not start a new one. `not(">")` // consumes nothing: it only tests, and it is what ends the body. rule section -> Section = ">" header:LINE body:body_line* -> { Section { header, lines: body } } rule body_line -> String = not(">") l:LINE -> { l } // `until` stops before the terminator, so the line ending is consumed // explicitly. Input whose last line has no line ending is refused as not // FASTA rather than losing that line. rule LINE -> String = s:until(line_ending) line_ending -> { s } } pub struct Section { header: String, lines: Vec[String], } // --------------------------------------------------------------------------- // 2. Counting // --------------------------------------------------------------------------- // A count is a struct with a method rather than a bare number: // `entry(..).and_modify` hands the lambda the value already there, and the // lambda calls a method on it. `sync` says a function cannot pause, and the // compiler checks it. pub struct Tally { n: i64, } impl Tally { pub fn(first: i64) -> Tally sync { return Tally { n: first } } fn bump(ref mut self) sync { self.n += 1 } } // Every fragment of length `k`, and how often each occurs. // // The keys are views into `dna`: `ref dna[i.. collections::HashMap[String, Tally] { let mut counts: collections::HashMap[String, Tally] = collections::HashMap() if dna.len() < k { return counts } for i in 0..