1use std::{
2 fmt::{Display, Formatter},
3 str::FromStr,
4};
5
6use itertools::Itertools;
7use serde::{Deserialize, Serialize};
8
9use strum::{Display, EnumIter, EnumString, IntoEnumIterator};
10
11use crate::vessel::Article;
12
13#[derive(
15 Clone,
16 Copy,
17 Debug,
18 PartialEq,
19 Eq,
20 PartialOrd,
21 Ord,
22 Serialize,
23 Deserialize,
24 EnumIter,
25 EnumString,
26 Display,
27)]
28#[strum(ascii_case_insensitive)]
29pub enum Verb {
30 Learn,
32 Create,
34 Become,
36 Enter,
38 Leave,
40 Take,
42 Drop,
44 Move,
46 Look,
48 Transform,
50 Note,
52 Warp,
54 Program,
56 Use,
58 Cast,
60 Exit,
62 Save,
64 Load,
66 Debug,
68}
69impl Verb {
70 pub fn short_help_text(&self) -> String {
72 match self {
73 Verb::Learn => r#"Learn [about] [action]: Display general information on Parade, or information on a specific action or topic."#,
74 Verb::Create => r#"Create <vessel>: Create a new vessel adjacent to yours."#,
75 Verb::Become => r#"Become <vessel>: Take control of an adjacent vessel."#,
76 Verb::Enter => r#"Enter <vessel>: Move your vessel into an adjacent vessel."#,
77 Verb::Leave => r#"Leave: Move your vessel out of the vessel it is in (unless it is inside itself)."#,
78 Verb::Take => r#"Take <vessel>: Move an adjacent vessel inside yours."#,
79 Verb::Drop => r#"Drop <vessel>: Move a vessel inside yours outside, so it is adjacent to yours."#,
80 Verb::Move => r#"Move <vessel> <to/into> <vessel>: Move an adjacent vessel into another adjacent vessel."#,
81 Verb::Look => r#"Look [[at/in] <vessel>]: See a list of the adjacent vessels, or of the vessels inside an adjacent vessel."#,
82 Verb::Transform => r#"Transform [to/into] <vessel>: Change your vessel's name."#,
83 Verb::Note => r#"Note [text]: Assign a description to the vessel you are inside."#,
84 Verb::Warp => r#"Warp [to/into] <vessel>: Move your vessel into another vessel, anywhere."#,
85 Verb::Program => r#"Program [vessel]: Begin programming a task for a vessel, which can be activated with Use or Cast."#,
86 Verb::Use => r#"Use <vessel>: Execute an adjacent vessel's program."#,
87 Verb::Cast => r#"Cast <vessel> <at/on> <vessel>: Executes a distant vessel's program as another vessel."#,
88 Verb::Exit => r#"Exit: Exit parade, or stop programming a vessel if you're doing so."#,
89 Verb::Save => r#"Save [filename]: Save the state of all vessels to a file."#,
90 Verb::Load => r#"Load [filename]: Load the state of all vessels from a file."#,
91 Verb::Debug => r#"Debug: Display the state of all vessels as raw json data."#,
92 }.to_string()
93 }
94 pub fn help_text(&self) -> String {
96 match self {
97 Verb::Learn => r#"Learn [about] [Action]: Learn about Parade.
98
99If used on its own, explains what Parade is, how to use it, and lists the available actions.
100If used with an action, displays more in-depth information on that action.
101
102Action descriptions begin with their parameters.
103Parameters in square brackets (e.g. "[Action]") are optional, whereas those in angle brackets ("<noun>") are required."#,
104 Verb::Create => r#"Create [article] <noun>: Create a new vessel adjacent to yours.
105
106"Adjacent", here, means that it is inside the same parent vessel.
107This action will not work if there is already a vessel in your world with the same name as the vessel you are trying to create. This applies regardless of where in your world the existing vessel is.
108
109Vessel names can contain any number of words: You can create a vessel named "pink pony", and it will be distinct from a vessel named just "pony".
110
111If an article is provided, parade will refer to the vessel using that article: e.g. "the library", "a ghost", etc.
112If no article is provided, parade will refer to the vessel without an article: e.g. "Parade", "Jack the Ripper", etc."#,
113 Verb::Become => r#"Become [article] <noun>: Take control of an adjacent vessel.
114
115"Adjacent", here, means that it is inside the same parent vessel.
116This action will not work if the selected vessel does not exist, or is not adjacent to yours.
117
118This does not change your vessel or the selected vessel in any way, aside from changing which one you are controlling."#,
119 Verb::Enter => r#"Enter [article] <noun>: Move your vessel into an adjacent vessel.
120
121"Adjacent", here, means that it is inside the same parent vessel as yours.
122This action will not work if the selected vessel does not exist, or is not adjacent to yours."#,
123 Verb::Leave => r#"Leave: Move your vessel out of the vessel it is in.
124
125If your vessel is inside itself, this will do nothing."#,
126 Verb::Take => r#"Take [article] <noun>: Move an adjacent vessel inside yours.
127
128"Adjacent", here, means that it is inside the same parent vessel.
129This action will not work if the selected vessel does not exist, or is not adjacent to yours."#,
130 Verb::Drop => r#"Drop [article] <noun>: Move a vessel inside yours outside, so it is adjacent to yours.
131
132"Adjacent", here, means that it is inside the same parent vessel.
133This action will not work if the selected vessel does not exist, or is not inside yours."#,
134 Verb::Move => r#"Move [article] <noun> <preposition> [article] <noun>: Move an adjacent vessel into another adjacent vessel.
135
136"Adjacent", here, means that it is inside the same parent vessel.
137This action will not work if either of the selected vessels do not exist, or are not adjacent to yours.
138
139Because vessel names can contain any number of words, you must separate the two vessel names a preposition.
140The prepositions available here are "to" and "into"."#,
141 Verb::Look => r#"Look [[preposition] [article] <noun>]: See a list of the vessels adjacent to yours or inside the selected adjacent vessel.
142
143"Adjacent", here, means that it is inside the same parent vessel.
144This action will not work if the selected vessel does not exist, or is not adjacent to yours.
145
146If used on its own, this will display a list of the adjacent vessels.
147If used with a noun, this will display a list of the vessels inside it.
148
149The prepositions available here are "at" and "in"."#,
150 Verb::Transform => r#"Transform [preposition] [article] <noun>: Change your vessel's name to the provided name.
151
152This action will not work if there is already a vessel in your world with the same name as the one you are trying to take. This applies regardless of where in your world the existing vessel is.
153
154The prepositions available here are "into" and "to"."#,
155 Verb::Note => r#"Note [text]: Assign a description to the vessel you are inside.
156
157The description will be displayed to anyone inside the vessel, or who Looks at it."#,
158 Verb::Warp => r#"Warp [preposition] [article] <noun>: Move your vessel into another vessel, anywhere.
159
160This can be used to move your vessel inside itself.
161
162This action will not work if the selected vessel does not exist.
163
164The prepositions available here are "into" and "to"."#,
165 Verb::Program => r#"Program [article] [noun]: Begin programming a task for a vessel, which can be activated with Use.
166
167If a vessel is provided, it must be adjacent. If no vessel is provided, the parent vessel will be selected.
168
169"Adjacent", here, means that it is inside the same parent vessel.
170This action will not work if the selected vessel does not exist, or is not adjacent to yours.
171
172While programming a vessel, all actions will be added to its "program", instead of being executed normally.
173The only exception to this is Exit, which will cause you to stop programming the vessel - Unless you're currently programming the vessel to program another vessel. You can nest programs as much as you like.
174
175Nouns mentioned in actions you add to a program are evaluated when you run the program, instead of when you add them to it.
176This means that you can reference vessels which do not currently exist or which are not yet accessible."#,
177 Verb::Use => r#"Use [article] <noun>: Execute an adjacent vessel's program.
178
179"Adjacent", here, means that it is inside the same parent vessel.
180This action will not work if the selected vessel does not exist, if it is not adjacent to yours, or it if does not have a program.
181
182No actions used by the program will display information to you, and the Save and Load actions will do nothing.
183When the program executes, it executes with control of the program's vessel, not yours. It can still, however, Become other vessels."#,
184 Verb::Cast => r#"Cast [article] <noun> <preposition> [article] <noun>: Execute a vessel's program as an adjacent vessel.
185
186"Adjacent", here, means that it is inside the same parent vessel.
187This action will not work if either of the selected vessels do not exist, if the first vessel does not have a program, or if the second vessel is not adjacent to yours.
188
189The first named vessel is the vessel whose program will be cast, and the second named vessel is the vessel which will have the program cast on/as it.
190
191This action otherwise functions as Use.
192
193The prepositions available here are "at" and "on"."#,
194 Verb::Exit => r#"Exit: Exit parade, or stop programming a vessel if you're doing so.
195
196Exits parade if you are outside of the Program interface.
197If you are inside the Program interface, exits it.
198If you are programming a vessel to program another vessel, this will add "Exit" to the former vessel's program.
199Parade-rs supports unlimited nesting of programs."#,
200 Verb::Save => r#"Save [text]: Save the state of all vessels to a file.
201
202If a name is provided, this will save to the file with that name.
203Otherwise, the name "parade_save.json" will be used."#,
204 Verb::Load => r#"Load [text]: Load the state of all vessels from a file.
205
206If a name is provided, this will save to the file with that name.
207Otherwise, the name "parade_save.json" will be used."#,
208 Verb::Debug => r#"Debug: Display the state of all vessels as raw json data."#,
209 }.to_string()
210 }
211 pub fn help_list_text() -> String {
213 Verb::iter().fold(r#"This is Parade (or, specifically, parade-rs).
214
215Parade is an experimental interactive-fiction playground, or maybe a filesystem or something.
216Parade-rs is based on paradise (https://wiki.xxiivv.com/site/paradise.html) and parade (https://wiki.xxiivv.com/site/parade.html).
217
218You interact with parade using the following actions:
219"#.to_string(), |acc, topic| {
220 format!("{acc}- {}\n", topic.short_help_text())
221 })
222 }
223}
224
225#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
227pub enum Action {
228 Create(VesselRequest),
230 Become(VesselRequest),
232 Enter(VesselRequest),
234 Leave,
236 Take(VesselRequest),
238 Drop(VesselRequest),
240 Move {
242 object_article: Option<Article>,
244 object_name: String,
246 preposition: IntoPrep,
248 target_article: Option<Article>,
250 target_name: String,
252 },
253 Look {
255 preposition: Option<AtPrep>,
257 basic: Option<VesselRequest>,
259 },
260 Transform {
262 preposition: Option<IntoPrep>,
264 basic: VesselRequest,
266 },
267 Note {
269 text: Option<String>,
271 },
272 Warp {
274 preposition: Option<IntoPrep>,
276 basic: VesselRequest,
278 },
279 Program(Option<VesselRequest>),
281 Use(VesselRequest),
283 Cast {
285 spell: VesselRequest,
287 preposition: OnPrep,
289 target: VesselRequest,
291 },
292 Save {
294 filename: Option<String>,
296 },
297 Load {
299 filename: Option<String>,
301 },
302 Debug,
304 Exit,
306 Learn {
308 preposition: Option<AboutPrep>,
310 topic: Option<Verb>,
312 },
313}
314impl Action {
315 pub fn from_vessel_request(verb: Verb, basic: VesselRequest) -> Result<Self, Error> {
329 match verb {
330 Verb::Create => Ok(Action::Create(basic)),
331 Verb::Become => Ok(Action::Become(basic)),
332 Verb::Enter => Ok(Action::Enter(basic)),
333 Verb::Take => Ok(Action::Take(basic)),
334 Verb::Drop => Ok(Action::Drop(basic)),
335 Verb::Program => Ok(Action::Program(Some(basic))),
336 Verb::Use => Ok(Action::Use(basic)),
337 Verb::Learn
338 | Verb::Leave
339 | Verb::Move
340 | Verb::Look
341 | Verb::Transform
342 | Verb::Note
343 | Verb::Warp
344 | Verb::Cast
345 | Verb::Exit
346 | Verb::Save
347 | Verb::Load
348 | Verb::Debug => Err(Error::NonBasicAction(verb)),
349 }
350 }
351
352 pub fn parse(action_string: &str) -> Result<Option<Action>, Error> {
358 let mut words = action_string.split_whitespace();
359 match words.next() {
360 Some(first_word) => match Verb::from_str(first_word) {
361 Ok(verb) => match verb {
362 Verb::Learn => match words.next() {
363 Some(second_word) => {
364 let preposition = AboutPrep::from_str(second_word);
365 let topic = if preposition.is_ok() {
366 match words.next() {
367 Some(word) => Verb::from_str(word),
368 None => {
369 return Ok(Some(Action::Learn {
370 preposition: preposition.ok(),
371 topic: None,
372 }));
373 }
374 }
375 } else {
376 Verb::from_str(second_word)
377 };
378 match topic {
379 Ok(help_topic) => Ok(Some(Action::Learn {
380 preposition: preposition.ok(),
381 topic: Some(help_topic),
382 })),
383 Err(_) => Err(Error::UnknownVerb(second_word.to_string())),
384 }
385 }
386 None => Ok(Some(Action::Learn {
387 preposition: None,
388 topic: None,
389 })),
390 },
391 Verb::Create
392 | Verb::Become
393 | Verb::Enter
394 | Verb::Take
395 | Verb::Drop
396 | Verb::Use => Ok(Some(Action::from_vessel_request(
397 verb,
398 VesselRequest::parse(words.next().ok_or(Error::MissingNoun)?, &mut words)?,
399 )?)),
400 Verb::Leave => Ok(Some(Action::Leave)),
401 Verb::Move => match words.next() {
402 Some(second_word) => {
403 let VesselRequest {
404 article: object_article,
405 name: object_name,
406 } = VesselRequest::parse(
407 second_word,
408 &mut words.take_while_ref(|word| IntoPrep::from_str(word).is_err()),
409 )?;
410
411 let preposition = words
412 .next()
413 .and_then(|prep_word| IntoPrep::from_str(prep_word).ok())
414 .ok_or(Error::MissingSecondNoun)?;
415
416 let VesselRequest {
417 article: target_article,
418 name: target_name,
419 } = VesselRequest::parse(
420 words.next().ok_or(Error::MissingSecondNoun)?,
421 &mut words,
422 )?;
423
424 Ok(Some(Action::Move {
425 object_article,
426 object_name,
427 preposition,
428 target_article,
429 target_name,
430 }))
431 }
432 None => Err(Error::MissingNoun),
433 },
434 Verb::Look => match words.next() {
435 Some(second_word) => {
436 let (preposition, basic) = VesselRequest::parse_with_prep::<AtPrep, _>(
437 second_word,
438 &mut words,
439 )?;
440 Ok(Some(Action::Look {
441 preposition,
442 basic: Some(basic),
443 }))
444 }
445 None => Ok(Some(Action::Look {
446 preposition: None,
447 basic: None,
448 })),
449 },
450 Verb::Transform => {
451 let (preposition, basic) = VesselRequest::parse_with_prep::<IntoPrep, _>(
452 words.next().ok_or(Error::MissingNoun)?,
453 &mut words,
454 )?;
455 Ok(Some(Action::Transform { preposition, basic }))
456 }
457 Verb::Note => Ok(Some(Action::Note {
458 text: join_or_none(&mut words, " "),
459 })),
460 Verb::Warp => {
461 let (preposition, basic) = VesselRequest::parse_with_prep::<IntoPrep, _>(
462 words.next().ok_or(Error::MissingNoun)?,
463 &mut words,
464 )?;
465 Ok(Some(Action::Warp { preposition, basic }))
466 }
467 Verb::Program => {
468 match words
469 .next()
470 .ok_or(Error::MissingNoun)
471 .and_then(|second_word| VesselRequest::parse(second_word, &mut words))
472 {
473 Ok(vessel_request) => {
474 Ok(Some(Action::from_vessel_request(verb, vessel_request)?))
475 }
476 Err(Error::MissingNoun) => Ok(Some(Action::Program(None))),
477 Err(e) => Err(e),
478 }
479 }
480 Verb::Cast => match words.next() {
481 Some(second_word) => {
482 let spell = VesselRequest::parse(
483 second_word,
484 &mut words.take_while_ref(|word| OnPrep::from_str(word).is_err()),
485 )?;
486
487 let preposition = words
488 .next()
489 .and_then(|prep_word| OnPrep::from_str(prep_word).ok())
490 .ok_or(Error::MissingSecondNoun)?;
491
492 let target = VesselRequest::parse(
493 words.next().ok_or(Error::MissingSecondNoun)?,
494 &mut words,
495 )?;
496
497 Ok(Some(Action::Cast {
498 spell,
499 preposition,
500 target,
501 }))
502 }
503 None => Err(Error::MissingNoun),
504 },
505 Verb::Exit => Ok(Some(Action::Exit)),
506 Verb::Save => Ok(Some(Action::Save {
507 filename: join_or_none(&mut words, " "),
508 })),
509 Verb::Load => Ok(Some(Action::Load {
510 filename: join_or_none(&mut words, " "),
511 })),
512 Verb::Debug => Ok(Some(Action::Debug)),
513 },
514 Err(_) => Err(Error::UnknownVerb(first_word.to_string())),
515 },
516 None => Ok(None),
517 }
518 }
519}
520
521#[derive(Debug)]
523pub enum Error {
524 MissingNoun,
526 MissingSecondNoun,
528 UnknownVerb(String),
530 NonBasicAction(Verb),
532}
533impl Display for Error {
534 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
535 match self {
536 Self::MissingNoun => write!(f, "This action requires a noun."),
537 Self::MissingSecondNoun => write!(
538 f,
539 "This action requires two nouns. Did you remember to delimit them?"
540 ),
541 Self::UnknownVerb(verb_string) => {
542 write!(f, "Unrecognized action: \"{verb_string}\".")
543 }
544 Self::NonBasicAction(verb) => write!(
545 f,
546 "Program tried to parse non-basic action \"{verb}\" as basic."
547 ),
548 }
549 }
550}
551impl std::error::Error for Error {}
552
553pub trait Preposition: FromStr {}
555
556#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
558#[strum(ascii_case_insensitive)]
559pub enum IntoPrep {
560 Into,
562 To,
564}
565impl Preposition for IntoPrep {}
566
567#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
569#[strum(ascii_case_insensitive)]
570pub enum AtPrep {
571 At,
573 In,
575}
576impl Preposition for AtPrep {}
577
578#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
580#[strum(ascii_case_insensitive)]
581pub enum OnPrep {
582 At,
584 On,
586}
587impl Preposition for OnPrep {}
588
589#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, EnumString)]
591#[strum(ascii_case_insensitive)]
592pub enum AboutPrep {
593 About,
595}
596impl Preposition for AboutPrep {}
597
598#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
600pub struct VesselRequest {
601 pub article: Option<Article>,
603 pub name: String,
605}
606impl VesselRequest {
607 pub fn parse<'a, T: Iterator<Item = &'a str>>(
610 second_word: &'a str,
611 words: &mut T,
612 ) -> Result<Self, Error> {
613 let article = Article::from_str(second_word).ok();
614 let name = join_or_none(
615 &mut if article.is_some() {
616 None
617 } else {
618 Some(second_word)
619 }
620 .into_iter()
621 .chain(words),
622 " ",
623 )
624 .ok_or(Error::MissingNoun)?;
625 Ok(Self { article, name })
626 }
627 pub fn parse_with_prep<'a, P: Preposition, T: Iterator<Item = &'a str>>(
630 second_word: &'a str,
631 words: &mut T,
632 ) -> Result<(Option<P>, Self), Error> {
633 let preposition = P::from_str(second_word).ok();
634 let third_word = if preposition.is_none() {
635 second_word
636 } else {
637 words.next().ok_or(Error::MissingNoun)?
638 };
639 Ok((preposition, Self::parse(third_word, words)?))
640 }
641}
642
643fn join_or_none<'a, T: Iterator<Item = &'a str>>(words: &mut T, sep: &str) -> Option<String> {
644 let string = words.join(sep);
645 if string.is_empty() {
646 None
647 } else {
648 Some(string)
649 }
650}