Oregami
Repositories/oxedyne/fe2o3

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

1use oxedyne_fe2o3_core::prelude::*;
2use 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)]
9pub enum Arity {
10 #[default]
11 One, // exactly one word
12 Optional, // zero or one
13 Many, // zero or more
14 OneOrMore,
15}
16
17impl 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)]
44pub 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
53impl 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
63impl From<(Kind, &str)> for Val {
64 fn from((kind, help): (Kind, &str)) -> Self {
65 Self::from((kind, help.to_string()))
66 }
67}
68
69impl 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)]
182pub 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
188impl<'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}