steel_doc/
lib.rs

1#![allow(unused)]
2
3use std::{env::current_dir, error::Error, io::BufWriter, path::PathBuf};
4
5use steel::compiler::modules::MANGLER_PREFIX;
6use steel::steel_vm::engine::Engine;
7
8use std::collections::HashSet;
9use std::io::Write;
10
11struct DocumentGenerator {
12    // Root directory for generated documentation to go
13    output_dir: PathBuf,
14    // Final output locations
15    writers: HashSet<PathBuf>,
16}
17
18impl DocumentGenerator {
19    pub fn new(output_dir: Option<PathBuf>) -> Self {
20        let output_dir = output_dir.unwrap_or({
21            // TODO: Come back and remove this unwrap
22            let mut current_dir = current_dir().unwrap();
23
24            current_dir.push("/docs");
25
26            if !current_dir.exists() {
27                std::fs::create_dir(&current_dir).unwrap();
28            }
29
30            current_dir
31        });
32
33        Self {
34            output_dir,
35            writers: HashSet::new(),
36        }
37    }
38
39    pub fn record(&mut self, path: PathBuf) {
40        self.writers.insert(path);
41    }
42}
43
44fn walk_for_defines<W: Write>(
45    writer: &mut W,
46    ast: &[steel::parser::ast::ExprKind],
47) -> Result<(), Box<dyn Error>> {
48    let mut nodes = ast.iter();
49
50    while let Some(node) = nodes.next() {
51        match &node {
52            steel::parser::ast::ExprKind::Define(d) => {
53                let name = d.name.atom_identifier().unwrap().resolve();
54
55                // We'll only check things that are values
56                if !name.starts_with(MANGLER_PREFIX) && name.ends_with("__doc__") {
57                    writeln!(writer, "### **{}**", name.trim_end_matches("__doc__"))?;
58
59                    let ast_node = nodes.next().unwrap();
60
61                    if let steel::parser::ast::ExprKind::Define(def) = &ast_node {
62                        if let steel::parser::ast::ExprKind::Quote(q) = &def.body {
63                            if let steel::parser::ast::ExprKind::Define(d) = &q.expr {
64                                if let steel::parser::ast::ExprKind::LambdaFunction(l) = &d.body {
65                                    writeln!(writer, "```scheme")?;
66                                    write!(writer, "({}", name.trim_end_matches("__doc__"))?;
67
68                                    for arg in &l.args {
69                                        if let Some(ident) = arg.atom_identifier() {
70                                            // Macros will generate unreadable symbols - so for the sake
71                                            // of the documentation generator, we probably want to make this
72                                            // more human readable
73                                            write!(
74                                                writer,
75                                                " {}",
76                                                ident.resolve().trim_start_matches('#')
77                                            )?;
78                                        } else {
79                                            write!(writer, " {arg}")?;
80                                        }
81                                    }
82
83                                    if l.rest && l.args.len() == 1 {
84                                        write!(writer, " ...")?;
85                                    }
86
87                                    writeln!(writer, ")")?;
88                                    writeln!(writer, "```")?;
89                                }
90                            }
91                        }
92                    }
93
94                    writeln!(writer, "{}", d.body.string_literal().unwrap())?;
95                }
96            }
97
98            steel::parser::ast::ExprKind::Begin(b) => {
99                walk_for_defines(writer, &b.exprs)?;
100            }
101
102            _ => {}
103        }
104    }
105
106    Ok(())
107}
108
109fn top_level_walk_directory(path: PathBuf, vm: &mut Engine) {
110    todo!()
111}
112
113pub fn walk_dir<W: Write>(
114    writer: &mut W,
115    path: PathBuf,
116    vm: &mut Engine,
117) -> Result<(), Box<dyn Error>> {
118    if path.is_dir() {
119        // If contents are a cog module, then we should grab it
120        // OR, if at any point in the recursion we've found a cog module in the directory, we should
121        // be good to check it out
122        let directory_contents = path.read_dir()?.collect::<Result<Vec<_>, _>>()?;
123
124        for file in directory_contents {
125            let path = file.path();
126            walk_dir(writer, path, vm)?;
127        }
128    } else if path.extension().and_then(|x| x.to_str()) == Some("scm")
129        && path.file_name().and_then(|x| x.to_str()) != Some("cog.scm")
130    {
131        writeln!(writer, "# {}", path.to_str().unwrap())?;
132
133        let contents = std::fs::read_to_string(&path)?;
134
135        // let full_path = std::fs::canonicalize(path.clone()).expect("Unable to canonicalize path!");
136
137        // {
138        //     println!("Requiring: {:?}", full_path);
139        //     vm.emit_fully_expanded_ast(
140        //         &format!("(require {:?})", full_path),
141        //         Some(full_path.clone()),
142        //     )
143        //     .unwrap();
144        // }
145
146        let ast = vm.emit_fully_expanded_ast(&contents, Some(path.clone()))?;
147
148        // println!("{:?}", vm.modules().keys().collect::<Vec<_>>());
149
150        // if let Some(module) = module {
151        //     println!("{:?}", module.get_requires());
152        // }
153
154        walk_for_defines(writer, &ast)?;
155
156        // let module = vm.modules().get(&full_path);
157
158        // println!("{:?}", vm.modules().keys().collect::<Vec<_>>());
159
160        // if let Some(module) = module {
161        //     println!("{:?}", module.get_requires());
162        // }
163    }
164
165    writer.flush()?;
166
167    Ok(())
168}
169
170// Parse the cog file located at the path, and return the package name
171// Other things are probably important, but for now we'll just deal with that
172pub fn parse_cog_file(path: PathBuf) -> steel::rvals::Result<String> {
173    let contents = std::fs::read_to_string(path)?;
174    let exprs = steel::parser::parser::Parser::parse(&contents)?;
175    todo!()
176}