请选择 进入手机版 | 继续访问电脑版

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 234|回复: 6

[转帖] Cadence Skill 语言入门

[复制链接]

5

主题

7

回帖

42

积分

实习生

积分
42
发表于 2023-5-12 16:29:21 | 显示全部楼层 |阅读模式
本帖最后由 huol 于 2023-5-12 16:31 编辑

Cadence Skill
Cadence 提供二次开发的 SKILL 语言,它是一种基于通用人工智能语言 — LISP 的交互式高级编程语言。①

SKILL 语言支持一套类似 C 语言的语法,大大降低了初学者学习的难度,同时高水平的编程者可以选择使用类似 LISP 语言的全部功能。所以 SKILL 语言既可以用作最简单的工具语言,也可以作为开发任何应用的、强大的编程语言。

SKILL 可以与底层系统交互,也提供了访问 Cadence 各个工具的丰富接口,用户可以通过 Skill 语言来访问,并且可以开发自己的基于 Cadence 平台的工具。

我的环境是 Virtuoso IC618 平台 ( SKILL37.00 ) ,可能存在某些特性不适用于旧版本。

① LISP 即 List Processing,是最早和最重要的符号处理编程语言之一,它于 1958 年由美国的 J. McCarthy 提出,LISP 在人工智能方面获得广泛应用。

如何查看官方资料
下面 $CDSHOME 指 Virtuoso 安装路径

cdsFinder
模糊查找 Skill 函数,查看简单介绍。
  1. sh> $CDSHOME/tools/bin/cdsFinder &
复制代码
cdnshelp
查看更加详细的内容,软件的使用手册。
  1. sh> $CDSHOME/tools/bin/cdnshelp &
复制代码
Skill Version
查看 Skill 版本
  1. sh> $CDSHOME/bin/skill -V
  2. # @(#)$CDS: skill version 37.18 06/26/2020 19:54 (sjfhw830) $
复制代码
也可以启动 Virtuoso 在 CIW 中执行
  1. getSkillVersion()
  2. ; "SKILL37.00"
复制代码

5

主题

7

回帖

42

积分

实习生

积分
42
 楼主| 发表于 2023-5-12 16:35:31 | 显示全部楼层
基础语法
Hello World
举例一种 Hello World 的写法。
  1. println( "Hello World" )
  2. ; "Hello World"
复制代码
一般习惯使用 println 来直接打印内容做简单的查看,同时也便于查看数据类型,这个后面讲数据类型部分会提到。

注释 (comment)
单行注释
  1. ; 这是单行注释
复制代码

多行注释
  1. /*
  2.   这是
  3.   多行注释
  4. */
复制代码

代码风格
语法风格
由于 Skill 语言是基于 LISP ,因此它除了支持 函数表示法 同时也支持 前缀表示法 来编写代码。

函数表示法 类似 C

f(x)
  1. func( arg1 arg2 )
复制代码

前缀表示法 类似 Tcl

(f x)
  1. ( func arg1 arg2 )
复制代码

这里推荐统一使用 函数表示法,并需要注意函数名和括号之间不能有空格。

以上的两种表示法可以同时存在,因此在编写代码时候最好注意格式的统一。

  1. 错误写法 : func ( arg1 arg2 )

  2. func 后面多了一个空格,这会将 arg1 作为函数名
复制代码

命名风格
其次函数和变量的命名风格,Skill 中一般是使用 驼峰命名法 ( Camel-Case ),命名以小写开头,之后的每一个逻辑断点(单词)首字母大写,一般代表类别、作者,像我写的函数和全局变量一般都习惯用 yc 开头。

驼峰命名法也是官方使用的命名方式,现在一些主流的编程语言也比较流行使用这种命名方式。

当然如何选择取决于你自己,下面是一个对比:

驼峰命名法

  1. geGetEditCellView()
  2. dbOpenCellViewByType(libName cellName viewName nil mode)
复制代码

蛇形命名法(下划线)

  1. ge_get_edit_cell_view()
  2. db_open_cell_view_by_type(lib_name cell_name view_name nil mode)
复制代码

无论使用哪种命名方式,都要注意风格的统一,所以更推荐与官方函数一样,使用驼峰命名法。

最后再注意命名不能过于简化,例如 a,b ,c。命名的目标是至少让人能一眼看出来这个变量是做什么用的(一眼不行看两眼),例如上面的 libName,cellName,viewName。

下面的内容中我可能会使用的一些非常简化变量命名,考虑到只是用于演示,一些变量也没有什么实际的含义,命名就随意些了。
代码块标注使用 LISP 语言只是为了能够正确识别单行注释,毕竟 Skill 还是比较冷门的,很多平台都不支持它的语法高亮。

真 / 假 (boolean)
Skill 中 真用 t 来表示,假用 nil 来表示。

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

判断 t

  1. when( t
  2.   println( "True" )
  3. )
  4. ; t 为真,打印 "True"
复制代码

判断 10

  1. when( 10
  2.   println( "True" )
  3. )
  4. ; 10 为真,打印 "True"
复制代码

判断 "YEUNGCHIE"

  1. when( "YEUNGCHIE"
  2.   println( "True" )
  3. )
  4. ; "YEUNGCHIE" 为真,打印 "True"
复制代码

判断 nil

  1. when( nil
  2.   println( "True" )
  3. )
  4. ; nil 为假,不运行 println 语句
复制代码

when 语句中第一个参数为判断条件,条件为真才会运行。

5

主题

7

回帖

42

积分

实习生

积分
42
 楼主| 发表于 2023-5-12 16:46:09 | 显示全部楼层
数据类型
可以用函数 type 查看一个数据的类型标识。

  1. type( 'YEUNGCHIE )          ; symbol
  2. type( "YEUNGCHIE" )         ; string
  3. type( list( "YEUNGCHIE" ))  ; list
复制代码

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

  1. println( 'YEUNGCHIE )          ; YEUNGCHIE
  2. println( "YEUNGCHIE" )         ; "YEUNGCHIE"
  3. println( list( "YEUNGCHIE" ))  ; ( YEUNGCHIE )
复制代码

字符串 (string)
定义方式
字符串用双引号括起来。

  1. myName = "YEUNGCHIE"
  2. ; "YEUNGCHIE"
复制代码

打印字符串

print
  1. print( myName )
  2. ; "YEUNGCHIE"
复制代码

println
  1. println( myName )
  2. ; "YEUNGCHIE"
复制代码

上面两个 print 的结果看似一样,实际上是有区别的。

Skill 提供了多种 print 函数,可以应对各种使用场景,具体的详解可以看这篇随笔:[ Skill ] print println printf fprintf sprintf lsprintf

相关函数

strcat 字符串连接
  1. a = "YEUNG"
  2. b = "CHIE"
  3. c = strcat( a b )
  4. println( c )
  5. ; "YEUNGCHIE"
复制代码

strlen 字符串长度

下面的 c 沿用上面的变量 c,为了演示不写得过于繁杂、重复。
  1. println(strlen( c ))
  2. ; 9
复制代码

数字 (number)
数字分为 整数 和 浮点数。

整数
  1. 18
复制代码

也可以直接编写成 二进制 ( 0b 前缀 )、八进制 ( 0 前缀 )、十六进制 ( 0x 前缀 ),但默认会输出成 十进制
  1. 0b10010    ; 18
  2. 024        ; 20
  3. 0xFE       ; 254
复制代码

浮点数
  1. 3.14
复制代码

浮点数也可以使用 科学计数法 和 单位后缀 来表示
  1. 1e-06    ; 0.000001
  2. 1u       ; 0.000001
复制代码

相关函数

整数判断 fixp
  1. fixp( 1 )    ; t
  2. fixp( 1.0 )  ; nil
复制代码

浮点数判断 floatp
  1. floatp( 1.2 )  ; t
  2. floatp( 2 )    ; nil
复制代码

奇数判断 oddp
  1. oddp( 1 )  ; t
  2. oddp( 2 )  ; nil
复制代码

偶数判断 evenp
  1. evenp( 2 )  ; t
  2. evenp( 3 )  ; nil
复制代码

取整

舍位取整 fix / fix2
  1. fix( 3.2 )              ; 3
  2. ;-----------------------------------
  3. flonum = 1.2/(0.1+0.3)  ; 3.0
  4. fix( flonum )           ; 2 warning!
复制代码

Tips : 第三行经过一番浮点运算后得出 3.0 赋值给 flonum,舍位取整后结果却是 2 。

这是由于浮点运算误差造成的,实际运算结果会比 3.0 小一点点,有兴趣了解的话可以看下知乎上的一篇回答:
为什么浮点运算有误差?

为了解决这个问题 Skill 在后面的版本中提供了另一个函数来解决。
  1. fix2( flonum )  ; 3
复制代码

进位取整 ceiling
  1. ceiling( 3.0 )  ; 3
  2. ceiling( 3.2 )  ; 4
复制代码

四舍五入 round / round2

这函数也有两个版本,与 fix2 同理,不多赘述,反正有 2 用 2 准没错。
  1. round2( 3.2 )  ; 3
  2. round2( 3.5 )  ; 4
  3. ;-----------------------------------
  4. round( flonum - 0.5 )   ; 2 warning!
  5. round2( flonum - 0.5 )  ; 3
复制代码

链表 (list)
链表其实不是一种数据类型,而是一种数据的存储结构。

定义方式

list

list( arg1 arg2 list( arg3 arg4 ) ... )
  1. listA = list( 1 2 )
  2. ; ( 1 2 )
复制代码

'
'( "value1" sym1 (1 2) ... )
这种表达方式需要注意,它不适用于变量元素,例如上面 sym1 并不会带入变量值,而是 symbol 类型 'sym1
  1. listB = '( 3 4 )
  2. ; ( 3 4 )
复制代码

:
arg1 : arg2
仅在只有两个元素时使用,通常用来表达一个坐标点
  1. point = 1 : 2
  2. ; ( 1 2 )
复制代码

相关函数

连接两个 list append
  1. listC = append( listA listB )
  2. println( listC )
  3. ; ( 1 2 3 4 )
复制代码

往末尾追加元素 append1
  1. listD = append1( listC 5 )
  2. println( listD )
  3. ; ( 1 2 3 4 5 )
复制代码

往开头追加 cons
  1. listE = cons( 0 listD )
  2. println( listE )
  3. ; ( 0 1 2 3 4 5 )
复制代码

翻转一个 list reverse
  1. listF = reverse( listE )
  2. println( listF )
  3. ; ( 5 4 3 2 1 0 )
复制代码

获取一个 list 元素个数 length
  1. length( listF )
  2. ; 6
复制代码

提取第一个元素 car
  1. car( listF )
  2. ; 5
复制代码

提取除了第一个元素之后的 list cdr
  1. cdr( listF )
  2. ; ( 4 3 2 1 0 )
复制代码

简化 car / cdr 的组合函数

cadr( listF ) 其实就是 car(cdr( listF )) 的简写
  1. cadr( listF )  ; 4
  2. caddr( listF ) ; 3
复制代码

根据索引提取 nth
  1. nth( 0 listF )                ; 5
  2. nth( 1 listF )                ; 4
  3. nth( length(listF)-1 listF )  ; 0
复制代码

元素去重 removeListDuplicates
  1. listG = list( 1 1 2 3 3 1 4 5 5 3 5 ) ; ( 1 1 2 3 3 1 4 5 5 3 5 )
  2. removeListDuplicates( listG )         ; ( 1 2 3 4 5 )
复制代码

list 是 Skill 语言中非常重要的结构,还有很多相关函数,后面再单独写一篇随笔介绍。

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

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

Print name ( name )
Value
Function binding
Property list
只有 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。

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

给 arg 赋值为 12,然后打印出来。
  1. arg = 12
  2. println( arg )
  3. ; 终端会打印 12
复制代码

给 arg 再赋值为 'unbound,然后尝试 print 一下。
  1. arg = 'unbound
  2. println( arg )
  3. ; 终端会提示 *Error* eval: unbound variable - arg
复制代码

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

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

arg 已经被赋值为 'unbound 现在是未定义的状态。
  1. boundp( 'arg )
  2. ; 结果是 nil
复制代码

再给它赋值一个值。
  1. arg = 1
  2. boundp( 'arg )
  3. ; 结果是 t
复制代码

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

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

dbCreateRect 是 Virtuoso 自带函数。
  1. fboundp( 'dbCreateRect )
  2. ; 结果是 lambda:dbCreateRect
复制代码

判断一下,函数 QWE123 是否存在:
  1. fboundp( 'QWE123 )
  2. ; 不存在,结果是 nil
复制代码

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

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

定义 makeTable
  1. HASH = makeTable( "Skill HASH" )
复制代码

赋值
  1. HASH[ 1 ]       = "ONE"
  2. HASH[ "2" ]     = 2
  3. HASH[ cvId ]    = "cellName"
  4. HASH[ 'myName ] = "YEUNGCHIE"
复制代码

访问

通过 key 查看
  1. HASH[ 1 ]    ; "ONE"
  2. HASH[ "2" ]  ; 2
复制代码

如果 key 是 symbol

这时你还可以使用箭头符号 ~> 来访问,非常舒服。
  1. HASH~>myName  ; "YEUNGCHIE"
复制代码

也可以使用函数 get,不过这个一般不用。

get( HASH "myName" )  ; "YEUNGCHIE"
查看一个哈希的所有 key / value
  1. HASH~>?
  2. ; ( myName "2" 1 db:0x21cfda1a )
  3. HASH~>??
  4. ; ( myName "YEUNGCHIE" "2" 2 1 "ONE" db:0x21cfda1a "cellName" )
复制代码

遍历一个哈希所有元素
  1. foreach( key HASH
  2.   printf( "key: %A  ,  value: %A\n" key HASH[ key ] )
  3. )
  4. ; key: db:0x21cfda1a  ,  value: "cellName"
  5. ; key: 1  ,  value: "ONE"
  6. ; key: "2"  ,  value: 2
复制代码

数组 / 向量
数组 和 向量 不常用,简单了解一下就行。

数组 (array) (不常用)
定义

declare
  1. declare( ARRAY[10] )
复制代码

赋值
  1. ARRAY[2] = 4
  2. ARRAY[3] = 5
复制代码

访问
  1. println( ARRAY[2] * ARRAY[3] )
  2. ; 20
  3. println( ARRAY[0] )
  4. ; unbound
  5. println( ARRAY[10] )
  6. ; *Error* arrayref: array index out of bounds - ARRAY[10]
复制代码

向量 (vector) (不常用)
定义

makeVector
  1. VECTOR = makeVector( 10 )
复制代码

赋值
  1. VECTOR[2] = 4
  2. VECTOR[3] = 5
复制代码

访问
  1. println( VECTOR[2] * VECTOR[3] )
  2. ; 20
  3. println( VECTOR[0] )
  4. ; unbound
  5. println( VECTOR[10] )
  6. ; *Error* arrayref: array index out of bounds - VECTOR[10]
复制代码

5

主题

7

回帖

42

积分

实习生

积分
42
 楼主| 发表于 2023-5-12 16:46:55 | 显示全部楼层
数据运算算数运算
操作符
函数
备注
+
a + b
plusplus(a b)
-
a - b
differencedifference(a b)
*
a * b
timestimes(a b)
/
a / b
quotientquotient(a b)
remainderremainder(a b)用于整数
modfmodf(a b)用于浮点数
乘方
**
a ** b
exptexpt(a b)
开方sqrtsqrt(a b)

赋值运算
操作符
函数
直接赋值
=
a = 1
setqsetq( a 1 )
自增
+=
a += 1
自增 (+1)
++
++a
add1add1( a )
a++
postincrementpostincrement( a )
自减
-=
a -= 1
自减 (-1)
--
--a
sub1sub1( a )
a--
postdecrementpostdecrement( a )

Tips :
  • 假设 a = 1
  • ++a 会将 a 加一后的值返回,返回值是 2
  • a++ 会先返回 a 的值后加一,返回值是 1
  • add1( a ) 会返回 2,但不改变 a 的值
  • postincrement( a ) 会返回 1,a 的值会加一变成 2
  • 自减同理
比较运算
操作符
函数
相等
==
a == b
equalequal( a b )
不相等
!=
a == b
nequalnequal( a b )
小于
<
a < b
lessplessp( a b )
小于等于
<=
a <= b
leqpleqp( a b )
大于
>
a > b
greaterpgreaterp( a b )
大于等于
>=
a >= b
geqpgeqp( a b )
几乎相等nearlyEqualnearlyEqual( a b )

逻辑运算
操作符
函数
&&
a && b
andand( a b )
||
a || b
oror( a b )
!
! a
notnot( a )



5

主题

7

回帖

42

积分

实习生

积分
42
 楼主| 发表于 2023-5-12 16:49:42 | 显示全部楼层
条件判断
if
真/假 情况运行的都是单一的语句
  1. if( 条件
  2.     当条件成立时运行
  3.     当条件不成立时运行
  4. )
复制代码

例如:当 a > b 成立时,打印 "Yes";不成立时,打印时 "No"。
  1. if( a > b
  2.   println( "Yes" )
  3.   println( "No"  )
  4. )
复制代码

真/假 情况需要运行多条语句
  1. if( 条件
  2. then
  3.     当条件成立时运行 1
  4.     当条件成立时运行 2
  5. else
  6.     当条件不成立时运行 3
  7.     当条件不成立时运行 4
  8. )
复制代码

例如:当 a > b 成立时,c 赋值 1 然后打印 "Yes";不成立时,c 赋值为 0 然后打印时 "No"。
  1. if( a > b
  2. then
  3.   c = 1
  4.   println( "Yes" )
  5. else
  6.   c = 0
  7.   println( "No"  )
  8. )
复制代码

多层嵌套

Skill 不能用 if-elsif-else 这种简化的写法,但是逻辑是一样的,后级的 if 需要在上一级的 else中。

单条运行语句
  1. if( 条件 A
  2.     当条件 A 成立时运行
  3.     if( 条件 B
  4.         否则 当条件 B 成立时运行
  5.         以上条件都不成立时运行
  6.     )
  7. )
复制代码

多条运行语句
  1. if( 条件 A
  2. then
  3.     当条件 A 成立时运行 1
  4.     当条件 A 成立时运行 2
  5. else
  6.     if( 条件 B
  7.     then
  8.         否则 当条件 B 成立时运行 3
  9.         否则 当条件 B 成立时运行 4
  10.     else
  11.         以上条件都不成立时运行 5
  12.         以上条件都不成立时运行 6
  13.     )
  14. )
复制代码

例如下面想实现:

当 a > b 成立时,打印 "Yes";
否则当 a > c 成立时,打印 "Yes";
都不成立时,打印时 "No"。
  1. if( a > b
  2.   println( "Yes" )
  3.   if( a > c
  4.     println( "Yes" )
  5.     println( "No"  )
  6.   )
  7. )
复制代码

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

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

when / unless
when / unless 就非常简单了,只当给定的条件为 真/假 的时候才运行给定的语句

条件为真才运行。
  1. when( a > b
  2.   println( "Yes" )
  3. )
复制代码

条件为假才运行。
  1. unless( a > b
  2.   println( "No" )
  3. )
复制代码

case / cond
前面说了 case/cond 可以用来优化多条件下的 if ,因此逻辑是一样的。

case

当所有的条件都是对一个变量做是否相等的判断的时候,可以使用 case。

例如,现在有一个变量 arg:

当 arg 等于 "a" 时,打印 "It is a";
否则当 arg 等于 "b" 或 "c" 时,打印 "It is b or c";
否则当 arg 等于 "d" 时,打印 "It is d";
全都不成立时,打印 "Unmatch"。
  1. case( arg
  2.     ( "a"
  3.         println( "It is a" )
  4.     )
  5.     (( "b" "c" )
  6.         println( "It is b or c" )
  7.     )
  8.     ( "d"
  9.         println( "It is d" )
  10.     )
  11.     ( t
  12.         println( "Unmatch" )
  13.     )
  14. )
复制代码

上面的语句换成 if 需要这样写:
  1. if( arg == "a"
  2.     println( "It is a" )
  3.     if( arg == "b" || arg == "c"
  4.         println( "It is b or c" )
  5.         if( arg == "d"
  6.             println( "It is d" )
  7.             println( "Unmatch" )
  8.         )
  9.     )
  10. )
复制代码

很明显 case 的写法更加清晰,观感上也更加舒服。

cond

case 的使用情景比较单一,当条件多且判断的对象或者逻辑不唯一的时候可以 cond。

例如,现在需要对变量 a 做几个判断:

当 a > b 时,打印 "Bigger than b";
否则当 a > c 时,打印 "Bigger than c";
都不成立时,打印 "Smallest"。
  1. cond(
  2.     ( a > b
  3.         println( "Bigger than b" )
  4.     )
  5.     ( a > c
  6.         println( "Bigger than c" )
  7.     )
  8.     ( t
  9.         println( "Smallest" )
  10.     )
  11. )
复制代码

5

主题

7

回帖

42

积分

实习生

积分
42
 楼主| 发表于 2023-5-12 16:51:06 | 显示全部楼层
循环控制
for
指定一个起始整数(initial)和终止整数(final),依次遍历从 initial 到 final 组成的 list 的每一个元素,间隔为 1。

下面用 for 打印从 0 到 2:
  1. for( x 0 2
  2.     println( x )
  3. )
  4. ; 0
  5. ; 1
  6. ; 2
复制代码

foreach
指定一个 list ,依此遍历每一个元素。

下面用 foreach 打印从 0 到 2:
  1. foreach( x list( 0 1 2 )
  2.     println( x )
  3. )
  4. ; 0
  5. ; 1
  6. ; 2
复制代码

也可以同时遍历多个 list
  1. foreach(( x y z ) list( 0 1 2 ) list( 3 4 5 ) list( 6 7 8 )
  2.     printf( "%d %d %d\n" x y z )
  3. )
  4. ; 0 3 6
  5. ; 1 4 7
  6. ; 2 5 8
复制代码

foreach 的返回值是第一个 list,( 0 1 2 )

while
指定一个条件,当条件为真时才会运行,当条件为假时跳出 while 循环。

下面用 while 打印从 0 到 2:
  1. a = 0
  2. while( a < 3
  3.     println( a )
  4.     a++
  5. )
  6. ; 0
  7. ; 1
  8. ; 2
复制代码

5

主题

7

回帖

42

积分

实习生

积分
42
 楼主| 发表于 2023-5-12 16:53:06 | 显示全部楼层
本帖最后由 huol 于 2023-5-12 17:06 编辑

子程序
定义子程序 (函数)
子程序代码块以 procedure 函数来声明。
一个子程序由三个部分组成:名称、参数、代码块。
代码块中以最后一段语句的运算结果作为子程序的返回值。
下面举个例子:
  1. procedure( myAdd( a b )
  2.   a + b
  3. ); myAdd
复制代码

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

myAdd 就是子程序的名称。
a 和 b 是定义需要两个参数,这两个参数会作为子程序的局部变量,局部变量等会再细讲。
a + b 是代码块部分,仅有一行也作为最后一行,因此会返回两个变量相加之后的值。
下面看下如何调用这个 myAdd:
  1. myAdd( 1 2 )
  2. ; 返回 3
复制代码

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

  1. procedure( myAddOneAndTwo()
  2.   3
  3. ); 这个子程序直接返回 3
  4. myAddOneAndTwo()
  5. ; 3
复制代码

p

局部变量
let
let 用来定义局部变量,定义变量的改动不会影响到代码块外的同名变量。
也是代码块中以最后一段语句的运算结果作为返回值。
用法:

  1. a = 5
  2. let(( a )
  3.   a = 7
  4.   println( a )
  5. )
  6. println( a )
复制代码

返回的结果是:

  1. 7
  2. 5
复制代码

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

  1. let(( a( 7 ) )
  2.   println( a )
  3. )
复制代码

prog
prog 相对于 let 增加了 return 和 go 函数的支持。
prog 的默认返回值是 nil
return
由于 prog默认的返回值是 nil,因此需要一个方法能够指定返回值是什么。而 return 就能够实现在一个 prog 中的任意位置跳出,并指定返回值。

示范一下:

  1. prog(( a )
  2.   a = 1
  3.   when( a < 5
  4.     return( t )
  5.   )
  6.   return() ; 当 return 不指定的返回值的时候等同于 return( nil )
  7. )
复制代码

上面的程序由于 a 为 1 ,小于 5,因此执行 when 中的语句,跳出 prog 并返回 t 。

运用 prog + return 可以更加灵活的控制 while 、 foreach 等循环结构。

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

满足条件提前跳出循环

  1. a = 0
  2. prog(( )
  3.   while( a <= 3
  4.     a++
  5.     when( a == 2 return())
  6.     print( a )
  7.   ); while
  8. ); prog
复制代码

当前 a == 2 时,跳出 prog ,由于 prog 在 while 循环外部,因此整个循环会结束。

结果打印 1 。

满足条件直接运行下一个循环

  1. a = 0
  2. while( a <= 3
  3.   prog(( )
  4.     a++
  5.     when( a == 2 return())
  6.     print( a )
  7.   ); prog
  8. ); while
复制代码

当前 a == 2 时,跳出 prog ,由于 prog 在 while 循环内部,因此当前循环结束,不执行 print 直接进入下一次循环。

结果打印 134 。

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

  1. prog(( a )
  2.   a = 0
  3.   LABEL          ; 打个标签
  4.   print( a++ )
  5.   when( a <= 3
  6.     go( LABEL )  ; 跳转到标签的位置
  7.   )
  8. ); prog
复制代码

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

go 的使用存在一些限制,不能在多个 prog 之间跳转,不能往循环内跳转:

可以从循环内往循环外跳转
不能从循环外往循环内跳转
不能从循环内往循环内跳转(除非循坏内部再加一个 prog)
输入类型限制
上面我们已经定义了一个 myAdd 函数,由于执行的过程是做加法,因此它有一个隐含的要求是:输入的两个变量都必须是数字,否则运行会报错

can't handle

我们可以在子程序的定义中加入这个变量类型的判断:

  1. procedure( myAdd( a b )
  2.   unless( numberp( a ) && numberp( b )
  3.     error("myAdd: Argument should be number.")
  4.   )
  5.   a + b
  6. ); myAdd
复制代码

运行上面的函数试试:

  1. myAdd( "1" "2" )
  2. ; *Error* myAdd: Argument should be number.
复制代码

不过我们不需要这么麻烦去直接写每个参数的判断,Skill 已经提供了更简单的方法:

  1. procedure( myAdd( a b "nn")
  2.   a + b
  3. ); myAdd
复制代码

再运行上面的函数试试:

  1. myAdd( "1" "2" )
  2. ; *Error* myAdd: argument #1 should be a number (type template = "nn") - "1"
复制代码

可以看到仅仅是在参数定义之后追加了 "n" 就可以起到效果,第一个 n 声明第一个参数需要为数字(number 缩写成 n),第二个 n 同理声明第二个参数。

不过这个写法还能简化:... ( a b "n") ... ,像这样只写一个 n 就代表所有的参数都必须为数字类型。

常见数据类型
下面举例一些常见的用于声明数据类型的缩写:
缩写内部命名数据类型
ddbobjectid , Cadence 数据对象
xinteger整数
fflonum浮点数
nnumber整数 或者 浮点数
ggeneral通用的 , 任何数据类型
llist链表
pportI / O 句柄
tstring字符串
ssymbolsymbol(符号)
SstringSymbolsymbol 或者 字符串
ufunction函数对象 , 函数名 或者 lambda 对象
.........

可选的输入参数



定义输入参数时候,可以使用一些 修饰 符号来做到类似 Getopt 的效果,常用的如下:

@rest

定义 不限数量 的输入

@optional

定义 可有可无 的输入,需要按顺序

@key

定义 可有可无 的输入,需要指定参数名

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

@rest
还是用上面的子程序 myAdd 来举例。

场景:现在这个程序,只能接受两个参数做加法,如果输入是 3 个或者更多怎么办? 我不知道有多少个参数需要一次性输入。这时候就需要用到 @rest

优化一下 myAdd:

  1. procedure( myAdd( @rest args ) ; 修饰符号写在被修饰参数的前面
  2.   prog(( result )
  3.     result = 0
  4.     foreach( num args
  5.       printf( "myAdd: %n + %n\n" result num )
  6.       result += num
  7.     )
  8.     return( result )
  9.   )
  10. ); myAdd
复制代码

运行一下:

  1. myAdd( 1 2 3 )
  2. ; myAdd: 0 + 1
  3. ; myAdd: 1 + 2
  4. ; myAdd: 3 + 3
  5. ; => 6
复制代码

例子中只定义了一个输入参数,被声明 @rest 后,args 会变成一个 list 参与子程序内部运行,接着遍历所有元素加起来就行了。

@optional
场景:myAdd 现在只能做加法,如果我想自定义运算类型,且要求不指定运算类型的时候默认做加法怎么办?这时候就需要用到 @optional

下面的例子为了避免矛盾,就不使用 @rest 了,将 args 用一个 list 来输入。

  1. procedure( myCalc( args @optional opt("+") )  ; 参数后面的括号内写上默认值,也可以写成 ( opt "+" )
  2.   prog(( result )
  3.     result = car( args )
  4.     args   = cdr( args )
  5.     foreach( num args
  6.       printf( "myCalc: %n %s %n ; " result opt num )
  7.       case(opt
  8.         ("+"  result += num )
  9.         ("-"  result -= num )
  10.         ("*"  result *= num )
  11.         ("/"  result /= num )
  12.       ); 按照不同的 opt 执行不同的操作
  13.     )
  14.     return( result )
  15.   )
  16. ); myCalc
复制代码

运行一下:

  1. myCalc( list( 1 2 3 ))
  2. ; myCalc: 1 + 2 ; myCalc: 3 + 3 ;
  3. ; => 6

  4. myCalc( list( 1 2 3 ) "-")
  5. ; myCalc: 1 - 2 ; myCalc: -1 - 3 ;
  6. ; => -4

  7. myCalc( list( 1 2 3 ) "*")
  8. ; myCalc: 1 * 2 ; myCalc: 2 * 3 ;
  9. ; => 6

  10. myCalc( list( 1.0 2 3 ) "/")
  11. ; myCalc: 1.000000 / 2 ; myCalc: 0.500000 / 3 ;
  12. ; => 0.1666667
复制代码

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

使用格式:

定义

  1. procedure( function( @key key1 key2 )
  2.   ...
  3. )
复制代码

不指定默认值的时候,默认值就是 nil

运行:

  1. function(  ?key1 arg1  ?key2 arg2  ...  )
复制代码

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

  1. procedure( myCalc( @key args opt("+") )
  2.   ; prog ... 过程完全一致,这里就不写了
  3. ); myCalc
复制代码

运行一下:

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

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

正确用法
  1. myCalc( ?args list( 1 2 3 ) ?opt "+")
  2. ; myCalc: 1 + 2 ; myCalc: 3 + 3 ;
  3. ; => 6
复制代码

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

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

  1. sum = lambda(( a b )
  2.   a + b
  3. )
复制代码

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

  1. funcall( sum 1 2 )
  2. ; 3
复制代码

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

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

  1. apply( sum list( 1 2 ))
  2. ; 3
复制代码

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

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

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

  1. apply( 'plus list( 1 2 ))
  2. ; 3
复制代码

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

mapcar
mapcar 的效果其实也是循环,之所以放到 《子程序》 章节来讲,是因为使用这个函数需要先了解子程序是什么。

假设现在有一个 list:

  1. numbers1 = list( 1 3 2 4 5 7 )
复制代码

现在要将这个 list 中的每个元素都 +1 ,用 foreach 可以这样做:

  1. numbers2 = nil
  2. foreach( x numbers1
  3.   numbers2 = append1( numbers2 ++x )
  4. )
  5. println( numbers2 )
  6. ; ( 2 4 3 5 6 8 )
复制代码

可以看到还是比较啰嗦的,换做 mapcar 就很方便了:

  1. numbers3 = mapcar( 'add1 numbers1 )
  2. ; ( 2 4 3 5 6 8 )
复制代码

也可以接受一个 lambda 匿名函数:

  1. numbers4 = mapcar( lambda(( a ) a++ ) numbers1 )
  2. ; ( 2 4 3 5 6 8 )
复制代码

另外前面讲到 foreach 的返回值是第一个 list,配合 mapcar 后可以将每次循环的结果作为返回值。

  1. numbers5 = foreach( mapcar x numbers1
  2.   x++
  3. )
  4. ; ( 2 4 3 5 6 8 )
复制代码
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

QQ|Archiver|手机版|小黑屋|EDA1024技术论坛

GMT+8, 2023-10-1 17:22 , Processed in 0.071717 second(s), 18 queries .

Powered by Discuz! X3.5

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表