Skip to main content

rucc_parse/
comment.rs

1//! `#pragma comment`, which asks the linker for something from inside a source file.
2//!
3//! Design: `$HOME/notes/Spec/2131/platform/windows/11-headers-and-dialect.md` section 11.5.
4//!
5//! `#pragma comment(lib, "ws2_32")` is how a lot of Windows C code says it wants a library,
6//! instead of `-lws2_32` on the command line, and `#pragma comment(linker, "...")` passes any
7//! option at all. Both end up in the object's `.drectve` section, which is where a COFF linker
8//! reads options from. The other kinds MSVC has (`compiler`, `exestr`, `user`) put a string in
9//! the object for a person to find, and clang takes them and writes nothing, as this does.
10//!
11//! The lines are read here because this is where the pragma lines are read, and the parser hands
12//! what they ask for to the driver, which is the part that knows whether the target has anywhere
13//! to put it.
14
15use rucc_diag::Span;
16use rucc_lex::{Encoding, Token, TokenKind};
17
18use crate::pack::eat_punct;
19use crate::parser::Parser;
20
21/// What one `#pragma comment` line asks the linker for.
22#[derive(Debug, Clone, PartialEq, Eq)]
23pub enum Comment {
24    /// `lib`: link against this library, as it was written.
25    Lib(String),
26    /// `linker`: pass this option to the linker, as it was written.
27    Linker(String),
28}
29
30impl Parser<'_> {
31    /// One `#pragma comment` line, with the word `comment` taken off.
32    pub(crate) fn comment_line(&mut self, mut rest: &[Token], span: Span) {
33        if !eat_punct(&mut rest, "(") {
34            self.warn("E0745", "missing `(` after `#pragma comment` - ignored", span);
35            return;
36        }
37        let Some(kind) = rest.first().and_then(|token| token.ident()) else {
38            self.warn("E0745", "malformed `#pragma comment` - ignored", span);
39            return;
40        };
41        let kind = self.cx.interner.resolve(kind).to_owned();
42        rest = &rest[1..];
43        let text = if eat_punct(&mut rest, ",") {
44            let Some(text) = self.comment_text(&mut rest) else {
45                self.warn("E0745", "`#pragma comment` wants a string after the comma", span);
46                return;
47            };
48            Some(text)
49        } else {
50            None
51        };
52        if !eat_punct(&mut rest, ")") || !rest.is_empty() {
53            self.warn("E0745", "malformed `#pragma comment` - ignored", span);
54            return;
55        }
56        match (kind.as_str(), text) {
57            ("lib", Some(text)) => self.comments.push(Comment::Lib(text)),
58            ("linker", Some(text)) => self.comments.push(Comment::Linker(text)),
59            ("lib" | "linker", None) => {
60                let what = format!("`#pragma comment({kind})` wants a string - ignored");
61                self.warn("E0745", what, span);
62            }
63            ("compiler" | "exestr" | "user", _) => {}
64            _ => {
65                let what = format!("unknown kind `{kind}` in `#pragma comment` - ignored");
66                self.warn("E0745", what, span);
67            }
68        }
69    }
70
71    /// The string after the comma, which has to be a plain one.
72    fn comment_text(&self, rest: &mut &[Token]) -> Option<String> {
73        let token = rest.first()?;
74        if token.kind != TokenKind::Str {
75            return None;
76        }
77        let literal = self.tokens.strings.get(token.value as usize)?;
78        if !matches!(literal.encoding, Encoding::Plain | Encoding::Utf8) {
79            return None;
80        }
81        *rest = &rest[1..];
82        let bytes: Vec<u8> = literal.elements.iter().map(|&element| element as u8).collect();
83        Some(String::from_utf8_lossy(&bytes).into_owned())
84    }
85}