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