package scotch.compiler.syntax.scope;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.sameInstance;
import static org.junit.Assert.assertThat;
import static org.mockito.Matchers.any;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static scotch.compiler.syntax.definition.Import.moduleImport;
import static scotch.compiler.syntax.scope.Scope.scope;
import static scotch.compiler.text.SourceLocation.NULL_SOURCE;
import static scotch.symbol.Symbol.qualified;
import static scotch.symbol.Symbol.unqualified;
import java.util.Optional;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import scotch.symbol.Symbol;
import scotch.symbol.SymbolEntry;
import scotch.symbol.SymbolResolver;
import scotch.compiler.syntax.type.Types;
import scotch.compiler.syntax.util.SymbolGenerator;
@RunWith(MockitoJUnitRunner.class)
public class RootScopeTest {
@Mock
private SymbolResolver resolver;
@Mock
private SymbolGenerator symbolGenerator;
private Scope rootScope;
private Scope module1Scope;
private Scope module2Scope;
@Before
public void setUp() {
when(resolver.getEntry(any(Symbol.class))).thenReturn(Optional.empty());
rootScope = scope(symbolGenerator, resolver);
module1Scope = rootScope.enterScope("scotch.module1");
module2Scope = rootScope.enterScope("scotch.module2");
}
@Test
public void nothingShouldBeAnOperator() {
assertThat(rootScope.isOperator_(qualified("scotch.module1", "fn")), is(false));
assertThat(rootScope.isOperator_(unqualified("fn")), is(false));
}
@Test
public void shouldDelegateQualifyingSymbolToSiblingModule() {
module1Scope.enterScope(emptyList()).defineValue(qualified("scotch.module1", "fn"), Types.t(1));
module2Scope.enterScope(asList(moduleImport(NULL_SOURCE, "scotch.module1")));
assertThat(module2Scope.qualify(unqualified("fn")), is(Optional.of(qualified("scotch.module1", "fn"))));
}
@Test
public void shouldDelegateToResolver_whenQualifyingSymbolNotFoundInModules() {
Symbol symbol = qualified("scotch.module1", "fn");
when(resolver.isDefined(symbol)).thenReturn(true);
rootScope.qualify(symbol);
verify(resolver).isDefined(symbol);
}
@Test
public void shouldDelegateToResolver_whenGettingEntryForQualifiedSymbolNotFoundInModules() {
Symbol symbol = qualified("scotch.module1", "fn");
when(resolver.isDefined(symbol)).thenReturn(true);
when(resolver.getEntry(symbol)).thenReturn(Optional.of(mock(SymbolEntry.class)));
rootScope.getEntry(symbol);
verify(resolver).getEntry(symbol);
}
@Test
public void leavingChildScopeShouldGiveBackRootScope() {
assertThat(rootScope.enterScope("test.module").leaveScope(), sameInstance(rootScope));
}
@Test(expected = IllegalStateException.class)
public void shouldThrow_whenEnteringScopeWithoutModuleName() {
rootScope.enterScope();
}
@Test(expected = IllegalStateException.class)
public void shouldThrow_whenGettingOperator() {
rootScope.getOperator(qualified("scotch.module1", "+"));
}
@Test(expected = IllegalStateException.class)
public void shouldThrow_whenGettingValue() {
rootScope.getValue(qualified("scotch.module1", "fn"));
}
@Test(expected = IllegalStateException.class)
public void shouldThrow_whenLeavingScope() {
rootScope.leaveScope();
}
@Test
public void shouldBeEmpty_whenQualifyingUndeclaredSymbol() {
assertThat(rootScope.qualify(qualified("scotch.module1", "fn")), is(Optional.empty()));
}
@Test
public void shouldThrowBeEmpty_whenQualifyingUnqualifiedSymbol() {
assertThat(rootScope.qualify(unqualified("fn")), is(Optional.empty()));
}
}