"static", more docs, indenting, fix set

This commit is contained in:
Jeremy Penner 2023-12-03 11:49:11 -05:00
parent 95d279e45e
commit 128224a97a
3 changed files with 172 additions and 47 deletions

115
README.md
View file

@ -7,11 +7,11 @@ Idk, seemed cool
## Usage ## Usage
At the top of your file, include the following: At the top of your file, include the following:
```fennel ```fennel
(import-macros {: def : q : ttype} :terra) (import-macros {: def : q : ttype : static} :terra)
``` ```
The `def` macro defines a new terra function. The `q` macro defines a quoted terra expression. 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 The `ttype` macro allows you to specify terra type definitions that can't be expressed with
regular lua syntax. regular lua syntax. The `static` macro allows you to define "global" terra variables.
Notably, all of these macros return values, and none of them define new variables, local or global. 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, I could maybe be persuaded to make `def` work like `fn` and optionally define a local, but for now,
@ -29,6 +29,12 @@ Simple example:
```fennel ```fennel
(local add (def [x int y int : int] (local add (def [x int y int : int]
(return (+ x y)))) (return (+ x y))))
; compiles to:
; local add = terra(x : int, y : int) : {int}
; return (x + y)
; end
(add 1 2) ; returns 3 (add 1 2) ; returns 3
``` ```
@ -38,19 +44,114 @@ Unlike Fennel, we do not implement implicit return semantics, and early returns
Sorry Phil. Sorry Phil.
### q ### q
Defines a terra quotation, compiling down to the `\`` operator if given one argument, and Defines a terra quotation, compiling down to the ` `` ` operator if given one argument, and
`quote` / `end` if given more than one. `quote` / `end` if given more than one.
### syntax within `def` and `q` forms ```fennel
(fn inc [x] (q (+ x 1))) ; compiles to: function(x) `(x + 1) end
```
### Type syntax
In Terra, types are Lua values and can be constructed from regular Lua code, outside of
`terra` or `quote` blocks. (There are certain places inside these blocks where types can
be constructed as well.) However, because Terra significantly extends the syntax of Lua to
allow for convenient type construction, Garden also must provide a mini-language to support
it.
Inside `terra` or `quote` blocks, whenever you have a form that requires a type to be passed
to it, the compiler will automatically enter a type-compiling context. Outside of these blocks,
the `ttype` macro can be used.
#### Pointers and arrays
Pointers to types and arrays of types use the Fennel "sequence table" syntax. Alternatively,
the `&` operator can be used.
```fennel
(local intptr (ttype [int])) ; compiles to: &int
(local intptr2 (ttype (& int))) ; also compiles to: &int
(local intarray (ttype [int 16])) ; compiles to: int[16]
```
#### Structs
Structs are defined using the Fennel "key-value table" syntax. For each row in the table,
the keys are Fennel symbols representing the name of the field, and the values are type
expressions. If, instead of a symbol, the compiler finds the string `:union`, the value
is expected to be another "key-value table" containing the same structure. Struct definitons
can be nested.
Note that the field names must be valid Lua symbols; no name-mangling is done. Might be
worth doing that at some point; it seems likely that I will be annoyed if we don't
auto-convert `-` to `_` at least.
```fennel ```fennel
(var name initial-value) (local Variant (ttype {tag int
(var name type initial-value) :union {number float
string [int8]
complex {real float imag float}}}))
; compiles to:
; local Variant = struct {
; tag : int,
; union {
; number : float,
; string : &int8,
; complex : struct {
; real : float,
; imag : float
; }
; }
; }
```
#### Function pointers
Function pointer types are defined with the `->` form, which accepts two arguments - a sequence
of types representing the input parameter types, and a sequence of types representing the return
value. Actually this kind of sucks, I think we should use the same syntax as `def`. I'll probably
change this. But this is how it works right now:
```fennel
(local callback (ttype (-> [[int] int] [int]))) ; compiles to: local callback = { &int, int } -> { int }
```
#### Tuples
Tuple types are defined in Terra with a generic Lua function call to `tuple` that takes a variable
number of types as parameters. This is supported directly, like any type declaration consisting of
a function call, but there is a shortened form using `$` to match the tuple instantiation syntax.
```fennel
(ttype (tuple int [int])) ; compiles to: tuple(int, &int)
(ttype ($ int [int])) ; compiles to: tuple(int, &int)
```
#### Escaping
Arbitrary Fennel expressions can be evaluated in a type-compilation context using Fennel's `,` prefix,
which is normally used by macros. If you need to re-enter a type-compilation context after escaping,
you'll need to nest a call to `ttype`. (I'm considering using ` `` ` for this purpose, but I might have
it consistently mean "create a quote" everywhere. Not sure.)
```fennel
(ttype ($ [int] (fn-accepting-type [int]) ,(fn-accepting-seq [5]) (fn-accepting-seq-of-types ,[(ttype [int])])))
; compiles to:
; tuple(&int, fn_accepting_type(&int), fn_accepting_seq({ 5 }), fn_accepting_seq_of_types({ &int }))
```
### Terra syntax
#### var
```fennel
(var name initial-value) ; compiles to: var name = initial-value
(var name type initial-value) ; compiles to: var name : type = initial-value
``` ```
Define a local variable named `var`, and set its initial value to `initial-value`. You can 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 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. syntax for _not_ initalizing the variable on declaration.
```fennel #### Pointers and arrays
Dereferencing a pointer or accessing an element in an array uses the same syntax as defining a pointer
or array type - the Fennel sequence literal. To take a reference to a value, you can use the `&` form.
```fennel
(def [ptr [int]] (return [ptr])) ; compiles to: terra (ptr : &int) return @ptr end
(def [arr [int 8]] (return [arr 5])) ; compiles to: terra (arr : int[8]) return arr[5] end
(def [nested [[int]]] (return [nested 0 3])) ; compiles to: terra (nested : &&int) return nested[0][3] end
(def [])
``` ```

22
go.fnl
View file

@ -1,15 +1,23 @@
(local fennel (require :fennel)) (local fennel (require :fennel))
(import-macros {: terra : unterra : untype : def : q : ttype} :terra) (import-macros {: def : q : ttype : static : unterra : untype} :terra)
(local N (static int 5))
(print N)
(fn inc [x]
(print "calling inc" x)
(q (+ ,x 1)))
(local N 5)
(fn inc [x] (q (+ ,x 1)))
(print (unterra (print (unterra
(def [x int] (def [x int]
(return ,(inc `x))))) (set N (+ N x))
(return N))))
(print (local addN (def [x int]
(def [x int] (set N (+ N x))
(return ,(inc `x)))) (return N)))
(print addN)
(print (N:get) (addN 3) (N:get))
; (local inc (def [x [int]] (return [x N]))) ; (local inc (def [x [int]] (return [x N])))
; (print (inc 5)) ; (print (inc 5))

View file

@ -12,6 +12,15 @@
(fn kvcommasep [tbl f ?sep] (fn kvcommasep [tbl f ?sep]
(commasep (. (getmetatable tbl) :keys) #(f $1 (. tbl $1)) ?sep)) (commasep (. (getmetatable tbl) :keys) #(f $1 (. tbl $1)) ?sep))
(fn indent [str scope]
(.. (string.rep " " scope.indent) str))
(fn block [pre stmts post scope f]
(set scope.indent (+ scope.indent 2))
(local block (commasep stmts #(indent (f $1) scope) "\n"))
(set scope.indent (- scope.indent 2))
(.. pre "\n" block "\n" (indent post scope)))
(local extract {}) (local extract {})
(fn extract.quotes-in-table [into tbl inputs locals] (fn extract.quotes-in-table [into tbl inputs locals]
(collect [k v (pairs tbl) &into into] k (extract.quotes v inputs locals))) (collect [k v (pairs tbl) &into into] k (extract.quotes v inputs locals)))
@ -40,8 +49,14 @@
{:locals {} {:locals {}
:env {} :env {}
:input [] :input []
:indent 0
:push (fn [self] (set self.locals (setmetatable {} {:__index self.locals}))) :push (fn [self] (set self.locals (setmetatable {} {:__index self.locals})))
:pop (fn [self] (set self.locals (. (getmetatable self.locals) :__index))) :pop (fn [self] (set self.locals (. (getmetatable self.locals) :__index)))
:with (fn [self f]
(self:push)
(local result (f))
(self:pop)
result)
:expr (fn [self expr] :expr (fn [self expr]
(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)
@ -55,22 +70,22 @@
(table.insert self.input {: name : expr}) (table.insert self.input {: name : expr})
ref))) 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)]
(if loc loc (if loc loc
(let [envloc (safesym name)] (let [envloc (safesym name)]
(tset self.env name envloc) (tset self.env name envloc)
(table.insert self.input {:name envloc :expr (sym name)}) (table.insert self.input {:name envloc :expr (sym name)})
envloc)))) envloc))))
:reference (fn [self symbol] :reference (fn [self symbol]
(let [name (tostring symbol) (let [name (tostring symbol)
loc (. self.locals name)] loc (. self.locals name)]
(if loc loc (self:env-ref symbol)))) (if loc loc (self:env-ref symbol))))
:addlocal (fn [self symbol] :addlocal (fn [self symbol]
(let [name (tostring symbol) (let [name (tostring symbol)
loc (safesym name)] loc (safesym name)]
(tset self.locals name loc) (tset self.locals name loc)
loc))}) loc))})
(local comp {}) (local comp {})
(local forms {}) (local forms {})
@ -150,10 +165,7 @@
[name initval] (.. "var " (scope:addlocal name) " = " (comp.expr initval scope)))) [name initval] (.. "var " (scope:addlocal name) " = " (comp.expr initval scope))))
(fn forms.do [stmts scope] (fn forms.do [stmts scope]
(scope:push) (scope:with #(block :do stmts :end scope #(comp.expr $1 scope))))
(local lines (icollect [_ stmt (ipairs stmts)] (comp.expr stmt scope)))
(scope:pop)
(table.concat lines "\n"))
(fn find-ival [tbl pred] (fn find-ival [tbl pred]
(var ival nil) (var ival nil)
@ -162,17 +174,14 @@
ival) ival)
(fn forms.def [[arglist & stmts] scope] (fn forms.def [[arglist & stmts] scope]
(scope:push) (scope:with #(let [iarg-return-sep (find-ival arglist #(and (sym? $1) (= (tostring $1) ":")))
(let [iarg-return-sep (find-ival arglist #(and (sym? $1) (= (tostring $1) ":"))) argpairs (fcollect [i 1 (length arglist) 2 &until (= i iarg-return-sep)]
argpairs (fcollect [i 1 (length arglist) 2 &until (= i iarg-return-sep)] {:name (. arglist i) :type (. arglist (+ i 1))})
{:name (. arglist i) :type (. arglist (+ i 1))}) rettyps (when iarg-return-sep (icollect [i typ (ipairs arglist)] (when (> i iarg-return-sep) typ)))
rettyps (when iarg-return-sep (icollect [i typ (ipairs arglist)] (when (> i iarg-return-sep) typ))) rettyp (if rettyps (.. ": { " (commasep rettyps #(comp.type $1 scope)) " }") "")
rettyp (if rettyps (.. ": { " (commasep rettyps #(comp.type $1 scope)) " }") "") argdefs (commasep argpairs #(.. (scope:addlocal $1.name) " : " (comp.type $1.type scope)))]
argdefs (commasep argpairs #(.. (scope:addlocal $1.name) " : " (comp.type $1.type scope))) (block (.. "terra (" argdefs ")" rettyp) stmts :end scope #(comp.expr $1 scope)))))
fulldef (.. "terra (" argdefs ")" rettyp "\n" (forms.do stmts scope) "\nend")]
(scope:pop)
fulldef))
(fn forms.predef [fntyp scope] (.. ))
(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) "))"))
@ -188,11 +197,16 @@
(def-infix op op)) (def-infix op op))
(def-infix := :==) (def-infix := :==)
(def-infix :not= "~=") (def-infix :not= "~=")
(def-infix :set :=)
(fn forms.set [[left right] scope]
(.. (comp.expr left scope) " = (" (comp.expr right scope) ")"))
(fn comp.quote [stmts scope] (fn comp.quote [stmts scope]
(if (= (length stmts) 1) (.. "`(" (comp.expr (. stmts 1) scope) ")") (if (= (length stmts) 1) (.. "`(" (comp.expr (. stmts 1) scope) ")")
(.. "quote\n" (commasep stmts #(comp.expr $1 scope) "\n") "\nend"))) (.. "quote\n" (commasep stmts #(comp.expr $1 scope)) "\nend")))
(fn comp.global [[typ initial-val] scope]
(.. "global(" (comp.type typ scope) "," (comp.expr initial-val scope) ")"))
(fn build [expr compiler] (fn build [expr compiler]
(let [scope (new-scope) (let [scope (new-scope)
@ -209,7 +223,9 @@
(fn def [...] (terra `(,(sym :def) ,...))) (fn def [...] (terra `(,(sym :def) ,...)))
(fn q [...] (build [...] comp.quote)) (fn q [...] (build [...] comp.quote))
(fn static [typ initial-value] (build [typ initial-value] comp.global))
(fn unterra [...] (view (macroexpand (terra ...)))) (fn unterra [...] (view (macroexpand (terra ...))))
(fn untype [...] (view (macroexpand (ttype ...)))) (fn untype [...] (view (macroexpand (ttype ...))))
{: terra : ttype : def : q : unterra : untype} {: terra : ttype : def : q : static : unterra : untype}