This commit is contained in:
Victor 2018-11-15 17:01:32 +02:00
commit 801320e77d
40 changed files with 3028 additions and 0 deletions

73
build.xml Normal file
View File

@ -0,0 +1,73 @@
<?xml version="1.0" encoding="UTF-8"?>
<!-- You may freely edit this file. See commented blocks below for -->
<!-- some examples of how to customize the build. -->
<!-- (If you delete it and reopen the project it will be recreated.) -->
<!-- By default, only the Clean and Build commands use this build script. -->
<!-- Commands such as Run, Debug, and Test only use this build script if -->
<!-- the Compile on Save feature is turned off for the project. -->
<!-- You can turn off the Compile on Save (or Deploy on Save) setting -->
<!-- in the project's Project Properties dialog box.-->
<project name="AutomateTool" default="default" basedir=".">
<description>Builds, tests, and runs the project AutomateTool.</description>
<import file="nbproject/build-impl.xml"/>
<!--
There exist several targets which are by default empty and which can be
used for execution of your tasks. These targets are usually executed
before and after some main targets. They are:
-pre-init: called before initialization of project properties
-post-init: called after initialization of project properties
-pre-compile: called before javac compilation
-post-compile: called after javac compilation
-pre-compile-single: called before javac compilation of single file
-post-compile-single: called after javac compilation of single file
-pre-compile-test: called before javac compilation of JUnit tests
-post-compile-test: called after javac compilation of JUnit tests
-pre-compile-test-single: called before javac compilation of single JUnit test
-post-compile-test-single: called after javac compilation of single JUunit test
-pre-jar: called before JAR building
-post-jar: called after JAR building
-post-clean: called after cleaning build products
(Targets beginning with '-' are not intended to be called on their own.)
Example of inserting an obfuscator after compilation could look like this:
<target name="-post-compile">
<obfuscate>
<fileset dir="${build.classes.dir}"/>
</obfuscate>
</target>
For list of available properties check the imported
nbproject/build-impl.xml file.
Another way to customize the build is by overriding existing main targets.
The targets of interest are:
-init-macrodef-javac: defines macro for javac compilation
-init-macrodef-junit: defines macro for junit execution
-init-macrodef-debug: defines macro for class debugging
-init-macrodef-java: defines macro for class execution
-do-jar: JAR building
run: execution of project
-javadoc-build: Javadoc generation
test-report: JUnit report generation
An example of overriding the target for project execution could look like this:
<target name="run" depends="AutomateTool-impl.jar">
<exec dir="bin" executable="launcher.exe">
<arg file="${dist.jar}"/>
</exec>
</target>
Notice that the overridden target depends on the jar target and not only on
the compile target as the regular run target does. Again, for a list of available
properties which you can use, check the target you are overriding in the
nbproject/build-impl.xml file.
-->
</project>

1419
nbproject/build-impl.xml Normal file

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,8 @@
build.xml.data.CRC32=67bd077b
build.xml.script.CRC32=2f28d9f7
build.xml.stylesheet.CRC32=8064a381@1.79.0.48
# This file is used by a NetBeans-based IDE to track changes in generated files such as build-impl.xml.
# Do not edit this file. You may delete it but then the IDE will never regenerate such files for you.
nbproject/build-impl.xml.data.CRC32=67bd077b
nbproject/build-impl.xml.script.CRC32=fa865cff
nbproject/build-impl.xml.stylesheet.CRC32=05530350@1.79.0.48

View File

View File

@ -0,0 +1,7 @@
application.args=paint_lines.txt
compile.on.save=true
do.depend=false
do.jar=true
javac.debug=true
javadoc.preview=true
user.properties.file=C:\\Users\\aNNiMON\\AppData\\Roaming\\NetBeans\\dev\\build.properties

View File

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project-private xmlns="http://www.netbeans.org/ns/project-private/1">
<editor-bookmarks xmlns="http://www.netbeans.org/ns/editor-bookmarks/2" lastBookmarkId="0"/>
<open-files xmlns="http://www.netbeans.org/ns/projectui-open-files/2">
<group name="_TUTORIALS"/>
</open-files>
</project-private>

View File

@ -0,0 +1,76 @@
annotation.processing.enabled=true
annotation.processing.enabled.in.editor=false
annotation.processing.processors.list=
annotation.processing.run.all.processors=true
annotation.processing.source.output=${build.generated.sources.dir}/ap-source-output
application.title=AutomateTool
application.vendor=aNNiMON
build.classes.dir=${build.dir}/classes
build.classes.excludes=**/*.java,**/*.form
# This directory is removed when the project is cleaned:
build.dir=build
build.generated.dir=${build.dir}/generated
build.generated.sources.dir=${build.dir}/generated-sources
# Only compile against the classpath explicitly listed here:
build.sysclasspath=ignore
build.test.classes.dir=${build.dir}/test/classes
build.test.results.dir=${build.dir}/test/results
# Uncomment to specify the preferred debugger connection transport:
#debug.transport=dt_socket
debug.classpath=\
${run.classpath}
debug.test.classpath=\
${run.test.classpath}
# Files in build.classes.dir which should be excluded from distribution jar
dist.archive.excludes=
# This directory is removed when the project is cleaned:
dist.dir=dist
dist.jar=${dist.dir}/AutomateTool.jar
dist.javadoc.dir=${dist.dir}/javadoc
endorsed.classpath=
excludes=
includes=**
jar.compress=false
javac.classpath=
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false
javac.external.vm=true
javac.processorpath=\
${javac.classpath}
javac.source=1.8
javac.target=1.8
javac.test.classpath=\
${javac.classpath}:\
${build.classes.dir}
javac.test.processorpath=\
${javac.test.classpath}
javadoc.additionalparam=
javadoc.author=false
javadoc.encoding=${source.encoding}
javadoc.noindex=false
javadoc.nonavbar=false
javadoc.notree=false
javadoc.private=false
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
main.class=com.annimon.automatetool.Main
manifest.file=manifest.mf
meta.inf.dir=${src.dir}/META-INF
mkdist.disabled=false
platform.active=default_platform
run.classpath=\
${javac.classpath}:\
${build.classes.dir}
# Space-separated list of JVM arguments used when running the project.
# You may also define separate properties like run-sys-prop.name=value instead of -Dname=value.
# To set system properties for unit tests define test-sys-prop.name=value:
run.jvmargs=
run.test.classpath=\
${javac.test.classpath}:\
${build.test.classes.dir}
source.encoding=UTF-8
src.dir=src
test.src.dir=test

15
nbproject/project.xml Normal file
View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://www.netbeans.org/ns/project/1">
<type>org.netbeans.modules.java.j2seproject</type>
<configuration>
<data xmlns="http://www.netbeans.org/ns/j2se-project/3">
<name>AutomateTool</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

18
paint_lines.txt Normal file
View File

@ -0,0 +1,18 @@
pause = 5
xstep = 50 ystep = 5
startx = 50
starty = 400 + ystep / 2
for (y = 0, y < 300, y = y + ystep) {
move startx (starty+y)
press BUTTON1
for (i = 0, i < 600, i = i + xstep) {
move (startx+i) (starty+y)
delay pause
}
release BUTTON1
delay (pause*3)
}

17
program.txt Normal file
View File

@ -0,0 +1,17 @@
# double click to create tab
move 1256 400
click BUTTON1
delay 5
click BUTTON1
# focus on editor
move 1256 460
click BUTTON1
# type text
text = "This is a text. Enjoy.
Multiline text also supported.
Thank you for whatching my stream :)"
type text

View File

@ -0,0 +1,69 @@
package com.annimon.automatetool;
import com.annimon.automatetool.parser.Lexer;
import com.annimon.automatetool.parser.Parser;
import com.annimon.automatetool.parser.Token;
import com.annimon.automatetool.parser.ast.Statement;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.List;
/**
* @author aNNiMON
*/
public final class Main {
public static void main(String[] args) {
final String source = readSource(args);
System.out.println("-= PROGRAM =-");
System.out.println(source);
final Lexer lexer = new Lexer(source);
final List<Token> tokens = lexer.tokenize();
System.out.println("\n\n-= TOKENS =-");
System.out.println(tokens);
RobotUtils.initialize();
final Parser parser = new Parser(tokens);
final Statement program = parser.parse();
program.execute();
System.out.println("\n\n-= AST =-");
System.out.println(program);
}
private static String readSource(String[] args) {
String source;
if (args.length >= 1) {
final File input = new File(args[0]);
if (input.exists() && input.isFile()) {
source = read(input);
} else {
source = args[0];
}
} else {
source = "move 200 200\n" +
"delay 100";
}
return source;
}
private static String read(File file) {
final String newLine = System.getProperty("line.separator");
final StringBuilder sb = new StringBuilder();
try ( final InputStream is = new FileInputStream(file);
final InputStreamReader isReader = new InputStreamReader(is, "UTF-8");
final BufferedReader reader = new BufferedReader(isReader) ) {
String line;
while ( (line = reader.readLine()) != null) {
sb.append(line);
sb.append(newLine);
}
} catch (IOException ex) {
}
return sb.toString();
}
}

View File

@ -0,0 +1,124 @@
package com.annimon.automatetool;
import java.awt.AWTException;
import java.awt.Robot;
import java.awt.event.KeyEvent;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author aNNiMON
*/
public final class RobotUtils {
private static final int CLICK_DELAY = 200;
private static final int TYPING_DELAY = 50;
private static final Map<Character, Integer> SYMBOL_CODES;
static {
SYMBOL_CODES = new HashMap<>(10);
SYMBOL_CODES.put('_', KeyEvent.VK_MINUS);
SYMBOL_CODES.put(':', KeyEvent.VK_SEMICOLON);
}
private static Robot robot;
public static void initialize() {
try {
robot = new Robot();
} catch (AWTException awte) {
throw new RuntimeException("Unable to create robot instance", awte);
}
}
public static synchronized void click(int buttons) {
mousePress(buttons);
delay(CLICK_DELAY);
mouseRelease(buttons);
}
public static synchronized void typeText(String text) {
for (char ch : text.toCharArray()) {
typeCharacter(ch);
}
}
private static void typeCharacter(char ch) {
int code = KeyEvent.getExtendedKeyCodeForChar(ch);
boolean isUpperCase = Character.isLetter(ch) && Character.isUpperCase(ch);
boolean needPressShift = isUpperCase;
if (!isUpperCase) {
final int symbolIndex = "!@#$%^&*()".indexOf(ch);
if (symbolIndex != -1) {
needPressShift = true;
code = '1' + symbolIndex;
} else if (SYMBOL_CODES.containsKey(ch)) {
needPressShift = true;
code = SYMBOL_CODES.get(ch);
}
}
if (code == KeyEvent.VK_UNDEFINED) return;
if (needPressShift) {
// press shift
keyPress(KeyEvent.VK_SHIFT);
delay(TYPING_DELAY);
}
keyPress(code);
delay(TYPING_DELAY);
keyRelease(code);
if (needPressShift) {
// release shift
delay(TYPING_DELAY);
keyRelease(KeyEvent.VK_SHIFT);
delay(TYPING_DELAY);
}
}
public static synchronized void mouseMove(int x, int y) {
try {
robot.mouseMove(x, y);
} catch (IllegalArgumentException iae) { }
}
public static synchronized void mousePress(int buttons) {
try {
robot.mousePress(buttons);
} catch (IllegalArgumentException iae) { }
}
public static synchronized void mouseRelease(int buttons) {
try {
robot.mouseRelease(buttons);
} catch (IllegalArgumentException iae) { }
}
public static synchronized void mouseWheel(int wheelAmt) {
try {
robot.mouseWheel(wheelAmt);
} catch (IllegalArgumentException iae) { }
}
public static synchronized void keyPress(int keycode) {
try {
robot.keyPress(keycode);
} catch (IllegalArgumentException iae) { }
}
public static synchronized void keyRelease(int keycode) {
try {
robot.keyRelease(keycode);
} catch (IllegalArgumentException iae) { }
}
public static synchronized void delay(int ms) {
try {
robot.delay(ms);
} catch (IllegalArgumentException iae) { }
}
}

View File

@ -0,0 +1,37 @@
package com.annimon.automatetool.lib;
/**
*
* @author aNNiMON
*/
public final class BooleanValue implements Value {
public static final BooleanValue FALSE = new BooleanValue(false);
public static final BooleanValue TRUE = new BooleanValue(true);
private final boolean value;
public BooleanValue(boolean value) {
this.value = value;
}
@Override
public boolean asBoolean() {
return value;
}
@Override
public int asInteger() {
return value ? 1 : 0;
}
@Override
public String asString() {
return String.valueOf(value);
}
@Override
public String toString() {
return "boolean{" + value + '}';
}
}

View File

@ -0,0 +1,37 @@
package com.annimon.automatetool.lib;
/**
*
* @author aNNiMON
*/
public final class IntegerValue implements Value {
public static final IntegerValue ZERO = new IntegerValue(0);
public static final IntegerValue ONE = new IntegerValue(1);
private final int value;
public IntegerValue(int value) {
this.value = value;
}
@Override
public boolean asBoolean() {
return value != 0;
}
@Override
public int asInteger() {
return value;
}
@Override
public String asString() {
return String.valueOf(value);
}
@Override
public String toString() {
return "int{" + value + '}';
}
}

View File

@ -0,0 +1,42 @@
package com.annimon.automatetool.lib;
/**
*
* @author aNNiMON
*/
public final class StringValue implements Value {
private final String value;
public StringValue(String value) {
this.value = value;
}
@Override
public boolean asBoolean() {
try {
return Boolean.parseBoolean(value);
} catch (NumberFormatException nfe) {
throw new RuntimeException("Cannot cast string value to boolean");
}
}
@Override
public int asInteger() {
try {
return Integer.parseInt(value);
} catch (NumberFormatException nfe) {
throw new RuntimeException("Cannot cast string value to integer");
}
}
@Override
public String asString() {
return value;
}
@Override
public String toString() {
return value;
}
}

View File

@ -0,0 +1,14 @@
package com.annimon.automatetool.lib;
/**
*
* @author aNNiMON
*/
public interface Value {
boolean asBoolean();
int asInteger();
String asString();
}

View File

@ -0,0 +1,35 @@
package com.annimon.automatetool.lib;
import java.awt.event.InputEvent;
import java.util.HashMap;
import java.util.Map;
/**
*
* @author aNNiMON
*/
public final class Variables {
private static final Map<String, Value> variables;
static {
variables = new HashMap<>();
// TODO: TrueExpression, FalseExpression
variables.put("false", BooleanValue.FALSE);
variables.put("true", BooleanValue.TRUE);
variables.put("BUTTON1", new IntegerValue(InputEvent.BUTTON1_MASK));
variables.put("BUTTON2", new IntegerValue(InputEvent.BUTTON2_MASK));
variables.put("BUTTON3", new IntegerValue(InputEvent.BUTTON3_MASK));
}
public static Value get(String variable) {
if (!variables.containsKey(variable)) {
throw new RuntimeException("Variable " + variable + " does not exist");
}
return variables.get(variable);
}
public static void set(String variable, Value value) {
variables.put(variable, value);
}
}

View File

@ -0,0 +1,148 @@
package com.annimon.automatetool.parser;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/**
*
* @author aNNiMON
*/
public final class Lexer {
private static final Map<String, TokenType> OPERATORS;
static {
OPERATORS = new HashMap<>(10);
OPERATORS.put("(", TokenType.LPAREN);
OPERATORS.put(")", TokenType.RPAREN);
OPERATORS.put(",", TokenType.COMMA);
OPERATORS.put("{", TokenType.LBRACE);
OPERATORS.put("}", TokenType.RBRACE);
OPERATORS.put("=", TokenType.EQ);
OPERATORS.put("<", TokenType.LT);
OPERATORS.put(">", TokenType.GT);
OPERATORS.put("+", TokenType.PLUS);
OPERATORS.put("-", TokenType.MINUS);
OPERATORS.put("*", TokenType.STAR);
OPERATORS.put("/", TokenType.SLASH);
}
private static final Map<String, TokenType> KEYWORDS;
static {
KEYWORDS = new HashMap<>(10);
KEYWORDS.put("move", TokenType.MOVE);
KEYWORDS.put("click", TokenType.CLICK);
KEYWORDS.put("delay", TokenType.DELAY);
KEYWORDS.put("press", TokenType.PRESS);
KEYWORDS.put("release", TokenType.RELEASE);
KEYWORDS.put("keypress", TokenType.KEYPRESS);
KEYWORDS.put("keyrelease", TokenType.KEYRELEASE);
KEYWORDS.put("type", TokenType.TYPE);
KEYWORDS.put("for", TokenType.FOR);
}
private final int length;
private final String source;
private final List<Token> tokens;
private int pos;
public Lexer(String source) {
this.source = source;
this.length = source.length();
tokens = new ArrayList<>();
pos = 0;
}
public List<Token> tokenize() {
while (pos < length) {
final char current = peek(0);
if (Character.isDigit(current)) tokenizeNumber();
else if (Character.isLetter(current)) tokenizeWord();
else if (current == '"') tokenizeText();
else if (current == '#') tokenizeComment();
else if (OPERATORS.containsKey(String.valueOf(current))) {
tokenizeOperator();
} else next();
}
return tokens;
}
private void tokenizeNumber() {
final StringBuilder sb = new StringBuilder();
char current = peek(0);
while (Character.isDigit(current)) {
sb.append(current);
current = next();
}
addToken(TokenType.NUMBER, sb.toString());
}
private void tokenizeWord() {
final StringBuilder sb = new StringBuilder();
char current = peek(0);
while (Character.isLetterOrDigit(current)) {
sb.append(current);
current = next();
}
final String word = sb.toString();
if (KEYWORDS.containsKey(word.toLowerCase())) {
addToken( KEYWORDS.get(word.toLowerCase()) );
} else {
addToken(TokenType.WORD, word);
}
}
private void tokenizeText() {
next();
final StringBuilder sb = new StringBuilder();
char current = peek(0);
while (current != '"') {
if (current == '\0') {
throw new RuntimeException("unexpected end of file");
}
sb.append(current);
current = next();
}
next();
addToken(TokenType.TEXT, sb.toString());
}
private void tokenizeComment() {
char current = peek(0);
while ("\r\n\0".indexOf(current) == -1) {
current = next();
}
}
private void tokenizeOperator() {
char current = peek(0);
addToken(OPERATORS.get(String.valueOf(current)));
next();
}
private char next() {
pos++;
return peek(0);
}
private char peek(int relativePosition) {
final int position = pos + relativePosition;
if (position >= length) {
return '\0';
}
return source.charAt(position);
}
private void addToken(TokenType type) {
tokens.add(new Token(type, ""));
}
private void addToken(TokenType type, String text) {
tokens.add(new Token(type, text));
}
}

View File

@ -0,0 +1,234 @@
package com.annimon.automatetool.parser;
import com.annimon.automatetool.parser.ast.AssignmentStatement;
import com.annimon.automatetool.parser.ast.BlockStatement;
import com.annimon.automatetool.parser.ast.MoveStatement;
import com.annimon.automatetool.parser.ast.Statement;
import com.annimon.automatetool.parser.ast.ValueExpression;
import java.util.List;
import static com.annimon.automatetool.parser.ast.AstHelper.*;
import com.annimon.automatetool.parser.ast.BinaryExpression;
import com.annimon.automatetool.parser.ast.ClickStatement;
import com.annimon.automatetool.parser.ast.ConditionalExpression;
import com.annimon.automatetool.parser.ast.DelayStatement;
import com.annimon.automatetool.parser.ast.Expression;
import com.annimon.automatetool.parser.ast.ForStatement;
import com.annimon.automatetool.parser.ast.KeyPressStatement;
import com.annimon.automatetool.parser.ast.PressStatement;
import com.annimon.automatetool.parser.ast.ReleaseStatement;
import com.annimon.automatetool.parser.ast.TypeStatement;
import com.annimon.automatetool.parser.ast.VariableExpression;
/**
*
* @author aNNiMON
*/
public final class Parser {
private static final Token EOF = new Token(TokenType.EOF);
private final int length;
private final List<Token> tokens;
private int pos;
public Parser(List<Token> tokens) {
this.tokens = tokens;
length = tokens.size();
pos = 0;
}
public Statement parse() {
final BlockStatement program = new BlockStatement();
while (!match(TokenType.EOF)) {
program.add(statement());
}
/*program.add(block(
move(intValue(228), intValue(400)),
delay(intValue(400)),
move(intValue(322), intValue(400)),
delay(intValue(400)),
move(intValue(400), intValue(400)),
delay(intValue(400)),
move(intValue(415), intValue(400)),
delay(intValue(400)),
move(intValue(620), intValue(400))
));*/
return program;
}
private BlockStatement block() {
final BlockStatement block = new BlockStatement();
consume(TokenType.LBRACE);
while (!match(TokenType.RBRACE)) {
block.add(statement());
}
return block;
}
private Statement blockOrStatement() {
if (lookMatch(0, TokenType.LBRACE)) {
return block();
}
return statement();
}
private Statement statement() {
if (match(TokenType.MOVE)) {
return new MoveStatement(expression(), expression());
}
if (match(TokenType.CLICK)) {
return new ClickStatement(expression());
}
if (match(TokenType.DELAY)) {
return new DelayStatement(expression());
}
if (match(TokenType.PRESS)) {
return new PressStatement(expression());
}
if (match(TokenType.RELEASE)) {
return new ReleaseStatement(expression());
}
if (match(TokenType.KEYPRESS)) {
return new KeyPressStatement(expression());
}
if (match(TokenType.KEYRELEASE)) {
return new KeyPressStatement(expression());
}
if (match(TokenType.TYPE)) {
return new TypeStatement(expression());
}
if (match(TokenType.FOR)) {
return forLoop();
}
if (lookMatch(0, TokenType.WORD) && lookMatch(1, TokenType.EQ)) {
final String variable = consume(TokenType.WORD).getText();
consume(TokenType.EQ);
return new AssignmentStatement(variable, expression());
}
System.out.println(peek(0) + ", " + peek(1));
throw new RuntimeException("Unknown statement: " + peek(0));
}
private Statement forLoop() {
match(TokenType.LPAREN);
final Statement initialization = statement();
match(TokenType.COMMA);
final Expression termination = expression();
match(TokenType.COMMA);
final Statement increment = statement();
match(TokenType.RPAREN);
final Statement statement = blockOrStatement();
return new ForStatement(initialization, termination, increment, statement);
}
private Expression expression() {
return conditional();
}
private Expression conditional() {
Expression result = additive();
if (match(TokenType.LT)) {
return new ConditionalExpression(
ConditionalExpression.Operation.LESS, result, expression());
}
if (match(TokenType.GT)) {
return new ConditionalExpression(
ConditionalExpression.Operation.GREATER, result, expression());
}
return result;
}
private Expression additive() {
Expression result = multiplicative();
while (true) {
if (match(TokenType.PLUS)) {
result = new BinaryExpression(
BinaryExpression.Operation.ADDITION, result, expression());
continue;
}
if (match(TokenType.MINUS)) {
result = new BinaryExpression(
BinaryExpression.Operation.SUBSTRACTION, result, expression());
continue;
}
break;
}
return result;
}
private Expression multiplicative() {
Expression result = unary();
while (true) {
if (match(TokenType.STAR)) {
result = new BinaryExpression(
BinaryExpression.Operation.MULTIPLICATION, result, expression());
continue;
}
if (match(TokenType.SLASH)) {
result = new BinaryExpression(
BinaryExpression.Operation.DIVISION, result, expression());
continue;
}
break;
}
return result;
}
private Expression unary() {
// TODO unary minus
return primary();
}
private Expression primary() {
final Token current = peek(0);
if (match(TokenType.NUMBER)) {
return new ValueExpression(Integer.parseInt(current.getText()));
}
if (match(TokenType.WORD)) {
return new VariableExpression(current.getText());
}
if (match(TokenType.TEXT)) {
return new ValueExpression(current.getText());
}
if (match(TokenType.LPAREN)) {
Expression result = expression();
match(TokenType.RPAREN);
return result;
}
throw new RuntimeException("Unknown expression: " + peek(0));
}
private Token consume(TokenType type) {
final Token token = peek(0);
if (token.getType() != type) {
throw new RuntimeException("Wrong token. Expected: " + type);
}
pos++;
return token;
}
private boolean match(TokenType type) {
final Token token = peek(0);
if (token.getType() != type) return false;
pos++;
return true;
}
private boolean lookMatch(int relativePosition, TokenType type) {
return (peek(relativePosition).getType() == type);
}
private Token peek(int relativePosition) {
final int position = pos + relativePosition;
if (position >= length) return EOF;
return tokens.get(position);
}
}

View File

@ -0,0 +1,33 @@
package com.annimon.automatetool.parser;
/**
*
* @author aNNiMON
*/
public final class Token {
private final TokenType type;
private final String text;
public Token(TokenType type) {
this(type, "");
}
public Token(TokenType type, String text) {
this.type = type;
this.text = text;
}
public TokenType getType() {
return type;
}
public String getText() {
return text;
}
@Override
public String toString() {
return String.format("%s(%s)", type.name(), text);
}
}

View File

@ -0,0 +1,42 @@
package com.annimon.automatetool.parser;
/**
*
* @author aNNiMON
*/
public enum TokenType {
NUMBER,
WORD,
TEXT,
MOVE,
CLICK,
DELAY,
PRESS,
RELEASE,
KEYPRESS,
KEYRELEASE,
TYPE,
FOR,
EQ,
LT,
GT,
PLUS,
MINUS,
STAR,
SLASH,
LPAREN,
RPAREN,
COMMA,
LBRACE,
RBRACE,
EOL,
EOF,
}

View File

@ -0,0 +1,28 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.Variables;
/**
*
* @author aNNiMON
*/
public final class AssignmentStatement implements Statement {
private final String variable;
private final Expression expression;
public AssignmentStatement(String variable, Expression expression) {
this.variable = variable;
this.expression = expression;
}
@Override
public void execute() {
Variables.set(variable, expression.eval());
}
@Override
public String toString() {
return String.format("%s = %s", variable, expression);
}
}

View File

@ -0,0 +1,49 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.IntegerValue;
import com.annimon.automatetool.lib.Value;
/**
*
* @author aNNiMON
*/
public final class AstHelper {
public static IntegerValue integer(int number) {
return new IntegerValue(number);
}
public static ValueExpression value(Value value) {
return new ValueExpression(value);
}
public static VariableExpression variable(String variable) {
return new VariableExpression(variable);
}
public static ValueExpression intValue(int number) {
return value(integer(number));
}
public static BlockStatement block(Statement... statements) {
final BlockStatement block = new BlockStatement();
for (Statement statement : statements) {
block.add(statement);
}
return block;
}
public static ForStatement forLoop(Statement initialization, Expression termination,
Statement increment, Statement block) {
return new ForStatement(initialization, termination, increment, block);
}
public static MoveStatement move(Expression x, Expression y) {
return new MoveStatement(x, y);
}
public static DelayStatement delay(Expression time) {
return new DelayStatement(time);
}
}

View File

@ -0,0 +1,60 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.IntegerValue;
import com.annimon.automatetool.lib.Value;
/**
*
* @author aNNiMON
*/
public final class BinaryExpression implements Expression {
public enum Operation {
ADDITION("+"),
SUBSTRACTION("-"),
MULTIPLICATION("*"),
DIVISION("/"),;
private final String operation;
private Operation(String operation) {
this.operation = operation;
}
public String getOperation() {
return operation;
}
}
private final Operation operation;
private final Expression expr1, expr2;
public BinaryExpression(Operation operator, Expression expr1, Expression expr2) {
this.operation = operator;
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public Value eval() {
final Value value1 = expr1.eval();
final Value value2 = expr2.eval();
// TODO check type (string)
switch (operation) {
case ADDITION:
return new IntegerValue(value1.asInteger() + value2.asInteger());
case SUBSTRACTION:
return new IntegerValue(value1.asInteger() - value2.asInteger());
case MULTIPLICATION:
return new IntegerValue(value1.asInteger() * value2.asInteger());
case DIVISION:
return new IntegerValue(value1.asInteger() / value2.asInteger());
}
throw new RuntimeException("Unknown operation: " + operation);
}
@Override
public String toString() {
return String.format("%s %s %s", expr1, operation.getOperation(), expr2);
}
}

View File

@ -0,0 +1,40 @@
package com.annimon.automatetool.parser.ast;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
/**
*
* @author aNNiMON
*/
public final class BlockStatement implements Statement {
private final List<Statement> statements;
public BlockStatement() {
this.statements = new ArrayList<>();
}
public BlockStatement(List<Statement> statements) {
this.statements = statements;
}
public void add(Statement statement) {
statements.add(statement);
}
@Override
public void execute() {
for (Statement statement : statements) {
statement.execute();
}
}
@Override
public String toString() {
return statements.stream()
.map(s -> s.toString())
.collect(Collectors.joining("\n"));
}
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class ClickStatement implements Statement {
private final Expression expression;
public ClickStatement(Expression expression) {
this.expression = expression;
}
@Override
public void execute() {
RobotUtils.click(expression.eval().asInteger());
}
@Override
public String toString() {
return String.format("click(%s)", expression);
}
}

View File

@ -0,0 +1,54 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.BooleanValue;
import com.annimon.automatetool.lib.Value;
/**
*
* @author aNNiMON
*/
public final class ConditionalExpression implements Expression {
public enum Operation {
LESS("<"),
GREATER(">");
private final String operation;
private Operation(String operation) {
this.operation = operation;
}
public String getOperation() {
return operation;
}
}
private final Operation operation;
private final Expression expr1, expr2;
public ConditionalExpression(Operation operator, Expression expr1, Expression expr2) {
this.operation = operator;
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public Value eval() {
final Value value1 = expr1.eval();
final Value value2 = expr2.eval();
// TODO check type (string)
switch (operation) {
case LESS:
return new BooleanValue(value1.asInteger() < value2.asInteger());
case GREATER:
return new BooleanValue(value1.asInteger() > value2.asInteger());
}
throw new RuntimeException("Unknown operation: " + operation);
}
@Override
public String toString() {
return String.format("%s %s %s", expr1, operation.getOperation(), expr2);
}
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class DelayStatement implements Statement {
private final Expression expression;
public DelayStatement(Expression expression) {
this.expression = expression;
}
@Override
public void execute() {
RobotUtils.delay(expression.eval().asInteger());
}
@Override
public String toString() {
return String.format("delay(%s)", expression);
}
}

View File

@ -0,0 +1,12 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.Value;
/**
*
* @author aNNiMON
*/
public interface Expression extends Node {
Value eval();
}

View File

@ -0,0 +1,31 @@
package com.annimon.automatetool.parser.ast;
/**
*
* @author aNNiMON
*/
public final class ForStatement implements Statement {
private final Statement initialization, increment;
private final Expression termination;
private final Statement block;
public ForStatement(Statement initialization, Expression termination, Statement increment, Statement block) {
this.initialization = initialization;
this.termination = termination;
this.increment = increment;
this.block = block;
}
@Override
public void execute() {
for (initialization.execute(); termination.eval().asBoolean(); increment.execute()) {
block.execute();
}
}
@Override
public String toString() {
return String.format("for (%s; %s; %s) %s", initialization, termination, increment, block);
}
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class KeyPressStatement implements Statement {
private final Expression key;
public KeyPressStatement(Expression key) {
this.key = key;
}
@Override
public void execute() {
RobotUtils.keyPress(key.eval().asInteger());
}
@Override
public String toString() {
return String.format("keypress(%s)", key);
}
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class KeyReleaseStatement implements Statement {
private final Expression key;
public KeyReleaseStatement(Expression key) {
this.key = key;
}
@Override
public void execute() {
RobotUtils.keyRelease(key.eval().asInteger());
}
@Override
public String toString() {
return String.format("keyrelease(%s)", key);
}
}

View File

@ -0,0 +1,30 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
import com.annimon.automatetool.lib.Value;
/**
*
* @author aNNiMON
*/
public final class MoveStatement implements Statement {
private final Expression xExpr, yExpr;
public MoveStatement(Expression x, Expression y) {
this.xExpr = x;
this.yExpr = y;
}
@Override
public void execute() {
final Value xValue = xExpr.eval();
final Value yValue = yExpr.eval();
RobotUtils.mouseMove(xValue.asInteger(), yValue.asInteger());
}
@Override
public String toString() {
return String.format("move(%s, %s)", xExpr, yExpr);
}
}

View File

@ -0,0 +1,9 @@
package com.annimon.automatetool.parser.ast;
/**
*
* @author aNNiMON
*/
public interface Node {
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class PressStatement implements Statement {
private final Expression button;
public PressStatement(Expression button) {
this.button = button;
}
@Override
public void execute() {
RobotUtils.mousePress(button.eval().asInteger());
}
@Override
public String toString() {
return String.format("press(%s)", button);
}
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class ReleaseStatement implements Statement {
private final Expression button;
public ReleaseStatement(Expression button) {
this.button = button;
}
@Override
public void execute() {
RobotUtils.mouseRelease(button.eval().asInteger());
}
@Override
public String toString() {
return String.format("release(%s)", button);
}
}

View File

@ -0,0 +1,10 @@
package com.annimon.automatetool.parser.ast;
/**
*
* @author aNNiMON
*/
public interface Statement {
void execute();
}

View File

@ -0,0 +1,26 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.RobotUtils;
/**
*
* @author aNNiMON
*/
public final class TypeStatement implements Statement {
private final Expression message;
public TypeStatement(Expression message) {
this.message = message;
}
@Override
public void execute() {
RobotUtils.typeText(message.eval().asString());
}
@Override
public String toString() {
return String.format("type(%s)", message);
}
}

View File

@ -0,0 +1,41 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.BooleanValue;
import com.annimon.automatetool.lib.IntegerValue;
import com.annimon.automatetool.lib.StringValue;
import com.annimon.automatetool.lib.Value;
/**
*
* @author aNNiMON
*/
public final class ValueExpression implements Expression {
private final Value value;
public ValueExpression(boolean value) {
this.value = new BooleanValue(value);
}
public ValueExpression(int value) {
this.value = new IntegerValue(value);
}
public ValueExpression(String value) {
this.value = new StringValue(value);
}
public ValueExpression(Value value) {
this.value = value;
}
@Override
public Value eval() {
return value;
}
@Override
public String toString() {
return value.toString();
}
}

View File

@ -0,0 +1,27 @@
package com.annimon.automatetool.parser.ast;
import com.annimon.automatetool.lib.Value;
import com.annimon.automatetool.lib.Variables;
/**
*
* @author aNNiMON
*/
public final class VariableExpression implements Expression {
private final String variable;
public VariableExpression(String variable) {
this.variable = variable;
}
@Override
public Value eval() {
return Variables.get(variable);
}
@Override
public String toString() {
return variable;
}
}