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.junit.rules.ExpectedException.none;
import static org.mockito.Matchers.any;
import static org.mockito.Matchers.anyListOf;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static scotch.compiler.syntax.scope.Scope.scope;
import static scotch.symbol.Symbol.qualified;
import static scotch.symbol.Symbol.symbol;
import static scotch.symbol.Symbol.unqualified;
import static scotch.compiler.syntax.type.Types.t;
import static scotch.util.TestUtil.intType;
import java.util.Optional;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.runners.MockitoJUnitRunner;
import scotch.compiler.syntax.definition.Import;
import scotch.symbol.Operator;
import scotch.symbol.Symbol;
import scotch.symbol.SymbolEntry;
import scotch.symbol.SymbolResolver;
import scotch.compiler.syntax.type.DefaultTypeScope;
import scotch.compiler.syntax.type.Type;
import scotch.compiler.syntax.util.DefaultSymbolGenerator;
import scotch.compiler.syntax.util.SymbolGenerator;
@RunWith(MockitoJUnitRunner.class)
public class ModuleScopeTest {
@Rule public final ExpectedException exception = none();
@Mock private SymbolResolver resolver;
@Mock private Scope rootScope;
@Mock private Import import_;
private Scope moduleScope;
private String moduleName;
@Before
public void setUp() {
moduleName = "scotch.test";
SymbolGenerator symbolGenerator = new DefaultSymbolGenerator();
moduleScope = scope(rootScope, new DefaultTypeScope(symbolGenerator, resolver), resolver, symbolGenerator, moduleName, asList(import_));
when(rootScope.enterScope(anyListOf(Import.class))).thenReturn(moduleScope);
when(rootScope.qualify(any(Symbol.class))).thenReturn(Optional.empty());
when(import_.qualify(any(String.class), any(SymbolResolver.class))).thenReturn(Optional.empty());
}
@Test
public void leavingChildScopeShouldGiveBackModuleScope() {
assertThat(moduleScope.enterScope(emptyList()).leaveScope(), sameInstance(moduleScope));
}
@Test
public void shouldThrow_whenEnteringScopeWithModuleName() {
exception.expect(IllegalStateException.class);
moduleScope.enterScope("module.name");
}
@Test
public void shouldThrow_whenDefiningValue() {
exception.expect(IllegalStateException.class);
moduleScope.defineValue(symbol("value"), mock(Type.class));
}
@Test
public void shouldQualifyValueDefinedInImportScopeByUnqualifiedSymbol() {
String memberName = "fn";
when(rootScope.getEntry(qualified(moduleName, memberName))).thenReturn(Optional.empty());
moduleScope.enterScope(emptyList()).defineValue(qualified(moduleName, memberName), t(2));
assertThat(moduleScope.qualify(unqualified(memberName)), is(Optional.of(qualified(moduleName, memberName))));
}
@Test
public void shouldGetNothingWhenQualifyingSymbolNotDefinedLocallyAndNotImported() {
assertThat(moduleScope.qualify(unqualified("fn")), is(Optional.empty()));
}
@Test
public void shouldDelegateQualificationToImportWhenUnqualifiedSymbolNotDefinedLocally() {
String externalModule = "scotch.external";
String memberName = "fn";
when(resolver.getEntry(any(Symbol.class))).thenReturn(Optional.empty());
when(import_.qualify(memberName, resolver)).thenReturn(Optional.of(qualified(externalModule, memberName)));
moduleScope.enterScope(asList(import_));
assertThat(moduleScope.qualify(unqualified(memberName)), is(Optional.of(qualified(externalModule, memberName))));
}
@Test
public void shouldDelegateToParentWhenNotDefinedInModule() {
String memberName = "fn";
moduleScope.enterScope(asList(import_));
when(rootScope.getEntry(qualified(moduleName, memberName))).thenReturn(Optional.of(mock(SymbolEntry.class)));
moduleScope.getEntry(qualified(moduleName, memberName));
verify(rootScope).getEntry(qualified(moduleName, memberName));
}
@Test
public void shouldGetNothingWhenQualifyingQualifiedSymbolThatHasUnimportedModuleName() {
assertThat(moduleScope.qualify(qualified("scotch.external", "fn")), is(Optional.empty()));
}
@Test
public void shouldQualifyQualifiedSymbolThatHasImportedModuleName() {
String moduleName = "scotch.external";
String memberName = "fn";
when(import_.isFrom(moduleName)).thenReturn(true);
when(import_.qualify(memberName, resolver)).thenReturn(Optional.of(qualified(moduleName, memberName)));
moduleScope.enterScope(asList(import_));
assertThat(moduleScope.qualify(qualified(moduleName, memberName)), is(Optional.of(qualified(moduleName, memberName))));
}
@Test
public void shouldThrow_whenDefiningOperator() {
exception.expect(IllegalStateException.class);
moduleScope.defineOperator(unqualified("fn"), mock(Operator.class));
}
@Test
public void shouldThrow_whenGettingContext() {
exception.expect(IllegalStateException.class);
moduleScope.getContext(intType());
}
@Test
public void shouldDelegateQualifiedSymbolsToRoot() {
Symbol symbol = qualified("scotch.external", "fn");
moduleScope.qualify(symbol);
verify(rootScope).qualify(symbol);
}
}