Initial
This commit is contained in:
commit
9144e65f7e
60
Main.j
Normal file
60
Main.j
Normal 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
73
build.xml
Normal 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
6
input.txt
Normal 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
1419
nbproject/build-impl.xml
Normal file
File diff suppressed because it is too large
Load Diff
8
nbproject/genfiles.properties
Normal file
8
nbproject/genfiles.properties
Normal 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
|
0
nbproject/private/config.properties
Normal file
0
nbproject/private/config.properties
Normal file
6
nbproject/private/private.properties
Normal file
6
nbproject/private/private.properties
Normal 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
|
7
nbproject/private/private.xml
Normal file
7
nbproject/private/private.xml
Normal 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>
|
80
nbproject/project.properties
Normal file
80
nbproject/project.properties
Normal 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
15
nbproject/project.xml
Normal 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>
|
46
src/com/annimon/stylang/Main.java
Normal file
46
src/com/annimon/stylang/Main.java
Normal 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();
|
||||
}
|
||||
}
|
39
src/com/annimon/stylang/lib/DoubleValue.java
Normal file
39
src/com/annimon/stylang/lib/DoubleValue.java
Normal 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);
|
||||
}
|
||||
}
|
39
src/com/annimon/stylang/lib/IntValue.java
Normal file
39
src/com/annimon/stylang/lib/IntValue.java
Normal 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);
|
||||
}
|
||||
}
|
37
src/com/annimon/stylang/lib/StringValue.java
Normal file
37
src/com/annimon/stylang/lib/StringValue.java
Normal 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;
|
||||
}
|
||||
}
|
46
src/com/annimon/stylang/lib/Type.java
Normal file
46
src/com/annimon/stylang/lib/Type.java
Normal 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";
|
||||
}
|
||||
};
|
||||
}
|
12
src/com/annimon/stylang/lib/Value.java
Normal file
12
src/com/annimon/stylang/lib/Value.java
Normal file
@ -0,0 +1,12 @@
|
||||
package com.annimon.stylang.lib;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public interface Value {
|
||||
|
||||
Type type();
|
||||
|
||||
Object raw();
|
||||
}
|
51
src/com/annimon/stylang/lib/Variables.java
Normal file
51
src/com/annimon/stylang/lib/Variables.java
Normal 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);
|
||||
}
|
||||
}
|
232
src/com/annimon/stylang/parser/Lexer.java
Normal file
232
src/com/annimon/stylang/parser/Lexer.java
Normal 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));
|
||||
}
|
||||
}
|
197
src/com/annimon/stylang/parser/Parser.java
Normal file
197
src/com/annimon/stylang/parser/Parser.java
Normal 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;
|
||||
}
|
||||
}
|
40
src/com/annimon/stylang/parser/Token.java
Normal file
40
src/com/annimon/stylang/parser/Token.java
Normal 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;
|
||||
}
|
||||
}
|
61
src/com/annimon/stylang/parser/TokenType.java
Normal file
61
src/com/annimon/stylang/parser/TokenType.java
Normal 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
|
||||
}
|
40
src/com/annimon/stylang/parser/ast/AssignmentStatement.java
Normal file
40
src/com/annimon/stylang/parser/ast/AssignmentStatement.java
Normal 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);
|
||||
}
|
||||
}
|
113
src/com/annimon/stylang/parser/ast/BinaryExpression.java
Normal file
113
src/com/annimon/stylang/parser/ast/BinaryExpression.java
Normal 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);
|
||||
}
|
||||
}
|
46
src/com/annimon/stylang/parser/ast/BlockStatement.java
Normal file
46
src/com/annimon/stylang/parser/ast/BlockStatement.java
Normal 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();
|
||||
}
|
||||
}
|
15
src/com/annimon/stylang/parser/ast/Expression.java
Normal file
15
src/com/annimon/stylang/parser/ast/Expression.java
Normal 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();
|
||||
}
|
10
src/com/annimon/stylang/parser/ast/Node.java
Normal file
10
src/com/annimon/stylang/parser/ast/Node.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.annimon.stylang.parser.ast;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public interface Node {
|
||||
|
||||
void accept(Visitor visitor);
|
||||
}
|
31
src/com/annimon/stylang/parser/ast/PrintStatement.java
Normal file
31
src/com/annimon/stylang/parser/ast/PrintStatement.java
Normal 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;
|
||||
}
|
||||
}
|
10
src/com/annimon/stylang/parser/ast/Statement.java
Normal file
10
src/com/annimon/stylang/parser/ast/Statement.java
Normal file
@ -0,0 +1,10 @@
|
||||
package com.annimon.stylang.parser.ast;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author aNNiMON
|
||||
*/
|
||||
public interface Statement extends Node {
|
||||
|
||||
void execute();
|
||||
}
|
73
src/com/annimon/stylang/parser/ast/TypeCastExpression.java
Normal file
73
src/com/annimon/stylang/parser/ast/TypeCastExpression.java
Normal 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;
|
||||
}
|
||||
}
|
65
src/com/annimon/stylang/parser/ast/UnaryExpression.java
Normal file
65
src/com/annimon/stylang/parser/ast/UnaryExpression.java
Normal 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);
|
||||
}
|
||||
}
|
48
src/com/annimon/stylang/parser/ast/ValueExpression.java
Normal file
48
src/com/annimon/stylang/parser/ast/ValueExpression.java
Normal 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();
|
||||
}
|
||||
}
|
39
src/com/annimon/stylang/parser/ast/VariableExpression.java
Normal file
39
src/com/annimon/stylang/parser/ast/VariableExpression.java
Normal 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);
|
||||
}
|
||||
}
|
34
src/com/annimon/stylang/parser/ast/Visitor.java
Normal file
34
src/com/annimon/stylang/parser/ast/Visitor.java
Normal 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);
|
||||
}
|
170
src/com/annimon/stylang/parser/visitors/ClassCompiler.java
Normal file
170
src/com/annimon/stylang/parser/visitors/ClassCompiler.java
Normal 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";
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
59
src/com/annimon/stylang/parser/visitors/VisitorAdapter.java
Normal file
59
src/com/annimon/stylang/parser/visitors/VisitorAdapter.java
Normal 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) {
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user