Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion java/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -12,4 +12,4 @@ FROM eclipse-temurin:${JAVA_VERSION}-alpine
WORKDIR /jmonkey
COPY --from=build /jmonkey/monkey.jar .
ENTRYPOINT ["java", "-jar", "monkey.jar"]
CMD ["let fac = fn (n) { let it = fn (n, acc) { if (n < 1) { acc } else { it(n-1, n*acc) }}; it(n, 1) }; fac(pow(10))"]
CMD ["let fac = fn (n) { let it = fn (n, acc) { if (n < 1) { acc } else { it(n-1, n*acc) }}; it(n, 1) }; puts(fac(10))"]
33 changes: 11 additions & 22 deletions java/monkey/src/main/java/root/evaluation/BuiltinFunctions.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,21 +43,19 @@ public enum BuiltinFunctions {
}),
UPPERCASE("uppercase", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.STRING, "uppercase");
var string = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyString.class, "uppercase");

return new MonkeyString(((MonkeyString) arguments.get(0)).getValue().toUpperCase());
return new MonkeyString(string.getValue().toUpperCase());
}),
LOWERCASE("lowercase", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.STRING, "lowercase");
var string = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyString.class, "lowercase");

return new MonkeyString(((MonkeyString) arguments.get(0)).getValue().toLowerCase());
return new MonkeyString(string.getValue().toLowerCase());
}),
FIRST("first", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.ARRAY, "first");

var array = (MonkeyArray) arguments.get(0);
var array = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyArray.class, "first");

if (array.getValue().isEmpty()) {
return MonkeyNull.INSTANCE;
Expand All @@ -66,9 +64,7 @@ public enum BuiltinFunctions {
}),
LAST("last", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.ARRAY, "last");

var array = (MonkeyArray) arguments.get(0);
var array = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyArray.class, "last");

if (array.getValue().isEmpty()) {
return MonkeyNull.INSTANCE;
Expand All @@ -77,9 +73,7 @@ public enum BuiltinFunctions {
}),
REST("rest", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.ARRAY, "rest");

var array = (MonkeyArray) arguments.get(0);
var array = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyArray.class, "rest");

if (array.getValue().isEmpty()) {
return MonkeyNull.INSTANCE;
Expand All @@ -95,9 +89,7 @@ public enum BuiltinFunctions {
}),
PUSH("push", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 2, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.ARRAY, "rest");

var array = (MonkeyArray) arguments.get(0);
var array = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyArray.class, "push");

var elementsCopy = new ArrayList<>(array.getValue());
elementsCopy.add(arguments.get(1));
Expand All @@ -106,13 +98,11 @@ public enum BuiltinFunctions {
}),
CHARS("chars", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.STRING, "chars");
var string = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyString.class, "chars");

var string = (MonkeyString) arguments.get(0);
char[] chars = string.getValue().toCharArray();
var monkeyChars = new ArrayList<MonkeyObject<?>>();

for (char c : chars) {
for (char c : string.getValue().toCharArray()) {
monkeyChars.add(new MonkeyString(String.valueOf(c)));
}

Expand Down Expand Up @@ -183,9 +173,8 @@ public enum BuiltinFunctions {
}),
AS_LIST("asList", (callToken, arguments) -> {
AbstractMonkeyFunction.checkArgumentCount(callToken, 1, arguments.size());
AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), ObjectType.HASH, "asList");
var hash = AbstractMonkeyFunction.checkArgumentType(callToken, arguments.get(0), MonkeyHash.class, "asList");

var hash = (MonkeyHash) arguments.get(0);
List<MonkeyObject<?>> asList = hash
.getValue()
.entrySet()
Expand Down
17 changes: 7 additions & 10 deletions java/monkey/src/main/java/root/evaluation/Evaluator.java
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
import root.evaluation.objects.MonkeyObject;
import root.evaluation.objects.impl.*;

import java.util.*;
import java.util.ArrayList;
import java.util.LinkedHashMap;
import java.util.List;

public class Evaluator {

Expand Down Expand Up @@ -308,15 +310,10 @@ private MonkeyObject<?> evalIfExpression(IfExpression ifExpression) throws Evalu
}

private MonkeyObject<?> evalIdentifierExpression(IdentifierExpression identifier) throws EvaluationException {
Optional<MonkeyObject<?>> value = environment.get(identifier.getValue());

if (value.isEmpty()) {
return BuiltinFunctions.getFunction(identifier.getValue()).orElseThrow(() ->
new EvaluationException(identifier.getToken(), "Variable %s is not declared", identifier.getValue())
);
}

return value.get();
return environment
.get(identifier.getValue())
.or(() -> BuiltinFunctions.getFunction(identifier.getValue()))
.orElseThrow(() -> new EvaluationException(identifier.getToken(), "Variable %s is not declared", identifier.getValue()));
}

private MonkeyObject<?> evalLetStatement(LetStatement letStatement) throws EvaluationException {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,14 +15,19 @@ public static void checkArgumentCount(LocalizedToken callToken, int expected, in
}
}

public static void checkArgumentType(
@SuppressWarnings("unchecked") // It is checked
public static <T extends MonkeyObject<?>> T checkArgumentType(
LocalizedToken callToken,
MonkeyObject<?> argument,
ObjectType expected,
Class<T> expected,
String functionName
) throws EvaluationException {
if (argument.getType() != expected) {
throw new EvaluationException(callToken, "Argument to `%s` must be %s, got %s", functionName, expected, argument.getType());
if (expected.isAssignableFrom(argument.getClass())) {
return (T) argument;
}

ObjectType expectedType = ObjectType.getTypeFromClass(expected);

throw new EvaluationException(callToken, "Argument to `%s` must be %s, got %s", functionName, expectedType, argument.getType());
}
}
35 changes: 26 additions & 9 deletions java/monkey/src/main/java/root/evaluation/objects/ObjectType.java
Original file line number Diff line number Diff line change
@@ -1,14 +1,31 @@
package root.evaluation.objects;

import root.evaluation.objects.impl.*;

import java.util.Arrays;

public enum ObjectType {

NULL,
BOOLEAN,
INTEGER,
RETURN_VALUE_OBJ,
FUNCTION,
BUILTIN_OBJ,
STRING,
ARRAY,
HASH,
NULL(MonkeyNull.class),
BOOLEAN(MonkeyBoolean.class),
INTEGER(MonkeyInteger.class),
RETURN_VALUE_OBJ(MonkeyReturn.class),
FUNCTION(MonkeyFunction.class),
BUILTIN_OBJ(BuiltinFunction.class),
STRING(MonkeyString.class),
ARRAY(MonkeyArray.class),
HASH(MonkeyHash.class);

private final Class<?> monkeyClass;

ObjectType(Class<?> monkeyClass) {
this.monkeyClass = monkeyClass;
}

public static ObjectType getTypeFromClass(Class<?> tClass) {
return Arrays.stream(values())
.filter(it -> it.monkeyClass == tClass)
.findFirst()
.orElseThrow(() -> new IllegalStateException("INTERPRETER BUG: No ObjectType associated with class " + tClass));
}
}
7 changes: 7 additions & 0 deletions java/monkey/src/test/java/EvaluatorTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -312,6 +312,13 @@ void testErrorHandling() {
Error evaluating the program: Index to an hash must be an Expression that yields an Int, String or Boolean
01: { fn(x) { x }: 'func' }
------^--------------------"""
),
List.of(
"push (10, 10)",
"""
Error evaluating the program: Argument to `push` must be ARRAY, got INTEGER
01: push (10, 10)
---------^-------"""
)
);

Expand Down