Сопоставление списков с образцом рэкета

Я пытаюсь выполнить сопоставление шаблонов со списками, но по какой-то причине я получаю неожиданное совпадение, когда делаю следующее:

> (define code '(h1 ((id an-id-here)) Some text here))
> (define code-match-expr '(pre ([class brush: python]) ...))
> (match code
    [code-match-expr #t]
    [_ #f])
#t

Вопрос. Почему code соответствует code-match-expr?

Практический вариант использования

Я попробовал это в Racket REPL, потому что на самом деле я хочу решить другую практическую проблему: использовать функции упаковки pygments Pollen для выделения кода, который позже будет выводиться в виде HTML. Для этого я написал следующий код, где возникает проблема:

(define (read-post-from-file path)
  (Post-from-content (replace-code-xexprs (parse-markdown path))))

(define (replace-code-xexprs list-of-xexprs)
  ;; define known languages
  (define KNOWN-LANGUAGE-SYMBOLS
    (list 'python
          'racket
          'html
          'css
          'javascript
          'erlang
          'rust))
  ;; check if it matches for a single language's match expression
  ;; if it mathces any language, return that language's name as a symbol
  (define (get-matching-language an-xexpr)
    (define (matches-lang-match-expr? an-xexpr lang-symbol)
      (display "XEXPR:") (displayln an-xexpr)
      (match an-xexpr
        [`(pre ([class brush: ,lang-symbol]) (code () ,more ...)) lang-symbol]
        [`(pre ([class brush: ,lang-symbol]) ,more ...) lang-symbol]
        [_ #f]))

    (ormap (lambda (lang-symbol)
             ;; (display "trying to match ")
             ;; (display an-xexpr)
             ;; (display " against ")
             ;; (displayln lang-symbol)
             (matches-lang-match-expr? an-xexpr lang-symbol))
           KNOWN-LANGUAGE-SYMBOLS))

  ;; replace code in an xexpr with highlightable code
  ;; TODO: What happens if the code is in a lower level of the xexpr?
  (define (replace-code-in-single-xexpr an-xexpr)
    (let ([matching-language (get-matching-language an-xexpr)])
      (cond [matching-language (code-highlight an-xexpr matching-language)]
            [else an-xexpr])))

  ;; apply the check to all xexpr
  (map replace-code-in-single-xexpr list-of-xexprs))

(define (code-highlight language code)
  (highlight language code))

В этом примере я разбираю файл уценки, который имеет следующее содержимое:

# Code Demo

```python
def hello():
    print("Hello World!")
```

И я получаю следующие xexprs:

1.

(h1 ((id code-demo)) Code Demo)

2.

(pre ((class brush: python)) (code () def hello():
    print("Hello World!")))

Однако ни один из них почему-то не совпадает.


person Zelphir Kaltstahl    schedule 14.11.2017    source источник


Ответы (2)


match является синтаксисом и не оценивает шаблон. Поскольку code-match-expr является символом, он свяжет все выражение (результат вычисления code) с переменной code-match-expr и оценит остальные выражения по мере совпадения с шаблоном. Результат всегда будет #t.

Обратите внимание, что второй шаблон, символ _, является таким же шаблоном. Он также соответствует всему выражению, но _ отличается тем, что не связывается, как code-match-expr.

Важно, чтобы ваша определенная переменная code-match-expr никогда не использовалась, но поскольку match связывает переменную с тем же именем, ваша исходная привязка будет скрыта в результате match.

Код, который работает так, как вы предполагали, может выглядеть так:

(define (test code)
  (match code 
    [`(pre ([class brush: python]) ,more ...) #t]
    [_ #f]))

(test '(h1 ((id an-id-here)) Some text here))
; ==> #f

(test '(pre ((class brush: python))))
; ==> #t

(test '(pre ((class brush: python)) a b c))
; ==> #t

Как видите, шаблон ,more ... означает ноль или больше, а какие скобки игнорируются, поскольку в Racket [] совпадает с () и {}.

ИЗМЕНИТЬ

Вы все же немного отстали. В этом коде:

(define (matches-lang-match-expr? an-xexpr lang-symbol)
  (display "XEXPR:") (displayln an-xexpr)
  (match an-xexpr
    [`(pre ([class brush: ,lang-symbol]) (code () ,more ...)) lang-symbol]
    [`(pre ([class brush: ,lang-symbol]) ,more ...) lang-symbol]
    [_ #f]))

Когда шаблон macthed, поскольку lang-symbol не заключен в кавычки, он будет соответствовать всему атомарному и будет привязан к нему как к переменной в этом предложении. Он не будет иметь ничего общего с привязанной переменной с тем же именем, поскольку match не использует переменные, а создает их. Вы возвращаете переменную. Таким образом:

(matches-lang-match-expr? '(pre ([class brush: jiffy]) bla bla bla) 'ignored-argument)
; ==> jiffy

Вот то, что делает то, что вы хотите:

 (define (get-matching-language an-xexpr)
    (define (get-language an-xexpr)
      (match an-xexpr
        [`(pre ([class brush: ,lang-symbol]) (code () ,more ...)) lang-symbol]
        [`(pre ([class brush: ,lang-symbol]) ,more ...) lang-symbol]
        [_ #f]))
    (let* ((matched-lang-symbol (get-language an-xexpr))
           (in-known-languages (memq matched-lang-symbol KNOWN-LANGUAGE-SYMBOLS)))
      (and in-known-languages (car in-known-languages))))

Опять же.. match злоупотребляет квазицитированием для чего-то совершенно другого, чем создание структуры списка. Он использует их для сопоставления литералов и захвата символов без кавычек в качестве переменных.

person Sylwester    schedule 14.11.2017
comment
У меня есть вопросы по этому поводу: (1) Когда я ввожу `(pre ([class brush: python]) 1) в оболочку Racket, она выводит '(pre ((class brush: python)) 1). Это потому, что выражение оценивается? В противном случае может показаться, что обратная кавычка вводит только другой список, и я не понимаю, почему обратная кавычка создает что-то отличное от списка, такого как одинарная кавычка. (2) Как я могу хранить выражения `````, например, в hash? Racket сообщает мне, что more не определено, когда я пытаюсь это сделать. Я предполагаю, что использование more в матче приведет к тому, что он будет привязан и не будет ошибки, но у меня их много. - person Zelphir Kaltstahl; 15.11.2017
comment
@Zelphir Это безумный способ #lang racket визуализировать значения. Вместо того, чтобы отображать значение, оно стало отображать выражение, которое будет оценивать это значение. Таким образом, `(some thing) оценивается как (some thing), но ракетка отображает '(some thing), потому что, если вы оцениваете, оно должно стать тем же значением (some thing), которое, конечно, по-прежнему будет отображаться как '(some thing). Квазицитировка ` ничем не отличается от ', если только вы не используете кавычки (,). Попробуйте оценить `(a b ,(+ 1 2)), а затем оцените это снова. - person Sylwester; 16.11.2017
comment
Ваш код работает для меня в Racket REPL, но когда я пытаюсь поместить его в код, я почему-то все равно всегда получаю совпадение или всегда ложь в зависимости от того, как я это пытаюсь. Не могли бы вы взглянуть на это? Я бы поставил это как правку для практического использования в вопросе. Если вы считаете, что мне следует задать новый вопрос, я приму это как ответ. - person Zelphir Kaltstahl; 16.11.2017
comment
@Zelphir Просто добавьте это в конец своего вопроса в качестве редактирования, и я посмотрю на это. - person Sylwester; 16.11.2017
comment
У меня есть несколько языков программирования, которые мне нужно сопоставить, и я заполняю их ,lang-symbol вместо того, чтобы писать python, например, где lang-symbol — это элемент из (list python racket css). Я добавлю код к вопросу, может быть, я просто что-то упускаю из виду. - person Zelphir Kaltstahl; 16.11.2017
comment
Последний абзац о том, что match использует квазицитаты по-другому, прояснил для меня, спасибо, очень полезно. - person Zelphir Kaltstahl; 17.11.2017

Убедитесь, что вы четко понимаете, что именно вы сопоставляете. В x-выражениях Racket имена атрибутов являются символами, а значения — строками. Таким образом, выражение, которому вы соответствуете, будет выглядеть примерно так: (pre ([class "brush: js"])) ___) -- не (pre ([class brush: js]) ___).

Чтобы сопоставить эту строку и извлечь часть после "brush: ", вы можете использовать шаблон соответствия pregexp. Вот фрагмент, который Frog использует для извлечения язык для Pygments:

(for/list ([x xs])
  (match x
    [(or `(pre ([class ,brush]) (code () ,(? string? texts) ...))
         `(pre ([class ,brush]) ,(? string? texts) ...))
     (match brush
       [(pregexp "\\s*brush:\\s*(.+?)\\s*$" (list _ lang))
        `(div ([class ,(str "brush: " lang)])
              ,@(pygmentize (apply string-append texts) lang
                            #:python-executable python-executable
                            #:line-numbers? line-numbers?
                            #:css-class css-class))]
       [_ `(pre ,@texts)])]
    [x x])))

(Здесь pygmentize — это функция, определенная в другом исходном коде Frog; это оболочка для запуска Pygments в качестве отдельного процесса и передачи текста между ними. Но вы можете заменить другой способ использования Pygments или любого другого средства подсветки синтаксиса. вопрос о match. Я упоминаю его только для того, чтобы это не стало отвлечением и еще одним встроенным вопросом. :))

person Greg Hendershott    schedule 16.11.2017
comment
Этот ответ исправляет мои выражения с неправильным регистром соответствия и показывает, как продолжить обработку совпадающего языка, но я могу принять только один ответ. Тем не менее +1 :) - person Zelphir Kaltstahl; 18.11.2017