public function interpString($m)
    {
        // this should only be called for doubly quoted strings
        // and triple-double quotes
        //
        // interpolation is betwee ${ ... }
        $patterns = array('interp' => '/(?<!\\$)\\$\\{/');
        $start = $this->pos();
        if (preg_match('/^"""/', $m[0])) {
            $patterns['term'] = '/"""/';
            $this->posShift(3);
        } else {
            assert(preg_match('/^"/', $m[0]));
            $patterns['term'] = '/"/';
            $this->posShift(1);
        }
        while (1) {
            $p = $this->pos();
            list($name, $index, $matches) = $this->getNextNamed($patterns);
            if ($name === null) {
                // no matches, terminate
                $this->record(substr($this->string(), $start), 'STRING');
                $this->terminate();
                break;
            } elseif ($name === 'term') {
                // end of the string
                $range = $index + strlen($matches[0]);
                $this->record(substr($this->string(), $start, $range - $start), 'STRING');
                $this->pos($range);
                break;
            } else {
                // interpolation, handle this with a subscanner
                $this->record(substr($this->string(), $start, $index - $start), 'STRING');
                $this->record($matches[0], 'DELIMITER');
                $subscanner = new GroovyScanner($this->string());
                $subscanner->interpolation = true;
                $subscanner->init();
                $subscanner->pos($index + strlen($matches[0]));
                $subscanner->main();

                $tagged = $subscanner->tagged();
                $this->record($tagged, 'INTERPOLATION', true);
                $this->pos($subscanner->pos());
                if ($this->scan('/\\}/')) {
                    $this->record($this->match(), 'DELIMITER');
                }
                $start = $this->pos();
            }
            assert($p < $this->pos());
        }
    }