Пользовательский язык - цикл FOR в замкнутом интерпретаторе? - PullRequest
1 голос
/ 16 января 2011

У меня есть основной переводчик в clojure. Теперь мне нужно реализовать

for (initialisation; finish-test; loop-update) {
    statements
}  

Реализовать аналогичный цикл for для интерпретируемого языка. Узор будет:

(for variable-declarations end-test loop-update do statement)

Объявления переменных устанавливают начальные значения для переменных. End-test возвращает логическое значение, и цикл завершается, если end-test возвращает false. Оператор интерпретируется с последующим обновлением цикла для каждого проход петли. Примеры использования:

(выполнить ’(для ((i 0)) ( (run ’(for ((i 0) (j 0)) (

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

Переводчик

(declare interpret make-env) ;; needed as language terms call out to 'interpret'

(def do-trace false) ;; change to 'true' to show calls to 'interpret'

;; simple utilities

(def third ; return third item in a list
 (fn [a-list]
  (second (rest a-list))))

(def fourth ; return fourth item in a list
 (fn [a-list]
  (third (rest a-list))))

(def run ; make it easy to test the interpreter
 (fn [e]
  (println "Processing: " e)
  (println "=> " (interpret e (make-env)))))

;; for the environment

(def make-env
  (fn []
    '()))

(def add-var
  (fn [env var val]
    (cons (list var val) env)))

(def lookup-var
  (fn [env var]
    (cond (empty? env) 'error
          (= (first (first env)) var) (second (first env))
          :else (lookup-var (rest env) var))))

;; for terms in language

;; -- define numbers

(def is-number?
 (fn [expn]
  (number? expn)))

(def interpret-number
 (fn [expn env]
  expn))

;; -- define symbols

(def is-symbol?
  (fn [expn]
    (symbol? expn)))

(def interpret-symbol
  (fn [expn env]
    (lookup-var env expn)))

;; -- define boolean

(def is-boolean?
  (fn [expn]
    (or
      (= expn 'true)
      (= expn 'false))))

(def interpret-boolean
  (fn [expn env]
    expn))

;; -- define functions

(def is-function?
  (fn [expn]
    (and 
      (list? expn)
      (= 3 (count expn))
      (= 'lambda (first expn)))))

(def interpret-function ; keep function definitions as they are written
  (fn [expn env]
    expn))

;; -- define addition

(def is-plus?
 (fn [expn]
  (and 
   (list? expn)
   (= 3 (count expn))
   (= '+ (first expn)))))

(def interpret-plus
 (fn [expn env]
  (+ 
   (interpret (second expn) env)
   (interpret (third expn) env))))

;; -- define subtraction

(def is-minus?
 (fn [expn]
  (and 
   (list? expn)
   (= 3 (count expn))
   (= '- (first expn)))))

(def interpret-minus
 (fn [expn env]
  (- 
   (interpret (second expn) env)
   (interpret (third expn) env))))

;; -- define multiplication

(def is-times?
 (fn [expn]
  (and 
   (list? expn)
   (= 3 (count expn))
   (= '* (first expn)))))

(def interpret-times
 (fn [expn env]
  (* 
   (interpret (second expn) env)
   (interpret (third expn) env))))

;; -- define division

(def is-divides?
 (fn [expn]
  (and 
   (list? expn)
   (= 3 (count expn))
   (= '/ (first expn)))))

(def interpret-divides
 (fn [expn env]
  (/ 
   (interpret (second expn) env)
   (interpret (third expn) env))))

;; -- define equals test

(def is-equals?
  (fn [expn]
    (and
      (list? expn)
      (= 3 (count expn))
      (= '= (first expn)))))

(def interpret-equals
  (fn [expn env]
    (=
      (interpret (second expn) env)
      (interpret (third expn) env))))

;; -- define greater-than test

(def is-greater-than?
  (fn [expn]
    (and
      (list? expn)
      (= 3 (count expn))
      (= '> (first expn)))))

(def interpret-greater-than
  (fn [expn env]
    (>
      (interpret (second expn) env)
      (interpret (third expn) env))))

;; -- define not

(def is-not?
  (fn [expn]
    (and 
      (list? expn)
      (= 2 (count expn))
      (= 'not (first expn)))))

(def interpret-not
  (fn [expn env]
    (not
      (interpret (second expn) env))))

;; -- define or

(def is-or?
  (fn [expn]
    (and
      (list? expn)
      (= 3 (count expn))
      (= 'or (first expn)))))

(def interpret-or
  (fn [expn env]
    (or
      (interpret (second expn) env)
      (interpret (third expn) env))))

;; -- define and

(def is-and?
  (fn [expn]
    (and
      (list? expn)
      (= 3 (count expn))
      (= 'and (first expn)))))

(def interpret-and
  (fn [expn env]
    (and
      (interpret (second expn) env)
      (interpret (third expn) env))))

;; -- define print

(def is-print?
     (fn [expn]
  (and
    (list? expn)
    (= 2 (count expn))
    (= 'println (first expn)))))

(def interpret-print
     (fn [expn env]
  (println (interpret (second expn) env))))

;; -- define with

(def is-with?
  (fn [expn]
    (and
      (list? expn)
      (= 3 (count expn))
      (= 'with (first expn)))))

(def interpret-with
  (fn [expn env]
    (interpret (third expn)
               (add-var env 
                        (first (second expn))
                        (interpret (second (second expn)) env)))))

;; -- define if

(def is-if?
  (fn [expn]
    (and
      (list? expn)
      (= 4 (count expn))
      (= 'if (first expn)))))

(def interpret-if
  (fn [expn env]
    (cond (interpret (second expn) env) (interpret (third expn) env)
          :else                         (interpret (fourth expn) env))))

;; -- define function-application

(def is-function-application?
  (fn [expn env]
    (and
      (list? expn)
      (= 2 (count expn))
      (is-function? (interpret (first expn) env)))))

(def interpret-function-application
  (fn [expn env]
    (let [function (interpret (first expn) env)]
      (interpret (third function)
                 (add-var env
                          (first (second function))
                          (interpret (second expn) env))))))

;; the interpreter itself

(def interpret
  (fn [expn env]
    (cond do-trace (println "Interpret is processing: " expn))
    (cond 
      ; basic values
      (is-number? expn) (interpret-number expn env)
      (is-symbol? expn) (interpret-symbol expn env)
      (is-boolean? expn) (interpret-boolean expn env)
      (is-function? expn) (interpret-function expn env)
      ; built-in functions
      (is-plus? expn) (interpret-plus expn env)
      (is-minus? expn) (interpret-minus expn env)
      (is-times? expn) (interpret-times expn env)
      (is-divides? expn) (interpret-divides expn env)
      (is-equals? expn) (interpret-equals expn env)
      (is-greater-than? expn) (interpret-greater-than expn env)
      (is-not? expn) (interpret-not expn env)
      (is-or? expn) (interpret-or expn env)
      (is-and? expn) (interpret-and expn env)
      (is-print? expn) (interpret-print expn env)
      ; special syntax
      (is-with? expn) (interpret-with expn env)
      (is-if? expn) (interpret-if expn env)
      ; functions
      (is-function-application? expn env) (interpret-function-application expn env)
      :else 'error)))

;; tests of using environment

(println "Environment tests:")
(println (add-var (make-env) 'x 1))
(println (add-var (add-var (add-var (make-env) 'x 1) 'y 2) 'x 3))
(println (lookup-var '() 'x))
(println (lookup-var '((x 1)) 'x))
(println (lookup-var '((x 1) (y 2)) 'x))
(println (lookup-var '((x 1) (y 2)) 'y))
(println (lookup-var '((x 3) (y 2) (x 1)) 'x))

;; examples of using interpreter

(println "Interpreter examples:")
(run '1)
(run '2)
(run '(+ 1 2))
(run '(/ (* (+ 4 5) (- 2 4)) 2))
(run '(with (x 1) x))
(run '(with (x 1) (with (y 2) (+ x y))))
(run '(with (x (+ 2 4)) x))
(run 'false)
(run '(not false))
(run '(with (x true) (with (y false) (or x y))))
(run '(or (= 3 4) (> 4 3)))
(run '(with (x 1) (if (= x 1) 2 3)))
(run '(with (x 2) (if (= x 1) 2 3)))
(run '((lambda (n) (* 2 n)) 4))
(run '(with (double (lambda (n) (* 2 n)))
            (double 4)))
(run '(with (sum-to (lambda (n) (if (= n 0) 0 (+ n (sum-to (- n 1))))))
            (sum-to 100)))
(run '(with (x 1)
            (with (f (lambda (n) (+ n x)))
                  (with (x 2)
                        (println (f 3))))))

Добавлено это для? и интерпретировать, но не уверен, что это не лучший способ написать это.

;; -- define for loop

(def is-for? 
  (fn [expn] 
    (and (list? expn) 
      (= 6 (count expn)) 
      (= 'for (first expn))
      (= 'do (fifth expn) ) 
      (list? (fourth expn)))))

(def interpret-for
  (fn [expn env]
    ()))

;; - вставлено в def интерпретировать

      (is-for? expn) (interpret-for expn env)

Ответы [ 4 ]

1 голос
/ 16 января 2011

Взгляните на clj-iter: https://github.com/nathell/clj-iter

1 голос
/ 16 января 2011

Цикл в Clojure обычно записывается в виде loop / recur, например ::100100

(loop [variable binding]
  ;; do something
  (if end-test
      return-value
      (recur (step variable))))

Документация .

Редактировать: Поскольку это кажется неудовлетворительным хотя бы для одного читателя, я добавлю немного того, что вам нужно с этим сделать в соответствии с соглашениями, изложенными в вашем интерпретаторе:

  • определить is-for?, который проверяет вашу форму
  • определяет interpret-for, который принимает соответствующие части выражения и создает форму цикла вида, показанного выше
  • добавить соответствующий пункт в форму cond в interpret
0 голосов
/ 27 марта 2012

Я рад видеть, что вы используете свой собственный язык! Это открывает глаза, потому что вы вынуждены понимать тонкие детали в значении языка.

Не очевидно, как добавить конструкцию цикла for к языку в его текущей форме. До сих пор ваш язык выглядит очень функционально в своем стиле. Значение выражения описывается в терминах значения его подвыражений. Очень хорошо! Возможны также некоторые побочные эффекты, например println.

Проблема с текущей формой интерпретатора заключается в том, что выражение никак не может изменить свою среду. Другими словами, вы не можете назначать переменные. Для императивной циклической конструкции, такой как «for», это необходимо. Сейчас самое время решить, хотите ли вы, чтобы циклы были обязательными (например, «для») или функциональными (с использованием рекурсии или чего-то вроде цикла / рекурсии в Clojure).

Чтобы ответить на реальный вопрос, я опишу, как добавить императивные конструкции в язык и добавить «для». Сначала я опишу чисто функциональный подход.

Сначала вам нужно реализовать конструкцию set. Что оно делает? Учитывая '(set x 2) и среду '((x 1)), она должна создать новую среду, такую ​​как '((x 2)). Это хорошо согласуется с существующими функциями интерпретации, поскольку они получают выражение и среду и возвращают значение. Нам также нужно иметь возможность вернуть измененную среду, чтобы реализовать set!

Одним из решений является изменение контракта функций «interpret-foo» и разрешение им возвращать пару значений и среду. Вот как выглядит interpret-plus в новой схеме:

(defn interpret-plus [plus-expn env0]
  (let [[_ l-expn r-expn] plus-expn
        [l-val env1] (interpret l-expn env0)
        [r-val env2] (interpret r-expn env1)]
    [(+ val1 val2) env2]))

Обратите внимание на то, как эффекты изменения среды «пронизывают» через интерпретирующие вызовы. Я также позволил себе немного реорганизовать код: (def f (fn ...))(defn f ...), (f (first x) (second x))(let [[a b] x] (f a b)), (list a b)[a b]. В новой схеме «набор» легко реализовать:

(defn set-var [env var val]
  (cond (empty? env) 'error
        (= (first (first env)) var) (cons (list var val) (rest env))
        :else (cons (first env) (set-var (rest env) var val))))

(defn interpret-set [set-expn env0]
  (let [[_ var val-expn] set-expn
        [val env1] (interpret val-expn env0)]
    [val (set-var env1 var val)]))

Здесь я решил позволить выражению (set ...) вычислять значение, которое оно связывает с переменной, как = в C. Также подходят другие значения, например nil или 'ok. Теперь можно реализовать конструкцию «for»:

(defn apply-decl [decl env0]
  (let [[var expn] decl
        [val env1] (interpret expn env0)]
    (add-var env1 var val)))

(defn apply-decls [decls env0]
  (reduce (fn [env decl]
            (apply-decl devl env))
          env0
          decls))

(defn interpret-for [for-expn env0]
  (let [[_ decls end-test loop-update _ statement] for-expn
        env1 (apply-decls devls env0)]
    (loop [env env1]
      (let [[end-test-val env2] (interpret end-test env)]
        (if end-test-val
          (let [[_ env3] (interpret statement env2)
                [_ env4] (interpret loop-update env3)]
            (recur env4))
          [nil env2])))))

(defn interpret-seq [seq-expn env0]
  (reduce (fn [env expn]
            (let [[_ env1] (interpret expn env)]
              env1))
          env0
          (rest seq-expn)))

Вот оно! Это чисто функциональная реализация. Вы также можете «позаимствовать» побочные эффекты у Clojure и позволить средам внутренне мутировать. Код становится короче, но менее явным. Вот альтернативная реализация в исходной схеме интерпретатора (где функции интерпретации просто возвращают значение):

(defn make-env []
 '())

(defn add-var [env var val]
  (cons (list var (atom val))
        env))

(defn lookup-var [env var]
  (cond (empty? env) 'error
        (= (first (first env)) var) (deref (second (first env)))
        :else (recur (rest env) var)))

(defn set-var [env var val]
  (cond (empty? env) 'error
        (= (first (first env)) var) (reset! (second (first env)) val)
        :else (recur (rest env) var val)))

(defn interpret-set [expn env]
  (set-var env (second expn) (interpret (third expn) env)))

(defn apply-decl [decl env]
  (add-var env (first decl) (interpret (second decl) env)))

(defn apply-decls [decls env0]
  (reduce (fn [env decl]
            (apply-decl devl env))
          env0
          decls))

(defn interpret-for [expn env0]
  (let [env1 (apply-decls (second expn) env0)]
    (loop []
      (if (interpret (third expn) env1)
        (do (interpret (sixth expn) env1)
            (interpret (fourth expn) env1)
            (recur))
        nil))))

(defn interpret-seq [seq-expn env]
  (doseq [expn (rest seq-expn)]
    (interpret expn env)))

Как видите, Clojure предпочитает открыто говорить о мутации. Используемый здесь эталонный примитив - это атом, а соответствующими функциями являются atom, deref и reset!. Также деталь (loop [] (if x (do a b c) nil)) можно заменить на (while x a b c). Я признаю, что был немного небрежным; Я не проверял весь код выше. Пожалуйста, оставьте комментарий, если что-то не работает или если вы хотите, чтобы я кое-что прояснил.

0 голосов
/ 26 марта 2012

Обратите внимание, насколько это полезно сейчас, но я написал макрос для цикла в Clojure, который, по сути, делает то, что вы хотите. Это хороший пример использования макросов для добавления новых конструкций в язык.

Возможно, вам потребуется изменить его, чтобы он соответствовал вашему переводчику:

(defmacro for-loop [[sym init check change :as params] & steps]
  (cond
    (not (vector? params)) 
      (throw (Error. "Binding form must be a vector for for-loop"))
    (not= 4 (count params)) 
      (throw (Error. "Binding form must have exactly 4 arguments in for-loop"))
    :default
      `(loop [~sym ~init value# nil]
         (if ~check
           (let [new-value# (do ~@steps)]
             (recur ~change new-value#))
           value#))))

Использование следующим образом:

(for-loop [i 0 (< i 10) (inc i)] 
  (println i))
...