mes/module/nyacc/lang/util.scm

428 lines
13 KiB
Scheme
Raw Normal View History

;;; module/nyacc/util.scm
;;;
2017-01-01 15:54:21 +00:00
;;; Copyright (C) 2015-2017 Matthew R. Wette
;;;
;;; This software is covered by the GNU GENERAL PUBLIC LICENCE, Version 3,
2017-01-01 15:54:21 +00:00
;;; or any later version published by the Free Software Foundation. See
;;; the file COPYING included with the nyacc distribution.
;; runtime utilities for the parsers -- needs work
(define-module (nyacc lang util)
#:export (lang-crn-lic
report-error
push-input pop-input reset-input-stack
make-tl tl->list ;; rename?? to tl->sx for sxml-expr
tl-append tl-insert tl-extend tl+attr
sx-tag
sx-attr sx-attr-ref sx-has-attr? sx-set-attr! sx-set-attr*
sx-ref sx-tail sx-find
;; for pretty-printing
make-protect-expr make-pp-formatter make-pp-formatter/ugly
;; for ???
move-if-changed
fmterr)
#:use-module ((srfi srfi-1) #:select(find))
)
(cond-expand
(guile-2)
(guile
(use-modules (ice-9 optargs))
(use-modules (ice-9 syncase)))
(mes))
;; This is a generic copyright/licence that will be printed in the output
;; of the examples/nyacc/lang/*/ actions.scm and tables.scm files.
2017-01-01 15:54:21 +00:00
(define lang-crn-lic "
This software is covered by the GNU GENERAL PUBLIC LICENCE, Version 3,
2017-01-01 15:54:21 +00:00
or any later version published by the Free Software Foundation. See
the file COPYING included with the this distribution.")
(define (fmterr fmt . args)
(apply simple-format (current-error-port) fmt args))
;; @deffn report-error fmt args
;; Report an error: to stderr, providing file and line num info, and add nl.
(define (report-error fmt args)
(let ((fn (or (port-filename (current-input-port)) "(unknown)"))
(ln (1+ (port-line (current-input-port)))))
(apply simple-format (current-error-port)
(string-append "~A:~A: " fmt "\n") fn ln args)))
;; === input stack =====================
(define *input-stack* (make-fluid '()))
(define (reset-input-stack)
2017-01-01 15:54:21 +00:00
(fluid-set! *input-stack* '()))
(define (push-input port)
2017-01-01 15:54:21 +00:00
(let ((curr (current-input-port))
(ipstk (fluid-ref *input-stack*)))
(fluid-set! *input-stack* (cons curr ipstk))
(set-current-input-port port)))
;; Return #f if empty
(define (pop-input)
2017-01-01 15:54:21 +00:00
(let ((ipstk (fluid-ref *input-stack*)))
(if (null? ipstk) #f
(begin
(set-current-input-port (car ipstk))
(fluid-set! *input-stack* (cdr ipstk))))))
;; === tl ==============================
;; @section Tagged Lists
;; Tagged lists are
;; They are implemented as a cons cell with the car and the cdr a list.
;; The cdr is used to accumulate appended items and the car is used to
;; keep the tag, attributes and inserted items.
;; @example
;; tl => '(H . T), H => (c a b 'tag); T =>
;; @end example
;; @table code
;; @deffn make-tl tag [item item ...]
;; Create a tagged-list structure.
(define (make-tl tag . rest)
(let iter ((tail tag) (l rest))
(if (null? l) (cons '() tail)
(iter (cons (car l) tail) (cdr l)))))
;; @deffn tl->list tl
;; Convert a tagged list structure to a list. This collects added attributes
;; and puts them right after the (leading) tag, resulting in something like
;; @example
;; (<tag> (@ <attr>) <rest>)
;; @end example
(define (tl->list tl)
(let ((heda (car tl))
(head (let iter ((head '()) (attr '()) (tl-head (car tl)))
(if (null? tl-head)
(if (pair? attr)
(cons (cons '@ attr) (reverse head))
(reverse head))
(if (and (pair? (car tl-head)) (eq? '@ (caar tl-head)))
(iter head (cons (cdar tl-head) attr) (cdr tl-head))
(iter (cons (car tl-head) head) attr (cdr tl-head)))))))
(let iter ((tail '()) (tl-tail (cdr tl)))
(if (pair? tl-tail)
(iter (cons (car tl-tail) tail) (cdr tl-tail))
(cons tl-tail (append head tail))))))
;; @deffn tl-insert tl item
;; Insert item at front of tagged list (but after tag).
(define (tl-insert tl item)
(cons (cons item (car tl)) (cdr tl)))
;; @deffn tl-append tl item ...
;; Append items at end of tagged list.
(define (tl-append tl . rest)
(cons (car tl)
(let iter ((tail (cdr tl)) (items rest))
(if (null? items) tail
(iter (cons (car items) tail) (cdr items))))))
;; @deffn tl-extend tl item-l
;; Extend with a list of items.
(define (tl-extend tl item-l)
(apply tl-append tl item-l))
;; @deffn tl-extend! tl item-l
;; Extend with a list of items. Uses @code{set-cdr!}.
(define (tl-extend! tl item-l)
(set-cdr! (last-pair tl) item-l)
tl)
;; @deffn tl+attr tl key val)
;; Add an attribute to a tagged list. Return the tl.
;; @example
;; (tl+attr tl 'type "int")
;; @end example
(define (tl+attr tl key val)
(tl-insert tl (cons '@ (list key val))))
;; @deffn tl-merge tl tl1
;; Merge guts of phony-tl @code{tl1} into @code{tl}.
(define (tl-merge tl tl1)
(error "not implemented (yet)")
)
;; === sx ==============================
;; @section SXML Utility Procedures
;; Some lot of these look like existing Guile list procedures (e.g.,
;; @code{sx-tail} versus @code{list-tail} but in sx lists the optional
;; attributea are `invisible'. For example, @code{'(elt (@abc) "d")}
;; is an sx of length two: the tag @code{elt} and the payload @code{"d"}.
;; @deffn sx-ref sx ix => item
;; Reference the @code{ix}-th element of the list, not counting the optional
;; attributes item. If the list is shorter than the index, return @code{#f}.
;; @example
;; (sx-ref '(abc "def") 1) => "def"
;; (sx-ref '(abc (@ (foo "1")) "def") 1) => "def"
;; @end example
(define (sx-ref sx ix)
(define (list-xref l x) (if (> (length l) x) (list-ref l x) #f))
(cond
((zero? ix) (car sx))
((and (pair? (cadr sx)) (eqv? '@ (caadr sx)))
(list-xref sx (1+ ix)))
(else
(list-xref sx ix))))
;; @deffn sx-tag sx => tag
;; Return the tag for a tree
(define (sx-tag sx)
(if (pair? sx) (car sx) #f))
;; @deffn sx-tail sx ix => (list)
;; Return the tail starting at the ix-th cdr, starting from 0.
;; For example, if sx has 3 items then (sx-tail sx 2) returns '().
(define (sx-tail sx ix)
(cond
((zero? ix) (error "sx-tail: expecting index greater than 0"))
((and (pair? (cdr sx)) (eqv? '@ (cadr sx))) (list-tail sx (1+ ix)))
(else (list-tail sx ix))))
;; @deffn sx-has-attr? sx
;; p to determine if @arg{sx} has attributes.
(define (sx-has-attr? sx)
(and (pair? (cdr sx)) (pair? (cadr sx)) (eqv? '@ (caadr sx))))
;; @deffn sx-attr sx => '(@ ...)|#f
;; @example
;; (sx-attr '(abc (@ (foo "1")) def) 1) => '(@ (foo "1"))
;; @end example
;; should change this to
;; @example
;; (sx-attr sx) => '((a . 1) (b . 2) ...)
;; @end example
(define (sx-attr sx)
(if (and (pair? (cdr sx)) (pair? (cadr sx)))
(if (eqv? '@ (caadr sx))
(cadr sx)
#f)
#f))
;; @deffn sx-attr-ref sx key => val
;; Return an attribute value given the key, or @code{#f}.
(define (sx-attr-ref sx key)
(and=> (sx-attr sx)
(lambda (attr)
(and=> (assq-ref (cdr attr) key) car))))
;; @deffn sx-set-attr! sx key val
;; Set attribute for sx. If no attributes exist, if key does not exist,
;; add it, if it does exist, replace it.
(define (sx-set-attr! sx key val . rest)
(if (sx-has-attr? sx)
(let ((attr (cadr sx)))
(set-cdr! attr (assoc-set! (cdr attr) key (list val))))
(set-cdr! sx (cons `(@ (,key ,val)) (cdr sx))))
sx)
;; @deffn sx-set-attr* sx key val [key val [key ... ]]
;; Set attribute for sx. If no attributes exist, if key does not exist,
;; add it, if it does exist, replace it.
(define (sx-set-attr* sx . rest)
(let iter ((attr (or (and=> (sx-attr sx) cdr) '())) (kvl rest))
(cond
((null? kvl) (cons* (sx-tag sx) (cons '@ (reverse attr)) (sx-tail sx 1)))
(else (iter (cons (list (car kvl) (cadr kvl)) attr) (cddr kvl))))))
2016-12-03 15:19:35 +00:00
;; @deffn sx-find tag sx => ((tag ...) (tag ...))
;; Find the first matching element (in the first level).
(define (sx-find tag sx)
(find (lambda (node)
(and (pair? node) (eqv? tag (car node))))
sx))
;;; === pp ==========================
;; @section Pretty-Print and Other Utility Procedures
;; @deffn make-protect-expr op-prec op-assc => side op expr => #t|#f
;; Generate procedure @code{protect-expr} for pretty-printers, which takes
;; the form @code{(protect-expr? side op expr)} and where @code{side}
;; is @code{'lval} or @code{'rval}, @code{op} is the operator and @code{expr}
;; is the expression. The argument @arg{op-prec} is a list of equivalent
;; operators in order of decreasing precedence and @arg{op-assc} is an
;; a-list of precedence with keys @code{'left}, @code{'right} and
;; @code{nonassoc}.
;; @example
;; (protect-expr? 'lval '+ '(mul ...)) => TBD
;; @end example
(define (make-protect-expr op-prec op-assc)
(define (assc-lt? op)
(memq op (assq-ref op-assc 'left)))
(define (assc-rt? op)
(memq op (assq-ref op-assc 'right)))
;; @deffn prec a b => '>|'<|'=|#f
;; Returns the prececence relation of @code{a}, @code{b} as
;; @code{<}, @code{>}, @code{=} or @code{#f} (no relation).
(define (prec a b)
(let iter ((ag #f) (bg #f) (opg op-prec)) ;; a-group, b-group
(cond
((null? opg) #f) ; indeterminate
((memq a (car opg))
(if bg '<
(if (memq b (car opg)) '=
(iter #t bg (cdr opg)))))
((memq b (car opg))
(if ag '>
(if (memq a (car opg)) '=
(iter ag #t (cdr opg)))))
(else
(iter ag bg (cdr opg))))))
(lambda (side op expr)
(let ((assc? (case side
((lt left) assc-rt?)
((rt right) assc-lt?)))
(vtag (car expr)))
(case (prec op vtag)
((>) #t)
((<) #f)
((=) (assc? op))
(else #f)))))
;; @deffn make-pp-formatter => fmtr
;; @example
;; (fmtr 'push) ;; push indent level
;; (fmtr 'pop) ;; pop indent level
;; (fmtr "fmt" arg1 arg2 ...)
;; @end example
(define* (make-pp-formatter)
(letrec
((maxcol 78)
(maxind 36)
(column 0)
(ind-lev 0)
(ind-len 0)
(blanks " ")
(ind-str (lambda () (substring blanks 0 ind-len)))
(cnt-str (lambda () (substring blanks 0 (+ 4 ind-len))))
;;(sf-nl (lambda () (newline) (set! column 0)))
(push-il
(lambda ()
(set! ind-lev (min maxind (1+ ind-lev)))
(set! ind-len (* 2 ind-lev))))
(pop-il
(lambda ()
(set! ind-lev (max 0 (1- ind-lev)))
(set! ind-len (* 2 ind-lev))))
(sf
(lambda (fmt . args)
(let* ((str (apply simple-format #f fmt args))
(len (string-length str)))
2016-12-03 15:19:35 +00:00
(cond
((zero? column)
(display (ind-str))
(set! column (+ column ind-len)))
2016-12-03 15:19:35 +00:00
((> (+ column len) maxcol)
(newline)
(display (cnt-str))
2016-12-03 15:19:35 +00:00
(set! column (+ column ind-len 4))))
(display str)
(when (and (positive? len)
(eqv? #\newline (string-ref str (1- len))))
(set! column 0))))))
(lambda (arg0 . rest)
(cond
2016-12-03 15:19:35 +00:00
;;((string? arg0) (if (> (string-length arg0) 0) (apply sf arg0 rest)))
((string? arg0) (apply sf arg0 rest))
((eqv? 'push arg0) (push-il))
((eqv? 'pop arg0) (pop-il))
((eqv? 'nlin arg0) ;; newline if needed
(cond ((positive? column) (newline) (set! column 0))))
(else (error "pp-formatter: bad args"))
))))
;; @deffn make-pp-formatter/ugly => fmtr
;; Makes a @code{fmtr} like @code{make-pp-formatter} but no indentation
;; and just adds strings on ...
2016-12-26 03:31:09 +00:00
;; This is specific to C/C++ because it will newline if #\# seen first.
(define* (make-pp-formatter/ugly)
(let*
((maxcol 78)
(column 0)
(sf (lambda (fmt . args)
(let* ((str (apply simple-format #f fmt args))
(len (string-length str)))
2016-12-26 03:31:09 +00:00
(if (and (positive? len)
(char=? #\newline (string-ref str (1- len))))
(string-set! str (1- len) #\space))
(cond
2016-12-26 03:31:09 +00:00
((zero? len) #t) ; we reference str[0] next
((and (equal? len 1) (char=? #\newline (string-ref str 0))) #t)
((char=? #\# (string-ref str 0)) ; CPP-stmt: force newline
(when (positive? column) (newline))
(display str) ; str always ends in \n
(set! column ; if ends \n then col= 0 else len
(if (char=? #\newline (string-ref str (1- len)))
0 len)))
((zero? column)
(display str)
(set! column len))
(else
(when (> (+ column len) maxcol)
(newline)
(set! column 0))
(display str)
(set! column (+ column len))))))))
(lambda (arg0 . rest)
(cond
((string? arg0) (apply sf arg0 rest))
((eqv? 'nlin arg0) ;; newline if needed
(cond ((positive? column) (newline) (set! column 0))))
((eqv? 'push arg0) #f)
((eqv? 'pop arg0) #f)
(else (error "pp-formatter/ugly: bad args"))))))
;; @deffn move-if-changed src-file dst-file [sav-file]
;; Return @code{#t} if changed.
(define (move-if-changed src-file dst-file . rest)
(define (doit)
(let ((sav-file (if (pair? rest) (car rest) #f)))
(if (and sav-file (access? sav-file W_OK))
(system (simple-format #f "mv ~A ~A" dst-file sav-file)))
(system (simple-format #f "mv ~A ~A" src-file dst-file))
#t))
(cond
;; src-file does not exist
((not (access? src-file R_OK)) #f)
;; dst-file does not exist, update anyhow
((not (access? dst-file F_OK))
(system (simple-format #f "mv ~A ~A" src-file dst-file)) #t)
;; both exist, but no changes
((zero? (system
(simple-format #f "cmp ~A ~A >/dev/null" src-file dst-file)))
(system (simple-format #f "rm ~A" src-file)) #f)
;; both exist, update
((access? dst-file W_OK)
(doit))
(else
(simple-format (current-error-port) "move-if-changed: no write access\n")
#f)))
;; @end table
;;; --- last line ---