1use std::ffi::{c_void, CStr};
24use std::fmt;
25use std::os::raw::{c_char, c_int};
26use std::panic::{catch_unwind, AssertUnwindSafe};
27
28use crate::{ffi, Error, Format, RuntimeId};
29
30#[derive(Clone, Debug, Default, Eq, PartialEq)]
32pub struct Description {
33 pub name: String,
35 pub extensions: Vec<String>,
37 pub aliases: Vec<String>,
38 pub write: bool,
40 pub samples: Vec<String>,
42}
43
44impl Description {
45 fn to_json(&self) -> String {
47 let mut out = String::from("{\"name\":");
48 json_string(&self.name, &mut out);
49 for (key, list) in [
50 ("extensions", &self.extensions),
51 ("aliases", &self.aliases),
52 ("samples", &self.samples),
53 ] {
54 out.push_str(",\"");
55 out.push_str(key);
56 out.push_str("\":[");
57 for (i, item) in list.iter().enumerate() {
58 if i > 0 {
59 out.push(',');
60 }
61 json_string(item, &mut out);
62 }
63 out.push(']');
64 }
65 out.push_str(",\"caps\":{\"read\":true,\"write\":");
66 out.push_str(if self.write { "true" } else { "false" });
67 out.push_str("}}");
68 out
69 }
70}
71
72fn json_string(s: &str, out: &mut String) {
73 out.push('"');
74 for c in s.chars() {
75 match c {
76 '"' => out.push_str("\\\""),
77 '\\' => out.push_str("\\\\"),
78 '\n' => out.push_str("\\n"),
79 '\r' => out.push_str("\\r"),
80 '\t' => out.push_str("\\t"),
81 c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
82 c => out.push(c),
83 }
84 }
85 out.push('"');
86}
87
88pub trait Language: Send + Sync + 'static {
92 fn describe(&self) -> Description;
93
94 fn parse(&self, row: &str, source: &[u8]) -> Result<Vec<u8>, String>;
98
99 fn print(&self, row: &str, table: &[u8]) -> Result<Vec<u8>, String> {
102 let _ = (row, table);
103 Err("this language does not print".to_owned())
104 }
105}
106
107#[derive(Clone, Debug, Eq, PartialEq)]
109pub struct RegisterError {
110 pub error: Error,
113 pub message: String,
115}
116
117impl fmt::Display for RegisterError {
118 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
119 if self.message.is_empty() {
120 write!(f, "{}", self.error)
121 } else {
122 write!(f, "{}: {}", self.error, self.message)
123 }
124 }
125}
126
127impl std::error::Error for RegisterError {}
128
129pub fn register<L: Language>(language: L) -> Result<Format, RegisterError> {
132 let description = language.describe();
133 let json = description.to_json();
134 let user_data = Box::into_raw(Box::new(language)) as *mut c_void;
135 let vtable = ffi::TwigLanguageVTable {
136 version: ffi::TWIG_LANGUAGE_VTABLE_VERSION,
137 user_data,
138 description: json.as_ptr(),
139 description_len: json.len(),
140 parse: Some(parse_trampoline::<L>),
141 print: if description.write {
142 Some(print_trampoline::<L>)
143 } else {
144 None
145 },
146 free: Some(free_trampoline),
147 };
148 let mut code: c_int = 0;
149 let mut err = [0 as c_char; 512];
150 let status =
151 unsafe { ffi::twig_language_register(&vtable, &mut code, err.as_mut_ptr(), err.len()) };
152 match Error::from_status(status) {
153 Ok(()) => Ok(Format::Runtime(RuntimeId(code))),
154 Err(error) => {
155 drop(unsafe { Box::from_raw(user_data as *mut L) });
157 let message = unsafe { CStr::from_ptr(err.as_ptr()) }
158 .to_string_lossy()
159 .into_owned();
160 Err(RegisterError { error, message })
161 }
162 }
163}
164
165pub(crate) unsafe fn bytes<'a>(ptr: *const u8, len: usize) -> &'a [u8] {
166 if len == 0 || ptr.is_null() {
167 &[]
168 } else {
169 unsafe { std::slice::from_raw_parts(ptr, len) }
170 }
171}
172
173pub(crate) unsafe fn give(bytes: Vec<u8>, out: *mut *mut u8, out_len: *mut usize) {
174 let boxed = bytes.into_boxed_slice();
175 unsafe {
176 *out_len = boxed.len();
177 *out = Box::into_raw(boxed) as *mut u8;
178 }
179}
180
181unsafe fn call(
185 f: impl FnOnce() -> Result<Vec<u8>, String>,
186 out: *mut *mut u8,
187 out_len: *mut usize,
188) -> c_int {
189 match catch_unwind(AssertUnwindSafe(f)) {
190 Ok(Ok(answer)) => {
191 unsafe { give(answer, out, out_len) };
192 0
193 }
194 Ok(Err(message)) => {
195 unsafe { give(message.into_bytes(), out, out_len) };
196 1
197 }
198 Err(_) => {
199 unsafe { give(b"the language panicked".to_vec(), out, out_len) };
200 1
201 }
202 }
203}
204
205unsafe extern "C" fn parse_trampoline<L: Language>(
206 user_data: *mut c_void,
207 row: *const u8,
208 row_len: usize,
209 input: *const u8,
210 input_len: usize,
211 out: *mut *mut u8,
212 out_len: *mut usize,
213) -> c_int {
214 let language = unsafe { &*(user_data as *const L) };
215 let row = std::str::from_utf8(unsafe { bytes(row, row_len) }).unwrap_or("");
216 let input = unsafe { bytes(input, input_len) };
217 unsafe { call(|| language.parse(row, input), out, out_len) }
218}
219
220unsafe extern "C" fn print_trampoline<L: Language>(
221 user_data: *mut c_void,
222 row: *const u8,
223 row_len: usize,
224 input: *const u8,
225 input_len: usize,
226 out: *mut *mut u8,
227 out_len: *mut usize,
228) -> c_int {
229 let language = unsafe { &*(user_data as *const L) };
230 let row = std::str::from_utf8(unsafe { bytes(row, row_len) }).unwrap_or("");
231 let input = unsafe { bytes(input, input_len) };
232 unsafe { call(|| language.print(row, input), out, out_len) }
233}
234
235pub(crate) unsafe extern "C" fn free_trampoline(_: *mut c_void, ptr: *mut u8, len: usize) {
236 if !ptr.is_null() {
237 drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) });
238 }
239}
240
241#[cfg(test)]
242mod tests {
243 use super::*;
244 use crate::{Document, Gesture, Target};
245
246 struct Lines(&'static str);
250
251 fn escape(s: &str) -> String {
252 let mut out = String::new();
253 json_string(s, &mut out);
254 out
255 }
256
257 impl Language for Lines {
258 fn describe(&self) -> Description {
259 Description {
260 name: self.0.to_owned(),
261 extensions: vec![format!("{}-ext", self.0)],
262 write: true,
263 samples: vec!["one\ntwo\n".to_owned(), "x\n".to_owned()],
264 ..Description::default()
265 }
266 }
267
268 fn parse(&self, _row: &str, source: &[u8]) -> Result<Vec<u8>, String> {
269 let src = std::str::from_utf8(source).map_err(|_| "not UTF-8".to_owned())?;
270 if src.contains('\0') {
271 return Err("a NUL byte is not a line".to_owned());
272 }
273 let mut rows = vec![format!("{{\"kind\":\"doc\",\"span\":[0,{}]}}", src.len())];
274 let mut start = 0;
275 for line in src.split_inclusive('\n') {
276 let text = line.trim_end_matches('\n');
277 if !text.is_empty() {
278 let para = rows.len();
279 let end = start + text.len();
280 rows.push(format!(
281 "{{\"kind\":\"para\",\"parent\":0,\"span\":[{start},{end}]}}"
282 ));
283 rows.push(format!(
284 "{{\"kind\":\"str\",\"parent\":{para},\"span\":[{start},{end}],\"text\":{}}}",
285 escape(text)
286 ));
287 }
288 start += line.len();
289 }
290 Ok(format!("{{\"nodes\":[{}]}}", rows.join(",")).into_bytes())
291 }
292
293 fn print(&self, _row: &str, table: &[u8]) -> Result<Vec<u8>, String> {
294 let table = std::str::from_utf8(table).map_err(|e| e.to_string())?;
295 let mut out = String::new();
296 let mut rest = table;
297 while let Some(at) = rest.find("\"text\":\"") {
298 rest = &rest[at + 8..];
299 let end = rest.find('"').ok_or("unterminated text")?;
300 out.push_str(&rest[..end]);
301 out.push('\n');
302 rest = &rest[end..];
303 }
304 Ok(out.into_bytes())
305 }
306 }
307
308 #[test]
309 fn a_registered_language_is_a_format_like_any_other() {
310 let format = register(Lines("rust-lines")).expect("register");
311 assert!(matches!(format, Format::Runtime(_)));
312 assert_eq!(format.name(), "rust-lines");
313 assert_eq!(Format::by_name("rust-lines"), Some(format));
314 assert_eq!(Format::by_name("gfm"), Some(Format::Gfm));
315 assert_eq!(Format::Gfm.name(), "gfm");
316 assert_eq!(Target::from(format).as_format(), Some(format));
317
318 let mut doc = Document::parse_str("alpha\nbeta\n", format).expect("parse");
319 assert_eq!(doc.render_html().unwrap(), b"<p>alpha</p>\n<p>beta</p>\n");
320 assert_eq!(
321 doc.serialize_to(Target::Markdown).unwrap(),
322 b"alpha\n\nbeta\n"
323 );
324 assert_eq!(
325 doc.serialize_to(Target::from(format)).unwrap(),
326 b"alpha\nbeta\n"
327 );
328
329 let mut md = Document::parse_str("# T\n\nx\n", Format::Markdown).expect("markdown");
330 assert_eq!(md.serialize_to(Target::from(format)).unwrap(), b"T\nx\n");
331
332 assert!(!format.supports(Gesture::SetBlock));
334 assert!(!format.supports(Gesture::InsertLink));
335
336 assert_eq!(
338 Document::parse_str("a\0b", format).err(),
339 Some(Error::ParseError)
340 );
341 }
342
343 #[test]
344 fn a_refusal_says_why_and_registers_nothing() {
345 struct Taken;
346 impl Language for Taken {
347 fn describe(&self) -> Description {
348 Description {
349 name: "markdown".into(),
350 samples: vec!["x".into()],
351 ..Description::default()
352 }
353 }
354 fn parse(&self, _: &str, _: &[u8]) -> Result<Vec<u8>, String> {
355 Ok(br#"{"nodes":[{"kind":"doc","span":[0,1]}]}"#.to_vec())
356 }
357 }
358 let err = register(Taken).unwrap_err();
359 assert_eq!(err.error, Error::InvalidLanguage);
360 assert!(
361 err.message.contains("already a format's"),
362 "{}",
363 err.message
364 );
365
366 struct Panics;
367 impl Language for Panics {
368 fn describe(&self) -> Description {
369 Description {
370 name: "rust-panics".into(),
371 samples: vec!["x".into()],
372 ..Description::default()
373 }
374 }
375 fn parse(&self, _: &str, _: &[u8]) -> Result<Vec<u8>, String> {
376 panic!("boom")
377 }
378 }
379 let err = register(Panics).unwrap_err();
380 assert!(
381 err.message.contains("the language panicked"),
382 "{}",
383 err.message
384 );
385 assert_eq!(Format::by_name("rust-panics"), None);
386 }
387}