This commit is contained in:
Nilton Constantino 2026-01-27 10:55:10 +00:00
parent d62503eb5a
commit b5cafe1a4a
No known key found for this signature in database
7 changed files with 181 additions and 34 deletions

14
Cargo.lock generated
View File

@ -1923,6 +1923,7 @@ dependencies = [
"prometeu-core", "prometeu-core",
"serde", "serde",
"serde_json", "serde_json",
"tempfile",
] ]
[[package]] [[package]]
@ -2320,6 +2321,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "tempfile"
version = "3.24.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "655da9c7eb6305c55742045d5a8d2037996d61d8de95806335c7c86ce0f82e9c"
dependencies = [
"fastrand",
"getrandom",
"once_cell",
"rustix 1.1.3",
"windows-sys 0.61.2",
]
[[package]] [[package]]
name = "termcolor" name = "termcolor"
version = "1.4.1" version = "1.4.1"

View File

@ -26,3 +26,6 @@ clap = { version = "4.5.54", features = ["derive"] }
serde = { version = "1.0.228", features = ["derive"] } serde = { version = "1.0.228", features = ["derive"] }
serde_json = "1.0.149" serde_json = "1.0.149"
anyhow = "1.0.100" anyhow = "1.0.100"
[dev-dependencies]
tempfile = "3.10.1"

View File

@ -0,0 +1,43 @@
use serde::{Deserialize, Serialize};
use std::path::{Path, PathBuf};
use anyhow::Result;
#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct ProjectConfig {
pub script_fe: String,
pub entry: PathBuf,
}
impl ProjectConfig {
pub fn load(project_dir: &Path) -> Result<Self> {
let config_path = project_dir.join("prometeu.json");
let content = std::fs::read_to_string(config_path)?;
let config: ProjectConfig = serde_json::from_str(&content)?;
Ok(config)
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::fs;
use tempfile::tempdir;
#[test]
fn test_load_valid_config() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("prometeu.json");
fs::write(
config_path,
r#"{
"script_fe": "pbs",
"entry": "main.pbs"
}"#,
)
.unwrap();
let config = ProjectConfig::load(dir.path()).unwrap();
assert_eq!(config.script_fe, "pbs");
assert_eq!(config.entry, PathBuf::from("main.pbs"));
}
}

View File

@ -2,3 +2,4 @@ pub mod diagnostics;
pub mod spans; pub mod spans;
pub mod files; pub mod files;
pub mod symbols; pub mod symbols;
pub mod config;

View File

@ -4,6 +4,7 @@
//! It handles the transition between different compiler phases: Frontend -> IR -> Backend. //! It handles the transition between different compiler phases: Frontend -> IR -> Backend.
use crate::backend; use crate::backend;
use crate::common::config::ProjectConfig;
use crate::common::files::FileManager; use crate::common::files::FileManager;
use crate::common::symbols::Symbol; use crate::common::symbols::Symbol;
use crate::frontends::Frontend; use crate::frontends::Frontend;
@ -13,6 +14,7 @@ use std::path::Path;
/// The result of a successful compilation process. /// The result of a successful compilation process.
/// It contains the final binary and the metadata needed for debugging. /// It contains the final binary and the metadata needed for debugging.
#[derive(Debug)]
pub struct CompilationUnit { pub struct CompilationUnit {
/// The raw binary data formatted as Prometeu ByteCode (PBC). /// The raw binary data formatted as Prometeu ByteCode (PBC).
/// This is what gets written to a `.pbc` file. /// This is what gets written to a `.pbc` file.
@ -51,35 +53,87 @@ impl CompilationUnit {
/// # Example /// # Example
/// ```no_run /// ```no_run
/// use std::path::Path; /// use std::path::Path;
/// let entry = Path::new("src/main.ts"); /// let project_dir = Path::new(".");
/// let unit = prometeu_compiler::compiler::compile(entry).expect("Failed to compile"); /// let unit = prometeu_compiler::compiler::compile(project_dir).expect("Failed to compile");
/// unit.export(Path::new("build/program.pbc"), true, true).unwrap(); /// unit.export(Path::new("build/program.pbc"), true, true).unwrap();
/// ``` /// ```
pub fn compile(entry: &Path) -> Result<CompilationUnit> { pub fn compile(project_dir: &Path) -> Result<CompilationUnit> {
let mut file_manager = FileManager::new(); let config = ProjectConfig::load(project_dir)?;
// 1. Select Frontend
// The _frontend is responsible for parsing source code and producing the IR.
let _frontend: Box<dyn Frontend> = match config.script_fe.as_str() {
"pbs" => anyhow::bail!("Frontend 'pbs' not yet implemented"),
_ => anyhow::bail!("Invalid frontend: {}", config.script_fe),
};
// 1. Select Frontend (Currently only TS is supported) #[allow(unreachable_code, unused_variables, unused_mut)]
// The frontend is responsible for parsing source code and producing the IR. {
let frontend = /** ??? **/; let entry = project_dir.join(&config.entry);
let mut file_manager = FileManager::new();
// 2. Compile to IR (Intermediate Representation)
// This step abstracts away source-specific syntax (like TypeScript) into a // 2. Compile to IR (Intermediate Representation)
// generic set of instructions that the backend can understand. // This step abstracts away source-specific syntax (like TypeScript) into a
let ir_module = frontend.compile_to_ir(entry, &mut file_manager) // generic set of instructions that the backend can understand.
.map_err(|bundle| anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len()))?; let ir_module = _frontend.compile_to_ir(&entry, &mut file_manager)
.map_err(|bundle| anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len()))?;
// 3. IR Validation
// Ensures the generated IR is sound and doesn't violate any VM constraints
// before we spend time generating bytecode.
ir::validate::validate_module(&ir_module)
.map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?;
// 4. Emit Bytecode
// The backend takes the validated IR and produces the final binary executable.
let result = backend::emit_module(&ir_module, &file_manager)?;
// 3. IR Validation Ok(CompilationUnit {
// Ensures the generated IR is sound and doesn't violate any VM constraints rom: result.rom,
// before we spend time generating bytecode. symbols: result.symbols,
ir::validate::validate_module(&ir_module) })
.map_err(|bundle| anyhow::anyhow!("IR Validation failed: {:?}", bundle))?; }
}
// 4. Emit Bytecode
// The backend takes the validated IR and produces the final binary executable. #[cfg(test)]
let result = backend::emit_module(&ir_module, &file_manager)?; mod tests {
use super::*;
Ok(CompilationUnit { use std::fs;
rom: result.rom, use tempfile::tempdir;
symbols: result.symbols,
}) #[test]
fn test_invalid_frontend() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("prometeu.json");
fs::write(
config_path,
r#"{
"script_fe": "invalid",
"entry": "main.pbs"
}"#,
)
.unwrap();
let result = compile(dir.path());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Invalid frontend: invalid"));
}
#[test]
fn test_frontend_pbs_not_implemented() {
let dir = tempdir().unwrap();
let config_path = dir.path().join("prometeu.json");
fs::write(
config_path,
r#"{
"script_fe": "pbs",
"entry": "main.pbs"
}"#,
)
.unwrap();
let result = compile(dir.path());
assert!(result.is_err());
assert!(result.unwrap_err().to_string().contains("Frontend 'pbs' not yet implemented"));
}
} }

View File

@ -96,12 +96,11 @@ pub fn run() -> Result<()> {
match cli.command { match cli.command {
Commands::Build { Commands::Build {
project_dir, project_dir,
entry,
out, out,
emit_disasm, emit_disasm,
emit_symbols, emit_symbols,
..
} => { } => {
let entry = entry.unwrap_or_else(|| project_dir.join("src/main.ts"));
let build_dir = project_dir.join("build"); let build_dir = project_dir.join("build");
let out = out.unwrap_or_else(|| build_dir.join("program.pbc")); let out = out.unwrap_or_else(|| build_dir.join("program.pbc"));
@ -110,18 +109,15 @@ pub fn run() -> Result<()> {
} }
println!("Building project at {:?}", project_dir); println!("Building project at {:?}", project_dir);
println!("Entry: {:?}", entry);
println!("Output: {:?}", out); println!("Output: {:?}", out);
let compilation_unit = compiler::compile(&entry)?; let compilation_unit = compiler::compile(&project_dir)?;
compilation_unit.export(&out, emit_disasm, emit_symbols)?; compilation_unit.export(&out, emit_disasm, emit_symbols)?;
} }
Commands::Verify { project_dir } => { Commands::Verify { project_dir } => {
let entry = project_dir.join("src/main.ts");
println!("Verifying project at {:?}", project_dir); println!("Verifying project at {:?}", project_dir);
println!("Entry: {:?}", entry);
compiler::compile(&entry)?; compiler::compile(&project_dir)?;
println!("Project is valid!"); println!("Project is valid!");
} }
} }

View File

@ -0,0 +1,36 @@
use std::fs;
use tempfile::tempdir;
use prometeu_compiler::compiler;
#[test]
fn test_project_root_and_entry_resolution() {
let dir = tempdir().unwrap();
let project_dir = dir.path();
// Create prometeu.json
fs::write(
project_dir.join("prometeu.json"),
r#"{
"script_fe": "pbs",
"entry": "src/main.pbs"
}"#,
).unwrap();
// Create src directory and main.pbs
fs::create_dir(project_dir.join("src")).unwrap();
fs::write(project_dir.join("src/main.pbs"), "").unwrap();
// Call compile
let result = compiler::compile(project_dir);
// It should fail with "Frontend 'pbs' not yet implemented"
// but ONLY after successfully loading the config and resolving the entry.
match result {
Err(e) => {
let msg = e.to_string();
assert!(msg.contains("Frontend 'pbs' not yet implemented"), "Unexpected error: {}", msg);
}
Ok(_) => panic!("Should have failed as pbs is not implemented yet"),
}
}