1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
//! **What a verb IS**: the row, the usage line it computes, the flags its own
//! word can raise, and the one envelope it becomes.
//!
//! Split from [`super`] at the 300-line wall on the seam that module's own doc
//! draws: [`super`] is the SURFACE — which words exist, which module each row
//! lives in, and what the whole table is for — and this is the one type every
//! row is an instance of. The first moves whenever a gesture is added; the
//! second only when the shape of a gesture changes, which has happened twice.
use serde_json::{Map, Value};
use super::rows::TABLE;
/// One verb: the word, what it takes, and what it is for.
///
/// **No usage string is stored.** [`Verb::usage`] computes it from the word and
/// the parameters, so a parameter added to a row cannot leave a usage line
/// behind saying otherwise.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Verb {
/// The word typed, which is also the envelope's `op`. One fact.
pub word: &'static str,
/// The envelope field each argument fills, in the order they are typed.
/// These are the **wire's** own field names, so what an operator reads in
/// the usage is what REMOTE calls it.
pub params: &'static [&'static str],
/// **The boolean fields this word can raise, each spelled as itself.**
///
/// A gesture parameter that is a `bool` cannot be one of [`params`] — the
/// table's rule is *a word and its parameters, all of them named strings*
/// — and until bl-9fd1 that meant such a gesture had no typed spelling at
/// all: `stop`'s `children`, the one control an operator reaches for when
/// something is wrong, was reachable only by hand-writing the envelope.
/// This is the reframe [`super`]'s own doc says to look for rather than a
/// special case: a flag is typed AFTER the parameters, as its own field
/// name, and raising it writes `true` under that name. Absent is absent —
/// never `false`, which would be this seat asserting into a field nobody
/// touched.
///
/// [`params`]: Self::params
pub flags: &'static [&'static str],
/// One line: what the verb is for.
pub summary: &'static str,
/// The page: what it answers with, and what to know before typing it.
pub detail: &'static str,
}
/// Every verb, in roster order.
pub fn table() -> Vec<Verb> {
TABLE.to_vec()
}
/// The verb that word names, if it is one.
pub fn find(word: &str) -> Option<Verb> {
TABLE.iter().find(|verb| verb.word == word).copied()
}
impl Verb {
/// **Whether this verb addresses one workspace**, read off its own
/// parameters rather than listed a second time.
///
/// It is the predicate two surfaces need and it has one home. A verb with
/// no `workspace` parameter has no way to name one, so its subject is
/// *every* channel this box holds — which is why `lernie workspaces` fans
/// (bl-0d54) where every other word goes down one channel. And a
/// `workspace` field written onto such a gesture by hand is therefore a
/// pure channel selector, with no reader at the far end, so a name no entry
/// holds refuses at the seat instead of answering `ok` from a channel
/// nobody named (bl-d574, [`crate::seat::route`]).
pub fn addresses_a_workspace(&self) -> bool {
self.params.contains(&crate::envelope::WORKSPACE)
}
/// The line an operator types, computed rather than stored — the
/// parameters in order, then each flag in brackets because each is
/// optional.
pub fn usage(&self) -> String {
std::iter::once(format!("lernie {}", self.word))
.chain(self.params.iter().map(|p| format!("<{p}>")))
.chain(self.flags.iter().map(|f| format!("[{f}]")))
.collect::<Vec<String>>()
.join(" ")
}
/// **The arity refusal**, over the range the flags open. It names the
/// range and not just its floor, for [`crate::verbs::doors::Door`]'s own
/// reason: a sentence about one end of a range says nothing about the
/// other.
fn arity(&self, got: usize) -> String {
let least = self.params.len();
let most = least + self.flags.len();
let takes = if least == most {
format!("{least}")
} else {
format!("{least} to {most}")
};
format!(
"`lernie {}` takes {takes} argument(s) and got {got} — usage: {}",
self.word,
self.usage()
)
}
/// **The envelope this verb becomes** — the one serialization, built from
/// the row rather than by an arm of its own.
///
/// Arity is exact and refuses by name (see the module doc on why the tail
/// is one argument here). The refusal carries this verb's own usage, so an
/// operator learns the grammar from the mistake rather than from the source.
pub fn envelope(&self, args: Vec<String>) -> Result<Value, String> {
let least = self.params.len();
if args.len() < least || args.len() > least + self.flags.len() {
return Err(self.arity(args.len()));
}
// **A word past the parameters is a flag or it is a mistake**, and the
// mistake earns the flag's own name rather than the arity sentence: a
// tail of the right LENGTH and the wrong word is a different error
// from a tail of the wrong length, and only one of them is answered by
// counting.
let mut raised: Vec<&str> = Vec::new();
for word in args.iter().skip(least) {
let Some(flag) = self.flags.iter().find(|known| *known == word) else {
return Err(self.stray(word));
};
raised.push(flag);
}
Ok(self.built(args.into_iter().take(least).collect(), &raised))
}
/// **The refusal a tail that is none of this verb's flags earns**, which
/// names the word it takes rather than counting: a tail of the right
/// LENGTH and the wrong word is a different mistake from a tail of the
/// wrong length, and only one of them is answered by an arity sentence.
fn stray(&self, word: &str) -> String {
format!(
"`lernie {}` has no word {word:?} — it takes {} after its arguments; usage: {}",
self.word,
self.flags
.iter()
.map(|flag| format!("{flag:?}"))
.collect::<Vec<String>>()
.join(" or "),
self.usage()
)
}
/// The envelope proper, with the arity already settled. **The one
/// builder**: [`envelope`](Self::envelope) is the checked door for argv,
/// [`message`] and [`nudge`] are the typed doors for the window, and both
/// arrive here — so a gesture has one spelling however it was composed.
pub(super) fn built(&self, args: Vec<String>, raised: &[&str]) -> Value {
Value::Object(self.fields(args, raised))
}
/// **The same envelope with one optional string field stated**, for the
/// one gesture whose request carries one: `enroll`'s `address`, the route
/// the DEVICE will dial (REMOTE §8.4 as amended, yog bl-fec6).
///
/// It is a door beside [`built`](Self::built) rather than a fourth kind of
/// row, because an optional string is not a parameter — the table's rule
/// is *a word and its parameters, all of them named strings*, and a
/// parameter is required by being one. **Absent is absent**: a field
/// nobody stated is not written, never written as `null`, which would be a
/// second spelling of the same absence the far end would then have to
/// read (yog's codec says so from the other side).
pub(super) fn stating(&self, args: Vec<String>, field: &str, value: Option<String>) -> Value {
let mut map = self.fields(args, &[]);
if let Some(stated) = value {
map.insert(field.to_owned(), Value::String(stated));
}
Value::Object(map)
}
/// The fields themselves — the op, the parameters in order, and each flag
/// raised. Both doors above are this map, wrapped.
fn fields(&self, args: Vec<String>, raised: &[&str]) -> Map<String, Value> {
let mut map = Map::new();
map.insert(
crate::envelope::OP.to_owned(),
Value::String(self.word.to_owned()),
);
for (key, value) in self.params.iter().zip(args) {
map.insert((*key).to_owned(), Value::String(value));
}
for flag in raised {
map.insert((*flag).to_owned(), Value::Bool(true));
}
map
}
}