~ chicken-core (chicken-5) 7796f12a385be5fbbfdca5c6aa632d3ad7037e9b


commit 7796f12a385be5fbbfdca5c6aa632d3ad7037e9b
Author:     Evan Hanson <evhan@foldling.org>
AuthorDate: Tue Aug 9 23:55:39 2016 +1200
Commit:     Peter Bex <peter@more-magic.net>
CommitDate: Tue Aug 9 19:35:05 2016 +0200

    Add `include-relative` form to "chicken" module
    
    Adds a new include form that searches for files relative to the
    including file rather than the CWD. In all other ways it behaves like
    the normal `include`.
    
    Because `load-relative` and `include-relative` are so similar, this
    change also moves some pathname handling code into a group of shared
    procedures in eval.scm and uses them for both.
    
    Also slightly simplifies the filename handling code in `load` and fixes
    a segfault in the following program caused by the use of
    `##sys#current-load-path` as a string when it's false:
    
      (load (open-input-string "(load-relative \"foo.scm\")"))
    
    Signed-off-by: Peter Bex <peter@more-magic.net>

diff --git a/NEWS b/NEWS
index d4eb49dc..27e6be58 100644
--- a/NEWS
+++ b/NEWS
@@ -38,6 +38,8 @@
     a single bidirectional port.
   - New `input-port-open?` and `output-port-open?` procedures have been
     added for testing whether a port is open in a specific direction.
+  - An `include-relative` form has been added to the chicken module.
+    This works like `load-relative` but for textual inclusion.
 
 - Module system
   - The compiler has been modularised, for improved namespacing.  This
diff --git a/batch-driver.scm b/batch-driver.scm
index 3848f3be..e33e38c5 100644
--- a/batch-driver.scm
+++ b/batch-driver.scm
@@ -437,7 +437,10 @@
     (let ([extends (collect-options 'extend)])
       (dribble "Loading compiler extensions...")
       (for-each
-       (lambda (f) (load (##sys#resolve-include-filename f #f #t))) 
+       (lambda (e)
+	 (let ((f (##sys#resolve-include-filename e #f #t #f)))
+	   (when (not f) (quit-compiling "cannot load extension: ~a" e))
+	   (load f)))
        extends) )
     (set! ##sys#features (delete #:compiler-extension ##sys#features))
     (set! ##sys#features (cons '#:compiling ##sys#features))
@@ -561,7 +564,9 @@
 	   (print-expr "source" '|1| forms)
 	   (begin-time)
 	   ;; Canonicalize s-expressions
-	   (let* ((exps0 (map canonicalize-expression
+	   (let* ((exps0 (map (lambda (x)
+				(fluid-let ((##sys#current-source-filename filename))
+				  (canonicalize-expression x)))
 			      (let ((forms (append initforms forms)))
 				(if (not module-name)
 				    forms
@@ -647,8 +652,7 @@
 		   (for-each
 		    (lambda (id)
 		      (and-let* ((ifile (##sys#resolve-include-filename
-					 (symbol->string id) '(".inline") #t))
-				 ((file-exists? ifile)))
+					 (symbol->string id) '(".inline") #t #f)))
 			(dribble "Loading inline file ~a ..." ifile)
 			(load-inline-file ifile)))
 		    mreq))
diff --git a/chicken-syntax.scm b/chicken-syntax.scm
index b30e8205..1ec885ce 100644
--- a/chicken-syntax.scm
+++ b/chicken-syntax.scm
@@ -169,7 +169,14 @@
  (##sys#er-transformer
   (lambda (form r c)
     (##sys#check-syntax 'include form '(_ string))
-    `(##core#include ,(cadr form)))))
+    `(##core#include ,(cadr form) #f))))
+
+(##sys#extend-macro-environment
+ 'include-relative '()
+ (##sys#er-transformer
+  (lambda (form r c)
+    (##sys#check-syntax 'include-relative form '(_ string))
+    `(##core#include ,(cadr form) ,##sys#current-source-filename))))
 
 (##sys#extend-macro-environment
  'assert '()
diff --git a/core.scm b/core.scm
index 806d7cff..6ba31078 100644
--- a/core.scm
+++ b/core.scm
@@ -111,7 +111,7 @@
 ; (##core#lambda ({<variable>}+ [. <variable>]) <body>)
 ; (##core#set! <variable> <exp>)
 ; (##core#begin <exp> ...)
-; (##core#include <string>)
+; (##core#include <string> <string> | #f)
 ; (##core#loop-lambda <llist> <body>)
 ; (##core#undefined)
 ; (##core#primitive <name>)
@@ -902,11 +902,12 @@
 				 bs) ) ) ) )
 
 		       ((##core#include)
-			(walk
-			 `(##core#begin
-			   ,@(fluid-let ((##sys#default-read-info-hook read-info-hook))
-			       (##sys#include-forms-from-file (cadr x))))
-			 e se dest ldest h ln))
+			(fluid-let ((##sys#default-read-info-hook read-info-hook))
+			  (##sys#include-forms-from-file
+			   (cadr x)
+			   (caddr x)
+			   (lambda (forms)
+			     (walk `(##core#begin ,@forms) e se dest ldest h ln)))))
 
 		       ((##core#let-module-alias)
 			(##sys#with-module-aliases
diff --git a/csc.scm b/csc.scm
index d12dba23..ea59f069 100644
--- a/csc.scm
+++ b/csc.scm
@@ -314,8 +314,8 @@
 (define (find-object-files name)
 
   (define (locate-object-file filename repo)
-    (let ((f (##sys#resolve-include-filename filename '() repo)))
-      (and (file-exists? f) (list f))))
+    (and-let* ((f (##sys#resolve-include-filename filename '() repo #f)))
+      (list f)))
 
   (define (static-extension-information name)
     (and-let* ((info  (extension-information name))
diff --git a/eval.scm b/eval.scm
index fba6d5da..7ee369a3 100644
--- a/eval.scm
+++ b/eval.scm
@@ -626,10 +626,11 @@
 			   e #f tf cntr se))
 
 			 ((##core#include)
-			  (compile
-			   `(##core#begin
-			     ,@(##sys#include-forms-from-file (cadr x)))
-			   e #f tf cntr se))
+			  (##sys#include-forms-from-file
+			   (cadr x)
+			   (caddr x)
+			   (lambda (forms)
+			     (compile `(##core#begin ,@forms) e #f tf cntr se))))
 
 			 ((##core#let-module-alias)
 			  (##sys#with-module-aliases
@@ -919,12 +920,30 @@
 			  (fx+ argc 1) ) ] ) ) ) ) )
 
 
+;;; Pathname helpers:
+
+(define path-separators
+  (if ##sys#windows-platform '(#\\ #\/) '(#\/)))
+
+(define (path-separator-index/right s)
+  (let loop ((i (fx- (##sys#size s) 1)))
+    (if (memq (##core#inline "C_subchar" s i) path-separators)
+	i
+	(and (fx< 0 i) (loop (fx- i 1))))))
+
+(define (make-relative-pathname from file)
+  (let ((i (and (string? from)
+		(positive? (##sys#size file)) ; XXX probably an error?
+		(not (memq (##core#inline "C_subchar" file 0) path-separators))
+		(path-separator-index/right from))))
+    (if (not i) file (string-append (##sys#substring from 0 i) "/" file))))
+
+
 ;;; Loading source/object files:
 
 (define load-verbose (make-parameter (##sys#fudge 13)))
 
-(define ##sys#current-source-filename #f)
-(define ##sys#current-load-path "")
+(define ##sys#current-load-filename #f)
 (define ##sys#dload-disabled #f)
 
 (define-foreign-variable _dlerror c-string "C_dlerror")
@@ -967,17 +986,10 @@
       (define evalproc
 	(or evaluator eval))
 
-      (define (has-slash? str)
-	(let loop ((i (fx- (##sys#size str) 1)))
-	  (if (memq (##core#inline "C_subchar" str i) '(#\\ #\/))
-	      i
-	      (and (fx< 0 i)
-		   (loop (fx- i 1))))))
-
       ;; dload doesn't consider filenames without slashes to be paths,
       ;; so we prepend a dot to force a relative pathname.
       (define (dload-path path)
-	(if (has-slash? path)
+	(if (path-separator-index/right path)
 	    path
 	    (##sys#string-append "./" path)))
 
@@ -996,17 +1008,12 @@
 	      ((not (string? input))
 	       (##sys#signal-hook #:type-error 'load "bad argument type - not a port or string" input))
 	      ((##sys#file-exists? input #t #f 'load) input)
+	      ((let ((f (##sys#string-append input ##sys#load-dynamic-extension)))
+		 (and dload? (##sys#file-exists? f #t #f 'load) f)))
+	      ((let ((f (##sys#string-append input source-file-extension)))
+		 (and (##sys#file-exists? f #t #f 'load) f)))
 	      (else
-	       (let ((fname2 (##sys#string-append input ##sys#load-dynamic-extension)))
-		 (if (and dload? (##sys#file-exists? fname2 #t #f 'load))
-		     fname2
-		     (let ((fname3 (##sys#string-append input source-file-extension)))
-		       (if (##sys#file-exists? fname3 #t #f 'load)
-			   fname3
-			   input)))))))
-
-      (when (and (string? input) (not fname))
-	(##sys#signal-hook #:file-error 'load "cannot open file" input))
+	       (##sys#signal-hook #:file-error 'load "cannot open file" input))))
 
       (when (and (load-verbose) fname)
 	(display "; loading ")
@@ -1018,11 +1025,8 @@
 	  (call-with-current-continuation
 	   (lambda (abrt)
 	     (fluid-let ((##sys#read-error-with-line-number #t)
-			 (##sys#current-source-filename fname)
-			 (##sys#current-load-path
-			  (and fname
-			       (let ((i (has-slash? fname)))
-				 (if i (##sys#substring fname 0 (fx+ i 1)) "")))))
+			 (##sys#current-load-filename fname)
+			 (##sys#current-source-filename fname))
 	       (let ((in (if fname (open-input-file fname) input)))
 		 (##sys#dynamic-wind
 		  (lambda () #f)
@@ -1060,9 +1064,7 @@
 
 (define (load-relative filename . evaluator)
   (load/internal
-   (if (memq (string-ref filename 0) '(#\\ #\/))
-       filename
-       (##sys#string-append ##sys#current-load-path filename))
+   (make-relative-pathname ##sys#current-load-filename filename)
    (optional evaluator #f)))
 
 (define (load-noisily filename #!key (evaluator #f) (time #f) (printer #f))
@@ -1128,16 +1130,19 @@
   (let ((with-input-from-file with-input-from-file)
 	(read read)
 	(reverse reverse))
-    (lambda (fname)
-      (let ((path (##sys#resolve-include-filename fname #t #f)))
-	(when (load-verbose) (print "; including " path " ..."))
+    (lambda (filename source k)
+      (let ((path (##sys#resolve-include-filename filename #t #f source)))
+	(when (not path)
+	  (##sys#signal-hook #:file-error 'include "cannot open file" filename))
+	(when (load-verbose)
+	  (print "; including " path " ..."))
 	(with-input-from-file path
 	  (lambda ()
 	    (fluid-let ((##sys#current-source-filename path))
 	      (do ((x (read) (read))
-		   (xs '() (cons x xs)) )
-		  ((eof-object? x) 
-		   (reverse xs))) ) ) ) ) ) ) )
+		   (xs '() (cons x xs)))
+		  ((eof-object? x)
+		   (k (reverse xs)))))))))))
 
 
 ;;; Extensions:
@@ -1390,7 +1395,7 @@
   (let ((string-append string-append) )
     (define (exists? fname)
       (##sys#file-exists? fname #t #f #f))
-    (lambda (fname exts repo)
+    (lambda (fname exts repo source)
       (define (test-extensions fname lst)
 	(if (null? lst)
 	    (and (exists? fname) fname)
@@ -1408,7 +1413,7 @@
 		(list ##sys#load-dynamic-extension source-file-extension))
 	       (else                   ; prefer source
 		(list source-file-extension ##sys#load-dynamic-extension)))))
-      (or (test fname)
+      (or (test (make-relative-pathname source fname))
 	  (let loop ((paths (if repo
 				(##sys#append 
 				 ##sys#include-pathnames 
@@ -1417,7 +1422,7 @@
 				       (list (##sys#repository-path))
 				       '())))
 				##sys#include-pathnames) ) )
-	    (cond ((eq? paths '()) fname)
+	    (cond ((eq? paths '()) #f)
 		  ((test (string-append (##sys#slot paths 0)
 					"/"
 					fname) ) )
diff --git a/expand.scm b/expand.scm
index d96477da..29ef3fa6 100644
--- a/expand.scm
+++ b/expand.scm
@@ -74,6 +74,9 @@
 (define-inline (putp sym prop val)
   (##core#inline_allocate ("C_a_i_putprop" 8) sym prop val))
 
+;;; Source file tracking
+
+(define ##sys#current-source-filename #f)
 
 ;;; Syntactic environments
 
@@ -1521,7 +1524,7 @@
 		     (if (and (pair? body) 
 			      (null? (cdr body))
 			      (string? (car body)))
-			 `((##core#include ,(car body)))
+			 `((##core#include ,(car body) ,##sys#current-source-filename))
 			 body))))))))))
 
 (##sys#extend-macro-environment
diff --git a/manual/Non-standard macros and special forms b/manual/Non-standard macros and special forms
index 3f45734d..78a56f5a 100644
--- a/manual/Non-standard macros and special forms	
+++ b/manual/Non-standard macros and special forms	
@@ -612,9 +612,16 @@ The following table should make this clearer:
 
 Include toplevel-expressions from the given source file in the currently
 compiled/interpreted program.  If the included file has the extension
-{{.scm}}, then it may be omitted.  The file is searched in the
-current directory and, if not found, in all directories specified in the
-{{-include-path}} option.
+{{.scm}}, then it may be omitted. The file is searched for in the
+current directory and all directories specified by the {{-include-path}}
+option.
+
+==== include-relative
+
+<macro>(include-relative STRING)</macro>
+
+Works like {{include}}, but the filename is searched for relative to the
+including file rather than the current directory.
 
 ==== nth-value
 
diff --git a/tests/runtests.bat b/tests/runtests.bat
index 07ec3b6d..8e488d0d 100644
--- a/tests/runtests.bat
+++ b/tests/runtests.bat
@@ -511,6 +511,26 @@ if errorlevel 1 exit /b 1
 echo ======================================== syntax-rules stress test ...
 %interpret% -bnq syntax-rule-stress-test.scm
 
+echo "======================================== include test ..."
+mkdir a\b
+echo > a\b\ok.scm
+echo '(include "a/b/ok.scm")' > a\b\include.scm
+%compile% -analyze-only a\b\include.scm
+if errorlevel 1 exit /b 1
+echo '(include "b/ok.scm")' > a\b\include.scm
+%compile% -analyze-only a\b\include.scm -include-path a
+if errorlevel 1 exit /b 1
+echo '(include-relative "ok.scm")' > a\b\include.scm
+%compile% -analyze-only a\b\include.scm
+if errorlevel 1 exit /b 1
+echo '(include-relative "b/ok.scm")' > a\include.scm
+%compile% -analyze-only a\include.scm
+if errorlevel 1 exit /b 1
+echo '(include-relative "b/ok.scm")' > a\b\include.scm
+%compile% -analyze-only a\b\include.scm -include-path a
+if errorlevel 1 exit /b 1
+del /f /s /q a
+
 echo "======================================== executable tests ..."
 %compile% executable-tests.scm
 if errorlevel 1 exit /b 1
diff --git a/tests/runtests.sh b/tests/runtests.sh
index 72cb56e8..279f70f3 100755
--- a/tests/runtests.sh
+++ b/tests/runtests.sh
@@ -435,6 +435,21 @@ $compile locative-stress-test.scm
 echo "======================================== syntax-rules stress test ..."
 time $interpret -bnq syntax-rule-stress-test.scm
 
+echo "======================================== include test ..."
+mkdir -p a/b
+echo > a/b/ok.scm
+echo '(include "a/b/ok.scm")' > a/b/include.scm
+$compile -analyze-only a/b/include.scm
+echo '(include "b/ok.scm")' > a/b/include.scm
+$compile -analyze-only a/b/include.scm -include-path a
+echo '(include-relative "ok.scm")' > a/b/include.scm
+$compile -analyze-only a/b/include.scm
+echo '(include-relative "b/ok.scm")' > a/include.scm
+$compile -analyze-only a/include.scm
+echo '(include-relative "b/ok.scm")' > a/b/include.scm
+$compile -analyze-only a/b/include.scm -include-path a
+rm -r a
+
 echo "======================================== executable tests ..."
 $compile executable-tests.scm
 ./a.out "$TEST_DIR/a.out"
Trap