1use crate::core::{
3 env::{ArgSlice, Env},
4 gc::{Context, Rt},
5 object::{Object, ObjectType},
6};
7use anyhow::{Result, bail, ensure};
8use rune_macros::defun;
9use std::{fmt::Write as _, io::Write};
10
11#[defun]
12fn message(format_string: &str, args: &[Object]) -> Result<String> {
13 let message = format(format_string, args)?;
14 println!("MESSAGE: {message}");
15 std::io::stdout().flush()?;
16 Ok(message)
17}
18
19defvar!(MESSAGE_NAME);
20defvar!(MESSAGE_TYPE, "new message");
21
22#[defun]
23fn format(string: &str, objects: &[Object]) -> Result<String> {
24 let mut result = String::new();
25 let mut arguments = objects.iter();
26 let mut remaining = string;
27
28 let mut escaped = false;
29 let mut is_format_char = |c: char| {
30 if escaped {
31 escaped = false;
32 false
33 } else if c == '\\' {
34 escaped = true;
35 false
36 } else {
37 c == '%'
38 }
39 };
40 while let Some(start) = remaining.find(&mut is_format_char) {
41 result += &remaining[..start];
42 let Some(specifier) = remaining.as_bytes().get(start + 1) else {
43 bail!("Format string ends in middle of format specifier")
44 };
45 if *specifier == b'%' {
47 result.push('%');
48 } else {
49 let Some(val) = arguments.next() else {
51 bail!("Not enough arguments for format string")
52 };
53 match val.untag() {
54 ObjectType::String(string) => write!(result, "{string}")?,
55 obj => write!(result, "{obj}")?,
56 }
57 }
58 remaining = &remaining[start + 2..];
59 }
60 result += remaining;
61 ensure!(arguments.next().is_none(), "Too many arguments for format string");
62 Ok(result)
63}
64
65#[defun]
66fn format_message(string: &str, objects: &[Object]) -> Result<String> {
67 let formatted = format(string, objects)?;
68 Ok(formatted
70 .chars()
71 .map(|c| if matches!(c, '`' | '\'') { '"' } else { c })
72 .collect())
73}
74
75#[defun]
76fn string_to_char(string: &str) -> char {
77 string.chars().next().unwrap_or('\0')
78}
79
80#[defun]
81fn char_to_string(chr: char) -> String {
82 format!("{chr}")
83}
84
85#[defun]
86pub(crate) fn insert(args: ArgSlice, env: &mut Rt<Env>, cx: &Context) -> Result<()> {
87 let env = &mut **env; let buffer = env.current_buffer.get_mut();
89 let args = Rt::bind_slice(env.stack.arg_slice(args), cx);
90 for arg in args {
91 buffer.insert(*arg)?;
92 }
93 Ok(())
94}
95
96#[defun]
98pub(crate) fn goto_char(position: usize, env: &mut Rt<Env>) -> Result<()> {
99 let buffer = env.current_buffer.get_mut();
100 buffer.text.set_cursor(position);
101 Ok(())
102}
103
104#[defun]
106pub(crate) fn point_max(env: &mut Rt<Env>) -> Result<usize> {
107 let buffer = env.current_buffer.get_mut();
108 Ok(buffer.text.len_chars() + 1)
110}
111
112#[defun]
113pub(crate) fn point_min() -> usize {
114 1
116}
117
118#[defun]
119pub(crate) fn point_marker(env: &mut Rt<Env>) -> usize {
120 env.current_buffer.get_mut().text.cursor().chars()
122}
123
124#[defun]
125fn delete_region(start: usize, end: usize, env: &mut Rt<Env>) -> Result<()> {
126 env.current_buffer.get_mut().delete(start, end)
127}
128
129#[defun]
130fn bolp(env: &Rt<Env>) -> bool {
131 let buf = env.current_buffer.get();
132 let chars = buf.text.cursor().chars();
133 chars == 0 || buf.text.char_at(chars - 1).unwrap() == '\n'
134}
135
136#[defun]
137fn point(env: &Rt<Env>) -> usize {
138 env.current_buffer.get().text.cursor().chars()
139}
140
141#[defun]
142fn system_name() -> String {
143 hostname::get()
144 .expect("Failed to get hostname")
145 .into_string()
146 .expect("Failed to convert OsString to String")
147}
148
149#[defun]
150fn user_uid() -> usize {
151 ids::effective_uid()
152}
153
154#[defun]
155fn user_real_uid() -> usize {
156 ids::real_uid()
157}
158
159#[defun]
160fn group_gid() -> usize {
161 ids::effective_gid()
162}
163
164#[defun]
165fn group_real_gid() -> usize {
166 ids::real_gid()
167}
168
169#[cfg(unix)]
170mod ids {
171 pub(super) fn effective_uid() -> usize {
172 unsafe { libc::geteuid() as usize }
173 }
174
175 pub(super) fn real_uid() -> usize {
176 unsafe { libc::getuid() as usize }
177 }
178
179 pub(super) fn effective_gid() -> usize {
180 unsafe { libc::getegid() as usize }
181 }
182
183 pub(super) fn real_gid() -> usize {
184 unsafe { libc::getgid() as usize }
185 }
186}
187
188#[cfg(not(unix))]
189mod ids {
190 const UNKNOWN_ID: usize = 123;
193
194 pub(super) fn effective_uid() -> usize {
195 UNKNOWN_ID
196 }
197
198 pub(super) fn real_uid() -> usize {
199 UNKNOWN_ID
200 }
201
202 pub(super) fn effective_gid() -> usize {
203 UNKNOWN_ID
204 }
205
206 pub(super) fn real_gid() -> usize {
207 UNKNOWN_ID
208 }
209}
210
211#[defun]
212fn emacs_pid() -> usize {
213 std::process::id() as usize
214}
215
216#[cfg(test)]
217mod test {
218 use crate::core::object::NIL;
219 use crate::{
220 buffer::{get_buffer_create, set_buffer},
221 core::gc::RootSet,
222 };
223 use rune_core::macros::root;
224
225 use super::*;
226
227 #[test]
228 fn test_format() {
229 assert_eq!(&format("%s", &[1.into()]).unwrap(), "1");
230 assert_eq!(&format("foo-%s", &[2.into()]).unwrap(), "foo-2");
231 assert_eq!(&format("%%", &[]).unwrap(), "%");
232 assert_eq!(&format("_%%_", &[]).unwrap(), "_%_");
233 assert_eq!(&format("foo-%s %s", &[3.into(), 4.into()]).unwrap(), "foo-3 4");
234 let sym = crate::core::env::sym::FUNCTION.into();
235 assert_eq!(&format("%s", &[sym]).unwrap(), "function");
236
237 assert!(&format("%s", &[]).is_err());
238 assert!(&format("%s", &[1.into(), 2.into()]).is_err());
239
240 assert!(format("`%s' %s%s%s", &[0.into(), 1.into(), 2.into(), 3.into()]).is_ok());
241 }
242
243 #[test]
244 fn test_insert() {
245 let roots = &RootSet::default();
246 let cx = &mut Context::new(roots);
247 root!(env, new(Env), cx);
248 let buffer = get_buffer_create(cx.add("test_insert"), Some(NIL), cx).unwrap();
249 set_buffer(buffer, env, cx).unwrap();
250 cx.garbage_collect(true);
251 env.stack.push(104);
252 env.stack.push(101);
253 env.stack.push(108);
254 env.stack.push(108);
255 env.stack.push(111);
256 insert(ArgSlice::new(5), env, cx).unwrap();
257 assert_eq!(env.current_buffer.get(), "hello");
258 }
259
260 #[test]
261 fn test_delete_region() {
262 let roots = &RootSet::default();
263 let cx = &mut Context::new(roots);
264 root!(env, new(Env), cx);
265 let buffer = get_buffer_create(cx.add("test_delete_region"), Some(NIL), cx).unwrap();
266 set_buffer(buffer, env, cx).unwrap();
267 cx.garbage_collect(true);
268 env.stack.push(cx.add("hello"));
269 env.stack.push(cx.add(" world"));
270 insert(ArgSlice::new(2), env, cx).unwrap();
271
272 assert_eq!(env.current_buffer.get(), "hello world");
273 delete_region(2, 4, env).unwrap();
274 assert_eq!(env.current_buffer.get(), "hlo world");
275 }
276}