Quellcodebibliothek Statistik Leitseite products/Sources/formale Sprachen/C/Firefox/third_party/rust/fluent-syntax/src/   (Firefox Browser Version 153.0.1©)  Datei vom 27.6.2026 mit Größe 20 kB image not shown  

Quelle  serializer.rs

  Sprache: Rust
 

//! Fluent Translation List serialization utilities
//!
//! This modules provides a way to serialize an abstract syntax tree representing a
//! Fluent Translation List. Use cases include normalization and programmatic
//! manipulation of a Fluent Translation List.
//!
//! # Example
//!
//! ```
//! use fluent_syntax::parser;
//! use fluent_syntax::serializer;
//!
//! let ftl = r#"# This is a message comment
//! hello-world = Hello World!
//! "#;
//!
//! let resource = parser::parse(ftl).expect("Failed to parse an FTL resource.");
//!
//! let serialized = serializer::serialize(&resource);
//!
//! assert_eq!(ftl, serialized);
//! ```

use crate::{ast::*, parser::matches_fluent_ws, parser::Slice};
use std::fmt::Write;

/// Serializes an abstract syntax tree representing a Fluent Translation List into a
/// String.
///
/// # Example
///
/// ```
/// use fluent_syntax::parser;
/// use fluent_syntax::serializer;
///
/// let ftl = r#"
/// unnormalized-message=This message has
///   abnormal spacing and indentation"#;
///
/// let resource = parser::parse(ftl).expect("Failed to parse an FTL resource.");
///
/// let serialized = serializer::serialize(&resource);
///
/// let expected = r#"unnormalized-message =
///     This message has
///     abnormal spacing and indentation
/// "#;
///
/// assert_eq!(expected, serialized);
/// ```
pub fn serialize<'s, S: Slice<'s>>(resource: &Resource<S>) -> String {
    serialize_with_options(resource, Options::default())
}

/// Serializes an abstract syntax tree representing a Fluent Translation List into a
/// String accepting custom options.
pub fn serialize_with_options<'s, S: Slice<'s>>(
    resource: &Resource<S>,
    options: Options,
) -> String {
    let mut ser = Serializer::new(options);
    ser.serialize_resource(resource);
    ser.into_serialized_text()
}

#[derive(Debug)]
struct Serializer {
    writer: TextWriter,
    options: Options,
    state: State,
}

impl Serializer {
    fn new(options: Options) -> Self {
        Serializer {
            writer: TextWriter::default(),
            options,
            state: State::default(),
        }
    }

    fn serialize_resource<'s, S: Slice<'s>>(&mut self, res: &Resource<S>) {
        for entry in &res.body {
            match entry {
                Entry::Message(msg) => self.serialize_message(msg),
                Entry::Term(term) => self.serialize_term(term),
                Entry::Comment(comment) => self.serialize_free_comment(comment, "#"),
                Entry::GroupComment(comment) => self.serialize_free_comment(comment, "##"),
                Entry::ResourceComment(comment) => self.serialize_free_comment(comment, "###"),
                Entry::Junk { content } => {
                    if self.options.with_junk {
                        self.serialize_junk(content.as_ref());
                    }
                }
            };

            self.state.wrote_non_junk_entry = !matches!(entry, Entry::Junk { .. });
        }
    }

    fn into_serialized_text(self) -> String {
        self.writer.buffer
    }

    fn serialize_junk(&mut self, junk: &str) {
        self.writer.write_literal(junk);
    }

    fn serialize_free_comment<'s, S: Slice<'s>>(&mut self, comment: &Comment<S>, prefix: &str) {
        if self.state.wrote_non_junk_entry {
            self.writer.newline();
        }
        self.serialize_comment(comment, prefix);
        self.writer.newline();
    }

    fn serialize_comment<'s, S: Slice<'s>>(&mut self, comment: &Comment<S>, prefix: &str) {
        for line in &comment.content {
            self.writer.write_literal(prefix);

            if !line.as_ref().trim_matches(matches_fluent_ws).is_empty() {
                self.writer.write_literal(" ");
                self.writer.write_literal(line.as_ref());
            }

            self.writer.newline();
        }
    }

    fn serialize_message<'s, S: Slice<'s>>(&mut self, msg: &Message<S>) {
        if let Some(comment) = msg.comment.as_ref() {
            self.serialize_comment(comment, "#");
        }

        self.writer.write_literal(msg.id.name.as_ref());
        self.writer.write_literal(" =");

        if let Some(value) = msg.value.as_ref() {
            self.serialize_pattern(value);
        }

        self.serialize_attributes(&msg.attributes);

        self.writer.newline();
    }

    fn serialize_term<'s, S: Slice<'s>>(&mut self, term: &Term<S>) {
        if let Some(comment) = term.comment.as_ref() {
            self.serialize_comment(comment, "#");
        }

        self.writer.write_literal("-");
        self.writer.write_literal(term.id.name.as_ref());
        self.writer.write_literal(" =");
        self.serialize_pattern(&term.value);

        self.serialize_attributes(&term.attributes);

        self.writer.newline();
    }

    fn serialize_pattern<'s, S: Slice<'s>>(&mut self, pattern: &Pattern<S>) {
        let start_on_newline = pattern.starts_on_new_line();

        if start_on_newline {
            self.writer.newline();
            self.writer.indent();
        } else {
            self.writer.write_literal(" ");
        }

        for element in &pattern.elements {
            self.serialize_element(element);
        }

        if start_on_newline {
            self.writer.dedent();
        }
    }

    fn serialize_attributes<'s, S: Slice<'s>>(&mut self, attrs: &[Attribute<S>]) {
        if attrs.is_empty() {
            return;
        }

        self.writer.indent();

        for attr in attrs {
            self.writer.newline();
            self.serialize_attribute(attr);
        }

        self.writer.dedent();
    }

    fn serialize_attribute<'s, S: Slice<'s>>(&mut self, attr: &Attribute<S>) {
        self.writer.write_literal(".");
        self.writer.write_literal(attr.id.name.as_ref());
        self.writer.write_literal(" =");

        self.serialize_pattern(&attr.value);
    }

    fn serialize_element<'s, S: Slice<'s>>(&mut self, elem: &PatternElement<S>) {
        match elem {
            PatternElement::TextElement { value } => self.writer.write_literal(value.as_ref()),
            PatternElement::Placeable { expression } => match expression {
                Expression::Inline(InlineExpression::Placeable { expression }) => {
                    // A placeable inside a placeable is a special case because we
                    // don't want the braces to look silly (e.g. "{ { Foo() } }").
                    self.writer.write_literal("{{ ");
                    self.serialize_expression(expression);
                    self.writer.write_literal(" }}");
                }
                Expression::Select { .. } => {
                    // select adds its own newline and indent, emit the brace
                    // *without* a space so we don't get 5 spaces instead of 4
                    self.writer.write_literal("{ ");
                    self.serialize_expression(expression);
                    self.writer.write_literal("}");
                }
                Expression::Inline(_) => {
                    self.writer.write_literal("{ ");
                    self.serialize_expression(expression);
                    self.writer.write_literal(" }");
                }
            },
        }
    }

    fn serialize_expression<'s, S: Slice<'s>>(&mut self, expr: &Expression<S>) {
        match expr {
            Expression::Inline(inline) => self.serialize_inline_expression(inline),
            Expression::Select { selector, variants } => {
                self.serialize_select_expression(selector, variants);
            }
        }
    }

    fn serialize_inline_expression<'s, S: Slice<'s>>(&mut self, expr: &InlineExpression<;S>) {
        match expr {
            InlineExpression::StringLiteral { value } => {
                self.writer.write_literal("\"");
                self.writer.write_literal(value.as_ref());
                self.writer.write_literal("\"");
            }
            InlineExpression::NumberLiteral { value } => self.writer.write_literal(value.as_ref()),
            InlineExpression::VariableReference {
                id: Identifier { name: value },
            } => {
                self.writer.write_literal("$");
                self.writer.write_literal(value.as_ref());
            }
            InlineExpression::FunctionReference { id, arguments } => {
                self.writer.write_literal(id.name.as_ref());
                self.serialize_call_arguments(arguments);
            }
            InlineExpression::MessageReference { id, attribute } => {
                self.writer.write_literal(id.name.as_ref());

                if let Some(attr) = attribute.as_ref() {
                    self.writer.write_literal(".");
                    self.writer.write_literal(attr.name.as_ref());
                }
            }
            InlineExpression::TermReference {
                id,
                attribute,
                arguments,
            } => {
                self.writer.write_literal("-");
                self.writer.write_literal(id.name.as_ref());

                if let Some(attr) = attribute.as_ref() {
                    self.writer.write_literal(".");
                    self.writer.write_literal(attr.name.as_ref());
                }
                if let Some(args) = arguments.as_ref() {
                    self.serialize_call_arguments(args);
                }
            }
            InlineExpression::Placeable { expression } => {
                self.writer.write_literal("{");
                self.serialize_expression(expression);
                self.writer.write_literal("}");
            }
        }
    }

    fn serialize_select_expression<'s, S: Slice<'s>>(
        &mut self,
        selector: &InlineExpression<S>,
        variants: &[Variant<S>],
    ) {
        self.serialize_inline_expression(selector);
        self.writer.write_literal(" ->");

        self.writer.newline();
        self.writer.indent();

        for variant in variants {
            self.serialize_variant(variant);
            self.writer.newline();
        }

        self.writer.dedent();
    }

    fn serialize_variant<'s, S: Slice<'s>>(&mut self, variant: &Variant<S>) {
        if variant.default {
            self.writer.write_char_into_indent('*');
        }

        self.writer.write_literal("[");
        self.serialize_variant_key(&variant.key);
        self.writer.write_literal("]");
        self.serialize_pattern(&variant.value);
    }

    fn serialize_variant_key<'s, S: Slice<'s>>(&mut self, key: &VariantKey<S>) {
        match key {
            VariantKey::NumberLiteral { value } | VariantKey::Identifier { name: value } => {
                self.writer.write_literal(value.as_ref());
            }
        }
    }

    fn serialize_call_arguments<'s, S: Slice<'s>>(&mut self, args: &CallArguments<S>) {
        let mut argument_written = false;

        self.writer.write_literal("(");

        for positional in &args.positional {
            if argument_written {
                self.writer.write_literal(", ");
            }

            self.serialize_inline_expression(positional);
            argument_written = true;
        }

        for named in &args.named {
            if argument_written {
                self.writer.write_literal(", ");
            }

            self.writer.write_literal(named.name.name.as_ref());
            self.writer.write_literal(": ");
            self.serialize_inline_expression(&named.value);
            argument_written = true;
        }

        self.writer.write_literal(")");
    }
}

impl<'s, S: Slice<'s>> Pattern<S> {
    fn starts_on_new_line(&self) -> bool {
        !self.has_leading_text_dot() && self.is_multiline()
    }

    fn is_multiline(&self) -> bool {
        self.elements.iter().any(|elem| match elem {
            PatternElement::TextElement { value } => value.as_ref().contains('\n'),
            PatternElement::Placeable { expression } => is_select_expr(expression),
        })
    }

    fn has_leading_text_dot(&self) -> bool {
        if let Some(PatternElement::TextElement { value }) = self.elements.first() {
            value.as_ref().starts_with('.')
        } else {
            false
        }
    }
}

fn is_select_expr<'s, S: Slice<'s>>(expr: &Expression<S>) -> bool {
    match expr {
        Expression::Select { .. } => true,
        Expression::Inline(InlineExpression::Placeable { expression }) => {
            is_select_expr(expression)
        }
        Expression::Inline(_) => false,
    }
}

/// Options for serializing an abstract syntax tree.
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
pub struct Options {
    /// Whether invalid text fragments should be serialized, too.
    pub with_junk: bool,
}

#[derive(Debug, Default, PartialEq)]
struct State {
    wrote_non_junk_entry: bool,
}

#[derive(Clone, Debug, Default)]
struct TextWriter {
    buffer: String,
    indent_level: usize,
}

impl TextWriter {
    fn indent(&mut self) {
        self.indent_level += 1;
    }

    fn dedent(&mut self) {
        self.indent_level = self
            .indent_level
            .checked_sub(1)
            .expect("Dedenting without a corresponding indent");
    }

    fn write_indent(&mut self) {
        for _ in 0..self.indent_level {
            self.buffer.push_str("    ");
        }
    }

    fn newline(&mut self) {
        if self.buffer.ends_with('\r') {
            // handle rare edge case, where the trailing `\r` would get confused
            // as part of the line ending
            self.buffer.push('\r');
        }
        self.buffer.push('\n');
    }

    fn write_literal(&mut self, item: &str) {
        if self.buffer.ends_with('\n') {
            // we've just added a newline, make sure it's properly indented
            self.write_indent();
        }

        write!(self.buffer, "{}", item).expect("Writing to an in-memory buffer never fails");
    }

    fn write_char_into_indent(&mut self, ch: char) {
        if self.buffer.ends_with('\n') {
            self.write_indent();
        }
        self.buffer.pop();
        self.buffer.push(ch);
    }
}

#[cfg(test)]
mod test {
    use super::*;
    use crate::parser::parse;

    #[test]
    fn write_something_then_indent() {
        let mut writer = TextWriter::default();

        writer.write_literal("foo =");
        writer.newline();
        writer.indent();
        writer.write_literal("first line");
        writer.newline();
        writer.write_literal("second line");
        writer.newline();
        writer.dedent();
        writer.write_literal("not indented");
        writer.newline();

        let got = &writer.buffer;
        assert_eq!(
            got,
            "foo =\n    first line\n    second line\nnot indented\n"
        );
    }

    macro_rules! text_message {
        ($name:expr, $value:expr) => {
            Entry::Message(Message {
                id: Identifier { name: $name },
                value: Some(Pattern {
                    elements: vec![PatternElement::TextElement { value: $value }],
                }),
                attributes: vec![],
                comment: None,
            })
        };
    }

    impl<'a> Entry<&'a str> {
        fn as_message(&mut self) -> &mut Message<&an style='color:blue'>'a str> {
            match self {
                Self::Message(msg) => msg,
                _ => panic!("Expected Message"),
            }
        }
    }

    impl<'a> Message<&'a str> {
        fn as_pattern(&mut self) -> &mut Pattern<&an style='color:blue'>'a str> {
            self.value.as_mut().expect("Expected Pattern")
        }
    }

    impl<'a> PatternElement<&'a str> {
        fn as_text(&mut self) -> &mut &'a str {
            match self {
                Self::TextElement { value } => value,
                _ => panic!("Expected TextElement"),
            }
        }

        fn as_expression(&mut self) -> &mut Expression<&'a str> {
            match self {
                Self::Placeable { expression } => expression,
                _ => panic!("Expected Placeable"),
            }
        }
    }

    impl<'a> Expression<&'a str> {
        fn as_variants(&mut self) -> &mut Vec<Variant<&'a str>> {
            match self {
                Self::Select { variants, .. } => variants,
                _ => panic!("Expected Select"),
            }
        }
        fn as_inline_variable_id(&mut self) -> &mut Identifier<&'a str> {
            match self {
                Self::Inline(InlineExpression::VariableReference { id }) => id,
                _ => panic!("Expected Inline"),
            }
        }
    }

    #[test]
    fn change_id() {
        let mut ast = parse("foo = bar\n").expect("failed to parse ftl resource");
        ast.body[0].as_message().id.name = "baz";
        assert_eq!(serialize(&ast), "baz = bar\n");
    }

    #[test]
    fn change_value() {
        let mut ast = parse("foo = bar\n").expect("failed to parse ftl resource");
        *ast.body[0].as_message().as_pattern().elements[0].as_text() = "baz";
        assert_eq!("foo = baz\n", serialize(&ast));
    }

    #[test]
    fn add_expression_variant() {
        let message = concat!(
            "foo =\n",
            "    { $num ->\n",
            "       *[other] { $num } bars\n",
            "    }\n"
        );
        let mut ast = parse(message).expect("failed to parse ftl resource");

        let one_variant = Variant {
            key: VariantKey::Identifier { name: "one" },
            value: Pattern {
                elements: vec![
                    PatternElement::Placeable {
                        expression: Expression::Inline(InlineExpression::VariableReference {
                            id: Identifier { name: "num" },
                        }),
                    },
                    PatternElement::TextElement { value: " bar" },
                ],
            },
            default: false,
        };
        ast.body[0].as_message().as_pattern().elements[0]
            .as_expression()
            .as_variants()
            .insert(0, one_variant);

        let expected = concat!(
            "foo =\n",
            "    { $num ->\n",
            "        [one] { $num } bar\n",
            "       *[other] { $num } bars\n",
            "    }\n"
        );
        assert_eq!(serialize(&ast), expected);
    }

    #[test]
    fn change_variable_reference() {
        let mut ast = parse("foo = { $bar }\n").expect("failed to parse ftl resource");
        ast.body[0].as_message().as_pattern().elements[0]
            .as_expression()
            .as_inline_variable_id()
            .name = "qux";
        assert_eq!("foo = { $qux }\n", serialize(&ast));
    }

    #[test]
    fn remove_message() {
        let mut ast = parse("foo = bar\nbaz = qux\n").expect("failed to parse ftl resource");
        ast.body.pop();
        assert_eq!("foo = bar\n", serialize(&ast));
    }

    #[test]
    fn add_message_at_top() {
        let mut ast = parse("foo = bar\n").expect("failed to parse ftl resource");
        ast.body.insert(0, text_message!("baz""qux"));
        assert_eq!("baz = qux\nfoo = bar\n", serialize(&ast));
    }

    #[test]
    fn add_message_at_end() {
        let mut ast = parse("foo = bar\n").expect("failed to parse ftl resource");
        ast.body.push(text_message!("baz""qux"));
        assert_eq!("foo = bar\nbaz = qux\n", serialize(&ast));
    }

    #[test]
    fn add_message_in_between() {
        let mut ast = parse("foo = bar\nbaz = qux\n").expect("failed to parse ftl resource");
        ast.body.insert(1, text_message!("hello""there"));
        assert_eq!("foo = bar\nhello = there\nbaz = qux\n", serialize(&ast));
    }

    #[test]
    fn add_message_comment() {
        let mut ast = parse("foo = bar\n").expect("failed to parse ftl resource");
        ast.body[0].as_message().comment.replace(Comment {
            content: vec!["great message!"],
        });
        assert_eq!("# great message!\nfoo = bar\n", serialize(&ast));
    }

    #[test]
    fn remove_message_comment() {
        let mut ast = parse("# great message!\nfoo = bar\n").expect("failed to parse ftl resource");
        ast.body[0].as_message().comment.take();
        assert_eq!("foo = bar\n", serialize(&ast));
    }

    #[test]
    fn edit_message_comment() {
        let mut ast = parse("# great message!\nfoo = bar\n").expect("failed to parse ftl resource");
        ast.body[0]
            .as_message()
            .comment
            .as_mut()
            .expect("comment is missing")
            .content[0] = "very original";
        assert_eq!("# very original\nfoo = bar\n", serialize(&ast));
    }
}

Messung V0.5 in Prozent
C=92 H=100 G=95

¤ Dauer der Verarbeitung: 0.7 Sekunden  ¤

*© Formatika GbR, Deutschland






Wurzel

Suchen

PVS Prover

Isabelle Prover

NIST Cobol Testsuite

Cephes Mathematical Library

Vienna Development Method

Haftungshinweis

Die Informationen auf dieser Webseite wurden nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit, noch Qualität der bereit gestellten Informationen zugesichert.

Bemerkung:

Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.