To take care of shadowing you need to keep track of which identifiers are declared in each internal definition context. Here is one way to do it. /Jens Axel
#lang racket ;;; ;;; Declarations ;;; ;; This is a demonstration of how to write a declaration (or definition) ;; form, that can raise a syntax error, if an identifier is declared twice ;; either at the module level or twice in the same internal defintion context. (require (for-syntax syntax/parse racket/format syntax/id-set)) (begin-for-syntax ; We need to keep track of two types of identifiers. ; 1. Identifiers declared at the module level ; 2. Identifiers declared locally (i.e. in an internal definition context) ;;; 1. Module Level Identifiers ; The set of declared module-level identifiers is represented ; as an identifier set. (define module-level-ids (mutable-free-id-set)) ; Declaring a new module level identifier amounts to adding the new identifier to the set. (define (add-module-level-id! id) (free-id-set-add! module-level-ids id)) ; To see if an identifier is declared, just look it up. (define (declared-at-module-level? id) (free-id-set-member? module-level-ids id)) ;; 2. Local Identifiers ; Each internal definition context represents a scope. ; We need a hash table to keep track of the scopes. (define local-ids (make-hasheq)) ; all locally declared identifiers ; The hash table is a map from internal definition contexts, to sets of identifiers ; declared in that definition context. ; The contexts returned by syntax-local-context can't be used directly in a hasheq - ; you have to use the first element which is guaranteed to eq? unique. (define (context-identity ctx) (car ctx)) ; To check whether an identifier id is in a scope, given by a context ctx, ; we find the identifier set of the context, and check for membership. (define (id-in-this-context? ctx id) (define ids (hash-ref local-ids (context-identity ctx) #f)) (and ids (free-id-set-member? ids id))) ; To add a new identifier to a local scope (an internal definition context, ctx). ; is done by updating the hash table. (define (add-local-id! id ctx) (define old-ids (hash-ref local-ids (context-identity ctx) #f)) (unless old-ids ; this context is new, so we must create the set (set! old-ids (mutable-free-id-set)) (hash-set! local-ids (context-identity ctx) old-ids)) (free-id-set-add! old-ids id) (hash-set! local-ids (context-identity ctx) old-ids)) ; The hash table can be used to determine, if a given scope is new. (define (new-scope? ctx) (hash-has-key? local-ids (context-identity ctx))) ; For debuging. Simply insert (displayln (serialize-locals)) where you need it. (define (serialize-locals) ; for debug (for/list ([(ctx-id ids) (in-hash local-ids)]) (map syntax-e (free-id-set->list ids))))) ; SYNTAX (declare id) ; Declare an identifier id. (define-syntax (declare stx) (syntax-parse stx [(_ id) ; Get the context in which the declare form was used. (define ctx (syntax-local-context)) (cond ;; In a module context, the identifier is declared at the module-level. [(eq? ctx 'module) ; Raise error, if the identifier has been declared previously. (when (declared-at-module-level? #'id) (raise-syntax-error 'declare "identifier already declared in this scope" stx #'id)) ;; Otherwise it is a new variable, so we add it. (add-module-level-id! #'id) ;; The declaration does nothing, so the expansion is (begin) #'(begin)] [(list? ctx) ; internal definition context i.e. a local scope (define seen-scope-before? (not (new-scope? ctx))) (when (id-in-this-context? ctx #'id) (raise-syntax-error 'declare "identifier already declared in this scope" stx #'id)) ; we are now sure that the identifier haven't been declared before (add-local-id! #'id ctx) #'(void)] [else (raise-syntax-error 'declare (~a "declarations are only allowed at the module level " "and in internal definition contexts") stx)])])) ;;; Test ; Uncomment to see the duplicate errors. (declare a) (let () (declare x) ; (declare x) (let () (declare s) (declare t)) ;(declare y) (declare y)) (declare b) ;(declare a) 2017-07-18 23:47 GMT+02:00 Sam Waxman <samwax...@gmail.com>: > Thanks both for your replies, but it doesn't look like either of these > will do exactly what I want. While the code keeping track of id's that you > wrote comes close, it doesn't allow for shadowing. I still want racket to > do everything that it used to do, so the program > > > (define a 3) > (let () > (define a 4) > a) > > should still be valid. Shadowing is okay, I just can't have two duplicate > identifiers in the same scope. I suppose I could use your code and clear > the id-set every time I entered a new scope, but at that point, I'm > basically keeping track of the entire environment and there's not a huge > difference between just implementing my own lexical scope. I'd like to just > have racket tell me when there are duplicate identifiers, as it has to know > to throw it's own errors. > > On Tuesday, July 18, 2017 at 2:08:17 PM UTC-4, Jens Axel Søgaard wrote: > > #lang racket > > > > > > (require (for-syntax syntax/parse syntax/id-set)) > > > > > > (begin-for-syntax > > (define defined-ids (mutable-free-id-set)) > > (define (already-defined? x) (free-id-set-member? defined-ids x)) > > (define (define-it! x) (free-id-set-add! defined-ids x))) > > > > > > (define-syntax (def stx) > > (syntax-parse stx > > [(_def x:id e:expr) > > (when (already-defined? #'x) > > (raise-syntax-error 'def "identifier defined twice" #'stx #'x)) > > (define-it! #'x) > > (syntax/loc stx > > (define x e))])) > > > > > > (def one 1) > > (def two (+ one one)) > > (def three (+ one two)) > > (def three (+ two one)) > > > > > > > > > > 2017-07-18 19:23 GMT+02:00 Matthias Felleisen <matt...@ccs.neu.edu>: > > > > > > > > > > #lang racket > > > > > > > > (define my-favorite-numbers '(1 2 3)) > > > > > > > > (define (duplicate-exists? n) > > > > (member n my-favorite-numbers)) > > > > > > > > (duplicate-exists? 3) > > > > (duplicate-exists? 4) > > > > > > > > > > > > > > > > > > > > > > > > > On Jul 17, 2017, at 3:43 AM, Sam Waxman <samw...@gmail.com> wrote: > > > > > > > > > > Hello, > > > > > > > > > > In the regular #racket, the following program > > > > > > > > > > (define a 1) > > > > > (define a 2) > > > > > > > > > > will result in a syntax error letting you know that you have a > duplicate identifier. I would like to make my own define that throws a > custom error message in this case. I.e. > > > > > > > > > > (define-syntax (my-define stx) > > > > > (syntax-parse stx > > > > > [(_ id:id expr) (begin > > > > > (if (duplicate-exists? id) > > > > > (syntax-error "Oh no!") > > > > > (void)) > > > > > #'(define id expr)])) > > > > > > > > > > I know there's a lot of functions like bound-identifier=? and > check-duplicate-identifier, but they seem to all be geared towards the user > putting in all the identifiers in question as function arguments. In this > case, I need a function like > > > > > > > > > > (duplicate-exists? id) > > > > > > > > > > that just takes in the single identifier, and checks to see if, in the > scope the function is invoked, there's a duplicate identifier already > there. I just can't seem to figure out how to write that function. > > > > > > > > > > Thanks in advance! > > > > > > > > > > -- > > > > > You received this message because you are subscribed to the Google > Groups "Racket Users" group. > > > > > To unsubscribe from this group and stop receiving emails from it, send > an email to racket-users...@googlegroups.com. > > > > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > -- > > > > You received this message because you are subscribed to the Google > Groups "Racket Users" group. > > > > To unsubscribe from this group and stop receiving emails from it, send > an email to racket-users...@googlegroups.com. > > > > For more options, visit https://groups.google.com/d/optout. > > > > > > > > > > > > -- > > > > -- > > Jens Axel Søgaard > > -- > You received this message because you are subscribed to the Google Groups > "Racket Users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to racket-users+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. > -- -- Jens Axel Søgaard -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.