cyclone/libraries.scm
2015-05-24 16:22:06 -04:00

219 lines
7.2 KiB
Scheme

;;
;; Cyclone Scheme
;; Copyright (c) 2014, Justin Ethier
;; All rights reserved.
;;
;; This module implements r7rs libraries. In our compiler, these are used to
;; encapsulate C modules.
;;
;; Initially, this a quicky-and-dirty (for now) implementation of r7rs libraries.
;;
;; TODO: go through functions and ensure consistent naming conventions.
;; probably should also clean up some of the function names, this is
;; not a very clean or nice API at the moment.
;;
(define (library? ast)
(tagged-list? 'define-library ast))
(define (lib:name ast) (cadr ast))
;; Convert name (as list of symbols) to a mangled string
(define (lib:name->string name)
(apply string-append (map mangle name)))
;; Convert library name to a unique symbol
(define (lib:name->symbol name)
(string->symbol
(string-append
"lib-init:" ;; Maybe make this an optional param? Trying to ensure uniqueness
(lib:name->string name))))
;; Helper function that returns an empty list as a default value
(define (lib:result result)
(if result result '()))
;; TODO: most of these below assume 0 or 1 instances of the directive.
;; may need to replace some of these later with filter operations to
;; support more than 1 instance.
(define (lib:exports ast)
(lib:result
(and-let* ((code (assoc 'export (cddr ast))))
(cdr code))))
(define (lib:imports ast)
(lib:result
(and-let* ((code (assoc 'import (cddr ast))))
(cdr code))))
(define (lib:body ast)
(lib:result
(and-let* ((code (assoc 'begin (cddr ast))))
(cdr code))))
(define (lib:includes ast)
(map
(lambda (inc-lst)
(cadr inc-lst))
(filter
(lambda (code)
(tagged-list? 'include code))
(cddr ast))))
;; TODO: include-ci, cond-expand
;; Resolve library filename given an import.
;; Assumes ".sld" file extension if one is not specified.
(define (lib:import->filename import . ext)
(let* ((file-ext
(if (null? ext)
".sld"
(car ext)))
(filename*
(string-append
(apply
string-append
(map
(lambda (i)
(string-append "/" (symbol->string i)))
import))
file-ext))
(filename
(substring filename* 1 (string-length filename*))))
(if (tagged-list? 'scheme import)
(string-append (cyc:get-lib-dir) filename) ;; Built-in library
filename)))
;; Get path to directory that contains the library
(define (lib:import->path import)
(let* ((import-path (reverse (cdr (reverse import))))
(path
(apply
string-append
(map
(lambda (i)
(string-append (symbol->string i) "/"))
import-path))))
(if (tagged-list? 'scheme import)
(string-append (cyc:get-lib-dir) path) ;; Built-in library
path)))
;; Given a program's import set, resolve each import to its .o file, then
;; process each import recursively to get the .o files that each one of those
;; libs requires. will probably need to prune duplicates from completed list.
;; Longer-term, do we want to look at file timestamps to see if files need to
;; be recompiled?
;(define (lib:imports->objs imports)
; (apply
; append
; (map
; (lambda (i)
; (cons
; (lib:import->filename i ".o")
; (lib:imports->objs (lib:read-imports i))
; ))
; imports)))
;; Given a single import from an import-set, open the corresponding
;; library file and retrieve the library's import-set.
(define (lib:read-imports import)
(let* ((dir (lib:import->filename import))
(fp (open-input-file dir))
(lib (read-all fp))
(imports (lib:imports (car lib))))
(close-input-port fp)
imports))
;; Read export list for a given import
(define (lib:import->export-list import)
(let* ((dir (string-append (lib:import->filename import)))
(fp (open-input-file dir))
(lib (read-all fp))
(exports (lib:exports (car lib))))
(close-input-port fp)
exports))
;; Take a list of imports and resolve it to the imported vars
(define (lib:resolve-imports imports)
(apply
append
(map
(lambda (import)
(lib:import->export-list import))
imports)))
;; Given an import set, get all dependant import names that are required
;; The list of deps is intended to be returned in order, such that the
;; libraries can be initialized properly in sequence.
(define (lib:get-all-import-deps imports)
(letrec ((libraries/deps '())
(find-deps!
(lambda (import-set)
(for-each
(lambda (i)
(cond
;; Prevent cycles by only processing new libraries
((not (assoc i libraries/deps))
;; Find all dependencies of i (IE, libraries it imports)
(let ((deps (lib:read-imports i)))
(set! libraries/deps (cons (cons i deps) libraries/deps))
(find-deps! deps)
))))
import-set))))
(find-deps! imports)
;`((deps ,libraries/deps) ; DEBUG
; (result ,(lib:get-dep-list libraries/deps)))
(lib:get-dep-list libraries/deps)
))
;; Given a list of alists (library-name . imports), return an ordered
;; list of library names such that each lib is encounted after the
;; libraries it imports (it's dependencies).
(define (lib:get-dep-list libs/deps)
; Overall strategy is:
; for each library
; compute index of result that is after any libs that lib imports
; compute index of result that is before any libs that import lib
; if there is a 'hole' then insert lib into result in that space
; otherwise, throw an error (unfortunate but will identify problems)
;
; To test, run this from hello directory:
; (pp (lib:get-all-import-deps '((scheme base) (scheme eval) (scheme base)
; (scheme read) (scheme eval) (libs lib1) (libs lib2))))
;
(let ((result '()))
(for-each
(lambda (lib/dep)
(cond
((null? result)
(set! result (cons lib/dep '())))
(else
(let ((idx-my-imports 0) ; lib must be placed after this
(idx-imports-me (length result))) ; lib must be before any libs that import it
(define (loop i)
(cond
((= i (length result))
'done)
(else
;; Does lib import this one?
(if (and
(> i idx-my-imports)
(member (car (list-ref result i)) (cdr lib/dep)))
(set! idx-my-imports i))
;; Does this one import lib?
(if (and
(< i idx-imports-me)
(member (car lib/dep) (cdr (list-ref result i))))
(set! idx-imports-me i))
(loop (+ i 1)))))
(loop 0)
;(pp `(JAE DEBUG ,result ,lib/dep ,idx-imports-me ,idx-my-imports))
(if (<= idx-my-imports idx-imports-me)
(list-insert-at! result lib/dep
(if (= idx-my-imports idx-imports-me)
idx-my-imports
(+ 1 idx-my-imports)))
(error "Internal error: unable to import library"))))
))
libs/deps)
(map car result)))