Skip to content

Commit

Permalink
Merge pull request #5 from FxMorin/molang/basic-string-support
Browse files Browse the repository at this point in the history
Molang/basic string support
  • Loading branch information
bernie-g authored Jan 4, 2024
2 parents 6e9c9dd + 7fa7a95 commit 80a1ea4
Show file tree
Hide file tree
Showing 16 changed files with 319 additions and 50 deletions.
4 changes: 4 additions & 0 deletions src/main/java/com/eliotlash/molang/Parser.java
Original file line number Diff line number Diff line change
Expand Up @@ -387,6 +387,10 @@ private Expr primary() {
return new Expr.Constant(Double.parseDouble(previous().lexeme()));
}

if (match(STRING)) {
return new Expr.Str(previous().lexeme());
}

if (match(OPEN_PAREN)) {
Expr expr = expression();
consume(CLOSE_PAREN, "Expect ')' after expression.");
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/com/eliotlash/molang/ast/ASTTransformation.java
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,11 @@ public Expr visitVariable(Expr.Variable expr) {
return expr;
}

@Override
public String visitString(Expr.Str str) {
return str.val();
}

@Override
public Expr visitSwitchContext(Expr.SwitchContext expr) {
return new Expr.SwitchContext(expr.left(), visit(expr.right()));
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/com/eliotlash/molang/ast/Evaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,11 @@ else if (access.target() instanceof Expr.Struct struct) {

@Override
public Double visitBinOp(Expr.BinOp expr) {

// Evaluate binops on strings if the expressions are strings
if (expr.left() instanceof Expr.Str lhs && expr.right() instanceof Expr.Str rhs) {
return expr.operator().applyString(lhs.val(), rhs.val());
}
return expr.operator().apply(() -> evaluate(expr.left()), () -> evaluate(expr.right()));
}

Expand Down Expand Up @@ -213,11 +218,23 @@ public Double visitVariable(Expr.Variable expr) {
return context.getVariableMap().getOrDefault(runtimeVariable, 0);
}

@Override
public String visitString(Expr.Str str) {
return str.val();
}

public Double evaluate(Expr expr) {
Double result = expr.accept(this);
return result == null ? 0 : result;
}

public String evaluateString(Expr expr) {
if (expr instanceof Expr.Str) {
return ((Expr.Str) expr).val();
}
return expr.accept(this).toString();
}

public Double evaluateNullable(Expr expr) {
return expr.accept(this);
}
Expand Down
15 changes: 12 additions & 3 deletions src/main/java/com/eliotlash/molang/ast/Expr.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,9 +40,9 @@ public boolean equals(Object o) {
Struct struct = (Struct) o;

if (!target.equals(struct.target)) return false;
if(parent == null) {
return struct.parent == null;
}
if(parent == null) {
return struct.parent == null;
}
return parent.equals(struct.parent);
}

Expand Down Expand Up @@ -213,6 +213,13 @@ public int hashCode() {
}
}

record Str(String val) implements Expr {
@Override
public <R> R accept(Visitor<R> visitor) {
return null;
}
}

interface Visitor<R> {
default R visit(Expr node) {
return node.accept(this);
Expand Down Expand Up @@ -245,5 +252,7 @@ default R visit(Expr node) {
R visitSwitchContext(SwitchContext expr);

R visitVariable(Variable expr);

String visitString(Str str);
}
}
15 changes: 15 additions & 0 deletions src/main/java/com/eliotlash/molang/ast/Operator.java
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ public double apply(DoubleSupplier lhs, DoubleSupplier rhs) {
};
}


public double applyString(String lhs, String rhs) {
switch (this) {
case EQ -> {
return bool(lhs.equals(rhs));
}
case NEQ -> {
return bool(!lhs.equals(rhs));
}
default -> {
return 0.0;
}
}
}

private static double bool(boolean b) {
return b ? 1.0 : 0.0;
}
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/com/eliotlash/molang/functions/strings/Length.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.eliotlash.molang.functions.strings;

import com.eliotlash.molang.ast.Expr;
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.variables.ExecutionContext;

public class Length extends Function {
public Length(String name) {
super(name);
}

@Override
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
return ctx.getEvaluator().evaluateString(arguments[0]).length();
}

@Override
public int getRequiredArguments() {
return 1;
}
}
26 changes: 26 additions & 0 deletions src/main/java/com/eliotlash/molang/functions/strings/Print.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.eliotlash.molang.functions.strings;

import com.eliotlash.molang.ast.Expr;
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.variables.ExecutionContext;

public class Print extends Function {
public Print(String name) {
super(name);
}

@Override
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
if (arguments[0] instanceof Expr.Str) {
System.out.println(ctx.getEvaluator().evaluateString(arguments[0]));
} else {
System.out.println(this.evaluateArgument(arguments, ctx, 0));
}
return 1.0;
}

@Override
public int getRequiredArguments() {
return 1;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.eliotlash.molang.functions.strings;

import com.eliotlash.molang.ast.Expr;
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.utils.MolangUtils;
import com.eliotlash.molang.variables.ExecutionContext;

public class StrEquals extends Function {
public StrEquals(String name) {
super(name);
}

@Override
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
final String first = ctx.getEvaluator().evaluateString(arguments[0]);
final String second = ctx.getEvaluator().evaluateString(arguments[1]);
return MolangUtils.booleanToFloat(first.equals(second));
}

@Override
public int getRequiredArguments() {
return 2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.eliotlash.molang.functions.strings;

import com.eliotlash.molang.ast.Expr;
import com.eliotlash.molang.functions.Function;
import com.eliotlash.molang.utils.MolangUtils;
import com.eliotlash.molang.variables.ExecutionContext;

public class StrEqualsIgnoreCase extends Function {
public StrEqualsIgnoreCase(String name) {
super(name);
}

@Override
public double _evaluate(Expr[] arguments, ExecutionContext ctx) {
final String first = ctx.getEvaluator().evaluateString(arguments[0]);
final String second = ctx.getEvaluator().evaluateString(arguments[1]);
return MolangUtils.booleanToFloat(first.equalsIgnoreCase(second));
}

@Override
public int getRequiredArguments() {
return 2;
}
}
121 changes: 82 additions & 39 deletions src/main/java/com/eliotlash/molang/lexer/Lexer.java
Original file line number Diff line number Diff line change
Expand Up @@ -68,9 +68,19 @@ private void scanToken() {
return;
}

var token = tryOperator(c);


var token = tryOperator(c);
if (token != null) {
if (token == TokenType.QUOTE) {
try {
eatString();
} catch (Exception e) {
throw new RuntimeException(e);
}
return;
}

setToken(token);
} else if (isDigit(c)) {
eatNumeral();
Expand All @@ -79,6 +89,38 @@ private void scanToken() {
}
}


private void eatString() throws Exception {
// skip over the current character ('), we don't want that to part of the string.
// The lexer seems to always include the startPos.
// Other option would be to make it like a state machine where if it comes across a quote
// it'll consume everything within it until it reaches another quote.
// But that would be behavior specific to Strings, and doesn't fit with
// current behavior for numerals.
startPos++;

// while we have a next character, and the next character does not equal "'"
// we are safe to advance.
while (hasNextChar() && peek() != '\'') {
advance();
}

// Early fail for unclosed strings
if (!hasNextChar() || peek() != '\'') {
System.out.println(peek());
// We want to skip over the closing quote.
// We don't however want to include it in the substring, so we skip over, after we set token.
// However, if we find that there isn't another quote, we mark it as an unclosed string
throw new Exception("string not closed");
}

setToken(TokenType.STRING);


// Skip over the next quote so the lexer doesn't pick it up as a new string.
advance();
}

private void eatWhitespace() {
while (Character.isWhitespace(peek())) {
advance();
Expand Down Expand Up @@ -111,54 +153,54 @@ private void eatIdentifier() {

private TokenType tryOperator(char c) {
switch (c) {
case '!' -> {
if (match('=')) {
return TokenType.BANG_EQUAL;
case '!' -> {
if (match('=')) {
return TokenType.BANG_EQUAL;
}
return TokenType.NOT;
}
return TokenType.NOT;
}
case '=' -> {
if (match('=')) {
return TokenType.EQUAL_EQUAL;
case '=' -> {
if (match('=')) {
return TokenType.EQUAL_EQUAL;
}
return TokenType.EQUALS;
}
return TokenType.EQUALS;
}
case '<' -> {
if (match('=')) {
return TokenType.LESS_EQUAL;
case '<' -> {
if (match('=')) {
return TokenType.LESS_EQUAL;
}
return TokenType.LESS_THAN;
}
return TokenType.LESS_THAN;
}
case '>' -> {
if (match('=')) {
return TokenType.GREATER_EQUAL;
case '>' -> {
if (match('=')) {
return TokenType.GREATER_EQUAL;
}
return TokenType.GREATER_THAN;
}
return TokenType.GREATER_THAN;
}
case '&' -> {
if (match('&')) {
case '&' -> {
if (match('&')) {
return TokenType.AND;
}
return TokenType.AND;
}
return TokenType.AND;
}
case '|' -> {
if (match('|')) {
case '|' -> {
if (match('|')) {
return TokenType.OR;
}
return TokenType.OR;
}
return TokenType.OR;
}
case '-' -> {
if (match('>')) {
return TokenType.ARROW;
case '-' -> {
if (match('>')) {
return TokenType.ARROW;
}
return TokenType.MINUS;
}
return TokenType.MINUS;
}
case '?' -> {
if (match('?')) {
return TokenType.COALESCE;
case '?' -> {
if (match('?')) {
return TokenType.COALESCE;
}
return TokenType.QUESTION;
}
return TokenType.QUESTION;
}
}

return switch (c) {
Expand All @@ -177,6 +219,7 @@ private TokenType tryOperator(char c) {
case ';' -> TokenType.SEMICOLON;
case ':' -> TokenType.COLON;
case '.' -> TokenType.DOT;
case '\'' -> TokenType.QUOTE;
default -> null;
};
}
Expand Down
7 changes: 7 additions & 0 deletions src/main/java/com/eliotlash/molang/lexer/TokenType.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@ public enum TokenType {
*/
IDENTIFIER,

/**
* A token for a string
*/
STRING,

// Single character tokens:

// '!'
Expand Down Expand Up @@ -77,6 +82,8 @@ public enum TokenType {
// '??'
COALESCE,

QUOTE,

EOF,

/**
Expand Down
Loading

0 comments on commit 80a1ea4

Please sign in to comment.