ass_core/parser/script/parse.rs
1//! Parsing entry points and context-aware single-line parsing.
2//!
3//! Provides [`Script::parse`], the [`Script::builder`] constructor, and the
4//! format-aware helpers used to parse individual style and event lines against
5//! the script's stored format definitions.
6
7use crate::parser::ast::{Event, Style};
8use crate::parser::errors::ParseError;
9use crate::parser::main::Parser;
10use crate::Result;
11
12use super::builder::ScriptBuilder;
13use super::Script;
14
15impl<'a> Script<'a> {
16 /// Parse ASS script from source text with zero-copy design
17 ///
18 /// Performs full validation and partial error recovery. Returns script
19 /// even with errors - check `issues()` for problems.
20 ///
21 /// # Performance
22 ///
23 /// Target <5ms for 1KB typical scripts. Uses minimal allocations via
24 /// zero-copy spans referencing input text.
25 ///
26 /// # Example
27 ///
28 /// ```rust
29 /// # use ass_core::parser::Script;
30 /// let script = Script::parse("[Script Info]\nTitle: Test")?;
31 /// assert_eq!(script.version(), ass_core::ScriptVersion::AssV4);
32 /// # Ok::<(), Box<dyn std::error::Error>>(())
33 /// ```
34 ///
35 /// # Errors
36 ///
37 /// Returns an error if the source contains malformed section headers or
38 /// other unrecoverable syntax errors.
39 pub fn parse(source: &'a str) -> Result<Self> {
40 let parser = Parser::new(source);
41 Ok(parser.parse())
42 }
43
44 /// Create a new script builder for parsing with optional extensions
45 ///
46 /// The builder pattern allows configuration of parsing options including
47 /// extension registry for custom tag handlers and section processors.
48 ///
49 /// # Example
50 ///
51 /// ```rust
52 /// # use ass_core::parser::Script;
53 /// # #[cfg(feature = "plugins")]
54 /// # use ass_core::plugin::ExtensionRegistry;
55 /// # #[cfg(feature = "plugins")]
56 /// let registry = ExtensionRegistry::new();
57 /// # #[cfg(feature = "plugins")]
58 /// let script = Script::builder()
59 /// .with_registry(®istry)
60 /// .parse("[Script Info]\nTitle: Test")?;
61 /// # #[cfg(not(feature = "plugins"))]
62 /// let script = Script::builder()
63 /// .parse("[Script Info]\nTitle: Test")?;
64 /// # Ok::<(), Box<dyn std::error::Error>>(())
65 /// ```
66 #[must_use]
67 pub const fn builder() -> ScriptBuilder<'a> {
68 ScriptBuilder::new()
69 }
70
71 /// Parse a style line with context from the script
72 ///
73 /// Uses the script's stored format for [V4+ Styles] section if available,
74 /// otherwise falls back to default format.
75 ///
76 /// # Arguments
77 ///
78 /// * `line` - The style line to parse (without "Style:" prefix)
79 /// * `line_number` - The line number for error reporting
80 ///
81 /// # Returns
82 ///
83 /// Parsed Style or error if the line is malformed
84 ///
85 /// # Errors
86 ///
87 /// Returns [`ParseError::InsufficientFields`] if the line has fewer fields than expected
88 pub fn parse_style_line_with_context(
89 &self,
90 line: &'a str,
91 line_number: u32,
92 ) -> core::result::Result<Style<'a>, ParseError> {
93 use crate::parser::sections::StylesParser;
94
95 let format = self.styles_format.as_deref().unwrap_or(&[
96 "Name",
97 "Fontname",
98 "Fontsize",
99 "PrimaryColour",
100 "SecondaryColour",
101 "OutlineColour",
102 "BackColour",
103 "Bold",
104 "Italic",
105 "Underline",
106 "StrikeOut",
107 "ScaleX",
108 "ScaleY",
109 "Spacing",
110 "Angle",
111 "BorderStyle",
112 "Outline",
113 "Shadow",
114 "Alignment",
115 "MarginL",
116 "MarginR",
117 "MarginV",
118 "Encoding",
119 ]);
120
121 StylesParser::parse_style_line(line, format, line_number)
122 }
123
124 /// Parse an event line with context from the script
125 ///
126 /// Uses the script's stored format for `[Events\]` section if available,
127 /// otherwise falls back to default format.
128 ///
129 /// # Arguments
130 ///
131 /// * `line` - The event line to parse (e.g., "Dialogue: 0,0:00:00.00,0:00:05.00,Default,,0,0,0,,Text")
132 /// * `line_number` - The line number for error reporting
133 ///
134 /// # Returns
135 ///
136 /// Parsed Event or error if the line is malformed
137 ///
138 /// # Errors
139 ///
140 /// Returns [`ParseError::InvalidEventType`] if the line doesn't start with a valid event type
141 /// Returns [`ParseError::InsufficientFields`] if the line has fewer fields than expected
142 pub fn parse_event_line_with_context(
143 &self,
144 line: &'a str,
145 line_number: u32,
146 ) -> core::result::Result<Event<'a>, ParseError> {
147 use crate::parser::sections::EventsParser;
148
149 let format = self.events_format.as_deref().unwrap_or(&[
150 "Layer", "Start", "End", "Style", "Name", "MarginL", "MarginR", "MarginV", "Effect",
151 "Text",
152 ]);
153
154 EventsParser::parse_event_line(line, format, line_number)
155 }
156}