This commit is contained in:
neoe 2018-03-21 15:51:33 +08:00
commit b8627cab31
9 changed files with 750 additions and 0 deletions

6
.classpath Normal file
View File

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER"/>
<classpathentry kind="output" path="bin"/>
</classpath>

8
.gitignore vendored Normal file
View File

@ -0,0 +1,8 @@
**/bin/
**/build/
**/dist/
**/private/
.svn/
**/.idea/
**/*.class
**/*.log

17
.project Normal file
View File

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>luaformatter</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

10
README.md Normal file
View File

@ -0,0 +1,10 @@
LUAFORMATTER
=====================
luaformatter is a lua source code formatter written in Java by Neoe.
It use a handcrafted parser as little as 300 lines of Java code.
# How to use
* install java (JRE from java.com) if not installed
* run.cmd (java -jar luaformatter.jar <input-filename>), output file will be put into the same dir as input source file.

6
mybuild Normal file
View File

@ -0,0 +1,6 @@
{ /* neoebuild script */
prjs:[
[luaformatter, "." , { main: "neoe.tools.LuaFormatter" }]
]
}

View File

@ -0,0 +1,325 @@
package neoe.tools;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.util.Stack;
import neoe.util.FileUtil;
public class LuaFormatter {
private static final boolean TESTING_LEVEL = false;
private static final boolean DEBUG = false;
public static void main(String[] args) throws Exception {
new LuaFormatter().formatFile(args[0]);
}
public static String ts() {
return Long.toString(System.currentTimeMillis(), 36);
}
private Writer debug;
public void formatFile(String fn) throws Exception {
if (DEBUG)
debug = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("debug.log"), "utf8"));
if (DEBUG)
debug.write("read " + fn + "\n");
String txt = FileUtil.readString(new FileInputStream(fn), "GBK");
String res = format(txt);
File f2 = new File(fn + /* "." + ts() + */ ".fmt.lua");
FileUtil.save(res.getBytes("UTF8"), f2.getAbsolutePath());
if (DEBUG)
debug.close();
}
StringBuilder sb;
LuaTokens tokens;
public String format(String txt) throws Exception {
sb = new StringBuilder();
tokens = new LuaTokens(txt);
loop(LuaTokenType.SPACE, null, null);
return sb.toString();
}
private void loop(LuaTokenType preType, String until, LuaTokenType operator) throws Exception {
lastType = preType;
addSpace();
while (true) {
Object[] tt = tokens.next();
if (tt == null)
break;
LuaTokenType type = (LuaTokenType) tt[0];
String token = (String) tt[1];
if (token == null)
break;
if (DEBUG)
debug.write(String.format("t:%s,v:%s\n", type, token));
addToken(type, token);
if (LuaTokenType.OPERATOR.equals(operator) && token.indexOf(until) >= 0) {
break;
}
if (until != null && token.equals(until)) {
break;
}
}
}
LuaTokenType lastType = LuaTokenType.SPACE;
private int indent;
private int changedLine;
private String lastToken;
private boolean forcedChangeLine;
private Stack stack = new Stack();
private void addToken(LuaTokenType type, String token) throws Exception {
forcedChangeLine = false;
if (type.equals(LuaTokenType.COMMENT)) {
if (changedLine > 0) {
printIndent();
changedLine = 0;
}
addSpace();
if (!isMultiLineToken(token)) {
sb.append(normalComment(token.trim()));
changeLine();
} else {
sb.append(token);
}
} else if (type.equals(LuaTokenType.SPACE)) {
if (!lastType.equals(LuaTokenType.SPACE)) {
int cnt = 0;
for (char c : token.toCharArray()) {
if (c == '\n') {
if (cnt >= changedLine) {
// sb.append("[A]");
sb.append(c);
}
cnt++;
}
}
if (cnt <= 0 && !lastType.equals(LuaTokenType.SPACE)) {
sb.append(" ");
} else {
if (changedLine > 0) {
printIndent();
changedLine = 0;
} else {
changedLine = cnt;
}
}
}
} else if (type.equals(LuaTokenType.IDENTIFIER)) {
if ("end".equals(token)) {
String key = decIndent();
if (changedLine > 0) {
printIndent();
changedLine = 0;
}
sb.append(token);
changeLine();
if ("function".equals(key)) {
sb.append("\n");
}
} else if ("else".equals(token)) {
String key = decIndent();
if (changedLine > 0) {
printIndent();
changedLine = 0;
}
sb.append(token);
incIndent(key);
} else if ("elseif".equals(token)) {
String key = decIndent();
if (changedLine > 0) {
printIndent();
changedLine = 0;
}
sb.append(token);
incIndent(key);
} else {
if (changedLine > 0) {
printIndent();
changedLine = 0;
}
addSpace();
sb.append(token);
if ("function".equals(token)) {
loop(type, ")", LuaTokenType.OPERATOR);
changeLine();
incIndent(token);
} else if ("until".equals(token)) {
decIndent();
changeLine();
} else if ("while".equals(token)) {
loop(type, "do", null);
incIndent(token);
changeLine();
} else if ("for".equals(token)) {
loop(type, "do", null);
incIndent(token);
changeLine();
} else if ("if".equals(token)) {
loop(type, "then", null);
incIndent(token);
changeLine();
} else if ("repeat".equals(token)) {
decIndent();
changeLine();
}
}
} else if (type.equals(LuaTokenType.OPERATOR) && token.startsWith("}")) {
String key = decIndent();
if (changedLine > 0) {
printIndent();
changedLine = 0;
} else {
addSpace();
}
sb.append(token);
if (indent == 0) {
sb.append("\n");
}
} else if (type.equals(LuaTokenType.OPERATOR) && token.equals(".")) {
sb.append(token);
} else {
if (changedLine > 0) {
printIndent();
changedLine = 0;
}
addSpace();
sb.append(token);
if (type.equals(LuaTokenType.OPERATOR)) {
for (char c : token.toCharArray()) {
if (c == '{') {
incIndent("{");
} else if (c == '}') {
decIndent();
} else if (c == ')') {
decIndent();
} else if (c == '(') {
incIndent("(");
} else if (c == ']') {
decIndent();
} else if (c == '[') {
incIndent("[");
}
}
}
}
lastType = type;
lastToken = token;
if (forcedChangeLine) {
lastType = LuaTokenType.SPACE;
}
}
private String normalComment(String s) {
if (s.startsWith("--") && s.length() > 2 && s.charAt(2) != ' ' && s.charAt(2) != '-') {
s = "-- " + s.substring(2);
}
return s;
}
private boolean isMultiLineToken(String token) {
return token.endsWith("]--");
}
private void printIndent() {
if (TESTING_LEVEL)
sb.append("[" + indent + "]");
for (int i = 0; i < indent; i++) {
sb.append("\t");
}
// if (indent > 0)
lastType = LuaTokenType.SPACE;
}
// private void passToOP(String s) {
// while (true) {
// Object[] tt = tokens.next();
// if (tt == null) {
// return;
// }
// String token = (String) tt[1];
// sb.append(token);
// if (LuaTokenType.OPERATOR.equals(tt[0]) && token.indexOf(s) >= 0) {
// return;
// }
// }
//
// }
private void incIndent(String key) {
// sb.append("[i++]");
indent++;
stack.push(key);
}
// private void passTo(String s) {
// while (true) {
// Object[] tt = tokens.next();
// if (tt == null) {
// return;
// }
// String token = (String) tt[1];
// sb.append(token);
// if (token.equals(s)) {
// return;
// }
// }
//
// }
private void changeLine() {
if (changedLine <= 0) {
sb.append("\n");
}
changedLine = 1;
forcedChangeLine = true;
}
private String decIndent() {
indent--;
// if (stack.isEmpty()) return "";
return (String) stack.pop();
}
private void addSpace() {
if (!lastType.equals(LuaTokenType.SPACE) && !".".equals(lastToken)) {
sb.append(" ");
}
}
}
/*-
* stat ::= while exp do block end
stat ::= repeat block until exp
stat ::= if exp then block {elseif exp then block} [else block] end
stat ::= for Name = exp , exp [, exp] do block end
stat ::= for namelist in explist do block end
function f () body end
*
*/

View File

@ -0,0 +1,5 @@
package neoe.tools;
public enum LuaTokenType {
COMMENT, SPACE, IDENTIFIER, STRING, OPERATOR,
}

View File

@ -0,0 +1,170 @@
package neoe.tools;
public class LuaTokens {
private String txt;
private int p;
private LuaTokenType type;
public LuaTokens(String txt) {
this.txt = txt;
this.p = 0;
}
StringBuilder sb = new StringBuilder();
public Object[] next() {
if (p >= txt.length())
return null;
sb.setLength(0);
char c = txt.charAt(p);
if (isSpace(c)) {
type = LuaTokenType.SPACE;
sb.append(c);
p++;
char c2;
while (p < txt.length()) {
if (isSpace(c2 = txt.charAt(p))) {
sb.append(c2);
p++;
} else {
break;
}
}
return submit(type, sb.toString());
} else if (isIdentifier(c)) {
type = LuaTokenType.IDENTIFIER;
sb.append(c);
p++;
char c2;
while (p < txt.length()) {
if (isIdentifier(c2 = txt.charAt(p))) {
sb.append(c2);
p++;
} else {
break;
}
}
return submit(type, sb.toString());
} else if (c == '-' && peek(1) == '-') {
type = LuaTokenType.COMMENT;
sb.append("--");
p += 2;
int level = peekLongBrackets();
if (level < 0) {
readUntil("\n");
} else {
readUntilLongBrackets(level);
if (peek(0) == '-' && peek(1) == '-') {
p += 2;
sb.append("--");
}
}
return submit(type, sb.toString());
} else if (c == '\'' || c == '"') {
type = LuaTokenType.STRING;
sb.append(c);
p++;
readUntil("" + (char) c);
return submit(type, sb.toString());
} else {
int level = peekLongBrackets();
if (level < 0) {
type = LuaTokenType.OPERATOR;
sb.append(c);
p++;
if (isOp1(c)) {
} else {
char c2;
while (p < txt.length()) {
if (isOperator(c2 = txt.charAt(p))) {
sb.append(c2);
p++;
} else {
break;
}
}
}
if (false) {
String s1 = sb.toString();
for (int i = 0; i < s1.length(); i++) {
System.out.printf("%d:%c ", (int) sb.charAt(i), sb.charAt(i));
}
System.out.println();
}
return submit(type, sb.toString());
} else {
type = LuaTokenType.STRING;
readUntilLongBrackets(level);
return submit(type, sb.toString());
}
}
}
private boolean isOp1(char c) {
return "[]{}()".indexOf(c) >= 0;
}
private Object[] submit(LuaTokenType type, String s) {
return new Object[] { type, s };
}
private boolean isOperator(char c) {
return !isIdentifier(c) && !isSpace(c) && c != '"' && c != '\'' && !isOp1(c);
}
private void readUntilLongBrackets(int level) {
// sb.append("<LV:" + level + ">");
StringBuilder sb = new StringBuilder("]");
for (int i = 0; i < level; i++) {
sb.append('=');
}
sb.append(']');
readUntil(sb.toString());
}
private int peekLongBrackets() {
if (peek(0) == '[') {
int lv = 0;
while (peek(lv + 1) == '=') {
lv++;
}
if (peek(1 + lv) == '[') {
return lv;
}
}
return -1;
}
private void readUntil(String s) {
int p1 = txt.indexOf(s, p);
if (p1 < 0) {
sb.append(txt.substring(p));
} else {
sb.append(txt.substring(p, p1 + s.length()));
p = p1 + s.length();
}
}
private char peek(int i) {
if (p + i >= txt.length())
return 0;
return txt.charAt(p + i);
}
private boolean isIdentifier(char c) {
return Character.isDigit(c) || Character.isLetter(c) || c == '_';
}
private boolean isSpace(char c) {
return Character.isWhitespace(c);
}
}

203
src/neoe/util/FileUtil.java Normal file
View File

@ -0,0 +1,203 @@
package neoe.util;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.EOFException;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
public class FileUtil {
public static void copy(File from, File to) throws IOException {
FileInputStream in = new FileInputStream(from);
FileOutputStream out = new FileOutputStream(to);
copy(in, out);
in.close();
out.close();
}
public static void copy(InputStream in, OutputStream outstream) throws IOException {
BufferedOutputStream out = new BufferedOutputStream(outstream);
byte[] buf = new byte[1024 * 16];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
in.close();
out.close();
}
public static void copy2(InputStream in, OutputStream outstream) throws IOException {
BufferedOutputStream out = new BufferedOutputStream(outstream);
byte[] buf = new byte[1024 * 16];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.flush();
}
public static BufferedReader getBufferedReader(String fn, String enc) throws IOException {
InputStream in = getFileInputStream(fn);
return new BufferedReader(new InputStreamReader(in, enc));
}
public static InputStream getFileInputStream(String fn) {
System.out.println("getFileInputStream:in " + FileUtil.class.getClassLoader() + ":" + fn);
InputStream in = FileUtil.class.getClassLoader().getResourceAsStream(fn);
if (in == null) {
if (fn.startsWith("/")) {
in = FileUtil.class.getClassLoader().getResourceAsStream(fn.substring(1));
} else {
in = FileUtil.class.getClassLoader().getResourceAsStream("/" + fn);
}
}
return in;
}
public static BufferedReader getRawBufferedReader(String fn, String enc)
throws UnsupportedEncodingException, FileNotFoundException {
InputStream in = new FileInputStream(fn);
return new BufferedReader(new InputStreamReader(in, enc));
}
public static void pass(InputStream in, OutputStream out) throws IOException {
byte[] buf = new byte[1024 * 16];
int len;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
}
out.flush();
}
public static void pass(InputStream in, OutputStream out, long total) throws IOException {
byte[] buf = new byte[1024 * 16];
int len;
long sum = 0;
while ((len = in.read(buf)) > 0) {
out.write(buf, 0, len);
sum += len;
if (sum >= total) {
// System.out.println("read finish");
break;
}
}
out.flush();
}
public static String readString(InputStream ins, String enc) throws IOException {
if (enc == null)
enc = "UTF-8";
BufferedReader in = new BufferedReader(new InputStreamReader(ins, enc));
char[] buf = new char[1000];
int len;
StringBuffer sb = new StringBuffer();
while ((len = in.read(buf)) > 0) {
sb.append(buf, 0, len);
}
in.close();
return sb.toString();
}
public static List<String> readStringBig(File f, String enc) throws IOException {
if (enc == null)
enc = "UTF-8";
BufferedReader in = new BufferedReader(new InputStreamReader(new FileInputStream(f), enc));
String line;
List<String> ret = new ArrayList<String>();
while (true) {
line = in.readLine();
if (line == null)
break;
ret.add(line);
}
in.close();
return ret;
}
public static byte[] read(InputStream in) throws IOException {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
copy(in, baos);
return baos.toByteArray();
}
public static void save(byte[] bs, String fn) throws IOException {
File f = new File(fn);
f.getAbsoluteFile().getParentFile().mkdirs();
File ftmp = new File(fn + "." + System.currentTimeMillis());
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(ftmp));
out.write(bs);
out.close();
if (f.exists())
f.delete();
Files.move(ftmp.toPath(), f.toPath());
}
public static Iterable<CharSequence> split(final CharSequence src, final char sep) {
return new Iterable<CharSequence>() {
@Override
public Iterator<CharSequence> iterator() {
return new Iterator<CharSequence>() {
int p1;
@Override
public CharSequence next() {
int p2 = -1;
for (int i = p1; i < src.length(); i++) {
if (src.charAt(i) == sep) {
p2 = i;
break;
}
}
if (p2 == -1) {
CharSequence r = src.subSequence(p1, src.length());
p1 = src.length();
return r;
} else {
CharSequence r = src.subSequence(p1, p2);
p1 = p2 + 1;
return r;
}
}
@Override
public boolean hasNext() {
if (src == null)
return false;
if (p1 >= src.length())
return false;
return true;
}
};
}
};
}
public static byte[] readBs(InputStream in, int len) throws IOException {
byte[] b = new byte[len];
int off = 0;
int n = 0;
while (n < len) {
int count = in.read(b, off + n, len - n);
if (count < 0)
throw new EOFException();
n += count;
}
return b;
}
}