mirror of
https://github.com/aNNiMON/Own-Programming-Language-Tutorial.git
synced 2024-09-20 00:34:20 +03:00
Урок 1. Заготовка + калькулятор
This commit is contained in:
parent
202cb6b60e
commit
a1c16e9d42
27
src/com/annimon/ownlang/Main.java
Normal file
27
src/com/annimon/ownlang/Main.java
Normal 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());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
99
src/com/annimon/ownlang/parser/Lexer.java
Normal file
99
src/com/annimon/ownlang/parser/Lexer.java
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
115
src/com/annimon/ownlang/parser/Parser.java
Normal file
115
src/com/annimon/ownlang/parser/Parser.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
40
src/com/annimon/ownlang/parser/Token.java
Normal file
40
src/com/annimon/ownlang/parser/Token.java
Normal 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;
|
||||||
|
}
|
||||||
|
}
|
21
src/com/annimon/ownlang/parser/TokenType.java
Normal file
21
src/com/annimon/ownlang/parser/TokenType.java
Normal 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
|
||||||
|
}
|
34
src/com/annimon/ownlang/parser/ast/BinaryExpression.java
Normal file
34
src/com/annimon/ownlang/parser/ast/BinaryExpression.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
10
src/com/annimon/ownlang/parser/ast/Expression.java
Normal file
10
src/com/annimon/ownlang/parser/ast/Expression.java
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
package com.annimon.ownlang.parser.ast;
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @author aNNiMON
|
||||||
|
*/
|
||||||
|
public interface Expression {
|
||||||
|
|
||||||
|
double eval();
|
||||||
|
}
|
24
src/com/annimon/ownlang/parser/ast/NumberExpression.java
Normal file
24
src/com/annimon/ownlang/parser/ast/NumberExpression.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
31
src/com/annimon/ownlang/parser/ast/UnaryExpression.java
Normal file
31
src/com/annimon/ownlang/parser/ast/UnaryExpression.java
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user