commit 9144e65f7e10e187d9898d42b9cafaffb8001311 Author: Victor Date: Thu Nov 15 18:11:16 2018 +0200 Initial diff --git a/Main.j b/Main.j new file mode 100644 index 0000000..98d2927 --- /dev/null +++ b/Main.j @@ -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 ()V + .limit stack 1 + .limit locals 1 + 0: aload_0 + 1: invokespecial java/lang/Object/()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/()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 + diff --git a/build.xml b/build.xml new file mode 100644 index 0000000..3d08533 --- /dev/null +++ b/build.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + Builds, tests, and runs the project StyLang. + + + diff --git a/input.txt b/input.txt new file mode 100644 index 0000000..3092c72 --- /dev/null +++ b/input.txt @@ -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); diff --git a/nbproject/build-impl.xml b/nbproject/build-impl.xml new file mode 100644 index 0000000..bb83557 --- /dev/null +++ b/nbproject/build-impl.xml @@ -0,0 +1,1419 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set src.dir + Must set test.src.dir + Must set build.dir + Must set dist.dir + Must set build.classes.dir + Must set dist.javadoc.dir + Must set build.test.classes.dir + Must set build.test.results.dir + Must set build.classes.excludes + Must set dist.jar + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + No tests executed. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must set JVM to use for profiling in profiler.info.jvm + Must set profiler agent JVM arguments in profiler.info.jvmargs.agent + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + To run this application from the command line without Ant, try: + + java -jar "${dist.jar.resolved}" + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + Must select one file in the IDE or set run.class + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set debug.class + + + + + Must select one file in the IDE or set debug.class + + + + + Must set fix.includes + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + Must select one file in the IDE or set profile.class + This target only works when run from inside the NetBeans IDE. + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + This target only works when run from inside the NetBeans IDE. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select one file in the IDE or set run.class + + + + + + Must select some files in the IDE or set test.includes + + + + + Must select one file in the IDE or set run.class + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Must select some files in the IDE or set javac.includes + + + + + + + + + + + + + + + + + + + + Some tests failed; see details above. + + + + + + + + + Must select some files in the IDE or set test.includes + + + + Some tests failed; see details above. + + + + Must select some files in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + Some tests failed; see details above. + + + + + Must select one file in the IDE or set test.class + + + + Must select one file in the IDE or set test.class + Must select some method in the IDE or set test.method + + + + + + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + Must select one file in the IDE or set applet.url + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/nbproject/genfiles.properties b/nbproject/genfiles.properties new file mode 100644 index 0000000..b78e7f6 --- /dev/null +++ b/nbproject/genfiles.properties @@ -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 diff --git a/nbproject/private/config.properties b/nbproject/private/config.properties new file mode 100644 index 0000000..e69de29 diff --git a/nbproject/private/private.properties b/nbproject/private/private.properties new file mode 100644 index 0000000..fbbfe24 --- /dev/null +++ b/nbproject/private/private.properties @@ -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 diff --git a/nbproject/private/private.xml b/nbproject/private/private.xml new file mode 100644 index 0000000..e74a594 --- /dev/null +++ b/nbproject/private/private.xml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/nbproject/project.properties b/nbproject/project.properties new file mode 100644 index 0000000..bb970e8 --- /dev/null +++ b/nbproject/project.properties @@ -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 diff --git a/nbproject/project.xml b/nbproject/project.xml new file mode 100644 index 0000000..7a57109 --- /dev/null +++ b/nbproject/project.xml @@ -0,0 +1,15 @@ + + + org.netbeans.modules.java.j2seproject + + + StyLang + + + + + + + + + diff --git a/src/com/annimon/stylang/Main.java b/src/com/annimon/stylang/Main.java new file mode 100644 index 0000000..761d8a0 --- /dev/null +++ b/src/com/annimon/stylang/Main.java @@ -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 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(); + } +} diff --git a/src/com/annimon/stylang/lib/DoubleValue.java b/src/com/annimon/stylang/lib/DoubleValue.java new file mode 100644 index 0000000..5019cdd --- /dev/null +++ b/src/com/annimon/stylang/lib/DoubleValue.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/lib/IntValue.java b/src/com/annimon/stylang/lib/IntValue.java new file mode 100644 index 0000000..8d68864 --- /dev/null +++ b/src/com/annimon/stylang/lib/IntValue.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/lib/StringValue.java b/src/com/annimon/stylang/lib/StringValue.java new file mode 100644 index 0000000..3db60e5 --- /dev/null +++ b/src/com/annimon/stylang/lib/StringValue.java @@ -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; + } +} diff --git a/src/com/annimon/stylang/lib/Type.java b/src/com/annimon/stylang/lib/Type.java new file mode 100644 index 0000000..ca28db0 --- /dev/null +++ b/src/com/annimon/stylang/lib/Type.java @@ -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"; + } + }; +} diff --git a/src/com/annimon/stylang/lib/Value.java b/src/com/annimon/stylang/lib/Value.java new file mode 100644 index 0000000..f99db1e --- /dev/null +++ b/src/com/annimon/stylang/lib/Value.java @@ -0,0 +1,12 @@ +package com.annimon.stylang.lib; + +/** + * + * @author aNNiMON + */ +public interface Value { + + Type type(); + + Object raw(); +} diff --git a/src/com/annimon/stylang/lib/Variables.java b/src/com/annimon/stylang/lib/Variables.java new file mode 100644 index 0000000..b3aa8b9 --- /dev/null +++ b/src/com/annimon/stylang/lib/Variables.java @@ -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> stack; + private static Map variables; + private static Map 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); + } +} diff --git a/src/com/annimon/stylang/parser/Lexer.java b/src/com/annimon/stylang/parser/Lexer.java new file mode 100644 index 0000000..5fc958a --- /dev/null +++ b/src/com/annimon/stylang/parser/Lexer.java @@ -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 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 tokens; + + private int pos; + + public Lexer(String input) { + this.input = input; + length = input.length(); + + tokens = new ArrayList<>(); + } + + public List 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)); + } +} diff --git a/src/com/annimon/stylang/parser/Parser.java b/src/com/annimon/stylang/parser/Parser.java new file mode 100644 index 0000000..2d66dfd --- /dev/null +++ b/src/com/annimon/stylang/parser/Parser.java @@ -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 tokens; + private final int size; + + private int pos; + + public Parser(List 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; + } +} diff --git a/src/com/annimon/stylang/parser/Token.java b/src/com/annimon/stylang/parser/Token.java new file mode 100644 index 0000000..0ff0523 --- /dev/null +++ b/src/com/annimon/stylang/parser/Token.java @@ -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; + } +} diff --git a/src/com/annimon/stylang/parser/TokenType.java b/src/com/annimon/stylang/parser/TokenType.java new file mode 100644 index 0000000..3eace04 --- /dev/null +++ b/src/com/annimon/stylang/parser/TokenType.java @@ -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 +} diff --git a/src/com/annimon/stylang/parser/ast/AssignmentStatement.java b/src/com/annimon/stylang/parser/ast/AssignmentStatement.java new file mode 100644 index 0000000..ad34ed1 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/AssignmentStatement.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/parser/ast/BinaryExpression.java b/src/com/annimon/stylang/parser/ast/BinaryExpression.java new file mode 100644 index 0000000..1670d09 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/BinaryExpression.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/parser/ast/BlockStatement.java b/src/com/annimon/stylang/parser/ast/BlockStatement.java new file mode 100644 index 0000000..7afe1ae --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/BlockStatement.java @@ -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 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(); + } +} diff --git a/src/com/annimon/stylang/parser/ast/Expression.java b/src/com/annimon/stylang/parser/ast/Expression.java new file mode 100644 index 0000000..74ba932 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/Expression.java @@ -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(); +} diff --git a/src/com/annimon/stylang/parser/ast/Node.java b/src/com/annimon/stylang/parser/ast/Node.java new file mode 100644 index 0000000..55ee148 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/Node.java @@ -0,0 +1,10 @@ +package com.annimon.stylang.parser.ast; + +/** + * + * @author aNNiMON + */ +public interface Node { + + void accept(Visitor visitor); +} diff --git a/src/com/annimon/stylang/parser/ast/PrintStatement.java b/src/com/annimon/stylang/parser/ast/PrintStatement.java new file mode 100644 index 0000000..3ef27aa --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/PrintStatement.java @@ -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; + } +} diff --git a/src/com/annimon/stylang/parser/ast/Statement.java b/src/com/annimon/stylang/parser/ast/Statement.java new file mode 100644 index 0000000..ccffaae --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/Statement.java @@ -0,0 +1,10 @@ +package com.annimon.stylang.parser.ast; + +/** + * + * @author aNNiMON + */ +public interface Statement extends Node { + + void execute(); +} diff --git a/src/com/annimon/stylang/parser/ast/TypeCastExpression.java b/src/com/annimon/stylang/parser/ast/TypeCastExpression.java new file mode 100644 index 0000000..46aa802 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/TypeCastExpression.java @@ -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; + } +} diff --git a/src/com/annimon/stylang/parser/ast/UnaryExpression.java b/src/com/annimon/stylang/parser/ast/UnaryExpression.java new file mode 100644 index 0000000..84574ab --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/UnaryExpression.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/parser/ast/ValueExpression.java b/src/com/annimon/stylang/parser/ast/ValueExpression.java new file mode 100644 index 0000000..3260541 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/ValueExpression.java @@ -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(); + } +} diff --git a/src/com/annimon/stylang/parser/ast/VariableExpression.java b/src/com/annimon/stylang/parser/ast/VariableExpression.java new file mode 100644 index 0000000..21c85aa --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/VariableExpression.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/parser/ast/Visitor.java b/src/com/annimon/stylang/parser/ast/Visitor.java new file mode 100644 index 0000000..87c6a23 --- /dev/null +++ b/src/com/annimon/stylang/parser/ast/Visitor.java @@ -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); +} diff --git a/src/com/annimon/stylang/parser/visitors/ClassCompiler.java b/src/com/annimon/stylang/parser/visitors/ClassCompiler.java new file mode 100644 index 0000000..f9821da --- /dev/null +++ b/src/com/annimon/stylang/parser/visitors/ClassCompiler.java @@ -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, "", "()V", null, null); + // pushes the 'this' variable + constructor.visitVarInsn(ALOAD, 0); + // invokes the super class constructor + constructor.visitMethodInsn(INVOKESPECIAL, "java/lang/Object", "", "()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", "", "()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"; + } +} diff --git a/src/com/annimon/stylang/parser/visitors/ConstantPoolPrinter.java b/src/com/annimon/stylang/parser/visitors/ConstantPoolPrinter.java new file mode 100644 index 0000000..8816f69 --- /dev/null +++ b/src/com/annimon/stylang/parser/visitors/ConstantPoolPrinter.java @@ -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); + } +} diff --git a/src/com/annimon/stylang/parser/visitors/VisitorAdapter.java b/src/com/annimon/stylang/parser/visitors/VisitorAdapter.java new file mode 100644 index 0000000..15612c1 --- /dev/null +++ b/src/com/annimon/stylang/parser/visitors/VisitorAdapter.java @@ -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) { + } +}