Projekcik Scalowo-Seleniowy przysiadł. Były ważniejsze rzeczy do robienia w fabryczce. Teraz jednak podobny problem wraca i to jak Kłahtiański Bumerang… niewątpliwie wrócił z całą rodziną.

Okazuje się, że początkowe założenie, że będzie to służyło tylko do testowania ekranów w aplikacji – emulatorze AS400 było nie do końca słuszne. Z pewnych organizacyjno-onanizacyjnych powodów potrzebujemy narzędzia, które będzie wstanie poruszać się po aplikacji wg. zdefiniowanych schematów… czyli będzie DSL. Do tego całość ma być odpalana po nocach za pomocą crontaba.
Jako, że schematy działania powinny być jakoś edytowalne zatem wskazane jest by użyć DSLa, który będzie interpretowany dynamicznie. Innymi słowy pure-java odpada z dwóch powodów:

  • Da się napisać interpreter w javie, ale to od cholery roboty. Nie mam tyle czasu.
  • W celach dydaktycznych użyjemy scali.

Interpretacja Scali w Scali

W ramach podstawowego pakietu Scali nie ma niestety dostępnego żadnego interpretera. Podstawowego czyli scala-language. Wystarczy jednak w pom.xml dać:

Listing 1. Włączamy interpreter

<dependency><groupid>org.scala-lang</groupid><artifactid>scala-compiler</artifactid><version>${scala.version}</version></dependency>

Czyli do zależności dodajemy kompilator Scala. W mrokach tego pakietu jest klasa IMain, czyli interpreter. Jest to dość specyficzny interpreter ponieważ jego plusem jest wykorzystanie kompilatora i w rezultacie otrzymujemy kod w pełni sprawny i zachowujący się tak jak kod skompilowany (szybkość). Z drugiej strony nie ma dostępnego redefiniowania klas. Sorry w javie trudne do wykonania.

W ramach ćwiczenia…

Jako, że nie mam jeszcze gotowego pełnego rozwiązania, a chcę coś przedstawić to zaprezentuję krótki kod, który odpowiada javascriptowej metodzie eval. Interpretowany będzie pierwszy argument z linii poleceń. Można co prawda rozbudować program o interpretację zawartości pliku… ale i tak dużo zrobiłem po godzinach.

Klasa główna

Zabawka składa się z trzech elementów. Klasy, obiektu i traita. LOL… wykorzystujemy wszystkie elementy scali. Nawet case classes będzie w użyciu. Dlatego też wybrałem tu scalę. Jest łatwiej.
Główna klasa będzie prosta jak konstrukcja POJO.

Listing 2. klasa główna

object App extends Eval {

  def main(args: Array[String]) {
    { eval(args(0)) } match {
      case None => println("nie bangla")
      case Some(x) => println(x)
    }
  }

}

Niestety Option nie dostarcza jakiejś zgrabnej metody pozwalającej na wykonanie się na wszystkich elementach podanej funkcji. Cóż… bywa.

Eval

Czyli serce i płuca programu:

Listing 3. Trait Eval

import scala.tools.nsc.interpreter.{ IMain, Results }

trait Eval {

  def eval(expresion: String): Option[Any] = {
    val interpreter = new IMain {
      override protected def parentClassLoader: ClassLoader = App.getClass().getClassLoader
    }
    val res = new ResultSet
    interpreter.beQuietDuring {
      interpreter.bind("res", res.getClass.getCanonicalName, res)
      interpreter.interpret("res.value = " + expresion)
    } match {
      case Results.Success => Option(res.value)
      case _ => None;
    }

  }
}

Tu warto zwrócić uwagę na sposób zdefiniowania obiektu interpretera. Nadpisanie classloadera jest niezbędne do prawidłowego działania programu. Można co prawda użyć klasy Settings, ale wtedy musimy nadpisać znacznie więcej elementów. Bywa 🙂

Na koniec pomocnicza klasa ResultSet

Listing 4. Klasa ResultSet

class ResultSet {

  var value : Any  = null
  
}

To taki kadłubek – debilek, czyli twój kandydat do sejmu w następnych wyborach. Istnieje tylko po to by pokazać jak przekazywać rezultaty działania interpretowanego kodu.