午夜视频在线网站,日韩视频精品在线,中文字幕精品一区二区三区在线,在线播放精品,1024你懂我懂的旧版人,欧美日韩一级黄色片,一区二区三区在线观看视频

分享

[ Skill ] Cadence Skill 語(yǔ)言入門(mén)

 玄遠(yuǎn)書(shū)志 2023-08-17 發(fā)布于廣東

https://www.cnblogs.com/yeungchie/

Cadence Skill

Cadence 提供二次開(kāi)發(fā)的 SKILL 語(yǔ)言,它是一種基于通用人工智能語(yǔ)言 — LISP 的交互式高級(jí)編程語(yǔ)言。①

SKILL 語(yǔ)言支持一套類(lèi)似 C 語(yǔ)言的語(yǔ)法,大大降低了初學(xué)者學(xué)習(xí)的難度,同時(shí)高水平的編程者可以選擇使用類(lèi)似 LISP 語(yǔ)言的全部功能。所以 SKILL 語(yǔ)言既可以用作最簡(jiǎn)單的工具語(yǔ)言,也可以作為開(kāi)發(fā)任何應(yīng)用的、強(qiáng)大的編程語(yǔ)言。

SKILL 可以與底層系統(tǒng)交互,也提供了訪問(wèn) Cadence 各個(gè)工具的豐富接口,用戶可以通過(guò) Skill 語(yǔ)言來(lái)訪問(wèn),并且可以開(kāi)發(fā)自己的基于 Cadence 平臺(tái)的工具。

我的環(huán)境是 Virtuoso IC618 平臺(tái) ( SKILL37.00 ) ,可能存在某些特性不適用于舊版本。

① LISP 即 List Processing,是最早和最重要的符號(hào)處理編程語(yǔ)言之一,它于 1958 年由美國(guó)的 J. McCarthy 提出,LISP 在人工智能方面獲得廣泛應(yīng)用。



如何查看官方資料

下面 $CDSHOME 指 Virtuoso 安裝路徑

cdsFinder

  • 模糊查找 Skill 函數(shù),查看簡(jiǎn)單介紹。
sh> $CDSHOME/tools/bin/cdsFinder &

cdnshelp

  • 查看更加詳細(xì)的內(nèi)容,軟件的使用手冊(cè)。
sh> $CDSHOME/tools/bin/cdnshelp &

Skill Version

  • 查看 Skill 版本
sh> $CDSHOME/bin/skill -V
# @(#)$CDS: skill version 37.18 06/26/2020 19:54 (sjfhw830) $
  • 也可以啟動(dòng) Virtuoso 在 CIW 中執(zhí)行
getSkillVersion()
; "SKILL37.00"


基礎(chǔ)語(yǔ)法

Hello World

舉例一種 Hello World 的寫(xiě)法。

println( "Hello World" )
; "Hello World"

一般習(xí)慣使用 println 來(lái)直接打印內(nèi)容做簡(jiǎn)單的查看,同時(shí)也便于查看數(shù)據(jù)類(lèi)型,這個(gè)后面講數(shù)據(jù)類(lèi)型部分會(huì)提到。

注釋 (comment)

  • 單行注釋
; 這是單行注釋
  • 多行注釋
/*
  這是
  多行注釋
*/

代碼風(fēng)格

語(yǔ)法風(fēng)格

由于 Skill 語(yǔ)言是基于 LISP ,因此它除了支持 函數(shù)表示法 同時(shí)也支持 前綴表示法 來(lái)編寫(xiě)代碼。

  • 函數(shù)表示法 類(lèi)似 C

    f(x)

    func( arg1 arg2 )
    
  • 前綴表示法 類(lèi)似 Tcl

    (f x)

    ( func arg1 arg2 )
    

這里推薦統(tǒng)一使用 函數(shù)表示法,并需要注意函數(shù)名和括號(hào)之間不能有空格。

以上的兩種表示法可以同時(shí)存在,因此在編寫(xiě)代碼時(shí)候最好注意格式的統(tǒng)一。

錯(cuò)誤寫(xiě)法 : func ( arg1 arg2 )

func 后面多了一個(gè)空格,這會(huì)將 arg1 作為函數(shù)名

命名風(fēng)格

其次函數(shù)和變量的命名風(fēng)格,Skill 中一般是使用 駝峰命名法 ( Camel-Case ),命名以小寫(xiě)開(kāi)頭,之后的每一個(gè)邏輯斷點(diǎn)(單詞)首字母大寫(xiě),一般代表類(lèi)別、作者,像我寫(xiě)的函數(shù)和全局變量一般都習(xí)慣用 yc 開(kāi)頭。

駝峰命名法也是官方使用的命名方式,現(xiàn)在一些主流的編程語(yǔ)言也比較流行使用這種命名方式。

當(dāng)然如何選擇取決于你自己,下面是一個(gè)對(duì)比:

  • 駝峰命名法

    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)
    

無(wú)論使用哪種命名方式,都要注意風(fēng)格的統(tǒng)一,所以更推薦與官方函數(shù)一樣,使用駝峰命名法。

最后再注意命名不能過(guò)于簡(jiǎn)化,例如 ab ,c。命名的目標(biāo)是至少讓人能一眼看出來(lái)這個(gè)變量是做什么用的(一眼不行看兩眼),例如上面的 libName,cellNameviewName。

下面的內(nèi)容中我可能會(huì)使用的一些非常簡(jiǎn)化變量命名,考慮到只是用于演示,一些變量也沒(méi)有什么實(shí)際的含義,命名就隨意些了。
代碼塊標(biāo)注使用 LISP 語(yǔ)言只是為了能夠正確識(shí)別單行注釋?zhuān)吘?Skill 還是比較冷門(mén)的,很多平臺(tái)都不支持它的語(yǔ)法高亮。

真 / 假 (boolean)

Skill 中 真用 t 來(lái)表示,假用 nil 來(lái)表示。

其實(shí)參與判斷中的值,除了 nil 和空鏈表以外都可以認(rèn)為是真,例如:

  • 判斷 t

    when( t
      println( "True" )
    )
    ; t 為真,打印 "True"
    
  • 判斷 10

    when( 10
      println( "True" )
    )
    ; 10 為真,打印 "True"
    
  • 判斷 "YEUNGCHIE"

    when( "YEUNGCHIE"
      println( "True" )
    )
    ; "YEUNGCHIE" 為真,打印 "True"
    
  • 判斷 nil

    when( nil
      println( "True" )
    )
    ; nil 為假,不運(yùn)行 println 語(yǔ)句
    

when 語(yǔ)句中第一個(gè)參數(shù)為判斷條件,條件為真才會(huì)運(yùn)行。



數(shù)據(jù)類(lèi)型

可以用函數(shù) type 查看一個(gè)數(shù)據(jù)的類(lèi)型標(biāo)識(shí)。

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

println 打印一個(gè)數(shù)據(jù)的內(nèi)容,同時(shí)也能夠從打印結(jié)果觀察到數(shù)據(jù)類(lèi)型。

println( 'YEUNGCHIE )          ; YEUNGCHIE
println( "YEUNGCHIE" )         ; "YEUNGCHIE"
println( list( "YEUNGCHIE" ))  ; ( YEUNGCHIE )

字符串 (string)

  • 定義方式

字符串用雙引號(hào)括起來(lái)。

myName = "YEUNGCHIE"
; "YEUNGCHIE"
  • 打印字符串

    • print

      print( myName )
      ; "YEUNGCHIE"
      
    • println

      println( myName )
      ; "YEUNGCHIE"
      

    上面兩個(gè) print 的結(jié)果看似一樣,實(shí)際上是有區(qū)別的。

    Skill 提供了多種 print 函數(shù),可以應(yīng)對(duì)各種使用場(chǎng)景,具體的詳解可以看這篇隨筆:[ Skill ] print println printf fprintf sprintf lsprintf

  • 相關(guān)函數(shù)

    • strcat 字符串連接

      a = "YEUNG"
      b = "CHIE"
      c = strcat( a b )
      println( c )
      ; "YEUNGCHIE"
      
    • strlen 字符串長(zhǎng)度

      下面的 c 沿用上面的變量 c,為了演示不寫(xiě)得過(guò)于繁雜、重復(fù)。

      println(strlen( c ))
      ; 9
      

數(shù)字 (number)

數(shù)字分為 整數(shù) 和 浮點(diǎn)數(shù)。

  • 整數(shù)

    18
    

    也可以直接編寫(xiě)成 二進(jìn)制 ( 0b 前綴 )、八進(jìn)制 ( 0 前綴 )、十六進(jìn)制 ( 0x 前綴 ),但默認(rèn)會(huì)輸出成 十進(jìn)制

    0b10010    ; 18
    024        ; 20
    0xFE       ; 254
    
  • 浮點(diǎn)數(shù)

    3.14
    

    浮點(diǎn)數(shù)也可以使用 科學(xué)計(jì)數(shù)法 和 單位后綴 來(lái)表示

    1e-06    ; 0.000001
    1u       ; 0.000001
    
  • 相關(guān)函數(shù)

    • 整數(shù)判斷 fixp

      fixp( 1 )    ; t
      fixp( 1.0 )  ; nil
      
    • 浮點(diǎn)數(shù)判斷 floatp

      floatp( 1.2 )  ; t
      floatp( 2 )    ; nil
      
    • 奇數(shù)判斷 oddp

      oddp( 1 )  ; t
      oddp( 2 )  ; nil
      
    • 偶數(shù)判斷 evenp

      evenp( 2 )  ; t
      evenp( 3 )  ; nil
      
    • 取整

      • 舍位取整 fix / fix2

        fix( 3.2 )              ; 3
        ;-----------------------------------
        flonum = 1.2/(0.1+0.3)  ; 3.0
        fix( flonum )           ; 2 warning!
        

        Tips ??: 第三行經(jīng)過(guò)一番浮點(diǎn)運(yùn)算后得出 3.0 賦值給 flonum,舍位取整后結(jié)果卻是 2 。

        img

        這是由于浮點(diǎn)運(yùn)算誤差造成的,實(shí)際運(yùn)算結(jié)果會(huì)比 3.0 小一點(diǎn)點(diǎn),有興趣了解的話可以看下知乎上的一篇回答:
        為什么浮點(diǎn)運(yùn)算有誤差?

        為了解決這個(gè)問(wèn)題 Skill 在后面的版本中提供了另一個(gè)函數(shù)來(lái)解決。

        fix2( flonum )  ; 3
        
      • 進(jìn)位取整 ceiling

        ceiling( 3.0 )  ; 3
        ceiling( 3.2 )  ; 4
        
      • 四舍五入 round / round2

        這函數(shù)也有兩個(gè)版本,與 fix2 同理,不多贅述,反正有 22 準(zhǔn)沒(méi)錯(cuò)。

        round2( 3.2 )  ; 3
        round2( 3.5 )  ; 4
        ;-----------------------------------
        round( flonum - 0.5 )   ; 2 warning!
        round2( flonum - 0.5 )  ; 3
        

鏈表 (list)

鏈表其實(shí)不是一種數(shù)據(jù)類(lèi)型,而是一種數(shù)據(jù)的存儲(chǔ)結(jié)構(gòu)。

  • 定義方式

    • list

      list( arg1 arg2 list( arg3 arg4 ) ... )

      listA = list( 1 2 )
      ; ( 1 2 )
      
    • '

      '( "value1" sym1 (1 2) ... )

      這種表達(dá)方式需要注意,它不適用于變量元素,例如上面 sym1 并不會(huì)帶入變量值,而是 symbol 類(lèi)型 'sym1

      listB = '( 3 4 )
      ; ( 3 4 )
      
    • :

      arg1 : arg2

      僅在只有兩個(gè)元素時(shí)使用,通常用來(lái)表達(dá)一個(gè)坐標(biāo)點(diǎn)

      point = 1 : 2
      ; ( 1 2 )
      
  • 相關(guān)函數(shù)

    • 連接兩個(gè) list append

      listC = append( listA listB )
      println( listC )
      ; ( 1 2 3 4 )
      
    • 往末尾追加元素 append1

      listD = append1( listC 5 )
      println( listD )
      ; ( 1 2 3 4 5 )
      
    • 往開(kāi)頭追加 cons

      listE = cons( 0 listD )
      println( listE )
      ; ( 0 1 2 3 4 5 )
      
    • 翻轉(zhuǎn)一個(gè) list reverse

      listF = reverse( listE )
      println( listF )
      ; ( 5 4 3 2 1 0 )
      
    • 獲取一個(gè) list 元素個(gè)數(shù) length

      length( listF )
      ; 6
      
    • 提取第一個(gè)元素 car

      car( listF )
      ; 5
      
    • 提取除了第一個(gè)元素之后的 list cdr

      cdr( listF )
      ; ( 4 3 2 1 0 )
      
    • 簡(jiǎn)化 car / cdr 的組合函數(shù)

      cadr( listF ) 其實(shí)就是 car(cdr( listF )) 的簡(jiǎn)寫(xiě)

      cadr( listF )  ; 4
      caddr( listF ) ; 3
      
    • 根據(jù)索引提取 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 )
      

    list 是 Skill 語(yǔ)言中非常重要的結(jié)構(gòu),還有很多相關(guān)函數(shù),后面再單獨(dú)寫(xiě)一篇隨筆介紹。

符號(hào) (symbol)

symbol 的寫(xiě)法是用一個(gè)單引號(hào)開(kāi)頭,例如:'a、'b、'c,這個(gè)類(lèi)型比較抽象,剛接觸知道有這么個(gè)東西就行,重點(diǎn)是需要用到的時(shí)候知道如何去使用。

symbol 實(shí)際上是一種指針,每個(gè) symbol 都存在以下幾個(gè)組成部分(slot):

  • Print name ( name )
  • Value
  • Function binding
  • Property list

只有 name 是必要的,其他的部分都可以為空,當(dāng)一個(gè) 文本引用 產(chǎn)生時(shí),會(huì)創(chuàng)建一個(gè) symbol ,且 Value 會(huì)默認(rèn)為 '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.

當(dāng)我們創(chuàng)建一個(gè)變量會(huì)自動(dòng)生成與之對(duì)應(yīng)的一個(gè) symbol ,默認(rèn)值為 'unbound,這個(gè)過(guò)程是非顯式的。

這樣,如果需要將一個(gè)變量設(shè)置為 未定義/未綁定 的狀態(tài),則可以將它賦值為 'unbound

舉個(gè)例子 ( 重點(diǎn)是這里 )

  • 給 arg 賦值為 12,然后打印出來(lái)。

    arg = 12
    println( arg )
    ; 終端會(huì)打印 12
    
  • 給 arg 再賦值為 'unbound,然后嘗試 print 一下。

    arg = 'unbound
    println( arg )
    ; 終端會(huì)提示 *Error* eval: unbound variable - arg
    

當(dāng)一個(gè)變量沒(méi)有被定義過(guò)的時(shí)候,引用它但是不賦值時(shí)運(yùn)行會(huì)報(bào) Error ,但如果只是想判斷某個(gè)變量是否被定義了,可以使用函數(shù) boundp 去檢測(cè)目標(biāo)變量的 symbol name。

例如,檢測(cè)一下變量 arg 是否被定義:

  • arg 已經(jīng)被賦值為 'unbound 現(xiàn)在是未定義的狀態(tài)。

    boundp( 'arg )
    ; 結(jié)果是 nil
    
  • 再給它賦值一個(gè)值。

    arg = 1
    boundp( 'arg )
    ; 結(jié)果是 t
    

判斷一個(gè)函數(shù)是否存在的時(shí)候也需要利用到 symbol。

例如,判斷一下函數(shù) dbCreateRect 是否存在:

dbCreateRect 是 Virtuoso 自帶函數(shù)。

fboundp( 'dbCreateRect )
; 結(jié)果是 lambda:dbCreateRect

判斷一下,函數(shù) QWE123 是否存在:

fboundp( 'QWE123 )
; 不存在,結(jié)果是 nil

對(duì)照表 / 哈希 (table)

Table 是 key / value 對(duì)的集合。類(lèi)似于 Python 中的字典,但又更加強(qiáng)大,Python 字典的 key 只能是不可變數(shù)據(jù)類(lèi)型,無(wú)法將列表、字典、對(duì)象等作為 key。

而在 Skill 中,Table 的 key 和 value 都可以是任意的數(shù)據(jù)類(lèi)型:string、symbol、number、list、table、id 等都可以。

  • 定義 makeTable

    HASH = makeTable( "Skill HASH" )
    
  • 賦值

    HASH[ 1 ]       = "ONE"
    HASH[ "2" ]     = 2
    HASH[ cvId ]    = "cellName"
    HASH[ 'myName ] = "YEUNGCHIE"
    
  • 訪問(wèn)

    • 通過(guò) key 查看

      HASH[ 1 ]    ; "ONE"
      HASH[ "2" ]  ; 2
      
    • 如果 key 是 symbol

      這時(shí)你還可以使用箭頭符號(hào) ~> 來(lái)訪問(wèn),非常舒服。

      HASH~>myName  ; "YEUNGCHIE"
      

      也可以使用函數(shù) get,不過(guò)這個(gè)一般不用。

      get( HASH "myName" )  ; "YEUNGCHIE"
      
    • 查看一個(gè)哈希的所有 key / value

      HASH~>?
      ; ( myName "2" 1 db:0x21cfda1a )
      HASH~>??
      ; ( myName "YEUNGCHIE" "2" 2 1 "ONE" db:0x21cfda1a "cellName" )
      
    • 遍歷一個(gè)哈希所有元素

      foreach( key HASH
        printf( "key: %A  ,  value: %A\n" key HASH[ key ] )
      )
      ; key: db:0x21cfda1a  ,  value: "cellName"
      ; key: 1  ,  value: "ONE"
      ; key: "2"  ,  value: 2
      

數(shù)組 / 向量

數(shù)組 和 向量 不常用,簡(jiǎn)單了解一下就行。

數(shù)組 (array) (不常用)

  • 定義

    declare

    declare( ARRAY[10] )
    
  • 賦值

    ARRAY[2] = 4
    ARRAY[3] = 5
    
  • 訪問(wèn)

    println( ARRAY[2] * ARRAY[3] )
    ; 20
    println( ARRAY[0] )
    ; unbound
    println( ARRAY[10] )
    ; *Error* arrayref: array index out of bounds - ARRAY[10]
    

向量 (vector) (不常用)

  • 定義

    makeVector

    VECTOR = makeVector( 10 )
    
  • 賦值

    VECTOR[2] = 4
    VECTOR[3] = 5
    
  • 訪問(wèn)

    println( VECTOR[2] * VECTOR[3] )
    ; 20
    println( VECTOR[0] )
    ; unbound
    println( VECTOR[10] )
    ; *Error* arrayref: array index out of bounds - VECTOR[10]
    


數(shù)據(jù)運(yùn)算

算數(shù)運(yùn)算

操作符 函數(shù) 備注
+ 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) 用于整數(shù)
modf modf(a b) 用于浮點(diǎn)數(shù)
乘方 ** a ** b expt expt(a b)
開(kāi)方 sqrt sqrt(a b)

賦值運(yùn)算

操作符 函數(shù)
直接賦值 = 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 ??:

  • 假設(shè) a = 1
  • ++a 會(huì)將 a 加一后的值返回,返回值是 2
  • a++ 會(huì)先返回 a 的值后加一,返回值是 1
  • add1( a ) 會(huì)返回 2,但不改變 a 的值
  • postincrement( a ) 會(huì)返回 1,a 的值會(huì)加一變成 2
  • 自減同理

比較運(yùn)算

操作符 函數(shù)
相等 == 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 )

邏輯運(yùn)算

操作符 函數(shù)
&& a && b and and( a b )
|| a || b or or( a b )
! ! a not not( a )


條件判斷

if

  • 真/假 情況運(yùn)行的都是單一的語(yǔ)句

    if( 條件
        當(dāng)條件成立時(shí)運(yùn)行
        當(dāng)條件不成立時(shí)運(yùn)行
    )
    

    例如:當(dāng) a > b 成立時(shí),打印 "Yes";不成立時(shí),打印時(shí) "No"。

    if( a > b
      println( "Yes" )
      println( "No"  )
    )
    
  • 真/假 情況需要運(yùn)行多條語(yǔ)句

    if( 條件
    then
        當(dāng)條件成立時(shí)運(yùn)行 1
        當(dāng)條件成立時(shí)運(yùn)行 2
    else
        當(dāng)條件不成立時(shí)運(yùn)行 3
        當(dāng)條件不成立時(shí)運(yùn)行 4
    )
    

    例如:當(dāng) a > b 成立時(shí),c 賦值 1 然后打印 "Yes";不成立時(shí),c 賦值為 0 然后打印時(shí) "No"。

    if( a > b
    then
      c = 1
      println( "Yes" )
    else
      c = 0
      println( "No"  )
    )
    
  • 多層嵌套

    Skill 不能用 if-elsif-else 這種簡(jiǎn)化的寫(xiě)法,但是邏輯是一樣的,后級(jí)的 if 需要在上一級(jí)的 else中。

    • 單條運(yùn)行語(yǔ)句
    if( 條件 A
        當(dāng)條件 A 成立時(shí)運(yùn)行
        if( 條件 B
            否則 當(dāng)條件 B 成立時(shí)運(yùn)行
            以上條件都不成立時(shí)運(yùn)行
        )
    )
    
    • 多條運(yùn)行語(yǔ)句
    if( 條件 A
    then
        當(dāng)條件 A 成立時(shí)運(yùn)行 1
        當(dāng)條件 A 成立時(shí)運(yùn)行 2
    else
        if( 條件 B
        then
            否則 當(dāng)條件 B 成立時(shí)運(yùn)行 3
            否則 當(dāng)條件 B 成立時(shí)運(yùn)行 4
        else
            以上條件都不成立時(shí)運(yùn)行 5
            以上條件都不成立時(shí)運(yùn)行 6
        )
    )
    

    例如下面想實(shí)現(xiàn):

    • 當(dāng) a > b 成立時(shí),打印 "Yes";
    • 否則當(dāng) a > c 成立時(shí),打印 "Yes";
    • 都不成立時(shí),打印時(shí) "No"。
    if( a > b
      println( "Yes" )
      if( a > c
        println( "Yes" )
        println( "No"  )
      )
    )
    

    第二個(gè) if 雖然不是 單一語(yǔ)句,但可以將整個(gè) if( a > c ... ) 看做一個(gè)整體,所以也可以忽略 then/else。

    這還有一個(gè)問(wèn)題,當(dāng)需要判斷多個(gè)條件的時(shí)候 if 的寫(xiě)法就太不好看了,這時(shí)就可以使用 case 或者 cond ,等會(huì)講。

when / unless

when / unless 就非常簡(jiǎn)單了,只當(dāng)給定的條件為 / 的時(shí)候才運(yùn)行給定的語(yǔ)句

條件為真才運(yùn)行。

when( a > b
  println( "Yes" )
)

條件為假才運(yùn)行。

unless( a > b
  println( "No" )
)

case / cond

前面說(shuō)了 case/cond 可以用來(lái)優(yōu)化多條件下的 if ,因此邏輯是一樣的。

  • case

    當(dāng)所有的條件都是對(duì)一個(gè)變量做是否相等的判斷的時(shí)候,可以使用 case

    例如,現(xiàn)在有一個(gè)變量 arg:

    • 當(dāng) arg 等于 "a" 時(shí),打印 "It is a";
    • 否則當(dāng) arg 等于 "b" 或 "c" 時(shí),打印 "It is b or c";
    • 否則當(dāng) arg 等于 "d" 時(shí),打印 "It is d";
    • 全都不成立時(shí),打印 "Unmatch"。
    case( arg
        ( "a"
            println( "It is a" )
        )
        (( "b" "c" )
            println( "It is b or c" )
        )
        ( "d"
            println( "It is d" )
        )
        ( t
            println( "Unmatch" )
        )
    )
    

    上面的語(yǔ)句換成 if 需要這樣寫(xiě):

    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 的寫(xiě)法更加清晰,觀感上也更加舒服。

  • cond

    case 的使用情景比較單一,當(dāng)條件多且判斷的對(duì)象或者邏輯不唯一的時(shí)候可以 cond

    例如,現(xiàn)在需要對(duì)變量 a 做幾個(gè)判斷:

    • 當(dāng) a > b 時(shí),打印 "Bigger than b";
    • 否則當(dāng) a > c 時(shí),打印 "Bigger than c";
    • 都不成立時(shí),打印 "Smallest"。
    cond(
        ( a > b
            println( "Bigger than b" )
        )
        ( a > c
            println( "Bigger than c" )
        )
        ( t
            println( "Smallest" )
        )
    )
    


循環(huán)控制

for

指定一個(gè)起始整數(shù)(initial)和終止整數(shù)(final),依次遍歷從 initial 到 final 組成的 list 的每一個(gè)元素,間隔為 1。

下面用 for 打印從 0 到 2:

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

foreach

指定一個(gè) list ,依此遍歷每一個(gè)元素。

下面用 foreach 打印從 0 到 2:

foreach( x list( 0 1 2 )
    println( x )
)
; 0
; 1
; 2

也可以同時(shí)遍歷多個(gè) 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 的返回值是第一個(gè) list,( 0 1 2 )

while

指定一個(gè)條件,當(dāng)條件為真時(shí)才會(huì)運(yùn)行,當(dāng)條件為假時(shí)跳出 while 循環(huán)。

下面用 while 打印從 0 到 2:

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


子程序

定義子程序 (函數(shù))

  • 子程序代碼塊以 procedure 函數(shù)來(lái)聲明。
  • 一個(gè)子程序由三個(gè)部分組成:名稱(chēng)、參數(shù)、代碼塊。
  • 代碼塊中以最后一段語(yǔ)句的運(yùn)算結(jié)果作為子程序的返回值。

下面舉個(gè)例子:

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

這個(gè)子程序用來(lái)實(shí)現(xiàn)輸入變量 ab ,返回 a + b 的值。

  • myAdd 就是子程序的名稱(chēng)。
  • ab 是定義需要兩個(gè)參數(shù),這兩個(gè)參數(shù)會(huì)作為子程序的局部變量,局部變量等會(huì)再細(xì)講。
  • a + b 是代碼塊部分,僅有一行也作為最后一行,因此會(huì)返回兩個(gè)變量相加之后的值。

下面看下如何調(diào)用這個(gè) myAdd

myAdd( 1 2 )
; 返回 3

參數(shù)不是必須的,也可以提供一個(gè)空列表(括號(hào)里空著不寫(xiě))作為參數(shù)的定義,意味著這個(gè)子程序不需要參數(shù)。

procedure( myAddOneAndTwo()
  3
); 這個(gè)子程序直接返回 3
myAddOneAndTwo()
; 3

p

局部變量

let

  • let 用來(lái)定義局部變量,定義變量的改動(dòng)不會(huì)影響到代碼塊外的同名變量。
  • 也是代碼塊中以最后一段語(yǔ)句的運(yùn)算結(jié)果作為返回值。

用法:

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

返回的結(jié)果是:

7
5

如果想給 let 中的變量賦值一個(gè)默認(rèn)值,出了在開(kāi)頭寫(xiě)一個(gè)賦值語(yǔ)句,上面的 let 還可以這樣簡(jiǎn)化:

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

prog

  • prog 相對(duì)于 let 增加了 returngo 函數(shù)的支持。
  • prog 的默認(rèn)返回值是 nil

return

由于 prog默認(rèn)的返回值是 nil,因此需要一個(gè)方法能夠指定返回值是什么。而 return 就能夠?qū)崿F(xiàn)在一個(gè) prog 中的任意位置跳出,并指定返回值。

示范一下:

prog(( a )
  a = 1
  when( a < 5
    return( t )
  )
  return() ; 當(dāng) return 不指定的返回值的時(shí)候等同于 return( nil )
)

上面的程序由于 a 為 1 ,小于 5,因此執(zhí)行 when 中的語(yǔ)句,跳出 prog 并返回 t 。

運(yùn)用 prog + return 可以更加靈活的控制 whileforeach 等循環(huán)結(jié)構(gòu)。

例如下面兩段相似的代碼中,prog 的位置不同對(duì)程序運(yùn)行的影響:

  • 滿足條件提前跳出循環(huán)

    a = 0
    prog(( )
      while( a <= 3
        a++
        when( a == 2 return())
        print( a )
      ); while
    ); prog
    

    當(dāng)前 a == 2 時(shí),跳出 prog ,由于 progwhile 循環(huán)外部,因此整個(gè)循環(huán)會(huì)結(jié)束。

    結(jié)果打印 1

  • 滿足條件直接運(yùn)行下一個(gè)循環(huán)

    a = 0
    while( a <= 3
      prog(( )
        a++
        when( a == 2 return())
        print( a )
      ); prog
    ); while
    

    當(dāng)前 a == 2 時(shí),跳出 prog ,由于 progwhile 循環(huán)內(nèi)部,因此當(dāng)前循環(huán)結(jié)束,不執(zhí)行 print 直接進(jìn)入下一次循環(huán)。

    結(jié)果打印 134 。

go

go 用來(lái)實(shí)現(xiàn)在一個(gè) prog 內(nèi)部,跳轉(zhuǎn)到指定的 標(biāo)簽

prog(( a )
  a = 0
  LABEL          ; 打個(gè)標(biāo)簽
  print( a++ )
  when( a <= 3
    go( LABEL )  ; 跳轉(zhuǎn)到標(biāo)簽的位置
  )
); prog

上面的例子實(shí)現(xiàn)一個(gè)循環(huán),判斷 ++a 的值小于等于 3 時(shí)回到 LABEL 標(biāo)記的位置重復(fù)運(yùn)行,結(jié)果打印 0123 。

go 的使用存在一些限制,不能在多個(gè) prog 之間跳轉(zhuǎn),不能往循環(huán)內(nèi)跳轉(zhuǎn):

  • 可以從循環(huán)內(nèi)往循環(huán)外跳轉(zhuǎn)
  • 不能從循環(huán)外往循環(huán)內(nèi)跳轉(zhuǎn)
  • 不能從循環(huán)內(nèi)往循環(huán)內(nèi)跳轉(zhuǎn)(除非循壞內(nèi)部再加一個(gè) prog

輸入類(lèi)型限制

上面我們已經(jīng)定義了一個(gè) myAdd 函數(shù),由于執(zhí)行的過(guò)程是做加法,因此它有一個(gè)隱含的要求是:輸入的兩個(gè)變量都必須是數(shù)字,否則運(yùn)行會(huì)報(bào)錯(cuò)

can't handle

我們可以在子程序的定義中加入這個(gè)變量類(lèi)型的判斷:

procedure( myAdd( a b )
  unless( numberp( a ) && numberp( b )
    error("myAdd: Argument should be number.")
  )
  a + b
); myAdd

運(yùn)行上面的函數(shù)試試:

myAdd( "1" "2" )
; *Error* myAdd: Argument should be number.

不過(guò)我們不需要這么麻煩去直接寫(xiě)每個(gè)參數(shù)的判斷,Skill 已經(jīng)提供了更簡(jiǎn)單的方法:

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

再運(yùn)行上面的函數(shù)試試:

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

可以看到僅僅是在參數(shù)定義之后追加了 "n" 就可以起到效果,第一個(gè) n 聲明第一個(gè)參數(shù)需要為數(shù)字(number 縮寫(xiě)成 n),第二個(gè) n 同理聲明第二個(gè)參數(shù)。

不過(guò)這個(gè)寫(xiě)法還能簡(jiǎn)化:... ( a b "n") ... ,像這樣只寫(xiě)一個(gè) n 就代表所有的參數(shù)都必須為數(shù)字類(lèi)型。

常見(jiàn)數(shù)據(jù)類(lèi)型

下面舉例一些常見(jiàn)的用于聲明數(shù)據(jù)類(lèi)型的縮寫(xiě):

縮寫(xiě) 內(nèi)部命名 數(shù)據(jù)類(lèi)型
d dbobject id , Cadence 數(shù)據(jù)對(duì)象
x integer 整數(shù)
f flonum 浮點(diǎn)數(shù)
n number 整數(shù) 或者 浮點(diǎn)數(shù)
g general 通用的 , 任何數(shù)據(jù)類(lèi)型
l list 鏈表
p port I / O 句柄
t string 字符串
s symbol symbol(符號(hào))
S stringSymbol symbol 或者 字符串
u function 函數(shù)對(duì)象 , 函數(shù)名 或者 lambda 對(duì)象
... ... ...

可選的輸入?yún)?shù)

定義輸入?yún)?shù)時(shí)候,可以使用一些 修飾 符號(hào)來(lái)做到類(lèi)似 Getopt 的效果,常用的如下:

  • @rest

    定義 不限數(shù)量 的輸入

  • @optional

    定義 可有可無(wú) 的輸入,需要按順序

  • @key

    定義 可有可無(wú) 的輸入,需要指定參數(shù)名

注意 ??:語(yǔ)法上 @optional 和 @key 不能同時(shí)使用,功能上 @rest 和 @optional 同時(shí)使用會(huì)存在矛盾。

@rest

還是用上面的子程序 myAdd 來(lái)舉例。

場(chǎng)景:現(xiàn)在這個(gè)程序,只能接受兩個(gè)參數(shù)做加法,如果輸入是 3 個(gè)或者更多怎么辦? 我不知道有多少個(gè)參數(shù)需要一次性輸入。這時(shí)候就需要用到 @rest

優(yōu)化一下 myAdd

procedure( myAdd( @rest args ) ; 修飾符號(hào)寫(xiě)在被修飾參數(shù)的前面
  prog(( result )
    result = 0
    foreach( num args
      printf( "myAdd: %n + %n\n" result num )
      result += num
    )
    return( result )
  )
); myAdd

運(yùn)行一下:

myAdd( 1 2 3 )
; myAdd: 0 + 1
; myAdd: 1 + 2
; myAdd: 3 + 3
; => 6

例子中只定義了一個(gè)輸入?yún)?shù),被聲明 @rest 后,args 會(huì)變成一個(gè) list 參與子程序內(nèi)部運(yùn)行,接著遍歷所有元素加起來(lái)就行了。

@optional

場(chǎng)景:myAdd 現(xiàn)在只能做加法,如果我想自定義運(yùn)算類(lèi)型,且要求不指定運(yùn)算類(lèi)型的時(shí)候默認(rèn)做加法怎么辦?這時(shí)候就需要用到 @optional

下面的例子為了避免矛盾,就不使用 @rest 了,將 args 用一個(gè) list 來(lái)輸入。

procedure( myCalc( args @optional opt("+") )  ; 參數(shù)后面的括號(hào)內(nèi)寫(xiě)上默認(rèn)值,也可以寫(xiě)成 ( 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 執(zhí)行不同的操作
    )
    return( result )
  )
); myCalc

運(yùn)行一下:

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

場(chǎng)景:上面 @optional 的要求是輸入的參數(shù)必須按順序,即 先 argsopt,我不想固定這個(gè)順序怎么辦?這時(shí)候就需要用到 @key

使用格式:

  • 定義

    procedure( function( @key key1 key2 )
      ...
    )
    

    不指定默認(rèn)值的時(shí)候,默認(rèn)值就是 nil

  • 運(yùn)行:

    function(  ?key1 arg1  ?key2 arg2  ...  )
    

在上面 myCalc 的基礎(chǔ)上改一下:

procedure( myCalc( @key args opt("+") )
  ; prog ... 過(guò)程完全一致,這里就不寫(xiě)了
); myCalc

運(yùn)行一下:

  1. 這里沒(méi)有指定參數(shù)名稱(chēng)( keyword ),會(huì)報(bào)錯(cuò)
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
  1. 輸入?yún)?shù)不需要按順序
myCalc( ?opt "+" ?args list( 1 2 3 ))
; myCalc: 1 + 2 ; myCalc: 3 + 3 ; 
; => 6

匿名函數(shù) (lambda)

匿名函數(shù),顧名思義沒(méi)有名字的函數(shù),不同于 procedure 需要指定一個(gè)函數(shù)名,lambda 不需要指定一個(gè)函數(shù)名,它會(huì)返回一個(gè) lambda 對(duì)象。

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

funcall

上面已經(jīng)定義了一個(gè)匿名函數(shù),并將 lambda 對(duì)象賦值給了 sum 變量。 接下來(lái)就可以用 funcall 函數(shù)來(lái)使用它:

funcall( sum 1 2 )
; 3

funcall 的第一個(gè)參數(shù)接收一個(gè)函數(shù)對(duì)象 sum,后面的參數(shù)依次作為輸入。

apply

如果待輸入的變量保存在一個(gè) list 中,也可以用 apply 函數(shù)來(lái)使用它:

apply( sum list( 1 2 ))
; 3

可以看到,apply 的第一個(gè)參數(shù)接收一個(gè)函數(shù)對(duì)象 sum,第二個(gè)參數(shù)是一個(gè) list ,list 中的元素依次對(duì)應(yīng) sum 需要接收的參數(shù)。

這里需要注意的是,并不是 sum 需要接收一個(gè) list,而是 apply 接收一個(gè) list,再把其中的元素依次作為 sum 的輸入進(jìn)行傳參。sum 接收到的依然是兩個(gè)參數(shù)。

此外 funcallapply 不光可以接收 lambda,也可以接收一個(gè) symbol 變量,前面講到 symbol 存在一個(gè) slot 是 Function binding ,因此也可以調(diào)用非匿名的子程序。

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

這里的 'plus 就作為 plus 函數(shù)的引用。

mapcar

mapcar 的效果其實(shí)也是循環(huán),之所以放到 《子程序》 章節(jié)來(lái)講,是因?yàn)槭褂眠@個(gè)函數(shù)需要先了解子程序是什么。

假設(shè)現(xiàn)在有一個(gè) list:

numbers1 = list( 1 3 2 4 5 7 )

現(xiàn)在要將這個(gè) list 中的每個(gè)元素都 +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 )

也可以接受一個(gè) lambda 匿名函數(shù):

numbers4 = mapcar( lambda(( a ) a++ ) numbers1 )
; ( 2 4 3 5 6 8 )

另外前面講到 foreach 的返回值是第一個(gè) list,配合 mapcar 后可以將每次循環(huán)的結(jié)果作為返回值。

numbers5 = foreach( mapcar x numbers1
  x++
)
; ( 2 4 3 5 6 8 )


文件讀寫(xiě)

看這篇:[ Skill ] 文件讀寫(xiě) & IO 句柄 - YEUNGCHIE - 博客園

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類(lèi)似文章 更多