From 4ac3433657cc3c8d6f467216700cf14592e3d4c5 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Tue, 24 May 2022 15:46:13 +0200 Subject: [PATCH 01/10] Added and refactored the R frontend to match the current frontend interface --- jplag.frontend.R/pom.xml | 39 ++ .../src/main/antlr4/de/jplag/R/grammar/R.g4 | 216 ++++++++++ .../main/antlr4/de/jplag/R/grammar/RFilter.g4 | 83 ++++ .../main/java/de/jplag/R/JplagRListener.java | 368 ++++++++++++++++++ .../src/main/java/de/jplag/R/Language.java | 68 ++++ .../main/java/de/jplag/R/RParserAdapter.java | 80 ++++ .../src/main/java/de/jplag/R/RToken.java | 96 +++++ .../main/java/de/jplag/R/RTokenConstants.java | 38 ++ jplag/pom.xml | 4 + .../java/de/jplag/options/LanguageOption.java | 1 + pom.xml | 11 + 11 files changed, 1004 insertions(+) create mode 100644 jplag.frontend.R/pom.xml create mode 100644 jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/R.g4 create mode 100644 jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 create mode 100644 jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java create mode 100644 jplag.frontend.R/src/main/java/de/jplag/R/Language.java create mode 100644 jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java create mode 100644 jplag.frontend.R/src/main/java/de/jplag/R/RToken.java create mode 100644 jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java diff --git a/jplag.frontend.R/pom.xml b/jplag.frontend.R/pom.xml new file mode 100644 index 000000000..9e3cf5691 --- /dev/null +++ b/jplag.frontend.R/pom.xml @@ -0,0 +1,39 @@ + + + 4.0.0 + + de.jplag + aggregator + ${revision} + + R + + + + org.antlr + antlr4-runtime + + + de.jplag + frontend-utils + + + + + + + org.antlr + antlr4-maven-plugin + 4.10.1 + + + + antlr4 + + + + + + + + diff --git a/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/R.g4 b/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/R.g4 new file mode 100644 index 000000000..73bd2389c --- /dev/null +++ b/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/R.g4 @@ -0,0 +1,216 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 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. +*/ + +/** +derived from http://svn.r-project.org/R/trunk/src/main/gram.y +http://cran.r-project.org/doc/manuals/R-lang.html#Parser +I'm no R genius but this seems to work. +Requires RFilter.g4 to strip away NL that are really whitespace, +not end-of-command. See TestR.java +Usage: +$ antlr4 R.g4 RFilter.g4 +$ javac *.java +$ java TestR sample.R +... prints parse tree ... +*/ + +/* +Modified version of the original in https://github.com/antlr/grammars-v4/blob/master/r/R.g4 so that I can separate the most relevant tokens of R in +the JplagRListenter.java file. +Author of the modification: Antonio Javier Rodriguez Perez +*/ + +grammar R; + +prog: ( expr (';'|NL) + | NL + )* + EOF + ; + +/* +expr_or_assign + : expr ('<-'|'='|'<<-') expr_or_assign + | expr + ; +*/ + +expr: expr index_statement // '[[' follows R's yacc grammar + | expr access_package expr + | expr ('$'|'@') expr + | expr '^' expr + | ('-'|'+') expr + | expr ':' expr + | expr USER_OP expr // anything wrappedin %: '%' .* '%' + | expr ('*'|'/') expr + | expr ('+'|'-') expr + | expr ('>'|'>='|'<'|'<='|'=='|'!=') expr + | '!' expr + | expr ('&'|'&&') expr + | expr ('|'|'||') expr + | '~' expr + | expr '~' expr + | expr assign_value expr + | function_definition // define function + | expr function_call // call function + | compound_statement + | if_statement + | for_statement + | while_statement + | repeat_statement + | help + | next_statement + | break_statement + | '(' expr ')' + | ID + | constant + ; + +index_statement : '[[' sublist ']' ']' | '[' sublist ']' ; + +access_package: '::'|':::' ; + +function_definition: 'function' '(' formlist? ')' expr ; + +function_call : '(' sublist ')' ; + +constant: constant_number | constant_string | constant_bool | 'NULL' | 'NA' | 'Inf' | 'NaN' ; + +constant_number: HEX | INT | FLOAT | COMPLEX ; + +constant_string: STRING ; + +constant_bool: 'TRUE' | 'FALSE' ; + +help: '?' expr ; // get help on expr, usually string or ID + +if_statement : 'if' '(' expr ')' expr | 'if' '(' expr ')' expr 'else' expr ; + +for_statement : 'for' '(' ID 'in' expr ')' expr ; + +while_statement : 'while' '(' expr ')' expr ; + +repeat_statement: 'repeat' expr ; + +next_statement: 'next' ; + +break_statement: 'break' ; + +compound_statement: '{' exprlist '}' ; + +exprlist + : expr ((';'|NL) expr?)* + | + ; + +formlist : form (',' form)* ; + +form: ID + | assign_func_declaration + ; + +sublist : sub (',' sub)* ; + +sub : expr + | assign_value_list + | + ; + +assign_value: '<-'|'<<-'|'='|'->'|'->>'|':='; + +assign_func_declaration: ID '=' expr | '...' ; + +assign_value_list: ID '=' | ID '=' expr | constant_string '=' | constant_string '=' expr | 'NULL' '=' | 'NULL' '=' expr | '...' ; + + + +HEX : '0' ('x'|'X') HEXDIGIT+ [Ll]? ; + +INT : DIGIT+ [Ll]? ; + +fragment +HEXDIGIT : ('0'..'9'|'a'..'f'|'A'..'F') ; + +FLOAT: DIGIT+ '.' DIGIT* EXP? [Ll]? + | DIGIT+ EXP? [Ll]? + | '.' DIGIT+ EXP? [Ll]? + ; + +fragment +DIGIT: '0'..'9' ; + +fragment +EXP : ('E' | 'e') ('+' | '-')? INT ; + +COMPLEX + : INT 'i' + | FLOAT 'i' + ; + +STRING + : '"' ( ESC | ~[\\"] )*? '"' + | '\'' ( ESC | ~[\\'] )*? '\'' + | '`' ( ESC | ~[\\'] )*? '`' + ; +fragment +ESC : '\\' [abtnfrv"'\\] + | UNICODE_ESCAPE + | HEX_ESCAPE + | OCTAL_ESCAPE + ; + +fragment +UNICODE_ESCAPE + : '\\' 'u' HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT + | '\\' 'u' '{' HEXDIGIT HEXDIGIT HEXDIGIT HEXDIGIT '}' + ; + +fragment +OCTAL_ESCAPE + : '\\' [0-3] [0-7] [0-7] + | '\\' [0-7] [0-7] + | '\\' [0-7] + ; + +fragment +HEX_ESCAPE + : '\\' HEXDIGIT HEXDIGIT? + ; + +ID : '.' (LETTER|'_'|'.') (LETTER|DIGIT|'_'|'.')* + | LETTER (LETTER|DIGIT|'_'|'.')* + ; + +fragment LETTER : [a-zA-Z] ; + +USER_OP : '%' .*? '%' ; + +COMMENT : '#' .*? '\r'? '\n' -> type(NL) ; + +// Match both UNIX and Windows newlines +NL : '\r'? '\n' ; + +WS : [ \t\u000C]+ -> skip ; \ No newline at end of file diff --git a/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 b/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 new file mode 100644 index 000000000..d66b85aa2 --- /dev/null +++ b/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 @@ -0,0 +1,83 @@ +/* + [The "BSD licence"] + Copyright (c) 2013 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. +*/ + +/** Must process R input with this before passing to R.g4; see TestR.java + We strip NL inside expressions. + */ + +parser grammar RFilter; + +options { tokenVocab=R; } + +@members { +protected int curlies = 0; +} + +// TODO: MAKE THIS GET ONE COMMAND ONLY +stream : (element|NL|';')* EOF ; + +eat : (NL {((WritableToken)$NL).setChannel(Token.HIDDEN_CHANNEL);})+ ; + +element: op eat? + | atom + | '{' eat? {curlies++;} (element|NL|';')* {curlies--;} '}' + | '(' (element|eat)* ')' + | '[' (element|eat)* ']' + | '[[' (element|eat)* ']' ']' + | 'function' eat? '(' (element|eat)* ')' eat? + | 'for' eat? '(' (element|eat)* ')' eat? + | 'while' eat? '(' (element|eat)* ')' eat? + | 'if' eat? '(' (element|eat)* ')' eat? + | 'else' + { + // ``inside a compound expression, a newline before else is discarded, + // whereas at the outermost level, the newline terminates the if + // construction and a subsequent else causes a syntax error.'' + /* + Works here + if (1==0) { print(1) } else { print(2) } + and correctly gets error here: + if (1==0) { print(1) } + else { print(2) } + this works too: + if (1==0) { + if (2==0) print(1) + else print(2) + } + */ + WritableToken tok = (WritableToken)_input.LT(-2); + if (curlies>0&&tok.getType()==NL) tok.setChannel(Token.HIDDEN_CHANNEL); + } + ; + +atom: 'next' | 'break' | ID | STRING | HEX | INT | FLOAT | COMPLEX | 'NULL' + | 'NA' | 'Inf' | 'NaN' | 'TRUE' | 'FALSE' + ; + +op : '+'|'-'|'*'|'/'|'^'|'<'|'<='|'>='|'>'|'=='|'!='|'&'|'&&'|USER_OP| + 'repeat'|'in'|'?'|'!'|'='|':'|'~'|'$'|'@'|'<-'|'->'|'='|'::'|':::'| + ','|'...'|'||'| '|' + ; \ No newline at end of file diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java new file mode 100644 index 000000000..5817f1826 --- /dev/null +++ b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java @@ -0,0 +1,368 @@ +package de.jplag.R; + +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.Token; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +import de.jplag.R.grammar.RFilter; +import de.jplag.R.grammar.RFilterListener; +import de.jplag.R.grammar.RListener; +import de.jplag.R.grammar.RParser; + +/* + * This class is responsible for implementing the RListener methods generated by ANTLR4 in such a way that every time a + * token of the language is recognized, the corresponding "enter" and "exit" methods will be called. If it is an + * important token (i.e. it is important to consider it when identifying plagiarisms), then all there is to do is to + * call "parser.add" in its corresponding method using its constant. Esta clase se encarga de implementar los métodos + * clase RListener que se genera por ANTLR4 de forma que cada vez que se reconozca un Token del lenguaje se entrará en + * su "enter" y "exit" y en caso de ser un token importante (es decir, que sea relevante + * considerarlo a la hora de identificar plagio) tan solo tendremos que irnos a su método he implementarlo haciendo un + * parser.add o un parser.addEnd (en caso de que sea un exit) con el nombre del token que le hayamos asociado en + * RToken.java y RTokenConstants.java . + */ + +public class JplagRListener implements RListener, RFilterListener, RTokenConstants { + + private final RParserAdapter parserAdapter; + + public JplagRListener(RParserAdapter parserAdapter) { + this.parserAdapter = parserAdapter; + } + + private void transformToken(int targetType, Token token) { + parserAdapter.addToken(targetType, token.getLine(), token.getCharPositionInLine() + 1, token.getText().length()); + } + + @Override + public void enterProg(RParser.ProgContext ctx) { + + } + + @Override + public void exitProg(RParser.ProgContext ctx) { + + } + + @Override + public void enterExpr(RParser.ExprContext ctx) { + + } + + @Override + public void exitExpr(RParser.ExprContext ctx) { + + } + + @Override + public void enterIndex_statement(RParser.Index_statementContext ctx) { + transformToken(INDEX, ctx.getStart()); + } + + @Override + public void exitIndex_statement(RParser.Index_statementContext ctx) { + + } + + @Override + public void enterAccess_package(RParser.Access_packageContext ctx) { + transformToken(PACKAGE, ctx.getStart()); + } + + @Override + public void exitAccess_package(RParser.Access_packageContext ctx) { + + } + + @Override + public void enterFunction_definition(RParser.Function_definitionContext ctx) { + transformToken(BEGIN_FUNCTION, ctx.getStart()); + } + + @Override + public void exitFunction_definition(RParser.Function_definitionContext ctx) { + transformToken(END_FUNCTION, ctx.getStart()); + } + + @Override + public void enterFunction_call(RParser.Function_callContext ctx) { + transformToken(FUNCTION_CALL, ctx.getStart()); + } + + @Override + public void exitFunction_call(RParser.Function_callContext ctx) { + + } + + @Override + public void enterConstant(RParser.ConstantContext ctx) { + + } + + @Override + public void exitConstant(RParser.ConstantContext ctx) { + + } + + @Override + public void enterConstant_number(RParser.Constant_numberContext ctx) { + transformToken(NUMBER, ctx.getStart()); + } + + @Override + public void exitConstant_number(RParser.Constant_numberContext ctx) { + + } + + @Override + public void enterConstant_string(RParser.Constant_stringContext ctx) { + transformToken(STRING, ctx.getStart()); + } + + @Override + public void exitConstant_string(RParser.Constant_stringContext ctx) { + + } + + @Override + public void enterConstant_bool(RParser.Constant_boolContext ctx) { + transformToken(BOOL, ctx.getStart()); + } + + @Override + public void exitConstant_bool(RParser.Constant_boolContext ctx) { + + } + + @Override + public void enterHelp(RParser.HelpContext ctx) { + transformToken(HELP, ctx.getStart()); + } + + @Override + public void exitHelp(RParser.HelpContext ctx) { + + } + + @Override + public void enterIf_statement(RParser.If_statementContext ctx) { + transformToken(IF_BEGIN, ctx.getStart()); + } + + @Override + public void exitIf_statement(RParser.If_statementContext ctx) { + transformToken(IF_END, ctx.getStart()); + } + + @Override + public void enterFor_statement(RParser.For_statementContext ctx) { + transformToken(FOR_BEGIN, ctx.getStart()); + } + + @Override + public void exitFor_statement(RParser.For_statementContext ctx) { + transformToken(FOR_END, ctx.getStart()); + } + + @Override + public void enterWhile_statement(RParser.While_statementContext ctx) { + transformToken(WHILE_BEGIN, ctx.getStart()); + } + + @Override + public void exitWhile_statement(RParser.While_statementContext ctx) { + transformToken(WHILE_END, ctx.getStart()); + } + + @Override + public void enterRepeat_statement(RParser.Repeat_statementContext ctx) { + transformToken(REPEAT_BEGIN, ctx.getStart()); + } + + @Override + public void exitRepeat_statement(RParser.Repeat_statementContext ctx) { + transformToken(REPEAT_END, ctx.getStart()); + } + + @Override + public void enterNext_statement(RParser.Next_statementContext ctx) { + transformToken(NEXT, ctx.getStart()); + } + + @Override + public void exitNext_statement(RParser.Next_statementContext ctx) { + + } + + @Override + public void enterBreak_statement(RParser.Break_statementContext ctx) { + transformToken(BREAK, ctx.getStart()); + } + + @Override + public void exitBreak_statement(RParser.Break_statementContext ctx) { + + } + + @Override + public void enterCompound_statement(RParser.Compound_statementContext ctx) { + transformToken(COMPOUND_BEGIN, ctx.getStart()); + } + + @Override + public void exitCompound_statement(RParser.Compound_statementContext ctx) { + transformToken(COMPOUND_END, ctx.getStart()); + } + + @Override + public void enterExprlist(RParser.ExprlistContext ctx) { + + } + + @Override + public void exitExprlist(RParser.ExprlistContext ctx) { + + } + + @Override + public void enterFormlist(RParser.FormlistContext ctx) { + + } + + @Override + public void exitFormlist(RParser.FormlistContext ctx) { + + } + + @Override + public void enterForm(RParser.FormContext ctx) { + + } + + @Override + public void exitForm(RParser.FormContext ctx) { + + } + + @Override + public void enterSublist(RParser.SublistContext ctx) { + + } + + @Override + public void exitSublist(RParser.SublistContext ctx) { + + } + + @Override + public void enterSub(RParser.SubContext ctx) { + + } + + @Override + public void exitSub(RParser.SubContext ctx) { + + } + + @Override + public void enterAssign_value(RParser.Assign_valueContext ctx) { + transformToken(ASSIGN, ctx.getStart()); + } + + @Override + public void exitAssign_value(RParser.Assign_valueContext ctx) { + + } + + @Override + public void enterAssign_func_declaration(RParser.Assign_func_declarationContext ctx) { + transformToken(ASSIGN_FUNC, ctx.getStart()); + } + + @Override + public void exitAssign_func_declaration(RParser.Assign_func_declarationContext ctx) { + + } + + @Override + public void enterAssign_value_list(RParser.Assign_value_listContext ctx) { + transformToken(ASSIGN_LIST, ctx.getStart()); + } + + @Override + public void exitAssign_value_list(RParser.Assign_value_listContext ctx) { + + } + + @Override + public void enterStream(RFilter.StreamContext ctx) { + + } + + @Override + public void exitStream(RFilter.StreamContext ctx) { + + } + + @Override + public void enterEat(RFilter.EatContext ctx) { + + } + + @Override + public void exitEat(RFilter.EatContext ctx) { + + } + + @Override + public void enterElem(RFilter.ElemContext ctx) { + + } + + @Override + public void exitElem(RFilter.ElemContext ctx) { + + } + + @Override + public void enterAtom(RFilter.AtomContext ctx) { + + } + + @Override + public void exitAtom(RFilter.AtomContext ctx) { + + } + + @Override + public void enterOp(RFilter.OpContext ctx) { + + } + + @Override + public void exitOp(RFilter.OpContext ctx) { + + } + + @Override + public void enterEveryRule(ParserRuleContext ctx) { + + } + + @Override + public void exitEveryRule(ParserRuleContext ctx) { + + } + + @Override + public void visitTerminal(TerminalNode node) { + if (node.getText().equals("=")) { + transformToken(ASSIGN, node.getSymbol()); + } + } + + @Override + public void visitErrorNode(ErrorNode node) { + + } +} \ No newline at end of file diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/Language.java b/jplag.frontend.R/src/main/java/de/jplag/R/Language.java new file mode 100644 index 000000000..ba5038722 --- /dev/null +++ b/jplag.frontend.R/src/main/java/de/jplag/R/Language.java @@ -0,0 +1,68 @@ +package de.jplag.R; + +import java.io.File; + +import de.jplag.ErrorConsumer; +import de.jplag.TokenList; + +public class Language implements de.jplag.Language { + + public static final String NAME = "R Parser"; + public static final String SHORT_NAME = "R"; + public static final int DEFAULT_MIN_TOKEN_MATCH = 8; + private RParserAdapter parserAdapter; + + public Language(ErrorConsumer consumer) { + this.parserAdapter = new RParserAdapter(consumer); + } + + @Override + public String[] suffixes() { + return new String[] {".R", ".r"}; + } + + @Override + public String getName() { + return NAME; + } + + @Override + public String getShortName() { + return SHORT_NAME; + } + + @Override + public int minimumTokenMatch() { + return DEFAULT_MIN_TOKEN_MATCH; + } + + @Override + public TokenList parse(File directory, String[] files) { + return parserAdapter.parse(directory, files); + } + + @Override + public boolean hasErrors() { + return parserAdapter.hasErrors(); + } + + @Override + public boolean supportsColumns() { + return true; + } + + @Override + public boolean isPreformatted() { + return true; + } + + @Override + public boolean usesIndex() { + return false; + } + + @Override + public int numberOfTokens() { + return RTokenConstants.NUM_DIFF_TOKENS; + } +} diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java b/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java new file mode 100644 index 000000000..861048b63 --- /dev/null +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java @@ -0,0 +1,80 @@ +package de.jplag.R; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; + +import org.antlr.v4.runtime.CharStreams; +import org.antlr.v4.runtime.CommonTokenStream; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ParseTree; +import org.antlr.v4.runtime.tree.ParseTreeWalker; + +import de.jplag.AbstractParser; +import de.jplag.ErrorConsumer; +import de.jplag.R.grammar.RFilter; +import de.jplag.R.grammar.RLexer; +import de.jplag.R.grammar.RParser; +import de.jplag.TokenList; + +/** + * This class sets up the lexer and parser generated by ANTLR4, feeds the submissions through them and passes the + * selected tokens on to the main program. + */ +public class RParserAdapter extends AbstractParser implements RTokenConstants { + + private String currentFile; + private TokenList tokens; + + public RParserAdapter(ErrorConsumer errorConsumer) { + super(errorConsumer); + } + + public TokenList parse(File directory, String[] fileNames) { + tokens = new TokenList(); + errors = 0; + for (String fileName : fileNames) { + if (!parseFile(directory, fileName)) { + errors++; + } + tokens.addToken(new RToken(FILE_END, fileName, -1, -1, -1)); + } + return tokens; + } + + private boolean parseFile(File directory, String fileName) { + File file = new File(directory, fileName); + try (FileInputStream inputStream = new FileInputStream(file)) { + currentFile = fileName; + + // create a lexer, a parser and a buffer between them. + RLexer lexer = new RLexer(CharStreams.fromStream(inputStream)); + CommonTokenStream tokens = new CommonTokenStream(lexer); + + RFilter filter = new RFilter(tokens); + filter.stream(); + tokens.seek(0); + + RParser parser = new RParser(tokens); + + // Create a tree walker and the entry context defined by the parser grammar + ParserRuleContext entryContext = parser.prog(); + ParseTreeWalker treeWalker = new ParseTreeWalker(); + + // Walk over the parse tree: + for (int i = 0; i < entryContext.getChildCount(); i++) { + ParseTree parseTree = entryContext.getChild(i); + treeWalker.walk(new JplagRListener(this), parseTree); + } + } catch (IOException exception) { + getErrorConsumer().addError("Parsing Error in '" + fileName + "':" + File.separator + exception); + return false; + } + return true; + } + + /* package-private */ void addToken(int type, int line, int start, int end) { + tokens.addToken(new RToken(type, currentFile, line, start, end - start)); + + } +} diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java new file mode 100644 index 000000000..85998aeb2 --- /dev/null +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java @@ -0,0 +1,96 @@ +package de.jplag.R; + +public class RToken extends de.jplag.Token implements RTokenConstants { + private static final long serialVersionUID = 1L; + private int line, column, length; + + public RToken(int type, String file, int line, int column, int length) { + super(type, file, line, column, length); + } + + public int getLine() { + return line; + } + + public int getColumn() { + return column; + } + + public int getLength() { + return length; + } + + public void setLine(int line) { + this.line = line; + } + + public void setColumn(int column) { + this.column = column; + } + + public void setLength(int length) { + this.length = length; + } + + @Override + public String type2string() { + switch (this.type) { + case FILE_END: + return "***********"; + case SEPARATOR_TOKEN: + return "METHOD_SEPARATOR"; + case BEGIN_FUNCTION: + return "FUNCTION{ "; + case END_FUNCTION: + return "}FUNCTION "; + case FUNCTION_CALL: + return "FUNCTION() "; + case NUMBER: + return "NUMBER "; + case STRING: + return "STRING "; + case BOOL: + return "BOOL "; + case ASSIGN: + return "ASSIGN "; + case ASSIGN_FUNC: + return "ASSIGN_FUNC"; + case ASSIGN_LIST: + return "ASSIGN_LIST"; + case HELP: + return "HELP "; + case INDEX: + return "INDEX "; + case PACKAGE: + return "PACKAGE "; + case IF_BEGIN: + return "IF{ "; + case IF_END: + return "}IF "; + case FOR_BEGIN: + return "FOR{ "; + case FOR_END: + return "}FOR "; + case WHILE_BEGIN: + return "WHILE{ "; + case WHILE_END: + return "}WHILE "; + case REPEAT_BEGIN: + return "REPEAT{ "; + case REPEAT_END: + return "}REPEAT "; + case NEXT: + return "NEXT "; + case BREAK: + return "BREAK "; + case COMPOUND_BEGIN: + return "COMPOUND{ "; + case COMPOUND_END: + return "}COMPOUND "; + default: + System.err.println("*UNKNOWN: " + type); + return "*UNKNOWN" + type; + } + } + +} diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java new file mode 100644 index 000000000..5133b7bce --- /dev/null +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java @@ -0,0 +1,38 @@ +package de.jplag.R; + +import de.jplag.TokenConstants; + +/** + * Tokens in R that are deemed important when comparing submissions for plagiarisms + */ +public interface RTokenConstants extends TokenConstants { + int FILE_END = 0; + int SEPARATOR_TOKEN = 1; + + int BEGIN_FUNCTION = 2; + int END_FUNCTION = 3; + int FUNCTION_CALL = 4; + int NUMBER = 5; + int STRING = 6; + int BOOL = 7; + int ASSIGN = 8; + int ASSIGN_FUNC = 9; + int ASSIGN_LIST = 10; + int HELP = 11; + int INDEX = 12; + int PACKAGE = 13; + int IF_BEGIN = 14; + int IF_END = 15; + int FOR_BEGIN = 16; + int FOR_END = 17; + int WHILE_BEGIN = 18; + int WHILE_END = 19; + int REPEAT_BEGIN = 20; + int REPEAT_END = 21; + int NEXT = 22; + int BREAK = 23; + int COMPOUND_BEGIN = 24; + int COMPOUND_END = 25; + + int NUM_DIFF_TOKENS = 26; +} diff --git a/jplag/pom.xml b/jplag/pom.xml index 425b63ae6..112fe7055 100644 --- a/jplag/pom.xml +++ b/jplag/pom.xml @@ -45,6 +45,10 @@ de.jplag cpp + + de.jplag + R + de.jplag scheme diff --git a/jplag/src/main/java/de/jplag/options/LanguageOption.java b/jplag/src/main/java/de/jplag/options/LanguageOption.java index d3a313e83..99b715263 100644 --- a/jplag/src/main/java/de/jplag/options/LanguageOption.java +++ b/jplag/src/main/java/de/jplag/options/LanguageOption.java @@ -13,6 +13,7 @@ public enum LanguageOption { PYTHON_3("python3", "de.jplag.python3.Language"), C_CPP("cpp", "de.jplag.cpp.Language"), C_SHARP("csharp", "de.jplag.csharp.Language"), + R("R", "de.jplag.R.Language"), CHAR("char", "de.jplag.chars.Language"), TEXT("text", "de.jplag.text.Language"), SCHEME("scheme", "de.jplag.scheme.Language"); diff --git a/pom.xml b/pom.xml index f8e1d74bb..2b4b1c010 100644 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ jplag.frontend.csharp-6 jplag.frontend.java jplag.frontend.python-3 + jplag.frontend.R jplag.frontend.scheme jplag.frontend.text jplag @@ -156,6 +157,16 @@ csharp-6 ${project.version} + + ${project.groupId} + R + ${project.version} + + + ${project.groupId} + scala + ${project.version} + ${project.groupId} cpp From 9c4e3a09a3156105d3ba20824b243586cbe6f70b Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Mon, 30 May 2022 18:08:38 +0200 Subject: [PATCH 02/10] Added documentation for R module --- .../main/java/de/jplag/R/JplagRListener.java | 28 +++-- .../src/main/java/de/jplag/R/Language.java | 5 +- .../main/java/de/jplag/R/RParserAdapter.java | 17 +++ .../src/main/java/de/jplag/R/RToken.java | 117 +++++------------- .../main/java/de/jplag/R/RTokenConstants.java | 4 +- 5 files changed, 73 insertions(+), 98 deletions(-) diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java index 5817f1826..cf8afdc0d 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java @@ -10,26 +10,28 @@ import de.jplag.R.grammar.RListener; import de.jplag.R.grammar.RParser; -/* - * This class is responsible for implementing the RListener methods generated by ANTLR4 in such a way that every time a - * token of the language is recognized, the corresponding "enter" and "exit" methods will be called. If it is an - * important token (i.e. it is important to consider it when identifying plagiarisms), then all there is to do is to - * call "parser.add" in its corresponding method using its constant. Esta clase se encarga de implementar los métodos - * clase RListener que se genera por ANTLR4 de forma que cada vez que se reconozca un Token del lenguaje se entrará en - * su "enter" y "exit" y en caso de ser un token importante (es decir, que sea relevante - * considerarlo a la hora de identificar plagio) tan solo tendremos que irnos a su método he implementarlo haciendo un - * parser.add o un parser.addEnd (en caso de que sea un exit) con el nombre del token que le hayamos asociado en - * RToken.java y RTokenConstants.java . +/** + * Listener class for visiting the R ANTLR parse tree. Transforms the visited ANTLR token into JPlag tokens. + * Based on an R frontend for JPlag v2.15 by Olmo Kramer, see their JPlag fork. + * @author Robin Maisch */ - public class JplagRListener implements RListener, RFilterListener, RTokenConstants { private final RParserAdapter parserAdapter; + /** + * Creates the listener. + * @param parserAdapter the JPlag parser adapter which receives the transformed tokens. + */ public JplagRListener(RParserAdapter parserAdapter) { this.parserAdapter = parserAdapter; } + /** + * Transforms an ANTLR Token into a JPlag token and transfers it to the token adapter. + * @param targetType the type of the JPlag token to be created. + * @param token the ANTLR token. + */ private void transformToken(int targetType, Token token) { parserAdapter.addToken(targetType, token.getLine(), token.getCharPositionInLine() + 1, token.getText().length()); } @@ -315,12 +317,12 @@ public void exitEat(RFilter.EatContext ctx) { } @Override - public void enterElem(RFilter.ElemContext ctx) { + public void enterElement(RFilter.ElementContext ctx) { } @Override - public void exitElem(RFilter.ElemContext ctx) { + public void exitElement(RFilter.ElementContext ctx) { } diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/Language.java b/jplag.frontend.R/src/main/java/de/jplag/R/Language.java index ba5038722..34d694344 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/Language.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/Language.java @@ -5,12 +5,15 @@ import de.jplag.ErrorConsumer; import de.jplag.TokenList; +/** + * This represents the R language as a language supported by JPlag. + */ public class Language implements de.jplag.Language { public static final String NAME = "R Parser"; public static final String SHORT_NAME = "R"; public static final int DEFAULT_MIN_TOKEN_MATCH = 8; - private RParserAdapter parserAdapter; + private final RParserAdapter parserAdapter; public Language(ErrorConsumer consumer) { this.parserAdapter = new RParserAdapter(consumer); diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java b/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java index 861048b63..b9bb2ccbd 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java @@ -26,10 +26,20 @@ public class RParserAdapter extends AbstractParser implements RTokenConstants { private String currentFile; private TokenList tokens; + /** + * Creates the RParserAdapter + * @param errorConsumer the ErrorConsumer that parser errors are passed on to. + */ public RParserAdapter(ErrorConsumer errorConsumer) { super(errorConsumer); } + /** + * Parsers a list of files into a single {@link TokenList}. + * @param directory the directory of the files. + * @param fileNames the file names of the files. + * @return a {@link TokenList} containing all tokens of all files. + */ public TokenList parse(File directory, String[] fileNames) { tokens = new TokenList(); errors = 0; @@ -73,6 +83,13 @@ private boolean parseFile(File directory, String fileName) { return true; } + /** + * Adds a new {@link de.jplag.Token} to the current {@link TokenList}. + * @param type the type of the new {@link de.jplag.Token} + * @param line the line of the Token in the current file + * @param start the start column of the Token in the line + * @param end the end column of the Token in the line + */ /* package-private */ void addToken(int type, int line, int start, int end) { tokens.addToken(new RToken(type, currentFile, line, start, end - start)); diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java index 85998aeb2..d370abda7 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java @@ -1,96 +1,47 @@ package de.jplag.R; +/** + * This class represents the occurrence of an R Token in the source code. + * Based on an R frontend for JPlag v2.15 by Olmo Kramer, see their JPlag fork. + * @author Robin Maisch + */ public class RToken extends de.jplag.Token implements RTokenConstants { - private static final long serialVersionUID = 1L; - private int line, column, length; public RToken(int type, String file, int line, int column, int length) { super(type, file, line, column, length); } - public int getLine() { - return line; - } - - public int getColumn() { - return column; - } - - public int getLength() { - return length; - } - - public void setLine(int line) { - this.line = line; - } - - public void setColumn(int column) { - this.column = column; - } - - public void setLength(int length) { - this.length = length; - } - @Override public String type2string() { - switch (this.type) { - case FILE_END: - return "***********"; - case SEPARATOR_TOKEN: - return "METHOD_SEPARATOR"; - case BEGIN_FUNCTION: - return "FUNCTION{ "; - case END_FUNCTION: - return "}FUNCTION "; - case FUNCTION_CALL: - return "FUNCTION() "; - case NUMBER: - return "NUMBER "; - case STRING: - return "STRING "; - case BOOL: - return "BOOL "; - case ASSIGN: - return "ASSIGN "; - case ASSIGN_FUNC: - return "ASSIGN_FUNC"; - case ASSIGN_LIST: - return "ASSIGN_LIST"; - case HELP: - return "HELP "; - case INDEX: - return "INDEX "; - case PACKAGE: - return "PACKAGE "; - case IF_BEGIN: - return "IF{ "; - case IF_END: - return "}IF "; - case FOR_BEGIN: - return "FOR{ "; - case FOR_END: - return "}FOR "; - case WHILE_BEGIN: - return "WHILE{ "; - case WHILE_END: - return "}WHILE "; - case REPEAT_BEGIN: - return "REPEAT{ "; - case REPEAT_END: - return "}REPEAT "; - case NEXT: - return "NEXT "; - case BREAK: - return "BREAK "; - case COMPOUND_BEGIN: - return "COMPOUND{ "; - case COMPOUND_END: - return "}COMPOUND "; - default: - System.err.println("*UNKNOWN: " + type); - return "*UNKNOWN" + type; - } + return switch (this.type) { + case FILE_END -> "***********"; + case SEPARATOR_TOKEN -> "METHOD_SEPARATOR"; + case BEGIN_FUNCTION -> "FUNCTION{"; + case END_FUNCTION -> "}FUNCTION"; + case FUNCTION_CALL -> "FUNCTION()"; + case NUMBER -> "NUMBER"; + case STRING -> "STRING"; + case BOOL -> "BOOL"; + case ASSIGN -> "ASSIGN"; + case ASSIGN_FUNC -> "ASSIGN_FUNC"; + case ASSIGN_LIST -> "ASSIGN_LIST"; + case HELP -> "HELP"; + case INDEX -> "INDEX"; + case PACKAGE -> "PACKAGE"; + case IF_BEGIN -> "IF{"; + case IF_END -> "}IF"; + case FOR_BEGIN -> "FOR{"; + case FOR_END -> "}FOR"; + case WHILE_BEGIN -> "WHILE{"; + case WHILE_END -> "}WHILE"; + case REPEAT_BEGIN -> "REPEAT{"; + case REPEAT_END -> "}REPEAT"; + case NEXT -> "NEXT"; + case BREAK -> "BREAK"; + case COMPOUND_BEGIN -> "COMPOUND{"; + case COMPOUND_END -> "}COMPOUND"; + default -> "".formatted(type); + }; } } diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java index 5133b7bce..a884e4a1f 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java @@ -3,7 +3,9 @@ import de.jplag.TokenConstants; /** - * Tokens in R that are deemed important when comparing submissions for plagiarisms + * Tokens in R that are deemed important when comparing submissions for plagiarisms. + * Based on an R frontend for JPlag v2.15 by Olmo Kramer, see their JPlag fork. + * @author Robin Maisch */ public interface RTokenConstants extends TokenConstants { int FILE_END = 0; From 395d0de892705ca4fc7bbf14e83cc268e85372a9 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Tue, 14 Jun 2022 11:35:19 +0200 Subject: [PATCH 03/10] Debug R frontend; add tests --- jplag.frontend.R/README.md | 9 ++ jplag.frontend.R/pom.xml | 7 ++ .../main/java/de/jplag/R/JplagRListener.java | 24 ++-- .../main/java/de/jplag/R/RParserAdapter.java | 6 +- .../src/main/java/de/jplag/R/RToken.java | 4 +- .../test/java/de/jplag/R/RFrontendTest.java | 108 ++++++++++++++++++ .../src/test/resources/de/jplag/R/Complete.R | 37 ++++++ .../src/test/resources/de/jplag/R/Game.R | 92 +++++++++++++++ 8 files changed, 271 insertions(+), 16 deletions(-) create mode 100644 jplag.frontend.R/README.md create mode 100644 jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java create mode 100644 jplag.frontend.R/src/test/resources/de/jplag/R/Complete.R create mode 100644 jplag.frontend.R/src/test/resources/de/jplag/R/Game.R diff --git a/jplag.frontend.R/README.md b/jplag.frontend.R/README.md new file mode 100644 index 000000000..15b74bff1 --- /dev/null +++ b/jplag.frontend.R/README.md @@ -0,0 +1,9 @@ +# JPlag R language frontend + +The JPlag R frontend allows the use of JPlag with submissions in R. + +### R specification compatibility +The underlying [grammar definition](https://github.com/antlr/grammars-v4/tree/master/r) was first created in June 2013, when R 3.0.1 was current. The latest commit is from April 2018, when R 3.5.0 was just released. Whether the grammar has been made to comply with any specific version of the R specification is unclear. Even if some parsing errors occur, the parser should be able to recover and still produce a valid analysis. + +### Usage +To use the R frontend, add the `-l R` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.R` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag). \ No newline at end of file diff --git a/jplag.frontend.R/pom.xml b/jplag.frontend.R/pom.xml index 9e3cf5691..1c088329e 100644 --- a/jplag.frontend.R/pom.xml +++ b/jplag.frontend.R/pom.xml @@ -17,6 +17,13 @@ de.jplag frontend-utils + + de.jplag + frontend-testutils + ${revision} + test-jar + test + diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java index cf8afdc0d..8d289e673 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java @@ -36,6 +36,10 @@ private void transformToken(int targetType, Token token) { parserAdapter.addToken(targetType, token.getLine(), token.getCharPositionInLine() + 1, token.getText().length()); } + private void transformToken(int targetType, Token start, Token end) { + parserAdapter.addToken(targetType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1); + } + @Override public void enterProg(RParser.ProgContext ctx) { @@ -83,12 +87,12 @@ public void enterFunction_definition(RParser.Function_definitionContext ctx) { @Override public void exitFunction_definition(RParser.Function_definitionContext ctx) { - transformToken(END_FUNCTION, ctx.getStart()); + transformToken(END_FUNCTION, ctx.getStop()); } @Override public void enterFunction_call(RParser.Function_callContext ctx) { - transformToken(FUNCTION_CALL, ctx.getStart()); + transformToken(FUNCTION_CALL, ctx.getStart(), ctx.getStop()); } @Override @@ -153,7 +157,7 @@ public void enterIf_statement(RParser.If_statementContext ctx) { @Override public void exitIf_statement(RParser.If_statementContext ctx) { - transformToken(IF_END, ctx.getStart()); + transformToken(IF_END, ctx.getStop()); } @Override @@ -163,17 +167,17 @@ public void enterFor_statement(RParser.For_statementContext ctx) { @Override public void exitFor_statement(RParser.For_statementContext ctx) { - transformToken(FOR_END, ctx.getStart()); + transformToken(FOR_END, ctx.getStop()); } @Override public void enterWhile_statement(RParser.While_statementContext ctx) { - transformToken(WHILE_BEGIN, ctx.getStart()); + transformToken(WHILE_BEGIN, ctx.getStart()); } @Override public void exitWhile_statement(RParser.While_statementContext ctx) { - transformToken(WHILE_END, ctx.getStart()); + transformToken(WHILE_END, ctx.getStop()); } @Override @@ -183,7 +187,7 @@ public void enterRepeat_statement(RParser.Repeat_statementContext ctx) { @Override public void exitRepeat_statement(RParser.Repeat_statementContext ctx) { - transformToken(REPEAT_END, ctx.getStart()); + transformToken(REPEAT_END, ctx.getStop()); } @Override @@ -213,7 +217,7 @@ public void enterCompound_statement(RParser.Compound_statementContext ctx) { @Override public void exitCompound_statement(RParser.Compound_statementContext ctx) { - transformToken(COMPOUND_END, ctx.getStart()); + transformToken(COMPOUND_END, ctx.getStop()); } @Override @@ -358,9 +362,7 @@ public void exitEveryRule(ParserRuleContext ctx) { @Override public void visitTerminal(TerminalNode node) { - if (node.getText().equals("=")) { - transformToken(ASSIGN, node.getSymbol()); - } + } @Override diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java b/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java index b9bb2ccbd..b7c0cd42e 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java @@ -88,10 +88,10 @@ private boolean parseFile(File directory, String fileName) { * @param type the type of the new {@link de.jplag.Token} * @param line the line of the Token in the current file * @param start the start column of the Token in the line - * @param end the end column of the Token in the line + * @param length the length of the Token */ - /* package-private */ void addToken(int type, int line, int start, int end) { - tokens.addToken(new RToken(type, currentFile, line, start, end - start)); + /* package-private */ void addToken(int type, int line, int start, int length) { + tokens.addToken(new RToken(type, currentFile, line, start, length)); } } diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java index d370abda7..f72830c00 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java @@ -14,7 +14,7 @@ public RToken(int type, String file, int line, int column, int length) { @Override public String type2string() { return switch (this.type) { - case FILE_END -> "***********"; + case FILE_END -> ""; case SEPARATOR_TOKEN -> "METHOD_SEPARATOR"; case BEGIN_FUNCTION -> "FUNCTION{"; case END_FUNCTION -> "}FUNCTION"; @@ -29,7 +29,7 @@ public String type2string() { case INDEX -> "INDEX"; case PACKAGE -> "PACKAGE"; case IF_BEGIN -> "IF{"; - case IF_END -> "}IF"; + case IF_END -> "}IF-ELSE"; case FOR_BEGIN -> "FOR{"; case FOR_END -> "}FOR"; case WHILE_BEGIN -> "WHILE{"; diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java new file mode 100644 index 000000000..95ed5b479 --- /dev/null +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -0,0 +1,108 @@ +package de.jplag.R; + +import de.jplag.Token; +import de.jplag.TokenConstants; +import de.jplag.TokenList; +import de.jplag.TokenPrinter; +import de.jplag.testutils.TestErrorConsumer; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.IntStream; +import java.util.stream.StreamSupport; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.fail; + +public class RFrontendTest { + + /** + * Regular expression for comments. + */ + private static final String R_COMMENT = "#.*"; + + /** + * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens. + */ + private static final String COMPLETE_TEST_FILE = "Complete.R"; + private final Logger logger = LoggerFactory.getLogger("R frontend test"); + private final String[] testFiles = new String[]{"Game.r", COMPLETE_TEST_FILE}; + private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "R").toFile(); + private Language language; + + @BeforeEach + void setup() { + TestErrorConsumer consumer = new TestErrorConsumer(); + language = new Language(consumer); + } + + @Test + void parseTestFiles() { + for (String fileName : testFiles) { + TokenList tokens = language.parse(testFileLocation, new String[]{fileName}); + String output = TokenPrinter.printTokens(tokens, testFileLocation, List.of(fileName)); + logger.info(output); + + testSourceCoverage(fileName, tokens); + if (fileName.equals(COMPLETE_TEST_FILE)) testTokenCoverage(tokens, fileName); + } + } + + /** + * Confirms that the code is covered to a basic extent, i.e. each line of code contains at least one token. + * @param fileName a code sample file name + * @param tokens the TokenList generated from the sample + */ + private void testSourceCoverage(String fileName, TokenList tokens) { + File testFile = new File(testFileLocation, fileName); + + try { + List lines = Files.readAllLines(testFile.toPath()); + String commentExpression = getCommentExpression(); + + // All lines that contain code + var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches("\\s*(%s)?".formatted(commentExpression))).toArray(); + // All lines that contain token + var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray(); + + if (codeLines.length > tokenLines.length) { + var diffLine = IntStream.range(0, codeLines.length).dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst(); + diffLine.ifPresent(lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName))); + } + assertArrayEquals(codeLines, tokenLines); + } catch (IOException exception) { + logger.info("Error while reading test file %s".formatted(fileName), exception); + fail(); + } + } + + /** + * Confirms that all Token types are 'reachable' with a complete code example. + * @param tokens TokenList which is supposed to contain all types of tokens + * @param fileName The file name of the complete code example + */ + private void testTokenCoverage(TokenList tokens, String fileName) { + var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType) + .sorted().distinct().toArray(); + // Exclude SEPARATOR_TOKEN, as it does not occur + var allTokens = IntStream.range(0, RTokenConstants.NUM_DIFF_TOKENS) + .filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray(); + + if (allTokens.length > foundTokens.length) { + var diffLine = IntStream.range(0, allTokens.length).dropWhile(lineIdx -> lineIdx < foundTokens.length && allTokens[lineIdx] == foundTokens[lineIdx]).findFirst(); + diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'.".formatted(new RToken(allTokens[lineIdx], fileName, -1, -1, -1).type2string(), fileName))); + } + assertArrayEquals(allTokens, foundTokens); + } + + private static String getCommentExpression() { + return R_COMMENT; + } + +} diff --git a/jplag.frontend.R/src/test/resources/de/jplag/R/Complete.R b/jplag.frontend.R/src/test/resources/de/jplag/R/Complete.R new file mode 100644 index 000000000..2be42db33 --- /dev/null +++ b/jplag.frontend.R/src/test/resources/de/jplag/R/Complete.R @@ -0,0 +1,37 @@ +# This R code sample is supposed to contain the corresponding AST structure for each type of RToken. +# It is also working code. +# Author: Robin Maisch + +main <- function() { + sixteen <- square(4); + squareOkay <- ifelse(sixteen==16, TRUE, FALSE); + cat("Should be 16: ", sixteen, squareOkay); + ?cat; + + if (squareOkay) { + perfectSquare <- square + } else { + perfectSquare <- function (x) x*x + } + + for (i in 1:10) { + print(apply(arg=i, func=perfectSquare)) + } + + repeat { + if (FALSE) next + else break + } + + idx <- 0 + while (FALSE) { + idx <- idx + 1 + print((0:40)[idx=]) + } +} +oneMore <- function(var) base::'+'(var, 1) # weird way to access + operator +square <- function(x) x+x # works for x=2, so that's not bad +identity <- function(x) x +apply <- function(arg=0, func=identity) func(arg) + +main() diff --git a/jplag.frontend.R/src/test/resources/de/jplag/R/Game.R b/jplag.frontend.R/src/test/resources/de/jplag/R/Game.R new file mode 100644 index 000000000..3ff3201b0 --- /dev/null +++ b/jplag.frontend.R/src/test/resources/de/jplag/R/Game.R @@ -0,0 +1,92 @@ +# Sample R program +# Author: Robin Maisch + +readBool <- function() +{ + n <- readline(prompt="Yes or no? (y/n): ") + while (!grepl("[YyNn]",n)) + { + n <- readline() + } + return(ifelse(n=="y"||n=="Y", TRUE, FALSE)) +} + + +getFilterBalance <- function(f, elements) { + return(abs(length(Filter(f, elements)) - length(elements)/2)) +} + +divisibleByFilter <- function(i) { + res <- c(function(x) x %% i == 0, paste("a multiple of", i), NA); + return(res) +} + +greaterThanFilter <- function(i) { + res <- c(function(x) x > i, paste("greater than", i), NA); + return(res) +} + +smallerThanFilter <- function(i) { + res <- c(function(x) x < i, paste("smaller than", i), NA); + return(res) +} + +endsWithFilter <- function(i) { + res <- c(function(x) x %% 10 == i, paste("'s last digit a'", i), NA); + return(res) +} + +# real program start here + +main <- function() { + filters <- c(lapply(2:20, divisibleByFilter), + lapply(0:100, greaterThanFilter), + lapply(0:100, smallerThanFilter), + lapply(0:10, endsWithFilter)) + filters <- aperm(simplify2array(filters, higher=FALSE), c(2,1)) + activeFilters = list() + + cat("Think of a number between 0 and 100.\n") + count <- 1 + candidates <- c() + repeat { + cat("\n +++ Round",count,"+++\n\n") + candidates = 0:100 + for (f in activeFilters) { + filterFunc = ifelse(f[[3]], function(x) f[[1]](x), function(x) !f[[1]](x)) + candidates = Filter(filterFunc, candidates) + } + if (length(candidates) == 1) { + break + } + + balance <- sapply(1:(length(filters)/3), function(i) {return(getFilterBalance(filters[i,][[1]], candidates))}) + rank <- rank(balance, ties="first") + risk = max(20-2*count, 1) + lowerEnd = 1 + topFiveIdx = order(rank)[lowerEnd:risk] + # print(filters[topFiveIdx[1:3],2]) + winnerIdx = topFiveIdx[sample.int(risk-lowerEnd + 1,1)] + winnerFilter = filters[winnerIdx,] + + cat("Is your number", winnerFilter[[2]], "?\n") + winnerFilter[[3]] <- readBool() + + cat("Okay, now, let's see...\n") + activeFilters[[length(activeFilters)+1]] = winnerFilter + # cat("Now we have", length(activeFilters), "active filters.\n") + + filters[- winnerIdx,] # remove element + count <- count + 1 + } + + cat("Your number must be", candidates[1], ", right???\n") + answer <- readBool() + if (answer) { + cat("Hahaha, I knew it. I'm a genius.\n") + } else { + cat("You must be joking!\n") + } + +} +main() From e539c3d99aff3acb83273f447737401147bfe3b4 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Tue, 14 Jun 2022 11:45:35 +0200 Subject: [PATCH 04/10] Reformat code --- .../main/java/de/jplag/R/JplagRListener.java | 7 ++- .../src/main/java/de/jplag/R/RToken.java | 4 +- .../main/java/de/jplag/R/RTokenConstants.java | 5 +- .../test/java/de/jplag/R/RFrontendTest.java | 55 ++++++++++--------- pom.xml | 5 -- 5 files changed, 39 insertions(+), 37 deletions(-) diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java index 8d289e673..bcd33601b 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java @@ -11,8 +11,9 @@ import de.jplag.R.grammar.RParser; /** - * Listener class for visiting the R ANTLR parse tree. Transforms the visited ANTLR token into JPlag tokens. - * Based on an R frontend for JPlag v2.15 by Olmo Kramer, see their JPlag fork. + * Listener class for visiting the R ANTLR parse tree. Transforms the visited ANTLR token into JPlag tokens. Based on an + * R frontend for JPlag v2.15 by Olmo Kramer, see their + * JPlag fork. * @author Robin Maisch */ public class JplagRListener implements RListener, RFilterListener, RTokenConstants { @@ -172,7 +173,7 @@ public void exitFor_statement(RParser.For_statementContext ctx) { @Override public void enterWhile_statement(RParser.While_statementContext ctx) { - transformToken(WHILE_BEGIN, ctx.getStart()); + transformToken(WHILE_BEGIN, ctx.getStart()); } @Override diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java index f72830c00..c55731f09 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java @@ -1,8 +1,8 @@ package de.jplag.R; /** - * This class represents the occurrence of an R Token in the source code. - * Based on an R frontend for JPlag v2.15 by Olmo Kramer, see their JPlag fork. + * This class represents the occurrence of an R Token in the source code. Based on an R frontend for JPlag v2.15 by Olmo + * Kramer, see their JPlag fork. * @author Robin Maisch */ public class RToken extends de.jplag.Token implements RTokenConstants { diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java index a884e4a1f..2f1e98bed 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java @@ -3,8 +3,9 @@ import de.jplag.TokenConstants; /** - * Tokens in R that are deemed important when comparing submissions for plagiarisms. - * Based on an R frontend for JPlag v2.15 by Olmo Kramer, see their JPlag fork. + * Tokens in R that are deemed important when comparing submissions for plagiarisms. Based on an R frontend for JPlag + * v2.15 by Olmo Kramer, see their JPlag + * fork. * @author Robin Maisch */ public interface RTokenConstants extends TokenConstants { diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java index 95ed5b479..08c38ca40 100644 --- a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -1,14 +1,7 @@ package de.jplag.R; -import de.jplag.Token; -import de.jplag.TokenConstants; -import de.jplag.TokenList; -import de.jplag.TokenPrinter; -import de.jplag.testutils.TestErrorConsumer; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.Test; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; +import static org.junit.jupiter.api.Assertions.assertArrayEquals; +import static org.junit.jupiter.api.Assertions.fail; import java.io.*; import java.nio.file.Files; @@ -17,22 +10,30 @@ import java.util.stream.IntStream; import java.util.stream.StreamSupport; -import static org.junit.jupiter.api.Assertions.assertArrayEquals; -import static org.junit.jupiter.api.Assertions.fail; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import de.jplag.Token; +import de.jplag.TokenConstants; +import de.jplag.TokenList; +import de.jplag.TokenPrinter; +import de.jplag.testutils.TestErrorConsumer; public class RFrontendTest { /** - * Regular expression for comments. + * Regular expression for comments. */ private static final String R_COMMENT = "#.*"; /** - * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens. + * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens. */ private static final String COMPLETE_TEST_FILE = "Complete.R"; private final Logger logger = LoggerFactory.getLogger("R frontend test"); - private final String[] testFiles = new String[]{"Game.r", COMPLETE_TEST_FILE}; + private final String[] testFiles = new String[] {"Game.r", COMPLETE_TEST_FILE}; private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "R").toFile(); private Language language; @@ -45,12 +46,13 @@ void setup() { @Test void parseTestFiles() { for (String fileName : testFiles) { - TokenList tokens = language.parse(testFileLocation, new String[]{fileName}); + TokenList tokens = language.parse(testFileLocation, new String[] {fileName}); String output = TokenPrinter.printTokens(tokens, testFileLocation, List.of(fileName)); logger.info(output); testSourceCoverage(fileName, tokens); - if (fileName.equals(COMPLETE_TEST_FILE)) testTokenCoverage(tokens, fileName); + if (fileName.equals(COMPLETE_TEST_FILE)) + testTokenCoverage(tokens, fileName); } } @@ -67,13 +69,16 @@ private void testSourceCoverage(String fileName, TokenList tokens) { String commentExpression = getCommentExpression(); // All lines that contain code - var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches("\\s*(%s)?".formatted(commentExpression))).toArray(); + var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches("\\s*(%s)?".formatted(commentExpression))) + .toArray(); // All lines that contain token var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray(); if (codeLines.length > tokenLines.length) { - var diffLine = IntStream.range(0, codeLines.length).dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst(); - diffLine.ifPresent(lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName))); + var diffLine = IntStream.range(0, codeLines.length) + .dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst(); + diffLine.ifPresent( + lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName))); } assertArrayEquals(codeLines, tokenLines); } catch (IOException exception) { @@ -88,15 +93,15 @@ private void testSourceCoverage(String fileName, TokenList tokens) { * @param fileName The file name of the complete code example */ private void testTokenCoverage(TokenList tokens, String fileName) { - var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType) - .sorted().distinct().toArray(); + var foundTokens = StreamSupport.stream(tokens.allTokens().spliterator(), true).mapToInt(Token::getType).sorted().distinct().toArray(); // Exclude SEPARATOR_TOKEN, as it does not occur - var allTokens = IntStream.range(0, RTokenConstants.NUM_DIFF_TOKENS) - .filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray(); + var allTokens = IntStream.range(0, RTokenConstants.NUM_DIFF_TOKENS).filter(i -> i != TokenConstants.SEPARATOR_TOKEN).toArray(); if (allTokens.length > foundTokens.length) { - var diffLine = IntStream.range(0, allTokens.length).dropWhile(lineIdx -> lineIdx < foundTokens.length && allTokens[lineIdx] == foundTokens[lineIdx]).findFirst(); - diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'.".formatted(new RToken(allTokens[lineIdx], fileName, -1, -1, -1).type2string(), fileName))); + var diffLine = IntStream.range(0, allTokens.length) + .dropWhile(lineIdx -> lineIdx < foundTokens.length && allTokens[lineIdx] == foundTokens[lineIdx]).findFirst(); + diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'." + .formatted(new RToken(allTokens[lineIdx], fileName, -1, -1, -1).type2string(), fileName))); } assertArrayEquals(allTokens, foundTokens); } diff --git a/pom.xml b/pom.xml index 5d856d011..a2455c766 100644 --- a/pom.xml +++ b/pom.xml @@ -162,11 +162,6 @@ R ${project.version} - - ${project.groupId} - scala - ${project.version} - ${project.groupId} cpp From 66fda9bcba69310f692ab758b5b8b28d79daf684 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Tue, 14 Jun 2022 14:01:02 +0200 Subject: [PATCH 05/10] Possible fix for failing test: Fix incorrect case in file name --- jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java index 08c38ca40..37d8e2fb4 100644 --- a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -33,7 +33,7 @@ public class RFrontendTest { */ private static final String COMPLETE_TEST_FILE = "Complete.R"; private final Logger logger = LoggerFactory.getLogger("R frontend test"); - private final String[] testFiles = new String[] {"Game.r", COMPLETE_TEST_FILE}; + private final String[] testFiles = new String[] {"Game.R", COMPLETE_TEST_FILE}; private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "R").toFile(); private Language language; From 7ddc844fbdf3af545930e69e2906d439f92b510f Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Mon, 20 Jun 2022 20:24:13 +0200 Subject: [PATCH 06/10] Clean up, extend README --- jplag.frontend.R/README.md | 9 +- .../main/java/de/jplag/R/JplagRListener.java | 315 +++------------- .../de/jplag/R/RCombinedBaseListener.java | 341 ++++++++++++++++++ .../main/java/de/jplag/R/RTokenConstants.java | 2 - .../test/java/de/jplag/R/RFrontendTest.java | 12 +- .../java/de/jplag/options/LanguageOption.java | 4 +- 6 files changed, 408 insertions(+), 275 deletions(-) create mode 100644 jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java diff --git a/jplag.frontend.R/README.md b/jplag.frontend.R/README.md index 15b74bff1..67b433494 100644 --- a/jplag.frontend.R/README.md +++ b/jplag.frontend.R/README.md @@ -1,9 +1,16 @@ # JPlag R language frontend -The JPlag R frontend allows the use of JPlag with submissions in R. +The JPlag R frontend allows the use of JPlag with submissions in R.
+It was in part adapted from a [JPLag fork by CodeGra-de](https://github.com/CodeGra-de/jplag/tree/master/jplag.frontend.R). ### R specification compatibility The underlying [grammar definition](https://github.com/antlr/grammars-v4/tree/master/r) was first created in June 2013, when R 3.0.1 was current. The latest commit is from April 2018, when R 3.5.0 was just released. Whether the grammar has been made to comply with any specific version of the R specification is unclear. Even if some parsing errors occur, the parser should be able to recover and still produce a valid analysis. +### Token Extraction + +The choice of tokens is based directly on the CodeGra-de version, whereas the extraction process itself contains some fixes. + +Like in other frontends, e.g. for Java and C#, the tokens account for the beginning and the end of control flow structures, for control flow keywords, and some kinds of expressions. As R is very different from other programming languages in JPlag, it remains to be seen whether the R frontend can hold up to the others. + ### Usage To use the R frontend, add the `-l R` flag in the CLI, or use a `JPlagOption` object set to `LanguageOption.R` in the Java API as described in the usage information in the [readme of the main project](https://github.com/jplag/JPlag#usage) and [in the wiki](https://github.com/jplag/JPlag/wiki/1.-How-to-Use-JPlag). \ No newline at end of file diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java index bcd33601b..fa7e9ad4b 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java @@ -1,22 +1,18 @@ package de.jplag.R; +import de.jplag.R.grammar.*; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.TerminalNode; -import de.jplag.R.grammar.RFilter; -import de.jplag.R.grammar.RFilterListener; -import de.jplag.R.grammar.RListener; -import de.jplag.R.grammar.RParser; - /** * Listener class for visiting the R ANTLR parse tree. Transforms the visited ANTLR token into JPlag tokens. Based on an * R frontend for JPlag v2.15 by Olmo Kramer, see their * JPlag fork. * @author Robin Maisch */ -public class JplagRListener implements RListener, RFilterListener, RTokenConstants { +public class JplagRListener extends RCombinedBaseListener implements RTokenConstants { private final RParserAdapter parserAdapter; @@ -41,333 +37,124 @@ private void transformToken(int targetType, Token start, Token end) { parserAdapter.addToken(targetType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1); } - @Override - public void enterProg(RParser.ProgContext ctx) { - - } - - @Override - public void exitProg(RParser.ProgContext ctx) { - - } - - @Override - public void enterExpr(RParser.ExprContext ctx) { - + @Override + public void enterIndex_statement(RParser.Index_statementContext context) { + transformToken(INDEX, context.getStart(), context.getStop()); } @Override - public void exitExpr(RParser.ExprContext ctx) { - + public void enterAccess_package(RParser.Access_packageContext context) { + transformToken(PACKAGE, context.getStart()); } @Override - public void enterIndex_statement(RParser.Index_statementContext ctx) { - transformToken(INDEX, ctx.getStart()); + public void enterFunction_definition(RParser.Function_definitionContext context) { + transformToken(BEGIN_FUNCTION, context.getStart()); } @Override - public void exitIndex_statement(RParser.Index_statementContext ctx) { - + public void exitFunction_definition(RParser.Function_definitionContext context) { + transformToken(END_FUNCTION, context.getStop()); } @Override - public void enterAccess_package(RParser.Access_packageContext ctx) { - transformToken(PACKAGE, ctx.getStart()); + public void enterFunction_call(RParser.Function_callContext context) { + transformToken(FUNCTION_CALL, context.getStart(), context.getStop()); } @Override - public void exitAccess_package(RParser.Access_packageContext ctx) { - + public void enterConstant_number(RParser.Constant_numberContext context) { + transformToken(NUMBER, context.getStart()); } @Override - public void enterFunction_definition(RParser.Function_definitionContext ctx) { - transformToken(BEGIN_FUNCTION, ctx.getStart()); + public void enterConstant_string(RParser.Constant_stringContext context) { + transformToken(STRING, context.getStart()); } @Override - public void exitFunction_definition(RParser.Function_definitionContext ctx) { - transformToken(END_FUNCTION, ctx.getStop()); + public void enterConstant_bool(RParser.Constant_boolContext context) { + transformToken(BOOL, context.getStart()); } @Override - public void enterFunction_call(RParser.Function_callContext ctx) { - transformToken(FUNCTION_CALL, ctx.getStart(), ctx.getStop()); + public void enterHelp(RParser.HelpContext context) { + transformToken(HELP, context.getStart()); } @Override - public void exitFunction_call(RParser.Function_callContext ctx) { - + public void enterIf_statement(RParser.If_statementContext context) { + transformToken(IF_BEGIN, context.getStart()); } @Override - public void enterConstant(RParser.ConstantContext ctx) { - + public void exitIf_statement(RParser.If_statementContext context) { + transformToken(IF_END, context.getStop()); } @Override - public void exitConstant(RParser.ConstantContext ctx) { - + public void enterFor_statement(RParser.For_statementContext context) { + transformToken(FOR_BEGIN, context.getStart()); } @Override - public void enterConstant_number(RParser.Constant_numberContext ctx) { - transformToken(NUMBER, ctx.getStart()); + public void exitFor_statement(RParser.For_statementContext context) { + transformToken(FOR_END, context.getStop()); } @Override - public void exitConstant_number(RParser.Constant_numberContext ctx) { - + public void enterWhile_statement(RParser.While_statementContext context) { + transformToken(WHILE_BEGIN, context.getStart()); } @Override - public void enterConstant_string(RParser.Constant_stringContext ctx) { - transformToken(STRING, ctx.getStart()); + public void exitWhile_statement(RParser.While_statementContext context) { + transformToken(WHILE_END, context.getStop()); } @Override - public void exitConstant_string(RParser.Constant_stringContext ctx) { - + public void enterRepeat_statement(RParser.Repeat_statementContext context) { + transformToken(REPEAT_BEGIN, context.getStart()); } @Override - public void enterConstant_bool(RParser.Constant_boolContext ctx) { - transformToken(BOOL, ctx.getStart()); + public void exitRepeat_statement(RParser.Repeat_statementContext context) { + transformToken(REPEAT_END, context.getStop()); } @Override - public void exitConstant_bool(RParser.Constant_boolContext ctx) { - + public void enterNext_statement(RParser.Next_statementContext context) { + transformToken(NEXT, context.getStart()); } @Override - public void enterHelp(RParser.HelpContext ctx) { - transformToken(HELP, ctx.getStart()); + public void enterBreak_statement(RParser.Break_statementContext context) { + transformToken(BREAK, context.getStart()); } @Override - public void exitHelp(RParser.HelpContext ctx) { - + public void enterCompound_statement(RParser.Compound_statementContext context) { + transformToken(COMPOUND_BEGIN, context.getStart()); } @Override - public void enterIf_statement(RParser.If_statementContext ctx) { - transformToken(IF_BEGIN, ctx.getStart()); + public void exitCompound_statement(RParser.Compound_statementContext context) { + transformToken(COMPOUND_END, context.getStop()); } @Override - public void exitIf_statement(RParser.If_statementContext ctx) { - transformToken(IF_END, ctx.getStop()); + public void enterAssign_value(RParser.Assign_valueContext context) { + transformToken(ASSIGN, context.getStart()); } @Override - public void enterFor_statement(RParser.For_statementContext ctx) { - transformToken(FOR_BEGIN, ctx.getStart()); - } - - @Override - public void exitFor_statement(RParser.For_statementContext ctx) { - transformToken(FOR_END, ctx.getStop()); - } - - @Override - public void enterWhile_statement(RParser.While_statementContext ctx) { - transformToken(WHILE_BEGIN, ctx.getStart()); - } - - @Override - public void exitWhile_statement(RParser.While_statementContext ctx) { - transformToken(WHILE_END, ctx.getStop()); - } - - @Override - public void enterRepeat_statement(RParser.Repeat_statementContext ctx) { - transformToken(REPEAT_BEGIN, ctx.getStart()); - } - - @Override - public void exitRepeat_statement(RParser.Repeat_statementContext ctx) { - transformToken(REPEAT_END, ctx.getStop()); - } - - @Override - public void enterNext_statement(RParser.Next_statementContext ctx) { - transformToken(NEXT, ctx.getStart()); - } - - @Override - public void exitNext_statement(RParser.Next_statementContext ctx) { - + public void enterAssign_func_declaration(RParser.Assign_func_declarationContext context) { + transformToken(ASSIGN_FUNC, context.getStart()); } @Override - public void enterBreak_statement(RParser.Break_statementContext ctx) { - transformToken(BREAK, ctx.getStart()); - } - - @Override - public void exitBreak_statement(RParser.Break_statementContext ctx) { - - } - - @Override - public void enterCompound_statement(RParser.Compound_statementContext ctx) { - transformToken(COMPOUND_BEGIN, ctx.getStart()); - } - - @Override - public void exitCompound_statement(RParser.Compound_statementContext ctx) { - transformToken(COMPOUND_END, ctx.getStop()); - } - - @Override - public void enterExprlist(RParser.ExprlistContext ctx) { - - } - - @Override - public void exitExprlist(RParser.ExprlistContext ctx) { - - } - - @Override - public void enterFormlist(RParser.FormlistContext ctx) { - - } - - @Override - public void exitFormlist(RParser.FormlistContext ctx) { - - } - - @Override - public void enterForm(RParser.FormContext ctx) { - - } - - @Override - public void exitForm(RParser.FormContext ctx) { - - } - - @Override - public void enterSublist(RParser.SublistContext ctx) { - - } - - @Override - public void exitSublist(RParser.SublistContext ctx) { - + public void enterAssign_value_list(RParser.Assign_value_listContext context) { + transformToken(ASSIGN_LIST, context.getStart()); } - @Override - public void enterSub(RParser.SubContext ctx) { - - } - - @Override - public void exitSub(RParser.SubContext ctx) { - - } - - @Override - public void enterAssign_value(RParser.Assign_valueContext ctx) { - transformToken(ASSIGN, ctx.getStart()); - } - - @Override - public void exitAssign_value(RParser.Assign_valueContext ctx) { - - } - - @Override - public void enterAssign_func_declaration(RParser.Assign_func_declarationContext ctx) { - transformToken(ASSIGN_FUNC, ctx.getStart()); - } - - @Override - public void exitAssign_func_declaration(RParser.Assign_func_declarationContext ctx) { - - } - - @Override - public void enterAssign_value_list(RParser.Assign_value_listContext ctx) { - transformToken(ASSIGN_LIST, ctx.getStart()); - } - - @Override - public void exitAssign_value_list(RParser.Assign_value_listContext ctx) { - - } - - @Override - public void enterStream(RFilter.StreamContext ctx) { - - } - - @Override - public void exitStream(RFilter.StreamContext ctx) { - - } - - @Override - public void enterEat(RFilter.EatContext ctx) { - - } - - @Override - public void exitEat(RFilter.EatContext ctx) { - - } - - @Override - public void enterElement(RFilter.ElementContext ctx) { - - } - - @Override - public void exitElement(RFilter.ElementContext ctx) { - - } - - @Override - public void enterAtom(RFilter.AtomContext ctx) { - - } - - @Override - public void exitAtom(RFilter.AtomContext ctx) { - - } - - @Override - public void enterOp(RFilter.OpContext ctx) { - - } - - @Override - public void exitOp(RFilter.OpContext ctx) { - - } - - @Override - public void enterEveryRule(ParserRuleContext ctx) { - - } - - @Override - public void exitEveryRule(ParserRuleContext ctx) { - - } - - @Override - public void visitTerminal(TerminalNode node) { - - } - - @Override - public void visitErrorNode(ErrorNode node) { - - } } \ No newline at end of file diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java new file mode 100644 index 000000000..bcb14605b --- /dev/null +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java @@ -0,0 +1,341 @@ +package de.jplag.R; + +import de.jplag.R.grammar.*; +import org.antlr.v4.runtime.ParserRuleContext; +import org.antlr.v4.runtime.tree.ErrorNode; +import org.antlr.v4.runtime.tree.TerminalNode; + +/** + * Empty base implementation for {@link RListener} and {@link RFilterListener}. + */ +public class RCombinedBaseListener implements RListener, RFilterListener { + @Override + public void enterStream(RFilter.StreamContext context) { + + } + + @Override + public void exitStream(RFilter.StreamContext context) { + + } + + @Override + public void enterEat(RFilter.EatContext context) { + + } + + @Override + public void exitEat(RFilter.EatContext context) { + + } + + @Override + public void enterElement(RFilter.ElementContext context) { + + } + + @Override + public void exitElement(RFilter.ElementContext context) { + + } + + @Override + public void enterAtom(RFilter.AtomContext context) { + + } + + @Override + public void exitAtom(RFilter.AtomContext context) { + + } + + @Override + public void enterOp(RFilter.OpContext context) { + + } + + @Override + public void exitOp(RFilter.OpContext context) { + + } + + @Override + public void enterProg(RParser.ProgContext context) { + + } + + @Override + public void exitProg(RParser.ProgContext context) { + + } + + @Override + public void enterExpr(RParser.ExprContext context) { + + } + + @Override + public void exitExpr(RParser.ExprContext context) { + + } + + @Override + public void enterIndex_statement(RParser.Index_statementContext context) { + + } + + @Override + public void exitIndex_statement(RParser.Index_statementContext context) { + + } + + @Override + public void enterAccess_package(RParser.Access_packageContext context) { + + } + + @Override + public void exitAccess_package(RParser.Access_packageContext context) { + + } + + @Override + public void enterFunction_definition(RParser.Function_definitionContext context) { + + } + + @Override + public void exitFunction_definition(RParser.Function_definitionContext context) { + + } + + @Override + public void enterFunction_call(RParser.Function_callContext context) { + + } + + @Override + public void exitFunction_call(RParser.Function_callContext context) { + + } + + @Override + public void enterConstant(RParser.ConstantContext context) { + + } + + @Override + public void exitConstant(RParser.ConstantContext context) { + + } + + @Override + public void enterConstant_number(RParser.Constant_numberContext context) { + + } + + @Override + public void exitConstant_number(RParser.Constant_numberContext context) { + + } + + @Override + public void enterConstant_string(RParser.Constant_stringContext context) { + + } + + @Override + public void exitConstant_string(RParser.Constant_stringContext context) { + + } + + @Override + public void enterConstant_bool(RParser.Constant_boolContext context) { + + } + + @Override + public void exitConstant_bool(RParser.Constant_boolContext context) { + + } + + @Override + public void enterHelp(RParser.HelpContext context) { + + } + + @Override + public void exitHelp(RParser.HelpContext context) { + + } + + @Override + public void enterIf_statement(RParser.If_statementContext context) { + + } + + @Override + public void exitIf_statement(RParser.If_statementContext context) { + + } + + @Override + public void enterFor_statement(RParser.For_statementContext context) { + + } + + @Override + public void exitFor_statement(RParser.For_statementContext context) { + + } + + @Override + public void enterWhile_statement(RParser.While_statementContext context) { + + } + + @Override + public void exitWhile_statement(RParser.While_statementContext context) { + + } + + @Override + public void enterRepeat_statement(RParser.Repeat_statementContext context) { + + } + + @Override + public void exitRepeat_statement(RParser.Repeat_statementContext context) { + + } + + @Override + public void enterNext_statement(RParser.Next_statementContext context) { + + } + + @Override + public void exitNext_statement(RParser.Next_statementContext context) { + + } + + @Override + public void enterBreak_statement(RParser.Break_statementContext context) { + + } + + @Override + public void exitBreak_statement(RParser.Break_statementContext context) { + + } + + @Override + public void enterCompound_statement(RParser.Compound_statementContext context) { + + } + + @Override + public void exitCompound_statement(RParser.Compound_statementContext context) { + + } + + @Override + public void enterExprlist(RParser.ExprlistContext context) { + + } + + @Override + public void exitExprlist(RParser.ExprlistContext context) { + + } + + @Override + public void enterFormlist(RParser.FormlistContext context) { + + } + + @Override + public void exitFormlist(RParser.FormlistContext context) { + + } + + @Override + public void enterForm(RParser.FormContext context) { + + } + + @Override + public void exitForm(RParser.FormContext context) { + + } + + @Override + public void enterSublist(RParser.SublistContext context) { + + } + + @Override + public void exitSublist(RParser.SublistContext context) { + + } + + @Override + public void enterSub(RParser.SubContext context) { + + } + + @Override + public void exitSub(RParser.SubContext context) { + + } + + @Override + public void enterAssign_value(RParser.Assign_valueContext context) { + + } + + @Override + public void exitAssign_value(RParser.Assign_valueContext context) { + + } + + @Override + public void enterAssign_func_declaration(RParser.Assign_func_declarationContext context) { + + } + + @Override + public void exitAssign_func_declaration(RParser.Assign_func_declarationContext context) { + + } + + @Override + public void enterAssign_value_list(RParser.Assign_value_listContext context) { + + } + + @Override + public void exitAssign_value_list(RParser.Assign_value_listContext context) { + + } + + @Override + public void visitTerminal(TerminalNode node) { + + } + + @Override + public void visitErrorNode(ErrorNode node) { + + } + + @Override + public void enterEveryRule(ParserRuleContext context) { + + } + + @Override + public void exitEveryRule(ParserRuleContext context) { + + } +} diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java index 2f1e98bed..7f368d132 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java @@ -9,8 +9,6 @@ * @author Robin Maisch */ public interface RTokenConstants extends TokenConstants { - int FILE_END = 0; - int SEPARATOR_TOKEN = 1; int BEGIN_FUNCTION = 2; int END_FUNCTION = 3; diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java index 37d8e2fb4..502162d2f 100644 --- a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -24,9 +24,9 @@ public class RFrontendTest { /** - * Regular expression for comments. + * Regular expression for lines that contain no code. */ - private static final String R_COMMENT = "#.*"; + private static final String R_NO_CODE_LINE = "\s*(?:#.*)?"; /** * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens. @@ -66,10 +66,10 @@ private void testSourceCoverage(String fileName, TokenList tokens) { try { List lines = Files.readAllLines(testFile.toPath()); - String commentExpression = getCommentExpression(); + String emptyLineExpression = getNoCodeLineExpression(); // All lines that contain code - var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches("\\s*(%s)?".formatted(commentExpression))) + var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches(emptyLineExpression)) .toArray(); // All lines that contain token var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray(); @@ -106,8 +106,8 @@ private void testTokenCoverage(TokenList tokens, String fileName) { assertArrayEquals(allTokens, foundTokens); } - private static String getCommentExpression() { - return R_COMMENT; + private static String getNoCodeLineExpression() { + return R_NO_CODE_LINE; } } diff --git a/jplag/src/main/java/de/jplag/options/LanguageOption.java b/jplag/src/main/java/de/jplag/options/LanguageOption.java index 99b715263..47411e111 100644 --- a/jplag/src/main/java/de/jplag/options/LanguageOption.java +++ b/jplag/src/main/java/de/jplag/options/LanguageOption.java @@ -13,7 +13,7 @@ public enum LanguageOption { PYTHON_3("python3", "de.jplag.python3.Language"), C_CPP("cpp", "de.jplag.cpp.Language"), C_SHARP("csharp", "de.jplag.csharp.Language"), - R("R", "de.jplag.R.Language"), + R("r", "de.jplag.R.Language"), CHAR("char", "de.jplag.chars.Language"), TEXT("text", "de.jplag.text.Language"), SCHEME("scheme", "de.jplag.scheme.Language"); @@ -35,7 +35,7 @@ public String getDisplayName() { } public static LanguageOption fromDisplayName(String displayName) { - return Arrays.stream(LanguageOption.values()).filter(languageOption -> languageOption.displayName.equals(displayName)).findFirst() + return Arrays.stream(LanguageOption.values()).filter(languageOption -> languageOption.displayName.equalsIgnoreCase(displayName)).findFirst() .orElse(getDefault()); } From feac19ed9c2a985213af5dc7a8b9fef801c6a4ef Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Mon, 20 Jun 2022 20:25:19 +0200 Subject: [PATCH 07/10] Reformat code --- .../src/main/java/de/jplag/R/JplagRListener.java | 8 +++----- .../src/main/java/de/jplag/R/RCombinedBaseListener.java | 5 +++-- .../src/test/java/de/jplag/R/RFrontendTest.java | 3 +-- 3 files changed, 7 insertions(+), 9 deletions(-) diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java index fa7e9ad4b..da686287b 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java @@ -1,10 +1,8 @@ package de.jplag.R; -import de.jplag.R.grammar.*; -import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.Token; -import org.antlr.v4.runtime.tree.ErrorNode; -import org.antlr.v4.runtime.tree.TerminalNode; + +import de.jplag.R.grammar.*; /** * Listener class for visiting the R ANTLR parse tree. Transforms the visited ANTLR token into JPlag tokens. Based on an @@ -37,7 +35,7 @@ private void transformToken(int targetType, Token start, Token end) { parserAdapter.addToken(targetType, start.getLine(), start.getCharPositionInLine() + 1, end.getStopIndex() - start.getStartIndex() + 1); } - @Override + @Override public void enterIndex_statement(RParser.Index_statementContext context) { transformToken(INDEX, context.getStart(), context.getStop()); } diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java index bcb14605b..ba4c6238c 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java @@ -1,12 +1,13 @@ package de.jplag.R; -import de.jplag.R.grammar.*; import org.antlr.v4.runtime.ParserRuleContext; import org.antlr.v4.runtime.tree.ErrorNode; import org.antlr.v4.runtime.tree.TerminalNode; +import de.jplag.R.grammar.*; + /** - * Empty base implementation for {@link RListener} and {@link RFilterListener}. + * Empty base implementation for {@link RListener} and {@link RFilterListener}. */ public class RCombinedBaseListener implements RListener, RFilterListener { @Override diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java index 502162d2f..32025113a 100644 --- a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -69,8 +69,7 @@ private void testSourceCoverage(String fileName, TokenList tokens) { String emptyLineExpression = getNoCodeLineExpression(); // All lines that contain code - var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches(emptyLineExpression)) - .toArray(); + var codeLines = IntStream.range(1, lines.size() + 1).filter(idx -> !lines.get(idx - 1).matches(emptyLineExpression)).toArray(); // All lines that contain token var tokenLines = IntStream.range(0, tokens.size()).mapToObj(tokens::getToken).mapToInt(Token::getLine).distinct().toArray(); From b0cc8493266dca00cb3712a55011fa33e422a0f7 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Mon, 20 Jun 2022 20:30:19 +0200 Subject: [PATCH 08/10] Fix mis-escaped character in String --- jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java index 32025113a..2eb2ee166 100644 --- a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -26,7 +26,7 @@ public class RFrontendTest { /** * Regular expression for lines that contain no code. */ - private static final String R_NO_CODE_LINE = "\s*(?:#.*)?"; + private static final String R_NO_CODE_LINE = "\\s*(?:#.*)?"; /** * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens. From 2a1831d554df5aea0122e25177fc30ff21b1eaa5 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Fri, 24 Jun 2022 18:01:47 +0200 Subject: [PATCH 09/10] Implement suggestions from PR reviews --- jplag.frontend.R/pom.xml | 1 - .../src/main/java/de/jplag/R/RCombinedBaseListener.java | 2 +- jplag.frontend.R/src/main/java/de/jplag/R/RToken.java | 1 - .../src/test/java/de/jplag/R/RFrontendTest.java | 8 +++++--- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/jplag.frontend.R/pom.xml b/jplag.frontend.R/pom.xml index 1c088329e..f736025e6 100644 --- a/jplag.frontend.R/pom.xml +++ b/jplag.frontend.R/pom.xml @@ -31,7 +31,6 @@ org.antlr antlr4-maven-plugin - 4.10.1 diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java b/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java index ba4c6238c..4ef4a8a6f 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java @@ -9,7 +9,7 @@ /** * Empty base implementation for {@link RListener} and {@link RFilterListener}. */ -public class RCombinedBaseListener implements RListener, RFilterListener { +public abstract class RCombinedBaseListener implements RListener, RFilterListener { @Override public void enterStream(RFilter.StreamContext context) { diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java index c55731f09..21089f19d 100644 --- a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java +++ b/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java @@ -3,7 +3,6 @@ /** * This class represents the occurrence of an R Token in the source code. Based on an R frontend for JPlag v2.15 by Olmo * Kramer, see their JPlag fork. - * @author Robin Maisch */ public class RToken extends de.jplag.Token implements RTokenConstants { diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java index 2eb2ee166..055ce5ad5 100644 --- a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java +++ b/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java @@ -32,6 +32,8 @@ public class RFrontendTest { * Test source file that is supposed to produce a complete set of tokens, i.e. all types of tokens. */ private static final String COMPLETE_TEST_FILE = "Complete.R"; + public static final int NOT_SET = -1; + private final Logger logger = LoggerFactory.getLogger("R frontend test"); private final String[] testFiles = new String[] {"Game.R", COMPLETE_TEST_FILE}; private final File testFileLocation = Path.of("src", "test", "resources", "de", "jplag", "R").toFile(); @@ -75,7 +77,7 @@ private void testSourceCoverage(String fileName, TokenList tokens) { if (codeLines.length > tokenLines.length) { var diffLine = IntStream.range(0, codeLines.length) - .dropWhile(lineIdx -> lineIdx < tokenLines.length && codeLines[lineIdx] == tokenLines[lineIdx]).findFirst(); + .dropWhile(lineIndex -> lineIndex < tokenLines.length && codeLines[lineIndex] == tokenLines[lineIndex]).findFirst(); diffLine.ifPresent( lineIdx -> fail("Line %d of file '%s' is not represented in the token list.".formatted(codeLines[lineIdx], fileName))); } @@ -98,9 +100,9 @@ private void testTokenCoverage(TokenList tokens, String fileName) { if (allTokens.length > foundTokens.length) { var diffLine = IntStream.range(0, allTokens.length) - .dropWhile(lineIdx -> lineIdx < foundTokens.length && allTokens[lineIdx] == foundTokens[lineIdx]).findFirst(); + .dropWhile(lineIndex -> lineIndex < foundTokens.length && allTokens[lineIndex] == foundTokens[lineIndex]).findFirst(); diffLine.ifPresent(lineIdx -> fail("Token type %s was not found in the complete code example '%s'." - .formatted(new RToken(allTokens[lineIdx], fileName, -1, -1, -1).type2string(), fileName))); + .formatted(new RToken(allTokens[lineIdx], fileName, NOT_SET, NOT_SET, NOT_SET).type2string(), fileName))); } assertArrayEquals(allTokens, foundTokens); } From 63e9f00a4044717d43c233ae0c5988404e90ff65 Mon Sep 17 00:00:00 2001 From: Robin Maisch Date: Sat, 25 Jun 2022 12:59:28 +0200 Subject: [PATCH 10/10] Rename package and artifactId 'R' to 'rlang' --- {jplag.frontend.R => jplag.frontend.rlang}/README.md | 0 {jplag.frontend.R => jplag.frontend.rlang}/pom.xml | 2 +- .../src/main/antlr4/de/jplag/R/grammar/R.g4 | 0 .../src/main/antlr4/de/jplag/R/grammar/RFilter.g4 | 0 .../src/main/java/de/jplag/R/JplagRListener.java | 0 .../src/main/java/de/jplag/R/Language.java | 0 .../src/main/java/de/jplag/R/RCombinedBaseListener.java | 0 .../src/main/java/de/jplag/R/RParserAdapter.java | 0 .../src/main/java/de/jplag/R/RToken.java | 0 .../src/main/java/de/jplag/R/RTokenConstants.java | 0 .../src/test/java/de/jplag/R/RFrontendTest.java | 0 .../src/test/resources/de/jplag/R/Complete.R | 0 .../src/test/resources/de/jplag/R/Game.R | 0 jplag/pom.xml | 2 +- jplag/src/main/java/de/jplag/options/LanguageOption.java | 2 +- pom.xml | 4 ++-- 16 files changed, 5 insertions(+), 5 deletions(-) rename {jplag.frontend.R => jplag.frontend.rlang}/README.md (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/pom.xml (97%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/antlr4/de/jplag/R/grammar/R.g4 (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/java/de/jplag/R/JplagRListener.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/java/de/jplag/R/Language.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/java/de/jplag/R/RCombinedBaseListener.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/java/de/jplag/R/RParserAdapter.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/java/de/jplag/R/RToken.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/main/java/de/jplag/R/RTokenConstants.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/test/java/de/jplag/R/RFrontendTest.java (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/test/resources/de/jplag/R/Complete.R (100%) rename {jplag.frontend.R => jplag.frontend.rlang}/src/test/resources/de/jplag/R/Game.R (100%) diff --git a/jplag.frontend.R/README.md b/jplag.frontend.rlang/README.md similarity index 100% rename from jplag.frontend.R/README.md rename to jplag.frontend.rlang/README.md diff --git a/jplag.frontend.R/pom.xml b/jplag.frontend.rlang/pom.xml similarity index 97% rename from jplag.frontend.R/pom.xml rename to jplag.frontend.rlang/pom.xml index f736025e6..0683719db 100644 --- a/jplag.frontend.R/pom.xml +++ b/jplag.frontend.rlang/pom.xml @@ -6,7 +6,7 @@ aggregator ${revision} - R + rlang diff --git a/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/R.g4 b/jplag.frontend.rlang/src/main/antlr4/de/jplag/R/grammar/R.g4 similarity index 100% rename from jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/R.g4 rename to jplag.frontend.rlang/src/main/antlr4/de/jplag/R/grammar/R.g4 diff --git a/jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 b/jplag.frontend.rlang/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 similarity index 100% rename from jplag.frontend.R/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 rename to jplag.frontend.rlang/src/main/antlr4/de/jplag/R/grammar/RFilter.g4 diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java b/jplag.frontend.rlang/src/main/java/de/jplag/R/JplagRListener.java similarity index 100% rename from jplag.frontend.R/src/main/java/de/jplag/R/JplagRListener.java rename to jplag.frontend.rlang/src/main/java/de/jplag/R/JplagRListener.java diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/Language.java b/jplag.frontend.rlang/src/main/java/de/jplag/R/Language.java similarity index 100% rename from jplag.frontend.R/src/main/java/de/jplag/R/Language.java rename to jplag.frontend.rlang/src/main/java/de/jplag/R/Language.java diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java b/jplag.frontend.rlang/src/main/java/de/jplag/R/RCombinedBaseListener.java similarity index 100% rename from jplag.frontend.R/src/main/java/de/jplag/R/RCombinedBaseListener.java rename to jplag.frontend.rlang/src/main/java/de/jplag/R/RCombinedBaseListener.java diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java b/jplag.frontend.rlang/src/main/java/de/jplag/R/RParserAdapter.java similarity index 100% rename from jplag.frontend.R/src/main/java/de/jplag/R/RParserAdapter.java rename to jplag.frontend.rlang/src/main/java/de/jplag/R/RParserAdapter.java diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RToken.java b/jplag.frontend.rlang/src/main/java/de/jplag/R/RToken.java similarity index 100% rename from jplag.frontend.R/src/main/java/de/jplag/R/RToken.java rename to jplag.frontend.rlang/src/main/java/de/jplag/R/RToken.java diff --git a/jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java b/jplag.frontend.rlang/src/main/java/de/jplag/R/RTokenConstants.java similarity index 100% rename from jplag.frontend.R/src/main/java/de/jplag/R/RTokenConstants.java rename to jplag.frontend.rlang/src/main/java/de/jplag/R/RTokenConstants.java diff --git a/jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java b/jplag.frontend.rlang/src/test/java/de/jplag/R/RFrontendTest.java similarity index 100% rename from jplag.frontend.R/src/test/java/de/jplag/R/RFrontendTest.java rename to jplag.frontend.rlang/src/test/java/de/jplag/R/RFrontendTest.java diff --git a/jplag.frontend.R/src/test/resources/de/jplag/R/Complete.R b/jplag.frontend.rlang/src/test/resources/de/jplag/R/Complete.R similarity index 100% rename from jplag.frontend.R/src/test/resources/de/jplag/R/Complete.R rename to jplag.frontend.rlang/src/test/resources/de/jplag/R/Complete.R diff --git a/jplag.frontend.R/src/test/resources/de/jplag/R/Game.R b/jplag.frontend.rlang/src/test/resources/de/jplag/R/Game.R similarity index 100% rename from jplag.frontend.R/src/test/resources/de/jplag/R/Game.R rename to jplag.frontend.rlang/src/test/resources/de/jplag/R/Game.R diff --git a/jplag/pom.xml b/jplag/pom.xml index 3612234be..d01ca4128 100644 --- a/jplag/pom.xml +++ b/jplag/pom.xml @@ -47,7 +47,7 @@ de.jplag - R + rlang de.jplag diff --git a/jplag/src/main/java/de/jplag/options/LanguageOption.java b/jplag/src/main/java/de/jplag/options/LanguageOption.java index 47411e111..fcde70724 100644 --- a/jplag/src/main/java/de/jplag/options/LanguageOption.java +++ b/jplag/src/main/java/de/jplag/options/LanguageOption.java @@ -13,7 +13,7 @@ public enum LanguageOption { PYTHON_3("python3", "de.jplag.python3.Language"), C_CPP("cpp", "de.jplag.cpp.Language"), C_SHARP("csharp", "de.jplag.csharp.Language"), - R("r", "de.jplag.R.Language"), + R_LANG("rlang", "de.jplag.rlang.Language"), CHAR("char", "de.jplag.chars.Language"), TEXT("text", "de.jplag.text.Language"), SCHEME("scheme", "de.jplag.scheme.Language"); diff --git a/pom.xml b/pom.xml index a2455c766..12f80dbad 100644 --- a/pom.xml +++ b/pom.xml @@ -49,7 +49,7 @@ jplag.frontend.csharp-6 jplag.frontend.java jplag.frontend.python-3 - jplag.frontend.R + jplag.frontend.rlang jplag.frontend.scheme jplag.frontend.text jplag @@ -159,7 +159,7 @@ ${project.groupId} - R + rlang ${project.version}