Scripting Languages
===================
Currently [Lua](https://www.lua.org) is the only supported language for coding LÖVR projects.
But there are several [languages](https://github.com/hengestone/lua-languages) available that
[trans-compile](https://en.wikipedia.org/wiki/Source-to-source_compiler) into Lua.
This document covers only some of them (in alphabetical order):
* [CSharp.lua (C# dialect)](#csharplua-c-dialect)
* [Fennel (Lisp/Clojure dialect)](#fennel-lispclojure-dialect)
* [Haxe (ECMAScript dialect)](#haxe-ecmascript-dialect)
* [MoonScript (CoffeeScript dialect)](#moonscript-coffeescript-dialect)
* [Wu (Rust dialect)](#wu-rust-dialect)
* [Yuescript (Moonscript dialect)](#yuescript-moonscript-dialect)
:::note
A *dialect* means the language is inspired by the parent but not fully syntax compatible.
:::
In general there are two ways of using one of them in a LÖVR project:
1. [Ahead-of-time compilation](https://en.wikipedia.org/wiki/Ahead-of-time_compilation)
* simple setup
The simplest setup is compiling the codebase to Lua before shipping.
* no additional dependencies
No additional compiler dependencies are required in this setup.
Although the language might introduce runtime dependencies.
* runtime error line translation
Most (or only some?) compilers allow to annotate each translated line with the
corresponding one's number in the source file.
A proper stack-trace with error lines rewritten requires the implementation of an
error handler function making use of the annotations.
* virtual file-system
works out of the box.
2. [Just-in-time compilation](https://en.wikipedia.org/wiki/Just-in-time_compilation)
* more complex setup
This solution needs more additional coding inside the project.
* additional dependencies required
In this case the project must ship with the compiler library and its dependencies.
Don't forget to give copyright credit and take care about software license documentation.
* Lua C libraries
* Usage: Plugins Guide
* Shipping: Distribution Guide
* Pure Lua
* Usage: Libraries Guide
* runtime error line translation
is usually done by the compiler without extra care.
* virtual file-system
The Lua loader for the language needs to make use of lovr.filesystem.read or it can't
access files via the virtual file-system.
Without a solution there are two issues:
1. save directory
Modules can't be required from within the LÖVR save directory.
This is a problem when downloading modules is supported since it is the only place
LÖVR allows writing to.
2. fuse mode
In fused mode the virtual file-system is the only working file access method.
Meaning the project won't work at all if it relies on 'require'.
:::note
The more elegant and recommended solution is the just-in-time compilation:
* Source and Lua files can't be out of sync.
* Developer friendly workflow.
* Runtime error line translation is usually done by the compiler without extra care.
* Allows reload during development.
:::
CSharp.lua (C# dialect)
---
* Home: [CSharp.lua Readme](https://github.com/yanghuan/CSharp.lua/blob/master/README.md)
* Source: [GitHub](https://github.com/yanghuan/CSharp.lua)
* Syntax: C# 7.0
* Semantic: C#
* jit compiler: (TODO)
* Features:
* Industrial-quality
* Readable Lua code output
* Supports all the syntax features of C# 7.0
* Supports most of C# 9.0
(TODO: Is CSharp.lua really a dialect? It feels more like a full implementation.)
### ahead-of-time
(TODO)
### just-in-time
(TODO)
Fennel (Lisp/Clojure dialect)
---
* Home: [Fennel](https://fennel-lang.org)
* Source: [GitHub](https://github.com/bakpakin/Fennel)
* Syntax: [Lisp](https://en.wikipedia.org/wiki/Lisp_\(programming_language\)) /
[Clojure](https://clojure.org/) inspired
* Semantic: mostly Lua
* jit compiler: pure Lua
* Dependencies: (TODO)
* Features
* Macro support
* Fully parenthesized prefix notation
* Functional language
### ahead-of-time
$ fennel --compile yourProject
(TODO: error line translation)
### just-in-time
* [Embedding Fennel](https://fennel-lang.org/setup#embedding-fennel)
* [Minimal LÖVR Setup](https://github.com/MattRoelle/min-lovr-fennel)
Haxe (ECMAScript dialect)
---
* Home: [Haxe](https://haxe.org/)
* Source: [GitHub](https://github.com/HaxeFoundation/haxe)
* Syntax: [ECMAScript](https://en.wikipedia.org/wiki/ECMAScript) inspired
* Semantic: (TODO)
* jit compiler: Not available
* Dependencies: (TODO)
* Features:
* Strictly typed
* Object oriented
* Array comprehension
* Pattern matching
* Properties
* Type Parameters
### ahead-of-time
* [Haxe Lua](https://haxe.org/manual/target-lua.html)
* [LÖVR bindings](https://github.com/excessive/lovr-haxe-wrappergen)
(TODO: Line Translation?)
### just-in-time
Haxe does not offer runtime compilation.
MoonScript (CoffeeScript dialect)
---
* Home: [MoonScript](https://www.moonscript.org)
* Source: [GitHub](https://github.com/leafo/moonscript)
* Syntax: [CoffeeScript](https://coffeescript.org) inspired
[(white space sensitive)](https://en.wikipedia.org/wiki/Off-side_rule)
* Semantic: Lua
* jit compiler: Lua + Lua C Library (LPeg)
* Dependencies: [LPeg](http://www.inf.puc-rio.br/~roberto/lpeg/)
* Features:
* Mature language with a good editor support
* Class system for object oriented coding
* Clean syntax avoiding keyword noise
* List and table comprehensions
### ahead-of-time
Compiles the whole project:
$ moonc yourGame
(TODO: Write more about error line rewriting.)
### just-in-time
Setup in conf.lua:
-- Check for moonscript/moonscript/init.lua.
-- The operating specific LPeg library must be at the right location as well.
local lua_path = '?.lua;?/init.lua;moonscript/?/init.lua;'
lovr.filesystem.setRequirePath(lua_path)
-- MoonScript rewrites and sets its require paths from package.path when first required.
-- If you want to alter it afterwards redefine package.moonpath.
package.path = lua_path
local moonscript = require'moonscript'
-- This loader uses lovr.filesystem instead of io.open.
-- Enables require() from LOVR's save directory and fuse mode.
local moon_loader = function(name)
local name_path = name:gsub("%.", "/")
local file, file_path
for path in package.moonpath:gmatch("[^;]+") do
file_path = path:gsub("?", name_path)
file = lovr.filesystem.read(file_path)
if file then break end
end
if file then
local res, err = moonscript.loadstring(file, "@" .. file_path)
if not res then error(file_path .. ": " .. err) end
return res
end
return nil, "Could not find moon file"
end
moonscript.remove_loader()
local loaders = package.loaders or package.searchers
table.insert(loaders, moon_loader)
-- This will require the first .lua or .moon file matching the require paths.
-- If both are present the Lua file is required.
require'myConf'
The main.lua file:
-- Requires either .moon or .lua module files.
-- If both are present the Lua one is required.
require'myMain'
(TODO: Write about error line translation.)
Wu (Rust dialect)
---
* Home: [Wu Guide](https://wu-lang.gitbook.io/guide)
* Source: [GitHub](https://github.com/wu-lang/wu)
* Syntax: [Rust](https://rust-lang.org) inspired
* Semantic: (TODO)
* jit-compiler: Not available
* Dependencies: Rust
* Features
* Gradual typing
* Rust-like trait system
* Expression oriented design
* Pattern matching
* Cool logo
### ahead-of-time
[LÖVR bindings](https://github.com/darltrash/lowr)
### just-in-time
There is no jit-compiler available for Wu.
Yuescript (MoonScript dialect)
---
* Home: [Yuescript](https://yuescript.org)
* Source: [GitHub](https://github.com/pigpigyyy/Yuescript)
* Syntax: [MoonScript](https://moonscript.org) inspired
[(white space sensitive)](https://en.wikipedia.org/wiki/Off-side_rule)
* Semantic: Lua
* jit-compiler: Lua C library
* Dependencies: None
* Features:
* Everything MoonScript features
* Improved table literal syntax (storing game content data)
* Expressive and extremely concise
* Build-in macro support
* Table matching
* Under active development
### ahead-of-time
Use the `-l` flag which enables commenting each line of the Lua file with the corresponding
line-number in the source file:
$ yue -l yourGame
A special error handler can extract this information
and point to the correct line in the source file when an error is thrown.
(TODO: Write more about this error handler function. I guess some Yuescript function can be reused.)
### just-in-time
Yuescript comes also (beside the standalone executable) in form of a Lua c library.
You need to ship a different one for each operating system you want to support.
The Yuescript compiler takes care of the line-number translation,
no extra work needed in this setup.
After the setup (in conf.lua) Lua and Yuescript modules can be required from each other
in the usual way.
Setup in conf.lua:
require('yue')
-- Yuescript's uses of io.open need to be replaced with lovr.filesystem ones.
yue.file_exist = lovr.filesystem.isFile
yue.read_file = function(fname)
contents, bytes = lovr.filesystem.read(fname)
if contents == nil then
return nil, 'File not found.'
end
return contents
end
-- It makes sense to keep Lua and Yuescript search paths in sync.
yue.options.path = '?.yue;?/init.yue'
lovr.filesystem.setRequirePath('?.lua;?/init.lua')
-- Requires either yueConf.yue or yueConf/init.yue.
-- Use yue() instead of require() to enable error line translation.
-- In yueConf require() works like expected.
yue('yueConf')
The main.lua file:
-- Requires file yueMain.yue or yueMain/init.yue.
-- Use yue() instead of require() to enable error line translation.
-- In yueMain require() works like expected.
yue'yueMain'