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#[cfg(test)]
150mod test {
151 use crate::core::object::NIL;
152 use crate::{
153 buffer::{get_buffer_create, set_buffer},
154 core::gc::RootSet,
155 };
156 use rune_core::macros::root;
157
158 use super::*;
159
160 #[test]
161 fn test_format() {
162 assert_eq!(&format("%s", &[1.into()]).unwrap(), "1");
163 assert_eq!(&format("foo-%s", &[2.into()]).unwrap(), "foo-2");
164 assert_eq!(&format("%%", &[]).unwrap(), "%");
165 assert_eq!(&format("_%%_", &[]).unwrap(), "_%_");
166 assert_eq!(&format("foo-%s %s", &[3.into(), 4.into()]).unwrap(), "foo-3 4");
167 let sym = crate::core::env::sym::FUNCTION.into();
168 assert_eq!(&format("%s", &[sym]).unwrap(), "function");
169
170 assert!(&format("%s", &[]).is_err());
171 assert!(&format("%s", &[1.into(), 2.into()]).is_err());
172
173 assert!(format("`%s' %s%s%s", &[0.into(), 1.into(), 2.into(), 3.into()]).is_ok());
174 }
175
176 #[test]
177 fn test_insert() {
178 let roots = &RootSet::default();
179 let cx = &mut Context::new(roots);
180 root!(env, new(Env), cx);
181 let buffer = get_buffer_create(cx.add("test_insert"), Some(NIL), cx).unwrap();
182 set_buffer(buffer, env, cx).unwrap();
183 cx.garbage_collect(true);
184 env.stack.push(104);
185 env.stack.push(101);
186 env.stack.push(108);
187 env.stack.push(108);
188 env.stack.push(111);
189 insert(ArgSlice::new(5), env, cx).unwrap();
190 assert_eq!(env.current_buffer.get(), "hello");
191 }
192
193 #[test]
194 fn test_delete_region() {
195 let roots = &RootSet::default();
196 let cx = &mut Context::new(roots);
197 root!(env, new(Env), cx);
198 let buffer = get_buffer_create(cx.add("test_delete_region"), Some(NIL), cx).unwrap();
199 set_buffer(buffer, env, cx).unwrap();
200 cx.garbage_collect(true);
201 env.stack.push(cx.add("hello"));
202 env.stack.push(cx.add(" world"));
203 insert(ArgSlice::new(2), env, cx).unwrap();
204
205 assert_eq!(env.current_buffer.get(), "hello world");
206 delete_region(2, 4, env).unwrap();
207 assert_eq!(env.current_buffer.get(), "hlo world");
208 }
209}