Skip to content

Commit

Permalink
#67 Support records
Browse files Browse the repository at this point in the history
  • Loading branch information
rfscholte committed Oct 22, 2021
1 parent 37916ae commit c3aac2d
Show file tree
Hide file tree
Showing 3 changed files with 189 additions and 13 deletions.
13 changes: 7 additions & 6 deletions src/grammar/lexer.flex
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ Id = ([:jletter:]|{UnicodeChar}) ([:jletterdigit:]|{UnicodeChar})*
JavadocEnd = "*"+ "/"

%state JAVADOC JAVADOCTAG JAVADOCLINE CODEBLOCK PARENBLOCK ASSIGNMENT STRING CHAR SINGLELINECOMMENT MULTILINECOMMENT ANNOTATION ANNOSTRING ANNOCHAR ARGUMENTS NAME
%state ANNOTATIONTYPE ENUM MODULE TYPE ANNOTATIONNOARG ATANNOTATION
%state ANNOTATIONTYPE ENUM MODULE RECORD TYPE ANNOTATIONNOARG ATANNOTATION
%state NAME_OR_MODIFIER

%%
Expand Down Expand Up @@ -234,7 +234,7 @@ JavadocEnd = "*"+ "/"
<ANNOTATIONNOARG> {
{WhiteSpace} { popState(); }
}
<YYINITIAL, ANNOTATIONNOARG, ANNOTATIONTYPE, ENUM, NAME, TYPE> {
<YYINITIAL, ANNOTATIONNOARG, ANNOTATIONTYPE, ENUM, NAME, RECORD, TYPE> {
"." { return Parser.DOT; }
"..." { return Parser.DOTDOTDOT; }
"," { return Parser.COMMA; }
Expand All @@ -260,6 +260,7 @@ JavadocEnd = "*"+ "/"
"implements" { return Parser.IMPLEMENTS; }
"super" { return Parser.SUPER; }
"new" { return Parser.NEW; }
"record" { return Parser.RECORD; }

"[" { nestingDepth++; return Parser.SQUAREOPEN; }
"]" { nestingDepth--; return Parser.SQUARECLOSE; }
Expand Down Expand Up @@ -421,7 +422,7 @@ JavadocEnd = "*"+ "/"
}
}
}
<ENUM, TYPE> {
<ENUM, RECORD, TYPE> {
"default" { return Parser.DEFAULT; }
}
<ANNOTATIONTYPE> {
Expand All @@ -432,7 +433,7 @@ JavadocEnd = "*"+ "/"
{Id} / {WhiteSpace}* [;{(] { resetAnnotatedElementLine(); popState(); return Parser.IDENTIFIER; }
{Id} { popState(); return Parser.IDENTIFIER; }
}
<YYINITIAL, ANNOTATIONNOARG, ANNOTATIONTYPE, ENUM, MODULE, TYPE> {
<YYINITIAL, ANNOTATIONNOARG, ANNOTATIONTYPE, ENUM, MODULE, RECORD, TYPE> {
{Id} { return Parser.IDENTIFIER;
}
}
Expand Down Expand Up @@ -646,12 +647,12 @@ JavadocEnd = "*"+ "/"
}
}

<ASSIGNMENT, YYINITIAL, CODEBLOCK, PARENBLOCK, ENUM, ANNOTATIONTYPE, TYPE> {
<ASSIGNMENT, YYINITIAL, CODEBLOCK, PARENBLOCK, ENUM, ANNOTATIONTYPE, RECORD, TYPE> {
"\"" { if (appendingToCodeBody) { codeBody.append('"'); } pushState(STRING); }
\' { if (appendingToCodeBody) { codeBody.append('\''); } pushState(CHAR); }
}

<ASSIGNMENT, YYINITIAL, CODEBLOCK, PARENBLOCK, ENUM, ANNOTATIONTYPE, ANNOTATION, ATANNOTATION, ARGUMENTS, TYPE, NAME, MODULE > {
<ASSIGNMENT, YYINITIAL, CODEBLOCK, PARENBLOCK, ENUM, ANNOTATIONTYPE, ANNOTATION, ATANNOTATION, ARGUMENTS, RECORD, TYPE, NAME, MODULE > {
"//" { if (appendingToCodeBody) { codeBody.append("//"); } pushState(SINGLELINECOMMENT); }
"/*" { if (appendingToCodeBody) { codeBody.append("/*"); } pushState(MULTILINECOMMENT); }
"/**/" { if (appendingToCodeBody) { codeBody.append("/**/"); } }
Expand Down
48 changes: 41 additions & 7 deletions src/grammar/parser.y
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import java.util.Stack;
%token STAREQUALS SLASHEQUALS PERCENTEQUALS PLUSEQUALS MINUSEQUALS LESSTHAN2EQUALS GREATERTHAN2EQUALS GREATERTHAN3EQUALS AMPERSANDEQUALS CIRCUMFLEXEQUALS VERTLINEEQUALS
%token PACKAGE IMPORT PUBLIC PROTECTED PRIVATE STATIC FINAL ABSTRACT NATIVE STRICTFP SYNCHRONIZED TRANSIENT VOLATILE DEFAULT
%token OPEN MODULE REQUIRES TRANSITIVE EXPORTS OPENS TO USES PROVIDES WITH
%token CLASS INTERFACE ENUM ANNOINTERFACE THROWS EXTENDS IMPLEMENTS SUPER DEFAULT NEW
%token CLASS INTERFACE ENUM RECORD ANNOINTERFACE THROWS EXTENDS IMPLEMENTS SUPER DEFAULT NEW
%token BRACEOPEN BRACECLOSE SQUAREOPEN SQUARECLOSE PARENOPEN PARENCLOSE
%token LESSTHAN GREATERTHAN LESSEQUALS GREATEREQUALS
%token LESSTHAN2 GREATERTHAN2 GREATERTHAN3
Expand Down Expand Up @@ -256,20 +256,22 @@ TypeDeclaration: ClassDeclaration
// ClassDeclaration:
// NormalClassDeclaration
// EnumDeclaration
// RecordDeclaration
ClassDeclaration: NormalClassDeclaration
| EnumDeclaration
| RecordDeclaration
;

// NormalClassDeclaration:
// {ClassModifier} class Identifier [TypeParameters] [Superclass] [Superinterfaces] ClassBody
// {ClassModifier} class Identifier [TypeParameters] [Superclass] [ClassImplements] ClassBody
NormalClassDeclaration: Modifiers_opt CLASS IDENTIFIER
{
cls.setType(ClassDef.CLASS);
cls.setLineNumber(lexer.getLine());
cls.getModifiers().addAll(modifiers); modifiers.clear();
cls.setName( $3 );
}
TypeParameters_opt Superclass_opt Superinterfaces_opt
TypeParameters_opt Superclass_opt ClassImplements_opt
{
cls.setTypeParameters(typeParams);
builder.beginClass(cls);
Expand Down Expand Up @@ -308,9 +310,9 @@ Superclass_opt:
}
;

// Superinterfaces:
// ClassImplements:
// implements InterfaceTypeList
Superinterfaces_opt:
ClassImplements_opt:
| IMPLEMENTS TypeList
{
cls.getImplements().addAll( typeList );
Expand Down Expand Up @@ -609,7 +611,7 @@ ConstructorDeclaration: Modifiers_opt IDENTIFIER
// Primary . [TypeArguments] super ( [ArgumentList] ) ;

// EnumDeclaration:
// {ClassModifier} enum Identifier [Superinterfaces] EnumBody
// {ClassModifier} enum Identifier [ClassImplements] EnumBody
EnumDeclaration: Modifiers_opt ENUM IDENTIFIER
{
cls.setLineNumber(lexer.getLine());
Expand All @@ -620,7 +622,7 @@ EnumDeclaration: Modifiers_opt ENUM IDENTIFIER
cls = new ClassDef();
fieldType = new TypeDef($3, 0);
}
Superinterfaces_opt EnumBody
ClassImplements_opt EnumBody
;

// EnumBody:
Expand Down Expand Up @@ -669,6 +671,38 @@ EnumBodyDeclarations_opt:
| SEMI ClassBodyDeclarations_opt
;

// RecordDeclaration:
// {ClassModifier} record TypeIdentifier [TypeParameters] RecordHeader [ClassImplements] RecordBody
RecordDeclaration: Modifiers_opt RECORD IDENTIFIER TypeParameters_opt RecordHeader ClassImplements_opt RecordBody

// RecordHeader:
// ( [RecordComponentList] )
RecordHeader: PARENOPEN RecordComponentList_opt PARENCLOSE

// RecordComponentList:
// RecordComponent {, RecordComponent}
RecordComponentList: RecordComponentList COMMA RecordComponent
| RecordComponent
;
RecordComponentList_opt:
| RecordComponentList
;

// RecordComponent:
// {RecordComponentModifier} UnannType Identifier
// VariableArityRecordComponent
RecordComponent: Annotations_opt /* ={RecordComponentModifier} */ Type /* =UnannType */ IDENTIFIER
| VariableArityRecordComponent
;

// VariableArityRecordComponent:
// {RecordComponentModifier} UnannType {Annotation} ... Identifier
VariableArityRecordComponent: Annotations_opt /* ={RecordComponentModifier} */ Type /* =UnannType */ DOTDOTDOT IDENTIFIER

// RecordBody:
// { {RecordBodyDeclaration} }
RecordBody: CODEBLOCK

// -----------------------------
// Productions from �9 (Interfaces)
// -----------------------------
Expand Down
141 changes: 141 additions & 0 deletions src/test/java/com/thoughtworks/qdox/RecordsTest.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package com.thoughtworks.qdox;

import java.io.StringReader;

import org.junit.Test;

/**
* Examples from <a href="https://docs.oracle.com/en/java/javase/16/language/records.html">https://docs.oracle.com/en/java/javase/16/language/records.html</a>
*
* @author Robert Scholte
*/
public class RecordsTest
{
private JavaProjectBuilder builder = new JavaProjectBuilder();

@Test
public void withTwoFields() {
String source = "record Rectangle(double length, double width) { }";
builder.addSource( new StringReader(source) );
}

@Test
public void withCanonicalConstructor() {
String source = "record Rectangle(double length, double width) {\n"
+ " public Rectangle(double length, double width) {\n"
+ " if (length <= 0 || width <= 0) {\n"
+ " throw new java.lang.IllegalArgumentException(\n"
+ " String.format(\"Invalid dimensions: %f, %f\", length, width));\n"
+ " }\n"
+ " this.length = length;\n"
+ " this.width = width;\n"
+ " }\n"
+ "}";
builder.addSource( new StringReader(source) );
}

@Test
public void withCompactConstructor() {
String source = "record Rectangle(double length, double width) {\n"
+ " public Rectangle {\n"
+ " if (length <= 0 || width <= 0) {\n"
+ " throw new java.lang.IllegalArgumentException(\n"
+ " String.format(\"Invalid dimensions: %f, %f\", length, width));\n"
+ " }\n"
+ " }\n"
+ "}";
builder.addSource( new StringReader(source) );
}

@Test
public void withPublicAccessorMethod() {
String source = "record Rectangle(double length, double width) {\n"
+ " \n"
+ " // Public accessor method\n"
+ " public double length() {\n"
+ " System.out.println(\"Length is \" + length);\n"
+ " return length;\n"
+ " }\n"
+ "}";
builder.addSource( new StringReader(source) );
}

@Test
public void withStaticMembers() {
String source = "record Rectangle(double length, double width) {\n"
+ " \n"
+ " // Static field\n"
+ " static double goldenRatio;\n"
+ "\n"
+ " // Static initializer\n"
+ " static {\n"
+ " goldenRatio = (1 + Math.sqrt(5)) / 2;\n"
+ " }\n"
+ "\n"
+ " // Static method\n"
+ " public static Rectangle createGoldenRectangle(double width) {\n"
+ " return new Rectangle(width, width * goldenRatio);\n"
+ " }\n"
+ "}";
builder.addSource( new StringReader(source) );
}

@Test
public void withNonStaticMembers() {
String source = "record Rectangle(double length, double width) {\n"
+ "\n"
+ " // Field declarations must be static:\n"
+ " BiFunction<Double, Double, Double> diagonal;\n"
+ "\n"
+ " // Instance initializers are not allowed in records:\n"
+ " {\n"
+ " diagonal = (x, y) -> Math.sqrt(x*x + y*y);\n"
+ " }\n"
+ "}";
builder.addSource( new StringReader(source) );
}

@Test
public void withNestedRecord() {
String source = "record Rectangle(double length, double width) {\n"
+ "\n"
+ " // Nested record class\n"
+ " record RotationAngle(double angle) {\n"
+ " public RotationAngle {\n"
+ " angle = Math.toRadians(angle);\n"
+ " }\n"
+ " }\n"
+ " \n"
+ " // Public instance method\n"
+ " public Rectangle getRotatedRectangleBoundingBox(double angle) {\n"
+ " RotationAngle ra = new RotationAngle(angle);\n"
+ " double x = Math.abs(length * Math.cos(ra.angle())) +\n"
+ " Math.abs(width * Math.sin(ra.angle()));\n"
+ " double y = Math.abs(length * Math.sin(ra.angle())) +\n"
+ " Math.abs(width * Math.cos(ra.angle()));\n"
+ " return new Rectangle(x, y);\n"
+ " }\n"
+ "}";
builder.addSource( new StringReader(source) );
}

@Test
public void withGenerics() {
String source = "record Triangle<C extends Coordinate> (C top, C left, C right) { }";
builder.addSource( new StringReader(source) );
}

@Test
public void withInterface() {
String source = "record Customer(String... data) implements Billable { }";
builder.addSource( new StringReader(source) );
}

@Test
public void withAnnotatedParameters() {
String source = "record Rectangle(\n"
+ " @GreaterThanZero double length,\n"
+ " @GreaterThanZero double width) { }";
builder.addSource( new StringReader(source) );
}
}

0 comments on commit c3aac2d

Please # to comment.