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:
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
Thanks for the suggestion, but changing the define to define-for-syntax doesn’t seem to do the trick.
@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) …]).
Use define-match-expander
.
that won’t ensure it’s the correct pattern of bytes though, will it?
Wouldn’t that just match anything and call said matched variables a, b, c, d?
I suppose it would be possible to have it expand into the literal bytes pattern?
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)
Time to define my first match expander, it seems! Thanks for the help, very much appreciated :slightly_smiling_face:
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:
(list 1 2 3 x …) is the same as (list* 1 2 3 x)
so delete the last …
or change list* into list
I think it should be used as (iNES-rom xs)
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:
Okay. I’ll try it.
You need to begin with (lambda (stx)
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))
Same code as before, with the entire function being: (define (main rom-path)
(define ROM (bytes->list
(file->bytes rom-path #:mode 'binary)))
(match ROM
[(iNES-rom ,xs) #t]))
(define-match-expander iNES-rom
(λ (stx)
(syntax-case stx ()
[(_ pat) #'(list* #x4e #x45 #x53 #x1a pat)])))
(match (bytes->list (bytes #x4e #x45 #x53 #x1a 1 2 3))
[(iNES-rom xs) xs])
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.
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.
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.
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.
Match-expanders are also handy when you want to accept multiple types of input.
Let’s say you have a function that accepts a color as the first argument.
The problem here is that the user might want to write “black”, (color 100 200 300), or has already got a color% object.
So one can write a match-expander such that
(match a-color [(color r g b) …]) will accept all three types of input.
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.
The down side is that you have to remember always to use pattern matching when accepting a color as argument.
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.
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 has joined the channel
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->bits b)
(define bits (string->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->list
(file->bytes rom-path #:mode 'binary)))
(match ROM
[(iNES-rom PRG-SIZE
CHR-SIZE
(app byte->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:
I might make a match expander that would expand to the (app byte->bits ...)
portion. And also write byte->bits without converting to a string.
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)
It returns a list of bits instead of values
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.
Practice! :slightly_smiling_face: and reading other people’s code
Think I came up with a stream solution :slightly_smiling_face: : (define (byte->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)))
@sydney.lambda I wrote some byte-to-bit utility functions: https://docs.racket-lang.org/rebellion/Bytes.html
Including a (byte-ref byte position)
function
I like that stream solution though
@notjack I can see those being very useful, thanks :slightly_smiling_face: