oxedyne/fe2o3/fe2o3_syntax/src/val.rs
7.5 KiB, 1 run
created by r1870400018:62024, which is this file's identity for as long as the history lasts, whatever it is later renamed to
download · who wrote it · its history
| 1 | use oxedyne_fe2o3_core::prelude::*; |
| 2 | use oxedyne_fe2o3_jdat::kind::Kind; |
| 3 | |
| 4 | |
| 5 | /// How many words a value takes. Only the last values of a command may be anything but |
| 6 | /// `One`: a value that must be present cannot follow one that may be absent, and a repeated |
| 7 | /// value is always the last. |
| 8 | #[derive(Clone, Copy, Debug, Default, Eq, PartialEq)] |
| 9 | pub enum Arity { |
| 10 | #[default] |
| 11 | One, // exactly one word |
| 12 | Optional, // zero or one |
| 13 | Many, // zero or more |
| 14 | OneOrMore, |
| 15 | } |
| 16 | |
| 17 | impl Arity { |
| 18 | /// Can the value be left with nothing? |
| 19 | pub fn may_be_empty(&self) -> bool { |
| 20 | match self { |
| 21 | Self::One => false, |
| 22 | Self::Optional => true, |
| 23 | Self::Many => true, |
| 24 | Self::OneOrMore => false, |
| 25 | } |
| 26 | } |
| 27 | |
| 28 | /// Does the value go on taking words after the first? |
| 29 | pub fn repeats(&self) -> bool { |
| 30 | match self { |
| 31 | Self::One => false, |
| 32 | Self::Optional => false, |
| 33 | Self::Many => true, |
| 34 | Self::OneOrMore => true, |
| 35 | } |
| 36 | } |
| 37 | } |
| 38 | |
| 39 | /// A value expected by a message, a command or an argument. |
| 40 | /// |
| 41 | /// A `(Kind, String)` pair, the form every caller used before values had names, converts with |
| 42 | /// `.into()`: the string becomes the help text and the value is unnamed and required. |
| 43 | #[derive(Clone, Debug, Default, PartialEq)] |
| 44 | pub struct Val { |
| 45 | pub kind: Kind, |
| 46 | pub name: String, // shown as <name>; empty shows the kind |
| 47 | pub help: String, |
| 48 | pub arity: Arity, |
| 49 | pub missing: Option<String>, // the caller's own sentence when it is absent |
| 50 | pub verbatim: bool, // a command line word is taken as typed |
| 51 | } |
| 52 | |
| 53 | impl From<(Kind, String)> for Val { |
| 54 | fn from((kind, help): (Kind, String)) -> Self { |
| 55 | Self { |
| 56 | kind, |
| 57 | help, |
| 58 | ..Default::default() |
| 59 | } |
| 60 | } |
| 61 | } |
| 62 | |
| 63 | impl From<(Kind, &str)> for Val { |
| 64 | fn from((kind, help): (Kind, &str)) -> Self { |
| 65 | Self::from((kind, help.to_string())) |
| 66 | } |
| 67 | } |
| 68 | |
| 69 | impl Val { |
| 70 | |
| 71 | pub fn new<S: Into<String>>(kind: Kind, name: S) -> Self { |
| 72 | Self { |
| 73 | kind, |
| 74 | name: name.into(), |
| 75 | ..Default::default() |
| 76 | } |
| 77 | } |
| 78 | |
| 79 | /// A string value taken from a command line word as it stands. |
| 80 | pub fn text<S: Into<String>>(name: S) -> Self { |
| 81 | Self::new(Kind::Str, name) |
| 82 | } |
| 83 | |
| 84 | pub fn help<S: Into<String>>(mut self, s: S) -> Self { |
| 85 | self.help = s.into(); |
| 86 | self |
| 87 | } |
| 88 | |
| 89 | pub fn arity(mut self, arity: Arity) -> Self { |
| 90 | self.arity = arity; |
| 91 | self |
| 92 | } |
| 93 | |
| 94 | pub fn optional(self) -> Self { self.arity(Arity::Optional) } |
| 95 | pub fn many(self) -> Self { self.arity(Arity::Many) } |
| 96 | pub fn one_or_more(self) -> Self { self.arity(Arity::OneOrMore) } |
| 97 | |
| 98 | pub fn missing<S: Into<String>>(mut self, s: S) -> Self { |
| 99 | self.missing = Some(s.into()); |
| 100 | self |
| 101 | } |
| 102 | |
| 103 | /// Takes command line words as they were typed, for a value such as a message or a name |
| 104 | /// that may say anything. A word shaped like an option that is not one of the command's own |
| 105 | /// is this value's rather than refused, though `-h`, `--help` and `--` keep their meaning. |
| 106 | /// A repeating value goes further: once the command's values have begun and it is the one |
| 107 | /// being filled, every word left on the line is its, those three included, so options must |
| 108 | /// come before it. |
| 109 | pub fn verbatim(mut self) -> Self { |
| 110 | self.verbatim = true; |
| 111 | self |
| 112 | } |
| 113 | |
| 114 | /// The value's name as help shows it, without brackets: its own name, or else its kind. |
| 115 | pub fn label(&self) -> String { |
| 116 | if self.name.is_empty() { |
| 117 | fmt!("{}", self.kind) |
| 118 | } else { |
| 119 | self.name.clone() |
| 120 | } |
| 121 | } |
| 122 | |
| 123 | /// The value as a synopsis shows it, e.g. `<mark>`, `[<dir>]`, `<path>...`. |
| 124 | pub fn synopsis(&self) -> String { |
| 125 | let base = fmt!("<{}>", self.label()); |
| 126 | match self.arity { |
| 127 | Arity::One => base, |
| 128 | Arity::Optional => fmt!("[{}]", base), |
| 129 | Arity::Many => fmt!("[{}...]", base), |
| 130 | Arity::OneOrMore => fmt!("{}...", base), |
| 131 | } |
| 132 | } |
| 133 | |
| 134 | /// Checks that a list of values has a shape a parser can read without guessing. |
| 135 | pub fn check_shape(vals: &[Val], owner: &str) -> Outcome<()> { |
| 136 | let mut loose = false; |
| 137 | for (i, val) in vals.iter().enumerate() { |
| 138 | if val.arity.repeats() && i + 1 != vals.len() { |
| 139 | return Err(err!( |
| 140 | "Value {} ('{}') of {} repeats, but only the last value may.", |
| 141 | i + 1, val.label(), owner; |
| 142 | Input, Invalid)); |
| 143 | } |
| 144 | if loose && val.arity == Arity::One { |
| 145 | return Err(err!( |
| 146 | "Value {} ('{}') of {} is required, but follows a value that may be \ |
| 147 | absent, so no reader could tell which one a word fills.", |
| 148 | i + 1, val.label(), owner; |
| 149 | Input, Invalid)); |
| 150 | } |
| 151 | if val.arity != Arity::One { |
| 152 | loose = true; |
| 153 | } |
| 154 | } |
| 155 | Ok(()) |
| 156 | } |
| 157 | |
| 158 | /// The value that the `i`th word fills, if any. |
| 159 | pub fn slot(vals: &[Val], i: usize) -> Option<&Val> { |
| 160 | match vals.get(i) { |
| 161 | Some(val) => Some(val), |
| 162 | None => match vals.last() { |
| 163 | Some(last) if last.arity.repeats() => Some(last), |
| 164 | _ => None, |
| 165 | }, |
| 166 | } |
| 167 | } |
| 168 | |
| 169 | /// Is `n` a number of words that the values can hold? |
| 170 | pub fn count_fits(vals: &[Val], n: usize) -> bool { |
| 171 | let min = vals.iter().filter(|v| !v.arity.may_be_empty()).count(); |
| 172 | let unbounded = match vals.last() { |
| 173 | Some(last) => last.arity.repeats(), |
| 174 | None => false, |
| 175 | }; |
| 176 | n >= min && (unbounded || n <= vals.len()) |
| 177 | } |
| 178 | } |
| 179 | |
| 180 | /// Where a parser has got to in a list of values. |
| 181 | #[derive(Clone, Debug)] |
| 182 | pub struct Slots<'a> { |
| 183 | pub vals: &'a [Val], |
| 184 | pub idx: usize, // the value being filled |
| 185 | pub count: usize, // words already given to it |
| 186 | } |
| 187 | |
| 188 | impl<'a> Slots<'a> { |
| 189 | |
| 190 | pub fn new(vals: &'a [Val]) -> Self { |
| 191 | Self { |
| 192 | vals, |
| 193 | idx: 0, |
| 194 | count: 0, |
| 195 | } |
| 196 | } |
| 197 | |
| 198 | pub fn current(&self) -> Option<&'a Val> { self.vals.get(self.idx) } |
| 199 | |
| 200 | /// Has any value taken a word yet? |
| 201 | pub fn begun(&self) -> bool { self.idx > 0 || self.count > 0 } |
| 202 | |
| 203 | /// Records that the current value took a word. |
| 204 | pub fn accept(&mut self) { |
| 205 | if let Some(val) = self.current() { |
| 206 | if val.arity.repeats() { |
| 207 | self.count += 1; |
| 208 | } else { |
| 209 | self.idx += 1; |
| 210 | self.count = 0; |
| 211 | } |
| 212 | } |
| 213 | } |
| 214 | |
| 215 | /// Could the values stop here and be complete? |
| 216 | pub fn satisfied(&self) -> bool { |
| 217 | for (i, val) in self.vals.iter().enumerate().skip(self.idx) { |
| 218 | let given = if i == self.idx { self.count } else { 0 }; |
| 219 | if !val.arity.may_be_empty() && given == 0 { |
| 220 | return false; |
| 221 | } |
| 222 | } |
| 223 | true |
| 224 | } |
| 225 | |
| 226 | /// The first value still wanting a word, if the values cannot stop here. |
| 227 | pub fn wanting(&self) -> Option<&'a Val> { |
| 228 | for (i, val) in self.vals.iter().enumerate().skip(self.idx) { |
| 229 | let given = if i == self.idx { self.count } else { 0 }; |
| 230 | if !val.arity.may_be_empty() && given == 0 { |
| 231 | return Some(val); |
| 232 | } |
| 233 | } |
| 234 | None |
| 235 | } |
| 236 | |
| 237 | /// The kinds of the values not yet filled, the current one included. |
| 238 | pub fn outstanding(&self) -> Vec<Kind> { |
| 239 | self.vals.iter().skip(self.idx).map(|v| v.kind.clone()).collect() |
| 240 | } |
| 241 | } |