limbo overview

7
1 Limbo Overview Wilfred Springer 1. Introduction Limbo is the code name of an expression language used in Preon 1 . It allows you to evaluate expressions on a certain context. The expression language is fairly simple; it supports basic logical and arithmetic operators, and it supports attribute references and item references. Example 1. Simple Limbo Examples 3 * 4 3 * person.age 2 ^ group.persons[3].age person.age >= 12 ... and that's probably where the correspondence with the JSP Expression Language and other expression languages ends. Because there is a big difference with those languages. And that difference is not based on notation (syntax and grammar), but on the way you apply Limbo. Here are some of the differences of Limbo: Limbo supports early binding. That is, it will validate the correctness of the expression before it actually validates it. So, when you build the expression, you know if it is an expression that can be evaluated at runtime within a certain context. Limbo allows you to bind to any context, not just state exposed through bean properties or private fields. The Limbo API allows you to export the expression as a natural language based human readable snippet of text. Limbo originates from a project to capture dependencies in different fields in a binary media format in an unambiguous way. Using Limbo, that project is capable of generating a human readable description of the encoding format. Expressions like the ones mentioned in Example 1, “Simple Limbo Examples” could be rendered into this: 3 times 4 3 times the age of a person 2 to the power of the age of the third person in the group the age of the person is greater than or equal to 2 This article is a tutorial on Limbo. It will explain the language itself, but it will also explain how you weave it into your own project. 1 Limbo used to be a separate project, however when moving to Preon 2.0, knowing that Limbo is only used inside Preon, it did not seem to make an aweful lot of sense to keep it a separate project, especially with some refactoring that had to be done.

Upload: wilfred-springer

Post on 08-Apr-2015

1.640 views

Category:

Documents


0 download

DESCRIPTION

Limbo is the expression language used by Preon. It features early binding and the ability to generate a human-readable description of the expression.

TRANSCRIPT

Page 1: Limbo Overview

1

Limbo OverviewWilfred Springer

1. Introduction

Limbo is the code name of an expression language used in Preon1. It allows you to evaluate expressions on acertain context. The expression language is fairly simple; it supports basic logical and arithmetic operators, and itsupports attribute references and item references.

Example 1. Simple Limbo Examples

3 * 43 * person.age2 ^ group.persons[3].ageperson.age >= 12

... and that's probably where the correspondence with the JSP Expression Language and other expressionlanguages ends. Because there is a big difference with those languages. And that difference is not based onnotation (syntax and grammar), but on the way you apply Limbo.

Here are some of the differences of Limbo:

• Limbo supports early binding. That is, it will validate the correctness of the expression before it actually validatesit. So, when you build the expression, you know if it is an expression that can be evaluated at runtime within acertain context.

• Limbo allows you to bind to any context, not just state exposed through bean properties or private fields.

• The Limbo API allows you to export the expression as a natural language based human readable snippet of text.

Limbo originates from a project to capture dependencies in different fields in a binary media format in anunambiguous way. Using Limbo, that project is capable of generating a human readable description of theencoding format. Expressions like the ones mentioned in Example 1, “Simple Limbo Examples” could be renderedinto this:

3 times 43 times the age of a person2 to the power of the age of the third person in the groupthe age of the person is greater than or equal to 2

This article is a tutorial on Limbo. It will explain the language itself, but it will also explain how you weave it intoyour own project.

1Limbo used to be a separate project, however when moving to Preon 2.0, knowing that Limbo is only used inside Preon, it did not seem tomake an aweful lot of sense to keep it a separate project, especially with some refactoring that had to be done.

Page 2: Limbo Overview

Limbo Overview

2

2. The Language

The first thing you need to know about Limbo is that it is an expression language, intended to execute on acertain context. The context might be a fairly complicated data structure, or it may not. Two things are for sure:Limbo only allows you to refer to things by name or their index, and Limbo does not allow you to define complexstructures in the language itself.

Let's start with the most commons example: let's look at an simple example object-based data structure. InFigure 1, “Simple data model”, Persons have a name and an age, a father and mother, and optionally somechildren of their own.

Figure 1. Simple data model

Based on this, here are some example Limbo expressions, given that the context is a person.

age // the age of the current personage >= 35 // the age is greater than or equal to 35father.age // the age of the fatherfather.age + mother.age >= 70 // the sum of the age of the father and // the age of the mother is greater than //or equal to 70children[0].age < 7

There are a couple of lessons to learn from the example above. First of all, valid Limbo expressions always evaluateto boolean values or integer values. You wonder why? Well, simply because we did not need anything else. Thewhole purpose of Limbo is to express arithmetical and logical relationships between things. Producing text simplyhas never been a requirement.

The next thing to notice is that the expressions do not look all that different than Java expressions2. It supportsarithmetic operators ('+', '-', '/', '*', '^'), comparison operators ('>', '<', '>=', '<=', '==') and logical operators ('&&','||', '!').

The third thing to notice is that you can refer to attributes as well as items. (In that sense, Limbo is comparable toPython.) In this example, that works out quite well. Objects also have attributes and some types of objects mighthave items. However, it is important to remember that Limbo does not bind to objects only. Limbo is able to bindto any model exposed as 'things' with attributes and items.

Integer literals can be expressed as decimals (1254), hexadecimals (0xFF, 0xff, etc.) or as binary numbers(0b10101011, 0b1001, etc.). Limbo ignores all whitespace.

2 Notice that I say it does not look all that different. It actually more different than you might expect. More on that somewhere else.

Page 3: Limbo Overview

Limbo Overview

3

3. The API

3.1. Getting Started

Let's start with a simple example first:

Example 2. Simple expression

Expression<Integer, Person> doubleAge = Expressions .from(Person.class) .toInteger("age * 2");Person wilfred = new Person();wilfred.name = "Wilfred";wilfred.age = 35;assert 70 == doubleAge.eval(wilfred);

In the first line, we build the Expression instance. Since the expression is based on a Person object, thefrom(...) method takes Person class reference. After that, we specify that we expect an integer result, andpass the expression at the same time. (The builder methods actually have a couple of other options, but we willleave that out for now.)

Once the Expression has been built, evaluating is simply calling eval(...) on the expression, passing in thePerson instance. And - like you could have expected - the result is 70. Note there is no cast in order to compare to70, courtesy of the use of generics and auto unboxing.

3.2. The ReferenceContext

I said before that Limbo is capable of binding to anything capable of representing itself as 'things' with namedattributes and numbered items. Let me refine that: it is capable of binding to anything for which you canimplement a ReferenceContext. So, when in Example 2, “Simple expression” you passed in a Person class,under the hood, Limbo wrapped that inside a ReferenceContext.

Now, if you ever used an expression language like the JSP EL, then you are probably aware of a similar mechanismin that expression language. JSP EL has a VariableResolver. Your EL expression can be evaluated againstanything, as long as there is a VariableResolver capable of resolving the named things.

One of the differences between Limbo's ReferenceContext and JSP EL's VaribleResolver is that theReferenceContext is parameterized with type of context passed in at evaluation time. Typically, with JSP EL, youwill evaluate your expression against a context of of type java.lang.Object. The Java compiler will not be ableto verify if the subtype of java.lang.Object you pass in is actually something against which you can evaluatethe expression.

If you are creating an expression in Limbo, you will always need to construct that expression parameterizedReferenceContext, in which the type parameter is the type of object on which you can apply the expression.So if you have an expression you want to evaluate against an instance of Person, you need to construct theExpression based on a ReferenceContext<Person>.

Page 4: Limbo Overview

Limbo Overview

4

Now, you probably wonder why all of that is relevant. What's the purpose of adding the extra complexity of havingto deal with parameterized types. After all, the JSP EL works fine with a non-parameterized VariableResolver,and expressions accepting java.lang.Object instances.

The real reason for this is that Limbo is capable of early binding. ReferenceContext implementations can makeguarantees on the validity of references used in the expression. Which means that the Expression based on thatReferenceContext can guarantee it will be capable of acting upon a certain context.

Example 3, “ReferenceContext and References” shows how you build references using a ReferenceContext.In this case, the data model to which we bind is a Java version of the object model outlined in Figure 1, “Simpledata model”. The ClassReferenceContext used in this case not only allows you to build references to datacontained by an instance of that class, but will also check for the existence of those attributes. Any attempt toreference something that is not defined by the class will generate a BindingException.

Example 3. ReferenceContext and References

ReferenceContext<Person> context = new ClassReferenceContext<Person>(Person.class);Reference<Person> personsName = context.selectAttribute("name");Reference<Person> fathersName = context.selectAttribute("father").selectAttribute("name");Person wilfred = new Person();wilfred.name = "Wilfred";wilfred.age = 35;Person levi = new Person();levi.name = "Levi";levi.age = 8;levi.father = wilfred;assert "Levi".equals(personsName.resolve(levi));assert "Wilfred".equals(fathersName.resolve(levi));assert "Wilfred".equals(personsName.resolve(wilfred));// ... and this will throw a BindingExceptionReference<Person> gender = context.selectAttribute("gender");

3.3. Natural Language Description

Early validation is not the only benefit we gain from ReferenceContexts supporting early binding. Anotherbenefit is that we basically gather enough information to generate a fairly decent description of the Referencescreated.

In the example above, the fathersName reference will be printed as: "the name (a String) of the father (a Person)of a Person ". Now, this description might not be ideally suited in your case, but the way your reference is renderedis also determined by the ReferenceContext. You can basically render it any way you like, as long as you arewilling to go through the trouble of implementing your own ReferenceContext.

Page 5: Limbo Overview

Limbo Overview

5

4. Embedding Limbo

If you ever consider using Limbo, you will most likely do that for its abilities to provide early validation ofexpressions, and the ability to turn expressions into human-readable descriptions.

There is a problem here though. Limbo doesn't define a single ideal way of 'early binding' the expression to acontext. You may feel that binding an expression to private inner variables of an object makes perfect sense. Otherpeople will consider that to be malpractice, and require a way to have early validation of expressions bound togetters and setters. So where do you encode these policies?

Similarly, Limbo also does not define a single ideal way to turn expressions into human readable language. Ofcourse, you will most likely benefit from Limbo's abilities to turn the main part of the expression into humanreadable text, but you will probably have a preference for deciding how references should be translated in humanreadable text. Like, do you want it to be like 'the age of the person', 'the value of the age property of a Personobject', or 'person.age'? Again, what do you implement in order to encode these policies?

The ReferenceContext is the answer to both of these questions. So basically, embedding Limbo in a context,normally starts by implementing a ReferenceContext. And, in all honesty, that's basically it. Once you start byimplementing a ReferenceContext, you will quickly run into having the need to implement various Referencesyourself, you so probably need to take a peek at that interface as well.

I currently can't tell you which ReferenceContexts and References you will want to implement. That will bebased totally on your own needs. However, I can give you some examples on the use of ReferenceContexts inPreon itself. That should give you a bit of a flavor on what you can do.

5. Limbo in Preon

5.1. BindingsContext

References in expressions used in Preon are not bound to properties. And although references typically resolveto values of (private) fields, the reverse doesn't hold. So, not every (private) field can be addressed by a referencein Preon. In fact, in general only fields that have been marked as 'bound' can be referenced from an expression.Other (private) fields are not even seen.

The reason behind this is fairly simple. Preon's objective is to make sure that you can always generatedocumentation on the encoded representation from the annotated data structure. If the 'specification' (datastructure + annotations) would contain references to fields that have been populated outside of Preon's control,then that description would have 'dangling' references; references that point to something of which we don't knowanything at all. So essentially, it would leave holes in the documentation.

That's where the BindingsContext comes into play. Almost all references in Preon are rooted in theBindingsContext. While constructing the Codec, Preon will construct a BindingsContext for every non-trivial class for which it needs a Codec. So it won't construct a BindingsContext for a Codec decoding a Date, oran Integer, but it will create an instance for your own homegrown class with a couple of 'bound' fields.

Now, if you closely examine the ReferenceContext's interface, then you will notice that it hasselectAttribute and selectItem methods. When creating a reference to the value of a bound field named'foobar', then what essentially is happening is that (in case of a named attribute), the selectAttribute is called

Page 6: Limbo Overview

Limbo Overview

6

with the name 'foobar'. The BindingsContext will return a Reference object that eventually will resolvecorrectly into the value of foobar.

Remember that Reference itself is not the end of it. A Reference can be used to determine a value at runtime,but it also offers the option of selecting other parts of the data structure by calling one of the Reference's ownselectAttribute or selectItem methods.

5.2. ImportSupportingObjectResolverContext

This one definitely deserves an explanation, even it were only because of its incredible long name. In the previoussection, I already alluded to the fact that not all references are references to bound fields. Preon uses more thanone ReferenceContext, and this one in particular is sometimes wrapped around an existing ReferenceContext inorder to make sure you can refer to constants.

So this is how it works. If you want to refer to constant values inside your expressions, then you will need to havea way to define those constants. Preon simply allows you to define these constants as you would normally do inJava. However, that does not automatically pull them into scope of your expressions. In order to 'import' theseconstant definitions, you place an @ImportStatic annotation on top of the class containing references to theseconstants.

The ImportSupportingObjectResolverContext will be instantiated by the ObjectCodecFactory for everyclass for which it creates a Codec, if and only if that class has the @ImportStatic annotation.

5.3. Index in Offset Expressions

When you use @BoundList, then Preon allows you to specify an offset attribute; that offset attribute can be anexpression. The expression allows you to calculate the starting position of an element, given a certain context.Question of course is which element?

It turns out that the offset attribute actually introduces a new variable, on top of all variables already in scope.That variable is the 'index'. You can express the offset of an element in terms of that index, by simply referring tothat variable.

The 'index' variable doesn't come to life just like that. Just like with all of the other examples shown before, itrequires a ReferenceContext to make sure the framework understands the existence of this variable. Thatparticular ReferenceContext is also responsible for generating a proper human readable description for thatreference in case the framework requires it.

6. Summary

Limbo originally started out as a project to have an expression language catering for the needs of Preon: it requiredan expression language with APIs for embedding it inside a context that required early binding, and an API forturning expressions into human readable text.

The ReferenceContext is one of the central abstractions for having early validation. From theReferenceContext, you will create References. Those References embody everything there is to know abouta reference, including information on how the reference should be rendered into a human-readable descriptivereference.

Page 7: Limbo Overview

Limbo Overview

7

Limbo was eventually folded back into Preon as the preon-el module, in order to ease migration to Preon 2.0.