Skip to content

Commit bb65642

Browse files
committed
Merge remote-tracking branch 'origin/master'
# Conflicts: # SUMMARY.md # namespaces/skript.md
2 parents 58d9950 + 64fb653 commit bb65642

File tree

5 files changed

+604
-0
lines changed

5 files changed

+604
-0
lines changed

.gitbook/assets/image (1).png

20.2 KB
Loading

.gitbook/assets/image.png

15.8 KB
Loading

readme/creating-a-library.md

Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
---
2+
description: >-
3+
A basic guide to creating an external language library (addon) that provides
4+
extra syntax or functionality.
5+
---
6+
7+
# Creating a Library
8+
9+
The default ByteSkript compiler supports external language libraries which can add new syntax, language features and even runtime dependency classes that will be exported when using the **SkriptJarBuilder**.
10+
11+
These libraries can be written in Java or other JVM languages, using the ByteSkript API.
12+
13+
### Basic Structure
14+
15+
All libraries need a core class implementing the `mx.kenzie.skript.api.Library` interface. An instance of this class will be registered and will provide the syntax and features.
16+
17+
Libraries also need a **main** class. This has to be specified in the `MANIFEST.MF`.
18+
19+
These can be the same class for convenience.
20+
21+
The main class needs a `static void load(Skript skript)` method.
22+
23+
For simplicity, you can extend the `ModifiableLibrary` class.
24+
25+
{% code title="MyLibrary.java" %}
26+
```java
27+
package org.example;
28+
29+
import mx.kenzie.skript.api.*;
30+
import mx.kenzie.skript.runtime.Skript;
31+
32+
33+
public class MyLibrary extends ModifiableLibrary implements Library {
34+
35+
public MyLibrary() {
36+
super("my_library");
37+
}
38+
39+
public static void main(String[] args) {
40+
41+
}
42+
43+
// This method will be called by ByteSkript
44+
public static void load(Skript skript) {
45+
skript.registerLibrary(new MyLibrary()); // simple registration
46+
}
47+
48+
}
49+
```
50+
{% endcode %}
51+
52+
The `Library` interface has several important methods you will need to implement if not using the `ModifiableLibrary` class.
53+
54+
| Method | Description |
55+
| --------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
56+
| `name` | Your library's name (for error messages, etc.) |
57+
| `getTypes` | An array of types that your library wants to use for `%Type%` syntax patterns. |
58+
| `getConstructs` | <p>An array of language constructs registered by your library. Basic libraries will not register any.<br>This can be used to add entirely new language grammar.</p> |
59+
| `getSyntax` | <p>An array of syntax classes registered by your library.<br>This is where you register new syntax.</p> |
60+
| `getProperties` | A list of properties registered by your library. This is for adding complex property expressions. This is for use with the v2 syntax API. |
61+
| `getHandlers` | <p>Returns a list of handlers valid for the current state and language element for filtering purposes.<br>This may simply be a version of <code>getSyntax</code> filtered by state.</p> |
62+
63+
The `mx.kenzie.skript.api.ModifiableLibrary` class can be extended to provide a basic Library implementation, where syntax can be manually registered in the constructor. This will mean you do not need to implement the methods.
64+
65+
ByteSkript's built-in `SkriptLangSpec` uses the `ModifiableLibrary` template internally.
66+
67+
{% hint style="info" %}
68+
**SkriptLangSpec** is the built-in implementation of the Skript language.
69+
70+
A modified version of ByteSkript could use a custom language implementation with the default compiler.
71+
{% endhint %}
72+
73+
### Registering Syntax
74+
75+
Most libraries will want to register syntax. This can be done using either the v1 or the v2 API. Examples in this section are for the v1 API, which allows greater control over registration.
76+
77+
#### Simple Syntax
78+
79+
Template syntax classes are available to extend, to add new behaviour quickly and without much effort.
80+
81+
For this guide we will be looking at adding an expression.
82+
83+
Most expressions will extend the `SimpleExpression` class, which deals with most of the basic functionality.
84+
85+
{% code title="SimpleExampleExpression.java" %}
86+
```java
87+
public class SimpleExampleExpression extends SimpleExpression {
88+
89+
public SimpleExampleExpression(Library provider) {
90+
super(provider, StandardElements.EXPRESSION, "pattern goes here");
91+
// provider = the library this comes from
92+
// the syntax type
93+
// the syntax pattern(s)
94+
}
95+
96+
}
97+
```
98+
{% endcode %}
99+
100+
Most expressions will use the `StandardElements.EXPRESSION` enum.
101+
102+
The syntax class does not necessarily need to provide the functionality it promises. Instead, methods can be specified in the `handlers` map for different states.
103+
104+
Below is a very simple example expression that returns the system line separator string.
105+
106+
{% code title="SimpleExampleExpression.java" %}
107+
```java
108+
public class SimpleExampleExpression extends SimpleExpression {
109+
110+
public SimpleExampleExpression(Library provider) {
111+
super(provider, StandardElements.EXPRESSION, "example expression");
112+
handlers.put(StandardHandlers.GET, findMethod(System.class, "lineSeparator"));
113+
}
114+
115+
}
116+
```
117+
{% endcode %}
118+
119+
The `GET` handler provides behaviour for when this expression is being 'retrieved' such as being used in another expression or effect. This handler is the most common.
120+
121+
Expressions that support the `set...` and `delete...` effects would register handlers for `SET` and `DELETE` as well.
122+
123+
In this example, when the `GET` mode is used, the `System.lineSeparator()` method will be called.
124+
125+
Lastly, we ought to specify what type this expression returns (so that the syntax its used in knows what to expect.)
126+
127+
{% hint style="info" %}
128+
This is specified as a `Type` rather than a `Class<?>` since the type may not exist yet (if it is a custom type, for example.)&#x20;
129+
130+
A `Type` can be created with `new Type(class)`.
131+
{% endhint %}
132+
133+
Common types (string, object, etc.) can be found in the `CommonTypes` class to avoid multiple of the same type object being created.
134+
135+
{% code title="SimpleExampleExpression.java" %}
136+
```java
137+
public class SimpleExampleExpression extends SimpleExpression {
138+
139+
public SimpleExampleExpression(Library provider) {
140+
super(provider, StandardElements.EXPRESSION, "example expression");
141+
handlers.put(StandardHandlers.GET, findMethod(System.class, "lineSeparator"));
142+
}
143+
144+
@Override
145+
public Type getReturnType() {
146+
return CommonTypes.STRING;
147+
}
148+
149+
}
150+
```
151+
{% endcode %}
152+
153+
Our expression class will then need to be registered with our Library. If we are using the ModifiableLibrary template, this can be done in the library's constructor.
154+
155+
```java
156+
public MyLibrary() {
157+
super("my_library");
158+
registerSyntax(CompileState.STATEMENT, new SimpleExampleExpression(this));
159+
}
160+
```
161+
162+
{% hint style="info" %}
163+
The `STATEMENT` compile-state is for expressions.
164+
{% endhint %}
165+
166+
Simply returning the value of a static method from `System` is not very useful, and most syntaxes will want to do more than this.
167+
168+
If we want to provide a custom implementation, we would need to link to our own method.
169+
170+
```java
171+
public SimpleExampleExpression(Library provider) {
172+
super(provider, StandardElements.EXPRESSION, "example expression %Object%");
173+
handlers.put(StandardHandlers.GET, findMethod(this.getClass(), "doSomething", Object.class));
174+
// pur our own method as the handler
175+
}
176+
177+
public static String doSomething(Object input) {
178+
return input + " blob"
179+
}
180+
```
181+
182+
In this example, the syntax would call our `doSomething` method with the `%Object%` input that the user provides.
183+
184+
If the user ran `example expression "hello"` our expression would return `hello blob`.
185+
186+
{% hint style="danger" %}
187+
Our method may not be available at runtime!
188+
{% endhint %}
189+
190+
If the `SkriptCompiler` or `SkriptJarBuilder` tools are used, our syntax class will not be available in the output. As it requires our `doSomething` method, this syntax will **not** be usable at runtime.
191+
192+
Obviously, this isn't useful, so we have three options.
193+
194+
Firstly, we can call a method from a class that **is** available at runtime.
195+
196+
Our library may specify runtime dependencies to be exported to the compiled Jar file. We could call a method from one of those classes rather than a syntax class.
197+
198+
Secondly, we can tell the ByteSkript compiler to **export** our method into the compiled script.
199+
200+
This can be done using the `@ForceExtract` annotation.
201+
202+
```java
203+
@ForceExtract
204+
public static String doSomething(Object input) {
205+
return input + " blob"
206+
}
207+
```
208+
209+
The compiler will extract the bytecode source of our method and add it to the script's class as a hidden **synthetic** method, which will be called instead.
210+
211+
This means we don't need any runtime dependencies for our syntax at all.
212+
213+
{% hint style="danger" %}
214+
This is not suitable for all methods.
215+
216+
* If your method contains a lambda this cannot be exported (the dynamic instruction uses a local hidden class.)
217+
* If your method accesses a local field this cannot be exported.
218+
* If your method calls another local method this cannot be exported.
219+
{% endhint %}
220+
221+
Thirdly, we can tell the ByteSkript compiler to **inline** our method directly into the compiled script
222+
223+
This can be done using the `@ForceInline` annotation.
224+
225+
```java
226+
@ForceInline
227+
public static String doSomething(Object input) {
228+
return input + " blob"
229+
}
230+
```
231+
232+
The compiler will extract the bytecode source of our method and attempt to convert it and write it directly into the compiled script's code without a method call.
233+
234+
This means we don't need any runtime dependencies for our syntax.
235+
236+
{% hint style="danger" %}
237+
This is a **very dangerous** extraction. It should not be used unless you understand how to balance stack frames.
238+
239+
Very few methods are suitable for inlining.
240+
241+
* They cannot contain non-trivial jumps.
242+
* They cannot contain switches/throws.
243+
* They cannot contain more than one return path.
244+
245+
This is designed for very precise instructions where inlining would give an efficiency bonus.
246+
{% endhint %}
247+
248+
#### Advanced Syntax
249+
250+
If more complex behaviour is required than a method invocation, syntax can interact directly with the **assembler**, which uses [Foundation](https://github.com/Moderocky/Foundation) for accessibility.
251+
252+
There are two entry-points for accessing this, during the **first** and **second** pass.
253+
254+
{% hint style="info" %}
255+
ByteSkript's compiler runs in two orders.
256+
257+
The **first** pass works outer -> inner, left-to-right.
258+
259+
The line `print 1 + 2` would be run in:
260+
261+
1. `print %Object%`
262+
2. `%Object% + %Object%`
263+
3. 1 (literal)
264+
4. 2 (literal)
265+
266+
The **second** pass works inner -> outer, left-to-right.
267+
268+
The line `print 1 + 1` would be run in:
269+
270+
1. 1 (literal)
271+
2. 2 (literal)
272+
3. `%Object% + %Object%`
273+
4. `print %Object%`
274+
{% endhint %}
275+
276+
Almost all instruction assembly will be done on the **second** pass: this follows the natural order of bytecode instructions. The **first** pass is typically used for providing lookahead information to inputs, when special behaviour is required.
277+
278+
E.g. the `set ...` effect uses the **first** pass to tell the first input expression to use the `SET` handler.
279+
280+
The **first** pass uses the `preCompile` method, and the **second** pass uses the `compile` method.
281+
282+
If we wanted to implement special behaviour, we can override this.
283+
284+
```java
285+
@Override
286+
public void compile(Context context, Pattern.Match match) {
287+
context // the compile context of this use
288+
.getMethod() // the method assembler
289+
.writeCode(pushNull()); // the aconst_null bytecode instruction
290+
context.setState(CompileState.STATEMENT); // tell the compiler we're still in a line of code
291+
}
292+
```
293+
294+
In this example we are pushing a `null` value onto the stack ('returning' it from our expression.)
295+
296+
The `Context` provides a lot of information about what's going on at this exact point in the script, giving us access to variables available, the programmatic flow tree (if/elses, loops, etc.) and a lot more.
297+
298+
This is also how we access the method assembler where the code is being written, using `getMethod`.
299+
300+
The `writeCode` instruction puts a [Foundation](https://github.com/Moderocky/Foundation) `WriteInstruction` into the assembler. During the final compilation (done after the entire script has been parsed), this will write the bytecode.
301+
302+
The `pushNull` write instruction pushes an `aconst_null` (null) value onto the stack, making it available for whatever is using this expression.
303+
304+
At the end of our expression we need to tell the compiler we're in a `CompileState.STATEMENT` state, so it knows what to look for next. While this is default, it is important in case an inner expression has changed this for some reason.
305+
306+
For very advanced users, bytecode can be written directly with [ASM](https://asm.ow2.io) in this compile method.
307+
308+
```java
309+
@Override
310+
public void compile(Context context, Pattern.Match match) {
311+
context
312+
.getMethod()
313+
.writeCode((writer, visitor) -> {
314+
visitor.visitIntInsn(16, 4);
315+
visitor.visitVarInsn(54, 2);
316+
visitor.visitIincInsn(2, 1);
317+
visitor.visitVarInsn(21, 2);
318+
});
319+
context.setState(CompileState.STATEMENT);
320+
}
321+
```
322+
323+
{% hint style="danger" %}
324+
This is not recommended unless you are experienced with the bytecode layout and the [Virtual Machine instruction set](https://docs.oracle.com/javase/specs/jvms/se17/html/jvms-6.html).
325+
{% endhint %}
326+
327+
This is provided for developers who wish to extend or modify the Skript language at a fundamental level, such as by adding entirely new constructs or features.

0 commit comments

Comments
 (0)