newLISP 簡介/更多示例
本節包含一些 newLISP 實踐中的簡單示例。您可以在網上和標準 newLISP 發行版中找到大量優秀的 newLISP 程式碼。
您可能會發現您不喜歡某些 newLISP 函式的名稱。您可以使用 constant 和 global 將另一個符號分配給函式
(constant (global 'set!) setf)
您現在可以使用 set! 而不是 setf。這樣做不會造成速度損失。
還可以定義您自己的內建函式替代方案。例如,前面我們定義了一個上下文和一個預設函式,它們的工作方式與 println 相同,但它們會記錄輸出的字元數量。要評估此程式碼而不是內建程式碼,請執行以下操作。
首先,定義函式
(define (Output:Output)
(if Output:counter
(inc Output:counter (length (string (args))))
(set 'Output:counter 0))
(map print (args))
(print "\n"))
透過為 println 定義別名來保留原始 newLISP 版本的 println
(constant (global 'newLISP-println) println)
將 println 符號分配給您的 Output 函式
(constant (global 'println) Output)
現在您可以像往常一樣使用 println
(for (i 1 10)
(println (inc i)))
2 3 4 5 6 7 8 9 10 11
(map println '(1 2 3 4 5))
1 2 3 4 5
它似乎與原始函式的工作方式相同。但現在您還可以利用您定義的替代 println 的附加功能
Output:counter
;-> 36
; or
println:counter
;-> 36
如果您一直在仔細計數,計數器一直在計算傳遞給 Output 函式的引數長度。當然,這些引數包括括號……
有時,使用現有軟體比自己編寫所有例程更容易,即使從頭開始設計可能會很有趣。例如,您可以透過使用現有的資料庫引擎(如 SQLite)而不是構建自定義資料結構和資料庫訪問函式來節省大量時間和精力。以下是如何在 newLISP 中使用 SQLite 資料庫引擎。
假設您有一組想要分析的資料。例如,我發現了一個關於元素週期表中元素的資訊列表,以簡單的空格分隔表的形式儲存
(set 'elements
[text]1 1.0079 Hydrogen H -259 -253 0.09 0.14 1776 1 13.5984
2 4.0026 Helium He -272 -269 0 0 1895 18 24.5874
3 6.941 Lithium Li 180 1347 0.53 0 1817 1 5.3917
...
108 277 Hassium Hs 0 0 0 0 1984 8 0
109 268 Meitnerium Mt 0 0 0 0 1982 9 0[/text])
(您可以在 GitHub 上的 此檔案 中找到該列表。)
此處的列是原子量、熔點、沸點、密度、地殼百分比、發現年份、族和電離能。(我使用 0 表示“不適用”,事實證明這不是一個很好的選擇)。
要載入 newLISP 的 SQLite 模組,請使用以下行
(load "/usr/share/newlisp/modules/sqlite3.lsp")
這將載入包含 SQLite 介面的 newLISP 原始檔。它還建立一個名為 sql3 的新上下文,其中包含用於處理 SQLite 資料庫的函式和符號。
接下來,我們要建立一個新資料庫或開啟一個現有資料庫
(if (sql3:open "periodic_table")
(println "database opened/created")
(println "problem: " (sql3:error)))
這將建立一個名為 periodic_table 的新 SQLite 資料庫檔案,並開啟它。如果檔案已經存在,它將被開啟以供使用。您不必再次引用此資料庫,因為 newLISP 的 SQLite 庫例程在 sql3 上下文中維護一個 當前資料庫。如果 open 函式失敗,則會列印儲存在 sql3:error 中的最新錯誤。
我剛剛建立了這個資料庫,所以下一步是建立一個表。首先,我將定義一個包含列名字串和每個列應使用的 SQLite 資料型別的符號。我不必這樣做,但它可能必須寫下來,因此,與其寫在信封背面,不如把它寫在一個 newLISP 符號中
(set 'column-def "number INTEGER, atomic_weight FLOAT,
element TEXT, symbol TEXT, mp FLOAT, bp FLOAT, density
FLOAT, earth_crust FLOAT, discovered INTEGER, egroup
INTEGER, ionization FLOAT")
現在我可以建立一個建立表的函式
(define (create-table)
(if (sql3:sql (string "create table t1 (" column-def ")"))
(println "created table ... OK")
(println "problem " (sql3:error))))
這很簡單,因為我剛剛以完全正確的格式建立了 column-def 符號!此函式使用 sql3:sql 函式建立一個名為 t1 的表。
我想要一個額外的函式:一個用儲存在元素列表中的資料填充 SQLite 表的函式。它不是一個漂亮的函式,但它完成了工作,而且只需要呼叫一次。
(define (init-table)
(dolist (e (parse elements "\n" 0))
(set 'line (parse e))
(if (sql3:sql
(format "insert into t1 values (%d,%f,'%s','%s',%f,%f,%f,%f,%d,%d,%f);"
(int (line 0))
(float (line 1))
(line 2)
(line 3)
(float (line 4))
(float (line 5))
(float (line 6))
(float (line 7))
(int (line 8))
(int (line 9))
(float (line 10))))
; success
(println "inserted element " e)
; failure
(println (sql3:error) ":" "problem inserting " e))))
此函式呼叫 parse 兩次。第一個 parse 將資料分成行。第二個 parse 將每行分解成一個欄位列表。然後我可以使用 format 將每個欄位的值括在單引號中,記住根據列定義將字串更改為整數或浮點數(使用 int 和 float)。
現在該構建資料庫了
(if (not (find "t1" (sql3:tables)))
(and
(create-table)
(init-table)))
- 如果表列表中不存在 t1 表,則會呼叫建立和填充它的函式。
現在資料庫已準備就緒。但首先,我將編寫一個簡單的實用程式函式來簡化查詢
(define (query sql-text)
(set 'sqlarray (sql3:sql sql-text)) ; results of query
(if sqlarray
(map println sqlarray)
(println (sql3:error) " query problem ")))
此函式提交提供的文字,並透過將 println 對映到結果列表上來列印結果,或顯示錯誤訊息。
以下是一些示例查詢。
查詢所有在 1900 年之前發現且在地殼中佔比超過 2% 的元素,並按發現日期對結果進行排序
(query
"select element,earth_crust,discovered
from t1
where discovered < 1900 and earth_crust > 2
order by discovered")
("Iron" 5.05 0)
("Magnesium" 2.08 1755)
("Oxygen" 46.71 1774)
("Potassium" 2.58 1807)
("Sodium" 2.75 1807)
("Calcium" 3.65 1808)
("Silicon" 27.69 1824)
("Aluminium" 8.07 1825)
惰性氣體(位於第 18 族)是什麼時候發現的?
(query
"select symbol, element, discovered
from t1
where egroup = 18")
("He" "Helium" 1895)
("Ne" "Neon" 1898)
("Ar" "Argon" 1894)
("Kr" "Krypton" 1898)
("Xe" "Xenon" 1898)
("Rn" "Radon" 1900)
所有以 A 開頭的元素的原子量是多少?
(query
"select element,symbol,atomic_weight
from t1
where symbol like 'A%'
order by element")
("Actinium" "Ac" 227)
("Aluminium" "Al" 26.9815)
("Americium" "Am" 243)
("Argon" "Ar" 39.948)
("Arsenic" "As" 74.9216)
("Astatine" "At" 210)
("Gold" "Au" 196.9665)
("Silver" "Ag" 107.8682)
這太簡單了,我的親愛的華生!也許那裡的科學家可以提供一些更具科學意義的查詢示例?
您也可以在網上找到 newLISP 的 MySQL 和 Postgres 模組。
接下來是一個作為命令列實用程式執行的簡單倒計時器。此示例展示了一些訪問指令碼中命令列引數的技術。
要開始倒計時,您需要輸入命令(newLISP 指令碼的名稱)後跟一個持續時間。持續時間可以是秒;分鐘和秒;小時、分鐘和秒;甚至可以是天、小時、分鐘和秒,用冒號隔開。它也可以是任何 newLISP 表示式。
> countdown 30 Started countdown of 00d 00h 00m 30s at 2006-09-05 15:44:17 Finish time: 2006-09-05 15:44:47 Elapsed: 00d 00h 00m 11s Remaining: 00d 00h 00m 19s
或
> countdown 1:30 Started countdown of 00d 00h 01m 30s at 2006-09-05 15:44:47 Finish time: 2006-09-05 15:46:17 Elapsed: 00d 00h 00m 02s Remaining: 00d 00h 01m 28s
或
> countdown 1:00:00 Started countdown of 00d 01h 00m 00s at 2006-09-05 15:45:15 Finish time: 2006-09-05 16:45:15 Elapsed: 00d 00h 00m 02s Remaining: 00d 00h 59m 58s
或
> countdown 5:04:00:00 Started countdown of 05d 04h 00m 00s at 2006-09-05 15:45:47 Finish time: 2006-09-10 19:45:47 Elapsed: 00d 00h 00m 05s Remaining: 05d 03h 59m 55s
或者,您可以提供 newLISP 表示式而不是數值持續時間。這可能是一個簡單的計算,例如 π 分鐘的秒數
> countdown "(mul 60 (mul 2 (acos 0)))" Started countdown of 00d 00h 03m 08s at 2006-09-05 15:52:49 Finish time: 2006-09-05 15:55:57 Elapsed: 00d 00h 00m 08s Remaining: 00d 00h 03m 00s
或者,更有用的是,倒計時到一個特定的時間點,您可以透過從目標時間減去當前時間來提供該時間點
> countdown "(- (date-value 2006 12 25) (date-value))" Started countdown of 110d 08h 50m 50s at 2006-09-05 16:09:10 Finish time: 2006-12-25 00:00:00 Elapsed: 00d 00h 00m 07s Remaining: 110d 08h 50m 43s
- 在此示例中,我們使用 date-value 指定了聖誕節,它返回自 1970 年以來的秒數,用於指定日期和時間。
表示式的求值由 eval-string 完成,在這裡它應用於輸入文字(如果它以“(”開頭 - 通常表明存在 newLISP 表示式!否則,假設輸入是冒號分隔的,並透過 parse 分割並轉換為秒)。
資訊來自命令列上給出的引數,並使用 main-args 提取,main-args 是執行程式時使用的引數列表
(main-args 2)
這將獲取引數 2;引數 0 是 newLISP 程式的名稱,引數 1 是指令碼的名稱,因此引數 2 是 countdown 命令後的第一個字串。
將此檔案儲存為 countdown,並使其可執行。
#!/usr/bin/newlisp
(if (not (main-args 2))
(begin
(println "usage: countdown duration [message]\n
specify duration in seconds or d:h:m:s")
(exit)))
(define (set-duration)
; convert input to seconds
(if (starts-with duration-input "(")
(set 'duration-input (string (eval-string duration-input))))
(set 'duration
(dolist (e (reverse (parse duration-input ":")))
(if (!= e)
(inc duration (mul (int e) ('(1 60 3600 86400) $idx)))))))
(define (seconds->dhms s)
; convert seconds to day hour min sec display
(letn
((secs (mod s 60))
(mins (mod (div s 60) 60))
(hours (mod (div s 3600) 24))
(days (mod (div s 86400) 86400)))
(format "%02dd %02dh %02dm %02ds" days hours mins secs)))
(define (clear-screen-normans-way)
; clear screen using codes - thanks to norman on newlisp forum :-)
(println "\027[H\027[2J"))
(define (notify announcement)
; MacOS X-only code. Change for other platforms.
(and
(= ostype "OSX")
; beep thrice
(exec (string {osascript -e 'tell application "Finder" to beep 3'}))
; speak announcment:
(if (!= announcement nil)
(exec (string {osascript -e 'say "} announcement {"'})))
; notify using Growl:
(exec (format
"/usr/local/bin/growlnotify %s -m \"Finished count down \""
(date (date-value) 0 "%Y-%m-%d %H:%M:%S")))))
(set 'duration-input (main-args 2) 'duration 0)
(set-duration)
(set 'start-time (date-value))
(set 'target-time (add (date-value) duration))
(set 'banner
(string "Started countdown of "
(seconds->dhms duration)
" at "
(date start-time 0 "%Y-%m-%d %H:%M:%S")
"\nFinish time: "
(date target-time 0 "%Y-%m-%d %H:%M:%S")))
(while (<= (date-value) target-time)
(clear-screen-normans-way)
(println
banner
"\n\n"
"Elapsed: "
(seconds->dhms (- (date-value) start-time ))
" Remaining: "
(seconds->dhms (abs (- (date-value) target-time))))
(sleep 1000))
(println
"Countdown completed at "
(date (date-value) 0
"%Y-%m-%d %H:%M:%S") "\n")
; do any notifications here
(notify (main-args 3))
(exit)
以下是一個簡單的函式,它透過查詢封閉標籤並將標籤之間的文字更改來更新每個資料夾中的一些文字日期戳。例如,您可能有一對包含檔案上次編輯日期的標籤,例如 <last-edited> 和 </last-edited>。
(define (replace-string-in-files start-str end-str repl-str folder)
(set 'path (real-path folder))
(set 'file-list (directory folder {^[^.]}))
(dolist (f file-list)
(println "processing file " f)
(set 'the-file (string path "/" f))
(set 'page (read-file the-file))
(replace
(append start-str "(.*?)" end-str) ; pattern
page ; text
(append start-str repl-str end-str) ; replacement
0) ; regex option number
(write-file the-file page)
))
可以像這樣呼叫它
(replace-string-in-files
{<last-edited>} {</last-edited>}
(date (date-value) 0 "%Y-%m-%d %H:%M:%S")
"/Users/me/Desktop/temp/")
replace-string-in-files 函式接受一個資料夾名稱。第一個任務是提取一個合適的檔案列表 - 我們使用 directory 以及正則表示式{^[^.]}來排除所有以點開頭的檔案。然後,對於每個檔案,內容都載入到一個符號中,replace 函式替換指定字串包圍的文字,最後,修改後的文字將儲存回磁碟。要呼叫該函式,請指定起始標籤和結束標籤,以及文字和資料夾名稱。在此示例中,我們只使用 date 和 date-value 提供的簡單 ISO 日期戳。
假設我們現在想讓它對資料夾中的資料夾中的資料夾起作用,即遍歷檔案層次結構,沿途更改每個檔案。為此,請重構 replace-string 函式,使其在傳遞的路徑名上起作用。然後編寫一個遞迴函式來查詢資料夾中的資料夾,並生成所有需要的路徑名,並將每個路徑名傳遞給 replace-string 函式。這種重構可能本身就是一件好事:至少它讓第一個函式更簡單。
(define (replace-string-in-file start-str end-str repl-str pn)
(println "processing file " pn)
(set 'page (read-file pn))
(replace
(append start-str "(.*?)" end-str) ; pattern
page ; text
(append start-str repl-str end-str) ; replacement
0) ; regex option number
(write-file pn page))
接下來,看一下這個遞迴遍歷樹的函式。它會檢視資料夾/目錄中的每個普通條目,並測試它是否為目錄(使用 **directory?**)。如果是,**replace-in-tree** 函式會呼叫自身並在新位置重新開始。如果不是,檔案的路徑名將傳遞給 **replace-string-in-file** 函式。
(define (replace-in-tree dir s e r)
(dolist (nde (directory dir {^[^.]}))
(if (directory? (append dir nde))
(replace-in-tree (append dir nde "/") s e r)
(replace-string-in-file (append dir nde) s e r))))
要更改整個樹的多個檔案,請像這樣呼叫函式
(replace-in-tree
{/Users/me/Desktop/temp/}
{<last-edited>}
{</last-edited>}
(date (date-value) 0 "%Y-%m-%d %H:%M:%S"))
在開始操作之前,務必先在臨時區域測試這些內容;程式碼中的一個小錯誤可能會對您的資料造成重大影響。新 LISPer 請注意!
newLISP 為將具有自身指令碼語言的應用程式程式中的功能粘合在一起提供了良好的環境。它速度快,體積小,不會妨礙指令碼解決方案的其他元件,並且非常適合在資訊透過工作流時進行處理。
以下是如何使用 newLISP 指令碼將非 newLISP 指令碼命令傳送到應用程式的示例。任務是在 Adobe Illustrator 中構建一個圓形,給出圓周上的三個點。
解決方案分為三個部分。首先,我們從應用程式中獲取選擇的座標。接下來,我們計算透過這些點的圓的半徑和圓心點。最後,我們可以繪製圓形。第一部分和最後部分使用 AppleScript,它使用 `osascript` 命令執行,因為 Adobe Illustrator 不理解任何其他指令碼語言(在 Windows 上,您使用的是 Visual Basic 而不是 AppleScript)。
使用 newLISP 進行計算和一般介面。這通常比使用原生 AppleScript 更好,因為 newLISP 提供了許多在預設 AppleScript 系統中找不到的強大的字串和數學函式。例如,如果我想使用三角學,我需要查詢並安裝額外的元件 - AppleScript 根本不提供任何三角函式。
newLISP 指令碼可以放在選單欄上的“指令碼”選單中;將其放到“庫”>“指令碼”>“應用程式”>“Adobe Illustrator”資料夾中,該資料夾接受文字檔案和 AppleScript)。然後,它可以在您在 Illustrator 中工作時選擇使用。要使用它,只需選擇至少包含三個點的路徑,然後執行指令碼。前三個點定義新圓形的位置。
#!/usr/bin/newlisp
; geometry routines from
; http://cgafaq.info/wiki/Circle_Through_Three_Points
; given three points, draw a circle through them
(set 'pointslist
(exec
(format [text]osascript -e 'tell application "Adobe Illustrator 10"
tell front document
set s to selection
repeat with p in s
set firstItem to p
set pathinfo to entire path of firstItem
set pointslist to ""
repeat with p1 in pathinfo
set a to anchor of p1
set pointslist to pointslist & " " & item 1 of a
set pointslist to pointslist & " " & item 2 of a
end repeat
end repeat
end tell
end tell
pointslist'
[/text])))
; cleanup
(set 'points
(filter float?
(map float (parse (first pointslist) { } 0))))
(set 'ax (points 0)
'ay (points 1)
'bx (points 2)
'by (points 3)
'cx (points 4)
'cy (points 5))
(set 'A (sub bx ax)
'B (sub by ay)
'C (sub cx ax)
'D (sub cy ay)
'E (add
(mul A (add ax bx))
(mul B (add ay by)))
'F (add
(mul C (add ax cx))
(mul D (add ay cy)))
'G (mul 2
(sub
(mul A (sub cy by))
(mul B (sub cx bx)))))
(if (= G 0) ; collinear, forget it
(exit))
(set 'centre-x (div (sub (mul D E) (mul B F)) G)
'centre-y (div (sub (mul A F) (mul C E)) G)
'r
(sqrt
(add
(pow (sub ax centre-x))
(pow (sub ay centre-y)))))
; we have coords of centre and the radius
; in centre-x, centre-y, and r
; Illustrator bounds are left-x, top-y, right-x, bottom-y
; ie centre-x - r, centre-y + r, centre-x + r, centre-y -r
(set 'bounds-string
(string "{" (sub centre-x r) ", "
(add centre-y r) ", "
(add centre-x r) ", "
(sub centre-y r) "}"))
(set 'draw-circle
(exec (format [text]osascript -e 'tell application "Adobe Illustrator 10"
tell front document
set e to make new ellipse at beginning with properties {bounds:%s}
end tell
end tell
'
[/text] bounds-string)))
(exit)
此指令碼幾乎沒有錯誤處理!在第一階段應該新增更多錯誤處理(因為選擇可能不適合後續處理)。
