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