New logic parser
This commit is contained in:
@@ -26,7 +26,8 @@ public class LogicStatementProcessor extends BaseProcessor{
|
|||||||
MethodSpec.Builder reader = MethodSpec.methodBuilder("read")
|
MethodSpec.Builder reader = MethodSpec.methodBuilder("read")
|
||||||
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
|
||||||
.returns(tname("mindustry.logic.LStatement"))
|
.returns(tname("mindustry.logic.LStatement"))
|
||||||
.addParameter(String[].class, "tokens");
|
.addParameter(String[].class, "tokens")
|
||||||
|
.addParameter(int.class, "length");
|
||||||
|
|
||||||
Seq<Stype> types = types(RegisterStatement.class);
|
Seq<Stype> types = types(RegisterStatement.class);
|
||||||
|
|
||||||
@@ -76,7 +77,7 @@ public class LogicStatementProcessor extends BaseProcessor{
|
|||||||
"");
|
"");
|
||||||
|
|
||||||
//reading primitives, strings and enums is supported; nothing else is
|
//reading primitives, strings and enums is supported; nothing else is
|
||||||
reader.addStatement("if(tokens.length > $L) result.$L = $L(tokens[$L])",
|
reader.addStatement("if(length > $L) result.$L = $L(tokens[$L])",
|
||||||
index + 1,
|
index + 1,
|
||||||
field.name(),
|
field.name(),
|
||||||
field.mirror().toString().equals("java.lang.String") ?
|
field.mirror().toString().equals("java.lang.String") ?
|
||||||
|
|||||||
@@ -4,9 +4,7 @@ import arc.func.*;
|
|||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
import arc.util.*;
|
import arc.util.*;
|
||||||
import mindustry.*;
|
import mindustry.*;
|
||||||
import mindustry.gen.*;
|
|
||||||
import mindustry.logic.LExecutor.*;
|
import mindustry.logic.LExecutor.*;
|
||||||
import mindustry.logic.LStatements.*;
|
|
||||||
|
|
||||||
/** "Compiles" a sequence of statements into instructions. */
|
/** "Compiles" a sequence of statements into instructions. */
|
||||||
public class LAssembler{
|
public class LAssembler{
|
||||||
@@ -37,10 +35,10 @@ public class LAssembler{
|
|||||||
putConst("@tick", 0);
|
putConst("@tick", 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static LAssembler assemble(String data, int maxInstructions){
|
public static LAssembler assemble(String data){
|
||||||
LAssembler asm = new LAssembler();
|
LAssembler asm = new LAssembler();
|
||||||
|
|
||||||
Seq<LStatement> st = read(data, maxInstructions);
|
Seq<LStatement> st = read(data);
|
||||||
|
|
||||||
asm.instructions = st.map(l -> l.build(asm)).filter(l -> l != null).toArray(LInstruction.class);
|
asm.instructions = st.map(l -> l.build(asm)).filter(l -> l != null).toArray(LInstruction.class);
|
||||||
return asm;
|
return asm;
|
||||||
@@ -57,107 +55,7 @@ public class LAssembler{
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static Seq<LStatement> read(String data){
|
public static Seq<LStatement> read(String data){
|
||||||
return read(data, LExecutor.maxInstructions);
|
return LParser.parse(data);
|
||||||
}
|
|
||||||
|
|
||||||
public static Seq<LStatement> read(String data, int max){
|
|
||||||
//empty data check
|
|
||||||
if(data == null || data.isEmpty()) return new Seq<>();
|
|
||||||
|
|
||||||
Seq<LStatement> statements = new Seq<>();
|
|
||||||
String[] lines = data.split("\n");
|
|
||||||
int index = 0;
|
|
||||||
for(String line : lines){
|
|
||||||
if(line.isEmpty()) continue;
|
|
||||||
//remove trailing semicolons in case someone adds them in for no reason
|
|
||||||
if(line.endsWith(";")) line = line.substring(0, line.length() - 1);
|
|
||||||
|
|
||||||
if(index++ > max) break;
|
|
||||||
|
|
||||||
line = line.replace("\t", "").trim();
|
|
||||||
|
|
||||||
try{
|
|
||||||
String[] arr;
|
|
||||||
if(line.startsWith("#")) continue;
|
|
||||||
|
|
||||||
//yes, I am aware that this can be split with regex, but that's slow and even more incomprehensible
|
|
||||||
if(line.contains(" ")){
|
|
||||||
Seq<String> tokens = new Seq<>();
|
|
||||||
boolean inString = false;
|
|
||||||
int lastIdx = 0;
|
|
||||||
|
|
||||||
for(int i = 0; i < line.length() + 1; i++){
|
|
||||||
char c = i == line.length() ? ' ' : line.charAt(i);
|
|
||||||
if(c == '#' && !inString){
|
|
||||||
break;
|
|
||||||
}else if(c == '"'){
|
|
||||||
inString = !inString;
|
|
||||||
}else if(c == ' ' && !inString){
|
|
||||||
tokens.add(line.substring(lastIdx, Math.min(i, lastIdx + maxTokenLength)));
|
|
||||||
lastIdx = i + 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
arr = tokens.toArray(String.class);
|
|
||||||
}else{
|
|
||||||
arr = new String[]{line};
|
|
||||||
}
|
|
||||||
|
|
||||||
//nothing found
|
|
||||||
if(arr.length == 0) continue;
|
|
||||||
|
|
||||||
String type = arr[0];
|
|
||||||
|
|
||||||
//legacy stuff
|
|
||||||
if(type.equals("bop")){
|
|
||||||
arr[0] = "op";
|
|
||||||
|
|
||||||
//field order for bop used to be op a, b, result, but now it's op result a b
|
|
||||||
String res = arr[4];
|
|
||||||
arr[4] = arr[3];
|
|
||||||
arr[3] = arr[2];
|
|
||||||
arr[2] = res;
|
|
||||||
}else if(type.equals("uop")){
|
|
||||||
arr[0] = "op";
|
|
||||||
|
|
||||||
if(arr[1].equals("negate")){
|
|
||||||
arr = new String[]{
|
|
||||||
"op", "mul", arr[3], arr[2], "-1"
|
|
||||||
};
|
|
||||||
}else{
|
|
||||||
//field order for uop used to be op a, result, but now it's op result a
|
|
||||||
String res = arr[3];
|
|
||||||
arr[3] = arr[2];
|
|
||||||
arr[2] = res;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
//fix up changed operaiton names
|
|
||||||
if(type.equals("op")){
|
|
||||||
arr[1] = opNameChanges.get(arr[1], arr[1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
LStatement st = LogicIO.read(arr);
|
|
||||||
|
|
||||||
if(st != null){
|
|
||||||
statements.add(st);
|
|
||||||
}else{
|
|
||||||
//attempt parsing using custom parser if a match is found - this is for mods
|
|
||||||
String first = arr[0];
|
|
||||||
if(customParsers.containsKey(first)){
|
|
||||||
statements.add(customParsers.get(first).get(arr));
|
|
||||||
}else{
|
|
||||||
//unparseable statement
|
|
||||||
statements.add(new InvalidStatement());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}catch(Exception parseFailed){
|
|
||||||
parseFailed.printStackTrace();
|
|
||||||
//when parsing fails, add a dummy invalid statement
|
|
||||||
statements.add(new InvalidStatement());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return statements;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return a variable ID by name.
|
/** @return a variable ID by name.
|
||||||
@@ -172,7 +70,7 @@ public class LAssembler{
|
|||||||
symbol = symbol.trim();
|
symbol = symbol.trim();
|
||||||
|
|
||||||
//string case
|
//string case
|
||||||
if(symbol.startsWith("\"") && symbol.endsWith("\"")){
|
if(!symbol.isEmpty() && symbol.charAt(0) == '\"' && symbol.charAt(symbol.length() - 1) == '\"'){
|
||||||
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
|
return putConst("___" + symbol, symbol.substring(1, symbol.length() - 1).replace("\\n", "\n")).id;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -74,8 +74,8 @@ public class LExecutor{
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void load(String data, int maxInstructions){
|
public void load(String data){
|
||||||
load(LAssembler.assemble(data, maxInstructions));
|
load(LAssembler.assemble(data));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Loads with a specified assembler. Resets all variables. */
|
/** Loads with a specified assembler. Resets all variables. */
|
||||||
|
|||||||
@@ -1,17 +1,33 @@
|
|||||||
package mindustry.logic;
|
package mindustry.logic;
|
||||||
|
|
||||||
import arc.struct.*;
|
import arc.struct.*;
|
||||||
|
import arc.util.*;
|
||||||
|
import mindustry.gen.*;
|
||||||
|
import mindustry.logic.LStatements.*;
|
||||||
|
|
||||||
public class LParser{
|
public class LParser{
|
||||||
|
private static final String[] tokens = new String[16];
|
||||||
|
private static final int maxJumps = 500;
|
||||||
|
private static final StringMap opNameChanges = StringMap.of(
|
||||||
|
"atan2", "angle",
|
||||||
|
"dst", "len"
|
||||||
|
);
|
||||||
|
|
||||||
|
private static final Seq<JumpIndex> jumps = new Seq<>();
|
||||||
|
private static final ObjectIntMap<String> jumpLocations = new ObjectIntMap<>();
|
||||||
|
|
||||||
Seq<LStatement> statements = new Seq<>();
|
Seq<LStatement> statements = new Seq<>();
|
||||||
char[] chars;
|
char[] chars;
|
||||||
int pos;
|
int pos, line, tok;
|
||||||
|
|
||||||
LParser(String text){
|
LParser(String text){
|
||||||
this.chars = text.toCharArray();
|
this.chars = text.toCharArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Parses a sequence of statements from a string. */
|
||||||
public static Seq<LStatement> parse(String text){
|
public static Seq<LStatement> parse(String text){
|
||||||
|
//don't waste time parsing null/empty text
|
||||||
|
if(text == null || text.isEmpty()) return new Seq<>();
|
||||||
return new LParser(text).parse();
|
return new LParser(text).parse();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,40 +36,158 @@ public class LParser{
|
|||||||
while(pos < chars.length && chars[pos++] != '\n');
|
while(pos < chars.length && chars[pos++] != '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
void label(){
|
void error(String message){
|
||||||
while(pos < chars.length){
|
//TODO
|
||||||
|
throw new RuntimeException("Invalid code. " + message);
|
||||||
|
}
|
||||||
|
|
||||||
|
String string(){
|
||||||
|
int from = pos;
|
||||||
|
|
||||||
|
while(pos++ < chars.length){
|
||||||
|
var c = chars[pos];
|
||||||
|
if(c == '\n'){
|
||||||
|
error("Missing closing quote \" before end of line.");
|
||||||
|
}else if(c == '"'){
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(chars[pos] != '"') error("Missing closing quote \" before end of file.");
|
||||||
|
|
||||||
|
return new String(chars, from, ++pos - from);
|
||||||
|
}
|
||||||
|
|
||||||
|
String token(){
|
||||||
|
int from = pos;
|
||||||
|
|
||||||
|
while(pos < chars.length){
|
||||||
|
char c = chars[pos];
|
||||||
|
if(c == '\n' || c == ' ' || c == '#' || c == '\t' || c == ';') break;
|
||||||
|
pos ++;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new String(chars, from, pos - from);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Apply changes after reading a list of tokens. */
|
||||||
|
void checkRead(){
|
||||||
|
if(tokens[0].equals("op")){
|
||||||
|
//legacy name change
|
||||||
|
tokens[1] = opNameChanges.get(tokens[1], tokens[1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Reads the next statement until EOL/EOF. */
|
||||||
void statement(){
|
void statement(){
|
||||||
//read jump
|
boolean expectNext = false;
|
||||||
if(chars[pos] == '['){
|
tok = 0;
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
while(pos < chars.length){
|
while(pos < chars.length){
|
||||||
char c = chars[pos++];
|
char c = chars[pos];
|
||||||
|
if(tok >= tokens.length) error("Line too long; may only contain " + tokens.length + " tokens");
|
||||||
|
|
||||||
//reached end of line, bail out.
|
//reached end of line, bail out.
|
||||||
if(c == '\n') break;
|
if(c == '\n' || c == ';') break;
|
||||||
|
|
||||||
|
if(expectNext && c != ' ' && c != '#' && c != '\t'){
|
||||||
|
error("Expected space after string/token.");
|
||||||
|
}
|
||||||
|
|
||||||
|
expectNext = false;
|
||||||
|
|
||||||
if(c == '#'){
|
if(c == '#'){
|
||||||
comment();
|
comment();
|
||||||
break;
|
break;
|
||||||
|
}else if(c == '"'){
|
||||||
|
tokens[tok ++] = string();
|
||||||
|
expectNext = true;
|
||||||
|
}else if(c != ' ' && c != '\t'){
|
||||||
|
tokens[tok ++] = token();
|
||||||
|
expectNext = true;
|
||||||
|
}else{
|
||||||
|
pos ++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//only process lines with at least 1 token
|
||||||
|
if(tok > 0){
|
||||||
|
checkRead();
|
||||||
|
|
||||||
|
//store jump location, always ends with colon
|
||||||
|
if(tok == 1 && tokens[0].charAt(tokens[0].length() - 1) == ':'){
|
||||||
|
if(jumpLocations.size >= maxJumps){
|
||||||
|
error("Too many jump locations. Max jumps: " + maxJumps);
|
||||||
|
}
|
||||||
|
jumpLocations.put(tokens[0].substring(0, tokens[0].length() - 1), line);
|
||||||
|
}else{
|
||||||
|
boolean wasJump;
|
||||||
|
String jumpLoc = null;
|
||||||
|
//clean up jump position before parsing
|
||||||
|
if(wasJump = (tokens[0].equals("jump") && tok > 1 && !Strings.canParseInt(tokens[1]))){
|
||||||
|
jumpLoc = tokens[1];
|
||||||
|
tokens[1] = "-1";
|
||||||
|
}
|
||||||
|
|
||||||
|
LStatement st;
|
||||||
|
|
||||||
|
try{
|
||||||
|
st = LogicIO.read(tokens, tok);
|
||||||
|
}catch(Exception e){
|
||||||
|
//log invalid statements
|
||||||
|
Log.err(e);
|
||||||
|
st = new InvalidStatement();
|
||||||
|
}
|
||||||
|
|
||||||
|
//store jumps that use labels
|
||||||
|
if(st instanceof JumpStatement jump && wasJump){
|
||||||
|
jumps.add(new JumpIndex(jump, jumpLoc));
|
||||||
|
}
|
||||||
|
|
||||||
|
if(st != null){
|
||||||
|
statements.add(st);
|
||||||
|
}else{
|
||||||
|
//attempt parsing using custom parser if a match is found; this is for mods
|
||||||
|
if(LAssembler.customParsers.containsKey(tokens[0])){
|
||||||
|
statements.add(LAssembler.customParsers.get(tokens[0]).get(tokens));
|
||||||
|
}else{
|
||||||
|
//unparseable statement
|
||||||
|
statements.add(new InvalidStatement());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
line ++;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Seq<LStatement> parse(){
|
Seq<LStatement> parse(){
|
||||||
while(pos < chars.length){
|
jumps.clear();
|
||||||
switch(chars[pos++]){
|
jumpLocations.clear();
|
||||||
case '\n', ' ' -> {} //skip newlines and spaces
|
|
||||||
case '\r' -> pos ++; //skip the newline after the \r
|
while(pos < chars.length && line < LExecutor.maxInstructions){
|
||||||
|
switch(chars[pos]){
|
||||||
|
case '\n', ' ' -> pos ++; //skip newlines and spaces
|
||||||
|
case '\r' -> pos += 2; //skip the newline after the \r
|
||||||
default -> statement();
|
default -> statement();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//load destination indices
|
||||||
|
for(var i : jumps){
|
||||||
|
i.jump.destIndex = jumpLocations.get(i.location, -1);
|
||||||
|
}
|
||||||
|
|
||||||
return statements;
|
return statements;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class JumpIndex{
|
||||||
|
JumpStatement jump;
|
||||||
|
String location;
|
||||||
|
|
||||||
|
public JumpIndex(JumpStatement jump, String location){
|
||||||
|
this.jump = jump;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package mindustry.world.blocks.logic;
|
package mindustry.world.blocks.logic;
|
||||||
|
|
||||||
import arc.*;
|
|
||||||
import arc.func.*;
|
import arc.func.*;
|
||||||
import arc.math.geom.*;
|
import arc.math.geom.*;
|
||||||
import arc.scene.ui.layout.*;
|
import arc.scene.ui.layout.*;
|
||||||
@@ -302,7 +301,7 @@ public class LogicBlock extends Block{
|
|||||||
|
|
||||||
try{
|
try{
|
||||||
//create assembler to store extra variables
|
//create assembler to store extra variables
|
||||||
LAssembler asm = LAssembler.assemble(str, LExecutor.maxInstructions);
|
LAssembler asm = LAssembler.assemble(str);
|
||||||
|
|
||||||
//store connections
|
//store connections
|
||||||
for(LogicLink link : links){
|
for(LogicLink link : links){
|
||||||
@@ -357,7 +356,7 @@ public class LogicBlock extends Block{
|
|||||||
Log.err(e);
|
Log.err(e);
|
||||||
|
|
||||||
//handle malformed code and replace it with nothing
|
//handle malformed code and replace it with nothing
|
||||||
executor.load("", LExecutor.maxInstructions);
|
executor.load("");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user