Implement evaluator materializer and CLI

This commit is contained in:
2026-06-16 02:10:54 +09:00
parent ddcee75c8d
commit c33c484bac
6 changed files with 1082 additions and 11 deletions
+162 -2
View File
@@ -1,3 +1,163 @@
fn main() {
println!("Decodal {}", decodal_core::version());
use std::{env, fs, process::ExitCode};
use decodal_core::{Data, Diagnostic, Engine, parse_source};
fn main() -> ExitCode {
match run() {
Ok(()) => ExitCode::SUCCESS,
Err(error) => {
print_diagnostic(&error);
ExitCode::FAILURE
}
}
}
fn run() -> Result<(), Diagnostic> {
let mut args = env::args().skip(1);
let first = args.next().unwrap_or_else(|| String::from("--help"));
match first.as_str() {
"--help" | "-h" => {
print_help();
Ok(())
}
"--version" | "-V" => {
println!("Decodal {}", decodal_core::version());
Ok(())
}
"check" => {
let path = args.next().unwrap_or_else(|| String::from("-"));
let source = read_source(&path)?;
let parsed = parse_source(&source)?;
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
let value = engine.eval_root()?;
engine.materialize(&value)?;
println!("ok");
Ok(())
}
"eval" | "materialize" => {
let path = args.next().unwrap_or_else(|| String::from("-"));
materialize_file(&path)
}
path => materialize_file(path),
}
}
fn materialize_file(path: &str) -> Result<(), Diagnostic> {
let source = read_source(path)?;
let parsed = parse_source(&source)?;
let mut engine = Engine::from_parse(parsed.ast, parsed.root);
let value = engine.eval_root()?;
let data = engine.materialize(&value)?;
print_data(&data, 0);
println!();
Ok(())
}
fn read_source(path: &str) -> Result<String, Diagnostic> {
if path == "-" {
use std::io::Read;
let mut source = String::new();
std::io::stdin()
.read_to_string(&mut source)
.map_err(|error| {
Diagnostic::new(
decodal_core::DiagnosticKind::Import,
decodal_core::Span::default(),
format!("failed to read stdin: {error}"),
)
})?;
Ok(source)
} else {
fs::read_to_string(path).map_err(|error| {
Diagnostic::new(
decodal_core::DiagnosticKind::Import,
decodal_core::Span::default(),
format!("failed to read `{path}`: {error}"),
)
})
}
}
fn print_help() {
println!("Decodal - Deferred Constraint Data Language");
println!();
println!("Usage:");
println!(" decodal <file.dcdl> Materialize a DCDL file");
println!(" decodal eval <file.dcdl> Materialize a DCDL file");
println!(" decodal check <file.dcdl> Evaluate and materialize without printing data");
println!(" decodal - Read DCDL source from stdin");
}
fn print_diagnostic(error: &Diagnostic) {
eprintln!(
"error[{kind:?}] {start}..{end}: {message}",
kind = error.kind,
start = error.span.start,
end = error.span.end,
message = error.message,
);
}
fn print_data(data: &Data, indent: usize) {
match data {
Data::String(value) => print_string(value),
Data::Int(value) => print!("{value}"),
Data::Float(value) => print!("{value}"),
Data::Bool(value) => print!("{value}"),
Data::Array(items) => {
print!("[");
if !items.is_empty() {
println!();
for (index, item) in items.iter().enumerate() {
print_indent(indent + 2);
print_data(item, indent + 2);
if index + 1 != items.len() {
print!(",");
}
println!();
}
print_indent(indent);
}
print!("]");
}
Data::Object(fields) => {
print!("{{");
if !fields.is_empty() {
println!();
for (index, field) in fields.iter().enumerate() {
print_indent(indent + 2);
print_string(&field.name);
print!(": ");
print_data(&field.value, indent + 2);
if index + 1 != fields.len() {
print!(",");
}
println!();
}
print_indent(indent);
}
print!("}}");
}
}
}
fn print_indent(indent: usize) {
for _ in 0..indent {
print!(" ");
}
}
fn print_string(value: &str) {
print!("\"");
for ch in value.chars() {
match ch {
'"' => print!("\\\""),
'\\' => print!("\\\\"),
'\n' => print!("\\n"),
'\r' => print!("\\r"),
'\t' => print!("\\t"),
ch => print!("{ch}"),
}
}
print!("\"");
}