Search code examples
parsingscalafunctional-programmingparser-combinators

How to embed Scala code inside a specially defined syntax?


I don't know if this info is relevant to the question, but I am learning Scala parser combinators. Using some examples (in this master thesis) I was able to write a simple functional (in the sense that it is non imperative) programming language.

Is there a way to improve my parser/evaluator such that it could allow/evaluate input like this:

<%
import scala.<some package / classes>
import weka.<some package / classes>
%>

some DSL code (lambda calculus)

<%
System.out.println("asdasd");
J48 j48 = new J48();
%>

as input written in the guest language (DSL)?

Should I use reflection or something similar* to evaluate such input? Is there some source code recommendation to study (may be groovy sources?)?

Maybe this is something similar: runtime compilation, but I am not sure this is the best alternative.

EDIT

Complete answer given bellow with "{" and "}". Maybe "{{" would be better.


Solution

  • I managed how to run Scala code embedded in my interpreted DSL.

    Insertion of DSL vars into Scala code and recovering returning value comes as a bonus. :)

    Minimal relevant code from parsing and interpreting until performing embedded Scala code run-time execution (Main Parser AST and Interpreter):

    object Main extends App {
         val ast = Parser1 parse "some dsl code here"
         Interpreter eval ast
    }
    
    object Parser1 extends RegexParsers with ImplicitConversions {
      import AST._
      val separator = ";"
      def parse(input: String): Expr = parseAll(program, input).get
      type P[+T] = Parser[T]
      def program = rep1sep(expr, separator) <~ separator ^^ Sequence
      def expr: Parser[Expr] = (assign /*more calls here*/)
      def scalacode: P[Expr] = "{" ~> rep(scala_text) <~ "}" ^^ {case l => Scalacode(l.flatten)}
      def scala_text = text_no_braces ~ "$" ~ ident ~ text_no_braces ^^ {case a ~ b ~ c ~ d => List(a, b + c, d)}
      //more rules here
      def assign = ident ~ ("=" ~> atomic_expr) ^^ Assign
      //more rules here
      def atomic_expr = (
         ident ^^ Var
            //more calls here 
            | "(" ~> expr <~ ")"
            | scalacode
            | failure("expression expected")
         )
      def text_no_braces = """[a-zA-Z0-9\"\'\+\-\_!@#%\&\(\)\[\]\/\?\:;\.\>\<\,\|= \*\\\n]*""".r //| fail("Scala code expected")
      def ident = """[a-zA-Z]+[a-zA-Z0-9]*""".r
    }
    
    object AST {
       sealed abstract class Expr
       // more classes here
       case class Scalacode(items: List[String]) extends Expr
       case class Literal(v: Any) extends Expr
       case class Var(name: String) extends Expr
    }
    
    object Interpreter {
      import AST._
      val env = collection.immutable.Map[VarName, VarValue]()
      def run(code: String) = {
         val code2 = "val res_1 = (" + code + ")"
         interpret.interpret(code2)
         val res = interpret.valueOfTerm("res_1")
         if (res == None) Literal() else Literal(res.get)
      }
    
      class Context(private var env: Environment = initEnv) {
        def eval(e: Expr): Any = e match {
           case Scalacode(l: List[String]) => {
              val r = l map {
                 x =>
                    if (x.startsWith("$")) {
                       eval(Var(x.drop(1)))
                    } else {
                       x
                    }
              }
              eval(run(r.mkString))
           }
           case Assign(id, expr) => env += (id -> eval(expr))
           //more pattern matching here
           case Literal(v) => v
           case Var(id) => {
              env getOrElse(id, sys.error("Undefined " + id))
           }
         }
        }  
      }