Cadence SKILL语言学习历程全记录

天牛不唱歌 EETOP 前天

来源:eetop bbs   作者:天牛不唱歌

注:skill相关的一些脚本已经整理在脚本区:
http://bbs.eetop.cn/viewthread.php?tid=590022
首先要感谢各位对于之前”LNA学习历程“的顶贴,不过话说回来,转眼回头看之前自己写的历程感觉还是不成熟的很,还望各位大大不要笑话呵呵。~~~~~~~  ~~~下面很大部分来自于坛子,有小部分总结呵呵,直接贴出来了,算是偷工减料的总结吧。。。
也是刚学习不久,为了先腾出点地方自己以后接着写,所以先占下了2~5楼的位置,大家勿怪呀!!!
  SKILL语言有点类似于C语言,是Cadence公司的程序接口语言,也就是说我们平时用的VirtuosoSpectre,Assura,Calibre,工艺库等等都是用skill写的。Skill可以大,当然可以小,譬如一条简单的skill语句就可以实现一个快捷键。这就是说,暂且不提skill那些高大上的功能,就凭可以帮助我们快速完成一些批量小任务来讲,skill也是值得我们学习一下的。   对于Skill,cadence公司并没有放出来太多的demon或者说example,对于俺们这样的小白来说,学习要略显困难一些,还好cadence对于skill给出来许多 user guide 和manual。这些manual里面给出了各个函数的参数和返回值。对于这些manual,论坛里和google都可以搜到。 坛子里也有一些关于skill使用的教程,尤其推荐一下纯粹版图网,里面nfmao大大总是会放出一些有用的skill代码,非常实用O(∩_∩)O~。这些manual不必亲力亲为的都看一遍,话说实在太多了。。。

  虽然坛子里也是高手如云,不过放出来的代码还是显得吝啬了点,呵呵。
  SKILL脚本一般是用.il和.ile结尾的文件,其中 .ile结尾的文件是加密的,调用时候需要密码。
  SKILL代码的使用有两种方式:1.在家目录里的 .cdsinit 文件里写入  load "***.il"  2.在CIW窗口输入 load "***.il"  。 
凡是实践为准!
/////////example 1////////
 ZOOM.zip (235 Bytes) 

把上面的附件解压是把鼠标滚轮设置为放大缩小的快捷键的skill——“ZOOM.il"
打开icfb &后,在CIW窗口输入  load ”路径/ZOOM.il" 后在原理图和版图中我们就可以实现缩放了。
如果我们将load "路径/ZOOM.il"  这句放到家目录的.cdsinit文件下,那每次我们启动icfb,都可以实现鼠标滚轮进行缩放了!

上面只是个简单的例子,只是为了有个朦胧的认识,真正学习一门语言先看看这个语言基本变量、函数、操作符是怎么定义怎么使用还是很有必要的,所以强烈推荐先看一下Skill 基本语法的user guide

 Skill Language User Guide.pdf (2.27 MB) 

之前学过C语言也就对skill的语法木有多大陌生感了,对于变量,函数,操作符的运算有些二者是一样的,下面主要说说二者不同的地方,些许记忆就好了:
1. skill 使用t 和nil 两个单词表示 真假。
2.skill里的字串或者集合用list表示,字串的定义用list来声明或者用一个符号 ' 来声明,二者部分情况下是通用的,但是有时候还是不同的,这个大家就要看语法了。对于list字串或者集合来说,有很多现成的函数,譬如计算这个字串包含元素的个数,合并字串,找到第n个元素。
3.注意使用基本的关系运算符和逻辑运算符
4.注意 if,when,unless,case,cond,for,foreach几个经常用的函数的使用方法
5.注意print,println,printf几个函数的使用方法,尤其是格式化输出printf,对于只学习了C++而没有学习c的朋友可能要费点心看一下了。
6.注意怎样用fprint和gets,fscanf写入和读取文件内容
7.用procedure定义函数的方法,其中 @reset ,@optional, @key几种决定参数传递的方式有什么不同。
8.用let和prog定义局部变量的方式以及二者的不同
9.知道一些常用的函数如abs,round,sqrt,random,zerop是什么含义,返回值是什么类型。
结合上面9个问题看skill的语法 user guide 那是真正学习skill 的第一步。一定要好好看一下。估计要用上两天以上的时间吧。总是要付出点努力的 你说对吧!!
正如盖一座房子需要一个根基,skill language user guide就是这个根基,但是正如伟大的c语言一样,它可以用在桌面编程,也可以用在嵌入式编程,还可以用来做操作系统,那它是否就需要包含n多函数,n多接口呢?
答案显然是否定的,我们学习的c语言只是basic c ,就是c语言的基本语法而已,对于一些特殊接口,俺们是要再根据具体应用而特殊扩展的,哈哈,同样我们学习的skill language user guide只是skill语言的基本语法框架而已。根据在仿真应用,原理图应用,版图应用,工艺库应用,cadence都扩展制作了很多接口函数。这些接口函数使skill可以在cadence框架里发挥更大的作用。这些扩展的函数也是一些user guide
tiger_lein老兄已经上传了这方面的userguide:
http://bbs.eetop.cn/thread-280627-1-1.html这里也传这个附件吧:
 skill manual大合集.zip (9.16 MB) 
话说这些user guide不是一般的多呀,大家看的时候开始粗略看一下就好。。。花太多的精力反而没有太多必要。


一、简介

1.1 Skill语言起源


Skill语言是Cadence公司开发的,但是Skill语言的前身可以追溯到LISP语言,在延续了LISP语言的特性的同时,Skill语言也融合了先进C语言的优点。所以Skill语言的语法结合了这二者的有点,同时有的语法也产生了两种书写风格。譬如(注:这两个程序先不用去理解,注意代码风格即可):

程序一:

procedure(fibonacci(n)

if( (n == 1 || n == 2) then

1

else fibonacci(n-1) + fibonacci(n-2)

)

)

程序二:

(defun fibonacci(n)

(cond

((or (equal n 1) (equal n 2)) 1)

(t (plus (fibonacci (difference n 1))

(fibonacci (difference n 2))))

)

)

第一种是代数表示法,这种方法的形式如 func( arg1 arg2 ...),该表示方法在大多语言里都被采用。另外一种是前缀表示法,类似于LISP语言,即 (func arg1 arg2 ...)这样的表述形式。这里可以看到类似Lisp语言的表示法后面有很多右括号,而且函数和参数容易混淆,所以一般推荐还是用常用的类C语言代数表示法


1.2 Skill语言的作用


Cadence提供给用户丰富的Skill语言接口,用户可以使用Skill来控制Cadence软件实现各种功能,譬如定制自己的cadence软件设置,进行Cadence批量操作,甚至根据自己需要开发Cadence相关插件或者工具软件(譬如:我们经常用的Calibre,Assura等软件就有大量Skill脚本与Cadence软件来通信)。

Skill可以大,当然可以小(譬如书写一条简单的Skill语句就可以实现设置某个快捷键的目的)。Skill功能的强大有时候超出我们的想象,但Skill的入门又非常的简单,学习Skill对于一般工程师来讲,经常用到的就是用它来帮助我们快速完成一些批量小任务,这极大提高了我们工作的效率。仅此一点Skill也是值得我们学习一下的,更不要说通过进一步的学习我们还能完成那么多高大上的功能。


1.3 Skill脚本的简单使用


Cadence对于skill语言的支持是无所不在的,我们可以从菜单栏,快捷键,程序函数,表格,以及CIW窗口等不同的渠道来触发执行已加载的Skill脚本程序。

一般加载自己的SKILL代码文件一般有两种方式:

① 在家目录里的 .cdsinit
  文件里写入 load "***.il"

这样,Cadence在启动后就会自动加载该Skill代码了。

② 在CIW窗口输入 load "***.il"。

这种手动的加载,一般只对当前起作用,会在下一次启动Cadence就失效了。当然我们也可以把 ***.il内的内容直接复制到CIW窗口并回车,效果是一样的。

注:双引号内的文件路径要设置正确才能load成功。


譬如下面的几行skill脚本,我们新建zoom.il文件,并把下面四行代码复制到该文件,然后在Cadence的CIW里按照漆面说的第二种方法load该文件,我们就在原理图和版图里实现了用鼠标中键控制视图缩放的功能。这样我们就算有了自己的第一个非常实用的Skill脚本,并且我们知道如何使用它了。


hiSetBindKey("Layout""<Btn4Down>" "hiZoomInAtMouse()");

hiSetBindKey("Layout""<Btn5Down>" "hiZoomOutAtMouse()");

hiSetBindKey("Schematics""<Btn4Down>" "hiZoomInAtMouse()");

hiSetBindKey("Schematics""<Btn5Down>" "hiZoomOutAtMouse()");


1.4 Skill脚本的加密


对于某些工程师而言,希望把自己的Skill脚本提供他人使用的同时,也希望保护自己的Skill脚本,不被别人乱改或者把源码传播,那就需要对脚本加密。

Skill脚本一般是用.il和.ile结尾的文件其中.il是普通文本文件,用文本编辑器可以直接打开,.ile结尾的文件是加密的,用文本编辑器是打不开的。

当我们写好自己的Skill脚本后,可以在CIW窗口用encrypt()函数来加密譬如

encrypt(“zoom.il” “zoom.ile” “abc123”)

这样就会把zoom.il 加密成zoom.ile文件了,并且密码是abc123.加密后的文件使用密码也是不能看到源代码的。而且在加载使用zoom.ile的时候是需要提供密码的。譬如:(注:一般最好用文件的绝对路径进行操作,否则可能会找不到文件)

load "zoom.ile" “abc123”

注:尽管Linux系统中文件的扩展名一般不影响文件的使用,但是为了区分我们还是在文件命令的时候遵从一定的规则让未加密的文件后缀是.il,让加密的文件以.ile结尾。

对于用encrypt加密的文件,但是如果没有密码的话,是可以用lineread函数配合pprint函数来获得源代码的。load下面函数后使用NlDecrypt(“123.ile”) 就可以恢复出来源码了,这个方法可以用来学习别人的源码。

procedure( NlDecrypt( inputFile @optionaloutputFile "tt")

prog( (inp out line)

if( inputFile then

unless( outputFile

outputFile = sprintf(nil"%s.dec" inputFile)

   )

when( isFile( outputFile )

print( "You must specify a file thatdoesn't exist!!!\n" )

hiGetAttention( )

return()

   )

unless( inp = infile(inputFile)

printf("Unable to open input file:%s\n" inputFile)

return()

   )

unless( out = outfile(outputFile"w")

printf("Unable to open output file:%s\n" outputFile)

return()

   )

while( line = lineread(inp)

when( line != t

pprint(line out)

    )

   )

close(inp)

close(out)

else

printf("You must at least specify aninput file!\n")

return()

) ; ** if inputFile **

return(t)

) ; ** let **

) ; ** procedure **



1.5 关于.cdsinit和.cdsenv文件


前面说到可以把加载Skill脚本的语句放到.cdsinit文件里,这样每次启动Cadence就能自动加载该脚本了。这里我们要说一下icfbvirtuoso 启动有两个重要文件就是 .cdsinit.cdsenv文件(文件名以点开始的在Linux下认为是隐藏文件)。我们每次启动Cadence软件,都会加载这两个文件,它们一般负责cadence的初始设置和一些用户自定义设置,其中

.cdsinit主要负责一些加载项的设置,

.cdsenv主要负责一些环境变量或者参数的设置,

两者功能有一些交叉,在有的时候都能完成某个设置。譬如:我们点击CIW的退出后,会弹出来是否要真的退出,如果我们不想让软件弹出来这个询问的窗口我们可以在.cdsenv里这样设置,

ddserv.ciw promptOnExit boolean nil

同样我们在.cdsinit里也可以进行设置来完成这个功能,如下用.cdsinit的设置和上面用.cdsenv的设置功能是等效的。

envSetVal ("ddserv.ciw" "promptOnExit" 'boolean nil)

注:Skill有个专门设置变量的函数envSetVal()

关于.cdsenv的设置我们可以在CIW窗口->option->save default里导出当前的cdsenv然后进行分析学习等。(注:在cdsenv我们一般会对字体,格点,加载工艺库,数字的有效位数等进行一些个性化的设置。且cdsenv每行一般分成四部分:大项,小项,类型,值)。

这里我们就不过多说明.cdsenv文件了。把重点放在.cdsinit文件,因为.cdsinit文件里的设置,除了可以完成cdsenv的功能外,还可以完成很多其它功能。另外补充一点,Cadence启动一般会最先在启动目录寻找.cdsinit文件,如果找到就加载,如果没有找到会到用户的家目录去寻找,如果还没有找到,应该会软件安装的某个目录下去寻找。

注:如果当前启动目录能找到.cdsinit文件,软件就不会自动去加载家目录下的.cdsinit文件了。


1.6 Skill语言的学习方法


Skill语言分为两部分,一部分是skill标准语法,另一部分是Cadence内置的关键字和接口函数。


对于Skill语言,Cadence公司并没有放出来太多的Demon或者Example,对于俺们这样的小白来说,学习要略显困难一些,还好Cadence对于Skill语言给出来许多 User Guide 和manual等帮助文档。的manual里面给出了可以被Skill调用的Cadence的各个接口函数的定义和返回值。对于这些Manual,eetop论坛里和Google都可以搜到。eetop论坛里也有一些关于skill使用的教程,在早前我会尤其推荐一下纯粹版图网,里面nfmao大大总是会放出一些有用的skill代码,非常实用,但目前该网站应该是关闭了。

对于一般学习者,Skill标准语法的manual是必须先要学习的,然后再去熟悉一下cadence纷繁重杂的接口函数,本书也秉承这样的学习顺序进行讲解。另外对于cadence的函数实在太多,一般我们没有必要全部去看一遍,只需要对于这些函数的分类要有了解,对于关键的经常用的函数要记忆一下即可,假如我们遇到一些接口函数不太了解它的定义和使用方法的时候,我们可以有三种方式查看该函数的定义和使用方法:

①最简单的方法,看~/CDS.log文件或者CIW的输出区域。首先我们要把Options->LogFilter都选上,这样我们进行任何操作,在CIW窗口或者log文件下都会显示当前操作所用到的函数,譬如添加器件删除器件等操作使用的接口函数,我们一目了然。


②在CIW里输入命令startFinder,或者在Terminal上输入cdsFinder& . 我们就打开了函数搜索器,如果我们想知道某个函数的大概作用和使用方法,我们可以在这里进行查询,或者我们只知道函数的部分名字,也可以通过匹配搜索,找到包含该字串的函数。

上面我们查到的关于函数用法的介绍很简单,如何知道其详细介绍呢。当然如果你知道函数在那个文档里,直接打开就行了,如果不知道,就需要打开help的search功能,这里可以查到详细的功能用法。运行cdsdoc&, 点击Search打开Cadence help的搜索功能。高的版本已经改为cdnshelp& 命令了,但是原理都是一样的。另外有可能端口或者/etc/hosts的设置导致连不上help。或者某些非完整版的Cadence软件,有可能为了缩小软件尺寸会将doc的内容删除了,所以运行cdsdoc命令也会出错。建议安装cadence官方版。另外我们也可以将所有skill的manual下载后,用pdf软件进行查看搜索我们所需的接口函数。


从简入繁,不断用小实例小的成功激励自己,这是快速稳固学习的不二法门,本书也会按照这样的结构和宗旨去书写,所以本书非常适合Skill学习爱好者通读,精读,也是最好的Skill入门教程,没有之一。


二、Skill基本语法

Skill语言的很多地方和C语言差不多,如变量,函数,控制结构,输入输出等,但二者的某些内置函数,操作符等也会有一些区别,学习的时候可以有意识的记忆一下二者的不一样的部分。对于Skill这里只作简单介绍。详情可以参考SKILL Language UserGuide。


2.1 Skill的变量


Skill的变量最大的特点是不需要事先声明,Skill第一次用到是会自动生成变量。变量可以由字符、数字、“_” 和 “?” 组成,注意第一个字符不能是数字和”?”。由于Cadence所开发的Skill中的变量、函数都是第一个字母小写,以_为开头的是Cadence的专用函数,为了避免冲突,所以建议大家函数和变量命名都以大写字母开头。


2.2 Skill的函数的调用


Skill的函数的调用方式有三种,以Skill内置的字串连接函数strcat为例:

strcat("Hello" "," " everyone" "!" ) ;常见的类C格式

(strcat "Hello" "," " everyone" "!" ) ;Lisp语言的格式

strcat"Hello" "," " everyone" "!" ;上面的括号可以省略

上面三种方式的返回的结果都是=> "Hello, everyone!"

推荐使用第一种方,需要注意的是函数和第一个括号之间没有空格,否则会报错

如: strcat ("Hello" "," " everyone" "!" );会产生如下错误!

=>*Error* eval: not a function - "Hello"


2.3 list数据格式


list是Skill继承Lisp(list Processing)而来,它是Skill编程中经常用到的。对于list,它有点类似于C语言中的数组,数据内容是有序的,但是它并不是数组(要知道Skill本身是有数组的类似定义和使用方法和C也是基本一样的),而更像是一个集合,它的数据对象可以是Skill中各种数据格式,所以我们经常给list定义就是“skill数据对象的一个有序集合”。skill数据甚至程序本身都可以看作是一个list,这是C语言中所没有的概念。下面是skill list形式的几个简单例子,对于list的操作我们之后再讲。

(1 2 3) ;这是一个list 包含1 2 3 三个元素

(1) ;这个list只包含一个元素1

( ) ;一个空的list等同于nil

(1 (2 3) 4) ;这个list的第二个元素也是一个list。


2.3.1 创建list的方法


创建list有以下几种基本的方法,譬如:用操作符'
或者用list()函数。另外我们可以用cons函数把指定元素添加到list中或者用append来合并两个list。下面举例说明,并说明他们的区别。


1).用 和 list 定义一个list,注意两者的差别,即list函数会把变量的值以实际的内容代替。

a = 1 => 1

b = 2 => 2

'( a b 3 ) => ( a b 3 )

list( a b 3 ) => ( 1 2 3 )

2).用cons命令添加一个元素到一个list的头部

result = '( 2 3 ) => ( 2 3 )

result = cons( 1 result ) => ( 1 2 3 )

3).用append命令合并两个list

lista = '( 4 5 6 ) => ( 4 5 6 )

listb = '( 1 2 3 ) => ( 1 2 3 )

listc = append( lista listb) => ( 4 5 6 1 2 3 )


2.3.2 访问list或list中元素的方法


对于list中元素的访问是,list的基本操作,Skill提供了很多内置的list操作的函数。使用这些函数,不仅让代码简洁,也增加了程序的可读性。

car()函数用于访问list的第一个元素。

numbers = '( 1 2 3 ) => ( 1 2 3 )

car( numbers ) => 1

cdr()函数访问list除了第一个元素外的其他元素,注意返回仍然是个list 。

numbers = '( 1 2 3 ) => ( 1 2 3 )

cdr( numbers ) => ( 2 3 )

nth()函数用索引访问list的某个元素,注意索引从0开始。

numbers = '( 1 2 3 ) => ( 1 2 3 )

nth( 1 numbers ) => 2

member()函数检查指定的元素是否在指定的list中,它只检查顶层元素的元素,返回值是从搜到值开始到结尾的list 。

numbers = '( 1 2 3 ) => ( 1 2 3 )

member( 4 numbers ) => nil

member( 2 numbers ) => ( 2 3 )

length()函数计算list所包含元素的个数。

numbers = '( 1 2 3 ) => ( 1 2 3 )

length( numbers ) => 3


2.3.3 关于x、y坐标或者bBox边界list的访问


在原理图和版图设计中,由于是一个二维的笛卡尔坐标系,所以对于坐标list操作经常用到的,它是一组2维的list,常见的表示方法有:

用 :表示一个坐标的list,其结果和list命令一样,用xCoord和yCoord命令可以分别访问x和y坐标。

xValue = 300

yValue = 400

aCoordinate = xValue:yValue => ( 300 400 )

xCoord( aCoordinate ) => 300

yCoord( aCoordinate ) => 400

用list命令或者符号 ' 来表示一个bBox,注意之前讲过二者区别:list命令先计算变量或者表达式,然后赋给list,而操作符' 表示的list和字面的一样,不会计算变量或者表达式的值。

bBox = list( 300:400 500:450 ) ;含有 : 的bBox

如果把两个点的坐标组合起来做bBox的话:

含有变量时要用list()函数:

lowerLeft = 300:400

upperRight = 500:450

bBox = list( lowerLeft upperRight )

' 表示的list严格按字面意思,适合不含有变量的情况

bBox = '(( 300 400 ) ( 500 450 ))

对于已有的bBox,我们可以用car和cdr函数配合使用来得到bBox内的每个元素。而且有相关的简化函数,如下表:


list的相关操作还有很多,这里就不详细介绍了,可以参考User Guide里的Advanced list Operations。


2.4 Skill的输入输出


Skill可以把输出写入到屏幕也就是CIW窗口,同时Skill还能对文件进行读入和写入,下面分别说明


2.4.1 输出显示数据:


print() 和println() 函数都可以用来显示单个数据,println可以在显示的数据后多加一个回车。

for(i 1 3 print( "hello" )) ;prints hello three times.

"hello""hello""hello"

for(i 1 3 println( "hello" )) ;prints hello three times.

"hello"

"hello"

"hello"

printf ()函数是格式化的输出(对于格式化输出后面会讲),下面的例子是一定格式输出图形层的统计。

printf("\n%-15s %-15s %-10d %-10d %-10d %-10d"

layerNamepurpose rectCount labelCount lineCount miscCount

)

对应参数的意义如下,printf需要注意输出类型的对应,一般格式如下:

%[-][width][.precision]conversion_code

-表示左对齐,width表示位数,.precision表示精度 ,conversion_code部分一般是变量。

对于精度

d表示整数,f表示浮点数,s表示string或者symbol,c表示字符,n表示数字,L表示list,P表示point list,B表示Bounding box。更多请参考下表:


2.4.2 输出数据到一个文件


想输出内容到一个文件,首先用outfile来定义输出接口文件,然后用print println fprintf输出到接口文件, 最后close关闭打开的接口,具体见下面的例子。


例子一:

myPort= outfile( "/tmp/myFile1" )

for(i 1 3

println( list( "Number:" i) myPort )

)

close(myPort )

输出到文件/tmp/myFile1.

("Number:"1)

("Number:"2)

("Number:"3)


例子二:

myPort= outfile( "/tmp/myFile2" )

for(i 1 3

fprintf( myPort "Number: %d\n" i ) ;注意printf函数不能输出到port

)

close(myPort )

输出到文件/tmp/myFile2.

Number:1

Number:2

Number:3

上面两个例子一个是用的println一个使用fprintf,注意二者的区别,并且注意printf函数不能输出到port。


2.4.3 从文件读取数据


如果想读入文件的内容,首先用intfile定义输入接口文件,然后用gets一次从接口文件读取一行字符串,或者用fscanf根据指定的格式从接口文件读取,最后用close关闭打开的接口。

下面例子实现的功能是:打开~/.cshrc,输出文件的每一行到CIW窗口的目的:

inPort= infile( "~/.cshrc" )

when(inPort

while( gets( nextLine inPort )

println( nextLine )

)

close(inPort )

)


下面例子实现的功能是:打开~/.cshrc,输出文件中的每一个字符串到CIW窗口:

inPort = infile( "~/.cshrc" )

when( inPort

while( fscanf( inPort"%s" word )

println( word )

)

close( inPort )

)


2.5 Skill的控制结构


2.5.1 关系操作符,


关系操作符和C语言基本差不多主要是用于判断,但是要注意Skill的关系操作符返回值是t或者nil。具体参考下表:


2.5.2 逻辑操作符


逻辑操作符主要是用于完成“与”和“或”运算,注意返回值可以使数值、或者t或者nil。具体参考下表:

注:SKILL中只有nil 是假(FALSE),其余的任何值都是真(TRUE)。

另外要特别注意,与/或逻辑操作只有在需要计算第二个表达式时,才计算第二个表表达式,比如&&操作,当第一个表达时为假时,就不会再计算第二个表达式,|| 操作,当第一个表达时为假时,才会再计算第二个表达式。返回的结果是最后一个计算的表达式,因此与/或逻辑操作可以代替繁琐的if / when等控制语句。

例如:C语言中的操作符, a>b ? c=a : c=b; 即c取a b中较大的一个Skill中没有类似的操作符,可以用下面语句来完成此操作:

if( a>b then

c=a

else

c=b

)

也可以用逻辑操作符: c= (a>b)&&a || (a<b)&&b

当然Skill还提供的有max(a b …)的函数,举这个例子是为了说明&& || 可以代替if then else之类的控制语句。所以关系操作符在某种意义上简化了代码。


2.5.3 预定义的函数


Skill还定义了不少预定义的函数,包括基本运算函数,三角函数以及随机产生函数,直接调用它们,不仅方便,而且运算的效率更高,下面是一些预定义函数的列表:


除此之外类型还有很多数据判断和数据类型判断函数,这也是经常用到的,采用这些函数有时候极大的简化了代码,同样避免使用过多的关系运算符和if语句。具体看下表:


2.5.4 控制语句


同c语言类似,Skill语言同样有各种判断和循环控制语句,下面分别举例说明一下。


if语句用于判断,有时候配合else使用,需要注意的是:
if…then…else的then是不能省略的(除了只有一个if,没有else的情况)例子如下:


when语句举例:

unless语句举例:

case语句:case语句的判断可以是数字和字符串,也可以是它们组成的list,但不支持变量和表达式把最可能出现的情况放在最前面,如果出现的几率都一样,把计算量最大的放在最后面,这样可以有效的提高代码效率。


cond语句:如果有很多判断语句,用cond代替if…then…else组合,代码比较清晰而且执行效率比较高,下面的两种代码是等效的。


上面的cond语句如果用if else表示可以表示成:

举例子说明cond的用法:

while语句用于循环,举例如下:

for语句用于指定次数的循环,和C语言基本相同,举例如下:

foreach语句是skill经常用到的,foreach经常用于对list的每个元素作循环操作,每个循环依次把各个元素的值赋给一个变量举例如下:

上面提到的if(…then…else….)whenunlesscasecond,循环语句forforecah等,控制语句和C语言类似,都是先判断某个变量或者表达式是否为真,然后执行下面的操作。


2.6 Skill的自定义函数


Skill中,函数大部分都是用procedure定义的,我们可以定义具有函数名的函数如下面例子一,或者用lambda用来定义一个没有函数名的函数,它在一些很小的函数里很方便,如下面例子二。

例子一:

procedure(trAdd( x y )

printf( "Adding %d and %d ... %d \n" x y x+y )

x+y

)=> trAdd 定义函数trAdd

    trAdd(6 7 ) => 13 调用函数trAdd


例子二:

        signallist= '(

        ( nil strength 1.5 )

        ( nil strength 0.4 )

        ( nil strength 2.5 )

        )

        sort(signallist

      'lambda( ( a b ) a->strength <=b->strength )

      ;定义一个用来按signalliststrength属性排序的内置函数,它没有具体的函数名

        )

函数定义还包括函数的参数,这些会在调用函数实例的时候传递给函数,除了基本的参数传递功能外,Skill还允许我们使用@option的方法控制参数的传递,譬如用@rest,调用函数时允许任意多个参数传递给函数,使用@optional可以传递一个可选参数,否则会用默认值,@key,可以指定参数是如何传递的,应此调用是参数的顺序不固定。下面分别用例子说明:



例子一:@rest,调用函数时允许任意多个参数传递给函数,

    procedure( trTrace( fun @rest args )

    let( ( result )

      printf("\nCalling %s passing %L" fun args )

      result = apply(fun args )

      printf("\nReturning from %s with %L\n" fun result )

      result

        ) ; let

    ); procedure


调用函数及返回结果:

        trTrace('plus 1 2 3 ) => 6 ;返回值

      =>Calling plus passing (1 2 3)

            Returning from plus with 6

例子二:@optional可以传递一个可选参数,否则会用默认值,

    procedure(trBuildBBox( height width @optional

        (xCoord 0 ) ( yCoord 0 ) )

      list(

        xCoord:yCoord ;;; lower left

        xCoord+width:yCoord+height ) ;;; upperright

    ); procedure

调用函数及返回结果:

    trBuildBBox(1 2 ) => ((0 0) (2 1))

    trBuildBBox(1 2 4 ) => ((4 0) (6 1))

    trBuildBBox(1 2 4 10) => ((4 10) (6 11))

例子三:@key,可以指定参数是如何传递的,应此调用是参数的顺序不固定

    procedure(trBuildBBox(

  @key ( height 0 ) ( width 0 ) ( xCoord 0 ) (yCoord 0 ) )

  list(

    xCoord:yCoord ;;; lower left

    xCoord+width:yCoord+height ) ;;; upper right

        ); procedure


调用函数及返回结果:

    trBuildBBox()=> ((0 0) (0 0))

    trBuildBBox(?height 10 ) => ((0 0) (0 10))

    trBuildBBox(?width 5 ?xCoord 10 ) => ((10 0) (15 0))


注:其中@key@optional不能同时出现在一个函数的定义中,另外的情况可以组合在一起譬如:

例子一:@optional和@reset共用


    procedure(functionname([var1var2 ...]

  [@optional opt1 opt2 ...]

  [@rest r])

    ..

    )


例子二:@key和@reset共用

    procedure(functionname([var1var2 ...]

  [@key key1 key2 ...]

  [@rest r])

        ..

        )


涉及到函数,另一个不得不提的就是变量的作用范围,在c语言中经常用到,譬如在C语言当中会有只当前文件作用的变量,只在某个函数或者子函数内部作用的变量,或者全局变量等等的区分。

在Skill中的变量默认都是全局变量,这可能会导致同时调用多个skill的时候,不同人编写的Skill命名可能有相同的,那就会导致冲突,所以要尽量减少全局变量的使用,而采用局部变量。

Skill可以用letprog定义局部变量,如下面的例子:

    procedure(trGetBBoxHeight( bBox )

      let( ( ( ll car( bBox ) ) ( ur cadr( bBox ) ) lly ury )

        lly = cadr( ll )

        ury = cadr( ur )

        ury - lly

      ) ; let

    ); procedure

局部变量默认初始化为nil,当然也可以初始化为别的值或表达式,表达式中不能有别的局部变量,prog和let的区别在于,prog支持函数goreturn,可以显示的循环和返回多个值,而let返回值是最后一个表达式的值,譬如上面例子的返回值是ury – lly. 除了有必要用prog,一般用let,更加简洁快速。


2.7 Skill代码书写风格

为了提高代码的可读性,我们一般会加一些注释,和c语言类似,段代码注释我们要/*和*/ 来注释,单行代码用; 来注释。


2.8  Skill 代码Debug

代码中由于我们的疏忽和不严谨,编写的程序难免会存在问题,一般根据在CIW窗口给出错的错误提示,我们就可以找到问题的所在。


例子一调用函数名和括号之间有空格


如: strcat ( "Hello" "," " everyone""!" );会产生如下错误!

              =>*Error* eval: not a function - "Hello"


该问题使我们经常会遇到的,书写时候一定要注意:


例子二:软件没响应,比如在CIW中输入段代码,软件没有反映,什么结果也没有。一般是因为( )或者” “不成对造成的,一般可以通过键入 ] 来解决,它表示补充完不对称的括号(可以代替任意多个右括号),如果还没有响应,键入 “] 这时大部分情况下,系统会有响应。


例子三:数据类型不匹配,如:

       strcat("Mary had a" 5 );会出现如下错误!

       =>*Error*strcat: argument #2 should be either a string

       ora symbol (type template = "S") – 5


例子四:函数不合法,一般是我们输入没有的函数会出现illegal function错误,我们要检查是否函数被加载了,或者函数名字写错了。

       =>illegalfunction


例子五:无约束变量错误,一般是调用一个变量,而这个变量没有被赋值,或者调用该变量的时候变量名字写错了  

     =>unboundvariable

点击下方阅读原文,查看论坛原帖,下载相关资料