Implement quoting of terra symbols inside lua escapes

This commit is contained in:
Jeremy Penner 2023-12-02 23:16:24 -05:00
parent 28f0289a6b
commit 95d279e45e
3 changed files with 111 additions and 35 deletions

56
README.md Normal file
View file

@ -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
```

29
go.fnl
View file

@ -1,28 +1,15 @@
(local fennel (require :fennel)) (local fennel (require :fennel))
(import-macros {: terra : unterra : untype : def : q : ttype} :terra) (import-macros {: terra : unterra : untype : def : q : ttype} :terra)
; (local N 5) (local N 5)
; (print (unterra (fn inc [x] (q (+ ,x 1)))
; (def [x [int] : int64] (print (unterra
; (var y (+ x N)) (def [x int]
; (return (cast int64 (& [y])))))) (return ,(inc `x)))))
(print (untype
{key int
:union {
string [int8]
number float
complex {real float imag float}
}
}))
(print (fennel.view (ttype (print
{key int (def [x int]
:union { (return ,(inc `x))))
string [int8]
number float
complex {real float imag float}
}
})))
; (local inc (def [x [int]] (return [x N]))) ; (local inc (def [x [int]] (return [x N])))
; (print (inc 5)) ; (print (inc 5))

View file

@ -7,6 +7,35 @@
(fn kv-table? [tbl] ; this should be exported to macros but is not ;_; (fn kv-table? [tbl] ; this should be exported to macros but is not ;_;
(and (table? tbl) (not (sequence? tbl)))) (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 [] (fn new-scope []
{:locals {} {:locals {}
:env {} :env {}
@ -17,9 +46,14 @@
(if (and (sym? expr) (not (multi-sym? expr))) (self:env-ref expr) (if (and (sym? expr) (not (multi-sym? expr))) (self:env-ref expr)
(= (type expr) :number) (tostring expr) (= (type expr) :number) (tostring expr)
(= expr nil) :nil (= expr nil) :nil
(let [name (safesym :inline-expr)] (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}) (table.insert self.input {: name : expr})
name))) ref)))
:env-ref (fn [self symbol] :env-ref (fn [self symbol]
(let [name (tostring symbol) (let [name (tostring symbol)
loc (. self.env name)] loc (. self.env name)]
@ -41,11 +75,6 @@
(local comp {}) (local comp {})
(local forms {}) (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] (fn comp.expr [form scope]
(case form (case form
(where [head & rest] (sym? head) (list? form) (. forms (tostring head))) (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 [[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 [[typ] scope] (.. :& (comp.type typ scope))))
(tset type-constructors :$ (fn [typs scope] (.. "tuple(" (commasep typs #(comp.type $1 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.rawterra [[text] scope] text)
(fn forms.var [defn scope] (fn forms.var [defn scope]
@ -147,7 +176,7 @@
(fn forms.return [[expr] scope] (.. "return " (comp.expr expr scope))) (fn forms.return [[expr] scope] (.. "return " (comp.expr expr scope)))
(fn forms.cast [[typ expr] scope] (fn forms.cast [[typ expr] scope]
(.. "([" (comp.type typ scope)"](" (comp.expr 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 [[expr] scope] (.. :& (comp.expr expr scope))))
(tset forms :$ (fn [items scope] (.. "{ " (commasep items #(comp.expr $1 scope)) " }"))) (tset forms :$ (fn [items scope] (.. "{ " (commasep items #(comp.expr $1 scope)) " }")))
@ -161,6 +190,10 @@
(def-infix :not= "~=") (def-infix :not= "~=")
(def-infix :set :=) (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] (fn build [expr compiler]
(let [scope (new-scope) (let [scope (new-scope)
terra-expr (compiler expr scope) terra-expr (compiler expr scope)
@ -175,7 +208,7 @@
(fn ttype [expr] (build expr comp.type)) (fn ttype [expr] (build expr comp.type))
(fn def [...] (terra `(,(sym :def) ,...))) (fn def [...] (terra `(,(sym :def) ,...)))
(fn q [...] (terra `(,(sym :quote) ,...))) (fn q [...] (build [...] comp.quote))
(fn unterra [...] (view (macroexpand (terra ...)))) (fn unterra [...] (view (macroexpand (terra ...))))
(fn untype [...] (view (macroexpand (ttype ...)))) (fn untype [...] (view (macroexpand (ttype ...))))