use crate::language::Token;
use crate::parsers::SyntaxKind;
use super::Parser;
impl Parser<'_> {
pub(crate) fn is_at_compiler_conditional_property_statement(&self) -> bool {
if !self.at_compiler_directive_keyword(Token::IfKeyword) {
return false;
}
let mut index = self.pos;
while let Some((_, token)) = self.tokens.get(index) {
index += 1;
if *token == Token::Newline {
break;
}
}
while let Some((_, token)) = self.tokens.get(index) {
match token {
Token::Whitespace
| Token::Newline
| Token::PublicKeyword
| Token::PrivateKeyword
| Token::FriendKeyword
| Token::StaticKeyword => {
index += 1;
}
Token::PropertyKeyword => return true,
_ => return false,
}
}
false
}
pub(crate) fn parse_property_statement(&mut self) {
self.parsing_header = false;
self.builder
.start_node(SyntaxKind::PropertyStatement.to_raw());
self.consume_property_signature_line();
self.parse_statement_list(|parser| {
(parser.at_token(Token::EndKeyword)
&& parser.peek_next_keyword() == Some(Token::PropertyKeyword))
|| parser.at_procedure_declaration_start()
});
self.consume_property_terminator();
self.builder.finish_node(); }
pub(crate) fn parse_compiler_conditional_property_statement(&mut self) {
self.parsing_header = false;
self.builder
.start_node(SyntaxKind::PropertyStatement.to_raw());
self.consume_until_after(Token::Newline);
self.consume_property_signature_line();
while self.at_compiler_directive_keyword(Token::ElseIfKeyword)
|| self.at_compiler_directive_keyword(Token::ElseKeyword)
{
self.consume_until_after(Token::Newline);
self.consume_property_signature_line();
}
if self.at_compiler_end_if_directive() {
self.consume_until_after(Token::Newline);
}
self.parse_statement_list(|parser| {
(parser.at_token(Token::EndKeyword)
&& parser.peek_next_keyword() == Some(Token::PropertyKeyword))
|| parser.at_procedure_declaration_start()
});
self.consume_property_terminator();
if self.at_compiler_end_if_directive() {
self.consume_compiler_directive_prefix();
self.consume_until_after(Token::Newline);
}
self.builder.finish_node(); }
fn consume_property_signature_line(&mut self) {
self.consume_whitespace();
if self.at_token(Token::PublicKeyword)
|| self.at_token(Token::PrivateKeyword)
|| self.at_token(Token::FriendKeyword)
{
self.consume_token();
self.consume_whitespace();
}
if self.at_token(Token::StaticKeyword) {
self.consume_token();
self.consume_whitespace();
}
self.consume_token();
self.consume_whitespace();
if self.at_token(Token::GetKeyword)
|| self.at_token(Token::LetKeyword)
|| self.at_token(Token::SetKeyword)
{
self.consume_token();
}
self.consume_whitespace();
if self.at_token(Token::Identifier) {
self.consume_token();
} else if self.at_keyword() {
self.consume_token_as_identifier();
}
self.consume_whitespace();
if self.at_token(Token::LeftParenthesis) {
self.parse_parameter_list();
}
self.consume_until_after(Token::Newline);
}
fn consume_property_terminator(&mut self) {
self.consume_expected_end_keyword_terminator(Token::PropertyKeyword, "Property");
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[test]
fn property_get_simple() {
let source = r"
Property Get Name() As String
Name = m_name
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_let_simple() {
let source = r"
Property Let Name(ByVal newName As String)
m_name = newName
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_set_simple() {
let source = r"
Property Set Container(glistNN As gList)
Set glistN = glistNN
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_set_with_object() {
let source = r"
Property Set Callback(ByRef newObj As InterPress)
Set mCallback = newObj
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_get_public() {
let source = r"
Public Property Get Value() As Long
Value = m_value
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_let_private() {
let source = r"
Private Property Let Count(ByVal newCount As Integer)
m_count = newCount
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_set_friend() {
let source = r"
Friend Property Set objref(RHS As Object)
Set m_objref = RHS
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_get_with_parameters() {
let source = r"
Public Property Get Item(index As Long) As Variant
Item = m_items(index)
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_set_with_index_parameter() {
let source = r"
Public Property Set item(curitem As Long, item As Variant)
Set m_items(curitem) = item
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_get_with_if_statement() {
let source = r"
Public Property Get CustomColor(i As Integer) As Long
If fNotFirst = False Then InitColors
If i >= 0 And i <= 15 Then
CustomColor = alCustom(i)
Else
CustomColor = -1
End If
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_let_with_if_statement() {
let source = r"
Public Property Let CustomColor(i As Integer, iValue As Long)
If fNotFirst = False Then InitColors
If i >= 0 And i <= 15 Then
alCustom(i) = iValue
End If
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_get_no_parameters() {
let source = r"
Property Get APIReturn() As Long
APIReturn = m_lApiReturn
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_set_preserves_whitespace() {
let source = r"
Property Set Container ( glistNN As gList )
Set glistN = glistNN
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn multiple_properties_in_class() {
let source = r"
Private m_name As String
Private m_value As Long
Public Property Get Name() As String
Name = m_name
End Property
Public Property Let Name(ByVal newName As String)
m_name = newName
End Property
Public Property Get Value() As Long
Value = m_value
End Property
Public Property Let Value(ByVal newValue As Long)
m_value = newValue
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_get_returns_object() {
let source = r"
Property Get Callback() As InterPress
Set Callback = mCallback
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_with_exit_property() {
let source = r#"
Property Get Test() As String
If m_value = "" Then Exit Property
Test = m_value
End Property
"#;
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_static() {
let source = r"
Public Static Property Get Counter() As Long
Static count As Long
count = count + 1
Counter = count
End Property
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn property_let_with_multiple_statements() {
let source = r#"
Public Property Let Caption(myCap As String)
mCaptext = myCap
If Not glistN Is Nothing Then
If glistN.CenterText Then
glistN.list(0) = mCaptext
Else
glistN.list(0) = " " + mCaptext
End If
glistN.ShowMe
End If
End Property
"#;
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.bas", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let mut settings = insta::Settings::clone_current();
settings.set_snapshot_path("../../../snapshots/parsers/cst/property_statements");
settings.set_prepend_module_to_snapshot(false);
let _guard = settings.bind_to_scope();
insta::assert_yaml_snapshot!(tree);
}
#[test]
fn compiler_conditional_property_consumes_end_if() {
let source = r"
#If DEBUG Then
Public Property Get TimerEx() As Double
TimerEx = 1
End Property
#End If
";
let (cst_opt, _failures) = ConcreteSyntaxTree::from_text("test.cls", source).unpack();
assert_eq!(_failures.len(), 0, "Expected no parse failures.");
let cst = cst_opt.expect("CST should be parsed");
let tree = cst.to_serializable();
let text = format!("{tree:#?}");
assert!(
!text.contains("Unknown"),
"Compiler-conditional property blocks should not leave Unknown tokens: {text}"
);
assert!(
text.contains("PropertyStatement"),
"Expected the compiler-conditional property to be parsed as a property statement"
);
}
}