8

Is there a way to define a dsl, that would allow the following form?

variable identifier identifier variable

For example:

1 equals to 2

I know how to create a simpler form: 1 equals to (2), but I want to avoid parentheses. Is there a way to do it?

Rogach
  • 26,050
  • 21
  • 93
  • 172

3 Answers3

13

You can ask the parser:

$ scala -Xprint:parser
Welcome to Scala version 2.9.2 ... blah

scala> variable1 identifier1 identifier2 variable2
// lots of stuff and inside:
val res0 = variable1.identifier1(identifier2).variable2
// this is how the parser sees it.
// if you can make that work (dynamic classes…?), you’re good to go.

However, there is a problem: This only works as long as variable2 is an identifier (so that it can be used as a method name). With

scala> 1 equals to 2

already the parser fails:

<console>:1: error: ';' expected but integer literal found.
       1 equals to 2
                   ^

Parentheses are really your only way around(*):

scala> 1 equals to (2)
// ...
val res1 = 1.equals(to(2))

(*) unless you make 2 an identifier by using it with backticks

scala> 1 equals to `2`
// ...
val res2 = 1.equals(to).2

… nah, maybe not.

Debilski
  • 66,976
  • 12
  • 110
  • 133
  • Ah, nice trick with the parser output. I'm sure that will prove to be handy once again. – rolve May 08 '12 at 19:26
3

As far as I know it's impossible at the moment to implement it as you want (I would be happy to be proven wrong). I also faced this issue, when I was creating my small DSL for injection. But I have realized, that even if you can't have 2 variables between 2 identifiers, you still can have three identifiers between them. It looks like this:

variable1 method constant method variable2

which is the same as:

variable1.method(constant).method(variable2)

With this I was able to come up with very nice DSL that looks like this:

by default defaultDb and identified by 'remote
'remote is by default defaultDb

You can find more examples of it's usage in this spec:

https://github.com/OlegIlyenko/scaldi/blob/48f7e4186cf7eb441116087003d7f45f16e0ac6c/src/test/scala/scaldi/InjectableSpec.scala#L47

It's implementation can be found here:

https://github.com/OlegIlyenko/scaldi/blob/master/src/main/scala/scaldi/Injectable.scala

I used ByWord and IdentifiedWord class types as method arguments. For example:

def is(by: ByWord) = and(by)

Which leaves possibility, that users of the library will extend ByWorld class and generally can provide their own ByWorld implementations. Now when I think about this, I find it not very nice. Better solution would be to create singelton objects for the constant words and then use their type like this:

object by
def is(byWord: by.type) = and(by)

This generally restricts argument to only one possible by word instance.

tenshi
  • 26,268
  • 8
  • 76
  • 90
1

Unary Operators

Well, you can have two successive identifiers if the final one is a unary operator:

a identifier1 - d

is parsed as

a.identifier1(d.unary_-)

Note that unary functions can be only ! ~ and -

Dynamic methods

If you don't mind to have your last variable name treated as a string (e.g. you have a way to later retrieve its value from a map or the interpreter itself), you can have the following using dynamics:

import scala.Dynamic
import scala.language.dynamics

class I2
class Res(i: I2) extends Dynamic {
  def selectDynamic(obj: String): Unit = {
      println(obj)
    }
}
class V1 { def i1(i2: I2): Res= new Res(i2) }
val v1 = new V1
val i2 = new I2

v1 i1 i2 v2

results in the string "v2".

Community
  • 1
  • 1
Mikaël Mayer
  • 10,425
  • 6
  • 64
  • 101