B3.语法概要
B3.语法概要
在使用模板的场景中,特别希望模板语法
不要破坏目标文件
的语法,两者无入侵的共存, 如用velocity生成java时,希望模板,同时受velocity和java语法检查和加持。 如生成html时,也希望模板能够不破坏html语法,能够直接在浏览器中预览。
模板语法
- 底层模板的语法,如 FreeMarker, Velocity目标语法
-母版语法
,有自己语法的目标文件,如java,html米波语法
- 利用母版语法的注释,做的简单标记指令,完成翻译- 文字语序均为从左至右,不支持从右至左语言。
在母版
中通过注释
做语法标记,逐行处理规则替换,以输出底层模板(backend)
。 母版的处理分为解析parse
和合并merge
两个过程,解析时的查找依赖正则表达式。 合并时,除了部分RNA
外,都是直接输出,效能等于StringBuilder.append
。
在RNA中没有复杂的流程控制
及执行函数
的功能,所以一次解析
后,后续合并
非常高效,
- 没有
RNA
时,相当于幂等操作的静态字符串,仅merge一次,后续直接使用。 RNA
依赖于执行引擎
,除动态语言外,相当于Map
+StringBuilder
java
执行引擎,动态编译成字节码,首次编译后等于原生java classStringBuilder
预先计算长度,以避免过程中扩容复制。
B3.1.模板特性
米波模板语法中,存在以下基础语素和约定。
空白
- 一个0x20
或0x09
,即英文空格或\t
英数
-[a-zA-z0-9]
,英文字母和数字,区分大小写母版注释
- 母版语言的注释,如,//
,/*
和*/
指令
- 米波语法中特殊意义的特征标记,如前缀DNA
- Defined Native AnnotationRNA
- Runnable Native Annotation?+*
- 分别为[0,1]
,[1,∞)
,[0,∞)
指令行
- 米波指令所在行,只被米波
解析,merge后不可见行
- 指[^\n]\n
或[^\n]$
两者格式。
为了简化,后续举例中,省略领起指令
的母版注释
+空白
HI-MEEPO
嗨!米波,用来定义母版注释
,以便后续解析DNA:SET
设定替换,在一个范围内定义一个模板替换。DNA:END
结束作用,结束多个作用
的作用域。DNA:BKB
免疫区域,被END
之前的区域不进行解析。DNA:RAW
原生模板,执行后,展现后面的原生模板语法。DNA:SON
子模板,可以在解析时引入其他魔板,作为组件使用。RNA:PUT
存入变量,使用引擎运行执行体
,结果存入环境。RNA:USE
使用变量,使用环境变量,内置或PUT
的变量。RNA:RUN
每次执行,每次都会执行功能体,比如计数器。RNA:WHEN
条件执行,组合成if-elseif-else逻辑块。RNA:EACH
循环执行,应用于数组或集合,循环输出。RNA:ELSE
否则条件,对WHEN
和EACH
执行否则分支。RNA:DONE
结束执行,结束WHEN
和EACH
的作用域。
在处理行符
时,以\n
断行,window的\r\n
也做\n
处理。 单行注释型,若结尾有\n
,会作为语法的结束符,即合并时\n
不会输出。
因为解析时使用了java的RegExp作为底层实现,所以需要一定的基础。
- 查找中,常误用字符有
.^$?+*{}()[]\|
。[]
内字符有些不用转义。 - 替换中,
\
主要用作反向引用
,部分()
组合有特殊含义。
正则的compile选项是UNIX_LINES
和MULTILINE
,通过embedded flag
设定
(?idmsuxU-idmsuxU)
Nothing, but turns matchflags
on/off(?idmsux-idmsux:X)
X, as a non-capturing group with the givenflags
on/off- i Pattern.CASE_INSENSITIVE 不区分大小写,默认关闭
- d Pattern.UNIX_LINES 只有
\n
作为行符
,默认开启 - m Pattern.MULTILINE
^
和$
会匹配行符
,默认开启 - s Pattern.DOTTAL
.
匹配行符
,默认关闭 - u Pattern.UNICODE_CASE 如全角字母的大小写,默认关闭
- x Pattern.COMMENTS 忽略空白,支持行注释,默认关闭
- U Pattern.UNICODE_CHARACTER_CLASS,默认关闭
B3.2.HI-MEEPO 嗨,米波
语法:注释头
空白
+ HI-MEEPO
!
? 空白
+ 注释尾
?
定义注释
并标识此文件为米波
模板,以解析其后续母版
。
注释头
- 单行注释或多行注释的首部,前一个非空白
字符串HI-MEEPO
- 这么怪的名字(hi meepo),主要是避免重复和转义。!
- 如果存在!
,表示保留指令前后的空白
,后详述。注释尾
- 多行注释的结尾,后一个非空白
字符串
嗨!米波
必须独占一行,最好有空白
分割,以便阅读时清晰。 类似sql的DELIMITER
定义结束符的用法和作用,举例如下,
- java -
// HI-MEEPO
,以//
为注释 - java -
/* HI-MEEPO */
,以/*
和*/
为注释 - sql -
-- HI-MEEPO
,以--
为注释 - bash -
# HI-MEEPO
,以#
为注释 - html -
<!-- HI-MEEPO -->
,以<!--
和-->
为注释
注意,注释头
存在尾字符叠字的情况,米波只处理同字符的叠字,举例如下,
/*
-/***** DNA:RAW
,无效,不处理//
-////// DNA:RAW
,处理叠字#
-##### DNA:RAW
,处理叠字
对于后续文本(DNA和RNA)的解析,存在行解析和块解析2种,规则如下
HI-MEEPO
始终是行解析,必须独占一行。单行注释
型米波,会按行解析,按行解析。多行注释
型米波,会跨行读取,按块解析。- 因非按行解析,故正则匹配时
^
和$
不定未行首和行尾。
关于HI-MEEPO!
和HI-MEEPO
处理指令行的首位空白
存在以下规则。
- 无
!
,并且指令独占一行,输出时忽略本行,即指令行后的第一个\n
。 - 有
!
时,只处理米波头尾之间的指令,前后空白保留。 DNA:RAW
比较特殊,无视!
设置,保留指令外,移除指令内的首位空白。@<!--_DNA:RAW_SUPER_-->@
中@
和_
分别标识保留和移除的空白。
后续举例中,都以// HI-MEEPO
为例,但省略书写。
B3.3.厂长DNA,静态替换
DNA好比一个厂长,定义替换指令,在parse时,进行高效的静态文本替换。
B3.4.DNA:SET 设定替换
语法:DNA:SET
空白
+ 界定
查找
界定
替换
界定
作用
?
在一定作用域内,把符合特征的字符串替换成底层模板的字符串。其中,
界定
- 第1个非(空白
,!
,英数
)1-2字节的char,常用的如/
,汉字。查找
- 不含界定
的正则特征,存在分组时参考分组引用
。查找
为空时,忽略此SET
。替换
- 不含界定
的字符串,存在引用时参考分组引用
。替换
为空时,表示删除,即替换成空。作用
- 生效的作用次数,即到何时结束作用,非空白
。
分组引用
指查找时有()
的group或替换时使用\1
的反向引用的情况。 这会对特征字符串的边界有影响,也要避开书写复杂的表达式,约定规则如下,
- 如果
查找
中无group,在使用group(0),即全部匹配。 - 如果
查找
有group时,取第一个(
,即group(1)内容。 - 如
((A)(B(C)))
,按(
从左到右出现的顺序计数。- group(1) - ((A)(B(C)))
- group(2) - (A)
- group(3) - (B(C))
- group(4) - (C)
作用
即作用次数
或作用域
,默认作用1
次,*
表示匿名
的无限次。
次数
,以,
分隔的单次或-
连接的闭区间,如1-3,15
。命名
的无限次作用,可被END
结束。
// DNA:SET /false/{{user.male}}/
var isMale = false;
/* 只把此行的false替换为user.male模板变量。底层模板输出为:
var isMale = {{user.male}};
*/
B3.5.DNA:END 结束作用
语法:DNA:END
(空白
+ 作用
)+
结束多个指令产生的作用
的作用域,如SET
的命名作用域。
// DNA:SET /1010100/{{id}}/id
// DNA:SET /"(性别)"/{{desc}}/1
// DNA:SET /性别/{{info}}/2
SUPER(1010100, "ConstantEnumTemplate", "性别", "性别")
// DNA:END id
/* 分别定义,命名的id;group(1)的desc;第2次匹配的info。底层模板输出为:
SUPER({{id}}, "ConstantEnumTemplate", "{{desc}}", "{{info}}")
*/
B3.6.DNA:BKB 免疫区域
语法:DNA:BKB
空白
+ 作用
定义一个命名
的全局免疫作用,可以被END
结束,之间的文本和指令不会被处理。
- 文本 - 任何非米波指令格式的文本
- 指令 - 除了当前生效的BKB对应的END外,都视为文本处理。
- 当前只能有一个生效的BKB
// DNA:BKB 黑皇杖
// DNA:SET /"(性别)"/desc/1
SUPER(1010100, "ConstantEnumTemplate", "性别", "性别")
// DNA:END 黑皇杖
/* 无视了SET指令,底层模板输出为:
// DNA:SET /"(性别)"/desc/1
SUPER(1010100, "ConstantEnumTemplate", "性别", "性别")
*/
B3.7.DNA:RAW 原生模板
语法:DNA:RAW
空白
+ 原生模板
用注释的语法定义一个模板
,用以弥补母版
语法不支持的情况。 使用单行注释表意清晰,多行注释时,只保留头尾直接的内容。 效果是,删除注释头
,DNA:RAW
和注释尾
及之间的空白
。
/* 以下两行具有相同的输出效果,即删除了`// DNA:RAW ` */
SUPER(1010100, "ConstantEnumTemplate", "性别", "性别")
// DNA:RAW SUPER(1010100, "ConstantEnumTemplate", "性别", "性别")
B3.8.DNA:SON 子模板
语法:DNA:SON
空白
+ 路径
把路径资源以UTF8读取,并在当前位置展开,作为模板解析。 路径可包含协议部分,默认classpath。仅支持一下协议。
http://
,https://
时,以GET读取file://
,/
时,从file system读取classpath:
时,从classloader读入,注意没有//
- 以
/
或.
开头,在以父模板起相对路径,但不建议使用。
需要注意,子模板需要单独声明HI-MEEPO
,属于静态解析, 在独立的context中解析,之后并入到当前的父模板中。
B3.9.主任RNA,动态执行
RNA好比车间主任,定义执行指令,在merge时调用执行引擎
,用其结果做替换。
- 一个
执行引擎
可以执行多种类型
的功能体
,一种类型简称一个引擎
。 引擎
的命名,必须为英数
,区分大小写,如js
。- 命名可以用
!
结尾,如js!
,执行时错误继续进行,返回null
- 执行结果为
null
时,在模板合并时会使用字符串空
代替。
RNA中默认的引擎
默认为map
。用户可以通过RnaManager注册引擎,后详述。
map
-session
级,以功能体
为key,到环境
中取值,没有则输出key。raw
-nothing
级,直接把功能体
当字符串返回,不会展开转义字符。
米波在多行注释时,使用多行的块解析,所以功能体
天然支持多行,提高可读性。
B3.A.RNA:PUT 存入变量
语法:RNA:PUT
空白
+ 引擎
? 界定
变量
界定
功能体
界定
指定引擎
执行功能体
,把函数
或执行结果
存入环境
(参加map引擎),以便其他RNA
取值。
环境
指米波context和部分脚本引擎上下文。引擎
,参考引擎说明。界定
同SET
。变量
指存入上下文的变量,非母版字面量。功能体
由具体的执行引擎执行,如spring,则可当做SpEL执行。变量
或功能体
为空时,不进行任何操作。
// DNA:PUT os/who/basename $(pwd)/
/* 把`basename $(pwd)`的输出,以`who`为key存入context中 */
B3.B.RNA:USE 使用变量
语法:RNA:USE
空白
+ 界定
查找
界定
变量
界定
作用
?
SET
的RNA
版本,区别在于从map
引擎中取得变量
值,而非底层模板的字面量替换。 变量获取规则(如,导航类对象,管道处理函数),详见map引擎说明。
在变量
合并时,会根据变量值
的类型进行自动多段缩排
支持,同时满足,
被查找
的字符串前有缩排的空白。变量值
是Array和Collection时,其内条目数大于1个。
对2个以上的元素进行缩进,与第1个元素列对齐。缩排后会出现不智能的情况,影响了美观。
- 缩排的对象,没有
\n
结尾,不换行,出现斑马线效果。 - 未缩排对象,包含
\n
,换行了,出现呲牙的效果。
// DNA:USE /meepo/user.home/
var userHome = "meepo";
/* 读取System.getProperty("user.home")。底层模板输出为:
var userHome = "/home/trydofor";
*/
B3.C.RNA:RUN 每次执行
语法:RNA:RUN
空白
+ 引擎
? 界定
查找
界定
功能体
界定
作用
?
PUT
和USE
的结合体,同样支持缩排,区别在于,
查找
为空时,表示仅执行,不替换功能体
执行结果立即使用,不存入变量
- 每次都执行,类似计数器功能,每次调用都会自增,无缓存。
// DNA:RUN os/rand/echo $RANDOM/1-3
var userName = "meepo-rand";
var userPass = "rand-rand";
/* 每次都获得随机数,输出3次。底层模板输出为:
var userName = "meepo-12599";
var userPass = "16345-31415";
*/
B3.D.RNA:WHEN 条件执行
语法:RNA:WHEN
空白
+ 引擎
? 界定
真假
界定
功能体
界定
归组
可以使用多个WHEN
组合成if
-else if
-else
逻辑块。
真假
- 必须是y|yes|n|no|not
,表示求值的取真
或取假
。功能体
- 引擎执行结果,并对结果求值。归组
- 必须是英数
,标识ELSE
和DONE
归组。
求值时,以下情况为false
,对false
执行not
则为true
- boolean的
false
- 对象
null
- Number的double值是
NaN
或在正负0.000000001
间(9位) empty
空字符串,空数组,空Collection,空Map
<!-- RNA:WHEN /yes/it.rem0/bg -->
<li value="code">rem0-name</li>
<!-- RNA:WHEN /not/it.rem1/bg -->
<li value="code">rem2-name</li>
<!-- RNA:ELSE bg -->
<li value="code">rem1-name</li>
<!-- RNA:DONE bg -->
等同于以下js的伪代码的if(a){}else if(!b){}else{}
分支逻辑
if (it.rem0){
console.log('<li value="code">rem0-name</li>')
} else if (!it.rem1){
console.log('<li value="code">rem2-name</li>')
} else {
console.log('<li value="code">rem1-name</li>')
}
B3.E.RNA:EACH 循环执行
语法:RNA:EACH
空白
+ 引擎
? 界定
步长
界定
功能体
界定
归组
通过归组
做为元素引用的循环体。若归组
名为it
,则it.x
表示当前元素的x
属性。
步长
- 必须-?[0-9]+
,表示循环顺序和步长,负数表示倒序
功能体
- 引擎执行结果,需要是数组或集合,否则等同于RNA:PUT
效果。归组
- 必须是英数
,标识ELSE
和DONE
归组,引用当前元素和内置状态属性。
根据不同的数据类型,执行不同的循环处理,空或null跳过,可被ELSE
执行。
- Array - Class.isArray()
Collection<E>
- instance of Collection- 其他类型,不做任何循环
- 倒序循环时,非RandomAccess和ReverseIterator,会toArray
循环体中,存在以下内置属性,用来表示循环的状态,若归组
名为it
,则,
it
- 当前循环的元素,避免同名,而产生环境污染- 引用当前元素的
x
属性时,其格式为it.x
it._count
- 内置变量,当前循环计数,1-base,未循环时为0it._total
- 内置变量,归组
内所有元素的数量it._first
- 内置变量,当前是否第一个it._last
- 内置变量,当前是否最后一个- 内置变量在循环结束后不移除,可以在循环外部使用。
因为米波是专业
的非专业
模板引擎,所以此for-each
十分低级,
- 支持有限的对象导航,使用
.
分隔对象,详见map
引擎。 - 集合内元素仅支持
Map<String,?>
和JavaBean的Getter取值。 - 没有作用域隔离,
归组
的名称,会造成context内变量覆盖。
<!-- RNA:EACH map/2/items/it -->
<!-- RNA:USE /name/it.name/* -->
<li value="code">rem0-name</li>
<!-- RNA:ELSE it -->
<li>no item</li>
<!-- RNA:DONE it -->
<!-- RNA:USE /total/it._total/ -->
<!-- RNA:USE /count/it._count/ -->
<div>result=count/total</div>
等同于以下js的伪代码的for(;;)
或for-in
循环逻辑,依集合
类型和步长
正负而定
let step=2 // 循环步长,负数为倒序,不可为0
let index=0 // 过程量
let it = null, count=0, total=items.length; // 内置变量
for(it in items){
if(index++ % step !== 0) continue // 控制步长
count++
console.log('<li value="code">rem0-'+it.name+'</li>')
}
if(count === 0){
console.log('<li>no item</li>')
}
console.log('<div>result='+count+'/'+total+'</div>')
B3.F.RNA:ELSE 否则条件
语法:RNA:ELSE
空白
+ 归组
通过作用
归组,对同组的WHEN
或EACH
执行否则分支,情况如何。
WHEN
时,表示没有任何一个WHEN
被执行。EACH
时,表示循环体从未执行(如集合无元素)EACH-ELSE
和pebble
语义相同,和python的for-else
不同。
B3.G.RNA:DONE 结束执行
语法:RNA:DONE
(空白
+ 归组
)+
通过归组
归组,结束一个或多个WHEN
和EACH
的归组
。
B3.H.占位符模板
简化模板,只进行表达式级的变量替换或函数处理,而非完整的Meepo模板语法。 比如,配置文件中的占位符,通常需要简单的替换或字符转换。
使用时,自定义变量的前后界定符
即可,默认是{{
和}}
。
定义转义符
可转义界定符
,默认是空
,不转义。转义有以下特点,以\
为例,
- 只对
界定符
有效,如\{{
和\}}
,解析后为{{
和}}
- 界定符前的自身转义,如
\\{{var}}
,解析后为\
+var
变量值 - 占位符,从左到右配对最相邻,不匹配内容做普通字符处理。
- 其他情况无效,如
\n
- 不支持占位符嵌套
- 变量名不能有空格,否则会按函数解析
B3.I.RNA的使用限制
因模板在静态解析时生成语法树,所以RNA
中的条件及循环执行不能识别运行时结构。 以下模板,模板意图为,对hash特征,使用GitHash,ModTime中第一个truthy值。 直觉上比较符合面向过程的条件赋值,但会引起校验时的作用域交叉语法。
// HI-MEEPO
// RNA:WHEN /yes/GitHash/bg
// RNA:USE /hash/GitHash/
// RNA:ELSE bg
// RNA:USE /hash/ModTime/
// RNA:DONE bg
project hash
出现交叉错误,是因为hash
这个特征,存在2个RNA:USE
与之对应, 在静态解析时,无法获得运行时的条件值,便无法确定其归属的语法树。修改如下
// HI-MEEPO
// RNA:USE /hash/GitHash/
// RNA:USE /time/ModTime/
// RNA:WHEN /yes/GitHash/bg
project hash
// RNA:ELSE bg
project time
// RNA:DONE bg
使用fun:see
函数辅助完成
// HI-MEEPO
// RNA:USE /hash/fun:see GitHash ModTime/
project hash