6 UB语言参考

除了可视化视图之外,还有很多用户喜欢使用UiBot的源代码视图来编写一个流程块。源代码视图使用一种UiBot自创的编程语言BotScript(以下简称UB语言)来描述流程块。在这一章,我们先学习UB语言的基本规则,为后面学习源代码视图打下基础。

本章需要读者有一点点编程基础,任何编程语言都可以,只要了解变量、函数等基本概念即可。如果完全没有基础,请先阅读初级开发者指南的附录部分,以便快速入门。

6.1 概述

前文提到,UiBot的设计理念是“强大”、“简单”、“快捷”。简言之,UiBot既要让没有计算机基础的初学者,通过简单的学习,即可快速掌握流程的编写方法;又要让有一定编程基础的专业人员,能够以最快的速度实现自己的流程。

为了实现这些指标,UiBot提供了可视化的流程编写界面,便于初学者快速掌握;同时提供了一种简单、易学、接近自然语言的UB编程语言,便于专业人员的快速实现。当然,同一个流程块,可以用两种界面来显示,并可以在开发过程中随时切换。

这一章主要介绍UB语言的基本语法规则。具有基本编程基础的读者,大约在两小时内即可掌握此规则,再经过数个小时的熟悉,即可灵活运用。对于有按键精灵基础的读者,还能进一步缩短学习时间。

对于UiBot来说,编程语言只是表达逻辑的工具,关键的功能还是由命令库或插件来实现。所以,语言设计只包括基本的逻辑,所有具体的功能,哪怕是最基本的“延时”功能,都不列入语言设计中,而在命令库中单独设计。本章内容亦不包命令库的介绍。

UB语言是专门设计的,而不是市面上流行的编程语言如Python、JavaScript等,是因为UiBot的主要受众是那些非计算机专业科班出身,但足够熟悉业务流程的非技术人员。UB语言的设计尽可能的接近自然语言,对于理解基本英文单词的人来说,即使没有学习过,也能大致读懂。

相比之下,以JavaScript为例,虽然JavaScript是一种很优秀的语言,在专业的程序员手里能发挥出很高的效率,甚至UiBot本身都有一部分代码是使用JavaScript编写的。但这种语言里面大量使用的括号,容易给非专业人员的学习带来障碍。如下图:

复杂的JavaScript
复杂的JavaScript

因此,我们设计了专门的UB语言,并使这门语言尽可能简化,甚至尽可能少用除了字母和数字之外的元素。实际上,我们也考虑过使用市面上流行的编程语言的可能性,因为如果这样做,我们的开发UiBot的工作量会大大降低,但与此同时,您的学习难度则会大大提高。所以,我们否定了这种思路,决定不采用流行的编程语言如Python等,非不能也,是不为也。

但是,在UB语言中,吸取了很多其他编程语言的优点。您会在UB语言的设计中看到Basic语言、Python语言、JavaScript语言的一些特点。因为我们在充分理解的基础上,博取众家之长,吸取最容易理解且常用的部分,删去复杂、不常用的部分,使UB语言精简、简单、易学、易用。

我们认为UB语言是目前最适合RPA领域的编程语言。

6.2 基本结构

UB语言的源代码文件是纯文本格式,扩展名不限,一律采用UTF-8编码。

UB语言的源代码由多条语句组成,和一般的脚本型语言,如Python、JavaScript等一样,UB语言并没有严格的结构和显式指定的入口。执行一个流程块的时候,从第一行开始执行,遇到函数定义暂时跳过,然后继续从函数结束后的一行开始执行(函数的概念请参考这里)。

一般来说,我们推荐一行只写一个语句。如果一定要写多个语句,则用冒号分隔符(:)进行分隔。

如果一行内容不够,需要折行,可以在任意语句中出现的逗号(,)或二元运算符(“二元运算符”的概念请参考这里)之后直接折行,不需要增加其他额外的符号,也不推荐在其他地方折行。但如果一定要在其他地方折行,则用反斜杠(\)作为折行符号。例如:

当一行中存在 // 时,表示从这以后的内容都是注释。包含在 /* */ 中的内容,无论多少行,都视作注释。例如:

注释在流程运行过程中没有任何作用,仅供阅读方便。

UB语言中所有关键字、变量名、函数名均不区分大小写。例如:变量名abc、ABC或者Abc都被认为是同一个变量。

6.3 变量、常量和数据类型

6.3.1 数据类型

变量是编程语言中最基础的功能,变量中可以存放数字、字符串等值,并且可以在运行的过程中,随时改变变量中的值。UB语言中的变量是动态类型的,即变量的值和类型都可以在运行过程中动态改变。这符合一般脚本型语言如Python、JavaScript的习惯。变量的类型分为以下几种:整数型、浮点数型、布尔型、字符串型、函数型、复合型和空值型。

整数型的值可以以十进制或者十六进制的方式表示,其中十六进制需加前缀 &H&h

浮点数的值可以用常规方式或者科学计数法方式表示。如 0.01 或者 1E-2 或者 1e-2 均代表同一个浮点数。

布尔型的值仅有 True 或者 False,两者皆不区分大小写。

字符串型的值用一对单引号(')或一对双引号(")所包围,字符串中可以用 \t 代表制表符,用 \n 代表换行,用 \' 代表单引号,用 \" 代表双引号,用 \\ 代表反斜杠本身(这种表示方式称为“转义”)。字符串中间可以直接换行,无需增加任何其他符号,换行符也会作为字符串的一部分。

也可以用前后各三个单引号(''')来表示一个字符串,这种字符串被称为长字符串。在长字符串中,可以直接写回车符、单引号和双引号,无需用 \n\' 或者 \" 进行转义。

函数型的值只能是已经定义好的函数,在后文详述。

复合型的值包括数组、字典等,在下一节详细阐述。

空值型的值总是 Null,不区分大小写。

例如:

6.3.2 变量和常量

变量的定义方式是:

定义变量名的同时,可以给变量赋值一个初始值:

想要定义多个变量的话,可以这样定义:

常量的定义方式和变量类似,只是把Dim改为Const,并且必须在定义时就指定值:

常量和变量的唯一区别是,常量只能在定义时指定一次值,后面不允许再修改。

例如:

对于有命名的变量、常量、函数等,其名字统称为标识符,标识符需要遵循一定规则定义。

标识符可以用英文字母、下划线(_),任意UTF-8编码中包含的除英语以外其他语言的字符(当然,也包括汉字)来表示,除了第一个字符外,后面还可以使用0-9的数字。当使用英文字母时,变量名不区分大小写。

UB语言推荐变量经过定义再使用(除了For语句中的循环变量、Try语句中的异常变量、函数参数等)。变量在函数范围内定义时,属于局部变量,在函数退出时即清空。在函数范围之外任何位置定义时,属于流程块级变量,在一个流程块的运行过程中都不会清空。流程块级变量可以定义在函数范围外任何位置,不影响其使用,甚至可以在使用变量之后定义。

如果变量未经定义而直接使用,UiBot也不会报错,但会有一个警告提示。这个警告能避免您在输入变量名时产生手误,比如把变量名 cat 误输入成为了 cart,通过警告信息,您就会发现 cart 是未经定义的,并进一步查出是因为手误所致。

6.3.3 复合类型

除了常用的整数型、字符串型等简单数据类型之外,UiBot还支持两种复合类型:数组、字典。两者在定义时和简单数据类型变量的定义并无区别。

数组类型变量的表示方法为:使用小写方括号包围起来,使用逗号来分隔每个元素,和 VBScript 中的数组定义类似。范例:

同一个数组中的多个元素的值可以是任意类型,例如:元素的值是整数,就构成一个整数数组;同一个数组中的多个元素也可以是不同类型,例如:第一个元素是整数,第二个元素是字符串等;甚至,一个数组中的元素也可以是另外一个数组,这样就构成了一般意义上的多维数组。范例:

Dim 数组变量 = [值 1, 值 2, [值 11, 值 22], 值 4]

字典类型变量的表示方法为:使用大括号来包围起来,名字和其对应的值为一对,用逗号分隔。范例:

其中 名字 只能是字符串, 可以是任意类型的表达式。如果您熟悉 JavaScript 或者 JSON,会发现这种初始化方法和 JSON 的表示形式高度相似。

数组和字典类型变量的使用方法为:无论是数组还是字典,要引用其中的元素,均采用方括号作为索引。范例:

使用这种方法引用数组或者字典中的元素,既可以作为左值也可以作为右值,也就是说,既可以读取该变量的值,也可以为该变量的内容赋值,甚至可以在其中增加新的值。范例:

注意:在引用数组或字典中的元素时,数组的索引只能是整数类型,用0作为起始索引;字典的索引只能是字符串类型。如果未能正确的使用,会在运行时报错。

数组或者字典的引用是可以嵌套的,如果要引用数组中的数组(即多维数组),或者字典中的数组,可以继续在后面写新的方括号。范例:

6.4 运算符和表达式

UB语言中的运算符及其含义如下表:

+ - * / & ^ < <=
加法 减法/求负 乘法 除法 连接字符串 求幂 小于 小于等于
> >= <> = And Or Not Mod
大于 大于等于 不相等 相等/赋值 逻辑与 逻辑或 逻辑非 取余数

把变量、常量和值用运算符和圆括号 ( ) 连接到一起,称为表达式。在上述运算符中,Not 是一元运算符、- 既可以用作一元运算符,也可以用作二元运算符,其他都是二元运算符。一元运算符只允许在右边出现一个元素(变量、常量、表达式或值),二元运算符只允许在左右两边同时出现两个元素。

注意:当 = 出现在表达式内部时,其含义是判断是否相等。当 = 构成一个独立的语句时,其含义是赋值。这里 = 的设计虽然具有二义性,但能更好的被初学者所接受。

UB语言中删掉一些其他语言中具备、但不常用的运算符,如整数除运算符、位操作运算符等等。因为这些运算符的使用场景较少,即使需要,也可以采用其他方式实现。

表达式常用于赋值语句,可以给某个变量赋值,其形式为:

注意,当表达式为一个独立的(没有使用任何运算符计算)数组、字典类型的变量时,赋值操作只赋值其引用,也就是说,只是为这个变量增加一个“别名”。当一个数组、字典中的元素发生改变时,另一个也会改变。

例如:

6.5 逻辑语句

6.5.1 条件分支语句

即一般编程语言中最常用的If…Else语句,主要用于对某一个或者多个条件进行判断,从而执行不同流程。在UB语言中,有以下几种形式:

当条件满足时,会执行条件之后的语句块,否则,语句块不会执行。Else后面的语句块则会在前面所有条件都不满足的时候,才会执行。

例如:

6.5.2 选择分支语句

根据一定的条件,选择多个分支中的一个。先计算Select Case后面的表达式,然后判断是否有某个Case分支和这个表达式的值是一致的。如果没有一致的Case分支,则执行Case Else(如果有)后面的语句块。

例如:

6.5.3 条件循环语句

在UB语言中,使用Do…Loop语句来实现条件循环,即满足一定条件时,循环执行某一语句块。Do…Loop语句有以下五种不同的形式,用法较为灵活:

  1. 前置条件成立则循环:先判断条件条件成立则循环执行语句块,否则自动退出循环。
  1. 前置条件不成立则循环:和前一条相反,条件成立则退出循环,否则循环执行语句块。
  1. 后置条件成立则循环:先执行语句块,再判断条件条件成立则继续循环执行语句块,否则自动退出循环。
  1. 后置条件不成立则循环:先执行语句块,再判断条件条件成立则自动退出循环,否则继续循环执行语句块。
  1. 无限循环:该循环语句本身不进行任何条件的判断,需要在语句块中自行做判断,如果语句块中没有跳出循环的语句,则会无限的执行该循环

例如:

6.5.4 计次循环语句

计次循环语句主要用于执行一定次数的循环,其基本形式为:

在计次循环语句中,起始值结束值步长都只允许是整数型或者浮点数型;步长可以省略,默认值为1。变量从起始值开始,每循环一次自动增加步长,直到大于结束值,循环才会结束。

在计次循环语句中,循环变量可以不用Dim语句定义,直接使用,但在循环结束后就不能再使用了。

例如:

6.5.5 遍历循环语句

遍历循环语句可以用于处理数组、字典中的每一个元素。遍历循环语句有以下两种形式:

在这种形式的循环语句中,会自动遍历数组、字典中的每一个值,并将其置入循环变量中,直到遍历完成为止。

或者:

在这种形式的循环语句中,会自动遍历数组、字典中的每一个索引和值,并将其分别置入循环变量1循环变量2中,直到遍历完成为止。

和计次循环语句类似,在遍历循环语句中,循环变量可以不用Dim语句定义,直接使用,但在循环结束后就不能再使用了。

例如:

6.5.6 跳出语句

在UB语言中,支持以下形式的循环跳出语句:

只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即跳出当前循环。

只能出现在条件循环、计次循环或遍历循环等循环语句的内部语句块中,其含义是立即结束当前循环,并开始下一次循环。

例如:

另外,在流程块中的任何地方,只需要书写

不需要任何参数,即可在执行到此行的时候,自动结束整个流程(不是当前流程块)的执行。

6.6 函数

所谓函数,是指把一组常用的功能包装成一个语句块(称之为“定义”),并且可以在其他语句中运行这个语句块(称之为“调用”)。使用函数可以有效的梳理逻辑,以及避免重复代码的编写。

函数的定义和调用没有先后关系,可以先出现调用,再出现定义。但函数必须定义在全局空间中,也就是说,函数定义不能出现在其他函数定义、分支语句、循环语句下面的语句块中。

函数定义中可以包含参数,参数相当于是一个变量,但在调用时,可以由调用者指定这些变量的值。

定义函数的格式如下:

  • 无参数的函数
  • 有参数的函数

其中,参数定义的格式可以只是一个变量名,也可以是变量名 = 表达式的形式。对于后者来说,表示这个参数带有一个“默认值”,其默认值由“表达式”来确定。

如果函数有参数,则参数中的每个变量名都被认为是此函数内已经定义好的局部变量,无需使用Dim语句定义。

在函数定义中,要退出函数并返回,采用以下写法:

当执行到这一语句时,将跳出函数并返回到调用语句的下一行。返回的时候可以带一个返回值(具体作用下文叙述)。返回值可以忽略,默认为Null。当执行到函数末尾的时候,无论有没有写Return语句,都会返回。

例如:

调用函数的格式如下:

或者

按照第一种格式调用,可以指定一个变量作为返回,当函数调用完成后,函数的返回值会自动赋值给这里的返回变量,调用者可以通过返回值,了解到函数调用的情况。此时,必须在被调用的函数名后面加圆括号。而当按照第二种格式调用时,调用者不需要返回值,则可以省略圆括号,使语句更符合自然语言习惯。

当调用时,相当于对函数中的参数进行了一次赋值运算,用表达式的值对其赋值。与赋值运算的规则相同,当表达式为一个独立的(没有使用任何运算符计算)数组、字典时,赋值操作只赋值其引用,也就是说,只是为变量增加一个“别名”。

调用函数时,传入的表达式的数量可以少于参数的数量。如果某个参数没有传入值,或者传入值为Null,则采用其默认值。没有默认值的参数,调用函数时必须传入值或者表达式。

例如,对于上面定义的函数,可以按照如下的方式调用:

当函数定义完成后,其名称可以作为一个函数类型的常量使用,也可以把函数名称赋值给某个变量,用这个变量也可以调用这个函数。

例如,对于上面定义的函数Add,可以按照如下的方式使用:

除了在流程块中定义的函数之外,UB语言中的各种命令实际上就是内置的函数。比如上面例子中的TracePrint就是一个内置函数,同时也是一条命令。所以,在使用TracePrint命令的时候,以下两种方式都是可以的:

  • TracePrint("Hello")
  • TracePrint "Hello"

请注意:“函数”和“参数”的称呼符合一般编程语言的习惯。但为了更好地让非IT人员理解,我们在UiBot软件本身中多采用“子程序”和“属性”的称呼,来指代“函数”和“参数”。但在本章中,由于介绍的是UB语言,所以仍然维持与其他编程语言一致的习惯,称为“函数”和“参数”。

6.7 其他

6.7.1 多模块

UB语言支持多模块,可以在UB语言中,调用另一个用UB语言编写的流程块。虽然UB语言并未规定扩展名,但如果要调用另一个UB语言的流程块,则两个流程块需要有相同的扩展名。

我们把被调用的流程块称为“模块”,则去掉扩展名以后,剩下的文件名就是模块的名字。比如某个流程块的文件名为UBTest.task,则其模块名为UBTest。

在UB语言中,采用以下方式导入一个模块:

注意这里的模块名的书写规则和变量名一致,不需要采用双引号,也不需要加扩展名。如Import UBTest。UiBot在编译和运行时会自动按照当前流程块文件的扩展名,为其补充扩展名,并在当前目录下查找。在Windows中,由于文件名不区分大小写,所以Import语句后面的模块名也可以不区分大小写。在其他操作系统中,需要注意模块名的大小写要和文件一致。

每个导入的模块,都会被放置在一个与模块名同名的“命名空间”中,可以通过下面这种方式来调用导入模块中的函数:

即在命名空间和函数名之间加一个点号(.)进行分隔。

导入一个模块之后,既可以调用模块中定义的函数,又可以直接以模块名作为函数名,直接运行这个流程块中的所有命令。例如,有一个流程块 ABC.task。在其他流程块中Import之后,直接采用下面的格式即可直接调用ABC.task(相当于运行了ABC.task这个流程块):

假设流程块 ABC.task中定义了一个函数,名为test,则可以采用下面的格式调用这个函数

6.7.2 异常

作为动态类型语言,有很多错误在编译时难以检查,只能在运行时报错。而且,由于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”)去执行。