Skip to main content

nil_lua/
lib.rs

1// Copyright (C) Call of Nil contributors
2// SPDX-License-Identifier: AGPL-3.0-only
3
4#![cfg_attr(docsrs, feature(doc_cfg))]
5#![doc(html_favicon_url = "https://nil.dev.br/favicon.png")]
6#![feature(iterator_try_collect)]
7
8pub mod client;
9pub mod error;
10pub mod script;
11pub mod stdio;
12
13use crate::client::ClientUserData;
14use crate::error::Result;
15use crate::script::ScriptOutput;
16use mlua::{LuaOptions, StdLib, Value, Variadic};
17use nil_client::Client;
18use std::mem;
19use std::sync::Arc;
20use stdio::{Stdio, StdioMessage};
21use tokio::sync::RwLock;
22use tokio::sync::mpsc::{self, UnboundedReceiver};
23
24pub struct Lua {
25  inner: mlua::Lua,
26  stdout: Stdio,
27  stderr: Stdio,
28}
29
30impl Lua {
31  pub fn new(client: &Arc<RwLock<Client>>) -> Result<Self> {
32    Self::with_libs(client, StdLib::MATH | StdLib::STRING | StdLib::TABLE)
33  }
34
35  pub fn with_libs(client: &Arc<RwLock<Client>>, libs: StdLib) -> Result<Self> {
36    let lua = mlua::Lua::new_with(libs, LuaOptions::default())?;
37
38    let globals = lua.globals();
39    let client_data = ClientUserData::new(Arc::clone(client));
40    globals.set("client", lua.create_userdata(client_data)?)?;
41
42    let stdout_rx = pipe_stdio(&lua, "print", "println")?;
43    let stderr_rx = pipe_stdio(&lua, "eprint", "eprintln")?;
44
45    Ok(Self {
46      inner: lua,
47      stdout: Stdio::new(stdout_rx),
48      stderr: Stdio::new(stderr_rx),
49    })
50  }
51
52  pub async fn execute(&mut self, chunk: &str) -> Result<ScriptOutput> {
53    self.flush();
54    self.clear();
55
56    self.inner.load(chunk).exec_async().await?;
57
58    Ok(self.output())
59  }
60
61  fn output(&mut self) -> ScriptOutput {
62    self.flush();
63    self.stdout.buffer.sort();
64    self.stderr.buffer.sort();
65
66    ScriptOutput {
67      stdout: mem::take(&mut self.stdout.buffer),
68      stderr: mem::take(&mut self.stderr.buffer),
69    }
70  }
71
72  fn flush(&mut self) {
73    self.stdout.flush();
74    self.stderr.flush();
75  }
76
77  fn clear(&mut self) {
78    self.stdout.buffer.clear();
79    self.stderr.buffer.clear();
80  }
81}
82
83fn pipe_stdio(
84  lua: &mlua::Lua,
85  name: &str,
86  name_ln: &str,
87) -> Result<UnboundedReceiver<StdioMessage>> {
88  let (tx, rx) = mpsc::unbounded_channel();
89  let create_fn = |line_break: bool| {
90    let tx = tx.clone();
91    lua.create_function(move |_, values: Variadic<Value>| {
92      let mut string = values
93        .into_iter()
94        .map(|it| it.to_string())
95        .try_collect::<String>()?;
96
97      if line_break {
98        string.push('\n');
99      }
100
101      let _ = tx.send(StdioMessage::new(string));
102
103      Ok(())
104    })
105  };
106
107  let globals = lua.globals();
108  globals.set(name, create_fn(false)?)?;
109  globals.set(name_ln, create_fn(true)?)?;
110
111  Ok(rx)
112}