From 128224a97a1191c5bd79c7394164ed64921c9206 Mon Sep 17 00:00:00 2001 From: Jeremy Penner Date: Sun, 3 Dec 2023 11:49:11 -0500 Subject: [PATCH] "static", more docs, indenting, fix set --- README.md | 115 ++++++++++++++++++++++++++++++++++++++++++++++++++---- go.fnl | 24 ++++++++---- terra.fnl | 80 ++++++++++++++++++++++--------------- 3 files changed, 172 insertions(+), 47 deletions(-) diff --git a/README.md b/README.md index 07946b0..0c584e3 100644 --- a/README.md +++ b/README.md @@ -7,11 +7,11 @@ Idk, seemed cool ## Usage At the top of your file, include the following: ```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 `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. 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 (local add (def [x int y int : int] (return (+ x y)))) + +; compiles to: +; local add = terra(x : int, y : int) : {int} +; return (x + y) +; end + (add 1 2) ; returns 3 ``` @@ -38,19 +44,114 @@ Unlike Fennel, we do not implement implicit return semantics, and early returns Sorry Phil. ### 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. -### 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 -(var name initial-value) -(var name type initial-value) +(local Variant (ttype {tag int + :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 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 +#### 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 []) ``` \ No newline at end of file diff --git a/go.fnl b/go.fnl index 60efacf..b838d04 100644 --- a/go.fnl +++ b/go.fnl @@ -1,15 +1,23 @@ (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 - (def [x int] - (return ,(inc `x))))) + (def [x int] + (set N (+ N x)) + (return N)))) -(print - (def [x int] - (return ,(inc `x)))) +(local addN (def [x int] + (set N (+ N x)) + (return N))) + +(print addN) +(print (N:get) (addN 3) (N:get)) ; (local inc (def [x [int]] (return [x N]))) ; (print (inc 5)) diff --git a/terra.fnl b/terra.fnl index 6b7d5de..6e91b8c 100644 --- a/terra.fnl +++ b/terra.fnl @@ -12,6 +12,15 @@ (fn kvcommasep [tbl f ?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 {}) (fn extract.quotes-in-table [into tbl inputs locals] (collect [k v (pairs tbl) &into into] k (extract.quotes v inputs locals))) @@ -40,8 +49,14 @@ {:locals {} :env {} :input [] + :indent 0 :push (fn [self] (set self.locals (setmetatable {} {:__index self.locals}))) :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] (if (and (sym? expr) (not (multi-sym? expr))) (self:env-ref expr) (= (type expr) :number) (tostring expr) @@ -55,22 +70,22 @@ (table.insert self.input {: name : expr}) ref))) :env-ref (fn [self symbol] - (let [name (tostring symbol) - loc (. self.env name)] - (if loc loc - (let [envloc (safesym name)] - (tset self.env name envloc) - (table.insert self.input {:name envloc :expr (sym name)}) - envloc)))) + (let [name (tostring symbol) + loc (. self.env name)] + (if loc loc + (let [envloc (safesym name)] + (tset self.env name envloc) + (table.insert self.input {:name envloc :expr (sym name)}) + envloc)))) :reference (fn [self symbol] - (let [name (tostring symbol) - loc (. self.locals name)] - (if loc loc (self:env-ref symbol)))) + (let [name (tostring symbol) + loc (. self.locals name)] + (if loc loc (self:env-ref symbol)))) :addlocal (fn [self symbol] - (let [name (tostring symbol) - loc (safesym name)] - (tset self.locals name loc) - loc))}) + (let [name (tostring symbol) + loc (safesym name)] + (tset self.locals name loc) + loc))}) (local comp {}) (local forms {}) @@ -150,10 +165,7 @@ [name initval] (.. "var " (scope:addlocal name) " = " (comp.expr initval scope)))) (fn forms.do [stmts scope] - (scope:push) - (local lines (icollect [_ stmt (ipairs stmts)] (comp.expr stmt scope))) - (scope:pop) - (table.concat lines "\n")) + (scope:with #(block :do stmts :end scope #(comp.expr $1 scope)))) (fn find-ival [tbl pred] (var ival nil) @@ -162,17 +174,14 @@ ival) (fn forms.def [[arglist & stmts] scope] - (scope:push) - (let [iarg-return-sep (find-ival arglist #(and (sym? $1) (= (tostring $1) ":"))) - argpairs (fcollect [i 1 (length arglist) 2 &until (= i iarg-return-sep)] - {:name (. arglist i) :type (. arglist (+ i 1))}) - 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)) " }") "") - argdefs (commasep argpairs #(.. (scope:addlocal $1.name) " : " (comp.type $1.type scope))) - fulldef (.. "terra (" argdefs ")" rettyp "\n" (forms.do stmts scope) "\nend")] - (scope:pop) - fulldef)) -(fn forms.predef [fntyp scope] (.. )) + (scope:with #(let [iarg-return-sep (find-ival arglist #(and (sym? $1) (= (tostring $1) ":"))) + argpairs (fcollect [i 1 (length arglist) 2 &until (= i iarg-return-sep)] + {:name (. arglist i) :type (. arglist (+ i 1))}) + 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)) " }") "") + argdefs (commasep argpairs #(.. (scope:addlocal $1.name) " : " (comp.type $1.type scope)))] + (block (.. "terra (" argdefs ")" rettyp) stmts :end scope #(comp.expr $1 scope))))) + (fn forms.return [[expr] scope] (.. "return " (comp.expr expr scope))) (fn forms.cast [[typ expr] scope] (.. "([" (comp.type typ scope)"](" (comp.expr expr scope) "))")) @@ -188,11 +197,16 @@ (def-infix op op)) (def-infix := :==) (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] (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] (let [scope (new-scope) @@ -209,7 +223,9 @@ (fn def [...] (terra `(,(sym :def) ,...))) (fn q [...] (build [...] comp.quote)) +(fn static [typ initial-value] (build [typ initial-value] comp.global)) + (fn unterra [...] (view (macroexpand (terra ...)))) (fn untype [...] (view (macroexpand (ttype ...)))) -{: terra : ttype : def : q : unterra : untype} \ No newline at end of file +{: terra : ttype : def : q : static : unterra : untype} \ No newline at end of file