diff --git a/README.md b/README.md index d2668949..b5cb1f79 100644 --- a/README.md +++ b/README.md @@ -40,14 +40,20 @@ each release. (print (fib 10)) ``` -## Try it +## Usage At [https://fennel-lang.org](https://fennel-lang.org) there's a live in-browser repl you can use without installing anything. -Otherwise clone this repository, and run `./fennel --repl` to quickly -start a repl. Use `./fennel my-file.fnl` to run code or `./fennel ---compile my-file.fnl > my-file.lua` to perform ahead-of-time compilation. +Check your OS's package manager to see if Fennel is available +there. If you use [LuaRocks](https://luarocks.org/) you can run +`luarocks install fennel`. + +Otherwise clone this repository, and run `./fennel` to start a +repl. Use `./fennel my-file.fnl` to run code or `./fennel --compile +my-file.fnl > my-file.lua` to perform ahead-of-time compilation. + +See the [API documentation](api.md) for how to embed Fennel in your program. ## Differences from Lua @@ -72,28 +78,6 @@ start a repl. Use `./fennel my-file.fnl` to run code or `./fennel (Obviously not all these apply to every lisp you could compare Fennel to.) -## Install with Luarocks - -You can install the dev package from luarocks via -```sh -luarocks install --server=http://luarocks.org/dev fennel -``` - -This will install both the fennel module, which can be required into via `local fennel = require 'fennel'`, -as well as the `fennel` executable which can be used to run a repl or compile Fennel to Lua. - -To start a repl: -```sh -fennel --repl -``` - -To compile a file: -```sh -fennel --compile myscript.fnl > myscript.lua -``` - -When given a file without a flag, it will simply load and run the file. - ## Resources * [Mailing list](https://lists.sr.ht/%7Etechnomancy/fennel) diff --git a/changelog.md b/changelog.md index cb325bf7..4bb90f4f 100644 --- a/changelog.md +++ b/changelog.md @@ -1,6 +1,11 @@ # Summary of user-visible changes -## 0.1.0 / ?? +## ??? + +* Fix bug in the repl where locals-saving would fail for certain input +* Fix launcher to write errors to stderr, not stdout + +## 0.1.0 / 2018-11-29 * Save locals in between chunks in the repl * Allow destructuring in more places diff --git a/fennel b/fennel index 4b957568..e558c147 100755 --- a/fennel +++ b/fennel @@ -8,10 +8,16 @@ local unpack = unpack or table.unpack local help = [[ Usage: fennel [FLAG] [FILE] +Run fennel, a lisp programming language for the Lua runtime. + --repl : Launch an interactive repl session --compile FILES : Compile files and write their Lua to stdout + + --no-searcher : Skip installing package.searchers entry when running code + --indent VAL : Indent compiler output with VAL + --help : Display this text - --no-searcher : Skip installing package.searchers entry + --version : Show version When not given a flag, runs the file given as the first argument. When given neither flag nor file, launches a repl. @@ -27,7 +33,7 @@ local function dosafe(filename, opts, args) return fennel.dofile(filename, opts, unpack(args)) end, fennel.traceback) if not ok then - print(val) + io.stderr.write(val .. "\n") os.exit(1) end return val @@ -79,10 +85,16 @@ elseif arg[1] == "--compile" then local ok, val = xpcall(function() return fennel.compileString(f:read("*all"), options) end, fennel.traceback) - print(val) - if not ok then os.exit(1) end + if ok then + print(val) + else + io.stderr:write(val .. "\n") + os.exit(1) + end f:close() end +elseif arg[1] == "--version" then + print("Fennel " .. fennel.version) elseif #arg >= 1 and arg[1] ~= "--help" then local filename = table.remove(arg, 1) -- let the script have remaining args dosafe(filename, nil, arg) diff --git a/fennel.lua b/fennel.lua index 56a37793..c7d30313 100644 --- a/fennel.lua +++ b/fennel.lua @@ -1720,9 +1720,11 @@ local function repl(options) for name in pairs(env.___replLocals___) do table.insert(splicedSource, 1, bind:format(name, name)) end - -- save off new locals at the end - if(#splicedSource > 1) then - table.insert(splicedSource, #splicedSource, saveSource) + -- save off new locals at the end - if safe to do so (i.e. last line is a return) + if (string.match(splicedSource[#splicedSource], "^ *return .*$")) then + if (#splicedSource > 1) then + table.insert(splicedSource, #splicedSource, saveSource) + end end return table.concat(splicedSource, "\n") end @@ -1794,7 +1796,8 @@ local module = { dofile = dofile_fennel, macroLoaded = macroLoaded, path = "./?.fnl;./?/init.fnl", - traceback = traceback + traceback = traceback, + version = "0.1.1-dev", } local function searchModule(modulename) diff --git a/lua-primer.md b/lua-primer.md index 3c82dfcd..f2367062 100644 --- a/lua-primer.md +++ b/lua-primer.md @@ -13,7 +13,7 @@ know where in the manual to look for further details, not to teach Lua. * `type`: returns a string describing the type of its argument * `pcall`: calls a function in protected mode so errors are not fatal * `error`: halts execution and break to the nearest `pcall` -* `assert`: raises an error if a condition is nil or false +* `assert`: raises an error if a condition is nil/false, otherwise returns it * `ipairs`: iterates over sequential tables * `pairs`: iterates over any table, sequential or not, in undefined order * `unpack`: turns a sequential table into multiple values diff --git a/tutorial.md b/tutorial.md index 2e2688a2..e8fe8733 100644 --- a/tutorial.md +++ b/tutorial.md @@ -1,13 +1,5 @@ # Getting Started with Fennel -If you know Lua and a lisp already, you'll feel right at home in -Fennel. Even if not, Lua is one of the simplest programming languages -in existence, so if you've programmed before you should be able to -pick it up without too much trouble, especially if you've used another -dynamic imperative language with closures. The -[Lua reference manual](https://www.lua.org/manual/5.1/) is a fine place to -look for details. - A programming language is made up of **syntax** and **semantics**. The semantics of Fennel vary only in small ways from Lua (all noted below). The syntax of Fennel comes from the lisp family of @@ -17,32 +9,38 @@ which makes it easier to as well as [structured editing](http://danmidwood.com/content/2014/11/21/animated-paredit.html). +If you know Lua and a lisp already, you'll feel right at home in +Fennel. Even if not, Lua is one of the simplest programming languages +in existence, so if you've programmed before you should be able to +pick it up without too much trouble, especially if you've used another +dynamic imperative language with closures. The +[Lua reference manual](https://www.lua.org/manual/5.1/) is a fine place to +look for details. + ## OK, so how do you do things? -Globals are set with `global`. Good code doesn't use too many of -these, but they can be nice for debugging and are needed in the repl, so -we'll start there. Note that unlike most forms, with `global` there is -no distinction between creating a new global and giving an existing -global a new value. Functions are created with `fn`, using square -brackets for arguments. +Use `fn` to make functions. If you provide an optional name, the +function will be bound to that name in local scope; otherwise it is +simply a value. The argument list is provided in square brackets. The +final value is returned. + +(If you've never used a lisp before, the main thing to note is that +the function or macro being called goes *inside* the parens, not +outside.) ```lisp -(global add (fn [x y] (+ x y))) -(add 32 12) ; -> 44 +(fn print-and-add [a b c] + (print a) + (+ b c)) ``` -Unless you are doing ahead-of-time compilation, Fennel will track all -known globals and prevent you from refering to unknown globals, which -prevents a common source of bugs in Lua where typos go undetected. - Functions defined with `fn` are fast; they have no runtime overhead compared to Lua. However, they also have no arity checking. (That is, calling a function with the wrong number of arguments does not cause an error.) For safer code you can use `lambda`: ```lisp -(global print-calculation - (lambda [x ?y z] (print (- x (* (or ?y 1) z))))) +(lambda print-calculation [x ?y z] (print (- x (* (or ?y 1) z)))) (print-calculation 5) ; -> error: Missing argument z ``` @@ -63,7 +61,7 @@ a single set of square brackets: Here `x` is bound to the result of adding 89 and 5.2, while `f` is bound to a function that prints twice its argument. These bindings are -only valid for the body of the `let` call. +only valid inside the body of the `let` call. You can also introduce locals with `local`, which is nice when they'll be used across the whole file, but in general `let` is preferred because @@ -86,7 +84,7 @@ introduce new locals that shadow the outer names: If you need to change the value of a local, you can use `var` which works like `local` except it allows `set` to work on it. There is no -nested equivalent of `var`. +nested `let`-like equivalent of `var`. ```lisp (var x 19) @@ -131,14 +129,6 @@ main syntax for tables uses curly braces with key/value pairs in them: "f" (fn [x] (+ x 2))} ``` -Some tables are used to store data that's used sequentially; the keys -in this case are just numbers starting with 1 and going up. Fennel -provides alternate syntax for these tables with square brackets: - -```lisp -["abc" "def" "xyz"] ; equivalent to {1 "abc" 2 "def" 3 "xyz"} -``` - You can use `.` to get values out of tables: ```lisp @@ -161,7 +151,38 @@ And `tset` to put them in: Immutable tables are not native to Lua, though it's possible to construct immutable tables using metatables with some performance overhead. -The `#` function returns the length of sequential tables and strings: +### Sequential Tables + +Some tables are used to store data that's used sequentially; the keys +in this case are just numbers starting with 1 and going up. Fennel +provides alternate syntax for these tables with square brackets: + +```lisp +["abc" "def" "xyz"] ; equivalent to {1 "abc" 2 "def" 3 "xyz"} +``` + +Lua's built-in `table.insert` function is meant to be used with sequential +tables; all values after the inserted value are shifted up by one index: +If you don't provide an index to `table.insert` it will append to the end +of the table. + +The `table.remove` function works similarly; it takes a table and an index +(which defaults to the end of the table) and removes the value at that +index, returning it. + +```lisp +(local ltrs ["a" "b" "c" "d"]) + +(table.remove ltrs) ; Removes "d" +(table.remove ltrs 1) ; Removes "a" +(table.insert ltrs "d") ; Appends "d" +(table.insert ltrs 1 "a") ; Prepends "a" + +(. ltrs 2) ; -> "b" +;; ltrs is back to its original value ["a" "b" "c" "d"] +``` + +The `#` form returns the length of sequential tables and strings: ```lisp (let [tbl ["abc" "def" "xyz"]] @@ -173,6 +194,11 @@ Note that the length of a table with gaps in it is undefined; it can return a number corresponding to any of the table's "boundary" positions between nil and non-nil values. +Lua's standard library is very small, and thus several functions you might +expect to be included are only found in 3rd-party libraries. For instance, +finding the index in a table given a value is done by `lume.find` in the +[Lume](https://github.com/rxi/lume) library. + ### Iteration Looping over table elements is done with `each` and an iterator like @@ -191,7 +217,15 @@ property of the table but depends on which iterator is used with it. You can call `ipairs` on any table, and it will only iterate over numeric keys starting with 1 until it hits a `nil`. -You can use any Lua iterator with `each`, but these are by far the most common. +You can use any [Lua iterator](https://www.lua.org/pil/7.1.html) with +`each`, but these are the most common. Here's an example that walks +through [matches in a string](https://www.lua.org/manual/5.1/manual.html#pdf-string.gmatch): + +```lisp +(var sum 0) +(each [digits (string.gmatch "244 127 163" "%d+")] + (set sum (+ sum (tonumber digits)))) +``` The other iteration construct is `for` which iterates numerically from the provided start value to the inclusive finish value: @@ -229,7 +263,7 @@ expression. Lua programmers will be glad to know there is no need to construct precarious chains of `and`/`or` just to get a value! The other conditional is `when`, which is used for an arbitrary number -of side-effects, has no else clause, and always returns `nil`: +of side-effects and has no else clause: ```lisp (when (currently-raining?) @@ -273,6 +307,8 @@ Finally, `let` can destructure a sequential table into multiple locals: (print x)) ``` +If the size of the table doesn't match the number of binding locals, +missing values are filled with `nil` and extra values are discarded. Note that unlike many languages, `nil` in Lua actually represents the absence of a value, and thus tables cannot contain `nil`. It is an error to try to use `nil` as a key, and using `nil` as a value removes @@ -299,11 +335,10 @@ by destructuring with parens instead of square brackets: You can write your own function which returns multiple values with `values`: ```lisp -(global use-file - (fn [filename] - (if (valid-file-name? filename) - (open-file filename) - (values nil (.. "Invalid filename: " filename))))) +(fn use-file [filename] + (if (valid-file-name? filename) + (open-file filename) + (values nil (.. "Invalid filename: " filename)))) ``` If you detect a serious error that needs to be signaled beyond just @@ -382,13 +417,33 @@ as the varargs in the inner function are out of scope. (print ...))) ``` +## Globals + +Globals are set with `global`. Good code doesn't use too many of +these, but they can be nice for debugging in some contexts. Note that +unlike most forms, with `global` there is no distinction between +creating a new global and giving an existing global a new +value. Functions are created with `fn`, using square brackets for +arguments. + +```lisp +(global add (fn [x y] (+ x y))) +(add 32 12) ; -> 44 +``` + +Unless you are doing ahead-of-time compilation, Fennel will track all +known globals and prevent you from refering to unknown globals, which +prevents a common source of bugs in Lua where typos go undetected. + ## Gotchas There are a few surprises that might bite seasoned lispers. Most of these result necessarily from Fennel's insistence upon imposing zero runtime overhead over Lua. -* The arithmetic and comparison operators are not first-class functions. +* The arithmetic and comparison operators are not first-class functions + They can behave in surprising ways with multiple-return-valued functions, + because the number of arguments to them must be known at compile-time. * There is no `apply` function; use `unpack` (or `table.unpack` depending on your Lua version) instead: `(f 1 3 (unpack [4 9])`. @@ -400,7 +455,7 @@ runtime overhead over Lua. calling `(print tbl)` will emit output like `table: 0x55a3a8749ef0`. If you don't already have one, it's recommended for debugging to define a printer function which calls `fennelview` on its argument - before printing it: `(local view (fennel.dofile "fennelview.fnl")) + before printing it: `(local view (require :fennelview)) (global pp (fn [x] (print (view x))))` * Lua's standard library is quite small, and so common functions like @@ -408,12 +463,6 @@ runtime overhead over Lua. in something like [Lume](https://github.com/rxi/lume) or [luafun](https://luafun.github.io/) for those. -* Locals are per-chunk. Normally this means per-file, but you can also - create chunks with `fennel.eval` or Lua's `loadstring`, etc. One - common source of confusion is that in repl sessions, each input is - its own chunk, which makes `local` basically useless. For values to - persist in a repl session beyond one entry they need to be `global`. - * Lua programmers should note Fennel functions cannot do early returns. ## Other stuff just works @@ -447,23 +496,36 @@ The last value in a Fennel file will be used as the value of the module. This is typically a table containing functions, though it can be any value, like a function. -Modules are looked up by looking thru all the directories on `package.path` -which usually contains the current directory. To require a module that's -in a subdirectory, take the file name, replace the slashes with dots, and -remove the extension, then pass that to `require`. For instance, a file -called `lib/ui/menu.lua` would be read when loading the module `lib.ui.menu`. +By default, modules are looked up by looking thru all the directories on +`package.path`. To require a module that's in a subdirectory, take the file +name, replace the slashes with dots, and remove the extension, then pass +that to `require`. For instance, a file called `lib/ui/menu.lua` would be +read when loading the module `lib.ui.menu`. Out of the box `require` doesn't work with Fennel files, but you can add an entry to Lua's `package.searchers` (`package.loaders` in Lua 5.1) to support it: ```lua +local fennel = require "fennel" table.insert(package.loaders or package.searchers, fennel.searcher) local mylib = require("mylib") -- will compile and load code in mylib.fnl ``` -Fennel has its own search path; `fennel.path` acts the same as -`package.path` but only for requiring Fennel modules. +Or if you're doing it from Fennel code: + +```lisp +(local fennel (require :fennel)) +(table.insert (or package.loaders package.searchers) fennel.searcher) +(local mylib (require :mylib)) +``` + +Once you add this, `require` will work on Fennel files just like it +does with Lua; for instance `(require :mylib.parser)` will look in +"mylib/parser.fnl" on Fennel's search path (stored in `fennel.path` +which is distinct from `package.path` used to find Lua modules). The +path usually includes an entry to let you load things relative to the +current directory by default. ## Embedding @@ -481,8 +543,8 @@ Here is an example of embedding the Fennel compiler inside a ```lua local fennel = require("fennel") local f = io.open("mycode.fnl", "rb") -;; mycode.fnl ends in a line like this: -;; {:draw (fn [] ...) :update (fn [dt] ...)} +-- mycode.fnl ends in a line like this: +-- {:draw (fn [] ...) :update (fn [dt] ...)} local mycode = fennel.dofile("mycode.fnl") f:close() @@ -508,8 +570,8 @@ end ``` -You can add `fennel.lua` as a single file to your project, but if you -also add `fennelview.fnl` then when you use a Fennel repl you'll get -results rendered much more nicely. You can use the function returned -by `fennel.dofile("fennelview.fnl")` directly to turn any table into a -fennel-syntax string rendering of that table for debugging. +You can add `fennel.lua` as a single file to your project, but if you also +add `fennelview.fnl` then when you use a Fennel repl you'll get results +rendered much more nicely. Running `(local view (require :fennelview))` +will get you a `view` function which turns any table into a fennel-syntax +string rendering of that table for debugging.