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
//! This module contains a struct which holds the information necessary to
//! execute a Rhiz task and the implementation for creating these structs from a
//! Rhizfile's AST.
use std::collections::HashMap;

use crate::ast;

pub type CompilationError = Box<dyn std::error::Error>;
pub type CompilationResult<T> = Result<T, CompilationError>;

/// Compilation target for s-xpressions of the format
/// ```ignore
/// (task "name" ["description"] [funcall]*)
/// ```
pub struct Task<'a> {
    pub name: String,
    pub description: Option<String>,
    pub items: Vec<&'a ast::RhizValue>,
}

impl<'a> Task<'a> {
    fn compile(sexpr: &'a ast::RhizValue) -> CompilationResult<Task<'a>> {
        let items = match sexpr {
            ast::RhizValue::SExpr(items) => items,
            _ => return Err(CompilationError::from("Expected a sexpr to make a task")),
        };
        if items.len() < 2 {
            return Err(CompilationError::from("Invalid task declaration"));
        };
        match &items[0] {
            ast::RhizValue::String(s) => {
                if s != "task" {
                    let msg = "Only 'task' declarations allowed at the top-level of a Rhizfile";
                    return Err(CompilationError::from(msg));
                }
            }
            _ => {
                let msg = "Top-level Rhizfile declarations should be of the form (task name [description] [commands]*)";
                return Err(CompilationError::from(msg));
            }
        }
        let name = match &items[1] {
            ast::RhizValue::String(s) => s.to_owned(),
            _ => {
                let msg = "Task names should be strings";
                return Err(CompilationError::from(msg));
            }
        };
        let description = if items.len() > 2 {
            match &items[2] {
                ast::RhizValue::String(s) => Some(s.to_owned()),
                _ => None,
            }
        } else {
            None
        };
        let rest = match description {
            Some(_) => &items[3..],
            None => &items[2..],
        };
        if rest.iter().any(|v| !matches!(v, ast::RhizValue::SExpr(_))) {
            let msg = "Tasks should only contain SExprs";
            return Err(CompilationError::from(msg));
        }
        Ok(Task {
            name,
            description,
            items: rest.iter().collect(),
        })
    }
}

pub fn compile<'a>(prog: &'a ast::RhizValue) -> CompilationResult<HashMap<String, Task<'a>>> {
    match prog {
        ast::RhizValue::Program(tasks) => {
            let compiled_tasks = tasks.iter().map(Task::compile);
            let mut tasks: HashMap<String, Task<'a>> = HashMap::new();
            for task in compiled_tasks {
                match task {
                    Ok(t) => tasks.insert(t.name.to_owned(), t),
                    Err(e) => return Err(e),
                };
            }
            Ok(tasks)
        }
        _ => Err(CompilationError::from(
            "I only know how to compile programs",
        )),
    }
}