diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml index dbb35aa39d..862e99911f 100644 --- a/pmd-dist/pom.xml +++ b/pmd-dist/pom.xml @@ -128,6 +128,11 @@ pmd-ruby ${project.version} + + net.sourceforge.pmd + pmd-swift + ${project.version} + net.sourceforge.pmd pmd-vm diff --git a/pmd-swift/pom.xml b/pmd-swift/pom.xml new file mode 100644 index 0000000000..65d9562637 --- /dev/null +++ b/pmd-swift/pom.xml @@ -0,0 +1,72 @@ + + + 4.0.0 + pmd-swift + PMD Swift + + + net.sourceforge.pmd + pmd + 5.3.7-SNAPSHOT + + + + 4.5.2-1 + ${basedir}/../pmd-core + + + + + + org.antlr + antlr4-maven-plugin + ${antlr.version} + + UTF-8 + + + + antlr + + antlr4 + + + + + + + maven-resources-plugin + + false + + ${*} + + + + + + + + + org.antlr + antlr4-runtime + ${antlr.version} + + + + net.sourceforge.pmd + pmd-core + + + + junit + junit + test + + + net.sourceforge.pmd + pmd-test + test + + + diff --git a/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 b/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 new file mode 100644 index 0000000000..3a8fef8af5 --- /dev/null +++ b/pmd-swift/src/main/antlr4/net/sourceforge/pmd/lang/swift/antlr4/Swift.g4 @@ -0,0 +1,999 @@ +// Downloaded on 2016/03/02 from https://github.com/sleekbyte/tailor/blob/master/src/main/antlr/com/sleekbyte/tailor/antlr/Swift.g4 + +/* + * [The "BSD license"] + * Copyright (c) 2014 Terence Parr + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + * Converted from Apple's doc, http://tinyurl.com/n8rkoue, to ANTLR's + * meta-language. + */ +grammar Swift; + +topLevel : (statement | expression)* EOF ; + +// Statements + +// GRAMMAR OF A STATEMENT + +statement + : declaration ';'? + | loopStatement ';'? + | branchStatement ';'? + | labeledStatement + | controlTransferStatement ';'? + | deferStatement ';' ? + | doStatement ':'? + | compilerControlStatement ';'? + | expression ';'? // Keep expression last to handle ambiguity + ; + +statements : statement+ ; + +// GRAMMAR OF A LOOP STATEMENT + +loopStatement : forStatement + | forInStatement + | whileStatement + | repeatWhileStatement + ; + +// GRAMMAR OF A FOR STATEMENT + +// Swift Language Reference has expression? instead of expressionList? +forStatement + : 'for' forInit? ';' expression? ';' expressionList? codeBlock + | 'for' '(' forInit?';' expression? ';' expressionList? ')' codeBlock + ; + +forInit : variableDeclaration | expressionList ; + +// GRAMMAR OF A FOR_IN STATEMENT + +forInStatement : 'for' 'case'? pattern 'in' expression whereClause? codeBlock ; + +// GRAMMAR OF A WHILE STATEMENT + +whileStatement : 'while' conditionClause codeBlock ; + +// GRAMMAR OF A REPEAT WHILE STATEMENT + +repeatWhileStatement: 'repeat' codeBlock 'while' conditionClause ; + +// GRAMMAR OF A BRANCH STATEMENT + +branchStatement : ifStatement | guardStatement | switchStatement ; + +// GRAMMAR OF AN IF STATEMENT + +ifStatement : 'if' conditionClause codeBlock elseClause? ; +elseClause : 'else' codeBlock | 'else' ifStatement ; + +// GRAMMAR OF A GUARD STATEMENT + +guardStatement : 'guard' conditionClause 'else' codeBlock ; + +// GRAMMAR OF A SWITCH STATEMENT + +switchStatement : 'switch' expression '{' switchCases? '}' ; +switchCases : switchCase switchCases? ; +switchCase : caseLabel statements | defaultLabel statements | caseLabel ';' | defaultLabel ';' ; +caseLabel : 'case' caseItemList ':' ; +caseItemList : caseItem (',' caseItem)* ; +caseItem: pattern whereClause? ; +defaultLabel : 'default' ':' ; + +// GRAMMAR OF A LABELED STATEMENT + +labeledStatement : statementLabel loopStatement | statementLabel switchStatement ; +statementLabel : labelName ':' ; +labelName : identifier ; + +// GRAMMAR OF A CONTROL TRANSFER STATEMENT + +controlTransferStatement : breakStatement + | continueStatement + | fallthroughStatement + | returnStatement + | throwStatement + ; + +// GRAMMAR OF A BREAK STATEMENT + +breakStatement : 'break' labelName? ; + +// GRAMMAR OF A CONTINUE STATEMENT + +continueStatement : 'continue' labelName? ; + +// GRAMMAR OF A FALLTHROUGH STATEMENT + +fallthroughStatement : 'fallthrough' ; + +// GRAMMAR OF A RETURN STATEMENT + +returnStatement : 'return' expression? ; + +// GRAMMAR OF A THROW STATEMENT + +throwStatement : 'throw' expression ; + +// GRAMMAR OF A DEFER STATEMENT + +deferStatement: 'defer' codeBlock ; + +// GRAMMAR OF A DO STATEMENT + +doStatement: 'do' codeBlock catchClauses? ; +catchClauses: catchClause catchClauses? ; +catchClause: 'catch' pattern? whereClause? codeBlock ; + +// GRAMMAR FOR CONDITION CLAUSES + +conditionClause : expression + | expression ',' conditionList + | conditionList + | availabilityCondition ',' expression + ; + +conditionList : condition (',' condition)* ; +condition: availabilityCondition | caseCondition | optionalBindingCondition ; +caseCondition: 'case' pattern initializer whereClause? ; +// optionalBindingCondition is incorrect in the Swift Language Reference (missing a ',') +optionalBindingCondition: optionalBindingHead (',' optionalBindingContinuationList)? whereClause? ; +optionalBindingHead: 'let' pattern initializer | 'var' pattern initializer ; +optionalBindingContinuationList: optionalBindingContinuation (',' optionalBindingContinuation)* ; +optionalBindingContinuation: optionalBindingHead | pattern initializer ; + +whereClause: 'where' whereExpression ; +whereExpression: expression ; + +// GRAMMAR OF AN AVAILABILITY CONDITION + +availabilityCondition: '#available' '(' availabilityArguments ')' ; +availabilityArguments: availabilityArgument (',' availabilityArguments)* ; +availabilityArgument: platformName platformVersion | '*' ; +platformName: 'iOS' | 'iOSApplicationExtension' | 'OSX' | 'OSXApplicationExtension' | 'watchOS' + | 'watchOSApplicationExtension' | 'tvOS' | 'tvOSApplicationExtension' ; +platformVersion: VersionLiteral | DecimalLiteral | FloatingPointLiteral ; // TODO: Find a way to make this only VersionLiteral + +// Generic Parameters and Arguments + +// GRAMMAR OF A GENERIC PARAMETER CLAUSE + +genericParameterClause : '<' genericParameterList requirementClause? '>' ; +genericParameterList : genericParameter (',' genericParameter)* ; +genericParameter : typeName | typeName ':' typeIdentifier | typeName ':' protocolCompositionType ; +requirementClause : 'where' requirementList ; +requirementList : requirement (',' requirement)* ; +requirement : conformanceRequirement | sameTypeRequirement ; +conformanceRequirement : typeIdentifier ':' typeIdentifier | typeIdentifier ':' protocolCompositionType ; +sameTypeRequirement : typeIdentifier '==' sType ; + +// GRAMMAR OF A GENERIC ARGUMENT CLAUSE + +genericArgumentClause : '<' genericArgumentList '>' ; +genericArgumentList : genericArgument (',' genericArgument)* ; +genericArgument : sType ; + +// Declarations + +// GRAMMAR OF A DECLARATION + +declaration + : importDeclaration ';'? + | constantDeclaration ';'? + | variableDeclaration ';'? + | typealiasDeclaration ';'? + | functionDeclaration ';'? + | enumDeclaration ';'? + | structDeclaration ';'? + | classDeclaration ';'? + | protocolDeclaration ';'? + | initializerDeclaration ';'? + | deinitializerDeclaration ';'? + | extensionDeclaration ';'? + | subscriptDeclaration ';'? + | operatorDeclaration ';'? + // compiler-control-statement not in Swift Language Reference + | compilerControlStatement ';'? + ; + +declarations : declaration declarations? ; +declarationModifiers : declarationModifier declarationModifiers? ; +declarationModifier : 'class' | 'convenience' | 'dynamic' | 'final' | 'infix' + | 'lazy' | 'mutating' | 'nonmutating' | 'optional' | 'override' | 'postfix' + | 'prefix' | 'required' | 'static' | 'unowned' | 'unowned' '(' 'safe' ')' + | 'unowned' '(' 'unsafe' ')' | 'weak' + | accessLevelModifier ; + +accessLevelModifier : 'internal' | 'internal' '(' 'set' ')' + | 'private' | 'private' '(' 'set' ')' + | 'public' | 'public' '(' 'set' ')' ; +accessLevelModifiers : accessLevelModifier accessLevelModifiers? ; + +// GRAMMAR OF A CODE BLOCK + +codeBlock : '{' statements? '}' ; + +// GRAMMAR OF AN IMPORT DECLARATION + +importDeclaration : attributes? 'import' importKind? importPath ; +importKind : 'typealias' | 'struct' | 'class' | 'enum' | 'protocol' | 'var' | 'func' ; +importPath : importPathIdentifier | importPathIdentifier '.' importPath ; +importPathIdentifier : identifier | operator ; + +// GRAMMAR OF A CONSTANT DECLARATION + +constantDeclaration : attributes? declarationModifiers? 'let' patternInitializerList ; +patternInitializerList : patternInitializer (',' patternInitializer)* ; +patternInitializer : pattern initializer? ; +initializer : '=' expression ; + +// GRAMMAR OF A VARIABLE DECLARATION + +variableDeclaration + : variableDeclarationHead variableName typeAnnotation getterSetterBlock + | variableDeclarationHead variableName typeAnnotation getterSetterKeywordBlock + | variableDeclarationHead variableName initializer willSetDidSetBlock + | variableDeclarationHead variableName typeAnnotation initializer? willSetDidSetBlock + // keep this below getter and setter rules for ambiguity reasons + | variableDeclarationHead variableName typeAnnotation codeBlock + | variableDeclarationHead patternInitializerList + ; + +variableDeclarationHead : attributes? declarationModifiers? 'var' ; +variableName : identifier ; +getterSetterBlock : '{' getterClause setterClause?'}' | '{' setterClause getterClause '}' ; +getterClause : attributes? 'get' codeBlock ; +setterClause : attributes? 'set' setterName? codeBlock ; +setterName : '(' identifier ')' ; +getterSetterKeywordBlock : '{' getterKeywordClause setterKeywordClause?'}' | '{' setterKeywordClause getterKeywordClause '}' ; +getterKeywordClause : attributes? 'get' ; +setterKeywordClause : attributes? 'set' ; +willSetDidSetBlock : '{' willSetClause didSetClause?'}' | '{' didSetClause willSetClause? '}' ; +willSetClause : attributes? 'willSet' setterName? codeBlock ; +didSetClause : attributes? 'didSet' setterName? codeBlock ; + +// GRAMMAR OF A TYPE ALIAS DECLARATION + +typealiasDeclaration : typealiasHead typealiasAssignment ; +typealiasHead : attributes? accessLevelModifier? 'typealias' typealiasName ; +typealiasName : identifier ; +typealiasAssignment : '=' sType ; + +// GRAMMAR OF A FUNCTION DECLARATION + +/* HACK: functionBody? is intentionally not used to force the parser to try and match a functionBody first + * This can be removed once we figure out how to enforce that statements are either separated by semi colons or new line characters + */ +functionDeclaration : functionHead functionName genericParameterClause? functionSignature functionBody + | functionHead functionName genericParameterClause? functionSignature + ; + +functionHead : attributes? declarationModifiers? 'func' ; +functionName : identifier | operator ; +// rethrows is not marked as optional in the Swift Language Reference +functionSignature : parameterClauses ('throws' | 'rethrows')? functionResult? ; +functionResult : '->' attributes? sType ; +functionBody : codeBlock ; +parameterClauses : parameterClause parameterClauses? ; +parameterClause : '(' ')' | '(' parameterList '...'? ')' ; +parameterList : parameter (',' parameter)* ; +// Parameters don't have attributes in the Swift Language Reference +parameter : attributes? 'inout'? 'let'? '#'? externalParameterName? localParameterName typeAnnotation? defaultArgumentClause? + | 'inout'? 'var' '#'? externalParameterName? localParameterName typeAnnotation? defaultArgumentClause? + | attributes? sType + | externalParameterName? localParameterName typeAnnotation '...' + ; +externalParameterName : identifier | '_' ; +localParameterName : identifier | '_' ; +defaultArgumentClause : '=' expression ; + +// GRAMMAR OF AN ENUMERATION DECLARATION + +enumDeclaration : attributes? accessLevelModifier? enumDef ; +enumDef: unionStyleEnum | rawValueStyleEnum ; +unionStyleEnum : 'indirect'? 'enum' enumName genericParameterClause? typeInheritanceClause? '{' unionStyleEnumMembers?'}' ; +unionStyleEnumMembers : unionStyleEnumMember unionStyleEnumMembers? ; +unionStyleEnumMember : declaration | unionStyleEnumCaseClause ';'? ; +unionStyleEnumCaseClause : attributes? 'indirect'? 'case' unionStyleEnumCaseList ; +unionStyleEnumCaseList : unionStyleEnumCase (',' unionStyleEnumCase)* ; +unionStyleEnumCase : enumCaseName tupleType? ; +enumName : identifier ; +enumCaseName : identifier ; +// typeInheritanceClause is not optional in the Swift Language Reference +rawValueStyleEnum : 'enum' enumName genericParameterClause? typeInheritanceClause? '{' rawValueStyleEnumMembers?'}' ; +rawValueStyleEnumMembers : rawValueStyleEnumMember rawValueStyleEnumMembers? ; +rawValueStyleEnumMember : declaration | rawValueStyleEnumCaseClause ; +rawValueStyleEnumCaseClause : attributes? 'case' rawValueStyleEnumCaseList ; +rawValueStyleEnumCaseList : rawValueStyleEnumCase (',' rawValueStyleEnumCase)* ; +rawValueStyleEnumCase : enumCaseName rawValueAssignment? ; +rawValueAssignment : '=' literal ; + +// GRAMMAR OF A STRUCTURE DECLARATION + +structDeclaration : attributes? accessLevelModifier? 'struct' structName genericParameterClause? typeInheritanceClause? structBody ; +structName : identifier ; +structBody : '{' declarations?'}' ; + +// GRAMMAR OF A CLASS DECLARATION + +// declarationModifier missing in Swift Language Reference +classDeclaration : attributes? declarationModifier* 'class' className genericParameterClause? typeInheritanceClause? classBody ; +className : identifier ; +classBody : '{' declarations? '}' ; + +// GRAMMAR OF A PROTOCOL DECLARATION + +protocolDeclaration : attributes? accessLevelModifier? 'protocol' protocolName typeInheritanceClause? protocolBody ; +protocolName : identifier ; +protocolBody : '{' protocolMemberDeclarations?'}' ; +protocolMemberDeclaration : protocolPropertyDeclaration ';'? + | protocolMethodDeclaration ';'? + | protocolInitializerDeclaration ';'? + | protocolSubscriptDeclaration ';'? + | protocolAssociatedTypeDeclaration ';'? + ; +protocolMemberDeclarations : protocolMemberDeclaration protocolMemberDeclarations? ; + +// GRAMMAR OF A PROTOCOL PROPERTY DECLARATION + +protocolPropertyDeclaration : variableDeclarationHead variableName typeAnnotation getterSetterKeywordBlock ; + +// GRAMMAR OF A PROTOCOL METHOD DECLARATION + +protocolMethodDeclaration : functionHead functionName genericParameterClause? functionSignature ; + +// GRAMMAR OF A PROTOCOL INITIALIZER DECLARATION + +protocolInitializerDeclaration : initializerHead genericParameterClause? parameterClause ('throws' | 'rethrows')? ; + +// GRAMMAR OF A PROTOCOL SUBSCRIPT DECLARATION + +protocolSubscriptDeclaration : subscriptHead subscriptResult getterSetterKeywordBlock ; + +// GRAMMAR OF A PROTOCOL ASSOCIATED TYPE DECLARATION + +protocolAssociatedTypeDeclaration : typealiasHead typeInheritanceClause? typealiasAssignment? ; + +// GRAMMAR OF AN INITIALIZER DECLARATION + +initializerDeclaration : initializerHead genericParameterClause? parameterClause ('throws' | 'rethrows')? initializerBody ; +initializerHead : attributes? declarationModifiers? 'init' ('?' | '!')? ; +initializerBody : codeBlock ; + +// GRAMMAR OF A DEINITIALIZER DECLARATION + +deinitializerDeclaration : attributes? 'deinit' codeBlock ; + +// GRAMMAR OF AN EXTENSION DECLARATION + +// attributes, requirementClause missing in the Swift Language Reference +extensionDeclaration : attributes? accessLevelModifier? 'extension' typeIdentifier requirementClause? typeInheritanceClause? extensionBody ; +extensionBody : '{' declarations?'}' ; + +// GRAMMAR OF A SUBSCRIPT DECLARATION + +subscriptDeclaration : subscriptHead subscriptResult getterSetterBlock + | subscriptHead subscriptResult getterSetterKeywordBlock + // most general form of subscript declaration; should be kept at the bottom. + | subscriptHead subscriptResult codeBlock + ; +subscriptHead : attributes? declarationModifiers? 'subscript' parameterClause ; +subscriptResult : '->' attributes? sType ; + +// GRAMMAR OF AN OPERATOR DECLARATION + +operatorDeclaration : prefixOperatorDeclaration | postfixOperatorDeclaration | infixOperatorDeclaration ; +prefixOperatorDeclaration : 'prefix' 'operator' operator '{' '}' ; +postfixOperatorDeclaration : 'postfix' 'operator' operator '{' '}' ; +infixOperatorDeclaration : 'infix' 'operator' operator '{' infixOperatorAttributes '}' ; +// Order of clauses can be reversed, not indicated in Swift Language Reference +infixOperatorAttributes : precedenceClause? associativityClause? | associativityClause? precedenceClause? ; +precedenceClause : 'precedence' precedenceLevel ; +precedenceLevel : integerLiteral ; +associativityClause : 'associativity' associativity ; +associativity : 'left' | 'right' | 'none' ; + +// Patterns + + +// GRAMMAR OF A PATTERN + +pattern + : wildcardPattern typeAnnotation? + | identifierPattern typeAnnotation? + | valueBindingPattern + | tuplePattern typeAnnotation? + | enumCasePattern + | 'is' sType + | pattern 'as' sType + | expressionPattern + ; + +// GRAMMAR OF A WILDCARD PATTERN + +wildcardPattern : '_' ; + +// GRAMMAR OF AN IDENTIFIER PATTERN + +identifierPattern : identifier ; + +// GRAMMAR OF A VALUE_BINDING PATTERN + +valueBindingPattern : 'var' pattern | 'let' pattern ; + +// GRAMMAR OF A TUPLE PATTERN + +tuplePattern : '(' tuplePatternElementList? ')' ; +tuplePatternElementList + : tuplePatternElement (',' tuplePatternElement)* + ; +tuplePatternElement : pattern ; + +// GRAMMAR OF AN ENUMERATION CASE PATTERN + +// Swift Language Reference has '.' as mandatory +enumCasePattern : typeIdentifier? '.'? enumCaseName tuplePattern? ; + +// GRAMMAR OF A TYPE CASTING PATTERN + +typeCastingPattern : isPattern | asPattern ; +isPattern : 'is' sType ; +asPattern : pattern 'as' sType ; + +// GRAMMAR OF AN EXPRESSION PATTERN + +expressionPattern : expression ; + +// Attributes + +// GRAMMAR OF AN ATTRIBUTE + +attribute : '@' attributeName attributeArgumentClause? ; +attributeName : identifier ; +attributeArgumentClause : '(' balancedTokens? ')' ; +attributes : attribute+ ; +// Swift Language Reference does not have ',' +balancedTokens : balancedToken balancedTokens? ; +balancedToken + : '(' balancedTokens? ')' + | '[' balancedTokens? ']' + | '{' balancedTokens? '}' + | identifier | expression | contextSensitiveKeyword | literal | operator + // | Any punctuation except ( , ')' , '[' , ']' , { , or } + // Punctuation is very ambiguous, interpreting punctuation as defined in www.thepunctuationguide.com + | ':' | ';' | ',' | '!' | '<' | '>' | '-' | '\'' | '/' | '...' | '"' + ; + +// Expressions + +// GRAMMAR OF AN EXPRESSION + +expressionList : expression (',' expression)* ; + +expression : tryOperator? prefixExpression binaryExpression* ; + +prefixExpression + : prefixOperator? postfixExpression ';'? + | inOutExpression + ; + +/* +expression + : prefixOperator expression + | inOutExpression + | primaryExpression + | expression binaryOperator expression + | expression assignmentOperator expression + | expression conditionalOperator expression + | expression typeCastingOperator + | expression postfixOperator + | expression parenthesizedExpression trailingClosure? + | expression '.' 'init' + | expression '.' DecimalLiteral + | expression '.' identifier genericArgumentClause? + | expression '.' 'self' + | expression '.' 'dynamicType' + | expression '[' expressionList ']' + | expression '!' + | expression '?' + ; +*/ + +// GRAMMAR OF A PREFIX EXPRESSION + +inOutExpression : '&' identifier ; + +// GRAMMAR OF A TRY EXPRESSION + +tryOperator : 'try' ('?' | '!')? ; + +// GRAMMAR OF A BINARY EXPRESSION + +binaryExpression + : binaryOperator prefixExpression + | assignmentOperator tryOperator? prefixExpression + | conditionalOperator tryOperator? prefixExpression + | typeCastingOperator + ; + +// GRAMMAR OF AN ASSIGNMENT OPERATOR + +assignmentOperator : '=' ; + +// GRAMMAR OF A CONDITIONAL OPERATOR + +conditionalOperator : '?' tryOperator? expression ':' ; + +// GRAMMAR OF A TYPE_CASTING OPERATOR + +typeCastingOperator + : 'is' sType + | 'as' '?' sType + | 'as' sType + | 'as' '!' sType + ; + +// GRAMMAR OF A PRIMARY EXPRESSION + +primaryExpression + : (identifier | operator) genericArgumentClause? // operator not mentioned in the Swift Language Reference + | literalExpression + | selfExpression + | superclassExpression + | closureExpression + | parenthesizedExpression + | implicitMemberExpression +// | implicit_member_expression disallow as ambig with explicit member expr in postfix_expression + | wildcardExpression + ; + +// GRAMMAR OF A LITERAL EXPRESSION + +literalExpression + : literal + | arrayLiteral + | dictionaryLiteral + | '__FILE__' | '__LINE__' | '__COLUMN__' | '__FUNCTION__' + ; + +arrayLiteral : '[' arrayLiteralItems? ']' ; +arrayLiteralItems : arrayLiteralItem (',' arrayLiteralItem)* ','? ; +arrayLiteralItem : expression ; +dictionaryLiteral : '[' dictionaryLiteralItems ']' | '[' ':' ']' ; +dictionaryLiteralItems : dictionaryLiteralItem (',' dictionaryLiteralItem)* ','? ; +dictionaryLiteralItem : expression ':' expression ; + +// GRAMMAR OF A SELF EXPRESSION + +selfExpression + : 'self' + | 'self' '.' identifier + | 'self' '[' expressionList ']' + | 'self' '.' 'init' + ; + +// GRAMMAR OF A SUPERCLASS EXPRESSION + +superclassExpression + : superclassMethodExpression + | superclassSubscriptExpression + | superclassInitializerExpression + ; + +superclassMethodExpression : 'super' '.' identifier ; +superclassSubscriptExpression : 'super' '[' expressionList ']' ; +superclassInitializerExpression : 'super' '.' 'init' ; + +// GRAMMAR OF A CLOSURE EXPRESSION + +// Statements are not optional in the Swift Language Reference +closureExpression : '{' closureSignature? statements? '}' ; +closureSignature + : parameterClause functionResult? 'in' + | identifierList functionResult? 'in' + | captureList parameterClause functionResult? 'in' + | captureList identifierList functionResult? 'in' + | captureList 'in' + ; + +captureList : '[' captureListItems ']' ; +captureListItems: captureListItem (',' captureListItem)? ; +captureListItem: captureSpecifier? expression ; +captureSpecifier : 'weak' | 'unowned' | 'unowned(safe)' | 'unowned(unsafe)' ; + +// GRAMMAR OF A IMPLICIT MEMBER EXPRESSION + +implicitMemberExpression : '.' identifier ; + +// GRAMMAR OF A PARENTHESIZED EXPRESSION + +parenthesizedExpression : '(' expressionElementList? ')' ; +expressionElementList : expressionElement (',' expressionElement)* ; +expressionElement : expression | identifier ':' expression ; + +// GRAMMAR OF A WILDCARD EXPRESSION + +wildcardExpression : '_' ; + +// GRAMMAR OF A POSTFIX EXPRESSION + +postfixExpression + : primaryExpression # primary + | postfixExpression postfixOperator # postfixOperation + // Function call with closure expression should always be above a lone parenthesized expression to reduce ambiguity + | postfixExpression parenthesizedExpression? closureExpression # functionCallWithClosureExpression + | postfixExpression parenthesizedExpression # functionCallExpression + | postfixExpression '.' 'init' # initializerExpression + // TODO: don't allow '_' here in DecimalLiteral: + | postfixExpression '.' DecimalLiteral # explicitMemberExpression1 + | postfixExpression '.' identifier genericArgumentClause? # explicitMemberExpression2 + | postfixExpression '.' 'self' # postfixSelfExpression + | postfixExpression '.' 'dynamicType' # dynamicTypeExpression + | postfixExpression '[' expressionList ']' # subscriptExpression + | postfixExpression '!' # forcedValueExpression + | postfixExpression '?' # optionalChainingExpression + ; + +// GRAMMAR OF A FUNCTION CALL EXPRESSION + +/* +functionCallExpression + : postfixExpression parenthesizedExpression + : postfixExpression parenthesizedExpression? trailingClosure + ; + */ + +//trailing_closure : closure_expression ; + +//initializer_expression : postfix_expression '.' 'init' ; + +/*explicitMemberExpression + : postfixExpression '.' DecimalLiteral // TODO: don't allow '_' here in DecimalLiteral + | postfixExpression '.' identifier genericArgumentClause? + ; + */ + +//postfix_self_expression : postfix_expression '.' 'self' ; + +// GRAMMAR OF A DYNAMIC TYPE EXPRESSION + +//dynamic_type_expression : postfix_expression '.' 'dynamicType' ; + +// GRAMMAR OF A SUBSCRIPT EXPRESSION + +//subscript_expression : postfix_expression '[' expression_list ']' ; + +// GRAMMAR OF A FORCED_VALUE EXPRESSION + +//forced_value_expression : postfix_expression '!' ; + +// GRAMMAR OF AN OPTIONAL_CHAINING EXPRESSION + +//optional_chaining_expression : postfix_expression '?' ; + +// GRAMMAR OF OPERATORS + +// split the operators out into the individual tokens as some of those tokens +// are also referenced individually. For example, type signatures use +// <...>. + +operatorHead: '=' | '<' | '>' | '!' | '*' | '&' | '==' | '?' | '-' | '&&' | '||' | '/' | OperatorHead; +operatorCharacter: operatorHead | OperatorCharacter; + +operator: operatorHead operatorCharacter* + | '..' (operatorCharacter)* + | '...' + ; + +// WHITESPACE scariness: + +/* http://tinyurl.com/oalzfus +"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). For the +purposes of these rules, the characters (, [, and { before an +operator, the characters ), ], and } after an operator, and the +characters ,, ;, and : are also considered whitespace. + +There is one caveat to the rules above. If the ! or ? 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 ? +operator as syntactic sugar for the Optional type, it must not have +whitespace on the left. To use it in the conditional (? :) operator, +it must have whitespace around both sides." + */ + +/** + "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." +*/ +binaryOperator : operator ; + +/** + "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." +*/ +prefixOperator : operator ; // only if space on left but not right + +/** + "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." + */ +postfixOperator : operator ; + +// Types + +// GRAMMAR OF A TYPE + +sType + : arrayType + | dictionaryType + | sType 'throws'? '->' sType // function-type + | sType 'rethrows' '->' sType // function-type + | typeIdentifier + | tupleType + | sType '?' // optional-type + | sType '!' // implicitly-unwrapped-optional-type + | protocolCompositionType + | sType '.' 'Type' | sType '.' 'Protocol' // metatype + ; + +arrayType: '[' sType ']' ; + +dictionaryType: '[' sType ':' sType ']' ; + +optionalType: sType '?' ; + +implicitlyUnwrappedOptionalType: sType '!' ; + +// GRAMMAR OF A TYPE ANNOTATION + +typeAnnotation : ':' attributes? sType ; + +// GRAMMAR OF A TYPE IDENTIFIER + +typeIdentifier + : typeName genericArgumentClause? + | typeName genericArgumentClause? '.' typeIdentifier + | 'Self' // Swift Language Reference does not have this + ; + +typeName : identifier ; + +// GRAMMAR OF A TUPLE TYPE + +tupleType : '(' tupleTypeBody? ')' ; +tupleTypeBody : tupleTypeElementList '...'? ; +tupleTypeElementList : tupleTypeElement | tupleTypeElement ',' tupleTypeElementList ; +tupleTypeElement : attributes? 'inout'? sType | 'inout'? elementName typeAnnotation ; +elementName : identifier ; + +// GRAMMAR OF A PROTOCOL COMPOSITION TYPE + +protocolCompositionType : 'protocol' '<' protocolIdentifierList? '>' ; +protocolIdentifierList : protocolIdentifier | protocolIdentifier ',' protocolIdentifierList ; +protocolIdentifier : typeIdentifier ; + +// GRAMMAR OF A METATYPE TYPE + +metatypeType : sType '.' 'Type' | sType '.' 'Protocol'; + +// GRAMMAR OF A TYPE INHERITANCE CLAUSE + +typeInheritanceClause : ':' classRequirement ',' typeInheritanceList + | ':' classRequirement + | ':' typeInheritanceList + ; +typeInheritanceList : typeIdentifier (',' typeIdentifier)* ; +classRequirement: 'class' ; + +// ------ Build Configurations (Macros) ------- + +compilerControlStatement: buildConfigurationStatement | lineControlStatement ; +buildConfigurationStatement: '#if' buildConfiguration statements? buildConfigurationElseIfClauses? buildConfigurationElseClause? '#endif' ; +buildConfigurationElseIfClauses: buildConfigurationElseIfClause+ ; +buildConfigurationElseIfClause: '#elseif' buildConfiguration statements? ; +buildConfigurationElseClause: '#else' statements? ; + +buildConfiguration: platformTestingFunction | identifier | booleanLiteral + | '(' buildConfiguration ')' + | '!' buildConfiguration + | buildConfiguration ('&&' | '||') buildConfiguration + ; + +platformTestingFunction: 'os' '(' operatingSystem ')' | 'arch' '(' architecture ')' ; +operatingSystem: 'OSX' | 'iOS' | 'watchOS' | 'tvOS' ; +architecture: 'i386' | 'x86_64' | 'arm' | 'arm64' ; + +lineControlStatement: '#line' (lineNumber fileName)? ; +lineNumber: integerLiteral ; +fileName: StringLiteral ; + +// ---------- Lexical Structure ----------- + +BooleanLiteral: 'true' | 'false' ; +NilLiteral: 'nil' ; + +// GRAMMAR OF AN IDENTIFIER + +identifier : Identifier | contextSensitiveKeyword ; + +keyword : 'convenience' | 'class' | 'deinit' | 'enum' | 'extension' | 'func' | 'import' | 'init' | 'let' | 'protocol' | 'static' | 'struct' | 'subscript' | 'typealias' | 'var' | 'break' | 'case' | 'continue' | 'default' | 'do' | 'else' | 'fallthrough' | 'if' | 'in' | 'for' | 'return' | 'switch' | 'where' | 'while' | 'as' | 'dynamicType' | 'is' | 'super' | 'self' | 'Self' | 'Type' | 'repeat' ; + +contextSensitiveKeyword : + 'associativity' | 'convenience' | 'dynamic' | 'didSet' | 'final' | 'get' | 'infix' | 'indirect' | + 'lazy' | 'left' | 'mutating' | 'none' | 'nonmutating' | 'optional' | 'operator' | 'override' | 'postfix' | 'precedence' | + 'prefix' | 'Protocol' | 'required' | 'right' | 'set' | 'Type' | 'unowned' | 'weak' | 'willSet' | + 'iOS' | 'iOSApplicationExtension' | 'OSX' | 'OSXApplicationExtension-' | 'watchOS' | 'x86_64' | + 'arm' | 'arm64' | 'i386' | 'os' | 'arch' + ; + +OperatorHead + : '/' | '=' | '-' | '+' | '!' | '*' | '%' | '<' | '>' | '&' | '|' | '^' | '~' | '?' + | [\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] + ; + +OperatorCharacter + : OperatorHead + | [\u0300–\u036F] + | [\u1DC0–\u1DFF] + | [\u20D0–\u20FF] + | [\uFE00–\uFE0F] + | [\uFE20–\uFE2F] + //| [\uE0100–\uE01EF] ANTLR can't do >16bit char + ; + +DotOperatorHead + : '..' + ; + +Identifier : IdentifierHead IdentifierCharacters? + | '`' IdentifierHead IdentifierCharacters? '`' + | ImplicitParameterName + ; + +identifierList : (identifier | '_') (',' (identifier | '_'))* ; + +fragment IdentifierHead : [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 or U+E0000–U+EFFFD +*/ + ; + +fragment IdentifierCharacter : [0-9] + | [\u0300-\u036F] | [\u1DC0-\u1DFF] | [\u20D0-\u20FF] | [\uFE20-\uFE2F] + | IdentifierHead + ; + +fragment IdentifierCharacters : IdentifierCharacter+ ; + +ImplicitParameterName : '$' DecimalLiteral ; // TODO: don't allow '_' here + +// GRAMMAR OF A LITERAL + +booleanLiteral: BooleanLiteral ; +literal : numericLiteral | StringLiteral | BooleanLiteral | NilLiteral ; + +// GRAMMAR OF AN INTEGER LITERAL + +numericLiteral: '-'? integerLiteral | '-'? FloatingPointLiteral ; + +integerLiteral + : BinaryLiteral + | OctalLiteral + | DecimalLiteral + | HexadecimalLiteral + ; + +BinaryLiteral : '0b' BinaryDigit BinaryLiteralCharacters? ; +fragment BinaryDigit : [01] ; +fragment BinaryLiteralCharacter : BinaryDigit | '_' ; +fragment BinaryLiteralCharacters : BinaryLiteralCharacter BinaryLiteralCharacters? ; + +OctalLiteral : '0o' OctalDigit OctalLiteralCharacters? ; +fragment OctalDigit : [0-7] ; +fragment OctalLiteralCharacter : OctalDigit | '_' ; +fragment OctalLiteralCharacters : OctalLiteralCharacter+ ; + +DecimalLiteral : DecimalDigit DecimalLiteralCharacters? ; +fragment DecimalDigit : [0-9] ; +fragment DecimalDigits : DecimalDigit+ ; +fragment DecimalLiteralCharacter : DecimalDigit | '_' ; +fragment DecimalLiteralCharacters : DecimalLiteralCharacter+ ; +HexadecimalLiteral : '0x' HexadecimalDigit HexadecimalLiteralCharacters? ; +fragment HexadecimalDigit : [0-9a-fA-F] ; +fragment HexadecimalLiteralCharacter : HexadecimalDigit | '_' ; +fragment HexadecimalLiteralCharacters : HexadecimalLiteralCharacter+ ; + +// GRAMMAR OF A FLOATING_POINT LITERAL + +FloatingPointLiteral + : DecimalLiteral DecimalFraction? DecimalExponent? + | HexadecimalLiteral HexadecimalFraction? HexadecimalExponent + ; +fragment DecimalFraction : '.' DecimalLiteral ; +fragment DecimalExponent : FloatingPointE Sign? DecimalLiteral ; +fragment HexadecimalFraction : '.' HexadecimalLiteral? ; +fragment HexadecimalExponent : FloatingPointP Sign? HexadecimalLiteral ; +fragment FloatingPointE : [eE] ; +fragment FloatingPointP : [pP] ; +fragment Sign : [+\-] ; + +VersionLiteral: DecimalLiteral DecimalFraction DecimalFraction ; + +// GRAMMAR OF A STRING LITERAL + +StringLiteral : '"' QuotedText? '"' ; +fragment QuotedText : QuotedTextItem QuotedText? ; +fragment QuotedTextItem : EscapedCharacter +// | '\\(' expression ')' + | ~["\\\u000A\u000D] + ; +EscapedCharacter : '\\' [0\\(tnr"'] + | '\\x' HexadecimalDigit HexadecimalDigit + | '\\u' '{' HexadecimalDigit HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? HexadecimalDigit? '}' +; + +WS : [ \n\r\t\u000B\u000C\u0000] -> channel(HIDDEN) ; + +/* Added optional newline character to prevent the whitespace lexer rule from matching newline + * at the end of the comment. This affects how blank lines are counted around functions. + */ +BlockComment : '/*' (BlockComment|.)*? '*/' '\n'? -> channel(HIDDEN) ; // nesting allow + +LineComment : '//' .*? ('\n'|EOF) -> channel(HIDDEN) ; diff --git a/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftLanguage.java b/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftLanguage.java new file mode 100644 index 0000000000..324c4820a1 --- /dev/null +++ b/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftLanguage.java @@ -0,0 +1,19 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import net.sourceforge.pmd.cpd.SwiftTokenizer; + +/** + * Language implementation for Swift + */ +public class SwiftLanguage extends AbstractLanguage { + + /** + * Creates a new Swift Language instance. + */ + public SwiftLanguage() { + super("Swift", "swift", new SwiftTokenizer(), ".swift"); + } +} diff --git a/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java b/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java new file mode 100644 index 0000000000..52f3d7fb3f --- /dev/null +++ b/pmd-swift/src/main/java/net/sourceforge/pmd/cpd/SwiftTokenizer.java @@ -0,0 +1,81 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import net.sourceforge.pmd.lang.ast.TokenMgrError; +import net.sourceforge.pmd.lang.swift.antlr4.SwiftLexer; + +import org.antlr.v4.runtime.ANTLRInputStream; +import org.antlr.v4.runtime.BaseErrorListener; +import org.antlr.v4.runtime.Lexer; +import org.antlr.v4.runtime.RecognitionException; +import org.antlr.v4.runtime.Recognizer; +import org.antlr.v4.runtime.Token; + +/** + * The Swift Tokenizer + */ +public class SwiftTokenizer implements Tokenizer { + + @Override + public void tokenize(SourceCode sourceCode, Tokens tokenEntries) { + StringBuilder buffer = sourceCode.getCodeBuffer(); + + try { + ANTLRInputStream ais = new ANTLRInputStream(buffer.toString()); + SwiftLexer lexer = new SwiftLexer(ais); + + lexer.removeErrorListeners(); + lexer.addErrorListener(new ErrorHandler()); + Token token = lexer.nextToken(); + + while (token.getType() != Token.EOF) { + if (token.getChannel() != Lexer.HIDDEN) { + TokenEntry tokenEntry = + new TokenEntry(token.getText(), sourceCode.getFileName(), token.getLine()); + + tokenEntries.add(tokenEntry); + } + token = lexer.nextToken(); + } + } catch (ANTLRSyntaxError err) { + // Wrap exceptions of the Swift tokenizer in a TokenMgrError, so they are correctly handled + // when CPD is executed with the '--skipLexicalErrors' command line option + throw new TokenMgrError( + "Lexical error in file " + sourceCode.getFileName() + " at line " + + err.getLine() + ", column " + err.getColumn() + ". Encountered: " + err.getMessage(), + TokenMgrError.LEXICAL_ERROR); + } finally { + tokenEntries.add(TokenEntry.getEOF()); + } + } + + private static class ErrorHandler extends BaseErrorListener { + @Override + public void syntaxError(Recognizer recognizer, Object offendingSymbol, int line, + int charPositionInLine, String msg, RecognitionException ex) { + throw new ANTLRSyntaxError(msg, line, charPositionInLine, ex); + } + } + + private static class ANTLRSyntaxError extends RuntimeException { + private static final long serialVersionUID = 1L; + private final int line; + private final int column; + + public ANTLRSyntaxError (String msg, int line, int column, RecognitionException cause) { + super(msg, cause); + this.line = line; + this.column = column; + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + } +} diff --git a/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java b/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java new file mode 100644 index 0000000000..365608dfe0 --- /dev/null +++ b/pmd-swift/src/main/java/net/sourceforge/pmd/lang/swift/SwiftLanguageModule.java @@ -0,0 +1,25 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.lang.swift; + +import net.sourceforge.pmd.lang.BaseLanguageModule; + +/** + * Language Module for Swift + */ +public class SwiftLanguageModule extends BaseLanguageModule { + + /** The name. */ + public static final String NAME = "Swift"; + /** The terse name. */ + public static final String TERSE_NAME = "swift"; + + /** + * Create a new instance of Swift Language Module. + */ + public SwiftLanguageModule() { + super(NAME, null, TERSE_NAME, null, "swift"); + addVersion("", null, true); + } +} diff --git a/pmd-swift/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-swift/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language new file mode 100644 index 0000000000..a5cd852cde --- /dev/null +++ b/pmd-swift/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.cpd.SwiftLanguage diff --git a/pmd-swift/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language b/pmd-swift/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language new file mode 100644 index 0000000000..467577ddb9 --- /dev/null +++ b/pmd-swift/src/main/resources/META-INF/services/net.sourceforge.pmd.lang.Language @@ -0,0 +1 @@ +net.sourceforge.pmd.lang.swift.SwiftLanguageModule diff --git a/pmd-swift/src/site/markdown/index.md b/pmd-swift/src/site/markdown/index.md new file mode 100644 index 0000000000..ac13d040c0 --- /dev/null +++ b/pmd-swift/src/site/markdown/index.md @@ -0,0 +1,3 @@ +# PMD Swift + +Only CPD is supported. There are no PMD rules for Swift. diff --git a/pmd-swift/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java b/pmd-swift/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java new file mode 100644 index 0000000000..8d125ead81 --- /dev/null +++ b/pmd-swift/src/test/java/net/sourceforge/pmd/LanguageVersionTest.java @@ -0,0 +1,27 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd; + +import java.util.Arrays; +import java.util.Collection; + +import net.sourceforge.pmd.lang.LanguageRegistry; +import net.sourceforge.pmd.lang.LanguageVersion; +import net.sourceforge.pmd.lang.swift.SwiftLanguageModule; + +import org.junit.runners.Parameterized.Parameters; + +public class LanguageVersionTest extends AbstractLanguageVersionTest { + + public LanguageVersionTest(String name, String terseName, String version, LanguageVersion expected) { + super(name, terseName, version, expected); + } + + @Parameters + public static Collection data() { + return Arrays.asList(new Object[][] { + { SwiftLanguageModule.NAME, SwiftLanguageModule.TERSE_NAME, "", LanguageRegistry.getLanguage(SwiftLanguageModule.NAME).getDefaultVersion() } + }); + } +} diff --git a/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java b/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java new file mode 100644 index 0000000000..059429850b --- /dev/null +++ b/pmd-swift/src/test/java/net/sourceforge/pmd/cpd/SwiftTokenizerTest.java @@ -0,0 +1,37 @@ +/** + * BSD-style license; for more info see http://pmd.sourceforge.net/license.html + */ +package net.sourceforge.pmd.cpd; + +import java.io.IOException; + +import net.sourceforge.pmd.testframework.AbstractTokenizerTest; + +import org.apache.commons.io.IOUtils; +import org.junit.Before; +import org.junit.Test; + + +public class SwiftTokenizerTest extends AbstractTokenizerTest { + + private static final String FILENAME = "BTree.swift"; + + @Before + @Override + public void buildTokenizer() throws IOException { + this.tokenizer = new SwiftTokenizer(); + this.sourceCode = new SourceCode(new SourceCode.StringCodeLoader(this.getSampleCode(), FILENAME)); + } + + @Override + public String getSampleCode() throws IOException { + return IOUtils.toString(SwiftTokenizer.class.getResourceAsStream(FILENAME)); + } + + @Test + public void tokenizeTest() throws IOException { + this.expectedTokenCount = 3811; + super.tokenizeTest(); + } +} + diff --git a/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift new file mode 100644 index 0000000000..96731997ad --- /dev/null +++ b/pmd-swift/src/test/resources/net/sourceforge/pmd/cpd/BTree.swift @@ -0,0 +1,890 @@ +// Downloaded on 2016/03/02 from https://github.com/lorentey/BTree/blame/master/Sources/BTree.swift +// +// BTree.swift +// BTree +// +// Created by Károly Lőrentey on 2016-02-19. +// Copyright © 2015–2016 Károly Lőrentey. +// + +/// B-trees are search trees that provide an ordered key-value store with excellent performance characteristics. +public struct BTree { + public typealias Element = (Key, Payload) + internal typealias Node = BTreeNode + + internal var root: Node + + internal init(_ root: Node) { + self.root = root + } + + /// Initialize a new b-tree with no elements. + /// + /// - Parameter order: The maximum number of children for tree nodes. + public init(order: Int = Node.defaultOrder) { + self.root = Node(order: order) + } + + /// The order of this tree, i.e., the maximum number of children for tree nodes. + public var order: Int { return root.order } + /// The depth of this tree. Depth starts at 0 for a tree that has a single root node. + public var depth: Int { return root.depth } +} + +//MAKE: Uniquing + +public extension BTree { + internal var isUnique: Bool { + mutating get { + return isUniquelyReferenced(&root) + } + } + + internal mutating func makeUnique() { + guard !isUnique else { return } + root = root.clone() + } +} + +//MARK: SequenceType + +extension BTree: SequenceType { + public typealias Generator = BTreeGenerator + + /// Returns true iff this tree has no elements. + public var isEmpty: Bool { return root.count == 0 } + + /// Returns a generator over the elements of this b-tree. Elements are sorted by key. + public func generate() -> Generator { + return Generator(BTreeStrongPath(root: root, position: 0)) + } + + /// Returns a generator starting at a specific index. + public func generate(from index: Index) -> Generator { + index.state.expectRoot(root) + return Generator(BTreeStrongPath(root: root, slotsFrom: index.state)) + } + + /// Returns a generator starting at a specific position. + public func generate(fromPosition position: Int) -> Generator { + return Generator(BTreeStrongPath(root: root, position: position)) + } + + /// Returns a generator starting at the element with the specified key. + /// If the tree contains no such element, the generator is positioned at the first element with a larger key. + /// If there are multiple elements with the same key, `selector` indicates which matching element to find. + public func generate(from key: Key, choosing selector: BTreeKeySelector = .Any) -> Generator { + return Generator(BTreeStrongPath(root: root, key: key, choosing: selector)) + } + + /// Call `body` on each element in self in the same order as a for-in loop. + public func forEach(@noescape body: (Element) throws -> ()) rethrows { + try root.forEach(body) + } + + /// A version of `forEach` that allows `body` to interrupt iteration by returning `false`. + /// + /// - Returns: `true` iff `body` returned true for all elements in the tree. + public func forEach(@noescape body: (Element) throws -> Bool) rethrows -> Bool { + return try root.forEach(body) + } +} + +//MARK: CollectionType + +extension BTree: CollectionType { + public typealias Index = BTreeIndex + public typealias SubSequence = BTree + + /// The index of the first element of this tree. Elements are sorted by key. + /// + /// - Complexity: O(log(`count`)) + public var startIndex: Index { + return Index(BTreeWeakPath(startOf: root)) + } + + /// The index after the last element of this tree. (Equals `startIndex` when the tree is empty.) + /// + /// - Complexity: O(1) + public var endIndex: Index { + return Index(BTreeWeakPath(endOf: root)) + } + + /// The number of elements in this tree. + public var count: Int { + return root.count + } + + /// Returns the element at `index`. + /// + /// - Complexity: O(1) + public subscript(index: Index) -> Element { + get { + index.state.expectRoot(self.root) + return index.state.element + } + } + + /// Returns a tree consisting of elements in the specified range of indexes. + /// + /// - Complexity: O(log(`count`)) + public subscript(range: Range) -> BTree { + get { + return subtree(with: range) + } + } +} + +//MARK: Lookups + +/// When the tree contains multiple elements with the same key, you can use a key selector to specify +/// that you want to use the first or last matching element, or that you don't care which element you get. +/// (The latter is sometimes faster.) +public enum BTreeKeySelector { + /// Look for the first element that matches the key, or insert a new element before existing matches. + case First + /// Look for the last element that matches the key, or insert a new element after existing matches. + case Last + /// Look for the first element that has a greater key, or insert a new element after existing matches. + case After + /// Accept any element that matches the key. This is sometimes faster, because the search may stop before reaching + /// a leaf node. + case Any +} + +public extension BTree { + + /// Returns the first element in this tree, or `nil` if the tree is empty. + /// + /// - Complexity: O(log(`count`)) + public var first: Element? { + return root.first + } + + /// Returns the last element in this tree, or `nil` if the tree is empty. + /// + /// - Complexity: O(log(`count`)) + public var last: Element? { + return root.last + } + + /// Returns the element at `position`. + /// + /// - Requires: `position >= 0 && position < count` + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func elementAtPosition(position: Int) -> Element { + precondition(position >= 0 && position < count) + var position = position + var node = root + while !node.isLeaf { + let slot = node.slotOfPosition(position) + if slot.match { + return node.elements[slot.index] + } + let child = node.children[slot.index] + position -= slot.position - child.count + node = child + } + return node.elements[position] + } + + /// Returns the payload of an element of this tree with the specified key, or `nil` if there is no such element. + /// If there are multiple elements with the same key, `selector` indicates which matching element to find. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func payloadOf(key: Key, choosing selector: BTreeKeySelector = .Any) -> Payload? { + switch selector { + case .Any: + var node = root + while true { + let slot = node.slotOf(key, choosing: .First) + if let m = slot.match { + return node.elements[m].1 + } + if node.isLeaf { + break + } + node = node.children[slot.descend] + } + return nil + default: + var node = root + var lastmatch: Payload? = nil + while true { + let slot = node.slotOf(key, choosing: selector) + if let m = slot.match { + lastmatch = node.elements[m].1 + } + if node.isLeaf { + break + } + node = node.children[slot.descend] + } + return lastmatch + } + } + + /// Returns an index to an element in this tree with the specified key, or `nil` if there is no such element. + /// If there are multiple elements with the same key, `selector` indicates which matching element to find. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func indexOf(key: Key, choosing selector: BTreeKeySelector = .Any) -> Index? { + let path = BTreeWeakPath(root: root, key: key, choosing: selector) + guard !path.isAtEnd && (selector == .After || path.key == key) else { return nil } + return Index(path) + } + + /// Returns the position of the first element in this tree with the specified key, or `nil` if there is no such element. + /// If there are multiple elements with the same key, `selector` indicates which matching element to find. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func positionOf(key: Key, choosing selector: BTreeKeySelector = .Any) -> Int? { + var node = root + var position = 0 + var match: Int? = nil + while !node.isLeaf { + let slot = node.slotOf(key, choosing: selector) + let child = node.children[slot.descend] + if let m = slot.match { + let p = node.positionOfSlot(m) + match = position + p + position += p - (m == slot.descend ? node.children[m].count : 0) + } + else { + position += node.positionOfSlot(slot.descend) - child.count + } + node = child + } + let slot = node.slotOf(key, choosing: selector) + if let m = slot.match { + return position + m + } + return match + } + + /// Returns the position of the element at `index`. + /// + /// - Complexity: O(1) + @warn_unused_result + public func positionOfIndex(index: Index) -> Int { + index.state.expectRoot(root) + return index.state.position + } + + /// Returns the index of the element at `position`. + /// + /// - Requires: `position >= 0 && position <= count` + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func indexOfPosition(position: Int) -> Index { + return Index(BTreeWeakPath(root: root, position: position)) + } +} + + +//MARK: Editing + +extension BTree { + /// Edit the tree at a path that is to be discovered on the way down, ensuring that all nodes on the path are + /// uniquely held by this tree. + /// This is a simple (but not easy, alas) interface that allows implementing basic editing operations using + /// recursion without adding a separate method on `BTreeNode` for each operation. + /// + /// Editing is split into two phases: the descent phase and the ascend phase. + /// + /// - During descent, the `descend` closure is called repeatedly to get the next child slot to drill down into. + /// When the closure returns `nil`, the phase stops and the ascend phase begins. + /// - During ascend, the `ascend` closure is called for each node for which `descend` returned non-nil, in reverse + /// order. + /// + /// - Parameter descend: A closure that, when given a node, returns the child slot toward which the editing should + /// continue descending, or `nil` if the descent should stop. The closure may set outside references to the + /// node it gets, and may modify the node as it likes; however, it shouldn't modify anything in the tree outside + /// the node's subtree, and it should not set outside references to the node's descendants. + /// - Parameter ascend: A closure that processes a step of ascending back towards the root. It receives a parent node + /// and the child slot from which this step is ascending. The closure may set outside references to the + /// node it gets, and may modify the subtree as it likes; however, it shouldn't modify anything in the tree outside + /// the node's subtree. + internal mutating func edit(@noescape descend descend: Node -> Int?, @noescape ascend: (Node, Int) -> Void) { + makeUnique() + root.edit(descend: descend, ascend: ascend) + } +} + +//MARK: Insertion + +extension BTree { + /// Insert the specified element into the tree at `position`. + /// + /// - Requires: The key of the supplied element does not violate the b-tree's ordering requirement. + /// (This is only verified in non-optimized builds.) + /// - Note: When you need to perform multiple modifications on the same tree, + /// `BTreeCursor` provides an alternative interface that's often more efficient. + /// - Complexity: O(log(`count`)) + public mutating func insert(element: Element, at position: Int) { + precondition(position >= 0 && position <= count) + makeUnique() + var pos = count - position + var splinter: BTreeSplinter? = nil + var element = element + edit( + descend: { node in + let slot = node.slotOfPosition(node.count - pos) + assert(slot.index == 0 || node.elements[slot.index - 1].0 <= element.0) + assert(slot.index == node.elements.count || node.elements[slot.index].0 >= element.0) + if !slot.match { + // Continue descending. + pos -= node.count - slot.position + return slot.index + } + if node.isLeaf { + // Found the insertion point. Insert, then start ascending. + node.insert(element, inSlot: slot.index) + if node.isTooLarge { + splinter = node.split() + } + return nil + } + // For internal nodes, put the new element in place of the old at the same position, + // then continue descending toward the next position, inserting the old element. + element = node.setElementInSlot(slot.index, to: element) + pos = node.children[slot.index + 1].count + return slot.index + 1 + }, + ascend: { node, slot in + node.count += 1 + if let s = splinter { + node.insert(s, inSlot: slot) + splinter = node.isTooLarge ? node.split() : nil + } + } + ) + if let s = splinter { + root = Node(left: root, separator: s.separator, right: s.node) + } + } + + /// Set the payload at `position`, and return the payload originally stored there. + /// + /// - Requires: `position < count` + /// - Note: When you need to perform multiple modifications on the same tree, + /// `BTreeCursor` provides an alternative interface that's often more efficient. + /// - Complexity: O(log(`count`)) + public mutating func setPayloadAt(position: Int, to payload: Payload) -> Payload { + precondition(position >= 0 && position < count) + makeUnique() + var pos = count - position + var old: Payload? = nil + edit( + descend: { node in + let slot = node.slotOfPosition(node.count - pos) + if !slot.match { + // Continue descending. + pos -= node.count - slot.position + return slot.index + } + old = node.elements[slot.index].1 + node.elements[slot.index].1 = payload + return nil + }, + ascend: { node, slot in + } + ) + return old! + } + + /// Insert `element` into the tree as a new element. + /// If the tree already contains elements with the same key, `selector` specifies where to put the new element. + /// + /// - Note: When you need to perform multiple modifications on the same tree, + /// `BTreeCursor` provides an alternative interface that's often more efficient. + /// - Complexity: O(log(`count`)) + public mutating func insert(element: Element, at selector: BTreeKeySelector = .Any) { + makeUnique() + let selector: BTreeKeySelector = (selector == .First ? .First : .After) + var splinter: BTreeSplinter? = nil + edit( + descend: { node in + let slot = node.slotOf(element.0, choosing: selector) + if !node.isLeaf { + return slot.descend + } + node.insert(element, inSlot: slot.descend) + if node.isTooLarge { + splinter = node.split() + } + return nil + }, + ascend: { node, slot in + node.count += 1 + if let s = splinter { + node.insert(s, inSlot: slot) + splinter = node.isTooLarge ? node.split() : nil + } + } + ) + if let s = splinter { + root = Node(left: root, separator: s.separator, right: s.node) + } + } + + /// Insert `element` into the tree, replacing an element with the same key if there is one. + /// If the tree already contains multiple elements with the same key, `selector` specifies which one to replace. + /// + /// - Note: When you need to perform multiple modifications on the same tree, + /// `BTreeCursor` provides an alternative interface that's often more efficient. + /// - Complexity: O(log(`count`)) + public mutating func insertOrReplace(element: Element, at selector: BTreeKeySelector = .Any) -> Payload? { + let selector = (selector == .After ? .Last : selector) + makeUnique() + var old: Payload? = nil + var match: (node: Node, slot: Int)? = nil + var splinter: BTreeSplinter? = nil + edit( + descend: { node in + let slot = node.slotOf(element.0, choosing: selector) + if node.isLeaf { + if let m = slot.match { + // We found the element we want to replace. + old = node.setElementInSlot(m, to: element).1 + match = nil + } + else if old == nil && match == nil { + // The tree contains no matching elements; insert a new one. + node.insert(element, inSlot: slot.descend) + if node.isTooLarge { + splinter = node.split() + } + } + return nil + } + if let m = slot.match { + if selector == .Any { + // When we don't care about which element to replace, we stop the descent at the first match. + old = node.setElementInSlot(m, to: element).1 + return nil + } + // Otherwise remember this match and replace it during ascend if it's the last one. + match = (node, m) + } + return slot.descend + }, + ascend: { node, slot in + if let m = match { + // We're looking for the node that contains the last match. + if m.node === node { + // Found it; replace the matching element and cancel the search. + old = node.setElementInSlot(m.slot, to: element).1 + match = nil + } + } + else if old == nil { + // We're ascending from an insertion. + node.count += 1 + if let s = splinter { + node.insert(s, inSlot: slot) + splinter = node.isTooLarge ? node.split() : nil + } + } + } + ) + if let s = splinter { + root = Node(left: root, separator: s.separator, right: s.node) + } + return old + } +} + +//MARK: Removal + +extension BTree { + /// Remove and return the first element. + /// + /// - Complexity: O(log(`count`)) + public mutating func removeFirst() -> Element { + return removeAt(0) + } + + /// Remove and return the last element. + /// + /// - Complexity: O(log(`count`)) + public mutating func removeLast() -> Element { + return removeAt(count - 1) + } + + /// Remove and return the first element, or return `nil` if the tree is empty. + /// + /// - Complexity: O(log(`count`)) + public mutating func popFirst() -> Element? { + guard !isEmpty else { return nil } + return removeAt(0) + } + + /// Remove and return the first element, or return `nil` if the tree is empty. + /// + /// - Complexity: O(log(`count`)) + public mutating func popLast() -> Element? { + guard !isEmpty else { return nil } + return removeAt(count - 1) + } + + /// Remove and return the element at the specified position. + /// + /// - Note: When you need to perform multiple modifications on the same tree, + /// `BTreeCursor` provides an alternative interface that's often more efficient. + /// - Complexity: O(log(`count`)) + public mutating func removeAt(position: Int) -> Element { + precondition(position >= 0 && position < count) + makeUnique() + var pos = count - position + var matching: (node: Node, slot: Int)? = nil + var old: Element? = nil + edit( + descend: { node in + let slot = node.slotOfPosition(node.count - pos) + if !slot.match { + // No match yet; continue descending. + assert(!node.isLeaf) + pos -= node.count - slot.position + return slot.index + } + if node.isLeaf { + // The position we're looking for is in a leaf node; we can remove it directly. + old = node.removeSlot(slot.index) + return nil + } + // When the position happens to fall into an internal node, remember the match and continue + // removing the next position (which is guaranteed to be in a leaf node). + // We'll replace the removed element with this one during the ascend. + matching = (node, slot.index) + pos = node.children[slot.index + 1].count + return slot.index + 1 + }, + ascend: { node, slot in + node.count -= 1 + if let m = matching where m.node === node { + // We've removed the element at the next position; put it back in place of the + // element we actually want to remove. + old = node.setElementInSlot(m.slot, to: old!) + matching = nil + } + if node.children[slot].isTooSmall { + node.fixDeficiency(slot) + } + } + ) + if root.children.count == 1 { + assert(root.elements.count == 0) + root = root.children[0] + } + return old! + } + + /// Remove an element with the specified key, if it exists. + /// If there are multiple elements with the same key, `selector` indicates which matching element to remove. + /// + /// - Returns: The removed element, or `nil` if there was no element with `key` in the tree. + /// - Note: When you need to perform multiple modifications on the same tree, + /// `BTreeCursor` provides an alternative interface that's often more efficient. + /// - Complexity: O(log(`count`)) + public mutating func remove(key: Key, at selector: BTreeKeySelector = .Any) -> Element? { + let selector = (selector == .After ? .Last : selector) + makeUnique() + var old: Element? = nil + var matching: (node: Node, slot: Int)? = nil + edit( + descend: { node in + let slot = node.slotOf(key, choosing: selector) + if node.isLeaf { + if let m = slot.match { + old = node.removeSlot(m) + matching = nil + } + else if matching != nil { + old = node.removeSlot(slot.descend == node.elements.count ? slot.descend - 1 : slot.descend) + } + return nil + } + if let m = slot.match { + matching = (node, m) + } + return slot.descend + }, + ascend: { node, slot in + if let o = old { + node.count -= 1 + if let m = matching where m.node === node { + old = node.setElementInSlot(m.slot, to: o) + matching = nil + } + if node.children[slot].isTooSmall { + node.fixDeficiency(slot) + } + } + } + ) + if root.children.count == 1 { + assert(root.elements.count == 0) + root = root.children[0] + } + return old + } + + /// Remove and return the element referenced by the given index. + /// + /// - Complexity: O(log(`count`)) + public mutating func removeAtIndex(index: Index) -> Element { + return withCursorAt(index) { cursor in + return cursor.remove() + } + } + + /// Remove all elements from this tree. + public mutating func removeAll() { + root = Node(order: root.order) + } +} + +//MARK: Subtree extraction + +extension BTree { + /// Returns a subtree containing the initial `maxLength` elements in this tree. + /// + /// If `maxLength` exceeds `self.count`, the result contains all the elements of `self`. + /// + /// - Complexity: O(log(`count`)) + public func prefix(maxLength: Int) -> BTree { + precondition(maxLength >= 0) + if maxLength == 0 { + return BTree(order: order) + } + if maxLength >= count { + return self + } + return BTreeStrongPath(root: root, position: maxLength).prefix() + } + + /// Returns a subtree containing all but the last `n` elements. + /// + /// - Complexity: O(log(`count`)) + public func dropLast(n: Int) -> BTree { + precondition(n >= 0) + return prefix(max(0, count - n)) + } + + /// Returns a subtree containing all elements before the specified index. + /// + /// - Complexity: O(log(`count`)) + public func prefixUpTo(end: Index) -> BTree { + end.state.expectRoot(root) + if end.state.isAtEnd { + return self + } + return end.state.prefix() + } + + /// Returns a subtree containing all elements whose key is less than `key`. + /// + /// - Complexity: O(log(`count`)) + public func prefixUpTo(end: Key) -> BTree { + let path = BTreeStrongPath(root: root, key: end, choosing: .First) + if path.isAtEnd { + return self + } + return path.prefix() + } + + /// Returns a subtree containing all elements at or before the specified index. + /// + /// - Complexity: O(log(`count`)) + public func prefixThrough(stop: Index) -> BTree { + return prefixUpTo(stop.successor()) + } + + /// Returns a subtree containing all elements whose key is less than or equal to `key`. + /// + /// - Complexity: O(log(`count`)) + public func prefixThrough(stop: Key) -> BTree { + let path = BTreeStrongPath(root: root, key: stop, choosing: .After) + if path.isAtEnd { + return self + } + return path.prefix() + } + + /// Returns a tree containing the final `maxLength` elements in this tree. + /// + /// If `maxLength` exceeds `self.count`, the result contains all the elements of `self`. + /// + /// - Complexity: O(log(`count`)) + public func suffix(maxLength: Int) -> BTree { + precondition(maxLength >= 0) + if maxLength == 0 { + return BTree(order: order) + } + if maxLength >= count { + return self + } + return BTreeStrongPath(root: root, position: count - maxLength - 1).suffix() + } + + /// Returns a subtree containing all but the first `n` elements. + /// + /// - Complexity: O(log(`count`)) + public func dropFirst(n: Int) -> BTree { + precondition(n >= 0) + return suffix(max(0, count - n)) + } + + /// Returns a subtree containing all elements at or after the specified index. + /// + /// - Complexity: O(log(`count`)) + public func suffixFrom(start: Index) -> BTree { + start.state.expectRoot(root) + if start.state.position == 0 { + return self + } + return start.predecessor().state.suffix() + } + + /// Returns a subtree containing all elements whose key is greater than or equal to `key`. + /// + /// - Complexity: O(log(`count`)) + public func suffixFrom(start: Key) -> BTree { + var path = BTreeStrongPath(root: root, key: start, choosing: .First) + if path.isAtStart { + return self + } + path.moveBackward() + return path.suffix() + } + + /// Return a subtree consisting of elements in the specified range of indexes. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func subtree(with range: Range) -> BTree { + range.startIndex.state.expectRoot(root) + range.endIndex.state.expectRoot(root) + let start = range.startIndex.state.position + let end = range.endIndex.state.position + precondition(0 <= start && start <= end && end <= self.count) + if start == end { + return BTree(order: self.order) + } + if start == 0 { + return prefixUpTo(range.endIndex) + } + return suffixFrom(range.startIndex).prefix(end - start) + } + + /// Return a subtree consisting of elements in the specified range of positions. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func subtree(with positions: Range) -> BTree { + precondition(positions.startIndex >= 0 && positions.endIndex <= count) + if positions.count == 0 { + return BTree(order: order) + } + return dropFirst(positions.startIndex).prefix(positions.count) + } + + /// Return a subtree consisting of all elements with keys greater than or equal to `start` but less than `end`. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func subtree(from start: Key, to end: Key) -> BTree { + precondition(start <= end) + return suffixFrom(start).prefixUpTo(end) + } + + /// Return a submap consisting of all elements with keys greater than or equal to `start` but less than or equal to `end`. + /// + /// - Complexity: O(log(`count`)) + @warn_unused_result + public func subtree(from start: Key, through stop: Key) -> BTree { + precondition(start <= stop) + return suffixFrom(start).prefixThrough(stop) + } +} + +//MARK: Bulk loading + +extension BTree { + /// Create a new b-tree from elements of an unsorted sequence, using a stable sort algorithm. + /// + /// - Parameter elements: An unsorted sequence of arbitrary length. + /// - Parameter order: The desired b-tree order. If not specified (recommended), the default order is used. + /// - Complexity: O(count * log(`count`)) + /// - SeeAlso: `init(sortedElements:order:fillFactor:)` for a (faster) variant that can be used if the sequence is already sorted. + public init(_ elements: S, dropDuplicates: Bool = false, order: Int = Node.defaultOrder) { + self.init(Node(order: order)) + withCursorAtEnd { cursor in + for element in elements { + cursor.move(to: element.0, choosing: .Last) + let match = !cursor.isAtEnd && cursor.key == element.0 + if match { + if dropDuplicates { + cursor.element = element + } + else { + cursor.insertAfter(element) + } + } + else { + cursor.insert(element) + } + } + } + } + + /// Create a new b-tree from elements of a sequence sorted by key. + /// + /// - Parameter sortedElements: A sequence of arbitrary length, sorted by key. + /// - Parameter order: The desired b-tree order. If not specified (recommended), the default order is used. + /// - Parameter fillFactor: The desired fill factor in each node of the new tree. Must be between 0.5 and 1.0. + /// If not specified, a value of 1.0 is used, i.e., nodes will be loaded with as many elements as possible. + /// - Complexity: O(count) + /// - SeeAlso: `init(elements:order:fillFactor:)` for a (slower) unsorted variant. + public init(sortedElements elements: S, dropDuplicates: Bool = false, order: Int = Node.defaultOrder, fillFactor: Double = 1) { + var generator = elements.generate() + self.init(order: order, fillFactor: fillFactor, dropDuplicates: dropDuplicates, next: { generator.next() }) + } + + internal init(order: Int = Node.defaultOrder, fillFactor: Double = 1, dropDuplicates: Bool = false, @noescape next: () -> Element?) { + precondition(order > 1) + precondition(fillFactor >= 0.5 && fillFactor <= 1) + let keysPerNode = Int(fillFactor * Double(order - 1) + 0.5) + assert(keysPerNode >= (order - 1) / 2 && keysPerNode <= order - 1) + + var builder = BTreeBuilder(order: order, keysPerNode: keysPerNode) + if dropDuplicates { + guard var buffer = next() else { + self.init(Node(order: order)) + return + } + while let element = next() { + precondition(buffer.0 <= element.0) + if buffer.0 < element.0 { + builder.append(buffer) + } + buffer = element + } + builder.append(buffer) + } + else { + var lastKey: Key? = nil + while let element = next() { + precondition(lastKey <= element.0) + lastKey = element.0 + builder.append(element) + } + } + self.init(builder.finish()) + } +} diff --git a/pom.xml b/pom.xml index 0f3d0c5eab..2fff9cdc86 100644 --- a/pom.xml +++ b/pom.xml @@ -893,6 +893,7 @@ pmd-plsql pmd-python pmd-ruby + pmd-swift pmd-test pmd-vm pmd-xml