dev/pbs #8
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -1923,6 +1923,7 @@ dependencies = [
|
||||
"prometeu-core",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tempfile",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@ -2320,6 +2321,19 @@ dependencies = [
|
||||
"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]]
|
||||
name = "termcolor"
|
||||
version = "1.4.1"
|
||||
|
||||
@ -26,3 +26,6 @@ clap = { version = "4.5.54", features = ["derive"] }
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.149"
|
||||
anyhow = "1.0.100"
|
||||
|
||||
[dev-dependencies]
|
||||
tempfile = "3.10.1"
|
||||
|
||||
43
crates/prometeu-compiler/src/common/config.rs
Normal file
43
crates/prometeu-compiler/src/common/config.rs
Normal 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"));
|
||||
}
|
||||
}
|
||||
@ -2,3 +2,4 @@ pub mod diagnostics;
|
||||
pub mod spans;
|
||||
pub mod files;
|
||||
pub mod symbols;
|
||||
pub mod config;
|
||||
|
||||
@ -4,6 +4,7 @@
|
||||
//! It handles the transition between different compiler phases: Frontend -> IR -> Backend.
|
||||
|
||||
use crate::backend;
|
||||
use crate::common::config::ProjectConfig;
|
||||
use crate::common::files::FileManager;
|
||||
use crate::common::symbols::Symbol;
|
||||
use crate::frontends::Frontend;
|
||||
@ -13,6 +14,7 @@ use std::path::Path;
|
||||
|
||||
/// The result of a successful compilation process.
|
||||
/// It contains the final binary and the metadata needed for debugging.
|
||||
#[derive(Debug)]
|
||||
pub struct CompilationUnit {
|
||||
/// The raw binary data formatted as Prometeu ByteCode (PBC).
|
||||
/// This is what gets written to a `.pbc` file.
|
||||
@ -51,35 +53,87 @@ impl CompilationUnit {
|
||||
/// # Example
|
||||
/// ```no_run
|
||||
/// use std::path::Path;
|
||||
/// let entry = Path::new("src/main.ts");
|
||||
/// let unit = prometeu_compiler::compiler::compile(entry).expect("Failed to compile");
|
||||
/// let project_dir = Path::new(".");
|
||||
/// let unit = prometeu_compiler::compiler::compile(project_dir).expect("Failed to compile");
|
||||
/// unit.export(Path::new("build/program.pbc"), true, true).unwrap();
|
||||
/// ```
|
||||
pub fn compile(entry: &Path) -> Result<CompilationUnit> {
|
||||
let mut file_manager = FileManager::new();
|
||||
pub fn compile(project_dir: &Path) -> Result<CompilationUnit> {
|
||||
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)
|
||||
// The frontend is responsible for parsing source code and producing the IR.
|
||||
let frontend = /** ??? **/;
|
||||
|
||||
// 2. Compile to IR (Intermediate Representation)
|
||||
// This step abstracts away source-specific syntax (like TypeScript) into a
|
||||
// generic set of instructions that the backend can understand.
|
||||
let ir_module = frontend.compile_to_ir(entry, &mut file_manager)
|
||||
.map_err(|bundle| anyhow::anyhow!("Compilation failed with {} errors", bundle.diagnostics.len()))?;
|
||||
#[allow(unreachable_code, unused_variables, unused_mut)]
|
||||
{
|
||||
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
|
||||
// generic set of instructions that the backend can understand.
|
||||
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
|
||||
// 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)?;
|
||||
|
||||
Ok(CompilationUnit {
|
||||
rom: result.rom,
|
||||
symbols: result.symbols,
|
||||
})
|
||||
Ok(CompilationUnit {
|
||||
rom: result.rom,
|
||||
symbols: result.symbols,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::tempdir;
|
||||
|
||||
#[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"));
|
||||
}
|
||||
}
|
||||
|
||||
@ -96,12 +96,11 @@ pub fn run() -> Result<()> {
|
||||
match cli.command {
|
||||
Commands::Build {
|
||||
project_dir,
|
||||
entry,
|
||||
out,
|
||||
emit_disasm,
|
||||
emit_symbols,
|
||||
..
|
||||
} => {
|
||||
let entry = entry.unwrap_or_else(|| project_dir.join("src/main.ts"));
|
||||
let build_dir = project_dir.join("build");
|
||||
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!("Entry: {:?}", entry);
|
||||
println!("Output: {:?}", out);
|
||||
|
||||
let compilation_unit = compiler::compile(&entry)?;
|
||||
let compilation_unit = compiler::compile(&project_dir)?;
|
||||
compilation_unit.export(&out, emit_disasm, emit_symbols)?;
|
||||
}
|
||||
Commands::Verify { project_dir } => {
|
||||
let entry = project_dir.join("src/main.ts");
|
||||
println!("Verifying project at {:?}", project_dir);
|
||||
println!("Entry: {:?}", entry);
|
||||
|
||||
compiler::compile(&entry)?;
|
||||
compiler::compile(&project_dir)?;
|
||||
println!("Project is valid!");
|
||||
}
|
||||
}
|
||||
|
||||
36
crates/prometeu-compiler/tests/config_integration.rs
Normal file
36
crates/prometeu-compiler/tests/config_integration.rs
Normal 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"),
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user