Refactor API

Refactor the API so that creating a selector results in a function that
accepts a scope against which to match the selector.
diff --git a/demo/search.js b/demo/search.js
index c51c1b8..150058f 100644
--- a/demo/search.js
+++ b/demo/search.js
@@ -13,29 +13,34 @@
  * the License.
  */
 
-import { createRangeSelector } from '@annotator/range';
-import { createAnySelectorCreator } from '@annotator/selector';
+import { makeRefinable } from '@annotator/selector';
+import { createRangeSelectorCreator } from '@annotator/range';
 import { createTextQuoteSelector } from '@annotator/text';
 
-const allSelectorTypes = {
-  TextQuoteSelector: createTextQuoteSelector,
-  RangeSelector: createRangeSelector,
-};
+const createSelector = makeRefinable(selector => {
+  const selectorCreator = {
+    TextQuoteSelector: createTextQuoteSelector,
+    RangeSelector: createRangeSelectorCreator(createSelector),
+  }[selector.type];
 
-const createSelector = createAnySelectorCreator(allSelectorTypes);
+  if (selectorCreator == null) {
+    throw new Error(`Unsupported selector type: ${selector.type}`);
+  }
+
+  return selectorCreator(selector);
+});
 
 /**
  * Locate a selector.
  * @param {Node} root node
- * @param {Selector} descriptor
+ * @param {Selector} selector
  * @return {Range}
  */
-export async function* search(root, descriptor) {
+export async function* search(root, selector) {
   for (const node of nodeIterator(root)) {
     if (!node.nodeValue) continue;
 
-    const selector = createSelector(node.nodeValue);
-    const matches = selector([descriptor]);
+    const matches = createSelector(selector)(node.nodeValue);
 
     for await (let match of matches) {
       const startIndex = match.index;
diff --git a/packages/dom/src/index.js b/packages/dom/src/index.js
index 80b1efd..a1d9cbd 100644
--- a/packages/dom/src/index.js
+++ b/packages/dom/src/index.js
@@ -15,11 +15,8 @@
 
 export * from './text';
 
-export function createCssSelector(context) {
-  async function* exec(descriptors) {
-    const cssSelector = descriptors.map(({ value }) => value).join(',');
-    yield* context.querySelectorAll(cssSelector);
-  }
-
-  return exec;
+export function createCssSelector(selector) {
+  return async function* matchAll(scope) {
+    yield* scope.querySelectorAll(selector.value);
+  };
 }
diff --git a/packages/dom/src/text/quote.js b/packages/dom/src/text/quote.js
index 13b6add..87c7926 100644
--- a/packages/dom/src/text/quote.js
+++ b/packages/dom/src/text/quote.js
@@ -30,7 +30,7 @@
   const contextText = context.cloneContents().textContent;
   const exact = range.cloneContents().textContent;
 
-  const descriptor = {
+  const selector = {
     type: 'TextQuoteSelector',
     exact,
   };
@@ -46,11 +46,11 @@
   ) {
     throw new Error(`Context not equal to range's container; not implemented.`);
   }
+
   const rangeIndex = range.startOffset;
   const rangeEndIndex = range.endOffset;
 
-  const selector = createTextQuoteSelector(contextText);
-  const matches = selector([descriptor]);
+  const matches = createTextQuoteSelector(selector)(contextText);
   const minSuffixes = [];
   const minPrefixes = [];
   for await (let match of matches) {
@@ -72,18 +72,15 @@
   }
   const [minSuffix, minPrefix] = minimalSolution(minSuffixes, minPrefixes);
   if (minSuffix > 0) {
-    descriptor.suffix = contextText.substring(
+    selector.suffix = contextText.substring(
       rangeEndIndex,
       rangeEndIndex + minSuffix,
     );
   }
   if (minPrefix > 0) {
-    descriptor.prefix = contextText.substring(
-      rangeIndex - minPrefix,
-      rangeIndex,
-    );
+    selector.prefix = contextText.substring(rangeIndex - minPrefix, rangeIndex);
   }
-  return descriptor;
+  return selector;
 }
 
 function overlap(text1, text2) {
diff --git a/packages/range/src/index.js b/packages/range/src/index.js
index a9b8b82..4432423 100644
--- a/packages/range/src/index.js
+++ b/packages/range/src/index.js
@@ -15,32 +15,30 @@
 
 import { product } from './cartesian.js';
 
-export function createRangeSelector(context, createAnySelector) {
-  const startSelector = createAnySelector(context);
-  const endSelector = createAnySelector(context);
+export function createRangeSelectorCreator(createSelector) {
+  return function createRangeSelector(selector) {
+    const startSelector = createSelector(selector.startSelector);
+    const endSelector = createSelector(selector.endSelector);
 
-  async function* rangeSelector(descriptors) {
-    const descriptor = descriptors[0]; // TODO handle multiple descriptors
-    const startMatches = startSelector([descriptor.startSelector]);
-    const endMatches = endSelector([descriptor.endSelector]);
-    const pairs = product(startMatches, endMatches);
-    for await (let [start, end] of pairs) {
-      if (start.index > end.index) {
-        continue;
+    return async function* matchAll(scope) {
+      const startMatches = startSelector(scope);
+      const endMatches = endSelector(scope);
+      const pairs = product(startMatches, endMatches);
+      for await (let [start, end] of pairs) {
+        if (start.index > end.index) {
+          continue;
+        }
+        const text = rangeBetween({ start, end, scope });
+        const result = [text];
+        result.index = start.index;
+        result.input = scope;
+        yield result;
       }
-      const text = rangeBetween({ start, end, context });
-      const result = [text];
-      result.index = start.index;
-      result.input = context;
-      result.descriptor = descriptor;
-      yield result;
-    }
-  }
-
-  return rangeSelector;
+    };
+  };
 }
 
-function rangeBetween({ start, end, context }) {
-  const range = context.substring(start.index, end.index);
+function rangeBetween({ start, end, scope }) {
+  const range = scope.substring(start.index, end.index);
   return range;
 }
diff --git a/packages/selector/src/index.js b/packages/selector/src/index.js
index 17b79f1..86c6ef9 100644
--- a/packages/selector/src/index.js
+++ b/packages/selector/src/index.js
@@ -13,52 +13,30 @@
  * the License.
  */
 
-export function createAnySelectorCreator(selectorCreatorsByType) {
-  function selectSelector(context, type) {
-    const selectorCreatorForType = selectorCreatorsByType[type];
-    if (selectorCreatorForType === undefined) {
-      throw new Error(`Unsupported selector type: ${type}`);
-    }
-    let selector = selectorCreatorForType(context, selectorCreator);
-    selector = makeRefinable(selector, selectorCreator);
-    return selector;
-  }
+export function makeRefinable(selectorCreator) {
+  return function createSelector(source) {
+    const selector = selectorCreator(source);
 
-  function selectorCreator(context) {
-    async function* anySelector(descriptors) {
-      const descriptor = descriptors[0]; // TODO handle multiple descriptors
-      const selectorFunc = selectSelector(context, descriptor.type);
-      yield* selectorFunc([descriptor]);
-    }
-    return anySelector;
-  }
+    if (source.refinedBy) {
+      const refiningSelector = createSelector(source.refinedBy);
 
-  return selectorCreator;
-}
-
-export function makeRefinable(selector, selectorCreator) {
-  async function* refinableSelector(descriptors) {
-    const matches = selector(descriptors);
-    for await (let match of matches) {
-      const refiningDescriptor = match.descriptor.refinedBy;
-      if (refiningDescriptor) {
-        const refiningContext = matchAsContext(match);
-        const refiningSelector = selectorCreator(refiningContext);
-        const refiningMatches = refiningSelector([refiningDescriptor]);
-        for await (let refiningMatch of refiningMatches) {
-          const refinedMatch = composeMatches(refiningMatch, match);
-          yield refinedMatch;
+      return async function* matchAll(scope) {
+        const matches = selector(scope);
+        for await (let match of matches) {
+          const refiningScope = matchAsScope(match);
+          const refiningMatches = refiningSelector(refiningScope);
+          for await (let refiningMatch of refiningMatches) {
+            yield composeMatches(refiningMatch, match);
+          }
         }
-      } else {
-        yield match;
-      }
+      };
     }
-  }
 
-  return refinableSelector;
+    return selector;
+  };
 }
 
-function matchAsContext(match) {
+function matchAsScope(match) {
   return match[0];
 }
 
@@ -67,7 +45,6 @@
     const refinedMatch = [...refiningMatch];
     refinedMatch.index = match.index + refiningMatch.index;
     refinedMatch.input = match.input;
-    refinedMatch.descriptor = match.descriptor;
     return refinedMatch;
   });
 }
diff --git a/packages/text/src/index.js b/packages/text/src/index.js
index fa69321..93e2825 100644
--- a/packages/text/src/index.js
+++ b/packages/text/src/index.js
@@ -13,32 +13,28 @@
  * the License.
  */
 
-export function createTextQuoteSelector(context) {
-  async function* exec(descriptors) {
-    for (let descriptor of descriptors) {
-      const prefix = descriptor.prefix || '';
-      const suffix = descriptor.suffix || '';
-      const pattern = prefix + descriptor.exact + suffix;
-      let lastIndex = 0;
-      let next = () => context.indexOf(pattern, lastIndex);
-      let match = next();
-      while (match !== -1) {
-        let result = [descriptor.exact];
-        result.index = match + prefix.length;
-        result.input = context;
-        result.descriptor = descriptor;
-        yield result;
-        lastIndex = match + 1;
-        match = next();
-      }
+export function createTextQuoteSelector(selector) {
+  return async function* matchAll(scope) {
+    const prefix = selector.prefix || '';
+    const suffix = selector.suffix || '';
+    const pattern = prefix + selector.exact + suffix;
+    let lastIndex = 0;
+    let next = () => scope.indexOf(pattern, lastIndex);
+    let match = next();
+    while (match !== -1) {
+      let result = [selector.exact];
+      result.index = match + prefix.length;
+      result.input = scope;
+      result.selector = selector;
+      yield result;
+      lastIndex = match + 1;
+      match = next();
     }
-  }
-
-  return exec;
+  };
 }
 
-export function describeTextQuote({ context, startIndex, endIndex }) {
-  const exact = context.substring(startIndex, endIndex);
+export function describeTextQuote({ scope, startIndex, endIndex }) {
+  const exact = scope.substring(startIndex, endIndex);
   return {
     type: 'TextQuoteSelector',
     exact,