语言参考
除了可视化视图之外,还有很多用户喜欢使用UiBot的源代码视图,来编写一个流程块。源代码视图是使用一种编程语言BotScript来描述流程块的,在这一章,我们先学习这种编程语言的基本规则,为后面学习源代码视图打下基础。当然,如果您根本不打算使用源代码视图,那么本章的内容实际上可以不看。
本章需要读者有一点点编程基础,任何编程语言都可以,只要了解变量、函数等基本概念即可。如果完全没有基础,建议先阅读[附录中的编程基础简介][附录A 编程基础知识],以便快速入门。
概述
[前文][RPA平台和UiBot]提到,UiBot的设计理念是“强大”、“简单”、“快捷”。简言之,UiBot既要让没有计算机基础的初学者,通过简单的学习,即可快速掌握流程的编写方法;又要让有一定编程基础的专业人员,能够以最快的速度实现自己的流程。
为了实现这些指标,UiBot提供了可视化的流程编写界面,便于初学者快速掌握;同时提供了一种简单、易学、接近自然语言的编程语言,便于专业人员的快速实现。当然,同一个流程块,可以用两种界面来显示,并可以在开发过程中随时切换。
本文档主要介绍UiBot提供的编程语言的基本语法规则。具有基本编程基础的读者,大约在两小时内即可掌握此规则,再经过数个小时的熟悉,即可灵活运用。对于有按键精灵基础的读者,还能进一步缩短学习时间。
对于UiBot来说,编程语言只是表达逻辑的工具,关键的功能还是由函数库或插件来实现。所以,语言设计只包括基本的逻辑,所有具体的功能,哪怕是最基本的“延时”功能,都不列入语言设计中,而在函数库中单独设计。本章内容亦不包括函数库的介绍。
UiBot的编程语言是专门设计的(下文简称UB语言),而不是市面上流行的编程语言如Python、JavaScript等,是因为UiBot的主要受众是那些非计算机专业科班出身,但足够熟悉业务流程的非技术人员。UB语言的设计尽可能的接近自然语言,对于理解基本英文单词的人来说,即使没有学习过,也能大致读懂。
相比之下,以JavaScript为例,虽然JavaScript是一种很棒的语言,在专业的程序员手里能发挥出很高的效率,甚至UiBot本身都有一部分代码是使用JavaScript编写的。但这种语言里面大量使用的括号,容易给非专业人员的学习带来障碍。如下图:
![复杂的JavaScript]!(https://upload-images.jianshu.io/upload_images/3353491-99c311062682ee70.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
因此,我们设计了专门的UB语言,并使这门语言尽可能简化,甚至尽可能少用除了字母和数字之外的元素。实际上,我们也考虑过使用市面上流行的编程语言的可能性,因为如果这样做,我们的开发工作量会大大降低,但与此同时,您的学习难度则会大大提高。所以,我们否定了这种思路,决定不采用流行的编程语言如Python等,非不能也,是不为也。
但是,在UB语言中,吸取了很多其他编程语言的优点。您会在UB语言的设计中看到Basic语言、Python语言、JavaScript语言的一些特点。因为我们在充分理解的基础上,博取众家之长,吸取最容易理解且常用的部分,删去复杂、不常用的部分,使UB语言精简、简单、易学、易用。
我们认为UB语言是目前最适合RPA领域的编程语言。
基本结构
UB语言的源代码文件是纯文本格式,扩展名不限,一律采用UTF-8编码。
UB语言的源代码由多条语句组成,和一般的脚本型语言,如Python、JavaScript等一样,UB语言并没有严格的结构和显式指定的入口。执行一个流程块的时候,从第一行开始执行,遇到函数定义暂时跳过,然后继续从函数结束后的一行开始执行(函数的概念请参考[这里][函数])。
一般来说,我们推荐一行只写一个语句。如果一定要写多个语句,则用冒号分隔符(:)进行分隔。
如果一行内容不够,需要折行,可以在任意语句中出现的逗号(,
)或二元运算符(“二元运算符”的概念请参考[这里][运算符和表达式])之后直接折行,不需要增加其他额外的符号,也不推荐在其他地方折行。但如果一定要在其他地方折行,则用反斜杠(\
)作为折行符号。例如:
Dim a= \
1
当一行中存在 //
时,表示从这以后的内容都是注释。包含在 /* */
中的内容,无论多少行都视作注释。例如:
// 这里是注释
/*
这里
也是
注释
*/
注释在流程运行过程中没有任何作用,仅供我们阅读方便。
UB语言中所有关键字、变量名、函数名均不区分大小写。例如:变量名abc、ABC或者Abc都被认为是同一个变量。
变量、常量和数据类型
数据类型
变量是编程语言中最基础的功能,变量中可以存放数字、字符串等值,并且可以在运行的过程中,随时改变变量中的值。UB语言中的变量是动态类型的,即变量的值和类型都可以在运行过程中动态改变。这符合一般脚本型语言如Python、JavaScript的习惯。变量的类型分为以下几种:整数型、浮点数型、布尔型、字符串型、函数型、复合型和空值型。
整数型的值可以以十进制或者十六进制的方式表示,其中十六进制需加前缀 &H
或 &h
。
浮点数的值可以用常规方式或者科学计数法方式表示。如 0.01
或者 1E-2
或者 1e-2
均代表同一个浮点数。
布尔型的值仅有 True
或者 False
,两者皆不区分大小写。
字符串型的值用一对单引号('
)或一对双引号("
)所包围,字符串中可以用 \t
代表制表符,用 \n
代表换行,用 \'
代表单引号,用 \"
代表双引号,用 \\
代表反斜杠本身。字符串中间可以直接换行,无需增加任何其他符号,换行符也会作为字符串的一部分。
也可以用前后各三个单引号('''
)来表示一个字符串,这种字符串被称为长字符串。在长字符串中,可以直接写回车符、单引号和双引号,无需用 \n
,\'
或者 \"
。
函数型的值只能是已经定义好的函数,在后文详述。
复合型的值包括数组、字典等,在下一节详细阐述。
空值型的值总是 Null
,不区分大小写。
例如:
a = 1 // a是整数型变量
a = &HFF // a还是整数型变量
a = True // a是布尔型变量。作为动态类型语言,a的类型可以随时变化
a = FALSE // a是布尔型变量,注意True和False都不区分大小写
a = 'UiBot' // a是字符串型变量
a = "UiBot
RPA" // a是字符串型变量,字符串中可以换行
a = null // a是空值型变量,可以写为Null、NULL或null(不区分大小写)
变量和常量
变量的定义方式是:
Dim 变量名
定义变量名的同时,可以给变量赋值一个初始值:
Dim 变量名=值
想要定义多个变量的话,可以这样定义:
Dim 变量名1 = 值1, 变量名2
Dim 变量名1 = 值1, 变量名2 = 值2
常量的定义方式和变量类似,只是把Dim改为Const,并且必须在定义时就指定值:
Const 常量名=值, 常量名=值
常量和变量的唯一区别是,常量只能在定义时指定一次值,后面不允许再修改。
例如:
Dim a // 定义名为a的变量,暂不赋值
Dim b = 1 // 定义名为b的变量,并赋值为1
Dim c, d = True // 定义名为c和d的两个变量,为d赋值True
Const e = 'UiBot' // 定义名为e的常量,为其赋值为字符串’UiBot’
Const f // 错误:常量必须有初始赋值
对于有命名的东西(例如:变量、常量、函数等),其名字统称为标识符,标识符需要遵循一定规则定义。
标识符可以用英文字母、下划线(_
),任意UTF-8编码中包含的除英语以外其他语言的字符(当然,也包括汉字)表示,除了第一个字符外,后面还可以使用0-9的数字。变量名不区分大小写。
UB语言规定变量必须经过定义才能使用(除了For
语句中的循环变量、Try
语句中的异常变量、函数参数等)。变量在函数范围内定义时,属于局部变量,在函数退出时即清空。在函数范围之外任何位置定义时,属于全局变量,在运行过程中不会清空。全局变量可以定义在函数范围外任何位置,不影响其使用,甚至可以在使用变量之后定义。
复合类型
除了常用的整数型、字符串型等数据类型之外,UB语言还包括两种复合类型:数组、字典。两者在定义时和普通变量并无区别。
数组的值可以采用这种方式书写:
[值1, 值2, 值3 ]
其中值可以是任意类型,同一个数组中的不同值也可以是不同类型,值甚至可以是另外一个数组,这样就构成了一般意义上的多维数组。
字典的值可以采用这种方式初始化:
{ 名字1:值1, 名字2:值2, 名字3:值3 }
其中 名字 只能是字符串,值可以是任意类型。如果您熟悉JavaScript或者JSON,会发现这种初始化方法和JSON的表示形式高度相似。
无论是数组还是字典,要引用其中的元素,均采用方括号作为索引,如果要引用数组中的数组(即多维数组),或字典中的数组,可以继续在后面写新的方括号,如:
变量名[索引1][索引2]
用这种方式表示的数组中的元素或者是字典中的元素,既可以作为左值也可以作为右值,也就是说,既可以使用其中的值,也可以为其中的内容赋值,甚至可以在其中增加新的值。
例如:
Dim 变量 = [486, 557, 256] // 变量可以用中文命名,初值是一个数组
a = 变量[1] // 此时a被赋值为557
变量 = {"key1":486, "key2":557, "key3":256} // 变量的类型改为一个字典
a = 变量["key1"] // 此时a被赋值为486
变量["key4"] = [235, 668] // 往字典中增加一个新值,可以是一个数组
//此时,字典中的内容为 {"key1":486, "key2":557, "key3":256, "key4":[235, 668]}
a = 变量["key4"][0] // 此时a被赋值为235
注意:在引用数组或字典中的元素时,数组的索引只能是整数类型,用0作为起始索引;字典的索引只能是字符串类型。如果未能正确的使用,会在运行时报错。
运算符和表达式
UB语言中的运算符及其含义如下表:
+ | - | * | / | & | ^ | < | <= |
---|---|---|---|---|---|---|---|
加法 | 减法/求负 | 乘法 | 除法 | 连接字符串 | 求幂 | 小于 | 小于等于 |
> | >= | <> | = | And | Or | Not | Mod |
---|---|---|---|---|---|---|---|
大于 | 大于等于 | 不相等 | 相等/赋值 | 逻辑与 | 逻辑或 | 逻辑非 | 取余数 |
把变量、常量和值用运算符和圆括号 ( )
连接到一起,称为表达式。在上述运算符中,Not
是一元运算符、-
既可以用作一元运算符,也可以用作二元运算符,其他都是二元运算符。一元运算符只允许在右边出现一个元素(变量、常量、表达式或值),二元运算符只允许在左右两边同时出现两个元素。
注意:当 =
出现在表达式内部时,其含义是判断是否相等。当 =
构成一个独立的语句时,其含义是赋值。这里 =
的设计虽然具有二义性,但能更好的被初学者所接受。
UB语言中删掉一些其他语言中具备、但不常用的运算符,如整数除运算符、位操作运算符等等。因为这些运算符的使用场景较少,即使需要,也可以采用其他方式实现。
表达式常用于赋值语句,可以给某个变量赋值,其形式为:
变量名 = 表达式
注意,当表达式为一个独立的(没有使用任何运算符计算)数组、字典类型的变量时,赋值操作只赋值其引用,也就是说,只是为这个变量增加一个“别名”。当一个数组、字典中的元素发生改变时,另一个也会改变。
例如:
a = [486, 557, 256] // a是一个数组
b = a // b是a的“别名”
b[1] = 558 // 改变b里面的值,a里面的值也会跟着改变
c = a[1] // 此时c的值是558,而不是原来的557
a = 557 // 此时a被赋值为557(变为整数型)
b = a // 此时b里面的值也是557,但和a分别保存
b = 558 // b里面的值发生改变,a的值不改变
c = a // 此时c的值仍然是原来的557,因为a不是字典、数组
逻辑语句
条件分支语句
即一般编程语言中最常用的If…Else语句,主要用于对某一个或者多个条件进行判断,从而执行不同流程。在UB语言中,有以下几种形式:
If 条件
语句块1
End If
If 条件
语句块1
Else
语句块2
End If
If 条件1
语句块1
ElseIf 条件2
语句块2
Else
语句块3
End If
当条件满足时,会执行条件之后的语句块,否则,语句块不会执行。Else
后面的语句块则会在前面所有条件都不满足的时候,才会执行。
例如:
// Time.Hour() 可以取得当前时间中的小时数
// TracePrint() 可以把指定的内容输出到UiBot的输出栏中
If Time.Hour() > 18 // 取得当前时间中的小时数
TracePrint("下班时间") // 如果大于18,则执行这里的语句
Else
TracePrint("上班时间") // 如果不满足前面的条件,则执行这里的语句
End If
选择分支语句
根据一定的条件,选择多个分支中的一个。先计算Select Case
后面的表达式
,然后判断是否有某个Case
分支和这个表达式
的值是一致的。如果没有一致的Case
分支,则执行Case Else
(如果有)后面的语句块。
Select Case 表达式
Case 表达式1, 表达式2
语句块1
Case 表达式3, 表达式4
语句块2
Case Else
语句块3
End Select
例如:
Select Case Time.Month() // 取得当前时间中的月份
Case 1,3,5,7,8,10,12 // 如果是1、3、5、7、8、10、12月
DayOfMonth = 31 // 当月有31天
Case 4,6,9,11 // 如果是4、6、9、11月
DayOfMonth = 30 // 当月有30天
Case Else // 如果是其他(也就是2月)
DayOfMonth = 28 // 当月有28天(不考虑闰年的情况)
End Select
TracePrint(DayOfMonth)
条件循环语句
在UB语言中,使用Do…Loop
语句来实现条件循环,即满足一定条件时,循环执行某一语句块。Do…Loop
语句有以下五种不同的形式,用法较为灵活:
-
前置条件成立则循环:先判断
条件
,条件
成立则循环执行语句块,否则自动退出循环。
Do While 条件
语句块
Loop
-
前置条件不成立则循环:和前一条相反,
条件
成立则退出循环,否则循环执行语句块。
Do Until 条件
语句块
Loop
-
后置条件成立则循环:先执行语句块,再判断
条件
,条件
成立则继续循环执行语句块,否则自动退出循环。
Do
语句块
Loop While 条件
-
后置条件不成立则循环:先执行语句块,再判断
条件
,条件
成立则自动退出循环,否则继续循环执行语句块。
Do
语句块
Loop Until 条件
- 无限循环:该循环语句本身不进行任何条件的判断,需要在语句块中自行做判断,如果语句块中没有跳出循环的语句,则会无限的执行该循环
Do
语句块
Loop
例如:
Do Until Time.Hour() > 18 // 判断当前时间中的小时数,只要不大于18就循环
TracePrint("还没有到下班时间") // 每次循环,都会执行这里的语句
Delay(1000) // 每判断一次,休息一秒钟
Loop
TracePrint("下班时间到啦") // 如果大于18,则跳出循环,执行这里的语句
计次循环语句
计次循环语句主要用于执行一定次数的循环,其基本形式为:
For 循环变量 = 起始值 To 结束值 Step 步长
语句块
Next
在计次循环语句中,起始值
、结束值
、步长
都只允许是整数型或者浮点数型;步长
可以省略,默认值为1。变量从起始值
开始,每循环一次自动增加步长
,直到大于结束值
,循环才会结束。
在计次循环语句中,循环变量
可以不用Dim
语句定义,直接使用,但在循环结束后就不能再使用了。
例如:
Dim count = 0 // 定义变量count
For i=1 To 100 // 每次循环,变量i都会加1。这里变量i不需要定义
count = count + i
Next
TracePrint(count) // 这里会显示1+2+3+…+100的结果,即5050
遍历循环语句
遍历循环语句可以用于处理数组、字典中的每一个元素。遍历循环语句有以下两种形式:
For Each 循环变量 In 数组或字典
语句块
Next
在这种形式的循环语句中,会自动遍历数组、字典中的每一个值,并将其置入循环变量
中,直到遍历完成为止。
或者:
For Each 循环变量1, 循环变量2 In 数组或字典
语句块
Next
在这种形式的循环语句中,会自动遍历数组、字典中的每一个索引和值,并将其分别置入循环变量1
、循环变量2
中,直到遍历完成为止。
和计次循环语句类似,在遍历循环语句中,循环变量
可以不用Dim
语句定义,直接使用,但在循环结束后就不能再使用了。
例如:
Dim days = [31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31] // 定义数组型变量days
Dim count = 0
For Each i In days // 每次循环,变量i的值分别为days中的每个值
count = count + i // 把数组中的每个值依次加起来
Next
TracePrint(count) // 这里会显示一年中每个月的天数的累加和,即365
跳出语句
在UB语言中,支持以下形式的循环跳出语句:
Break
只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即跳出当前循环。
Continue
只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即结束当前循环,并开始下一次循环。
例如:
Dim days = { '一月':31, '二月':28, '三月':31,
'四月':30, '五月':31, '六月':30,
'七月':31, '八月':31, '九月':30,
'十月':31, '十一月':30, '十二月':31 } // 定义字典型变量days
For Each i,j In days // 每次循环,变量i, j分别为days中每个名字和值
If j Mod 2 = 0 // 如果j是偶数
Continue // 结束本次循环,开始下一次循环
End If
TracePrint(i) // 把days中的名字(其值不是偶数)显示出来
Next
另外,在流程块中的任何地方,只需要书写
Exit
不需要任何参数,即可在执行到此行的时候,自动结束整个流程(不是当前流程块)的执行。
函数
所谓函数,是指把一组常用的功能包装成一个语句块(称之为“定义”),并且可以在其他语句中运行这个语句块(称之为“调用”)。使用函数可以有效的梳理逻辑,以及避免重复代码的编写。
函数的定义和调用没有先后关系,可以先出现调用,再出现定义。但函数必须定义在全局空间中,也就是说,函数定义不能出现在其他函数定义、分支语句、循环语句下面的语句块中。
函数定义中可以包含参数,参数相当于是一个变量,但在调用时,可以由调用者指定这些变量的值。
定义函数的格式如下:
- 无参数的函数
Function 函数名( )
语句块
End Function
- 有参数的函数
Function 函数名(参数定义1, 参数定义2)
语句块
End Function
其中,参数定义的格式可以只是一个变量名
,也可以是变量名 = 表达式
的形式。对于后者来说,表示这个参数带有一个“默认值”,其默认值由“表达式”来确定。
如果函数有参数,则参数中的每个变量名都被认为是此函数内已经定义好的局部变量,无需使用Dim
语句定义。
在函数定义中,要退出函数并返回,采用以下写法:
Return 返回值
当执行到这一语句时,将跳出函数并返回到调用语句的下一行。返回的时候可以带一个返回值(具体作用下文叙述)。返回值可以忽略,默认为Null
。当执行到函数末尾的时候,无论有没有写Return
语句,都会返回。
例如:
Function Add(x, y=1) // 定义了两个参数的函数,第二个参数有默认值
Return x + y // 返回值为x+y的值
End Function
调用函数的格式如下:
返回 = 函数名(表达式1, 表达式2)
或者
函数名(表达式1,表达式2)
按照第一种格式调用,可以指定一个变量作为返回,当函数调用完成后,函数的返回值会自动赋值给这里的返回变量,调用者可以通过返回值,了解到函数调用的情况。此时,必须在被调用的函数名后面加圆括号。而当按照第二种格式调用时,调用者不需要返回值,则可以省略圆括号,使语句更符合自然语言习惯。
当调用时,相当于对函数中的参数进行了一次赋值运算,用表达式的值对其赋值。与赋值运算的规则相同,当表达式为一个独立的(没有使用任何运算符计算)数组、字典时,赋值操作只赋值其引用,也就是说,只是为变量增加一个“别名”。
调用函数时,传入的表达式的数量可以少于参数的数量,如果某个参数没有传入值,或者传入值为Null
,则采用其默认值,如果连默认值也没有,则其值自动为Null
。
例如,对于上面定义的函数,可以按照如下的方式调用:
a = Add(100) // 调用Add函数,第二个参数取默认值1,所以a的值是101
b = Add(100, 200) // 调用Add函数,指定了两个参数,所以b的值是300
Add 100, 200 // 调用Add函数,不关心返回值,所以可以不写括号
当函数定义完成后,其名称可以作为一个函数类型的常量使用,也可以把函数名称赋值给某个变量,用这个变量也可以调用这个函数。
例如,对于上面定义的函数Add
,可以按照如下的方式使用:
Dim Plus = Add
TracePrint Plus(100, 200)
// 相当于先调用了Add函数,再用其返回值调用了TracePrint函数,结果是300
除了在流程块中定义的函数之外,UB语言中也已经内置了很多函数,可以完成各种丰富的功能。比如上面例子中的TracePrint
就是一个内置函数。
其他
多模块
UB语言支持多模块,可以用其他语言实现扩展模块,并在当前流程块中使用。目前支持以下几种类型的模块:1)UB语言的流程块;2)Python语言的模块;3)C/C++语言的模块;4).Net的模块;5)Lua语言的模块。不同的模块有不同的扩展名,去掉扩展名以后,剩下的文件名就是模块的名字。比如某个Python语言的模块,文件名为Rest.py,则其模块名为Rest。
在UB语言中,采用以下方式导入一个模块:
Import 模块名
注意这里的模块名的书写规则和变量名一致,不需要采用双引号,也不需要加扩展名。如Import Rest。UiBot在编译和运行时会自动按照Lua语言模块、C语言模块、.Net语言模块、Python语言模块、UB语言流程块的先后顺序,依次加上相应的扩展名进行查找。在Windows中,由于文件名不区分大小写,所以Import语句后面的模块名也可以不区分大小写。在其他操作系统中,需要注意模块名的大小写要和文件一致。
每个导入的模块,都会被放置在一个与模块名同名的“命名空间”中,可以通过下面这种方式来调用导入模块中的函数:
命名空间.函数名
即在命名空间和函数名之间加一个点号(.)进行分隔。
对于Python、Lua语言的模块,只会保留其中的全局变量定义和函数定义,其他内容都会被忽略。对于C语言的模块和.Net模块,只能调用其中定义的函数。
如果要导入一个UB语言的流程块,则需要导入和被导入的流程块文件在同一个目录下。导入UB语言的流程块之后,既可以调用被导入的流程块中定义的函数,又可以直接以流程块的名字作为函数名,直接运行这个流程块中的所有命令。例如,有一个流程块 ABC.task。在其他流程块中Import之后,直接采用下面的格式即可直接调用ABC.task(相当于运行了ABC.task这个流程块):
ABC()
假设流程块 ABC.task中定义了一个函数,名为test,则可以采用下面的格式调用这个函数
ABC.test()
异常
作为动态类型语言,有很多错误在编译时难以检查,只能在运行时报错。而且,由于UiBot不强调运行速度,而更强调运行的稳定性,也会在运行时加入比较多的检查。当出错的时候,比较合适的报错手段是抛出异常。
比如,对于有目标命令(“有目标命令”的概念可以参考[这里][目标选取]),在运行的时候,如果到了超时时间都不能找到目标,就会自动抛出一个异常。
除了自动抛出的异常之外,在流程块中,还可以采用Throw
语句抛出一个异常:
Throw 字符串
在抛出异常时,可以把异常相关信息以字符串的形式一起抛出,也可以省略这个字符串。
如果在流程块中没有对异常进行处理,当出现异常时,整个流程都会终止执行,并且把异常相关信息显示出来。如下图所示:
如果不希望流程在发生异常的时候终止,可以采用以下语句对异常进行处理:
Try
语句块
Catch 变量名
语句块
Else
语句块
End Try
如果在Try
后面的语句块中发生了异常,会跳到Catch
后面的语句块中执行。如果在Try
语句块中没有发生异常,且定义了Else
语句块(当然,也可以省略Else
语句块),则会跳到Else
语句块中执行。
Catch
语句后面的变量名可以省略。如果不省略,可以不用Dim
语句提前定义,当发生异常时,这个变量的值是一个字典,其中包含“File”、“Line”和“Message”三个字段,分别代表发生异常的文件名、发生异常的行号、异常包含的信息。
返回目录
注: 上述内容经 UiBot 官方 授权发布,版权归 UiBot 官方所有,如需转载请先联系。
更多 RPA 相关的资讯,请关注公众号:流程自动化机器人教程
由于简书禁止直接在文章中插入公众号二维码,请点击 这里 了解添加该公众号的细节。