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
}
}
}