Урок 1. Заготовка + калькулятор

This commit is contained in:
Victor 2015-05-18 17:07:40 +03:00
parent 202cb6b60e
commit a1c16e9d42
9 changed files with 401 additions and 0 deletions

View File

@ -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<Token> tokens = new Lexer(input2).tokenize();
for (Token token : tokens) {
System.out.println(token);
}
final List<Expression> expressions = new Parser(tokens).parse();
for (Expression expr : expressions) {
System.out.println(expr + " = " + expr.eval());
}
}
}

View File

@ -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<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 (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));
}
}

View File

@ -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<Token> tokens;
private final int size;
private int pos;
public Parser(List<Token> tokens) {
this.tokens = tokens;
size = tokens.size();
}
public List<Expression> parse() {
final List<Expression> 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);
}
}

View File

@ -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;
}
}

View File

@ -0,0 +1,21 @@
package com.annimon.ownlang.parser;
/**
*
* @author aNNiMON
*/
public enum TokenType {
NUMBER,
HEX_NUMBER,
PLUS,
MINUS,
STAR,
SLASH,
LPAREN, // (
RPAREN, // )
EOF
}

View File

@ -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);
}
}

View File

@ -0,0 +1,10 @@
package com.annimon.ownlang.parser.ast;
/**
*
* @author aNNiMON
*/
public interface Expression {
double eval();
}

View File

@ -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);
}
}

View File

@ -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);
}
}