aboutsummaryrefslogtreecommitdiff
path: root/src/test/java/org/zwobble/couscous/tests/interpreter/InterpreterTests.java
blob: 66a1f8b75aa871a8289d3a61a28d563f7e2ad9c9 (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
package org.zwobble.couscous.tests.interpreter;

import org.junit.Test;
import org.zwobble.couscous.MapBackedProject;
import org.zwobble.couscous.ast.Assignment;
import org.zwobble.couscous.ast.ClassNode;
import org.zwobble.couscous.ast.ExpressionStatementNode;
import org.zwobble.couscous.ast.FormalArgumentNode;
import org.zwobble.couscous.ast.LiteralNode;
import org.zwobble.couscous.ast.MethodNode;
import org.zwobble.couscous.ast.ReturnNode;
import org.zwobble.couscous.interpreter.Interpreter;
import org.zwobble.couscous.interpreter.VariableNotInScope;
import org.zwobble.couscous.interpreter.WrongNumberOfArguments;
import org.zwobble.couscous.values.ConcreteType;
import org.zwobble.couscous.values.InterpreterValue;
import org.zwobble.couscous.values.StringValue;

import com.google.common.collect.ImmutableMap;

import static java.util.Arrays.asList;
import static org.junit.Assert.assertEquals;
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.VariableReferenceNode.reference;
import static org.zwobble.couscous.tests.util.ExtraAsserts.assertThrows;
import static org.zwobble.couscous.values.UnitValue.UNIT;

import lombok.val;

public class InterpreterTests {
    @Test
    public void methodWithNoStatementsReturnsUnit() {
        val method = staticMethod("hello");
        val result = runMethod(method);
        
        assertEquals(UNIT, result);
    }
    
    @Test
    public void canReturnLiteralValue() {
        val method = staticMethod("hello")
            .statement(new ReturnNode(LiteralNode.literal("hello, world!")));
        val result = runMethod(method);
        
        assertEquals(new StringValue("hello, world!"), result);
    }
    
    @Test
    public void canPassValueToMethod() {
        val arg = new FormalArgumentNode(42, StringValue.REF, "x");
        val method = staticMethod("hello")
            .argument(arg)
            .statement(new ReturnNode(reference(arg)));
        val result = runMethod(method, new StringValue("hello, world!"));
        
        assertEquals(new StringValue("hello, world!"), result);
    }
    
    @Test
    public void errorIfWrongNumberOfArgumentsArePassed() {
        val method = staticMethod("hello");
        
        val exception = assertThrows(WrongNumberOfArguments.class,
            () -> runMethod(method, new StringValue("hello, world!")));
        
        assertEquals(new WrongNumberOfArguments(0, 1), exception);
    }
    
    @Test
    public void canReassignValueToArgument() {
        val arg = new FormalArgumentNode(42, StringValue.REF, "x");
        val method = staticMethod("hello")
            .argument(arg)
            .statement(new ExpressionStatementNode(new Assignment(reference(arg), LiteralNode.literal("[updated value]"))))
            .statement(new ReturnNode(reference(arg)));
        val result = runMethod(method, new StringValue("[initial value]"));
        
        assertEquals(new StringValue("[updated value]"), result);
    }
    
    @Test
    public void canDeclareVariable() {
        val localVariableDeclaration = localVariableDeclaration(
            42, StringValue.REF, "x", LiteralNode.literal("[initial value]"));
        val method = staticMethod("hello")
            .statement(localVariableDeclaration)
            .statement(new ReturnNode(reference(localVariableDeclaration)));
        val result = runMethod(method);
        
        assertEquals(new StringValue("[initial value]"), result);
    }
    
    @Test
    public void canDeclareVariableAndThenAssignValues() {
        val localVariableDeclaration = localVariableDeclaration(
            42, StringValue.REF, "x", LiteralNode.literal("[initial value]"));
        val method = staticMethod("hello")
            .statement(localVariableDeclaration)
            .statement(new ExpressionStatementNode(new Assignment(reference(localVariableDeclaration), LiteralNode.literal("[updated value]"))))
            .statement(new ReturnNode(reference(localVariableDeclaration)));
        val result = runMethod(method);
        
        assertEquals(new StringValue("[updated value]"), result);
    }
    
    @Test
    public void errorIfTryingToAssignToVariableNotInScope() {
        val localVariableDeclaration = localVariableDeclaration(
            42, StringValue.REF, "x", literal(""));
        val method = staticMethod("hello")
            .statement(new ExpressionStatementNode(new Assignment(reference(localVariableDeclaration), LiteralNode.literal("[updated value]"))));

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

    private InterpreterValue runMethod(MethodNode.MethodNodeBuilder methodBuilder, InterpreterValue... arguments) {
        val method = methodBuilder.build();
        val className = "com.example.Program";
        val classNode = ClassNode.builder(className)
            .method(method)
            .build();
        val interpreter = new Interpreter(new MapBackedProject(ImmutableMap.of(
            className, ConcreteType.fromNode(classNode))));
        
        return interpreter.run(className, method.getName(), asList(arguments));
    }
}