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::{CallOrIndexed, ExternalName, ExternalPath};
use crate::formatting::buffer::Buffer;
use crate::formatting::VHDLFormatter;
use crate::syntax::Kind;
use crate::{TokenAccess, TokenSpan};
use vhdl_lang::ast::{AttributeName, Name};

impl VHDLFormatter<'_> {
    pub fn format_name(&self, name: WithTokenSpan<&Name>, buffer: &mut Buffer) {
        use Name::*;
        let span = name.span;
        match &name.item {
            Designator(_) => self.join_token_span(span, buffer),
            Selected(name, designator) => {
                self.format_name(name.as_ref().as_ref(), buffer);
                self.join_token_span(
                    TokenSpan::new(designator.token - 1, designator.token),
                    buffer,
                );
            }
            SelectedAll(name) => {
                self.format_name(name.as_ref().as_ref(), buffer);
                self.join_token_span(TokenSpan::new(span.end_token - 1, span.end_token), buffer);
            }
            Slice(name, range) => {
                self.format_name(name.as_ref().as_ref(), buffer);
                self.format_token_id(name.span.end_token + 1, buffer);
                self.format_discrete_range(range, buffer);
                self.format_token_id(span.end_token, buffer);
            }
            Attribute(attr_name) => self.format_attribute_name(attr_name, buffer),
            CallOrIndexed(call_or_indexed) => {
                self.format_call_or_indexed(call_or_indexed, span, buffer)
            }
            External(external) => {
                self.format_external_name(WithTokenSpan::new(external, span), buffer)
            }
        }
    }

    pub fn format_call_or_indexed(
        &self,
        call: &CallOrIndexed,
        span: TokenSpan,
        buffer: &mut Buffer,
    ) {
        self.format_name(call.name.as_ref(), buffer);
        let open_paren = call.name.span.end_token + 1;
        if self.tokens.index(open_paren).kind == Kind::LeftPar {
            self.format_token_id(open_paren, buffer);
        }
        for (i, parameter) in call.parameters.items.iter().enumerate() {
            self.format_association_element(parameter, buffer);
            if let Some(token) = call.parameters.tokens.get(i) {
                self.format_token_id(*token, buffer);
                buffer.push_whitespace();
            }
        }
        let close_paren = span.end_token;
        if self.tokens.index(close_paren).kind == Kind::RightPar {
            self.format_token_id(close_paren, buffer);
        }
    }

    pub fn format_attribute_name(&self, name: &AttributeName, buffer: &mut Buffer) {
        self.format_name(name.name.as_ref(), buffer);
        if let Some(signature) = &name.signature {
            self.format_signature(signature, buffer);
        }
        // '
        self.format_token_id(name.attr.token - 1, buffer);
        self.format_token_id(name.attr.token, buffer);
        if let Some(expr) = &name.expr {
            self.format_token_id(expr.span.start_token - 1, buffer);
            self.format_expression(expr.as_ref().as_ref(), buffer);
            self.format_token_id(expr.span.end_token + 1, buffer);
        }
    }

    pub fn format_external_name(&self, name: WithTokenSpan<&ExternalName>, buffer: &mut Buffer) {
        // <<
        self.format_token_id(name.span.start_token, buffer);
        buffer.push_whitespace();
        // entity class
        self.format_token_id(name.span.start_token + 1, buffer);
        buffer.push_whitespace();
        let path = &name.item.path;
        match &path.item {
            ExternalPath::Package(name) => {
                // @
                self.format_token_id(name.span.start_token - 1, buffer);
                self.format_name(name.as_ref(), buffer)
            }
            ExternalPath::Absolute(name) => {
                // .
                self.format_token_id(name.span.start_token - 1, buffer);
                self.format_name(name.as_ref(), buffer);
            }
            ExternalPath::Relative(name, up_levels) => {
                for i in (1..=*up_levels).rev() {
                    // ^
                    self.format_token_id(name.span.start_token - (2 * i), buffer);
                    // .
                    self.format_token_id(name.span.start_token - (2 * i - 1), buffer);
                }
                self.format_name(name.as_ref(), buffer)
            }
        }
        buffer.push_whitespace();
        self.format_token_id(name.item.colon_token, buffer);
        buffer.push_whitespace();
        self.format_subtype_indication(&name.item.subtype, buffer);
        buffer.push_whitespace();
        // >>
        self.format_token_id(name.span.end_token, buffer);
    }

    pub fn format_name_list(&self, buffer: &mut Buffer, names: &[WithTokenSpan<Name>]) {
        for name in names {
            self.format_name(name.as_ref(), buffer);
            if self
                .tokens
                .get_token(name.span.end_token + 1)
                .is_some_and(|token| token.kind == Kind::Comma)
            {
                self.format_token_id(name.span.end_token + 1, buffer);
                buffer.push_whitespace();
            }
        }
    }
}

#[cfg(test)]
pub mod tests {
    use crate::syntax::test::Code;
    use vhdl_lang::formatting::test_utils::check_formatted;

    pub fn check_name(input: &str) {
        check_formatted(input, input, Code::name, |formatter, ast, buffer| {
            formatter.format_name(ast.as_ref(), buffer)
        })
    }

    #[test]
    fn simple_names() {
        check_name("\"+\"");
        check_name("\"AND\"");
        check_name("\"and\"");
    }

    #[test]
    fn selected_names() {
        check_name("foo.bar.baz");
        check_name("foo.all");
    }

    #[test]
    fn slice_names() {
        check_name("prefix(0 to 3)");
        check_name("prefix(3 downto 0)");
    }

    #[test]
    fn attribute_name() {
        check_name("prefix'subtype");
        check_name("prefix'element");
        check_name("prefix'foo(expr + 1)");
        check_name("prefix[return natural]'foo(expr + 1)");
    }

    #[test]
    fn complex_names() {
        check_name("prefix(foo(0)'range)");
    }

    #[test]
    fn external_names() {
        check_name("<< signal dut.gen(0) : std_logic >>");
        check_name("<< signal .dut.gen(0) : std_logic >>");
        check_name("<< signal @dut.gen(0) : std_logic >>");
        check_name("<< signal ^.dut.gen(0) : std_logic >>");
        check_name("<< signal ^.^.^.dut.gen(0) : std_logic >>");
    }
}