trinitry/src/lib.rs

215 lines
7.0 KiB
Rust
Raw Normal View History

2023-12-23 21:24:41 +00:00
/*
* Copyright (C) 2024 - 2024:
* The Trinitrix Project <bpeetz@b-peetz.de, antifallobst@systemausfall.org>
* SPDX-License-Identifier: LGPL-3.0-or-later
2023-12-23 21:24:41 +00:00
*
* This file is part of the Trinitry crate for Trinitrix.
*
* Trinitry is free software: you can redistribute it and/or modify
* it under the terms of the Lesser GNU General Public License as
* published by the Free Software Foundation, either version 3 of
* the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* and the Lesser GNU General Public License along with this program.
* If not, see <https://www.gnu.org/licenses/>.
*/
//! This crate is a parser for the 'Trinitry' (not 'Trinity') language, used to map all sort of
//! Functions to a memorable command.
//!
//! This parser is more of a validator, as Trinitry does not support any language features besides
//! the aforementioned commands and arguments. That includes some simple constructs like: '||' (OR)
//! or '&&' (AND). If you need these features, simple write them in the language, you've written your
//! Function in.
//!
//! # General specification
//! ## Command
//! Basically every command can be a series of alphanumeric ASCII values.
//!
//! Correctly spoken, the Language, containing all valid command names, is just the Kleene closure
//! over an Alphabet $\Sigma$, which contains all alphanumeric characters:
//! $$ \Sigma_{cmd} = \\{x | 0 \leqslant x \leqslant 9\\} \cup \\{x | "a" \leqslant x \leqslant "z"\\} \cup \\{x | "A" \leqslant x \leqslant "Z"\\} \cup \\{"\\_", "\text{-}", "."\\} $$
//!
//! ## Argument
//! Arguments are similar to the command, although they can also contain spaces and quotes,
//! if it's quoted and additional characters (here notated as "$\\dots{}$"):
//! $$ \Sigma_{args-quoted} = \Sigma_{cmd} \cup \\{"\\text{"}", "\\ ", \\dots{}\\} $$
//! $$ \Sigma_{args-single-quoted} = \Sigma_{cmd} \cup \\{"'", "\\ ", \\dots{}\\} $$
//! $$ \Sigma_{args} = \Sigma_{cmd} \cup \\{\\dots{}\\} $$
//! Look at the [trinitry.pest](../../../src/trinitry.pest) file for a full list of the additional
//! allowed characters.
//!
//! # Examples
//! ## Command
//! A valid command would be something like that:
//! ```text
//! quit
//! ```
//! something like that would not be valid however, as Trinitry does not support these 'complex'
//! language features:
//! ```text
//! write && quit
//! ```
//! ## Arguments
//! A valid argumented command would be:
//! ```text
//! lua "function() print('Hi!') end"
//! ```
//! Whilst this would not be valid (that is, it would very likely not be what you want):
//! ```text
//! lua "function() print("Hi!") end"
//! ```
//! as the double quotes in the print statement actually unquote the argument, leaving you with
//! three arguments:
//! 1. `function() print(`
//! 1. `Hi!`
//! 1. `) end`
use std::fmt::Display;
use pest::{error::Error, Parser};
use pest_derive::Parser;
#[derive(Parser)]
#[grammar = "trinitry.pest"]
pub struct Trinitry {
command: String,
arguments: Vec<String>,
}
impl Trinitry {
pub fn new(input: &str) -> Result<Self, Error<Rule>> {
let parsed = Self::parse(Rule::trinitry, input)?;
let command = {
let command: Vec<_> = parsed.clone().find_tagged("command").collect();
// Ensure that we have only one command
// This should be ensured by the grammar, thus the 'debug_assert'
debug_assert_eq!(command.len(), 1);
// PERFORMANCE(@soispha): Replace this with `mem::take` (when pairs implements Default)
// <2023-11-01>
command
.first()
.expect("This should contain exactly one element")
.to_owned()
};
let arguments: Vec<_> = parsed.clone().find_tagged("argument").collect();
Ok(Trinitry {
command: command.as_str().to_owned(),
arguments: arguments
.iter()
.map(|arg| {
let mut arg = arg.as_str().trim();
arg = if let Some(new_arg) = arg.strip_prefix("\"") {
new_arg
} else {
arg
};
arg = if let Some(new_arg) = arg.strip_suffix("\"") {
new_arg
} else {
arg
};
arg = if let Some(new_arg) = arg.strip_prefix("'") {
new_arg
} else {
arg
};
arg = if let Some(new_arg) = arg.strip_suffix("'") {
new_arg
} else {
arg
};
arg.to_owned()
})
.collect(),
})
}
}
impl Display for Trinitry {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.arguments.is_empty() {
f.write_str(&self.command)
} else {
f.write_fmt(format_args!(
"{} {}",
&self.command,
&self.arguments.join(" ")
))
}
}
}
mod tests;
#[cfg(test)]
mod test {
use crate::Trinitry;
#[test]
fn parse_cmd() {
let string = "quit";
let p = Trinitry::new(string).unwrap_or_else(|e| {
panic!("{}", e);
});
assert_eq!(&p.command, "quit");
assert!(&p.arguments.is_empty());
}
#[test]
fn parse_arg_clean() {
let string = r##"lua print("Hi")"##;
let p = Trinitry::new(string).unwrap_or_else(|e| {
panic!("{}", e);
});
assert_eq!(&p.command, "lua");
assert_eq!(&p.arguments[0], r#"print("Hi")"#);
}
#[test]
fn parse_arg_quote() {
let string = r##"write "some 'file' name""##;
let p = Trinitry::new(string).unwrap_or_else(|e| {
panic!("{}", e);
});
assert_eq!(&p.command, "write");
assert_eq!(&p.arguments[0], "some 'file' name");
}
#[test]
fn parse_arg_single_quote() {
let string = r##"write 'some "file" name'"##;
let p = Trinitry::new(string).unwrap_or_else(|e| {
panic!("{}", e);
});
assert_eq!(&p.command, "write");
assert_eq!(&p.arguments[0], "some \"file\" name");
}
#[test]
fn parse_arg_multi() {
let string = r##"write 'some "file" name' "other name" last"##;
let p = Trinitry::new(string).unwrap_or_else(|e| {
panic!("{}", e);
});
let expected_args = vec!["some \"file\" name", "other name", "last"]
.iter()
.map(|str| (*str).to_owned())
.collect::<Vec<String>>();
assert_eq!(&p.command, "write");
assert_eq!(&p.arguments, &expected_args);
}
}