///|
fn string_text_rest(input : StringView, quote : UInt16) -> StringView {
  if quote == '\'' {
    lexmatch input with longest {
      (re"^[^'\\\r\n]+", after=rest) => rest
      _ => input
    }
  } else {
    lexmatch input with longest {
      (re"^[^\"\\\r\n]+", after=rest) => rest
      _ => input
    }
  }
}

///|
fn Lexer::string(self : Lexer, quote : UInt16) -> Unit raise ParseError {
  self.position += 1
  while true {
    let input = self.source.view(start_offset=self.position)
    let remaining = string_text_rest(input, quote)
    self.position = remaining.start_offset()
    lexmatch remaining with longest {
      (re"^\\\r\n", after=rest) => self.position = rest.start_offset()
      (re"^\\[\u{0}-\u{10FFFF}]", after=rest) =>
        self.position = rest.start_offset()
      (re"^['\"]", after=rest) => {
        self.finish_token(StringLiteral, rest)
        return
      }
      (re"^[\r\n]", after=_) =>
        raise self.syntax_error("newline in string literal")
      (re"^\\", after=_) => {
        guard self.position + 1 < self.source.length() else {
          raise self.unterminated("string escape")
        }
        self.position += 2
      }
      _ => {
        guard self.position < self.source.length() else {
          raise self.unterminated("string literal")
        }
        self.position += 1
      }
    }
  }
}

///|
fn Lexer::regex(self : Lexer) -> Unit raise ParseError {
  self.position += 1
  let mut in_class = false
  while true {
    lexmatch self.source.view(start_offset=self.position) with longest {
      (re"^[^\\/\[\]\r\n\u{2028}\u{2029}]+", after=rest) =>
        self.position = rest.start_offset()
      (re"^\\[^\r\n\u{2028}\u{2029}]", after=rest) =>
        self.position = rest.start_offset()
      (re"^\\[\r\n\u{2028}\u{2029}]", after=_) =>
        raise self.unterminated("regular expression escape")
      (re"^\\", after=_) => {
        guard self.position + 1 < self.source.length() else {
          raise self.unterminated("regular expression escape")
        }
        self.position += 2
      }
      (re"^/", after=rest) => {
        self.position = rest.start_offset()
        guard !in_class else { continue }
        let flags_end = raw_identifier_end(self.source, self.position)
        self.finish_token(Regex, self.source.view(start_offset=flags_end))
        return
      }
      (re"^\[", after=rest) => {
        in_class = true
        self.position = rest.start_offset()
      }
      (re"^\]", after=rest) => {
        in_class = false
        self.position = rest.start_offset()
      }
      (re"^[\r\n\u{2028}\u{2029}]", after=_) =>
        raise self.syntax_error("newline in regular expression")
      _ => {
        guard self.position < self.source.length() else {
          raise self.unterminated("regular expression")
        }
        self.position += 1
      }
    }
  }
}

///|
fn Lexer::template_chunk(
  self : Lexer,
  is_start : Bool,
) -> Unit raise ParseError {
  let content_start = self.position
  while true {
    lexmatch self.source.view(start_offset=self.position) with longest {
      (re"^[^\\`$]+", after=rest) => self.position = rest.start_offset()
      (re"^\\[\u{0}-\u{10FFFF}]", after=rest) =>
        self.position = rest.start_offset()
      (re"^`", after=_) => {
        let token : Token = if is_start {
          TemplateNoSubstitution
        } else {
          TemplateTail
        }
        self.finish_template_chunk(content_start, 1, token)
        return
      }
      (re"^\$[{]", after=_) => {
        let token : Token = if is_start { TemplateHead } else { TemplateMiddle }
        self.finish_template_chunk(content_start, 2, token)
        return
      }
      (re"^\$", after=rest) => self.position = rest.start_offset()
      (re"^\\", after=_) => {
        guard self.position + 1 < self.source.length() else {
          raise self.unterminated("template escape")
        }
        self.position += 2
      }
      _ => {
        guard self.position < self.source.length() else {
          raise self.unterminated("template literal")
        }
        self.position += 1
      }
    }
  }
}

///|
fn Lexer::finish_template_chunk(
  self : Lexer,
  content_start : Int,
  delimiter_width : Int,
  token : Token,
) -> Unit {
  self.quasi_start = content_start
  self.quasi_end = self.position
  self.position += delimiter_width
  self.token = token
  self.end = self.position
}

///|
fn Lexer::resume_template(self : Lexer) -> Unit raise ParseError {
  self.start = self.position
  self.template_chunk(false)
}