summaryrefslogtreecommitdiff
path: root/chickadee/graphics/seagull/pass-expand.scm
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/seagull/pass-expand.scm')
-rw-r--r--chickadee/graphics/seagull/pass-expand.scm310
1 files changed, 310 insertions, 0 deletions
diff --git a/chickadee/graphics/seagull/pass-expand.scm b/chickadee/graphics/seagull/pass-expand.scm
new file mode 100644
index 0000000..cbfd325
--- /dev/null
+++ b/chickadee/graphics/seagull/pass-expand.scm
@@ -0,0 +1,310 @@
+;;; Chickadee Game Toolkit
+;;; Copyright © 2023 David Thompson <dthompson2@worcester.edu>
+;;;
+;;; Licensed under the Apache License, Version 2.0 (the "License");
+;;; you may not use this file except in compliance with the License.
+;;; You may obtain a copy of the License at
+;;;
+;;; http://www.apache.org/licenses/LICENSE-2.0
+;;;
+;;; Unless required by applicable law or agreed to in writing, software
+;;; distributed under the License is distributed on an "AS IS" BASIS,
+;;; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+;;; See the License for the specific language governing permissions and
+;;; limitations under the License.
+
+;; The expansion pass transforms a tree of Seagull syntax objects into
+;; a tree of primitives in the format of the base intermediate
+;; language. The expander also takes care of "alpha conversion",
+;; which is a fancy math way of saying that all names are given new,
+;; unique names so that the compiler doesn't have to worry about name
+;; collisions, like if '+' refers to the primitive addition function
+;; or a lexical variable that shadows it.
+(define-module (chickadee graphics seagull pass-expand)
+ #:use-module (chickadee graphics seagull base)
+ #:use-module (chickadee graphics seagull primitives)
+ #:use-module (chickadee graphics seagull syntax)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:export (expand))
+
+;; Unique name generation.
+(define name-counter (make-parameter 0))
+
+(define (reset-name-counter!)
+ (name-counter 0))
+
+(define (next-name-id)
+ (let ((id (name-counter)))
+ (name-counter (+ id 1))
+ id))
+
+(define (fresh-name)
+ (string->symbol (format #f "t~a" (next-name-id))))
+
+(define (fresh-names names)
+ (map (lambda (_name) (fresh-name)) names))
+
+(define (alpha-convert names)
+ (define names* (map (lambda (_name) (fresh-name)) names))
+ (fold extend (fresh-environment) names names*))
+
+(define (expand:list exps stage env)
+ (map (lambda (x) (expand* x stage env)) exps))
+
+(define (expand:body body src stage env)
+ (match body
+ ;; Empty body, invalid.
+ (()
+ (seagull-syntax-error "body requires at least one expression"
+ (build-syntax src '())
+ expand:discard))
+ ;; Body with a single expression, just expand that expression.
+ ((exp)
+ (expand* exp stage env))
+ ;; Body with multiple expressions, expand to a sequence.
+ ((exps ...)
+ (make-sequence src (expand:list exps stage env)))))
+
+;; Lookup alpha-converted name for the given source name.
+(define (expand:reference name src stage env)
+ (let ((name* (lookup name env)))
+ (unless name*
+ (seagull-syntax-error "unbound variable"
+ (make-seagull-syntax src name)
+ expand:reference))
+ (make-lexical-reference src name name*)))
+
+(define (expand:if predicate consequent alternate src stage env)
+ (make-conditional src
+ (expand* predicate stage env)
+ (expand* consequent stage env)
+ (expand* alternate stage env)))
+
+(define (expand:function params body src stage env)
+ (define params* (fresh-names params))
+ (define env* (fold extend env params params*))
+ (make-function src params params*
+ (expand:body body src stage env*)))
+
+(define (expand:primcall operator arguments src stage env)
+ (let* ((op (lookup-primitive-operator operator))
+ (op:expand (primitive-operator-expand op)))
+ (op:expand arguments src (lambda (stx) (expand* stx stage env)))))
+
+(define (expand:call function arguments src stage env)
+ (make-call src (expand* function stage env)
+ (expand:list arguments stage env)))
+
+(define (expand:let names values body src stage env)
+ (match names
+ (()
+ (expand:body body src stage env))
+ (_
+ (let* ((values* (expand:list values stage env))
+ (names* (fresh-names names))
+ (env* (fold extend env names names*)))
+ (make-let src names names* values* (expand:body body src stage env*))))))
+
+(define (expand:let-values names values body src stage env)
+ (match names
+ (()
+ (expand:body body src stage env))
+ (_
+ (let* ((values* (expand:list values stage env))
+ (names* (map fresh-names names))
+ (env* (fold extend env (concatenate names) (concatenate names*))))
+ (make-let-values src names names* values* (expand:body body src stage env*))))))
+
+(define (expand:values exps src stage env)
+ (make-values src (expand:list exps stage env)))
+
+(define (expand:struct-ref value fields src stage env)
+ (define value* (expand* value stage env))
+ (match fields
+ ((field . rest)
+ (let loop ((fields rest)
+ (prev (make-struct-reference src value* field)))
+ (match fields
+ (() prev)
+ ((field . rest)
+ (loop rest (make-struct-reference src prev field))))))))
+
+(define (expand:array-ref value indices src stage env)
+ (define value* (expand* value stage env))
+ (match indices
+ ((i . rest)
+ (let ((i* (expand* i stage env)))
+ (let loop ((indices rest)
+ (prev (make-array-reference src value* i*)))
+ (match indices
+ (() prev)
+ ((j . rest)
+ (let ((j* (expand* j stage env)))
+ (loop rest (make-array-reference src prev j*))))))))))
+
+(define (expand:assignment name value src stage env)
+ (make-assignment src name (lookup name env) (expand* value stage env)))
+
+(define (expand:discard src stage)
+ (unless (fragment-stage? stage)
+ (seagull-syntax-error "'discard' is only permitted in fragment shaders"
+ (build-syntax src '(discard))
+ expand:discard)))
+
+;; Macros:
+(define (expand:let* bindings body src stage env)
+ (match bindings
+ (()
+ (expand* body stage env))
+ ((binding . rest)
+ (expand* (build-syntax (seagull-syntax-source binding)
+ `(let (,binding)
+ (let* ,rest ,body)))
+ stage env))))
+
+(define (expand:or exps src stage env)
+ (match exps
+ (()
+ (make-constant src #f))
+ ((exp)
+ (expand* exp stage env))
+ ((exp . rest)
+ (expand* (build-syntax src `(let ((x ,exp)) (if x x (or ,@rest))))
+ stage env))))
+
+(define (expand:and exps src stage env)
+ (match exps
+ (()
+ (make-constant src #t))
+ ((exp)
+ (expand* exp stage env))
+ ((exp . rest)
+ (expand* (build-syntax src `(if ,exp (and ,@rest) #f))
+ stage env))))
+
+(define (expand:cond clauses src stage env)
+ (define (cond->if clauses*)
+ (match clauses*
+ ;; Our version of 'cond' requires a final 'else' clause because
+ ;; the static type checker enforces that both branches of an
+ ;; 'if' must be the same type. If 'else' were optional then we
+ ;; wouldn't know what type the final alternate branch should be.
+ ((($ <syntax> _ (($ <syntax> _ 'else) exp)))
+ exp)
+ ((($ <syntax> _ (predicate consequent)) . rest)
+ (build-syntax src `(if ,predicate ,consequent ,(cond->if rest))))
+ (()
+ (seagull-syntax-error "'cond' form must end with 'else' clause"
+ (build-syntax src `(cond ,@clauses))
+ expand:cond))
+ (_
+ (seagull-syntax-error "invalid 'cond' form"
+ (build-syntax src `(cond ,@clauses))
+ expand:cond))))
+ (expand* (cond->if clauses) stage env))
+
+(define (expand:case key clauses src stage env)
+ (define (case->if clauses*)
+ (match clauses*
+ ;; Like our version of 'cond', our version of 'case' requires a
+ ;; final 'else' clause.
+ ((($ <syntax> _ (($ <syntax> _ 'else) exp)))
+ exp)
+ ((($ <syntax> _ (($ <syntax> _ (possibilities ..1)) consequent)) . rest)
+ (build-syntax src
+ `(if (or ,@(map (lambda (n) `(= key ,n)) possibilities))
+ ,consequent
+ ,(case->if rest))))
+ (()
+ (seagull-syntax-error "'case' form must end with 'else' clause"
+ (build-syntax src `(case ,key ,@clauses))
+ expand:case))
+ (_
+ (seagull-syntax-error "invalid 'case' form"
+ (build-syntax src `(case ,key ,@clauses))
+ expand:case))))
+ (expand* (build-syntax src
+ `(let ((key ,key))
+ ,(case->if clauses)))
+ stage env))
+
+;; Constant types are fundamental data types that need no compilation.
+(define (primitive-constant? x)
+ (or (exact-integer? x)
+ (float? x)
+ (boolean? x)))
+
+(define (primitive-operator-for-stage? name stage)
+ (let ((op (lookup-primitive-operator name)))
+ (and op (memq stage (primitive-operator-stages op)))))
+
+(define (expand* syntax stage env)
+ (define (lexically-bound? x)
+ (and (symbol? x) (bound? x env)))
+ (define (primop? x)
+ (and (symbol? x) (primitive-operator-for-stage? x stage)))
+ (define src (seagull-syntax-source syntax))
+ (match (seagull-syntax-expression syntax)
+ ;; Constants and variable references
+ ((? primitive-constant? c)
+ (make-constant src c))
+ ((? symbol? name)
+ (expand:reference name src stage env))
+ ;; Function call with a variable reference in operator position
+ ;; that *might* shadow a built-in, so we need to check for it
+ ;; early.
+ (((and ($ <syntax> name-src (? lexically-bound?)) function) arguments ...)
+ (expand:call function arguments src stage env))
+ ;; Primitive syntax
+ ((($ <syntax> _ 'if) predicate consequent alternate)
+ (expand:if predicate consequent alternate src stage env))
+ ((($ <syntax> _ 'lambda)
+ ($ <syntax> _ (($ <syntax> _ (? symbol? params)) ...))
+ body ...)
+ (expand:function params body src stage env))
+ ((($ <syntax> _ 'let)
+ ($ <syntax> _ (($ <syntax> _ (($ <syntax> _ (? symbol? names))
+ values))
+ ...))
+ body ...)
+ (expand:let names values body src stage env))
+ ((($ <syntax> _ 'let-values)
+ ($ <syntax> _ (($ <syntax> _ (($ <syntax> _ (($ <syntax> _ (? symbol? names)) ...))
+ values))
+ ...))
+ body ...)
+ (expand:let-values names values body src stage env))
+ ((($ <syntax> _ 'values) exps ...)
+ (expand:values exps src stage env))
+ ((($ <syntax> _ '->) value ($ <syntax> _ (? symbol? fields)) ..1)
+ (expand:struct-ref value fields src stage env))
+ ((($ <syntax> _ '@) value indices ..1)
+ (expand:array-ref value indices src stage env))
+ ((($ <syntax> _ 'set!) ($ <syntax> _ (? symbol? name)) value)
+ (expand:assignment name value src stage env))
+ ((($ <syntax> _ 'discard))
+ (expand:discard src stage))
+ ;; Built-in macros
+ ((($ <syntax> _ 'let*) ($ <syntax> _ (bindings ...)) body)
+ (expand:let* bindings body src stage env))
+ ((($ <syntax> _ 'and) exps ...)
+ (expand:and exps src stage env))
+ ((($ <syntax> _ 'or) exps ...)
+ (expand:or exps src stage env))
+ ((($ <syntax> _ 'cond) clauses ...)
+ (expand:cond clauses src stage env))
+ ((($ <syntax> _ 'case) key clauses ...)
+ (expand:case key clauses src stage env))
+ ;; Function calls
+ ((($ <syntax> _ (? primop? op)) arguments ...)
+ (expand:primcall op arguments src stage env))
+ ((function arguments ...)
+ (expand:call function arguments src stage env))
+ ;; Uh oh
+ (_
+ (seagull-syntax-error "unknown form" syntax expand*))))
+
+(define (expand syntax stage)
+ (expand* syntax stage (fresh-environment)))