单行注释使用`//`开头。例如
```java
// i am a comment
```
多行注释以`/*`开头,以`*/`结尾。例如
```java
/* comment */
/*
multiple line comment
*/
a/* comment */=1
/* the comment splits an expression */
```
```python
if a > b
return a
else
return b
```
或者
```kotlin
result = (if a > b {a} else {b})
```
>一个方法需要返回值,那么只要末尾一个值为表达式,Latte就会自动生成方法的return语句
详见[2.3.1 If 语句](#p2-3-1)
1.1.9 for循环
```swift
for item in list
println(item)
```
或者
```swift
for i in 0 until list.size
println(list[i])
```
详见[2.3.2 For 语句](#p2-3-2)
1.1.10 while循环
```swift
i = 0
while i < list.size
println(list[i++])
```
详见[2.3.3 While 语句](#p2-3-3)
1.1.11 范围
检查x是否在范围中
```kotlin
if x in 1 to y-1
print("OK")
```
从1到5进行循环
```swift
for x in 1 to 5
print(x)
```
详见[5.2 范围](#p5-2)
1.1.12 Lambda
```kotlin
list.stream.
filter{it.startsWith("A")}.
map{it.toUpperCase()}.
forEach{print(it)}
f = a -> 1 + a
f(2) /* 结果为 3 */
```
详见[4.2 高阶函数和Lambda](#p4-2)
```
#js
def method(a)
return a+1
```
将会被转换成如下JavaScript代码
```js
function method(a) {
return a + 1;
}
```
1.1.16 模式匹配
```scala
val (x,y) = Bean(1,2)
```
详见[5.9 解构](#p5-9)
```scala
o match
case Bean(a,b) => ...
case People(name, age) if age > 20 => ...
case _ => ...
```
详见[5.10 解构](#p5-10)
Latte保留了Java的8种基本类型。由于Latte是动态类型语言,并且可以在编译期和运行时自动装包和拆包,所以基本类型与包装类型可以认为没有差异。
八种基本类型:
```
int
long
float
double
short
byte
char
bool
```
其中`int/long/float/double/short/byte`为数字类型。
注意,与Java不同的是,Latte使用`bool`而非`boolean`。
Latte支持所有Java的运算符,并在其基础上有所扩展
对于数字的基本运算,其结果均为“精度较高的值”的类型,且最低为`int`型。例如:
```c#
r1 = (1 as long) + (2 as int) /* r1 是 long */
r2 = (1 as byte) + (2 as short) /* r2 是 int */
```
由于Latte可以任意转换基本类型,所以这么写也可以正常编译并运行:
```java
a:short = 1
a+=1 /* a == 2 */
a++ /* a == 3 */
```
>由于Latte支持基本类型的互相转换,你可以直接把`int`的结果赋值给`short`。
Latte支持所有Java运算符,当然也包括位运算。和`java`一样,位运算必须作用于整数上。Latte支持所有整数类型的位运算:`int/long/short/byte`。
此外,Latte还支持乘方运算:
```groovy
a ^^ b
```
结果均为`double`型。
> 本质来说,Latte不存在“运算符”。所有运算符都是方法调用。基本类型的运算是由其包装类型隐式转换后调用方法完成的。
2.3 控制流
2.3.1 If 语句
和Java一样,if是一个语句而非像Kotlin,Scala那样作为表达式。
但是,Latte支持`Procedure`,并且可以自动添加返回语句,所以使用起来和作为表达式区别并不大。
```ruby
if a > b
return 1
else
return 2
val result = (if a>b {1} else {2})
```
2.3.2 For 语句
for语句格式如下:
```kotlin
for item in iter
...
```
其中`iter`可以是数组、Iterable对象、Iterator对象、Enumerable对象、Map对象。
当`iter`为前4种时,for语句将把其包含的对象依次赋值给`item`并执行循环体。当`iter`为Map对象时,`item`是一个Entry对象,它来自`Map#entrySet()`。
你也可以使用`to`或者`until`,并在循环体内使用下标来访问元素:
```kotlin
for i in 0 until arr.length
val elem = arr[i]
...
```
2.3.3 While 语句
while语句格式如下:
```python
while boolExp
...
do
...
while boolExp
```
它的含义和Java完全一致。
2.3.4 break, continue, return
在循环中可以使用 `break` 和 `continue` 来控制循环。break将直接跳出循环,continue会跳到循环末尾,然后立即开始下一次循环。它的含义与Java完全一致。
`return`可以用在lambda、方法(包括“内部方法”)、Procedure、脚本、函数类中:
```kotlin
/* lambda */
foo = ()->return 1
/* 方法 */
def bar()
return 2
/* Procedure */
(
return 3
)
/* 函数类 */
fun Fun1
return 4
```
脚本中的return语句表示将这个值返回到外部,在`require`这个脚本时将返回这个值。
如果`return`是这个函数/方法最后的一条语句,或者该函数任意一条逻辑分支的最末尾,那么`return`都可以被省略:
```kotlin
fun add(a, b)
a+b
val result = add(1, 2)
/* result is 3 */
```
转换方式很简单,首先取出这个函数/方法的最后一条语句,如果是表达式,而且这个函数/方法要求返回值,则直接将其包装在`AST.Return`中。
如果最后一条语句是`if`,那么对其每一个逻辑分支进行该算法。
3. 类和对象
3.1 类与继承
3.1.1 类
类通过`class`关键字进行定义
当类内部不需要填充任何内容时,可以非常简单的书写为:
```kotlin
class Empty
```
当需要提供构造函数参数时,写为:
```kotlin
class User(id, name)
```
当然,你也可以为参数指定类型:
```scala
class User(id:int, name:String)
```
如果不指定类型则类型视为`java.lang.Object`
Latte不支持在类内部再定义构造函数,不过,你可以指定参数默认值来创建多个构造函数:
```scala
class Rational(a:int, b:int=1)
```
此时你可以使用`Rational(1)`或者`Rational(1, 2)`来实例化这个类。
--
构造函数内容直接书写在class内:
```kotlin
class Customer(name: String)
logger = Logger.getLogger('')
logger.info("Customer initialized with value ${name}")
```
直接定义在类中的变量,以及构造函数参数,将直接视为字段(Field)。也就是说,上述例子中定义的name和logger都是字段。详见 [3.3 字段和方法](#p3-3) 。
--
使用`private`修饰符来确保类不会被实例化:
```kotlin
private class DontCreateMe
```
在Latte中,所有类都是`public`的,所以,在`class`前的任何“访问关键字”均为该类构造函数的访问关键字。
3.1.2 继承
和Java一样:Latte是单继承,并且所有的类都默认继承自`java.lang.Object`。你可以使用类型符号`:`来指定继承的类。继承的规则和Java完全一致。
```kotlin
class Base(p:int)
class Derived(p:int) : Base(p)
```
父类的构造函数参数直接在父类类型后面的括号中指定。
如果使用了父类的无参构造函数,那么可以省略括号:
```kotlin
class Example : Object
```
如果想指定一个类不可被继承,那么需要在它前面加上`val`修饰符:
```kotlin
val class NoInher
```
3.1.3 抽象类
使用`abstract`关键字定义抽象类:
```kotlin
abstract class MyAbsClass
abstract f()
```
抽象类规则与Java完全一致。抽象类可以拥有未实现的方法。
继承一个抽象类:
```kotlin
class MyImpl : MyAbsClass
@Override
def f=1
```
3.1.4 静态成员
使用`static`定义静态成员。static可以“看作”一个修饰符,也可以“看作”一个结构块的起始。例如:
```js
class TestStatic
static
public val STATIC_FIELD = 'i am a static field'
static func()=1
```
3.2 接口
Latte接口遵循Java的接口定义。使用`interface`关键字:
```kotlin
interface MyInterface
foo()=...
```
定义了`abstract`方法`foo()`。
让一个类实现接口,也使用类型符号`:`。
```kotlin
class Child : MyInterface
foo()=456
child = Child
child.foo /* result is 456 */
child.bar /* result is 123 */
```
接口可以拥有字段,但是和Java规则一样,字段必须是`static public val`(默认也是)。
```kotlin
interface MyInterface
FLAG = 1
```
接口也可以拥有`static`方法(和Java一样)
```js
interface TestStaticMethod
static
method()=111
TestStaticMethod.method() /* result is 111 */
```
3.3 字段和方法
3.3.1 定义字段
你可以在类或接口中定义字段:
```kotlin
class Address(name)
public street
public city
public state
public zip
interface MyInterface
FLAG = 1
```
在类中定义的字段默认被`private`修饰,可选的访问修饰符还有`public`, `protected`, `internal`。
字段可以为`static`,只要写在static块中即可(接口默认就是static的,不需要修改)。也可以为不可变的,使用`val`修饰即可。由于构造函数参数也是字段,所以这些修饰符可以直接写在构造函数参数中。例如:
```kotlin
class User(protected val id, public val name)
```
使用字段很简单,直接使用`.`符号访问即可,和Java一致。
```kotlin
val user = User(1, 'latte')
user.name /* result is 'latte' */
val address = Address('home')
address.city = 'hz'
```
Latte提供所谓的`property`支持:使用`getter`和`setter`来定义`property`。详情见 [3.3.3 Accessor](#p3-3-3)
Latte支持所有Java的修饰符,但是名称可能有改动:
* var 表示可变变量(可省略,默认即为可变)
* val 表示不可变变量,或者不可被重载的方法,或者不可被继承的类
* abstract 抽象类/方法
* native 本地方法
* synchronized 同步方法
* transient 不持久化的字段
* volatile 原子性的字段
* strictfp 方法内的符点计算完全遵循标准
* data 类是一个data class:详见 [3.5 data class](#p3-5)
>`val` 其实就是Java的 `final`
3.5 Data Class
编译器会为data class的每一个字段生成一个getter和setter。并生成无参构造函数,`toString()`, `hashCode()`, `equals(Object)`方法。此外,还会实现Serializable和Cloneable接口。
```kotlin
data class User(val name: String, val age: int)
user = User('cass', 22)
user.toString() /* result is User(name='cass', age=22) */
```
你也可以定义自己的getter/setter/toString/hashCode/equals,编译器将跳过对应方法的生成。
3.6 实例化
Latte不需要`new`关键字就可以实例化一个类。对于无参数的实例化,甚至不需要附加括号:
```kotlin
class Empty
empty = Empty
class User(id, name)
user = User(1, 'latte')
```
当然,Latte也允许你加上`new`:
```scala
empty = new Empty
user = new User(1, "latte")
```
不过,Latte中的`new`的“优先级”非常低,java中的`new X().doSth()`的写法在Latte中必须写为`(new X).doSth`这样的写法。
> Latte中建议不要写new
此外,Latte提供另外一种特殊的实例化方式
调用无参构造函数,并依次赋值:
```python
class open(file, mode)
public encoding
f = open('/User/a', "r", encoding='utf-8')
```
这是一个语法糖,相当于如下Latte代码:
```kotlin
f = open('/User/a', "r")
f.encoding = 'utf-8'
```
即:首先使用不带`=`的参数进行类型的实例化,然后把剩余“参数”看作对accessor的赋值操作。
这个语法糖不光适用于Latte定义的data class,还可以支持任意具有无参构造函数,并有可访问的field或者[accessor](#p3-3-3)的对象。例如标准Java Bean就可以使用这个语法。
```java
class User {
private int id;
private String name;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
```
3.7 Object Class
```scala
object DataProviderManager
def registerDataProvider(provider: DataProvider)
...
val allDataProviders: Collection = ...
```
本质上,`object class`定义了一个类,但是这个类不能拥有构造函数的参数,构造函数为private,并拥有一个`static public val`的字段来存放单例。
object class可以继承父类、实现接口,其规则与普通的class完全相同
```scala
object DefaultListener : MouseAdapter()
def mouseClicked(e: MouseEvent):Unit
...
def mouseEntered(e: MouseEvent):Unit
...
```
你可以直接使用类名来获取这个单例对象:
```
val o = DataProviderManager
```
Latte支持和Java完全一样的Lambda语法:
```java
strList.stream().
map(s->Integer.parseInt(s)).
filter(n->n>10).
collect(Collectors.toList())
```
从外观上看不出任何差别?没错,语法上完全一致(特别是使用大括号区分层次的时候)。
使用缩进的情况下,多行lambda可以这么写:
```coffee
strList.stream.map(
s-> s = s.subString(1)
Integer.parseInt(s)
)
```
>注:可以不写return,因为Latte会帮你把需要的return补上。这个特性适用于任何“编译为JVM方法”的语法。
如果lambda只有一个参数(例如上述代码),那么名称和`->`可以被省略。其中,名称会被标记为`it`。
```js
strList.stream.map
it = it.subString(1)
Integer.parseInt(it)
```
这个特性可以让代码更简洁,同时也可以构造更灵活的内部DSL
```python
latteIsWrittenInJava
if it is great
star the repo
```
>做一丁点处理后,这是可以正常编译的代码!(不需要hack编译器)
可以写成一行
```coffee
latteIsWrittenInJava { if it is great { star the repo }}
```
此外Lambda的变量捕捉机制和Java不同。Latte可以在Lambda中的任何地方修改被捕获的变量。
```coffee
var count = 0
(1 to 10).forEach { count+=it }
println(count)
```
5. 其他
5.1 Json 集合
Latte支持Json格式的字面量。
Json数组:
```js
var list = [1, 2, 3, 4]
```
使用Json的数组语法,可以创建一个`java.util.LinkedList`实例,也可以创建一个数组。这取决于你将它赋值给什么类型的变量,或者使用`as`符号把它转换为什么类型。
int数组:
```c#
[1, 2, 3, 4] as []int
```
Object数组:
```c#
[1, 2, 3, 4] as []Object
```
在Latte中,你可以将一个`java.util.List`类型的对象转换为其它种类的Object。该特性将尝试使用无参构造函数构造目标类型对象,然后对每一个List中的元素,调用add方法。
```kotlin
class JsonArray
list = []
def add(o)=list.add(o)
res = [1,2,3] as JsonArray
/*
same as:
res = JsonArray()
for item in [1,2,3]
res.add(item)
*/
```
--
Json对象:
```swift
var map = [
'one': 1,
'two': 2,
'three', 3
]
```
其中`,`是不必须的。“换行”和`,`都可以用来来分割list的元素,以及map的entry
在Latte中,你还可以把一个"所有键都是string"的map转换为指定类型的对象。
```kotlin
data class Bean(hello, foo)
res = [
"hello" : "world"
"foo" : "bar
] as Bean
/* res will be Bean(hello=world, foo=bar) */
```
该转换将首先用无参构造函数构造指定类型,然后对map中每一个键,进行Latte的赋值操作。
不光可以显式的转换,还可以作为方法参数隐式转换过去。
Latte为静态类型和动态类型混合的语言。总体来说,类型检查比较宽松,并且不一定在编译期检查。
Latte不要求显式转换,在赋值、方法return时,值会自动的尝试转换为需要的类型。
```kotlin
class Base
class Sub:Base
var x:Base = Sub
var y:Sub = x
```
其中在赋值给`y`时,自动增加了一个类型转换。
5.3.2 类型转换
在调用方法时,自动转换并不会做,因为并不确定变量的类型,也不确定方法参数需要哪种类型。
如果编译期没有找到方法,则会在运行时再获取参数对象的类型并进行方法的寻找。如果一定要在编译期确定调用何种方法,可以手动转换类型。
```kotlin
class Data
i:int
def setI(i:int) { this.i=i }
fun call(x)
var data:Data = Data
data.setI(x as int)
```
上述例子中,x类型不确定,但是可以显式地转化为int。
5.4 运算符绑定
Latte支持运算符“绑定”。Latte的运算符绑定策略非常简单。每个运算符都看作方法调用。`a+b`看作`a.add(b)`,`a*b`看作`a.multiply(b)`,`!a`看作`a.not()`
这些运算符绑定依照`BigInteger`和`BigDecimal`的命名,所以你可以直接使用运算符来计算大数。
```kotlin
a = BigInteger(3)
b = BigInteger(4)
c = a + b
```
若需要绑定运算符,只需要写出签名相符的方法即可,例如定义一个`Rational`类来表示分数:
```kotlin
class Rational(a, b)
add(that: Rational)=Rational(this.a * that.b + that.a * this.b, this.a * that.b)
toString():String="${a}/${b}"
a = Rational(1, 4) /* 1/4 */
b = Rational(3, 7) /* 3/7 */
c = a + b /* 19/28 */
```
有一些运算符是“复合”的,即:它们可以由多个操作构成。例如`++a`,就可以由`(a = a + 1 , return a)`构成。这类运算符不提供绑定,如果有需要,请绑定它们的展开式用到的运算符。
下表描述了所有提供绑定的运算符,以及一些展开式规则:
| 运算符 | 方法签名 |
|----------|-------------------------|
| a:::b | a.concat(b) |
| a * b | a.multiply(b) |
| a / b | a.divide(b) |
| a % b | a.remainder(b) |
| a + b | a.add(b) |
| a - b | a.subtract(b) |
| a << b | a.shiftLeft(b) |
| a >> b | a.shiftRight(b) |
| a >>> b | a.unsignedShiftRight(b) |
| a > b | a.gt(b) |
| a < b | a.lt(b) |
| a >= b | a.ge(b) |
| a <= b | a.le(b) |
| a == b | a.eq(b) |
| a != b | !a.ne(b) |
| a in b | b.contains(a) |
| a & b | a.`and`(b) |
| a ^ b | a.xor(b) |
| a | b | a.`or`(b) |
| !a | a.logicNot() |
| ~a | a.not() |
| -a | a.negate() |
| a\[0\] | a.get(0) |
| a\[0, 1\] | a.get(0, 1) |
> `+a` 这种用法在Latte中不对`+`做任何处理,当做`a`
> 上面的`a[0, 1]`用法,如果a是一个二维数组,则相当于java的`a[0][1]`
> Latte对所有对象均可隐式转换到`RichObject`,在其中提供了`==`和`!=`的绑定,其中会调用对象的`equals`方法
Latte的运算符优先级和Java完全一致,而Latte特有的运算符优先级如下:
* `in` 和 `==` 优先级相同
* `:::` 优先级最高
由于`==`被绑定到equals方法,所以检查引用相同使用`===`,引用不同使用`!==`。
此外,Latte还提供两个运算符`is`和`not`,它除了可以检查引用、equals,在右侧对象是一个Class实例时还可以检查左侧对象是否为右侧对象的实例。
> 虽然`==`绑定到equals方法,但是如果写为`null==x`,编译器能够知道左侧一定为null,这时会检查null值而不是调用equals
> `!=`同理。
| 运算符 | 展开式 |
|--------|----------------------------------|
| a?=b | a = a ? b |
| a++ | tmp = a , a = a + 1 , return tmp |
| ++a | a = a + 1 , return a |
| a-- | tmp = a , a = a - 1 , return tmp |
| --a | a = a - 1 , return a |
> 其中`?=`的`?`代表任何二元运算符
---
Latte中,和普通的方法调用不同,运算符前不需要附加`.`,也不需要对参数包裹括号。但是因为Latte的运算符和方法调用是一回事,所以为了一致性,普通方法调用也可以将方法名看作运算符来书写:
```scala
list isEmpty // list.isEmpty()
map put "Feb", 2 // map.put("Feb", 2)
.println o // println(o)
```
使用逗号分隔多个参数。使用`.`表示直接调用方法(而不是在某个对象或者某个类上调用)。
5.5 异常
Latte和Java总体上是类似的,但是仍有多处不同:
* Latte没有`checked exception`
* Latte可以`throw`任何类型的对象,比如`throw 'error-message'`
* Latte可以`catch`任何类型的对象
* 由于上一条,Latte不提供`catch(Type e)`这种写法,需使用if-elseif-else来处理
```kotlin
fun isGreaterThanZero(x)
if x <= 0 { throw '${x} is littler than 0' }
var a = -1
try
isGreaterThanZero(a)
catch e
e.toCharArray /* succeed. `e` is a String */
finally
a = 1
```
Latte支持把一组语句当做一个值,这个特性称作“过程”。
过程由小括号开始,小括号结束。
用这个特性可以省略不必要的中间变量声明。
```kotlin
class Rational(a, b)
toString():String = a + (
if b == 1
return ""
else
return "/" + b
)
```
过程最终也是编译为“方法”的,可以省略最后的`return`,所以可以写为:
```kotlin
;; :scanner-brace
class Rational(a, b)
toString():String = a + ( if b==1 {""} else {"/" + b} )
```
解构指的是将一个对象分解为其组成部分的多个对象。
例如有如下定义和实例化:
```kotlin
data class Bean(a,b)
val bean = Bean(1,2)
```
可以知道,bean是由`1`和`2`组成的,它应当被分解为(1,2)。
Latte提供这样简化的分解:
```scala
val (x,y) = bean
```
定义了x和y,并分别赋值为1、2。
5.9.2 解构实现方式
使用解构,首先需要定义一个static方法`unapply`:
```java
class X {
static {
unapply(o)=...
}
}
```
这个方法需要接受一个参数,表示被解构的对象,并返回`null`或一个`java::util::List`实例。
如果返回`null`则说明解构失败,如果返回`List`实例,则表示会被分解为存在于列表中的对象。
如果解构失败,则解构表达式返回`false`,否则返回`true`。
可以指定使用“带有unapply方法的类”来执行解构:
```scala
Bean(x,y) <- bean
```
如果没有指定,则尝试使用右侧对象的类中的unapply方法进行解构。
```scala
(x,y) <- bean /* 相当于 Bean(x,y) <- bean */
```
如果没有指定类型,则可以将`<-`替换为`=`。
解构可以放在`if`中使用:
```scala
if List(a,b,c) <- o
println("result is ${a},${b},${c}")
else
println("destruct failed!")
```
5.10 模式匹配
和`scala`一样,`Latte`不提供`java`的`switch`语句,但是提供更强大的模式匹配。
```scala
def doMatch(o) = o match
case 1 => ... /* 根据值匹配 */
case b:Apple => ... /* 检查类型并定义一个新的变量 */
case _:Banana => ... /* 根据类型匹配 */
case Bean(x,y) => ... /* 根据解构匹配 */
case Bean(1, Bean(x, _:Integer)) => ... /* 多重模式 */
case Bean(x,y) if x > 0 => ... /* 解构后再做判断 */
case _ => ... /* 匹配所有(默认行为) */
```
模式匹配会从上到下依次尝试匹配。如果匹配成功则进入该分支执行语句,最终返回一个值(也可能返回`Unit`)。如果匹配失败,则会抛出`lt::lang::MatchError`。
任何匹配模式都可以添加if语句,仅当if判断成立时才会进入执行。
6. Java交互
在设计时就考虑了Latte和Java的互操作。所以它们基本是无缝衔接的。
6.1 在Latte中调用Java代码
实际上这里不会出现任何问题。Latte源代码最终是编译到Java字节码的,所以Latte调用Java就像Latte调用自己一样。
而设计时也考虑到了互通性,几乎所有Latte特性都可以通过编写Java源代码来模拟。
这里给出一些Latte与Java相同语义的表达:
### 1. 规定变量和它的类型
在Field操作时会有交互。
java:
```java
Integer integer;
List list;
final int anInt;
Object obj;
```
latte:
```scala
integer : Integer
list : List
val anInt : int
obj
```
Latte可以使用`var`表示可变变量,不过也可以不写,默认即为可变变量。Object类型不需要写,同样也是默认值。
### 2. 定义方法、参数类型和返回类型
在调用方法时会有交互。
java:
```java
void method1() {}
Object method2() { return null; }
int method3(int x) { return x; }
```
latte:
```kotlin
method1():Unit=...
method2()=null
method3(x:int):int = x
```
### 3. 获取java类,判断类型
java:
```java
Class c = Object.class;
if (s instanceof String) {}
```
latte:
```typescript
c = type Object
if s is type String
```