除了可视化视图之外,还有很多用户喜欢使用UiBot的源代码视图来编写一个流程块。源代码视图使用一种UiBot自创的编程语言BotScript(以下简称UB语言)来描述流程块。在这一章,我们先学习UB语言的基本规则,为后面学习源代码视图打下基础。
本章需要读者有一点点编程基础,任何编程语言都可以,只要了解变量、函数等基本概念即可。如果完全没有基础,请先阅读初级开发者指南的附录部分,以便快速入门。
前文提到,UiBot的设计理念是“强大”、“简单”、“快捷”。简言之,UiBot既要让没有计算机基础的初学者,通过简单的学习,即可快速掌握流程的编写方法;又要让有一定编程基础的专业人员,能够以最快的速度实现自己的流程。
为了实现这些指标,UiBot提供了可视化的流程编写界面,便于初学者快速掌握;同时提供了一种简单、易学、接近自然语言的UB编程语言,便于专业人员的快速实现。当然,同一个流程块,可以用两种界面来显示,并可以在开发过程中随时切换。
这一章主要介绍UB语言的基本语法规则。具有基本编程基础的读者,大约在两小时内即可掌握此规则,再经过数个小时的熟悉,即可灵活运用。对于有按键精灵基础的读者,还能进一步缩短学习时间。
对于UiBot来说,编程语言只是表达逻辑的工具,关键的功能还是由命令库或插件来实现。所以,语言设计只包括基本的逻辑,所有具体的功能,哪怕是最基本的“延时”功能,都不列入语言设计中,而在命令库中单独设计。本章内容亦不包命令库的介绍。
UB语言是专门设计的,而不是市面上流行的编程语言如Python、JavaScript等,是因为UiBot的主要受众是那些非计算机专业科班出身,但足够熟悉业务流程的非技术人员。UB语言的设计尽可能的接近自然语言,对于理解基本英文单词的人来说,即使没有学习过,也能大致读懂。
相比之下,以JavaScript为例,虽然JavaScript是一种很优秀的语言,在专业的程序员手里能发挥出很高的效率,甚至UiBot本身都有一部分代码是使用JavaScript编写的。但这种语言里面大量使用的括号,容易给非专业人员的学习带来障碍。如下图:
因此,我们设计了专门的UB语言,并使这门语言尽可能简化,甚至尽可能少用除了字母和数字之外的元素。实际上,我们也考虑过使用市面上流行的编程语言的可能性,因为如果这样做,我们的开发UiBot的工作量会大大降低,但与此同时,您的学习难度则会大大提高。所以,我们否定了这种思路,决定不采用流行的编程语言如Python等,非不能也,是不为也。
但是,在UB语言中,吸取了很多其他编程语言的优点。您会在UB语言的设计中看到Basic语言、Python语言、JavaScript语言的一些特点。因为我们在充分理解的基础上,博取众家之长,吸取最容易理解且常用的部分,删去复杂、不常用的部分,使UB语言精简、简单、易学、易用。
我们认为UB语言是目前最适合RPA领域的编程语言。
UB语言的源代码文件是纯文本格式,扩展名不限,一律采用UTF-8编码。
UB语言的源代码由多条语句组成,和一般的脚本型语言,如Python、JavaScript等一样,UB语言并没有严格的结构和显式指定的入口。执行一个流程块的时候,从第一行开始执行,遇到函数定义暂时跳过,然后继续从函数结束后的一行开始执行(函数的概念请参考这里)。
一般来说,我们推荐一行只写一个语句。如果一定要写多个语句,则用冒号分隔符(:)进行分隔。
如果一行内容不够,需要折行,可以在任意语句中出现的逗号(,
)或二元运算符(“二元运算符”的概念请参考这里)之后直接折行,不需要增加其他额外的符号,也不推荐在其他地方折行。但如果一定要在其他地方折行,则用反斜杠(\
)作为折行符号。例如:
当一行中存在 //
时,表示从这以后的内容都是注释。包含在 /* */
中的内容,无论多少行,都视作注释。例如:
注释在流程运行过程中没有任何作用,仅供阅读方便。
UB语言中所有关键字、变量名、函数名均不区分大小写。例如:变量名abc、ABC或者Abc都被认为是同一个变量。
变量是编程语言中最基础的功能,变量中可以存放数字、字符串等值,并且可以在运行的过程中,随时改变变量中的值。UB语言中的变量是动态类型的,即变量的值和类型都可以在运行过程中动态改变。这符合一般脚本型语言如Python、JavaScript的习惯。变量的类型分为以下几种:整数型、浮点数型、布尔型、字符串型、函数型、复合型和空值型。
整数型的值可以以十进制或者十六进制的方式表示,其中十六进制需加前缀 &H
或 &h
。
浮点数的值可以用常规方式或者科学计数法方式表示。如 0.01
或者 1E-2
或者 1e-2
均代表同一个浮点数。
布尔型的值仅有 True
或者 False
,两者皆不区分大小写。
字符串型的值用一对单引号('
)或一对双引号("
)所包围,字符串中可以用 \t
代表制表符,用 \n
代表换行,用 \'
代表单引号,用 \"
代表双引号,用 \\
代表反斜杠本身(这种表示方式称为“转义”)。字符串中间可以直接换行,无需增加任何其他符号,换行符也会作为字符串的一部分。
也可以用前后各三个单引号('''
)来表示一个字符串,这种字符串被称为长字符串。在长字符串中,可以直接写回车符、单引号和双引号,无需用 \n
,\'
或者 \"
进行转义。
函数型的值只能是已经定义好的函数,在后文详述。
复合型的值包括数组、字典等,在下一节详细阐述。
空值型的值总是 Null
,不区分大小写。
例如:
变量的定义方式是:
定义变量名的同时,可以给变量赋值一个初始值:
想要定义多个变量的话,可以这样定义:
常量的定义方式和变量类似,只是把Dim改为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
语句中的异常变量、函数参数等)。变量在函数范围内定义时,属于局部变量,在函数退出时即清空。在函数范围之外任何位置定义时,属于流程块级变量,在一个流程块的运行过程中都不会清空。流程块级变量可以定义在函数范围外任何位置,不影响其使用,甚至可以在使用变量之后定义。
如果变量未经定义而直接使用,UiBot也不会报错,但会有一个警告提示。这个警告能避免您在输入变量名时产生手误,比如把变量名 cat
误输入成为了 cart
,通过警告信息,您就会发现 cart
是未经定义的,并进一步查出是因为手误所致。
除了常用的整数型、字符串型等简单数据类型之外,UiBot还支持两种复合类型:数组、字典。两者在定义时和简单数据类型变量的定义并无区别。
数组类型变量的表示方法为:使用小写方括号包围起来,使用逗号来分隔每个元素,和 VBScript 中的数组定义类似。范例:
同一个数组中的多个元素的值可以是任意类型,例如:元素的值是整数,就构成一个整数数组;同一个数组中的多个元素也可以是不同类型,例如:第一个元素是整数,第二个元素是字符串等;甚至,一个数组中的元素也可以是另外一个数组,这样就构成了一般意义上的多维数组。范例:
Dim 数组变量 = [值 1, 值 2, [值 11, 值 22], 值 4]
字典类型变量的表示方法为:使用大括号来包围起来,名字和其对应的值为一对,用逗号分隔。范例:
其中 名字 只能是字符串,值 可以是任意类型的表达式。如果您熟悉 JavaScript 或者 JSON,会发现这种初始化方法和 JSON 的表示形式高度相似。
数组和字典类型变量的使用方法为:无论是数组还是字典,要引用其中的元素,均采用方括号作为索引。范例:
使用这种方法引用数组或者字典中的元素,既可以作为左值也可以作为右值,也就是说,既可以读取该变量的值,也可以为该变量的内容赋值,甚至可以在其中增加新的值。范例:
Dim 变量 = [486, 557, 256] // 变量可以用中文命名,初值是一个数组
a = 变量[1] // 此时a被赋值为557
变量 = {"key1":486, "key2":557, "key3":256} // 变量的类型改为一个字典
a = 变量["key1"] // 此时a被赋值为486
注意:在引用数组或字典中的元素时,数组的索引只能是整数类型,用0作为起始索引;字典的索引只能是字符串类型。如果未能正确的使用,会在运行时报错。
数组或者字典的引用是可以嵌套的,如果要引用数组中的数组(即多维数组),或者字典中的数组,可以继续在后面写新的方括号。范例:
UB语言中的运算符及其含义如下表:
+ | - | * | / | & | ^ | < | <= |
---|---|---|---|---|---|---|---|
加法 | 减法/求负 | 乘法 | 除法 | 连接字符串 | 求幂 | 小于 | 小于等于 |
> | >= | <> | = | And | Or | Not | Mod |
---|---|---|---|---|---|---|---|
大于 | 大于等于 | 不相等 | 相等/赋值 | 逻辑与 | 逻辑或 | 逻辑非 | 取余数 |
把变量、常量和值用运算符和圆括号 ( )
连接到一起,称为表达式。在上述运算符中,Not
是一元运算符、-
既可以用作一元运算符,也可以用作二元运算符,其他都是二元运算符。一元运算符只允许在右边出现一个元素(变量、常量、表达式或值),二元运算符只允许在左右两边同时出现两个元素。
注意:当 =
出现在表达式内部时,其含义是判断是否相等。当 =
构成一个独立的语句时,其含义是赋值。这里 =
的设计虽然具有二义性,但能更好的被初学者所接受。
UB语言中删掉一些其他语言中具备、但不常用的运算符,如整数除运算符、位操作运算符等等。因为这些运算符的使用场景较少,即使需要,也可以采用其他方式实现。
表达式常用于赋值语句,可以给某个变量赋值,其形式为:
注意,当表达式为一个独立的(没有使用任何运算符计算)数组、字典类型的变量时,赋值操作只赋值其引用,也就是说,只是为这个变量增加一个“别名”。当一个数组、字典中的元素发生改变时,另一个也会改变。
例如:
即一般编程语言中最常用的If…Else语句,主要用于对某一个或者多个条件进行判断,从而执行不同流程。在UB语言中,有以下几种形式:
当条件满足时,会执行条件之后的语句块,否则,语句块不会执行。Else
后面的语句块则会在前面所有条件都不满足的时候,才会执行。
例如:
根据一定的条件,选择多个分支中的一个。先计算Select Case
后面的表达式
,然后判断是否有某个Case
分支和这个表达式
的值是一致的。如果没有一致的Case
分支,则执行Case Else
(如果有)后面的语句块。
例如:
在UB语言中,使用Do…Loop
语句来实现条件循环,即满足一定条件时,循环执行某一语句块。Do…Loop
语句有以下五种不同的形式,用法较为灵活:
条件
,条件
成立则循环执行语句块,否则自动退出循环。条件
成立则退出循环,否则循环执行语句块。条件
,条件
成立则继续循环执行语句块,否则自动退出循环。条件
,条件
成立则自动退出循环,否则继续循环执行语句块。例如:
计次循环语句主要用于执行一定次数的循环,其基本形式为:
在计次循环语句中,起始值
、结束值
、步长
都只允许是整数型或者浮点数型;步长
可以省略,默认值为1。变量从起始值
开始,每循环一次自动增加步长
,直到大于结束值
,循环才会结束。
在计次循环语句中,循环变量
可以不用Dim
语句定义,直接使用,但在循环结束后就不能再使用了。
例如:
遍历循环语句可以用于处理数组、字典中的每一个元素。遍历循环语句有以下两种形式:
在这种形式的循环语句中,会自动遍历数组、字典中的每一个值,并将其置入循环变量
中,直到遍历完成为止。
或者:
在这种形式的循环语句中,会自动遍历数组、字典中的每一个索引和值,并将其分别置入循环变量1
、循环变量2
中,直到遍历完成为止。
和计次循环语句类似,在遍历循环语句中,循环变量
可以不用Dim
语句定义,直接使用,但在循环结束后就不能再使用了。
例如:
在UB语言中,支持以下形式的循环跳出语句:
只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即跳出当前循环。
只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即结束当前循环,并开始下一次循环。
例如:
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
另外,在流程块中的任何地方,只需要书写
不需要任何参数,即可在执行到此行的时候,自动结束整个流程(不是当前流程块)的执行。
所谓函数,是指把一组常用的功能包装成一个语句块(称之为“定义”),并且可以在其他语句中运行这个语句块(称之为“调用”)。使用函数可以有效的梳理逻辑,以及避免重复代码的编写。
函数的定义和调用没有先后关系,可以先出现调用,再出现定义。但函数必须定义在全局空间中,也就是说,函数定义不能出现在其他函数定义、分支语句、循环语句下面的语句块中。
函数定义中可以包含参数,参数相当于是一个变量,但在调用时,可以由调用者指定这些变量的值。
定义函数的格式如下:
其中,参数定义的格式可以只是一个变量名
,也可以是变量名 = 表达式
的形式。对于后者来说,表示这个参数带有一个“默认值”,其默认值由“表达式”来确定。
如果函数有参数,则参数中的每个变量名都被认为是此函数内已经定义好的局部变量,无需使用Dim
语句定义。
在函数定义中,要退出函数并返回,采用以下写法:
当执行到这一语句时,将跳出函数并返回到调用语句的下一行。返回的时候可以带一个返回值(具体作用下文叙述)。返回值可以忽略,默认为Null
。当执行到函数末尾的时候,无论有没有写Return
语句,都会返回。
例如:
调用函数的格式如下:
或者
按照第一种格式调用,可以指定一个变量作为返回,当函数调用完成后,函数的返回值会自动赋值给这里的返回变量,调用者可以通过返回值,了解到函数调用的情况。此时,必须在被调用的函数名后面加圆括号。而当按照第二种格式调用时,调用者不需要返回值,则可以省略圆括号,使语句更符合自然语言习惯。
当调用时,相当于对函数中的参数进行了一次赋值运算,用表达式的值对其赋值。与赋值运算的规则相同,当表达式为一个独立的(没有使用任何运算符计算)数组、字典时,赋值操作只赋值其引用,也就是说,只是为变量增加一个“别名”。
调用函数时,传入的表达式的数量可以少于参数的数量。如果某个参数没有传入值,或者传入值为Null
,则采用其默认值。没有默认值的参数,调用函数时必须传入值或者表达式。
例如,对于上面定义的函数,可以按照如下的方式调用:
a = Add(100) // 调用Add函数,第二个参数取默认值1,所以a的值是101
b = Add(100, 200) // 调用Add函数,指定了两个参数,所以b的值是300
Add 100, 200 // 调用Add函数,不关心返回值,所以可以不写括号
当函数定义完成后,其名称可以作为一个函数类型的常量使用,也可以把函数名称赋值给某个变量,用这个变量也可以调用这个函数。
例如,对于上面定义的函数Add
,可以按照如下的方式使用:
除了在流程块中定义的函数之外,UB语言中的各种命令实际上就是内置的函数。比如上面例子中的TracePrint
就是一个内置函数,同时也是一条命令。所以,在使用TracePrint
命令的时候,以下两种方式都是可以的:
TracePrint("Hello")
TracePrint "Hello"
请注意:“函数”和“参数”的称呼符合一般编程语言的习惯。但为了更好地让非IT人员理解,我们在UiBot软件本身中多采用“子程序”和“属性”的称呼,来指代“函数”和“参数”。但在本章中,由于介绍的是UB语言,所以仍然维持与其他编程语言一致的习惯,称为“函数”和“参数”。
UB语言支持多模块,可以在UB语言中,调用另一个用UB语言编写的流程块。虽然UB语言并未规定扩展名,但如果要调用另一个UB语言的流程块,则两个流程块需要有相同的扩展名。
我们把被调用的流程块称为“模块”,则去掉扩展名以后,剩下的文件名就是模块的名字。比如某个流程块的文件名为UBTest.task,则其模块名为UBTest。
在UB语言中,采用以下方式导入一个模块:
注意这里的模块名的书写规则和变量名一致,不需要采用双引号,也不需要加扩展名。如Import UBTest。UiBot在编译和运行时会自动按照当前流程块文件的扩展名,为其补充扩展名,并在当前目录下查找。在Windows中,由于文件名不区分大小写,所以Import语句后面的模块名也可以不区分大小写。在其他操作系统中,需要注意模块名的大小写要和文件一致。
每个导入的模块,都会被放置在一个与模块名同名的“命名空间”中,可以通过下面这种方式来调用导入模块中的函数:
即在命名空间和函数名之间加一个点号(.)进行分隔。
导入一个模块之后,既可以调用模块中定义的函数,又可以直接以模块名作为函数名,直接运行这个流程块中的所有命令。例如,有一个流程块 ABC.task。在其他流程块中Import之后,直接采用下面的格式即可直接调用ABC.task(相当于运行了ABC.task这个流程块):
假设流程块 ABC.task中定义了一个函数,名为test,则可以采用下面的格式调用这个函数
作为动态类型语言,有很多错误在编译时难以检查,只能在运行时报错。而且,由于UiBot不强调运行速度,而更强调运行的稳定性,也会在运行时加入比较多的检查。当出错的时候,比较合适的报错手段是抛出异常。 比如,对于界面元素自动化中的“有目标命令”,在运行的时候,如果到了超时时间都不能找到目标,就会自动抛出一个异常。
除了自动抛出的异常之外,在流程块中,还可以采用Throw
语句抛出一个异常:
在抛出异常时,可以把异常相关信息以字符串的形式一起抛出,也可以省略这个字符串。
如果在流程块中没有对异常进行处理,当出现异常时,整个流程都会终止执行,并且把异常相关信息显示出来。如下图所示:
如果不希望流程在发生异常的时候终止,可以采用以下语句对异常进行处理:
如果在Try
后面的语句块中发生了异常,会跳到Catch
后面的语句块中执行。如果在Try
语句块中没有发生异常,且定义了Else
语句块(当然,也可以省略Else
语句块),则会跳到Else
语句块中执行。
Catch
语句后面的变量名可以省略。如果不省略,可以不用Dim
语句提前定义,当发生异常时,这个变量的值是一个字典,其中包含“File”、“Line”和“Message”三个字段,分别代表发生异常的文件名、发生异常的行号、异常包含的信息。
对于界面元素自动化来说,有的时候可能会因为界面卡顿等引起失败,实际上再试一次可能又正常了。因此,在Try
语句后面,还可以加一个“重试次数”。如下所示:
这里的N
可以是变量、表达式或常量,但通常应为整数类型。其含义是:
在Try和Catch之间的语句(如上例中的“语句块1”),如果发生了异常,会自动回到Try的地方去重试,当重试N
次之后,如果仍然有异常,才会跳到Catch后面的语句(如上例中的“语句块2”)去执行。 当在N
次尝试中,只要有1次成功了,就不会再继续后面的尝试(比如第1次异常,第2次没有异常,就不会再试第3次了),而是跳到Else后面的语句(如上例中的“语句块3”)去执行。