Skip to content

Commit 46a0bb1

Browse files
committed
docs(readme): describe the plugin and the bridge
1 parent 42622d3 commit 46a0bb1

1 file changed

Lines changed: 242 additions & 12 deletions

File tree

README.md

Lines changed: 242 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
</p>
66

77
<p align="center">
8+
<img alt="Version" src="https://img.shields.io/badge/version-26.1-blue">
89
<img alt="Kotlin" src="https://img.shields.io/badge/Kotlin-2.4.10-7F52FF?logo=kotlin&logoColor=white">
910
<img alt="Build" src="https://img.shields.io/badge/build-Gradle-02303A?logo=gradle&logoColor=white">
1011
<img alt="JVM" src="https://img.shields.io/badge/JVM-17+-ED8B00?logo=openjdk&logoColor=white">
@@ -49,41 +50,270 @@ Artifacts are published to [repo.blueva.net](https://repo.blueva.net/releases),
4950
**Gradle (Kotlin DSL)**
5051

5152
```kotlin
52-
repositories {
53-
maven("https://repo.blueva.net/releases")
53+
plugins {
54+
java
55+
id("net.blueva.mawu") version "26.1"
5456
}
5557

56-
plugins {
57-
id("net.blueva.mawu") version "<version>"
58+
repositories {
59+
maven("https://repo.blueva.net/releases")
5860
}
5961
```
6062

61-
Lua sources are then picked up from:
63+
Every source set gains a Lua directory, so sources are picked up from:
6264

6365
```text
6466
src/
6567
main/
6668
kotlin/
6769
lua/ <- your .lua files
6870
resources/
71+
test/
72+
lua/ <- compiled by compileTestLua
73+
```
74+
75+
The plugin puts `net.blueva:mawu-runtime` on the project's implementation
76+
classpath, which brings `net.blueva:luak-jvm` with it. Building runs
77+
`compileLua`, which writes one prototype per script into the jar, and
78+
`compileTestLua` for the test source set. Only the scripts that changed are
79+
compiled again.
80+
81+
To read Lua from somewhere else as well:
82+
83+
```kotlin
84+
import net.blueva.mawu.gradle.lua
85+
86+
sourceSets {
87+
main {
88+
lua {
89+
srcDir("scripts")
90+
}
91+
}
92+
}
93+
```
94+
95+
## What the Build Checks
96+
97+
`compileLua` parses every script, so the build fails on anything Lua itself
98+
refuses to compile, at the file and line that caused it:
99+
100+
```text
101+
> Lua compilation failed in 2 sources:
102+
/project/src/main/lua/first.lua:1: syntax error near 'is'
103+
/project/src/main/lua/second.lua:4: 'end' expected (to close 'function' at line 1) near <eof>
104+
```
105+
106+
Every source is checked before the build gives up, so one broken file does not
107+
hide the next. What is caught is what a Lua compiler catches: syntax, unclosed
108+
blocks and strings, `goto` and `break` with no label to reach, assignment to a
109+
`<const>`, and the compiler's own limits.
110+
111+
Two more checks read the compiled form rather than the source, so they cost a
112+
walk over instructions and no second parser. **Both fail the build**, because a
113+
name that is wrong is wrong whether Lua notices it now or three frames into a
114+
run:
115+
116+
- **Undefined globals.** A global a script reads that no script writes, that
117+
the standard library does not have, and that the build was not told about.
118+
- **Names off the classpath.** `import 'a.b.C'` and `java.util.logging.Logger`
119+
are constants in the bytecode, so the class or package they name is looked
120+
up while it is still just a string. The same walk follows what a call
121+
returned, so a method missing from the object in hand is caught too.
122+
123+
```text
124+
> Undefined globals:
125+
/project/src/main/lua/main.lua:12: undefined global 'confguration'
126+
127+
> Unknown classes:
128+
/project/src/main/lua/main.lua:2: no class or package named 'java.util.logging.Loggerr'
129+
/project/src/main/lua/main.lua:7: 'java.util.logging.Logger' has no member 'infoo'
69130
```
70131

132+
Both take `MawuSeverity.WARN` to report without failing, or `IGNORE` to skip:
133+
134+
```kotlin
135+
mawu {
136+
// Globals your host installs before running a script.
137+
knownGlobals.addAll("housing", "player")
138+
139+
undefinedGlobals.set(MawuSeverity.WARN)
140+
unknownClasses.set(MawuSeverity.WARN)
141+
}
142+
```
143+
144+
A value that crosses into Lua as a string or a number stops being a Java object
145+
there, so `logger:getName():upper()` is Lua's string library and is left alone.
146+
A name built at runtime is not a constant and cannot be checked at all, which
147+
is the case the severities exist for.
148+
149+
What stays unchecked is what only running settles: a function called with the
150+
wrong number of arguments, a method a Lua table does not have, a field of a
151+
value that turns out to be `nil`. Those surface when the line runs, the same
152+
way they do in Lua itself.
153+
154+
## Configuration
155+
156+
```kotlin
157+
mawu {
158+
// Drop line numbers and local names from compiled scripts. Off by default:
159+
// a stack trace from a running script is worth more than the bytes.
160+
stripDebugInfo.set(false)
161+
162+
// Add mawu-runtime to the implementation classpath. On by default.
163+
addRuntimeDependency.set(true)
164+
165+
// Version of mawu-runtime to depend on. Defaults to the plugin's own.
166+
runtimeVersion.set("26.1")
167+
}
168+
```
169+
170+
## What Ends Up in the Jar
171+
172+
Compiled scripts travel as resources, addressed by the path they had in the
173+
source set, without the extension:
174+
175+
```text
176+
META-INF/mawu/scripts/hello.luac
177+
META-INF/mawu/scripts/lanes/worker.luac
178+
META-INF/mawu/scripts.index
179+
```
180+
181+
`src/main/lua/lanes/worker.lua` is therefore the script id `lanes/worker`. The
182+
index lists every id a jar carries, so an application can enumerate what it was
183+
built with. Nothing here is a `.class` file, and nothing is compiled again at
184+
startup.
185+
71186
## Running Compiled Scripts
72187

73-
Compiled scripts ship as resources in your jar. At runtime, build a Luak environment, load a prototype and call it:
188+
`MawuScripts` reads the prototypes out of the classpath:
189+
190+
```kotlin
191+
import net.blueva.luak.lib.jvm.JvmPlatform
192+
import net.blueva.mawu.runtime.MawuScripts
193+
194+
val globals = JvmPlatform.standardGlobals()
195+
MawuScripts.run(globals, "hello")
196+
```
197+
198+
`load(id)` returns the prototype, `bind(globals, id)` returns it as a callable
199+
function, and `ids()` lists everything on the classpath. Bind one prototype into
200+
as many environments as you need: each is independent, so one script cannot
201+
reach into another's state.
202+
203+
Prototypes are undumped directly rather than passed through `Globals.load`, so
204+
they still load in an environment configured to refuse binary chunks.
205+
206+
## Calling Between Java and Lua
207+
208+
A `MawuLane` is one Lua environment plus the calls that cross it in both
209+
directions. Lanes are independent: what one exposes, another never sees.
74210

75211
```kotlin
76-
import net.blueva.luak.lib.LuaPlatform
212+
import net.blueva.mawu.bridge.MawuLane
213+
214+
val lane = MawuLane()
215+
```
216+
217+
### Java calling Lua
218+
219+
```kotlin
220+
// A module script, one that ends in: return { greet = function(name) ... end }
221+
val api = lane.run("api")
222+
223+
lane.callFunction(api.get("greet"), "world") // "hello world"
224+
lane.call("greet", "world") // a global function
225+
lane.call("net.request", 21) // one nested in a table
226+
lane.callMethod(api, "add", 2) // colon style: function M:add(value)
227+
```
228+
229+
Or with types, by handing the table to a Java interface:
230+
231+
```java
232+
public interface Greeter {
233+
String greet(String name);
234+
default String shout(String name) { return greet(name).toUpperCase(); }
235+
}
77236

78-
val globals = LuaPlatform.standardGlobals()
79-
globals.bind(prototype).call()
237+
Greeter greeter = lane.module("api", Greeter.class);
238+
greeter.greet("world");
80239
```
81240

82-
Each environment is independent, so one script cannot reach into another's state.
241+
Calls go to the table by name, so the script writes `function M.greet(name)`
242+
rather than `function M:greet(name)`. A method the table has no function for
243+
falls back to the interface's own default, and fails by name if there is none.
83244

84-
## Sandboxing
245+
### Lua calling Java
85246

86-
A host that runs Lua it did not write can set bounds on any `Globals` before binding a prototype to it: an instruction budget, a memory ceiling, a refusal to load binary chunks, a fixed random seed. These are Luak features and apply to Mawu-compiled scripts like any other. See the [Luak documentation](https://github.com/BluevaDevelopment/Luak) for the full list.
247+
A script names the classes it wants. Nothing has to be registered from the host
248+
first:
249+
250+
```lua
251+
local Logger = java.util.logging.Logger
252+
local log = Logger:getLogger('my.plugin')
253+
log:info('hello')
254+
255+
local Thing = import 'net.blueva.example.Thing'
256+
local text = java.lang.StringBuilder.new('a'):append('b'):toString()
257+
local level = java.util.logging.Level.WARNING
258+
```
259+
260+
A dependency of the build is on the classpath like anything else, so a script
261+
reaches a third-party library by name too:
262+
263+
```kotlin
264+
dependencies {
265+
implementation("org.apache.commons:commons-lang3:3.19.0")
266+
}
267+
```
268+
269+
```lua
270+
local StringUtils = import 'org.apache.commons.lang3.StringUtils'
271+
result = StringUtils:reverse('mawu')
272+
```
273+
274+
Statics and fields come off the class (`Class:method(...)`, `Class.FIELD`),
275+
constructors are `Class.new(...)`, instance methods are `object:method(...)`,
276+
and a nested class is written with a dot, as `java.util.Map.Entry`. Overloaded
277+
methods pick the overload that fits the arguments.
278+
279+
What the host hands over is live values, not permission:
280+
281+
```kotlin
282+
lane.expose("calculator", Calculator()) // an object that already exists
283+
lane.exposeClass("Logger", Logger::class.java) // a short name for a long one
284+
lane.exposeFunction("log") { args -> println(args.first()); null }
285+
lane.exposeFunctions("host", mapOf("sum" to MawuFunction { args -> args.sumOf { it as Int } }))
286+
```
287+
288+
```lua
289+
local total = calculator:add(40, 2)
290+
log('total is ' .. total)
291+
local sum = host.sum(1, 2, 3)
292+
```
293+
294+
### What crosses
295+
296+
| Lua | Java |
297+
|---|---|
298+
| `nil` | `null` |
299+
| boolean | `Boolean` |
300+
| integer | `Integer` |
301+
| float | `Double` |
302+
| string | `String` |
303+
| a Java object the host exposed | itself, methods and all |
304+
| table, function | `LuaValue`, or a Java interface through `module`/`proxy` |
305+
306+
### What a script can reach
307+
308+
A lane's environment is `JvmPlatform.standardGlobals()` by default, plus `java`
309+
and `import`. The scripts are your project's own source, as trusted as the
310+
Kotlin next to them, so they reach the classpath the same way it does.
311+
312+
Two ways out of that, for a host that runs Lua it did not write:
313+
`MawuLane(exposeClasspath = false)` drops `java` and `import`, and passing a
314+
`Globals` of your own decides the rest. Luak has the bounds for that case (an
315+
instruction budget, a memory ceiling, no binary chunks), and they work on
316+
Mawu-compiled prototypes like on any other.
87317

88318
## Authors
89319

0 commit comments

Comments
 (0)