aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java
blob: d5a7617c58151c3e54f85661e027202a50e5414b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
package org.zwobble.couscous.tests.interpreter;

import java.util.List;
import java.util.stream.Collectors;

import org.junit.Test;
import org.zwobble.couscous.MapBackedProject;
import org.zwobble.couscous.ast.AssignmentNode;
import org.zwobble.couscous.ast.ClassNode;
import org.zwobble.couscous.ast.ExpressionStatementNode;
import org.zwobble.couscous.ast.LiteralNode;
import org.zwobble.couscous.ast.ReturnNode;
import org.zwobble.couscous.interpreter.Interpreter;
import org.zwobble.couscous.interpreter.UnboundVariable;
import org.zwobble.couscous.interpreter.UnexpectedValueType;
import org.zwobble.couscous.interpreter.VariableNotInScope;
import org.zwobble.couscous.interpreter.WrongNumberOfArguments;
import org.zwobble.couscous.interpreter.values.ConcreteType;
import org.zwobble.couscous.interpreter.values.InterpreterValues;
import org.zwobble.couscous.tests.BackendTests;
import org.zwobble.couscous.tests.MethodRunner;
import org.zwobble.couscous.values.IntegerValue;
import org.zwobble.couscous.values.PrimitiveValue;
import org.zwobble.couscous.values.StringValue;

import com.google.common.collect.ImmutableMap;

import static org.junit.Assert.assertEquals;
import static org.zwobble.couscous.ast.FormalArgumentNode.formalArg;
import static org.zwobble.couscous.ast.LiteralNode.literal;
import static org.zwobble.couscous.ast.LocalVariableDeclarationNode.localVariableDeclaration;
import static org.zwobble.couscous.ast.MethodNode.staticMethod;
import static org.zwobble.couscous.ast.VariableDeclaration.var;
import static org.zwobble.couscous.ast.VariableReferenceNode.reference;
import static org.zwobble.couscous.tests.util.ExtraAsserts.assertThrows;
import static org.zwobble.couscous.values.PrimitiveValues.value;

import lombok.val;

public class InterpreterTests extends BackendTests {
    @Test
    public void errorIfWrongNumberOfArgumentsArePassed() {
        val method = staticMethod("hello");
        
        val exception = assertThrows(WrongNumberOfArguments.class,
            () -> runMethod(method, value("hello, world!")));
        
        assertEquals(new WrongNumberOfArguments(0, 1), exception);
    }
    
    @Test
    public void errorIfTryingToAssignValueOfWrongTypeToVariable() {
        val arg = formalArg(var(42, "x", StringValue.REF));
        val method = staticMethod("hello")
            .argument(arg)
            .statement(new ExpressionStatementNode(new AssignmentNode(reference(arg), literal(0))));

        val exception = assertThrows(UnexpectedValueType.class,
            () -> runMethod(method, value("")));
        
        assertEquals(new UnexpectedValueType(StringValue.REF, IntegerValue.REF), exception);
    }
    
    @Test
    public void canDeclareVariableAndThenAssignValues() {
        val localVariableDeclaration = localVariableDeclaration(
            42, "x", StringValue.REF, LiteralNode.literal("[initial value]"));
        val method = staticMethod("hello")
            .statement(localVariableDeclaration)
            .statement(new ExpressionStatementNode(new AssignmentNode(reference(localVariableDeclaration), LiteralNode.literal("[updated value]"))))
            .statement(new ReturnNode(reference(localVariableDeclaration)));
        val result = runMethod(method);
        
        assertEquals(value("[updated value]"), result);
    }
    
    @Test
    public void errorIfTryingToAssignToVariableNotInScope() {
        val localVariableDeclaration = localVariableDeclaration(
            42, "x", StringValue.REF, literal(""));
        val method = staticMethod("hello")
            .statement(new ExpressionStatementNode(new AssignmentNode(reference(localVariableDeclaration), LiteralNode.literal("[updated value]"))));

        val exception = assertThrows(VariableNotInScope.class,
            () -> runMethod(method));
        
        assertEquals(new VariableNotInScope(42), exception);
    }
    
    @Test
    public void errorIfTryingToGetValueOfVariableNotInScope() {
        val localVariableDeclaration = localVariableDeclaration(
            42, "x", StringValue.REF, literal(""));
        val method = staticMethod("hello")
            .statement(new ReturnNode(reference(localVariableDeclaration)));

        val exception = assertThrows(VariableNotInScope.class,
            () -> runMethod(method));
        
        assertEquals(new VariableNotInScope(42), exception);
    }
    
    @Test
    public void errorIfTryingToGetValueOfUnboundVariable() {
        val localVariableDeclaration = localVariableDeclaration(
            42, "x", StringValue.REF, literal(""));
        val method = staticMethod("hello")
            .statement(new ReturnNode(reference(localVariableDeclaration)))
            .statement(localVariableDeclaration);

        val exception = assertThrows(UnboundVariable.class,
            () -> runMethod(method));
        
        assertEquals(new UnboundVariable(42), exception);
    }

    @Override
    protected MethodRunner buildMethodRunner() {
        return new MethodRunner() {
            @Override
            public PrimitiveValue runMethod(
                    ClassNode classNode,
                    String methodName,
                    List<PrimitiveValue> arguments) {
                val interpreter = new Interpreter(new MapBackedProject(ImmutableMap.of(
                    classNode.getName(), ConcreteType.fromNode(classNode))));
                val argumentValues = arguments.stream()
                    .map(InterpreterValues::value)
                    .collect(Collectors.toList());
                
                return interpreter.run(classNode.getName(), methodName, argumentValues)
                    .toPrimitiveValue().get();
            }
        };
    }
}