Skip to main content

blue_lang_runtime/
host_surface.rs

1use std::path::{Path, PathBuf};
2
3pub const FILE: &str = "blue-host.json";
4
5#[derive(Debug, Clone, PartialEq, Eq)]
6pub struct HostName {
7    pub name: String,
8    pub params: Vec<String>,
9    pub required: usize,
10}
11
12impl HostName {
13    #[must_use]
14    pub fn nullary(&self) -> bool {
15        self.params.is_empty()
16    }
17}
18
19#[derive(Debug, Clone, PartialEq, Eq)]
20pub struct Surface {
21    pub file: PathBuf,
22    pub functions: Vec<HostName>,
23}
24
25#[derive(Debug, thiserror::Error)]
26pub enum SurfaceError {
27    #[error("{file}: {reason}")]
28    Unreadable { file: String, reason: String },
29}
30
31#[must_use]
32pub fn find(entry: &Path) -> Option<PathBuf> {
33    entry
34        .ancestors()
35        .skip(1)
36        .map(|dir| dir.join(FILE))
37        .find(|candidate| candidate.is_file())
38}
39
40pub fn near(entry: &Path) -> Result<Option<Surface>, SurfaceError> {
41    find(entry).map(|file| read(&file)).transpose()
42}
43
44pub fn read(file: &Path) -> Result<Surface, SurfaceError> {
45    let shown = file.display().to_string();
46    let unreadable = |reason: String| SurfaceError::Unreadable {
47        file: shown.clone(),
48        reason,
49    };
50    let text = std::fs::read_to_string(file).map_err(|e| unreadable(e.to_string()))?;
51    parse(&text)
52        .map(|functions| Surface {
53            file: file.to_path_buf(),
54            functions,
55        })
56        .map_err(unreadable)
57}
58
59pub fn parse(text: &str) -> Result<Vec<HostName>, String> {
60    let doc: serde_json::Value = serde_json::from_str(text).map_err(|e| e.to_string())?;
61    let functions = doc
62        .get("functions")
63        .and_then(serde_json::Value::as_array)
64        .ok_or("expected an object with a `functions` list")?;
65    functions
66        .iter()
67        .enumerate()
68        .map(|(i, f)| {
69            let name = f
70                .get("name")
71                .and_then(serde_json::Value::as_str)
72                .ok_or(format!("functions[{i}] has no string `name`"))?;
73            let params = f
74                .get("params")
75                .and_then(serde_json::Value::as_array)
76                .ok_or(format!("{name} has no `params` list"))?
77                .iter()
78                .map(|p| p.as_str().map(str::to_owned))
79                .collect::<Option<Vec<_>>>()
80                .ok_or(format!("{name}'s params are not all strings"))?;
81            let required = f
82                .get("required")
83                .and_then(serde_json::Value::as_u64)
84                .and_then(|n| usize::try_from(n).ok())
85                .unwrap_or(params.len());
86            if required > params.len() {
87                return Err(format!(
88                    "{name} requires {required} of {} params",
89                    params.len()
90                ));
91            }
92            Ok(HostName {
93                name: name.to_owned(),
94                params,
95                required,
96            })
97        })
98        .collect()
99}
100
101#[must_use]
102pub fn render<'a>(
103    functions: impl IntoIterator<Item = (&'a str, &'a [&'a str], usize, &'a str)>,
104) -> String {
105    let functions: Vec<serde_json::Value> = functions
106        .into_iter()
107        .map(|(name, params, required, doc)| {
108            serde_json::json!({
109                "name": name,
110                "params": params,
111                "required": required,
112                "doc": doc,
113            })
114        })
115        .collect();
116    let mut out = serde_json::to_string_pretty(&serde_json::json!({ "functions": functions }))
117        .unwrap_or_default();
118    out.push('\n');
119    out
120}