2016-09-28 1 views
0

Как упражнение в изучении макросистемы Racket, я реализовал единую систему тестирования, основанную на C++ catch framework. Одной из особенностей этой структуры является то, что если я пишу чек так:Macro для записи шагов оценки и промежуточных значений в Racket?

CHECK(x == y); // (check x y) 

Когда проверка нарушается сообщение об ошибке будет распечатать значения х и у, даже если макрос используется полностью универсален , в отличие от других тестовых фреймворков, которые требуют использования макросов, таких как CHECK_EQUALS, CHECK_GREATER и т. д. Это возможно благодаря хакерству, включающему шаблоны экспрессии и перегрузку оператора.

Мне приходит в голову, что в Racket вы сможете сделать еще лучшую работу. В версии C++ макрос не может видеть внутри подвыражения, так что если вы пишете что-то вроде:

CHECK(f(x, g(y)) == z); // (check (= (f x (g y)) z)) 

Когда проверка нарушается вы только выяснить значения левой и правой стороны от знака равенства, а не значения x, y или g (y). В ракетке я ожидаю, что можно будет переписать в подвыражения и напечатать дерево, показывающее каждый шаг оценки.

Проблема Я понятия не имею, что лучший способ сделать это:

  • Я получил довольно знакомы с синтаксисом-синтаксического анализа, но это, кажется, за пределами его возможностей.
  • Я читал о настройке приложения #%, которое почти похоже на то, что я хочу, но если, например, f является макросом, я не хочу печатать каждую оценку выражений, находящихся в расширении, просто оценки выражения, которые были видны, когда пользователь вызывал макрос проверки. Также не уверен, могу ли я использовать его без определения языка.
  • Я мог бы использовать параметр синтаксиса для захвата смысла основных операторов, но это не поможет с вызовами функций, такими как g (y).
  • Я мог бы использовать синтаксис-> datum и вручную ходить по AST, вызывая eval на подвыражения сам. Это кажется сложным.
  • Библиотека трассировки почти похожа на то, что она делает то, что я хочу, но вы должны дать ей список функций заранее, и она, похоже, не дает вам никакого контроля над тем, куда идет вывод (я хочу только распечатать ничего, если проверка завершится неудачей, а не если она преуспеет, поэтому мне нужно сохранить промежуточные значения в сторону по мере продолжения выполнения).

Что было бы лучшим или по крайней мере идиоматическим способом реализации этого?

+0

бы кто голосовал, чтобы закрыть разум, объясняя, почему? вопрос довольно конкретный, я не уверен, как его можно интерпретировать как «широкий», –

ответ

2

Вот что вам поможет.

#lang racket 

(require (for-syntax syntax/parse racket/list)) 

(begin-for-syntax 
    (define (expression->subexpressions stx) 
    (define expansion (local-expand stx 'expression '())) 
    (syntax-parse expansion 
     #:datum-literals (#%app quote) 
     [x:id  (list #'x)] 
     [b:boolean (list #'b)] 
     [n:number (list #'n)] 
     ; insert other atoms here 
     [(quote literal) (list #'literal)] 
     [(#%app e ...) 
     (cons stx 
      (append-map expression->subexpressions (syntax->list #'(e ...))))] 
     ; other forms in fully expanded syntax goes here 
     [else 
     (raise-syntax-error 'expression->subexpressions 
          "implement this construct" 
          stx)]))) 

(define-syntax (echo-and-eval stx) 
    (syntax-parse stx 
    [(_ expr) 
    #'(begin 
     (display "] ") (displayln (syntax->datum #'expr)) 
     (displayln expr))])) 

(define-syntax (echo-and-eval-subexpressions stx) 
    (syntax-parse stx 
    [(_ expr) 
    (define subs (expression->subexpressions #'expr)) 
    (with-syntax ([(sub ...) subs]) 
     #'(begin 
      ; sub expressions 
      (echo-and-eval sub) 
      ... 
      ; original expression 
      (echo-and-eval expr)))])) 


(echo-and-eval-subexpressions (+ 1 2 (* 4 5))) 

Выход:

] (+ 1 2 (* 4 5)) 
23 
] + 
#<procedure:+> 
] 1 
1 
] 2 
2 
] (#%app * '4 '5) 
20 
] * 
#<procedure:*> 
] 4 
4 
] 5 
5 
] (+ 1 2 (* 4 5)) 
23 
2

Альтернатива печати всех чтобы добавить маркер для вещей, которые должны быть показаны. Вот грубый простой эскиз:

#lang racket 

(require racket/stxparam) 

(define-syntax-parameter ? 
    (λ(stx) (raise-syntax-error '? "can only be used in a `test' context"))) 

(define-syntax-rule (test expr) 
    (let ([log '()]) 
    (define (log! stuff) (set! log (cons stuff log))) 
    (syntax-parameterize ([? (syntax-rules() 
           [(_ E) (let ([r E]) (log! `(E => ,r)) r)])]) 
     (unless expr 
     (printf "Test failure: ~s\n" 'expr) 
     (for ([l (in-list (reverse log))]) 
      (for-each display 
        `(" " ,@(add-between (map ~s l) " ") "\n"))))))) 

(define x 11) 
(define y 22) 
(test (equal? (? (* (? x) 2)) (? y))) 
(test (equal? (? (* (? x) 3)) (? y))) 

, что приводит к этому выходу:

Test failure: (equal? (? (* (? x) 3)) (? y)) 
    x => 11 
    (* (? x) 3) => 33 
    y => 22