On 10/14/2017 3:00 AM, Jack Firth wrote:

    So is there a way ... from normal code ... to get at the locals of
    functions higher in the call chain?  Or at least the immediate
    caller?
    Some reflective capability that I haven't yet discovered?


I'm not sure if there's a way to do that, but I'm wondering if what you want to do can be achieved more simply with plain functions and a very small macro wrapper. In particular, I suspect putting too much logic in the macro is what led you to eval which is the root of all evil. From what I can tell there shouldn't be any need at all for eval or any sort of dynamic runtime compilation to do things like what you're describing. Could you give a few more details about your use case? Ideally with some example code illustrating the problem?

Basically, this is a sort of Unix at-like function for flexible scheduling.  It takes an expression containing dates, times, certain keywords and arbitrary numeric expressions, and it produces seconds since the epoch.  Code is attached - hope it survives posting to the list.  It should be runnable as is.

What led me to eval originally was wanting to reference arbitrary functions/variables from the runtime environment.  I'd like to be able to say
things like:

   (let [(y 42)] (schedule (at now + y mins) ...))

and similar involving top-level defined functions [which already works with eval].

I know that eval is not needed if I generate inline code rather than having the macro invoke a normal function.  But that is complicated by having to deal with free form expressions: they can have internal references between things which are not necessarily adjacent.  It is doable, but at some expense and not very cleanly.

I started out going the "compile" route - just generating inline code.  But as more functionality was added, that became unwieldy. So I switched to a runtime function. Right now the assoc list code is overly complex [so please ignore it] - it is there as a debugging tool until I get everything working exactly right.

George

--
You received this message because you are subscribed to the Google Groups "Racket 
Users" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to racket-users+unsubscr...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.
#lang racket/base

(require
  (for-syntax racket/base
              racket/format
              racket/pretty
              )

  racket/base
  racket/match
  racket/list
  racket/format
  racket/dict
  racket/date
  racket/port
  )


(provide
 at
 at-time
 month/year
 )


;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
#|

allows free form date/time descriptions in
the style of the Unix "at" command. converts
the description into seconds since the Unix
epoch: 1970-01-01 at 00:00hrs.

the resulting seconds value may be converted
into a usable date structure, or passed to
an alarm event for scheduling.

freestanding + operators will be ignored.
for signed values use [+-]?[:digits:]+.

key words:
  "now" = current date/time
  "today"      - at 00hrs
  "tomorrow"   - at 00hrs
  "this-month" - at 00hrs on the 1st
  "next-month" - at 00hrs on the 1st

dates must be entered in yyyy-mm-dd format.

times are 24hr format: hours and minutes
are required - seconds are optional.  a
separate AM/PM qualifier can be used to adjust
ambiguous times.

anything not a date, time or keyword will be
evaluated as a racket expression. expressions
may reference variables or functions from the
surrounding code environment.

an expression may be followed immediately by
a unit multiplier.  units may be seconds,
minutes, hours, days, or weeks (or some of
the typical abbreviations for these units).

|#
;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


;======================================
;
;  macro interface - generates call to
;  parsing function at runtime
;
;======================================

(define-syntax (at stx)
  (let* [
         (input   (cdr (syntax->datum stx)))
         (input   (map ~a input))
         (fnspec  (list 'apply '+ (list* 'at-time input)))
        ]

    (datum->syntax stx fnspec)
    ))


;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


;======================================
;
;  parse a time/date description
;
;======================================


(define (at-time #:namespace [ns (current-namespace)] . input)
  (let* [
         (now    (current-seconds))
         (today  (current-date))
         (hour12 (* 12 60 60))
         (hour13 (* 13 60 60))
        ]
    
    (let loop [
               (input  input)
               (output '())
              ]
      
      (match input
      
        ; done
        ([? empty?]
         (eprintf "=> ~s~n" (reverse output))
         (map cdr output)
         )


        ; current time
        ([list "now" _ ___ ]
         (loop (cdr input) (cons (cons 'now now) output)))


        ; date (at 00:00:00)
        ([list (pregexp px-date [list _ yy mm dd]) _ ___]
         (let* [
                (yy (string->number yy))
                (mm (string->number mm))
                (dd (string->number dd))
                (result (find-seconds 0 0 0 dd mm yy))
               ]
           (loop (cdr input) (cons (cons 'date result) output))
           ))


        ; time 24hr format
        ([list (pregexp px-time [list _ hh mm _ ss]) _ ___]
         (let* [
                (hh (string->number hh))
                (mm (string->number mm))
                (ss (if ss (string->number ss) 0))
                ; translate as absolute offset from epoch
                (result (find-seconds ss mm hh 1 1 1970 #f))
               ]
           (loop (cdr input) (cons (cons 'time result) output))
           ))


        ; PM - offset (time < 12:00)
        ([list (or "pm" "PM") _ ___]
         (let* [
                (time (assq 'time output))
                (ok   (and time (< (cdr time) hour12)))
               ]
           (if ok
               (loop (cdr input) (cons (cons 'am/pm hour12) output))
               (loop (cdr input) output))
           ))


        ; AM - offset (12:00 <= time < 13:00)
        ([list (or "am" "AM") _ ___]
         (let* [
                (time (assq 'time output))
                (ok   (and time
                           (or (= (cdr time) hour12)
                               (< hour12 (cdr time) hour13)
                               ))) 
               ]
           (if ok
               (loop (cdr input) (cons (cons 'am/pm (- hour12)) output))
               (loop (cdr input) output))
           ))


        ; today (at 00:00:00)
        ([list "today" _ ___]
         (let [
               (result (find-seconds 0 0 0 [date-day today][date-month 
today][date-year today]))
              ]
           (loop (cdr input) (cons (cons 'today result) output))
           ))


        ; tomorrow (at 00:00:00)
        ([list "tomorrow" _ ___]
         (let* [
                (result (find-seconds 0 0 0 [date-day today][date-month 
today][date-year today]))
                (result (+ result (dict-ref units "day")))
               ]
           (loop (cdr input) (cons (cons 'tomorrow result) output))
           ))
      

        ; this-month (1st day at 00:00:00)
        ([list "this-month" _ ___]
         (let [
               (result (find-seconds 0 0 0 1 [date-month today][date-year 
today]))
              ]
           (loop (cdr input) (cons (cons 'this-month result) output))
           ))

      
        ; next-month (1st day at 00:00:00)
        ([list "next-month" _ ___]
         (let*-values
             [
              ((month year) (month/year [date-month today] [date-year today] 
+1))
              ((result) (find-seconds 0 0 0 1 month year))
             ]
           (loop (cdr input) (cons (cons 'next-month result) output))
           ))


        ; punctuation/whitespace
        ([list "+" _ ___ ]
         (loop (cdr input) output))

      
        ; unit multiplier
        ([list (app (λ(e) (dict-ref units e #f)) unit)
               _ ___]
         (if unit
             (let* [
                    (expr (if [pair? output] (car output) #f))
                    (expr (if [and expr (eq? (car expr) 'expr)]
                              (* (cdr expr) unit)
                              #f))
                   ]
               (if expr
                   (loop (cdr input) (cons (cons 'unit-expr expr) (cdr output)))
                   (error 'at "unit must follow expression")))
             (failure-cont)
             ))


        ; number
        ([list (app string->number num) _ ___]
         (if num
             (loop (cdr input) (cons (cons 'expr num) output))
             (failure-cont) ))

        
        ; expression
        ([list expr _ ___]
         (let [
               (val (with-handlers [(exn:fail? (λ(e)#f))]
                      (with-input-from-string expr
                        (λ() (eval (read) ns)))))
              ]
           (eprintf "expr: ~s -> ~s~n" expr val)
           (if [number? val]
               (loop (cdr input) (cons (cons 'expr val) output))
               (failure-cont))
           ))
                      

        (else
         (error 'at "I don't understand: ~a" input))

        ) ; end match
      
      )))
    

;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


(define units
  (let* [
         (second 1)
         (minute (* 60 second))
         (hour   (* 60 minute))
         (day    (* 24 hour  ))
         (week   (*  7 day   ))
         (myhash (make-custom-hash member))
        ]
    (dict-set*! myhash
      '("sec" "secs" "second" "seconds") second
      '("min" "mins" "minute" "minutes") minute
      '("hr"  "hrs"  "hour"   "hours"  ) hour
      '(             "day"    "days"   ) day
      '(             "week"   "weeks"  ) week
      )
    myhash))


(define px-time #px"(2[0-3]|[01]?[0-9]):([0-5][0-9])(:([0-5][0-9]))?" )
(define px-date #px"([0-9]{4})-(0[1-9]|1[0-2])-(0[1-9]|[12][0-9]|3[01])")
(define px-integer #px"[+-]?[[:digit:]]+")


;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%


;======================================
;
;  compute ending month & year
;
;======================================

(define (month/year month year offset)
  
  (let* [
         ; offset month for modular arithmetic
         (month  (- month 1))
           
         ; compute ending year
         (year   (let [
                       (op     (if (>= offset 0) + -))
                       (offset (abs offset))
                      ]
                   (if [eq? op +] 
                       (let [(left (- 11 month))]
                         (if (<= offset left)
                             year
                             (+ year (ceiling (/ (- offset left) 12)))
                             ))
                       ;[eq? op -]
                       (if (<= offset month)
                           year
                           (- year (ceiling (/ (- offset month) 12)))
                           )
                       )
                   ))
         
         ; compute ending month
         (month  (modulo (+ month offset) 12))
         (month  (+ month 1))
        ]
    ; return month and year
    (values month year)
    ))


;%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%

Reply via email to