Xtext——9. Xtend之表达式

函数中最为重要的部分当然是其实现,Xtend中要么是一个单独的块表达式或丰富的字符串表达式。

常量(Literals)

常量是固定不变的值。支持字符串常量、整型常量、布尔型常量、null常量,以及Java类型常量。

字符串常量

字符创常量是一个有效的表达式,其返回给性值的java.lang.String的一个实例。

  • 'Hello World !'
  • "Hello World !"
  • "Hello 
            World !"

整型常量

一个整型常量会创建一个int,不存在有符号的int,如果在int常量前边加一个减号,将会将其看做是有一个实参(正的int常量)的一元操作符。

  • 42
  • 234254

布尔常量

有两个布尔常量,true和false对应于Java中的布尔类型。

  • true
  • false

null常量

null指针常量为null,跟Java中的类似,是所有的引用类型的成员。

  • null

类型常量

类型常量是指使用关键字typeof

  • typeof(java.lang.String) which yields java.lang.String.class

类型转换

同Java中的类型转换有些类似,不过其可读性更强。类型转换的绑定强度比其他操作符要强,不过比feature调用要弱。

类型转换规则见Java语言规范。

语法

XCastedExpression:
    Expression 'as' JvmTypeReference;

示例

  • my.foo as MyType
  • (1 + 3 * 5 * (- 23)) as BigInteger

中缀操作符/操作符重载

有几个常用的预先定义的中缀操作符。与Java不同的是,操作符并不局限于特定的类型,操作符到方法的映射允许用户为任意类型重新定义操作符,仅需实现相应的签名即可。下面定义了操作符以及对应的Java方法签名/表达式。

e1 += e2 e1._operator_add(e2)
   
e1 || e2 e1._operator_or(e2)
   
e1 && e2 e1._operator_and(e2)
   
e1 == e2 e1._operator_equals(e2)
e1 != e2 e1._operator_notEquals(e2)
e1 < e2 e1._operator_lessThan(e2)
e1 > e2 e1._operator_greaterThan(e2)
e1 <= e2 e1._operator_lessEqualsThan(e2)
e1 >= e2 e1._operator_greaterEqualsThan(e2)
   
e1 -> e2 e1._operator_mappedTo(e2)
e1 .. e2 e1._operator_upTo(e2)
   
e1 + e2 e1._operator_plus(e2)
e1 - e2 e1._operator_minus(e2)
   
e1 * e2 e1._operator_multiply(e2)
e1 / e2 e1._operator_divide(e2)
e1 % e2 e1._operator_modulo(e2)
e1 ** e2 e1._operator_power(e2)
   
! e1 e1._operator_not()
- e1 e1._operator_minus()
   
上边的表定义了操作符优先级(升序排列),空行用来分隔优先级。跟纯粹的赋值操作符=一样,赋值操作符+=是从右向左结合的,a = b = c在执行时为a = (b = c),所有其他的操作符是从左向右结合的。可以用括弧来改变默认的处理和结合。

简化布尔操作符

如果操作符||和&&的左操作数为布尔类型,采用简化模式来进行计算。也就是说,在下面两种情况下之一,不会对右操作数进行计算:

  1. 如果||的左操作数计算结果为true
  2. 如果&&的左操作数计算结果为false

示例

  • my.foo = 23
  • myList += 23
  • x > 23 && y < 23
  • x && y || z
  • 1 + 3 * 5 * (- 23)
  • !(x)
  • my.foo = 23
  • my.foo = 23

赋值

可以使用=操作符对本地变量重新进行赋值。同样,可以使用该操作符来设置属性。给定下面的表达式:

myObj.myProperty = "foo"

编译器在类型myObj中查找是否有可以访问的命名为myProperty的域。如果有的话,就转换成下面的Java代码:

myObj.myProperty = "foo";

记住,Xtend中万物皆表达式,并且会返回某些东西。如果是简单的赋值,返回的值相应的Java表达式的返回值,也就是被赋的值。

如果左操作数类型没有相应的可访问的域,会查找名为setMyProperty(OneArg)的方法(JavaBeans的setter方法)。该方法应该可以接受右手边的操作数作为其类型的实参。返回值setter方法的返回值(通常为null), 结果,编译器将其转换为:

myObj.setMyProperty("foo")

Feature Call

feature call在当前的表达式作用域中用来调用对象的成员,例如域或方法,同时也可以引用帮内变量和参数。

语法

下面的代码片段是真正的Xtext规则的一个简化,所涵盖的不仅仅是具体的语法。

FeatureCall :
    ID |
    Expression ('.' ID ('(' Expression (',' Expression)* ')')?)*

访问属性(Property Access)

feature call are directly translated to their Java equivalent with the exception, that for calls to properties an equivalent rule as described in section Xtend_Expressions_PropertyAssignment applies. That is, for the following expression

myObj.myProperty

the compiler first looks for an accessible field in the type of myObj. If no such field exists it looks for a method called myProperty() before it looks for the getter methods getMyProperty(). If none of these members can be found the expression is unbound and a compiliation error is thrown.

隐式'this'变量

如果当前作用域中含有一个名为this的变量,编译器将会使得在该作用域中其成员均可访问。如果

this.myProperty

是一个有效的表达式

myProperty

is valid as well and is equivalent, as long as there is no local variable 'myProperty' on the scope, which would have higher precedence.

非安全Feature Call

Checking for null references can make code very unreadable. In many situations it is ok for an expression to return null if a receiver was null. Xtend supports the safe navigation operator ?. to make such code more readable.

Instead of writing

if ( myRef != null ) myRef.doStuff()

one can write

myRef?.doStuff()

Constructor Call

Construction of objects is done by invoking Java constructors. The syntax is exactly as in Java.

示例

  • new String()

  • new java.util.ArrayList<java.math.BigDecimal>()

语法

XConstructorCall:
  'new' QualifiedName 
          ('<' JvmTypeArgument (',' JvmTypeArgument)* '>')? 
          ('('(XExpression (',' XExpression)*)?')')?;

Closures

A closure is a literal that defines an anonymous function. A closure also captures the current scope, so that any final variables and parameters visible at construction time can be referred to in the closure's expression.

语法

XClosure:
    '[' ( JvmFormalParameter (',' JvmFormalParameter)* )? 
        '|' XExpression ']';

The surrounding square brackets are optional if the closure is the single argument of a method invocation. That is you can write

myList.find(e|e.name==null)

而不是

myList.find([e|e.name==null])

But in all other cases the square brackets are mandatory:

val func = [String s| s.length>3]

Typing

Closures are expressions which produce function objects. The type is a function type, consisting of the types of the parameters as well as the return type. The return type is never specified explicitly but is always inferred from the expression. The parameter types can be inferred if the closure is used in a context where this is possible.

For instance, given the following Java method signature:

public T <T>getFirst(List<T> list, Function0<T,Boolean> predicate)

the type of the parameter can be inferred. Which allows users to write:

arrayList( "Foo""Bar" ).findFirst( e | e == "Bar" )

instead of

arrayList( "Foo""Bar" ).findFirst( String e | e == "Bar" )

Function Mapping

An Xtend closure is a Java object of one of the Function interfaces shipped with the runtime library of Xtend. There is an interface for each number of parameters (current maximum is six parameters). The names of the interfaces are

  • Function0 for zero parameters,
  • Function1 for one parameters,
  • Function2 for two parameters,
  • ...
  • Function6 for six parameters,

In order to allow seamless integration with existing Java libraries such as the JDK or Google Guava (formerly known as Google Collect) closures are auto coerced to expected types if those types declare only one method (methods from java.lang.Object don't count).

As a result given the method java.util.Collections.sort(List<T>, Comparator<? super T>) is available as an extension method, it can be invoked like this

newArrayList( 'aaa''bb''c' ).sort(
    e1, e2 | if ( e1.length > e2.length ) {
                -1 
             } else if ( e1.length < e2.length ) { 
                1
             } else { 
                0
             })

示例

  • [ | "foo" ]   // closure without parameters
  • [ String s | s.toUpperCase() ] // explicit argument type
  • [ a,b,a | a+b+c ] // inferred argument types

if表达式

An if expression is used to choose two different values based on a predicate. While it has the syntax of Java's if statement it behaves like Java's ternary operator (predicate ? thenPart : elsePart), i.e. it is an expression that returns a value. Consequently, you can use if expressions deeply nested within expressions.

语法

XIfExpression:
    'if' '(' XExpression ')'
        XExpression
    ('else' XExpression)?;

An expression if (p) e1 else e2 results in either the value e1 or e2 depending on whether the predicatep evaluates to true or false. The else part is optional which is a shorthand for else null. That means

if (foo) x

is the a short hand for

if (foo) x else null

Typing

The type of an if expression is calculated by the return types T1 and T2 of the two expression e1 and e2. It uses the rules defined in section Xbase_Types_CommonSuperType.

示例

  • if (isFoo) this else that
  • if (isFoo) { this } else if (thatFoo) { that } else { other }
  • if (isFoo) this

switch表达式

The switch expression is a bit different from Java's. First, there is no fall through which means only one case is evaluated at most. Second, the use of switch is not limited to certain values but can be used for any object reference instead. For a switch expression

switch e {
    case e1 : er1
    case e2 : er2
    ...
    case en : ern
    default : er
}

the main expression e is evaluated first and then each case sequentially. If the switch expression contains a variable declaration using the syntax known from section Xtend_Expressions_ForLoop, the value is bound to the given name. Expressions of type java.lang.Boolean or boolean are not allowed in a switch expression.

The guard of each case clause is evaluated until the switch value equals the result of the case's guard expression or if the case's guard expression evaluates to true. Then the right hand expression of the case evaluated and the result is returned.

If none of the guards matches the default expression is evaluated an returned. If no default expression is specified the expression evaluates to null.

Example:

switch myString {
    case myString.length>5 : 'a long string.'
    case 'foo' : 'It's a foo.'
    default : '
It's a short non-foo string.'
}

Type guards

In addition to the case guards one can add a so called Type Guard which is syntactically just a type reference preceding the than optional case keyword. The compiler will use that type for the switch expression in subsequent expressions. Example:

var Object x = ...;
switch x {
    String case x.length()>0 : x.length()
    List<?> : x.size()
    default : -1
}

Only if the switch value passes a type guard, i.e. an instanceof operation returns true, the case's guard expression is executed using the same semantics explained in previously. If the switch expression contains an explicit declaration of a local variable or the expression references a local variable, the type guard acts like a cast, that is all references to the switch value will be of the type specified in the type guard.

Typing

The return type of a switch expression is computed using the rules defined in section Xbase_Types_CommonSuperType. The set of types from which the common super type is computed corresponds to the types of each case's result expression. In case a switch expression's type is computed using the expected type from the context, it is sufficient to return the expected type if all case branches types conform to the expected type.

示例

  • switch foo {
        Entity : foo.superType.name
        Datatype : foo.name
        default : throw new IllegalStateException
    }

  • switch x : foo.bar.complicated('hello',42) {
        case "hello42" : ...
        case x.length<2 : ...
        default : ....
    }

语法

XSwitchExpression:
    'switch' (ID ':')? XExpression '{'
        XCasePart+
        ('default' ':' XExpression))?
    '}';

XCasePart:
    JvmTypeReference? ('case' XExpression)? ':' XExpression );
}

变量声明

Variable declarations are only allowed within blocks. They are visible in any subsequent expressions in the block. Although overriding or shadowing variables from outer scopes is allowed, it is usually only used to overload the variable name 'this', in order to subsequently access an object's features in an unqualified manner.

A variable declaration starting with the keyword val denotes a so called value, which is essentially a final (i.e. unsettable) variable. In rare cases, one needs to update the value of a reference. In such situations the variable needs to be declared with the keyword var, which stands for 'variable'.

A typical example for using var is a counter in a loop.

{
    val max = 100
    var i = 0
    while (i > max) {
        println("Hi there!")
        i = i +1
    }
}

Variables declared outside a closure using the var keyword are not accessible from within a closure.

语法

XVariableDeclaration:
    ('val' | 'var') JvmTypeReference? ID '=' XExpression;

Typing

The return type of a variable declaration expression is always void. The type of the variable itself can either be explicitly declared or be inferred from the right hand side expression. Here is an example for an explicitly declared type:

var List<String> msg = new ArrayList<String>();

In such cases, the right hand expression's type must  conform  to the type on the left hand side.

Alternatively the type can be left out and will be inferred from the initialization expression:

var msg = new ArrayList<String>(); // -> type ArrayList<String>

The block expression allows to have imperative code sequences. It consists of a sequence of expressions, and returns the value of the last expression. The return type of a block is also the type of the last expression. Empty blocks return null. Variable declarations are only allowed within blocks and cannot be used as a block's last expression.

A block expression is surrounded by curly braces and contains at least one expression. It can optionally be terminated by a semicolon.

示例

{
    doSideEffect("foo")
    result
}

{
    var x = greeting();
    if (x.equals("Hello ")) {
        x+"World!"
    } else {
        x;
    }
}

语法

XBlockExpression:
    '{'
        (XExpressionInsideBlock ';'?)*
    '}';

for循环

The for loop for (T1 variable : iterableOfT1) expression is used to execute a certain expression for each element of an array of an instance of java.lang.Iterable. The local variable is final, hence canot be updated.

The return type of a for loop is void. The type of the local variable can be left out. In that case it is inferred from the type of the array or java.lang.Iterable returned by the iterable expression.

  • for (String s : myStrings) {
        doSideEffect(s);
    }

  • for (s : myStrings)
        doSideEffect(s)

语法

XForExpression:
    'for' '(' JvmFormalParameter ':' XExpression ')' 
        XExpression
    ;

while循环

A while loop while (predicate) expression is used to execute a certain expression unless the predicate is evaluated to false. The return type of a while loop is void.

语法

XWhileExpression:
    'while' '(' predicate=XExpression ')'
        body=XExpression;

示例

  • while (true) {
        doSideEffect("foo");
    }

  • while ( ( i = i + 1 ) < max ) 
        doSideEffect( "foo" )

do-while循环

A do-while loop do expression while (predicate) is used to execute a certain expression unless the predicate is evaluated to false. The difference to the while loop is that the execution starts by executing the block once before evaluating the predicate for the first time. The return type of a do-while loop is void.

语法

XDoWhileExpression:
    'do'
        body=XExpression
    'while' '(' predicate=XExpression ')';

示例

  • do {
        doSideEffect("foo");
    } while (true)

  • do doSideEffect("foo") while ((i=i+1)<max)

return表达式

Although an explicit return is often not necessary, it is supported. In a closure for instance a return expression is always implied if the expression itself is not of type void. Anyway you can make it explicit:

listOfStrings.map(e| {
    if (e==null) 
        return "NULL"
    e.toUpperCase
})

抛出异常

Like in Java it is possible to throw java.lang.Throwable. The syntax is exactly the same as in Java.

{
    ...
    if (myList.isEmpty)
        throw new IllegalArgumentException("the list must not be empty")
    ...
}

try、catch、finally

The try-catch-finally expression is used to handle exceptional situations. You are not forced to declare checked exceptions, if you don't catch checked exceptions they are rethrown in a wrapping runtime exception. Other than that the syntax again is like the one known from Java.

try {
    throw new RuntimeException()
} catch (NullPointerException e) {
    // handle e
} finally {
    // do stuff
}

Rich Strings

Rich Strings allow for readable string concatenation, which is the main thing you do when writing a code generator. Let's have a look at an example of how a typical function with template expressions looks like:

toClass(Entity e) '''
  package «e.packageName»;

  «placeImports»

  public class «e.name» «IF e.extends!=null»extends «e.extends»«ENDIF» {
    «FOR e.members»
      «member.toMember»
    «ENDFOR»
  }
'
''

If you are familiar with Xpand, you'll notice that it is exactly the same syntax. The difference is, that the template syntax is actually an expression, which means it can occur everywhere where an expression is expected. For instance in conjunction the powerful switch expression:

toMember(Member m) {
    switch m {
        Field : '''private «m.type» «m.name» ;'''
        Method case isAbstract : ''' abstract «...'''
        Method : ''' ..... '''
    }
}

Conditions in Rich Strings

There is a special IF to be used within rich strings which is identical in syntax and meaning to the old IFfrom Xpand. Note that you could also use the if expression, but since it has not an explicit terminal token, it is not as readable in that context.

Loops in Rich Strings

Also the FOR statement is available and can only be used in the context of a rich string. It also supports theSEPARATOR from Xpand. In addition, a BEFORE expression can be defined that is only evaluated if the loop is at least evaluated once before the very first iteration. Consequently AFTER is evaluated after the last iteration if there is any element.

Typing

The rich string is translated to an efficient string concatenation and the return type of a rich string isCharSequence which allows room for efficient implementation.

White Space Handling

One of the key features of rich strings is the smart handling of white space in the template output. The white space is not written into the output data structure as is but preprocessed. This allows for readable templates as well as nicely formatted output. This can be achieved by applying three simple rules when the rich string is evaluated.

  1. An evaluated rich string as part of another string will be prefixed with the current indentation of the caller before it is inserted into the result.
  2. Indentation in the template that is relative to a control structure will not be propagated to the output string. A control structure is a FOR-loop or a condition (IF) as well as the opening and closing marks of the rich string itself. The indentation is considered to be relative to such a control structure if the previous line ends with a control structure followed by optional white space. The amount of white space is not taken into account but the delta to the other lines.
  3. Lines that do not contain any static text which is not white space but do contain control structures or invocations of other templates which evaluate to an empty string, will not appear in the output.

The behavior is best described with a set of examples. The following table assumes a data structure of nested nodes.

class Template {
    print(Node n) '''
        node «n.name» {}
    '
''
}

node NodeName{}

The indentation before node «n.name» will be skipped as it is relative to the opening mark of the rich string and thereby not considered to be relevant for the output but only for readability of the template itself.

class Template {
    print(Node n) '''
        node «n.name» {
            «IF hasChildren»
                «n.children*.print»
            «ENDIF»
        }
    '
''
}

node Parent{
    node FirstChild {
    }
    node SecondChild {
        node Leaf {
        }
    }
}

As in the previous example, there is no indentation on the root level for the same reason. The first nesting level has only one indentation level in the output. This is derived from the indentation of the IF hasChildrencondition in the template which is nested in the node. The additional nesting of the recursive invocationchildren*.print is not visible in the output as it is relative the the surrounding control structure. The line with IF and ENDIF contain only control structures thus they are skipped in the output. Note the additional indentation of the node Leaf which happens due to the first rule: Indentation is propagated to called templates.

转载请表明出处:

英文原文地址:http://www.eclipse.org/Xtext/documentation/2_1_0/03-Xtend_Expressions.php

  • D

你可能感兴趣的:(java,String,Parameters,Types,structure,concatenation)