End-To-End Workflows
This chapter ties the public modules together into practical usage patterns.
Workflow 1: Parse A Real C File
#![allow(unused)]
fn main() {
use parc::driver::{parse, Config};
let parsed = parse(&Config::default(), "include/demo.h")?;
println!("items: {}", parsed.unit.0.len());
Ok::<(), parc::driver::Error>(())
}
This is the baseline path when:
- includes matter
- macros matter
- compiler predefined types or macros matter
The result gives you both the AST and the exact preprocessed source PARC saw.
Workflow 2: Parse A Preprocessed Snapshot
#![allow(unused)]
fn main() {
use parc::driver::{parse_preprocessed, Config};
let source = std::fs::read_to_string("snapshots/demo.i").unwrap();
let parsed = parse_preprocessed(&Config::default(), source)?;
Ok::<(), parc::driver::SyntaxError>(())
}
Use this when:
- reproducing a parse bug
- building deterministic tests
- integrating with a nonstandard build system
This workflow isolates parser behavior from preprocessor invocation behavior.
Workflow 3: Parse A Fragment In Tests
#![allow(unused)]
fn main() {
use parc::driver::Flavor;
use parc::parse;
let decl = parse::declaration("typedef unsigned long word_t;", Flavor::StdC11)?;
let expr = parse::expression("ptr->field + 1", Flavor::GnuC11)?;
Ok::<(), parc::parse::ParseError>(())
}
This is the right workflow for:
- unit tests
- grammar debugging
- editor or language-server experiments
Workflow 4: Build A Syntax Analyzer
#![allow(unused)]
fn main() {
use parc::driver::{parse, Config};
use parc::visit::{self, Visit};
use parc::{ast, span};
struct ReturnCounter {
count: usize,
}
impl<'ast> Visit<'ast> for ReturnCounter {
fn visit_statement(&mut self, node: &'ast ast::Statement, span: &'ast span::Span) {
if matches!(node, ast::Statement::Return(_)) {
self.count += 1;
}
visit::visit_statement(self, node, span);
}
}
let parsed = parse(&Config::default(), "src/main.c")?;
let mut counter = ReturnCounter { count: 0 };
counter.visit_translation_unit(&parsed.unit);
println!("return statements: {}", counter.count);
Ok::<(), parc::driver::Error>(())
}
This is the normal PARC analyzer pattern:
- parse
- traverse
- inspect spans and locations
- emit your own diagnostics or analysis data
Workflow 5: Build Diagnostics With Real File Locations
#![allow(unused)]
fn main() {
use parc::driver::{parse, Config};
use parc::loc::get_location_for_offset;
let parsed = parse(&Config::default(), "src/main.c")?;
for item in &parsed.unit.0 {
let (loc, _) = get_location_for_offset(&parsed.source, item.span.start);
println!("top-level item starts at {}:{}", loc.file, loc.line);
}
Ok::<(), parc::driver::Error>(())
}
Use this when your users care about original file locations rather than raw byte offsets in the preprocessed stream.
Workflow 6: Debug A New Syntax Form
#![allow(unused)]
fn main() {
use parc::driver::Flavor;
use parc::parse;
use parc::print::Printer;
use parc::visit::Visit;
let expr = parse::expression("({ int x = 1; x + 1; })", Flavor::GnuC11)?;
let mut out = String::new();
Printer::new(&mut out).visit_expression(&expr.node, &expr.span);
println!("{}", out);
Ok::<(), parc::parse::ParseError>(())
}
This is the most effective loop when exploring unfamiliar AST shapes.
Workflow 7: Regression-Test A Parse Failure
A practical bug workflow is:
- capture the smallest failing input
- decide whether preprocessing is relevant
- add a
parse_apitest or a reftest - patch the grammar
- verify the printed AST or error outcome
That keeps parser changes concrete and reviewable.