soegaard2
2019-8-13 08:56:50

@trhawes What do you need the provides for? If you need a list of all the individual provided variables, then expansion is needed. If you want to reindent the original provide forms, you don’t.


trhawes
2019-8-13 11:57:47

I want to copy the provides from each file into a corresponding stub file in each directory. This is actually a repo for students wanting to learn Racket on Exercism,io. Each exercise has a unit test file, a stub file (the student recieves to get started on the exercise), and an example.rkt file with the exercise fully solved (we use this to validate the unit tests). My goal is to extract the provide line from the example.rkt for each exercise, and make sure the corresponding stub file has this provide line in it, so the student understands what functions (and their names) the unit tests expect. This would be something we could re-run for new exercises.

Looking at the docs, it is not clear how I might load each example.rkt in for expansion. I did find read-lang-file (https://docs.racket-lang.org/read-lang-file/index.html), so that lets me load the file, and return a syntax object I can expand. But when I run (syntax-property-symbol-keys ex)

it returns just '(module-body-inside-context module-body-context-simple? module-body-context) None of the keys you linked to.

Surely, I am missing something.


trhawes
2019-8-13 12:01:05

For now, I just run syntax->datum on the expanded module, and then recurse through the resulting list to find my provide line.


soegaard2
2019-8-13 12:32:24

Here is a quick hack. Which basically do as you, but doesn’t expand anything: #lang racket (require racket/list) (provide get-provide-forms) (provide (all-from-out racket/list)) (define (provide-form? f) (match f [(list* 'provide more) f] [_ #f])) (define (get-provide-forms file) (with-input-from-file file (λ () (read-line) ; skip #lang line (filter values (for/list ([form (in-port read)]) (provide-form? form)))))) ; test it on it self - don't we all love self-reference (get-provide-forms "get-provide-form.rkt")


soegaard2
2019-8-13 12:33:16

This works as long as no macros in the file expand to use uses of provide.


soegaard2
2019-8-13 12:33:49

Use the expand solution, if you need that.


trhawes
2019-8-13 14:13:16

Thank you!


badkins
2019-8-13 19:43:49

Is it possible to access a stack trace from an exn:fail ? If so, how?


soegaard2
2019-8-13 19:44:09

DrRacket?


badkins
2019-8-13 19:44:17

no


soegaard2
2019-8-13 19:44:28

racket-mode in Emacs?


badkins
2019-8-13 19:45:04

no, while running the program. In other words, I’m using with-handlers and I catch an exn:fail - I can print the message, but I’d like to know the stack trace


badkins
2019-8-13 19:45:42

exn only has message and continuation-marks, so I figure it must be somewhere in the continuation-marks


soegaard2
2019-8-13 19:46:39

You probably need to use error-trace. I think, it stores information in continuation marks.


soegaard2
2019-8-13 19:47:17

There is an: (print-error-trace output-port exn) which looks at point.


badkins
2019-8-13 19:48:15

With Ruby, it would simply be: def self.foo ... rescue => e logger.error(e.message) logger.error(e.backtrace) end So, I was hoping something similarly easy was available.


badkins
2019-8-13 19:50:45

@soegaard2 (print-error-trace …) seems to do the trick - thanks!


soegaard2
2019-8-13 19:50:57

great!


badkins
2019-8-13 19:54:06

@soegaard2 oops - spoke too soon. It works in DrRacket, but I get no output from running via the commandline.


badkins
2019-8-13 19:54:42
#lang racket

(require errortrace)

(define (boom)
  (/ 1 0))

(define (run)
  (with-handlers ([exn:fail?
                   (λ (e)
                     (print-error-trace (current-output-port) e))])
    (boom)))

(module+ main
  (run))

Maybe there is no current output port?


soegaard2
2019-8-13 19:55:04

I think you need to enable error-trace when racket is invoked (I remember I was puzzled the first time I used errortrace in the terminal).


badkins
2019-8-13 19:55:27

Hmm… then I’m wondering if I want to do that normally in production :disappointed:


badkins
2019-8-13 19:57:08

I would get a stack trace from Racket if I wasn’t catching the exception myself, so I’m not sure why the errortrace module is necessary.


soegaard2
2019-8-13 19:57:14

racket -l errortrace my-script.rkt


badkins
2019-8-13 19:58:00

I guess I can read the code of the default exception handler, and see how it does it. Your -l example above still produced no output.


samth
2019-8-13 19:59:11

@badkins you want exn-continuation-marks and then the continuation-mark-set->context function


soegaard2
2019-8-13 20:00:42

@badkins did you also use -m to invoke main?


badkins
2019-8-13 20:01:27

No, but I’m pretty sure I don’t want to use errortrace in production. I’m trying Sam’s idea…


badkins
2019-8-13 20:02:15
#lang racket

(define (boom)
  (/ 1 0))

(define (run)
  (with-handlers ([exn:fail?
                   (λ (e)
                     (print (continuation-mark-set->context (exn-continuation-marks e))))])
    (boom)))

(module+ main
  (run))

Produces: (list (cons #f (srcloc #<path:/Applications/Racket v7.3/collects/racket/private/more-scheme.rkt> 261 28 9063 56)) (cons '\|[running body]\| (srcloc "(submod \"/Users/badkins/Desktop/exn-test.rkt\" main)" #f #f #f #f)) '(temp37_0 . #f) '(for-loop . #f) '(run-module-instance!125 . #f))


samth
2019-8-13 20:02:27

yes, that’s what you should get


badkins
2019-8-13 20:02:35

and then deconstruct srcloc ?


soegaard2
2019-8-13 20:03:03

If you don’t invoke main, boom isn’t called.


badkins
2019-8-13 20:03:25

Doesn’t module+ main do the trick? That’s how I’ve been running all my programs.


badkins
2019-8-13 20:04:35

@samth having all the good stuff as #f is kind of useless, no?


soegaard2
2019-8-13 20:04:51

Oh - sorry. You are right.


soegaard2
2019-8-13 20:05:06

Ran it Emacs - and boom wasn’t called.


badkins
2019-8-13 20:05:10

i.e. line, column, position, span are all #f, so not very helpful


samth
2019-8-13 20:06:11

The ones that are #f are for running body, which is saying it’s just running the body of the main submodule


samth
2019-8-13 20:06:21

that’s the same info you get in the regular error message


badkins
2019-8-13 20:07:08

Ah, indeed you are correct. <sigh>


samth
2019-8-13 20:07:11

Another thing you might want: (lambda (e) ((error-display-handler) (exn-message e) e)) which just prints the exception the way it normally gets printed


badkins
2019-8-13 20:07:39

Perfect, I’ll try that in the production program…


badkins
2019-8-13 20:13:19

Awesome - ((error-display-handler) (exn-message e) e) did exactly what I needed. Thanks @samth and @soegaard2 !


samth
2019-8-13 20:13:37

:slightly_smiling_face:


badkins
2019-8-13 20:14:21

Printing a stack trace is something most newbies will want fairly soon, so making that more obvious in the docs would be super helpful. I read through a few pages in both the reference and guide before asking here.


samth
2019-8-13 20:21:02

any suggestions for which page would be good to add that to?


soegaard2
2019-8-13 20:26:22

FWIW there is an error-display in unstable/errors.


badkins
2019-8-13 20:30:07

@samth I first went to exn:fail, then looked at the parent i.e. exn, so I think regardless of the page, having a link in the description for exn would be good. I think most people would be wanting to find out what they could do with the exn object.


badkins
2019-8-13 20:30:36

It’s clear that the message can be obtained, but it’s not clear how to get a stacktrace.


badkins
2019-8-13 20:31:43

I think I got a little confused distinguishing between continuation-marks and continuation-mark-set


badkins
2019-8-13 20:32:44

Had I clicked on the continuation-mark-set? link for exn, I would’ve seen the continuation-mark-set-&gt;context function you mentioned, but I think I went down another trail.


badkins
2019-8-13 20:34:07

@samth to answer your original question, probably here: https://docs.racket-lang.org/guide/exns.html


badkins
2019-8-13 20:34:29

An example printing a stack trace would do the trick I think.


sydney.lambda
2019-8-13 22:33:09

Does anyone happen to have any code examples/advice on parsing nested lists without mutation? Any representation will do really. This code I found on StackOverflow uses lists of strings, along with returning values of both the result of the current level of parsing, and any input still left to be parsed: (define (parse-sequence lst) (define (parse-seq lst) (let loop ((lst lst) (res null)) (cond ((null? lst) (values null res)) ((string=? (car lst) "[") (let-values ([(lst2 res2) (parse-seq (cdr lst))]) (loop lst2 (append res (list res2))))) ((string=? (car lst) "]") (values (cdr lst) res)) (else (loop (cdr lst) (append res (list (car lst)))))))) (let-values ([(lst res) (parse-seq lst)]) res)) I planned on putting something like this together in a few minutes, but (to my dismay) I found it rather difficult. I’m usually okay with recursive functions but this one just baffled me for some reason. The great majority of examples I’ve found are in a mutating/side-effectful style, with a while loop and a shared, mutable list so there’s no concern about keeping the recursive calls “in sync” with eachother. However, I really want to know how to do this elegantly (or, as possible) in a purely functional way, and I was hoping someone could provide some examples of how to go about it. I initially thought I could just get the length of the recursive list parse and then drop that many elements from the current list; I’m still confused as to why that doesn’t work. Thanks :)


soegaard2
2019-8-13 22:35:37

What is the input grammar?


sydney.lambda
2019-8-13 22:42:53

Sorry, completely forgot to specify. Just nested lists of numbers for now, keep it easy on myself. The delimiters in the code are "[" but that was just the best example I could find on SO - any paren style will do really.


soegaard2
2019-8-13 22:46:55
; &lt;exp&gt; ::= &lt;number&gt;
;        \|  ( &lt;exp&gt; * )

soegaard2
2019-8-13 22:47:44

Can we assume the input is correct?


sydney.lambda
2019-8-13 22:58:32

We can. I was worrying a little about matching delimiters, but I think I should get a handle of parsing correct input first.


soegaard2
2019-8-13 23:06:47

@sydney.lambda Working on an example.


sydney.lambda
2019-8-13 23:09:55

thank you :)


soegaard2
2019-8-13 23:17:33

soegaard2
2019-8-13 23:19:21

It does give you an error if it sees an unexpected character.


soegaard2
2019-8-13 23:19:57

If you turn line and column count for the input port, you can get the exact position from the port.


sydney.lambda
2019-8-13 23:39:14

Awesome example, thanks @soegaard2. That’s practically a tutorial on how to implement a parser combinator library! I guess I should stop worrying about functional purity for now and use input ports - I’m not gaining anything from doing so at this level of experience. I’ve messed around defining individual parsers but struggled a great deal in “gluing” them together at the top level. I think I have a much better idea of how to do so now after seeing your example.


soegaard2
2019-8-13 23:42:52

Thanks! Btw - each parse function assumes that any white space has been consumed by the caller.


sydney.lambda
2019-8-13 23:54:53

That was actually one of the others things I’ve been rather uncertain about whilst messing around with parsing over the past few days — how far “up the chain” (if that makes sense) to parse whitespace, when to do it before, when after, and such.

I tended towards having the lowest-level parsers (individual operands and operators to use assembly as that’s my ultimate goal) assuming that all whitespace at the start has been pre-consumed, and not consuming any whitspace or \n afterwards. That means something may “erroneously” match a the lowest levels due to, say, matching "#$22" in "#$22foo" even though nothing should ever appear after the "#$22" except a \n, but that’s taken care of higher up the chain by the “glue” functions which tie the smaller ones together. It seems I got the whitespace-consumed-by-caller thing right, at least! :)

Sorry for the wall of text, it almost slipped my mind as I was completely focused on parsing nested lists specifically.


soegaard2
2019-8-14 00:08:14

It’s an old reference, but the book “Brinch Hansen on Pascal Compilers” explains this sort of parsing really well. A library is needed - I can’t find it online.



sydney.lambda
2019-8-14 00:16:23

I swear, I thought I had tried every permutation of “Scheme parser guide” trying to find some examples of a nested-list parser. That link is exactly the type of thing I was looking for! Thanks a bunch.


soegaard2
2019-8-14 00:17:58

Indiana University unfortunately removed those pages. Only way of finding them is finding an old link to them, and then using http://archive.org\|archive.org. So if you don’t know they exist …


philip.mcgrath
2019-8-14 01:25:35

@sydney.lambda I think one thing that could be confusing is that there are a few different “jobs” being interleaved together. A traditional way to break this up is into “lexing” and “parsing,” where lexing would turn a raw input string like " ( 1 (11 12 13) 2 3 (41 42) 5 )" into a flat stream of tokens, like '(open 1 open 11 12 13 close 2 3 open 41 42 close 5 close), then parsing would match nested 'open and 'close pairs and produce nested lists.


sydney.lambda
2019-8-14 01:29:54

thanks @philip.mcgrath I’m afraid even with a lexing step, it’s the general way that solution works that confuses me. My initial solution was to (upon encountering an open paren (when already in the process of parsing a list) parse the nested list recursively, get the return result of the parse, and then just drop that many elements from the input. However, that doesn’t seem to work - you need to have the functions return a pair of (remaining-input . parse-result) and I can’t seem to get my head around why.


sydney.lambda
2019-8-14 01:33:52

I thought it would be really darn cool to utilise continuations, such that when you encounter a nested list you pass a continuation argument that says “hey, when you’re finished parsing that other list and whatever else, remember to let me finish parsing the rest of my list, okay?” but alas, I couldn’t figure it out :/

There’s a great pattern matcher from the lambda papers posted here: http://blog.veitheller.de/Pattern_Matching,_A_Thing_Of_The_Past.html which enables backtracking using continuations which each consume a tiny bit more input if the current input isn’t sufficient to match. I figure there must be a way to use this in aid of parsing nested lists, but I guess I’m just not experienced enough yet.


philip.mcgrath
2019-8-14 01:41:17

To understand the problem with your strategy, let’s think about the input "(1 2 (3 4 (5 6)) 7)". After parsing 2, we see (, so we call ourself recursively. Let’s imagine that recursive call works and we get back '(3 4 (5 6)). The length of '(3 4 (5 6)) is three, but it’s consumed more than 3 tokens of input! You could write a helper function to walk an intermediate result list like '(3 4 (5 6)) and count how many tokens it must have consumed from the input list, but, at that point, it’s probably easier to just return the rest of the input and avoid counting explicitly.


philip.mcgrath
2019-8-14 01:43:49

In a sense, the “rest of the input” return value is the continuation.


sydney.lambda
2019-8-14 01:46:21

I think it’s starting to register in my brain now, (5 6) consumes 5 tokens (space included) yet its length is technically one after being converted to a list. Even with the ( and ) taken into account which are not consumed, you’re still missing everything between the parens, if I’m getting this right.


philip.mcgrath
2019-8-14 01:46:46

Yes, that’s right


sydney.lambda
2019-8-14 01:48:49

I mean, it registers as one element in a larger list… Argh, I really don’t know why I’m finding this so confusing! haha. One of those occasions when you go over it so many times you burn yourself out, instead of just taking a break. Thanks for taking the time to explain it, I really appreciate it. I’m sure when I look over it tomorrow with a fresh pair of eyes (and synapses) I’ll wonder what I was fussing about.