EPICS StreamDevice模块--3

四、格式化转换器

1、格式化语法

StreamDevice格式化转换器作用非常类似C函数printf()和scanf()格式化转换器。但StreamDevice提供了更多不同的转换器,并且你也可以编写自己的转换器。在引号字符串中指定的格式作为out或in命令的参数。

一个格式转换器由以下组成:

  • %字符
  • 可选地在()一个字段或者记录名
  • 可选地出自*# +0-?=!的标记
  • 可选地一个整数宽度字段
  • 可选的一个点字符(.)后跟一个整数精度字段(输入,仅用于大多数格式)
  • 一个转换字符
  • 某些转换器所需的其它信息

一个例外是%%序列,它代表单个文字%。为了兼容C函数printf()和scanf()添加了这个。它作用与转义百分号\%相同。

标记*# +0-作用像在C函数printf()和scanf()中。标记?,=和!是扩展。

*标记跳过在输入格式化中数据。输入被消耗和解析,不匹配是一个错误,但读取的数据被丢弃。如果输入包含多个值时,这是有用的。示例:in "%*f%f";读取第二个浮点数值。

#标记可以更改格式,取决于这个转换器。

' '(空格)和+标记通常在正数前打印一个空格或者+号,而负数将有一个-。某些转换器可以重新定义这些标记的含义。

0标记通常表示如果宽度大于所需,数值应该被左填充0。某些转换器可以重新定义这个标记的含义。

-标记通常指定如果宽度大于所需,输出应该被左对齐。某些转换器可以重新定义这个标记的含义。

?标记使得出错的输入转换以一个默认0值成功(0, 0.0, 或"",取决于格式类型)。

=标记允许比较输入和当前值。仅在输入格式化中允许它。不是从输入读取一个新值,当前值被格式化并且介质与输入比较。

!标记要求输入完全是width字节长(通常width定义用很多格式读取的最大字节数)。

示例:

  • in "%f"; 读取一个浮点值。
  • out "%(HOPR)7.4f"; 以4位精度的7字符浮点写HOPR字段。
  • out "%#010x";  使用别的格式(以0x开头)写一个0填充的10字符十六进制整数。
  • in "%[_a-zA-Z0-9]"; 读取一个字面数字或下划线的字符串。
  • in "%*i"; 跳过一个整数数值。
  • in "%?d"; 读取一个十进制数值或者如果这出错,假装那个值是0
  • in "%=.3f"; 确保输入等于一个以3位精度浮点格式化的当前值。
  • in "%!5d"; 预计完全5个十进制数字。数字少了被认为数据丢失并且使格式化失败。
  • in "%d%%"; 读取一个十进制数值后跟一个%符号

2、数据类型和记录字段

默认字段

每次转换字符对应数据类型DOUBLE,LONG,ULONG,ENUM或STRING之一。对比C函数printf()和scanf(),不需要指定一个要转换的变量。变量一般是记录的VAL或RVAL字段,取决于数据类型自动选择。不是所有数据类型对所有记录类型都有意义。详细请参考可支持的记录类型。

StreamDevice在float和double之间不做区分,在short,int和long之间也不做区分。因而,像l或h的数据类型修饰符在StreamDevice格式化中不存在。

I/O重定向到其它记录或字段

要使用一个记录默认字段外或者甚至使用相同IOC上其它记录的字段的格式化,使用语法%(record.FIELD)。如果仅指定一个字段名,而没有记录,认为是有效的记录。如果仅指定一个记录名而没有字段名,认为是VAL字段。

示例1out "%(EGU)s"; 输出到有效记录的EGU字段。

示例2in "%(otherrecord.RVAL)i"; 在另一个记录的RVAL字段中存储接收到的整数值并且接着运行那个记录。为了转换RVAL到VAL,其它记录应该可能使用DTYP="Raw Soft Channel"。

示例3in "%(otherrecord)f"; 在其它记录的VAL字段中存储接收到的浮点值。其它记录应该可能使用DTYP="Soft Channel"。在其它记录名与有效记录一个字段名相同的不太可能情况中(例如,如果你命名一个记录"DESC"),则使用.VAL显式地指向这个记录,而不是有效记录的字段。

这种特性在一输入行包含需要被存储到多个记录的多个值或者一输出行需要被从来自多个记录的值构建的情况时非常有用。为了避免在协议文件中使用完整的记录名,推荐传递其它记录的名称或部分名称(例如:设备前缀)作为一个协议参数。在这种情况,重定向通常看起来像这样:in "%(\$1recordpart)f"并且这个记录像这样调用这个协议:field(INP, "@protocolfile protocol($(PREFIX)) $(PORT)"),为前缀部分使用一个宏,其接着用于\$1。

如果其它记录是被动的,并且这个字段有PP属性,这个记录将被运行。这个记录字段的数据类型兼容于转换器的数据类型是你的责任。STRING格式兼容CHAR或UCHAR数组。

注意:使用这种语法到目前效率低于使用默认字段。到目前,如果有任何错误,设置其它记录为一个警报状态是不可能的。如果错误发生在处理它前或者在处理它时,只是不运行它,如果错误发生在处理它之后,它将已经被处理。

伪转换器

一些格式实际不是转换器。它们格式化不被存储在一个记录字段中的数据,诸如校验或正则表达式替换。没有数据类型对应那些伪转换器并且不能使用%(FIELD)语法。

3、标准的DOUBLE转换器(%f, %e, %E, %g, %G)

输出:%f打印固定点,%e打印e指数标记,而%g取决于这个值的大小打印固定点或e指数。%E和%G使用E替代e分隔指数部分。

用#标记,输出总是包含一个点字符。

输入:所有这些格式是相当的。跳过开头的空格。

用#标记,在符号和数值之间的其它空白被接受。

当给出一个最大字段宽度,当使用空白标记时,开头的空格才算入这个字段宽度。

4、标准的LONG和ULONG转换器(%d, %i, %u, %x, %X)

输出:%d和%i打印有符号十进制,%u打印无符号十进制数,%o无符号八进制,和%x或%X无符号十六进制数。%X使用大写字母。

用#标记,八进制值带前缀0,而16进制值带前缀0x或0X。

与printf不同,%x和%X截短输出到指定宽度。

输入:%d匹配有符号十进制,%u匹配无符号十进制,%o无符号八进制。%x和%X都匹配大小写无符号十六进制。八进制和十六进制值可以可选地带前缀。%i匹配十进制、或带前缀八进制或十六进制形式的整数。跳过开头的空格。

用-标记,接受负的八进制和十六进制值。

用#标记,接受符号和数值之间的其它空格。

当指定一个最大字段宽度时,在使用空格标记时,开头的空格才算入字段宽度。

5、标准的STRING转换器(%s, %c)

输出:%s打印一个字符串。如果制定了精度,这是最大字符串长度。%c是在输出中一个LONG格式,打印一个字符。

输入:%s匹配一个非空白字符的序列并且%c匹配一个非null字符的序列。最大字符串长度由width指定。默认宽度对于%s是无限的,而对于%c是1。除非当使用空白标记,但没有用%c,否则用%s忽略开头的空格。

用#标记,%s匹配一个非null字符的序列,而不是非空格字符。

用0标记,%s用0字节而不是空格填充。

6、标准字符集STRING转换器(%[charset])

这是一个仅输入格式。它匹配一个来自字符集字符的序列。如果字符集以^开始,这个格式匹配不在字符集中所有字符。开头空白不被忽略。

示例:%[_a-z]匹配一个完全由_(下划线)或者a到z字母组成的字符串。

7、ENUM转换器(%{string0|string1|...})

这个格式在一个字符串集合上映射一个无符号整数值。值0对应string0等等。字符串被|分隔。

示例:%{OFF|STANDBY|ON}映射字符串OFF为值0,STANDBY为1和ON为2。

当使用#标记,允许它使用=赋整数值给字符串。未被赋值的字符串通常增加它们的值1。

如果一个字符串是另一个字符串的初始子串,这个子串必须之后出现来确保正确的匹配。尤其,如果一个字符串是空串,因为它总是匹配,它必须是最后一个字符串。如果需要,使用#和=来重新编号。

对末尾字符串使用赋值=?来确保它为输出格式的默认值。

示例:%#{neg=-1|stop|pos|fast=10|rewind=-10}。

如果字符串之一包含|或}(或如果使用了#标记,=),一个\必须用于转义这个字符。

输出:取决于这个值,打印其中一个字符串,或如果给定和没有值匹配,默认。

输入:如果任何字符串匹配,相应地设置这个值。

8、二进制LONG或ULONG转换器(%b, %B zo)

这个格式或扫描一个以二进制字符串表示地无符号整数(每位一个字符)。%b格式使用字符0和1。用%B格式,你可以选择其它两个字符表示0和1。用#标记,位序被更改成小端,即:最低位第一。

示例:%B.!或者%B\x00\xff。%B01等价于%b。

在输出中,如果宽度大于有效位数目,则标记0表示这个值被选择的0字符而不是空格填充。如果设置了精度,它表示有效位的数目。否则,最高1位定义了有效位数目。

在输入中,跳过开头空白。读取最大width个字符。用第一个非0或1字符的字符停止转换。

9、Raw LONG或ULONG转换器(%r)

raw转换器不真的"转换"。用计算机内部表示(通常2的补码)写或读取一个有符号或无符号整数值。通常字节序是大端,即:最高位最先。用#标记,字节序被更改为小端,即:最低位最先。用0标记,这个值是无符号的,否则有符号。

在输出中,取决于0标记,值的precsion(或sizeof(long)只要小一点)个最低位字节被符号扩展或者0扩展。precsion默认值是1.因而如果你不指定精度,仅写最低位字节!写out "%2r";而不是out "%.2r";是一个常见错误。

在输入中,width个字节被读取并且放入这个值。如果width大于一个long的尺寸,仅使用最低位字节。如果width小于一个long的尺寸,取决于0标记,这个值被有符号扩展或者0扩展。

示例:out "%.2r"; in "%02r";

10、Raw Double转换器(%R)

raw转换器不真的是"转换器"。用计算机内部表示(可能IEEE)写或读取一个float或double值。正常字节序是大端,即,最高位在前。用#标记,字节序被改为小端,即:最低位在先。width必须是4(float)或8(double)。默认是4。

11、BCD码(二进制编码的十进制)LONG或ULONG转换器(%D)

BCD码是一种格式,在此格式中每个字节包含两个二进制编码十进制数字(0...9)。因而,一个BCD字节范围从0x00到0x99。正常字节序是大端,即最高位在前。用#标记,字节序被改为小端,即,最低位在先。+标记定义这个值是有符号的,使用最高位字节的一半用于这个符号。否则这个值是无符号的。

在输出中,用至少width个输出字节打印precision个十进制数字。有符号负值在它们最高位半字节有0xF后跟这个绝对值。

在输入中,读取width个字节。如果这个值有符号,在最高位中1被解析成一个负号。用(在符号后)第一个不表示一个BCD值得字节停止输入,即上半或下半字节大于0。

12、校验和伪转换器(%(<checksum>))

这不是一个常规的"转换器",因为没有用户数据被转换。而是,从输入或输出就按一个校验和。width字段是从其开始计算校验和的字节序号。默认是0,即:从当前命令的输入或输出的第一个字节。末尾字节是在这个校验和前倒数precision个字节(默认0)。例如:在"abcdefg%<xor>"从abcdefg计算校验和,但在"abcdefg%2.1<xor>"只从cdef计算。

通常,多字节校验和是大端字节序,即:最高位字节在前。当用#标记时,字节序被改成了小端,即:最低字节在前。

0标记更改校验和表示为十六进制ASCII(每个校验和字节2个字符)

-标记更改校验和表示为"穷人的十六进制":0x30 ... 0x3f(每个校验字节2个字符)

+标记更改校验和表示为十进制ASCII(用%d格式化)

在输出中,追加校验和。

在输入中,下面的字节必须匹配这个校验和。

实现的检验和函数:

1) %<sum>或%<sum8>:一个字节。所有字符的和模2^8。

2) %<sum16>:两个字节。所有字符的和模2^16。

3) %<sum32>:四个字节。所有字符的和模2^32。

4) %<negsum>, %<nsum>, %<-sum>, %<negsum8>, %<sum8>或%<-sum8>:一个字节。所有字符的和取负数模2^8。

5) %<negsum16>, %<nsum16>或%<-sum16>:两个字节。所有字符的和取负数模2^16。

6) %<negsum32>, %<nsum32>或%<-sum32>:四个字节。所有字符的和取负数模2^32。

7) %<notsum>, %<~sum>:一个字节。所有字符的和的位取反模2^8。

8) %<xor>:一个字节。所有字节xor一起。

9) %<xor7>:一个字节。所有字节xor一起并且位与0x7F。

10) %<crc8>:一个字节。一个经常使用的8位crc校验和(poly=0x07, init=0x00, xorout=0x00)。

11) %<ccitt8>:一个字节。CCITT标准的8位crc校验和(poly=0x31, init=0x00, xorout=0x00,反射)

12) %<crc16>:两个字节。一个常用的16位crc校验和(poly=0x8005, init=0x0000, xorout=0x00)。

13) %<crc16r>:两个字节。一个常用的反射16位crc校验和(poly=0x8005, init=0x00, xorout=0x00,反射的)。

14) %<modbus>:两个字节。modbus 16位crc校验和(poly=0x8005, init=0xffff, xorout=0x00, 反射)。

15) %<ccitt16>:两个字节。CCITT标准16位crc校验和(poly=0x1021, init=0xFFFF, xorout=0x0000)的通常实现。

16) %<ccitt16a>:两个字节。带增强的CCITT标准16位crc校验和(poly=0x1021, init=0x1D0F, xorout=0x0000)的反常实现。

17) %<ccitt16x>或%<crc16c>或%<xmodem>:两个字节。XMODEM校验和(poly=0x1021, init=0x0000, xorout=0x0000)。

18) %<crc32>:四个字节。标准的32位crc校验和。(poly=0x04C11DB7,init=0xFFFFFFFF,xorout=0xFFFFFFFF)。

19) %<crc32r>:四个字节。标准的反射32位crc校验和。(poly=0x04C11DB7,init=0xFFFFFFFF,xorout=0xFFFFFFFF,反射的)

20) %<jamcrc>:四个字节。另一种反射的32位crc校验和。(poly=0x04C11DB, init=0xFFFFFFFF, xorout=0x00000000, 反射的)

21) %<adler32>:四个字节。根据RFC 1950的Adler32校验和。

22) %<hexsum8>:一个字节。所有hex数字之和。(忽略其它字符)。

23) %<lrc>:一个字节。纵向冗余检查。

24)%<hexlrc>:一个字节。对hex数字的LRC(其它字符被忽略。)

13、正则表达式STRING转换器(%/regex/)

仅输入格式匹配Perl兼容的正则表达式(PCRE)。只在安装了PCRE库时,它才可用。

如果PCRE对于你主机或交叉架构不可用,从www.pcre.org下载源代码,并且尝试我的EPICS兼容的Makefile像一个正常的EPICS支持模块编译它。已知这个Makefile对EPICS 3.14.8以及PCRE 7.2有效。在你的RELEASE文件中,定义变量PCRE指向PCRE的安装位置。

如果PCRE已经安装在你的系统上了,你可以添加架构到变量WITH_SYSTEM_PCRE,在标准include和库位置中找到PCRE的架构。如果头文件或库文件是在非标准位置,在RELEASE文件中为各自架构设置变量PCRE_INCLUDE_arch和/或PCRE_LIB_arch为正确的目录或者在架构特定的RELEASE.Common.arch文件中设置PCRE_INLCUE和/或PCRE_LIB。

如果正则表达式未被锚定,即不是以^开头,开头的不匹配输入被忽略。要匹配多行模式(跨越新行),在模式开头添加(?m)。要忽略大小写匹配,添加(?i)。

如果指定width,匹配最长width个字节。如果指定precision,它指定在()中的子表达式,其匹配被返回。否则,返回完整的匹配。在任何情况下,从输入缓存消耗完整的匹配。如果表达式包含一个/,它必须像\/被转义。

示例:%.1/<title>(.*)<\/title>/返回一个HTMP也得标题,在缓存中跳过<title>标签之前得所有东西以及留下</title>之后的所有东西。

14、正则表达式替代的伪转换器(%#/regex/subt/)

这是前一个转换器的变体(注意:#),但不是返回匹配的字符串,它可以用作输入的预处理器或者输出的后处理器。

regex的匹配被字符串subst替代,其中在subst中所有&被匹配的自身替代,而如果这样的子串存在,则\1到|9被匹配的相应子串替代。\Un, \Ln, \un或\ln的出现,n是一个0到9的数值,或&被转换成全大写,全小写,第一个字母大写或者第一个字母小写的相应表达式替代。

由于解析器的限制,\1和\x1是相同的,这使得在subst中使用只小于10的文字字节变得困难。因而,\0总是表示一个文字字节(与更早版本的不兼容变化),而\1到\9如果大于子表达式数目,表示文字字节。要在替换中得到一个文件&或\或/,写\&或\\或\/。

如果指定width,它限制要被处理的字符数目。如果使用-标记(即:看起来像一个负数的宽度),仅处理最后的width个字符,否则最前的字符。没有width(或0),处理所有可获取的字符。

如果指定precision,它表明哪个匹配要替换。用+标记,precsion是要替换的匹配最大数目。否则,precsion是要替换的匹配索引(从1算起)。没有precsion(或0),所有匹配被替换。

当替换多个匹配时,在当前被替换的字符串后直接搜索下个匹配,因而subst字符串自身将不会被递归的修改。但如果一个空串被匹配,为了避免再次匹配相同的空串,搜索前进一个字符。

在输入中,在接下来的转换器读取它前,这个转换器预处理接收自设备的数据。在这之前的转换器将读取未被修改的输入。因而在其输入应该被预处理的那些转换器前放置这个转换器。

在输出中,在发送数据到设备前,它后处理已经被前面转换器格式化的数据。在这之后的转换器应该不修改的发送它们的输出。因而,在那些输出应该被后处理的转换器之后放置这个转换器。

示例:

1)%#+-10.2/ab/X/:在最后10个字符中用X替代字符串ab,替换次数最多2次(abcabcabcabc变成abcXcXcabc)

2)  %#/\\/\//:用/替代所有\(\dir\file变成了/dir/file)

3) %#/..\B/&:/:在不为一个词结尾的每两个字母后插入:。(0b19353134变成了0b:19:5:31:34)。

4) %#/://:移除所有:字符。(0b:19:5:31:34变成了0b19353134)

5) %#/(^+-)*([+-])/\2\1/:一个后缀符号变到了前面(1.23-变成了-1.23)

6) %#-2/.*/\U0/:转换最后2个字符为大写。

15、mantissaExponent DOUBLE转换器(%m)

这种实验格式匹配以[sign] mantissa sign exponent格式的数值,例如:+123-4表示123e-4或者0.0123。Mantissa和e指数都是十进制整数。mantissa的符号是可选的。对比于标准e格式,这个格式不包含字符.和e。

输出格式化是不清楚的(例如:123-4和1230-5)。我选择了以下规则:

格式precision定义在mantissa的数字数目。在mantissa中没有带头的'0'。在指数中的数字数目至少是2。用常见方式支持格式标记+,-和空白(总是符号,左对齐,空白替代+号)。不支持标记#和0。

16、时间戳DOUBLE转换器(%T(timeformat))

这个格式读取和写时间戳并且转换它们为一个double数值。这个值替代自1970以来的秒数(UNIX epoch)。一个double的精度对于毫秒足够大。这种格式最好结合对TIME字段的一个重定向使用。在这种情况中,这个值被转换成EPICS时间戳(从1990年以来的秒和纳秒)。时间戳格式理解C函数strftime()理解的常规转换器。此外,可以指定一个秒的小数部分并且能够在格式化字符串中指定时区。

示例:%(TIME)T(%d %b %Y %H:%M:%.3S %z)会打印像3 Sep 2010 15:45:59 +0200的东西。

可以以%.nS(带有n个小数数字的秒),以%0nf或%nf(n个小数数字)或以%N(纳秒)指定一个秒的小数。在输入中,n是被解析的最大数字数目,可能在输入中实际有更少数字。如果没有指定n(%.S或%f),它使用一个默认值6。

在输入中,在被解析的时间戳没有指定时区的情况中,用像%+hhmm或%-hhmm的格式指定时区,此处hhmm是一个用小时和分钟指定偏移的4位数字数值。

在输出中,系统函数strftime()用于格式化时间。在操作系统之间实现中可能有不同。

在输入中,StreamDevice使用它自己的实现,因为很多系统缺失strptime()函数并且支持更多格式化。

周天可以被解析,但被忽略,因为在一起使用day,month和year时,这个信息是多余的。不进行一致性检查。

因为这个问题的复杂性,不支持本地语言。因而,只能使用英语月份名。

猜你喜欢

转载自blog.csdn.net/yuyuyuliang00/article/details/127826727