【问题标题】:How do I create a case-insensitive lexer如何创建不区分大小写的词法分析器
【发布时间】:2014-08-19 17:34:15
【问题描述】:

我正在尝试创建一个 SQL 词法分析器(好吧,一个完整的解析器,但您必须从某个地方开始),但我不确定如何继续。我想写这样的东西:

def nextToken(input: List[Char]) = input match {
  case 'S'::'E'::'L'::'E'::'C'::'T'::tail => (SELECT, tail)
  case _ => ??? // etc.
}

但 SQL 不区分大小写。我可以将输入中的所有字符都大写,但字符串也会大写。我真正需要的是一种进行不区分大小写比较的方法,然后留下正确的tail(匹配令牌后剩余的List[Char])。有没有办法在 Scala 2.10.x 中轻松做到这一点?

【问题讨论】:

  • 你可以先把整个输入字符串大写,然后你的所有代码都可以只使用大写字符吗?
  • 不,那么字符串也会是大写的。我不想破坏输入。

标签: scala lexer parser-combinators


【解决方案1】:

如果输入保留为字符串,正则表达式似乎可以很好地解决这个问题。

val SelectRe = """(?i)SELECT(.*)""".r            // the (?i) makes it case-insensitive

def nextToken(input: String) = input match {
  case SelectRe(tail) => (SELECT, tail)
  case _ => ??? // etc.
}

【讨论】:

  • 有没有办法做到这一点浪费大量的内存?我使用List[Char] 作为输入,因为将头部和尾部分开是恒定的时间,额外的内存使用量为零。
【解决方案2】:

我需要这是一个高效的解析器,所以@dhg 的答案对我不起作用。这是我确定的答案。它使用不区分大小写的Trie 的变体,但在哪里

  1. 最短的比赛获胜
  2. 我们不使用整个搜索输入。我们最多只消耗一场比赛,不会更多。

这是特里:

    case class CaseInsensitiveTrie[T](children: List[Node[T]] = Nil) {
        def add(key: List[Char], value: T): CaseInsensitiveTrie[T] = {
            def loop(key: List[Char], nodes: List[Node[T]]): List[Node[T]] = key match {
                case c::Nil =>
                    nodes.find(_.key == c) match {
                        case Some(node) => nodes // No updating values
                        case None => Node(c, Some(value), Nil) :: nodes
                    }
                case c::tail =>
                    nodes.find(_.key == c) match {
                        case Some(node) =>
                            val children = loop(tail, node.children)
                            val newNode = Node(c, None, children)
                            newNode :: nodes.filterNot(_.key == c)
                        case None =>
                            val children = loop(tail, Nil)
                            val newNode = Node(c, None, children)
                            newNode :: nodes
                    }

                case Nil => ??? // Programmer error
            }

            CaseInsensitiveTrie(loop(key, children))
        }

        def find(input: List[Char]): Option[(T, List[Char])] = {
            def loop(input: List[Char], nodes: List[Node[T]]): Option[(T, List[Char])] = input match {
                case c::Nil =>
                    nodes.find(_.key.toUpper == c.toUpper).flatMap(_.value.map(_ -> Nil))
                case c::tail =>
                    nodes.find(_.key.toUpper == c.toUpper).flatMap {
                        case node if node.value.isDefined =>
                            node.value.map(_ -> tail)
                        case node =>
                            loop(tail, node.children)
                    }
            }

            loop(input, children)
        }
    }

    object CaseInsensitiveTrie {
        case class Node[T](key: Char, value: Option[T], children: List[CaseInsensitiveTrie.Node[T]])
    }

只有 2 个函数 - findadd。只用不区分大小写的关键字填充 trie,然后我们创建这个 unapply 函数:

val trie = CaseInsensitiveTrie[Token]()
  .add("SELECT".toList, SELECT)
  .add("FROM".toList, FROM)
  .add("WHERE".toList, WHERE)
  .add("AS".toList, AS)

object Keyword {
  def unapply(input: List[Char]): Option[(Token, List[Char])] = {
    trie.find(input)
  }
}

所以在匹配中我们可以测试匹配并同时拉取匹配和输入的剩余部分:

  input match {
    case '*'::tail         => (STAR, Scan(tail, location + 1).trim)
    case '('::tail         => (LPAREN, Scan(tail, location + 1).trim)
    case ')'::tail         => (RPAREN, Scan(tail, location + 1).trim)
    case '.'::tail         => (DOT, Scan(tail, location + 1).trim)
    case ','::tail         => (COMMA, Scan(tail, location + 1).trim)
    case '='::tail         => (EQ, Scan(tail, location + 1).trim)
    case '<'::'>'::tail    => (NE, Scan(tail, location + 2).trim)
    case '>'::tail         => (GT, Scan(tail, location + 1).trim)
    case '>'::'='::tail    => (GE, Scan(tail, location + 2).trim)
    case '<'::tail         => (LT, Scan(tail, location + 1).trim)
    case '>'::'='::tail    => (LE, Scan(tail, location + 2).trim)
    case Keyword(kw, tail) if tail.headOption.fold(true)(_.isWhitespace) =>
      (kw, Scan(tail, location + kwLength(kw)).trim)
  }

这不是可用的最快的 Trie 实现,但它可以满足我的需求。如果性能完全成为问题,我可以将其压缩为尝试

【讨论】:

    猜你喜欢
    • 2015-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-03-06
    • 1970-01-01
    • 2011-10-14
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多