sydney.lambda
2019-8-7 09:02:21

Having some difficulty expressing what I’d like to with match, was hoping someone can help: I have a header (as a list of bytes) defined like so: (define iNES-identifier (bytes->list (bytes #x4e #x45 #x53 #x1a))) and a ROM file loaded like so: (define ROM (bytes->list (file->bytes rom-path #:mode 'binary))) and I can match just fine like so: (match ROM [`(#x4e #x45 #x53 #x1a . ,xs) (car xs)]) but I’d like to be able to say iNES-header rather than the literal byte string. I messed around with regular lists using unquote-splicing and such, and I couldn’t figure a way to do it even with the bytes requirement removed. Seems you can’t say “I want you to match this predefined pattern inserted/spliced here that isn’t a literal” or at least, I can’t figure out how.

Any ideas? Thanks :slightly_smiling_face:


sergej
2019-8-7 09:14:01

I suspect (warning: beginner here too) that you need to define iNES-identifier “on the syntax level” by replacing define with define-for-syntax. Then you’ll be able to use it in the match


sydney.lambda
2019-8-7 09:25:57

Thanks for the suggestion, but changing the define to define-for-syntax doesn’t seem to do the trick.


soegaard2
2019-8-7 09:28:49

@sydney.lambda You can define a match-expander called ines-header that expands (ines-header a b c d) into (list a b c d). In that way you can write (match ROM [(ines-header a b c d) …]).


soegaard2
2019-8-7 09:29:37

Use define-match-expander.


sydney.lambda
2019-8-7 09:32:45

that won’t ensure it’s the correct pattern of bytes though, will it?


sydney.lambda
2019-8-7 09:33:35

Wouldn’t that just match anything and call said matched variables a, b, c, d?


sydney.lambda
2019-8-7 09:34:12

I suppose it would be possible to have it expand into the literal bytes pattern?


soegaard2
2019-8-7 09:35:10

Sorry didn’t look close enough on your example. I thought the bytes were an example - but they are of course the “magic” bytes for a rom file. Then you want (ines-rom pattern) to expand into (list* #x4e #x45 #x53 #x1a pattern)


sydney.lambda
2019-8-7 09:36:30

Time to define my first match expander, it seems! Thanks for the help, very much appreciated :slightly_smiling_face:


sydney.lambda
2019-8-7 09:56:32

Please bear with me as macros aren’t my forte at all, but I can’t seem to get it right. I came up with: (define-match-expander iNES-rom (lambda stx (syntax-case stx () [((_ pat ...)) #`(list* #x4e #x45 #x53 #x1a pat ...)]))) which yields: "?: bad syntax in: (#<match-expander> (iNES-rom unquote xs)) " though I tried without the ellipsis too as suggested and got the same error. Just to be clear, I’m calling it like so: (match ROM [(iNES-rom . ,xs) #t]) mind pointing me in the right direction? This is what I get for using this language for so long without ever defining a macro! Thanks :slightly_smiling_face:


soegaard2
2019-8-7 09:57:14

(list 1 2 3 x …) is the same as (list* 1 2 3 x)


soegaard2
2019-8-7 09:57:22

so delete the last …


soegaard2
2019-8-7 09:57:32

or change list* into list


soegaard2
2019-8-7 09:58:12

I think it should be used as (iNES-rom xs)


sydney.lambda
2019-8-7 09:59:37

I’m afraid i’m still getting the same error with (define-match-expander iNES-rom (λ stx (syntax-case stx () [((_ pat ...)) #`(list #x4e #x45 #x53 #x1a pat ...)]))) and (iNES-rom xs) though it fails with the . in there too :confused:


soegaard2
2019-8-7 10:01:31

Okay. I’ll try it.


soegaard2
2019-8-7 10:02:00

You need to begin with (lambda (stx)


sydney.lambda
2019-8-7 10:03:48

Sorry to be a pain… I have changed it to: (define-match-expander iNES-rom (λ (stx) (syntax-case stx () [((_ pat ...)) #`(list #x4e #x45 #x53 #x1a pat ...)]))) and instead get this error: iNES-rom: bad syntax in: (iNES-rom (unquote xs))


sydney.lambda
2019-8-7 10:04:21

Same code as before, with the entire function being: (define (main rom-path) (define ROM (bytes-&gt;list (file-&gt;bytes rom-path #:mode 'binary))) (match ROM [(iNES-rom ,xs) #t]))


soegaard2
2019-8-7 10:04:25
(define-match-expander iNES-rom
  (λ (stx)
   (syntax-case stx ()
     [(_ pat) #'(list* #x4e #x45 #x53 #x1a pat)])))

(match (bytes-&gt;list (bytes #x4e #x45 #x53 #x1a 1 2 3))
  [(iNES-rom xs) xs])

sydney.lambda
2019-8-7 10:08:11

Thank you! Appreciate you helping me work through it. I guess I’ll stay away from unquoting as much as possible with these, I’m not experienced enough with macros to follow what’s being unquoted where and such.


soegaard2
2019-8-7 10:09:46

I think the simplest is to not to use #` and #, . Instead bind all pattern variables either with syntax-case or with with-syntax. Then #’ inserts the matched syntax automatically.


sydney.lambda
2019-8-7 10:18:22

I think I understand. I’ll just have to practice more in order to gain an intuitive understanding. Keep putting off learning about macros but this is an example of them being a necessity to express the idea best. Thanks again @soegaard2.


sydney.lambda
2019-8-7 10:22:40

Sure beats the wall of (if valid-iNES-header? (if filesize-matches-header? ... I had before :wink: I feel like pattern matching is a suprisingly good way of dissecting roms like this; hopefully should be as useful for breaking apart the opcodes and operands too. There’s a bitsyntax module for Racket but - and this is probably just me - it actually seemed more difficult than just manipulating the bytes as a list.


soegaard2
2019-8-7 10:27:04

Match-expanders are also handy when you want to accept multiple types of input.


soegaard2
2019-8-7 10:27:22

Let’s say you have a function that accepts a color as the first argument.


soegaard2
2019-8-7 10:28:00

The problem here is that the user might want to write “black”, (color 100 200 300), or has already got a color% object.


soegaard2
2019-8-7 10:28:21

So one can write a match-expander such that


soegaard2
2019-8-7 10:28:39

(match a-color [(color r g b) …]) will accept all three types of input.


soegaard2
2019-8-7 10:29:28

No explicit conversion at all. And if at some point we want to accept ’black too - then we just change the definition of the match-expander. No other code needs a change.


soegaard2
2019-8-7 10:30:23

The down side is that you have to remember always to use pattern matching when accepting a color as argument.


sydney.lambda
2019-8-7 10:33:02

Like a predicate dispatch, almost. Being that it allows you to change the definition in only one place, that’s really powerful. Sometimes I miss things because I assume they’re specifically for crafting DSLs when really they’re generally applicable; that’s what I thought of match expanders before this.


sydney.lambda
2019-8-7 10:38:23

I’ve actually been yearning for a Haskell/Clojureish syntax that matches the arguments and optionally binds the values to names. I know you can just use match-lambda, but the syntax makes it feel very different, to me at least: f[] = --base case f(x : xs) --recur --or getField Thing{thingField=field} = field versus having to use define/match or match-lambda. They just feel cubersome, personally. All the more reason I should spend more time studying macros!

I can’t remember the clojure syntax, but it’s pretty weird iirc.


nbtheduke
2019-8-7 13:10:36

@nbtheduke has joined the channel


sydney.lambda
2019-8-8 02:06:25

returning to the byte-matching shenanigans of yesterday, I needed a way to split a byte in the matched list into its constituent bits and then match those to variables: (define (byte-&gt;bits b) (define bits (string-&gt;list (~r b #:base 2 #:min-width 8 #:pad-string "0"))) (values (first bits) (second bits) (third bits) (fourth bits) (fifth bits) (sixth bits) (seventh bits) (eighth bits))) (define (main rom-path) (define ROM (bytes-&gt;list (file-&gt;bytes rom-path #:mode 'binary))) (match ROM [(iNES-rom PRG-SIZE CHR-SIZE (app byte-&gt;bits mapper-bit3 mapper-bit2 mapper-bit1 mapper-bit0 four-way-mirroring? _ battery-prg? mirroring) xs) (list mapper-bit3 mapper-bit2 mapper-bit1 mapper-bit0)])) the app byte->bits … is the best (only, actually) way I could think to match the individual bits of that byte like so: 76543210 \|\|\|\|\|\|\|\| \|\|\|\|\|\|\|+- Mirroring: 0: horizontal (vertical arrangement) (CIRAM A10 = PPU A11) \|\|\|\|\|\|\| 1: vertical (horizontal arrangement) (CIRAM A10 = PPU A10) \|\|\|\|\|\|+-- 1: Cartridge contains battery-backed PRG RAM ($6000-7FFF) or other persistent memory \|\|\|\|\|+--- 1: 512-byte trainer at $7000-$71FF (stored before PRG data) \|\|\|\|+---- 1: Ignore mirroring control or above mirroring bit; instead provide four-screen VRAM ++++----- Lower nybble of mapper number If that made any sense at all with my lacklustre description, I just wanted to see if that’s a reasonable enough way to do it, or if anyone has a better suggestion? It seems to work fine, but I just wanted to check in the event I’m ignoring something more obvious. Thanks :slightly_smiling_face:


samdphillips
2019-8-8 02:11:19

I might make a match expander that would expand to the (app byte-&gt;bits ...) portion. And also write byte->bits without converting to a string.



samdphillips
2019-8-8 02:15:29

This is untested, but is close to what you want (lambda (n) (for/fold ([n n] [bs null] #:result bs) ([_i 8]) (values (arithmetic-shift n -1) (cons (bitwise-and 1 n) bs)))) (edit fixed sign on shift)


samdphillips
2019-8-8 02:15:58

It returns a list of bits instead of values


sydney.lambda
2019-8-8 02:19:58

I think that’s the same method we used in college i.e. repeated division by two and counting the remainder. I’d have never worked out how to do it using a fold like that though! thank you.


samdphillips
2019-8-8 02:20:32

Practice! :slightly_smiling_face: and reading other people’s code


sydney.lambda
2019-8-8 03:11:00

Think I came up with a stream solution :slightly_smiling_face: : (define (byte-&gt;bits n) (define bits (stream-map (curryr modulo 2) (stream-iterate (curryr quotient 2) n))) (values (stream-ref bits 7) (stream-ref bits 6) (stream-ref bits 5) (stream-ref bits 4) (stream-ref bits 3) (stream-ref bits 2) (stream-ref bits 1) (stream-ref bits 0)))


notjack
2019-8-8 04:17:34

@sydney.lambda I wrote some byte-to-bit utility functions: https://docs.racket-lang.org/rebellion/Bytes.html


notjack
2019-8-8 04:18:16

Including a (byte-ref byte position) function


notjack
2019-8-8 04:21:18

I like that stream solution though


sydney.lambda
2019-8-8 04:35:03

@notjack I can see those being very useful, thanks :slightly_smiling_face: