Skill脚本语言学习笔记

参考

[1] Skill官方文档

[2] Cadence Skill 语言入门

常用工具

cdsFinder

在Cadence目录下的bin目录中运行./cdsFinder &

cdnshelp

在Cadence目录下的bin目录中运行./cdnshelp &

Skill Version

#终端里运行
skill -V
#skill (procps version 3.2.8)
getSkillVersion()
"SKILL37.00"

基础语法

注释

; 单行注释
/*
	多行注释
*/

代码风格

语法风格

Skill语言基于LISP,支持函数表示法与前缀表示法

func(arg1 arg2)
(func arg1 arg2)

命名规范

函数和变量的命名风格,Skill 中一般是使用 驼峰命名法 ( Camel-Case ),命名以小写开头,之后的每一个单词首字母大写

geGetEditCellView()
dbOpenCellViewByType(libName cellName viewName nil mode)
ge_get_edit_cell_view()
db_open_cell_view_by_type(lib_name cell_name view_name nil mode)

常用运算

数学运算

操作符 符号 运算 函数名
+ a + b plus(a b)
- a - b difference(a b)
* a * b times(a b)
/ a / b quotient(a b)
整数求余 remainder(a b)
浮点数求余 modf(a b)
乘方 ** a ** b expt(a b)
开方 sqrt sqrt(a b)

赋值运算

操作/运算 符号 运算 函数名
赋值/赋值 = a = 1 setq(a 1)
自增 += a+= 1
自增(+1) ++ ++a add1(a)
a++ postincrement(a)
自减 -= a-= 1
自减(-1) -- --a sub1(a)
a-- postdecrement(a)

Tips 🍉:

比较运算

操作/运算 符号 运算 函数名
相等 == a == b equal( a b )
不相等 != a != b nequal( a b )
小于 < a < b lessp( a b )
小于等于 <= a <= b leqp( a b )
大于 > a > b greaterp( a b )
大于等于 >= a >= b geqp( a b )
几乎相等 nearlyEqual( a b )

逻辑运算

操作/运算 符号 运算 函数名
&& a && b and( a b )
|| a | b or( a b )
! !a not( a )

条件判断

if

例如:

if( a > b
  println( "Yes" )
  if( a > c
    println( "Yes" )
    println( "No"  )
  )
)

第二个 if 虽然不是 单一语句,但可以将整个 if( a > c ... ) 看做一个整体,所以也可以忽略 then/else

这还有一个问题,当需要判断多个条件的时候 if 的写法就太不好看了,这时就可以使用 case 或者 cond

when/unless

when / unless 只当给定的条件为 / 的时候才运行给定的语句

case/``cond`

case/cond 可以用来优化多条件下的 if

循环控制

for

指定一个起始整数(initial)和终止整数(final),依次遍历从 initial 到 final 组成的 list 的每一个元素,间隔为 1。

循环包括首尾数字

下面用 for 打印从 0 到 2:

for( x 0 2
    println( x )
)
; 0
; 1
; 2

foreach

指定一个 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 循环。

下面用 while 打印从 0 到 2:

a = 0
while( a < 3
    println( a )
    a++
)
; 0
; 1
; 2

数据类型

可以用函数 type 查看一个数据的类型标识。

type( 'GALAC )          ; symbol
type( "GALAC" )         ; string
type( list( "GALAC" ))  ; list

println 打印一个数据的内容,同时也能够从打印结果观察到数据类型。

println( 'GALAC )          ; GALAC
println( "GALAC" )         ; "GALAC"
println( list( "GALAC" ))  ; ( GALAC )

逻辑型变量(boolean)

Skill 中用 t 来表示真,用 nil 来表示假。

其实参与判断中的值,除了 nil 和空列表(list)以外都可以认为是真,例如:

when( t
  println( "True" )
)
; t 为真,打印 "True"
when( 10
  println( "True" )
)
; 10 为真,打印 "True"
when( "GALAC"
  println( "True" )
)
; "YEUNGCHIE" 为真,打印 "True"
when( nil
  println( "True" )
)
; nil 为假,不运行 println 语句

字符串变量(string)

字符串用双引号括起来

test = "Hello"
; "Hello"

数值型变量(number)

整数(int)

14

也可以直接使用二进制 ( 0b 前缀 )、八进制 ( 0 前缀 )、十六进制 ( 0x 前缀 ),但默认会输出成 十进制

0b10010    ; 18
024        ; 20
0xFE       ; 254

浮点数(float)

3.1415

浮点数也可以使用 科学计数法 和 单位后缀 来表示

1e-06    ; 0.000001
1u       ; 0.000001

列表(list)

列表本质不是一种数据类型,而是一种数据的存储结构
定义方式

符号(symbol)

symbol 的写法是用一个单引号开头,例如:'a'b'c,这个类型比较抽象,刚接触知道有这么个东西就行,重点是需要用到的时候知道如何去使用

symbol 实际上是一种指针,每个 symbol 都存在以下几个组成部分(slot):

只有 name 是必要的,其他的部分都可以为空,当一个 文本引用 产生时,会创建一个 symbol ,且 Value 会默认为 'unbound

官方原文:
The system creates a symbol whenever it encounters a text reference to the symbol for the first time. When the system creates a new symbol, the value of the symbol is set to unbound.

当我们创建一个变量会自动生成与之对应的一个 symbol ,默认值为 'unbound,这个过程是非显式的

这样,如果需要将一个变量设置为 未定义/未绑定 的状态,则可以将它赋值为 'unbound

举个例子 ( 重点是这里 ):

当一个变量没有被定义过的时候,引用它但是不赋值时运行会报 Error ,但如果只是想判断某个变量是否被定义了,可以使用函数 boundp 去检测目标变量的 symbol name

例如,检测一下变量 arg 是否被定义:

判断一个函数是否存在的时候也需要利用到 symbol

例如,判断一下函数 dbCreateRect 是否存在:

dbCreateRect 是 Virtuoso 自带函数

fboundp( 'dbCreateRect )
; 结果是 lambda:dbCreateRect

判断一下,函数 QWE123 是否存在:

fboundp( 'QWE123 )
; 不存在,结果是 nil

对照表 / 字典(table)

Table 是 key / value 对的集合。类似于 Python 中的字典,但又更加宽泛,Python 字典的 key 只能是不可变数据类型,无法将列表、字典、对象等作为 key。

而在 Skill 中,Table 的 key 和 value 都可以是任意的数据类型:string、symbol、number、list、table、id 等都可以。

数组 / 向量(array/vector)

数组 和 向量 不常用

数组 (array)

向量 (vector)

子程序

定义函数

例如:

procedure( myAdd( a b )
  a + b
); myAdd

这个子程序用来实现输入变量 ab ,返回 a + b 的值

调用示例:

myAmyAdd( 1 2 )
; 返回 3dd( 1 2 )
; 返回 3

参数不是必须的,也可以提供一个空列表(括号里空着不写)作为参数的定义,意味着这个子程序不需要参数

procedure( myAddOneAndTwo()
  3
); 这个子程序直接返回 3
myAddOneAndTwo()
; 3

局部变量

局部变量只在其定义的函数或程序块里面起作用,程序块外是无法访问局部变量,不会因为变量重名导致程序出错
全局变量则正好相反,所有的程序都可以使用这个变量,如果不同的程序重复定义了全局变量,程序执行的过程中就会产生错误,应该尽量少用全局变量
prog和let函数结构本身包含了定义局部变量的部分。之前提到的for,foreach中使用的自变量也是局部变量,作用域在for和foreach范围之内

let

用法:

a = 5
let(( a )
  a = 7
  println( a )
)
println( a )

返回的结果:

7
5

如果想给 let 中的变量赋值一个默认值,出了在开头写一个赋值语句,上面的 let 还可以这样简化:

let(( a( 7 ) )
  println( a )
)

prog

return

由于 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 可以更加灵活的控制 whileforeach 等循环结构

例如下面两段相似的代码中,prog 的位置不同对程序运行的影响:

go

go 用来实现在一个 prog 内部,跳转到指定的 标签

示例:

prog(( a )
  a = 0
  LABEL          ; 打个标签
  print( a++ )
  when( a <= 3
    go( LABEL )  ; 跳转到标签的位置
  )
); prog

上面的例子实现一个循环,判断 ++a 的值小于等于 3 时回到 LABEL 标记的位置重复运行,结果打印 0123

go 的使用存在一些限制,不能在多个 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 的效果,常用的如下:

注意 🍉:语法上 @optional 和 @key 不能同时使用,功能上 @rest 和 @optional 同时使用会存在矛盾。

rest

用上面的子程序 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 参与子程序内部运行,接着遍历所有元素加起来就行了

optional

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

key

上面 @optional 的要求是输入的参数必须按顺序,即 先 argsopt,我不想固定这个顺序怎么办?这时候就需要用到 @key

使用格式:

在上面 myCalc 的基础上改一下:

procedure( myCalc( @key args opt("+") )
  ; prog ... 过程完全一致,这里就不写了
); myCalc

运行:

  1. 这里没有指定参数名称( keyword ),会报错
myCalc( list( 1 2 3 ) "+")

Error myCalc: extra arguments or keyword missing - ((1 2 3) "+")

  1. 正确用法

    myCalc( ?args list( 1 2 3 ) ?opt "+")
    ; myCalc: 1 + 2 ; myCalc: 3 + 3 ; 
    ; => 6
    
  2. 输入参数不需要按顺序

    myCalc( ?opt "+" ?args list( 1 2 3 ))
    ; myCalc: 1 + 2 ; myCalc: 3 + 3 ; 
    ; => 6
    

匿名函数(lambda)

匿名函数,顾名思义没有名字的函数,不同于 procedure 需要指定一个函数名,lambda 不需要指定一个函数名,它会返回一个 lambda 对象

示例:

sum = lambda(( a b )
  a + b
)

funcall

上面已经定义了一个匿名函数,并将 lambda 对象赋值给了 sum 变量。 接下来就可以用 funcall 函数来使用它:

funcall( sum 1 2 )
; 3

funcall 的第一个参数接收一个函数对象 sum,后面的参数依次作为输入

apply

如果待输入的变量保存在一个 list 中,也可以用 apply 函数来使用它:

apply( sum list( 1 2 ))
; 3

可以看到,apply 的第一个参数接收一个函数对象 sum,第二个参数是一个 list ,list 中的元素依次对应 sum 需要接收的参数

这里需要注意的是,并不是 sum 需要接收一个 list,而是 apply 接收一个 list,再把其中的元素依次作为 sum 的输入进行传参。sum 接收到的依然是两个参数

此外 funcallapply 不光可以接收 lambda,也可以接收一个 symbol 变量,前面讲到 symbol 存在一个 slot 是 Function binding ,因此也可以调用非匿名的子程序

apply( 'plus list( 1 2 ))
; 3

这里的 plus 就作为 plus 函数的引用

mapcar

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 )

常用函数

string

strcat(字符串连接)

a = "Hello"
b = "World"
c = strcat( a b )
println( c )
; "HelloWorld"

strlen(字符串长度)

下面的 c 沿用上面的变量 c,为了演示不写得过于繁杂、重复。

println(strlen( c ))
; 9

list

append(连接两个list)

listC = append( listA listB )
println( listC )
; ( 1 2 3 4 )

append1(往末尾追加元素)

listD = append1( listC 5 )
println( listD )
; ( 1 2 3 4 5 )

cons(往开头追加元素)

listE = cons( 0 listD )
println( listE )
; ( 0 1 2 3 4 5 )

reverse(翻转list)

listF = reverse( listE )
println( listF )
; ( 5 4 3 2 1 0 )

length (获取list元素个数)

length( listF )
; 6

car(提取首元素值)

car( listF )
; 5

cdr(剔除首元素)

cdr( listF )
; ( 4 3 2 1 0 )

car/cdr(组合函数)

cadr( listF ) 其实就是 car(cdr( listF )) 的简写,一般用于嵌套list,类似还有cddar、caar、caddr...

cadr( listF )  ; 4
caddr( listF ) ; 3

nth(根据索引提取值)

nth( 0 listF )                ; 5
nth( 1 listF )                ; 4
nth( length(listF)-1 listF )  ; 0

removeListDuplicates(元素去重)

listG = list( 1 1 2 3 3 1 4 5 5 3 5 ) ; ( 1 1 2 3 3 1 4 5 5 3 5 )
removeListDuplicates( listG )         ; ( 1 2 3 4 5 )

rplaca(替换list首元素)

rplacd(替换list中后面的list单元)

进阶内容