@tbrooke has joined the channel
Hello all! I am trying to understand how to customize pretty-printing and I was thinking to myself… how do I display the contents of a pretty-print-style-table
… https://docs.racket-lang.org/reference/pretty-print.html#%28def._%28%28lib._racket%2Fpretty..rkt%29._pretty-print-current-style-table%29%29
but I can’t… pretty-print-current-style-table
only yields… #<procedure:parameter-procedure>
If I try calling it with (pretty-print-current-style-table)
, I get: #<pretty-print-style-table>
How can I see what is inside one of these?
Clearly, there is a lack of understanding in my brain regarding this…
@sturgman The style-table is represented as a struct. If a struct is declared with #:transparent, then the printer can “see inside” and print the fields. If not, it can just see what kind of struct it is. In this case we can cheat and look at the code. We find: (define-struct pretty-print-style-table (hash))
So in principle, we ought to write pretty-print-style-table-hash
to get the hash table. But racket/pretty doesn’t export the accessor. The reason? Well maybe at some point the pretty printer will changed to store the styles in a different way, and then it is problematic, if others have used the internal representation.
The intention is that you use pretty-print-extend-style-table
to change the styles.
If you just want to play around with style-tables, you can add a (provide (struct-out pretty-print-style-table))
and a #:transparent
to the struct definition (and then run raco setup
).
https://github.com/racket/racket/blob/master/racket/collects/racket/pretty.rkt
Thank you @soegaard2! That is so interesting… I understand the desire to hide the details of the style table… but how is one supposed to use pretty-print-extend-style-table
? Is there an example somewhere? What is the format of that thing?
Thanks for pointing out where to find the source as well!
#lang racket
(require racket/pretty)
(pretty-print-current-style-table
(pretty-print-extend-style-table
(pretty-print-current-style-table)
'(foo bar)
'(lambda define)))
(pretty-print-columns 20)
(pretty-print '(bar (x 1)
"this is is a long string"))
This makes foo prints like lambda is printed, and bar as define is printed.
Ahh… that clarifies the explanation in the documentation as well… so the extent of customization here is to customize how new things are pretty printed but not how the default things are pretty printed…
Yes. That’s my understanding.
But … there is an alternative if you want to change the way structs you define yourself is printed.
You can add a #:methods gen:write
and provide your own printer for that particular struct type.
Sounds good… thanks a lot for your help!
I’ve been following this tutorial on compiling Scheme: https://generalproblem.net/lets_build_a_compiler/01-starting-out/ at the same time as the tutorial it is based off, “An Incremental Approach to Compiler Construction”. They differ in some ways, with the main one for the stage I’m at being that the former uses the same word-size as my system, so I’m using that representation.
type \| 31 bit 0
-------------------------------------------------
integer \| iiiiiiiiiiiiiiiiiiiiiiiiiiiii00
boolean \| 0000000000000000000000b00001111
char \| 000000000000000cccccccc00000111
pair pointer \| pppppppppppppppppppppppppppp001
vector pointer \| pppppppppppppppppppppppppppp010
string pointer \| pppppppppppppppppppppppppppp011
symbol pointer \| pppppppppppppppppppppppppppp101
closure pointer \| pppppppppppppppppppppppppppp110
However, that representation has no way of tagging/representing the empty list! not only that, but it seems all possible tags are taken! Can anybody think of a way to work around this to provide a tag/identity/representation for the empty list?
The best solution I can think of is to reserve address 0000000000000000000000000000000 somehow and add the list-pointer (or any, I suppose, but just for symmetry) identification tag yielding 0000000000000000000000000000001. That way, the empty list is just a comparison against that literal binary value - no need to mask and dereference like the other actual pointer types as there’s nothing actually “there”; that that memory location is just reserved/unusable so as to enable the use of it as a literal identity value. Otherwise you’d risk confusing it with an “actual” pointer to a list stored in the same location.
Any ideas? Thanks :)
@sydney.lambda Take a look at: http://www.ccs.neu.edu/home/lth/larceny/notes/note2-repr.html
@soegaard2 looks to be exactly what I needed. Thanks :)
On a slightly different note, I’ve been working on my 6502 emulator a bit today, and started to think that I must find a way to define the various opcodes without having such a ridiculous amount of repeated code. It’s easy enough for simple cases like (roughly using fancy-app notation for definitions formed by partial-application): (define (LOAD register-lens processor mode)
;do stuff, set flags etc.)
(define LOAD-IMM (LOAD _ _ identity) ; operand is directly loaded into register, no modification
(define LDA-IMM (LOAD-IMM Processor-A-lens _))
(define LDY-IMM (LOAD-IMM Processor-Y-lens _))
(define LDX-IMM ....
but - and this may just be down to my inexperience, it seems to get to the point where you’re making simple functions seem very complex out of a need to make them “sufficiently generic” (even when they may be very specific) because another derived function is defined in terms of it, and so it must accommodate this.
Now, sorry for the rambling (I hope this actually made sense), here’s the actual question: In a situation like this, do you think it makes sense (i.e. isn’t cheating or bad form) to use macros to define all the various functions in full using templates, even if it does produce a lot of duplicate code and doesn’t necessarily make the best use of composition and modularity, because it prevents the cognitive load of having to piece all these excessively-generic functions together that successively implement eachother by being combined in more-and-more specific ways?
@sydney.lambda I think that’s reasonable. Make sure the generated code doesn’t unnecessarily duplicate code though, otherwise the macros will explode code size. Generating functions that just call slightly more generic functions like you’re already doing is a great way to do that, so you’re on the right track.
Really there’s only so much you can do when making an API to an assembler. ISAs usually have a bajillion instructions that are only slight variations of each other.
RISC-V assembly has a notion of "pseudo-instructions* which are like assembly-level macros. Maybe 6502 has something similar? Not my area of expertise though.
Also, are those lenses you’re using? Neat!
I agree with notjack, that using macros is the perfect tool to automatically generate many, similar functions.
However in this case it is better to 1) read the next opcode, 2) find the adressing mode, 3) compute the effective address (based on the adressing mode), and 4) dispatch to, say, LDA which receives the effective address.
I’m * fetching the byte at the PC to get the operator (and therefore the addressing mode also encoded in the byte) * then passing the processor-state-struct to the function-representation of the opcode * which grabs however many bytes it needs (either just the PC+1, or the PC+1 & the PC+2) * does what it needs to do (load/store/update flags) * and finally increments the PC based on how many operands were “consumed”, or, the instruction’s length. does that differ from the method you describe @soegaard2 ? I think it’s does, but I’m not sure I understand how to do it the way you describe I’m afraid. Of the things I’m less-read on, how the processor /actually/ executes instructions isn’t something I’m knowledgeable about, only “what” happens, the result it produces, and where to go from there, if that makes sense.
@notjack thanks for the clarification, good to know I’m not completely on the wrong track haha. I’ll look into the RISC-V stuff - sounds fairly interesting regardless. And yes, lenses indeed! the Racket library is great in my experience; struct-copy started to get very unwieldy when I added the memory vector and status-flag updating routines.
Alternatively you could probably encode the empty list as pppppppppppppppppppppppppppp001
All p set to zero
Since there probably won’t be a pointer to a pair at the 1 address. :stuck_out_tongue:
(here’s just the very-much-experimental modes themselves which I intended to somehow use to parameterise the opcodes http://pasterack.org/pastes/44332) (just realised IND modes are incorrect after pasting, oops…)
@sydney.lambda Maybe I am reading your snippet wrong, but I got the impression that you have a function for each mode, LDA-IMM, LDA-ZEROPAGE, LDA-ABSOLUTE etc. I was just suggesting that it is simpler to have one LDA, which receives the effective operand.
And by effective operand I mean: LDA #$20 effective operand is #$20
LDA $20 effective operand is contents of $20
LDA $20,x effective operatind is contents of $20+x
etc
Sorry, I’ve changed things around that many times I’m probably being very confusing. At the moment, the tentative plan is to have a generic LOAD instruction, which takes a register-lens (Processor-A-lens, Processor-Y-lens…) and one of the modes in the paste above to parameterize it in some way I haven’t yet fully formulated, but I think can work almost entirely by working on the operand/s (use it as an immediate value, use it as a zero-page address, use it as a zero-page address+x etc.) That is to say, I think we’re thinking along the same lines, other than having a high-level LOAD (or, LOAD-REG) function that takes a register-lens.
Thank you both for your advice, much appreciated :)
looks like 0000000000000000000000000011111 isn’t being used for anything in that table, so that could be another alternative
It seems I underestimated just how many extra possibilities were left! Thank you both, I appreciate the help :)
@samdphillips That’s bloody perfect! I managed to get a decent-enough version by converting a C++ algorithm, but it doesn’t handle variable-length arguments or preserve order like your version. I think I understand most of the cases, but could you explain a little how the first one applies? I’m a bit mystified as to how a single operator and operand results in just dropping the operator. Is it because (other than unary negation and division which I didn’t specify) single-operand numerical-operator applications are just the identity? That is, (+ 1) is just 1, and (* 5) is just 5? Thanks :)
Hah, yeah I think that is an underspecification. It’s mostly there to handle recursing on the tail for the >2 case. There is probably a better way to handle the >2 case though. Maybe a function like: ;; combine-forth :: forth-expr op lisp-expr -> forth-expr
(define (combine-forth fexpr op expr)
(append fexpr (forthify expr) (list op)))
I’ve updated it to handle the case of a single argument: https://gist.github.com/samdphillips/a62bcda26e7577ac2eba4dec6326f958