diff --git a/README.md b/README.md index ce0f4d39..aec5dfc6 100644 --- a/README.md +++ b/README.md @@ -22,13 +22,13 @@ Chapi (pronounced /tʃɑpi/) can also be read as “XP” in Chinese if you pron ### Language stages -| Feature | Java | Python | Go | Kotlin | TS/JS | C | C# | Scala | C++ | Rust | -|----------------|------|--------|----|--------|-------|----|----|-------|-----|------| -| HTTP API decl | ✅ | 🆕 | 🆕 | ✅ | ✅ | 🆕 | 🆕 | | ✅ | 🆕 | -| Syntax parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | 🆕 | ✅ | 🆕 | ✅ | -| Function calls | ✅ | ✅ | 🆕 | ✅ | ✅ | 🆕 | | | | ✅ | -| Arch/package | ✅ | | | ✅ | ✅ | 🆕 | | ✅ | ✅ | 🆕 | -| Real-world | ✅ | | | ✅ | ✅ | | | | | | +| Feature | Java | Python | Go | Kotlin | TS/JS | C | C# | Scala | C++ | Rust | Swift | +|----------------|------|--------|----|--------|-------|----|----|-------|-----|------|-------| +| HTTP API decl | ✅ | 🆕 | ✅ | ✅ | ✅ | 🆕 | ✅ | ✅ | 🆕 | ✅ | | +| Syntax parsing | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | +| Function calls | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | ✅ | | | ✅ | ✅ | +| Arch/package | ✅ | | ✅ | ✅ | ✅ | 🆕 | ✅ | ✅ | ✅ | ✅ | ✅ | +| Real-world | ✅ | | | ✅ | ✅ | | | | | | | ### IDL stages @@ -59,6 +59,7 @@ Tested language versions: - Kotlin - Rust: v1.60.0 - Python: 2, 3 +- Swift: 5, 6 (with typed throws, async/await, actors, ownership modifiers) Gradle modules (by tier): @@ -69,6 +70,7 @@ Gradle modules (by tier): // tier 1 model language ":chapi-ast-protobuf", +":chapi-ast-thrift", // tier 2 languages ":chapi-ast-kotlin", @@ -81,6 +83,7 @@ Gradle modules (by tier): ":chapi-ast-csharp", ":chapi-ast-c", ":chapi-ast-cpp", +":chapi-ast-swift", // others ":chapi-parser-toml", @@ -135,8 +138,8 @@ Add dependencies: ```groovy dependencies { - implementation "com.phodal.chapi:chapi-ast-java:2.3.6" - implementation "com.phodal.chapi:chapi-domain:2.3.6" + implementation "com.phodal.chapi:chapi-ast-java:2.5.2" + implementation "com.phodal.chapi:chapi-domain:2.5.2" } ``` diff --git a/build.gradle.kts b/build.gradle.kts index b2551a4d..d0f2ceb3 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -184,6 +184,7 @@ dependencies { jacocoAggregation(project(":chapi-ast-scala")) jacocoAggregation(project(":chapi-ast-cpp")) jacocoAggregation(project(":chapi-ast-protobuf")) + jacocoAggregation(project(":chapi-ast-swift")) jacocoAggregation(project(":chapi-parser-toml")) jacocoAggregation(project(":chapi-parser-cmake")) diff --git a/chapi-ast-swift/build.gradle.kts b/chapi-ast-swift/build.gradle.kts new file mode 100644 index 00000000..4a36527b --- /dev/null +++ b/chapi-ast-swift/build.gradle.kts @@ -0,0 +1,56 @@ +plugins { + id("antlr") + java + kotlin("jvm") + kotlin("plugin.serialization") version "1.9.24" + + `jacoco-conventions` +} + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + antlr("org.antlr:antlr4:4.13.2") + + // project deps + implementation(project(":chapi-domain")) + + implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") + + implementation(kotlin("stdlib-jdk8")) + implementation(kotlin("reflect")) + testImplementation(kotlin("test")) + + // JUnit 5 + testImplementation("org.junit.jupiter:junit-jupiter-api:5.10.2") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.10.2") + testRuntimeOnly("org.junit.platform:junit-platform-console:1.10.2") + + implementation("org.antlr:antlr4:4.13.2") + implementation("org.antlr:antlr4-runtime:4.13.2") +} + +sourceSets.main { + java.srcDirs("${project.layout.buildDirectory.get().asFile}/generated-src") +} + +tasks.generateGrammarSource { + maxHeapSize = "256m" + arguments = arguments + listOf("-package", "chapi.ast.antlr") + listOf("-visitor", "-long-messages") + outputDirectory = file("${project.layout.buildDirectory.get().asFile}/generated-src/chapi/ast/antlr") +} + +tasks.named("compileKotlin") { + dependsOn(tasks.withType()) +} + +tasks.withType { + useJUnitPlatform() + testLogging { + events("passed", "skipped", "failed") + } +} + diff --git a/chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 b/chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 new file mode 100644 index 00000000..87157625 --- /dev/null +++ b/chapi-ast-swift/src/main/antlr/Swift5Lexer.g4 @@ -0,0 +1,425 @@ +// $antlr-format alignTrailingComments true, columnLimit 150, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine true, allowShortBlocksOnASingleLine true, minEmptyLines 0, alignSemicolons ownLine +// $antlr-format alignColons trailing, singleLineOverrulesHangingColon true, alignLexerCommands true, alignLabels true, alignTrailers true + +// Swift 6 Lexer (upgraded from Swift 5) +lexer grammar Swift5Lexer; +// Insert here @header for C++ lexer. + +options { + superClass = SwiftSupportLexer; +} +AS : 'as'; +ALPHA : 'alpha'; +BREAK : 'break'; +CASE : 'case'; +CATCH : 'catch'; +CLASS : 'class'; +CONTINUE : 'continue'; +DEFAULT : 'default'; +DEFER : 'defer'; +DO : 'do'; +GUARD : 'guard'; +ELSE : 'else'; +ENUM : 'enum'; +FOR : 'for'; +FALLTHROUGH : 'fallthrough'; +FUNC : 'func'; +IN : 'in'; +IF : 'if'; +IMPORT : 'import'; +INTERNAL : 'internal'; +FINAL : 'final'; +OPEN : 'open'; +PRIVATE : 'private'; +PUBLIC : 'public'; +WHERE : 'where'; +WHILE : 'while'; +LET : 'let'; +VAR : 'var'; +PROTOCOL : 'protocol'; +GET : 'get'; +SET : 'set'; +WILL_SET : 'willSet'; +DID_SET : 'didSet'; +REPEAT : 'repeat'; +SWITCH : 'switch'; +STRUCT : 'struct'; +RETURN : 'return'; +THROW : 'throw'; +THROWS : 'throws'; +RETHROWS : 'rethrows'; +ASYNC : 'async'; +AWAIT : 'await'; +INDIRECT : 'indirect'; +INIT : 'init'; +DEINIT : 'deinit'; +ASSOCIATED_TYPE : 'associatedtype'; +EXTENSION : 'extension'; +SUBSCRIPT : 'subscript'; +PREFIX : 'prefix'; +INFIX : 'infix'; +LEFT : 'left'; +RIGHT : 'right'; +NONE : 'none'; +PRECEDENCE_GROUP : 'precedencegroup'; +HIGHER_THAN : 'higherThan'; +LOWER_THAN : 'lowerThan'; +ASSIGNMENT : 'assignment'; +ASSOCIATIVITY : 'associativity'; +POSTFIX : 'postfix'; +OPERATOR : 'operator'; +TYPEALIAS : 'typealias'; +OS : 'os'; +ARCH : 'arch'; +SWIFT : 'swift'; +COMPILER : 'compiler'; +CAN_IMPORT : 'canImport'; +TARGET_ENVIRONMENT : 'targetEnvironment'; +CONVENIENCE : 'convenience'; +DYNAMIC : 'dynamic'; +LAZY : 'lazy'; +OPTIONAL : 'optional'; +OVERRIDE : 'override'; +REQUIRED : 'required'; +STATIC : 'static'; +WEAK : 'weak'; +UNOWNED : 'unowned'; +SAFE : 'safe'; +UNSAFE : 'unsafe'; +MUTATING : 'mutating'; +NONMUTATING : 'nonmutating'; +FILE_PRIVATE : 'fileprivate'; + +// Swift 6 new keywords +CONSUMING : 'consuming'; +BORROWING : 'borrowing'; +NONISOLATED : 'nonisolated'; +ISOLATED : 'isolated'; +SENDING : 'sending'; +PACKAGE : 'package'; +DISCARD : 'discard'; +COPY : 'copy'; +CONSUME : 'consume'; + +// Macro-related (Swift 5.9+) +MACRO : 'macro'; +FREESTANDING : 'freestanding'; +ATTACHED : 'attached'; + +// Distributed actor (Swift 5.5+) +DISTRIBUTED : 'distributed'; +ACTOR : 'actor'; +IS : 'is'; +TRY : 'try'; +SUPER : 'super'; +ANY : 'Any'; +FALSE : 'false'; +RED : 'red'; +BLUE : 'blue'; +GREEN : 'green'; +RESOURCE_NAME : 'resourceName'; +TRUE : 'true'; +NIL : 'nil'; +INOUT : 'inout'; +SOME : 'some'; +TYPE : 'Type'; +PRECEDENCE : 'precedence'; +SELF : 'self'; +SELF_BIG : 'Self'; + +MAC_OS : 'macOS'; +I_OS : 'iOS'; +OSX : 'OSX'; +WATCH_OS : 'watchOS'; +TV_OS : 'tvOS'; +LINUX : 'Linux'; +WINDOWS : 'Windows'; + +I386 : 'i386'; +X86_64 : 'x86_64'; +ARM : 'arm'; +ARM64 : 'arm64'; + +SIMULATOR : 'simulator'; +MAC_CATALYST : 'macCatalyst'; + +I_OS_APPLICATION_EXTENSION : 'iOSApplicationExtension'; +MAC_CATALYST_APPLICATION_EXTENSION : 'macCatalystApplicationExtension'; +MAC_OS_APPLICATION_EXTENSION : 'macOSApplicationExtension'; +SOURCE_LOCATION : '#sourceLocation'; + +FILE : 'file'; +LINE : 'line'; +ERROR : '#error'; +WARNING : '#warning'; +AVAILABLE : '#available'; + +HASH_IF : '#if'; +HASH_ELSEIF : '#elseif'; +HASH_ELSE : '#else'; +HASH_ENDIF : '#endif'; +HASH_FILE : '#file'; +HASH_FILE_ID : '#fileID'; +HASH_FILE_PATH : '#filePath'; +HASH_LINE : '#line'; +HASH_COLUMN : '#column'; +HASH_FUNCTION : '#function'; +HASH_DSO_HANDLE : '#dsohandle'; +HASH_SELECTOR : '#selector'; +HASH_KEYPATH : '#keyPath'; +HASH_COLOR_LITERAL : '#colorLiteral'; +HASH_FILE_LITERAL : '#fileLiteral'; +HASH_IMAGE_LITERAL : '#imageLiteral'; + +// Swift 6 macro-related hash keywords +HASH_EXTERNAL_MACRO: '#externalMacro'; + +GETTER : 'getter'; +SETTER : 'setter'; + +UNDERSCORE : '_'; + +Identifier: + Identifier_head Identifier_characters? + | Implicit_parameter_name + | Property_wrapper_projection +; + +fragment Identifier_head: + [a-zA-Z] + | '_' + | '\u00A8' + | '\u00AA' + | '\u00AD' + | '\u00AF' + | [\u00B2-\u00B5] + | [\u00B7-\u00BA] + | [\u00BC-\u00BE] + | [\u00C0-\u00D6] + | [\u00D8-\u00F6] + | [\u00F8-\u00FF] + | [\u0100-\u02FF] + | [\u0370-\u167F] + | [\u1681-\u180D] + | [\u180F-\u1DBF] + | [\u1E00-\u1FFF] + | [\u200B-\u200D] + | [\u202A-\u202E] + | [\u203F-\u2040] + | '\u2054' + | [\u2060-\u206F] + | [\u2070-\u20CF] + | [\u2100-\u218F] + | [\u2460-\u24FF] + | [\u2776-\u2793] + | [\u2C00-\u2DFF] + | [\u2E80-\u2FFF] + | [\u3004-\u3007] + | [\u3021-\u302F] + | [\u3031-\u303F] + | [\u3040-\uD7FF] + | [\uF900-\uFD3D] + | [\uFD40-\uFDCF] + | [\uFDF0-\uFE1F] + | [\uFE30-\uFE44] + | [\uFE47-\uFFFD] + | [\u{10000}-\u{1FFFD}] + | [\u{20000}-\u{2FFFD}] + | [\u{30000}-\u{3FFFD}] + | [\u{40000}-\u{4FFFD}] + | [\u{50000}-\u{5FFFD}] + | [\u{60000}-\u{6FFFD}] + | [\u{70000}-\u{7FFFD}] + | [\u{80000}-\u{8FFFD}] + | [\u{90000}-\u{9FFFD}] + | [\u{A0000}-\u{AFFFD}] + | [\u{B0000}-\u{BFFFD}] + | [\u{C0000}-\u{CFFFD}] + | [\u{D0000}-\u{DFFFD}] + | [\u{E0000}-\u{EFFFD}] +; + +fragment Identifier_character: + [0-9] + | [\u0300-\u036F] + | [\u1DC0-\u1DFF] + | [\u20D0-\u20FF] + | [\uFE20-\uFE2F] + | Identifier_head +; + +fragment Identifier_characters: Identifier_character+; + +fragment Implicit_parameter_name: '$' Decimal_digits; + +fragment Property_wrapper_projection: '$' Identifier_characters; + +DOT : '.'; +LCURLY : '{'; +LPAREN : '(' { if(!parenthesis.isEmpty()) parenthesis.push(parenthesis.pop()+1);}; +LBRACK : '['; +RCURLY : '}'; +RPAREN: + ')' { if(!parenthesis.isEmpty()) + { + parenthesis.push(parenthesis.pop()-1); + if(parenthesis.peek() == 0) + { + parenthesis.pop(); + popMode(); + } + } + } +; +RBRACK : ']'; +COMMA : ','; +COLON : ':'; +SEMI : ';'; +LT : '<'; +GT : '>'; +BANG : '!'; +QUESTION : '?'; +AT : '@'; +AND : '&'; +SUB : '-'; +EQUAL : '='; +OR : '|'; +DIV : '/'; +ADD : '+'; +MUL : '*'; +MOD : '%'; +CARET : '^'; +TILDE : '~'; +HASH : '#'; +BACKTICK : '`'; +DOLLAR : '$'; +BACKSLASH : '\\'; + +Operator_head_other: // valid operator chars not used by Swift itself + [\u00A1-\u00A7] + | [\u00A9\u00AB] + | [\u00AC\u00AE] + | [\u00B0-\u00B1\u00B6\u00BB\u00BF\u00D7\u00F7] + | [\u2016-\u2017\u2020-\u2027] + | [\u2030-\u203E] + | [\u2041-\u2053] + | [\u2055-\u205E] + | [\u2190-\u23FF] + | [\u2500-\u2775] + | [\u2794-\u2BFF] + | [\u2E00-\u2E7F] + | [\u3001-\u3003] + | [\u3008-\u3020\u3030] +; + +Operator_following_character: + [\u0300-\u036F] + | [\u1DC0-\u1DFF] + | [\u20D0-\u20FF] + | [\uFE00-\uFE0F] + | [\uFE20-\uFE2F] + | [\u{E0100}-\u{E01EF}] +; + +Binary_literal : '0b' Binary_digit Binary_literal_characters?; +fragment Binary_digit : [01]; +fragment Binary_literal_character : Binary_digit | '_'; +fragment Binary_literal_characters : Binary_literal_character+; + +Octal_literal : '0o' Octal_digit Octal_literal_characters?; +fragment Octal_digit : [0-7]; +fragment Octal_literal_character : Octal_digit | '_'; +fragment Octal_literal_characters : Octal_literal_character+; + +Decimal_digits : Decimal_digit+; +Decimal_literal : Decimal_digit Decimal_literal_characters?; +fragment Decimal_digit : [0-9]; +fragment Decimal_literal_character : Decimal_digit | '_'; +fragment Decimal_literal_characters : Decimal_literal_character+; + +Hexadecimal_literal : '0x' Hexadecimal_digit Hexadecimal_literal_characters?; +fragment Hexadecimal_digit : [0-9a-fA-F]; +fragment Hexadecimal_literal_character : Hexadecimal_digit | '_'; +fragment Hexadecimal_literal_characters : Hexadecimal_literal_character+; + +// Floating-Point Literals +Floating_point_literal: + Decimal_literal Decimal_fraction? Decimal_exponent? + | Hexadecimal_literal Hexadecimal_fraction? Hexadecimal_exponent +; +fragment Decimal_fraction : '.' Decimal_literal; +fragment Decimal_exponent : Floating_point_e Sign? Decimal_literal; +fragment Hexadecimal_fraction : '.' Hexadecimal_digit Hexadecimal_literal_characters?; +fragment Hexadecimal_exponent : Floating_point_p Sign? Decimal_literal; +fragment Floating_point_e : [eE]; +fragment Floating_point_p : [pP]; +fragment Sign : [+-]; + +WS: [ \n\r\t\u000B\u000C\u0000]+ -> channel(HIDDEN); + +HASHBANG: '#!' .*? [\r\n]+ -> channel(HIDDEN); + +Block_comment: '/*' (Block_comment | .)*? '*/' -> channel(HIDDEN); + +Line_comment: '//' .*? ('\n' | EOF) -> channel(HIDDEN); + +Multi_line_extended_string_open: '#'+ '"""' -> pushMode(MultiLineExtended); + +Single_line_extended_string_open: '#'+ '"' -> pushMode(SingleLineExtended); + +Multi_line_string_open: '"""' -> pushMode(MultiLine); + +Single_line_string_open: '"' -> pushMode(SingleLine); + +mode SingleLine; + +Interpolation_single_line: '\\(' { parenthesis.push(1);} -> pushMode(DEFAULT_MODE); + +Single_line_string_close: '"' -> popMode; + +Quoted_single_line_text: Quoted_text; + +mode MultiLine; + +Interpolation_multi_line: '\\(' {parenthesis.push(1); } -> pushMode(DEFAULT_MODE); + +Multi_line_string_close: '"""' -> popMode; + +Quoted_multi_line_text: Multiline_quoted_text; + +mode SingleLineExtended; + +Single_line_extended_string_close: '"' '#'+ -> popMode; + +Quoted_single_line_extended_text: ~[\r\n"]+; + +mode MultiLineExtended; + +Multi_line_extended_string_close: '"""' '#'+ -> popMode; + +Quoted_multi_line_extended_text: ~["]+ | '"' '"'?; + +fragment Quoted_text: Quoted_text_item+; + +fragment Quoted_text_item: Escaped_character | ~["\n\r\\]; + +fragment Multiline_quoted_text: Escaped_character | ~[\\"]+ | '"' '"'? | Escaped_newline; + +fragment Escape_sequence: '\\' '#'*; + +fragment Escaped_character: + Escape_sequence ([0\\tnr"'\u201c] | 'u' '{' Unicode_scalar_digits '}') +; + +//Between one and eight hexadecimal digits +fragment Unicode_scalar_digits: + Hexadecimal_digit Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit? Hexadecimal_digit? + Hexadecimal_digit? +; + +fragment Escaped_newline: Escape_sequence Inline_spaces? Line_break; + +fragment Inline_spaces: [\u0009\u0020]; + +fragment Line_break: [\u000A\u000D]| '\u000D' '\u000A'; \ No newline at end of file diff --git a/chapi-ast-swift/src/main/antlr/Swift5Parser.g4 b/chapi-ast-swift/src/main/antlr/Swift5Parser.g4 new file mode 100644 index 00000000..5718c569 --- /dev/null +++ b/chapi-ast-swift/src/main/antlr/Swift5Parser.g4 @@ -0,0 +1,1923 @@ +// $antlr-format alignTrailingComments true, columnLimit 150, minEmptyLines 1, maxEmptyLinesToKeep 1, reflowComments false, useTab false +// $antlr-format allowShortRulesOnASingleLine false, allowShortBlocksOnASingleLine true, alignSemicolons hanging, alignColons hanging + +// Swift 6 Parser (upgraded from Swift 5) +// Supports: typed throws, noncopyable types, ownership modifiers, macros, trailing commas +parser grammar Swift5Parser; + +// Insert here @header for C++ parser. + +options { + superClass = SwiftSupport; + tokenVocab = Swift5Lexer; +} + +top_level + : statements? EOF + ; + +// Statements +statement + : ( + loop_statement + | declaration + | branch_statement + | labeled_statement + | control_transfer_statement + | defer_statement + | do_statement + | expression + ) SEMI? + | compiler_control_statement + ; + +statements + locals[int indexBefore = -1] + : ( + {this.isSeparatedStatement(_input, $indexBefore)}? statement {$indexBefore = _input.index(); + } + )+ + ; + +// Loop Statements +loop_statement + : for_in_statement + | while_statement + | repeat_while_statement + ; + +// For-In Statement +for_in_statement + : FOR CASE? pattern IN expression where_clause? code_block + ; + +// While Statement +while_statement + : WHILE condition_list code_block + ; + +condition_list + : condition (COMMA condition)* + ; + +condition + : availability_condition + | expression + | case_condition + | optional_binding_condition + ; + +case_condition + : CASE pattern initializer + ; + +optional_binding_condition + : (LET | VAR) pattern initializer + ; + +// Repeat-While Statement +repeat_while_statement + : REPEAT code_block WHILE expression + ; + +// Branch Statements +branch_statement + : if_statement + | guard_statement + | switch_statement + ; + +// If Statement +if_statement + : IF condition_list code_block else_clause? + ; + +else_clause + : ELSE code_block + | ELSE if_statement + ; + +// Guard Statement +guard_statement + : GUARD condition_list ELSE code_block + ; + +// Switch Statement +switch_statement + : SWITCH expression LCURLY switch_cases? RCURLY + ; + +switch_cases + : switch_case switch_cases? + ; + +switch_case + : (case_label | default_label) statements + | conditional_switch_case + ; + +case_label + : attributes? CASE case_item_list COLON + ; + +case_item_list + : pattern where_clause? (COMMA pattern where_clause?)* + ; + +default_label + : attributes? DEFAULT COLON + ; + +where_clause + : WHERE where_expression + ; + +where_expression + : expression + ; + +conditional_switch_case + : switch_if_directive_clause switch_elseif_directive_clauses? switch_else_directive_clause? HASH_ENDIF + ; + +switch_if_directive_clause + : HASH_IF compilation_condition switch_cases? + ; + +switch_elseif_directive_clauses + : elseif_directive_clause switch_elseif_directive_clauses? + ; + +switch_elseif_directive_clause + : HASH_ELSEIF compilation_condition switch_cases? + ; + +switch_else_directive_clause + : HASH_ELSE switch_cases? + ; + +// Labeled Statement +labeled_statement + : statement_label (loop_statement | if_statement | switch_statement | do_statement) + ; + +statement_label + : label_name COLON + ; + +label_name + : identifier + ; + +// Control Transfer Statements +control_transfer_statement + : break_statement + | continue_statement + | fallthrough_statement + | return_statement + | throw_statement + ; + +// Break Statement +break_statement + : BREAK label_name? + ; + +// Continue Statement +continue_statement + : CONTINUE label_name? + ; + +// Fallthrough Statement +fallthrough_statement + : FALLTHROUGH + ; + +// Return Statement +return_statement + : RETURN expression? + ; + +// Throw Statement +throw_statement + : THROW expression + ; + +// Defer Statement +defer_statement + : DEFER code_block + ; + +// Do Statement +do_statement + : DO code_block catch_clauses? + ; + +catch_clauses + : catch_clause+ + ; + +catch_clause + : CATCH catch_pattern_list? code_block + ; + +catch_pattern_list + : catch_pattern (COMMA catch_pattern)* + ; + +catch_pattern + : pattern where_clause? + ; + +// Compiler Control Statements +compiler_control_statement + : conditional_compilation_block + | line_control_statement + | diagnostic_statement + ; + +// Conditional Compilation Block +conditional_compilation_block + : if_directive_clause elseif_directive_clauses? else_directive_clause? HASH_ENDIF + ; + +if_directive_clause + : HASH_IF compilation_condition statements? + ; + +elseif_directive_clauses + : elseif_directive_clause+ + ; + +elseif_directive_clause + : HASH_ELSEIF compilation_condition statements? + ; + +else_directive_clause + : HASH_ELSE statements? + ; + +compilation_condition + : platform_condition + | identifier + | boolean_literal + | LPAREN compilation_condition RPAREN + | BANG compilation_condition + | compilation_condition (compilation_condition_AND | compilation_condition_OR) compilation_condition + ; + +platform_condition + : OS LPAREN operating_system RPAREN + | ARCH LPAREN architecture RPAREN + | (SWIFT | COMPILER) LPAREN (compilation_condition_GE | compilation_condition_L) swift_version RPAREN + | CAN_IMPORT LPAREN module_name RPAREN + | TARGET_ENVIRONMENT LPAREN environment RPAREN + ; + +swift_version + : Decimal_digits swift_version_continuation? + ; + +swift_version_continuation + : DOT Decimal_digits swift_version_continuation? + ; + +operating_system + : MAC_OS + | I_OS + | OSX + | WATCH_OS + | TV_OS + | LINUX + | WINDOWS + ; + +architecture + : I386 + | X86_64 + | ARM + | ARM64 + ; + +module_name + : identifier (DOT identifier)* + ; + +environment + : SIMULATOR + | MAC_CATALYST + ; + +// Line Control Statement +line_control_statement + : SOURCE_LOCATION LPAREN (FILE COLON file_name COMMA LINE COLON line_number)? RPAREN + ; + +line_number + : Decimal_literal + ; // TODO: A decimal integer greater than zero + +file_name + : static_string_literal + ; + +// Compile-Time Diagnostic Statement +diagnostic_statement + : (ERROR | WARNING) LPAREN diagnostic_message RPAREN + ; + +diagnostic_message + : static_string_literal + ; + +// Availability Condition +availability_condition + : AVAILABLE LPAREN availability_arguments RPAREN + ; + +availability_arguments + : availability_argument (COMMA availability_argument)* + ; + +availability_argument + : platform_name platform_version + | MUL + ; + +platform_name + : I_OS + | OSX + | I_OS_APPLICATION_EXTENSION + | MAC_OS + | MAC_OS_APPLICATION_EXTENSION + | MAC_CATALYST + | MAC_CATALYST_APPLICATION_EXTENSION + | WATCH_OS + | TV_OS + ; + +platform_version + : Decimal_literal + | Decimal_digits + | Floating_point_literal (DOT Decimal_digits)? + ; + +// Generic Parameter Clause +generic_parameter_clause + : LT generic_parameter_list GT + ; + +generic_parameter_list + : generic_parameter (COMMA generic_parameter)* + ; + +generic_parameter + : type_name (COLON (type_identifier | protocol_composition_type))? + ; + +generic_where_clause + : WHERE requirement_list + ; + +requirement_list + : requirement (COMMA requirement)* + ; + +requirement + : conformance_requirement + | same_type_requirement + ; + +conformance_requirement + : type_identifier COLON (type_identifier | protocol_composition_type) + ; + +same_type_requirement + : type_identifier same_type_equals (type_identifier | type) + ; + +// Generic Argument Clause +generic_argument_clause + : LT generic_argument_list GT + ; + +generic_argument_list + : generic_argument (COMMA generic_argument)* + ; + +generic_argument + : type + ; + +// Declarations +declaration + : ( + import_declaration + | constant_declaration + | variable_declaration + | typealias_declaration + | function_declaration + | enum_declaration + | struct_declaration + | class_declaration + | actor_declaration // Swift 5.5+ + | protocol_declaration + | initializer_declaration + | deinitializer_declaration + | extension_declaration + | subscript_declaration + | operator_declaration + | precedence_group_declaration + | macro_declaration // Swift 5.9+ + ) SEMI? + ; + +declarations + : declaration+ + ; + +// Top-Level Code +top_level_declaration + : statements? + ; + +// Code Blocks +code_block + : LCURLY statements? RCURLY + ; + +// Import Declaration +import_declaration + : attributes? IMPORT import_kind? import_path + ; + +import_kind + : TYPEALIAS + | STRUCT + | CLASS + | ENUM + | PROTOCOL + | LET + | VAR + | FUNC + ; + +import_path + : import_path_identifier (DOT import_path_identifier)* + ; + +import_path_identifier + : identifier + | operator + ; + +// Constant Declaration +constant_declaration + : attributes? declaration_modifiers? LET pattern_initializer_list + ; + +pattern_initializer_list + : pattern_initializer (COMMA pattern_initializer)* + ; + +pattern_initializer + : pattern initializer? + ; + +initializer + : EQUAL expression + ; + +// Variable Declaration +variable_declaration + : variable_declaration_head ( + variable_name ( + initializer willSet_didSet_block + | type_annotation ( + initializer? willSet_didSet_block + | getter_setter_block // contains code_block + | getter_setter_keyword_block + ) + ) + | pattern_initializer_list + ) + ; + +variable_declaration_head + : attributes? declaration_modifiers? VAR + ; + +variable_name + : identifier + ; + +getter_setter_block + : LCURLY (getter_clause setter_clause? | setter_clause getter_clause) RCURLY + | code_block + ; + +getter_clause + : attributes? mutation_modifier? GET code_block? + ; + +setter_clause + : attributes? mutation_modifier? SET setter_name? code_block? + ; + +setter_name + : LPAREN identifier RPAREN + ; + +getter_setter_keyword_block + : LCURLY ( + getter_keyword_clause setter_keyword_clause? + | setter_keyword_clause getter_keyword_clause + ) RCURLY + ; + +getter_keyword_clause + : attributes? mutation_modifier? GET + ; + +setter_keyword_clause + : attributes? mutation_modifier? SET + ; + +willSet_didSet_block + : LCURLY (willSet_clause didSet_clause? | didSet_clause willSet_clause?) RCURLY + ; + +willSet_clause + : attributes? WILL_SET setter_name? code_block + ; + +didSet_clause + : attributes? DID_SET setter_name? code_block + ; + +// Type Alias Declaration +typealias_declaration + : attributes? access_level_modifier? TYPEALIAS typealias_name generic_parameter_clause? typealias_assignment + ; + +typealias_name + : identifier + ; + +typealias_assignment + : EQUAL type + ; + +// Function Declaration +function_declaration + : function_head function_name generic_parameter_clause? function_signature generic_where_clause? function_body? + ; + +function_head + : attributes? declaration_modifiers? FUNC + ; + +function_name + : identifier + | operator + ; + +function_signature + : parameter_clause async_clause? throw_clause? function_result? + ; + +// Swift 6: async clause +async_clause + : ASYNC + ; + +// Swift 6: typed throws - throws(ErrorType) +throw_clause + : THROWS (LPAREN type RPAREN)? + | RETHROWS + ; + +function_result + : arrow_operator attributes? type + ; + +function_body + : code_block + ; + +parameter_clause + : LPAREN parameter_list? RPAREN + ; + +parameter_list + : parameter (COMMA parameter)* + ; + +parameter + : attributes? ownership_modifier? external_parameter_name? local_parameter_name type_annotation ( + default_argument_clause? + | range_operator + ) + ; + +// Swift 6: ownership modifiers for parameters +ownership_modifier + : CONSUMING + | BORROWING + | INOUT + ; + +external_parameter_name + : identifier + ; + +local_parameter_name + : identifier + ; + +default_argument_clause + : EQUAL expression + ; + +// Enumeration Declaration +enum_declaration + : attributes? access_level_modifier? (union_style_enum | raw_value_style_enum) + ; + +union_style_enum + : INDIRECT? ENUM enum_name generic_parameter_clause? type_inheritance_clause? generic_where_clause? LCURLY union_style_enum_members? RCURLY + ; + +union_style_enum_members + : union_style_enum_member+ + ; + +union_style_enum_member + : declaration + | union_style_enum_case_clause + | compiler_control_statement + ; + +union_style_enum_case_clause + : attributes? INDIRECT? CASE union_style_enum_case_list + ; + +union_style_enum_case_list + : union_style_enum_case (COMMA union_style_enum_case)* + ; + +union_style_enum_case + : opaque_type + | enum_case_name (tuple_type | LPAREN type RPAREN)? + ; + +enum_name + : identifier + ; + +enum_case_name + : identifier + ; + +raw_value_style_enum + : ENUM enum_name generic_parameter_clause? type_inheritance_clause generic_where_clause? LCURLY raw_value_style_enum_members RCURLY + ; + +raw_value_style_enum_members + : raw_value_style_enum_member+ + ; + +raw_value_style_enum_member + : declaration + | raw_value_style_enum_case_clause + | compiler_control_statement + ; + +raw_value_style_enum_case_clause + : attributes? CASE raw_value_style_enum_case_list + ; + +raw_value_style_enum_case_list + : raw_value_style_enum_case (COMMA raw_value_style_enum_case)* + ; + +raw_value_style_enum_case + : enum_case_name raw_value_assignment? + ; + +raw_value_assignment + : EQUAL raw_value_literal + ; + +raw_value_literal + : numeric_literal + | static_string_literal + | boolean_literal + ; + +// Structure Declaration +struct_declaration + : attributes? access_level_modifier? STRUCT struct_name generic_parameter_clause? type_inheritance_clause? generic_where_clause? struct_body + ; + +struct_name + : identifier + ; + +struct_body + : LCURLY struct_members RCURLY + ; + +struct_members + : struct_member* + ; + +struct_member + : declaration + | compiler_control_statement + ; + +// Class Declaration +class_declaration + : attributes? (access_level_modifier? FINAL? | FINAL access_level_modifier?) CLASS class_name generic_parameter_clause? type_inheritance_clause? + generic_where_clause? class_body + ; + +class_name + : identifier + ; + +class_body + : LCURLY class_members RCURLY + ; + +class_members + : class_member* + ; + +class_member + : declaration + | compiler_control_statement + ; + +// Actor Declaration (Swift 5.5+) +actor_declaration + : attributes? access_level_modifier? DISTRIBUTED? ACTOR actor_name generic_parameter_clause? type_inheritance_clause? generic_where_clause? actor_body + ; + +actor_name + : identifier + ; + +actor_body + : LCURLY actor_members RCURLY + ; + +actor_members + : actor_member* + ; + +actor_member + : declaration + | compiler_control_statement + ; + +// Protocol Declaration +protocol_declaration + : attributes? access_level_modifier? PROTOCOL protocol_name ( + COLON CLASS + | type_inheritance_clause + )? generic_where_clause? protocol_body + ; + +protocol_name + : identifier + ; + +protocol_body + : LCURLY protocol_members RCURLY + ; + +protocol_members + : protocol_member* + ; + +protocol_member + : protocol_member_declaration + | compiler_control_statement + ; + +protocol_member_declaration + : protocol_property_declaration + | protocol_method_declaration + | protocol_initializer_declaration + | protocol_subscript_declaration + | protocol_associated_type_declaration + | typealias_declaration + ; + +// Protocol Property Declaration +protocol_property_declaration + : variable_declaration_head variable_name type_annotation getter_setter_keyword_block + ; + +// Protocol Method Declaration +protocol_method_declaration + : function_head function_name generic_parameter_clause? function_signature generic_where_clause? + ; + +// Protocol Initializer Declaration +protocol_initializer_declaration + : initializer_head generic_parameter_clause? parameter_clause (THROWS? | RETHROWS) generic_where_clause? + ; + +// Protocol Subscript Declaration +protocol_subscript_declaration + : subscript_head subscript_result generic_where_clause? getter_setter_keyword_block + ; + +// Protocol Associated Type Declaration +protocol_associated_type_declaration + : attributes? access_level_modifier? ASSOCIATED_TYPE typealias_name type_inheritance_clause? typealias_assignment? generic_where_clause? + ; + +// Initializer Declaration +initializer_declaration + : initializer_head generic_parameter_clause? parameter_clause (THROWS | RETHROWS)? generic_where_clause? initializer_body + ; + +initializer_head + : attributes? declaration_modifiers? INIT (QUESTION | BANG)? + ; + +initializer_body + : code_block + ; + +// Deinitializer Declaration +deinitializer_declaration + : attributes? DEINIT code_block + ; + +// Extension Declaration +extension_declaration + : attributes? access_level_modifier? EXTENSION type_identifier type_inheritance_clause? generic_where_clause? extension_body + ; + +extension_body + : LCURLY extension_members RCURLY + ; + +extension_members + : extension_member* + ; + +extension_member + : declaration + | compiler_control_statement + ; + +// Subscript Declaration +subscript_declaration + : subscript_head subscript_result generic_where_clause? ( + code_block + | getter_setter_block + | getter_setter_keyword_block + ) + ; + +subscript_head + : attributes? declaration_modifiers? SUBSCRIPT generic_parameter_clause? parameter_clause + ; + +subscript_result + : arrow_operator attributes? type + ; + +// Operator Declaration +operator_declaration + : prefix_operator_declaration + | postfix_operator_declaration + | infix_operator_declaration + ; + +prefix_operator_declaration + : PREFIX OPERATOR operator + ; + +postfix_operator_declaration + : POSTFIX OPERATOR operator + ; + +infix_operator_declaration + : INFIX OPERATOR operator infix_operator_group? + ; + +infix_operator_group + : COLON precedence_group_name + ; + +// Precedence Group Declaration +precedence_group_declaration + : PRECEDENCE_GROUP precedence_group_name LCURLY precedence_group_attributes? RCURLY + ; + +precedence_group_attributes + : precedence_group_attribute+ + ; + +precedence_group_attribute + : precedence_group_relation + | precedence_group_assignment + | precedence_group_associativity + ; + +precedence_group_relation + : (HIGHER_THAN | LOWER_THAN) COLON precedence_group_names + ; + +precedence_group_assignment + : ASSIGNMENT COLON boolean_literal + ; + +precedence_group_associativity + : ASSOCIATIVITY COLON (LEFT | RIGHT | NONE) + ; + +precedence_group_names + : precedence_group_name (COMMA precedence_group_name)* + ; + +precedence_group_name + : identifier (DOT identifier)* + ; + +// Declaration Modifiers +declaration_modifier + : CLASS + | CONVENIENCE + | DYNAMIC + | FINAL + | INFIX + | LAZY + | OPTIONAL + | OVERRIDE + | POSTFIX + | PREFIX + | REQUIRED + | STATIC + | UNOWNED (LPAREN (SAFE | UNSAFE) RPAREN)? + | WEAK + | access_level_modifier + | mutation_modifier + // Swift 6 new modifiers + | NONISOLATED + | ISOLATED + | CONSUMING + | BORROWING + | DISTRIBUTED + ; + +declaration_modifiers + : declaration_modifier+ + ; + +access_level_modifier + : (PRIVATE | FILE_PRIVATE | INTERNAL | PUBLIC | OPEN) (LPAREN SET RPAREN)? + ; + +mutation_modifier + : MUTATING + | NONMUTATING + ; + +// Patterns + +//The following sets of rules are mutually left-recursive [pattern, Type-Casting], to avoid this they were integrated into the same rule. +pattern + : (wildcard_pattern | identifier_pattern | tuple_pattern) type_annotation? + | value_binding_pattern + | enum_case_pattern + | optional_pattern + | IS type + | pattern AS type + | expression_pattern + ; + +// Wildcard Pattern +wildcard_pattern + : UNDERSCORE + ; + +// identifier Pattern +identifier_pattern + : identifier + ; + +// Value-Binding Pattern +value_binding_pattern + : VAR pattern + | LET pattern + ; + +// Tuple Pattern +tuple_pattern + : LPAREN tuple_pattern_element_list? RPAREN + ; + +tuple_pattern_element_list + : tuple_pattern_element (COMMA tuple_pattern_element)* + ; + +tuple_pattern_element + : (identifier COLON)? pattern + ; + +// Enumeration Case Pattern +enum_case_pattern + : type_identifier? DOT enum_case_name tuple_pattern? + ; + +// Optional Pattern +optional_pattern + : identifier_pattern QUESTION + ; + +// Expression Pattern +expression_pattern + : expression + ; + +// Attributes +attribute + : AT attribute_name attribute_argument_clause? + ; + +attribute_name + : identifier (DOT identifier)* + ; + +attribute_argument_clause + : LPAREN balanced_tokens? RPAREN + ; + +attributes + : attribute+ + ; + +balanced_tokens + : balanced_token+ + ; + +balanced_token + : LPAREN balanced_tokens? RPAREN + | LBRACK balanced_tokens? RBRACK + | LCURLY balanced_tokens? RCURLY + //Any identifier, keyword, literal, or operator Any punctuation except (, ), [, ], {, or } + | identifier + | keyword + | literal + | operator + | balanced_token_punctuation + ; + +balanced_token_punctuation + : (DOT | COMMA | COLON | SEMI | EQUAL | AT | HASH | BACKTICK | QUESTION) + | arrow_operator + | {this.isPrefixOp(_input)}? AND + | {this.isPostfixOp(_input)}? BANG + ; + +// Expressions +expression + : try_operator? prefix_expression binary_expressions? + ; + +expression_list + : expression (COMMA expression)* + ; + +// Prefix Expressions +prefix_expression + : prefix_operator? postfix_expression + | in_out_expression + ; + +in_out_expression + : AND identifier + ; + +// Try Operator +try_operator + : TRY (QUESTION | BANG)? + ; + +// Binary Expressions +binary_expression + : binary_operator prefix_expression + | (assignment_operator | conditional_operator) try_operator? prefix_expression + | type_casting_operator + ; + +binary_expressions + : binary_expression+ + ; + +// Conditional Operator +conditional_operator + : QUESTION expression COLON + ; + +// Ternary Conditional Operator +type_casting_operator + : (IS | AS ( QUESTION | BANG)?) type + ; + +// Primary Expressions +primary_expression + : unqualified_name generic_argument_clause? + | array_type + | dictionary_type + | literal_expression + | self_expression + | superclass_expression + | closure_expression + | parenthesized_operator + | parenthesized_expression + | tuple_expression + | implicit_member_expression + | wildcard_expression + | key_path_expression + | selector_expression + | key_path_string_expression + ; + +unqualified_name + : identifier (LPAREN argument_names RPAREN)? + ; + +// Literal Expression +literal_expression + : literal + | array_literal + | dictionary_literal + | playground_literal + | HASH_FILE + | HASH_FILE_ID + | HASH_FILE_PATH + | HASH_LINE + | HASH_COLUMN + | HASH_FUNCTION + | HASH_DSO_HANDLE + ; + +array_literal + : LBRACK array_literal_items? RBRACK + ; + +array_literal_items + : array_literal_item (COMMA array_literal_item)* COMMA? + ; + +array_literal_item + : expression + ; + +dictionary_literal + : LBRACK (dictionary_literal_items | COLON) RBRACK + ; + +dictionary_literal_items + : dictionary_literal_item (COMMA dictionary_literal_item)* COMMA? + ; + +dictionary_literal_item + : expression COLON expression + ; + +playground_literal + : HASH_COLOR_LITERAL LPAREN RED COLON expression COMMA GREEN COLON expression COMMA BLUE COLON expression COMMA ALPHA COLON expression RPAREN + | HASH_FILE_LITERAL LPAREN RESOURCE_NAME COLON expression RPAREN + | HASH_IMAGE_LITERAL LPAREN RESOURCE_NAME COLON expression RPAREN + ; + +// Self Expression +self_expression + : SELF # self_pure_expression + | SELF DOT identifier # self_method_expression + | SELF LBRACK function_call_argument_list RBRACK # self_subscript_expression + | SELF DOT INIT # self_initializer_expression + ; + +// Superclass Expression +superclass_expression + : SUPER DOT identifier # superclass_method_expression + | SUPER LBRACK function_call_argument_list RBRACK # superclass_subscript_expression + | SUPER DOT INIT # superclass_initializer_expression + ; + +// Capture Lists +closure_expression + : LCURLY closure_signature? statements? RCURLY + ; + +closure_signature + : capture_list? closure_parameter_clause THROWS? function_result? IN + | capture_list IN + ; + +closure_parameter_clause + : LPAREN closure_parameter_list? RPAREN + | identifier_list + ; + +closure_parameter_list + : closure_parameter (COMMA closure_parameter)* + ; + +closure_parameter + : UNDERSCORE? closure_parameter_name = identifier (type_annotation range_operator?)? + ; + +capture_list + : LBRACK capture_list_items RBRACK + ; + +capture_list_items + : capture_list_item (COMMA capture_list_item)* + ; + +capture_list_item + : capture_specifier? (identifier EQUAL? expression | self_expression) + ; + +capture_specifier + : WEAK + | UNOWNED (LPAREN (SAFE | UNSAFE) RPAREN)? + ; + +// Implicit Member Expression +implicit_member_expression + : DOT (identifier | keyword) (DOT postfix_expression)? + ; + +// let a: MyType = .default; static let `default` = MyType() + +// Parenthesized Expression +parenthesized_operator + : LPAREN operator RPAREN + ; + +// Parenthesized Expression +parenthesized_expression + : LPAREN expression RPAREN + ; + +// Tuple Expression +tuple_expression + : LPAREN RPAREN + | LPAREN tuple_element COMMA tuple_element_list RPAREN + ; + +tuple_element_list + : tuple_element (COMMA tuple_element)* + ; + +tuple_element + : (identifier COLON)? expression + ; + +// Wildcard Expression +wildcard_expression + : UNDERSCORE + ; + +// Key-Path Expression +key_path_expression + : BACKSLASH type? DOT key_path_components + ; + +key_path_components + : key_path_component (DOT key_path_component)* + ; + +key_path_component + : identifier key_path_postfixes? + | key_path_postfixes + ; + +key_path_postfixes + : key_path_postfix+ + ; + +key_path_postfix + : QUESTION + | BANG + | SELF + | LBRACK function_call_argument_list RBRACK + ; + +// Selector Expression +selector_expression + : HASH_SELECTOR LPAREN ((GETTER | SETTER) COLON)? expression RPAREN + ; + +// Key-Path String Expression +key_path_string_expression + : HASH_KEYPATH LPAREN expression RPAREN + ; + +// Postfix Expressions + +//The following sets of rules are mutually left-recursive [postfix_expression, +// function_call_expression, initializer_expression, explicit_member_expression, +// postfix_self_expression, subscript_expression, forced_value_expression, +// optional_chaining_expression], to avoid this the rule inner_postfix_expression was implemented. +/*postfix_expression: + postfix_expression postfix_operator | function_call_expression | initializer_expression | + explicit_member_expression | postfix_self_expression | subscript_expression | + forced_value_expression | optional_chaining_expression | primary_expression ; + */ +postfix_expression + : primary_expression ( + function_call_suffix + | initializer_suffix + | explicit_member_suffix + | postfix_self_suffix + | subscript_suffix + | forced_value_suffix + | optional_chaining_suffix + )* postfix_operator*? + ; + +function_call_suffix + : function_call_argument_clause? trailing_closures + | function_call_argument_clause + ; + +initializer_suffix + : DOT INIT (LPAREN argument_names RPAREN)? + ; + +explicit_member_suffix + : DOT (Decimal_digits | identifier ( generic_argument_clause | LPAREN argument_names RPAREN)?) + ; + +postfix_self_suffix + : DOT SELF + ; + +subscript_suffix + : LBRACK function_call_argument_list RBRACK + ; + +forced_value_suffix + : {!this.isBinaryOp(_input)}? BANG + ; + +optional_chaining_suffix + : {!this.isBinaryOp(_input) && _input.get(_input.index()-1).getType()!=WS}? QUESTION + ; + +// Function Call Expression + +function_call_argument_clause + : LPAREN function_call_argument_list? RPAREN + ; + +function_call_argument_list + : function_call_argument (COMMA function_call_argument)* + ; + +function_call_argument + : argument_name? (identifier /*optimization */ | expression | operator) + ; + +trailing_closures + : closure_expression labeled_trailing_closures? + ; + +labeled_trailing_closures + : labeled_trailing_closure+ + ; + +labeled_trailing_closure + : identifier COLON closure_expression + ; + +argument_names + : argument_name+ + ; + +argument_name + : identifier COLON + ; + +// Types + +// The following sets of rules are mutually left-recursive [type, optional_type, implicitly_unwrapped_optional_type, metatype_type], to avoid this they were integrated into the same rule. +type + : function_type + | array_type + | dictionary_type + | protocol_composition_type + | type_identifier + | tuple_type + | opaque_type + | type ( + {!this.isBinaryOp(_input)}? QUESTION //optional_type + | {!this.isBinaryOp(_input)}? BANG //implicitly_unwrapped_optional_type + | DOT TYPE + | DOT PROTOCOL + ) //metatype_type + | any_type + | self_type + | LPAREN type RPAREN + ; + +// Type Annotation +type_annotation + : COLON attributes? INOUT? type + ; + +// Type identifier +type_identifier + : type_name generic_argument_clause? (DOT type_identifier)? + ; + +type_name + : identifier + ; + +// Tuple Type +tuple_type + : LPAREN tuple_type_element_list? RPAREN + ; + +tuple_type_element_list + : tuple_type_element (COMMA tuple_type_element)* + ; + +tuple_type_element + : (element_name type_annotation | type) (EQUAL expression)? /* assigning value */ + ; + +element_name + : identifier+ + ; // TODO understand a specific thing - _ + +// Function Type +function_type + : attributes? function_type_argument_clause (THROWS (LPAREN type RPAREN)?)? arrow_operator type + ; + +function_type_argument_clause + : LPAREN (function_type_argument_list range_operator?)? RPAREN + ; + +function_type_argument_list + : function_type_argument (COMMA function_type_argument)* + ; + +function_type_argument + : attributes? INOUT? type + | argument_label type_annotation + ; + +argument_label + : identifier+ + ; // TODO Understand a specific thing - _ + +// Array Type +array_type + : LBRACK type RBRACK + ; + +// Dictionary Type +dictionary_type + : LBRACK type COLON type RBRACK + ; + +// Protocol Composition Type +protocol_composition_type + : type_identifier (AND type_identifier)* trailing_composition_and? + ; + +trailing_composition_and + : {!this.isBinaryOp(_input)}? AND + ; + +// Opaque Type +opaque_type + : SOME type + ; + +// Any Type +any_type + : ANY + ; + +// Self Type +self_type + : SELF_BIG + ; + +// Type Inheritance Clause +type_inheritance_clause + : COLON type_inheritance_list + ; + +type_inheritance_list + : type_inheritance_item (COMMA type_inheritance_item)* + ; + +// Swift 6: support ~Copyable (noncopyable) in inheritance +type_inheritance_item + : TILDE? type_identifier + ; + +// Identifiers +identifier + : ( + LINUX + | WINDOWS + | ALPHA + | ARCH + | ARM + | ARM64 + | ASSIGNMENT + | BLUE + | CAN_IMPORT + | COMPILER + | FILE + | GREEN + | HIGHER_THAN + | I386 + | I_OS + | OSX + | I_OS_APPLICATION_EXTENSION + | LINE + | LOWER_THAN + | MAC_CATALYST + | MAC_CATALYST_APPLICATION_EXTENSION + | MAC_OS + | MAC_OS_APPLICATION_EXTENSION + | OS + | PRECEDENCE_GROUP + | RED + | RESOURCE_NAME + | SAFE + | SIMULATOR + | SOME + | SWIFT + | TARGET_ENVIRONMENT + | TV_OS + | UNSAFE + | WATCH_OS + | X86_64 + + // Keywords reserved in particular contexts + | ASSOCIATIVITY + | CONVENIENCE + | DYNAMIC + | DID_SET + | FINAL + | GET + | INFIX + | INDIRECT + | LAZY + | LEFT + | MUTATING + | NONE + | NONMUTATING + | OPTIONAL + | OVERRIDE + | POSTFIX + | PRECEDENCE + | PREFIX + | PROTOCOL + | REQUIRED + | RIGHT + | SET + | TYPE + | UNOWNED + | WEAK + | WILL_SET + | IN + | FOR + | GUARD + | WHERE + | DEFAULT + | INTERNAL + | PRIVATE + | PUBLIC + | OPEN + | AS + | PREFIX + | POSTFIX + | WHILE + | SELF + | SELF_BIG + | SET + | CLASS + | STRUCT + | GETTER + | SETTER + | OPERATOR + | DO + | CATCH + // Swift 6 keywords that can also be identifiers + | ASYNC + | AWAIT + | ACTOR + | NONISOLATED + | ISOLATED + | CONSUMING + | BORROWING + | SENDING + | MACRO + ) + | Identifier + | BACKTICK (keyword | Identifier | DOLLAR) BACKTICK + | UNDERSCORE + ; + +identifier_list + : identifier (COMMA identifier)* + ; + +// Keywords and Punctuation +keyword + : + // Keywords used in declarations + ASSOCIATED_TYPE + | CLASS + | DEINIT + | ENUM + | EXTENSION + | FILE_PRIVATE + | FUNC + | IMPORT + | INIT + | INOUT + | INTERNAL + | LET + | OPEN + | OPERATOR + | PRIVATE + | PROTOCOL + | PUBLIC + | RETHROWS + | STATIC + | STRUCT + | SUBSCRIPT + | TYPEALIAS + | VAR + // Keywords used in statements + | BREAK + | CASE + | CONTINUE + | DEFAULT + | DEFER + | DO + | ELSE + | FALLTHROUGH + | FOR + | GUARD + | IF + | IN + | REPEAT + | RETURN + | SWITCH + | WHERE + | WHILE + // Keywords used in expressions and types + | AS + | ANY + | CATCH + | FALSE + | IS + | NIL + | SUPER + | SELF + | SELF_BIG + | THROW + | THROWS + | TRUE + | TRY + // Keywords used in patterns + | UNDERSCORE + // Keywords that begin with a number sign (#) + | AVAILABLE + | HASH_COLOR_LITERAL + | HASH_COLUMN + | HASH_ELSE + | HASH_ELSEIF + | HASH_ENDIF + | ERROR + | HASH_FILE + | HASH_FILE_ID + | HASH_FILE_LITERAL + | HASH_FILE_PATH + | HASH_FUNCTION + | HASH_IF + | HASH_IMAGE_LITERAL + | HASH_LINE + | HASH_SELECTOR + | SOURCE_LOCATION + | WARNING + ; + +// Operators + +// Assignment Operator +assignment_operator + : {this.isBinaryOp(_input)}? EQUAL + ; + +negate_prefix_operator + : {this.isPrefixOp(_input)}? SUB + ; + +compilation_condition_AND + : {this.isOperator(_input,"&&")}? AND AND + ; + +compilation_condition_OR + : {this.isOperator(_input,"||")}? OR OR + ; + +compilation_condition_GE + : {this.isOperator(_input,">=")}? GT EQUAL + ; + +compilation_condition_L + : {this.isOperator(_input,"<")}? LT + ; + +arrow_operator + : {this.isOperator(_input,"->")}? SUB GT + ; + +range_operator + : {this.isOperator(_input,"...")}? DOT DOT DOT + ; + +same_type_equals + : {this.isOperator(_input,"==")}? EQUAL EQUAL + ; + +binary_operator + : {this.isBinaryOp(_input)}? operator + ; + +prefix_operator + : {this.isPrefixOp(_input)}? operator + ; + +postfix_operator + : {this.isPostfixOp(_input)}? operator + ; + +operator + : operator_head operator_characters? + | dot_operator_head dot_operator_characters + ; + +operator_head + : ( + DIV + | EQUAL + | SUB + | ADD + | BANG + | MUL + | MOD + | AND + | OR + | LT + | GT + | CARET + | TILDE + | QUESTION + ) // wrapping in (..) makes it a fast set comparison + | Operator_head_other + ; + +operator_character + : operator_head + | Operator_following_character + ; + +operator_characters + : ({_input.get(_input.index()-1).getType()!=WS}? operator_character)+ + ; + +dot_operator_head + : DOT + ; + +dot_operator_character + : DOT + | operator_character + ; + +dot_operator_characters + : ({_input.get(_input.index()-1).getType()!=WS}? dot_operator_character)+ + ; + +// Literals +literal + : numeric_literal + | string_literal + | boolean_literal + | nil_literal + ; + +numeric_literal + : negate_prefix_operator? integer_literal + | negate_prefix_operator? Floating_point_literal + ; + +boolean_literal + : TRUE + | FALSE + ; + +nil_literal + : NIL + ; + +// Integer Literals +integer_literal + : Decimal_digits + | Decimal_literal + | Binary_literal + | Octal_literal + | Hexadecimal_literal + ; + +// String Literals +string_literal + : extended_string_literal + | interpolated_string_literal + | static_string_literal + ; + +extended_string_literal + : Multi_line_extended_string_open Quoted_multi_line_extended_text+ Multi_line_extended_string_close + | Single_line_extended_string_open Quoted_single_line_extended_text+ Single_line_extended_string_close + ; + +static_string_literal + : Single_line_string_open Quoted_single_line_text* Single_line_string_close + | Multi_line_string_open Quoted_multi_line_text* Multi_line_string_close + ; + +interpolated_string_literal + : Single_line_string_open ( + Quoted_single_line_text + | Interpolation_single_line (expression | tuple_element COMMA tuple_element_list) RPAREN + )* Single_line_string_close + | Multi_line_string_open ( + Quoted_multi_line_text + | Interpolation_multi_line (expression | tuple_element COMMA tuple_element_list) RPAREN + )* Multi_line_string_close + ; + +// ==================== Swift 5.9+ Macro Declarations ==================== + +// Macro Declaration +macro_declaration + : attributes? access_level_modifier? MACRO macro_name generic_parameter_clause? macro_signature generic_where_clause? macro_definition? + ; + +macro_name + : identifier + ; + +macro_signature + : parameter_clause (arrow_operator type)? + ; + +macro_definition + : EQUAL macro_expansion_expression + ; + +macro_expansion_expression + : HASH_EXTERNAL_MACRO LPAREN macro_arguments RPAREN + | HASH identifier LPAREN macro_arguments? RPAREN + ; + +macro_arguments + : macro_argument (COMMA macro_argument)* + ; + +macro_argument + : (identifier COLON)? expression + ; + +// Freestanding macro expression (used in expressions) +freestanding_macro_expression + : HASH identifier generic_argument_clause? (LPAREN macro_arguments? RPAREN)? trailing_closures? + ; \ No newline at end of file diff --git a/chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java b/chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java new file mode 100644 index 00000000..ce5d6056 --- /dev/null +++ b/chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupport.java @@ -0,0 +1,384 @@ +package chapi.ast.antlr; + +import org.antlr.v4.runtime.*; +import org.antlr.v4.runtime.misc.Interval; + +import java.util.BitSet; + +public abstract class SwiftSupport extends Parser +{ + protected SwiftSupport(TokenStream input) + { + super(input); + } + + /* TODO + There is one caveat to the rules above. If the ! or ? predefined operator + has no whitespace on the left, it is treated as a postfix operator, + regardless of whether it has whitespace on the right. To use the ? as + the optional-chaining operator, it must not have whitespace on the left. + To use it in the ternary conditional (? :) operator, it must have + whitespace around both sides. + */ + + /* + operator-head : / = - + ! * % < > & | ^ ~ ? + | [\u00A1-\u00A7] + | [\u00A9\u00AB] + | [\u00AC\u00AE] + | [\u00B0-\u00B1\u00B6\u00BB\u00BF\u00D7\u00F7] + | [\u2016-\u2017\u2020-\u2027] + | [\u2030-\u203E] + | [\u2041-\u2053] + | [\u2055-\u205E] + | [\u2190-\u23FF] + | [\u2500-\u2775] + | [\u2794-\u2BFF] + | [\u2E00-\u2E7F] + | [\u3001-\u3003] + | [\u3008-\u3030] + ; + */ + public static final BitSet operatorHead = new BitSet(0x10000); + public static final BitSet operatorCharacter; + + public static final BitSet leftWS = new BitSet(255); + public static final BitSet rightWS = new BitSet(255); + + static { + // operator-head → / =­ -­ +­ !­ *­ %­ <­ >­ &­ |­ ^­ ~­ ?­ + operatorHead.set('/'); + operatorHead.set('='); + operatorHead.set('-'); + operatorHead.set('+'); + operatorHead.set('!'); + operatorHead.set('*'); + operatorHead.set('%'); + operatorHead.set('&'); + operatorHead.set('|'); + operatorHead.set('<'); + operatorHead.set('>'); + operatorHead.set('^'); + operatorHead.set('~'); + operatorHead.set('?'); + + // operator-head → U+00A1–U+00A7 + operatorHead.set(0x00A1, 0x00A7 + 1); + + // operator-head → U+00A9 or U+00AB + operatorHead.set(0x00A9); + operatorHead.set(0x00AB); + + // operator-head → U+00AC or U+00AE + operatorHead.set(0x00AC); + operatorHead.set(0x00AE); + + // operator-head → U+00B0–U+00B1, U+00B6, U+00BB, U+00BF, U+00D7, or U+00F7 + operatorHead.set(0x00B0, 0x00B1 + 1); + operatorHead.set(0x00B6); + operatorHead.set(0x00BB); + operatorHead.set(0x00BF); + operatorHead.set(0x00D7); + operatorHead.set(0x00F7); + + // operator-head → U+2016–U+2017 or U+2020–U+2027 + operatorHead.set(0x2016, 0x2017 + 1); + operatorHead.set(0x2020, 0x2027 + 1); + + // operator-head → U+2030–U+203E + operatorHead.set(0x2030, 0x203E + 1); + + // operator-head → U+2041–U+2053 + operatorHead.set(0x2041, 0x2053 + 1); + + // operator-head → U+2055–U+205E + operatorHead.set(0x2055, 0x205E + 1); + + // operator-head → U+2190–U+23FF + operatorHead.set(0x2190, 0x23FF + 1); + + // operator-head → U+2500–U+2775 + operatorHead.set(0x2500, 0x2775 + 1); + + // operator-head → U+2794–U+2BFF + operatorHead.set(0x2794, 0x2BFF + 1); + + // operator-head → U+2E00–U+2E7F + operatorHead.set(0x2E00, 0x2E7F + 1); + + // operator-head → U+3001–U+3003 + operatorHead.set(0x3001, 0x3003 + 1); + + // operator-head → U+3008–U+3030 + operatorHead.set(0x3008, 0x3030 + 1); + + // operator-character → operator-head­ + operatorCharacter = (BitSet) operatorHead.clone(); + + // operator-character → U+0300–U+036F + operatorCharacter.set(0x0300, 0x036F + 1); + // operator-character → U+1DC0–U+1DFF + operatorCharacter.set(0x1DC0, 0x1DFF + 1); + // operator-character → U+20D0–U+20FF + operatorCharacter.set(0x20D0, 0x20FF + 1); + // operator-character → U+FE00–U+FE0F + operatorCharacter.set(0xFE00, 0xFE0F + 1); + // operator-character → U+FE20–U+FE2F + operatorCharacter.set(0xFE20, 0xFE2F + 1); + operatorCharacter.set(0xE0100, 0xE01EF + 1); + + // operator-character → U+E0100–U+E01EF + // Java works with 16-bit unicode chars. However, it can work for targets in other languages, e.g. in Swift + // operatorCharacter.set(0xE0100,0xE01EF+1); + + leftWS.set(Swift5Parser.WS); + leftWS.set(Swift5Parser.LPAREN); + leftWS.set(Swift5Parser.Interpolation_multi_line); + leftWS.set(Swift5Parser.Interpolation_single_line); + leftWS.set(Swift5Parser.LBRACK); + leftWS.set(Swift5Parser.LCURLY); + leftWS.set(Swift5Parser.COMMA); + leftWS.set(Swift5Parser.COLON); + leftWS.set(Swift5Parser.SEMI); + + rightWS.set(Swift5Parser.WS); + rightWS.set(Swift5Parser.RPAREN); + rightWS.set(Swift5Parser.RBRACK); + rightWS.set(Swift5Parser.RCURLY); + rightWS.set(Swift5Parser.COMMA); + rightWS.set(Swift5Parser.COLON); + rightWS.set(Swift5Parser.SEMI); + rightWS.set(Swift5Parser.Line_comment); + rightWS.set(Swift5Parser.Block_comment); + } + + private static boolean isCharacterFromSet(Token token, BitSet bitSet) { + if (token.getType() == Token.EOF) { + return false; + } else { + String text = token.getText(); + int codepoint = text.codePointAt(0); + if (Character.charCount(codepoint) != text.length()) { + // not a single character + return false; + } else { + return bitSet.get(codepoint); + } + } + } + + public static boolean isOperatorHead(Token token) { + return isCharacterFromSet(token, operatorHead); + } + + public static boolean isOperatorCharacter(Token token) { + return isCharacterFromSet(token, operatorCharacter); + } + + public static boolean isOpNext(TokenStream tokens) { + int start = tokens.index(); + Token lt = tokens.get(start); + int stop = getLastOpTokenIndex(tokens); + return stop != -1; + // System.out.printf("isOpNext: i=%d t='%s'", start, lt.getText()); + // System.out.printf(", op='%s'\n", tokens.getText(Interval.of(start,stop))); + } + + /** + * Find stop token index of next operator; return -1 if not operator. + */ + public static int getLastOpTokenIndex(TokenStream tokens) { + SwiftSupport.fillUp(tokens); + int currentTokenIndex = tokens.index(); // current on-channel lookahead token index + Token currentToken = tokens.get(currentTokenIndex); + + //System.out.println("getLastOpTokenIndex: "+currentToken.getText()); + + + // operator → dot-operator-head­ dot-operator-characters + if (currentToken.getType() == Swift5Parser.DOT && tokens.get(currentTokenIndex + 1).getType() == Swift5Parser.DOT) { + //System.out.println("DOT"); + + + // dot-operator + currentTokenIndex += 2; // point at token after ".." + currentToken = tokens.get(currentTokenIndex); + + // dot-operator-character → .­ | operator-character­ + while (currentToken.getType() == Swift5Parser.DOT || isOperatorCharacter(currentToken)) { + //System.out.println("DOT"); + currentTokenIndex++; + currentToken = tokens.get(currentTokenIndex); + } + + //System.out.println("result: "+(currentTokenIndex - 1)); + return currentTokenIndex - 1; + } + + // operator → operator-head­ operator-characters­? + + if (isOperatorHead(currentToken)) { + //System.out.println("isOperatorHead"); + + currentToken = tokens.get(currentTokenIndex); + while (isOperatorCharacter(currentToken)) { + //System.out.println("isOperatorCharacter"); + currentTokenIndex++; + currentToken = tokens.get(currentTokenIndex); + } + //System.out.println("result: "+(currentTokenIndex - 1)); + return currentTokenIndex - 1; + } else { + //System.out.println("result: "+(-1)); + return -1; + } + } + + /** + * "If an operator has whitespace around both sides or around neither side, + * it is treated as a binary operator. As an example, the + operator in a+b + * and a + b is treated as a binary operator." + */ + public static boolean isBinaryOp(TokenStream tokens) { + SwiftSupport.fillUp(tokens); + int stop = getLastOpTokenIndex(tokens); + if (stop == -1) return false; + + int start = tokens.index(); + if (start == 0) return false; // Guard against index out of bounds + Token currentToken = tokens.get(start); + Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens + Token nextToken = tokens.get(stop + 1); + boolean prevIsWS = isLeftOperatorWS(prevToken); + boolean nextIsWS = isRightOperatorWS(nextToken); + //String text = tokens.getText(Interval.of(start, stop)); + //System.out.println("isBinaryOp: '"+prevToken+"','"+text+"','"+nextToken+"' is "+result); + // accept only '??'' as binary operator + if (currentToken.getType() == Swift5Lexer.QUESTION && start == stop) { + return false; + } + if (prevIsWS) { + return nextIsWS; + } else { + if (currentToken.getType() == Swift5Lexer.BANG || currentToken.getType() == Swift5Lexer.QUESTION) { + return false; + } else { + if (!nextIsWS) return nextToken.getType() != Swift5Lexer.DOT; + } + } + return false; + } + + /** + * "If an operator has whitespace on the left side only, it is treated as a + * prefix unary operator. As an example, the ++ operator in a ++b is treated + * as a prefix unary operator." + */ + public static boolean isPrefixOp(TokenStream tokens) { + SwiftSupport.fillUp(tokens); + int stop = getLastOpTokenIndex(tokens); + if (stop == -1) return false; + + int start = tokens.index(); + if (start == 0) return true; // At beginning, treat as prefix + Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens + Token nextToken = tokens.get(stop + 1); + boolean prevIsWS = isLeftOperatorWS(prevToken); + boolean nextIsWS = isRightOperatorWS(nextToken); + //String text = tokens.getText(Interval.of(start, stop)); + // System.out.println("isPrefixOp: '"+prevToken+"','"+text+"','"+nextToken+"' is "+result); + return prevIsWS && !nextIsWS; + } + + /** + * "If an operator has whitespace on the right side only, it is treated as a + * postfix unary operator. As an example, the ++ operator in a++ b is treated + * as a postfix unary operator." + *

+ * "If an operator has no whitespace on the left but is followed immediately + * by a dot (.), it is treated as a postfix unary operator. As an example, + * the ++ operator in a++.b is treated as a postfix unary operator (a++ .b + * rather than a ++ .b)." + */ + public static boolean isPostfixOp(TokenStream tokens) { + SwiftSupport.fillUp(tokens); + int stop = getLastOpTokenIndex(tokens); + if (stop == -1) return false; + + int start = tokens.index(); + if (start == 0) return false; // At beginning, cannot be postfix + Token prevToken = tokens.get(start - 1); // includes hidden-channel tokens + Token nextToken = tokens.get(stop + 1); + boolean prevIsWS = isLeftOperatorWS(prevToken); + boolean nextIsWS = isRightOperatorWS(nextToken); + //String text = tokens.getText(Interval.of(start, stop)); + // System.out.println("isPostfixOp: '"+prevToken+"','"+text+"','"+nextToken+"' is "+result); + return !prevIsWS && nextIsWS || + !prevIsWS && nextToken.getType() == Swift5Parser.DOT; + } + + public static boolean isOperator(TokenStream tokens, String op) { + SwiftSupport.fillUp(tokens); + int stop = getLastOpTokenIndex(tokens); + if (stop == -1) return false; + + int start = tokens.index(); + String text = tokens.getText(Interval.of(start, stop)); + // System.out.println("text: '"+text+"', op: '"+op+"', text.equals(op): '"+text.equals(op)+"'"); + + // for (int i = 0; i <= stop; i++) { + // System.out.println("token["+i+"] = '"+tokens.getText(Interval.of(i, i))+"'"); + // } + + return text.equals(op); + } + + public static boolean isLeftOperatorWS(Token t) { + return leftWS.get(t.getType()); + } + + public static boolean isRightOperatorWS(Token t) { + return rightWS.get(t.getType()) || t.getType() == Token.EOF; + } + + public static boolean isSeparatedStatement(TokenStream tokens, int indexOfPreviousStatement) { + SwiftSupport.fillUp(tokens); + //System.out.println("------"); + //System.out.println("indexOfPreviousStatement: " + indexOfPreviousStatement); + + int indexFrom = indexOfPreviousStatement - 1; + int indexTo = tokens.index() - 1; + + if (indexFrom >= 0) { + // Stupid check for new line and semicolon, can be optimized + while (indexFrom >= 0 && tokens.get(indexFrom).getChannel() == Token.HIDDEN_CHANNEL) { + indexFrom--; + } + + //System.out.println("from: '" + tokens.getText(Interval.of(indexFrom, indexFrom))+"', "+tokens.get(indexFrom)); + //System.out.println("to: '" + tokens.getText(Interval.of(indexTo, indexTo))+"', "+tokens.get(indexTo)); + //System.out.println("in_between: '" + tokens.getText(Interval.of(indexFrom, indexTo))); + + //for (int i = previousIndex; i < currentIndex; i++) + for(int i =indexTo;i>= indexFrom;i--){ + String t = tokens.get(i).getText(); + if(t.contains("\n") || t.contains(";")){ + return true; + } + } + return false; + //String text = tokens.getText(Interval.of(indexFrom, indexTo)); + //return text.contains("\n") || text.contains(";"); + } else { + return true; + } + } + public static void fillUp(TokenStream tokens) + { + for (int jj = 1;;++jj) + { + int t = tokens.LA(jj); + if (t == -1) break; + } + } +} diff --git a/chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupportLexer.java b/chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupportLexer.java new file mode 100644 index 00000000..ff9afbfc --- /dev/null +++ b/chapi-ast-swift/src/main/java/chapi/ast/antlr/SwiftSupportLexer.java @@ -0,0 +1,21 @@ +package chapi.ast.antlr; + +import org.antlr.v4.runtime.*; + +import java.util.Stack; + +public abstract class SwiftSupportLexer extends Lexer +{ + protected SwiftSupportLexer(CharStream input) + { + super(input); + } + + public Stack parenthesis = new Stack(); + + @Override + public void reset(){ + super.reset(); + parenthesis.clear(); + } +} diff --git a/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftAnalyser.kt b/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftAnalyser.kt new file mode 100644 index 00000000..ad22d52a --- /dev/null +++ b/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftAnalyser.kt @@ -0,0 +1,25 @@ +package chapi.ast.swiftast + +import chapi.ast.antlr.Swift5Lexer +import chapi.ast.antlr.Swift5Parser +import chapi.domain.core.CodeContainer +import chapi.parser.Analyser +import org.antlr.v4.runtime.CharStreams +import org.antlr.v4.runtime.CommonTokenStream +import org.antlr.v4.runtime.tree.ParseTreeWalker + +open class SwiftAnalyser : Analyser { + override fun analysis(code: String, filePath: String): CodeContainer { + val context = parse(code).top_level() + val listener = SwiftFullIdentListener(filePath = filePath) + ParseTreeWalker().walk(listener, context) + return listener.getNodeInfo() + } + + private fun parse(str: String): Swift5Parser = + CharStreams.fromString(str) + .let(::Swift5Lexer) + .let(::CommonTokenStream) + .let(::Swift5Parser) +} + diff --git a/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt b/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt new file mode 100644 index 00000000..63471931 --- /dev/null +++ b/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftFullIdentListener.kt @@ -0,0 +1,768 @@ +package chapi.ast.swiftast + +import chapi.ast.antlr.Swift5Parser +import chapi.ast.antlr.Swift5ParserBaseListener +import chapi.domain.core.* +import org.antlr.v4.runtime.ParserRuleContext + +class SwiftFullIdentListener(private val filePath: String) : Swift5ParserBaseListener() { + private val imports = mutableListOf() + private val dataStructures = mutableListOf() + private val stack = ArrayDeque() + private val topLevelFunctions = mutableListOf() + private val topLevelFields = mutableListOf() + + // Current context tracking + private var currentFunction: CodeFunction? = null + + // ==================== Import Declaration ==================== + + override fun enterImport_declaration(ctx: Swift5Parser.Import_declarationContext) { + val source = ctx.import_path()?.text ?: return + val kind = ctx.import_kind()?.text + + val codeImport = CodeImport( + Source = source, + UsageName = listOf(source.split(".").last()), + PathSegments = source.split(".") + ) + + // If import kind is specified (class, struct, enum, etc.) + if (kind != null) { + codeImport.Scope = kind + } + + imports += codeImport + } + + // ==================== Attribute and Modifier Helpers ==================== + + private fun buildAnnotationsFromAttributes(ctx: Swift5Parser.AttributesContext?): List { + return ctx?.attribute()?.map { buildAnnotation(it) } ?: listOf() + } + + private fun buildAnnotation(ctx: Swift5Parser.AttributeContext): CodeAnnotation { + val name = ctx.attribute_name()?.text ?: "" + val keyValues = mutableListOf() + + // Parse attribute arguments if present + ctx.attribute_argument_clause()?.balanced_tokens()?.balanced_token()?.forEach { token -> + keyValues.add(AnnotationKeyValue(Key = token.text, Value = "")) + } + + return CodeAnnotation( + Name = name, + KeyValues = keyValues, + Position = ctx.toPosition() + ) + } + + private fun buildAccessLevelModifier(ctx: Swift5Parser.Access_level_modifierContext?): String { + if (ctx == null) return "" + + val base = when { + ctx.PRIVATE() != null -> "private" + ctx.FILE_PRIVATE() != null -> "fileprivate" + ctx.INTERNAL() != null -> "internal" + ctx.PUBLIC() != null -> "public" + ctx.OPEN() != null -> "open" + else -> "" + } + + // Check for (set) suffix + return if (ctx.SET() != null) { + "$base(set)" + } else { + base + } + } + + private fun buildModifiersFromDeclarationModifiers(ctx: Swift5Parser.Declaration_modifiersContext?): List { + return ctx?.declaration_modifier()?.mapNotNull { modCtx -> + // Check for access level modifier + modCtx.access_level_modifier()?.let { return@mapNotNull buildAccessLevelModifier(it) } + + // Check for mutation modifier + modCtx.mutation_modifier()?.let { return@mapNotNull it.text } + + // Other modifiers (static, final, lazy, etc.) + modCtx.text.takeIf { it.isNotEmpty() } + } ?: listOf() + } + + // ==================== Struct Declaration ==================== + + override fun enterStruct_declaration(ctx: Swift5Parser.Struct_declarationContext) { + val name = ctx.struct_name()?.text ?: return + + // Extract attributes and access modifier directly from context + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) + val modifierAnnotations = if (accessModifier.isNotEmpty()) { + listOf(CodeAnnotation(Name = accessModifier)) + } else { + listOf() + } + + val struct = CodeDataStruct( + NodeName = name, + Type = DataStructType.STRUCT, + FilePath = filePath, + Position = ctx.toPosition(), + Annotations = annotations + modifierAnnotations + ) + + // Parse inheritance + ctx.type_inheritance_clause()?.let { inheritanceCtx -> + struct.Implements = buildInheritanceList(inheritanceCtx) + } + + stack.addLast(struct) + } + + override fun exitStruct_declaration(ctx: Swift5Parser.Struct_declarationContext) { + finishDataStruct() + } + + // ==================== Class Declaration ==================== + + override fun enterClass_declaration(ctx: Swift5Parser.Class_declarationContext) { + val name = ctx.class_name()?.text ?: return + + // Extract attributes and modifiers directly from context + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val modifiers = extractClassModifiers(ctx) + + val modifierAnnotations = modifiers.filter { it.isNotEmpty() }.map { CodeAnnotation(Name = it) } + + val classStruct = CodeDataStruct( + NodeName = name, + Type = DataStructType.CLASS, + FilePath = filePath, + Position = ctx.toPosition(), + Annotations = annotations + modifierAnnotations + ) + + // Parse inheritance + ctx.type_inheritance_clause()?.let { inheritanceCtx -> + val inheritanceList = buildInheritanceList(inheritanceCtx) + if (inheritanceList.isNotEmpty()) { + // First item is typically the superclass for classes + classStruct.Extend = inheritanceList.first() + if (inheritanceList.size > 1) { + classStruct.Implements = inheritanceList.drop(1) + } + } + } + + stack.addLast(classStruct) + } + + private fun extractClassModifiers(ctx: Swift5Parser.Class_declarationContext): List { + val modifiers = mutableListOf() + + // Use parse-tree tokens instead of substring matching to avoid issues + // like "fileprivate" matching both "fileprivate" and "private" + ctx.access_level_modifier() + ?.let { buildAccessLevelModifier(it) } + ?.takeIf { it.isNotEmpty() } + ?.let { modifiers.add(it) } + + if (ctx.FINAL() != null) { + modifiers.add("final") + } + + return modifiers + } + + override fun exitClass_declaration(ctx: Swift5Parser.Class_declarationContext) { + finishDataStruct() + } + + // ==================== Actor Declaration (Swift 5.5+) ==================== + + override fun enterActor_declaration(ctx: Swift5Parser.Actor_declarationContext) { + val name = ctx.actor_name()?.text ?: return + + // Extract attributes and modifiers + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) + val modifiers = mutableListOf() + + if (accessModifier.isNotEmpty()) { + modifiers.add(accessModifier) + } + + // Check for distributed modifier + if (ctx.DISTRIBUTED() != null) { + modifiers.add("distributed") + } + + val modifierAnnotations = modifiers.map { CodeAnnotation(Name = it) } + + val actorStruct = CodeDataStruct( + NodeName = name, + Type = DataStructType.CLASS, // Actor is similar to class + FilePath = filePath, + Position = ctx.toPosition(), + Annotations = annotations + modifierAnnotations + ) + + // Parse inheritance + ctx.type_inheritance_clause()?.let { inheritanceCtx -> + actorStruct.Implements = buildInheritanceList(inheritanceCtx) + } + + stack.addLast(actorStruct) + } + + override fun exitActor_declaration(ctx: Swift5Parser.Actor_declarationContext) { + finishDataStruct() + } + + // ==================== Enum Declaration ==================== + + override fun enterEnum_declaration(ctx: Swift5Parser.Enum_declarationContext) { + val name = ctx.union_style_enum()?.enum_name()?.text + ?: ctx.raw_value_style_enum()?.enum_name()?.text + ?: return + + // Extract attributes and access modifier directly from context + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) + val modifierAnnotations = if (accessModifier.isNotEmpty()) { + listOf(CodeAnnotation(Name = accessModifier)) + } else { + listOf() + } + + val enumStruct = CodeDataStruct( + NodeName = name, + Type = DataStructType.ENUM, + FilePath = filePath, + Position = ctx.toPosition(), + Annotations = annotations + modifierAnnotations + ) + + // Parse inheritance/raw value type + ctx.union_style_enum()?.type_inheritance_clause()?.let { inheritanceCtx -> + enumStruct.Implements = buildInheritanceList(inheritanceCtx) + } + ctx.raw_value_style_enum()?.type_inheritance_clause()?.let { inheritanceCtx -> + val inheritanceList = buildInheritanceList(inheritanceCtx) + if (inheritanceList.isNotEmpty()) { + // First is typically the raw value type + enumStruct.Extend = inheritanceList.first() + if (inheritanceList.size > 1) { + enumStruct.Implements = inheritanceList.drop(1) + } + } + } + + // Parse enum cases + ctx.union_style_enum()?.union_style_enum_members()?.union_style_enum_member()?.forEach { member -> + member.union_style_enum_case_clause()?.union_style_enum_case_list()?.union_style_enum_case()?.forEach { caseCtx -> + val caseName = caseCtx.enum_case_name()?.text ?: return@forEach + enumStruct.Fields += CodeField( + TypeKey = caseName, + TypeType = caseCtx.tuple_type()?.text ?: "" + ) + } + } + + ctx.raw_value_style_enum()?.raw_value_style_enum_members()?.raw_value_style_enum_member()?.forEach { member -> + member.raw_value_style_enum_case_clause()?.raw_value_style_enum_case_list()?.raw_value_style_enum_case()?.forEach { caseCtx -> + val caseName = caseCtx.enum_case_name()?.text ?: return@forEach + val rawValue = caseCtx.raw_value_assignment()?.raw_value_literal()?.text ?: "" + enumStruct.Fields += CodeField( + TypeKey = caseName, + TypeValue = rawValue + ) + } + } + + stack.addLast(enumStruct) + } + + override fun exitEnum_declaration(ctx: Swift5Parser.Enum_declarationContext) { + finishDataStruct() + } + + // ==================== Protocol Declaration ==================== + + override fun enterProtocol_declaration(ctx: Swift5Parser.Protocol_declarationContext) { + val name = ctx.protocol_name()?.text ?: return + + // Extract attributes and access modifier directly from context + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) + val modifierAnnotations = if (accessModifier.isNotEmpty()) { + listOf(CodeAnnotation(Name = accessModifier)) + } else { + listOf() + } + + val protocol = CodeDataStruct( + NodeName = name, + Type = DataStructType.INTERFACE, // Protocol is similar to Interface + FilePath = filePath, + Position = ctx.toPosition(), + Annotations = annotations + modifierAnnotations + ) + + // Parse inheritance (protocol can inherit from other protocols) + ctx.type_inheritance_clause()?.let { inheritanceCtx -> + protocol.Implements = buildInheritanceList(inheritanceCtx) + } + + stack.addLast(protocol) + } + + override fun exitProtocol_declaration(ctx: Swift5Parser.Protocol_declarationContext) { + finishDataStruct() + } + + // ==================== Extension Declaration ==================== + + override fun enterExtension_declaration(ctx: Swift5Parser.Extension_declarationContext) { + val typeName = ctx.type_identifier()?.text ?: return + + // Extract attributes and access modifier directly from context + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val accessModifier = buildAccessLevelModifier(ctx.access_level_modifier()) + val modifierAnnotations = if (accessModifier.isNotEmpty()) { + listOf(CodeAnnotation(Name = accessModifier)) + } else { + listOf() + } + + val extension = CodeDataStruct( + NodeName = typeName, + Type = DataStructType.CLASS, // Extension extends existing type + FilePath = filePath, + Position = ctx.toPosition(), + Annotations = annotations + modifierAnnotations + ) + + // Parse protocol conformance + ctx.type_inheritance_clause()?.let { inheritanceCtx -> + extension.Implements = buildInheritanceList(inheritanceCtx) + } + + stack.addLast(extension) + } + + override fun exitExtension_declaration(ctx: Swift5Parser.Extension_declarationContext) { + finishDataStruct() + } + + // ==================== Function Declaration ==================== + + override fun enterFunction_declaration(ctx: Swift5Parser.Function_declarationContext) { + val name = ctx.function_name()?.text ?: return + + val returnTypeText = ctx.function_signature() + ?.function_result() + ?.type() + ?.text + ?.takeIf { it.isNotBlank() } + + // Extract attributes and modifiers from function_head + val functionHead = ctx.function_head() + val annotations = buildAnnotationsFromAttributes(functionHead?.attributes()) + val modifiers = buildModifiersFromDeclarationModifiers(functionHead?.declaration_modifiers()).toMutableList() + + val function = CodeFunction( + Name = name, + ReturnType = returnTypeText ?: "", + ReturnTypeRef = returnTypeText?.let { SwiftTypeRefBuilder.build(it) }, + Position = ctx.toPosition(), + Annotations = annotations, + Modifiers = modifiers + ) + + // Parse parameters + ctx.function_signature()?.parameter_clause()?.parameter_list()?.parameter()?.forEach { paramCtx -> + function.Parameters += buildParameter(paramCtx) + } + + // Check for async (Swift 5.5+) + ctx.function_signature()?.async_clause()?.let { + function.Modifiers = function.Modifiers + "async" + } + + // Check for throws/rethrows with typed throws support (Swift 6) + ctx.function_signature()?.throw_clause()?.let { throwClause -> + if (throwClause.RETHROWS() != null) { + function.Modifiers = function.Modifiers + "rethrows" + } else if (throwClause.THROWS() != null) { + // Check for typed throws: throws(ErrorType) + val errorType = throwClause.type()?.text + if (errorType != null) { + function.Modifiers = function.Modifiers + "throws($errorType)" + } else { + function.Modifiers = function.Modifiers + "throws" + } + } + } + + currentFunction = function + + if (stack.isEmpty()) { + topLevelFunctions += function + } else { + val current = stack.last() + current.Functions = current.Functions + function + } + } + + override fun exitFunction_declaration(ctx: Swift5Parser.Function_declarationContext) { + currentFunction = null + } + + // ==================== Initializer Declaration ==================== + + override fun enterInitializer_declaration(ctx: Swift5Parser.Initializer_declarationContext) { + // Extract attributes and modifiers from initializer_head + val initHead = ctx.initializer_head() + val annotations = buildAnnotationsFromAttributes(initHead?.attributes()) + val modifiers = buildModifiersFromDeclarationModifiers(initHead?.declaration_modifiers()) + + val function = CodeFunction( + Name = "init", + IsConstructor = true, + Position = ctx.toPosition(), + Annotations = annotations, + Modifiers = modifiers + ) + + // Check for failable initializer + initHead?.let { head -> + if (head.QUESTION() != null) { + function.Name = "init?" + } + if (head.BANG() != null) { + function.Name = "init!" + } + } + + // Parse parameters + ctx.parameter_clause()?.parameter_list()?.parameter()?.forEach { paramCtx -> + function.Parameters += buildParameter(paramCtx) + } + + // Check for throws/rethrows + if (ctx.THROWS() != null) { + function.Modifiers = function.Modifiers + "throws" + } + if (ctx.RETHROWS() != null) { + function.Modifiers = function.Modifiers + "rethrows" + } + + currentFunction = function + + if (stack.isNotEmpty()) { + val current = stack.last() + current.Functions = current.Functions + function + } + } + + override fun exitInitializer_declaration(ctx: Swift5Parser.Initializer_declarationContext) { + currentFunction = null + } + + // ==================== Deinitializer Declaration ==================== + + override fun enterDeinitializer_declaration(ctx: Swift5Parser.Deinitializer_declarationContext) { + // Extract attributes + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + + val function = CodeFunction( + Name = "deinit", + Position = ctx.toPosition(), + Annotations = annotations + ) + + if (stack.isNotEmpty()) { + val current = stack.last() + current.Functions = current.Functions + function + } + } + + // ==================== Variable Declaration ==================== + + override fun enterVariable_declaration(ctx: Swift5Parser.Variable_declarationContext) { + // Extract attributes and modifiers from variable_declaration_head + val varHead = ctx.variable_declaration_head() + val annotations = buildAnnotationsFromAttributes(varHead?.attributes()) + val modifiers = buildModifiersFromDeclarationModifiers(varHead?.declaration_modifiers()) + + val varName = ctx.variable_name()?.text + + if (varName != null) { + // Single variable with type annotation + val typeAnnotation = ctx.type_annotation()?.type()?.text ?: "" + val initialValue = ctx.initializer()?.expression()?.text ?: "" + + val field = CodeField( + TypeKey = varName, + TypeType = typeAnnotation, + TypeValue = initialValue, + Annotations = annotations, + Modifiers = modifiers + "var", + TypeRef = if (typeAnnotation.isNotEmpty()) SwiftTypeRefBuilder.build(typeAnnotation) else null + ) + + addFieldToCurrentScope(field) + } else { + // Pattern initializer list (e.g., var a = 1, b = 2) + ctx.pattern_initializer_list()?.pattern_initializer()?.forEach { patternInit -> + val pattern = patternInit.pattern() + val name = pattern?.identifier_pattern()?.text ?: pattern?.text ?: return@forEach + val typeAnnotation = pattern?.type_annotation()?.type()?.text ?: "" + val initialValue = patternInit.initializer()?.expression()?.text ?: "" + + val field = CodeField( + TypeKey = name, + TypeType = typeAnnotation, + TypeValue = initialValue, + Annotations = annotations, + Modifiers = modifiers + "var", + TypeRef = if (typeAnnotation.isNotEmpty()) SwiftTypeRefBuilder.build(typeAnnotation) else null + ) + + addFieldToCurrentScope(field) + } + } + } + + // ==================== Constant Declaration ==================== + + override fun enterConstant_declaration(ctx: Swift5Parser.Constant_declarationContext) { + // Extract attributes and modifiers + val annotations = buildAnnotationsFromAttributes(ctx.attributes()) + val modifiers = buildModifiersFromDeclarationModifiers(ctx.declaration_modifiers()) + + ctx.pattern_initializer_list()?.pattern_initializer()?.forEach { patternInit -> + val pattern = patternInit.pattern() + val name = pattern?.identifier_pattern()?.text ?: pattern?.text ?: return@forEach + val typeAnnotation = pattern?.type_annotation()?.type()?.text ?: "" + val initialValue = patternInit.initializer()?.expression()?.text ?: "" + + val field = CodeField( + TypeKey = name, + TypeType = typeAnnotation, + TypeValue = initialValue, + Annotations = annotations, + Modifiers = modifiers + "let", + TypeRef = if (typeAnnotation.isNotEmpty()) SwiftTypeRefBuilder.build(typeAnnotation) else null + ) + + addFieldToCurrentScope(field) + } + } + + // ==================== Protocol Member Declarations ==================== + + override fun enterProtocol_method_declaration(ctx: Swift5Parser.Protocol_method_declarationContext) { + val name = ctx.function_name()?.text ?: return + + // Extract attributes and modifiers from function_head + val functionHead = ctx.function_head() + val annotations = buildAnnotationsFromAttributes(functionHead?.attributes()) + val modifiers = buildModifiersFromDeclarationModifiers(functionHead?.declaration_modifiers()) + + val function = CodeFunction( + Name = name, + Position = ctx.toPosition(), + Annotations = annotations, + Modifiers = modifiers + ) + + // Parse return type + ctx.function_signature()?.function_result()?.type()?.text?.let { returnType -> + function.ReturnType = returnType + function.ReturnTypeRef = SwiftTypeRefBuilder.build(returnType) + } + + // Parse parameters + ctx.function_signature()?.parameter_clause()?.parameter_list()?.parameter()?.forEach { paramCtx -> + function.Parameters += buildParameter(paramCtx) + } + + if (stack.isNotEmpty()) { + val current = stack.last() + current.Functions = current.Functions + function + } + } + + override fun enterProtocol_property_declaration(ctx: Swift5Parser.Protocol_property_declarationContext) { + val varName = ctx.variable_name()?.text ?: return + val typeAnnotation = ctx.type_annotation()?.type()?.text ?: "" + + // Extract attributes and modifiers from variable_declaration_head + val varHead = ctx.variable_declaration_head() + val annotations = buildAnnotationsFromAttributes(varHead?.attributes()) + val modifiers = buildModifiersFromDeclarationModifiers(varHead?.declaration_modifiers()) + + val field = CodeField( + TypeKey = varName, + TypeType = typeAnnotation, + Annotations = annotations, + Modifiers = modifiers, + TypeRef = if (typeAnnotation.isNotEmpty()) SwiftTypeRefBuilder.build(typeAnnotation) else null + ) + + // Check getter/setter requirements + ctx.getter_setter_keyword_block()?.let { block -> + if (block.getter_keyword_clause() != null) { + field.Modifiers = field.Modifiers + "get" + } + if (block.setter_keyword_clause() != null) { + field.Modifiers = field.Modifiers + "set" + } + } + + addFieldToCurrentScope(field) + } + + // ==================== Function Call Expression ==================== + + override fun enterFunction_call_suffix(ctx: Swift5Parser.Function_call_suffixContext) { + if (currentFunction == null) return + + // Get the function call context + val postfixExpr = ctx.parent as? Swift5Parser.Postfix_expressionContext ?: return + val primaryExpr = postfixExpr.primary_expression() + + // For member calls like foo.bar(), extract "bar" from explicit_member_suffix + // instead of "foo" from primary_expression + val lastMember = postfixExpr.explicit_member_suffix().lastOrNull() + val functionName = lastMember?.identifier()?.text + ?: primaryExpr?.unqualified_name()?.identifier()?.text + ?: primaryExpr?.text + ?: return + + val call = CodeCall( + FunctionName = functionName, + Position = ctx.toPosition() + ) + + // Parse arguments + ctx.function_call_argument_clause()?.function_call_argument_list()?.function_call_argument()?.forEach { argCtx -> + val argName = argCtx.argument_name()?.identifier()?.text ?: "" + val argValue = argCtx.expression()?.text ?: argCtx.identifier()?.text ?: "" + + call.Parameters += CodeProperty( + TypeValue = argValue, + TypeType = argName + ) + } + + currentFunction?.let { func -> + func.FunctionCalls = func.FunctionCalls + call + } + } + + // ==================== Helper Methods ==================== + + private fun buildParameter(ctx: Swift5Parser.ParameterContext): CodeProperty { + val externalName = ctx.external_parameter_name()?.text ?: "" + val localName = ctx.local_parameter_name()?.text ?: "" + val typeAnnotation = ctx.type_annotation()?.type()?.text ?: "" + val defaultValue = ctx.default_argument_clause()?.expression()?.text ?: "" + + // Build annotations from parameter attributes + val annotations = ctx.attributes()?.attribute()?.map { buildAnnotation(it) } ?: listOf() + + // Check for ownership modifier (Swift 6: consuming, borrowing, inout) + val modifiers = mutableListOf() + ctx.ownership_modifier()?.let { ownershipCtx -> + when { + ownershipCtx.CONSUMING() != null -> modifiers.add("consuming") + ownershipCtx.BORROWING() != null -> modifiers.add("borrowing") + ownershipCtx.INOUT() != null -> modifiers.add("inout") + else -> { /* no modifier */ } + } + } + + return CodeProperty( + TypeValue = if (externalName.isNotEmpty() && externalName != localName) { + "$externalName $localName" + } else { + localName + }, + TypeType = typeAnnotation, + DefaultValue = defaultValue, + Annotations = annotations, + Modifiers = modifiers, + TypeRef = if (typeAnnotation.isNotEmpty()) SwiftTypeRefBuilder.build(typeAnnotation) else null + ) + } + + private fun buildInheritanceList(ctx: Swift5Parser.Type_inheritance_clauseContext): List { + // Swift 6: support ~Copyable syntax in inheritance + return ctx.type_inheritance_list()?.type_inheritance_item()?.mapNotNull { item -> + val typeText = item.type_identifier()?.text ?: return@mapNotNull null + // Check if it's a noncopyable type (~Copyable) + if (item.TILDE() != null) { + "~$typeText" + } else { + typeText + } + } ?: listOf() + } + + private fun addFieldToCurrentScope(field: CodeField) { + if (stack.isEmpty()) { + topLevelFields += field + } else { + val current = stack.last() + current.Fields = current.Fields + field + } + } + + fun getNodeInfo(): CodeContainer { + val topLevel = if (topLevelFunctions.isEmpty() && topLevelFields.isEmpty()) { + null + } else { + TopLevelScope( + Functions = topLevelFunctions.toList(), + Fields = topLevelFields.toList() + ) + } + + return CodeContainer( + FullName = filePath, + PackageName = "", + Imports = imports.toList(), + DataStructures = dataStructures.toList(), + Language = "swift", + Kind = ContainerKind.SOURCE_FILE, + DeclaredPackage = "", + TopLevel = topLevel, + ) + } + + private fun finishDataStruct() { + if (stack.isEmpty()) return + val finished = stack.removeLast() + if (stack.isEmpty()) { + dataStructures += finished + } else { + val parent = stack.last() + parent.InnerStructures = parent.InnerStructures + finished + } + } +} + +private fun ParserRuleContext.toPosition(): CodePosition { + val startToken = this.start + val stopToken = this.stop + return CodePosition( + StartLine = startToken?.line ?: 0, + StartLinePosition = startToken?.charPositionInLine ?: 0, + StopLine = stopToken?.line ?: 0, + StopLinePosition = stopToken?.charPositionInLine ?: 0, + ) +} diff --git a/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt b/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt new file mode 100644 index 00000000..de586f88 --- /dev/null +++ b/chapi-ast-swift/src/main/kotlin/chapi/ast/swiftast/SwiftTypeRefBuilder.kt @@ -0,0 +1,222 @@ +package chapi.ast.swiftast + +import chapi.domain.core.CodeTypeRef +import chapi.domain.core.TypeRefKind + +/** + * Builder for creating CodeTypeRef from Swift type strings. + * + * Handles Swift-specific type syntax including: + * - Optional types: String?, Int? + * - Implicitly unwrapped optionals: String!, Int! + * - Array types: [String], [[Int]] + * - Dictionary types: [String: Int] + * - Tuple types: (Int, String) + * - Function types: (Int, String) -> Bool + * - Generic types: Array, Dictionary + * - Protocol composition: Protocol1 & Protocol2 + * - Opaque types: some Protocol + * - Metatypes: Type.Type, Type.Protocol + */ +object SwiftTypeRefBuilder { + + fun build(typeText: String): CodeTypeRef { + val trimmed = typeText.trim() + if (trimmed.isEmpty()) return CodeTypeRef() + + return parseType(trimmed) + } + + private fun parseType(typeText: String): CodeTypeRef { + val trimmed = typeText.trim() + + // Handle optional types (suffix ? or !) + // This correctly handles parenthesized optionals like (Int, String)? and ((Int)->Void)? + if (trimmed.endsWith("?")) { + val baseType = parseType(trimmed.dropLast(1)) + return CodeTypeRef.nullable(baseType) + } + + if (trimmed.endsWith("!")) { + val baseType = parseType(trimmed.dropLast(1)) + return baseType.copy( + raw = trimmed, + nullable = true // Implicitly unwrapped optional is also nullable + ) + } + + // Handle array literal syntax [Type] or dictionary literal syntax [Key: Value] + if (trimmed.startsWith("[") && trimmed.endsWith("]")) { + val inner = trimmed.drop(1).dropLast(1).trim() + val colonIndex = findTopLevelColon(inner) + if (colonIndex >= 0) { + // Dictionary type [Key: Value] + val keyType = parseType(inner.substring(0, colonIndex).trim()) + val valueType = parseType(inner.substring(colonIndex + 1).trim()) + return CodeTypeRef.map(keyType, valueType) + } else { + // Array type [Type] + val elementType = parseType(inner) + return CodeTypeRef.array(elementType) + } + } + + // Handle tuple types (Type1, Type2, ...) + if (trimmed.startsWith("(") && trimmed.endsWith(")") && !trimmed.contains("->")) { + val inner = trimmed.drop(1).dropLast(1).trim() + if (inner.isEmpty()) { + return CodeTypeRef(raw = trimmed, name = "Void", kind = TypeRefKind.TUPLE) + } + val elements = splitByTopLevelComma(inner).map { parseType(it.trim()) } + if (elements.size == 1) { + // Single element in parentheses is just the element type + return elements.first() + } + return CodeTypeRef.tuple(*elements.toTypedArray()) + } + + // Handle function types (Params) -> ReturnType + val arrowIndex = findTopLevelArrow(trimmed) + if (arrowIndex > 0) { + val paramPart = trimmed.substring(0, arrowIndex).trim() + val returnPart = trimmed.substring(arrowIndex + 2).trim() + + val paramTypes = if (paramPart.startsWith("(") && paramPart.endsWith(")")) { + val inner = paramPart.drop(1).dropLast(1).trim() + if (inner.isEmpty()) { + listOf() + } else { + splitByTopLevelComma(inner).map { parseType(it.trim()) } + } + } else { + listOf(parseType(paramPart)) + } + + val returnType = parseType(returnPart) + return CodeTypeRef.function(paramTypes, returnType) + } + + // Handle protocol composition Type1 & Type2 (top-level only) + val compositionParts = splitByTopLevelAmpersand(trimmed) + if (compositionParts.size > 1) { + val types = compositionParts.map { parseType(it) } + return CodeTypeRef.intersection(*types.toTypedArray()) + } + + // Handle opaque types: some Protocol + if (trimmed.startsWith("some ")) { + val protocolType = parseType(trimmed.removePrefix("some ").trim()) + return protocolType.copy(raw = trimmed) + } + + // Handle metatypes: Type.Type or Type.Protocol + if (trimmed.endsWith(".Type") || trimmed.endsWith(".Protocol")) { + val baseName = when { + trimmed.endsWith(".Type") -> trimmed.dropLast(5) + trimmed.endsWith(".Protocol") -> trimmed.dropLast(9) + else -> trimmed + } + return CodeTypeRef( + raw = trimmed, + name = baseName, + kind = TypeRefKind.SIMPLE + ) + } + + // Handle generic types: Type + val genericStart = trimmed.indexOf('<') + if (genericStart > 0 && trimmed.endsWith(">")) { + val baseName = trimmed.substring(0, genericStart) + val argsText = trimmed.substring(genericStart + 1, trimmed.length - 1) + val args = splitByTopLevelComma(argsText).map { parseType(it.trim()) } + return CodeTypeRef.generic(baseName, *args.toTypedArray()) + } + + // Handle inout parameter type + if (trimmed.startsWith("inout ")) { + val baseType = parseType(trimmed.removePrefix("inout ").trim()) + return baseType.copy( + raw = trimmed, + isReference = true + ) + } + + // Simple type + return CodeTypeRef.simple(trimmed) + } + + /** + * Find the index of a top-level colon (not inside brackets or generics) + */ + private fun findTopLevelColon(text: String): Int { + var depth = 0 + for (i in text.indices) { + when (text[i]) { + '[', '(', '<' -> depth++ + ']', ')', '>' -> depth-- + ':' -> if (depth == 0) return i + } + } + return -1 + } + + /** + * Find the index of a top-level arrow (->) not inside brackets or generics + */ + private fun findTopLevelArrow(text: String): Int { + var depth = 0 + var i = 0 + while (i < text.length - 1) { + when (text[i]) { + '[', '(', '<' -> depth++ + ']', ')', '>' -> depth-- + '-' -> if (depth == 0 && text[i + 1] == '>') return i + } + i++ + } + return -1 + } + + /** + * Split by comma at the top level (not inside brackets or generics) + */ + private fun splitByTopLevelComma(text: String): List { + return splitByTopLevelDelimiter(text, ',') + } + + /** + * Split by ampersand at the top level (not inside brackets or generics) + * Used for protocol composition types like "Foo & Qux" + */ + private fun splitByTopLevelAmpersand(text: String): List { + return splitByTopLevelDelimiter(text, '&') + } + + /** + * Split by a delimiter at the top level (not inside brackets or generics) + */ + private fun splitByTopLevelDelimiter(text: String, delimiter: Char): List { + val result = mutableListOf() + var depth = 0 + var start = 0 + + for (i in text.indices) { + when (text[i]) { + '[', '(', '<' -> depth++ + ']', ')', '>' -> depth-- + delimiter -> { + if (depth == 0) { + result.add(text.substring(start, i).trim()) + start = i + 1 + } + } + } + } + + if (start < text.length) { + result.add(text.substring(start).trim()) + } + + return result + } +} diff --git a/chapi-ast-swift/src/test/kotlin/chapi/ast/swiftast/SwiftAnalyserTest.kt b/chapi-ast-swift/src/test/kotlin/chapi/ast/swiftast/SwiftAnalyserTest.kt new file mode 100644 index 00000000..66c3aab1 --- /dev/null +++ b/chapi-ast-swift/src/test/kotlin/chapi/ast/swiftast/SwiftAnalyserTest.kt @@ -0,0 +1,612 @@ +package chapi.ast.swiftast + +import chapi.domain.core.ContainerKind +import chapi.domain.core.DataStructType +import chapi.domain.core.TypeRefKind +import org.junit.jupiter.api.Test +import kotlin.test.assertEquals +import kotlin.test.assertNotNull +import kotlin.test.assertTrue + +class SwiftAnalyserTest { + + @Test + fun shouldParseImportsAndTypes() { + val code = """ + import Foundation + import SwiftUI + + enum State { + case idle + case loading + } + + struct User { + let id: Int + func name() -> String { return "u" } + } + + class Service { + func fetch() -> Int { return 1 } + } + + func topLevel() -> Void { } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "example.swift") + + assertEquals("swift", container.Language) + assertEquals(ContainerKind.SOURCE_FILE, container.Kind) + + assertEquals(2, container.Imports.size) + assertEquals("Foundation", container.Imports[0].Source) + assertEquals("SwiftUI", container.Imports[1].Source) + + val enumDs = container.DataStructures.firstOrNull { it.NodeName == "State" } + assertNotNull(enumDs) + assertEquals(DataStructType.ENUM, enumDs.Type) + + val structDs = container.DataStructures.firstOrNull { it.NodeName == "User" } + assertNotNull(structDs) + assertEquals(DataStructType.STRUCT, structDs.Type) + assertTrue(structDs.Functions.any { it.Name == "name" }) + + val classDs = container.DataStructures.firstOrNull { it.NodeName == "Service" } + assertNotNull(classDs) + assertEquals(DataStructType.CLASS, classDs.Type) + assertTrue(classDs.Functions.any { it.Name == "fetch" }) + + val topLevel = container.TopLevel + assertNotNull(topLevel) + assertTrue(topLevel.Functions.any { it.Name == "topLevel" }) + } + + @Test + fun shouldParseFunctionParameters() { + val code = """ + func greet(name: String, age: Int = 18) -> String { + return "Hello" + } + + func calculate(_ value: Int, with multiplier: Double) -> Double { + return Double(value) * multiplier + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "params.swift") + + val topLevel = container.TopLevel + assertNotNull(topLevel) + assertEquals(2, topLevel.Functions.size) + + val greet = topLevel.Functions.find { it.Name == "greet" } + assertNotNull(greet) + assertEquals(2, greet.Parameters.size) + assertEquals("name", greet.Parameters[0].TypeValue) + assertEquals("String", greet.Parameters[0].TypeType) + assertEquals("age", greet.Parameters[1].TypeValue) + assertEquals("Int", greet.Parameters[1].TypeType) + assertEquals("18", greet.Parameters[1].DefaultValue) + + val calculate = topLevel.Functions.find { it.Name == "calculate" } + assertNotNull(calculate) + assertEquals(2, calculate.Parameters.size) + assertEquals("Double", calculate.ReturnType) + } + + @Test + fun shouldParseClassInheritance() { + val code = """ + class Animal { + var name: String = "" + } + + class Dog: Animal, Hashable, Equatable { + var breed: String = "" + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "inheritance.swift") + + val dog = container.DataStructures.find { it.NodeName == "Dog" } + assertNotNull(dog) + assertEquals(DataStructType.CLASS, dog.Type) + assertEquals("Animal", dog.Extend) + assertTrue(dog.Implements.contains("Hashable")) + assertTrue(dog.Implements.contains("Equatable")) + } + + @Test + fun shouldParseProtocol() { + val code = """ + protocol Drawable { + var color: String { get set } + func draw() -> Void + func resize(width: Int, height: Int) + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "protocol.swift") + + val drawable = container.DataStructures.find { it.NodeName == "Drawable" } + assertNotNull(drawable) + assertEquals(DataStructType.INTERFACE, drawable.Type) + + // Protocol property + val colorField = drawable.Fields.find { it.TypeKey == "color" } + assertNotNull(colorField) + assertEquals("String", colorField.TypeType) + + // Protocol methods + assertTrue(drawable.Functions.any { it.Name == "draw" }) + val resize = drawable.Functions.find { it.Name == "resize" } + assertNotNull(resize) + assertEquals(2, resize.Parameters.size) + } + + @Test + fun shouldParseExtension() { + val code = """ + extension String: CustomStringConvertible { + func trimmed() -> String { + return self.trimmingCharacters(in: .whitespaces) + } + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "extension.swift") + + val stringExt = container.DataStructures.find { it.NodeName == "String" } + assertNotNull(stringExt) + assertTrue(stringExt.Implements.contains("CustomStringConvertible")) + assertTrue(stringExt.Functions.any { it.Name == "trimmed" }) + } + + @Test + fun shouldParseInitializer() { + val code = """ + class Person { + var name: String + var age: Int + + init(name: String, age: Int) { + self.name = name + self.age = age + } + + init?(json: [String: Any]) { + guard let name = json["name"] as? String else { return nil } + self.name = name + self.age = 0 + } + + deinit { + print("Person deallocated") + } + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "init.swift") + + val person = container.DataStructures.find { it.NodeName == "Person" } + assertNotNull(person) + + // Regular init + val regularInit = person.Functions.find { it.Name == "init" && it.Parameters.size == 2 } + assertNotNull(regularInit) + assertTrue(regularInit.IsConstructor) + + // Failable init + val failableInit = person.Functions.find { it.Name == "init?" } + assertNotNull(failableInit) + assertTrue(failableInit.IsConstructor) + + // Deinit + val deinit = person.Functions.find { it.Name == "deinit" } + assertNotNull(deinit) + } + + @Test + fun shouldParseStructWithFields() { + val code = """ + struct Point { + let x: Double + let y: Double + var label: String = "point" + + func distance(to other: Point) -> Double { + return 0.0 + } + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "struct.swift") + + val point = container.DataStructures.find { it.NodeName == "Point" } + assertNotNull(point) + assertEquals(DataStructType.STRUCT, point.Type) + + assertEquals(3, point.Fields.size) + + val xField = point.Fields.find { it.TypeKey == "x" } + assertNotNull(xField) + assertEquals("Double", xField.TypeType) + assertTrue(xField.Modifiers.contains("let")) + + val labelField = point.Fields.find { it.TypeKey == "label" } + assertNotNull(labelField) + assertEquals("\"point\"", labelField.TypeValue) + } + + @Test + fun shouldParseEnumWithCases() { + val code = """ + enum NetworkError: Error { + case timeout + case notFound + case serverError(code: Int) + } + + enum Status: String { + case active = "active" + case inactive = "inactive" + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "enum.swift") + + val networkError = container.DataStructures.find { it.NodeName == "NetworkError" } + assertNotNull(networkError) + assertEquals(DataStructType.ENUM, networkError.Type) + assertTrue(networkError.Implements.contains("Error")) + assertEquals(3, networkError.Fields.size) + + val status = container.DataStructures.find { it.NodeName == "Status" } + assertNotNull(status) + assertEquals("String", status.Extend) + + val activeCase = status.Fields.find { it.TypeKey == "active" } + assertNotNull(activeCase) + assertEquals("\"active\"", activeCase.TypeValue) + } + + @Test + fun shouldParseAttributes() { + val code = """ + @available(iOS 13.0, *) + @MainActor + class ViewController { + @Published var count: Int = 0 + + @discardableResult + func increment() -> Int { + count += 1 + return count + } + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "attributes.swift") + + val vc = container.DataStructures.find { it.NodeName == "ViewController" } + assertNotNull(vc) + assertTrue(vc.Annotations.any { it.Name == "available" }) + assertTrue(vc.Annotations.any { it.Name == "MainActor" }) + + val countField = vc.Fields.find { it.TypeKey == "count" } + assertNotNull(countField) + assertTrue(countField.Annotations.any { it.Name == "Published" }) + + val increment = vc.Functions.find { it.Name == "increment" } + assertNotNull(increment) + assertTrue(increment.Annotations.any { it.Name == "discardableResult" }) + } + + @Test + fun shouldParseAccessModifiers() { + val code = """ + public class APIClient { + private var token: String = "" + internal var baseURL: String = "" + public private(set) var lastResponse: String = "" + + public func request() -> Void { } + private func authenticate() -> Bool { return true } + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "modifiers.swift") + + val client = container.DataStructures.find { it.NodeName == "APIClient" } + assertNotNull(client) + // Modifiers are stored as annotations for CodeDataStruct + assertTrue(client.Annotations.any { it.Name == "public" }) + + val tokenField = client.Fields.find { it.TypeKey == "token" } + assertNotNull(tokenField) + assertTrue(tokenField.Modifiers.contains("private")) + + val lastResponseField = client.Fields.find { it.TypeKey == "lastResponse" } + assertNotNull(lastResponseField) + assertTrue(lastResponseField.Modifiers.any { it.contains("public") }) + + val requestFunc = client.Functions.find { it.Name == "request" } + assertNotNull(requestFunc) + assertTrue(requestFunc.Modifiers.contains("public")) + } + + @Test + fun shouldParseNestedTypes() { + val code = """ + struct Outer { + struct Inner { + var value: Int = 0 + } + + enum Status { + case active + case inactive + } + + var inner: Inner = Inner() + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "nested.swift") + + val outer = container.DataStructures.find { it.NodeName == "Outer" } + assertNotNull(outer) + assertEquals(2, outer.InnerStructures.size) + + val inner = outer.InnerStructures.find { it.NodeName == "Inner" } + assertNotNull(inner) + assertEquals(DataStructType.STRUCT, inner.Type) + + val status = outer.InnerStructures.find { it.NodeName == "Status" } + assertNotNull(status) + assertEquals(DataStructType.ENUM, status.Type) + } + + @Test + fun shouldParseThrowingFunctions() { + val code = """ + func loadData() throws -> Data { + throw NSError() + } + + func transform(_ value: T) rethrows -> T { + return value + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "throws.swift") + + val topLevel = container.TopLevel + assertNotNull(topLevel) + + val loadData = topLevel.Functions.find { it.Name == "loadData" } + assertNotNull(loadData) + assertTrue(loadData.Modifiers.contains("throws")) + + val transform = topLevel.Functions.find { it.Name == "transform" } + assertNotNull(transform) + assertTrue(transform.Modifiers.contains("rethrows")) + } +} + +// ==================== Swift 6 Features Tests ==================== + +class Swift6FeaturesTest { + + @Test + fun shouldParseTypedThrows() { + val code = """ + enum NetworkError: Error { + case timeout + case notFound + } + + func fetchData() throws(NetworkError) -> Data { + throw .timeout + } + + func process() throws -> String { + return "" + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "typed_throws.swift") + + val topLevel = container.TopLevel + assertNotNull(topLevel) + + val fetchData = topLevel.Functions.find { it.Name == "fetchData" } + assertNotNull(fetchData) + assertTrue(fetchData.Modifiers.any { it.contains("throws(NetworkError)") }) + + val process = topLevel.Functions.find { it.Name == "process" } + assertNotNull(process) + assertTrue(process.Modifiers.contains("throws")) + } + + @Test + fun shouldParseAsyncFunctions() { + val code = """ + func fetchUser() async -> User { + return User() + } + + func fetchWithError() async throws -> Data { + return Data() + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "async.swift") + + val topLevel = container.TopLevel + assertNotNull(topLevel) + + val fetchUser = topLevel.Functions.find { it.Name == "fetchUser" } + assertNotNull(fetchUser) + assertTrue(fetchUser.Modifiers.contains("async")) + + val fetchWithError = topLevel.Functions.find { it.Name == "fetchWithError" } + assertNotNull(fetchWithError) + assertTrue(fetchWithError.Modifiers.contains("async")) + assertTrue(fetchWithError.Modifiers.contains("throws")) + } + + @Test + fun shouldParseActorDeclaration() { + val code = """ + actor BankAccount { + var balance: Double = 0.0 + + func deposit(amount: Double) { + balance += amount + } + } + + distributed actor GamePlayer { + var score: Int = 0 + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "actor.swift") + + val bankAccount = container.DataStructures.find { it.NodeName == "BankAccount" } + assertNotNull(bankAccount) + assertTrue(bankAccount.Functions.any { it.Name == "deposit" }) + assertTrue(bankAccount.Fields.any { it.TypeKey == "balance" }) + + val gamePlayer = container.DataStructures.find { it.NodeName == "GamePlayer" } + assertNotNull(gamePlayer) + assertTrue(gamePlayer.Annotations.any { it.Name == "distributed" }) + } + + @Test + fun shouldParseOwnershipModifiers() { + val code = """ + struct FileHandle: ~Copyable { + func read() -> Data { return Data() } + } + + func process(consuming handle: FileHandle) { + // handle is consumed + } + + func inspect(borrowing handle: FileHandle) { + // handle is borrowed + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "ownership.swift") + + val fileHandle = container.DataStructures.find { it.NodeName == "FileHandle" } + assertNotNull(fileHandle) + // Check for ~Copyable in inheritance + assertTrue(fileHandle.Implements.any { it.contains("Copyable") }) + + val topLevel = container.TopLevel + assertNotNull(topLevel) + + val processFunc = topLevel.Functions.find { it.Name == "process" } + assertNotNull(processFunc) + val consumingParam = processFunc.Parameters.find { it.TypeValue == "handle" } + assertNotNull(consumingParam) + assertTrue(consumingParam.Modifiers.contains("consuming")) + + val inspectFunc = topLevel.Functions.find { it.Name == "inspect" } + assertNotNull(inspectFunc) + val borrowingParam = inspectFunc.Parameters.find { it.TypeValue == "handle" } + assertNotNull(borrowingParam) + assertTrue(borrowingParam.Modifiers.contains("borrowing")) + } + + @Test + fun shouldParseNonisolatedModifier() { + val code = """ + actor DataStore { + nonisolated let id: String = "store" + + nonisolated func description() -> String { + return id + } + } + """.trimIndent() + + val container = SwiftAnalyser().analysis(code, "nonisolated.swift") + + val dataStore = container.DataStructures.find { it.NodeName == "DataStore" } + assertNotNull(dataStore) + + val descFunc = dataStore.Functions.find { it.Name == "description" } + assertNotNull(descFunc) + assertTrue(descFunc.Modifiers.contains("nonisolated")) + } +} + +class SwiftTypeRefBuilderTest { + + @Test + fun shouldParseSimpleType() { + val ref = SwiftTypeRefBuilder.build("String") + assertEquals("String", ref.name) + assertEquals(TypeRefKind.SIMPLE, ref.kind) + } + + @Test + fun shouldParseOptionalType() { + val ref = SwiftTypeRefBuilder.build("String?") + assertEquals("String", ref.name) + assertTrue(ref.nullable) + } + + @Test + fun shouldParseArrayType() { + val ref = SwiftTypeRefBuilder.build("[Int]") + assertEquals(TypeRefKind.ARRAY, ref.kind) + assertEquals("Int", ref.valueType?.name) + } + + @Test + fun shouldParseDictionaryType() { + val ref = SwiftTypeRefBuilder.build("[String: Int]") + assertEquals(TypeRefKind.MAP, ref.kind) + assertEquals("String", ref.keyType?.name) + assertEquals("Int", ref.valueType?.name) + } + + @Test + fun shouldParseGenericType() { + val ref = SwiftTypeRefBuilder.build("Array") + assertEquals(TypeRefKind.GENERIC, ref.kind) + assertEquals("Array", ref.name) + assertEquals(1, ref.args.size) + assertEquals("String", ref.args[0].name) + } + + @Test + fun shouldParseTupleType() { + val ref = SwiftTypeRefBuilder.build("(Int, String)") + assertEquals(TypeRefKind.TUPLE, ref.kind) + assertEquals(2, ref.tupleElements.size) + assertEquals("Int", ref.tupleElements[0].name) + assertEquals("String", ref.tupleElements[1].name) + } + + @Test + fun shouldParseFunctionType() { + val ref = SwiftTypeRefBuilder.build("(Int, String) -> Bool") + assertEquals(TypeRefKind.FUNCTION, ref.kind) + assertEquals(2, ref.parameterTypes.size) + assertEquals("Bool", ref.returnType?.name) + } + + @Test + fun shouldParseProtocolComposition() { + val ref = SwiftTypeRefBuilder.build("Hashable & Equatable") + assertEquals(TypeRefKind.INTERSECTION, ref.kind) + assertEquals(2, ref.intersection.size) + } +} diff --git a/settings.gradle.kts b/settings.gradle.kts index beff6727..eb5b77e4 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -22,6 +22,7 @@ include( ":chapi-ast-csharp", ":chapi-ast-c", ":chapi-ast-cpp", + ":chapi-ast-swift", // others ":chapi-parser-toml",