dtrace_parser/
lib.rs

1//! A small library for parsing DTrace provider files.
2
3// Copyright 2021 Oxide Computer Company
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9//     http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17use pest::iterators::{Pair, Pairs};
18use pest_derive::Parser;
19use std::collections::HashSet;
20use std::convert::TryFrom;
21use std::fs;
22use std::path::Path;
23use thiserror::Error;
24
25type PestError = pest::error::Error<Rule>;
26
27/// Type representing errors that occur when parsing a D file.
28#[derive(Error, Debug)]
29pub enum DTraceError {
30    #[error("Unexpected token type, expected {expected:?}, found {found:?}")]
31    UnexpectedToken { expected: Rule, found: Rule },
32    #[error("This set of pairs contains no tokens")]
33    EmptyPairsIterator,
34    #[error("Provider and probe name pairs must be unique: duplicated \"{0:?}\"")]
35    DuplicateProbeName((String, String)),
36    #[error("The provider name \"{0}\" is invalid")]
37    InvalidProviderName(String),
38    #[error("The probe name \"{0}\" is invalid")]
39    InvalidProbeName(String),
40    #[error(transparent)]
41    IO(#[from] std::io::Error),
42    #[error("Input is not a valid DTrace provider definition:\n{0}")]
43    ParseError(#[from] Box<PestError>),
44}
45
46#[derive(Parser, Debug)]
47#[grammar = "dtrace.pest"]
48struct DTraceParser;
49
50// Helper which verifies that the given `pest::Pair` conforms to the expected grammar rule.
51fn expect_token(pair: &Pair<'_, Rule>, rule: Rule) -> Result<(), DTraceError> {
52    if pair.as_rule() == rule {
53        Ok(())
54    } else {
55        Err(DTraceError::UnexpectedToken {
56            expected: rule,
57            found: pair.as_rule(),
58        })
59    }
60}
61
62/// The bit-width of an integer data type
63#[derive(Clone, Copy, Debug, PartialEq)]
64pub enum BitWidth {
65    Bit8,
66    Bit16,
67    Bit32,
68    Bit64,
69    /// The width of the native pointer type.
70    Pointer,
71}
72
73/// The signed-ness of an integer data type
74#[derive(Clone, Copy, Debug, PartialEq)]
75pub enum Sign {
76    Signed,
77    Unsigned,
78}
79
80/// An integer data type
81#[derive(Clone, Copy, Debug, PartialEq)]
82pub struct Integer {
83    pub sign: Sign,
84    pub width: BitWidth,
85}
86
87const RUST_TYPE_PREFIX: &str = "::std::os::raw::c_";
88
89impl Integer {
90    fn width_to_c_str(&self) -> &'static str {
91        match self.width {
92            BitWidth::Bit8 => "8",
93            BitWidth::Bit16 => "16",
94            BitWidth::Bit32 => "32",
95            BitWidth::Bit64 => "64",
96            #[cfg(target_pointer_width = "32")]
97            BitWidth::Pointer => "32",
98            #[cfg(target_pointer_width = "64")]
99            BitWidth::Pointer => "64",
100            #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
101            BitWidth::Pointer => compile_error!("Unsupported pointer width"),
102        }
103    }
104
105    pub fn to_c_type(&self) -> String {
106        let prefix = match self.sign {
107            Sign::Unsigned => "u",
108            _ => "",
109        };
110        format!("{prefix}int{}_t", self.width_to_c_str())
111    }
112
113    pub fn to_rust_ffi_type(&self) -> String {
114        let ty = match (self.sign, self.width) {
115            (Sign::Unsigned, BitWidth::Bit8) => "uchar",
116            (Sign::Unsigned, BitWidth::Bit16) => "ushort",
117            (Sign::Unsigned, BitWidth::Bit32) => "uint",
118            (Sign::Unsigned, BitWidth::Bit64) => "ulonglong",
119            #[cfg(target_pointer_width = "32")]
120            (Sign::Unsigned, BitWidth::Pointer) => "uint",
121            #[cfg(target_pointer_width = "64")]
122            (Sign::Unsigned, BitWidth::Pointer) => "ulonglong",
123            (Sign::Signed, BitWidth::Bit8) => "schar",
124            (Sign::Signed, BitWidth::Bit16) => "short",
125            (Sign::Signed, BitWidth::Bit32) => "int",
126            (Sign::Signed, BitWidth::Bit64) => "longlong",
127            #[cfg(target_pointer_width = "32")]
128            (Sign::Signed, BitWidth::Pointer) => "int",
129            #[cfg(target_pointer_width = "64")]
130            (Sign::Signed, BitWidth::Pointer) => "longlong",
131
132            #[cfg(not(any(target_pointer_width = "32", target_pointer_width = "64")))]
133            (_, BitWidth::Pointer) => compile_error!("Unsupported pointer width"),
134        };
135        format!("{RUST_TYPE_PREFIX}{ty}")
136    }
137
138    fn width_to_str(&self) -> &'static str {
139        match self.width {
140            BitWidth::Pointer => "size",
141            _ => self.width_to_c_str(),
142        }
143    }
144
145    pub fn to_rust_type(&self) -> String {
146        let prefix = match self.sign {
147            Sign::Signed => "i",
148            Sign::Unsigned => "u",
149        };
150        format!("{prefix}{}", self.width_to_str())
151    }
152}
153
154/// Represents the data type of a single probe argument.
155#[derive(Clone, Copy, Debug, PartialEq)]
156pub enum DataType {
157    Integer(Integer),
158    Pointer(Integer),
159    String,
160}
161
162impl From<Pair<'_, Rule>> for Integer {
163    fn from(integer_type: Pair<'_, Rule>) -> Integer {
164        let sign = match integer_type.as_rule() {
165            Rule::SIGNED_INT => Sign::Signed,
166            Rule::UNSIGNED_INT => Sign::Unsigned,
167            _ => unreachable!("Expected a signed or unsigned integer"),
168        };
169        let width = match integer_type.into_inner().as_str() {
170            "8" => BitWidth::Bit8,
171            "16" => BitWidth::Bit16,
172            "32" => BitWidth::Bit32,
173            "64" => BitWidth::Bit64,
174            "ptr" => BitWidth::Pointer,
175            _ => unreachable!("Expected a bit width"),
176        };
177        Integer { sign, width }
178    }
179}
180
181impl TryFrom<&Pair<'_, Rule>> for DataType {
182    type Error = DTraceError;
183
184    fn try_from(pair: &Pair<'_, Rule>) -> Result<DataType, Self::Error> {
185        expect_token(pair, Rule::DATA_TYPE)?;
186        let inner = pair
187            .clone()
188            .into_inner()
189            .next()
190            .expect("Data type token is expected to contain a concrete type");
191        let typ = match inner.as_rule() {
192            Rule::INTEGER => {
193                let integer = pair
194                    .clone()
195                    .into_inner()
196                    .next()
197                    .expect("Expected a signed or unsigned integral type");
198                assert!(matches!(integer.as_rule(), Rule::INTEGER));
199                DataType::Integer(Integer::from(
200                    integer
201                        .clone()
202                        .into_inner()
203                        .next()
204                        .expect("Expected an integral type"),
205                ))
206            }
207            Rule::INTEGER_POINTER => {
208                let pointer = pair
209                    .clone()
210                    .into_inner()
211                    .next()
212                    .expect("Expected a pointer to a signed or unsigned integral type");
213                assert!(matches!(pointer.as_rule(), Rule::INTEGER_POINTER));
214                let mut parts = pointer.clone().into_inner();
215                let integer = parts
216                    .next()
217                    .expect("Expected a signed or unsigned integral type");
218                let star = parts.next().expect("Expected a literal `*`");
219                assert_eq!(star.as_rule(), Rule::STAR);
220                DataType::Pointer(Integer::from(
221                    integer
222                        .clone()
223                        .into_inner()
224                        .next()
225                        .expect("Expected an integral type"),
226                ))
227            }
228            Rule::STRING => DataType::String,
229            _ => unreachable!("Parsed an unexpected DATA_TYPE token"),
230        };
231        Ok(typ)
232    }
233}
234
235impl TryFrom<&Pairs<'_, Rule>> for DataType {
236    type Error = DTraceError;
237
238    fn try_from(pairs: &Pairs<'_, Rule>) -> Result<DataType, Self::Error> {
239        DataType::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
240    }
241}
242
243impl DataType {
244    /// Convert a type into its C type represenation as a string
245    pub fn to_c_type(&self) -> String {
246        match self {
247            DataType::Integer(int) => int.to_c_type(),
248            DataType::Pointer(int) => format!("{}*", int.to_c_type()),
249            DataType::String => String::from("char*"),
250        }
251    }
252
253    /// Return the Rust FFI type representation of this data type
254    pub fn to_rust_ffi_type(&self) -> String {
255        match self {
256            DataType::Integer(int) => int.to_rust_ffi_type(),
257            DataType::Pointer(int) => format!("*const {}", int.to_rust_ffi_type()),
258            DataType::String => format!("*const {RUST_TYPE_PREFIX}char"),
259        }
260    }
261
262    /// Return the native Rust type representation of this data type
263    pub fn to_rust_type(&self) -> String {
264        match self {
265            DataType::Integer(int) => int.to_rust_type(),
266            DataType::Pointer(int) => format!("*const {}", int.to_rust_type()),
267            DataType::String => String::from("&str"),
268        }
269    }
270}
271
272/// Type representing a single D probe definition within a provider.
273#[derive(Clone, Debug, PartialEq)]
274pub struct Probe {
275    pub name: String,
276    pub types: Vec<DataType>,
277}
278
279impl TryFrom<&Pair<'_, Rule>> for Probe {
280    type Error = DTraceError;
281
282    fn try_from(pair: &Pair<'_, Rule>) -> Result<Self, Self::Error> {
283        expect_token(pair, Rule::PROBE)?;
284        let mut inner = pair.clone().into_inner();
285        expect_token(
286            &inner.next().expect("Expected the literal 'probe'"),
287            Rule::PROBE_KEY,
288        )?;
289        let token = inner.next().expect("Expected a probe name");
290        let name = token.as_str().to_string();
291        if name == "probe" || name == "start" {
292            return Err(DTraceError::InvalidProbeName(name));
293        }
294        expect_token(
295            &inner.next().expect("Expected the literal '('"),
296            Rule::LEFT_PAREN,
297        )?;
298        let possibly_argument_list = inner
299            .next()
300            .expect("Expected an argument list or literal ')'");
301        let mut types = Vec::new();
302        if expect_token(&possibly_argument_list, Rule::ARGUMENT_LIST).is_ok() {
303            let arguments = possibly_argument_list.clone().into_inner();
304            for data_type in arguments {
305                expect_token(&data_type, Rule::DATA_TYPE)?;
306                types.push(DataType::try_from(&data_type)?);
307            }
308        }
309        expect_token(
310            &inner.next().expect("Expected a literal ')'"),
311            Rule::RIGHT_PAREN,
312        )?;
313        expect_token(
314            &inner.next().expect("Expected a literal ';'"),
315            Rule::SEMICOLON,
316        )?;
317        Ok(Probe { name, types })
318    }
319}
320
321impl TryFrom<&Pairs<'_, Rule>> for Probe {
322    type Error = DTraceError;
323
324    fn try_from(pairs: &Pairs<'_, Rule>) -> Result<Self, Self::Error> {
325        Probe::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
326    }
327}
328
329/// Type representing a single DTrace provider and all of its probes.
330#[derive(Debug, Clone, PartialEq)]
331pub struct Provider {
332    pub name: String,
333    pub probes: Vec<Probe>,
334}
335
336impl TryFrom<&Pair<'_, Rule>> for Provider {
337    type Error = DTraceError;
338
339    fn try_from(pair: &Pair<'_, Rule>) -> Result<Self, Self::Error> {
340        expect_token(pair, Rule::PROVIDER)?;
341        let mut inner = pair.clone().into_inner();
342        expect_token(
343            &inner.next().expect("Expected the literal 'provider'"),
344            Rule::PROVIDER_KEY,
345        )?;
346        let name = inner
347            .next()
348            .expect("Expected a provider name")
349            .as_str()
350            .to_string();
351        if name == "provider" {
352            return Err(DTraceError::InvalidProviderName(name));
353        }
354        expect_token(
355            &inner.next().expect("Expected the literal '{'"),
356            Rule::LEFT_BRACE,
357        )?;
358        let mut probes = Vec::new();
359        let mut possibly_probe = inner
360            .next()
361            .expect("Expected at least one probe in the provider");
362        while expect_token(&possibly_probe, Rule::PROBE).is_ok() {
363            probes.push(Probe::try_from(&possibly_probe)?);
364            possibly_probe = inner.next().expect("Expected a token");
365        }
366        expect_token(&possibly_probe, Rule::RIGHT_BRACE)?;
367        expect_token(
368            &inner.next().expect("Expected a literal ';'"),
369            Rule::SEMICOLON,
370        )?;
371        Ok(Provider { name, probes })
372    }
373}
374
375impl TryFrom<&Pairs<'_, Rule>> for Provider {
376    type Error = DTraceError;
377
378    fn try_from(pairs: &Pairs<'_, Rule>) -> Result<Self, Self::Error> {
379        Provider::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
380    }
381}
382
383/// Type representing a single D file and all the providers it defines.
384#[derive(Debug, Clone, PartialEq)]
385pub struct File {
386    name: String,
387    providers: Vec<Provider>,
388}
389
390impl TryFrom<&Pair<'_, Rule>> for File {
391    type Error = DTraceError;
392
393    fn try_from(pair: &Pair<'_, Rule>) -> Result<Self, Self::Error> {
394        expect_token(pair, Rule::FILE)?;
395        let mut providers = Vec::new();
396        let mut names = HashSet::new();
397        for item in pair.clone().into_inner() {
398            if item.as_rule() == Rule::PROVIDER {
399                let provider = Provider::try_from(&item)?;
400                for probe in provider.probes.iter() {
401                    let name = (provider.name.clone(), probe.name.clone());
402                    if names.contains(&name) {
403                        return Err(DTraceError::DuplicateProbeName(name));
404                    }
405                    names.insert(name.clone());
406                }
407                providers.push(provider);
408            }
409        }
410
411        Ok(File {
412            name: "".to_string(),
413            providers,
414        })
415    }
416}
417
418impl TryFrom<&Pairs<'_, Rule>> for File {
419    type Error = DTraceError;
420
421    fn try_from(pairs: &Pairs<'_, Rule>) -> Result<Self, Self::Error> {
422        File::try_from(&pairs.peek().ok_or(DTraceError::EmptyPairsIterator)?)
423    }
424}
425
426impl File {
427    /// Load and parse a provider from a D file at the given path.
428    pub fn from_file(filename: &Path) -> Result<Self, DTraceError> {
429        let mut f = File::try_from(fs::read_to_string(filename)?.as_str())?;
430        f.name = filename
431            .file_stem()
432            .unwrap()
433            .to_os_string()
434            .into_string()
435            .unwrap();
436        Ok(f)
437    }
438
439    /// Return the name of the file.
440    pub fn name(&self) -> &String {
441        &self.name
442    }
443
444    /// Return the list of providers this file defines.
445    pub fn providers(&self) -> &Vec<Provider> {
446        &self.providers
447    }
448}
449
450impl TryFrom<&str> for File {
451    type Error = DTraceError;
452
453    fn try_from(s: &str) -> Result<Self, Self::Error> {
454        use pest::Parser;
455        File::try_from(&DTraceParser::parse(Rule::FILE, s).map_err(|e| {
456            Box::new(e.renamed_rules(|rule| match *rule {
457                Rule::DATA_TYPE | Rule::BIT_WIDTH => {
458                    format!(
459                        "{:?}.\n\n{}",
460                        *rule,
461                        concat!(
462                            "Unsupported type, the following are supported:\n",
463                            "  - uint8_t\n",
464                            "  - uint16_t\n",
465                            "  - uint32_t\n",
466                            "  - uint64_t\n",
467                            "  - int8_t\n",
468                            "  - int16_t\n",
469                            "  - int32_t\n",
470                            "  - int64_t\n",
471                            "  - &str\n",
472                        )
473                    )
474                }
475                _ => format!("{:?}", rule),
476            }))
477        })?)
478    }
479}
480
481#[cfg(test)]
482mod tests {
483    use super::BitWidth;
484    use super::DTraceParser;
485    use super::DataType;
486    use super::File;
487    use super::Integer;
488    use super::Probe;
489    use super::Provider;
490    use super::Rule;
491    use super::Sign;
492    use super::TryFrom;
493    use ::pest::Parser;
494    use rstest::{fixture, rstest};
495
496    #[rstest(
497        token,
498        rule,
499        case("probe", Rule::PROBE_KEY),
500        case("provider", Rule::PROVIDER_KEY),
501        case(";", Rule::SEMICOLON),
502        case("(", Rule::LEFT_PAREN),
503        case(")", Rule::RIGHT_PAREN),
504        case("{", Rule::LEFT_BRACE),
505        case("}", Rule::RIGHT_BRACE)
506    )]
507    fn test_basic_tokens(token: &str, rule: Rule) {
508        assert!(DTraceParser::parse(rule, token).is_ok());
509    }
510
511    #[test]
512    #[should_panic]
513    fn test_bad_basic_token() {
514        assert!(DTraceParser::parse(Rule::LEFT_BRACE, "x").is_ok())
515    }
516
517    #[test]
518    fn test_identifier() {
519        assert!(DTraceParser::parse(Rule::IDENTIFIER, "foo").is_ok());
520        assert!(DTraceParser::parse(Rule::IDENTIFIER, "foo_bar").is_ok());
521        assert!(DTraceParser::parse(Rule::IDENTIFIER, "foo9").is_ok());
522
523        assert!(DTraceParser::parse(Rule::IDENTIFIER, "_bar").is_err());
524        assert!(DTraceParser::parse(Rule::IDENTIFIER, "").is_err());
525        assert!(DTraceParser::parse(Rule::IDENTIFIER, "9foo").is_err());
526    }
527
528    #[test]
529    fn test_data_types() {
530        assert!(DTraceParser::parse(Rule::DATA_TYPE, "uint8_t").is_ok());
531        assert!(DTraceParser::parse(Rule::DATA_TYPE, "int").is_err());
532        assert!(DTraceParser::parse(Rule::DATA_TYPE, "flaot").is_err());
533    }
534
535    #[test]
536    fn test_probe() {
537        let defn = "probe foo(uint8_t, uint16_t, uint16_t);";
538        assert!(DTraceParser::parse(Rule::PROBE, defn).is_ok());
539        assert!(DTraceParser::parse(Rule::PROBE, &defn[..defn.len() - 2]).is_err());
540    }
541
542    #[test]
543    fn test_basic_provider() {
544        let defn = r#"
545            provider foo {
546                probe bar();
547                probe baz(char*, uint16_t, uint8_t);
548            };"#;
549        println!("{:?}", DTraceParser::parse(Rule::FILE, defn));
550        assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
551        assert!(DTraceParser::parse(Rule::FILE, &defn[..defn.len() - 2]).is_err());
552    }
553
554    #[test]
555    fn test_null_provider() {
556        let defn = "provider foo { };";
557        assert!(DTraceParser::parse(Rule::FILE, defn).is_err());
558    }
559
560    #[test]
561    fn test_comment_provider() {
562        let defn = r#"
563            /* Check out this fly provider */
564            provider foo {
565                probe bar();
566                probe baz(char*, uint16_t, uint8_t);
567            };"#;
568        assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
569    }
570
571    #[test]
572    fn test_pragma_provider() {
573        let defn = r#"
574            #pragma I am a robot
575            provider foo {
576                probe bar();
577                probe baz(char*, uint16_t, uint8_t);
578            };
579            "#;
580        println!("{}", defn);
581        assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
582    }
583
584    #[test]
585    fn test_two_providers() {
586        let defn = r#"
587            provider foo {
588                probe bar();
589                probe baz(char*, uint16_t, uint8_t);
590            };
591            provider bar {
592                probe bar();
593                probe baz(char*, uint16_t, uint8_t);
594            };
595            "#;
596        println!("{}", defn);
597        assert!(DTraceParser::parse(Rule::FILE, defn).is_ok());
598    }
599
600    #[rstest(
601        defn,
602        data_type,
603        case("uint8_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit8 })),
604        case("uint16_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit16 })),
605        case("uint32_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit32 })),
606        case("uint64_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit64 })),
607        case("uintptr_t", DataType::Integer(Integer { sign: Sign::Unsigned, width: BitWidth::Pointer })),
608        case("int8_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit8 })),
609        case("int16_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit16 })),
610        case("int32_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit32 })),
611        case("int64_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Bit64 })),
612        case("intptr_t", DataType::Integer(Integer { sign: Sign::Signed, width: BitWidth::Pointer })),
613        case("uint8_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit8})),
614        case("uint16_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit16})),
615        case("uint32_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit32})),
616        case("uint64_t*", DataType::Pointer(Integer { sign: Sign::Unsigned, width: BitWidth::Bit64})),
617        case("int8_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit8})),
618        case("int16_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit16})),
619        case("int32_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit32})),
620        case("int64_t*", DataType::Pointer(Integer { sign: Sign::Signed, width: BitWidth::Bit64})),
621        case("char*", DataType::String)
622    )]
623    fn test_data_type_enum(defn: &str, data_type: DataType) {
624        let dtype =
625            DataType::try_from(&DTraceParser::parse(Rule::DATA_TYPE, defn).unwrap()).unwrap();
626        assert_eq!(dtype, data_type);
627    }
628
629    #[test]
630    fn test_data_type_conversion() {
631        let dtype =
632            DataType::try_from(&DTraceParser::parse(Rule::DATA_TYPE, "uint8_t").unwrap()).unwrap();
633        assert_eq!(dtype.to_rust_ffi_type(), "::std::os::raw::c_uchar");
634    }
635
636    #[fixture]
637    fn probe_data() -> (String, String) {
638        let provider = String::from("foo");
639        let probe = String::from("probe baz(char*, uint16_t, uint8_t*);");
640        (provider, probe)
641    }
642
643    #[fixture]
644    fn probe(probe_data: (String, String)) -> (String, Probe) {
645        (
646            probe_data.0,
647            Probe::try_from(&DTraceParser::parse(Rule::PROBE, &probe_data.1).unwrap()).unwrap(),
648        )
649    }
650
651    #[rstest]
652    fn test_probe_struct_parse(probe_data: (String, String)) {
653        let (_, probe) = probe_data;
654        let probe = Probe::try_from(&DTraceParser::parse(Rule::PROBE, &probe).unwrap())
655            .expect("Could not parse probe tokens");
656        assert_eq!(probe.name, "baz");
657        assert_eq!(
658            probe.types,
659            &[
660                DataType::String,
661                DataType::Integer(Integer {
662                    sign: Sign::Unsigned,
663                    width: BitWidth::Bit16,
664                }),
665                DataType::Pointer(Integer {
666                    sign: Sign::Unsigned,
667                    width: BitWidth::Bit8,
668                }),
669            ]
670        );
671    }
672
673    fn data_file(name: &str) -> String {
674        format!("{}/test-data/{}", env!("CARGO_MANIFEST_DIR"), name)
675    }
676
677    #[test]
678    fn test_provider_struct() {
679        let provider_name = "foo";
680        let defn = std::fs::read_to_string(data_file(&format!("{}.d", provider_name))).unwrap();
681        let provider = Provider::try_from(
682            &DTraceParser::parse(Rule::FILE, &defn)
683                .unwrap()
684                .next()
685                .unwrap()
686                .into_inner(),
687        );
688        let provider = provider.unwrap();
689        assert_eq!(provider.name, provider_name);
690        assert_eq!(provider.probes.len(), 1);
691        assert_eq!(provider.probes[0].name, "baz");
692    }
693
694    #[test]
695    fn test_file_struct() {
696        let defn = r#"
697            /* a comment */
698            #pragma do stuff
699            provider foo {
700                probe quux();
701                probe quack(char*, uint16_t, uint8_t);
702            };
703            provider bar {
704                probe bar();
705                probe baz(char*, uint16_t, uint8_t);
706            };
707            "#;
708        let file = File::try_from(&DTraceParser::parse(Rule::FILE, defn).unwrap()).unwrap();
709        assert_eq!(file.providers.len(), 2);
710        assert_eq!(file.providers[0].name, "foo");
711        assert_eq!(file.providers[1].probes[1].name, "baz");
712
713        let file2 = File::try_from(defn).unwrap();
714        assert_eq!(file, file2);
715
716        assert!(File::try_from("this is not a D file").is_err());
717    }
718}