1pub mod errors;
36mod file;
37pub mod key;
38#[doc(hidden)]
39pub mod position;
40pub mod settings;
41mod string;
42
43use std::collections::HashMap;
44use std::io::{BufRead, BufReader};
45use std::marker::PhantomData;
46use std::path::{Path, PathBuf};
47
48use errors::{Error, ParseError, Result};
49use errors::ErrorType::{MissingArgument, NoCommand, Parse, UnknownCommand};
50use key::{Key, parse_keys};
51use position::Pos;
52use string::{StrExt, check_ident, maybe_word, word, words};
53
54use Command::*;
55use Value::*;
56
57#[macro_export]
58macro_rules! rtry {
59 ($parse_result:expr, $result:expr) => {
60 rtry_no_return!($parse_result, $result, { return $parse_result; });
61 };
62}
63
64#[macro_export]
65macro_rules! rtry_no_return {
66 ($parse_result:expr, $result:expr, $error_block:block) => {
67 match $result {
68 Ok(result) => result,
69 Err(error) => {
70 $parse_result.errors.push(error.into());
71 $error_block
72 },
73 }
74 };
75}
76
77pub trait CompletionValues {
79 fn completion_values() -> Vec<String>;
81}
82
83impl CompletionValues for bool {
84 fn completion_values() -> Vec<String> {
85 vec!["true".to_string(), "false".to_string()]
86 }
87}
88
89impl CompletionValues for i64 {
90 fn completion_values() -> Vec<String> {
91 vec![]
92 }
93}
94
95impl CompletionValues for String {
96 fn completion_values() -> Vec<String> {
97 vec![]
98 }
99}
100
101pub trait EnumFromStr
103 where Self: Sized
104{
105 fn create(variant: &str, argument: &str, prefix: Option<u32>) -> std::result::Result<Self, String>;
107
108 fn has_argument(variant: &str) -> std::result::Result<bool, String>;
110}
111
112pub trait EnumMetaData {
120 fn get_metadata() -> HashMap<String, MetaData>;
122}
123
124#[derive(Debug)]
127pub struct MetaData {
128 pub completion_hidden: bool,
130 pub help_text: String,
132 pub is_special_command: bool,
135}
136
137pub struct ParseResult<T> {
139 pub commands: Vec<Command<T>>,
141 pub errors: Vec<Error>,
143}
144
145impl<T> ParseResult<T> {
146 #[allow(unknown_lints, new_without_default_derive)]
147 pub fn new() -> Self {
149 ParseResult {
150 commands: vec![],
151 errors: vec![],
152 }
153 }
154
155 fn new_with_command(command: Command<T>) -> Self {
156 ParseResult {
157 commands: vec![command],
158 errors: vec![],
159 }
160 }
161
162 fn merge(&mut self, mut parse_result: ParseResult<T>) {
163 self.commands.append(&mut parse_result.commands);
164 self.errors.append(&mut parse_result.errors);
165 }
166}
167
168pub trait SettingCompletion {
170 fn get_value_completions() -> HashMap<String, Vec<String>>;
172}
173
174#[derive(Debug, PartialEq)]
176pub enum Command<T> {
177 App(String),
179 Custom(T),
181 Map {
183 action: String,
185 keys: Vec<Key>,
187 mode: String,
189 },
190 Set(String, Value),
192 Unmap {
194 keys: Vec<Key>,
196 mode: String,
198 },
199}
200
201#[derive(Default)]
203pub struct Config {
204 pub application_commands: Vec<&'static str>,
206 pub mapping_modes: Vec<&'static str>,
208}
209
210pub struct Parser<T> {
212 column: usize,
213 config: Config,
214 include_path: PathBuf,
215 line: usize,
216 _phantom: PhantomData<T>,
217}
218
219impl<T: EnumFromStr> Parser<T> {
220 #[allow(unknown_lints, new_without_default_derive)]
221 pub fn new() -> Self {
223 Parser {
224 column: 1,
225 config: Config::default(),
226 include_path: Path::new("./").to_path_buf(),
227 line: 1,
228 _phantom: PhantomData,
229 }
230 }
231
232 pub fn new_with_config(config: Config) -> Self {
234 Parser {
235 column: 1,
236 config: config,
237 include_path: Path::new("./").to_path_buf(),
238 line: 1,
239 _phantom: PhantomData,
240 }
241 }
242
243 fn check_eol(&self, line: &str, index: usize) -> Result<()> {
245 if line.len() > index {
246 let rest = &line[index..];
247 if let Some(word) = maybe_word(rest) {
248 let index = word.index;
249 return Err(ParseError::new(
250 Parse,
251 rest.to_string(),
252 "<end of line>".to_string(),
253 Pos::new(self.line, self.column + index),
254 ));
255 }
256 }
257 Ok(())
258 }
259
260 fn custom_command(&self, line: &str, word: &str, start_index: usize, index: usize, prefix: Option<u32>)
262 -> Result<Command<T>>
263 {
264 let args =
265 if line.len() > start_index {
266 line[start_index..].trim()
267 }
268 else if let Ok(true) = T::has_argument(word) {
269 return Err(self.missing_args(start_index));
270 }
271 else {
272 ""
273 };
274 if let Ok(command) = T::create(word, args, prefix) {
275 Ok(Custom(command))
276 }
277 else if self.config.application_commands.contains(&word) {
278 Ok(App(word.to_string()))
279 }
280 else {
281 return Err(ParseError::new(
282 UnknownCommand,
283 word.to_string(),
284 "command or comment".to_string(),
285 Pos::new(self.line, index + 1)
286 ))
287 }
288 }
289
290 fn get_rest<'a>(&self, line: &'a str, column: usize) -> Result<&'a str> {
293 if line.len() > column {
294 Ok(&line[column..])
295 }
296 else {
297 Err(self.missing_args(column))
298 }
299 }
300
301 fn line(&mut self, line: &str, prefix: Option<u32>) -> ParseResult<T> {
303 let mut result = ParseResult::new();
304 if let Some(word) = maybe_word(line) {
305 let index = word.index;
306 let word = word.word;
307 let start_index = index + word.len() + 1;
308 self.column = start_index + 1;
309
310 let (start3, end3) = word.rsplit_at(3);
311 let (start5, end5) = word.rsplit_at(5);
312 if word.starts_with('#') {
313 return result;
314 }
315
316 if word == "include" {
317 let rest = rtry!(result, self.get_rest(line, start_index));
318 self.include_command(rest)
319 }
320 else {
321 let command =
322 if word == "set" {
323 let rest = rtry!(result, self.get_rest(line, start_index));
324 self.set_command(rest)
325 }
326 else if end3 == "map" && self.config.mapping_modes.contains(&start3) {
327 let rest = rtry!(result, self.get_rest(line, start_index));
328 self.map_command(rest, start3)
329 }
330 else if end5 == "unmap" && self.config.mapping_modes.contains(&start5) {
331 let rest = rtry!(result, self.get_rest(line, start_index));
332 self.unmap_command(rest, start5)
333 }
334 else {
335 self.custom_command(line, word, start_index, index, prefix)
336 };
337 let command = rtry!(result, command);
338 ParseResult::new_with_command(command)
339 }
340 }
341 else {
342 result
343 }
344 }
345
346 fn include_command(&mut self, line: &str) -> ParseResult<T> {
348 let word = word(line);
349 let index = word.index;
350 let word = word.word;
351 let after_index = index + word.len() + 1;
352 self.column += after_index;
353 let mut result = ParseResult::new();
354 rtry_no_return!(result, self.check_eol(line, after_index), {});
355 let path = Path::new(&self.include_path).join(word);
356 let file = rtry!(result, file::open(&path));
357 let buf_reader = BufReader::new(file);
358 result.merge(self.parse(buf_reader, None));
359 result
360 }
361
362 fn map_command(&self, line: &str, mode: &str) -> Result<Command<T>> {
364 let word = word(line);
365 let index = word.index;
366 let word = word.word;
367 let rest = &line[index + word.len() ..].trim();
368 if !rest.is_empty() {
369 Ok(Map {
370 action: rest.to_string(),
371 keys: parse_keys(word, self.line, self.column + index)?,
372 mode: mode.to_string(),
373 })
374 }
375 else {
376 Err(ParseError::new(
377 Parse,
378 "<end of line>".to_string(),
379 "mapping action".to_string(),
380 Pos::new(self.line, self.column + line.len())
381 ))
382 }
383 }
384
385 fn missing_args(&self, column: usize) -> Error {
387 ParseError::new(
388 MissingArgument,
389 "<end of line>".to_string(),
390 "command arguments".to_string(),
391 Pos::new(self.line, column)
392 )
393 }
394
395 pub fn parse<R: BufRead>(&mut self, input: R, prefix: Option<u32>) -> ParseResult<T> {
397 let mut result = ParseResult::new();
398 for (line_num, input_line) in input.lines().enumerate() {
399 self.line = line_num + 1;
400 let input_line = rtry_no_return!(result, input_line, { continue });
401 result.merge(self.line(&input_line, prefix));
402 }
403 result
404 }
405
406 pub fn parse_line(&mut self, line: &str, prefix: Option<u32>) -> ParseResult<T> {
408 let mut result = self.parse(line.as_bytes(), prefix);
409 if result.commands.is_empty() && result.errors.is_empty() {
410 result.errors.push(ParseError::new(
411 NoCommand,
412 "comment or <end of line>".to_string(),
413 "command".to_string(),
414 Pos::new(self.line, 1)
415 ));
416 }
417 result
418 }
419
420 fn set_command(&mut self, line: &str) -> Result<Command<T>> {
422 if let Some(words) = words(line, 2) {
423 let index = words[0].index;
424 let word = words[0].word;
425 let identifier = check_ident(word.to_string(), &Pos::new(self.line, self.column + index))?;
426
427 let operator = words[1].word;
428 let operator_index = words[1].index;
429 if operator == "=" {
430 let rest = &line[operator_index + 1..];
431 self.column += operator_index + 1;
432 Ok(Set(identifier.to_string(), self.value(rest)?))
433 }
434 else {
435 return Err(ParseError::new(
436 Parse,
437 operator.to_string(),
438 "=".to_string(),
439 Pos::new(self.line, self.column + operator_index)
440 ))
441 }
442 }
443 else {
444 return Err(ParseError::new(
445 Parse,
446 "<end of line>".to_string(),
447 "=".to_string(),
448 Pos::new(self.line, self.column + line.len()),
449 ))
450 }
451 }
452
453 pub fn set_include_path<P: AsRef<Path>>(&mut self, directory: P) {
455 self.include_path = directory.as_ref().to_path_buf();
456 }
457
458 fn unmap_command(&mut self, line: &str, mode: &str) -> Result<Command<T>> {
460 let word = word(line);
461 let index = word.index;
462 let word = word.word;
463 let after_index = index + word.len() + 1;
464 self.column += after_index;
465 self.check_eol(line, after_index)?;
466 Ok(Unmap {
467 keys: parse_keys(word, self.line, self.column + index)?,
468 mode: mode.to_string(),
469 })
470 }
471
472 fn value(&self, input: &str) -> Result<Value> {
474 let string: String = input.chars().take_while(|&character| character != '#').collect();
475 let string = string.trim();
476 match string {
477 "" => Err(ParseError::new(
478 Parse,
479 "<end of line>".to_string(),
480 "value".to_string(),
481 Pos::new(self.line, self.column + string.len())
482 )),
483 "true" => Ok(Bool(true)),
484 "false" => Ok(Bool(false)),
485 _ => {
486 if string.chars().all(|character| character.is_digit(10)) {
487 Ok(Int(string.parse().unwrap()))
489 }
490 else if string.chars().all(|character| character.is_digit(10) || character == '.') {
491 Ok(Float(string.parse().unwrap()))
493 }
494 else {
495 Ok(Str(input.trim().to_string()))
496 }
497 },
498 }
499 }
500}
501
502pub trait SpecialCommand
504 where Self: Sized
505{
506 fn identifier_to_command(identifier: char, input: &str) -> std::result::Result<Self, String>;
508
509 fn is_identifier(character: char) -> bool;
511
512 fn is_incremental(identifier: char) -> bool;
516}
517
518#[derive(Debug, PartialEq)]
520pub enum Value {
521 Bool(bool),
523 Float(f64),
525 Int(i64),
527 Str(String),
529}
530
531impl Value {
532 pub fn to_type(&self) -> &str {
534 match *self {
535 Bool(_) => "bool",
536 Float(_) => "float",
537 Int(_) => "int",
538 Str(_) => "string",
539 }
540 }
541}