Skip to main content

twig/
language.rs

1//! Languages registered at runtime: a format this library did not compile in,
2//! supplied as a Rust [`Language`] and registered with [`register`].
3//!
4//! A language **reads**: its [`Language::parse`] turns source into the node
5//! table — the JSON `twig convert -o table` prints for a compiled format, one
6//! row per node in pre-order, each naming its parent, with its spans and its
7//! kind's fields. It may **write**: [`Language::print`] turns a node table
8//! (without positions) back into source. It may **author**: its
9//! [`Description::syntax`] is the table an [`Editor`]'s gestures write with —
10//! `twig lang syntax <format>` prints a compiled format's — and
11//! [`Language::render`] answers the renderers that table names.
12//!
13//! A language may declare [`Feature`]s, switches its parser reads, and
14//! [`Set`]s, each a name for a list of them registered as a [`Format`] of
15//! its own. Every call says which row it is through and which features are on
16//! ([`Call`]); a caller lays more on with [`Document::parse_with_features`]
17//! and [`Editor::new_with_features`].
18//!
19//! Registration runs the load check over the language before it exists —
20//! every sample parses to a table the library accepts, a language that prints
21//! reparses every sample's print to the same tree, a language that authors
22//! keeps every promise the engine holds a compiled format to, under each row's
23//! features, each with one more, and all of them; and the fidelity probe
24//! measures what a conversion into it loses — and after that the returned
25//! [`Format`] works everywhere a compiled one does: [`Document::parse`],
26//! [`Document::render_html`], [`Document::serialize_to`], the diagnostics, the
27//! editor.
28//!
29//! [`Server`] and [`serve`] are the other end: the same language answering
30//! the helper wire, which is how a Rust program becomes a helper the `twig`
31//! command line spawns.
32//!
33//! [`Editor`]: crate::Editor
34//! [`Editor::new_with_features`]: crate::Editor::new_with_features
35//! [`Document::parse`]: crate::Document::parse
36//! [`Document::parse_with_features`]: crate::Document::parse_with_features
37//! [`Document::render_html`]: crate::Document::render_html
38//! [`Document::serialize_to`]: crate::Document::serialize_to
39
40use std::ffi::{c_void, CStr};
41use std::fmt;
42use std::io::{BufRead, Write};
43use std::os::raw::{c_char, c_int};
44use std::panic::{catch_unwind, AssertUnwindSafe};
45use std::ptr::NonNull;
46
47use crate::{ffi, Error, Format, RuntimeId};
48
49/// What a language says about itself.
50#[derive(Clone, Debug, Default, Eq, PartialEq)]
51pub struct Description {
52    /// A lowercase identifier no format already answers to.
53    pub name: String,
54    /// Dot-less and lowercase, and no format's already.
55    pub extensions: Vec<String>,
56    pub aliases: Vec<String>,
57    /// Whether the language prints — [`Language::print`] is then called.
58    pub write: bool,
59    /// Whether the language authors: [`Description::syntax`] is then its
60    /// table.
61    pub author: bool,
62    /// The table an editor writes with, as a JSON object — the shape
63    /// `twig lang syntax <format>` prints. Its `renderers` name what
64    /// [`Language::render`] answers.
65    pub syntax: Option<String>,
66    pub features: Vec<Feature>,
67    pub sets: Vec<Set>,
68    /// Small documents the load check holds the language to. At least one,
69    /// across this and [`Description::feature_samples`], and at least one here.
70    pub samples: Vec<String>,
71    /// Samples that need features on to mean what they say.
72    pub feature_samples: Vec<Sample>,
73}
74
75/// A switch the language's parser reads.
76#[derive(Clone, Debug, Default, Eq, PartialEq)]
77pub struct Feature {
78    /// A lowercase identifier.
79    pub name: String,
80    /// Whether the language's own row has it on.
81    pub default: bool,
82    /// Features that come on with this one.
83    pub requires: Vec<String>,
84    /// A JSON object of the table's members this feature replaces while it
85    /// is on — per key in `inline_delims`, `text_leaf_delims` and
86    /// `container_spelling`. No two features may patch the same one.
87    pub syntax: Option<String>,
88}
89
90/// A named list of features, registered as a [`Format`] of its own.
91#[derive(Clone, Debug, Default, Eq, PartialEq)]
92pub struct Set {
93    pub name: String,
94    pub extensions: Vec<String>,
95    pub aliases: Vec<String>,
96    pub features: Vec<String>,
97}
98
99/// A sample, and the features it needs.
100#[derive(Clone, Debug, Default, Eq, PartialEq)]
101pub struct Sample {
102    pub text: String,
103    pub features: Vec<String>,
104}
105
106impl Description {
107    /// The `describe` document the C ABI reads. `syntax` goes in as written.
108    fn to_json(&self) -> String {
109        let mut out = String::from("{\"name\":");
110        json_string(&self.name, &mut out);
111        for (key, list) in [
112            ("extensions", &self.extensions),
113            ("aliases", &self.aliases),
114        ] {
115            out.push_str(",\"");
116            out.push_str(key);
117            out.push_str("\":");
118            json_strings(list, &mut out);
119        }
120        out.push_str(",\"caps\":{\"read\":true,\"write\":");
121        out.push_str(if self.write { "true" } else { "false" });
122        out.push_str(",\"author\":");
123        out.push_str(if self.author { "true" } else { "false" });
124        out.push('}');
125        if let Some(syntax) = &self.syntax {
126            out.push_str(",\"syntax\":");
127            out.push_str(syntax);
128        }
129        out.push_str(",\"features\":[");
130        for (i, f) in self.features.iter().enumerate() {
131            if i > 0 {
132                out.push(',');
133            }
134            out.push_str("{\"name\":");
135            json_string(&f.name, &mut out);
136            out.push_str(",\"default\":");
137            out.push_str(if f.default { "true" } else { "false" });
138            out.push_str(",\"requires\":");
139            json_strings(&f.requires, &mut out);
140            if let Some(syntax) = &f.syntax {
141                out.push_str(",\"syntax\":");
142                out.push_str(syntax);
143            }
144            out.push('}');
145        }
146        out.push_str("],\"sets\":[");
147        for (i, set) in self.sets.iter().enumerate() {
148            if i > 0 {
149                out.push(',');
150            }
151            out.push_str("{\"name\":");
152            json_string(&set.name, &mut out);
153            for (key, list) in [
154                ("extensions", &set.extensions),
155                ("aliases", &set.aliases),
156                ("features", &set.features),
157            ] {
158                out.push_str(",\"");
159                out.push_str(key);
160                out.push_str("\":");
161                json_strings(list, &mut out);
162            }
163            out.push('}');
164        }
165        out.push_str("],\"samples\":[");
166        let mut first = true;
167        for text in &self.samples {
168            if !first {
169                out.push(',');
170            }
171            first = false;
172            json_string(text, &mut out);
173        }
174        for sample in &self.feature_samples {
175            if !first {
176                out.push(',');
177            }
178            first = false;
179            out.push_str("{\"text\":");
180            json_string(&sample.text, &mut out);
181            out.push_str(",\"features\":");
182            json_strings(&sample.features, &mut out);
183            out.push('}');
184        }
185        out.push_str("]}");
186        out
187    }
188}
189
190fn json_strings(list: &[String], out: &mut String) {
191    out.push('[');
192    for (i, item) in list.iter().enumerate() {
193        if i > 0 {
194            out.push(',');
195        }
196        json_string(item, out);
197    }
198    out.push(']');
199}
200
201fn json_string(s: &str, out: &mut String) {
202    out.push('"');
203    for c in s.chars() {
204        match c {
205            '"' => out.push_str("\\\""),
206            '\\' => out.push_str("\\\\"),
207            '\n' => out.push_str("\\n"),
208            '\r' => out.push_str("\\r"),
209            '\t' => out.push_str("\\t"),
210            c if (c as u32) < 0x20 => out.push_str(&format!("\\u{:04x}", c as u32)),
211            c => out.push(c),
212        }
213    }
214    out.push('"');
215}
216
217/// Which call a language is answering: the row it is through — the
218/// language's name, or a set's — and the features in force, the row's own
219/// and whatever the caller laid over them, in declaration order.
220#[derive(Clone, Copy, Debug)]
221pub struct Call<'a> {
222    pub row: &'a str,
223    pub features: &'a [&'a str],
224}
225
226impl Call<'_> {
227    /// Whether `feature` is on.
228    pub fn has(&self, feature: &str) -> bool {
229        self.features.contains(&feature)
230    }
231}
232
233/// A language supplied at runtime. Called from whichever thread parses or
234/// prints, hence `Send + Sync`; kept for the life of the process, since there
235/// is no unregistration.
236///
237/// A language implements [`Language::parse`], or [`Language::parse_with`]
238/// when it reads features; the same for printing.
239pub trait Language: Send + Sync + 'static {
240    fn describe(&self) -> Description;
241
242    /// `source` to the node table of its parse, as JSON. `row` is the name
243    /// the call is through. An `Err` is a parse error whose message the
244    /// library reports.
245    fn parse(&self, row: &str, source: &[u8]) -> Result<Vec<u8>, String> {
246        let _ = (row, source);
247        Err("this language implements neither parse nor parse_with".to_owned())
248    }
249
250    /// A node table without positions to source. Called only for a language
251    /// whose description declares `write`.
252    fn print(&self, row: &str, table: &[u8]) -> Result<Vec<u8>, String> {
253        let _ = (row, table);
254        Err("this language does not print".to_owned())
255    }
256
257    /// [`Language::parse`], told the features in force.
258    fn parse_with(&self, call: &Call<'_>, source: &[u8]) -> Result<Vec<u8>, String> {
259        self.parse(call.row, source)
260    }
261
262    /// [`Language::print`], told the features in force.
263    fn print_with(&self, call: &Call<'_>, table: &[u8]) -> Result<Vec<u8>, String> {
264        self.print(call.row, table)
265    }
266
267    /// Answer one of the renderers the description's `syntax` names. The
268    /// request is JSON, as the node tables are:
269    ///
270    /// - `{"which":"render_text","text":…,"position":…}` — spell `text` so it
271    ///   reparses as itself, at `position` `inline_text`, `block_start` or
272    ///   `verbatim`;
273    /// - `{"which":"render_block","table":{…}}` — spell a fragment: a node
274    ///   table without positions, rooted at the node to print;
275    /// - `{"which":"spells_autolink","text":"<…>"}` — answer `true` or
276    ///   `false`.
277    ///
278    /// The default answers `render_block` with [`Language::print_with`] —
279    /// so a language whose print spells every fragment a gesture builds names
280    /// `render_block` and writes nothing more — and refuses the rest. One
281    /// whose table states `text_escapes` names no `render_text`.
282    fn render(&self, call: &Call<'_>, request: &[u8]) -> Result<Vec<u8>, String> {
283        // The library writes a render_block request as exactly this prefix,
284        // the table, and a closing brace.
285        const BLOCK: &[u8] = br#"{"which":"render_block","table":"#;
286        if request.starts_with(BLOCK) && request.ends_with(b"}") {
287            return self.print_with(call, &request[BLOCK.len()..request.len() - 1]);
288        }
289        Err("this language names no renderers but render_block".to_owned())
290    }
291}
292
293/// Why [`register`] refused a language.
294#[derive(Clone, Debug, Eq, PartialEq)]
295pub struct RegisterError {
296    /// [`Error::InvalidLanguage`] for a refusal; otherwise what the call
297    /// failed with.
298    pub error: Error,
299    /// The library's reason, naming the field or sample at fault.
300    pub message: String,
301}
302
303impl fmt::Display for RegisterError {
304    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
305        if self.message.is_empty() {
306            write!(f, "{}", self.error)
307        } else {
308            write!(f, "{}: {}", self.error, self.message)
309        }
310    }
311}
312
313impl std::error::Error for RegisterError {}
314
315/// A language behind the C table: the language, and its features' names in
316/// the order a call's mask counts them.
317struct Hosted<L> {
318    language: L,
319    features: Vec<String>,
320}
321
322/// The C table over `hosted`, whose description is `json`.
323fn vtable<L: Language>(hosted: *mut Hosted<L>, json: &str, description: &Description) -> ffi::TwigLanguageVTable {
324    ffi::TwigLanguageVTable {
325        version: ffi::TWIG_LANGUAGE_VTABLE_VERSION,
326        user_data: hosted as *mut c_void,
327        description: json.as_ptr(),
328        description_len: json.len(),
329        parse: Some(parse_trampoline::<L>),
330        print: if description.write {
331            Some(print_trampoline::<L>)
332        } else {
333            None
334        },
335        render: Some(render_trampoline::<L>),
336        free: Some(free_trampoline),
337    }
338}
339
340fn host<L: Language>(language: L, description: &Description) -> *mut Hosted<L> {
341    Box::into_raw(Box::new(Hosted {
342        language,
343        features: description.features.iter().map(|f| f.name.clone()).collect(),
344    }))
345}
346
347fn message(err: &[c_char]) -> String {
348    unsafe { CStr::from_ptr(err.as_ptr()) }
349        .to_string_lossy()
350        .into_owned()
351}
352
353/// Register `language` and return the [`Format`] of its own row in this
354/// process; each of its sets is a format too, found with [`Format::by_name`].
355/// On refusal nothing is registered and the language is dropped.
356pub fn register<L: Language>(language: L) -> Result<Format, RegisterError> {
357    let description = language.describe();
358    let json = description.to_json();
359    let hosted = host(language, &description);
360    let vtable = vtable(hosted, &json, &description);
361    let mut code: c_int = 0;
362    let mut err = [0 as c_char; 512];
363    let status =
364        unsafe { ffi::twig_language_register(&vtable, &mut code, err.as_mut_ptr(), err.len()) };
365    match Error::from_status(status) {
366        Ok(()) => Ok(Format::Runtime(RuntimeId(code))),
367        Err(error) => {
368            // Refused: the library kept nothing that points at it.
369            drop(unsafe { Box::from_raw(hosted) });
370            Err(RegisterError {
371                error,
372                message: message(&err),
373            })
374        }
375    }
376}
377
378/// A language answering the helper wire — one request line to one response
379/// line, the codec the library's own. What [`serve`] loops over, and what a
380/// host with its own transport hands lines to.
381pub struct Server {
382    raw: NonNull<ffi::TwigServer>,
383    /// The language, kept alive as long as the library may call it.
384    free: unsafe fn(*mut c_void),
385    hosted: *mut c_void,
386}
387
388// The library's server is used from one thread at a time through `&mut`, and
389// the language it calls is `Send + Sync`.
390unsafe impl Send for Server {}
391
392unsafe fn drop_hosted<L>(p: *mut c_void) {
393    drop(unsafe { Box::from_raw(p as *mut Hosted<L>) });
394}
395
396impl Server {
397    /// Serve `language`. A description the library cannot read is refused,
398    /// with why; the load check is the calling end's.
399    pub fn new<L: Language>(language: L) -> Result<Server, RegisterError> {
400        let description = language.describe();
401        let json = description.to_json();
402        let hosted = host(language, &description);
403        let vtable = vtable(hosted, &json, &description);
404        let mut raw = std::ptr::null_mut();
405        let mut err = [0 as c_char; 512];
406        let status =
407            unsafe { ffi::twig_server_create(&vtable, &mut raw, err.as_mut_ptr(), err.len()) };
408        match Error::from_status(status).and_then(|()| NonNull::new(raw).ok_or(Error::Internal)) {
409            Ok(raw) => Ok(Server {
410                raw,
411                free: drop_hosted::<L>,
412                hosted: hosted as *mut c_void,
413            }),
414            Err(error) => {
415                drop(unsafe { Box::from_raw(hosted) });
416                Err(RegisterError {
417                    error,
418                    message: message(&err),
419                })
420            }
421        }
422    }
423
424    /// One request line, without its newline, to one response line, without
425    /// its newline. A request the server cannot read, and the language's own
426    /// refusal, are answered `{"ok":false,"message":…}`.
427    pub fn handle(&mut self, request: &[u8]) -> Result<Vec<u8>, Error> {
428        let mut ptr: *const u8 = std::ptr::null();
429        let mut len = 0usize;
430        let status = unsafe {
431            ffi::twig_server_handle(self.raw.as_ptr(), request.as_ptr(), request.len(), &mut ptr, &mut len)
432        };
433        Error::from_status(status)?;
434        Ok(unsafe { bytes(ptr, len) }.to_vec())
435    }
436}
437
438impl Drop for Server {
439    fn drop(&mut self) {
440        unsafe {
441            ffi::twig_server_destroy(self.raw.as_ptr());
442            (self.free)(self.hosted);
443        }
444    }
445}
446
447/// Serve `language` on this process's stdin and stdout until stdin ends —
448/// the whole of a helper's `main`:
449///
450/// ```no_run
451/// # struct Org;
452/// # impl twig::Language for Org { fn describe(&self) -> twig::Description { unimplemented!() } }
453/// fn main() -> std::io::Result<()> {
454///     twig::serve(Org)
455/// }
456/// ```
457///
458/// A description the library cannot read is an `InvalidData` error before
459/// any line is read.
460pub fn serve<L: Language>(language: L) -> std::io::Result<()> {
461    let mut server = Server::new(language)
462        .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidData, e.to_string()))?;
463    let stdin = std::io::stdin();
464    let mut stdout = std::io::stdout().lock();
465    for line in stdin.lock().split(b'\n') {
466        let mut line = line?;
467        if line.last() == Some(&b'\r') {
468            line.pop();
469        }
470        let response = server
471            .handle(&line)
472            .map_err(|e| std::io::Error::other(e.to_string()))?;
473        stdout.write_all(&response)?;
474        stdout.write_all(b"\n")?;
475        stdout.flush()?;
476    }
477    Ok(())
478}
479
480pub(crate) unsafe fn bytes<'a>(ptr: *const u8, len: usize) -> &'a [u8] {
481    if len == 0 || ptr.is_null() {
482        &[]
483    } else {
484        unsafe { std::slice::from_raw_parts(ptr, len) }
485    }
486}
487
488pub(crate) unsafe fn give(bytes: Vec<u8>, out: *mut *mut u8, out_len: *mut usize) {
489    let boxed = bytes.into_boxed_slice();
490    unsafe {
491        *out_len = boxed.len();
492        *out = Box::into_raw(boxed) as *mut u8;
493    }
494}
495
496/// Run one of the language's functions across the C boundary: its answer or
497/// its message out through `out`, and a panic turned into a failure rather
498/// than unwound into the library.
499unsafe fn call(
500    f: impl FnOnce() -> Result<Vec<u8>, String>,
501    out: *mut *mut u8,
502    out_len: *mut usize,
503) -> c_int {
504    match catch_unwind(AssertUnwindSafe(f)) {
505        Ok(Ok(answer)) => {
506            unsafe { give(answer, out, out_len) };
507            0
508        }
509        Ok(Err(message)) => {
510            unsafe { give(message.into_bytes(), out, out_len) };
511            1
512        }
513        Err(_) => {
514            unsafe { give(b"the language panicked".to_vec(), out, out_len) };
515            1
516        }
517    }
518}
519
520/// The language, and the call record read into a [`Call`] for `f`.
521unsafe fn with_call<L: Language>(
522    user_data: *mut c_void,
523    record: *const ffi::TwigLanguageCall,
524    out: *mut *mut u8,
525    out_len: *mut usize,
526    f: impl FnOnce(&L, &Call<'_>, &[u8]) -> Result<Vec<u8>, String>,
527) -> c_int {
528    let hosted = unsafe { &*(user_data as *const Hosted<L>) };
529    let record = unsafe { &*record };
530    let row = std::str::from_utf8(unsafe { bytes(record.row, record.row_len) }).unwrap_or("");
531    let input = unsafe { bytes(record.input, record.input_len) };
532    let features: Vec<&str> = hosted
533        .features
534        .iter()
535        .enumerate()
536        .filter(|(i, _)| *i < 32 && record.features & (1u32 << i) != 0)
537        .map(|(_, name)| name.as_str())
538        .collect();
539    let c = Call {
540        row,
541        features: &features,
542    };
543    unsafe { call(|| f(&hosted.language, &c, input), out, out_len) }
544}
545
546unsafe extern "C" fn parse_trampoline<L: Language>(
547    user_data: *mut c_void,
548    record: *const ffi::TwigLanguageCall,
549    out: *mut *mut u8,
550    out_len: *mut usize,
551) -> c_int {
552    unsafe { with_call::<L>(user_data, record, out, out_len, |l, c, input| l.parse_with(c, input)) }
553}
554
555unsafe extern "C" fn print_trampoline<L: Language>(
556    user_data: *mut c_void,
557    record: *const ffi::TwigLanguageCall,
558    out: *mut *mut u8,
559    out_len: *mut usize,
560) -> c_int {
561    unsafe { with_call::<L>(user_data, record, out, out_len, |l, c, input| l.print_with(c, input)) }
562}
563
564unsafe extern "C" fn render_trampoline<L: Language>(
565    user_data: *mut c_void,
566    record: *const ffi::TwigLanguageCall,
567    out: *mut *mut u8,
568    out_len: *mut usize,
569) -> c_int {
570    unsafe { with_call::<L>(user_data, record, out, out_len, |l, c, input| l.render(c, input)) }
571}
572
573pub(crate) unsafe extern "C" fn free_trampoline(_: *mut c_void, ptr: *mut u8, len: usize) {
574    if !ptr.is_null() {
575        drop(unsafe { Box::from_raw(std::ptr::slice_from_raw_parts_mut(ptr, len)) });
576    }
577}
578
579#[cfg(test)]
580mod tests {
581    use super::*;
582    use crate::{Document, Gesture, Target};
583
584    /// Every non-empty line a paragraph of one `str`; printed back one per
585    /// line. Reads the print table with a scan for `"text":` values, which is
586    /// enough for the tables it prints from.
587    struct Lines(&'static str);
588
589    fn escape(s: &str) -> String {
590        let mut out = String::new();
591        json_string(s, &mut out);
592        out
593    }
594
595    impl Language for Lines {
596        fn describe(&self) -> Description {
597            Description {
598                name: self.0.to_owned(),
599                extensions: vec![format!("{}-ext", self.0)],
600                write: true,
601                samples: vec!["one\ntwo\n".to_owned(), "x\n".to_owned()],
602                ..Description::default()
603            }
604        }
605
606        fn parse(&self, _row: &str, source: &[u8]) -> Result<Vec<u8>, String> {
607            let src = std::str::from_utf8(source).map_err(|_| "not UTF-8".to_owned())?;
608            if src.contains('\0') {
609                return Err("a NUL byte is not a line".to_owned());
610            }
611            let mut rows = vec![format!("{{\"kind\":\"doc\",\"span\":[0,{}]}}", src.len())];
612            let mut start = 0;
613            for line in src.split_inclusive('\n') {
614                let text = line.trim_end_matches('\n').trim_end_matches('\r');
615                if !text.is_empty() {
616                    let para = rows.len();
617                    let end = start + text.len();
618                    rows.push(format!(
619                        "{{\"kind\":\"para\",\"parent\":0,\"span\":[{start},{end}]}}"
620                    ));
621                    rows.push(format!(
622                        "{{\"kind\":\"str\",\"parent\":{para},\"span\":[{start},{end}],\"text\":{}}}",
623                        escape(text)
624                    ));
625                }
626                start += line.len();
627            }
628            Ok(format!("{{\"nodes\":[{}]}}", rows.join(",")).into_bytes())
629        }
630
631        fn print(&self, _row: &str, table: &[u8]) -> Result<Vec<u8>, String> {
632            let table = std::str::from_utf8(table).map_err(|e| e.to_string())?;
633            let mut out = String::new();
634            let mut rest = table;
635            while let Some(at) = rest.find("\"text\":\"") {
636                rest = &rest[at + 8..];
637                let end = rest.find('"').ok_or("unterminated text")?;
638                out.push_str(&rest[..end]);
639                out.push('\n');
640                rest = &rest[end..];
641            }
642            Ok(out.into_bytes())
643        }
644    }
645
646    #[test]
647    fn a_registered_language_is_a_format_like_any_other() {
648        let format = register(Lines("rust-lines")).expect("register");
649        assert!(matches!(format, Format::Runtime(_)));
650        assert_eq!(format.name(), "rust-lines");
651        assert_eq!(Format::by_name("rust-lines"), Some(format));
652        assert_eq!(Format::by_name("gfm"), Some(Format::Gfm));
653        assert_eq!(Format::Gfm.name(), "gfm");
654        assert_eq!(Target::from(format).as_format(), Some(format));
655
656        let mut doc = Document::parse_str("alpha\nbeta\n", format).expect("parse");
657        assert_eq!(doc.render_html().unwrap(), b"<p>alpha</p>\n<p>beta</p>\n");
658        assert_eq!(
659            doc.serialize_to(Target::Markdown).unwrap(),
660            b"alpha\n\nbeta\n"
661        );
662        assert_eq!(
663            doc.serialize_to(Target::from(format)).unwrap(),
664            b"alpha\nbeta\n"
665        );
666
667        let mut md = Document::parse_str("# T\n\nx\n", Format::Markdown).expect("markdown");
668        assert_eq!(md.serialize_to(Target::from(format)).unwrap(), b"T\nx\n");
669
670        // It reads and writes, and does not author.
671        assert!(!format.supports(Gesture::SetBlock));
672        assert!(!format.supports(Gesture::InsertLink));
673
674        // The language's own refusal is a parse error.
675        assert_eq!(
676            Document::parse_str("a\0b", format).err(),
677            Some(Error::ParseError)
678        );
679    }
680
681    #[test]
682    fn a_refusal_says_why_and_registers_nothing() {
683        struct Taken;
684        impl Language for Taken {
685            fn describe(&self) -> Description {
686                Description {
687                    name: "markdown".into(),
688                    samples: vec!["x".into()],
689                    ..Description::default()
690                }
691            }
692            fn parse(&self, _: &str, _: &[u8]) -> Result<Vec<u8>, String> {
693                Ok(br#"{"nodes":[{"kind":"doc","span":[0,1]}]}"#.to_vec())
694            }
695        }
696        let err = register(Taken).unwrap_err();
697        assert_eq!(err.error, Error::InvalidLanguage);
698        assert!(
699            err.message.contains("already a format's"),
700            "{}",
701            err.message
702        );
703
704        struct Panics;
705        impl Language for Panics {
706            fn describe(&self) -> Description {
707                Description {
708                    name: "rust-panics".into(),
709                    samples: vec!["x".into()],
710                    ..Description::default()
711                }
712            }
713            fn parse(&self, _: &str, _: &[u8]) -> Result<Vec<u8>, String> {
714                panic!("boom")
715            }
716        }
717        let err = register(Panics).unwrap_err();
718        assert!(
719            err.message.contains("the language panicked"),
720            "{}",
721            err.message
722        );
723        assert_eq!(Format::by_name("rust-panics"), None);
724    }
725
726    /// [`Lines`] with a `crlf` feature that prints `\r\n` line ends — which
727    /// the parse reads back the same — a set that has it on, and a literal
728    /// spelled by a renderer: all a table needs to author with.
729    struct CrlfLines;
730
731    impl Language for CrlfLines {
732        fn describe(&self) -> Description {
733            Description {
734                name: "rust-crlf".into(),
735                write: true,
736                author: true,
737                syntax: Some(r#"{"renderers":["render_text"]}"#.into()),
738                features: vec![Feature {
739                    name: "crlf".into(),
740                    ..Feature::default()
741                }],
742                sets: vec![Set {
743                    name: "rust-crlf-crlf".into(),
744                    features: vec!["crlf".into()],
745                    ..Set::default()
746                }],
747                samples: vec!["one\ntwo\n".into()],
748                feature_samples: vec![Sample {
749                    text: "x\n".into(),
750                    features: vec!["crlf".into()],
751                }],
752                ..Description::default()
753            }
754        }
755
756        fn parse_with(&self, call: &Call<'_>, source: &[u8]) -> Result<Vec<u8>, String> {
757            Lines("rust-crlf").parse(call.row, source)
758        }
759
760        fn print_with(&self, call: &Call<'_>, table: &[u8]) -> Result<Vec<u8>, String> {
761            let out = Lines("rust-crlf").print(call.row, table)?;
762            Ok(if call.has("crlf") {
763                String::from_utf8_lossy(&out).replace('\n', "\r\n").into_bytes()
764            } else {
765                out
766            })
767        }
768
769        /// A literal is itself. The request's `text` is read with a scan
770        /// that undoes the two escapes the load check's literal needs.
771        fn render(&self, _: &Call<'_>, request: &[u8]) -> Result<Vec<u8>, String> {
772            let request = std::str::from_utf8(request).map_err(|e| e.to_string())?;
773            let at = request.find("\"text\":\"").ok_or("no text")? + 8;
774            let mut out = String::new();
775            let mut chars = request[at..].chars();
776            while let Some(c) = chars.next() {
777                match c {
778                    '"' => return Ok(out.into_bytes()),
779                    '\\' => out.push(chars.next().ok_or("unterminated escape")?),
780                    c => out.push(c),
781                }
782            }
783            Err("unterminated text".to_owned())
784        }
785    }
786
787    #[test]
788    fn features_sets_and_renderers_reach_every_entry_point() {
789        let format = register(CrlfLines).expect("register");
790        let set = Format::by_name("rust-crlf-crlf").expect("the set is a format");
791        assert_ne!(set, format);
792        assert_eq!(set.dialect_of(), Some(format));
793        assert_eq!(format.dialect_of(), None);
794        assert_eq!(format.feature_flags(&["crlf"]), Ok(1));
795        assert_eq!(format.feature_flags(&["tabs"]), Err(Error::NotFound));
796        assert_eq!(Format::Djot.feature_flags(&["math"]), Err(Error::UnsupportedFormat));
797
798        let mut doc = Document::parse_with_features(b"a\nb\n", format, &["crlf"]).unwrap();
799        assert_eq!(doc.serialize_to(Target::from(format)).unwrap(), b"a\r\nb\r\n");
800        let mut doc = Document::parse_str("a\nb\n", set).unwrap();
801        assert_eq!(doc.serialize_to(Target::from(set)).unwrap(), b"a\r\nb\r\n");
802        let mut doc = Document::parse_str("a\nb\n", format).unwrap();
803        assert_eq!(doc.serialize_to(Target::from(format)).unwrap(), b"a\nb\n");
804
805        // It authors: a literal goes in through the renderer.
806        assert!(format.supports(Gesture::InsertLiteral));
807        assert!(format.supports_with_features(&["crlf"], Gesture::InsertLiteral));
808        assert!(!format.supports_with_features(&["tabs"], Gesture::InsertLiteral));
809        let mut editor = crate::Editor::new_with_features(b"a\n", format, &["crlf"]).unwrap();
810        editor.insert_literal(0, "*x* ").unwrap();
811        assert_eq!(editor.source().unwrap(), b"*x* a\n");
812    }
813
814    #[test]
815    fn a_server_answers_the_wire_for_a_language() {
816        let mut server = Server::new(CrlfLines).expect("serve");
817        let described = server.handle(br#"{"op":"describe"}"#).unwrap();
818        let described = String::from_utf8(described).unwrap();
819        assert!(described.starts_with(r#"{"ok":true,"description":{"name":"rust-crlf""#), "{described}");
820        let printed = server
821            .handle(br#"{"op":"print","dialect":"rust-crlf","features":["crlf"],"table":{"nodes":[{"kind":"doc"},{"kind":"para","parent":0},{"kind":"str","parent":1,"text":"hi"}]}}"#)
822            .unwrap();
823        assert_eq!(printed, br#"{"ok":true,"output":"hi\r\n"}"#);
824        let refused = server.handle(b"not json").unwrap();
825        assert!(refused.starts_with(br#"{"ok":false"#));
826
827        struct Unreadable;
828        impl Language for Unreadable {
829            fn describe(&self) -> Description {
830                Description {
831                    name: "rust-unreadable".into(),
832                    syntax: Some("not json".into()),
833                    samples: vec!["x".into()],
834                    ..Description::default()
835                }
836            }
837        }
838        let err = Server::new(Unreadable).err().expect("refused");
839        assert_eq!(err.error, Error::InvalidLanguage);
840    }
841
842}