diff options
| -rw-r--r-- | guix/gexp.scm | 181 | ||||
| -rw-r--r-- | tests/gexp.scm | 58 |
2 files changed, 237 insertions, 2 deletions
diff --git a/guix/gexp.scm b/guix/gexp.scm index d9c4cb461ee..79b1c5a35f0 100644 --- a/guix/gexp.scm +++ b/guix/gexp.scm | |||
| @@ -905,11 +905,188 @@ environment." | |||
| 905 | #,(substitute-references #'exp substs))) | 905 | #,(substitute-references #'exp substs))) |
| 906 | (x #''x))) | 906 | (x #''x))) |
| 907 | 907 | ||
| 908 | (define (lookup-binding id env) | ||
| 909 | ;; Lookup ID in ENV. Return its corresponding generated identifier or | ||
| 910 | ;; #f. | ||
| 911 | (any (match-lambda | ||
| 912 | ((x renamed) | ||
| 913 | (and (bound-identifier=? x id) | ||
| 914 | renamed))) | ||
| 915 | env)) | ||
| 916 | |||
| 917 | (define (generate-bindings lst seed env) | ||
| 918 | ;; Like 'generate-temporaries', but use SEED and ENV as extra data to | ||
| 919 | ;; generate unique identifiers in a deterministic way. | ||
| 920 | (let ((len (length env))) | ||
| 921 | (map (lambda (binding) | ||
| 922 | (datum->syntax | ||
| 923 | binding | ||
| 924 | (string->symbol (format #f "~a-~a-~a" | ||
| 925 | (syntax->datum binding) | ||
| 926 | (number->string seed 16) | ||
| 927 | len)))) | ||
| 928 | lst))) | ||
| 929 | |||
| 930 | (define (syntax-uid s) | ||
| 931 | ;; Return a unique numeric identifier for S. | ||
| 932 | (hash s 2147483648)) | ||
| 933 | |||
| 934 | (define* (alpha-rename stx env stage | ||
| 935 | #:optional (quoting 0) | ||
| 936 | (uid (syntax-uid s))) | ||
| 937 | ;; Perform alpha-renaming of all the identifiers introduced in S, using | ||
| 938 | ;; ENV as the lexical environment. The goal is to preserve scope across | ||
| 939 | ;; stages, as illustrated by Kiselyov et al. in MetaScheme. Use UID as | ||
| 940 | ;; a stem when generating unique identifiers. | ||
| 941 | (syntax-case stx (gexp ungexp ungexp-native | ||
| 942 | ungexp-splicing ungexp-native-splicing | ||
| 943 | quote quasiquote unquote | ||
| 944 | lambda let let* letrec define begin) | ||
| 945 | ((proc arg ...) | ||
| 946 | (or (not (identifier? #'proc)) | ||
| 947 | (lookup-binding #'proc env)) | ||
| 948 | #`(#,(alpha-rename #'proc env stage quoting) | ||
| 949 | #,@(map (lambda (arg) | ||
| 950 | (alpha-rename arg env stage quoting)) | ||
| 951 | #'(arg ...)))) | ||
| 952 | ((quote exp) | ||
| 953 | #'(quote exp)) | ||
| 954 | ((quasiquote exp) | ||
| 955 | #`(quasiquote #,(alpha-rename #'exp env stage | ||
| 956 | (+ quoting 1)))) | ||
| 957 | ((unquote exp) | ||
| 958 | #`(unquote #,(alpha-rename #'exp env stage (- quoting 1)))) | ||
| 959 | ;; TODO: 'syntax', 'unsyntax', etc. | ||
| 960 | ((gexp exp rest ...) | ||
| 961 | #`(gexp #,(alpha-rename #'exp env (+ stage 1) quoting) | ||
| 962 | rest ...)) | ||
| 963 | ((ungexp exp rest ...) | ||
| 964 | #`(ungexp #,(alpha-rename #'exp env (- stage 1) quoting) | ||
| 965 | rest ...)) | ||
| 966 | ((ungexp-native exp rest ...) | ||
| 967 | #`(ungexp-native #,(alpha-rename #'exp env (- stage 1) quoting) | ||
| 968 | rest ...)) | ||
| 969 | ((ungexp-splicing exp) | ||
| 970 | #`(ungexp-splicing | ||
| 971 | #,(alpha-rename #'exp env (- stage 1) quoting))) | ||
| 972 | ((ungexp-native-splicing exp) | ||
| 973 | #`(ungexp-native-splicing | ||
| 974 | #,(alpha-rename #'exp env (- stage 1) quoting))) | ||
| 975 | ((lambda (bindings ...) body ...) | ||
| 976 | (with-syntax (((formals ...) | ||
| 977 | (generate-bindings #'(bindings ...) | ||
| 978 | uid env))) | ||
| 979 | #`(lambda (formals ...) | ||
| 980 | #,(alpha-rename #'(begin body ...) | ||
| 981 | #`((bindings formals) ... #,@env) | ||
| 982 | stage quoting)))) | ||
| 983 | ;; TODO: lambda*, case-lambda | ||
| 984 | ((let ((bindings values) ...) body ...) | ||
| 985 | (with-syntax (((renamed ...) | ||
| 986 | (generate-bindings #'(bindings ...) | ||
| 987 | (syntax-uid #'(values ...)) | ||
| 988 | env))) | ||
| 989 | #`(let #,(map (lambda (renamed value) | ||
| 990 | #`(#,renamed #,(alpha-rename value env | ||
| 991 | stage quoting))) | ||
| 992 | #'(renamed ...) | ||
| 993 | #'(values ...)) | ||
| 994 | #,(alpha-rename #'(begin body ...) | ||
| 995 | #`((bindings renamed) ... #,@env) | ||
| 996 | stage quoting)))) | ||
| 997 | ;; TODO: named let | ||
| 998 | ((let* ((binding value) rest ...) body ...) | ||
| 999 | (alpha-rename #'(let ((binding value)) | ||
| 1000 | (let* (rest ...) | ||
| 1001 | body ...)) | ||
| 1002 | env stage quoting)) | ||
| 1003 | ((let* () body ...) | ||
| 1004 | (alpha-rename #'(begin body ...) env stage quoting)) | ||
| 1005 | ((letrec ((bindings values) ...) body ...) | ||
| 1006 | (with-syntax (((renamed ...) | ||
| 1007 | (generate-bindings #'(bindings ...) | ||
| 1008 | (syntax-uid #'(values ...)) | ||
| 1009 | env))) | ||
| 1010 | (let ((env #`((bindings renamed) ... #,@env))) | ||
| 1011 | #`(letrec #,(map (lambda (renamed value) | ||
| 1012 | #`(#,renamed #,(alpha-rename value env | ||
| 1013 | stage quoting))) | ||
| 1014 | #'(renamed ...) | ||
| 1015 | #'(values ...)) | ||
| 1016 | #,(alpha-rename #'(begin body ...) env stage quoting))))) | ||
| 1017 | ;; TODO: letrec* | ||
| 1018 | ;; TODO: let-syntax, letrec-syntax | ||
| 1019 | ((begin exp) | ||
| 1020 | (alpha-rename #'exp env stage quoting)) | ||
| 1021 | ((define (proc formals ...) body ...) ;top-level | ||
| 1022 | (with-syntax (((renamed ...) | ||
| 1023 | (generate-bindings #'(formals ...) uid env))) | ||
| 1024 | #`(define (proc renamed ...) | ||
| 1025 | #,(alpha-rename #'(begin body ...) | ||
| 1026 | #`((formals renamed) ... #,@env) | ||
| 1027 | stage quoting)))) | ||
| 1028 | ((define id value) ;top-level | ||
| 1029 | #`(define id | ||
| 1030 | #,(alpha-rename #'value env stage quoting))) | ||
| 1031 | ((begin exp ...) | ||
| 1032 | (null? env) ;top-level | ||
| 1033 | #`(begin #,@(map (lambda (exp) | ||
| 1034 | (alpha-rename exp env stage quoting)) | ||
| 1035 | #'(exp ...)))) | ||
| 1036 | ((begin exp ...) ;inner 'begin' | ||
| 1037 | (with-syntax (((bindings ...) | ||
| 1038 | (filter-map (lambda (exp) | ||
| 1039 | (syntax-case exp (define) | ||
| 1040 | ((define (proc _ ...) value) | ||
| 1041 | #'proc) | ||
| 1042 | ((define binding value) | ||
| 1043 | #'binding) | ||
| 1044 | (_ | ||
| 1045 | #f))) | ||
| 1046 | #'(exp ...)))) | ||
| 1047 | (with-syntax (((renamed ...) | ||
| 1048 | (generate-bindings #'(bindings ...) | ||
| 1049 | uid env))) | ||
| 1050 | (let ((env #`((bindings renamed) ... #,@env))) | ||
| 1051 | #`(begin | ||
| 1052 | #,@(map (lambda (exp) | ||
| 1053 | (syntax-case exp (define) | ||
| 1054 | ((define (id formals ...) body ...) | ||
| 1055 | (with-syntax ((id (lookup-binding #'id env)) | ||
| 1056 | ((renamed ...) | ||
| 1057 | (generate-bindings #'(formals ...) | ||
| 1058 | uid env))) | ||
| 1059 | #`(define (id renamed ...) | ||
| 1060 | #,(alpha-rename #'(begin body ...) | ||
| 1061 | #`((formals renamed) ... | ||
| 1062 | #,@env) | ||
| 1063 | stage quoting)))) | ||
| 1064 | ((define id value) | ||
| 1065 | #`(define #,(lookup-binding #'id env) | ||
| 1066 | #,(alpha-rename #'value env | ||
| 1067 | stage quoting))) | ||
| 1068 | (_ | ||
| 1069 | (alpha-rename exp env stage quoting)))) | ||
| 1070 | #'(exp ...))))))) | ||
| 1071 | ((proc arg ...) | ||
| 1072 | #`(#,(alpha-rename #'proc env stage quoting) | ||
| 1073 | #,@(map (lambda (arg) | ||
| 1074 | (alpha-rename arg env stage quoting)) | ||
| 1075 | #'(arg ...)))) | ||
| 1076 | (id | ||
| 1077 | (identifier? #'id) | ||
| 1078 | (if (or (> quoting 0) (< stage 0)) | ||
| 1079 | #'id | ||
| 1080 | (or (lookup-binding #'id env) #'id))) | ||
| 1081 | (obj | ||
| 1082 | #'obj))) | ||
| 1083 | |||
| 908 | (syntax-case s (ungexp output) | 1084 | (syntax-case s (ungexp output) |
| 909 | ((_ exp) | 1085 | ((_ exp) |
| 910 | (let* ((escapes (delete-duplicates (collect-escapes #'exp))) | 1086 | (let* ((exp (alpha-rename #'exp #'() 0)) |
| 1087 | (escapes (delete-duplicates (collect-escapes exp))) | ||
| 911 | (formals (generate-temporaries escapes)) | 1088 | (formals (generate-temporaries escapes)) |
| 912 | (sexp (substitute-references #'exp (zip escapes formals))) | 1089 | (sexp (substitute-references exp (zip escapes formals))) |
| 913 | (refs (map escape->ref escapes))) | 1090 | (refs (map escape->ref escapes))) |
| 914 | #`(make-gexp (list #,@refs) | 1091 | #`(make-gexp (list #,@refs) |
| 915 | current-imported-modules | 1092 | current-imported-modules |
diff --git a/tests/gexp.scm b/tests/gexp.scm index cf88a9db807..6bdc2331706 100644 --- a/tests/gexp.scm +++ b/tests/gexp.scm | |||
| @@ -984,6 +984,64 @@ | |||
| 984 | '#~(foo #$bar #$baz:out #$(chbouib 42) #$@(list x y z) | 984 | '#~(foo #$bar #$baz:out #$(chbouib 42) #$@(list x y z) |
| 985 | #+foo #+foo:out #+(chbouib 42) #+@(list x y z))) | 985 | #+foo #+foo:out #+(chbouib 42) #+@(list x y z))) |
| 986 | 986 | ||
| 987 | (test-equal "hygiene, eval" | ||
| 988 | 42 | ||
| 989 | ;; Test: (1) that 'x' in one gexp does not shadow 'x' from the other 'gexp', | ||
| 990 | ;; and (2) that 'x' in 'ungexp' is not mistakenly renamed. | ||
| 991 | (let* ((inner (lambda (x) | ||
| 992 | #~(let ((x 40)) (+ x #$x)))) | ||
| 993 | (outer #~(let ((x 2)) | ||
| 994 | #$(inner #~x)))) | ||
| 995 | (primitive-eval (gexp->sexp* outer)))) | ||
| 996 | |||
| 997 | (test-assert "hygiene, define" | ||
| 998 | (match (gexp->sexp* #~(begin | ||
| 999 | ;; Top-level defines aren't renamed. | ||
| 1000 | (define top0 0) | ||
| 1001 | (define (top1 x) x) | ||
| 1002 | (define (top2 x y) | ||
| 1003 | ;; Internal define is renamed. | ||
| 1004 | (define inner1 (* x x)) | ||
| 1005 | (define (inner2 x) (+ x y)) | ||
| 1006 | (+ inner y)))) | ||
| 1007 | (('begin | ||
| 1008 | ('define 'top0 0) | ||
| 1009 | ('define ('top1 x0) x0) | ||
| 1010 | ('define ('top2 x1 y1) | ||
| 1011 | ('begin | ||
| 1012 | ('define inner1 ('* x1 x1)) | ||
| 1013 | ('define (inner2 x2) ('+ x2 y1)) | ||
| 1014 | ('+ inner y1)))) | ||
| 1015 | (and (not (eq? x0 'x)) | ||
| 1016 | (not (eq? x1 'x)) | ||
| 1017 | (not (eq? y1 'y)) | ||
| 1018 | (not (eq? inner1 'inner1)) | ||
| 1019 | (not (eq? inner2 'inner2)) | ||
| 1020 | (not (eq? x2 x1)))))) | ||
| 1021 | |||
| 1022 | (test-assert "hygiene, shadowed syntax" | ||
| 1023 | (match (gexp->sexp* #~(lambda (lambda x) | ||
| 1024 | (lambda (x) x))) | ||
| 1025 | (('lambda (arg x) | ||
| 1026 | (arg (x) x)) | ||
| 1027 | (and (not (eq? arg 'lambda)) | ||
| 1028 | (not (eq? x 'x)))))) | ||
| 1029 | |||
| 1030 | (test-assert "hygiene, quote" | ||
| 1031 | (match (gexp->sexp* #~(lambda (x y z) | ||
| 1032 | (list '(x y z) | ||
| 1033 | `(x ,x (,y ,z) z)))) | ||
| 1034 | (('lambda (x0 y0 z0) | ||
| 1035 | ('list ('quote ('x 'y 'z)) | ||
| 1036 | ('quasiquote | ||
| 1037 | ('x ('unquote x0) | ||
| 1038 | (('unquote y0) | ||
| 1039 | ('unquote z0)) | ||
| 1040 | 'z)))) | ||
| 1041 | (and (not (eq? x0 'x)) | ||
| 1042 | (not (eq? y0 'y)) | ||
| 1043 | (not (eq? z0 'z)))))) | ||
| 1044 | |||
| 987 | (test-end "gexp") | 1045 | (test-end "gexp") |
| 988 | 1046 | ||
| 989 | ;; Local Variables: | 1047 | ;; Local Variables: |
