blob: 32ade66d48ab24b898b258cd580082ddb11d1db1 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.daffodil.processors.parsers
import scala.reflect.macros.blackbox.Context
object PointOfUncertaintyMacros {
/**
* For creating a point of uncertainty and ensuring that it is cleaned up
* appropriately. This allows the use of standard scala-style syntax to
* create and use points of uncertainties, while avoiding allocating
* functions. Thus, this allows something like this:
*
* pstate.withPointOfUncertainty { pou =>
* // code that implements parsing, and may potentially reset to,
* // discard, or resolve this pou variable
* }
*
* Upon completion of the block, if the new point of uncertainty has not been
* discarded, reset to, or resovled, this pou will then be discarded,
* ensuring that Marks are always cleaned up appropriately.
*/
def withPointOfUncertainty[A, B](c: Context)(pouID: c.Expr[String], context: c.Tree)(func: c.Expr[A => B]) = {
import c.universe._
val state = TermName(c.freshName("state"))
val id = TermName(c.freshName("id"))
val pou = TermName(c.freshName("pou"))
val ctx = TermName(c.freshName("context"))
func.tree match {
// The func tree is something like (param => body), where param is the
// name the caller wants to name the point of uncertainty mark, and body
// is the code that uses this point of uncertainty. We transform this
// code to create a new point of uncertainty, evaluate the body, and then
// use a finally block to ensure we discard the PoU if it has not been
// resolved.
case q"""($param: $_) => $body""" => {
// The "body" uses the "param" variable name, and scala doesn't like it
// if we just create a new variable with this name, with a very unclear
// error message. Instead, we need to create a "fresh" variable name to
// hold the new point of uncertainty, and then transform the body so
// that all instances of "param" are replaced with our new fresh name.
val transformer = new Transformer {
override def transform(tree: Tree): Tree = tree match {
case Ident(`param`) => Ident(pou)
case other => super.transform(other)
}
}
val newBody = transformer.transform(c.untypecheck(body))
q"""{
// ensure we do not evaluate passed in parameters more than once
val $state = ${c.prefix}
val $id = $pouID
val $ctx = $context
// create our new point of uncertainty
val $pou = $state.createPointOfUncertainty($id, $ctx)
try {
// evalute the transformed body, which can use this new point of uncertainty
$newBody
} finally {
// if the pou still exists at this point, then simply discard it.
// This is likely because an exception occurred, or just because we
// do not require that parsers discard PoUs, with the understanding
// that it will always happen here
if (!$state.isPointOfUncertaintyResolved($pou)) {
$state.discardPointOfUncertainty($pou)
}
}
}"""
}
}
}
}