diff --git a/src/com/annimon/ownlang/Main.java b/src/com/annimon/ownlang/Main.java new file mode 100644 index 0000000..7607ee2 --- /dev/null +++ b/src/com/annimon/ownlang/Main.java @@ -0,0 +1,27 @@ +package com.annimon.ownlang; + +import com.annimon.ownlang.parser.Lexer; +import com.annimon.ownlang.parser.Parser; +import com.annimon.ownlang.parser.Token; +import com.annimon.ownlang.parser.ast.Expression; +import java.util.List; + +/** + * @author aNNiMON + */ +public final class Main { + + public static void main(String[] args) { + final String input1 = "2 + 2"; + final String input2 = "(2 + 2) * #f"; + final List tokens = new Lexer(input2).tokenize(); + for (Token token : tokens) { + System.out.println(token); + } + + final List expressions = new Parser(tokens).parse(); + for (Expression expr : expressions) { + System.out.println(expr + " = " + expr.eval()); + } + } +} diff --git a/src/com/annimon/ownlang/parser/Lexer.java b/src/com/annimon/ownlang/parser/Lexer.java new file mode 100644 index 0000000..b05e60e --- /dev/null +++ b/src/com/annimon/ownlang/parser/Lexer.java @@ -0,0 +1,99 @@ +package com.annimon.ownlang.parser; + +import java.util.ArrayList; +import java.util.List; + +/** + * + * @author aNNiMON + */ +public final class Lexer { + + private static final String OPERATOR_CHARS = "+-*/()"; + private static final TokenType[] OPERATOR_TOKENS = { + TokenType.PLUS, TokenType.MINUS, + TokenType.STAR, TokenType.SLASH, + TokenType.LPAREN, TokenType.RPAREN, + }; + + 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 (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 (Character.isDigit(current)) { + 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() { + final int position = OPERATOR_CHARS.indexOf(peek(0)); + addToken(OPERATOR_TOKENS[position]); + 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/ownlang/parser/Parser.java b/src/com/annimon/ownlang/parser/Parser.java new file mode 100644 index 0000000..1205283 --- /dev/null +++ b/src/com/annimon/ownlang/parser/Parser.java @@ -0,0 +1,115 @@ +package com.annimon.ownlang.parser; + +import com.annimon.ownlang.parser.ast.BinaryExpression; +import com.annimon.ownlang.parser.ast.Expression; +import com.annimon.ownlang.parser.ast.NumberExpression; +import com.annimon.ownlang.parser.ast.UnaryExpression; +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 List parse() { + final List result = new ArrayList<>(); + while (!match(TokenType.EOF)) { + result.add(expression()); + } + return result; + } + + 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 = unary(); + + while (true) { + // 2 * 6 / 3 + if (match(TokenType.STAR)) { + result = new BinaryExpression('*', result, unary()); + continue; + } + if (match(TokenType.SLASH)) { + result = new BinaryExpression('/', result, unary()); + continue; + } + break; + } + + return result; + } + + 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)) { + return new NumberExpression(Double.parseDouble(current.getText())); + } + if (match(TokenType.HEX_NUMBER)) { + return new NumberExpression(Long.parseLong(current.getText(), 16)); + } + if (match(TokenType.LPAREN)) { + Expression result = expression(); + match(TokenType.RPAREN); + return result; + } + throw new RuntimeException("Unknown expression"); + } + + 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); + } +} diff --git a/src/com/annimon/ownlang/parser/Token.java b/src/com/annimon/ownlang/parser/Token.java new file mode 100644 index 0000000..d8d4b8f --- /dev/null +++ b/src/com/annimon/ownlang/parser/Token.java @@ -0,0 +1,40 @@ +package com.annimon.ownlang.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/ownlang/parser/TokenType.java b/src/com/annimon/ownlang/parser/TokenType.java new file mode 100644 index 0000000..8e5da80 --- /dev/null +++ b/src/com/annimon/ownlang/parser/TokenType.java @@ -0,0 +1,21 @@ +package com.annimon.ownlang.parser; + +/** + * + * @author aNNiMON + */ +public enum TokenType { + + NUMBER, + HEX_NUMBER, + + PLUS, + MINUS, + STAR, + SLASH, + + LPAREN, // ( + RPAREN, // ) + + EOF +} diff --git a/src/com/annimon/ownlang/parser/ast/BinaryExpression.java b/src/com/annimon/ownlang/parser/ast/BinaryExpression.java new file mode 100644 index 0000000..e20dfe1 --- /dev/null +++ b/src/com/annimon/ownlang/parser/ast/BinaryExpression.java @@ -0,0 +1,34 @@ +package com.annimon.ownlang.parser.ast; + +/** + * + * @author aNNiMON + */ +public final class BinaryExpression implements Expression { + + private final Expression expr1, expr2; + private final char operation; + + public BinaryExpression(char operation, Expression expr1, Expression expr2) { + this.operation = operation; + this.expr1 = expr1; + this.expr2 = expr2; + } + + @Override + public double eval() { + switch (operation) { + case '-': return expr1.eval() - expr2.eval(); + case '*': return expr1.eval() * expr2.eval(); + case '/': return expr1.eval() / expr2.eval(); + case '+': + default: + return expr1.eval() + expr2.eval(); + } + } + + @Override + public String toString() { + return String.format("%s %c %s", expr1, operation, expr2); + } +} diff --git a/src/com/annimon/ownlang/parser/ast/Expression.java b/src/com/annimon/ownlang/parser/ast/Expression.java new file mode 100644 index 0000000..2184c00 --- /dev/null +++ b/src/com/annimon/ownlang/parser/ast/Expression.java @@ -0,0 +1,10 @@ +package com.annimon.ownlang.parser.ast; + +/** + * + * @author aNNiMON + */ +public interface Expression { + + double eval(); +} diff --git a/src/com/annimon/ownlang/parser/ast/NumberExpression.java b/src/com/annimon/ownlang/parser/ast/NumberExpression.java new file mode 100644 index 0000000..7e06dc5 --- /dev/null +++ b/src/com/annimon/ownlang/parser/ast/NumberExpression.java @@ -0,0 +1,24 @@ +package com.annimon.ownlang.parser.ast; + +/** + * + * @author aNNiMON + */ +public final class NumberExpression implements Expression { + + private final double value; + + public NumberExpression(double value) { + this.value = value; + } + + @Override + public double eval() { + return value; + } + + @Override + public String toString() { + return Double.toString(value); + } +} diff --git a/src/com/annimon/ownlang/parser/ast/UnaryExpression.java b/src/com/annimon/ownlang/parser/ast/UnaryExpression.java new file mode 100644 index 0000000..86a005a --- /dev/null +++ b/src/com/annimon/ownlang/parser/ast/UnaryExpression.java @@ -0,0 +1,31 @@ +package com.annimon.ownlang.parser.ast; + +/** + * + * @author aNNiMON + */ +public final class UnaryExpression implements Expression { + + private final Expression expr1; + private final char operation; + + public UnaryExpression(char operation, Expression expr1) { + this.operation = operation; + this.expr1 = expr1; + } + + @Override + public double eval() { + switch (operation) { + case '-': return -expr1.eval(); + case '+': + default: + return expr1.eval(); + } + } + + @Override + public String toString() { + return String.format("%c %s", operation, expr1); + } +}