rebolred-langrebol3

Tracer function written in Rebol


It is possible in Rebol (Red, R3 Ren-c) to write a function similar to TRACE that produces the following result:

foo: func [val1 val2 /local temp] [
       temp: val1 + 5
       val2 + temp
]
bar: func [x /ris 'var "blablalba"][
       if ris [set var "fatto"]
       (foo x 2) + 8
]
trace [foo bar]
bar/ris 7 yyy

Enter BAR
   x = 7
   var = yyy
     Enter FOO
       val1 = 7
       val2 = 2
     FOO returned 14
BAR returned 22

Solution

  • At the user-level, the most straightforward approach is to write a closure-like wrapper that would call tracing hooks you provide before and after calling the main code, and which then would return the result of evaluation.

    A rough sketch of that idea in Red is as follows:

    frame-of: function [
        func [function!]
        /local
            match
    ][
        parse spec-of :func [
            collect any [
                set match [not quote return: all-word!] keep (to word! match)
                | skip
            ]
        ]
    ]
    
    report: function [frame [block!]][
        environment: construct collect [forall frame [keep to set-word! frame/1]]
        also environment set environment reduce frame
    ]
    
    trace: function [
        'target [word!]
        enter   [block!]
        leave   [block!]
    ][
        chain: reduce [
            'do enter
            'set/any quote 'result 'do body-of get target
            'do leave
            quote :result
        ]
        
        new:  func spec-of get target chain
        info: context [
            frame:  bind frame-of get target :new
            name:   target
            result: none
        ]
        
        bind body-of :new info
        set target :new
        exit
    ]
    

    With that in place, you can then:

    enter: [print ['entering name 'with mold/flat body-of report frame]]
    leave: [print [name 'returned result]]
    
    trace foo enter leave
    trace bar enter leave
    

    Which, for your example, gives:

    >> bar/ris 7 yyy
    entering bar with [x: 7 ris: true var: 'yyy]
    entering foo with [val1: 7 val2: 2 local: false temp: none]
    foo returned 14
    bar returned 22
    == 22
    

    Mind that this is just a PoC. The basic idea is that your original function gets replaced by an instrumented version, created via a closure over some internal namespace with debugging info. You can also throw some Red/System into the mix to gain fine-grained access to runtime information, like e.g. evaluation stack.

    I am leaving pretty-printing with indentation and disabling of tracing as an exercise for the reader ;)