https://www.cnblogs.com/yeungchie/ Cadence SkillCadence 提供二次开发的 SKILL 语言,它是一种基于通用人工智能语言 — LISP 的交互式高级编程语言。① SKILL 语言支持一套类似 C 语言的语法,大大降低了初学者学习的难度,同时高水平的编程者可以选择使用类似 LISP 语言的全部功能。所以 SKILL 语言既可以用作最简单的工具语言,也可以作为开发任何应用的、强大的编程语言。 SKILL 可以与底层系统交互,也提供了访问 Cadence 各个工具的丰富接口,用户可以通过 Skill 语言来访问,并且可以开发自己的基于 Cadence 平台的工具。 我的环境是 Virtuoso IC618 平台 ( SKILL37.00 ) ,可能存在某些特性不适用于旧版本。
如何查看官方资料
cdsFinder
cdnshelp
Skill Version
基础语法Hello World举例一种 Hello World 的写法。
注释
|
操作符 | 函数 | 备注 | |||
加 | + | a + b | plus | plus(a b) | |
减 | - | a - b | difference | difference(a b) | |
乘 | * | a * b | times | times(a b) | |
除 | / | a / b | quotient | quotient(a b) | |
余 | remainder | remainder(a b) | 用于整数 | ||
modf | modf(a b) | 用于浮点数 | |||
乘方 | ** | a ** b | expt | expt(a b) | |
开方 | sqrt | sqrt(a b) |
操作符 | 函数 | |||
直接赋值 | = | a = 1 | setq | setq( a 1 ) |
自增 | += | a += 1 | ||
自增 (+1) | ++ | ++a | add1 | add1( a ) |
a++ | postincrement | postincrement( a ) | ||
自减 | -= | a -= 1 | ||
自减 (-1) | -- | --a | sub1 | sub1( a ) |
a-- | postdecrement | postdecrement( a ) |
Tips 🍉:
++a
会将 a 加一后的值返回,返回值是 2a++
会先返回 a 的值后加一,返回值是 1add1( a )
会返回 2,但不改变 a 的值postincrement( a )
会返回 1,a 的值会加一变成 2操作符 | 函数 | |||
相等 | == | a == b | equal | equal( a b ) |
不相等 | != | a == b | nequal | nequal( a b ) |
小于 | < | a < b | lessp | lessp( a b ) |
小于等于 | <= | a <= b | leqp | leqp( a b ) |
大于 | > | a > b | greaterp | greaterp( a b ) |
大于等于 | >= | a >= b | geqp | geqp( a b ) |
几乎相等 | nearlyEqual | nearlyEqual( a b ) |
操作符 | 函数 | |||
与 | && | a && b | and | and( a b ) |
或 | || | a || b | or | or( a b ) |
非 | ! | ! a | not | not( a ) |
真/假 情况运行的都是单一的语句
if( 条件
当条件成立时运行
当条件不成立时运行
)
例如:当 a > b 成立时,打印 "Yes";不成立时,打印时 "No"。
if( a > b
println( "Yes" )
println( "No" )
)
真/假 情况需要运行多条语句
if( 条件
then
当条件成立时运行 1
当条件成立时运行 2
else
当条件不成立时运行 3
当条件不成立时运行 4
)
例如:当 a > b 成立时,c 赋值 1 然后打印 "Yes";不成立时,c 赋值为 0 然后打印时 "No"。
if( a > b
then
c = 1
println( "Yes" )
else
c = 0
println( "No" )
)
多层嵌套
Skill 不能用 if-elsif-else
这种简化的写法,但是逻辑是一样的,后级的 if
需要在上一级的 else
中。
if( 条件 A
当条件 A 成立时运行
if( 条件 B
否则 当条件 B 成立时运行
以上条件都不成立时运行
)
)
if( 条件 A
then
当条件 A 成立时运行 1
当条件 A 成立时运行 2
else
if( 条件 B
then
否则 当条件 B 成立时运行 3
否则 当条件 B 成立时运行 4
else
以上条件都不成立时运行 5
以上条件都不成立时运行 6
)
)
例如下面想实现:
if( a > b
println( "Yes" )
if( a > c
println( "Yes" )
println( "No" )
)
)
第二个 if
虽然不是 单一语句,但可以将整个 if( a > c ... )
看做一个整体,所以也可以忽略 then
/else
。
这还有一个问题,当需要判断多个条件的时候 if
的写法就太不好看了,这时就可以使用 case
或者 cond
,等会讲。
when / unless 就非常简单了,只当给定的条件为 真/假 的时候才运行给定的语句
条件为真才运行。
when( a > b
println( "Yes" )
)
条件为假才运行。
unless( a > b
println( "No" )
)
前面说了 case
/cond
可以用来优化多条件下的 if
,因此逻辑是一样的。
case
当所有的条件都是对一个变量做是否相等的判断的时候,可以使用 case
。
例如,现在有一个变量 arg:
case( arg
( "a"
println( "It is a" )
)
(( "b" "c" )
println( "It is b or c" )
)
( "d"
println( "It is d" )
)
( t
println( "Unmatch" )
)
)
上面的语句换成 if
需要这样写:
if( arg == "a"
println( "It is a" )
if( arg == "b" || arg == "c"
println( "It is b or c" )
if( arg == "d"
println( "It is d" )
println( "Unmatch" )
)
)
)
很明显 case
的写法更加清晰,观感上也更加舒服。
cond
case
的使用情景比较单一,当条件多且判断的对象或者逻辑不唯一的时候可以 cond
。
例如,现在需要对变量 a 做几个判断:
cond(
( a > b
println( "Bigger than b" )
)
( a > c
println( "Bigger than c" )
)
( t
println( "Smallest" )
)
)
指定一个起始整数(initial)和终止整数(final),依次遍历从 initial 到 final 组成的 list 的每一个元素,间隔为 1。
下面用 for
打印从 0 到 2:
for( x 0 2
println( x )
)
; 0
; 1
; 2
指定一个 list ,依此遍历每一个元素。
下面用 foreach
打印从 0 到 2:
foreach( x list( 0 1 2 )
println( x )
)
; 0
; 1
; 2
也可以同时遍历多个 list
foreach(( x y z ) list( 0 1 2 ) list( 3 4 5 ) list( 6 7 8 )
printf( "%d %d %d\n" x y z )
)
; 0 3 6
; 1 4 7
; 2 5 8
foreach 的返回值是第一个 list,( 0 1 2 )
指定一个条件,当条件为真时才会运行,当条件为假时跳出 while 循环。
下面用 while
打印从 0 到 2:
a = 0
while( a < 3
println( a )
a++
)
; 0
; 1
; 2
(函数)
procedure
函数来声明。下面举个例子:
procedure( myAdd( a b )
a + b
); myAdd
这个子程序用来实现输入变量 a
和 b
,返回 a + b
的值。
myAdd
就是子程序的名称。a
和 b
是定义需要两个参数,这两个参数会作为子程序的局部变量,局部变量等会再细讲。a + b
是代码块部分,仅有一行也作为最后一行,因此会返回两个变量相加之后的值。下面看下如何调用这个 myAdd
:
myAdd( 1 2 )
; 返回 3
参数不是必须的,也可以提供一个空列表(括号里空着不写)作为参数的定义,意味着这个子程序不需要参数。
procedure( myAddOneAndTwo()
3
); 这个子程序直接返回 3
myAddOneAndTwo()
; 3
p
let
用来定义局部变量,定义变量的改动不会影响到代码块外的同名变量。用法:
a = 5
let(( a )
a = 7
println( a )
)
println( a )
返回的结果是:
7
5
如果想给 let
中的变量赋值一个默认值,出了在开头写一个赋值语句,上面的 let
还可以这样简化:
let(( a( 7 ) )
println( a )
)
prog
相对于 let
增加了 return
和 go
函数的支持。prog
的默认返回值是 nil
由于 prog
默认的返回值是 nil
,因此需要一个方法能够指定返回值是什么。而 return
就能够实现在一个 prog
中的任意位置跳出,并指定返回值。
示范一下:
prog(( a )
a = 1
when( a < 5
return( t )
)
return() ; 当 return 不指定的返回值的时候等同于 return( nil )
)
上面的程序由于 a
为 1 ,小于 5,因此执行 when 中的语句,跳出 prog 并返回 t 。
运用 prog + return
可以更加灵活的控制 while
、 foreach
等循环结构。
例如下面两段相似的代码中,prog
的位置不同对程序运行的影响:
满足条件提前跳出循环
a = 0
prog(( )
while( a <= 3
a++
when( a == 2 return())
print( a )
); while
); prog
当前 a == 2 时,跳出 prog
,由于 prog
在 while
循环外部,因此整个循环会结束。
结果打印 1
。
满足条件直接运行下一个循环
a = 0
while( a <= 3
prog(( )
a++
when( a == 2 return())
print( a )
); prog
); while
当前 a == 2 时,跳出 prog
,由于 prog
在 while
循环内部,因此当前循环结束,不执行 print
直接进入下一次循环。
结果打印 134
。
go
用来实现在一个 prog
内部,跳转到指定的 标签
。
prog(( a )
a = 0
LABEL ; 打个标签
print( a++ )
when( a <= 3
go( LABEL ) ; 跳转到标签的位置
)
); prog
上面的例子实现一个循环,判断 ++a
的值小于等于 3 时回到 LABEL
标记的位置重复运行,结果打印 0123
。
go
的使用存在一些限制,不能在多个 prog
之间跳转,不能往循环内跳转:
prog
)上面我们已经定义了一个 myAdd
函数,由于执行的过程是做加法,因此它有一个隐含的要求是:输入的两个变量都必须是数字,否则运行会报错
can't handle
我们可以在子程序的定义中加入这个变量类型的判断:
procedure( myAdd( a b )
unless( numberp( a ) && numberp( b )
error("myAdd: Argument should be number.")
)
a + b
); myAdd
运行上面的函数试试:
myAdd( "1" "2" )
; *Error* myAdd: Argument should be number.
不过我们不需要这么麻烦去直接写每个参数的判断,Skill 已经提供了更简单的方法:
procedure( myAdd( a b "nn")
a + b
); myAdd
再运行上面的函数试试:
myAdd( "1" "2" )
; *Error* myAdd: argument #1 should be a number (type template = "nn") - "1"
可以看到仅仅是在参数定义之后追加了 "n"
就可以起到效果,第一个 n 声明第一个参数需要为数字(number 缩写成 n),第二个 n 同理声明第二个参数。
不过这个写法还能简化:... ( a b "n") ...
,像这样只写一个 n
就代表所有的参数都必须为数字类型。
下面举例一些常见的用于声明数据类型的缩写:
缩写 | 内部命名 | 数据类型 |
---|---|---|
d | dbobject | id , Cadence 数据对象 |
x | integer | 整数 |
f | flonum | 浮点数 |
n | number | 整数 或者 浮点数 |
g | general | 通用的 , 任何数据类型 |
l | list | 链表 |
p | port | I / O 句柄 |
t | string | 字符串 |
s | symbol | symbol(符号) |
S | stringSymbol | symbol 或者 字符串 |
u | function | 函数对象 , 函数名 或者 lambda 对象 |
... | ... | ... |
定义输入参数时候,可以使用一些 修饰 符号来做到类似 Getopt 的效果,常用的如下:
@rest
定义 不限数量 的输入
@optional
定义 可有可无 的输入,需要按顺序
@key
定义 可有可无 的输入,需要指定参数名
注意 🍉:语法上 @optional 和 @key 不能同时使用,功能上 @rest 和 @optional 同时使用会存在矛盾。
还是用上面的子程序 myAdd
来举例。
场景:现在这个程序,只能接受两个参数做加法,如果输入是 3 个或者更多怎么办? 我不知道有多少个参数需要一次性输入。这时候就需要用到 @rest
优化一下 myAdd
:
procedure( myAdd( @rest args ) ; 修饰符号写在被修饰参数的前面
prog(( result )
result = 0
foreach( num args
printf( "myAdd: %n + %n\n" result num )
result += num
)
return( result )
)
); myAdd
运行一下:
myAdd( 1 2 3 )
; myAdd: 0 + 1
; myAdd: 1 + 2
; myAdd: 3 + 3
; => 6
例子中只定义了一个输入参数,被声明 @rest
后,args
会变成一个 list 参与子程序内部运行,接着遍历所有元素加起来就行了。
场景:
myAdd
现在只能做加法,如果我想自定义运算类型,且要求不指定运算类型的时候默认做加法怎么办?这时候就需要用到 @optional
下面的例子为了避免矛盾,就不使用 @rest
了,将 args
用一个 list 来输入。
procedure( myCalc( args @optional opt("+") ) ; 参数后面的括号内写上默认值,也可以写成 ( opt "+" )
prog(( result )
result = car( args )
args = cdr( args )
foreach( num args
printf( "myCalc: %n %s %n ; " result opt num )
case(opt
("+" result += num )
("-" result -= num )
("*" result *= num )
("/" result /= num )
); 按照不同的 opt 执行不同的操作
)
return( result )
)
); myCalc
运行一下:
myCalc( list( 1 2 3 ))
; myCalc: 1 + 2 ; myCalc: 3 + 3 ;
; => 6
myCalc( list( 1 2 3 ) "-")
; myCalc: 1 - 2 ; myCalc: -1 - 3 ;
; => -4
myCalc( list( 1 2 3 ) "*")
; myCalc: 1 * 2 ; myCalc: 2 * 3 ;
; => 6
myCalc( list( 1.0 2 3 ) "/")
; myCalc: 1.000000 / 2 ; myCalc: 0.500000 / 3 ;
; => 0.1666667
场景:上面 @optional 的要求是输入的参数必须按顺序,即 先
args
后opt
,我不想固定这个顺序怎么办?这时候就需要用到 @key
使用格式:
定义
procedure( function( @key key1 key2 )
...
)
不指定默认值的时候,默认值就是 nil
运行:
function( ?key1 arg1 ?key2 arg2 ... )
在上面 myCalc
的基础上改一下:
procedure( myCalc( @key args opt("+") )
; prog ... 过程完全一致,这里就不写了
); myCalc
运行一下:
myCalc( list( 1 2 3 ) "+")
*Error* myCalc: extra arguments or keyword missing - ((1 2 3) "+")
myCalc( ?args list( 1 2 3 ) ?opt "+")
; myCalc: 1 + 2 ; myCalc: 3 + 3 ;
; => 6
myCalc( ?opt "+" ?args list( 1 2 3 ))
; myCalc: 1 + 2 ; myCalc: 3 + 3 ;
; => 6
(lambda)
匿名函数,顾名思义没有名字的函数,不同于 procedure
需要指定一个函数名,lambda
不需要指定一个函数名,它会返回一个 lambda 对象。
sum = lambda(( a b )
a + b
)
上面已经定义了一个匿名函数,并将 lambda 对象赋值给了 sum
变量。 接下来就可以用 funcall
函数来使用它:
funcall( sum 1 2 )
; 3
funcall
的第一个参数接收一个函数对象 sum
,后面的参数依次作为输入。
如果待输入的变量保存在一个 list 中,也可以用 apply
函数来使用它:
apply( sum list( 1 2 ))
; 3
可以看到,apply
的第一个参数接收一个函数对象 sum
,第二个参数是一个 list ,list 中的元素依次对应 sum
需要接收的参数。
这里需要注意的是,并不是
sum
需要接收一个 list,而是apply
接收一个 list,再把其中的元素依次作为sum
的输入进行传参。sum
接收到的依然是两个参数。
此外 funcall
、apply
不光可以接收 lambda,也可以接收一个 symbol 变量,前面讲到 symbol 存在一个 slot 是 Function binding ,因此也可以调用非匿名的子程序。
apply( 'plus list( 1 2 ))
; 3
这里的
'plus
就作为plus
函数的引用。
mapcar
的效果其实也是循环,之所以放到 《子程序》 章节来讲,是因为使用这个函数需要先了解子程序是什么。
假设现在有一个 list:
numbers1 = list( 1 3 2 4 5 7 )
现在要将这个 list 中的每个元素都 +1
,用 foreach
可以这样做:
numbers2 = nil
foreach( x numbers1
numbers2 = append1( numbers2 ++x )
)
println( numbers2 )
; ( 2 4 3 5 6 8 )
可以看到还是比较啰嗦的,换做 mapcar
就很方便了:
numbers3 = mapcar( 'add1 numbers1 )
; ( 2 4 3 5 6 8 )
也可以接受一个 lambda
匿名函数:
numbers4 = mapcar( lambda(( a ) a++ ) numbers1 )
; ( 2 4 3 5 6 8 )
另外前面讲到 foreach
的返回值是第一个 list,配合 mapcar
后可以将每次循环的结果作为返回值。
numbers5 = foreach( mapcar x numbers1
x++
)
; ( 2 4 3 5 6 8 )
|