[HN Gopher] Fennel - Lisp in Lua ___________________________________________________________________ Fennel - Lisp in Lua Author : tosh Score : 186 points Date : 2020-09-06 12:30 UTC (10 hours ago) (HTM) web link (fennel-lang.org) (TXT) w3m dump (fennel-lang.org) | galfarragem wrote: | AFAIK, for his author, Fennel is legacy. Nowadays he seems to | contribute only to Janet (https://janet-lang.org). | molloy wrote: | It's still under very active development by technomancy and | others, with a friendly community and lively IRC channel. | miguendes wrote: | Interesting project. I had never heard of a lisp flavour for lua | before. Looks very elegant. | | It reminds me of hy, for python. [1] [1] | https://github.com/hylang/hy | dunham wrote: | There is another small lisp that targets both Lua and | Javascript called "lumen"[1]. Small enough that you can easily | tinker with it. I added clojure style {} literals to the reader | and paired it with hyperapp to write a toy web application. | | [1]: https://github.com/sctb/lumen | rcarmo wrote: | If you are interested in LISP variants, check my "catalogue" | over at https://taoofmac.com/space/dev/LISP | Rochus wrote: | Plenty of lisp frontends for lua, see | https://github.com/hengestone/lua-languages#lisp | mumblemumble wrote: | I haven't spent any time with Fennel, but it seems more | promising to me. | | I couldn't get comfortable with Hy. It drops so many lispy | things, such as let statements and persistent data structures, | in order to stay close to the Python substrate. (You can get | both from libraries, but at the cost of easy interop with | Python packages, which kind of defeats the whole point of being | on the Python platform in the first place.) It ultimately came | out feeling less like I was working with a lisp, and more like | I was working with an s-expression syntax for Python. | | OTOH, if you think that Python, while otherwise great, is | really suffering for lack of defmacro, Hy absolutely succeeds | in getting you Python with defmacro. | rcarmo wrote: | I ended up dropping Hy very soon after they dropped let. Had | an entire blog engine written in it: | https://github.com/rcarmo/sushy | | ...but I wanted Hy to be LISP, not a thin veneer over Python. | slumos wrote: | I wrote my simple Hammerspoon config in Fennel and am really | happy with it. | | https://github.com/slumos/dot.hammerspoon | j_m_b wrote: | What are some projects that people are using that use Lua? Why | use it over something like python for scripting? | pkphilip wrote: | I have embedded lua in Spring Boot webapps where I required an | external rules files which could be easy to read for business | folks. | | Example: Specialised handling for different customer types and | you don't want to manage all the rules in the RDBMS but would | rather run a separate script for each customer type and have | this script in a plain text format and version managed using | Git. | stevedonovan wrote: | Probably because Lua is meant to be embedded in an application | and provides a small, highly optimized VM. On my system Lua is | smaller than PCRE. | jmiskovic wrote: | I've made a suite of digital musical instruments for Android | phones in Lua. Now I'm using same language to build some VR | prototypes. | | Here are some benefits of Lua over Python. It's really powerful | language (anonymous functions, closures, tail-call | optimization, full lexical scoping, coroutines...) that | supports many paradigms, and yet it is tiny and elegant. There | are few surprises and everything is explicit. | | Runtime is easily embeddable, which means it will be more | readily available on exotic platforms. The interpreter is much | faster than Python. C code is effortless to call into with FFI | and resulting code looks just like normal Lua. | | Not everything is roses. I prefer readability and less | verbosity of Python. The 1-indexing needs some getting used to. | Batteries are not included (to be more portable), so there are | dozens of implementations of basic things like serialization, | OO classes and table copying. Most online material (wiki) was | written very long ago and it's full of language proposals that | never succeeded. Some valuable resources can be found in | documentation of hosting frameworks (Defold, Solar2D, Roblox, | ComputerCraft, WoW). | gumby wrote: | the easiest way to put it is that you embed Lua into your app | (say scripts that are loaded at startup or even as a command | line) while it's more natural to embed your external | functionality into Python (e.e. numpy). | | It's simpler to connect lua to your c/c++ datastructures than | it is to connect Python, and it's more complicated to write | little things in Python. | | Sometimes it's easier to grab a pencil and scribble a little | note on a piece of paper. Sometimes it's a lot more useful to | type a note into your phone or computer than to deal with | paper. | stevekemp wrote: | I wrote a console-based email-client, using Lua for all | configuration and scripting needs. | | Using Lua is generally done because it is easy to embed inside | a host-application. While it is possible to embed Python, Perl, | or other languages, they're relatively heavyweight and not so | commonly used in that case. | | In my own application I always felt annoyed that mutt didn't | have "real" scripting. Just an ad-hoc configuration that made | lots of things possible, but neglected some basics (such as | loops and similar). | | Configuring a mail-client in Lua was a nice exercise, but | eventually I moved on to pay for gsuite rather than self- | hosting a mailserver of my own so it became a "done" project. | Definitely a useful learning experience though, experimenting | with user-interface, embedded scripting, and going through lots | of learning relating to MIME-handling & etc. | fit2rule wrote: | Your project sounds cool - is it possible to process email | with Lua scripts, so that I could use a mailbox as a business | process queue that is also user-friendly, while having a | suite of Lua apps processing the mailboxes? If so, care to | share details of your project if its open/available? | masklinn wrote: | Lua is way more common for game scripting than Python. One of | the few major games which used Python I can think of was Civ | IV, and Civ V switched to Lua. | | Lua is smaller, easier to embed interface / interact with (e.g. | it's way less ceremony to expose a native function to Lua than | to Python), and tends to be faster, especially when jumping | back and forth between the engine and the scripting. Embedding | / scripting (within a larger program) is the original use-case | of Lua, not so Python. | | I believe it's also much easier to secure / sandbox (remove | bits you don't want script writers to have access to) as well, | the stdlib is smaller and I think it has less interactions | between modules. | | Python embedding / scripting tends to be more common for | software where the securing / restriction aspect is smaller but | flexibility & larger embeds are necessary e.g. 3D, CAD and | other "production pipeline" software tends to be scripted with | Python. | pansa2 wrote: | > _I believe [Lua is] also much easier to secure / sandbox | [than Python]_ | | Yes. C code that embeds the Lua VM has total control over | which functions are exposed to the Lua code. It's possible to | create a VM that, for example, has no access to the | filesystem. | | AFAIK it's not possible to do that if you embed Python. | Dangerous functions like `open` are always available and | sandboxing facilities have to try and prevent untrusted code | from getting a reference to them. Unfortunately, there are | numerous ways to work around these restrictions and obtain | access to dangerous functions, e.g. via `__builtins__`. | | Another reason running untrusted Lua code is considered | fairly safe is that the VM is well-engineered and has a | history of very few bugs [0]. However, the latest 5.4.0 | release seems to have more bugs than older releases. | | It might still possible for untrusted Lua code to use 100% | CPU and hang a program, or to read data from the embedding | process using a Spectre-style attack (although even that's | unlikely because the Lua VM is an interpreter rather than a | JIT compiler). However, it's quite possible to secure an | embedded Lua VM to prevent it from doing things like | accessing arbitrary files. | | [0] https://www.lua.org/bugs.html | philsnow wrote: | > the Lua VM is an interpreter rather than a JIT compiler | | There's also luajit, but I don't know how common it is vs | "just Lua". My impression was that luajit was used pretty | often though. | formerly_proven wrote: | Sandboxing Python used to be and still is hairy and very | hard (it requires intricate knowledge of CPython, because | you are going to inspect the AST for unwanted things and do | some other relatively low-level stuff) and Python is also a | pretty big dependency (~5-10 MB). I think many recent-ish | additions to Python may make sandboxing easier (e.g. audit | hooks, subinterpreters), but it's still hard and messy. | formerly_proven wrote: | Lua is a tiny, relatively simple language with a simple data | model built on a small runtime that's very fast for an | interpreter and easy to embed into C/C++ applications (once you | dealt with the stack, mentally or otherwise). Startup time is | ~nil. | | Python is a large, complex language with an exceedingly | complicated data model built on a runtime where the core | interpreter is about ~4 MB, which is also a pretty slow | interpreter (mostly due to the exceedingly complicated data | model). Python is certainly not hard to embed on an API level, | but somewhat annoying to package up for a build. Python is very | hard to sandbox, whereas Lua is basically sandboxed by default. | Startup time is on the order of ~100-200 ms. | chgibb wrote: | We're using Lua as a base for bringing Flutter into other | languages https://github.com/chgibb/hydro-sdk | fortran77 wrote: | The reason I used Lua was its "accessibility" -- so end users can | script -- and not necessarily for the beauty and elegance of the | language. It was also very easy to integrate into a C++ project. | mumblemumble wrote: | A nice summary of what Fennel's about here: | http://jakob.space/blog/thoughts-on-lisps.html#org4d7f824 | masklinn wrote: | It's kinda weird that the only mention of Clojure is | | > Clojure's license makes it a complete non starter for me, | sorry. Live free or die. | | when at first glance fennel looks a lot like clojure e.g. `fn`, | square brackets, {} for maps / table literals. | SahAssar wrote: | > when at first glance fennel looks a lot like clojure e.g. | `fn`, square brackets, {} for maps / table literals. | | Aren't those mostly inherited from lua and present in a lot | of different languages invented before and after clojure? | masklinn wrote: | Lua defines functions with the `function` keywords, and as | most other C-style languages uses parenthesis to wrap the | function parameters, not brackets. | | It does use `{}` for table literals but requires the `=` | and `,` separators, where fennel (and clojure) uses no | internal separator at all: a map literal is a pair of | braces around a _plist_ (https://www.gnu.org/software/emacs | /manual/html_node/elisp/Pr.... | | Historically, lisp have used something around "define" to | define functions (e.g. define, defun), parenthesis as | essentially the only grouping, and would use macros or | special-forms rather than complex literals. | | Clojure is the first lisp I know of (so I may well have | missed a predecessor which inspired it) which largely | diverged from that. | | [0] clojure allows commas but ignores them entirely, I | don't know whether fennel allows them as well | lispm wrote: | If we think Clojure is a 'diverging Lisp', then there are | other diverging Lisps with different syntax like MDL, | Logo, Dylan, even JavaScript. Some Scheme code uses | angular brackets. | | Historically core Lisp has left other grouping character | or syntax to sublanguages or new data types. There are | many examples for that. This is usually done via reader | macros, which open up the s-expression reader to the | programmer. | | This is a Joshua rule: (defrule | grandfather-determiner (:backward) ;; there are | 3 ways to find your grandfather IF [or [is- | genetic-grandfather-of ?gramps ?kid] [is- | grandfather-thru-step-parent ?gramps ?kid] [is- | step-grandfather-of ?gramps ?kid]] THEN [is- | grandfather-of ?gramps ?kid]) | | Many internal or external domain specific languages for | Lisp use special syntax and defining forms, since their | authors either believed that it's easier to use in | general or that it was necessary for 'end users' of their | systems. Early examples were maths systems like Macsyma, | which were programmable, but addressed mathematicians as | audience. Logo, which was Lisp for kids. One of the first | theorem provers had the first ML as the programming | language interface for its users. | | Historically Lisp 2 was supposed to get rid of simple | s-expressions on the surface. There was a different | syntax defined for it. | masklinn wrote: | I was mostly talking about diverging in the way Fennel | had. | | > Historically core Lisp has left other grouping | character or syntax to sublanguages or new data types. | | Sure, but Clojure uses other grouping characters for the | main dialect itself, and Fennel uses very similar | deviations from the "Lisp baseline", hence my wondering | about the inspiration. | lispm wrote: | Square brackets have been used in some Scheme for a long | time. There are also Scheme books which use them in code. | masklinn wrote: | Used them to define the formal parameters of a function? | Because that's what I'm referring to. Fennel uses | (fn [param*] expr*) | | for function definition, which is also how it is spelled | in Clojure. | lispm wrote: | One can use them everywhere instead of parentheses. | nonbirithm wrote: | I wish Lua had immutable data structures with memory sharing. | It would make the designs of some things much more | performant. | vsurabhi wrote: | This and a recent feature addition to neovim (passing lua | closures to vimscript functions [1]) should make a good | substitute for elisp to configure neovim. | | [1] https://github.com/neovim/neovim/pull/12507 | merricksb wrote: | If curious, see previous discussion from 2018 | | https://news.ycombinator.com/item?id=18016168 | minxomat wrote: | Here's an example web app hello-world in Fennel, parsing requests | and responding with HTML rendered from the S expressions: | https://gist.github.com/turbo/4388c9ad19028560053951a25b3b45... | yawn wrote: | Thanks for posting this. I took a look at Fennel a few months | ago and although they list 2 web frameworks on the front page, | there's little documentation (that may have changed since then) | about how to interface with other Lua projects. I kept running | into things I couldn't figure out when working with the | existing Lua ecosystem and projects. | erikcw wrote: | I've been using it quite successfully in a large OpenResty | project. | | While you're right that there aren't any docs on working with | any of the 3rd party projects listed in the homepage - I | found that it was pretty straight forward to use as a drop in | Lua replacement by examining the compiled Lua from the online | REPL. I was surprised how clean the Lua output is. There is | really no magic here. I believe one of the project goals is | to stay as close to Lua semantics as possible. Basically just | lisp flavored Lua. | | In my project I just use the compiled .fnl files as I would | any of other .lua file. | | Is something specific you're getting stuck with? I'd be happy | to try to point you in the right direction. | | It's really nice! I encourage you to give it another try. | rcarmo wrote: | It should be noted that 0.6.0 came out 3 days ago, with a few | improvements: | | https://fennel-lang.org/v0.6.0/ | molloy wrote: | A lot of the questions in this thread are answered here: | https://fennel-lang.org/rationale | rudolfwinestock wrote: | A Hacker News regular, technomancy (the same guy behind the | Atreus keyboard), has contributed a great deal to the Fennel | language. https://builds.sr.ht/~technomancy/fennel-lang.org | | I'd like to see him visit this thread. | | https://news.ycombinator.com/user?id=technomancy | reitzensteinm wrote: | Not to mention lein for Clojure. He only posts here a few times | a year which might be how he manages to be so prolific. | bokchoi wrote: | And if you haven't checked out his pico-8 game he used fennel | to write, you ought to. It's great! | | https://technomancy.us/190 | manexploitsman wrote: | Why do you need all of those brackets? | remix2000 wrote: | In the realm of Lua Lisps, there is also Urn: https://urn- | lang.com/ | aasasd wrote: | I really wish Lua were popular for small-scale utility scripting | instead of Python and JS. Because Lua is way snappier: even with | language translation, plenty of Fennel scripts will run under | Python's startup time. Likewise, Lua seems like a great fit for | small mobile apps and automation (dunno about threading though, | but I seem to have read that Lua can do threads with a _library_ | ). | | My primary woe with Lua is the absence of 'null'. You can't | preserve structure of deserialized tables if there are nil values | in them, so you can't really build API middleware and such, | without extra effort for that (so you'd also not be able to use | functional libraries like Penlight in your middleware). | ebg13 wrote: | Lua will never be more popular without a proper standard | library. | | Leaving everything up to the user so that you have to manually | implement (or hunt the web for) even the most basic features is | mega annoying. How many millions of people need to rewrite | string.startswith? | | The whole "sequence iteration stops at the first internal hole | and therefore the # operator doesn't do what you think it | should" gotcha is extremely user hostile. | | Metatables are cool and all, but holy crap are they confusing | for Lua newbies. Having an integrated class system would go a | long way. | aasasd wrote: | Ah, yes, the table length/ iteration thing probably deserves | the second place or a tie in the annoyances. | | The library thing is eh: just grab Penlight if you aren't | running Lua on a microchip. | dwheeler wrote: | Agreed. One of the reasons that Python grew popular so fast | was its "batteries included" approach. When a developer needs | to do some common thing, it's really helpful to have one | obvious way to do it that is highly reliable and well | integrated with the rest of the system. | fit2rule wrote: | 1. Luarocks. 2. I regularly carry these little enumerables | around with me and use it so frequently I found it quaint to | be reminded of the 'hole problem': --- get | all packed entries, until gap -- func on the provided | list -- @param list table to be inspected -- | @param func do func what you func want table.each = | function(list, func) for i,v in ipairs(list) do | func(v, i) end end --- get all | entries, packed or not, until completion -- func on | the provided list, completely -- @param list table to | be inspected -- @param func do func what you func | want table.all = function(list, func) if | (list ~= nil) then for i,v in pairs(list) do | func(v, i) end end end | | Disclaimer: I love Lua and would us it for everything if I | could. | ebg13 wrote: | Do you also carry a table deepcopy with you? What about | string.trim? | | And LuaRocks is a trash fire that doesn't integrate well | with Lua's primary role as an embedded plugin engine and | leaves you to waste your time in endless discovery of a | slew of unmaintained packages that all do the same thing in | different ways and with different bugs. Want to | collaborate? I hope everyone is familiar with "classy" and | not "oops" or "objectlua" or "Luaoop" or "lobject" or | "classyng" or "klesi" or "halo" or "lua-c3class" or "pool" | or "middleclass" or "Sunclass" or any other module that | implements one of the half dozen official recommendations | for faking classes with metatables or closures. It also | becomes a huge "left-pad" risk. | fit2rule wrote: | Table deepcopy: table.unpack() does pretty much | everything I've ever needed it to do, but for those | tricker tables, there's always | https://gist.github.com/tylerneylon/81333721109155b2d244 | - and I freely admit that yes, the link to that gist is | in my "Lua dev notes" bookmarks file, which I refer to | often. | | Hmmm ... string.trim: umm, not really something I find I | need, a lot of times.. but as usual, Lua-users provides | some incite into why, exactly, its not always as simple | as one might think. (http://lua- | users.org/wiki/StringTrim) | | As for your brilliant discourse on Luarocks, ok. Not | gonna get any resistance from me on that. I know the pain | you describe. But I have had success with using it to | copy environments on other machines 99% of the time .. | which is not what I could say about the hell that python | has put me through, at times. But I guess that's par for | the course. | pansa2 wrote: | > _Lua will never be more popular without a proper standard | library._ | | How would you add a large standard library without | compromising Lua's main purpose - to be a lightweight, | embeddable language? | ebg13 wrote: | It would still be lightweight and embeddable with common | sense pure Lua functionality like classes and major utility | methods like string.startswith/string.endswith/string.trim/ | string.split/table.deepcopy. You don't have to integrate a | multiprocessing web server for different operating systems. | | Instead you're left with bullshit community essays like | http://lua-users.org/wiki/SplitJoin which shows pages and | pages of different user attempts for one extremely common | function, many of which explicitly do not work! | | Lua has its strengths, but faffing about refusing to just | standardize the right way to do things is super user- | hostile. | stjohnswarts wrote: | have a "smol-lua" that is a proper subset of the "hugely- | lua" ? I really wish there were more good options for that | for python. | samatman wrote: | could you expand on that a bit? | | null and nil are effectively synonyms, and I'm having | difficulty understanding what about Lua's use of nil has caused | problems for you. | aasasd wrote: | In Lua, nil is not a value. It's an all-encompassing void in | which variables are suspended when not having some other | value--simultaneously existing and non-existing: both | manifest variables or fields of this reality and the infinite | multitude of potential variables of other timelines. And you | can't tell which reality you're in. | | So it's useless for interfacing with 'null's of other | languages. | samatman wrote: | In Lua, nil is assuredly a value. `return type(nil) == | 'nil'` is a valid program which returns `true`. | | It doesn't have the semantics you're used to from other | languages. I happen to prefer Lua's approach, I think the | distinction between undefined and null is clunky and I'm | glad that `undef` didn't make the cut in 5.4. | | You presumably disagree, which is fine. | | There is one rough edge I've encountered, which is down to | a serious wart in the standard library: `table.insert(tab, | value)` inserts the value at the end of the array portion, | which in Lua we express as `#tab + 1`. | | But `table.insert(tab, index, value)` is what's used to | insert into another slot in the array portion. This is a | serious mistake which only prevails for historical reasons. | | I beg of you: never shift the meaning of parameters around | when you add more of them. Nothing good will ever come of | this! | | As a result, `table.insert(a, b, nil)` doesn't do the same | thing as `table.insert(a, b)`. In the three parameter | version, `b` is an index, in the two parameter version, | it's a value. | | It's not possible to write this sort of monstrosity in pure | Lua, where nil is well behaved; but `table.insert` is a | builtin, implemented in C, or whatever the host language of | your particular Lua implementation happens to be. | | In C, there's a difference between a `nil` on the parameter | stack, and a shorter parameter stack. A Lua programmer will | never see this: passing a third `nil` or just calling a | function with two parameters will have the same effect. | mumblemumble wrote: | Lua doesn't distinguish between, "the key is not in the | table," and, "there is a value for that key, and the value is | nil." Setting the value for the key to nil simply erases it | from the table. | samatman wrote: | Ah, so the problem is JSON compatibility. | | Yeah, I can see that. But I have to say: I think the | mistake was on JSONs part. That's what `nil` should mean, | imho: the value of anything which has no other value. | | An object { "foo" : 12, "bar" : null } could just be { | "foo" : 12 }, and if I was expecting a "bar" field, well, I | can set it to null/nil myself. | | Other people have pointed out ways to work around it, so I | won't go down that road myself. | nikki93 wrote: | You can just create a sentinel value and use that. eg. | `local mySentinel = {}`. Then insert that value when that's | the behavior you want. `nil` on its own rarely makes for a | good semantic understanding of what value is actually in | the table anyways. | | Also, in Lua 5.4, the behavior is different -- you need to | do `t[key] = undef` (which is the only context you can use | `undef` in). And `nil` can actually be a value in a table. | Not saying what's better or worse, but that the behavior in | 5.4 is different. | pansa2 wrote: | Did `undef` actually make it into 5.4? I thought it was | removed during the beta period. | samatman wrote: | It was. | aasasd wrote: | Do libraries like Penlight know about your sentinels or | 'undef'? Maybe in ten years they will use undef, or maybe | you whipped up your own functional library and keep it | optimized and full-featured. But now we have what we | have. | | Besides, please tell: if you receive an empty object as a | value in JSON, how do you preserve it in your 'sentinel' | approach? Gotta need another special value for that. | nikki93 wrote: | For the JSON case, it doesn't make sense to use an empty | object as a sentinel, for sure. JSON parsers usually have | their own sentinel for `null`. Lua has a lot of issues, | but `nil` being not-present hasn't been an issue that has | come up a lot in practice. If you have any specific cases | where that has been an issue, would be interesting to | hear about them. | aasasd wrote: | As I wrote in the first comment, the use-case is e.g. API | integration, which is what 'scripting' often is, these | days. More widely, any transformations or filtering or | helper code where you accept structures and pass them on | after mild fiddling--again, quite typical use for Python | or JS if you ever pipe data between scripts in the | terminal or over the network. You don't put your data | schema in each of those scripts and maintain them on | every change at the source. Imagine implementing `jq` in | Lua with all those 'sentinels' throughout the whole | function library. | | The issue is that interoperability is crippled. The fact | that you haven't so far run into these use-cases doesn't | help with the problem that an entire, very common, class | of functionality can't be done without tracking those | nulls at each and every step with some crutches that | bring their own troubles. | nikki93 wrote: | Yeah I actually don't think Lua is great for those | things, tbh. Lua is not a good tool for manipulating JSON | --JavaScript is probably sensible for that (it is | JavaScript's object format, after all). And yeah I don't | think it's good to use a bunch of external libraries in | Lua, since as you say there's not a common ground for how | data is organized. The libraries I rely on are are more | native integration things like lpeg, luasocket, luasec, | love2d, ... They've all been quite good. And yeah you | keep saying 'sentinels' in quotes but honestly it's | worked fine when using lua-cjson. There's always an | impedance mismatch between JavaScript's object format and | Lua's though. Lua also allows non-string keys which are | great in Lua but not a thing in JavaScript, and in | practice I've found the array/object-table mismatch more | problematic than `nil`/`null` and so on. | | I've mostly applied Lua for embedding in game engines, | and to that end there's nothing that comes close, short | of rolling your own VM that's exactly built the way you | want. The main way to use it I think is to embed it | somewhere and build up the set of utilities / libraries | that you need to script over. But yeah it's not great for | API glue code bc. APIs tend to bias toward JavaScript / | JSON and other ways of doing things. | sillysaurusx wrote: | Lumen - Lisp in Lua and JavaScript: https://github.com/sctb/lumen | | Scott Bell wrote a lot of that. I was fascinated by the self- | hosting technique. The whole repo is so small that you can almost | dismiss it as some kind of academic toy, if you don't realize | it's a full-featured production-grade lisp. | | (Not to take anything away from Fennel! The more lisp, the | better.) | tgbugs wrote: | I knew about Fennel, but did not know about Lumen. Thanks for | the pointer! | | edit: Looking at the git history is very educational for how to | bootstrap a self hosting system like this. Fascinating. | pmoriarty wrote: | Would someone be so kind as to compare Lumen to Fennel? ___________________________________________________________________ (page generated 2020-09-06 23:00 UTC)