sydney.lambda
2019-8-31 22:27:13

Can someone please give me some tips regarding a macro I’m trying to write? I’d like to take syntax of the form: (:= A (+ A M C)) and transform it into a form like this: (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor) (lens-view M processor) (lens-view Processor-C-lens processor)) processor) such that the identifier M stays the same, and the identifiers A and C are transformed into Processor-A-lens and Processor-C-lens. Additionally, any instances of those identifiers that appear in the expression half of the syntax (the (+ A M C) half) are wrapped with a lens-view and have “processor” appended to them. So, any instance of A in the expression half will become: (lens-view Processor-A-lens processor) and any instances of M will become (lens-view M processor) ;because M isn't transformed, it just remains as M can someone give me some advice on how I should go about doing this, and what syntax functions are best suited? I could probably kludge it together with a combination of syntax->list and lots of car-cdring to replace what needs to be replaced, but I get the feeling that’s not how I should be going about doing this. There’s a fixed amount of literal identifiers which are valid (A C and a few others).

Thank you :)


soegaard2
2019-8-31 22:30:38

Let’s take one thing at time. Let’s look at generating the result: (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor) (lens-view M processor) (lens-view Processor-C-lens processor)) processor)


soegaard2
2019-8-31 22:32:01

A nice approach is to fill in the missing pieces in: (with-syntax ( <binding-missing-pieces-here> ) (syntax/loc stx (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor) (lens-view M processor) (lens-view Processor-C-lens processor)) processor)))


soegaard2
2019-8-31 22:32:40

The missing pieces are the identifiers that doesn’t appear in the input.


soegaard2
2019-8-31 22:34:09
(with-syntax ( [Processor-A-lens  <mumble>]
              [Processor-C-lens  <mumble>])
  (syntax/loc stx
    (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor)
                              (lens-view M processor)
                              (lens-view Processor-C-lens processor))
          processor)))

soegaard2
2019-8-31 22:34:59

Here <mumble> needs to turn A into Processor-A-lens.


soegaard2
2019-8-31 22:35:13

And similarly for C.


soegaard2
2019-8-31 22:35:51

Since we need to do the same twice, let’s make a function.


soegaard2
2019-8-31 22:36:42
(with-syntax ( [Processor-A-lens  (build-processor-id-lens #'A)]
              [Processor-C-lens  (build-processor-id-lens #'C)])
  (syntax/loc stx
    (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor)
                              (lens-view M processor)
                              (lens-view Processor-C-lens processor))
          processor)))

soegaard2
2019-8-31 22:37:28

Now actually building the identifier can be done in several ways, but I like to use format-id.


soegaard2
2019-8-31 22:38:54

It is used as: (format-id lctx fmt v ...) (I am ignoring the optional keyword arguments.


soegaard2
2019-8-31 22:39:30

Here lctx provides the lexical context - which is the same as the input syntax — so we can use stx.


soegaard2
2019-8-31 22:40:34

The argument fmt is a format string, which means it is a string with where occurences of ~a can be filled in by the values v … .


soegaard2
2019-8-31 22:41:32

Our format string becomes: “Processor-~a-lens” and the the value v will be either #’A or #’C.


sydney.lambda
2019-8-31 22:41:52

Wow! I figured modifying pieces of syntax would be much more difficult that just using an equivalent of format that takes a context! that’s very awesome.


soegaard2
2019-8-31 22:42:32

We have: (define (build-processor-id-lens id) (format-id stx "Processor-~a-lens" id))


soegaard2
2019-8-31 22:42:57

Let’s assemble it, and try it out.


sydney.lambda
2019-8-31 22:46:55

do we need to pass a stx argument to build-processor-id-lens?


soegaard2
2019-8-31 22:47:21
#lang racket
(require (for-syntax syntax/parse racket/syntax))

(define-syntax (transform stx)
  (syntax-parse stx
    [(_transform A M C) ; for now
     (define (build-processor-id-lens id)
       (format-id stx "Processor-~a-lens" id))
     (with-syntax ([Processor-A-lens (build-processor-id-lens #'A)]
                   [Processor-C-lens (build-processor-id-lens #'C)])
       (syntax/loc stx
         (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor)
                                       (lens-view M processor)
                                       (lens-view Processor-C-lens processor))
                   processor)))]))

(transform A M C)

soegaard2
2019-8-31 22:47:45

We don’t if we define it locally.


sydney.lambda
2019-8-31 22:47:46

oh, defined as a local function, gotcha.


soegaard2
2019-8-31 22:48:43

To test it, I added a quote, like this: (syntax/loc stx '(lens-set ...))


soegaard2
2019-8-31 22:49:22

This produces the expansion as a datum, which sometimes is nice to see.


soegaard2
2019-8-31 22:49:45

Here I can’t test it without, because I don’t have the code where Processor-A-lens etc are defined.


soegaard2
2019-8-31 22:51:43

Now we need to make it more general.


soegaard2
2019-8-31 22:53:40

It looks like, there are two transformations: (+ A M C) becomes (+ ...) as above, and (:= ...) correponds to the lens-set.


sydney.lambda
2019-8-31 22:54:08

Exactly.


sydney.lambda
2019-8-31 22:54:40

I suppose the outer lens-set is (almost) the same as the inner transformations to lens-view, in a way.


soegaard2
2019-8-31 22:56:37

Why is M special in: (+ (lens-view Processor-A-lens processor) (lens-view M processor) (lens-view Processor-C-lens processor)) ?


sydney.lambda
2019-8-31 22:58:24

The mode functions return lenses: (define (MODE-IMM processor) (lens-compose (vector-ref-lens 1) Processor-MEM-lens))


soegaard2
2019-8-31 22:59:03

So M stands for memory, A for accumulator and C for carry?


sydney.lambda
2019-8-31 23:00:25

Yup, that’s right. Though, in the context of some opcodes (ones defined for the MODE-ACCUMULATOR mode) the M would refer to A also, if that makes sense.


sydney.lambda
2019-8-31 23:00:59

I wanted to make things LOAD and STORE agnostic, so these function-defining-macros don’t care whether you’re loading a register or storing to a location, the MODE functions just a return a lens, for storing or loading.


sydney.lambda
2019-8-31 23:01:05

Hope I explained that alright.


soegaard2
2019-8-31 23:02:03

Does that mean that the only names are M, A, C, X and Y?


sydney.lambda
2019-8-31 23:04:02

I wasn’t going to mention this because I didn’t want to get too ahead of myself, but the full syntax I had in mind was something like: (define-op ADC (:= A (+ A M C)) #:imm 69 #:zp 65 #:zpx 75 #:abs 6D #:absx 7D #:absy 79 #:indx 61 #:indy 71 #:z (zero? A) #:n (twos-complement-negative? A)) so, in total, A X Y M C Z I D B V N.


soegaard2
2019-8-31 23:05:00

I like it!


sydney.lambda
2019-8-31 23:05:18

Thank you! :)


sydney.lambda
2019-8-31 23:06:44

I dare say I can accomplish (most of) the rest of what’s there with just the tools you’ve shown me thus far. Would you agree?


soegaard2
2019-8-31 23:07:19

Yes.


sydney.lambda
2019-8-31 23:08:04

The only other part I had in mind, was iterating over all modes in a for loop, and using hash-set! to add the newly-generated functions to a table, with the opcode’s hex as the key.


sydney.lambda
2019-8-31 23:08:39

unless that doesn’t sound like the best way to do it?


soegaard2
2019-8-31 23:09:16

Sounds fine.


soegaard2
2019-8-31 23:11:40

Btw you can expand (+ A M C) into: (let ([A (lens-view Processor-A-lens processor)]) [M (lens-view M processor)] [C (lens-view Processor-C-lens processor)]) (+ A M C))


soegaard2
2019-8-31 23:12:23

The idea is that the body expression is the one from the input syntax, and each clause in the let corresponds to a register/thing.


soegaard2
2019-8-31 23:13:51

It might be easier than figuring out how to substitute things in the original expression.


sydney.lambda
2019-8-31 23:15:37

Ah, so regardless of what the expression is, say (another example I wrote out long-form): (define-op ROR (:= M (shift-right M)) ;etc they all just get transformed into a let of all the potentially-relevant bindings (whether utilised or not) with the body being the expression.


sydney.lambda
2019-8-31 23:16:27

I wasn’t thinking generically enough, I reckon. That seems much better, thanks.


sydney.lambda
2019-8-31 23:17:46

as one last thing, could you please let me know what I need to look into regarding using #:keys in a macro the way I have planned?


soegaard2
2019-8-31 23:21:13

I don’t think you need anything special. I believe ’#:foo can be used as a pattern.


sydney.lambda
2019-8-31 23:23:23

Thank you. I really appreciate you walking me through that macro. Also, I wouldn’t have even considered this approach if it wasn’t for you and @notjack confirming that it was viable, so thanks for that too :)


notjack
2019-8-31 23:31:50

@sydney.lambda drive by tip: when using syntax-parse, most uses of with-syntax can be replaced with the #:with pattern directive. So instead of this: (syntax-parse stx [(_transform A M C) ; for now (define (build-processor-id-lens id) (format-id stx "Processor-~a-lens" id)) (with-syntax ([Processor-A-lens (build-processor-id-lens #'A)] [Processor-C-lens (build-processor-id-lens #'C)]) (syntax/loc stx (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor) (lens-view M processor) (lens-view Processor-C-lens processor)) processor)))]) You can write this: (syntax-parse stx [(_transform A M C) ; for now #:do [(define (build-processor-id-lens id) (format-id stx "Processor-~a-lens" id))] #:with Processor-A-lens (build-processor-id-lens #'A) #:with Processor-C-lens (build-processor-id-lens #'C) (syntax/loc stx (lens-set Processor-A-lens (+ (lens-view Processor-A-lens processor) (lens-view M processor) (lens-view Processor-C-lens processor)) processor))]) You need to use #:do so you can insert definitions that are visible to #:with, since you can’t use #:with after you’ve used (define ...) in a pattern clause.


notjack
2019-8-31 23:33:05

You can read more about pattern directives like #:with and #:do here: https://docs.racket-lang.org/syntax/stxparse-specifying.html#%28part._.Pattern_.Directives%29


sydney.lambda
2019-8-31 23:34:15

@notjack thanks for the tip :)


sydney.lambda
2019-9-1 00:30:20

I think I’ve got something that’s passable: (define-syntax (:= stx) (syntax-parse stx [(_ dst src) (define (transform e) (datum-&gt;syntax stx (for/list ([elem (syntax-&gt;datum e)]) (if (member elem '(M A X Y C Z I D B V N)) (list 'lens-view elem 'processor) elem)))) (with-syntax ([new-src (transform #'src)]) (syntax/loc stx (λ (processor) (let ([A Processor-A-lens] [C Processor-C-lens]) (lens-set dst processor new-src)))))])) (:= A (+ A M C)) however, I’m getting unbound identifier errors for A, M, and C when called like so: (:= A (+ A M C)) is there a way I can have these identifiers “ignored” when used in said macro, if that makes sense? At the time of generating the functions, no processor, A, M, or C will be bound. Or is it something to do with how I’m replacing the elements using for/list?


sydney.lambda
2019-9-1 00:42:41

Eventually what will be produced will be a function like: (λ (processor) (define M (mode processor) (let ([A Processor-A-lens] [C Processor-C-lens] ;....) ;the given expression))) such that M A and C are only “given meaning” when the function is actually called later (during the runtime of the emulator) and a processor is provided. When the := macro is called, A M and C are just “symbols”.


notjack
2019-9-1 00:47:55

Hmmm. What about something like this? (define ((:= lens f) x) (lens-set lens x (f (lens-view x)))) (define ((lensify func . lenses) x) (apply func (for/list ([lens (in-list lenses)]) (lens-view lens x)))) (define A Processor-A-Lens) (define C Processor-C-Lens) (define M (mode processor)) (:= A (lensify + A M C))

That is, ditch the macros entirely and introduce some local aliases that make the lensy nature of the operation easier to understand. You’ll need to explicitly say which parts of the expression are lensy and which aren’t (using lensify), but IMO that’s a good thing. This approach is kind of haskell-y, it’s based on something called “idiom brackets” which is like Haskell’s version of do-notation but for applicative expressions.


sydney.lambda
2019-9-1 00:51:17

I just really like the (:= A (+ A M C)) syntax due to how clearly it expresses what’s going on with the opcodes.


sydney.lambda
2019-9-1 00:51:34

Purely aesthetic.


notjack
2019-9-1 00:51:45

(oh and just in case you haven’t seen it before, (define ((f x) y) body ...) is equivalent to (define (f x) (lambda (y) body ...)))


notjack
2019-9-1 00:52:49

I think it is clearer. In this case I’d probably not go that far though and just stick to the lensify approach because it’s very close, and getting all the way there in a way that’s robust is hard


notjack
2019-9-1 00:53:33

tradeoff of surface syntax clarity v.s. implementation complexity


sydney.lambda
2019-9-1 00:56:16

thanks for the advice @notjack. Being completely new to macros, this is the type of thing I have no past experience with, so my brain is stopping at “wow, that looks nice!” versus “that looks nice, but putting the time and effort into implementing it isn’t worth it” haha.


notjack
2019-9-1 00:56:44

example of how the macro approach can get thorny: what if someone does this? (:= A (+ A M (let ([x C]) x))) Should that work?


notjack
2019-9-1 00:57:37

also, does it? I actually don’t know, I think with your impl it might work fine. Something similar involving let-syntax and constructing the reference to C unhygienically would probably break it though.


sydney.lambda
2019-9-1 00:58:45

I guess what I had in mind was only arithmetic expressions, with no binding clauses or anything fancy. I tried out the widest variety of opcodes I could think of, and almost all of them can be expressed in this way.


notjack
2019-9-1 00:58:54

Oh wait, this is a simpler example of something that I know will break: (:= A (let ([V +]) (V A M C))


notjack
2019-9-1 00:59:41

Yeah I think you’ll be fine if the expressions allowed are limited to a fixed grammar that you specify ahead of time and check for. You could make a syntax class for it with define-syntax-class too, which would give you error checking at compile time.


sydney.lambda
2019-9-1 01:01:15

In that case, can you suggest how I can progress from here? I’m still at “it’s giving undefined errors for A, C etc.” I’m afraid, haha.


sydney.lambda
2019-9-1 01:02:11

the closest I could find that seemed relevant was syntax parameters, but I’m not sure they’re what I’m looking for.


notjack
2019-9-1 01:02:41

I think that would probably be a hygiene thing. I think syntax parameters are what you want, but with a nice little wrapper API over them. One sec, I’ll try something out in drracket.


sydney.lambda
2019-9-1 01:03:23

I really appreciate you taking the time to help me figure this out, thank you :)


notjack
2019-9-1 01:03:38

happy to help :)


notjack
2019-9-1 01:26:58

notjack
2019-9-1 01:27:25

I don’t have the ability to test it, but it does compile and seems to expand to what you want, I think


sydney.lambda
2019-9-1 01:32:50

hmm, I can’t seem to find where (define-location-alias) comes from?


notjack
2019-9-1 01:33:46

From this part:

(define-simple-macro
  (define-location-alias id:id (~optional (~seq #:default lens:id)))
  (~?
   (define-syntax-parameter id (λ (_) #'(lens-view lens the-processor)))
   (define-syntax-parameter id
     (λ (stx) (raise-undefined-location-alias-error #'id stx)))))

I tend to indent macros like that when their header is too long to fit on a single line.


sydney.lambda
2019-9-1 01:37:24

edit: I’m getting the error: syntax-parameterize: not bound as a syntax parameter at: the-processor but let me keep trying with it.


notjack
2019-9-1 01:53:13

There’s a (define-syntax-parameter the-processor #f) at the top that I suspect you removed, since it was next to the other (define &lt;thing&gt; #f) definitions that were just to get it to compile


sydney.lambda
2019-9-1 02:28:58

https://gist.github.com/dys-bigwig/dcda1640cf751468d2e5bacd2974ad52 I couldn’t work it out at the finish, I’m afraid. After adding back in the lines you mentioned, all I can seem to get is “application: not a procedure”. Thank you for all your help, I’ll try again tomorrow and see if I can figure it out :)


notjack
2019-9-1 02:47:12

worth a shot ¯_(ツ)_/¯


notjack
2019-9-1 02:47:25

sorry it didn’t quite work