1#[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 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 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 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 let lockfile_toml = toml::to_string(&lockfile)?;
144 let lockfile_path = path.join("petr.lock");
145 fs::write(lockfile_path, lockfile_toml)?;
146 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 timings.start("symbol resolution");
174 let (resolution_errs, resolved) = petr_resolve::resolve_symbols(ast, interner, dependencies);
175 timings.end("symbol resolution");
176
177 if !resolution_errs.is_empty() {
179 dbg!(&resolution_errs);
180 }
181 timings.start("type check");
184 let (type_errs, type_checker) = petr_typecheck::type_check(resolved);
186
187 timings.end("type check");
188
189 if !type_errs.is_empty() {
191 dbg!(&type_errs);
192 }
193 timings.start("lowering");
196 let lowerer = Lowerer::new(type_checker);
197 timings.end("lowering");
198
199 Ok(lowerer?)
200}