vhdl_lang 0.86.0

VHDL Language Frontend
Documentation
// This Source Code Form is subject to the terms of the Mozilla Public
// Lic// License, v. 2.0. If a copy of the MPL was not distributed with this file,
// This Source Code Form is subject to the terms of the Mozilla Public
// You can obtain one at http://mozilla.org/MPL/2.0/.
//
// Copyright (c) 2024, Olof Kraigher olof.kraigher@gmail.com

use crate::ast::token_range::WithTokenSpan;
use crate::ast::{
    ElementAssociation, Expression, Operator, ResolutionIndication, SubtypeConstraint,
    SubtypeIndication,
};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::HasTokenSpan;
use vhdl_lang::ast::{Allocator, QualifiedExpression};

impl VHDLFormatter<'_> {
    pub fn format_expression(&self, expression: WithTokenSpan<&Expression>, buffer: &mut Buffer) {
        let span = expression.span;
        use Expression::*;
        match &expression.item {
            Binary(op, lhs, rhs) => {
                self.format_expression(lhs.as_ref().as_ref(), buffer);
                buffer.push_whitespace();
                self.format_token_id(op.token, buffer);
                buffer.push_whitespace();
                self.format_expression(rhs.as_ref().as_ref(), buffer);
            }
            Unary(op, rhs) => {
                self.format_token_id(op.token, buffer);
                match op.item.item {
                    Operator::Minus | Operator::Plus | Operator::QueQue => {
                        // Leave as unary operator without whitespace
                    }
                    _ => buffer.push_whitespace(),
                }
                self.format_expression(rhs.as_ref().as_ref(), buffer);
            }
            Aggregate(aggregate) => {
                self.format_token_id(span.start_token, buffer);
                self.format_element_associations(aggregate, buffer);
                self.format_token_id(span.end_token, buffer);
            }
            Qualified(qualified_expr) => self.format_qualified_expression(qualified_expr, buffer),
            Name(name) => self.format_name(WithTokenSpan::new(name, span), buffer),
            Literal(_) => self.format_token_span(span, buffer),
            New(allocator) => self.format_allocator(allocator, buffer),
            Parenthesized(expression) => {
                self.format_token_id(span.start_token, buffer);
                self.format_expression(expression.as_ref().as_ref(), buffer);
                self.format_token_id(span.end_token, buffer);
            }
        }
    }

    pub fn format_element_associations(
        &self,
        associations: &[WithTokenSpan<ElementAssociation>],
        buffer: &mut Buffer,
    ) {
        for (i, association) in associations.iter().enumerate() {
            match &association.item {
                ElementAssociation::Positional(expression) => {
                    self.format_expression(expression.as_ref(), buffer)
                }
                ElementAssociation::Named(choices, expression) => {
                    for (j, choice) in choices.iter().enumerate() {
                        self.format_choice(choice, buffer);
                        if j < choices.len() - 1 {
                            buffer.push_whitespace();
                            self.format_token_id(choice.span.end_token + 1, buffer);
                            buffer.push_whitespace();
                        }
                    }
                    buffer.push_whitespace();
                    self.format_token_id(expression.span.start_token - 1, buffer);
                    buffer.push_whitespace();
                    self.format_expression(expression.as_ref(), buffer);
                }
            }
            if i < associations.len() - 1 {
                self.format_token_id(association.span.end_token + 1, buffer);
                buffer.push_whitespace();
            }
        }
    }

    pub fn format_subtype_indication(&self, indication: &SubtypeIndication, buffer: &mut Buffer) {
        if let Some(resolution) = &indication.resolution {
            self.format_resolution_indication(resolution, buffer);
            buffer.push_whitespace();
        }
        self.format_name(indication.type_mark.as_ref(), buffer);
        if let Some(constraint) = &indication.constraint {
            if matches!(constraint.item, SubtypeConstraint::Range(_)) {
                buffer.push_whitespace();
            }
            self.format_subtype_constraint(constraint, buffer)
        }
    }

    pub fn format_resolution_indication(
        &self,
        indication: &ResolutionIndication,
        buffer: &mut Buffer,
    ) {
        match &indication {
            ResolutionIndication::FunctionName(name) => self.format_name(name.as_ref(), buffer),
            ResolutionIndication::ArrayElement(element) => {
                self.format_token_id(element.span.start_token - 1, buffer);
                self.format_name(element.as_ref(), buffer);
                self.format_token_id(element.span.end_token + 1, buffer);
            }
            ResolutionIndication::Record(record) => {
                let span = record.span;
                self.format_token_id(span.start_token, buffer);
                for (i, element_resolution) in record.item.iter().enumerate() {
                    self.format_token_id(element_resolution.ident.token, buffer);
                    buffer.push_whitespace();
                    self.format_resolution_indication(&element_resolution.resolution, buffer);
                    if i < record.item.len() - 1 {
                        // ,
                        self.format_token_id(
                            element_resolution.resolution.get_end_token() + 1,
                            buffer,
                        );
                        buffer.push_whitespace();
                    }
                }
                self.format_token_id(span.end_token, buffer);
            }
        }
    }

    // Helper to format ` := <expression>`
    pub(crate) fn format_default_expression(
        &self,
        expression: Option<&WithTokenSpan<Expression>>,
        buffer: &mut Buffer,
    ) {
        if let Some(expr) = expression {
            buffer.push_whitespace();
            self.format_token_id(expr.span.start_token - 1, buffer);
            buffer.push_whitespace();
            self.format_expression(expr.as_ref(), buffer);
        }
    }

    pub fn format_qualified_expression(
        &self,
        expression: &QualifiedExpression,
        buffer: &mut Buffer,
    ) {
        self.format_name(expression.type_mark.as_ref(), buffer);
        // '
        self.format_token_id(expression.type_mark.span.end_token + 1, buffer);
        self.format_expression(expression.expr.as_ref(), buffer);
    }

    pub fn format_allocator(&self, allocator: &WithTokenSpan<Allocator>, buffer: &mut Buffer) {
        // new
        self.format_token_id(allocator.span.start_token - 1, buffer);
        buffer.push_whitespace();
        match &allocator.item {
            Allocator::Qualified(expr) => self.format_qualified_expression(expr, buffer),
            Allocator::Subtype(subtype) => self.format_subtype_indication(subtype, buffer),
        }
    }
}

#[cfg(test)]
mod test {
    use crate::analysis::tests::Code;
    use crate::ast::token_range::WithTokenSpan;
    use crate::formatting::VHDLFormatter;
    use vhdl_lang::formatting::buffer::Buffer;
    use vhdl_lang::formatting::test_utils::check_formatted;

    fn check_expression(input: &str) {
        let code = Code::new(input);
        let expression = code.expr();
        let tokens = code.tokenize();
        let formatter = VHDLFormatter::new(&tokens);
        let mut buffer = Buffer::new();
        formatter.format_expression(
            WithTokenSpan::new(&expression.item, code.token_span()),
            &mut buffer,
        );
        assert_eq!(buffer.as_str(), input);
    }

    #[test]
    fn test_simple_expression() {
        check_expression("name")
    }

    #[test]
    fn test_parenthesized_expression() {
        check_expression("(name)");
        check_expression("(A) or (B)");
    }

    #[test]
    fn formal_literal() {
        check_expression("12387.44e7");
        check_expression("7");
    }

    #[test]
    fn binary_expressions() {
        check_expression("1 + B");
        check_expression("2 sll 2");
    }

    #[test]
    fn unary_expressions() {
        check_expression("+B");
        check_expression("-2");
        check_expression("not A")
    }

    #[test]
    fn complex_expression() {
        check_expression("A + B - C");
        check_expression("(A * B) + C");
        check_expression("((A * B) + C)");
    }

    #[test]
    fn aggregate() {
        check_expression("(1, 2)");
        check_expression("(1 => 2, 3)");
        check_expression("(others => 1, others => 2)");
        check_expression("(1 downto 0 => 2)");
        check_expression("(0 to 1 => 2)");
        check_expression("(1 | 2 => 3)");
    }

    #[test]
    fn qualified_expressions() {
        check_expression("integer_vector'(0, 1)");
        check_expression("foo'(1 + 2)");
        check_expression("foo'(others => '1')");
    }

    #[test]
    fn allocator_expressions() {
        check_expression("new integer_vector'(0, 1)");
        check_expression("new integer_vector");
        check_expression("new integer_vector(0 to 1)");
        check_expression("new integer_vector(foo'range)");
    }

    fn check_subtype_indication(input: &str) {
        check_formatted(
            input,
            input,
            Code::subtype_indication,
            |formatter, subtype_indication, buffer| {
                formatter.format_subtype_indication(subtype_indication, buffer)
            },
        );
    }

    #[test]
    fn resolution_indication() {
        check_subtype_indication("resolve std_logic");
        check_subtype_indication("(resolve) integer_vector");
        check_subtype_indication("(elem resolve) rec_t");
        check_subtype_indication(
            "(elem1 (resolve1), elem2 resolve2, elem3 (sub_elem sub_resolve)) rec_t",
        );
    }

    #[test]
    fn expression_with_comments() {
        check_expression(
            "\
-- Some comment
A & -- And
B & -- as well as
C",
        )
    }
}