pr01
This commit is contained in:
parent
d62503eb5a
commit
b5cafe1a4a
14
Cargo.lock
generated
14
Cargo.lock
generated
@ -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"
|
||||||
|
|||||||
@ -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"
|
||||||
|
|||||||
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 spans;
|
||||||
pub mod files;
|
pub mod files;
|
||||||
pub mod symbols;
|
pub mod symbols;
|
||||||
|
pub mod config;
|
||||||
|
|||||||
@ -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"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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!");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
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