[关闭]
@yacent 2016-07-11T11:45:48.000000Z 字数 12177 阅读 1469

FreeMarker 学习笔记

网易|前端


后端部分开发指南

What is Freemarker

Freemarker是一款模板引擎:即一种基于模板和要改变的数据,并用生来成输出文本(eg: HTML等)的通用工具。
简单点来说,模板和数据模型是FreeMarker来生成输出所必须的组成部分:模板 + 数据类型 = 输出


快速入门

an example

图1
图2


数据模型(Data Model)

Freemarker使用的数据模型实际上是实现了 freemarker.template.TemplateModel接口的对象,后台传入的数据模型通常会被内部转换成 TemplateModel类型的对象,该功能叫做 object wrapping,即实例代码中的高级功能,设置wrapper,设置configuration的包装类的读取方式。cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER)

要成功转换对象,必须选取适合的包裹类,TemplateModel主要有三个子类,每一个子类分别表示一种FreeMarker的基本类型,有 TemplateHashModelTemplateSequenceModelTemplateNumberModel

数据类型

标量:不可变类,赋值后不可改变
+ Boolean
+ Number
+ String
+ Date

容器类型:
+ Hashes
+ Sequences
+ Collections

方法变量:
可以写在root当中直接写入方法

转换器变量(很少使用)

节点变量(很少使用)


配置(Configuration)

bla bla 不需要了解太多,会常用的匹配就好


前端部分开发指南

模板

  1. 基本组成部分
    插值(interpolations):${...}
    注释(Comments):<#-- -->
    FTL tags标签(区分大小写):一般以符号#开头,用户自定义的用 @
    Directives指令:if else | list | inlcude
    处理不存在的变量:<#if user??><h1>Welcome{$user}!</h1></#if>

  2. 指令(注意区分 模板自带和自定义的符号的不同)
    标签:<#directivename parameters>
    自定义标签:<@mydirective>

    1. <#assign ages = ["Joe":23, "Fred":25]>
    2. - Joe is ${ages.Joe}
    3. </#assign>
  3. 表达式
    当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式
    eg: ${r"${foo}"},在字符串前添加r传入表达式

    1) 字符串操作:插值 或 连接
    ${"hello ${user}!"}
    ${"Hello " + user + "!"}

    2) 序列操作

    1. <#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
    2. - ${user}
    3. </#list>

    3) 哈希表操作
    连接: + 如果两个哈希表含有键相同的项,那么在+号右侧的哈希表中的项目优先

    1. <#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
    2. - Joe is ${ages.Joe}
    3. - Fred is ${ages.Fred}
    4. - Julia is ${ages.Julia}
    5. </#assign>

    4) 算数运算: + - * / %
    ${3 + "5"} -> 35
    ${1.6?int} 使用内建函数 int来解决

    5) 比较运算符: = != < > <= >=
    <#if (x >y)>

    6) 内建函数:使用内建函数的语法和访问哈希表子变量的语法很像,除了使用?号来代替点,其他都一样 test?upper_case?html

    字符串的内建函数
        html: 字符串中所有的特殊HTML字符都需要用实体来代替
        cap_first: 字符串第一个字母变大写
        lower_case:字符串的小写形式
        upper_case:字符串的大写形式
        trim:去掉字符串首尾的空格
    序列的内建函数
        size: 序列中元素的个数
    数字的内建函数
        int: 数字的整数部分
    

    7) 方法调用(参数、返回值):${repeat("What", "3")}

    8) 不存在的值

    检测不存在的值:
        unsafe_expr?? 或 (unsafe_expr)??
    处理不存在的值
        unsafe_expr!default_expr
    
    ! 的优先级特别低,所以写的时候尽量带上括号
    `${(x!1) + y}` 即表示 如果x存在,则使用x,x不存在则使用y
    

其他

  1. 自定义指令
    a)无参数:

    1. <#macro greet>
    2. <font size="+2">Hello Joe!</font>
    3. </#macro>
    4. <@greet></@greet>
    5. <@greet/>

    b)有参数:

    1. <#macro greet person color>
    2. <font size="+2" color="${color}">Hello ${person}!</font>
    3. </#macro><@greet person="Fred" color="black"/>

    默认值:

    1. <#macro greet person color="black">
    2. <font size="+2" color="${color}">Hello ${person}!</font>
    3. </#macro>
    4. <@greet person="Fred"/>

    宏参数注意点:

    someParam=foo和someParam="${foo}"是不同的 数值/字符串
    宏参数是局部变量

    c)嵌套内容

    1. <#macro border>
    2. <table border=4 cellspacing=0 cellpadding=4>
    3. <tr><td>
    4. <#nested>
    5. </td></tr>
    6. </table>
    7. </#macro>
    8. <@border>The bordered text</@border>
    9. =>
    10. <table border=4 cellspacing=0 cellpadding=4><tr><td>
    11. The bordered text
    12. </td></tr></table>

    nested指令也可以多次被调用
    嵌套的内容可以是任意有效的FTL,包含其他的用户自定义指令
    在嵌套的内容中,宏的局部变量是不可见的
    不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱

    d)宏和循环变量
    循环变量的名称是在自定义指令的开始标记(<@...>)的参数后面通过分号确定的, 一个宏可以使用多个循环变量

    1. <#macro repeat count>
    2. <#list 1..count as x>
    3. <#nested x, x/2, x==count>
    4. </#list>
    5. </#macro>
    6. <@repeat count=4 ; c, halfc, last>
    7. ${c}. ${halfc}<#if last> Last!</#if>
    8. </@repeat>

    如果在分号之后指定的循环变量少,那么就看不到nested指令提供的最后的值
    如果在分号后面指定了比nested指令还多的变量,那么最后的循环变量将不会被创建

  2. 在模板中定义变量
    简单变量 <#assign x = "plain">
    局部变量 <#local x = "local">
    循环变量 <#list ["loop"] as x>

    局部变量也会隐藏(不是覆盖)同名的简单变量。循环变量也会隐藏(不是覆盖)同名的局部变量和简单变量
    内部循环变量可以隐藏外部循环变量
    有时会发生一个变量隐藏数据模型中的同名变量,但是如果想访问数据模型中的变量,就可以使用特殊变量globals ??
    <#assign user = "Joe Hider">

    1. ${user}
    2. 打印: Joe Hider
    3. ${.globals.user}
    4. 打印: Big Joe
  3. 命名空间
    a) 创建一个库:

    1. <#macro copyright date>
    2. <p>Copyright (C) ${date} Julia Smith. All rights reserved.
    3. <br>Email: ${mail}</p>
    4. </#macro>
    5. <#assign mail = "jsmith@acme.com">
    6. <#import "/lib/my_test.ftl" as my>
    7. <#assign mail="fred@acme.com">
    8. <@my.copyright date="1999-2002"/>
    9. ${my.mail}
    10. ${mail}
    11. ->
    12. <p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.
    13. <br>Email: jsmith@acme.com</p>
    14. jsmith@acme.com
    15. fred@acme.com

    b) 在引入的命名空间上编写变量:

    1. <#import "/lib/my_test.ftl" as my>
    2. ${my.mail}
    3. <#assign mail="jsmith@other.com" in my>
    4. ${my.mail}

    c) 命名空间和数据模型
    数据模型中的变量在任何位置都是可见的
    在模板的命名空间(可以使用assign或macro指令来创建的变量)中的变量有着比数据模型中的变量更高的优先级。因此,数据模型的内容不会干涉到由库创建的变量。

    d) 命名空间的生命周期
    命名空间由使用的import指令中所写的路径来识别。如果想多次import这个路径,那么只会为第一次的import引用创建命名空间执行模板。后面相同路径的import只是创建一个哈希表当作访问相同命名空间的“门”。

    1. <#import "/lib/my_test.ftl" as my>
    2. <#import "/lib/my_test.ftl" as foo>
    3. <#import "/lib/my_test.ftl" as bar>
    4. ${my.mail}, ${foo.mail}, ${bar.mail}
    5. <#assign mail="jsmith@other.com" in my>
    6. ${my.mail}, ${foo.mail}, ${bar.mail}

    通过my,foo和bar访问相同的命名空间。

  4. 空白处理
    a) 忽略某些模板文件的空白的工具: 剥离空白(默认开启)
    b) 从输出中移除空白的工具: compress指令

    1. <#compress>...</#compress>
    2. <@compress single_line=true>...</@compress>
    3. //设置single_line属性,这将会移除所有的介于其中的换行符
  5. 替换(方括号)语法
    用[#ftl]来开始模板,要记住这个要放在文件的最前面(除了它前面的空格)
    2.4版本中的默认配置将会自动检测,也就是说第一个FreeMarker标签决定了语法形式(它可以是任意的,而不仅仅是ftl)。


参考文档

第一章 内建函数参考文档

说明: 加粗为内建函数名

  1. 处理字符串的内建函数

    substring 取子串:
    exp?substring(from, toExclusive) , exp?substring(from)

    cap_first: 字符串中的第一个单词的首字母大写
    exp?cap_first 例如 ${" green mouse"?cap_first}

    uncap_first 首字母小写

    capitalize 字符串的所有单词首字母大写,其余小写
    ${"GreEN mouse"?capitalize} -> Green Mouse

    chop_linebreak 切断换行符: 如果在末尾没有换行符的字符串,那么可以换行,否则不改变字符串

    date,time,datetime 日期,时间,时间日期, 字符串转换成日期值, 建议指定一个确定格式参数:
    <#assign test1 = "10/25/1995"?date("MM/dd/yyyy")>
    <#assign test2 = "15:05:30"?time("HH:mm:ss")>
    <#assign test3 = "1995-10-25 03:05 PM"?datetime("yyyy-MM-dd hh:mm a")>

    ends_with 以…结尾, 返回是否这个字符串以指定的子串结尾

    html HTML格式的转义文本, 字符串按照HTML标记输出: 注意在HTML页面中,通常你想对所有插值使用这个内建函数, 所以你可以使用escape指令来节约很多输入,减少偶然错误的机会。

    group 分组, 这个函数只和内建函数matches的结果使用(matches 匹配)

    index_of 索引所在位置, 返回第一次字符串中出现子串时的索引位置, 你可以指定开始搜索的索引位置
    将"abcabc"?index_of("bc", 2)会返回4

    last_index_of 最后的索引所在位置, 返回最后一次(最右边)字符串中出现子串时的索引位置:
    例如"abcabc"?last_index_of("ab"):将会返回3。而且你可以指定开始搜索的索引。"abcabc"?last_index_of("ab", 2),将会返回0。要注意第二个参数暗示了子串开始的最大索引

    length 字符串长度

    lower_case 小写形式, 字符串全部字符小写

    left_pad 距左边:
    如果它仅仅用1个参数,那么它将在字符串的开始插入空白,直到整个串的长度达到参数指定的值, [${"a"?left_pad(5)}]

    如果使用了两个参数,那么第二个参数指定用什么东西来代替空白字符;第二个参数也可以是个长度比1大的字符串,那么这个字符串会周期性的插入

    right_pad 距右边:和left_pad相同,但是它从末尾开始插入字符而不是从开头

    contains 包含: 如果函数中的参数可以作为源字符串的子串,那么返回true

    number 数字格式: 字符串转化为数字格式

    replace 替换:
    在源字符串中,用另外一个字符串来替换源字符串中出现它的部分。它不处理词的边界
    如果第一个参数是空字符串,那么所有的空字符串将会被替换,比如"foo"?replace("","|"),就会得到"|f|o|o|"
    replace接受一个可选的标记参数,作为第三个参数。

    rtf 富文本:
    字符串作为富文本(RTF 文本),也就是说,下列字符串:\替换为
    , {替换为\{ , }替换为\}
    url URL转义: 在URL之后的字符串进行转义

    注意它会转义所有保留的URL字符(/,=,&等),所以编码可以被用来对查询参数的值进行,比如:Click here...

    split 分割: 根据另外一个字符串的出现将原字符串分割成字符串序列,
    split函数接受一个可选的标记参数作为第二个参数。

    starts_with 以…开头: 如果字符串以指定的子字符串开头,那么返回true
    e.g. "redhead"?starts_with("red")

    string 返回和其内容一致的字符串

    trim 去掉字符串首尾的空格

    upper_case 字符串的大写形式

    word_list 词列表: 包含字符串词的列表,并按它们在字符串中的顺序出现。词是连续的字符序列,包含任意字符,但是不包括空格

    xhtml XHTML格式: 字符串作为XHTML格式文本输出
    <替换为&lt\;
    >替换为&gt\;
    &替换为&amp\;
    "替换为&quot\;
    '替换为&#39\;

    xml XML格式: 字符串作为XML格式文本输出
    <替换为&lt\;
    >替换为&gt\;
    &替换为&amp\;
    "替换为&quot\;
    '替换为&apos\;

    通用标记(*):
    i:大小写不敏感
    f:仅第一个;替换/查找等,只争对第一次出现的那个
    r:查找的子串是正则表达式
    m:正则表达式多行模式
    s:启用正则表达式的do-tall模式
    c:在正则表达式中许可空白和注释

  2. 处理数字的内建函数

    c 数字转字符:${x?c}

    string 数字转字符串:
    有四种预定义的数字格式computer,currency,number和percent; ${x?string.number}; ${x?string("0.####")};

    round,floor,ceiling 数字的舍入处理:
    ${result?floor} 这些内建函数在分页处理时也许有用

  3. 处理日期的内建函数

    string(当用于日期值时)日期转字符串
    预定义的格式是short,medium,long和full;

    ${lastUpdated?string.long}
    ${lastUpdated?datetime?string.short}

    date,time,datetime: 用来指定日期变量中的哪些部分被使用

    1. <#assign x = openingTime> //no problem can occur here
    2. ${openingTime?time} // without ?time it would fail
    3. // For the sake of better understanding, consider this:
    4. <#assign openingTime = openingTime?time>
    5. ${openingTime} // this will work now

    iso_... 内建函数族:这些内建函数转换日期,时间或时间日期值到字符串,并遵循ISO 8601的扩展格式

  4. 处理布尔值的内建函数

    string(当被用作是布尔值时) 转换布尔值为字符串:
    foo?string,true被翻译为"true",而false被翻译为"false";
    foo?string("yes", "no"):如果布尔值是true,这会返回第一个参数(这里是:"yes"),否则就返回第二个参数(这里是:"no")。注意返回的的值是一个字符串;如果参数是数字类型,首先它会被转换成字符串

  5. 处理序列的内建函数

    first 序列的第一个子变量:

    last 序列的最后一个子变量:

    seq_contanis 序列包含…
    辨别序列中是否包含指定值,和contains区分开(查找字符串中是否存在某子串);

    1. <#assign x = ["red", 16, "blue", "cyan"]>
    2. "blue": ${x?seq_contains("blue")?string("yes", "no")}

    seq_index_of 第一次出现…时的位置
    返回序列中第一次出现该值时的索引位置,如果序列不包含指定的值时返回-1;搜索开始的地方可以由第二个可选的参数来确定;和index_of区分开(针对字符串函数);

    1. <#assign colors = ["red", "green", "blue"]>
    2. ${colors?seq_index_of("blue")} // 2
    3. <#assign names = ["Joe", "Fred", "Joe", "Susan"]>
    4. No 2nd param:
    5. ${names?seq_index_of("Joe")} // 0
    6. ${names?seq_index_of("Joe", 1)} // 2

    seq_last_index_of 最后一次出现..的位置:
    返回序列中最后一次出现值的索引位置,如果序列不包含指定的值时返回-1;支持可选的第二个参数来确定从哪里开始搜索的索引位置;和last_index_of区分开;

    reverse 反转序列:返回序列的反序形式

    size 序列大小
    序列中子变量的数量(作为一个数值)。假设序列中至少有一个子变量,那么序列s中最大的索引是s?size - 1(因为第一个子变量的序列是0)。

    sort 排序:以升序方式返回。
    (要使用降序排列时,使用它之后还要使用reverse内建函数)这仅在子变量都是字符串时有效(通常是大小写不敏感的),或者子变量都是数字/日期值/布尔值

    sort_by 以…来排序
    返回由给定的哈希表子变量来升序排序的哈希表序列(要降序排列使用这个内建函数后还要使用reverse内建函数)

    1. <#assign ls = [
    2. {"name":"whale", "weight":2000},
    3. {"name":"Barbara", "weight":53},
    4. {"name":"zeppelin", "weight":-200},
    5. {"name":"aardvark", "weight":30},
    6. {"name":"beetroot", "weight":0.3}
    7. ]>
    8. Order by name:
    9. <#list ls?sort_by("name") as i>
    10. - ${i.name}: ${i.weight}
    11. </#list>
    12. <#assign members = [
    13. {"name": {"first": "Joe", "last": "Smith"}, "age": 40},
    14. {"name": {"first": "Fred", "last": "Crooger"}, "age": 35},
    15. {"name": {"first": "Amanda", "last": "Fox"}, "age": 25}
    16. ]>
    17. Sorted by name.last:
    18. <#list members?sort_by(['name', 'last']) as m>
    19. - ${m.name.last}, ${m.name.first}: ${m.age} years old
    20. </#list>

    chunk 区块
    分割序列到多个 大小为函数的第一个参数给定的 序列,(就像mySeq?chunk(3))。结果是包含这些序列的一个序列。
    最后一个序列可能比给定的大小要小,除非第二个参数也给定了(比如mySeq?chunk(3,'-')),那个'-'就是用来填充最后一个序列,以达到给定的大小。

  6. 处理哈希表的内建函数

    keys 键的集合:一个包含哈希表中 '查找到的键' 的序列

    1. <#assign h = {"name":"mouse", "price":50}>
    2. <#assign keys = h?keys>
    3. <#list keys as key>${key} = ${h[key]}; </#list>
    4. => name = mouse; price = 50;

    values 值的集合: 一个包含哈希表中 '子变量' 的序列

  7. 处理节点(XML)的内建函数(常用)

    children 子节点序列:
    一个包含该节点所有子节点(也就是直接后继节点)的序列

    parent 父节点:
    返回该节点的直接父节点

    root 根节点:

    ancestors 祖先节点:
    一个包含节点的所有祖先节点的序列,以直接父节点开始,以根节点结束

    node_name 节点名称:
    node_type 节点类型
    node_namespace 节点命名空间

  8. 很少使用的和专家级的内建函数

    在特殊情况下(调试,高级宏)它们会有用。如果你需要在普通页面模板中使用这些函数,你可能会重新访问数据模型,所以你不要使用它们。


第二章 指令参考文档

说明: 加粗为内建函数名

  1. if,else,elseif指令:
    switch,case,default,break指令:
    list,break指令:

    a)在list循环中,有两个特殊的循环变量可用:
    item_index:这是一个包含当前项在循环中的步进索引的数值。
    item_has_next:来辨别当前项是否是序列的最后一项的布尔值。

    b)你可以使用list在两个数字中来计数,使用一个数字范围序列表达式

    1. <#assign x=3>
    2. <#list 1..x as i>
    3. ${i}
    4. </#list> -> 1 2 3
  2. include指令:

    1. <#include path>
    2. or
    3. <#include path options> options: parse, encoding

    a) 获得机制: *
    b) 本地化查找: e.g. en_US

  3. import 指令:

    1. <#import path as hash>
  4. noparse指令:

    1. <#noparse> ...</#noparse>

    FreeMarker不会在这个指令体中间寻找FTL标签,插值和其他特殊的字符序列

  5. compress指令:
    压缩指令对于移除多余的空白是很有用的(缩小所有不间断的空白序列到一个单独的空白字符。如果被替代的序列包含换行符或是一段空间,那么被插入的字符也会是一个换行符,开头和结尾的不间断的空白序列将会完全被移除)

    1. <#compress>...</#compress>
  6. escape,noescape指令:

    1. <#escape identifier as expression> // <#escape x as x?html>
    2. ...
    3. <#noescape>...</#noescape>
    4. // 有时需要暂时为一个或两个在转义区块中的插值关闭转义
    5. ...
    6. </#escape>
  7. assign 指令:

    1. <#assign name=value>
    2. or
    3. <#assign name1=value1 name2=value2 ... nameN=valueN>
    4. or
    5. <#assign same as above... in namespacehash>
    6. or
    7. <#assign name>
    8. // assign的极端使用是它捕捉它的开始标记和结束标记中间生成的输出。也就是说,在标记之间打印的东西将不会在页面上显示,但是会存储在变量name中
    9. capture this
    10. </#assign>
    11. or
    12. <#assign name in namespacehash>
    13. capture this
    14. </#assign>
  8. global 指令:
    这个指令和assign相似,但是被创建的变量在所有的命名空间中都可见,但又不会存在于任何一个命名空间之中。如果在当前的命名空间中,一个相同名称的变量存在的话,那么会隐藏由global指令创建的变量。

    这种情形下,你可以使用特殊变量globals,比如${.globals.x},注意使用globals你看到所有全局可访问的变量,不但由global指令创建的变量

    1. <#global name=value>
    2. or
    3. <#global name1=value1 name2=value2 ... nameN=valueN>
    4. or
    5. <#global name>
    6. capture this
    7. </#global>
  9. local 指令:
    同global,但是它创建或替换局部变量,这仅仅在宏和方法的内部定义才会有作用。

  10. setting 指令:
    <#setting name=value>,如 locale,number_format,boolean_format,...

  11. 用户自定义指令(<@...>):

    1. <@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN/>
    2. // 如果你需要循环变量,
    3. <@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN ; lv1, lv2, ..., lvN/>
    4. // 或者和上面两个相同但是使用结束标签,
    5. <@user_def_dir_exp ...>...</@user_def_dir_exp>
    6. // 或
    7. <@user_def_dir_exp ...>...</@>
    8. // 或和上面的相同但是使用位置参数传递,
    9. <@user val1, val2, ..., valN/>
    10. // 位置参数传递(如<@heading "Preface", 1/>)是正常命名参数传递(如<@heading title="Preface" level=1/>)的速记形式,这里忽略了参数的名称
  12. macro,nested,return 指令:

    1. <#macro name param1 param2 ... paramN>
    2. ...
    3. <#nested loopvar1, loopvar2, ..., loopvarN>
    4. ...
    5. <#return>
    6. ...
    7. </#macro>
  13. function,return 指令:
    这个指令和macro指令的工作方式一样,除了return指令必须有一个参数来指定方法的返回值,

    1. <#function name param1 param2 ... paramN>
    2. ...
    3. <#return returnValue>
    4. ...
    5. </#function>
  14. flush 指令:
    stop 指令:
    ftl 指令:
    t,lt,rt 指令:

    这些指令在行内的放置不重要, 也就是说,不管你是将它们放在行的开头,或是行的末尾,或是在行的中间,效果都是一样的。
    <#t>
    <#lt>
    <#rt>
    <#nt>
    // 这个指令关闭行中出现的空白削减

  15. attempt,recover 指令:

    1. <#attempt>
    2. attempt block
    3. // 这是会被执行的,但是如果期间发生了错误,那么这块内容的输出将会回滚; 之后recover block就会被执行
    4. <#recover>
    5. recover block
    6. // 仅在attempt block执行期间发生错误时被执行,可以在这里打印错误信息或其他操作
    7. </#attempt>
  16. visit,recurse,fallback 指令:
    visit和recurse指令是用来递归处理树的; 在实践中,这通常被用来处理XML。

    1. <#visit node using namespace>
    2. // 或
    3. <#visit node>
    4. <#recurse node using namespace>
    5. // 或
    6. <#recurse node>
    7. // 或
    8. <#recurse using namespace>
    9. // 或
    10. <#recurse>
    11. <#fallback>

    visit:
    a) 如果visit既没有在和之前描述规则的名称相同名字(node_name)的FTL命名空间发现自定义指令,
    b) 那么它会尝试用名称@node_type来查找,
    c) 又如果节点不支持节点类型属性(也就是node?node_type返回未定义变量),那么使用名称@default。
    d) 如果仍然没有找到处理节点的自定义指令,那么visit停止模板执行,并抛出错误。

    recurse: 它访问节点的所有子节点(而没有节点本身)
    <#recurse someNode using someLib> 等价于 <#list someNode?children as child><#visit child using someLib>

    fallback:


第三章 特殊变量参考文档

特殊变量是由FreeMarker引擎自己定义的变量。要访问它们,你可以使用.variable_name语法。比如,你不能仅仅写version,而必须写.version
支持的特殊变量有:
globals(.globals),language(.lang),now( The current time is ${.now?time} )


第四章 FTL中的保留名称

true:布尔值“true”
false:布尔值“false”
gt:比较运算符“大于”
gte:比较运算符“大于或等于”
lt:比较运算符“小于”
lte:比较运算符“小于或等于”
as:由少数指令使用
in:由少数指令使用
using:由少数指令使用
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注