Generate glue code using Scala macros
The problem
Regular scala functions and PHP functions do not fit well which each other:
- Scala functions are strictly typed while PHP functions are usually untyped or “not completely” typed.
- Since PHP 5 it is possible to declare type hints for function parameters, but this only applies for classes, interfaces, arrays and callables. There is no way to distinguished a string parameter from an integer parameter.
- PHP functions support call-by references, while scala functions usually take immutable parameters.
- Of course it is possible to use “holder” objects for results, but his is not considered clean code.
- In scala the parameter match of a function call is always evaluated at compile time, in PHP it is evaluated at interpretation time.
Example
At the moment the PHP function trait in JBJ looks like this:
1 trait PFunction {
2 def name: NamespaceName
3
4 def parameters: Seq[PParamDef]
5
6 def call(parameters: List[PParam])(implicit callerCtx: Context): PAny
7 }
name
is the name of the function as it should be used in a function call. Since the function be defined in a namespace this is aNamespaceName
instead of just a string.parameters
list of parameter definitions including type hints if presentcall
the actual invocation of the function with concrete parameters.PParam
serves as a wrapper if the parameter was given as concrete value of by reference.
Consider one wants to implement the buildin function strncmp. It would be quite inconvenient to implement the PFunction
trait itself, instead one would like to just implement it as regular scala function like this
1 object StringFunctions {
2 def strncmp(str1: String, str2: String, len: Int): Int = {
3 str1.take(len).compareTo(str2.take(len))
4 }
5 }
First we need a generic helper to extract parameters with PHP compatible error handling.
1 trait ParameterAdapter[T] {
2 def adapt(parameters: Iterator[PParam])(implicit ctx: Context): T
3 }
parameters
is the iterator of the parameters that need to be processed. The adapter is supposed to take one or more parameters from the iterator (or throw some error)- The result is a tuple of the extracted scala value and a list of remaining parameters to be forwarded to the next
ParameterAdapter
.
Now we need some glue code to convert this function to a PFunction
usable by the interpreted PHP, which might look like this:
1 new PFunction {
2 def name = NamespaceName("strncmp")
3
4 def parameters = Seq(
5 AdaptedParamDef("str1", None, false, None),
6 AdaptedParamDef("str2", None, false, None),
7 AdaptedParamDef("len", None, false, None)
8 )
9
10 private val errorHandlers = ParameterAdapter.errorHandlers("strncmp", ParameterMode.EXACTLY_WARN, 3, 3, false, NullVal)
11
12 private val adapters = (
13 RelaxedParamterAdapter(0, StringConverter, errorHandlers),
14 RelaxedParamterAdapter(1, StringConverter, errorHandlers),
15 RelaxedParamterAdapter(2, IntConverter, errorHandlers)
16 )
17
18 def doCall(parameters: List[PParam])(implicit callerCtx: Context): PAny = {
19 if (parameters.size > 3)
20 errorHandlers.tooManyParameters(parameters.size)
21
22 val parametersIt = parameters.iterator
23 val param1 = adapters._1.adapt(parametersIt)
24 val param2 = adapters._2.adapt(parametersIt)
25 val param3 = adapters._3.adapt(parametersIt)
26 val result = StringFunctions.strncmp(param1, param2, param3)
27 PValConverter.toJbj(result)(callerCtx)
28 }
29 }
Of course it is extremely cumbersome to write all this by hand for each function. I.e. one would like to have a function like this:
1 def generatePFunctions(instance: Any): Seq[PFunction]
instance
is the instance/object to be scanned for scala functions, which should be decorated by aPFunction
as shown above.
The usual Java-approach would be to “generate” this kind of glue-code via reflection and (potentially) AOP. In Scala it is possible to use type-safe marcos for this kind of work.