软件工程师手册/语言词典/PLI
这是维基百科条目。
PL/I 是一种完整的过程式语言。
程序的入口点指定为OPTIONS(MAIN)
,在过程语句中,例如
tree: procedure options (main);
没有列特性。
赋值示例
a = b*(c-d);
在这个数值赋值语句中,d
从 c
中减去,结果乘以 b
的值,然后存储到变量 a
中。
s = t || u;
在这个字符串赋值示例中,字符串 u
连接到字符串 t
的末尾,组合字符串存储到字符串变量 s
中。
注意:使用编译时选项,连接运算符||
可以替换为另一对符号,以兼容现有的 PL/I 程序,通常使用!!
。
每个注释以/*
开头,以 */
结尾。注释可以跨越多行。
/*
* this is a long comment.
*/
声明不是强制性的,尽管大多数 PL/I 编译器提供一个编译器选项来强制声明,而一些子集编译器则要求声明每个变量。
要将 i
声明为二进制
declare i fixed binary;
要将 i
声明为二进制并将其初始值设置为 0
declare i fixed binary initial (0);
要将变量声明为十进制,
declare D fixed decimal;
declare E fixed decimal (10,4);
第一个声明是存储在十进制格式中的整数 D
,第二个是能够存储十位数字且小数点后有 4 位数字的变量 E
。要初始化 E
,请编写
declare E fixed decimal (10,4) initial (123456.7891);
请注意,诸如 0.1 之类的值是精确存储的。
要定义一个浮点变量
declare f float;
使用单精度硬件;和双精度
declare float decimal (15);
和扩展精度
declare float decimal (18);
(PC 的典型声明)。数字 15 和 18 表示将存储的小数位数。(虽然值实际上以二进制形式保存,但小数精度比以二进制形式指定精度更容易记住。)要指定一个复数变量,只需在声明中包含单词 COMPLEX
,因此
DECLARE C FLOAT DECIMAL (15) COMPLEX;
C = 5.2+8i;
赋值包括复数常数 5.2+8i,其中 5.2 是实部,8 是虚部。
基数(二进制或十进制)、范围(定点或浮点)和模式(实数或复数)的算术数据属性可以按任何顺序出现,精度属性(例如 (15)
)不能是第一个属性。
以下定义是等效的,最后一个除外。
declare number fixed binary (15) real;
declare number binary fixed (15) real;
declare number binary fixed real (15);
declare number binary fixed (15); /* because real is the default value of mode */
declare number (15) binary fixed real; /* defines 15 numbers with default precision */
要声明一个具有图片属性的变量
declare p picture ('9999v.999');
将变量 P
描述为具有 7 位十进制数字,小数点后有三位数字。小数点存储在存储器中。字母 V
指示实际小数点的位置。对于欧洲,规范可以包含逗号
declare p picture ('9999v,999');
要将变量 S
声明为字符字符串
declare S character(10);
字符串 S
可以精确存储 10 个字符。
要将变量 V
声明为能够存储最多 100 个字符的字符字符串
declare V character (100) varying;
简单的赋值
V= 'abc';
V 包含三个字符 abc
。
V = 'friendly';
V 包含 8 个字符 friendly
。
PL/I 也允许位字符串
declare b bit(10);
允许存储长度为 10 的位字符串。赋值语句
b = '1011110010'b;
按给定顺序存储位。此类位字符串可以像字符字符串一样进行搜索等。
可以声明数据结构或聚合
declare 1 name,
2 first_name character (10),
2 surname character (15);
数组通过上述方法之一以及维度信息来声明
declare x(10) float;
指定了一个包含十个元素的数组,x(1)
到 x(10)
。默认下界为 1。要指定不同的下界
declare y(0:15) float;
声明了一个包含 16 个元素的数组 Y(0) 到 Y(15)
,其中元素为浮点型。
多维数组通过逗号分隔维度来声明
declare M ( 2 , 4:5 ) fixed binary (15)
定义了一个包含 4 个元素的二维数组:M (1, 4)
、M (1, 5)
、M (2, 4)
、M (2, 5)
为了处理以两位数年份和四位数年份给出的日期,这些日期可能在同一个程序中混合使用,可以使用 DATE
属性。
declare d date ('YY'); [more to come]
关键字 DECLARE
和 INITIAL
可以缩写为 DCL
和 INIT
,许多属性也可以缩写。
属性 | 缩写 |
---|---|
BINARY
|
BIN
|
DECIMAL
|
DEC
|
CHARACTER
|
CHAR
|
VARYING
|
VAR
|
PICTURE
|
PIC
|
POINTER
|
PTR
|
put list ('Hello world!');
put skip list ('Hello world!');
第一个示例将 Hello world!
写入标准输出(屏幕或打印机或文件)。这些单词在该行上任何已有的输出之后打印。第二个示例在打印之前开始新的一行(单词 SKIP
)。
描述方法/函数/过程是如何声明和实现的。以下示例是一个包含函数的完整 PL/I 程序。
/* A program to calculate the area of a triangle, */
/* given two sides and an included angle. */
d: procedure options (main);
declare (b, c, angle) float;
put list ('Today is ', date());
put skip list ('Please type the lengths of two sides, and an angle');
get list (b, c, angle);
put skip list ('The two sides are', b, c, 'and the angle is', angle, 'degrees');
put list ('Area of triangle=', area(b, c, angle));
area: procedure (b, c, angle) returns (float);
declare (b, c, angle) float;
return (b*c*sin(angle)/2);
end area;
end d;
上述程序包含了一些典型的列表定向形式的输入和输出语句。第一个PUT
语句打印日期。第二个请求读者输入三个值,表示某个三角形的参数。第三个回显输入作为确认。最后一个PUT
语句包含一个函数引用area(b, c, angle)
,它调用了该函数。计算完成后,当前的PUT
语句打印该面积。函数AREA
的定义包括一个PROCEDURE
语句,该语句列出了从调用程序所需的参数,即b
、c
和angle
。这些参数在下一条语句(DECLARE
)中定义。PROCEDURE
语句还说明了要返回给调用程序的结果类型,即浮点值。这由RETURNS (FLOAT)
表示。RETURN
语句包含用于计算三角形面积的公式。然后它将控制权返回给调用程序。RETURN
语句通常包含变量的名称,但也可以包含表达式(如本例所示)。
PL/I 变量的作用域限制在声明它们的程序中。作用域扩展到该程序中包含的任何程序(当然,除了任何程序中出现相同名称的新声明)。
如果未声明变量,则该变量的作用域为整个程序。同样,如果声明(对于同名变量)出现在从属(使用变量)的一个或多个块中,则作用域将从包含声明的块中排除。
PL/I 有两种类型的块。一种是用于子程序和函数的PROCEDURE
块。另一种是BEGIN
块(BEGIN; ... END;
)。BEGIN
块的行为类似于PROCEDURE
块,但BEGIN
块没有参数,也不必有标签。它以正常的顺序执行进入,而PROCEDURE
块只能通过执行CALL
语句或函数引用来执行。
考虑条件语句的两个示例
if a = b then c = d;
if a > b then c = d; else c = e;
第一个示例说明了一个可选执行的语句c = d;
第二个示例显示了两个可选执行的语句,第一个在a
> b
时执行,第二个在a
<= b
时执行;
当需要有条件地执行多个语句时,使用语句括号do
和end
。
if a > b then
do; p = q; r = s; end;
select 组提供多分支评估,它可以用以下两种语法方案之一编写
SELECT ( var_or_expr ); WHEN ( val_1 ) unit_1; .... OTHERWISE unit_other; END;
SELECT ; WHEN ( expr_1 ) unit_1; .... OTHERWISE unit_other; END;
方案类型 1 的 select 组具有以下形式
SELECT ( var_or_expr );
WHEN ( val_1 ) unit_1;
....
WHEN ( val_n ) unit_n;
OTHERWISE unit_other;
END;
var_or_expr
和 val_I
(I
= 1..n)可以是变量或表达式。
unit_I
(I
= 1..n)和unit_other
可以是单个语句或 do 组。
select 组的评估方式为(伪代码)
Evaluate var_or_expr and store it to the variable TEMP. For I = 1 to n: Evaluate val_I, if the result equals TEMP: Execute unit_I and leave the select-group. Execute unit_other.
OTHERWISE
子句(可以缩写为OTHER
)是可选的,但如果省略此子句并且没有任何val_I
等于TEMP
,则会发生运行时错误。
WHEN
子句可以包含多个值,例如
SELECT ( my_char );
WHEN ( '0' , '2' , '4' , '6' , '8' )
put skip list ( 'character is an even digit' );
WHEN ( '1' , '3' , '5' , '7' , '9' )
put skip list ( 'character is an odd digit' );
OTHERWISE
put skip list ( 'character is no decimal digit' );
END;
方案类型 2 的 select 组具有以下形式
SELECT;
WHEN ( expr_1 ) unit_1;
....
WHEN ( expr_n ) unit_n;
OTHERWISE unit_other;
END;
expr_I
(I
= 1..n)可以是逻辑表达式或逻辑变量。
unit_I
(I
= 1..n)和 unit_other 可以是单个语句或 do 组。
select 组的评估方式为(伪代码)
For I = 1 to n: if expr_I is true: Execute unit_I and leave the select-group. Execute unit_other.
一个示例
SELECT;
WHEN ( a > b ) DO; call use_as_max ( a ); put skip list ( 'max = a' ); END;
WHEN ( b > a ) DO; call use_as_max ( b ); put skip list ( 'max = b' ); END;
OTHERWISE ; /* clause with empty statement to avoid run time error if a = b */
END;
循环语句的基本形式为
do k = 1 to 10;
>>body<<
end;
循环体执行十次,控制变量k
取值从 1 到 10。循环退出时,k
的值会增加 1,即 11。循环扩展到下一个END
语句。循环语句的变体有
do k = 1 to n by 2;
do k = 20 to 1 by -1;
可以使用多个迭代规范
do k = 1 to 10, 20 to 100 by 5, 120 to 200 by 20;
其中循环将执行,控制变量k
取值从 1 到 10,然后 20、25、30、35、... 100、120、140、160、180 和 200。
有条件终止的循环使用WHILE
结构
do while (a > b);
此类循环执行零次或多次,测试在循环开始时执行。
do until (a > b);
上述语句指定的循环至少执行一次,测试在循环结束时执行。
在以下示例中,使用REPEAT
指定控制变量值的异常变化。
do k = 1 repeat k*i until (k > 1000);
在这种情况下,控制变量k
取值 1、2、4、8、16,依此类推,直到k
大于 1000。
REPEAT
形式
do next = head repeat next > link until (link = null);
可用于遍历链接列表。
WHILE
和UNTIL
可以组合使用
do while ( W ) until ( U ); ... end;
do until ( U ) while ( W ); ... end;
(两个示例是等效的,即语句中WHILE
和UNTIL
的顺序无关紧要)
DO I = ...
后面可以跟WHILE
或REPEAT
或两者
do I = 1 to 5 while ( W ); ... end;
do I = 1 to 5 until ( U ); ... end;
do I = 1 to 5 while ( W ) until ( U ); ... end;
do I = 1 to 5 until ( U ) while ( W ); ... end;
(最后两个示例是等效的)
评估顺序
do I = 3 to 9 by 2 while ( W ) until ( U );
... some_statements ...
end;
put skip list ( 'I am ready' );
评估方式为
令 I 为 3
如果 I > 9 则转到 [7]
如果 W 为假则转到 [7]
...一些语句...
如果 U 为真则转到 [7]
令 I 为 I + 2 并转到 [2]
put skip list ( 'I am ready' );
另一个示例:以下语句指定 do 组必须执行八次,I 的值分别为 1、3、5、9、18、36、9、18。
do I = 1 to 5 by 2, /* 1 3 5 */
9 repeat 2 * I until ( I > 20 ), /* 9 18 36 */
9 repeat 2 * I while ( I < 21 ); /* 9 18 */
在 PL/I 中,bit (1)
字符串表示单个布尔值
'1'B
解释为True
'0'B
解释为False
Bit (1)
字符串可以在条件和循环语句中用作布尔表达式。
declare my_bit bit (1);
my_bit = ( 1 < 2 ); /* now my_bit has the value '1'B */
my_bit = ( 2 < 1 ); /* now my_bit has the value '0'B */
.....
if my_bit then
put skip list ( 'value of my_bit is true' );
else
put skip list ( 'value of my_bit is false' );
do while ( my_bit );
.....
end;
布尔运算符可用于计算新值
- 前缀运算符
¬
用作逻辑NOT
。 - 中缀运算符
&
用作逻辑AND
。 - 中缀运算符
|
用作逻辑OR
。
declare bit_a bit (1);
declare bit_b bit (1);
declare result bit (1);
result = ¬ bit_a; /* result = '1'B if and only if bit_a is '0'B */
result = bit_a & bit_b; /* result = '1'B if and only if both bit_a and bit_b are '1'B */
result = bit_a | bit_b; /* result = '0'B if and only if both bit_a and bit_b are '0'B */
注意:使用编译时选项,NOT
运算符和OR
运算符可能会被替换为其他符号,为了与现有的 PL/I 程序兼容,通常^
用作NOT
,!
用作OR
。
PL/I 提供错误检测和错误处理。检测到的错误包括
- 浮点溢出和下溢;
- 定点溢出;
- 除以零;
- 下标越界错误;
- 子字符串违规;
- 字符串截断。
- 一般错误。
用户可以允许系统报告错误并执行默认操作,或者用户可以指定在检测到给定错误时要执行的计算操作。
事件(包括错误)称为条件。
要检测条件,必须首先启用它。如果启用了条件并且发生了相应的条件,则会执行相应的ON
单元(如果用户提供)。
考虑以下代码
on overflow snap go to restart;
a = b * c;
...
restart:
如果产品b*c
超过允许的最大值,则会引发OVERFLOW
条件,并执行OVERFLOW
的ON
单元。此处的ON
单元是最简单的(go to
)之一,仅将控制权转移到标记为RESTART
的语句(在该语句中,程序可能处理下一组值)。
关键字SNAP
导致正在运行的程序打印错误消息,以及发生错误的语句编号。
以下是一个更一般的示例
(SUBSCRIPTRANGE):
a: procedure;
declare x(10) float;
declare (i, k) fixed binary;
ON SUBSCRIPTRANGE SNAP BEGIN; PUT DATA (K); STOP; END;
get list (k);
do i = 1 to k;
x(i) = 0;
end;
end a;
在本例中,错误检测和处理部分以大写显示。下标检查由条件前缀SUBSCRIPTRANGE
启用(在PROCEDURE
语句之前用括号括起来)。在执行任何语句之前,必须执行ON
语句,该语句建立发生下标错误时要执行的操作。接下来执行的语句是输入语句GET
,它读取一个值,例如12。进入循环,并初始化x的十个值。在尝试初始化x(11)
时,会引发SUBSCRIPTRANGE
条件,并导致执行ON
单元BEGIN; PUT DATA (K); STOP; END;
。SNAP
导致打印错误消息以及语句号,然后执行BEGIN
块中的语句,即PUT DATA
语句,它打印K = 11
。然后程序STOP
。一些特定的条件是
OVERFLOW | 指数溢出 |
UNDERFLOW | 指数下溢 |
FIXEDOVERFLOW | 定点溢出 |
SIZE | 定点溢出 |
ZERODIVIDE | 被零除 |
SUBSCRIPTRANGE | 下标越界错误 |
STRINGRANGE | 子字符串错误 |
STRINGSIZE | 字符串截断 |
ENDFILE | 文件结束 |
ENDPAGE | 页面结束 |
ATTENTION | 键盘请求 |
提供了一些条件来处理日常事件,例如文件结束和页面结束。例如,在每一页的顶部打印页码
ON ENDPAGE PUT PAGE LIST('Page', Page_no);
检测输入文件IN
的结束,并转移到命名语句
ON ENDFILE(IN) go to next;
程序员可以发明自己的条件,并可以发出信号。
提供SIGNAL
语句用于在程序测试期间测试给定的ON
单元(错误处理程序)。例如,SIGNAL ZERODIVIDE;
要引发名为RANGE
的用户定义条件,将使用语句SIGNAL CONDITION(RANGE);
。
<列出此语言本机提供的容器或容器列表的引用。如果容器不是该语言的本机功能,请列出整合容器的方法。>
任何垃圾回收都是自动的。所有具有automatic属性或使用automatic
(auto
)builtin
分配的变量都会在声明它们的块结束时从存储器中删除。所有分配的存储器(ALLOC
语句或alloc builtin
)都会在程序结束时从存储器中删除。
标准函数库都是自动提供的;无需执行任何操作即可访问它们,例如指定目录或使用INCLUDE
语句等。
- 源程序可以用大写或小写编写。除了字符串之外,大小写之间没有区别。
- 可变字符串(用属性
VARYING
声明)类似于C的char。
对于完全等效的版本,在Enterprise PL/I for z/OS中,可以使用属性VARYINGZ
,其中字符串以零结尾。
如果没有属性VARYING
,则字符串为固定长度。此类字符串始终存储指定数量的字符。因此,
DECLARE S CHARACTER(8);
S = 'abc';
存储八个字符,即abc
后跟五个空格,而
declare V character(8) varying;
V = 'abc';
需要10字节的存储空间:2字节用于V的实际长度,8字节用于字符。后一种功能使得赋值成为可能
V = V || 'dog';
它将单词dog追加到已存储在V中的内容(在本例中最多8个字符,当然)。
Content of V after V = 'abc';: +---+---+---+---+---+---+---+---+---+---+ | 3 | a | b | c | ? | ? | ? | ? | ? | /* "?" means: value is undefined */ +---+---+---+---+---+---+---+---+---+---+ 1 2 3 4 5 6 7 8 /* index of character */
Content of V after V = V || 'dog';: +---+---+---+---+---+---+---+---+---+---+ | 6 | a | b | c | d | o | g | ? | ? | /* "?" means: value is undefined */ +---+---+---+---+---+---+---+---+---+---+ 1 2 3 4 5 6 7 8 /* index of character */
declare i fixed binary;
等效于C的int
;
declare i fixed binary (31);
等效于C的long int
。
declare i fixed binary (63);
等效于C的long long int
。
- 十进制算术,因为它固定点,需要小心。
提供内置函数ADD
、SUBTRACT
、MULTIPLY
和DIVIDE
,使程序员能够指定每个结果的精度。因此,它们有助于避免溢出。对于十进制定点除法,强烈建议使用DIVIDE
函数。因此,要将A
除以B
,其中A
的精度为(10,5)
,B
的精度为(6)
,推荐的形式为
declare A fixed decimal (10,5), B fixed decimal(6), C fixed decimal (10,5);
A = 12345.67891; b = 98765;
C = divide(A, B, 10, 5);
在DIVIDE
引用中,参数10和5分别指示结果A/B具有总共十位数字,小数点后有5位数字。然后将此值存储在C中。IBM PL/I允许最多31位十进制数字用于定点运算。
- 在运行PL/I程序时,在初始过程语句上使用条件前缀
SIZE
、STRINGRANGE
、STRINGSIZE
和SUBSCRIPTRANGE
,如下所示
(SIZE, STRINGRANGE, STRINGSIZE, SUBSCRIPTRANGE):
trial: procedure options (main);
这使得可以检查定点溢出,以及字符串和下标的范围检查和截断。
- 数组的下界默认为1,而不是0。
- 整数(无论是十进制、二进制还是混合)的除法可能会产生具有比例因子的定点结果。因此,9/2产生4.5000000,这与产生整数4的Fortran不同。如果需要整数结果,可以使用
TRUNC
或DIVIDE
,例如:TRUNC(9/2)
或更一般地 -DIVIDE(J, K, 31)
,后者为二进制整数结果提供31位的精度。或者,将二进制整数变量声明为具有最大精度(通常为31位)将确保两个此类整数变量相除的结果将产生整数结果。
- J. K. Hughes,PL/I结构化编程,第3版,Wiley,1986年。(初学者到专业人士;商业应用)
- R. Reddy和C. Ziegler,PL/I:结构化编程和问题解决,West,1986年,ISBN 0-314-93915-6。(初学者到高级)
- R. A. Barnes,程序员的PL/I,North-Holland,1979年。(专业)
- G. F. Groner,PL/I在技术应用中的编程,按需印刷书籍,密歇根州安阿伯,1971年。(专业)
- M. E. Anderson,程序员的PL/I,Prentice-Hall,1973年。(专业)
- D. R. Stoutemyer,工程与科学的PL/I编程,Prentice-Hall,1971年。(专业)
- E. Sturm,Das neue PL/I(für PC、Workstations和Mainframe),第5版,Vieweg-Verlag,2002年。