packages feed

cpsa-2.2.12: src/cpsaperm.scm

#!/usr/bin/guile \
-e main -s
!#
(use-modules (ice-9 pretty-print))
;;; For an R6RS script, add an import statement.

;;; Synopsis: renumber strands in skeletons

;;; A permutation of length n is represented as a list of natural
;;; numbers that contains all the numbers less than n.

;;; This program renumbers all the skeletons in a file that have the
;;; same number of strands as is the length of the permutation.
;;; Everything else is passed through unchanged.

;;; Usage: cpsaperm [-h] [-o FILE] FILE INT...
;;; where INT... specifies a permutation.
;;; Use - in place of FILE for the standard port

;;; Example: cpsaperm -o out.txt in.txt 1 0
;;; Swaps the strands in skeletons with two strands.

;;; This program does not check that the given permutation is in fact
;;; a valid permutation, so be sure to check your output carefully.
;;; Make sure your permutation has no repeats.

;;; John D. Ramsdell -- October 2012

;;; Create a function that applies a permutation to a form
(define (apply-perm perm)
  (let ((n (length perm)))
    (lambda (form)
      (let ((skel (parse-skel form)))
	(cond ((not skel) form)		; Ignore non-skeletons
	      ;; Ignore skeletons with the wrong number of strands
	      ((not (= n (length (skel-strands skel)))) form)
	      (else
	       (let ((skel (apply-perm-to-skel perm skel)))
		  `(defskeleton
		    ,(skel-protocol skel)
		    ,(skel-vars skel)
		    ,@(append (skel-strands skel) (skel-alist skel))))))))))

;;; A skeleton is represented as list with the following fields.

(define (skel-protocol skel) (car skel))
(define (skel-vars skel) (cadr skel))
(define (skel-strands skel) (caddr skel))
(define (skel-alist skel) (cdddr skel))
(define (make-skel protocol vars strands alist)
  (cons protocol (cons vars (cons strands alist))))

;;; Creates a skeleton structure or returns #f on error.
(define (parse-skel form)
  (and (skel? form)
       (let loop ((body (cdddr form)) (strands '()))
	 (cond ((null? body)
		(make-skel (cadr form) (caddr form)
			   (reverse strands) body))
	       ((or (eq? 'defstrand (caar body))
		    (eq? 'deflistener (caar body)))
		(loop (cdr body) (cons (car body) strands)))
	       (else
		(make-skel (cadr form) (caddr form)
			   (reverse strands) body))))))

;;; Is form a defskeleton

(define (skel? form)
  (and (list? form)
       (<= 4 (length form))
       (eq? 'defskeleton (car form))
       (alist? (cddr form))))

(define (alist? form)			; Is form a list of pairs?
  (or (null? form)
      (and (pair? form)
	   (pair? (car form))
	   (alist? (cdr form)))))

;;; To apply a permutation to a skeleton, permute the strands and the
;;; node orderings.
(define (apply-perm-to-skel perm skel)
  (let ((strands (skel-strands skel)))
    (make-skel
     (skel-protocol skel)
     (skel-vars skel)
     (map (lambda (index)
	    (list-ref strands index))
	  perm)
     (map (apply-perm-to-alist perm) (skel-alist skel)))))

;;; Apply a permutation to the node orderings
(define (apply-perm-to-alist perm)
  (lambda (entry)
    (if (eq? (car entry) 'precedes)
	(cons (car entry)
	      (map (apply-perm-to-pair perm) (cdr entry)))
	entry)))

(define (apply-perm-to-pair perm)
  (lambda (ordered-pair)
    (if (and (list? ordered-pair) (= 2 (length ordered-pair)))
	(list (apply-perm-to-node perm (car ordered-pair))
	      (apply-perm-to-node perm (cadr ordered-pair)))
	ordered-pair)))

(define (apply-perm-to-node perm node)
    (if (and (list? node) (= 2 (length node)))
	(list (list-ref perm (car node)) (cadr node))
	node))

;;; The main driver loop and command-line processing

;;; The main loop calls the transformer on each S-expression read from
;;; the current input.

(define (filter perm)
  (let ((transformer (apply-perm perm)))
    (lambda ()
      (do ((form (read) (read)))
	  ((eof-object? form))
	(pretty-print (transformer form))))))

;;; A file description is #f for the standard port, or a file name
;;; that is a non-empty string that does not start with hyphen.

(define (file-description? file)
   (or (not file)
       (and (> (string-length file) 0)
	    (not (char=? #\- (string-ref file 0))))))

;;; After command-line processing, this routine opens files as needed.

(define (go input output perm)
  (let ((thunk (filter perm)))
    (cond ((not (file-description? input))
	   (display-error "bad input file name"))
	  ((not (file-description? output))
	   (display-error "bad output file name"))
	  (input
	   (with-input-from-file input
	     (lambda ()
	       (if output
		   (with-output-to-file output thunk)
		   (thunk)))))
	  (output
	   (with-output-to-file output thunk))
	  (else
	   (thunk)))))

;;; Parse command-line arguments and pass the result to the go function.

(define (main args)
  (let loop ((args (cdr args)) (output #f))
    (cond ((null? args) 		; No input file specified
	   (display-error "bad args"))
	  ((string=? (car args) "-h")
	   (display-help))		; Print help message
	  ((string=? (car args) "-o")
	   (if (null? (cdr args))
	       (display-error "bad args")
	       (loop (cddr args) (cadr args)))) ; Found an output file
	  ((string=? (car args) "-")
	   (check-perm #f output (cdr args)))
	  (else					; Found input file
	   (check-perm (car args) output (cdr args))))))

;;; Make sure a permutation is a list of non-negative integers less
;;; that the length of the permutation.
(define (check-perm input output args)
  (let ((n (length args)))
    (let loop ((args args) (perm '()))
      (if (null? args)
	  (go input output (reverse perm))
	  (let ((index (string->number (car args))))
	    (if (and (integer? index)
		     (not (negative? index))
		     (< index n))
		(loop (cdr args) (cons index perm))
		(display-error "bad permutation")))))))

(define (display-help)
  (display-error "cpsaperm [-h] [-o FILE] FILE INT...")
  (display-error "where INT... specifies a permutation.")
  (display-error "Use - as FILE for the standard input."))

(define (display-error obj)
  (display obj (current-error-port))
  (newline (current-error-port)))

;;; For an R6RS script, add the following:
;;; (main (command-line))