This commit is contained in:
Victor 2018-11-15 18:11:16 +02:00
commit 9144e65f7e
36 changed files with 3250 additions and 0 deletions

60
Main.j Normal file
View File

@ -0,0 +1,60 @@
; Main.j
; Generated by ClassFileAnalyzer (Can)
.bytecode 49.0
.class public Main
; Flag ACC_SUPER not set, see JVM spec
.super java/lang/Object
.field private static x D
.field private static y I
.method public <init>()V
.limit stack 1
.limit locals 1
0: aload_0
1: invokespecial java/lang/Object/<init>()V
4: return
.end method
.method public static main([Ljava/lang/String;)V
.limit stack 6
.limit locals 1
0: ldc 15
2: i2d
3: putstatic Main/x D
6: ldc 20
8: putstatic Main/y I
11: getstatic java/lang/System/out Ljava/io/PrintStream;
14: new java/lang/StringBuilder
17: dup
18: invokespecial java/lang/StringBuilder/<init>()V
21: ldc "x + y = "
23: invokevirtual java/lang/StringBuilder/append(Ljava/lang/String;)Ljava/lang/StringBuilder;
26: getstatic Main/x D
29: getstatic Main/y I
32: i2d
33: dadd
34: invokevirtual java/lang/StringBuilder/append(D)Ljava/lang/StringBuilder;
37: invokevirtual java/lang/StringBuilder/toString()Ljava/lang/String;
40: invokevirtual java/io/PrintStream/println(Ljava/lang/String;)V
43: getstatic java/lang/System/out Ljava/io/PrintStream;
46: ldc 20
48: ldc 5
50: idiv
51: ldc 2
53: ldc 2
55: iadd
56: ldc 2
58: imul
59: iadd
60: ldc 2
62: ldc 2
64: ldc 2
66: imul
67: iadd
68: isub
69: invokevirtual java/io/PrintStream/println(I)V
72: return
.end method

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="StyLang" default="default" basedir=".">
<description>Builds, tests, and runs the project StyLang.</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="StyLang-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>

6
input.txt Normal file
View File

@ -0,0 +1,6 @@
double x = (double)15;
int y = 20;
// print "x + y = " + (x + y);
print "x + y = " + (x + (double)y);
print 20 / 5 + (2 + 2) * 2 - (2 + 2 * 2);

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=59061f6b
build.xml.script.CRC32=1fd52470
build.xml.stylesheet.CRC32=8064a381@1.78.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=59061f6b
nbproject/build-impl.xml.script.CRC32=45404d2a
nbproject/build-impl.xml.stylesheet.CRC32=9ca7cb75@1.80.0.48

View File

View File

@ -0,0 +1,6 @@
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,80 @@
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=StyLang
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}/StyLang.jar
dist.javadoc.dir=${dist.dir}/javadoc
endorsed.classpath=
excludes=
file.reference.asm-5.0.4.jar=asm-5.0.4.jar
includes=**
jar.compress=false
javac.classpath=\
${file.reference.asm-5.0.4.jar}
# 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.reference.asm-5.0.4.jar=D:\\dev\\__frameworks\\asm-5.0.4\\doc\\javadoc\\user
javadoc.splitindex=true
javadoc.use=true
javadoc.version=false
javadoc.windowtitle=
main.class=com.annimon.stylang.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
source.reference.asm-5.0.4.jar=D:\\dev\\__frameworks\\asm-5.0.4\\src.zip
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>StyLang</name>
<source-roots>
<root id="src.dir"/>
</source-roots>
<test-roots>
<root id="test.src.dir"/>
</test-roots>
</data>
</configuration>
</project>

View File

@ -0,0 +1,46 @@
package com.annimon.stylang;
import com.annimon.stylang.parser.*;
import com.annimon.stylang.parser.ast.Statement;
import com.annimon.stylang.parser.visitors.ClassCompiler;
import com.annimon.stylang.parser.visitors.ConstantPoolPrinter;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
/**
* @author aNNiMON
*/
public final class Main {
public static void main(String[] args) throws IOException {
final String input = new String( Files.readAllBytes(Paths.get("input.txt")), "UTF-8");
final List<Token> tokens = new Lexer(input).tokenize();
for (Token token : tokens) {
System.out.println(token);
}
final Statement program = new Parser(tokens).parse();
System.out.println(program.toString());
program.execute();
System.out.println("\nConstant Pool");
program.accept(new ConstantPoolPrinter());
// Компиляция
final ClassCompiler compiler = new ClassCompiler();
final byte[] classRaw = compiler.compile(program);
Files.write(Paths.get("Main.class"), classRaw);
}
private static void ma() {
double x = (double)15;
int y = 20;
System.out.println("x + y = " + (x + y));
}
private static String concat(Object s1, Object s2) {
return new StringBuilder().append(s1).append(s2).toString();
}
}

View File

@ -0,0 +1,39 @@
package com.annimon.stylang.lib;
/**
*
* @author aNNiMON
*/
public final class DoubleValue implements Value {
public static final DoubleValue ZERO = new DoubleValue(0);
public static double get(Value value) {
return ((DoubleValue) value).value;
}
private final double value;
public DoubleValue(double value) {
this.value = value;
}
@Override
public Type type() {
return Type.DOUBLE;
}
public double getValue() {
return value;
}
@Override
public Object raw() {
return value;
}
@Override
public String toString() {
return Double.toString(value);
}
}

View File

@ -0,0 +1,39 @@
package com.annimon.stylang.lib;
/**
*
* @author aNNiMON
*/
public final class IntValue implements Value {
public static int get(Value value) {
return ((IntValue) value).value;
}
public static final IntValue ZERO = new IntValue(0);
private final int value;
public IntValue(int value) {
this.value = value;
}
@Override
public Type type() {
return Type.INT;
}
public int getValue() {
return value;
}
@Override
public Object raw() {
return value;
}
@Override
public String toString() {
return Integer.toString(value);
}
}

View File

@ -0,0 +1,37 @@
package com.annimon.stylang.lib;
/**
*
* @author aNNiMON
*/
public final class StringValue implements Value {
public static String get(Value value) {
return ((StringValue) value).value;
}
private final String value;
public StringValue(String value) {
this.value = value;
}
@Override
public Type type() {
return Type.STRING;
}
public String getValue() {
return value;
}
@Override
public Object raw() {
return value;
}
@Override
public String toString() {
return value;
}
}

View File

@ -0,0 +1,46 @@
package com.annimon.stylang.lib;
/**
*
* @author aNNiMON
*/
public interface Type {
public static final Type INVALID = new Type() {
@Override
public String toString() {
return "INVALID";
}
};
public static final Type INT = new Type() {
@Override
public String toString() {
return "int";
}
};
public static final Type LONG = new Type() {
@Override
public String toString() {
return "long";
}
};
public static final Type FLOAT = new Type() {
@Override
public String toString() {
return "float";
}
};
public static final Type DOUBLE = new Type() {
@Override
public String toString() {
return "double";
}
};
public static final Type STRING = new Type() {
@Override
public String toString() {
return "string";
}
};
}

View File

@ -0,0 +1,12 @@
package com.annimon.stylang.lib;
/**
*
* @author aNNiMON
*/
public interface Value {
Type type();
Object raw();
}

View File

@ -0,0 +1,51 @@
package com.annimon.stylang.lib;
import java.util.HashMap;
import java.util.Map;
import java.util.Stack;
/**
*
* @author aNNiMON
*/
public final class Variables {
private static final Stack<Map<String, Value>> stack;
private static Map<String, Value> variables;
private static Map<String, Type> types;
static {
stack = new Stack<>();
variables = new HashMap<>();
types = new HashMap<>();
}
public static void push() {
stack.push(new HashMap<>(variables));
}
public static void pop() {
variables = stack.pop();
}
public static boolean isExists(String key) {
return variables.containsKey(key);
}
public static Value get(String key) {
if (!isExists(key)) throw new RuntimeException("Variable " + key + " not found");
return variables.get(key);
}
public static void set(String key, Value value) {
variables.put(key, value);
}
public static Type getType(String key) {
return types.get(key);
}
public static void setType(String key, Type type) {
types.put(key, type);
}
}

View File

@ -0,0 +1,232 @@
package com.annimon.stylang.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 String OPERATOR_CHARS = "+-*/(){}=<>!&|,;";
private static final Map<String, TokenType> OPERATORS;
static {
OPERATORS = new HashMap<>();
OPERATORS.put("+", TokenType.PLUS);
OPERATORS.put("-", TokenType.MINUS);
OPERATORS.put("*", TokenType.STAR);
OPERATORS.put("/", TokenType.SLASH);
OPERATORS.put("(", TokenType.LPAREN);
OPERATORS.put(")", TokenType.RPAREN);
OPERATORS.put("{", TokenType.LBRACE);
OPERATORS.put("}", TokenType.RBRACE);
OPERATORS.put("=", TokenType.EQ);
OPERATORS.put("<", TokenType.LT);
OPERATORS.put(">", TokenType.GT);
OPERATORS.put(",", TokenType.COMMA);
OPERATORS.put(";", TokenType.SEMICOLON);
OPERATORS.put("!", TokenType.EXCL);
OPERATORS.put("&", TokenType.AMP);
OPERATORS.put("|", TokenType.BAR);
OPERATORS.put("==", TokenType.EQEQ);
OPERATORS.put("!=", TokenType.EXCLEQ);
OPERATORS.put("<=", TokenType.LTEQ);
OPERATORS.put(">=", TokenType.GTEQ);
OPERATORS.put("&&", TokenType.AMPAMP);
OPERATORS.put("||", TokenType.BARBAR);
}
private final String input;
private final int length;
private final List<Token> tokens;
private int pos;
public Lexer(String input) {
this.input = input;
length = input.length();
tokens = new ArrayList<>();
}
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 == '#') {
next();
tokenizeHexNumber();
}
else if (OPERATOR_CHARS.indexOf(current) != -1) {
tokenizeOperator();
} else {
// whitespaces
next();
}
}
return tokens;
}
private void tokenizeNumber() {
final StringBuilder buffer = new StringBuilder();
char current = peek(0);
while (true) {
if (current == '.') {
if (buffer.indexOf(".") != -1) throw new RuntimeException("Invalid float number");
} else if (!Character.isDigit(current)) {
break;
}
buffer.append(current);
current = next();
}
addToken(TokenType.NUMBER, buffer.toString());
}
private void tokenizeHexNumber() {
final StringBuilder buffer = new StringBuilder();
char current = peek(0);
while (Character.isDigit(current) || isHexNumber(current)) {
buffer.append(current);
current = next();
}
addToken(TokenType.HEX_NUMBER, buffer.toString());
}
private static boolean isHexNumber(char current) {
return "abcdef".indexOf(Character.toLowerCase(current)) != -1;
}
private void tokenizeOperator() {
char current = peek(0);
if (current == '/') {
if (peek(1) == '/') {
next();
next();
tokenizeComment();
return;
} else if (peek(1) == '*') {
next();
next();
tokenizeMultilineComment();
return;
}
}
final StringBuilder buffer = new StringBuilder();
while (true) {
final String text = buffer.toString();
if (!OPERATORS.containsKey(text + current) && !text.isEmpty()) {
addToken(OPERATORS.get(text));
return;
}
buffer.append(current);
current = next();
}
}
private void tokenizeWord() {
final StringBuilder buffer = new StringBuilder();
char current = peek(0);
while (true) {
if (!Character.isLetterOrDigit(current) && (current != '_') && (current != '$')) {
break;
}
buffer.append(current);
current = next();
}
final String word = buffer.toString();
switch (word) {
case "int": addToken(TokenType.INT); break;
case "long": addToken(TokenType.LONG); break;
case "float": addToken(TokenType.FLOAT); break;
case "double": addToken(TokenType.DOUBLE); break;
case "string": addToken(TokenType.STRING); break;
case "var": addToken(TokenType.VAR); break;
case "print": addToken(TokenType.PRINT); break;
case "if": addToken(TokenType.IF); break;
case "else": addToken(TokenType.ELSE); break;
case "while": addToken(TokenType.WHILE); break;
case "for": addToken(TokenType.FOR); break;
case "do": addToken(TokenType.DO); break;
case "break": addToken(TokenType.BREAK); break;
case "continue": addToken(TokenType.CONTINUE); break;
case "def": addToken(TokenType.DEF); break;
case "return": addToken(TokenType.RETURN); break;
default:
addToken(TokenType.WORD, word);
break;
}
}
private void tokenizeText() {
next();// skip "
final StringBuilder buffer = new StringBuilder();
char current = peek(0);
while (true) {
if (current == '\\') {
current = next();
switch (current) {
case '"': current = next(); buffer.append('"'); continue;
case 'n': current = next(); buffer.append('\n'); continue;
case 't': current = next(); buffer.append('\t'); continue;
}
buffer.append('\\');
continue;
}
if (current == '"') break;
buffer.append(current);
current = next();
}
next(); // skip closing "
addToken(TokenType.TEXT, buffer.toString());
}
private void tokenizeComment() {
char current = peek(0);
while ("\r\n\0".indexOf(current) == -1) {
current = next();
}
}
private void tokenizeMultilineComment() {
char current = peek(0);
while (true) {
if (current == '\0') throw new RuntimeException("Missing close tag");
if (current == '*' && peek(1) == '/') break;
current = next();
}
next(); // *
next(); // /
}
private char next() {
pos++;
return peek(0);
}
private char peek(int relativePosition) {
final int position = pos + relativePosition;
if (position >= length) return '\0';
return input.charAt(position);
}
private void addToken(TokenType type) {
addToken(type, "");
}
private void addToken(TokenType type, String text) {
tokens.add(new Token(type, text));
}
}

View File

@ -0,0 +1,197 @@
package com.annimon.stylang.parser;
import com.annimon.stylang.lib.DoubleValue;
import com.annimon.stylang.lib.IntValue;
import com.annimon.stylang.lib.Type;
import com.annimon.stylang.lib.Value;
import com.annimon.stylang.parser.ast.*;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author aNNiMON
*/
public final class Parser {
private static final Token EOF = new Token(TokenType.EOF, "");
private final List<Token> tokens;
private final int size;
private int pos;
public Parser(List<Token> tokens) {
this.tokens = tokens;
size = tokens.size();
}
public Statement parse() {
final BlockStatement result = new BlockStatement();
while (!match(TokenType.EOF)) {
result.add(statement());
}
return result;
}
private Statement block() {
final BlockStatement block = new BlockStatement();
consume(TokenType.LBRACE);
while (!match(TokenType.RBRACE)) {
block.add(statement());
}
return block;
}
private Statement statementOrBlock() {
if (lookMatch(0, TokenType.LBRACE)) return block();
return statement();
}
private Statement statement() {
if (match(TokenType.PRINT)) {
final Expression expression = expression();
consume(TokenType.SEMICOLON);
return new PrintStatement(expression);
}
return assignmentStatement();
}
private Statement assignmentStatement() {
// WORD EQ
final Type type = type();
final Token current = get(0);
if (match(TokenType.WORD) && lookMatch(0, TokenType.EQ)) {
final String variable = current.getText();
consume(TokenType.EQ);
final Expression expression = expression();
final Type exprType = expression.type();
consume(TokenType.SEMICOLON);
if (exprType != type) throw new RuntimeException("Type mismatch. Expect " + type + ", but found " + exprType);
return new AssignmentStatement(type, variable, expression);
}
throw new RuntimeException("Unknown statement");
}
private Type type() {
if (match(TokenType.INT)) return Type.INT;
if (match(TokenType.LONG)) return Type.LONG;
if (match(TokenType.DOUBLE)) return Type.DOUBLE;
if (match(TokenType.STRING)) return Type.STRING;
throw new RuntimeException("Unknown type");
}
private Expression expression() {
return additive();
}
private Expression additive() {
Expression result = multiplicative();
while (true) {
if (match(TokenType.PLUS)) {
result = new BinaryExpression('+', result, multiplicative());
continue;
}
if (match(TokenType.MINUS)) {
result = new BinaryExpression('-', result, multiplicative());
continue;
}
break;
}
return result;
}
private Expression multiplicative() {
Expression result = typeCast();
while (true) {
if (match(TokenType.STAR)) {
result = new BinaryExpression('*', result, typeCast());
continue;
}
if (match(TokenType.SLASH)) {
result = new BinaryExpression('/', result, typeCast());
continue;
}
break;
}
return result;
}
private Expression typeCast() {
if (lookMatch(0, TokenType.LPAREN) && lookMatch(2, TokenType.RPAREN)) {
if (lookMatch(1, TokenType.INT) || lookMatch(1, TokenType.DOUBLE)) {
consume(TokenType.LPAREN);
final Type type = type();
consume(TokenType.RPAREN);
return new TypeCastExpression(type, unary());
}
}
return unary();
}
private Expression unary() {
if (match(TokenType.MINUS)) {
return new UnaryExpression('-', primary());
}
if (match(TokenType.PLUS)) {
return primary();
}
return primary();
}
private Expression primary() {
final Token current = get(0);
if (match(TokenType.NUMBER)) {
Value value;
if (current.getText().contains(".")) {
value = new DoubleValue(Double.parseDouble(current.getText()));
} else {
value = new IntValue(Integer.parseInt(current.getText()));
}
return new ValueExpression(value);
}
if (match(TokenType.HEX_NUMBER)) {
return new ValueExpression((int)Long.parseLong(current.getText(), 16));
}
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");
}
private Token consume(TokenType type) {
final Token current = get(0);
if (type != current.getType()) throw new RuntimeException("Token " + current + " doesn't match " + type);
pos++;
return current;
}
private boolean match(TokenType type) {
final Token current = get(0);
if (type != current.getType()) return false;
pos++;
return true;
}
private Token get(int relativePosition) {
final int position = pos + relativePosition;
if (position >= size) return EOF;
return tokens.get(position);
}
private boolean lookMatch(int i, TokenType type) {
return get(i).getType() == type;
}
}

View File

@ -0,0 +1,40 @@
package com.annimon.stylang.parser;
/**
*
* @author aNNiMON
*/
public final class Token {
private TokenType type;
private String text;
public Token() {
}
public Token(TokenType type, String text) {
this.type = type;
this.text = text;
}
public TokenType getType() {
return type;
}
public void setType(TokenType type) {
this.type = type;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
@Override
public String toString() {
return type + " " + text;
}
}

View File

@ -0,0 +1,61 @@
package com.annimon.stylang.parser;
/**
*
* @author aNNiMON
*/
public enum TokenType {
NUMBER,
HEX_NUMBER,
WORD,
TEXT,
// types
INT,
LONG,
FLOAT,
DOUBLE,
STRING,
VAR,
// keyword
PRINT,
IF,
ELSE,
WHILE,
FOR,
DO,
BREAK,
CONTINUE,
DEF,
RETURN,
PLUS,
MINUS,
STAR,
SLASH,
EQ,
EQEQ,
EXCL,
EXCLEQ,
LT,
LTEQ,
GT,
GTEQ,
BAR,
BARBAR,
AMP,
AMPAMP,
LPAREN, // (
RPAREN, // )
LBRACE, // {
RBRACE, // }
COMMA, // ,
SEMICOLON, // ;
EOF
}

View File

@ -0,0 +1,40 @@
package com.annimon.stylang.parser.ast;
import com.annimon.stylang.lib.Type;
import com.annimon.stylang.lib.Value;
import com.annimon.stylang.lib.Variables;
/**
*
* @author aNNiMON
*/
public final class AssignmentStatement implements Statement {
public final Type type;
public final String variable;
public final Expression expression;
public AssignmentStatement(Type type, String variable, Expression expression) {
this.type = type;
this.variable = variable;
this.expression = expression;
Variables.setType(variable, type);
}
@Override
public void execute() {
Variables.set(variable, expression.eval());
}
@Override
public void accept(Visitor visitor) {
visitor.start(this);
expression.accept(visitor);
visitor.finish(this);
}
@Override
public String toString() {
return String.format("%s = %s", variable, expression);
}
}

View File

@ -0,0 +1,113 @@
package com.annimon.stylang.parser.ast;
import com.annimon.stylang.lib.*;
/**
*
* @author aNNiMON
*/
public final class BinaryExpression implements Expression {
public final Expression expr1, expr2;
public final char operation;
public BinaryExpression(char operation, Expression expr1, Expression expr2) {
this.operation = operation;
this.expr1 = expr1;
this.expr2 = expr2;
}
@Override
public Type type() {
final Type t1 = expr1.type();
final Type t2 = expr2.type();
switch (operation) {
case '+':
if (t1 == Type.STRING) return Type.STRING;
if (t1 == Type.INT && t2 == Type.INT) return Type.INT;
if (t1 == Type.DOUBLE && t2 == Type.INT) return Type.DOUBLE;
if (t1 == Type.DOUBLE && t2 == Type.DOUBLE) return Type.DOUBLE;
break;
case '-':
case '*':
case '/':
if (t1 == Type.INT && t2 == Type.INT) return Type.INT;
break;
}
throw new RuntimeException("Unsupported types in operation");
}
@Override
public Value eval() {
final Value value1 = expr1.eval();
final Value value2 = expr2.eval();
switch (operation) {
case '+': return plus(value1, value2);
case '-': return minus(value1, value2);
case '*': return multiply(value1, value2);
case '/': return divide(value1, value2);
}
throw new UnsupportedOperationException("Unknown operation");
}
@Override
public void accept(Visitor visitor) {
visitor.start(this);
expr1.accept(visitor);
visitor.visit(this);
expr2.accept(visitor);
visitor.finish(this);
}
private Value plus(Value value1, Value value2) {
if (value1.type() == Type.INT) {
if (value2.type() == Type.INT) {
return new IntValue(IntValue.get(value1) + IntValue.get(value2));
}
}
if (value1.type() == Type.DOUBLE) {
if (value2.type() == Type.INT) {
return new DoubleValue(DoubleValue.get(value1) + IntValue.get(value2));
}
if (value2.type() == Type.DOUBLE) {
return new DoubleValue(DoubleValue.get(value1) + DoubleValue.get(value2));
}
}
if (value1.type() == Type.STRING) {
return new StringValue(StringValue.get(value1) + value2.toString());
}
throw new UnsupportedOperationException("plus in unsupported type " + value1.type() + " and " + value2.type());
}
private Value minus(Value value1, Value value2) {
if (value1.type() == Type.INT) {
if (value2.type() == Type.INT) {
return new IntValue(IntValue.get(value1) - IntValue.get(value2));
}
}
throw new UnsupportedOperationException("minus in unsupported type " + value1.type() + " and " + value2.type());
}
private Value multiply(Value value1, Value value2) {
if (value1.type() == Type.INT) {
if (value2.type() == Type.INT) {
return new IntValue(IntValue.get(value1) * IntValue.get(value2));
}
}
throw new UnsupportedOperationException("multiply onn unsupported type " + value1.type() + " and " + value2.type());
}
private Value divide(Value value1, Value value2) {
if (value1.type() == Type.INT) {
if (value2.type() == Type.INT) {
return new IntValue(IntValue.get(value1) / IntValue.get(value2));
}
}
throw new UnsupportedOperationException("divide on unsupported type " + value1.type() + " and " + value2.type());
}
@Override
public String toString() {
return String.format("[%s %c %s]", expr1, operation, expr2);
}
}

View File

@ -0,0 +1,46 @@
package com.annimon.stylang.parser.ast;
import java.util.ArrayList;
import java.util.List;
/**
*
* @author aNNiMON
*/
public final class BlockStatement implements Statement {
public final List<Statement> statements;
public BlockStatement() {
statements = new ArrayList<>();
}
public void add(Statement statement) {
statements.add(statement);
}
@Override
public void execute() {
for (Statement statement : statements) {
statement.execute();
}
}
@Override
public void accept(Visitor visitor) {
visitor.start(this);
for (Statement statement : statements) {
statement.accept(visitor);
}
visitor.finish(this);
}
@Override
public String toString() {
final StringBuilder result = new StringBuilder();
for (Statement statement : statements) {
result.append(statement.toString()).append(System.lineSeparator());
}
return result.toString();
}
}

View File

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

View File

@ -0,0 +1,10 @@
package com.annimon.stylang.parser.ast;
/**
*
* @author aNNiMON
*/
public interface Node {
void accept(Visitor visitor);
}

View File

@ -0,0 +1,31 @@
package com.annimon.stylang.parser.ast;
/**
*
* @author aNNiMON
*/
public final class PrintStatement implements Statement {
public final Expression expression;
public PrintStatement(Expression expression) {
this.expression = expression;
}
@Override
public void execute() {
System.out.println(expression.eval());
}
@Override
public void accept(Visitor visitor) {
visitor.start(this);
expression.accept(visitor);
visitor.finish(this);
}
@Override
public String toString() {
return "print " + expression;
}
}

View File

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

View File

@ -0,0 +1,73 @@
package com.annimon.stylang.parser.ast;
import com.annimon.stylang.lib.DoubleValue;
import com.annimon.stylang.lib.IntValue;
import com.annimon.stylang.lib.Type;
import com.annimon.stylang.lib.Value;
/**
*
* @author aNNiMON
*/
public final class TypeCastExpression implements Expression {
public enum Direction {
NONE,
I2D,
D2I,
CUSTOM,
INVALID,
}
public final Type type;
public final Expression expression;
public final Direction direction;
public TypeCastExpression(Type type, Expression expression) {
this.type = type;
this.expression = expression;
direction = getDirection();
}
private Direction getDirection() {
final Type from = expression.type();
final Type to = type;
if (from == to) return Direction.NONE;
if (from == Type.INT) {
if (to == Type.DOUBLE) return Direction.I2D;
}
if (from == Type.DOUBLE) {
if (to == Type.INT) return Direction.D2I;
}
return Direction.INVALID;
}
@Override
public Type type() {
if (direction == Direction.INVALID) throw new RuntimeException("Invalid type cast");
return type;
}
@Override
public Value eval() {
final Value result = expression.eval();
switch (direction) {
case NONE: return result;
case I2D: return new DoubleValue((double) IntValue.get(result));
case D2I: return new IntValue((int) DoubleValue.get(result));
}
throw new RuntimeException("Unsupported type cast from " + result.type() + " to " + type);
}
@Override
public void accept(Visitor visitor) {
expression.accept(visitor);
visitor.visit(this);
}
@Override
public String toString() {
return "(" + type + ")" + expression;
}
}

View File

@ -0,0 +1,65 @@
package com.annimon.stylang.parser.ast;
import com.annimon.stylang.lib.*;
/**
*
* @author aNNiMON
*/
public final class UnaryExpression implements Expression {
public final char operation;
private final Expression expr1;
public UnaryExpression(char operation, Expression expr1) {
this.operation = operation;
this.expr1 = expr1;
}
@Override
public Type type() {
final Type t1 = expr1.type();
switch (operation) {
case '+':
case '-':
if (t1 == Type.INT) return Type.INT;
break;
}
throw new RuntimeException("Unsupported types in operation");
}
@Override
public Value eval() {
switch (operation) {
case '-': return minus(expr1.eval());
case '+':
return plus(expr1.eval());
}
throw new UnsupportedOperationException("Unknown operation");
}
private Value plus(Value value) {
if (value.type() == Type.INT) {
return value;
}
throw new UnsupportedOperationException("plus in unsupported type");
}
private Value minus(Value value) {
if (value.type() == Type.INT) {
return new IntValue(-IntValue.get(value));
}
throw new UnsupportedOperationException("minus in unsupported type");
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
expr1.accept(visitor);
}
@Override
public String toString() {
return String.format("%c %s", operation, expr1);
}
}

View File

@ -0,0 +1,48 @@
package com.annimon.stylang.parser.ast;
import com.annimon.stylang.lib.*;
/**
*
* @author aNNiMON
*/
public final class ValueExpression implements Expression {
public final Value value;
public ValueExpression(int value) {
this.value = new IntValue(value);
}
public ValueExpression(double value) {
this.value = new DoubleValue(value);
}
public ValueExpression(String value) {
this.value = new StringValue(value);
}
public ValueExpression(Value value) {
this.value = value;
}
@Override
public Type type() {
return value.type();
}
@Override
public Value eval() {
return value;
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public String toString() {
return value.toString();
}
}

View File

@ -0,0 +1,39 @@
package com.annimon.stylang.parser.ast;
import com.annimon.stylang.lib.Type;
import com.annimon.stylang.lib.Value;
import com.annimon.stylang.lib.Variables;
/**
*
* @author aNNiMON
*/
public final class VariableExpression implements Expression {
public final String variable;
public VariableExpression(String name) {
this.variable = name;
}
@Override
public Type type() {
return Variables.getType(variable);
}
@Override
public Value eval() {
return Variables.get(variable);
}
@Override
public void accept(Visitor visitor) {
visitor.visit(this);
}
@Override
public String toString() {
// return String.format("%s [%f]", name, Constants.get(name));
return String.format("%s", variable);
}
}

View File

@ -0,0 +1,34 @@
package com.annimon.stylang.parser.ast;
/**
*
* @author aNNiMON
*/
public interface Visitor {
void start(AssignmentStatement assignment);
void finish(AssignmentStatement assignment);
void start(BinaryExpression expr);
void visit(BinaryExpression expr);
void finish(BinaryExpression expr);
void start(BlockStatement block);
void finish(BlockStatement block);
void start(PrintStatement statement);
void finish(PrintStatement statement);
void visit(TypeCastExpression expr);
void visit(UnaryExpression expr);
void visit(ValueExpression v);
void visit(VariableExpression ve);
}

View File

@ -0,0 +1,170 @@
package com.annimon.stylang.parser.visitors;
import com.annimon.stylang.lib.*;
import com.annimon.stylang.parser.ast.*;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.MethodVisitor;
import static org.objectweb.asm.Opcodes.*;
/**
*
* @author aNNiMON
*/
public final class ClassCompiler extends VisitorAdapter {
private String className;
private ClassWriter cw;
private MethodVisitor mw;
public byte[] compile(Node node) {
cw = new ClassWriter(ClassWriter.COMPUTE_MAXS);
// public class Main
className = "Main";
cw.visit(V1_5, ACC_PUBLIC, className, null, "java/lang/Object", null);
// Конструктор public Main() {}
final MethodVisitor constructor = cw.visitMethod(ACC_PUBLIC, "<init>", "()V", null, null);
// pushes the 'this' variable
constructor.visitVarInsn(ALOAD, 0);
// invokes the super class constructor
constructor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
constructor.visitInsn(RETURN);
// this code uses a maximum of one stack element and one local variable
constructor.visitMaxs(1, 1);
constructor.visitEnd();
// public static void main(String[] args) { }
mw = cw.visitMethod(ACC_PUBLIC | ACC_STATIC, "main", "([Ljava/lang/String;)V", null, null);
// Компилируем код
node.accept(this);
mw.visitInsn(RETURN);
// max stack and max locals automatically computed
mw.visitMaxs(0, 0);
mw.visitEnd();
return cw.toByteArray();
}
@Override
public void start(AssignmentStatement assignment) {
cw.visitField(ACC_PRIVATE | ACC_STATIC, assignment.variable, typeToJavaType(assignment.type), null, null);
}
@Override
public void finish(AssignmentStatement assignment) {
mw.visitFieldInsn(PUTSTATIC, className, assignment.variable, typeToJavaType(assignment.type));
}
@Override
public void start(BinaryExpression expr) {
if (expr.operation == '+' && expr.type() == Type.STRING) {
// Конкатенация строк - начало
mw.visitTypeInsn(NEW, "java/lang/StringBuilder");
mw.visitInsn(DUP);
mw.visitMethodInsn(INVOKESPECIAL, "java/lang/StringBuilder", "<init>", "()V", false);
}
}
@Override
public void visit(BinaryExpression expr) {
if (expr.operation == '+' && expr.type() == Type.STRING) {
// Конкатенация строк - середина
// Записали первый аргумент, теперь вызывает append(..)
mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", "(Ljava/lang/String;)Ljava/lang/StringBuilder;", false);
}
}
@Override
public void finish(BinaryExpression expr) {
final Type type = expr.type();
switch (expr.operation) {
case '+': {
if (type == Type.INT) mw.visitInsn(IADD);
if (type == Type.DOUBLE) mw.visitInsn(DADD);
if (type == Type.STRING) {
// Конкатенация строк - завершение
// Записали второй аргумент, теперь вызывает append(..) и toString
final String signature = "("+ typeToJavaType(expr.expr2.type()) + ")Ljava/lang/StringBuilder;";
mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "append", signature, false);
mw.visitMethodInsn(INVOKEVIRTUAL, "java/lang/StringBuilder", "toString", "()Ljava/lang/String;", false);
}
break;
}
case '-': {
if (type == Type.INT) mw.visitInsn(ISUB);
if (type == Type.DOUBLE) mw.visitInsn(DSUB);
break;
}
case '*': {
if (type == Type.INT) mw.visitInsn(IMUL);
if (type == Type.DOUBLE) mw.visitInsn(DMUL);
break;
}
case '/': {
if (type == Type.INT) mw.visitInsn(IDIV);
if (type == Type.DOUBLE) mw.visitInsn(DDIV);
break;
}
}
}
@Override
public void start(BlockStatement block) {
}
@Override
public void finish(BlockStatement block) {
}
@Override
public void start(PrintStatement statement) {
// pushes the 'out' field (of type PrintStream) of the System class
mw.visitFieldInsn(GETSTATIC, "java/lang/System", "out", "Ljava/io/PrintStream;");
}
@Override
public void finish(PrintStatement statement) {
// invokes the 'println' method (defined in the PrintStream class)
final String signature = "("+ typeToJavaType(statement.expression.type()) + ")V";
mw.visitMethodInsn(INVOKEVIRTUAL, "java/io/PrintStream", "println", signature, false);
}
@Override
public void visit(TypeCastExpression expr) {
switch (expr.direction) {
case D2I: mw.visitInsn(D2I);
case I2D: mw.visitInsn(I2D);
}
}
@Override
public void visit(UnaryExpression expr) {
}
@Override
public void visit(ValueExpression v) {
mw.visitLdcInsn(v.value.raw());
/*final Type type = v.type();
if (type == Type.INT) {
mw.visitLdcInsn(IntValue.get(v.value));
} else if (type == Type.DOUBLE) {
mw.visitLdcInsn(DoubleValue.get(v.value));
} else if (type == Type.STRING) {
mw.visitLdcInsn(StringValue.get(v.value));
}*/
}
@Override
public void visit(VariableExpression ve) {
mw.visitFieldInsn(GETSTATIC, className, ve.variable, typeToJavaType(ve.type()));
}
private String typeToJavaType(Type type) {
if (type == Type.INT) return "I";
if (type == Type.DOUBLE) return "D";
if (type == Type.STRING) return "Ljava/lang/String;";
return "java/lang/Object";
}
}

View File

@ -0,0 +1,23 @@
package com.annimon.stylang.parser.visitors;
import com.annimon.stylang.parser.ast.ValueExpression;
import com.annimon.stylang.parser.ast.VariableExpression;
/**
*
* @author aNNiMON
*/
public final class ConstantPoolPrinter extends VisitorAdapter {
@Override
public void visit(ValueExpression v) {
super.visit(v);
System.out.println(v.value);
}
@Override
public void visit(VariableExpression ve) {
super.visit(ve);
System.out.println(ve.variable);
}
}

View File

@ -0,0 +1,59 @@
package com.annimon.stylang.parser.visitors;
import com.annimon.stylang.parser.ast.*;
/**
*
* @author aNNiMON
*/
public class VisitorAdapter implements Visitor {
@Override
public void start(AssignmentStatement assignment) {
}
@Override
public void finish(AssignmentStatement assignment) {
}
@Override
public void start(BinaryExpression expr) {
}
@Override
public void visit(BinaryExpression expr) {
}
@Override
public void finish(BinaryExpression expr) {
}
@Override
public void start(BlockStatement block) {
}
@Override
public void finish(BlockStatement block) {
}
@Override
public void start(PrintStatement statement) {
}
@Override
public void finish(PrintStatement statement) {
}
@Override
public void visit(TypeCastExpression expr) {
}
@Override
public void visit(UnaryExpression expr) {
}
@Override
public void visit(ValueExpression v) {
}
@Override
public void visit(VariableExpression ve) {
}
}