/**
*
*/
package apu.scratch.converter;
import static apu.antlr.clike.ScratchCLikeLexer.BOOL_EQUALS;
import static apu.antlr.clike.ScratchCLikeLexer.BOOL_GT;
import static apu.antlr.clike.ScratchCLikeLexer.BOOL_LT;
import static apu.antlr.clike.ScratchCLikeLexer.BOOL_NEQUALS;
import static apu.antlr.clike.ScratchCLikeLexer.HEX_CODE;
import static apu.antlr.clike.ScratchCLikeLexer.IDENTIFIER;
import static apu.antlr.clike.ScratchCLikeLexer.NUMBER;
import static apu.antlr.clike.ScratchCLikeLexer.STRINGLITERAL;
import static apu.antlr.clike.ScratchCLikeLexer.WHEN_CLICKED;
import static apu.antlr.clike.ScratchCLikeLexer.WHEN_CLONED;
import static apu.antlr.clike.ScratchCLikeLexer.WHEN_FLAG;
import static apu.antlr.clike.ScratchCLikeLexer.WHEN_KEY;
import static apu.antlr.clike.ScratchCLikeLexer.WHEN_RECEIVE;
import static apu.antlr.clike.ScratchCLikeLexer.WHEN_SENSOR;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardOpenOption;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.Stack;
import org.antlr.v4.runtime.ANTLRInputStream;
import org.antlr.v4.runtime.BaseErrorListener;
import org.antlr.v4.runtime.CommonTokenStream;
import org.antlr.v4.runtime.RecognitionException;
import org.antlr.v4.runtime.Recognizer;
import org.antlr.v4.runtime.Token;
import org.antlr.v4.runtime.tree.ParseTree;
import org.antlr.v4.runtime.tree.TerminalNode;
import org.json.JSONArray;
import org.json.JSONObject;
import apu.antlr.clike.ScratchCLikeLexer;
import apu.antlr.clike.ScratchCLikeParser;
import apu.antlr.clike.ScratchCLikeParser.ArrayCreateContext;
import apu.antlr.clike.ScratchCLikeParser.ArrayIdentifierContext;
import apu.antlr.clike.ScratchCLikeParser.BodyContext;
import apu.antlr.clike.ScratchCLikeParser.BracesContext;
import apu.antlr.clike.ScratchCLikeParser.ConditionAndOrContext;
import apu.antlr.clike.ScratchCLikeParser.ConditionAndOrNoPContext;
import apu.antlr.clike.ScratchCLikeParser.ConditionContext;
import apu.antlr.clike.ScratchCLikeParser.ConditionItemContext;
import apu.antlr.clike.ScratchCLikeParser.EmptyBracesContext;
import apu.antlr.clike.ScratchCLikeParser.EvalContext;
import apu.antlr.clike.ScratchCLikeParser.ForLoopContext;
import apu.antlr.clike.ScratchCLikeParser.IfElseContext;
import apu.antlr.clike.ScratchCLikeParser.LineContext;
import apu.antlr.clike.ScratchCLikeParser.MathAdditionExpContext;
import apu.antlr.clike.ScratchCLikeParser.MathAtomExpContext;
import apu.antlr.clike.ScratchCLikeParser.MathExpContext;
import apu.antlr.clike.ScratchCLikeParser.MathMultiplyExpContext;
import apu.antlr.clike.ScratchCLikeParser.MethodBodyContext;
import apu.antlr.clike.ScratchCLikeParser.MethodCallContext;
import apu.antlr.clike.ScratchCLikeParser.ParamDefContext;
import apu.antlr.clike.ScratchCLikeParser.ParamsContext;
import apu.antlr.clike.ScratchCLikeParser.ParamsDefContext;
import apu.antlr.clike.ScratchCLikeParser.RepeatSingleFrameLoopContext;
import apu.antlr.clike.ScratchCLikeParser.ReturnStatementContext;
import apu.antlr.clike.ScratchCLikeParser.VarExpContext;
import apu.antlr.clike.ScratchCLikeParser.VariableSetContext;
import apu.antlr.clike.ScratchCLikeParser.WhenDefContext;
import apu.antlr.clike.ScratchCLikeParser.WhileLoopContext;
/**
* @author MegaApuTurkUltra
*/
public class ScratchConverter {
public static void main(String[] args) {
try {
init();
} catch (IOException e1) {
e1.printStackTrace();
System.err.println();
System.err.println("Whoops! Unable to read Scratch "
+ "definition files. Must be a bug...");
printUsage();
}
if (args.length < 2) {
printUsage();
}
if (args[0].equals("-help")) {
printUsage();
}
File source = new File(args[0]);
if (!source.exists()) {
System.err.println("Your source file doesn't exist "
+ "or you don't have the correct " + "permissions.");
printUsage();
}
File dest = new File(args[1]);
String code = null;
try {
code = new String(Files.readAllBytes(Paths.get(source.toURI())));
} catch (IOException e) {
e.printStackTrace();
System.err.println();
System.err
.println("Unable to read your code file. Make sure you have "
+ "the correct permissions.");
printUsage();
}
try {
CompileResult result = compile(code);
if (result.errors.size() > 0) {
System.err.println("Your code has errors:");
for (CompileError err : result.errors) {
System.err.println(err.toString());
}
printUsage();
} else {
try {
writeToZip(dest, result.scripts);
System.out.println("Compile successful!");
System.out.println("\tCompiled " + source.getAbsolutePath()
+ " to");
System.out.println("\t" + dest.getAbsolutePath());
} catch (Exception e) {
e.printStackTrace();
System.err.println();
System.err
.println("Unable to write to destination"
+ " file. Make sure you have the correct permissions.");
printUsage();
}
}
} catch (Exception e) {
e.printStackTrace();
System.err.println();
System.err.println("Unable to compile your code because "
+ "of a severe syntax error (or bug here)");
printUsage();
}
}
public static void printUsage() {
System.err.println("Usage:\n\t"
+ "java -jar apuc-compile.jar <source> <destination>"
+ "\n\t\tCompiles ApuC code\n\t\tsource: The text"
+ " file containing your source "
+ "code\n\t\tdestination: A sprite2 or zip file to put "
+ "the code in\n\n\tjava -jar apuc-compile.jar -help\n\t"
+ "\tPrints this section.");
System.exit(1);
}
public static void createProjectFile(File f, Map<String, JSONArray> sprites)
throws IOException, URISyntaxException {
JSONArray children = new JSONArray();
int i = 1;
for (String name : sprites.keySet()) {
JSONObject sprite = new JSONObject(spriteBase);
sprite.put("indexInLibrary", i);
sprite.put("objName", name);
sprite.put("scripts", sprites.get(name));
i++;
children.put(sprite);
}
if (!f.exists()) {
InputStream in = ScratchConverter.class
.getResourceAsStream("/BaseProject.sb2");
Files.copy(in, Paths.get(f.toURI()));
in.close();
}
URI uri = f.toURI();
uri = new URI("jar:file", uri.getHost(), uri.getPath(),
uri.getFragment());
FileSystem fs = FileSystems.newFileSystem(uri,
new HashMap<String, String>());
Path json = fs.getPath("/project.json");
JSONObject obj = new JSONObject(new String(Files.readAllBytes(json)));
if (obj.has("children"))
obj.remove("children");
obj.put("children", children);
Files.write(json, obj.toString().getBytes(), StandardOpenOption.WRITE,
StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.SYNC);
fs.close();
}
public static void writeToZip(File f, JSONArray code) throws IOException,
URISyntaxException {
if (!f.exists()) {
InputStream in = ScratchConverter.class
.getResourceAsStream("/Sprite1.sprite2");
Files.copy(in, Paths.get(f.toURI()));
in.close();
}
URI uri = f.toURI();
uri = new URI("jar:file", uri.getHost(), uri.getPath(),
uri.getFragment());
FileSystem fs = FileSystems.newFileSystem(uri,
new HashMap<String, String>());
Path sprite = fs.getPath("/sprite.json");
JSONObject obj = new JSONObject(new String(Files.readAllBytes(sprite)));
if (obj.has("scripts"))
obj.remove("scripts");
obj.put("scripts", code);
Files.write(sprite, obj.toString().getBytes(),
StandardOpenOption.WRITE, StandardOpenOption.TRUNCATE_EXISTING,
StandardOpenOption.SYNC);
fs.close();
}
static class Method {
String identifier;
String scratchName;
String desc;
int numParams;
List<String> paramNames = new ArrayList<String>();
public String toString() {
StringBuffer buf = new StringBuffer();
buf.append("Method[");
buf.append(identifier);
buf.append(", ");
buf.append(numParams);
buf.append(", ");
buf.append(desc);
buf.append("]");
return buf.toString();
}
}
static class MethodCall {
JSONArray call;
Method calledMethod;
JSONArray returnVal;
public MethodCall(JSONArray call, JSONArray returnVal,
Method calledMethod) {
super();
this.call = call;
this.returnVal = returnVal;
this.calledMethod = calledMethod;
}
}
static class When {
static List<Integer> usedIds = new ArrayList<>();
static Random random = new Random();
int uid;
public When() {
do {
uid = Math.abs(random.nextInt());
} while (usedIds.contains(uid));
usedIds.add(uid);
}
public String getReturnStackName() {
return "returnStack" + uid;
}
}
static class Context {
static List<Integer> usedIds = new ArrayList<>();
static List<Integer> usedReturnIds = new ArrayList<>();
static Random random = new Random();
static Map<String, String> builtInMath = new HashMap<>();
static {
builtInMath.put("abs", "abs");
builtInMath.put("floor", "floor");
builtInMath.put("ceiling", "ceiling");
builtInMath.put("sqrt", "sqrt");
builtInMath.put("sin", "sin");
builtInMath.put("cos", "cos");
builtInMath.put("tan", "tan");
builtInMath.put("asin", "asin");
builtInMath.put("acos", "acos");
builtInMath.put("atan", "atan");
builtInMath.put("ln", "ln");
builtInMath.put("log", "log");
builtInMath.put("powe", "e ^");
builtInMath.put("pow10", "10 ^");
}
static Stack<MethodCall> methodCalls = new Stack<MethodCall>();
int uid;
Context parent;
List<String> variables = new ArrayList<String>();
List<Context> children = new ArrayList<Context>();
Method belongsTo = null;
public Context(Context parent) {
this.parent = parent;
if (parent != null)
parent.children.add(this);
do {
uid = Math.abs(random.nextInt());
} while (usedIds.contains(uid));
usedIds.add(uid);
}
public boolean hasVar(String name) {
System.out.println(uid + " " + name + " "
+ variables.contains(name));
return variables.contains(name);
}
public boolean parentHasVar(String name) {
System.out.println("new parenthasvar");
if (parent == null)
return false;
else
return parent._parentHasVar0(name);
}
private boolean _parentHasVar0(String name) {
if (parent != null) {
return variables.contains(name) || parent._parentHasVar0(name);
} else {
return variables.contains(name);
}
}
public String getLocalVarName(String name) {
return name + uid;
}
public String getNonLocalVarName(String name) {
if (parent != null)
return parent._applyVarName0(name);
else
return name;
}
private String _applyVarName0(String name) {
if (hasVar(name)) {
return getLocalVarName(name);
}
if (parent != null) {
return parent._applyVarName0(name);
} else {
return name;
}
}
// public static String getUniqueReturnVar() {
// int returnUid;
// do {
// returnUid = Math.abs(random.nextInt());
// } while (usedReturnIds.contains(returnUid));
// usedReturnIds.add(returnUid);
// return "return" + returnUid;
// }
}
static Map<String, Method> methods = new HashMap<>();
static List<CompileError> errors = new ArrayList<>();
static JSONArray root = new JSONArray();
static List<JSONArray> methodDefs = new ArrayList<JSONArray>();
static JSONArray current;
static Stack<JSONArray> stack = new Stack<>();
static Context currentContext;
static Stack<Context> contexts = new Stack<>();
static When currentWhen;
static Stack<When> whens = new Stack<>();
static String spriteBase = "";
private static Map<String, Method> builtInMethods = new HashMap<>();
static class CompileError {
int position;
int endPosition;
int line;
int positionInLine;
String message;
public CompileError(int position, int line, int positionInLine,
String message) {
this(position, position + 1, line, positionInLine, message);
}
public CompileError(int position, int endPosition, int line,
int positionInLine, String message) {
this.position = position;
this.endPosition = endPosition;
this.line = line;
this.positionInLine = positionInLine;
this.message = message;
}
public String toString() {
StringBuilder b = new StringBuilder();
b.append(line);
b.append(":");
b.append(positionInLine);
b.append(" (");
b.append(position);
b.append("-");
b.append(endPosition);
b.append("): ");
b.append(message);
return b.toString();
}
}
static class CompileResult {
JSONArray scripts;
List<CompileError> errors;
}
public static void init() throws IOException {
Reader base = new InputStreamReader(
ScratchConverter.class.getResourceAsStream("/BaseSprite.json"));
char[] c = new char[128];
int len;
StringBuilder sb = new StringBuilder();
while ((len = base.read(c)) != -1) {
sb.append(c, 0, len);
}
spriteBase = sb.toString();
base.close();
BufferedReader in = new BufferedReader(new InputStreamReader(
ScratchConverter.class.getResourceAsStream("/builtin.csv")));
String line;
in.readLine(); // skip header
while ((line = in.readLine()) != null) {
String[] split = line.split(",");
String name = split[0];
String scratchName = split[1];
String desc = split[2];
Method m = new Method();
m.identifier = name;
m.desc = desc;
m.scratchName = scratchName;
builtInMethods.put(m.identifier, m);
int lastIndex = 0;
while (lastIndex != -1) {
lastIndex = desc.indexOf("()", lastIndex);
if (lastIndex != -1) {
m.numParams++;
lastIndex += 2;
}
}
}
System.out.println("Done");
}
/**
* Compiles code and returns a {@link CompileResult}<br/>
* NOTE: NOT thread safe
*
* @param code
* The code to compile
* @return The result
* @throws Exception
* If a serious syntax error happens
*/
public static CompileResult compile(final String code) throws Exception {
System.out.println("Compiling...");
methods.clear();
errors.clear();
root = new JSONArray();
methodDefs.clear();
current = null;
stack.clear();
currentContext = null;
contexts.clear();
whens.clear();
currentWhen = null;
methods.putAll(builtInMethods);
root.put(10).put(10);
current = new JSONArray();
root.put(current);
current.put(new JSONArray("['whenGreenFlag']"));
// System.out.println(root.toString());
final ScratchCLikeLexer lexer = new ScratchCLikeLexer(null);
final ScratchCLikeParser parser = new ScratchCLikeParser(null);
ANTLRInputStream input = new ANTLRInputStream(new StringReader(code));
lexer.removeErrorListeners();
lexer.setInputStream(input);
CommonTokenStream tokens = new CommonTokenStream(lexer);
tokens.fill();
parser.removeErrorListeners();
parser.addErrorListener(new BaseErrorListener() {
@Override
public void syntaxError(Recognizer<?, ?> recognizer,
Object offendingSymbol, int line, int charPositionInLine,
String msg, RecognitionException e) {
int pos = 0;
int counter = 1;
while (counter < line) {
if (code.charAt(pos) == '\n')
counter++;
pos++;
}
pos--;
pos += charPositionInLine;
CompileError error = new CompileError(pos, line - 1,
charPositionInLine, msg);
if (e != null && e.getOffendingToken() != null) {
error.position = e.getOffendingToken().getStopIndex();
error.endPosition = e.getOffendingToken().getStopIndex();
}
errors.add(error);
}
});
parser.setBuildParseTree(true);
parser.setTokenStream(tokens);
EvalContext main = parser.eval();
methodPass(main.body());
currentContext = new Context(null);
currentWhen = new When();
parseBody(main.body());
// for (CompileError s : errors)
// System.err.println(s.line + ":" + s.positionInLine + s.message);
JSONArray arr = new JSONArray();
arr.put(root);
for (JSONArray a : methodDefs)
arr.put(a);
// System.out.println(arr.toString());
CompileResult result = new CompileResult();
result.scripts = arr;
result.errors = new ArrayList<>(errors);
parseContexts(currentContext, "");
return result;
}
static void parseContexts(Context c, String d) {
System.out.print(d + c.uid);
for (String s : c.variables) {
System.out.print(" " + s);
}
System.out.println();
for (Context c2 : c.children) {
parseContexts(c2, d + "\t");
}
}
static void pushWhen() {
When when = new When();
whens.push(currentWhen);
currentWhen = when;
}
static void popWhen() {
currentWhen = whens.pop();
}
static void pushContext() {
contexts.push(currentContext);
Context current = currentContext;
currentContext = new Context(current);
currentContext.belongsTo = current.belongsTo;
}
static void popContext() {
currentContext = contexts.pop();
}
static JSONArray newJsonArray(Object... objects) {
return new JSONArray(objects);
}
static void pushCurrent(JSONArray newArray) {
stack.push(current);
current.put(newArray);
current = newArray;
}
static JSONArray popCurrent() {
JSONArray old = current;
current = stack.pop();
return old;
}
static void log(Object... log) {
// for (int i = 0; i < Thread.currentThread().getStackTrace().length -
// 4; i++) {
// System.out.print(" ");
// }
// for (Object o : log) {
// if (o.getClass() != String.class) {
// System.out.print(o.getClass().getSimpleName());
// System.out.print("(");
// System.out.print(o.toString());
// System.out.print(")");
// } else {
// System.out.print(o.toString());
// }
// System.out.print(" ");
// }
// System.out.println();
}
static void methodPass(BodyContext body) {
if (body == null || body.children == null || body.children.size() == 0)
return;
for (ParseTree tree : body.children) {
if (tree instanceof BracesContext) {
BracesContext braces = (BracesContext) tree;
MethodBodyContext method;
IfElseContext ifElse;
ForLoopContext forLoop;
WhileLoopContext whileLoop;
if ((method = braces.methodBody()) != null) {
String identifier = method.IDENTIFIER().getText();
if (methods.containsKey(identifier)) {
Token start = method.getStart();
errors.add(new CompileError(start.getStartIndex(),
start.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Duplicate method: " + identifier));
}
Method m = new Method();
methods.put(identifier, m);
m.identifier = identifier;
m.desc = "User defined";
m.numParams = 1;
ParamsDefContext paramsDef = method.paramsDef();
if (paramsDef != null) {
List<ParamDefContext> paramDefs = paramsDef.paramDef();
m.numParams = paramDefs.size() + 1;
for (ParamDefContext param : paramDefs) {
String pIdentifier = param.IDENTIFIER().getText();
if (m.paramNames.contains(pIdentifier)) {
Token start = param.getStart();
errors.add(new CompileError(start
.getStartIndex(), start.getStopIndex(),
start.getLine(), start
.getCharPositionInLine(),
"Duplicate parameter: " + pIdentifier
+ " in method " + identifier));
} else {
m.paramNames.add(pIdentifier);
}
}
}
methodPass(method.body());
}
if ((ifElse = braces.ifElse()) != null) {
for (BodyContext body2 : ifElse.body())
methodPass(body2);
}
if ((whileLoop = braces.whileLoop()) != null) {
methodPass(whileLoop.body());
}
if ((forLoop = braces.forLoop()) != null) {
methodPass(forLoop.body());
}
}
}
}
static void parseBody(BodyContext body) {
log("Body");
if (body == null || body.children == null || body.children.size() == 0)
return;
for (ParseTree tree : body.children) {
if (tree instanceof BracesContext)
parseBraces((BracesContext) tree);
else if (tree instanceof LineContext)
parseLine((LineContext) tree);
}
}
static void parseLine(LineContext line) {
VariableSetContext varSet;
MethodCallContext methodCall;
ArrayCreateContext arrayCreate;
ReturnStatementContext returnSt;
if ((varSet = line.variableSet()) != null) {
parseVariableSet(varSet);
}
if ((methodCall = line.methodCall()) != null) {
parseMethodCall(methodCall, false);
}
if ((arrayCreate = line.arrayCreate()) != null) {
parseArrayCreate(arrayCreate);
}
if ((returnSt = line.returnStatement()) != null) {
parseReturnStatement(returnSt);
}
int size = Context.methodCalls.size();
int size2 = size;
Object returnStackName = currentWhen.getReturnStackName();
if (currentContext.belongsTo != null) {
returnStackName = newJsonArray("getParam", "returnStackName", "r");
}
while (Context.methodCalls.size() > 0) {
MethodCall call = Context.methodCalls.pop();
Object o = current.get(current.length() - 1);
current.put(current.length() - 1, call.call);
current.put(o);
call.returnVal.put("getLine:ofList:");
call.returnVal
.put(newJsonArray("-",
newJsonArray("lineCountOfList:", returnStackName),
size - 1));
size--;
call.returnVal.put(returnStackName);
}
for (int i = 0; i < size2; i++) {
pushCurrent(newJsonArray(
"deleteLine:ofList:",
returnSt == null ? "last" : newJsonArray("-",
newJsonArray("lineCountOfList:", returnStackName),
1), returnStackName));
popCurrent();
}
if (returnSt != null) {
pushCurrent(newJsonArray("stopScripts", "this script"));
popCurrent();
}
}
static void parseReturnStatement(ReturnStatementContext returnSt) {
if (currentContext.belongsTo == null) {
Token start = returnSt.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Return is not allowed outside of a method"));
return;
}
if (returnSt.varExp() != null) {
pushCurrent(newJsonArray("append:toList:"));
parseVarExp(returnSt.varExp());
current.put(newJsonArray("getParam", "returnStackName", "r"));
popCurrent();
}
}
static void parseArrayCreate(ArrayCreateContext arrayCreate) {
String identifier = arrayCreate.arrayDef().IDENTIFIER().getText();
if (currentContext.belongsTo != null
&& currentContext.belongsTo.paramNames.contains(identifier)) {
Token start = arrayCreate.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Setting a parameter is not allowed: " + identifier));
} else {
if (currentContext.hasVar(identifier)) {
identifier = currentContext.getLocalVarName(identifier);
} else if (currentContext.parentHasVar(identifier)) {
identifier = currentContext.getNonLocalVarName(identifier);
} else {
currentContext.variables.add(identifier);
identifier = currentContext.getLocalVarName(identifier);
}
}
int size = Integer.parseInt(arrayCreate.NUMBER().getText());
log("ArrayCreate", identifier, size);
pushCurrent(new JSONArray("[\"deleteLine:ofList:\", \"all\", \""
+ identifier + "\"]"));
popCurrent();
if (arrayCreate.SINGLE_FRAME_TAG() != null) {
for (int i = 0; i < size; i++) {
pushCurrent(new JSONArray("[\"append:toList:\", \"0\", \""
+ identifier + "\"]"));
popCurrent();
}
} else {
pushCurrent(new JSONArray("[\"doRepeat\", " + size + ", ["
+ "[\"append:toList:\", \"0\", \"" + identifier + "\"]]]"));
popCurrent();
}
}
static void parseMethodCall(MethodCallContext methodCall,
boolean needsReturnVal) {
String identifier = methodCall.IDENTIFIER().getText();
if (identifier.equals("length")) {
ParamsContext params = methodCall.params();
if (params == null || params.arrayDef() == null) {
Token start = (Token) methodCall.IDENTIFIER().getPayload();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"length needs an array parameter: " + identifier));
return;
}
String arrayId = params.arrayDef().IDENTIFIER().getText();
if (currentContext.belongsTo != null
&& currentContext.belongsTo.paramNames.contains(arrayId)) {
Token start = params.arrayDef().getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Array paramaters are not allowed: " + arrayId));
} else {
if (currentContext.hasVar(arrayId)) {
arrayId = currentContext.getLocalVarName(arrayId);
} else if (currentContext.parentHasVar(arrayId)) {
arrayId = currentContext.getNonLocalVarName(arrayId);
} else {
Token start = params.arrayDef().getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(), arrayId
+ " has not been set yet in this context"));
currentContext.variables.add(arrayId);
arrayId = currentContext.getLocalVarName(arrayId);
}
}
pushCurrent(newJsonArray("lineCountOfList:"));
current.put(arrayId);
popCurrent();
return;
}
if (Context.builtInMath.containsKey(identifier)) {
if (methodCall.params() == null
|| methodCall.params().varExp().size() != 1) {
Token start = methodCall.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Built in math functions require exactly 1 parameter: "
+ identifier));
return;
}
pushCurrent(newJsonArray("computeFunction:of:",
Context.builtInMath.get(identifier)));
parseVarExp(methodCall.params().varExp(0));
popCurrent();
return;
}
String status = "Custom";
boolean isBuiltin = false;
Method m;
if (methods.containsKey(identifier)) {
m = methods.get(identifier);
if (m.scratchName != null) {
status = "Built in: " + m.scratchName;
isBuiltin = true;
}
} else {
Token start = methodCall.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(), "Method does not exist: "
+ identifier));
return;
}
log("MethodCall", identifier, status);
StringBuilder name = new StringBuilder(identifier);
if (isBuiltin) {
pushCurrent(newJsonArray(m.scratchName));
} else {
for (int i = 0; i < m.numParams; i++) {
name.append(" %s"); // because string input works for everything
// also laziness
}
if (needsReturnVal) {
JSONArray callJson = newJsonArray("call", name.toString());
JSONArray returnVal = newJsonArray();
MethodCall call = new MethodCall(callJson, returnVal, m);
pushCurrent(returnVal);
popCurrent();
stack.push(current);
current = callJson;
Context.methodCalls.push(call);
} else
pushCurrent(newJsonArray("call", name.toString()));
}
ParamsContext params = methodCall.params();
if (params != null) {
List<VarExpContext> varExps = params.varExp();
for (VarExpContext varExp : varExps) {
parseVarExp(varExp);
}
}
if (!isBuiltin) {
Object returnStackName = currentWhen.getReturnStackName();
if (currentContext.belongsTo != null) {
returnStackName = newJsonArray("getParam", "returnStackName",
"r");
}
current.put(returnStackName);
}
popCurrent();
}
static void parseVariableSet(VariableSetContext varSet) {
String identifier;
ArrayIdentifierContext arrayId = null;
if (varSet.IDENTIFIER() != null) {
identifier = varSet.IDENTIFIER().getText();
log("VarSet", identifier);
} else {
arrayId = varSet.arrayIdentifier();
identifier = arrayId.IDENTIFIER().getText();
log("ArraySet", identifier);
}
if (currentContext.belongsTo != null
&& currentContext.belongsTo.paramNames.contains(identifier)) {
Token start = varSet.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Setting parameters is not allowed: " + identifier));
} else {
if (currentContext.hasVar(identifier)) {
identifier = currentContext.getLocalVarName(identifier);
} else if (currentContext.parentHasVar(identifier)) {
identifier = currentContext.getNonLocalVarName(identifier);
} else {
currentContext.variables.add(identifier);
identifier = currentContext.getLocalVarName(identifier);
}
}
if (varSet.PLUS_PLUS() != null) {
if (varSet.IDENTIFIER() != null) {
pushCurrent(newJsonArray("setVar:to:", identifier));
pushCurrent(newJsonArray("+",
newJsonArray("readVariable", identifier), 1));
popCurrent();
popCurrent();
} else {
pushCurrent(newJsonArray("setLine:ofList:to:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
pushCurrent(newJsonArray("+"));
pushCurrent(newJsonArray("getLine:ofList:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
popCurrent();
current.put(1);
popCurrent();
popCurrent();
}
} else if (varSet.MINUS_MINUS() != null) {
if (varSet.IDENTIFIER() != null) {
pushCurrent(newJsonArray("setVar:to:", identifier));
pushCurrent(newJsonArray("-",
newJsonArray("readVariable", identifier), 1));
popCurrent();
popCurrent();
} else {
pushCurrent(newJsonArray("setLine:ofList:to:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
pushCurrent(newJsonArray("-"));
pushCurrent(newJsonArray("getLine:ofList:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
popCurrent();
current.put(1);
popCurrent();
popCurrent();
}
} else if (varSet.PLUS_EQUALS() != null) {
if (varSet.IDENTIFIER() != null) {
pushCurrent(newJsonArray("setVar:to:", identifier));
pushCurrent(newJsonArray("+",
newJsonArray("readVariable", identifier)));
parseVarExp(varSet.varExp());
popCurrent();
popCurrent();
} else {
pushCurrent(newJsonArray("setLine:ofList:to:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
pushCurrent(newJsonArray("+"));
pushCurrent(newJsonArray("getLine:ofList:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
popCurrent();
parseVarExp(varSet.varExp());
popCurrent();
popCurrent();
}
} else if (varSet.MINUS_EQUALS() != null) {
if (varSet.IDENTIFIER() != null) {
pushCurrent(newJsonArray("setVar:to:", identifier));
pushCurrent(newJsonArray("-",
newJsonArray("readVariable", identifier)));
parseVarExp(varSet.varExp());
popCurrent();
popCurrent();
} else {
pushCurrent(newJsonArray("setLine:ofList:to:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
pushCurrent(newJsonArray("-"));
pushCurrent(newJsonArray("getLine:ofList:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
popCurrent();
parseVarExp(varSet.varExp());
popCurrent();
popCurrent();
}
} else {
if (varSet.IDENTIFIER() != null) {
pushCurrent(newJsonArray("setVar:to:", identifier));
parseVarExp(varSet.varExp());
popCurrent();
} else {
pushCurrent(newJsonArray("setLine:ofList:to:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
parseVarExp(varSet.varExp());
popCurrent();
}
}
}
static void parseBraces(BracesContext braces) {
EmptyBracesContext empty = braces.emptyBraces();
if (empty != null) {
pushContext();
parseBody(empty.body());
popContext();
return;
}
WhenDefContext when = braces.whenDef();
if (when != null) {
parseWhen(when);
return;
}
MethodBodyContext method;
IfElseContext ifElse;
ForLoopContext forLoop;
WhileLoopContext whileLoop;
RepeatSingleFrameLoopContext repeatLoop;
if ((method = braces.methodBody()) != null) {
parseMethod(method);
}
if ((ifElse = braces.ifElse()) != null) {
parseIfElse(ifElse);
}
if ((whileLoop = braces.whileLoop()) != null) {
parseWhileLoop(whileLoop);
}
if ((forLoop = braces.forLoop()) != null) {
parseForLoop(forLoop);
}
if ((repeatLoop = braces.repeatSingleFrameLoop()) != null) {
parseRepeatLoop(repeatLoop);
}
}
static void parseWhen(WhenDefContext when) {
pushWhen();
JSONArray methodArr = new JSONArray();
methodArr.put(10).put(10);
JSONArray scripts = new JSONArray();
methodArr.put(scripts);
JSONArray header = new JSONArray();
scripts.put(header);
switch (((Token) when.children.get(1).getPayload()).getType()) {
case WHEN_CLICKED:
header.put("whenClicked");
break;
case WHEN_RECEIVE:
header.put("whenIReceive");
break;
case WHEN_KEY:
header.put("whenKeyPressed");
break;
case WHEN_CLONED:
header.put("whenCloned");
break;
case WHEN_SENSOR:
header.put("whenSensorGreaterThan");
break;
case WHEN_FLAG:
default:
header.put("whenGreenFlag");
break;
}
stack.push(current);
current = header;
for (VarExpContext varExp : when.varExp()) {
parseVarExp(varExp);
}
current = stack.pop();
stack.push(current);
current = scripts;
pushContext();
currentContext.belongsTo = null;
parseBody(when.body());
popContext();
methodDefs.add(methodArr);
current = stack.pop();
popWhen();
}
static void parseRepeatLoop(RepeatSingleFrameLoopContext repeat) {
int times = Integer.parseInt(repeat.NUMBER().getText());
pushContext();
for (int i = 0; i < times; i++) {
parseBody(repeat.body());
}
popContext();
}
static void parseForLoop(ForLoopContext forLoop) {
VariableSetContext initial = forLoop.variableSet(0), increment = forLoop
.variableSet(1);
log("ForLoop");
String initialId = initial.IDENTIFIER().getText();
pushContext();
if (!currentContext.parentHasVar(initialId))
currentContext.variables.add(initialId);
parseVariableSet(initial);
pushCurrent(newJsonArray("doUntil"));
pushCurrent(newJsonArray("not"));
parseConditionAndOrNoP(forLoop.conditionAndOrNoP());
popCurrent();
pushCurrent(newJsonArray());
parseBody(forLoop.body());
parseVariableSet(increment);
popContext();
popCurrent();
popCurrent();
}
static void parseWhileLoop(WhileLoopContext whileLoop) {
if (whileLoop.BOOL_TRUE() != null) {
pushCurrent(newJsonArray("doForever"));
log("Forever");
} else {
log("WhileLoop");
pushCurrent(newJsonArray("doUntil"));
pushCurrent(newJsonArray("not"));
parseConditionAndOr(whileLoop.conditionAndOr());
popCurrent();
}
pushCurrent(newJsonArray());
pushContext();
parseBody(whileLoop.body());
popContext();
popCurrent();
popCurrent();
}
static void parseIfElse(IfElseContext ifElse) {
List<BodyContext> bodies = ifElse.body();
boolean ifOnly = bodies.size() == 1;
if (ifOnly) {
log("If");
pushCurrent(newJsonArray("doIf"));
} else {
log("IfElse");
pushCurrent(newJsonArray("doIfElse"));
}
parseConditionAndOr(ifElse.conditionAndOr());
pushCurrent(newJsonArray());
pushContext();
parseBody(bodies.get(0));
popContext();
popCurrent();
if (!ifOnly) {
pushCurrent(newJsonArray());
pushContext();
parseBody(bodies.get(1));
popContext();
popCurrent();
}
popCurrent();
}
static void parseConditionAndOrNoP(ConditionAndOrNoPContext andOr) {
parseConditionItems(andOr.conditionItem(), andOr.BOOL_AND(),
andOr.BOOL_OR());
}
static void parseConditionAndOr(ConditionAndOrContext andOr) {
if (andOr == null)
return;
parseConditionItems(andOr.conditionItem(), andOr.BOOL_AND(),
andOr.BOOL_OR());
}
static void parseConditionItems(List<ConditionItemContext> items,
TerminalNode boolAnd, TerminalNode boolOr) {
log("ConditionItems");
if (items.size() > 1) {
if (boolAnd != null) {
log("AND");
pushCurrent(newJsonArray("&"));
} else {
log("OR");
pushCurrent(newJsonArray("|"));
}
for (ConditionItemContext item : items)
parseConditionItem(item);
popCurrent();
} else {
parseConditionItem(items.get(0));
}
}
static void parseConditionItem(ConditionItemContext item) {
if (item.BOOL_NOT() != null) {
log("NOT");
pushCurrent(newJsonArray("not"));
}
ConditionAndOrContext andOr = item.conditionAndOr();
if (andOr != null) {
parseConditionAndOr(andOr);
} else {
ConditionContext condition = item.condition();
MethodCallContext methodCall = condition.methodCall();
if (methodCall != null) {
parseMethodCall(methodCall, true);
return;
}
List<VarExpContext> varExps = condition.varExp();
if (condition.BOOL_GTE() != null) {
log("GTE");
pushCurrent(newJsonArray("|"));
pushCurrent(newJsonArray("="));
parseVarExp(varExps.get(0));
parseVarExp(varExps.get(1));
popCurrent();
pushCurrent(newJsonArray(">"));
parseVarExp(varExps.get(0));
parseVarExp(varExps.get(1));
popCurrent();
popCurrent();
} else if (condition.BOOL_LTE() != null) {
log("LTE");
pushCurrent(newJsonArray("|"));
pushCurrent(newJsonArray("="));
parseVarExp(varExps.get(0));
parseVarExp(varExps.get(1));
popCurrent();
pushCurrent(newJsonArray("<"));
parseVarExp(varExps.get(0));
parseVarExp(varExps.get(1));
popCurrent();
popCurrent();
} else {
ParseTree op = condition.children.get(1);
int opType = ((Token) op.getPayload()).getType();
if (opType == BOOL_EQUALS) {
log("EQUALS");
pushCurrent(newJsonArray("="));
} else if (opType == BOOL_NEQUALS) {
log("NEQUALS");
pushCurrent(newJsonArray("not"));
pushCurrent(newJsonArray("="));
} else if (opType == BOOL_GT) {
log("GT");
pushCurrent(newJsonArray(">"));
} else if (opType == BOOL_LT) {
log("LT");
pushCurrent(newJsonArray("<"));
}
parseVarExp(condition.varExp(0));
parseVarExp(condition.varExp(1));
if (opType == BOOL_NEQUALS) {
popCurrent();
popCurrent();
} else {
popCurrent();
}
}
}
if (item.BOOL_NOT() != null)
popCurrent();
}
static void parseVarExp(VarExpContext varExp) {
log("VarExp");
ParseTree exp = varExp.getChild(0);
Object payload = exp.getPayload();
if (payload instanceof Token) {
Token token = ((Token) payload);
int type = token.getType();
if (type == NUMBER)
parseNumber((TerminalNode) exp);
else if (type == STRINGLITERAL)
parseStringLiteral((TerminalNode) exp);
else if (type == IDENTIFIER)
parseVariable((TerminalNode) exp);
else if (type == HEX_CODE) {
current.put(Integer.decode(exp.getText()));
}
} else {
MathExpContext mathExp = varExp.mathExp();
if (mathExp != null)
parseMathExp(varExp.mathExp());
else
parseArrayItem(varExp.arrayIdentifier());
}
}
static void parseMathExp(MathExpContext mathExp) {
log("MathExp");
parseMathAdditionExp(mathExp.mathAdditionExp());
}
static void parseMathAdditionExp(MathAdditionExpContext mathAdd) {
log("AdditionExp");
LinkedList<ParseTree> items = new LinkedList<ParseTree>(
mathAdd.children);
if (items.size() == 1) {
parseMathMultiplyExp((MathMultiplyExpContext) items.get(0)
.getPayload());
return;
}
JSONArray beforeC = current;
List<JSONArray> myOps = new ArrayList<>();
while (items.size() > 1) {
MathMultiplyExpContext add = (MathMultiplyExpContext) items
.removeLast().getPayload();
Token op = (Token) items.removeLast().getPayload();
log(op.getText());
pushCurrent(newJsonArray(op.getText()));
myOps.add(current);
parseMathMultiplyExp(add);
}
MathMultiplyExpContext last = (MathMultiplyExpContext) items
.removeLast().getPayload();
parseMathMultiplyExp(last);
for (JSONArray op : myOps) {
Object op1 = op.get(1);
Object op2 = op.get(2);
op.put(1, op2);
op.put(2, op1);
}
while (current != beforeC)
popCurrent();
}
static void parseMathMultiplyExp(MathMultiplyExpContext mathMult) {
log("MultExp");
LinkedList<ParseTree> items = new LinkedList<ParseTree>(
mathMult.children);
if (items.size() == 1) {
parseMathAtomExp((MathAtomExpContext) items.get(0).getPayload());
return;
}
JSONArray beforeC = current;
List<JSONArray> myOps = new ArrayList<>();
while (items.size() > 1) {
MathAtomExpContext add = (MathAtomExpContext) items.removeLast()
.getPayload();
Token op = (Token) items.removeLast().getPayload();
log(op.getText());
pushCurrent(newJsonArray(op.getText()));
myOps.add(current);
parseMathAtomExp(add);
}
MathAtomExpContext last = (MathAtomExpContext) items.removeLast()
.getPayload();
parseMathAtomExp(last);
for (JSONArray op : myOps) {
Object op1 = op.get(1);
Object op2 = op.get(2);
op.put(1, op2);
op.put(2, op1);
}
while (current != beforeC)
popCurrent();
}
static void parseMathAtomExp(MathAtomExpContext mathAtom) {
log("AtomExp");
MathAdditionExpContext add;
ArrayIdentifierContext arrayId;
MethodCallContext methodCall;
if ((add = mathAtom.mathAdditionExp()) != null) {
parseMathAdditionExp(add);
} else if ((arrayId = mathAtom.arrayIdentifier()) != null) {
parseArrayItem(arrayId);
} else if ((methodCall = mathAtom.methodCall()) != null) {
parseMethodCall(methodCall, true);
} else {
Token token = ((Token) mathAtom.getChild(0).getPayload());
int type = token.getType();
if (type == NUMBER)
parseNumber((TerminalNode) mathAtom.getChild(0));
else if (type == IDENTIFIER)
parseVariable((TerminalNode) mathAtom.getChild(0));
}
}
static void parseNumber(TerminalNode number) {
double num = Double.parseDouble(number.getText());
log("Number", num);
current.put(num);
}
static void parseStringLiteral(TerminalNode string) {
log("StringLiteral", string.getText());
current.put(string.getText()
.substring(1, string.getText().length() - 1));
}
static void parseVariable(TerminalNode variable) {
String identifier = variable.getText();
log("VarGet", identifier);
if (currentContext.belongsTo != null
&& currentContext.belongsTo.paramNames.contains(identifier)) {
log("Param");
pushCurrent(newJsonArray("getParam", identifier, "r"));
// strangely the Wiki has nothing
// about getParam so I don't even
// know what "r" does
// I assumed params had "readVariable" too
// but then I tried it an it didn't work
// so I had to manually look at some JSON
// to figure this out
popCurrent();
return;
} else {
if (currentContext.hasVar(identifier)) {
identifier = currentContext.getLocalVarName(identifier);
} else if (currentContext.parentHasVar(identifier)) {
identifier = currentContext.getNonLocalVarName(identifier);
} else {
Token start = (Token) variable.getPayload();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(), identifier
+ " has not been set yet in this context"));
currentContext.variables.add(identifier);
identifier = currentContext.getLocalVarName(identifier);
}
}
pushCurrent(newJsonArray("readVariable", identifier));
popCurrent();
}
static void parseArrayItem(ArrayIdentifierContext arrayId) {
String identifier = arrayId.IDENTIFIER().getText();
log("ArrayItemGet", identifier);
if (currentContext.belongsTo != null
&& currentContext.belongsTo.paramNames.contains(identifier)) {
Token start = arrayId.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Array paramaters are not allowed: " + identifier));
} else {
if (currentContext.hasVar(identifier)) {
identifier = currentContext.getLocalVarName(identifier);
} else if (currentContext.parentHasVar(identifier)) {
identifier = currentContext.getNonLocalVarName(identifier);
} else {
Token start = arrayId.getStart();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(), identifier
+ " has not been set yet in this context"));
currentContext.variables.add(identifier);
identifier = currentContext.getLocalVarName(identifier);
}
}
pushCurrent(newJsonArray("getLine:ofList:"));
pushCurrent(newJsonArray("+", "1"));
parseVarExp(arrayId.varExp());
popCurrent();
current.put(identifier);
popCurrent();
}
static void parseMethod(MethodBodyContext method) {
String identifier = method.IDENTIFIER().getText();
boolean atomic = method.ATOMIC_METHOD_TAG() != null;
log("MethodDef", identifier);
Method m = methods.get(identifier);
StringBuilder name = new StringBuilder(identifier);
for (int i = 0; i < m.numParams; i++) {
name.append(" %s");
}
JSONArray methodArr = new JSONArray();
methodArr.put(10).put(10);
JSONArray scripts = new JSONArray();
methodArr.put(scripts);
JSONArray header = new JSONArray();
scripts.put(header);
header.put("procDef").put(name.toString());
JSONArray varNames = new JSONArray();
header.put(varNames);
JSONArray defaults = new JSONArray();
header.put(defaults);
for (int i = 0; i < m.numParams; i++)
defaults.put("");
header.put(atomic);
ParamsDefContext paramsDef = method.paramsDef();
if (paramsDef != null) {
List<ParamDefContext> paramDefs = paramsDef.paramDef();
for (ParamDefContext paramDef : paramDefs) {
TerminalNode param = paramDef.IDENTIFIER();
if (param != null) {
varNames.put(param.getText());
} else {
Token start = (Token) paramDef.arrayDef().IDENTIFIER()
.getPayload();
errors.add(new CompileError(start.getStartIndex(), start
.getStopIndex(), start.getLine(), start
.getCharPositionInLine(),
"Arrays are not allowed as method parameters"));
}
}
}
varNames.put("returnStackName");
stack.push(current);
current = scripts;
pushContext();
currentContext.belongsTo = m;
parseBody(method.body());
popContext();
current = stack.pop();
methodDefs.add(methodArr);
}
}