diff --git a/pmd-dist/pom.xml b/pmd-dist/pom.xml
index a992ec8570..e25aa23c7c 100644
--- a/pmd-dist/pom.xml
+++ b/pmd-dist/pom.xml
@@ -127,6 +127,11 @@
pmd-groovy
${project.version}
+
+ net.sourceforge.pmd
+ pmd-lua
+ ${project.version}
+
net.sourceforge.pmd
pmd-java
diff --git a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
index 5da0b5f5a1..8caab02442 100644
--- a/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
+++ b/pmd-dist/src/test/java/net/sourceforge/pmd/it/BinaryDistributionIT.java
@@ -112,7 +112,7 @@ public class BinaryDistributionIT {
result = CpdExecutor.runCpd(tempDir, "-h");
- result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]");
+ result.assertExecutionResult(1, "Supported languages: [apex, cpp, cs, dart, ecmascript, fortran, go, groovy, java, jsp, kotlin, lua, matlab, objectivec, perl, php, plsql, python, ruby, scala, swift, vf]");
result = CpdExecutor.runCpd(tempDir, "--minimum-tokens", "10", "--format", "text", "--files", srcDir);
result.assertExecutionResult(4, "Found a 10 line (55 tokens) duplication in the following files:");
diff --git a/pmd-lua/pom.xml b/pmd-lua/pom.xml
new file mode 100644
index 0000000000..6e3c30cd2f
--- /dev/null
+++ b/pmd-lua/pom.xml
@@ -0,0 +1,57 @@
+
+
+ 4.0.0
+ pmd-lua
+ PMD Lua
+
+
+ net.sourceforge.pmd
+ pmd
+ 6.16.0-SNAPSHOT
+
+
+
+
+
+ org.antlr
+ antlr4-maven-plugin
+
+
+
+ maven-resources-plugin
+
+ false
+
+ ${*}
+
+
+
+
+
+
+
+
+ org.antlr
+ antlr4-runtime
+
+
+ net.sourceforge.pmd
+ pmd-core
+
+
+ commons-io
+ commons-io
+
+
+
+ junit
+ junit
+ test
+
+
+ net.sourceforge.pmd
+ pmd-test
+ test
+
+
+
diff --git a/pmd-lua/src/main/antlr4/net/sourceforge/pmd/lang/lua/antlr4/Lua.g4 b/pmd-lua/src/main/antlr4/net/sourceforge/pmd/lang/lua/antlr4/Lua.g4
new file mode 100644
index 0000000000..497f11060e
--- /dev/null
+++ b/pmd-lua/src/main/antlr4/net/sourceforge/pmd/lang/lua/antlr4/Lua.g4
@@ -0,0 +1,335 @@
+/*
+BSD License
+
+Copyright (c) 2013, Kazunori Sakamoto
+Copyright (c) 2016, Alexander Alexeev
+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. Neither the NAME of Rainer Schuster nor the NAMEs of its contributors
+ may be used to endorse or promote products derived from this software
+ without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+"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 COPYRIGHT
+HOLDER OR CONTRIBUTORS 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.
+
+This grammar file derived from:
+
+ Lua 5.3 Reference Manual
+ http://www.lua.org/manual/5.3/manual.html
+
+ Lua 5.2 Reference Manual
+ http://www.lua.org/manual/5.2/manual.html
+
+ Lua 5.1 grammar written by Nicolai Mainiero
+ http://www.antlr3.org/grammar/1178608849736/Lua.g
+
+Tested by Kazunori Sakamoto with Test suite for Lua 5.2 (http://www.lua.org/tests/5.2/)
+
+Tested by Alexander Alexeev with Test suite for Lua 5.3 http://www.lua.org/tests/lua-5.3.2-tests.tar.gz
+*/
+
+grammar Lua;
+
+chunk
+ : block EOF
+ ;
+
+block
+ : stat* retstat?
+ ;
+
+stat
+ : ';'
+ | varlist '=' explist
+ | functioncall
+ | label
+ | 'break'
+ | 'goto' NAME
+ | 'do' block 'end'
+ | 'while' exp 'do' block 'end'
+ | 'repeat' block 'until' exp
+ | 'if' exp 'then' block ('elseif' exp 'then' block)* ('else' block)? 'end'
+ | 'for' NAME '=' exp ',' exp (',' exp)? 'do' block 'end'
+ | 'for' namelist 'in' explist 'do' block 'end'
+ | 'function' funcname funcbody
+ | 'local' 'function' NAME funcbody
+ | 'local' namelist ('=' explist)?
+ ;
+
+retstat
+ : 'return' explist? ';'?
+ ;
+
+label
+ : '::' NAME '::'
+ ;
+
+funcname
+ : NAME ('.' NAME)* (':' NAME)?
+ ;
+
+varlist
+ : var (',' var)*
+ ;
+
+namelist
+ : NAME (',' NAME)*
+ ;
+
+explist
+ : exp (',' exp)*
+ ;
+
+exp
+ : 'nil' | 'false' | 'true'
+ | number
+ | string
+ | '...'
+ | functiondef
+ | prefixexp
+ | tableconstructor
+ | exp operatorPower exp
+ | operatorUnary exp
+ | exp operatorMulDivMod exp
+ | exp operatorAddSub exp
+ | exp operatorStrcat exp
+ | exp operatorComparison exp
+ | exp operatorAnd exp
+ | exp operatorOr exp
+ | exp operatorBitwise exp
+ ;
+
+prefixexp
+ : varOrExp nameAndArgs*
+ ;
+
+functioncall
+ : varOrExp nameAndArgs+
+ ;
+
+varOrExp
+ : var | '(' exp ')'
+ ;
+
+var
+ : (NAME | '(' exp ')' varSuffix) varSuffix*
+ ;
+
+varSuffix
+ : nameAndArgs* ('[' exp ']' | '.' NAME)
+ ;
+
+nameAndArgs
+ : (':' NAME)? args
+ ;
+
+/*
+var
+ : NAME | prefixexp '[' exp ']' | prefixexp '.' NAME
+ ;
+
+prefixexp
+ : var | functioncall | '(' exp ')'
+ ;
+
+functioncall
+ : prefixexp args | prefixexp ':' NAME args
+ ;
+*/
+
+args
+ : '(' explist? ')' | tableconstructor | string
+ ;
+
+functiondef
+ : 'function' funcbody
+ ;
+
+funcbody
+ : '(' parlist? ')' block 'end'
+ ;
+
+parlist
+ : namelist (',' '...')? | '...'
+ ;
+
+tableconstructor
+ : '{' fieldlist? '}'
+ ;
+
+fieldlist
+ : field (fieldsep field)* fieldsep?
+ ;
+
+field
+ : '[' exp ']' '=' exp | NAME '=' exp | exp
+ ;
+
+fieldsep
+ : ',' | ';'
+ ;
+
+operatorOr
+ : 'or';
+
+operatorAnd
+ : 'and';
+
+operatorComparison
+ : '<' | '>' | '<=' | '>=' | '~=' | '==';
+
+operatorStrcat
+ : '..';
+
+operatorAddSub
+ : '+' | '-';
+
+operatorMulDivMod
+ : '*' | '/' | '%' | '//';
+
+operatorBitwise
+ : '&' | '|' | '~' | '<<' | '>>';
+
+operatorUnary
+ : 'not' | '#' | '-' | '~';
+
+operatorPower
+ : '^';
+
+number
+ : INT | HEX | FLOAT | HEX_FLOAT
+ ;
+
+string
+ : NORMALSTRING | CHARSTRING | LONGSTRING
+ ;
+
+// LEXER
+
+NAME
+ : [a-zA-Z_][a-zA-Z_0-9]*
+ ;
+
+NORMALSTRING
+ : '"' ( EscapeSequence | ~('\\'|'"') )* '"'
+ ;
+
+CHARSTRING
+ : '\'' ( EscapeSequence | ~('\''|'\\') )* '\''
+ ;
+
+LONGSTRING
+ : '[' NESTED_STR ']'
+ ;
+
+fragment
+NESTED_STR
+ : '=' NESTED_STR '='
+ | '[' .*? ']'
+ ;
+
+INT
+ : Digit+
+ ;
+
+HEX
+ : '0' [xX] HexDigit+
+ ;
+
+FLOAT
+ : Digit+ '.' Digit* ExponentPart?
+ | '.' Digit+ ExponentPart?
+ | Digit+ ExponentPart
+ ;
+
+HEX_FLOAT
+ : '0' [xX] HexDigit+ '.' HexDigit* HexExponentPart?
+ | '0' [xX] '.' HexDigit+ HexExponentPart?
+ | '0' [xX] HexDigit+ HexExponentPart
+ ;
+
+fragment
+ExponentPart
+ : [eE] [+-]? Digit+
+ ;
+
+fragment
+HexExponentPart
+ : [pP] [+-]? Digit+
+ ;
+
+fragment
+EscapeSequence
+ : '\\' [abfnrtvz"'\\]
+ | '\\' '\r'? '\n'
+ | DecimalEscape
+ | HexEscape
+ | UtfEscape
+ ;
+
+fragment
+DecimalEscape
+ : '\\' Digit
+ | '\\' Digit Digit
+ | '\\' [0-2] Digit Digit
+ ;
+
+fragment
+HexEscape
+ : '\\' 'x' HexDigit HexDigit
+ ;
+
+fragment
+UtfEscape
+ : '\\' 'u{' HexDigit+ '}'
+ ;
+
+fragment
+Digit
+ : [0-9]
+ ;
+
+fragment
+HexDigit
+ : [0-9a-fA-F]
+ ;
+
+COMMENT
+ : '--[' NESTED_STR ']' -> channel(HIDDEN)
+ ;
+
+LINE_COMMENT
+ : '--'
+ ( // --
+ | '[' '='* // --[==
+ | '[' '='* ~('='|'['|'\r'|'\n') ~('\r'|'\n')* // --[==AA
+ | ~('['|'\r'|'\n') ~('\r'|'\n')* // --AAA
+ ) ('\r\n'|'\r'|'\n'|EOF)
+ -> channel(HIDDEN)
+ ;
+
+WS
+ : [ \t\u000C\r\n]+ -> skip
+ ;
+
+SHEBANG
+ : '#' '!' ~('\n'|'\r')* -> channel(HIDDEN)
+ ;
diff --git a/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaLanguage.java b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaLanguage.java
new file mode 100644
index 0000000000..e2a87ec878
--- /dev/null
+++ b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaLanguage.java
@@ -0,0 +1,18 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+/**
+ * Language implementation for Lua
+ */
+public class LuaLanguage extends AbstractLanguage {
+
+ /**
+ * Creates a new Lua Language instance.
+ */
+ public LuaLanguage() {
+ super("Lua", "lua", new LuaTokenizer(), ".lua");
+ }
+}
diff --git a/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaTokenizer.java b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaTokenizer.java
new file mode 100644
index 0000000000..23c292dbe7
--- /dev/null
+++ b/pmd-lua/src/main/java/net/sourceforge/pmd/cpd/LuaTokenizer.java
@@ -0,0 +1,28 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+import org.antlr.v4.runtime.CharStream;
+
+import net.sourceforge.pmd.cpd.token.AntlrTokenFilter;
+import net.sourceforge.pmd.lang.antlr.AntlrTokenManager;
+import net.sourceforge.pmd.lang.lua.antlr4.LuaLexer;
+
+/**
+ * The Lua Tokenizer
+ */
+public class LuaTokenizer extends AntlrTokenizer {
+
+ @Override
+ protected AntlrTokenManager getLexerForSource(SourceCode sourceCode) {
+ CharStream charStream = AntlrTokenizer.getCharStreamFromSourceCode(sourceCode);
+ return new AntlrTokenManager(new LuaLexer(charStream), sourceCode.getFileName());
+ }
+
+ @Override
+ protected AntlrTokenFilter getTokenFilter(final AntlrTokenManager tokenManager) {
+ return new AntlrTokenFilter(tokenManager);
+ }
+}
diff --git a/pmd-lua/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language b/pmd-lua/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language
new file mode 100644
index 0000000000..ff792867ee
--- /dev/null
+++ b/pmd-lua/src/main/resources/META-INF/services/net.sourceforge.pmd.cpd.Language
@@ -0,0 +1 @@
+net.sourceforge.pmd.cpd.LuaLanguage
diff --git a/pmd-lua/src/test/java/net/sourceforge/pmd/cpd/LuaTokenizerTest.java b/pmd-lua/src/test/java/net/sourceforge/pmd/cpd/LuaTokenizerTest.java
new file mode 100644
index 0000000000..5328e71823
--- /dev/null
+++ b/pmd-lua/src/test/java/net/sourceforge/pmd/cpd/LuaTokenizerTest.java
@@ -0,0 +1,56 @@
+/**
+ * BSD-style license; for more info see http://pmd.sourceforge.net/license.html
+ */
+
+package net.sourceforge.pmd.cpd;
+
+import java.io.IOException;
+import java.nio.charset.StandardCharsets;
+import java.util.Arrays;
+import java.util.Collection;
+
+import org.apache.commons.io.IOUtils;
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.Parameterized;
+
+import net.sourceforge.pmd.testframework.AbstractTokenizerTest;
+
+@RunWith(Parameterized.class)
+public class LuaTokenizerTest extends AbstractTokenizerTest {
+
+ private final String filename;
+ private final int nExpectedTokens;
+
+ public LuaTokenizerTest(String filename, int nExpectedTokens) {
+ this.filename = filename;
+ this.nExpectedTokens = nExpectedTokens;
+ }
+
+ @Parameterized.Parameters
+ public static Collection