e757b453a4
Introduces a new AST emitter module responsible for serializing AST nodes back into Myc source code. This emitter preserves leading comments and whitespace, enabling a round-trip guarantee for the parser and emitter. The documentation in `docs/BNF.md` has been updated to reflect the new syntax rules for comments, including single-line comments (`;`) and documentation comments (`;;`). The rules clarify that comments must be whole lines and that inline comments are disallowed. The lexer and AST node structures have been updated to accommodate `CommentLine` variants, allowing for different types of comments (regular, documentation, and blank lines) to be stored and processed. A new test suite `tests/comment_roundtrip.rs` has been added to verify the round-trip property of the parser and emitter across all example `.myc` files. This test ensures that parsing source code, emitting it, and then parsing the emitted code again results in the same output string. The `ast.rs` binary now includes a `--emit` flag that uses the new emitter to output the parsed source code, facilitating debugging and testing of the emitter itself.
200 lines
5.8 KiB
Rust
200 lines
5.8 KiB
Rust
use clap::Parser;
|
|
use myc::ast::compiler::emitter;
|
|
use myc::ast::environment::Environment;
|
|
use myc::ast::parser::Parser as MycParser;
|
|
use myc::utils::tester;
|
|
use std::fs;
|
|
use std::io::{self, IsTerminal, Read};
|
|
use std::path::PathBuf;
|
|
|
|
#[derive(Parser)]
|
|
#[command(author, version, about = "MYC AST Compiler & Benchmarker", long_about = None)]
|
|
struct Cli {
|
|
/// The script file to run or benchmark
|
|
#[arg(value_name = "FILE")]
|
|
file: Option<PathBuf>,
|
|
|
|
/// Run a script string directly
|
|
#[arg(short, long)]
|
|
eval: Option<String>,
|
|
|
|
/// Run benchmarks (Only allowed in Release mode)
|
|
#[arg(short, long)]
|
|
bench: bool,
|
|
|
|
/// Update the benchmark baseline (Only allowed in Release mode)
|
|
#[arg(short, long)]
|
|
update_bench: bool,
|
|
|
|
/// Dump the compiled AST
|
|
#[arg(short, long)]
|
|
dump: bool,
|
|
|
|
/// Run with TracingObserver enabled
|
|
#[arg(short, long)]
|
|
trace: bool,
|
|
|
|
/// Library directories to load before execution
|
|
#[arg(short, long)]
|
|
lib: Vec<PathBuf>,
|
|
|
|
/// Disable optimization
|
|
#[arg(long)]
|
|
no_opt: bool,
|
|
|
|
/// Start MCP server (stdio transport, for LLM integration)
|
|
#[arg(long)]
|
|
mcp: bool,
|
|
|
|
/// Emit the parsed source back as Myc code (round-trip test)
|
|
#[arg(long)]
|
|
emit: bool,
|
|
}
|
|
|
|
fn main() {
|
|
let cli = Cli::parse();
|
|
|
|
if cli.mcp {
|
|
if let Err(e) = myc::ast::mcp_server::run() {
|
|
eprintln!("MCP server error: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
return;
|
|
}
|
|
let mut env = Environment::new();
|
|
env.optimization = !cli.no_opt;
|
|
|
|
// Load libraries (Now just search paths)
|
|
for lib_path in &cli.lib {
|
|
env.add_search_path(lib_path);
|
|
}
|
|
|
|
if cli.bench || cli.update_bench {
|
|
if cfg!(debug_assertions) {
|
|
eprintln!("❌ ERROR: Benchmarks must be run in Release mode!");
|
|
eprintln!(" Use: cargo run --release --bin ast -- --bench");
|
|
std::process::exit(1);
|
|
}
|
|
|
|
println!("🚀 Running benchmarks in RELEASE mode...\n");
|
|
let filter = cli
|
|
.file
|
|
.as_ref()
|
|
.and_then(|p| p.file_name())
|
|
.map(|n| n.to_string_lossy().to_string());
|
|
let results = tester::run_benchmarks(cli.update_bench, filter.as_deref());
|
|
for res in results {
|
|
let diff = res
|
|
.diff_pct
|
|
.map_or(String::new(), |d| format!(" ({:+.1}%)", d));
|
|
println!("{}: {} - {:?}{}", res.status, res.name, res.median, diff);
|
|
}
|
|
return;
|
|
}
|
|
|
|
// Determine the source code to execute
|
|
let source = if let Some(script_str) = cli.eval {
|
|
Some(script_str)
|
|
} else if let Some(file_path) = cli.file {
|
|
if file_path.to_str() == Some("-") {
|
|
read_stdin()
|
|
} else {
|
|
match fs::read_to_string(&file_path) {
|
|
Ok(content) => Some(content),
|
|
Err(e) => {
|
|
eprintln!("Error reading file {:?}: {}", file_path, e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
} else if !io::stdin().is_terminal() {
|
|
read_stdin()
|
|
} else {
|
|
None
|
|
};
|
|
|
|
if let Some(content) = source {
|
|
if cli.emit {
|
|
let mut parser = MycParser::new(&content);
|
|
let syntax_ast = parser.parse_expression();
|
|
if parser.diagnostics.has_errors() {
|
|
for diag in &parser.diagnostics.items {
|
|
eprintln!("ERROR: {}", diag.message);
|
|
}
|
|
std::process::exit(1);
|
|
}
|
|
print!("{}", emitter::emit(&syntax_ast));
|
|
} else if cli.dump {
|
|
match env.dump_ast(&content) {
|
|
Ok(dump) => println!("{}", dump),
|
|
Err(e) => eprintln!("Error dumping AST: {}", e),
|
|
}
|
|
} else if cli.trace {
|
|
execute_trace(&env, &content);
|
|
} else {
|
|
execute(&env, &content);
|
|
}
|
|
} else {
|
|
println!("MYC AST Compiler CLI. Use --help for usage.");
|
|
}
|
|
}
|
|
|
|
fn read_stdin() -> Option<String> {
|
|
let mut buffer = String::new();
|
|
match io::stdin().read_to_string(&mut buffer) {
|
|
Ok(_) => Some(buffer),
|
|
Err(e) => {
|
|
eprintln!("Error reading from stdin: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn execute(env: &Environment, source: &str) {
|
|
let result = env.compile(source);
|
|
for diag in &result.diagnostics.items {
|
|
let level = format!("{:?}", diag.level).to_uppercase();
|
|
let loc = diag
|
|
.identity
|
|
.as_ref()
|
|
.and_then(|id| id.location)
|
|
.map(|l| format!(" at {}:{}", l.line, l.col))
|
|
.unwrap_or_default();
|
|
eprintln!("{}{} : {}", level, loc, diag.message);
|
|
}
|
|
|
|
if result.diagnostics.has_errors() {
|
|
std::process::exit(1);
|
|
}
|
|
|
|
match env.run_script_compiled(result.ast.unwrap()) {
|
|
Ok(res) => println!("{}", res),
|
|
Err(e) => {
|
|
eprintln!("Runtime Error: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|
|
|
|
fn execute_trace(env: &Environment, source: &str) {
|
|
match env.run_debug(source) {
|
|
Ok((Ok(result), logs)) => {
|
|
for line in logs {
|
|
println!("{}", line);
|
|
}
|
|
println!("Result: {}", result);
|
|
}
|
|
Ok((Err(e), logs)) => {
|
|
for line in logs {
|
|
println!("{}", line);
|
|
}
|
|
eprintln!("Runtime Error: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
Err(e) => {
|
|
eprintln!("Compilation Error: {}", e);
|
|
std::process::exit(1);
|
|
}
|
|
}
|
|
}
|