Showing posts with label scala. Show all posts
Showing posts with label scala. Show all posts

Tuesday, April 10, 2012

Autocompletion support for Scala's combinator parsers

Recently, I came to revisit Scala a bit more in depth -- compared to the odd script or hack I did before. Now, things at work change a bit: some green-field stuff to be done, even calling for some kind of DSL -- ok, maybe not really calling, really loudly but close enough to warrant spending some quality playing-time with my favorite "next great programming language".
So I threw together some combinator parsers which was some fun in itself and most of it felt very good. Except there was no autocompletion. The envisioned DSL should, among other things, navigate a mildly complex domain model. But, in contrast to a generic expression language, typos in property-names should be caught at the syntax level, and possible completions should be displayed when the user types the start of a property (or a dot). What I wanted to do was something like the made-up example below:
Of course, in the real DSL, the properties would not be hardcoded but reflectively collected from the model. The missing key-feature here is the CompletingParser mix-in: It doesn't come with the standard Scala library and I didn't easily find a nice solution readymade. So, that looked like a good opportunity to take Scala's extensibility for a test-drive that's both, interesting and easily within a Scala-beginner's reach
The basic ideas are
  • override the Parser class' alternative combinator "|" to make it collect possible completions in the ParseResult instead of just returning a Failure
  • override the implicit factory method for string literals to produce an alternative literal parser which differentiates between an unexpected and a missing character
As it turns out, in the standard implementation, the | combinator just delegates to ParseResult's append method which normally discards all results but the successful one, or, failing that, returns the failure which consumed most of the input stream. So we just need to replace the Failure parse result with a version that's able to keep track of possible completions, rather smellily named MissingCompletionOrFailure. This plan led me to the following little trait:
Now a simple command-loop to take our work for an interactive test-drive looks like this:

Monday, June 04, 2007

Tapestry Components in Scala

Recently, I became interested in Scala: Multiple inheritance, good support for functional programming, a nice syntax, to mention the greatest highlights.

As a first exercise, I tried to write some Tapestry-4 components in Scala to gain some real-world experience, and to see whether Tapestry's pretty extensive use of bytecode-generation would somehow break Scala's Java-compatibility. I was pretty pleased with the results:

The following is a trait to add authorisation support to arbitrary (form-)components. It controls whether to render the component into which it is mixed in and binds its disabled parameter.

package ch.marcus.components;

import org.apache.tapestry._

trait AccessControlled extends AbstractComponent {
object binding extends IBinding {
def getObject = Boolean.box(getIdPath.contains("readOnly")) // TODO: delegate to ac-service
def getObject(c: Class) = getObject
def getDescription = "AccessControl disabled-binding"
def setObject(o: Object) = {}
def isInvariant = false;
def getLocation = null;
}

override def finishLoad = {
setBinding("disabled", binding )
}

/**Derived components delegate to the desired renderComponent method here: */
def renderAccessControlled ( w: IMarkupWriter, c: IRequestCycle )

override def renderComponent( w: IMarkupWriter, c: IRequestCycle ) {
if ( ! getIdPath.contains("invisible") ) // TODO: delegate to ac-service
renderAccessControlled(w,c);
}
}


Here is how to derive an access-controlled version of the standard TextField component by inheriting from TextField and the trait we just defined:


package ch.marcus.components;

import org.apache.tapestry._
import org.apache.tapestry.form._

abstract class AuthorisedTextField extends TextField with AccessControlled {
//duplicate from scala.Object to make Tap's class-enhancer happy
@remote override def $tag(): Int = 0

override def renderAccessControlled( w:IMarkupWriter, c: IRequestCycle )
= super[TextField].renderComponent(w,c);

}

The only gotcha is the override of $tag which seems to be necessary due to a glitch in Tapestries class-enhancer. Oh, and for components with a specification(.jwc)-file this must be copied for the derived component. This is slightly annoying, but unnecessary for component that consequently use annotations (specless components).

This is how a Tapestry page looks like in Scala:

package ch.marcus.pages
import org.apache.tapestry.annotations._
import scala.reflect._

abstract class Home extends ScalaPage {
val text = "Hello, this is Scala."

@Persist
def getMbr : String
def setMbr( m:String )

def onSubmit = {
setMbr (getMbr + "x")
}
}

Note, how clean the code looks without all the syntactic noise you'd have to add in Java and how nice the Tapestry annotations (Persist) work with Scala.