diff --git a/CHANGELOG.md b/CHANGELOG.md index d813eb72..ab73d735 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,10 +10,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/). ### Fixed - Fixed parsing of logical lines with sequential `<` and `>` comparisons. +- Fixed formatting of `interface` as a generic constraint. ### Added - Added support for Delphi 13 `noreturn` directive. +- Added support for Delphi 13 `unmanaged` generic constraint. ## [0.7.0] - 2025-11-11 diff --git a/core/CHANGELOG.md b/core/CHANGELOG.md index d55c4419..07cde473 100644 --- a/core/CHANGELOG.md +++ b/core/CHANGELOG.md @@ -10,10 +10,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Fixed - Fixed parsing of logical lines with sequential `<` and `>` comparisons. +- Fixed formatting of `interface` as a generic constraint. ### Added - Added support for Delphi 13 `noreturn` directive. +- Added `KeywordKind::is_generic_constraint`. +- Added support for Delphi 13 `unmanaged` generic constraint. ## 0.7.0 - 2025-11-11 diff --git a/core/datatests/generators/optimising_line_formatter.rs b/core/datatests/generators/optimising_line_formatter.rs index d7d0f00d..d0a18d7b 100644 --- a/core/datatests/generators/optimising_line_formatter.rs +++ b/core/datatests/generators/optimising_line_formatter.rs @@ -2640,7 +2640,7 @@ mod routines { procedure Apples< AA, BB: record; AAAAAAAAAA, BBBBBBBB: - IInterface; + interface; AAAAAAAA, BBBBBBBB, CCCCCCC: record; @@ -2651,12 +2651,18 @@ mod routines { CC: constructor, record, + interface, class; + CC: + AAAAAAAA, + BBBBBBBB, + CCCCCCCC, + DDDDDDDD; CC: constructor, record, class, - IInterface + interface >(); ", generic_routines_and_params = " diff --git a/core/src/defaults/lexer.rs b/core/src/defaults/lexer.rs index 3293a9cf..1b662906 100644 --- a/core/src/defaults/lexer.rs +++ b/core/src/defaults/lexer.rs @@ -321,7 +321,7 @@ const fn make_byte_map(map: &[(ByteSet<'_>, T)], default: T) -> [T; 256 // endregion: byte-set // region: keywords -const KEYWORDS: [(&str, RawTokenType); 123] = [ +const KEYWORDS: [(&str, RawTokenType); 124] = [ ("absolute", TT::IdentifierOrKeyword(KK::Absolute)), ("abstract", TT::IdentifierOrKeyword(KK::Abstract)), ("align", TT::IdentifierOrKeyword(KK::Align)), @@ -433,6 +433,7 @@ const KEYWORDS: [(&str, RawTokenType); 123] = [ ("try", TT::Keyword(KK::Try)), ("type", TT::Keyword(KK::Type)), ("unit", TT::Keyword(KK::Unit)), + ("unmanaged", TT::IdentifierOrKeyword(KK::Unmanaged)), ("unsafe", TT::IdentifierOrKeyword(KK::Unsafe)), ("until", TT::Keyword(KK::Until)), ("uses", TT::Keyword(KK::Uses)), @@ -2136,6 +2137,7 @@ mod tests { ("try", TT::Keyword(KK::Try)), ("type", TT::Keyword(KK::Type)), ("unit", TT::Keyword(KK::Unit)), + ("unmanaged", TT::IdentifierOrKeyword(KK::Unmanaged)), ("unsafe", TT::IdentifierOrKeyword(KK::Unsafe)), ("until", TT::Keyword(KK::Until)), ("uses", TT::Keyword(KK::Uses)), diff --git a/core/src/defaults/parser.rs b/core/src/defaults/parser.rs index 32988b8a..45331d88 100644 --- a/core/src/defaults/parser.rs +++ b/core/src/defaults/parser.rs @@ -1449,8 +1449,44 @@ impl<'a, 'b> InternalDelphiLogicalLineParser<'a, 'b> { parser.parse_expression(); } } + // Generics Some(TT::Op(OK::LessThan(_))) => { - parser.skip_pair(); + let paren_level = parser.paren_level; + let brack_level = parser.brack_level; + let generic_level = parser.generic_level; + let mut in_constraint = false; + parser.next_token(); + while (parser.paren_level != paren_level + || parser.brack_level != brack_level + || parser.generic_level != generic_level) + && parser.get_current_token_type().is_some() + { + match parser.get_current_token_type() { + Some(TT::Op(OK::Colon)) => { + /* + E.g., function (); + In constraint ^ + */ + in_constraint = true; + } + Some(TT::Op(OK::Semicolon)) => { + /* + E.g., function (); + Not in constraint ^ + */ + in_constraint = false; + } + Some(TT::IdentifierOrKeyword(kk)) if kk.is_generic_constraint() => { + if in_constraint { + parser.consolidate_current_keyword(); + } else { + parser.consolidate_current_ident(); + } + } + _ => {} + } + parser.next_token(); + } } Some(TT::Op(OK::Semicolon)) => { parser.next_token(); diff --git a/core/src/defaults/parser/token_consolidation_tests.rs b/core/src/defaults/parser/token_consolidation_tests.rs index bde22e56..be8ea063 100644 --- a/core/src/defaults/parser/token_consolidation_tests.rs +++ b/core/src/defaults/parser/token_consolidation_tests.rs @@ -627,6 +627,13 @@ casing_token_consolidation_test!( strict, identifier = { "type foo = class STRICT: STRICT; strict private STRICT: STRICT end;" }, ); +casing_token_consolidation_test!( + generic_constraint, + unmanaged = { + "function UNMANAGED(UNMANAGED: UNMANAGED; UNMANAGED: UNMANAGED);" + }, + fake = { "function SEALED(UNMANAGED: UNMANAGED);" }, +); /* In cases like these, `helper` can be both an identifier and a keyword based on the conditional diff --git a/core/src/lang.rs b/core/src/lang.rs index a318f782..28b39cfa 100644 --- a/core/src/lang.rs +++ b/core/src/lang.rs @@ -172,6 +172,7 @@ pub enum KeywordKind { StdCall, Stored, Strict, + Unmanaged, Unsafe, VarArgs, Virtual, @@ -256,6 +257,17 @@ impl KeywordKind { | KeywordKind::Type ) } + + pub fn is_generic_constraint(&self) -> bool { + matches!( + self, + KeywordKind::Record + | KeywordKind::Class + | KeywordKind::Constructor + | KeywordKind::Interface + | KeywordKind::Unmanaged + ) + } } /// Used to distinguish the semantic meanings of `=` diff --git a/core/src/rules/generics_consolidator.rs b/core/src/rules/generics_consolidator.rs index 6c0ff296..28ca3648 100644 --- a/core/src/rules/generics_consolidator.rs +++ b/core/src/rules/generics_consolidator.rs @@ -44,6 +44,7 @@ impl TokenConsolidator for DistinguishGenericTypeParamsConsolidator { Some(TokenType::Op(OperatorKind::Comma)) => { comma_found = true; } + Some(TokenType::Keyword(kk)) if kk.is_generic_constraint() => {} // all the other tokens you can include in a generic type parameter list Some( TokenType::Identifier @@ -54,10 +55,7 @@ impl TokenConsolidator for DistinguishGenericTypeParamsConsolidator { | TokenType::Comment(_) | TokenType::ConditionalDirective(_) | TokenType::Keyword( - KeywordKind::Class - | KeywordKind::Record - | KeywordKind::Constructor - | KeywordKind::String + KeywordKind::String | KeywordKind::Array | KeywordKind::Set | KeywordKind::Of, diff --git a/misc/keywords.txt b/misc/keywords.txt index d4703f7f..6c5e116d 100644 --- a/misc/keywords.txt +++ b/misc/keywords.txt @@ -109,6 +109,7 @@ to try type unit +unmanaged unsafe until uses