mirror of
https://github.com/fabro-sh/fabro.git
synced 2026-09-05 08:10:39 +00:00
Extract parse_ast() and use Write sink in parse_command
- Extract shared strip+parse+trailing-check into parser::parse_ast() so both parser::parse() and parse_command reuse it - Accept impl Write in parse_command so tests verify actual JSON output instead of re-parsing independently Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
parent
57dd00a1a5
commit
dc348d553e
2 changed files with 34 additions and 31 deletions
|
|
@ -1,5 +1,4 @@
|
|||
use crate::error::ArcError;
|
||||
use crate::parser::{grammar, lexer};
|
||||
use std::io::Write;
|
||||
|
||||
use super::{read_dot_file, ParseArgs};
|
||||
|
||||
|
|
@ -9,21 +8,15 @@ use super::{read_dot_file, ParseArgs};
|
|||
///
|
||||
/// Returns an error if the file cannot be read, parsed, or contains trailing content.
|
||||
pub fn parse_command(args: &ParseArgs) -> anyhow::Result<()> {
|
||||
let stdout = std::io::stdout();
|
||||
parse_command_to(args, stdout.lock())
|
||||
}
|
||||
|
||||
fn parse_command_to(args: &ParseArgs, mut out: impl Write) -> anyhow::Result<()> {
|
||||
let source = read_dot_file(&args.workflow)?;
|
||||
let stripped = lexer::strip_comments(&source);
|
||||
let (rest, ast) = grammar::parse_dot_graph(&stripped)
|
||||
.map_err(|e| ArcError::Parse(format!("grammar error: {e}")))?;
|
||||
|
||||
let remaining = rest.trim();
|
||||
if !remaining.is_empty() {
|
||||
return Err(ArcError::Parse(format!(
|
||||
"unexpected trailing content: {:?}",
|
||||
&remaining[..remaining.len().min(50)]
|
||||
))
|
||||
.into());
|
||||
}
|
||||
|
||||
println!("{}", serde_json::to_string_pretty(&ast)?);
|
||||
let ast = crate::parser::parse_ast(&source)?;
|
||||
serde_json::to_writer_pretty(&mut out, &ast)?;
|
||||
writeln!(out)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
|
|
@ -50,16 +43,10 @@ mod tests {
|
|||
let args = ParseArgs {
|
||||
workflow: tmp.path().to_path_buf(),
|
||||
};
|
||||
let result = parse_command(&args);
|
||||
assert!(result.is_ok(), "expected Ok but got: {result:?}");
|
||||
|
||||
// Re-run and capture stdout by reading the file through the parser directly
|
||||
let source = std::fs::read_to_string(tmp.path()).unwrap();
|
||||
let stripped = lexer::strip_comments(&source);
|
||||
let (_, ast) = grammar::parse_dot_graph(&stripped).unwrap();
|
||||
let json = serde_json::to_string_pretty(&ast).unwrap();
|
||||
let deserialized: DotGraph = serde_json::from_str(&json).unwrap();
|
||||
let mut buf = Vec::new();
|
||||
parse_command_to(&args, &mut buf).unwrap();
|
||||
|
||||
let deserialized: DotGraph = serde_json::from_slice(&buf).unwrap();
|
||||
assert_eq!(deserialized.name, "Hello");
|
||||
assert_eq!(deserialized.statements.len(), 3);
|
||||
}
|
||||
|
|
@ -72,7 +59,7 @@ mod tests {
|
|||
let args = ParseArgs {
|
||||
workflow: tmp.path().to_path_buf(),
|
||||
};
|
||||
let result = parse_command(&args);
|
||||
let result = parse_command_to(&args, Vec::new());
|
||||
assert!(result.is_err(), "expected Err for invalid syntax");
|
||||
}
|
||||
|
||||
|
|
@ -81,7 +68,7 @@ mod tests {
|
|||
let args = ParseArgs {
|
||||
workflow: PathBuf::from("/tmp/nonexistent_parse_test_12345.dot"),
|
||||
};
|
||||
let result = parse_command(&args);
|
||||
let result = parse_command_to(&args, Vec::new());
|
||||
assert!(result.is_err(), "expected Err for missing file");
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,16 +6,18 @@ pub mod semantic;
|
|||
use crate::error::ArcError;
|
||||
use crate::graph::types::Graph;
|
||||
|
||||
/// Parse a DOT source string into a semantic `Graph`.
|
||||
use self::ast::DotGraph;
|
||||
|
||||
/// Parse a DOT source string into a raw `DotGraph` AST.
|
||||
///
|
||||
/// Strips comments, parses the grammar, and performs semantic transformation
|
||||
/// (expanding chained edges, applying defaults, flattening subgraphs).
|
||||
/// Strips comments, parses the grammar, and validates there is no
|
||||
/// trailing content. Does NOT perform semantic transformation.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the input is not valid DOT syntax or contains
|
||||
/// trailing content after the graph definition.
|
||||
pub fn parse(input: &str) -> Result<Graph, ArcError> {
|
||||
pub fn parse_ast(input: &str) -> Result<DotGraph, ArcError> {
|
||||
let stripped = lexer::strip_comments(input);
|
||||
let (rest, dot_graph) = grammar::parse_dot_graph(&stripped)
|
||||
.map_err(|e| ArcError::Parse(format!("grammar error: {e}")))?;
|
||||
|
|
@ -28,6 +30,20 @@ pub fn parse(input: &str) -> Result<Graph, ArcError> {
|
|||
)));
|
||||
}
|
||||
|
||||
Ok(dot_graph)
|
||||
}
|
||||
|
||||
/// Parse a DOT source string into a semantic `Graph`.
|
||||
///
|
||||
/// Strips comments, parses the grammar, and performs semantic transformation
|
||||
/// (expanding chained edges, applying defaults, flattening subgraphs).
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Returns an error if the input is not valid DOT syntax or contains
|
||||
/// trailing content after the graph definition.
|
||||
pub fn parse(input: &str) -> Result<Graph, ArcError> {
|
||||
let dot_graph = parse_ast(input)?;
|
||||
semantic::ast_to_graph(&dot_graph)
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue