ought_spec/parser/mod.rs
1//! Public parsing interface for ought specs.
2//!
3//! The [`Parser`] trait is the abstraction boundary; [`OughtMdParser`] is
4//! the canonical implementation for `.ought.md` files. The state-machine
5//! driving the actual parse, plus all pure helpers, live in sibling
6//! submodules below this one.
7
8use std::path::Path;
9
10use crate::types::{ParseError, Spec};
11
12mod clauses;
13mod ids;
14mod keywords;
15mod metadata;
16mod state;
17
18/// The public interface for parsing spec files into the ought IR.
19///
20/// Mirrors `ought_run::Runner`: one trait, one concrete implementation today
21/// (`OughtMdParser`), and room to add more spec formats without breaking
22/// callers. Most consumers should take `&dyn Parser` (or `impl Parser`)
23/// rather than naming a concrete type.
24pub trait Parser: Send + Sync {
25 /// Parse a spec file from disk. The default implementation reads the
26 /// file and delegates to [`Parser::parse_string`], so format-specific
27 /// parsers usually only need to implement `parse_string`.
28 fn parse_file(&self, path: &Path) -> Result<Spec, Vec<ParseError>> {
29 let content = std::fs::read_to_string(path).map_err(|e| {
30 vec![ParseError {
31 file: path.to_path_buf(),
32 line: 0,
33 message: format!("failed to read file: {}", e),
34 }]
35 })?;
36 self.parse_string(&content, path)
37 }
38
39 /// Parse a spec from an in-memory string, using `path` only as the
40 /// source-location label for error messages and source locations.
41 fn parse_string(&self, content: &str, path: &Path) -> Result<Spec, Vec<ParseError>>;
42
43 /// Short, stable name for this parser (e.g. `"ought.md"`). Used for
44 /// diagnostics and, eventually, format dispatch.
45 fn name(&self) -> &str;
46}
47
48/// Canonical parser for `.ought.md` files: CommonMark markdown with bold
49/// deontic keywords (`**MUST**`, `**SHOULD**`, …), GIVEN nesting,
50/// OTHERWISE chains, and MUST BY duration literals.
51///
52/// Pure Rust, no LLM dependency.
53#[derive(Debug, Default, Clone, Copy)]
54pub struct OughtMdParser;
55
56impl Parser for OughtMdParser {
57 fn parse_string(&self, content: &str, path: &Path) -> Result<Spec, Vec<ParseError>> {
58 state::parse_string(content, path)
59 }
60
61 fn name(&self) -> &str {
62 "ought.md"
63 }
64}