petr_api/
lib.rs

1//! Top-level API for the petr programming language.
2//! Exposes relevant APIs from all compiler stages and tooling.
3
4#[cfg(not(feature = "no_std"))]
5use std::{
6    fs,
7    path::{Path, PathBuf},
8};
9
10pub use petr_fmt::{format_sources, Formattable, FormatterConfig, FormatterContext};
11pub use petr_ir::{Lowerer, LoweringError};
12pub use petr_parse::Parser;
13#[cfg(not(feature = "no_std"))]
14pub use petr_pkg::{manifest::find_manifest, BuildPlan};
15pub use petr_resolve::{resolve_symbols, Dependency};
16pub use petr_typecheck::type_check;
17pub use petr_utils::{render_error, Identifier, IndexMap, SourceId, SpannedItem};
18pub use petr_vm::Vm;
19#[cfg(not(feature = "no_std"))]
20use termcolor::{ColorChoice, ColorSpec, StandardStream, WriteColor};
21
22pub mod error {
23    use petr_ir::LoweringError;
24    use petr_utils::SpannedItem;
25    use thiserror::Error;
26    #[derive(Error, Debug)]
27    pub enum PeteError {
28        #[error(transparent)]
29        Io(#[from] std::io::Error),
30        #[error(transparent)]
31        TomlSeriatlize(#[from] toml::ser::Error),
32        #[cfg(not(feature = "no_std"))]
33        #[error(transparent)]
34        Pkg(#[from] petr_pkg::error::PkgError),
35        #[error("Failed to lower code")]
36        FailedToLower(#[from] SpannedItem<LoweringError>),
37    }
38}
39
40#[cfg(not(feature = "no_std"))]
41#[allow(clippy::type_complexity)]
42pub fn load_project_and_dependencies(path: &Path) -> Result<(petr_pkg::Lockfile, Vec<(PathBuf, String)>, BuildPlan), crate::error::PeteError> {
43    let manifest = petr_pkg::manifest::find_manifest(Some(path.to_path_buf())).expect("Failed to find manifest");
44    let dependencies = manifest.dependencies;
45    let mut stdout = StandardStream::stdout(ColorChoice::Always);
46
47    if !dependencies.is_empty() {
48        stdout.set_color(ColorSpec::new().set_bold(true))?;
49        /*
50        todo!(
51            "instead of saying fetching, pay attention to if it already exists
52        and print if it does or doesn't. also, check if checksum agrees with lockfile
53        and use rev etc on github dep to determine thet key"
54        );
55        */
56        println!(
57            "Fetching {} {} for package {}",
58            dependencies.len(),
59            if dependencies.len() == 1 { "dependency" } else { "dependencies" },
60            manifest.name
61        );
62
63        stdout.set_color(ColorSpec::new().set_bold(false))?;
64    }
65    let (lockfile, build_plan) = petr_pkg::load_dependencies(dependencies)?;
66
67    let files = load_files(path);
68    Ok((lockfile, files, build_plan))
69}
70
71#[cfg(not(feature = "no_std"))]
72pub fn load_files(path: &Path) -> Vec<(PathBuf, String)> {
73    let mut buf = Vec::new();
74
75    fn read_petr_files(
76        dir: &PathBuf,
77        buf: &mut Vec<(PathBuf, String)>,
78    ) {
79        let entries = fs::read_dir(dir).expect("Failed to read directory");
80        for entry in entries {
81            let entry = entry.expect("Failed to read directory entry");
82            let path = entry.path();
83            if path.is_dir() {
84                read_petr_files(&path, buf);
85            } else if path.extension().and_then(|s| s.to_str()) == Some("pt") {
86                let source = fs::read_to_string(&path).expect("Failed to read file");
87                buf.push((path, source));
88            }
89        }
90    }
91
92    read_petr_files(&path.join("src"), &mut buf);
93    buf
94}
95
96pub fn render_errors<T>(
97    errs: Vec<SpannedItem<T>>,
98    sources: &IndexMap<SourceId, (&'static str, &'static str)>,
99) where
100    T: miette::Diagnostic + Send + Sync + 'static,
101{
102    for err in errs {
103        let rendered = petr_utils::render_error(sources, err);
104        eprintln!("{:?}", rendered);
105    }
106}
107
108#[cfg(not(feature = "no_std"))]
109pub fn compile(
110    path: PathBuf,
111    timings: &mut petr_profiling::Timings,
112) -> Result<Lowerer, crate::error::PeteError> {
113    timings.start("full compile");
114    timings.start("load project and dependencies");
115    let (lockfile, buf, build_plan) = load_project_and_dependencies(&path)?;
116    let lockfile_toml = toml::to_string(&lockfile).expect("Failed to serialize lockfile to TOML");
117    let lockfile_path = path.join("petr.lock");
118    fs::write(lockfile_path, lockfile_toml).expect("Failed to write lockfile");
119    timings.end("load project and dependencies");
120
121    // convert pathbufs into strings for the parser
122    let buf = buf
123        .into_iter()
124        .map(|(pathbuf, s)| (pathbuf.to_string_lossy().to_string(), s))
125        .collect::<Vec<_>>();
126
127    timings.start("parsing stage");
128    timings.start("parse user code");
129    // parse
130    // construct an interner for symbols, which will be used throughout the whole compilation.
131    let parser = Parser::new(buf);
132    let (ast, mut parse_errs, mut interner, mut source_map) = parser.into_result();
133
134    timings.end("parse user code");
135    timings.start("parse dependencies");
136
137    let mut dependencies = Vec::with_capacity(build_plan.items.len());
138
139    for item in build_plan.items {
140        let (lockfile, buf, _build_plan) = load_project_and_dependencies(&item.path_to_source)?;
141        // TODO(alex) -- transitive dependencies, get these build plans too
142
143        let lockfile_toml = toml::to_string(&lockfile)?;
144        let lockfile_path = path.join("petr.lock");
145        fs::write(lockfile_path, lockfile_toml)?;
146        // the idea here is that we re-use the interner and source map,
147        // so we don't have to worry about scoping symbol IDs and source IDs to packages
148        let parser = Parser::new_with_existing_interner_and_source_map(
149            buf.into_iter()
150                .map(|(pathbuf, s)| (pathbuf.to_string_lossy().to_string(), s))
151                .collect::<Vec<_>>(),
152            interner,
153            source_map,
154        );
155        let (ast, mut new_parse_errs, new_interner, new_source_map) = parser.into_result();
156        interner = new_interner;
157        parse_errs.append(&mut new_parse_errs);
158        source_map = new_source_map;
159        dependencies.push(petr_resolve::Dependency {
160            key: item.key,
161            name: item.manifest.name,
162            dependencies: item.depends_on,
163            ast,
164        });
165    }
166
167    timings.end("parse dependencies");
168    timings.end("parsing stage");
169
170    render_errors(parse_errs, &source_map);
171    // errs.append(&mut parse_errs);
172    // resolve symbols
173    timings.start("symbol resolution");
174    let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner, dependencies);
175    timings.end("symbol resolution");
176
177    // TODO impl diagnostic for resolution errors
178    if !resolution_errs.is_empty() {
179        dbg!(&resolution_errs);
180    }
181    // errs.append(&mut resolution_errs);
182
183    timings.start("type check");
184    // type check
185    let (type_errs, type_checker) = petr_typecheck::type_check(resolved);
186
187    timings.end("type check");
188
189    // TODO impl diagnostic for type errors
190    if !type_errs.is_empty() {
191        dbg!(&type_errs);
192    }
193    // errs.append(&mut type_errs);
194
195    timings.start("lowering");
196    let lowerer = Lowerer::new(type_checker);
197    timings.end("lowering");
198
199    Ok(lowerer?)
200}