summaryrefslogtreecommitdiff
path: root/chickadee/graphics/seagull
diff options
context:
space:
mode:
Diffstat (limited to 'chickadee/graphics/seagull')
-rw-r--r--chickadee/graphics/seagull/base.scm313
-rw-r--r--chickadee/graphics/seagull/cps.scm306
-rw-r--r--chickadee/graphics/seagull/glsl.scm151
-rw-r--r--chickadee/graphics/seagull/pass-expand.scm310
-rw-r--r--chickadee/graphics/seagull/pass-infer.scm182
-rw-r--r--chickadee/graphics/seagull/pass-linearize.scm256
-rw-r--r--chickadee/graphics/seagull/primitives.scm142
-rw-r--r--chickadee/graphics/seagull/syntax.scm95
-rw-r--r--chickadee/graphics/seagull/types.scm337
-rw-r--r--chickadee/graphics/seagull/utils.scm124
10 files changed, 2216 insertions, 0 deletions
diff --git a/chickadee/graphics/seagull/base.scm b/chickadee/graphics/seagull/base.scm
new file mode 100644
index 0000000..27f5886
--- /dev/null
+++ b/chickadee/graphics/seagull/base.scm
@@ -0,0 +1,313 @@
+;;; 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 base intermediate language for Seagull, before it is converted
+;; to continuation-passing style. No static type checking at this
+;; level. Analogous to Guile's Tree-IL.
+(define-module (chickadee graphics seagull base)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-9 gnu)
+ #:export (<constant>
+ make-constant
+ constant?
+ constant-source
+ constant-value
+
+ <lexical-reference>
+ make-lexical-reference
+ lexical-reference?
+ lexical-reference-source
+ lexical-reference-source-name
+ lexical-reference-name
+
+ <struct-reference>
+ make-struct-reference
+ struct-reference?
+ struct-reference-source
+ struct-reference-value
+ struct-reference-field
+
+ <array-reference>
+ make-array-reference
+ array-reference?
+ array-reference-source
+ array-reference-value
+ array-reference-index
+
+ <assignment>
+ make-assignment
+ assignment?
+ assignment-source
+ assignment-source-name
+ assignment-name
+ assignment-value
+
+ <values>
+ make-values
+ values?
+ values-source
+ values-expressions
+
+ <sequence>
+ make-sequence
+ sequence?
+ sequence-source
+ sequence-expressions
+
+ <let>
+ make-let
+ let?
+ let-source
+ let-source-names
+ let-names
+ let-expressions
+ let-body
+
+ <let-values>
+ make-let-values
+ let-values?
+ let-values-source
+ let-values-source-names
+ let-values-names
+ let-values-expressions
+ let-values-body
+
+ <conditional>
+ make-conditional
+ conditional?
+ conditional-source
+ conditional-predicate
+ conditional-consequent
+ conditional-alternate
+
+ <function>
+ make-function
+ function?
+ function-source
+ function-source-names
+ function-names
+ function-body
+
+ <primitive-call>
+ make-primitive-call
+ primitive-call?
+ primitive-call-source
+ primitive-call-name
+ primitive-call-arguments
+
+ <call>
+ make-call
+ call?
+ call-source
+ call-function
+ call-arguments
+
+ <discard>
+ make-discard
+ discard?
+ discard-source
+
+ base->sexp
+ sexp->base))
+
+(define-record-type <constant>
+ (make-constant source value)
+ constant?
+ (source constant-source)
+ (value constant-value))
+
+(define-record-type <lexical-reference>
+ (make-lexical-reference source source-name name)
+ lexical-reference?
+ (source lexical-reference-source)
+ (source-name lexical-reference-source-name)
+ (name lexical-reference-name))
+
+(define-record-type <struct-reference>
+ (make-struct-reference source value field)
+ struct-reference?
+ (source struct-reference-source)
+ (value struct-reference-value)
+ (field struct-reference-field))
+
+(define-record-type <array-reference>
+ (make-array-reference source value index)
+ array-reference?
+ (source array-reference-source)
+ (value array-reference-value)
+ (index array-reference-index))
+
+(define-record-type <assignment>
+ (make-assignment source source-name name value)
+ assignment?
+ (source assignment-source)
+ (source-name assignment-source-name)
+ (name assignment-name)
+ (value assignment-value))
+
+(define-record-type <values>
+ (make-values source expressions)
+ values?
+ (source values-source)
+ (expressions values-expressions))
+
+(define-record-type <sequence>
+ (make-sequence source expressions)
+ sequence?
+ (source sequence-source)
+ (expressions sequence-expressions))
+
+(define-record-type <let>
+ (make-let source source-names names expressions body)
+ let?
+ (source let-source)
+ (source-names let-source-names)
+ (names let-names)
+ (expressions let-expressions)
+ (body let-body))
+
+(define-record-type <let-values>
+ (make-let-values source source-names names expressions body)
+ let-values?
+ (source let-values-source)
+ (source-names let-values-source-names)
+ (names let-values-names)
+ (expressions let-values-expressions)
+ (body let-values-body))
+
+(define-record-type <conditional>
+ (make-conditional source predicate consequent alternate)
+ conditional?
+ (source conditional-source)
+ (predicate conditional-predicate)
+ (consequent conditional-consequent)
+ (alternate conditional-alternate))
+
+(define-record-type <function>
+ (make-function source source-names names body)
+ function?
+ (source function-source)
+ (source-names function-source-names)
+ (names function-names)
+ (body function-body))
+
+(define-record-type <primitive-call>
+ (make-primitive-call source name arguments)
+ primitive-call?
+ (source primitive-call-source)
+ (name primitive-call-name)
+ (arguments primitive-call-arguments))
+
+(define-record-type <call>
+ (make-call source function arguments)
+ call?
+ (source call-source)
+ (function call-function)
+ (arguments call-arguments))
+
+(define-record-type <discard>
+ (make-discard source)
+ discard?
+ (source discard-source))
+
+(define (base->sexp exp)
+ (match exp
+ (($ <constant> _ value)
+ `(constant ,value))
+ (($ <lexical-reference> _ name _)
+ `(lexical-reference ,name))
+ (($ <struct-reference> _ value field)
+ `(struct-reference ,(base->sexp value) ,field))
+ (($ <array-reference> _ value index)
+ `(array-reference ,(base->sexp value) ,index))
+ (($ <assignment> _ name _ value)
+ `(assignment ,name ,(base->sexp value)))
+ (($ <values> _ exps)
+ `(values ,@(map base->sexp exps)))
+ (($ <sequence> _ exps)
+ `(sequence ,@(map base->sexp exps)))
+ (($ <let> _ names _ exps body)
+ `(let ,(zip names (map base->sexp exps)) ,(base->sexp body)))
+ (($ <let-values> _ names _ exps body)
+ `(let-values ,(zip names (map base->sexp exps)) ,(base->sexp body)))
+ (($ <conditional> _ predicate consequent alternate)
+ `(conditional ,(base->sexp predicate)
+ ,(base->sexp consequent)
+ ,(base->sexp alternate)))
+ (($ <function> _ names _ body)
+ `(function ,names ,(base->sexp body)))
+ (($ <primitive-call> _ op args)
+ `(primitive-call ,op ,@(map base->sexp args)))
+ (($ <call> _ function args)
+ `(call ,(base->sexp function) ,@(map base->sexp args)))
+ (($ <discard> _)
+ '(discard))))
+
+;; Useful for tests.
+(define (sexp->base exp)
+ (match exp
+ (('constant value)
+ (make-constant #f value))
+ (('lexical-reference (? symbol? name))
+ (make-lexical-reference #f name name))
+ (('struct-reference value (? symbol? field))
+ (make-struct-reference #f (sexp->base value) field))
+ (('array-reference value index)
+ (make-array-reference #f (sexp->base value) (sexp->base index)))
+ (('assignment (? symbol? name) value)
+ (make-assignment #f name name (sexp->base value)))
+ (('values exps ...)
+ (make-values #f (map sexp->base exps)))
+ (('sequence exps ...)
+ (make-sequence #f (map sexp->base exps)))
+ (('let ((names exps) ...) body)
+ (make-let #f names names (map sexp->base exps) (sexp->base body)))
+ (('let-values (((names ...) exps) ...) body)
+ (make-let-values #f names names (map sexp->base exps) (sexp->base body)))
+ (('conditional predicate consequent alternate)
+ (make-conditional #f
+ (sexp->base predicate)
+ (sexp->base consequent)
+ (sexp->base alternate)))
+ (('function (names ...) body)
+ (make-function #f names names (sexp->base body)))
+ (('primitive-call (? symbol? op) args ...)
+ (make-primitive-call #f op (map sexp->base args)))
+ (('call function args ...)
+ (make-call #f (sexp->base function) (map sexp->base args)))
+ (('discard)
+ (make-discard #f))))
+
+(define (print-base exp port)
+ (format port "#<base ~a>" (base->sexp exp)))
+
+(set-record-type-printer! <constant> print-base)
+(set-record-type-printer! <lexical-reference> print-base)
+(set-record-type-printer! <struct-reference> print-base)
+(set-record-type-printer! <array-reference> print-base)
+(set-record-type-printer! <assignment> print-base)
+(set-record-type-printer! <values> print-base)
+(set-record-type-printer! <sequence> print-base)
+(set-record-type-printer! <let> print-base)
+(set-record-type-printer! <let-values> print-base)
+(set-record-type-printer! <conditional> print-base)
+(set-record-type-printer! <function> print-base)
+(set-record-type-printer! <primitive-call> print-base)
+(set-record-type-printer! <call> print-base)
+(set-record-type-printer! <discard> print-base)
diff --git a/chickadee/graphics/seagull/cps.scm b/chickadee/graphics/seagull/cps.scm
new file mode 100644
index 0000000..3c2f1fe
--- /dev/null
+++ b/chickadee/graphics/seagull/cps.scm
@@ -0,0 +1,306 @@
+;;; 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.
+
+;; A simple form of a continuation-passing style intermediate
+;; language. This intermediate representation provides a control flow
+;; graph. Inspired by Guile's CPS soup but simplified because our
+;; compilation target is a lot simpler than what a general purpose
+;; language needs to handle and also I'm a compiler baby and not a
+;; genius like Andy Wingo. Unlike Guile's CPS, Seagull's CPS is
+;; statically typed. The initial CPS conversion pass leaves the type
+;; annotations blank but the type inference pass must ultimately be
+;; able to assign types to all variables and functions or else the
+;; program is not valid.
+(define-module (chickadee graphics seagull cps)
+ #:use-module (chickadee graphics seagull types)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 match)
+ #:use-module (language cps intmap)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-9 gnu)
+ #:export (<arguments>
+ make-arguments
+ arguments?
+ arguments-names
+ arguments-term
+ arguments-types
+
+ <function-entry>
+ make-function-entry
+ function-entry?
+ function-entry-source
+ function-entry-params
+ function-entry-results
+ function-entry-start
+ function-entry-return
+ function-entry-type
+
+ <continue>
+ make-continue
+ continue?
+ continue-source
+ continue-label
+ continue-expression
+
+ <return>
+ make-return
+ return?
+ return-outputs
+
+ <branch>
+ make-branch
+ branch?
+ branch-source
+ branch-name
+ branch-consequent
+ branch-alternate
+
+ <discard>
+ make-discard
+ discard?
+ discard-source
+
+ <cps-constant>
+ make-cps-constant
+ cps-constant?
+ cps-constant-value
+ cps-constant-type
+
+ <cps-primitive-call>
+ make-cps-primitive-call
+ cps-primitive-call?
+ cps-primitive-call-operator
+ cps-primitive-call-arguments
+ cps-primitive-call-types
+
+ <cps-call>
+ make-cps-call
+ cps-call?
+ cps-call-function
+ cps-call-arguments
+ cps-call-type
+
+ <cps-values>
+ make-cps-values
+ cps-values?
+ cps-values-arguments
+
+ <cps-assignment>
+ make-cps-assignment
+ cps-assignment?
+ cps-assignment-variable
+ cps-assignment-value
+
+ <cps-function>
+ make-cps-function
+ cps-function?
+ cps-function-body
+
+ continuation?
+ term?
+ expression?
+ cps->sexp
+ sexp->cps
+ graph->sexp
+ sexp->graph))
+
+;; Continuations
+(define-record-type <arguments>
+ (make-arguments names term types)
+ arguments?
+ (names arguments-names)
+ (term arguments-term)
+ (types arguments-types))
+
+(define-record-type <function-entry>
+ (make-function-entry source params results start return type)
+ function-entry?
+ (source function-entry-source)
+ (params function-entry-params)
+ (results function-entry-results)
+ (start function-entry-start)
+ (return function-entry-return)
+ (type function-entry-type))
+
+(define (continuation? obj)
+ (or (arguments? obj)
+ (function-entry? obj)))
+
+;; Terms
+(define-record-type <continue>
+ (make-continue source label expression)
+ continue?
+ (source continue-source)
+ (label continue-label)
+ (expression continue-expression))
+
+(define-record-type <return>
+ (make-return outputs)
+ return?
+ (outputs return-outputs))
+
+(define-record-type <branch>
+ (make-branch source name consequent alternate)
+ branch?
+ (source branch-source)
+ (name branch-name)
+ (consequent branch-consequent)
+ (alternate branch-alternate))
+
+;; Discard is available only in fragment shaders and is used to
+;; terminate early and throw away the fragment, which means it has no
+;; continuation.
+(define-record-type <discard>
+ (make-discard source)
+ discard?
+ (source discard-source))
+
+(define (term? obj)
+ (or (continue? obj)
+ (branch? obj)
+ (discard? obj)))
+
+;; Expressions (prefixed with 'cps' to distinguish them from the
+;; tree-based IL when both modules are imported)
+(define-record-type <cps-constant>
+ (make-cps-constant value type)
+ cps-constant?
+ (value cps-constant-value)
+ ;; TODO: Unnecessary. Delete it.
+ (type cps-constant-type))
+
+(define-record-type <cps-primitive-call>
+ (make-cps-primitive-call operator arguments types)
+ cps-primitive-call?
+ (operator cps-primitive-call-operator)
+ (arguments cps-primitive-call-arguments)
+ ;; TODO: Unused. Delete it.
+ (types cps-primitive-call-types))
+
+(define-record-type <cps-call>
+ (make-cps-call function arguments types)
+ cps-call?
+ (function cps-call-function)
+ (arguments cps-call-arguments)
+ ;; TODO: Unused. Delete it.
+ (types cps-call-types))
+
+(define-record-type <cps-values>
+ (make-cps-values arguments)
+ cps-values?
+ (arguments cps-values-arguments))
+
+(define-record-type <cps-assignment>
+ (make-cps-assignment variable value)
+ cps-assignment?
+ (variable cps-assignment-variable)
+ (value cps-assignment-value))
+
+(define-record-type <cps-function>
+ (make-cps-function body)
+ cps-function?
+ (body cps-function-body))
+
+(define (expression? obj)
+ (or (cps-constant? obj)
+ (cps-primitive-call? obj)
+ (cps-call? obj)
+ (cps-values? obj)))
+
+(define (cps->sexp exp)
+ (match exp
+ (($ <arguments> names term types)
+ `(arguments ,names ,(cps->sexp term)))
+ (($ <function-entry> _ params results start return _)
+ `(function-entry ,params ,results ,start ,return))
+ (($ <return> outputs)
+ `(return ,@outputs))
+ (($ <continue> _ label exp)
+ `(continue ,label ,(cps->sexp exp)))
+ (($ <branch> _ name consequent alternate)
+ `(branch ,name ,consequent ,alternate))
+ (($ <discard> _)
+ '(discard))
+ (($ <cps-constant> val _)
+ `(constant ,val))
+ (($ <cps-primitive-call> op args _)
+ `(primitive-call ,op ,args))
+ (($ <cps-call> f args _)
+ `(call ,f ,args))
+ (($ <cps-values> args)
+ `(values ,@args))
+ (($ <cps-assignment> var val)
+ `(assignment ,var ,val))
+ (($ <cps-function> body)
+ `(function ,body))))
+
+(define (graph->sexp graph)
+ (intmap-fold-right (lambda (k cont memo)
+ (alist-cons k (cps->sexp cont) memo))
+ graph '()))
+
+(define (sexp->cps exp)
+ (match exp
+ (('arguments ((? exact-integer? names) ...) term)
+ (make-arguments names (sexp->cps term) #f))
+ (('function-entry (? exact-integer? name)
+ ((? exact-integer? params) ...)
+ ((? exact-integer? results) ...)
+ (? exact-integer? start)
+ (? exact-integer? return))
+ (make-function-entry #f params results start return #f))
+ (('continue (and (or (? exact-integer?) #f) label) exp)
+ (make-continue #f label (sexp->cps exp)))
+ (('return outputs ...)
+ (make-return outputs))
+ (('branch (? exact-integer? name)
+ (? exact-integer? consequent)
+ (? exact-integer? alternate))
+ (make-branch #f name consequent alternate))
+ (('discard)
+ (make-discard #f))
+ (('constant val)
+ (make-cps-constant val (type-for-constant val)))
+ (('primitive-call (? symbol? op) ((? exact-integer? args) ...))
+ (make-cps-primitive-call op args (list (fresh-type-variable))))
+ (('call (? exact-integer? f) ((? exact-integer? args) ...))
+ (make-cps-call f args (list (fresh-type-variable))))
+ (('values (? exact-integer? args) ...)
+ (make-cps-values args))
+ (('assignment (? exact-integer? var) (? exact-integer? val))
+ (make-cps-assignment var val))))
+
+(define (sexp->graph exp)
+ (fold (lambda (pair graph)
+ (match pair
+ (((? exact-integer? k) . exp*)
+ (intmap-add graph k (sexp->cps exp*)))))
+ empty-intmap exp))
+
+(define (print-cps cps port)
+ (format port "<cps ~a>" (cps->sexp cps)))
+
+(set-record-type-printer! <arguments> print-cps)
+(set-record-type-printer! <function-entry> print-cps)
+(set-record-type-printer! <return> print-cps)
+(set-record-type-printer! <continue> print-cps)
+(set-record-type-printer! <branch> print-cps)
+(set-record-type-printer! <discard> print-cps)
+(set-record-type-printer! <cps-constant> print-cps)
+(set-record-type-printer! <cps-primitive-call> print-cps)
+(set-record-type-printer! <cps-call> print-cps)
+(set-record-type-printer! <cps-values> print-cps)
+(set-record-type-printer! <cps-function> print-cps)
diff --git a/chickadee/graphics/seagull/glsl.scm b/chickadee/graphics/seagull/glsl.scm
new file mode 100644
index 0000000..50f75d6
--- /dev/null
+++ b/chickadee/graphics/seagull/glsl.scm
@@ -0,0 +1,151 @@
+;;; 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.
+
+;; CPS to GLSL emitter.
+(define-module (chickadee graphics seagull glsl)
+ #:use-module (chickadee graphics seagull cps)
+ #:use-module (chickadee graphics seagull primitives)
+ #:use-module (chickadee graphics seagull types)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 match)
+ #:use-module (language cps intmap)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-11)
+ #:export (emit-glsl))
+
+(define (indent level port)
+ (unless (= level 0)
+ (display " " port)
+ (indent (- level 1) port)))
+
+(define (variable-name id)
+ (format #f "v~a" id))
+
+(define (emit-glsl* graph k exp level port)
+ (define (emit-exp exp var type)
+ (match (pk 'emit-exp exp)
+ (#f #f)
+ (($ <cps-constant> val _)
+ (indent level port)
+ (format port "~a ~a = ~a;\n"
+ (type-glsl-name type)
+ (variable-name var)
+ (match val
+ (#t "true")
+ (#f "false")
+ (_ val))))
+ (($ <cps-values> (arg))
+ (indent level port)
+ (format port "~a ~a = ~a;\n"
+ (type-glsl-name type)
+ (variable-name var)
+ (variable-name arg)))
+ (($ <cps-assignment> from to)
+ (indent level port)
+ (format port "~a = ~a;\n" (variable-name from) (variable-name to)))
+ (($ <cps-primitive-call> op args _)
+ (let ((op* (lookup-primitive-operator op)))
+ (indent level port)
+ (format port "~a ~a = "
+ (type-glsl-name type)
+ (variable-name var))
+ (if (primitive-operator-infix? op*)
+ (match args
+ ;; Infix binary operator
+ ((a b)
+ (format port "~a ~a ~a;\n"
+ (variable-name a)
+ (primitive-operator-glsl-name op*)
+ (variable-name b))))
+ ;; Regular function call
+ (begin
+ (format port "~a(" (primitive-operator-glsl-name op*))
+ (let loop ((args args))
+ (match args
+ (() #t)
+ ((arg)
+ (display (variable-name arg) port))
+ ((arg . rest)
+ (format port "~a, " (variable-name arg))
+ (loop rest))))
+ (format port ");\n")))))
+ (($ <cps-function> body)
+ (match (intmap-ref graph body)
+ (($ <function-entry> _ params results start return type)
+ (indent level port)
+ (format port "function ~a(~a) {\n"
+ (variable-name var)
+ (string-join (append (map (lambda (param type)
+ (param-string 'in param type))
+ params (function-type-parameters type))
+ (map (lambda (result type)
+ (param-string 'out result type))
+ results (function-type-returns type)))
+ ", "))
+ (pk 'start-function-body)
+ (emit-glsl* graph start #f (+ level 1) port)
+ (pk 'done-function-body)
+ (indent level port)
+ (format port "}\n"))))))
+ (define (param-string qualifier var type)
+ (format #f "~a ~a ~a"
+ qualifier
+ (type-glsl-name type)
+ (variable-name var)))
+ (match (pk 'emit-cont (intmap-ref graph k))
+ (($ <arguments> () ($ <return> ()) ())
+ (indent level port)
+ (format port "return;\n"))
+ (($ <arguments> (var) ($ <return> (result)) (type))
+ (emit-exp exp var type)
+ (indent level port)
+ (format port "return;\n"))
+ (($ <arguments> (var) ($ <return> ()) (type))
+ (pk 'blah exp type)
+ (emit-exp exp var type)
+ (indent level port)
+ (indent level port)
+ (format port "return;\n"))
+ (($ <arguments> () ($ <continue> _ k* exp*) _)
+ (emit-exp exp #f #f)
+ (emit-glsl* graph k* exp* level port))
+ (($ <arguments> (var) ($ <continue> _ k* exp*) (type))
+ (emit-exp exp var type)
+ (emit-glsl* graph k* exp* level port))
+ (($ <arguments> (var) ($ <branch> _ test k-conseq k-alt) (type))
+ (emit-exp exp var type)
+ (indent level port)
+ (format port "if(~a) {\n" (variable-name test))
+ (emit-glsl* graph k-conseq #f (+ level 1) port)
+ (format port "} else {\n")
+ (emit-glsl* graph k-alt #f (+ level 1) port)
+ (format port "}\n"))))
+
+(define (emit-glsl graph)
+ (call-with-output-string
+ (lambda (port)
+ (emit-glsl* graph 0 #f 0 port))))
+
+;; (define test-cps
+;; (let ((int (lookup-type 'int)))
+;; (alist->intmap
+;; `((0 . ,(make-arguments '() (make-continue #f 1 (make-cps-constant 1))))
+;; (1 . ,(make-arguments '(0) (make-continue #f 3 (make-cps-constant 2))))
+;; (2 . ,(make-arguments '(1) (make-continue #f 5 (make-cps-primitive-call '+ '(0 1) int))))
+;; (3 . ,(make-arguments '(3) (make-continue #f 4 (make-cps-constant 3))))
+;; (4 . ,(make-arguments '(4) (make-continue #f 2 (make-cps-primitive-call '* '(3 4) int))))
+;; (5 . ,(make-arguments '(5) (make-continue #f #f (make-cps-values '(5)))))))))
+
+;; (emit-glsl test-cps)
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)))
diff --git a/chickadee/graphics/seagull/pass-infer.scm b/chickadee/graphics/seagull/pass-infer.scm
new file mode 100644
index 0000000..0ac204e
--- /dev/null
+++ b/chickadee/graphics/seagull/pass-infer.scm
@@ -0,0 +1,182 @@
+;;; 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.
+
+;; Walk the CPS control flow graph and solve for all of the type
+;; variables using a variant of the Hindley-Milner type inference
+;; algorithm extended to handle qualified types (types with
+;; predicates.) GLSL is a statically typed language, but thanks to
+;; type inference the user doesn't have to specify any types expect
+;; for shader inputs, outputs, and uniforms.
+
+;; Dedicated to the memory of Ian Denhardt (zenhack), who pointed me
+;; towards type predicate systems and answered my questions on the
+;; fediverse. That was the critical piece that I, someone who knows
+;; little about static typing, needed to extend traditional
+;; Hindley-Milner type inference to work with GLSL's function
+;; overloading.
+(define-module (chickadee graphics seagull pass-infer)
+ #:use-module (chickadee graphics seagull cps)
+ #:use-module (chickadee graphics seagull primitives)
+ #:use-module (chickadee graphics seagull syntax)
+ #:use-module (chickadee graphics seagull types)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 match)
+ #:use-module (language cps intmap)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-11)
+ #:export (infer))
+
+(define bool (lookup-type 'bool))
+(define int (lookup-type 'int))
+
+(define &seagull-type-error
+ (make-exception-type '&seagull-type-error &error '()))
+
+(define make-seagull-type-error
+ (record-constructor &seagull-type-error))
+
+(define (seagull-type-error msg args src origin)
+ (raise-exception
+ (make-exception
+ (make-seagull-type-error)
+ (make-exception-with-origin origin)
+ (make-exception-with-message
+ (format #f "seagull type error at ~a: ~a"
+ (sourcev->string src)
+ msg))
+ (make-exception-with-irritants args))))
+
+(define (infer:values vals graph env subs)
+ (values graph
+ (map (lambda (var) (lookup var env)) vals)
+ subs))
+
+(define (infer:assignment var val graph env subs)
+ (let ((subs* (unify (lookup var env) (lookup val env))))
+ (values graph '() (compose-substitutions subs subs*))))
+
+(define (infer:primitive-call op args graph env subs)
+ (let* ((return-types (list (fresh-type-variable)))
+ (call-type (make-function-type
+ (map (lambda (arg) (lookup arg env)) args)
+ return-types))
+ (call-subs (unify call-type (primitive-operator-type op)))
+ (subs* (compose-substitutions subs call-subs)))
+ (values graph (substitute-types subs* return-types) subs*)))
+
+(define (infer* graph k types result-types env subs)
+ (define (type-mismatch-handler src e)
+ (let ((args (exception-args e)))
+ (match args
+ (((a ...) (b ...))
+ (seagull-type-error (format #f "expected ~a, got ~a"
+ (map type-name b) (map type-name a))
+ args src infer*))
+ ((a b)
+ (seagull-type-error (format #f "expected ~a, got ~a"
+ (type-name b) (type-name a))
+ args src infer*)))))
+ (define (with-error-handling src thunk)
+ (with-exception-handler (lambda (e) (type-mismatch-handler src e))
+ thunk
+ #:unwind? #t
+ #:unwind-for-type 'type-mismatch))
+ (define (infer-exp exp env)
+ (match (pk 'infer-exp exp)
+ (($ <cps-constant> _ type)
+ (values graph (list type) subs))
+ (($ <cps-values> vals)
+ (infer:values vals graph env subs))
+ (($ <cps-assignment> var val)
+ (infer:assignment var val graph env subs))
+ (($ <cps-primitive-call> (= lookup-primitive-operator op) args _)
+ (infer:primitive-call op args graph env subs))
+ (($ <cps-function> body)
+ (infer* graph body '() '() env subs))))
+ (define (infer-term term env)
+ (match (pk 'infer-term term)
+ ;; Regular continuation.
+ (($ <continue> src k* exp)
+ (with-error-handling src
+ (lambda ()
+ (let-values (((graph* types* subs*) (infer-exp exp env)))
+ (infer* graph* k* types* result-types env subs*)))))
+ ;; Function exit.
+ (($ <return> results)
+ (let* ((return-types (map (lambda (var) (lookup var env)) results))
+ ;; TODO: Unify with the return types of the current function.
+ ;;(expected-result-types result-types)
+ (subs* (unify return-types result-types)))
+ (values graph result-types (compose-substitutions subs subs*)))
+ (values graph types subs))
+ ;; Conditional branch.
+ (($ <branch> src name k-conseq k-alt)
+ (with-error-handling src
+ (lambda ()
+ ;; Type checking a branch goes like this:
+ ;; 1) Unify type of test variable with bool.
+ ;; 2) Infer types of consequent and alternate.
+ ;; 3) Unify types of consequent and alternate.
+ (let*-values (((subs1) (unify (lookup name env) bool))
+ ((subs2) (compose-substitutions subs subs1))
+ ((graph1 conseq-types subs3)
+ (infer* graph k-conseq types result-types env subs2))
+ ((graph2 alt-types subs4)
+ (infer* graph1 k-alt types result-types env subs3))
+ ((subs5) (unify conseq-types alt-types))
+ ((subs6) (compose-substitutions subs4 subs5)))
+ (values graph2 conseq-types subs6)))))))
+ (pk 'subs subs)
+ (pk 'env env)
+ (match (intmap-ref graph k)
+ (($ <arguments> names term _)
+ ;; Add newly defined variables to type environment, then infer
+ ;; the term in that environment.
+ (let*-values (((env*) (fold extend env names types))
+ ((graph* types* subs*) (infer-term term env*))
+ ((subs2) (unify types types*)))
+ (pk 'term-subs subs*)
+ (pk 'term-types 'before types 'after types*)
+ (values (intmap-replace graph* k (make-arguments names term types*))
+ types*
+ subs*
+ ;; (compose-substitutions subs* subs2)
+ )))
+ (($ <function-entry> src params results start return _)
+ (pk 'infer-function src params results start return)
+ ;; We don't know the type signature yet, so params and results
+ ;; are all type variables.
+ (let* ((param-types (fresh-type-variables params))
+ (result-types (fresh-type-variables results))
+ (func-type (make-function-type param-types result-types))
+ ;; Add params and results to type environment.
+ (env* (fold extend env
+ (append params results)
+ (append param-types result-types))))
+ ;; Infer types of function body using new type environment.
+ (define-values (graph* types* subs*)
+ (infer* graph start types result-types env* subs))
+ ;; Apply substitutions to function type.
+ ;; TODO: Handle polymorphism and type predicates.
+ (let* ((func-type* (substitute-type subs* func-type))
+ (func (make-function-entry src params results start return
+ func-type)))
+ (values (intmap-replace graph* k func)
+ (list func-type*)
+ subs*))))))
+
+(define (infer graph)
+ (infer* graph 0 '() '() (fresh-environment) no-substitutions))
diff --git a/chickadee/graphics/seagull/pass-linearize.scm b/chickadee/graphics/seagull/pass-linearize.scm
new file mode 100644
index 0000000..9735a90
--- /dev/null
+++ b/chickadee/graphics/seagull/pass-linearize.scm
@@ -0,0 +1,256 @@
+;;; 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 linearization pass transforms a tree of base IL objects into a
+;; control flow graph of continuations. Extremely based on Guile's
+;; own "CPS soup" but greatly simplified for this much simpler
+;; language and also my own inability to understand something more
+;; complicated.
+(define-module (chickadee graphics seagull pass-linearize)
+ #:use-module (chickadee graphics seagull base)
+ #:use-module (chickadee graphics seagull cps)
+ #:use-module (chickadee graphics seagull types)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 match)
+ #:use-module (language cps intmap)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-11)
+ #:export (linearize))
+
+;; Unique variable generation.
+(define var-counter (make-parameter 0))
+
+(define (reset-var-counter!)
+ (var-counter 0))
+
+(define (next-var)
+ (let ((var (var-counter)))
+ (var-counter (+ var 1))
+ var))
+
+(define (fresh-var)
+ (next-var))
+
+(define (fresh-vars lst)
+ (map (lambda (_x) (fresh-var)) lst))
+
+(define (make-fresh-vars n)
+ (list-tabulate n (lambda (_i) (fresh-var))))
+
+;; Unique continuation label generation
+(define label-counter (make-parameter 0))
+
+(define (reset-label-counter!)
+ (label-counter 0))
+
+(define (fresh-label)
+ (let ((label (label-counter)))
+ (label-counter (+ label 1))
+ label))
+
+(define (num-values exp)
+ (match exp
+ ((or (? assignment?)
+ (? discard?))
+ 0)
+ ((or (? constant?)
+ (? lexical-reference?)
+ (? struct-reference?)
+ (? array-reference?)
+ (? function?)
+ (? primitive-call?)
+ ($ <values> (_)))
+ 1)
+ ;; Number of values is unknown at this stage. The type inference
+ ;; pass will have to figure it out.
+ (_ #f)))
+
+(define (compile:constant src val graph k)
+ (let* ((type (type-for-constant val))
+ (const (make-cps-constant val type))
+ (term (make-continue src k const)))
+ (values graph term)))
+
+(define (compile:conditional src predicate consequent alternate env graph k)
+ (let ((branch-k (fresh-label))
+ (consequent-k (fresh-label))
+ (alternate-k (fresh-label)))
+ (let*-values (((graph1 predicate-term)
+ (compile-cps predicate env graph branch-k))
+ ((graph2 consequent-term)
+ (compile-cps consequent env graph1 k))
+ ((graph3 alternate-term)
+ (compile-cps alternate env graph2 k)))
+ (let* ((branch-var (fresh-var))
+ (branch-term (make-branch src branch-var consequent-k alternate-k))
+ (branch-cont (make-arguments (list branch-var) branch-term #f))
+ (consequent-cont (make-arguments '() consequent-term #f))
+ (alternate-cont (make-arguments '() alternate-term #f)))
+ (values (intmap-add (intmap-add (intmap-add graph3 branch-k branch-cont)
+ consequent-k consequent-cont)
+ alternate-k alternate-cont)
+ predicate-term)))))
+
+;; Converting a linear sequence of expressions to continuation passing
+;; style requires an algorithm written in continuation passing style.
+;; We have to traverse to the end of the list, and then chain together
+;; the continuations as the algorithm works backwards.
+(define (compile:arguments exps env graph return)
+ (match exps
+ (()
+ (return '() graph))
+ ((exp . rest)
+ (let*-values (((name) (fresh-var))
+ ((graph* next-term)
+ (compile:arguments rest env graph
+ (lambda (names graph)
+ (return (cons name names) graph))))
+ ((k) (fresh-label))
+ ((cont) (make-arguments (list name) next-term #f))
+ ((graph** this-term)
+ (compile-cps exp env graph* k)))
+ (values (intmap-add graph** k cont)
+ this-term)))))
+
+(define (compile:primitive-call src name args env graph k)
+ (compile:arguments args env graph
+ (lambda (vars graph*)
+ (let* ((type (fresh-type-variable))
+ (prim (make-cps-primitive-call name vars type))
+ (term (make-continue src k prim)))
+ (values graph* term)))))
+
+(define (compile:let src names exps body env graph k)
+ (compile:arguments exps env graph
+ (lambda (vars graph*)
+ (let ((env* (fold extend env names vars)))
+ (compile-cps body env* graph* k)))))
+
+(define (compile:lexical-reference src name env graph k)
+ (let ((vals (make-cps-values (list (lookup name env)))))
+ (values graph (make-continue src k vals))))
+
+(define (compile:assignment src name value env graph k)
+ (pk 'assignment name value)
+ (let ((k* (fresh-label))
+ (var (lookup name env)))
+ (let*-values (((graph* term)
+ (compile-cps value env graph k*)))
+ (let* ((val (fresh-var))
+ (assign (make-cps-assignment var val))
+ (term* (make-continue src k assign))
+ (cont (make-arguments (list val) term* #f)))
+ (values (intmap-add graph* k* cont) term)))))
+
+(define (compile:sequence exps env graph k)
+ ;; (match exps
+ ;; (()
+ ;; (return '() graph))
+ ;; ((exp . rest)
+ ;; (let*-values (((names) (and=> (num-values exp) make-fresh-vars))
+ ;; ((graph* next-term)
+ ;; (compile:arguments rest env graph
+ ;; (lambda (graph)
+ ;; (return graph))))
+ ;; ((k) (fresh-label))
+ ;; ((cont) (make-arguments (list name) next-term #f))
+ ;; ((graph** this-term)
+ ;; (compile-cps exp env graph* k)))
+ ;; (values (intmap-add graph** k cont)
+ ;; this-term))))
+
+ (match exps
+ ((exp)
+ (pk 'seq exp)
+ (compile-cps exp env graph k))
+ ((exp . rest)
+ (pk 'seq exp)
+ (let ((vars (pk 'seq-vars (and=> (pk 'num-values exp (num-values exp)) make-fresh-vars))))
+ (let-values (((graph* term)
+ (compile:sequence rest env graph k)))
+ (let* ((k* (fresh-label))
+ (cont (make-arguments vars term #f)))
+ (compile-cps exp env (intmap-add graph* k* cont) k*)))))))
+
+'((0 . (arguments () (continue 4 (constant 1))))
+ (1 . (arguments (2) (return 2)))
+ (2 . (arguments () (continue 1 (values 0))))
+ (3 . (arguments (1) (continue 2 (assignment 0 1))))
+ (4 . (arguments (0) (continue 3 (constant 2)))))
+
+(define (compile-cps exp env graph k)
+ (match (pk 'compile exp)
+ (($ <constant> src val)
+ (compile:constant src val graph k))
+ (($ <conditional> src predicate consequent alternate)
+ (compile:conditional src predicate consequent alternate env graph k))
+ (($ <primitive-call> src name args)
+ (compile:primitive-call src name args env graph k))
+ (($ <sequence> src exps)
+ (compile:sequence exps env graph k))
+ (($ <let> src _ names exps body)
+ (compile:let src names exps body env graph k))
+ (($ <lexical-reference> src _ name)
+ (compile:lexical-reference src name env graph k))
+ (($ <assignment> src _ name value)
+ (compile:assignment src name value env graph k))))
+
+;; (define (term-arity term)
+;; (define (exp-arity exp)
+;; (match exp
+;; (($ <cps-constant> _ _)
+;; 1)
+;; (($ <cps-assignment> _ _)
+;; 0)))
+;; (match term
+;; (($ <continue> _ _ exp)
+;; (exp-arity exp))
+;; (($ ))))
+
+(define-syntax intmap-add*
+ (syntax-rules ()
+ ((_ intmap) intmap)
+ ((_ intmap (i v) rest ...)
+ (intmap-add (intmap-add* intmap rest ...) i v))))
+
+(define (linearize exp)
+ (parameterize ((var-counter 0)
+ (label-counter 0))
+ ;; The initial continuation is always at index 0.
+ (let* ((init-label (fresh-label))
+ (func-return-label (fresh-label)))
+ (let-values (((graph term)
+ (compile-cps exp (fresh-environment) empty-intmap func-return-label)))
+ (let* ((func-start-label (fresh-label))
+ (func-label (fresh-label))
+ (end-label (fresh-label))
+ (params '())
+ (results (list (fresh-var)))
+ (init-term (make-continue #f end-label (make-cps-function func-label)))
+ (init-cont (make-arguments '() init-term #f))
+ (func-cont (make-function-entry #f params results func-start-label func-return-label #f))
+ (func-start-cont (make-arguments '() term #f))
+ (func-return-term (make-return results))
+ (func-return-cont (make-arguments results func-return-term #f))
+ (end-results (list (fresh-var)))
+ (end-term (make-return '()))
+ (end-cont (make-arguments end-results end-term #f)))
+ (intmap-add* graph
+ (init-label init-cont)
+ (func-label func-cont)
+ (func-start-label func-start-cont)
+ (func-return-label func-return-cont)
+ (end-label end-cont)))))))
diff --git a/chickadee/graphics/seagull/primitives.scm b/chickadee/graphics/seagull/primitives.scm
new file mode 100644
index 0000000..dc74a5f
--- /dev/null
+++ b/chickadee/graphics/seagull/primitives.scm
@@ -0,0 +1,142 @@
+;;; 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.
+
+(define-module (chickadee graphics seagull primitives)
+ #:use-module (chickadee graphics seagull base)
+ #:use-module (chickadee graphics seagull syntax)
+ #:use-module (chickadee graphics seagull types)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:export (make-primitive-operator
+ define-primitive-operator
+ lookup-primitive-operator
+ primitive-operator?
+ primitive-operator-name
+ primitive-operator-glsl-name
+ primitive-operator-stages
+ primitive-operator-arity
+ primitive-operator-expand
+ primitive-operator-infix?
+ primitive-operator-type))
+
+(define-record-type <primitive-operator>
+ (%make-primitive-operator name glsl-name stages arity expand infix? type)
+ primitive-operator?
+ (name primitive-operator-name)
+ (glsl-name primitive-operator-glsl-name)
+ (stages primitive-operator-stages)
+ (arity primitive-operator-arity)
+ (expand primitive-operator-expand)
+ (infix? primitive-operator-infix?)
+ (type primitive-operator-type))
+
+(define (make-default-expander name)
+ (lambda (args src expand)
+ (make-primitive-call src name (map expand args))))
+
+(define* (make-primitive-operator #:key name type proc arity
+ (glsl-name name)
+ (stages (list stage:vertex stage:fragment))
+ (expand (make-default-expander name))
+ infix?)
+ (%make-primitive-operator name glsl-name stages arity expand infix? type))
+
+(define *primitive-operators* (make-hash-table))
+
+(define (register-primitive-operator! primitive-operator)
+ (hashq-set! *primitive-operators*
+ (primitive-operator-name primitive-operator)
+ primitive-operator)
+ *unspecified*)
+
+(define (lookup-primitive-operator name)
+ (hashq-ref *primitive-operators* name))
+
+(define-syntax-rule (define-primitive-operator name args ...)
+ (register-primitive-operator!
+ (make-primitive-operator #:name 'name args ...)))
+
+(define int (lookup-type 'int))
+(define -> make-function-type)
+
+(define-primitive-operator +
+ #:arity 2
+ #:type (-> (list int int) (list int))
+ #:infix? #t
+ #:expand
+ (lambda (args src expand)
+ (let loop ((args args))
+ (match args
+ (()
+ (make-constant src 0))
+ ((n)
+ (expand n))
+ ((n . rest)
+ (make-primitive-call src '+ (list (expand n) (loop rest))))))))
+
+(define-primitive-operator -
+ #:arity 2
+ #:type (-> (list int int) (list int))
+ #:infix? #t
+ #:expand
+ (lambda (args src expand)
+ (let loop ((args args))
+ (match args
+ ((n) (make-primitive-call src '- (list (expand n) (make-constant src 0))))
+ ((m n)
+ (make-primitive-call src '- (list (expand m) (expand n))))
+ ((n . rest)
+ (make-primitive-call src '- (list (expand n) (loop rest))))))))
+
+(define-primitive-operator *
+ #:arity 2
+ #:type (-> (list int int) (list int))
+ #:infix? #t
+ #:expand
+ (lambda (args src expand)
+ (let loop ((args args))
+ (match args
+ (() (make-constant src 1))
+ ((n) (expand n))
+ ((n . rest)
+ (make-primitive-call src '* (list (expand n) (loop rest))))))))
+
+(define-primitive-operator /
+ #:arity 2
+ #:type (-> (list int int) (list int))
+ #:infix? #t
+ #:expand
+ (lambda (args src expand)
+ (match args
+ ((n)
+ (make-primitive-call src '/ (list (make-constant src 1) (expand n))))
+ ((m n)
+ (make-primitive-call src '/ (list (expand m) (expand n))))
+ ((m n . rest)
+ (let loop ((rest rest)
+ (exp (make-primitive-call src '/ (list (expand m) (expand n)))))
+ (match rest
+ ((l)
+ (make-primitive-call src '/ (list exp (expand l))))
+ ((l . rest)
+ (loop rest (make-primitive-call src '/ (list exp (expand l)))))))))))
+
+(define-primitive-operator = #:arity 2 #:infix? #t)
+
+(define-primitive-operator vec2 #:arity 2)
+(define-primitive-operator vec3 #:arity 3)
+(define-primitive-operator vec4 #:arity 4)
diff --git a/chickadee/graphics/seagull/syntax.scm b/chickadee/graphics/seagull/syntax.scm
new file mode 100644
index 0000000..81876b4
--- /dev/null
+++ b/chickadee/graphics/seagull/syntax.scm
@@ -0,0 +1,95 @@
+;;; 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.
+
+(define-module (chickadee graphics seagull syntax)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-9)
+ #:use-module (system syntax internal)
+ #:export (<syntax>
+ make-seagull-syntax
+ seagull-syntax?
+ seagull-syntax-source
+ seagull-syntax-expression
+ seagull-syntax->sexp
+ build-syntax
+ $gl
+ &seagull-syntax-error
+ seagull-syntax-error
+ seagull-syntax-error-syntax))
+
+(define-record-type <syntax>
+ (make-seagull-syntax source exp)
+ seagull-syntax?
+ (source seagull-syntax-source)
+ (exp seagull-syntax-expression))
+
+(define (seagull-syntax->sexp syntax)
+ (match (seagull-syntax-expression syntax)
+ ((children ...)
+ (map seagull-syntax->sexp children))
+ (atom atom)))
+
+(define (build-syntax src exp)
+ (match exp
+ ((? seagull-syntax?)
+ exp)
+ ((children ...)
+ (make-seagull-syntax src
+ (map (lambda (child)
+ (build-syntax src child))
+ children)))
+ (atom
+ (make-seagull-syntax src atom))))
+
+;; Annotate seagull expressions with source information, producing
+;; seagull-specific syntax objects that can't be confused with scheme
+;; syntax objects.
+(define-syntax $gl
+ (lambda (x)
+ (syntax-case x ()
+ ((_ exp)
+ (with-syntax ((src (datum->syntax x (syntax-sourcev #'exp))))
+ #'(make-seagull-syntax src ($gl-recur exp)))))))
+
+;; Helper macro to annotate nested expressions.
+(define-syntax $gl-recur
+ (syntax-rules ()
+ ((_ (item ...))
+ (list ($gl item) ...))
+ ((_ atom)
+ 'atom)))
+
+(define &seagull-syntax-error
+ (make-exception-type '&seagull-syntax-error &error '(syntax)))
+
+(define make-seagull-syntax-error
+ (record-constructor &seagull-syntax-error))
+
+(define seagull-syntax-error-syntax
+ (exception-accessor &seagull-syntax-error
+ (record-accessor &seagull-syntax-error 'syntax)))
+
+(define (seagull-syntax-error msg syntax origin)
+ (raise-exception
+ (make-exception
+ (make-seagull-syntax-error syntax)
+ (make-exception-with-origin origin)
+ (make-exception-with-message
+ (format #f "Syntax error at ~a: ~a"
+ (sourcev->string (seagull-syntax-source syntax))
+ msg))
+ (make-exception-with-irritants (list (seagull-syntax->sexp syntax))))))
diff --git a/chickadee/graphics/seagull/types.scm b/chickadee/graphics/seagull/types.scm
new file mode 100644
index 0000000..095d1a8
--- /dev/null
+++ b/chickadee/graphics/seagull/types.scm
@@ -0,0 +1,337 @@
+;;; 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 Seagull static type system.
+(define-module (chickadee graphics seagull types)
+ #:use-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 match)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:use-module (srfi srfi-9 gnu)
+ #:export (lookup-type
+ base-type-environment
+
+ type-variable-counter
+ fresh-type-variable
+ fresh-type-variables
+ make-type-variable
+ type-variable?
+ type-variable-id
+
+ primitive-type?
+ primitive-type-name
+ primitive-type-glsl-name
+
+ <function-type>
+ make-function-type
+ function-type?
+ function-type-parameters
+ function-type-returns
+
+ type?
+ type-name
+ type-glsl-name
+ type-for-constant
+
+ make-substitutions
+ substitutions?
+ unbox-substitutions
+ no-substitutions
+ add-substitution
+ substitute-type
+ substitute-types
+ compose-substitutions
+ occurs?
+ unify))
+
+
+;;;
+;;; Built-in type registry
+;;;
+
+(define *types* (make-hash-table))
+
+(define (lookup-type name)
+ (hashq-ref *types* name))
+
+(define (register-type! name type)
+ (hashq-set! *types* name type))
+
+(define (base-type-environment)
+ (hash-fold extend (fresh-environment) *types*))
+
+
+;;;
+;;; Type variables
+;;;
+
+(define-record-type <type-variable>
+ (make-type-variable id)
+ type-variable?
+ (id type-variable-id))
+
+(set-record-type-printer! <type-variable>
+ (lambda (tvar port)
+ (format port "#<type-variable ~a>"
+ (type-variable-id tvar))))
+
+(define type-variable-counter (make-parameter 0))
+
+(define (fresh-type-variable)
+ (let ((id (type-variable-counter)))
+ (type-variable-counter (+ id 1))
+ (make-type-variable id)))
+
+(define (fresh-type-variables lst)
+ (map (lambda (_x) (fresh-type-variable)) lst))
+
+
+;;;
+;;; Primitive types
+;;;
+
+(define-record-type <primitive-type>
+ (make-primitive-type name glsl-name)
+ primitive-type?
+ (name primitive-type-name)
+ (glsl-name primitive-type-glsl-name))
+
+(set-record-type-printer! <primitive-type>
+ (lambda (t port)
+ (format port "#<primitive-type ~a>"
+ (primitive-type-name t))))
+
+(define-syntax define-primitive-type
+ (syntax-rules ()
+ ((_ name)
+ (register-type! 'name (make-primitive-type 'name (symbol->string 'name))))
+ ((_ name glsl-name)
+ (register-type! 'name (make-primitive-type 'name glsl-name)))))
+
+(define-primitive-type bool)
+(define-primitive-type int)
+(define-primitive-type float)
+(define-primitive-type mat3)
+(define-primitive-type mat4)
+(define-primitive-type sampler-2d "sampler2D")
+
+
+;;;
+;;; Structs
+;;;
+
+(define-record-type <struct-field>
+ (%make-struct-field name glsl-name type)
+ struct-field?
+ (name struct-field-name)
+ (glsl-name struct-field-glsl-name)
+ (type struct-field-type))
+
+(define* (make-struct-field name type #:key (glsl-name (symbol->string name)))
+ (%make-struct-field name glsl-name type))
+
+(define-record-type <struct>
+ (make-struct name glsl-name fields)
+ struct?
+ (name struct-name)
+ (glsl-name struct-glsl-name)
+ (fields struct-fields))
+
+(define-syntax define-struct
+ (syntax-rules ()
+ ((_ (name glsl-name) (field-name field-type field-opt ...) ...)
+ (register-type! 'name
+ (make-struct 'name glsl-name
+ (list
+ (make-struct-field 'field-name
+ (lookup-type 'field-type)
+ field-opt ...)
+ ...))))
+ ((_ name fields ...)
+ (define-struct (name (symbol->string 'name)) fields ...))))
+
+(define-struct vec2
+ (x float)
+ (y float))
+
+(define-struct vec3
+ (x float)
+ (y float)
+ (z float))
+
+(define-struct vec4
+ (x float)
+ (y float)
+ (z float)
+ (w float))
+
+
+;;;
+;;; Function type
+;;;
+
+(define-record-type <function-type>
+ (make-function-type parameters returns)
+ function-type?
+ (parameters function-type-parameters)
+ (returns function-type-returns))
+
+
+;;;
+;;; Utils
+;;;
+
+(define (type? obj)
+ (or (primitive-type? obj)))
+
+(define (type-name type)
+ (match type
+ (($ <primitive-type> _ name) name)
+ (($ <function-type> params results)
+ (format #f "(-> ~a ~a)"
+ (map type-name params)
+ (map type-name results)))
+ (($ <type-variable> id)
+ (format #f "tvar-~a" id))))
+
+(define (type-glsl-name type)
+ (match type
+ (($ <primitive-type> _ name) name)
+ ;; TODO: Remove this eventually.
+ (($ <type-variable> id)
+ (format #f "t~a" id))))
+
+(define (type-for-constant x)
+ (lookup-type
+ (cond
+ ((boolean? x) 'bool)
+ ((exact-integer? x) 'int)
+ ((float? x) 'float))))
+
+
+;;;
+;;; Unification
+;;;
+
+(define-record-type <substitutions>
+ (make-substitutions subs)
+ substitutions?
+ (subs unbox-substitutions))
+
+(define no-substitutions (make-substitutions '()))
+
+(define (add-substitution subs a b)
+ (make-substitutions (alist-cons a b (unbox-substitutions subs))))
+
+(define (%substitute-type type from to)
+ (cond
+ ((primitive-type? type)
+ type)
+ ((type-variable? type)
+ (if (eq? type from) to type))))
+
+(define (substitute-type subs type)
+ (fold (lambda (pair type*)
+ (match pair
+ ((from . to)
+ (%substitute-type type* from to))))
+ type
+ (unbox-substitutions subs)))
+
+(define (substitute-types subs types)
+ (map (lambda (type)
+ (substitute-type subs type))
+ types))
+
+(define (compose-substitutions a b)
+ (define b*
+ (map (match-lambda
+ ((from . to)
+ (cons from (substitute-type a to))))
+ (unbox-substitutions b)))
+ (define a*
+ (filter-map (match-lambda
+ ((from . to)
+ (if (assq-ref b* from)
+ #f
+ (cons from to))))
+ (unbox-substitutions a)))
+ (make-substitutions (append a* b*)))
+
+(define (occurs? a b)
+ "Return #t if type A appears in type B."
+ (define (a-occurs? b*)
+ (occurs? a b*))
+ (cond
+ ((and (type-variable? a) (type-variable? b))
+ (eq? a b))
+ ((and (type-variable? a) (function-type? b))
+ (or (any a-occurs? (function-type-parameters b))
+ (any a-occurs? (function-type-returns b))))
+ (else #f)))
+
+(define (type-mismatch a b)
+ (throw 'type-mismatch a b))
+
+(define (unify:primitives a b)
+ (if (eq? a b)
+ no-substitutions
+ (type-mismatch a b)))
+
+(define (unify:variable a b)
+ (cond
+ ((eq? a b)
+ no-substitutions)
+ ((occurs? a b)
+ (type-mismatch a b))
+ (else
+ (add-substitution no-substitutions a b))))
+
+(define (unify:functions a b)
+ (define param-subs
+ (unify (function-type-parameters a)
+ (function-type-parameters b)))
+ (define return-subs
+ (unify (substitute-types param-subs
+ (function-type-returns a))
+ (substitute-types param-subs
+ (function-type-returns b))))
+ (compose-substitutions param-subs return-subs))
+
+(define (unify:pair a b)
+ (define car-subs (unify (car a) (car b)))
+ (define cdr-subs (unify (substitute-types car-subs (cdr a))
+ (substitute-types car-subs (cdr b))))
+ (compose-substitutions car-subs cdr-subs))
+
+(define (unify a b)
+ "Return a list of substitutions that unify types A and B, or throw an
+error if A and B are incompatible."
+ (cond
+ ((and (primitive-type? a) (primitive-type? b))
+ (unify:primitives a b))
+ ((type-variable? a)
+ (unify:variable a b))
+ ((type-variable? b)
+ (unify:variable b a))
+ ((and (function-type? a) (function-type? b))
+ (unify:functions a b))
+ ((and (null? a) (null? b))
+ no-substitutions)
+ ((and (pair? a) (pair? b))
+ (unify:pair a b))
+ (else
+ (type-mismatch a b))))
diff --git a/chickadee/graphics/seagull/utils.scm b/chickadee/graphics/seagull/utils.scm
new file mode 100644
index 0000000..af155a6
--- /dev/null
+++ b/chickadee/graphics/seagull/utils.scm
@@ -0,0 +1,124 @@
+;;; 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.
+
+(define-module (chickadee graphics seagull utils)
+ #:use-module (ice-9 exceptions)
+ #:use-module (ice-9 match)
+ #:use-module (language cps intmap)
+ #:use-module (srfi srfi-1)
+ #:use-module (srfi srfi-9)
+ #:export (float?
+ sourcev->string
+ stage:vertex
+ stage:fragment
+ shader-stage?
+ vertex-stage?
+ fragment-stage?
+
+ environment?
+ fresh-environment
+ environment
+ bound?
+ lookup
+ extend
+ compose-environments
+
+ intmap->alist
+ alist->intmap))
+
+(define (float? x)
+ (and (number? x) (inexact? x)))
+
+(define (sourcev->string sourcev)
+ (match sourcev
+ (#f
+ "unknown location")
+ (#(#f line column)
+ (format #f "unknown:~a:~a" line column))
+ (#(file line column)
+ (format #f "~a:~a:~a" file line column))))
+
+
+;;;
+;;; Shader stages
+;;;
+
+(define-record-type <shader-stage>
+ (shader-stage name)
+ shader-stage?
+ (name shader-stage-name))
+
+(define stage:vertex (shader-stage 'vertex))
+(define stage:fragment (shader-stage 'fragment))
+
+(define (vertex-stage? obj)
+ (eq? obj stage:vertex))
+
+(define (fragment-stage? obj)
+ (eq? obj stage:fragment))
+
+
+;;;
+;;; Environments
+;;;
+
+;; Environments provide a lookup table. Used for lexical scoping,
+;; alpha conversion, etc.
+(define-record-type <environment>
+ (make-environment bindings)
+ environment?
+ (bindings environment-bindings))
+
+(define (fresh-environment)
+ (make-environment '()))
+
+(define-syntax-rule (environment (key value) ...)
+ (make-environment (list (cons 'key value) ...)))
+
+(define (lookup name env)
+ (assq-ref (environment-bindings env) name))
+
+(define (bound? name env)
+ (not (not (lookup name env))))
+
+(define (extend name value env)
+ (make-environment
+ (alist-cons name value (environment-bindings env))))
+
+(define (compose-environments . envs)
+ (match envs
+ ((a b)
+ (make-environment
+ (append (environment-bindings a)
+ (environment-bindings b))))
+ (_
+ (make-environment
+ (concatenate
+ (map environment-bindings envs))))))
+
+
+;;;
+;;; Intmap helpers
+;;;
+
+(define (intmap->alist intmap)
+ (intmap-fold-right alist-cons intmap '()))
+
+(define (alist->intmap alist)
+ (fold (lambda (pair intmap)
+ (match pair
+ ((k . v)
+ (intmap-add intmap k v))))
+ empty-intmap alist))