manywaypark's Blog
개발, 검색, 함수

둘 다 Bill Clementson님의 글이다.
처음 것은 약간 간략 버전(?) 정도 된다.

Setting Up SLIME for Win32 CL Implementations
The Common Lisp Cookbook - Setting up an IDE with Emacs on Windows or Mac OS X

중생들이 많이 사용하는 Windows/OSX에서 다양한 CL implementation들을 설정하고 사용하는 법에 대해서 설명해주는 주옥같은 글들이다.

Bill에게 경의를...

ps. 갑자기 위 글들 내용중에 필요한 것이 있어서 여기저기 찾아 헤맸다. 둘 다 내 del.icio.us에 고이 간직되어 있었다는.... Orz.

happy hackin'




Windows에서 SBCL을 실행했을 때 다음과 같은 에러가 나면서 실행이 안되는 경우가 있다.
VirtualAlloc: 0x1e7.
ensure_space: failed to validate 536870912 bytes at 0x09000000
(hint: Try "ulimit -a"; maybe you should increase memory limits.)

해결책은 간단하다. 다음과 같은 옵션을 줘서 실행한다.
sbcl --dynamic-space-size 128

그래도 안되면 128을 좀더 줄여보면 실행될 것이다.

테스트 환경:
Windows XP Professional
SBCL 1.0.13

참고: http://objectmix.com/lisp/254740-cannot-activate-sbcl.html

happy hackin'
synopsis - the bug
mysql 데이터를 oracle로 옮기는 아주 단순한 모듈을 clsql을 사용해서 만들었다.
처음에는 oracle 서버 설정이 잘못되어 oci를 통해 oracle에 접근하는 것이 불가능했다.
ORA-12705: Cannot access NLS data files or invalid environment specified.
서버 설정은 다음과 같이 하면 별 무리가 없을 듯했다.
# in .bash_profile of user oracle
export NLS_LANG=(KOREAN_KOREA|AMERICAN_AMERICA).UTF8
export ORA_NLS33=$ORACLE_HOME/ocommon/nls/admin/data      ## 9i  이하 버전
export ORA_NLS10=$ORACLE_HOME/nls/data                    ## 10g 이상 버전

클라이언트 쪽은 utf-8 환경의 우분투이므로,
export NLS_LANG=.UTF8

서버/클라이언트 설정을 맞추고 나자 다음과 같은 서버 에러를 내면서 레코드 삽입이 멈추는 심각한 상태가 되어 버렸다. 운좋게 들어간 몇몇 레코드들은 한글이 모조리 깨져있었다.
ORA-01756 quoted string not properly terminated
처음에는 인코딩 문제인것같아 환경변수 설정을 바꿔가면서 해봤다. 테스트용 모듈이 가끔 정상적인 한글을 insert할 때가 있었는데, 그 경우는 lisp 소스에 한글이 static하게 박혀있고 그 모듈을 (load "test.lisp")의 형태로 읽어들일 경우에만 한글이 제대로 오라클 DB로 전송되었다(지금도 이 부분이 정확히 어떻게 해서 성공적으로 그렇게 되었는지는 잘 이해가 안가는 상태다. lisp reader의 농간으로 추측되긴한다). 즉, 같은 소스를 emacs에서 읽어들여 function을 eval하면 깨진 한글이 전송되는... 신기하긴 하지만 돌아버릴 것같은 상황이 연출된 것이다. Orz...

이것이 눈물겨운 hacking의 시작이었다.

problem solving - the patch
clsql-oracle 및 uffi의 소스를 뒤졌다. oracle-sql.lisp에서 일단 실마리를 찾았다. sql-stmt-exec에서 oci interfact를 통해 sql statement를 실행하는데, uffi를 사용해 foreign string으로 변환 후에 oci interface를 통해 서버측으로 전달되게 되어있었다.
인코딩의 문제라는 강력한 심증이 있었으므로, lisp string 에서 변환된 foreign string을 lisp string으로 다시 변환(convert-from-foreign-string)해서 로그를 찍어보았다. 로그는 출력되지 않고 encoding error가 났다.
debugger invoked on a SB-INT:C-STRING-DECODING-ERROR in thread
#<THREAD "initial thread" {10025DEED1}>:
c-string decoding error (:external-format :UTF-8):
the octet sequence 3 cannot be decoded.

문제의 근원 (root cause)이 파악되는 순간이었다 (반이나 왔다고 긍정적으로 생각할 수도, 반이나 남았다고 비관적으로 생각할 수도 있는... go냐 stop이냐를 정말 갈등하게 하는 순간이다). '그냥 bug report하고 기다릴까? 테스트 케이스까지만 만들어 주고 report and wait??' 이런 유혹에 잠시 빠질려는 순간 어디선지 모르게 승부욕이 용솟음쳤다. 내나라의 아름다운 글을 바르게 표시하는 것은 내손으로 하자. 막판에는 사명감까지 생겼다. --;;

이제, 지금 이순간 필요한 것은 확실한 test case의 작성!!
(defparameter *tests* '("english" "한글"))
(dolist (test *tests*)
  (let* ((f    (uffi:convert-to-foreign-string test))
         (back (uffi:convert-from-foreign-string f)))
    (format t "~%test=~a, back=~a" test back)
    (assert (string= test back))))

역시 한글에서만 error가 났다. 용의자는 convert-to-foreign-string 아니면 convert-from-foreign-string 으로 압축되었다.

테스트 코드를 좀 끼워넣어보고, 함수를 재정의(lisp이 정말 강력한 부분)해보고 해서 찾은 진범은 바로,
uffi:convert-to-foreign-string

unicode에 대한 고려가 안되어있었다.

sbcl의 unicode관련 코드를 좀 살펴보고, uffi쪽 코드도 좀 살펴보고 해서 uffi:convert-to-foreign-string를 수정해서 상기 테스트 코드가 통과되게 만들었다.
이젠 DB쪽 삽입을 테스트할 차례. 자신감으로 충만해서 잠시 잊었던 나의 원래 목적인 (제일 처음 작성했던!!) mysql2oracle migration 모듈을 실행했다.
그런데 또 위에서 나왔던 ORA-01756에러가 났다. Orz...

이상하게 이번에는 전혀 짜증나지도 않았고, 해결할 수 없을지도 모른다는 생각도 들지 않았다. 이미 uffi와 clsql-oracle의 내부를 어느정도 파악하고 있었기 때문에 할 수 있다는 생각이 들었다.
잠시 살펴본 결과 sql-stmt-exec에서 oci interface로 전달하는 파라미터 중에 sql statememt의 길이를 전달하는 부분이 있었는데 여기에 원래 lisp string의 길이를 전달하고 있었다 (ascii string은 utf-8로 인코딩해도 길이가 같기 때문에 문제가 안되지만 한글등의 non-ascii는 당연히 짤려버려서 에러가 난다).
그래서 간단히 uffi:foreign-string-length를 불러서 해결하려했으나, 이 함수는 아무도 안쓰는지 완전히 잘못되어 있었다. macro의 body를 defun으로 정의해놓았고, 그나마 그것도 문법오류를 포함하고 있었고, unicode 고려도 안되어있었다. 악의 축같은 함수(매크로) 였지만, 이미 익숙해져 있는 모듈이었으므로 간단히 고칠 수 있었다.

이제 남은 것은 기존 테스트 케이스 돌려보기 및 upstream 반영을 위해 maintainer에게 patch 전달하기.



happy hackin'
sqlite3 backend로 clsql을 쓸때 다음과 같은 에러가 나는 경우가 있다.
"Error 21 / library routine called out of sequence"
기본적으로 라이브러리를 잘못 사용하고 있다는 말인데, 현재 상황에 비추어 검색한 결과 thread safe 문제인듯했다. 아래 참고 링크에서 SQLite 사이트의 FAQ를 인용해 놓은 것을 보면,
It is never safe to use the same sqlite3 structure pointer in two or more threads.
라고 되어 있으나 현재의 FAQ에서는 찾아 볼 수 없다 (FAQ.6에 비슷한 말이 나온다. sqlite 3.3.1이전 버전은 sqlite3 structure를 같은 thread에서만 열어야하며 thread safe를 위해서는 3.3.1 이후 버전을 SQLITE_THREADSAFE = 1로 설정하고 컴파일을 해야만한다. 아쉽게도 dapper에 깔리는 sqlite3는 버전이 3.2.8-1ubuntu1로 좀 낮다).
개발 중인 프로그램이 multithread인데 이전 버전들(sbcl 1:0.9.14.0-2 + cl-sql 3.6.3-1 on edgy)의 조합에서는 나타나지 않던 문제였으나 얼마전 내가 만든 backport들 (sbcl 1.0 + cl-sql 3.7.8 on dapper)의 조합에서는 나타났다.

DB 연결부분을 다음과 같이 기존 연결이 있을 때는 경고를 내고 그것을 재사용하도록 한 것이 문제일 수도 있겠지만, 문제의 여지를 남겨두기 싫어서 필요할 때마다 연결하고 끊는 매크로를 사용하고, thread간에는 mutex를 사용해서 동기화 해버렸다.
(clsql:connect (list *db-file*) :database-type +DB-TYPE+ :if-exists :warn-old)

(기존 연결 재사용은  thread safe 버전의 sqlite3를 사용하면 간단히 해결될 수도 있을 것같다.)

참고:
http://trac.edgewall.org/ticket/2196

happy hackin'

일단 잡담부터...
서버쪽에서는 하드웨어 문제나 안정성 때문에 웬만하면 그냥 Dapper LTS를 쓰려고했는데, sbcl이 0.9.8에서 더이상 버전이 안올라 가는 듯보였다. 내가 필요한 thread 및 timer기능이 온전하게 돌아가는 버전이 아니었다.

그래서 손쉬운 방법으로 서버 한대를 Edgy로 업그레이드를 해버렸다. 그런데, 이놈이 자주 죽는거 같더니 마지막으로는 부트파티션이 깨졌다. Orz.
아무래도 SATA RAID랑 궁합이 잘 안맞는다는 추측만 했다. 결론은 역시 LTS 버전이 안정적이라는 것.

이제 여기부터 backport이야기...
그래서 SBCL을 backport해보기로 했다.
날로 먹어버리겠다는 일념으로(좀 얍삽한가?) IDC에 있는 서버중에 동일 아키텍쳐(AMD64)에 Feisty가 깔려있는 곳에서 sbcl의 버전을 확인했더니 1.0이다. 충분하다! 그래서 그 머신에서 "apt-get source"로 소스를 받은 다음 통째로 Dapper LTS가 깔린 곳에 옮겨서 debian package를 만들었다.

"dpkg -i"".deb"파일을 설치하면 끝
이라고 생각했는데, 그게 아니였다. common-lisp-controller의 버전이 너무 낮다고 불평을 했다.
살짝 좌절했다. '이거 common-lisp-controller 하나만 더 가지고 온다고 될까? 그 놈을 설치하려면 또 다른 놈(들)에게 의존성이 걸려있는거 아닐까? 결국에는 시스템 전체 업그레이드?' 불안이 밀려왔다. 그래도 해보는데까지는 해보기로 했다. 어차피 내 잘못으로 빚어진 일이니 내 손으로 끝장을 봐야했다(데스크탑(edgy)에서 열심히 개발하고 서버(dapper)에 올리니 안돌아갔다. Orz).

비장한 각오로 common-lisp-controller를 위의 과정과 똑같이 backport했더니 그냥 해결됐다.
잠시 비장했던 마음가짐이 아까웠다.

잡설끝.

설치에 관한 간략한 설명:
  1. 먼저 dapper 박스에 sbcl이 안깔려 있다면 일단 깐다.
    ~$ sudo apt-get install sbcl
  2. 첨부파일을 다운로드 후에 적당한 곳에 압축을 풀고 패키지 두 개를 설치한다 (md5sum 체크도 한번 해주면 좋다).
    ~$ tar xvzf sbcl_1.0.0.0-1~dapper1_amd64.tgz
    ~$ sudo dpkg -i common-lisp-controller_6.1~dapper1_all.deb
    ~$ sudo dpkg -i sbcl_1.0.0.0-1~dapper1_amd64.deb
  3. 실행해보고 버전 확인한다.
    ~$ sbcl --version
    SBCL 1.0

대충 package cache 디렉토리 뒤져서 눈짐작으로 버전과 아키텍쳐사이에 ~dapper1을 넣는 것이 backport한 패키지의 명명법이란 것을 추측했는데 정확한 건지는 잘 모르겠다.
하고 나서 보니 backport라기보다는 build가 아닐까 하는 생각도 좀 든다. ^^;

happy hackin'

invalid-file

package 압축파일

invalid-file

md5sum

apache2에 mod_lisp2 설치하기

함수형 언어/Lisp 2007. 10. 11. 18:57 by manywaypark
apache2에 mod_lisp2 를 설치하는 방법을 간단히 설명한다.
  1. mod_lisp2.c 다운로드 (http://www.fractalconcept.com/asp/html/mod_lisp.html)

  2. 컴파일 및 설치 (gcc, apxs2 필요):
    sudo apxs2 -i -c mod_lisp2.c
    /usr/lib/apache2/modulesmod_lisp2.so가 설치 되었는지 확인.

  3. apache2 설정:
    /etc/apache2/mods-available/ 에 다음 내용의 파일을 적당한 이름(lisp.load)으로 저장.
    LoadModule lisp_module /usr/lib/apache2/modules/mod_lisp2.so
    모듈 활성화:
    sudo ln -s /etc/apache2/mods-available/lisp.load /etc/apache2/mods-enabled/lisp.load
     
  4. apache2 재시작:
    sudo /etc/init.d/apache2 restart
    /var/log/apache2/error.log 파일에 다음과 같은 로그가 찍히는지 확인.
    [Mon Oct 08 22:00:38 2007] [notice] caught SIGTERM, shutting down
    [Mon Oct 08 22:00:39 2007] [notice] Apache/2.0.55 (Ubuntu) mod_lisp2/1.3.1 configured -- resuming normal operations

happy hackin'


현재 진행중인 프로젝트에 cl-ftp를 사용해 자동화 하는 모듈을 작성 중이다. 그런데 cl-ftp의 기능들 중 내가 원하는 기능은 대부분 잘 작동했다. 제일 중요한 파일 다운로드만 빼고... T.T
약간의 공부와, 약간의 삽질 끝에 cl-ftp를 조금 손보았더니 제대로 동작했다.
내경우에는 다음과 같은 에러를 내면서 파일 다운로드가 실패했다.
error message:
The value -117 is not of type (UNSIGNED-BYTE 8).
  [Condition of type TYPE-ERROR]
msb가  1인 바이트가 음수로 읽혀지는 듯했다 (나중에 확인해본 결과 실제로 그랬다).
ftp.lisp의 소스를 좀 뒤졌더니 소켓을 생성할 때 cl-acl-compat 패키지의 acl-socket.lisp 내의 make-socket()을 이용해 소켓을 생성하고 있었다. 다음은 make-socket()의 일부다.
(defun make-socket (&key
......
(let ((element-type (ecase format
(:text 'base-char)
(:binary 'signed-byte)
(:bivalent 'unsigned-byte)))
......
ftp.lisp에서는 make-socket()을 부를 때 :binary 또는 :text를 사용할 수 있으며, :binary의 경우 (unsigned-byte 8) stream에, :text의 경우 character stream에 저장하게 되어있었다. 양쪽에서 :binary를 처리하는 방식이 다르니 에러가 날수 밖에 없어 보였다.
데이터 전송 소켓 생성 부분인 ftp.lispestablish-data-transfer()에서,
;;; ftp.lisp
;;; change underlined :binary to :bivalent.
(defmethod establish-data-transfer
......
(let ((data-socket
(make-socket :remote-host dtp-hostname
:remote-port dtp-port
:format (ecase type
((:binary :image) :binary)
(:ascii :text)))))
......
(let ((server-socket (setup-port conn
:format (ecase type
((:binary :image)
:binary)
(:ascii :text)))))
......
밑줄친 :binary를 전부 :bivalent로 바꾼후 테스트를 해보니 정상적으로 동작했다.

편의를 위해 패치를 첨부한다.
patch file:
--- ftp.lisp.ORIG       2007-08-04 16:44:18.000000000 +0900
+++ ftp.lisp 2007-08-04 18:09:20.000000000 +0900
@@ -307,7 +307,7 @@
(make-socket :remote-host dtp-hostname
:remote-port dtp-port
:format (ecase type
- ((:binary :image) :binary)
+ ((:binary :image) :bivalent)
(:ascii :text)))))
(when (and rest (integerp rest))
(send-raw-line conn (format nil "REST ~A" rest)))
@@ -317,7 +317,7 @@
(let ((server-socket (setup-port conn
:format (ecase type
((:binary :image)
- :binary)
+ :bivalent)
(:ascii :text)))))
(unwind-protect
(progn

테스트환경은 cl-ftp 1.3.3-1, sbcl, ubuntu edgy box이고 기본적인 시스템 정보는 다음과 같다.
$ sbcl --version
SBCL 0.9.14
$ uname -a
Linux hyper-server 2.6.17-12-386 #2 Mon Jul 16 19:35:45 UTC 2007 i686 GNU/Linux

패치를 데비안 패키지 관리자에게 보내긴 했는데 적용될지는 잘 모르겠다.

happy hackin'



cl-sql 사용하기

함수형 언어/Lisp 2007. 5. 29. 14:01 by manywaypark
cl-sql은 common lisp에서 여러 종류의 DB backend에 접근하는데 사용할 수 있는 package이다.
간단히 (asdf-install:install 'clsql)로 될줄 았았는데, 잘 안됐다. library 로딩에 실패하고 어쩌구...
혹시나 하는 마음에 ubuntu(debian) packaging system apt를 이용해보기로 했다.
$ apt-cache search clsql
cl-sql - SQL Interface for Common Lisp
cl-sql-aodbc - CLSQL database backend, AODBC
cl-sql-mysql - CLSQL database backend, MySQL
cl-sql-odbc - CLSQL database backend, ODBC
cl-sql-postgresql - CLSQL database backend, PostgreSQL
cl-sql-postgresql-socket - CLSQL database backend, PostgreSQL
cl-sql-sqlite - CLSQL database backend, SQLite
cl-sql-sqlite3 - CLSQL database backend, SQLite3
cl-sql-tests - Testing suite for CLSQL
cl-sql-uffi - Common UFFI functions for CLSQL database backends
cl-sql-oracle - CLSQL database backend, Oracle
오호라, 필요한건 다 있는 거같다.
$ sudo apt-get install cl-sql cl-sql-mysql
......
sbcl을 실행하고 테스트해봤다.
$ sbcl
......
* (require 'clsql)

; loading system definition from /home/USER/.sbcl/systems/uffi.asd into
; #<PACKAGE "ASDF1">
; registering #<SYSTEM UFFI {10028CA001}> as UFFI
NIL
* (require 'clsql-mysql)
......
Couldn't load foreign libraries "libmysqlclient", "libmysql". (searched CLSQL-SYS:*FOREIGN-LIBRARY-SEARCH-PATHS*)
......
엥? 에러 컨디션?
사실 (require 'clsql-mysql)은 직접 호출하지 않아도 connect류 등의 관련 함수를 부르면 자동으로 올라오는데 그것들도 다 실패했다. 시스템에는 libmysql*이 전부 설치되어있었다.
이 문제는 다음 두가지 방법 중 하나를 선택해서 해결할 수 있다.
해결책1: 직접 uffi를 이용해서 library를 로딩. (참고:http://common-lisp.net/pipermail/cl-debian/2006-May/001287.html)
CL-USER> (uffi:load-foreign-library "/usr/lib/libmysqlclient.so.15")
해결책2: symbolic link 생성.
$ sudo ln -s /usr/lib/libmysqlclient.so.15.0.0 /usr/lib/libmysqlclient.so
(library 버전 번호는 필요한 것으로 선택한다.)

테스트:
CL-USER> (require 'clsql)
......
;;; connect params: host, db, user, password.
CL-USER> (clsql:connect '("localhost" "mysql" "root" "") :database-type :mysql)
#<CLSQL-MYSQL:MYSQL-DATABASE localhost/mysql/root OPEN {1003707EF1}>
CL-USER> (clsql:status)
CLSQL STATUS: 2007-05-28 21:21:31,,0
-----------------------------------------
   DATABASE              TYPE   RECORDING 
-----------------------------------------
*  localhost/mysql/root  mysql  nil       
-----------------------------------------
; No value
CL-USER> (clsql:list-databases '("localhost" "mysql" "root" "") :database-type :mysql)
("information_schema" "mysql")
CL-USER> (clsql:query "select * from user")
......
CL-USER> (clsql:disconnect :database (clsql:find-database "localhost/mysql/root"))
T
CL-USER> (clsql:status)
CLSQL STATUS: 2007-05-28 21:48:23,,0
; No value

참고: apt를 이용해서 lisp 모듈을 설치하면, /usr/share/common-lisp/ 아래에 설치된다. 소스 참고시 유용하다.

2008-04-15 :
oracle을 backend로 쓰려면 instant client download 페이지에서 필요한 파일을 다운로드 받아 특정 디렉토리에 압축을 풀어놓고 그 디렉토리를 LD_LIBRARY_PATH 환경변수에 추가하면 된다. 물론 위의 해결책2에서 처럼 symbolic link를 걸어주는 작업(버전 번호를 떼주는...)이 필요하다.

happy hackin'

local-time 사용하기

함수형 언어/Lisp 2007. 5. 25. 16:13 by manywaypark
시간/날짜 처리 관련한 package로 local-time이 있다.
설치(ASDF Brief 참조):
CL-USER> (require 'asdf)
NIL
CL-USER> (require 'asdf-install)
......
CL-USER> (asdf-install:install 'local-time)
......

예제:
;;; 현재 시간 얻기
CL-USER> (local-time:now)
@2007-05-25T16:02:30.000

좀더 실용적인 예제 (기념일 계산에 쓰면 될듯):
;;; adds days
;;; eg, (add-days "2007-01-01" 123)
(defun add-days (from days)
(local-time:make-local-time :day (+ (local-time:local-time-day (local-time:parse-timestring from)) days)))

;;; 오늘 만난 커플의 100일 되는 날은?
CL-USER> (add-days "2007-05-25" 100)
@2007-09-02T00:00:00.000
;;; 9월2일이군요.

happy hackin'

2013-12-30: 그간 local-time package에 변경이 있었던듯... 여튼 오늘 해보니 상기 예제는 다음과 같이 하면 해결된다.
;;; 오늘 만난 커플의 100일 되는 날은?
* (local-time:timestamp+ (local-time:parse-timestring "2013-12-30") 100 :day)
@2014-04-09T09:00:00.000000+09:00
;;; 내년 4월9일이군요.



그냥 막연히 make-string-input-stream으로 input stream을 만들어서 cxml:parse-stream에 걸면 될 줄로 생각했는데, binary stream을 만들어야한다.
;;; simple binary stream for string parsing with cxml (just a wrapper for string).
(defclass bin-stream (fundamental-binary-input-stream)
((string-input-stream
:initarg :string-input-stream
:accessor string-input-stream)))

(defmethod stream-read-byte ((s bin-stream))
(char-code (or (read-char (string-input-stream s) nil)
(return-from stream-read-byte :eof))))

;;; makes binary stream of a string for parsing with cxml.
(defun make-bin-stream (string)
(assert (stringp string))
(make-instance 'bin-stream :string-input-stream (make-string-input-stream string)))
다음과 같이 테스트
CL-USER> (make-bin-stream "<foo><bar/></foo>")
#<BIN-STREAM {1002C69051}>
CL-USER> (dom:node-name (dom:document-element (cxml:parse-stream * (cxml-dom:make-dom-builder))))
"foo"
CL-USER>
binary stream wrapper를 만드는데 시간이 좀 걸렸다.

참고:
http://www.mikemac.com/mikemac/clim/gray-streams.html (맨 밑에 보면 binary stream을 만들려면, fundamental-binary-*-stream를 상속하고 stream-read-byte 또는 stream-write-byte method를 구현하라고 나온다).
http://www.cs.queensu.ca/software_docs/gnudev/gcl-ansi/gcl_1178.html (read-char의 optional parameter에 eof-error-p를 주의. nil로 설정!! nil로 설정하지 않으면 end-of-file condition error가 뜬다.)

happy hackin'


1 2 
분류 전체보기 (306)
잡담 (20)
함수형 언어 (65)
Scheme (5)
Lisp (14)
Erlang (31)
R (3)
Elixir (11)
emacs (16)
java (18)
tips & tricks (154)
사랑 (1)
가사 (0)
독서 (4)
mobile (6)
비함수형 언어 (2)

공지사항

최근에 올라온 글

최근에 달린 댓글

최근에 받은 트랙백

03-29 04:16