以函数方式计算语法的可空非终结符(最好在Scala中)

时间:2013-01-20 18:41:26

标签: scala functional-programming context-free-grammar

我是函数式编程的新手,并且想知道如何在不使用变量赋值的情况下以纯函数方式解决在无上下文语法中计算可空非终结集的问题。

可以为空的非终结符号是直接产生空的非终结符号,例如A :: =,或 具有可以为空的非终结符号的主体,例如A :: = B C D,其中所有B C和D都是空的。

我在Scala中使用以下定义来定义语法:

case class Grammar(name:String, startSymbol:Nonterminal, rules:List[Rule])
case class Rule(head: Nonterminal, body:List[Symbol])
abstract class Symbol
case class Terminal(c:Char) extends Symbol
case class Nonterminal(name:String) extends Symbol

一个基本算法是收集所有可直接为空的非终结符并将它们放在一个集合中。 然后在每次迭代中尝试确定哪些生产规则具有所有可为空的非终结符 在他们的身上。这些非终结符将被添加到集合中,直到没有添加新的非终结符 组。

我已在Scala中实现此过程:

  def getNullableNonterminals(grammar:Grammar) = {

  var nieuw : Set[Nonterminal] = (for(Rule(head, Nil) <- grammar.rules) yield head) (collection.breakOut)
  var old = Set[Nonterminal]()

  while(old != nieuw) {
    old = nieuw
    for{
        Rule(head, symbols) <- grammar.rules
        if symbols.length > 0
        if symbols.forall( s => s.isInstanceOf[Nonterminal] && old.contains(s.asInstanceOf[Nonterminal]))
       } nieuw = nieuw + head
  }
  nieuw   
}

虽然代码比同等的Java版本短得多,但它使用变量。有什么建议 以功能样式重写这段代码?

3 个答案:

答案 0 :(得分:1)

这是一个更惯用的Scala解决方案:

object Algorithm {

  def getNullableNonterminals(grammar:Grammar) = {
    loop(grammar, Set())
  }

  @tailrec
  private def loop(grammar: Grammar, nullablesSoFar: Set[Nonterminal]): Set[Nonterminal] = {
    val newNullables = generateNew(grammar, nullablesSoFar)
    if (newNullables.isEmpty)
      nullablesSoFar //no new nullables found, so we just return the ones we have
    else
      loop(grammar, nullablesSoFar ++ newNullables) //add the newly found nullables to the solution set and we keep going
  }

  private def generateNew(grammar: Grammar, nullableSoFar: Set[Nonterminal]) = {
    for {
      Rule(head, body) <- grammar.rules
      if !nullableSoFar.contains(head)
      if body.forall(isNullable(_, nullableSoFar))
    } yield head
  }

  //checks if the symbol is nullable given the current set of nullables
  private def isNullable(symbol: Symbol, provenNullable: Set[Nonterminal]) = symbol match {
    case Terminal(_) => false
    case x@Nonterminal(_) => provenNullable.contains(x)
  }

}

while语句替换为递归函数 - loop。 另外,避免使用isInstanceOf - 模式匹配更适合这种情况。

小观察 - 制作符号类sealed,因为这可以在模式匹配中强制执行缺失案例的警告。

答案 1 :(得分:1)

这是使用 memoisation a referenceanother reference)的另一种方法,它避免了像你和M. A. D.的解决方案那样需要定点计算。此外,它是适用于场景负载的一般模式。看看Scalaz implementation

def getNullableNonterminals(g: Grammar): Iterable[Nonterminal] = {
  /* Cache that is used by isNullable to memoise results. */
  var cache: Map[Nonterminal, Boolean] = Map()

  /* Assumption: For each nonterminal nt there exists only one rule r
   * such that r.head == nt.
   */
  var rules: Map[Nonterminal, List[Symbol]] = g.rules.map(r => (r.head, r.body)).toMap

  def isNullable(s: Symbol): Boolean = s match {
    case _: Terminal => false
    case nt: Nonterminal =>
      /* Either take the cached result, or compute it and store it in the cache. */
      cache.getOrElse(nt, {
        /* rules(nt) assumes that there is a rule for every nonterminal */
        val nullable = rules(nt) forall isNullable
        cache += ((nt, nullable))
        nullable
      })
  }

  rules.keys filter isNullable
}

测试用例:

val ta = Terminal('a')
val tb = Terminal('b')

val ntX = Nonterminal("X")
val ntY = Nonterminal("Y")
val ntZ = Nonterminal("Z")
val ntP = Nonterminal("P")
val ntQ = Nonterminal("Q")
val ntR = Nonterminal("R")
val ntS = Nonterminal("S")

val rX = Rule(ntX, ntP :: ntQ :: Nil)
val rY = Rule(ntY, ntP :: ta :: ntQ :: Nil)
val rZ = Rule(ntZ, ntR :: Nil)
val rP = Rule(ntP, ntQ :: Nil)
val rQ = Rule(ntQ, Nil)
val rR = Rule(ntR, tb :: Nil)
val rS = Rule(ntS, ntX :: ntY :: ntZ :: Nil)

val g = Grammar("Test", ntS, List(rX, rY, rZ, rP, rQ, rR, rS))

getNullableNonterminals(g) foreach println
  // Nonterminal(Q), Nonterminal(X), Nonterminal(P)

答案 2 :(得分:0)

我终于有时间写一个如何编写语法的例子 使用循环属性语法的可空性计算。以下代码使用 我们的Kiama language processing library for Scala。你可以在凯马找到full source code of the example and tests。请参阅SemanticAnalysis.scala 主要归因代码,例如nullable

简而言之,该方法执行以下操作:

  • 将语法表示为抽象语法树结构,

  • 对树结构执行名称分析以解析使用中的引用 语法符号与这些符号的定义,以及

  • 计算可空性作为生成的DAG结构的循环属性。

我使用的属性定义与用作示例的属性定义非常相似 在马格努森和赫丁的论文Circular Reference Attribute Grammars中 LDTA 2003.他们在JastAdd system和我{{3}}中实现循环属性 我会强烈推荐那些希望了解这个主题的人。 我们在Kiama中使用基本相同的算法。

以下是该示例使用的AST的定义。 Tree是凯马 提供一些常见行为的类型。

sealed abstract class GrammarTree extends Tree

case class Grammar (startRule : Rule, rules : Seq[Rule]) extends GrammarTree

case class Rule (lhs : NonTermDef, rhs : ProdList) extends GrammarTree

sealed abstract class ProdList extends GrammarTree
case class EmptyProdList () extends ProdList
case class NonEmptyProdList (head : Prod, tail : ProdList) extends ProdList

case class Prod (symbols : SymbolList) extends GrammarTree

sealed abstract class SymbolList extends GrammarTree
case class EmptySymbolList () extends SymbolList
case class NonEmptySymbolList (head : Symbol, tail : SymbolList) extends SymbolList

sealed abstract class Symbol extends GrammarTree
case class TermSym (name : String) extends Symbol
case class NonTermSym (nt : NonTermUse) extends Symbol

sealed abstract class NonTerm extends GrammarTree {
    def name : String
}
case class NonTermDef (name : String) extends NonTerm
case class NonTermUse (name : String) extends NonTerm

下面的代码显示了nullable属性的定义。它 从false开始,然后输入一个固定点“循环”来计算,直到 价值稳定。这些案例展示了如何计算不同的属性 AST中的节点类型。

Kiama的circular属性构造函数包含所有实现 属性,包括存储缓存,定点检测等。

val nullable : GrammarTree => Boolean =
    circular (false) {

        // nullable of the start rule
        case Grammar (r, _) =>
            r->nullable

        // nullable of the right-hand side of the rule
        case Rule (_, rhs) =>
            rhs->nullable

        // nullable of the component productions
        case EmptyProdList () =>
            false
        case NonEmptyProdList (h, t) =>
            h->nullable || t->nullable

        // nullable of the component symbol lists
        case Prod (ss) =>
            ss->nullable
        case EmptySymbolList () =>
            true
        case NonEmptySymbolList (h, t) =>
            h->nullable && t->nullable

        // terminals are not nullable
        case TermSym (_) =>
            false

        // Non-terminal definitions are nullable if the rule in which they
        // are defined is nullable. Uses are nullable if their associated
        // declaration is nullable.
        case NonTermSym (n) =>
            n->nullable
        case n : NonTermDef =>
            (n.parent[Rule])->nullable
        case n : NonTermUse =>
            (n->decl).map (nullable).getOrElse (false)

    }

引用属性decl是连接非终端用途的引用属性 它在规则左侧的相应定义。 parent field是AST中从节点到其父节点的引用。

由于一个规则或符号的可为空性取决于其可为空性 其他人,你得到的是一组参与的属性事件 依赖循环。结果是可空性计算的声明版本 非常类似于“教科书”的定义。 (该例子也定义了 用于定义的FIRST和FOLLOW集计算的属性 可空性。)循环属性结合了记忆和固定点 在这种问题的方便包中计算。