Open5

yuni: Cyclone 0.3xに対応する (未遂)

okuokuokuoku

壊れ方

普通に実行すると、ライブラリ内のグローバル変数が参照できない:

("/opt/yunibase/current/cyclone/bin/icyc" "-A" "/opt/yunibase/current/cyclone/share/cyclone" "-s" "/home/oku/repos/yuni/lib-runtime/selfboot/cyclone/selfboot-entry.scm" "core0.sps")
"/home/oku/repos/yuni/lib-runtime/selfboot/cyclone/selfboot-entry.scm"
(LOADLIB "/home/oku/repos/yuni/lib/yunitest/mini.sls")
-> EXPAND-LIBRARY: (yunitest mini)
-> AUTOPROMOTE: (yunitest mini)
-> REALIZE-HOOK: (yuni/realize-library-hook (yuni scheme) #t)
-> SKIP: (yuni scheme)
-> REGLIB! (yunitest mini)
(LOADPROG "core0.sps")
(import: ((yuni scheme) (yunitest mini)))
-> REALIZE-HOOK: (yuni/realize-library-hook$869 (yuni scheme) #t)
-> SKIP: (yuni scheme)
-> REALIZE-HOOK: (yuni/realize-library-hook$869 (yunitest mini) #t)
-> LOOKUP (yunitest mini)
-> (yunitest mini) => #((yunitest mini) #t #t #f #f ())
(eval: (check-equal #t (boolean=? #t #t)))
Error: Unbound variable: %%yunitest-mini-test-counter++
Call history, most recent first:
[1] scheme/base.sld:raise
[2] scheme/base.sld:error
[3] scheme/cyclone/util.sld:env:_lookup-variable-value
[4] scheme/cyclone/util.sld:env:lookup-variable-value
[5] scheme/cyclone/util.sld:env:extend-environment
[6] scheme/cyclone/util.sld:pack-lambda-arguments
[7] scheme/cyclone/util.sld:formals->list
[8] scheme/cyclone/util.sld:tagged-list?
[9] scheme/eval.sld:execute-application

%%yunitest-mini-test-counter++ はエクスポートされない、ライブラリ (yunitest mini) 内のシンボル 。マクロ check-equaleval できなかったのかな。

yuniがR5RS形式に展開したライブラリ

(begin
  (begin
    (yuni/realize-library-hook (yuni scheme) #t))
  (begin
    (define %%yunitest-mini-test-counter 0)
    (define %%yunitest-mini-success-counter 0)
    (define %%yunitest-mini-failed-forms (quote ()))
    (define (check-finish)
      (define (print-failed v)
        (let ((expected (vector-ref v 0))
              (actual (vector-ref v 1))
              (frm (vector-ref v 2)))
          (display "[") (write frm) (display "]")
          (display "  Expected: ") (write expected)
          (display "  Actual: ") (write actual)))
      (display "Test: ")
      (display %%yunitest-mini-success-counter)
      (display "/")
      (display %%yunitest-mini-test-counter)
      (display " passed.\n")
      (unless (null? %%yunitest-mini-failed-forms)
        (display "\nFailed: \n")
        (for-each (lambda (x)
                    (display "    ") (print-failed x) (display "\n") #t)
                  (reverse %%yunitest-mini-failed-forms)))
      (exit (if (null? %%yunitest-mini-failed-forms) 0 1)))
    (define (%%yunitest-mini-test-counter++)
      (set! %%yunitest-mini-test-counter
        (+ 1 %%yunitest-mini-test-counter)))
    (define (%%yunitest-mini-success-counter++)
      (set! %%yunitest-mini-success-counter
        (+ 1 %%yunitest-mini-success-counter)))
    (define (%%yunitest-mini-proc-fail! expected actual frm)
      (let ((v (vector expected actual frm)))
       (set! %%yunitest-mini-failed-forms
         (cons v %%yunitest-mini-failed-forms))))
    (define-syntax check-equal
      (syntax-rules ()
        ((_ obj form) (begin (%%yunitest-mini-test-counter++)
                             (let ((e form))
                              (cond ((equal? obj e)
                                     (%%yunitest-mini-success-counter++))
                                    (else
                                      (%%yunitest-mini-proc-fail!
                                        obj e (quote form))))))))))
  (yuni/register-library! (quote (yunitest mini)) #f))
okuokuokuoku

REPLでは問題なく実行できる

REPLでは問題なく実行できるので、ライブラリ自体の問題ではないようだ。プログラムの load 手法に問題があるのかな。

単純ケース

cyclone> (begin
  (begin

    (define (a) (display "a!\n"))

    (define-syntax do-a
      (syntax-rules ()
        ((_ "a") (a)))))
  (write "done.\n"))
"done.\n"
cyclone> (do-a "a")
a!

実際のライブラリケース

cyclone> (define-syntax yuni/realize-library-hook (syntax-rules () ((_ . x) 'ok)))
ok
cyclone> (define (yuni/register-library! . x) 'ok)
ok
cyclone> (begin
  (begin
    (yuni/realize-library-hook (yuni scheme) #t))
  (begin
    (define %%yunitest-mini-test-counter 0)
    (define %%yunitest-mini-success-counter 0)
    (define %%yunitest-mini-failed-forms (quote ()))
    (define (check-finish)
      (define (print-failed v)
        (let ((expected (vector-ref v 0))
              (actual (vector-ref v 1))
              (frm (vector-ref v 2)))
          (display "[") (write frm) (display "]")
          (display "  Expected: ") (write expected)
          (display "  Actual: ") (write actual)))
      (display "Test: ")
      (display %%yunitest-mini-success-counter)
      (display "/")
      (display %%yunitest-mini-test-counter)
      (display " passed.\n")
      (unless (null? %%yunitest-mini-failed-forms)
        (display "\nFailed: \n")
        (for-each (lambda (x)
                    (display "    ") (print-failed x) (display "\n") #t)
                  (reverse %%yunitest-mini-failed-forms)))
      (exit (if (null? %%yunitest-mini-failed-forms) 0 1)))
    (define (%%yunitest-mini-test-counter++)
      (set! %%yunitest-mini-test-counter
        (+ 1 %%yunitest-mini-test-counter)))
    (define (%%yunitest-mini-success-counter++)
      (set! %%yunitest-mini-success-counter
        (+ 1 %%yunitest-mini-success-counter)))
    (define (%%yunitest-mini-proc-fail! expected actual frm)
      (let ((v (vector expected actual frm)))
       (set! %%yunitest-mini-failed-forms
         (cons v %%yunitest-mini-failed-forms))))
    (define-syntax check-equal
      (syntax-rules ()
        ((_ obj form) (begin (%%yunitest-mini-test-counter++)
                             (let ((e form))
                              (cond ((equal? obj e)
                                     (%%yunitest-mini-success-counter++))
                                    (else
                                      (%%yunitest-mini-proc-fail!
                                        obj e (quote form))))))))))
  (yuni/register-library! (quote (yunitest mini)) #f))
ok
cyclone> (check-equal #t #t)
ok
cyclone> (check-finish)
Test: 1/1 passed.
okuokuokuoku

意図せずrenameされている

どうも環境に正しく反映されていないようだ。REPLとプログラムの実行時の両方で expand して比較してみる:

cyclone> (expand '(check-equal #t #t))
((lambda () (%%yunitest-mini-test-counter++) ((lambda (e$94$96) (if (equal? #t e$94$96) ((lambda () (%%yunitest-mini-success-counter++))) ((lambda () (%%yunitest-mini-proc-fail! #t e$94$96 (quote #t)))))) #t)))

REPLでは、 %%yunitest-mini-test-counter++ と正しく展開されるが、プログラムから呼ぶと、

((lambda () (%%yunitest-mini-test-counter++$929) ((lambda (e$930$934) (if (equal? #t e$930$934) ((lambda () (%%yunitest-mini-success-counter++$931))) ((lambda () (%%yunitest-mini-proc-fail!$933 #t e$930$934 (quote (boolean=? #t #t))))))) (boolean=? #t #t))))

のように、 %%yunitest-mini-test-counter++$929 とリネームされてしまう。

よって、プログラムを eval する際のコンテキストが正しくないように見える。

okuokuokuoku

そもそも (lambda () ...) でwrapされている

↑ の出力をよく見ると、 check-equal の展開結果が (lambda () ...) で wrapされている。これだとスコープを作ってしまうので define で定義した変数が外部からアクセスできなくなってしまう。

というわけで、Cyclone側のexpanderを使わずに手動でライブラリを展開するしか無いようだ。。一応yuniは define-macro だけで実装できる専用のランタイム(Generic Scheme)を持っているが、Cycloneの仕様だと define-macro すら使えないのでかなり厳しい戦いになりそうだ。。

ただ、非常に単純な syntax-rules をREPLで試す限りはwrapされないので、何か条件があるような気がする。 begin が最適化されないケースが不味いのかな。。

cyclone> (define-syntax define10 (syntax-rules () ((_ nam) (define nam 10))))
ok
cyclone> (define10 a)
ok
cyclone> a
10
cyclone> (expand '(define10 a))
(define a 10)

どうせ後で必要になるし、 define-macro なしのSchemeでも使える実装を用意するか。。