Skip to content

Commit 5e85aa8

Browse files
committed
Add VHDL 2019 parsing for components
1 parent 2096ea0 commit 5e85aa8

5 files changed

Lines changed: 97 additions & 4 deletions

vhdl_syntax/src/parser/mod.rs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,16 @@ pub(crate) fn parse_syntax(
8888
let (green, diagnostics) = parser.end();
8989
(SyntaxNode::new_root(green), diagnostics)
9090
}
91+
92+
#[cfg(test)]
93+
pub(crate) fn parse_syntax_with_standard(
94+
standard: VHDLStandard,
95+
input: impl IntoIterator<Item = u8>,
96+
parser_fn: impl FnOnce(&mut Parser),
97+
) -> (SyntaxNode, Vec<diagnostics::ParserDiagnostic>) {
98+
let token_stream: TokenStream = Tokenizer::with_standard(standard, input.into_iter()).collect();
99+
let mut parser = Parser::new(token_stream.into(), standard);
100+
parser_fn(&mut parser);
101+
let (green, diagnostics) = parser.end();
102+
(SyntaxNode::new_root(green), diagnostics)
103+
}

vhdl_syntax/src/parser/productions/component_declaration.rs

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Copyright (c) 2025, Lukas Scheller lukasscheller@icloud.com
66

77
use crate::parser::Parser;
8+
use crate::standard::VHDLStandard;
89
use crate::syntax::node_kind::NodeKind::*;
910
use crate::tokens::token_kind::Keyword as Kw;
1011
use crate::tokens::TokenKind::*;
@@ -31,7 +32,12 @@ impl Parser {
3132

3233
pub fn component_declaration_epilogue(&mut self) {
3334
self.start_node(ComponentDeclarationEpilogue);
34-
self.expect_tokens([Keyword(Kw::End), Keyword(Kw::Component)]);
35+
self.expect_token(Keyword(Kw::End));
36+
if self.standard().is_at_least(VHDLStandard::VHDL2019) {
37+
self.opt_token(Keyword(Kw::Component));
38+
} else {
39+
self.expect_token(Keyword(Kw::Component));
40+
}
3541
self.opt_identifier();
3642
self.expect_token(SemiColon);
3743
self.end_node();
@@ -40,8 +46,9 @@ impl Parser {
4046

4147
#[cfg(test)]
4248
mod tests {
43-
use crate::parser::test_utils::to_test_text;
44-
use crate::parser::Parser;
49+
use crate::parser::test_utils::{to_test_text, to_test_text_with_standard};
50+
use crate::parser::{parse_syntax_with_standard, Parser};
51+
use crate::standard::VHDLStandard;
4552

4653
#[test]
4754
fn simple_components() {
@@ -95,4 +102,40 @@ end component;
95102
",
96103
));
97104
}
105+
106+
#[test]
107+
fn component_end_without_keyword_vhdl2019() {
108+
// `end;` without the trailing `component` keyword is valid from VHDL-2019 onwards.
109+
insta::assert_snapshot!(to_test_text_with_standard(
110+
VHDLStandard::VHDL2019,
111+
Parser::component_declaration,
112+
"\
113+
component foo is
114+
end;
115+
"
116+
));
117+
// Optional identifier after `end` is also accepted.
118+
insta::assert_snapshot!(to_test_text_with_standard(
119+
VHDLStandard::VHDL2019,
120+
Parser::component_declaration,
121+
"\
122+
component foo is
123+
end foo;
124+
"
125+
));
126+
}
127+
128+
#[test]
129+
fn component_end_without_keyword_is_error_before_vhdl2019() {
130+
// Omitting the trailing `component` keyword must produce a diagnostic under VHDL-2008.
131+
let (_, diagnostics) = parse_syntax_with_standard(
132+
VHDLStandard::VHDL2008,
133+
"component foo is\nend;\n".bytes(),
134+
Parser::component_declaration,
135+
);
136+
assert!(
137+
!diagnostics.is_empty(),
138+
"expected a diagnostic for missing 'component' keyword under VHDL-2008"
139+
);
140+
}
98141
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
source: vhdl_syntax/src/parser/productions/component_declaration.rs
3+
expression: "to_test_text_with_standard(VHDLStandard::VHDL2019,\nParser::component_declaration, \"\\\ncomponent foo is\nend foo;\n\")"
4+
---
5+
ComponentDeclaration
6+
ComponentDeclarationPreamble
7+
Keyword(Component)
8+
Identifier 'foo'
9+
Keyword(Is)
10+
ComponentDeclarationEpilogue
11+
Keyword(End)
12+
Identifier 'foo'
13+
SemiColon
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
source: vhdl_syntax/src/parser/productions/component_declaration.rs
3+
expression: "to_test_text_with_standard(VHDLStandard::VHDL2019,\nParser::component_declaration, \"\\\ncomponent foo is\nend;\n\")"
4+
---
5+
ComponentDeclaration
6+
ComponentDeclarationPreamble
7+
Keyword(Component)
8+
Identifier 'foo'
9+
Keyword(Is)
10+
ComponentDeclarationEpilogue
11+
Keyword(End)
12+
SemiColon

vhdl_syntax/src/parser/test_utils.rs

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,11 +4,23 @@
44
//
55
// Copyright (c) 2025, Lukas Scheller lukasscheller@icloud.com
66

7-
use crate::parser::{parse_syntax, Parser};
7+
use crate::parser::{parse_syntax, parse_syntax_with_standard, Parser};
8+
use crate::standard::VHDLStandard;
89

910
/// Returns the AST text for snapshot assertions.
1011
pub fn to_test_text(func: impl FnOnce(&mut Parser), input: &str) -> String {
1112
let (entity, diagnostics) = parse_syntax(input, func);
1213
assert!(diagnostics.is_empty(), "got diagnostics: {:?}", diagnostics);
1314
entity.test_text()
1415
}
16+
17+
/// Returns the AST text for snapshot assertions, tokenizing and parsing under `standard`.
18+
pub fn to_test_text_with_standard(
19+
standard: VHDLStandard,
20+
func: impl FnOnce(&mut Parser),
21+
input: &str,
22+
) -> String {
23+
let (entity, diagnostics) = parse_syntax_with_standard(standard, input.bytes(), func);
24+
assert!(diagnostics.is_empty(), "got diagnostics: {:?}", diagnostics);
25+
entity.test_text()
26+
}

0 commit comments

Comments
 (0)