Skip to content

Commit 65fc244

Browse files
kangaliotinaun
authored andcommitted
Add Godbolt command
1 parent 75ed7ae commit 65fc244

File tree

4 files changed

+155
-1
lines changed

4 files changed

+155
-1
lines changed

Cargo.lock

Lines changed: 26 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,10 +12,12 @@ serenity = { version = "0.8.7", features = ["model"] }
1212
diesel = { version = "1.4.0", features = ["postgres", "r2d2"] }
1313
diesel_migrations = { version = "1.4.0", features = ["postgres"] }
1414
reqwest = { version = "0.10", features = ["blocking", "json"] }
15-
serde = "1.0"
15+
serde = { version = "1.0", features = ["derive"] }
16+
serde_json = "1.0"
1617
serde_derive = "1.0"
1718
lazy_static = "1.4.0"
1819
log = "0.4.0"
1920
env_logger = "0.7.1"
2021
envy = "0.4"
2122
indexmap = "1.6"
23+
strip-ansi-escapes = "0.1.0" # For normalizing godbolt responses

src/godbolt.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
pub enum Compilation {
2+
Success { asm: String },
3+
Error { stderr: String },
4+
}
5+
6+
#[derive(Debug, serde::Deserialize)]
7+
struct GodboltOutputSegment {
8+
text: String,
9+
}
10+
11+
#[derive(Debug, serde::Deserialize)]
12+
struct GodboltOutput(Vec<GodboltOutputSegment>);
13+
14+
impl GodboltOutput {
15+
pub fn full_with_ansi_codes_stripped(&self) -> Result<String, crate::Error> {
16+
let mut complete_text = String::new();
17+
for segment in self.0.iter() {
18+
complete_text.push_str(&segment.text);
19+
complete_text.push_str("\n");
20+
}
21+
Ok(String::from_utf8(strip_ansi_escapes::strip(
22+
complete_text.trim(),
23+
)?)?)
24+
}
25+
}
26+
27+
#[derive(Debug, serde::Deserialize)]
28+
struct GodboltResponse {
29+
code: u8,
30+
stdout: GodboltOutput,
31+
stderr: GodboltOutput,
32+
asm: GodboltOutput,
33+
}
34+
35+
/// Compile a given Rust source code file on Godbolt using the latest nightly compiler with
36+
/// full optimizations (-O3)
37+
/// Returns a multiline string with the pretty printed assembly
38+
pub fn compile_rust_source(
39+
http: &reqwest::blocking::Client,
40+
source_code: &str,
41+
) -> Result<Compilation, crate::Error> {
42+
let response: GodboltResponse = http
43+
.execute(
44+
http.post("https://godbolt.org/api/compiler/nightly/compile")
45+
.query(&[("options", "-Copt-level=3")])
46+
.header(reqwest::header::ACCEPT, "application/json")
47+
.body(source_code.to_owned())
48+
.build()?,
49+
)?
50+
.json()?;
51+
52+
dbg!(&response);
53+
54+
Ok(if response.code == 0 {
55+
Compilation::Success {
56+
asm: response.asm.full_with_ansi_codes_stripped()?,
57+
}
58+
} else {
59+
Compilation::Error {
60+
stderr: response.stderr.full_with_ansi_codes_stripped()?,
61+
}
62+
})
63+
}

src/main.rs

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ mod command_history;
1313
mod commands;
1414
mod crates;
1515
mod db;
16+
mod godbolt;
1617
mod jobs;
1718
mod playground;
1819
mod schema;
@@ -147,6 +148,34 @@ fn app() -> Result<(), Error> {
147148
});
148149
}
149150

151+
cmds.add("?godbolt ```\ncode```", |args| {
152+
let code = args
153+
.params
154+
.get("code")
155+
.ok_or("Unable to retrieve param: code")?;
156+
let (lang, text) = match godbolt::compile_rust_source(args.http, code)? {
157+
godbolt::Compilation::Success { asm } => ("x86asm", asm),
158+
godbolt::Compilation::Error { stderr } => ("rust", stderr),
159+
};
160+
161+
reply_potentially_long_text(
162+
&args,
163+
&format!("```{}\n{}", lang, text),
164+
"\n```",
165+
"Note: the output was truncated",
166+
)?;
167+
168+
Ok(())
169+
});
170+
cmds.help("?godbolt", "View assembly using Godbolt", |args| {
171+
api::send_reply(
172+
&args,
173+
"Compile Rust code using https://rust.godbolt.org. Full optimizations are applied. \
174+
```?godbolt ``\u{200B}`code``\u{200B}` ```",
175+
)?;
176+
Ok(())
177+
});
178+
150179
// Slow mode.
151180
// 0 seconds disables slowmode
152181
cmds.add_protected("?slowmode {channel} {seconds}", api::slow_mode, api::is_mod);
@@ -201,6 +230,40 @@ fn app() -> Result<(), Error> {
201230
Ok(())
202231
}
203232

233+
/// Send a Discord reply message and truncate the message with a given truncation message if the
234+
/// text is too long.
235+
///
236+
/// Only `text_body` is truncated. `text_end` will always be appended at the end. This is useful
237+
/// for example for large code blocks. You will want to truncate the code block contents, but the
238+
/// finalizing \`\`\` should always stay - that's what `text_end` is for.
239+
fn reply_potentially_long_text(
240+
args: &Args,
241+
text_body: &str,
242+
text_end: &str,
243+
truncation_msg: &str,
244+
) -> Result<(), Error> {
245+
let msg = if text_body.len() + text_end.len() > 2000 {
246+
// This is how long the text body may be at max to conform to Discord's limit
247+
let available_space = 2000 - text_end.len() - truncation_msg.len();
248+
249+
let mut cut_off_point = available_space;
250+
while !text_body.is_char_boundary(cut_off_point) {
251+
cut_off_point -= 1;
252+
}
253+
254+
format!(
255+
"{}{}{}",
256+
&text_body[..cut_off_point],
257+
text_end,
258+
truncation_msg
259+
)
260+
} else {
261+
format!("{}{}", text_body, text_end)
262+
};
263+
264+
api::send_reply(args, &msg)
265+
}
266+
204267
fn main_menu(args: &Args, commands: &IndexMap<&str, (&str, GuardFn)>) -> String {
205268
let mut menu = commands.iter().fold(
206269
"Commands:\n".to_owned(),

0 commit comments

Comments
 (0)