(in-package #:input) (defstruct input (cursor 0) (file nil :read-only t) (data nil :read-only t)) (defun has-data? (input &optional (window-size 1)) (<= (+ window-size (input-cursor input)) (length (input-data input)))) (defun peek-1 (input) (char (input-data input) (input-cursor input))) (defun peek-n (input window-size) (subseq (input-data input) (input-cursor input) (+ window-size (input-cursor input)))) (defun peek-rest (input) (subseq (input-data input) (input-cursor input) (length (input-data input)))) (defun advance (input &optional (amount 1)) (let ((new-input (copy-structure input))) (incf (input-cursor new-input) amount) new-input)) (defun advance-to-end (input) (let ((new-input (copy-structure input))) (setf (input-cursor new-input) (length (input-data input))) new-input)) (declaim (ftype (function (simple-string) (values input &optional)) from-string)) (defun from-string (str) (make-input :data str)) (declaim (ftype (function (simple-string) (values input &optional)) from-file)) (defun from-file (filename) (make-input :file filename :data (str:read-file filename))) (defun generate-report (input message) (let ((line 1) (column 1)) (dotimes (i (input-cursor input)) (let ((c (char (input-data input) i))) (case c (#\Newline (incf line) (setf column 1)) (t (incf column))))) (if (input-file input) (format nil "~a:~a:~a: ~a" (input-file input) line column message) (format nil "~a:~a: ~a" line column message))))