Skip to main content

asdf/
parser_ffi.rs

1//! `asdf/parser.h`, `asdf/event.h` and `asdf/yaml.h`: the low-level
2//! event-based parser.
3//!
4//! This is libasdf's streaming interface: rather than building a tree, it
5//! walks a file and reports what it finds -- the version headers, any
6//! comments, the block index, the tree's extent, optionally the YAML events
7//! inside it, then each block, then the end.
8//!
9//! # Ownership
10//!
11//! The header's contract is unusual and is reproduced rather than
12//! simplified:
13//!
14//! - `asdf_parser_parse` hands out an event the *caller* releases with
15//!   `asdf_event_free`.
16//! - `asdf_event_iterate` releases the event it handed out last time before
17//!   producing the next, so a loop over it frees nothing by hand.
18//! - Anything still outstanding is released by `asdf_parser_destroy`, so a
19//!   caller that abandons a parse leaks nothing.
20//!
21//! An event's strings belong to the event and stay valid until it is freed.
22
23use crate::ffi::write_out;
24use alloc::ffi::CString;
25use core::ffi::{CStr, c_char, c_int, c_void};
26
27use asdf_core::events::{Event as CoreEvent, EventOptions, events, render_event};
28use asdf_core::yaml::{YamlEvent, YamlEventKind};
29
30use asdf_core::ErrorCode;
31
32use crate::error_ffi::ErrorState;
33use crate::panic::guard;
34use crate::types::{
35    AsdfEventType, AsdfParserOptFlags, AsdfYamlEventType, asdf_block_header_t, asdf_block_info_t,
36    asdf_parser_cfg_t, asdf_tree_info_t, parser_opt,
37};
38
39/// The storage an event hands C pointers into.
40///
41/// Only the events with an accessor in the public headers need one; the rest
42/// carry [`Payload::None`], and everything about them is still reachable
43/// through the event's `source`.
44#[derive(Debug)]
45enum Payload {
46    /// Nothing to hand out a pointer to.
47    None,
48    /// A comment line, without its leading `#`.
49    Comment(CString),
50    /// The tree's extent, and its text when buffering was asked for.
51    Tree {
52        info: Box<asdf_tree_info_t>,
53        /// Backs `info.buf`. Never read directly -- dropping it would dangle
54        /// the pointer C was handed.
55        #[allow(dead_code)]
56        text: Option<CString>,
57    },
58    /// A YAML sub-event.
59    Yaml(YamlSub),
60    /// A block's position and header.
61    Block(Box<asdf_block_info_t>),
62}
63
64/// A YAML sub-event, with its strings owned.
65#[derive(Debug)]
66struct YamlSub {
67    kind: AsdfYamlEventType,
68    tag: Option<CString>,
69    value: Option<CString>,
70}
71
72/// A parser event. Opaque to C.
73///
74/// The type sits first so that code built against upstream's internal header
75/// -- which is how its own test suite reads events -- sees it where it
76/// expects.
77#[repr(C)]
78#[derive(Debug)]
79pub struct AsdfEvent {
80    event_type: AsdfEventType,
81    payload: Payload,
82    /// The engine's own form of this event, kept so that
83    /// [`asdf_event_print`] can use the engine's renderer rather than a
84    /// second copy of upstream's output format.
85    source: CoreEvent,
86}
87
88/// A parser handle. Opaque to C.
89#[derive(Debug)]
90pub struct AsdfParser {
91    flags: AsdfParserOptFlags,
92    error: ErrorState,
93    /// The file's bytes, once an input is set.
94    buffer: Vec<u8>,
95    /// The name reported in messages, when the input came from a path.
96    filename: Option<CString>,
97    /// The remaining events to produce, from the engine's stream.
98    steps: alloc::collections::VecDeque<CoreEvent>,
99    /// Events handed out and not yet freed.
100    live: Vec<*mut AsdfEvent>,
101    /// The event `asdf_event_iterate` produced last, which it frees on the
102    /// next call.
103    iterated: *mut AsdfEvent,
104    /// Set once the stream has run out.
105    finished: bool,
106}
107
108impl AsdfParser {
109    fn new(flags: AsdfParserOptFlags) -> Self {
110        Self {
111            flags,
112            error: ErrorState::default(),
113            buffer: Vec::new(),
114            filename: None,
115            steps: alloc::collections::VecDeque::new(),
116            live: Vec::new(),
117            iterated: core::ptr::null_mut(),
118            finished: false,
119        }
120    }
121
122    fn emits_yaml(&self) -> bool {
123        self.flags & parser_opt::EMIT_YAML_EVENTS != 0
124    }
125
126    fn buffers_tree(&self) -> bool {
127        self.flags & parser_opt::BUFFER_TREE != 0
128    }
129
130    /// Scan the buffer and lay out the whole event stream.
131    ///
132    /// Returns 0 on success and -1 on failure, which is what the
133    /// `asdf_parser_set_input_*` functions report.
134    fn ingest(&mut self, buffer: Vec<u8>) -> c_int {
135        self.buffer = buffer;
136        self.steps.clear();
137        self.finished = false;
138
139        let options = EventOptions { yaml: self.emits_yaml(), buffer_tree: self.buffers_tree() };
140        match events(&self.buffer, options) {
141            Ok(stream) => {
142                self.steps.extend(stream);
143                self.error.clear();
144                0
145            }
146            Err(e) => {
147                self.error.set_error(&e);
148                -1
149            }
150        }
151    }
152
153    /// Turn the next event from the engine's stream into one the caller owns.
154    fn next_event(&mut self) -> *mut AsdfEvent {
155        let Some(step) = self.steps.pop_front() else {
156            self.finished = true;
157            return core::ptr::null_mut();
158        };
159
160        let built = match step.clone() {
161            CoreEvent::AsdfVersion(_) => Some((AsdfEventType::AsdfVersion, Payload::None)),
162            CoreEvent::StandardVersion(_) => Some((AsdfEventType::StandardVersion, Payload::None)),
163            CoreEvent::Comment(text) => CString::new(text)
164                .ok()
165                .map(|owned| (AsdfEventType::Comment, Payload::Comment(owned))),
166            CoreEvent::BlockIndex(_) => Some((AsdfEventType::BlockIndex, Payload::None)),
167            CoreEvent::TreeStart { start } => {
168                let info = Box::new(asdf_tree_info_t { start, end: 0, buf: core::ptr::null() });
169                Some((AsdfEventType::TreeStart, Payload::Tree { info, text: None }))
170            }
171            CoreEvent::TreeEnd { start, end, text } => {
172                // `ASDF_PARSER_OPT_BUFFER_TREE` asks for the tree's text to
173                // be kept; without it `buf` stays null, as upstream's does.
174                let text = text.and_then(|text| CString::new(text).ok());
175                // `buf` is filled in below, once the event is in its final
176                // home. Deriving it here would leave C holding a pointer that
177                // Rust considers invalid.
178                let info = Box::new(asdf_tree_info_t { start, end, buf: core::ptr::null() });
179                Some((AsdfEventType::TreeEnd, Payload::Tree { info, text }))
180            }
181            CoreEvent::Yaml(event) => Some((AsdfEventType::Yaml, Payload::Yaml(yaml_sub(&event)))),
182            CoreEvent::Block(location) => {
183                let header = &location.header;
184                let info = Box::new(asdf_block_info_t {
185                    index: location.index,
186                    header_pos: i64::try_from(location.header_pos).unwrap_or(i64::MAX),
187                    data_pos: i64::try_from(location.data_pos).unwrap_or(i64::MAX),
188                    header: asdf_block_header_t {
189                        header_size: header.header_size,
190                        flags: header.flags,
191                        compression: header.compression,
192                        allocated_size: header.allocated_size,
193                        used_size: header.used_size,
194                        data_size: header.data_size,
195                        checksum: header.checksum,
196                    },
197                });
198                Some((AsdfEventType::Block, Payload::Block(info)))
199            }
200            CoreEvent::End => Some((AsdfEventType::End, Payload::None)),
201        }
202        .map(|(event_type, payload)| AsdfEvent { event_type, payload, source: step });
203
204        let Some(event) = built else {
205            return core::ptr::null_mut();
206        };
207        let mut boxed = Box::new(event);
208
209        // Point `asdf_tree_info_t.buf` at the buffered tree text, and do it
210        // only now. C reads that field directly, so the pointer has to name
211        // the copy that outlives this call -- and every move of the owning
212        // `CString` on the way here retags its buffer, which invalidates any
213        // pointer taken beforehand. Miri rejects the earlier form; on a real
214        // allocator it happens to work, which is exactly why it needed a
215        // checker to find.
216        if let Payload::Tree { info, text: Some(text) } = &mut boxed.payload {
217            let buf = text.as_ptr();
218            info.buf = buf;
219        }
220
221        let handle = Box::into_raw(boxed);
222        self.live.push(handle);
223        handle
224    }
225
226    /// Drop an event this parser handed out. Unknown pointers are ignored.
227    fn release(&mut self, event: *mut AsdfEvent) {
228        let Some(position) = self.live.iter().position(|e| *e == event) else {
229            return;
230        };
231        self.live.remove(position);
232        if self.iterated == event {
233            self.iterated = core::ptr::null_mut();
234        }
235        drop(unsafe { Box::from_raw(event) });
236    }
237}
238
239impl Drop for AsdfParser {
240    fn drop(&mut self) {
241        for event in core::mem::take(&mut self.live) {
242            drop(unsafe { Box::from_raw(event) });
243        }
244    }
245}
246
247/// Convert an engine YAML event into its C-visible form.
248fn yaml_sub(event: &YamlEvent) -> YamlSub {
249    let kind = match event.kind {
250        YamlEventKind::StreamStart => AsdfYamlEventType::StreamStart,
251        YamlEventKind::StreamEnd => AsdfYamlEventType::StreamEnd,
252        YamlEventKind::DocumentStart => AsdfYamlEventType::DocumentStart,
253        YamlEventKind::DocumentEnd => AsdfYamlEventType::DocumentEnd,
254        YamlEventKind::MappingStart => AsdfYamlEventType::MappingStart,
255        YamlEventKind::MappingEnd => AsdfYamlEventType::MappingEnd,
256        YamlEventKind::SequenceStart => AsdfYamlEventType::SequenceStart,
257        YamlEventKind::SequenceEnd => AsdfYamlEventType::SequenceEnd,
258        YamlEventKind::Scalar => AsdfYamlEventType::Scalar,
259        YamlEventKind::Alias => AsdfYamlEventType::Alias,
260    };
261    YamlSub {
262        kind,
263        tag: event.tag.as_ref().and_then(|t| CString::new(t.as_str()).ok()),
264        value: event.value.as_ref().and_then(|v| CString::new(v.as_str()).ok()),
265    }
266}
267
268fn parser_ref<'a>(parser: *const AsdfParser) -> Option<&'a AsdfParser> {
269    unsafe { crate::ffi::as_ref(parser) }
270}
271
272fn parser_mut<'a>(parser: *mut AsdfParser) -> Option<&'a mut AsdfParser> {
273    unsafe { crate::ffi::as_mut(parser) }
274}
275
276fn event_ref<'a>(event: *const AsdfEvent) -> Option<&'a AsdfEvent> {
277    unsafe { crate::ffi::as_ref(event) }
278}
279
280// ---- Parser lifecycle ------------------------------------------------
281
282/// Create a parser.
283///
284/// `config` may be null, which selects the defaults: no YAML events and no
285/// tree buffering.
286///
287/// # Safety
288/// `config` must be null or point to a valid `asdf_parser_cfg_t`. The result
289/// must be released with [`asdf_parser_destroy`].
290#[unsafe(no_mangle)]
291pub unsafe extern "C" fn asdf_parser_create(config: *const asdf_parser_cfg_t) -> *mut AsdfParser {
292    guard("asdf_parser_create", core::ptr::null_mut(), || {
293        let flags = if config.is_null() { 0 } else { unsafe { &*config }.flags };
294        Box::into_raw(Box::new(AsdfParser::new(flags)))
295    })
296}
297
298/// Release a parser and everything it handed out.
299///
300/// # Safety
301/// `parser` must be null or a handle from [`asdf_parser_create`] that has not
302/// already been destroyed.
303#[unsafe(no_mangle)]
304pub unsafe extern "C" fn asdf_parser_destroy(parser: *mut AsdfParser) {
305    guard("asdf_parser_destroy", (), || {
306        if parser.is_null() {
307            return;
308        }
309        drop(unsafe { Box::from_raw(parser) });
310    })
311}
312
313/// Read a file and lay out its event stream. Returns 0 on success.
314///
315/// # Safety
316/// `parser` must be a valid handle and `filename` a valid string.
317#[unsafe(no_mangle)]
318pub unsafe extern "C" fn asdf_parser_set_input_file(
319    parser: *mut AsdfParser,
320    filename: *const c_char,
321) -> c_int {
322    guard("asdf_parser_set_input_file", -1, || {
323        let Some(state) = parser_mut(parser) else {
324            return -1;
325        };
326        if filename.is_null() {
327            state.error.set(ErrorCode::InvalidArgument as i32, "no filename given");
328            return -1;
329        }
330        let name = unsafe { CStr::from_ptr(filename) };
331        let path = std::path::PathBuf::from(name.to_string_lossy().into_owned());
332        match std::fs::read(&path) {
333            Ok(bytes) => {
334                state.filename = Some(name.to_owned());
335                state.ingest(bytes)
336            }
337            Err(e) => {
338                state.error.set_system(e.raw_os_error().unwrap_or(0));
339                -1
340            }
341        }
342    })
343}
344
345/// Read an already-open stream. Returns 0 on success.
346///
347/// The whole stream is read up front, so `fp` may be closed once this
348/// returns. `filename` is optional and is used only in messages.
349///
350/// # Safety
351/// `fp` must be a `FILE *` open for reading, positioned where the ASDF file
352/// begins.
353#[unsafe(no_mangle)]
354pub unsafe extern "C" fn asdf_parser_set_input_fp(
355    parser: *mut AsdfParser,
356    fp: *mut c_void,
357    filename: *const c_char,
358) -> c_int {
359    guard("asdf_parser_set_input_fp", -1, || {
360        let Some(state) = parser_mut(parser) else {
361            return -1;
362        };
363        if fp.is_null() {
364            state.error.set(ErrorCode::InvalidArgument as i32, "no stream given");
365            return -1;
366        }
367        if !filename.is_null() {
368            state.filename = Some(unsafe { CStr::from_ptr(filename) }.to_owned());
369        }
370        match read_stream(fp) {
371            Some(bytes) => state.ingest(bytes),
372            None => {
373                state.error.set(ErrorCode::System as i32, "could not read the stream");
374                -1
375            }
376        }
377    })
378}
379
380/// Read a buffer. Returns 0 on success.
381///
382/// The bytes are copied, so `buf` need not outlive the call.
383///
384/// # Safety
385/// `buf` must point to at least `size` readable bytes.
386#[unsafe(no_mangle)]
387pub unsafe extern "C" fn asdf_parser_set_input_mem(
388    parser: *mut AsdfParser,
389    buf: *const c_void,
390    size: usize,
391) -> c_int {
392    guard("asdf_parser_set_input_mem", -1, || {
393        let Some(state) = parser_mut(parser) else {
394            return -1;
395        };
396        if buf.is_null() {
397            state.error.set(ErrorCode::InvalidArgument as i32, "no buffer given");
398            return -1;
399        }
400        let bytes = unsafe { core::slice::from_raw_parts(buf.cast::<u8>(), size) }.to_vec();
401        state.ingest(bytes)
402    })
403}
404
405/// Read a whole `FILE *` through `fread`.
406fn read_stream(fp: *mut c_void) -> Option<Vec<u8>> {
407    let mut out = Vec::new();
408    let mut chunk = [0u8; 64 * 1024];
409    loop {
410        let read =
411            unsafe { libc::fread(chunk.as_mut_ptr().cast::<c_void>(), 1, chunk.len(), fp.cast()) };
412        if read > 0 {
413            out.extend_from_slice(&chunk[..read]);
414        }
415        if read < chunk.len() {
416            // Short read: either the end, or an error worth reporting.
417            if unsafe { libc::ferror(fp.cast()) } != 0 {
418                return None;
419            }
420            return Some(out);
421        }
422    }
423}
424
425/// Produce the next event, or null at the end of the stream.
426///
427/// The caller owns the event and releases it with [`asdf_event_free`].
428///
429/// # Safety
430/// `parser` must be null or a valid handle.
431#[unsafe(no_mangle)]
432pub unsafe extern "C" fn asdf_parser_parse(parser: *mut AsdfParser) -> *mut AsdfEvent {
433    guard("asdf_parser_parse", core::ptr::null_mut(), || match parser_mut(parser) {
434        Some(state) => state.next_event(),
435        None => core::ptr::null_mut(),
436    })
437}
438
439// ---- Parser errors ---------------------------------------------------
440
441/// Whether the parser has recorded an error.
442///
443/// # Safety
444/// `parser` must be null or a valid handle.
445#[unsafe(no_mangle)]
446pub unsafe extern "C" fn asdf_parser_has_error(parser: *const AsdfParser) -> bool {
447    guard("asdf_parser_has_error", false, || {
448        parser_ref(parser).is_some_and(|p| p.error.code() != 0)
449    })
450}
451
452/// The recorded error message, or null.
453///
454/// # Safety
455/// `parser` must be null or a valid handle. The string is owned by the parser
456/// and is invalidated by the next error it records.
457#[unsafe(no_mangle)]
458pub unsafe extern "C" fn asdf_parser_get_error(parser: *const AsdfParser) -> *const c_char {
459    guard("asdf_parser_get_error", core::ptr::null(), || match parser_ref(parser) {
460        Some(p) => p.error.message_ptr(),
461        None => core::ptr::null(),
462    })
463}
464
465/// The recorded error code.
466///
467/// # Safety
468/// `parser` must be null or a valid handle.
469#[unsafe(no_mangle)]
470pub unsafe extern "C" fn asdf_parser_error_code(parser: *const AsdfParser) -> c_int {
471    guard("asdf_parser_error_code", 0, || parser_ref(parser).map_or(0, |p| p.error.code()))
472}
473
474/// The recorded `errno`, meaningful only for a system error.
475///
476/// # Safety
477/// `parser` must be null or a valid handle.
478#[unsafe(no_mangle)]
479pub unsafe extern "C" fn asdf_parser_error_errno(parser: *const AsdfParser) -> c_int {
480    guard("asdf_parser_error_errno", 0, || parser_ref(parser).map_or(0, |p| p.error.errno()))
481}
482
483// ---- Events ----------------------------------------------------------
484
485/// An event's type; `ASDF_NONE_EVENT` for a null event.
486///
487/// # Safety
488/// `event` must be null or a valid event handle.
489#[unsafe(no_mangle)]
490pub unsafe extern "C" fn asdf_event_type(event: *mut AsdfEvent) -> AsdfEventType {
491    guard("asdf_event_type", AsdfEventType::None, || match event_ref(event) {
492        Some(e) => e.event_type,
493        None => AsdfEventType::None,
494    })
495}
496
497/// The name of an event type, as its enum member is spelled.
498///
499/// # Safety
500/// Always safe; the result is a `'static` string.
501#[unsafe(no_mangle)]
502pub extern "C" fn asdf_event_type_name(event_type: c_int) -> *const c_char {
503    // Taken as an `int`: C may pass any value, and holding one outside the
504    // enum's range in a Rust enum is undefined behaviour.
505    match AsdfEventType::from_i32(event_type) {
506        Some(known) => known.name().as_ptr(),
507        None => c"ASDF_UNKNOWN_EVENT".as_ptr(),
508    }
509}
510
511/// A comment event's text, without its leading `#`, or null.
512///
513/// # Safety
514/// `event` must be null or a valid event handle. The string is owned by the
515/// event.
516#[unsafe(no_mangle)]
517pub unsafe extern "C" fn asdf_event_comment(event: *const AsdfEvent) -> *const c_char {
518    guard("asdf_event_comment", core::ptr::null(), || match event_ref(event).map(|e| &e.payload) {
519        Some(Payload::Comment(text)) => text.as_ptr(),
520        _ => core::ptr::null(),
521    })
522}
523
524/// A tree event's extent, or null for any other event.
525///
526/// # Safety
527/// `event` must be null or a valid event handle. The struct is owned by the
528/// event.
529#[unsafe(no_mangle)]
530pub unsafe extern "C" fn asdf_event_tree_info(event: *const AsdfEvent) -> *const asdf_tree_info_t {
531    guard("asdf_event_tree_info", core::ptr::null(), || {
532        match event_ref(event).map(|e| &e.payload) {
533            Some(Payload::Tree { info, .. }) => core::ptr::from_ref::<asdf_tree_info_t>(info),
534            _ => core::ptr::null(),
535        }
536    })
537}
538
539/// A block event's position and header, or null for any other event.
540///
541/// # Safety
542/// `event` must be null or a valid event handle. The struct is owned by the
543/// event.
544#[unsafe(no_mangle)]
545pub unsafe extern "C" fn asdf_event_block_info(
546    event: *const AsdfEvent,
547) -> *const asdf_block_info_t {
548    guard("asdf_event_block_info", core::ptr::null(), || {
549        match event_ref(event).map(|e| &e.payload) {
550            Some(Payload::Block(info)) => core::ptr::from_ref::<asdf_block_info_t>(info),
551            _ => core::ptr::null(),
552        }
553    })
554}
555
556/// Produce the next event, releasing the one this call produced last.
557///
558/// This is the loop-friendly form: nothing needs freeing by hand.
559///
560/// # Safety
561/// `parser` must be null or a valid handle.
562#[unsafe(no_mangle)]
563pub unsafe extern "C" fn asdf_event_iterate(parser: *mut AsdfParser) -> *mut AsdfEvent {
564    guard("asdf_event_iterate", core::ptr::null_mut(), || {
565        let Some(state) = parser_mut(parser) else {
566            return core::ptr::null_mut();
567        };
568        if !state.iterated.is_null() {
569            let previous = state.iterated;
570            state.release(previous);
571        }
572        let event = state.next_event();
573        state.iterated = event;
574        event
575    })
576}
577
578/// Release an event from [`asdf_parser_parse`].
579///
580/// # Safety
581/// `parser` must be the parser that produced `event`, which must not already
582/// have been freed. Both may be null.
583#[unsafe(no_mangle)]
584pub unsafe extern "C" fn asdf_event_free(parser: *mut AsdfParser, event: *mut AsdfEvent) {
585    guard("asdf_event_free", (), || {
586        if let Some(state) = parser_mut(parser) {
587            state.release(event);
588        }
589    })
590}
591
592/// Print an event, in the format `asdf info --events` uses.
593///
594/// # Safety
595/// `event` must be a valid event handle and `file` a `FILE *` open for
596/// writing; a null `file` selects `stdout`.
597#[unsafe(no_mangle)]
598pub unsafe extern "C" fn asdf_event_print(
599    event: *const AsdfEvent,
600    file: *mut c_void,
601    verbose: bool,
602) {
603    guard("asdf_event_print", (), || {
604        let Some(state) = event_ref(event) else {
605            return;
606        };
607        write_c_stream(file, &render_event(&state.source, verbose));
608    })
609}
610
611/// Write to a C `FILE *`, falling back to `stdout` when it is null.
612fn write_c_stream(file: *mut c_void, text: &str) {
613    let stream = if file.is_null() { unsafe { stdout_stream() } } else { file.cast() };
614    if stream.is_null() {
615        return;
616    }
617    unsafe {
618        libc::fwrite(text.as_ptr().cast::<c_void>(), 1, text.len(), stream);
619    }
620}
621
622/// The process's `stdout`, as a `FILE *`.
623unsafe fn stdout_stream() -> *mut libc::FILE {
624    // `stdout` is a macro in C; libc exposes it as a function on every
625    // platform this builds for.
626    unsafe { libc::fdopen(1, c"w".as_ptr()) }
627}
628
629// ---- YAML sub-events -------------------------------------------------
630
631/// The YAML sub-event type, or `ASDF_YAML_NONE_EVENT`.
632///
633/// # Safety
634/// `event` must be null or a valid event handle.
635#[unsafe(no_mangle)]
636pub unsafe extern "C" fn asdf_yaml_event_type(event: *const AsdfEvent) -> AsdfYamlEventType {
637    guard("asdf_yaml_event_type", AsdfYamlEventType::None, || {
638        match event_ref(event).map(|e| &e.payload) {
639            Some(Payload::Yaml(sub)) => sub.kind,
640            _ => AsdfYamlEventType::None,
641        }
642    })
643}
644
645/// A human-readable name for the YAML sub-event type.
646///
647/// Empty for an event that carries none, as upstream's does.
648///
649/// # Safety
650/// `event` must be null or a valid event handle.
651#[unsafe(no_mangle)]
652pub unsafe extern "C" fn asdf_yaml_event_type_text(event: *const AsdfEvent) -> *const c_char {
653    guard("asdf_yaml_event_type_text", c"".as_ptr(), || {
654        match event_ref(event).map(|e| &e.payload) {
655            Some(Payload::Yaml(sub)) => sub.kind.text().as_ptr(),
656            _ => c"".as_ptr(),
657        }
658    })
659}
660
661/// A scalar sub-event's raw text, or null.
662///
663/// # Safety
664/// `event` must be null or a valid event handle and `lenp` writable or null.
665/// The string is owned by the event.
666#[unsafe(no_mangle)]
667pub unsafe extern "C" fn asdf_yaml_event_scalar_value(
668    event: *const AsdfEvent,
669    lenp: *mut usize,
670) -> *const c_char {
671    guard("asdf_yaml_event_scalar_value", core::ptr::null(), || {
672        let text = match event_ref(event).map(|e| &e.payload) {
673            Some(Payload::Yaml(sub)) => sub.value.as_ref(),
674            _ => None,
675        };
676        match text {
677            Some(value) => {
678                if !lenp.is_null() {
679                    unsafe { write_out(lenp, value.as_bytes().len()) };
680                }
681                value.as_ptr()
682            }
683            None => {
684                if !lenp.is_null() {
685                    unsafe { write_out(lenp, 0) };
686                }
687                core::ptr::null()
688            }
689        }
690    })
691}
692
693/// A YAML sub-event's tag, or null.
694///
695/// # Safety
696/// See [`asdf_yaml_event_scalar_value`].
697#[unsafe(no_mangle)]
698pub unsafe extern "C" fn asdf_yaml_event_tag(
699    event: *const AsdfEvent,
700    lenp: *mut usize,
701) -> *const c_char {
702    guard("asdf_yaml_event_tag", core::ptr::null(), || {
703        let tag = match event_ref(event).map(|e| &e.payload) {
704            Some(Payload::Yaml(sub)) => sub.tag.as_ref(),
705            _ => None,
706        };
707        match tag {
708            Some(value) => {
709                if !lenp.is_null() {
710                    unsafe { write_out(lenp, value.as_bytes().len()) };
711                }
712                value.as_ptr()
713            }
714            None => {
715                if !lenp.is_null() {
716                    unsafe { write_out(lenp, 0) };
717                }
718                core::ptr::null()
719            }
720        }
721    })
722}
723
724#[cfg(test)]
725mod tests {
726    use super::*;
727    use crate::types::parser_opt;
728
729    /// A small file with a tree and one block-free body.
730    fn sample() -> Vec<u8> {
731        let mut buf = Vec::new();
732        buf.extend_from_slice(b"#ASDF 1.0.0\n#ASDF_STANDARD 1.6.0\n#a note\n");
733        buf.extend_from_slice(b"%YAML 1.1\n%TAG ! tag:stsci.edu:asdf/\n--- !core/asdf-1.1.0\n");
734        buf.extend_from_slice(b"n: 1\nname: probe\n");
735        buf.extend_from_slice(b"...\n");
736        buf
737    }
738
739    struct Parser(*mut AsdfParser);
740    impl Drop for Parser {
741        fn drop(&mut self) {
742            unsafe { asdf_parser_destroy(self.0) };
743        }
744    }
745
746    fn parse(bytes: &[u8], flags: AsdfParserOptFlags) -> Parser {
747        let cfg = asdf_parser_cfg_t { flags, log: core::ptr::null_mut() };
748        let parser = unsafe { asdf_parser_create(&cfg) };
749        assert!(!parser.is_null());
750        assert_eq!(
751            unsafe { asdf_parser_set_input_mem(parser, bytes.as_ptr().cast(), bytes.len()) },
752            0
753        );
754        Parser(parser)
755    }
756
757    /// Every event type in order, walked with `asdf_event_iterate`.
758    fn event_types(parser: &Parser) -> Vec<AsdfEventType> {
759        let mut out = Vec::new();
760        loop {
761            let event = unsafe { asdf_event_iterate(parser.0) };
762            if event.is_null() {
763                return out;
764            }
765            out.push(unsafe { asdf_event_type(event) });
766        }
767    }
768
769    #[test]
770    fn reports_the_expected_event_sequence() {
771        let bytes = sample();
772        let parser = parse(&bytes, 0);
773        assert_eq!(
774            event_types(&parser),
775            vec![
776                AsdfEventType::AsdfVersion,
777                AsdfEventType::StandardVersion,
778                AsdfEventType::Comment,
779                AsdfEventType::TreeStart,
780                AsdfEventType::TreeEnd,
781                AsdfEventType::End,
782            ]
783        );
784    }
785
786    #[test]
787    fn yaml_events_appear_only_when_asked_for() {
788        let bytes = sample();
789        let quiet = parse(&bytes, 0);
790        assert!(!event_types(&quiet).contains(&AsdfEventType::Yaml));
791
792        let loud = parse(&bytes, parser_opt::EMIT_YAML_EVENTS);
793        let types = event_types(&loud);
794        assert!(types.contains(&AsdfEventType::Yaml));
795        // Stream start/end, document start/end, mapping start/end, and two
796        // key/value pairs.
797        assert_eq!(types.iter().filter(|t| **t == AsdfEventType::Yaml).count(), 10);
798    }
799
800    #[test]
801    fn comment_text_drops_its_leading_hash() {
802        let bytes = sample();
803        let parser = parse(&bytes, 0);
804        loop {
805            let event = unsafe { asdf_event_iterate(parser.0) };
806            assert!(!event.is_null(), "no comment event in the stream");
807            if unsafe { asdf_event_type(event) } == AsdfEventType::Comment {
808                let text = unsafe { asdf_event_comment(event) };
809                assert_eq!(unsafe { CStr::from_ptr(text) }, c"a note");
810                return;
811            }
812        }
813    }
814
815    #[test]
816    fn tree_info_brackets_the_yaml() {
817        let bytes = sample();
818        let parser = parse(&bytes, 0);
819        let mut start = None;
820        let mut end = None;
821        loop {
822            let event = unsafe { asdf_event_iterate(parser.0) };
823            if event.is_null() {
824                break;
825            }
826            match unsafe { asdf_event_type(event) } {
827                AsdfEventType::TreeStart => {
828                    let info = unsafe { &*asdf_event_tree_info(event) };
829                    start = Some(info.start);
830                }
831                AsdfEventType::TreeEnd => {
832                    let info = unsafe { &*asdf_event_tree_info(event) };
833                    end = Some(info.end);
834                }
835                _ => {}
836            }
837        }
838        let (start, end) = (start.expect("tree start"), end.expect("tree end"));
839        // The tree starts at the `%YAML` directive, just past the two `#`
840        // header lines and the comment.
841        assert_eq!(&bytes[start..start + 5], b"%YAML");
842        assert!(end > start && end <= bytes.len());
843    }
844
845    #[test]
846    fn buffered_tree_hands_back_its_text() {
847        let bytes = sample();
848        let parser = parse(&bytes, parser_opt::BUFFER_TREE);
849        loop {
850            let event = unsafe { asdf_event_iterate(parser.0) };
851            assert!(!event.is_null());
852            if unsafe { asdf_event_type(event) } == AsdfEventType::TreeEnd {
853                let info = unsafe { &*asdf_event_tree_info(event) };
854                assert!(!info.buf.is_null());
855                let text = unsafe { CStr::from_ptr(info.buf) }.to_string_lossy().into_owned();
856                assert!(text.starts_with("%YAML 1.1"), "unexpected tree text: {text}");
857                assert!(text.contains("name: probe"));
858                return;
859            }
860        }
861    }
862
863    #[test]
864    fn accessors_reject_events_of_the_wrong_type() {
865        let bytes = sample();
866        let parser = parse(&bytes, 0);
867        let event = unsafe { asdf_event_iterate(parser.0) };
868        assert_eq!(unsafe { asdf_event_type(event) }, AsdfEventType::AsdfVersion);
869        assert!(unsafe { asdf_event_comment(event) }.is_null());
870        assert!(unsafe { asdf_event_tree_info(event) }.is_null());
871        assert!(unsafe { asdf_event_block_info(event) }.is_null());
872        assert_eq!(unsafe { asdf_yaml_event_type(event) }, AsdfYamlEventType::None);
873    }
874
875    #[test]
876    fn parse_hands_out_events_the_caller_frees() {
877        let bytes = sample();
878        let parser = parse(&bytes, 0);
879        // Unlike `iterate`, `parse` keeps every event alive until freed, so
880        // two of them may be held at once.
881        let first = unsafe { asdf_parser_parse(parser.0) };
882        let second = unsafe { asdf_parser_parse(parser.0) };
883        assert!(!first.is_null() && !second.is_null());
884        assert_eq!(unsafe { asdf_event_type(first) }, AsdfEventType::AsdfVersion);
885        assert_eq!(unsafe { asdf_event_type(second) }, AsdfEventType::StandardVersion);
886        unsafe { asdf_event_free(parser.0, first) };
887        unsafe { asdf_event_free(parser.0, second) };
888        // Anything left outstanding is released with the parser.
889        let _ = unsafe { asdf_parser_parse(parser.0) };
890    }
891
892    #[test]
893    fn event_type_names_match_the_enum_spelling() {
894        let name = asdf_event_type_name(AsdfEventType::BlockIndex as c_int);
895        assert_eq!(unsafe { CStr::from_ptr(name) }, c"ASDF_BLOCK_INDEX_EVENT");
896        // A value C could pass but the enum does not name is reported as
897        // unknown rather than indexing past the end of the table.
898        assert_eq!(unsafe { CStr::from_ptr(asdf_event_type_name(99)) }, c"ASDF_UNKNOWN_EVENT");
899        assert_eq!(unsafe { CStr::from_ptr(asdf_event_type_name(-1)) }, c"ASDF_UNKNOWN_EVENT");
900    }
901
902    #[test]
903    fn a_bad_buffer_is_reported_not_fatal() {
904        let cfg = asdf_parser_cfg_t { flags: 0, log: core::ptr::null_mut() };
905        let parser = Parser(unsafe { asdf_parser_create(&cfg) });
906        let junk = b"not an asdf file at all\n";
907        assert_eq!(
908            unsafe { asdf_parser_set_input_mem(parser.0, junk.as_ptr().cast(), junk.len()) },
909            -1
910        );
911        assert!(unsafe { asdf_parser_has_error(parser.0) });
912        assert!(!unsafe { asdf_parser_get_error(parser.0) }.is_null());
913        assert_ne!(unsafe { asdf_parser_error_code(parser.0) }, 0);
914    }
915
916    #[test]
917    fn null_handles_are_tolerated() {
918        assert!(unsafe { asdf_parser_parse(core::ptr::null_mut()) }.is_null());
919        assert!(unsafe { asdf_event_iterate(core::ptr::null_mut()) }.is_null());
920        assert!(!unsafe { asdf_parser_has_error(core::ptr::null()) });
921        assert_eq!(unsafe { asdf_event_type(core::ptr::null_mut()) }, AsdfEventType::None);
922        assert!(unsafe { asdf_event_comment(core::ptr::null()) }.is_null());
923        unsafe { asdf_parser_destroy(core::ptr::null_mut()) };
924        unsafe { asdf_event_free(core::ptr::null_mut(), core::ptr::null_mut()) };
925        let mut len = 7usize;
926        assert!(unsafe { asdf_yaml_event_tag(core::ptr::null(), &mut len) }.is_null());
927        assert_eq!(len, 0, "the length must be cleared even when there is no tag");
928    }
929
930    #[test]
931    fn missing_input_files_are_reported() {
932        let cfg = asdf_parser_cfg_t { flags: 0, log: core::ptr::null_mut() };
933        let parser = Parser(unsafe { asdf_parser_create(&cfg) });
934        let path = CString::new("/nonexistent/definitely-not-here.asdf").unwrap();
935        assert_eq!(unsafe { asdf_parser_set_input_file(parser.0, path.as_ptr()) }, -1);
936        assert!(unsafe { asdf_parser_has_error(parser.0) });
937        assert_ne!(unsafe { asdf_parser_error_errno(parser.0) }, 0);
938    }
939}