diff --git a/resources/expand/scm/r7rs.ebnf b/resources/expand/scm/r7rs.ebnf index d55a2e4..f9d1aa7 100644 --- a/resources/expand/scm/r7rs.ebnf +++ b/resources/expand/scm/r7rs.ebnf @@ -20,9 +20,14 @@ list = datum* improper-list = datum+ datum (* symbol *) -symbol = ordinary-symbol +symbol + = ordinary-symbol + | peculiar-symbol + = #"[\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{No}\p{Pd}\p{Pc}\p{Sc}\p{Po}\p{Sm}\p{Sk}\p{So}\p{Co}\u200c\u200d&&[^;|\".,#]][\p{Lu}\p{Ll}\p{Lt}\p{Lm}\p{Lo}\p{Nl}\p{No}\p{Pd}\p{Pc}\p{Sc}\p{Po}\p{Sm}\p{Sk}\p{So}\p{Co}\u200c\u200d\p{Nd}\p{Me}\p{Mc}.&&[^;|\",#]]*" + = '...' + (* number *) number = num-10 diff --git a/src/expand/core.clj b/src/expand/core.clj index b314b64..312993e 100644 --- a/src/expand/core.clj +++ b/src/expand/core.clj @@ -10,6 +10,12 @@ [expand.syntax-rules :as syntax-rules] [spec-dict.main :refer [dict]])) +(defn- conform! [spec x] + (let [r (s/conform spec x)] + (assert (not (s/invalid? r)) + (s/explain-str spec x)) + r)) + (s/def ::scope scm/scope?) (s/def ::binding @@ -109,7 +115,17 @@ :kind sequential?)) :body ::body))) -(s/def ::prim-let-syntax ::prim-let) +(s/def ::prim-let-syntax + (s/and ::unsyntax + (s/cat :keyword ::identifier + :bindings + (s/and ::unsyntax + (s/coll-of + (s/and ::unsyntax + (s/cat :rhs ::identifier + :lhs scm/datum+compilation?)) + :kind sequential?)) + :body ::body))) (s/def ::prim-if (s/and ::unsyntax @@ -120,10 +136,11 @@ ;; cf. `::transformer` (s/def ::prim-syntax-rules - (s/and seq? (s/cat :keyword ::identifier - :keywords (s/and ::unsyntax - (s/coll-of ::identifier)) - :rules (s/* ::datum)))) + (s/and ::unsyntax + (s/cat :keyword ::identifier + :keywords (s/and ::unsyntax + (s/coll-of ::identifier)) + :rules (s/* ::datum)))) @@ -148,17 +165,6 @@ (scm/datum->syntax (f scopeholder* env*) scopeholder))) -(comment - (let [env {} - store-atom (atom {})] - (binding-form #scm/stx [lambda #{}] - env - store-atom - {:x [:var]} - (fn [ident* env*] - (tap> (inherit ident* env*)) - (list (scm/datum->syntax 'x ident*)))))) - (defn- store-scopes @@ -181,7 +187,6 @@ ss (store-scopes store sym) xs (nearest scopes ss) r (compare (count xs) 1)] - (tap> (inherit ident env store)) (cond (neg? r) (throw (ex-info "undefined sym" {:sym sym})) (zero? r) (or (get env (or (get store [sym (first xs)]) @@ -242,7 +247,15 @@ (defmethod expand-primitive 'syntax-rules [stx _ env store-atom] - (let [_ (s/conform ::prim-syntax-rules stx)])) + (let [trans (conform! ::prim-syntax-rules stx)] + (dbg> trans) + (dbg> (scm/datum->syntax + [:macro + #(let [intro (scm/new-scopeholder stx) + r (syntax-rules/invoke trans %)] + (assert r "맞은 패턴은 찾을 수 없었다") + r)] + stx)))) (defn expand ([stx] @@ -252,9 +265,8 @@ (expand (scm/rescope stx scopeholder) env store-atom))) ([stx env store-atom] - (tap> (inherit stx)) (match (scm/syntax-e stx) - (_ :guard scm/constant?) stx + (_ :guard (some-fn scm/macro? scm/constant?)) stx (_ :guard symbol?) (do (lookup stx env @store-atom) ; throw if unbound stx) @@ -329,16 +341,16 @@ :else decl))))) (comment - (-> "t.expand.scm" + (-> "t.scm" slurp - (scm/read-syntax :library? true) + (scm/read-syntax :library? true :throw? true) expand-library library-syntax->datum library->datum) (let [scopeholder (scm/new-scopeholder) env {[:global 'lambda] [:primitive 'lambda]} store-atom (atom {['lambda #{}] [:global 'lambda]})] - (expand (-> (slurp "t.expand.scm") + (expand (-> (slurp "t.scm") (scm/read-syntax #_#_:library? true) (scm/rescope scopeholder)) env store-atom))) diff --git a/src/expand/scm.clj b/src/expand/scm.clj index a45b416..38d6064 100644 --- a/src/expand/scm.clj +++ b/src/expand/scm.clj @@ -27,6 +27,9 @@ (and (satisfies? ISyntax y) (.equals datum (syntax-e y)) (.equals ctx (syntax-context y))))) + clojure.lang.IMeta + (meta [this] + {:type (class this)}) expand.scm/ISyntax (syntax-e [this] datum) (syntax-context [this] ctx) @@ -42,6 +45,9 @@ ;; 진짜 나쁘다ㅋㅋㅋㅋ. 이러니까 `clojure.walk/walk`로 쓰다. clojure.lang.IRecord) +(defn ->syntax [datum ctxt] + (->Syntax datum ctxt)) + (defn unresolved? [stx] ;; 지금, 콘텍스트 필드가 비어 있은 신택스 옵제크트들이 ;; `::unresolved`로 한상 표한다. 왜냐하면 빈 집합(#{})은 이미 써서 @@ -192,7 +198,8 @@ instaparse)] (if (insta/failure? r) (if throw? - (throw (ex-info "reader error" {:err r})) + (throw (ex-info (pr-str (insta/get-failure r)) + {:err r})) r) (insta/transform {:boolean (fn [x] @@ -276,8 +283,12 @@ (or (number? x) (boolean? x) (string? x) - (macro? x) (list? x) (symbol? x) (improper-list? x) (and (syntax? x) (-> x syntax-e datum?)))) + +(defn datum+compilation? [x] + (if (syntax? x) + (-> x syntax-e datum+compilation?) + (or (macro? x) (datum? x)))) diff --git a/src/expand/syntax_rules.clj b/src/expand/syntax_rules.clj index 911717f..fb4bdec 100644 --- a/src/expand/syntax_rules.clj +++ b/src/expand/syntax_rules.clj @@ -1,12 +1,11 @@ (ns expand.syntax-rules - (:require - [clojure.core.match :refer [match]] - [clojure.spec.alpha :as s] - [clojure.test :as t] - [clojure.walk :as walk] - [expand.common :refer :all] - [expand.scm :as scm] - [spec-dict.main :refer [dict]])) + (:require [clojure.core.match :refer [match]] + [clojure.spec.alpha :as s] + [clojure.test :as t] + [clojure.walk :as walk] + [expand.common :refer :all] + [expand.scm :as scm] + [spec-dict.main :refer [dict]])) (s/def ::pattern scm/datum?) (s/def ::template scm/datum?) @@ -132,3 +131,10 @@ (unify (scm/datum->syntax-recursive pattern) (scm/datum->syntax-recursive datum))) scm/syntax->datum))) + +(defn invoke [syntax-rules datum] + (let [{:keys [rules keywords]} syntax-rules] + (some (fn [[pattern template]] + (some->> (unify pattern datum) + (transcribe template))) + rules))) diff --git a/t.scm b/t.scm index e9f1fb8..280d79f 100644 --- a/t.scm +++ b/t.scm @@ -1,4 +1,6 @@ (define-library (blah) (import (primitives+builtins)) (begin - (lambda (x) x))) + (syntax-rules () + ((_ test body ...) + (if test (begin body ...)))))) diff --git a/test/expand/scm_test.clj b/test/expand/scm_test.clj index 52e1544..e09e209 100644 --- a/test/expand/scm_test.clj +++ b/test/expand/scm_test.clj @@ -11,6 +11,7 @@ (t/is (= false (sut/read "#false")))) (t/testing "symbol" (t/is (= 'abc (sut/read "abc"))) + (t/is (= '... (sut/read "..."))) (t/is (= 'truth-nuke (sut/read "truth-nuke")))) (t/testing "list" (t/is (= (list) (sut/read "()"))) diff --git a/test/expand/syntax_rules_test.clj b/test/expand/syntax_rules_test.clj index 635b033..2c1d900 100644 --- a/test/expand/syntax_rules_test.clj +++ b/test/expand/syntax_rules_test.clj @@ -127,4 +127,15 @@ (t/is (= '(1 3 6) (sut/transcribe* '(x ...) '((x _ ...) ...) - '((1 2) (3 4 5) (6 7 8 9)))))) + '((1 2) (3 4 5) (6 7 8 9))))) + (t/testing "preservation of lexical context" + ;; 나는 테스트를 더 작성하고 싶지만 너무 귀찮을겠다…ㅠㅠ + (t/is (= '#{A} + (scm/syntax-context + (sut/transcribe + #scm/stx [(#scm/stx [x #{A}]) + #{A}] + (sut/unify #scm/stx [(#scm/stx [x #{A}]) + #{A}] + #scm/stx [(#scm/stx [123 #{A}]) + #{A}])))))))