diff --git a/program.txt b/program.txt index 029fe18..d637fe4 100644 --- a/program.txt +++ b/program.txt @@ -6,6 +6,8 @@ print "word = " + word + "\n" print "word2 = " + word2 + "\n" print "1" > "abc" print "\n" -if (1 < 2) print "1 = 1" +if (1 <= 2) print "1 = 1" else print "1 != 1" print "\n" +if (40 < 50 && 50 > 60) print "true" +else print "false" \ No newline at end of file diff --git a/src/com/annimon/ownlang/parser/Lexer.java b/src/com/annimon/ownlang/parser/Lexer.java index 4277bf4..a7f94d8 100644 --- a/src/com/annimon/ownlang/parser/Lexer.java +++ b/src/com/annimon/ownlang/parser/Lexer.java @@ -1,7 +1,9 @@ package com.annimon.ownlang.parser; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** * @@ -9,13 +11,33 @@ import java.util.List; */ 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, - TokenType.EQ, TokenType.LT, TokenType.GT - }; + private static final String OPERATOR_CHARS = "+-*/()=<>!&|"; + + private static final Map 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.EQ); + OPERATORS.put("<", TokenType.LT); + OPERATORS.put(">", TokenType.GT); + + 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; @@ -81,9 +103,30 @@ public final class Lexer { } private void tokenizeOperator() { - final int position = OPERATOR_CHARS.indexOf(peek(0)); - addToken(OPERATOR_TOKENS[position]); - next(); + 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() { @@ -132,6 +175,24 @@ public final class Lexer { 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); diff --git a/src/com/annimon/ownlang/parser/Parser.java b/src/com/annimon/ownlang/parser/Parser.java index 4e9627a..8b24c5a 100644 --- a/src/com/annimon/ownlang/parser/Parser.java +++ b/src/com/annimon/ownlang/parser/Parser.java @@ -74,23 +74,68 @@ public final class Parser { private Expression expression() { - return conditional(); + return logicalOr(); + } + + private Expression logicalOr() { + Expression result = logicalAnd(); + + while (true) { + if (match(TokenType.BARBAR)) { + result = new ConditionalExpression(ConditionalExpression.Operator.OR, result, logicalAnd()); + continue; + } + break; + } + + return result; + } + + private Expression logicalAnd() { + Expression result = equality(); + + while (true) { + if (match(TokenType.AMPAMP)) { + result = new ConditionalExpression(ConditionalExpression.Operator.AND, result, equality()); + continue; + } + break; + } + + return result; + } + + private Expression equality() { + Expression result = conditional(); + + if (match(TokenType.EQEQ)) { + return new ConditionalExpression(ConditionalExpression.Operator.EQUALS, result, conditional()); + } + if (match(TokenType.EXCLEQ)) { + return new ConditionalExpression(ConditionalExpression.Operator.NOT_EQUALS, result, conditional()); + } + + return result; } private Expression conditional() { Expression result = additive(); while (true) { - if (match(TokenType.EQ)) { - result = new ConditionalExpression('=', result, additive()); + if (match(TokenType.LT)) { + result = new ConditionalExpression(ConditionalExpression.Operator.LT, result, additive()); continue; } - if (match(TokenType.LT)) { - result = new ConditionalExpression('<', result, additive()); + if (match(TokenType.LTEQ)) { + result = new ConditionalExpression(ConditionalExpression.Operator.LTEQ, result, additive()); continue; } if (match(TokenType.GT)) { - result = new ConditionalExpression('>', result, additive()); + result = new ConditionalExpression(ConditionalExpression.Operator.GT, result, additive()); + continue; + } + if (match(TokenType.GTEQ)) { + result = new ConditionalExpression(ConditionalExpression.Operator.GTEQ, result, additive()); continue; } break; diff --git a/src/com/annimon/ownlang/parser/TokenType.java b/src/com/annimon/ownlang/parser/TokenType.java index 03ad2a7..b5860ce 100644 --- a/src/com/annimon/ownlang/parser/TokenType.java +++ b/src/com/annimon/ownlang/parser/TokenType.java @@ -21,8 +21,18 @@ public enum TokenType { STAR, SLASH, EQ, + EQEQ, + EXCL, + EXCLEQ, LT, + LTEQ, GT, + GTEQ, + + BAR, + BARBAR, + AMP, + AMPAMP, LPAREN, // ( RPAREN, // ) diff --git a/src/com/annimon/ownlang/parser/ast/ConditionalExpression.java b/src/com/annimon/ownlang/parser/ast/ConditionalExpression.java index 1ded250..76f25ce 100644 --- a/src/com/annimon/ownlang/parser/ast/ConditionalExpression.java +++ b/src/com/annimon/ownlang/parser/ast/ConditionalExpression.java @@ -10,10 +10,38 @@ import com.annimon.ownlang.lib.Value; */ public final class ConditionalExpression implements Expression { - private final Expression expr1, expr2; - private final char operation; + public static enum Operator { + PLUS("+"), + MINUS("-"), + MULTIPLY("*"), + DIVIDE("/"), + + EQUALS("=="), + NOT_EQUALS("!="), + + LT("<"), + LTEQ("<="), + GT(">"), + GTEQ(">="), + + AND("&&"), + OR("||"); + + private final String name; - public ConditionalExpression(char operation, Expression expr1, Expression expr2) { + private Operator(String name) { + this.name = name; + } + + public String getName() { + return name; + } + } + + private final Expression expr1, expr2; + private final Operator operation; + + public ConditionalExpression(Operator operation, Expression expr1, Expression expr2) { this.operation = operation; this.expr1 = expr1; this.expr2 = expr2; @@ -23,31 +51,36 @@ public final class ConditionalExpression implements Expression { public Value eval() { final Value value1 = expr1.eval(); final Value value2 = expr2.eval(); + + double number1, number2; if (value1 instanceof StringValue) { - final String string1 = value1.asString(); - final String string2 = value2.asString(); - switch (operation) { - case '<': return new NumberValue(string1.compareTo(string2) < 0); - case '>': return new NumberValue(string1.compareTo(string2) > 0); - case '=': - default: - return new NumberValue(string1.equals(string2)); - } + number1 = value1.asString().compareTo(value2.asString()); + number2 = 0; + } else { + number1 = value1.asNumber(); + number2 = value2.asNumber(); } - final double number1 = value1.asNumber(); - final double number2 = value2.asNumber(); + boolean result; switch (operation) { - case '<': return new NumberValue(number1 < number2); - case '>': return new NumberValue(number1 > number2); - case '=': + case LT: result = number1 < number2; break; + case LTEQ: result = number1 <= number2; break; + case GT: result = number1 > number2; break; + case GTEQ: result = number1 >= number2; break; + case NOT_EQUALS: result = number1 != number2; break; + + case AND: result = (number1 != 0) && (number2 != 0); break; + case OR: result = (number1 != 0) || (number2 != 0); break; + + case EQUALS: default: - return new NumberValue(number1 == number2); + result = number1 == number2; break; } + return new NumberValue(result); } @Override public String toString() { - return String.format("[%s %c %s]", expr1, operation, expr2); + return String.format("[%s %s %s]", expr1, operation.getName(), expr2); } }