diff --git a/README.md b/README.md new file mode 100644 index 0000000..07946b0 --- /dev/null +++ b/README.md @@ -0,0 +1,56 @@ +# Garden +An experiment in combining [Fennel](https://fennel-lang.org) and [Terra](https://terralang.org). + +## Rationale +Idk, seemed cool + +## Usage +At the top of your file, include the following: +```fennel +(import-macros {: def : q : ttype} :terra) +``` +The `def` macro defines a new terra function. The `q` macro defines a quoted terra expression. +The `ttype` macro allows you to specify terra type definitions that can't be expressed with +regular lua syntax. + +Notably, all of these macros return values, and none of them define new variables, local or global. +I could maybe be persuaded to make `def` work like `fn` and optionally define a local, but for now, +eh, whatever. + +### def +Defines a function, compiling down to the `terra` keyword. + +Syntax: +```fennel +(def [argname1 argtype1 argname2 argtype2... : rettype1 rettype2...] statement...) +``` + +Simple example: +```fennel +(local add (def [x int y int : int] + (return (+ x y)))) +(add 1 2) ; returns 3 +``` + +To define a function as returning "void", simply end the argument list with a `:`. +To make terra infer the return type, do not include a `:` in the argument list at all. +Unlike Fennel, we do not implement implicit return semantics, and early returns are A-OK. +Sorry Phil. + +### q +Defines a terra quotation, compiling down to the `\`` operator if given one argument, and +`quote` / `end` if given more than one. + +### syntax within `def` and `q` forms + +```fennel +(var name initial-value) +(var name type initial-value) +``` +Define a local variable named `var`, and set its initial value to `initial-value`. You can +manually specify a `type`, or you can let terra infer it from `initial-value`. There is no +syntax for _not_ initalizing the variable on declaration. + +```fennel + +``` \ No newline at end of file diff --git a/go.fnl b/go.fnl index 34ae4f8..60efacf 100644 --- a/go.fnl +++ b/go.fnl @@ -1,28 +1,15 @@ (local fennel (require :fennel)) (import-macros {: terra : unterra : untype : def : q : ttype} :terra) -; (local N 5) -; (print (unterra -; (def [x [int] : int64] -; (var y (+ x N)) -; (return (cast int64 (& [y])))))) -(print (untype - {key int - :union { - string [int8] - number float - complex {real float imag float} - } - })) +(local N 5) +(fn inc [x] (q (+ ,x 1))) +(print (unterra + (def [x int] + (return ,(inc `x))))) -(print (fennel.view (ttype - {key int - :union { - string [int8] - number float - complex {real float imag float} - } - }))) +(print + (def [x int] + (return ,(inc `x)))) ; (local inc (def [x [int]] (return [x N]))) ; (print (inc 5)) diff --git a/terra.fnl b/terra.fnl index 2cbae5a..6b7d5de 100644 --- a/terra.fnl +++ b/terra.fnl @@ -7,6 +7,35 @@ (fn kv-table? [tbl] ; this should be exported to macros but is not ;_; (and (table? tbl) (not (sequence? tbl)))) +(fn commasep [list f ?sep] + (table.concat (icollect [_ v (ipairs list)] (f v)) (or ?sep ", "))) +(fn kvcommasep [tbl f ?sep] + (commasep (. (getmetatable tbl) :keys) #(f $1 (. tbl $1)) ?sep)) + +(local extract {}) +(fn extract.quotes-in-table [into tbl inputs locals] + (collect [k v (pairs tbl) &into into] k (extract.quotes v inputs locals))) + +(fn extract.ensure-input [input inputs] + (var found false) + (each [_ sym (ipairs inputs)] + (when (= sym input) (set found true))) + (when (not found) (table.insert inputs input)) + input) + +(fn extract.quotes [form inputs locals] + (case form + (where [q ref] (list? form) (sym? q) (sym? ref) (= (tostring q) :quote)) + (let [localref (. locals (tostring ref))] + (if localref (extract.ensure-input (sym localref) inputs) + (error (.. "Unknown local: " (tostring ref))))) + + (where l (list? l)) (extract.quotes-in-table (list) l inputs locals) + (where s (sequence? s)) (extract.quotes-in-table (sequence) s inputs locals) + (where t (kv-table? t)) (extract.quotes-in-table {} t inputs locals) + + _ form)) + (fn new-scope [] {:locals {} :env {} @@ -14,12 +43,17 @@ :push (fn [self] (set self.locals (setmetatable {} {:__index self.locals}))) :pop (fn [self] (set self.locals (. (getmetatable self.locals) :__index))) :expr (fn [self expr] - (if (and (sym? expr) (not (multi-sym? expr))) (self:env-ref expr) - (= (type expr) :number) (tostring expr) - (= expr nil) :nil - (let [name (safesym :inline-expr)] - (table.insert self.input {: name : expr}) - name))) + (if (and (sym? expr) (not (multi-sym? expr))) (self:env-ref expr) + (= (type expr) :number) (tostring expr) + (= expr nil) :nil + (let [name (safesym :inline-expr) + arglist (sequence) + expr (extract.quotes expr arglist self.locals) + bare-ref? (= (length arglist) 0) + expr (if bare-ref? expr `(fn ,arglist ,expr)) + ref (if bare-ref? name (.. name "(" (commasep arglist #(tostring $1)) ")"))] + (table.insert self.input {: name : expr}) + ref))) :env-ref (fn [self symbol] (let [name (tostring symbol) loc (. self.env name)] @@ -41,11 +75,6 @@ (local comp {}) (local forms {}) -(fn commasep [list f] - (table.concat (icollect [_ v (ipairs list)] (f v)) ", ")) -(fn kvcommasep [tbl f] - (commasep (. (getmetatable tbl) :keys) #(f $1 (. tbl $1)))) - (fn comp.expr [form scope] (case form (where [head & rest] (sym? head) (list? form) (. forms (tostring head))) @@ -112,7 +141,7 @@ (tset type-constructors :-> (fn [[in out] scope] (.. (arglist-type in scope) " -> " (arglist-type out scope)))) (tset type-constructors :& (fn [[typ] scope] (.. :& (comp.type typ scope)))) (tset type-constructors :$ (fn [typs scope] (.. "tuple(" (commasep typs #(comp.type $1 scope)) ")"))) -(fn type-constructors.hashfn [[expr] scope] (scope:expr expr)) +(fn type-constructors.unquote [[expr] scope] (scope:expr expr)) (fn forms.rawterra [[text] scope] text) (fn forms.var [defn scope] @@ -147,7 +176,7 @@ (fn forms.return [[expr] scope] (.. "return " (comp.expr expr scope))) (fn forms.cast [[typ expr] scope] (.. "([" (comp.type typ scope)"](" (comp.expr expr scope) "))")) -(fn forms.hashfn [[expr] scope] (.. "([" (scope:expr expr) "])")) +(fn forms.unquote [[expr] scope] (.. "([" (scope:expr expr) "])")) (tset forms :& (fn [[expr] scope] (.. :& (comp.expr expr scope)))) (tset forms :$ (fn [items scope] (.. "{ " (commasep items #(comp.expr $1 scope)) " }"))) @@ -161,6 +190,10 @@ (def-infix :not= "~=") (def-infix :set :=) +(fn comp.quote [stmts scope] + (if (= (length stmts) 1) (.. "`(" (comp.expr (. stmts 1) scope) ")") + (.. "quote\n" (commasep stmts #(comp.expr $1 scope) "\n") "\nend"))) + (fn build [expr compiler] (let [scope (new-scope) terra-expr (compiler expr scope) @@ -175,7 +208,7 @@ (fn ttype [expr] (build expr comp.type)) (fn def [...] (terra `(,(sym :def) ,...))) -(fn q [...] (terra `(,(sym :quote) ,...))) +(fn q [...] (build [...] comp.quote)) (fn unterra [...] (view (macroexpand (terra ...)))) (fn untype [...] (view (macroexpand (ttype ...))))