KalaParser Scalas
AKT-s oli korduvaks näiteks niinimetatud KalaParser. See tegeles näiteks sellise sisendi parsimisega mingiteks abstraktse süntaksipuu klassideks: (kala, (x,y , null, (), (kala,()) ))
.
AKT-s tehti selle parsimist kas täiesti käsitsi või ANTLR-iga grammatikast genereeritud parseri abil. scala-parser-combinators võimaldab põhimõtteliselt sama grammatika kirja panna otse Scala koodis, kasutades selleks hulka eeldefineeritud infiksoperaatoreid ja funktsioone (Scala on väga hea DSL'ide defineerimiseks ja kasutamiseks). Lisaks saab sealsamas defineerida ka, mida eduka parsimise korral teha/tagastada, mis võimaldab konstrueerida näiteks avaldise puu.
Järgnevas koodis ongi kogu KalaParser ülesanne lahendatud, aga nüüd Scalas. Defineeritud on vastava puu klassid, toodud kaks erineva keerukusega viisi scala-parser-combinators abil sisendsõne parsida ning näidatud, kuidas parserit käivitada ja selle tulemust kasutada. Lisaks on niisama defineeritud funktsioon nende kala avaldiste "väärtustamiseks".
KalaParser.scala
package kala import scala.util.parsing.combinator.RegexParsers // Kala AST klassid sealed trait KalaNode case object KalaNull extends KalaNode case class KalaIdent(name: String) extends KalaNode case class KalaList(args: List[KalaNode]) extends KalaNode object KalaParser extends RegexParsers { // RegexParsers annab kõik parsimiseks vajaliku // pikem, detailsem ja lihtsam variant // grammatika reegel 'kala' on parser, mille tulemuseks on KalaNode def kala: Parser[KalaNode] = ( "null" ^^ { case "null" => KalaNull } // reeglis võib esineda String, ^^-ga defineeritakse parsimise tulemuseks olev väärtus | "[a-z]+".r ^^ { case name => KalaIdent(name) } // reeglis võib esineda Regex, ^^ annab parsitud sõne argumendina | "(" ~ kalaArgs ~ ")" ^^ { case "(" ~ args ~ ")" => KalaList(args) } // reeglis saab kasutada teisi reegleid ning osi ~-ga järjestada, ^^ all saab ~ kasutada ka mustrisobituses ) // alternatiivid on eraldatud |-ga // grammatika reegel 'kalaArgs' on parser, mille tulemuseks on List[KalaNode] def kalaArgs: Parser[List[KalaNode]] = ( kala ~ "," ~ kalaArgs ^^ { case node ~ "," ~ args => node :: args } | kala ^^ { case node => node :: Nil } | "" ^^ { case "" => Nil } ) // alternatiivide järjekord on siin väga oluline // lühem, kavalam ja keerukam variant // keerukamad konstruktsioonid võimaldavad ülaldefineeritu kirjutada palju kompaktsemalt def kalaLyhem: Parser[KalaNode] = ( "null" ^^^ KalaNull // ^^^-ga defineeritakse tulemuseks konstantne väärtus | "[a-z]+".r ^^ KalaIdent // KalaIdent konstruktorit kasutatakse kui funktsiooni otse | "(" ~> repsep(kalaLyhem, ",") <~ ")" ^^ KalaList // repsep parsib komadega eraldatud jada ise List-iks, ~> ja <~ ignoreerivad tulemuse defineerimisel fikseeritud literaale ) // RegexParsers vaikimisi ignoreerib tühikuid // funktsioon, mis kala AST-iga töötab def kalaSum(node: KalaNode)(implicit env: Map[String, Int]): Int = node match { case KalaNull => 0 case KalaIdent(name) => env(name) case KalaList(args) => args.map(kalaSum).sum // implicit väldib env-i käsitsi edasi andmist } def main(args: Array[String]): Unit = { // terve String-i parsimine käib RegexParsers-ist tuleva parseAll funktsiooni abil, esimeseks argumendiks on Parser (parsitav grammatikareegel) parseAll(kalaLyhem, "(kala, (x,y , null, (), (kala,()) ))") match { case Success(result, next) => // parsimine õnnestus println(result) val env = Map("kala" -> 1, "x" -> 2, "y" -> 3) println(kalaSum(result)(env)) // 7 case NoSuccess(msg, next) => println(s"Viga: $msg") // parsimine ebaõnnestus } } }