Parsing 如何为统一的diff语法编写解析器

Parsing 如何为统一的diff语法编写解析器,parsing,scala,parser-combinators,Parsing,Scala,Parser Combinators,我应该使用regexparser、standardtokenparser,还是这些都适合解析这种语法?语法示例可从中找到。此格式设计为易于解析,无需任何正则表达式,也无需标记输入。一行一行地看前两个字符。文件头和chunks头需要更多的注意,但是使用split没有什么不能做的 当然,如果你想学习如何使用一些解析库,那就去学吧。我会使用正则表达式。它简化了一些事情,并使其余的事情成为标准 def process(src: scala.io.Source) { import scala.util

我应该使用regexparser、standardtokenparser,还是这些都适合解析这种语法?语法示例可从中找到。

此格式设计为易于解析,无需任何正则表达式,也无需标记输入。一行一行地看前两个字符。文件头和chunks头需要更多的注意,但是使用split没有什么不能做的


当然,如果你想学习如何使用一些解析库,那就去学吧。

我会使用正则表达式。它简化了一些事情,并使其余的事情成为标准

def process(src: scala.io.Source) {
  import scala.util.matching.Regex

  val FilePattern = """(.*) ''(.*)''"""
  val OriginalFile = new Regex("--- "+FilePattern, "path", "timestamp")
  val NewFile = new Regex("+++ "+FilePattern, "path", "timestamp")
  val Chunk = new Regex("""@@ -(\d+),(\d+) +(\d+),(\d+) @@""", "orgStarting", "orgSize", "newStarting", "newSize")
  val AddedLine = """+(.*)""".r
  val RemovedLine = """-(.*)""".r
  val UnchangedLine = """ (.*)""".r

  src.getLines() foreach {
    case OriginalFile(path, timestamp) => println("Original file: "+path)
    case NewFile(path, timestamp) => println("New file: "+path)
    case Chunk(l1, s1, l2, s2) => println("Modifying %d lines at line %d, to %d lines at %d" format (s1, l1, s2, l2))
    case AddedLine(line) => println("Adding line "+line)
    case RemovedLine(line) => println("Removing line "+line)
    case UnchangedLine(line) => println("Keeping line "+line)
  }
}

下面是一个使用
RegexParsers
的解决方案

import scala.util.parsing.combinator.RegexParsers

object UnifiedDiffParser extends RegexParsers {

  // case classes representing the data of the diff
  case class UnifiedDiff(oldFile: File, newFile: File, changeChunks: List[ChangeChunk])
  case class File(name: String, timeStamp: String)
  case class ChangeChunk(rangeInformation: RangeInformation, changeLines: List[String])
  case class RangeInformation(oldOffset: Int, oldLength: Int, newOffset: Int, newLength: Int)

  override def skipWhitespace = false

  def unifiedDiff: Parser[UnifiedDiff] = oldFile ~ newFile ~ rep1(changeChunk) ^^ {
    case of ~ nf ~ l => UnifiedDiff(of, nf, l)
  }   

  def oldFile: Parser[File] = ("--- " ~> filename) ~ ("""\s+""".r ~> timestamp <~ newline) ^^ {
    case f~t => File(f, t)
  }   
  def newFile: Parser[File] = ("+++ " ~> filename) ~ ("""\s+""".r ~> timestamp <~ newline) ^^ {
    case f~t => File(f, t)
  }   
  def filename: Parser[String] = """[\S]+""".r
  def timestamp: Parser[String] = """.*""".r

  def changeChunk: Parser[ChangeChunk] = rangeInformation ~ (newline ~> rep1(lineChange)) ^^ {
    case ri ~ l => ChangeChunk(ri, l)
  }   
  def rangeInformation: Parser[RangeInformation] = ("@@ " ~> "-" ~> number) ~ ("," ~> number) ~ (" +" ~> number) ~ ("," ~> number) <~ " @@" ^^ {
    case a ~ b ~ c ~ d => RangeInformation(a, b, c, d)
  }   

  def lineChange: Parser[String] = contextLine | addedLine | deletedLine
  def contextLine: Parser[String] = """ .*""".r <~ newline
  def addedLine: Parser[String] = """\+.*""".r <~ newline
  def deletedLine: Parser[String] = """-.*""".r <~ newline

  def newline: Parser[String] = """\n""".r
  def number: Parser[Int] = """\d+""".r ^^ {_.toInt}

  def main(args: Array[String]) {
    val reader = { 
      if (args.length == 0) {
        // read from stdin
        Console.in
      } else {
        new java.io.FileReader(args(0))
      }   
    }   
    println(parseAll(unifiedDiff, reader))
  }   
}   
import scala.util.parsing.combinator.RegexParsers
对象UnifiedDiffParser扩展RegexParsers{
//表示差异数据的案例类
案例类UnifiedDiff(旧文件:File,新文件:File,changeChunks:List[ChangeChunk])
案例类文件(名称:String,时间戳:String)
案例类ChangeChunk(范围信息:范围信息,变更行:列表[字符串])
案例类范围信息(oldOffset:Int、oldLength:Int、newOffset:Int、newLength:Int)
覆盖def skipWhitespace=false
def unifiedDiff:Parser[unifiedDiff]=oldFile~newFile~rep1(changeChunk)^{
~nf~l=>UnifiedDiff(of,nf,l)的情况
}   
def oldFile:Parser[File]=(“--”~>文件名)~(“\s+”.r~>时间戳文件(f,t)
}   
def newFile:Parser[File]=(“++++”~>文件名)~(“\s+”.r~>时间戳文件(f,t)
}   
def文件名:解析器[字符串]=“”“[\S]+“””。r
def时间戳:解析器[字符串]=“”*“”.r
def changeChunk:Parser[changeChunk]=rangeInformation~(newline~>rep1(lineChange))^{
案例ri~l=>ChangeChunk(ri,l)
}   
def rangeInformation:Parser[rangeInformation]=(“@”~>“-”~>number)~(“,“~>number)~(“+”~>number)~(“,”~>number)rangeInformation(a,b,c,d)
}   
def lineChange:Parser[String]=contextLine | addedLine | deletedLine

def contextLine:Parser[String]=“*”。r在为git diff构建Scala解析器时偶然发现了这一点,该解析器是通过运行
git diff tree
生成的。这与unified diff非常相似,但它确实有一些有趣的变体

我在很大程度上依赖于上面的答案,并最终编写了这里包含的解析器。当然,这并不是原始海报所追求的,但我认为它可能对其他人有用

import util.parsing.combinator._

object GitDiff {
  // file names have "a/" or "b/" as prefix, need to drop that to compare
  def apply (files: (String,String), op: FileOperation, chunks: List[ChangeChunk]) = {
    def strip(s: String) = s.dropWhile(_ != '/').drop(1)
    new GitDiff( strip( files._1 ), strip( files._2 ), op, chunks )
  }
}

case class GitDiff(oldFile: String, newFile: String, op: FileOperation, chunks: List[ChangeChunk]) {
  val isRename = oldFile != newFile
}

sealed trait FileOperation
case class NewFile(mode: Int) extends FileOperation
case class DeletedFile(mode: Int) extends FileOperation
case object UpdatedFile extends FileOperation

sealed trait LineChange { def line: String }
case class ContextLine(line: String) extends LineChange
case class LineRemoved(line: String) extends LineChange
case class LineAdded(line: String) extends LineChange
case class RangeInformation(oldOffset: Int, oldLength: Int, newOffset: Int, newLength: Int)
case class ChangeChunk(rangeInformation: RangeInformation, changeLines: List[LineChange])

// Code taken from http://stackoverflow.com/questions/3560073/how-to-write-parser-for-unified-diff-syntax
object GitDiffParser extends RegexParsers {

  override def skipWhitespace = false

  def allDiffs: Parser[List[GitDiff]] = rep1(gitDiff)

  def gitDiff: Parser[GitDiff] = filesChanged ~ fileOperation ~ diffChunks ^^ {
    case files ~ op ~ chunks => GitDiff(files, op, chunks)
  }

  def filesChanged: Parser[(String, String)] =
    "diff --git " ~> filename ~ (" " ~> filename) <~ newline ^^ { case f1 ~ f2 => (f1,f2) }

  def fileOperation: Parser[FileOperation] =
    opt(deletedFileMode | newFileMode) <~ index ^^ { _ getOrElse UpdatedFile }

  def index: Parser[Any] = ( "index " ~ hash ~ ".." ~ hash ) ~> opt(" " ~> mode) <~ newline
  def deletedFileMode: Parser[DeletedFile] = "deleted file mode " ~> mode <~ newline ^^ { m => DeletedFile(m) }
  def newFileMode: Parser[NewFile] = "new file mode " ~> mode <~ newline ^^ { m => NewFile(m) }
  def hash: Parser[String] = """[0-9a-f]{7}""".r
  def mode: Parser[Int] = """\d{6}""".r ^^ { _.toInt }

  def diffChunks: Parser[List[ChangeChunk]] = (oldFile ~ newFile) ~> rep1(changeChunk)

  def oldFile: Parser[String] = "--- " ~> filename <~ newline
  def newFile: Parser[String] = "+++ " ~> filename <~ newline
  def filename: Parser[String] = """[\S]+""".r

  def changeChunk: Parser[ChangeChunk] = rangeInformation ~ opt(contextLine) ~ (opt(newline) ~> rep1(lineChange)) ^^ {
    case ri ~ opCtx ~ lines => ChangeChunk(ri, opCtx map (_ :: lines) getOrElse (lines))
  }
  def rangeInformation: Parser[RangeInformation] =
    ("@@ " ~> "-" ~> number) ~ opt("," ~> number) ~ (" +" ~> number) ~ opt("," ~> number) <~ " @@" ^^ {
      case a ~ b ~ c ~ d => RangeInformation(a, b getOrElse 0, c, d getOrElse 0)
    }

  def lineChange: Parser[LineChange] = contextLine | addedLine | deletedLine
  def contextLine: Parser[ContextLine] = " " ~> """.*""".r <~ newline ^^ { l => ContextLine(l) }
  def addedLine: Parser[LineAdded] = "+" ~> """.*""".r <~ newline ^^ { l => LineAdded(l) }
  def deletedLine: Parser[LineRemoved] = "-" ~> """.*""".r <~ newline ^^ { l => LineRemoved(l) }

  def newline: Parser[String] = """\n""".r
  def number: Parser[Int] = """\d+""".r ^^ { _.toInt }

  def parse(str: String) = parseAll(allDiffs, str)

  def main(args: Array[String]) {
    val reader = {
      if (args.length == 0) {
        // read from stdin
        Console.in
      } else {
        new java.io.FileReader(args(0))
      }
    }
    parseAll(allDiffs, reader) match {
      case Success(s,_) => println( s )
      case NoSuccess(msg,_) => sys.error("ERROR: " + msg)
    }
  }
}
import util.parsing.combinator_
对象GitDiff{
//文件名的前缀为“a/”或“b/”,需要删除该前缀以进行比较
def apply(文件:(字符串,字符串),op:FileOperation,chunks:List[changehunk])={
def strip(s:String)=s.dropWhile(!='/').drop(1)
新的GitDiff(strip(files.\u 1)、strip(files.\u 2)、op、chunks)
}
}
案例类GitDiff(oldFile:String,newFile:String,op:FileOperation,chunks:List[changehunk]){
val isRename=oldFile!=newFile
}
密封特征文件操作
案例类NewFile(模式:Int)扩展了FileOperation
案例类DeletedFile(模式:Int)扩展文件操作
案例对象UpdatedFile扩展文件操作
密封特征换行{def line:String}
case类ContextLine(line:String)扩展了LineChange
案例类LineRemoved(行:String)扩展了LineChange
案例类LineAdded(行:String)扩展了LineChange
案例类范围信息(oldOffset:Int、oldLength:Int、newOffset:Int、newLength:Int)
案例类ChangeChunk(rangeInformation:rangeInformation,changeLines:List[LineChange])
//代码取自http://stackoverflow.com/questions/3560073/how-to-write-parser-for-unified-diff-syntax
对象GitDiffParser扩展RegexParsers{
覆盖def skipWhitespace=false
def alldiff:Parser[List[GitDiff]]=rep1(GitDiff)
def gitDiff:Parser[gitDiff]=filechanged~fileOperation~diffChunks^{
case files~op~ chunks=>GitDiff(文件,op,chunks)
}
def FILECHANGED:解析器[(字符串,字符串)]=
“diff--git”~>filename~(“~>filename)(f1,f2)}
def fileOperation:Parser[fileOperation]=
opt(deletedFileMode | newFileMode)opt(“~>mode)mode DeletedFile(m)}
def newFileMode:Parser[NewFile]=“新文件模式”~>mode NewFile(m)}
def hash:Parser[String]=“0-9a-f]{7}”“。r
定义模式:解析器[Int]=“”“\d{6}”“.r^^{{uuu.toInt}”
def diffChunks:Parser[List[ChangeChunk]=(oldFile~newFile)~>rep1(ChangeChunk)
def oldFile:Parser[String]=“--”~>文件名rep1(lineChange))^{
case ri~opCtx~lines=>changehunk(ri,opCtx映射(u::lines)getOrElse(lines))
}
def rangeInformation:解析器[rangeInformation]=
(“@”~>“-”~>数字)~opt(“,”~>数字)~(“+”~>数字)~opt(“,”~>数字)范围信息(a、b getOrElse 0、c、d getOrElse 0)
}
def lineChange:Parser[lineChange]=contextLine | addedLine | deletedLine
def contextLine:Parser[contextLine]=“~>”*“.r contextLine(l)}
def addedLine:Parser[LineAdded]=“+”~>“*”。r LineAdded(l)}
def deletedLine:Parser[LineRemoved]=“-”~>“*”。r LineRemoved(l)}
def换行符:解析器[字符串]=“”“\n”“。r
定义编号:解析器[Int]=“”“\d+”.r^^{{uu.toInt}
def parse(str:String)=parseAll(alldiff,str)
def main(参数:数组[字符串]){
val读取器={
如果(args.length==0){
//从标准文本中读取
控制台
}否则{
新的java.io.FileReader(args(0))
}
}
parseAll(AllDiff,reader)匹配{
案例成功率=>println
case NoSuccess(msg,)=>系统错误(“错误:+msg”)
}
}
}

几年前,在我编程的第一周,我开始为Nethack创建补丁。由于不知道
diff
,我开始手工编写这些该死的东西。你可以想象,当新闻组中有人礼貌地告诉我,我可能疯了时,我会感到尴尬。好吧,无论如何,统一的diff不仅很容易解析,而且还很容易理解