2010-08-24 4 views

Répondre

3

Voici une solution utilisant 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)) 
    } 
} 
+0

Merci, juste ce que je cherchais! – JtR

4

Ce format a été conçu pour être facile à analyser, vous pouvez le faire sans expressions régulières et sans que votre entrée soit fragmentée. Allez juste ligne par ligne et regardez les deux premiers caractères. L'en-tête du fichier et les en-têtes des blocs nécessiteront un peu plus d'attention, mais ce n'est pas ce que vous ne pouvez pas faire avec split.

Bien sûr, si vous voulez apprendre à utiliser certaines bibliothèques d'analyse, alors allez-y.

+2

Il y a quelques années, au cours de ma première semaine de programmation, j'ai commencé à créer des correctifs pour Nethack. Ne sachant pas 'diff', j'ai commencé à écrire les foutues choses à la main. Vous pouvez imaginer mon embarras quand quelqu'un dans le groupe de nouvelles m'a poliment informé que je pourrais être hors de mon esprit. Eh bien, de toute façon non seulement les diffs unifiés sont faciles à analyser, mais ils ne sont pas si difficiles à écrire à la main non plus. :) – guns

+0

Je ne voudrais pas garder explicitement l'état moi-même et apprendre à utiliser les combinateurs de l'analyseur est également l'un de mes objectifs. Sur la plupart des exemples, il y a une certaine syntaxe à utiliser pour le langage de programmation, mais je me demandais si les combinateurs de l'analyseur ne pouvaient pas être utilisés pour l'analyse de la syntaxe diff ou même des formats binaires. – JtR

5

J'utiliserais regex. Cela simplifie un certain nombre de choses et rend le reste standard.

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) 
    } 
} 
+0

J'espérais pouvoir utiliser des combinateurs d'analyseurs pour me débarrasser de mon état. Je construis un graphique d'objet à partir des détails du correctif quelque chose comme ça que j'ai un correctif qui contient FileModifications qui contiennent des morceaux. Il semble que les combinateurs d'analyseurs pourraient offrir un moyen plus simple de créer des objets à partir de choses analysées au lieu de construire le graphe d'objets sur certaines variables et de suivre l'état d'analyse. – JtR

+0

Btw, je ne savais pas que les regexps peuvent être utilisés dans la correspondance de motifs comme ça, très soigné! – JtR

1

trébuché sur ce tout en cherchant à construire un analyseur Scala pour une diff git, tel que généré en exécutant git diff-tree. Ceci est très similaire à diff unifié, mais il a quelques variantes intéressantes. J'ai beaucoup compté sur une réponse ci-dessus, et j'ai fini par écrire l'analyseur inclus ici.

Ce n'est pas strictement ce que l'affiche originale était après, bien sûr, mais j'ai pensé que cela pourrait être utile aux autres.

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) 
    } 
    } 
} 
Questions connexes