muScript: better documentation
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful
This commit is contained in:
parent
cf49056104
commit
516fcea3a1
@ -2,83 +2,14 @@
|
|||||||
|
|
||||||
μScript was created to allow respackopts pack authors to specify conditions for loading resources
|
μScript was created to allow respackopts pack authors to specify conditions for loading resources
|
||||||
in a more human-friendly manner than the previous json tree-based system.
|
in a more human-friendly manner than the previous json tree-based system.
|
||||||
|
|
||||||
It is intended to be vaguely like java in its syntax, though it deviates in some aspects.
|
It is intended to be vaguely like java in its syntax, though it deviates in some aspects.
|
||||||
|
|
||||||
The language is parsed into an AST representation which is executed in-place, no compilation is performed.
|
The language is parsed into an AST representation which is executed in-place, no compilation is performed.
|
||||||
μScript supports outputting data using various types, not just strings or booleans
|
μScript supports outputting data using various types, not just strings or booleans.
|
||||||
|
|
||||||
## Value types
|
The purpose of this document is to be a guide on embedding μScript in your application or library.
|
||||||
|
For details on how to use the language, look [here](src/test/resources/example.md).
|
||||||
This DSL supports numbers (double), booleans, strings, objects, lists and functions.
|
|
||||||
The topmost operator of a condition must always return a boolean for it to be valid in the context of respackopts.
|
|
||||||
The values of input data are according to the pack config.
|
|
||||||
String literals may be written with quotation marks as follows: `"some text"` or `'some text'`
|
|
||||||
Numbers may be written as follows: `103` or `10.15`
|
|
||||||
Booleans may be written either as `true` or `false`
|
|
||||||
Lists may be created with the `listOf` function (part of StandardLib): `listOf(1, 2, 3, 4)`
|
|
||||||
Functions may be created with the syntax explained below (--> Closures).
|
|
||||||
You may assign any of these to a variable, including closures.
|
|
||||||
Objects cannot be created manually but may be provided to the script as parameters or from functions defined in Java.
|
|
||||||
|
|
||||||
Please ensure you use proper whitespace, as this might behave unexpectedly otherwise.
|
|
||||||
|
|
||||||
## Operators
|
|
||||||
|
|
||||||
Numbers support the following operators (x and y are numbers):
|
|
||||||
|
|
||||||
- Addition: `x + y`
|
|
||||||
- Subtraction: `x - y`
|
|
||||||
- Multiplication: `x * y`
|
|
||||||
- Division: `x / y`
|
|
||||||
- Modulo: `x % y`
|
|
||||||
- Power: `x ^ y`
|
|
||||||
- Inversion: `-x`
|
|
||||||
- Greater than: `x > y`
|
|
||||||
- Greater than or equal: `x >= y`
|
|
||||||
- Less than: `x < y`
|
|
||||||
- Less than or equal: `x <= y`
|
|
||||||
- Equality: `x == y`
|
|
||||||
- Inequality: `x != y`
|
|
||||||
|
|
||||||
Strings support the following operators (x and y are strings, a is any value):
|
|
||||||
|
|
||||||
- Equality: `x == y`
|
|
||||||
- Inequality: `x != y`
|
|
||||||
- Concatenation: `x || a` or `a || x`
|
|
||||||
|
|
||||||
Booleans support the following operators (x and y are booleans, a and b are values of the same type):
|
|
||||||
|
|
||||||
- Conditional: `x ? a : b`
|
|
||||||
- Inversion: `!x`
|
|
||||||
- And: `x & y`
|
|
||||||
- Or: `x | y`
|
|
||||||
- Equality (=XNOR): `x == y`
|
|
||||||
- Inequality (=XOR): `x != y`
|
|
||||||
|
|
||||||
Objects support the following operators (x is an object with an entry called `entry`):
|
|
||||||
|
|
||||||
- Value access via `.`: `x.entry`
|
|
||||||
- Value access via square brackets: `x["entry"]`
|
|
||||||
|
|
||||||
Lists support the following operators (x is a list, someFunc is a method taking variadic arguments):
|
|
||||||
|
|
||||||
- Value access via square brackets: `x[0]`
|
|
||||||
- Expanding via `...` (only in calls): `someFunc(x...)`
|
|
||||||
|
|
||||||
Parentheses (`()`) may be used to indicate order of operation.
|
|
||||||
|
|
||||||
## Closures (functions)
|
|
||||||
One may define a closure as follows: `{list, of, arguments -> body}`.
|
|
||||||
The body of a closure may contain multiple expressions, which can be (but don't have to be) seperated by semicolons.
|
|
||||||
In that case, the return value of the last expression will be the return value of the closure.
|
|
||||||
The arrow (`->`) must always be present, even if the closure takes no arguments.
|
|
||||||
The amount of arguments is checked at runtime, so the amount used to call the function MUST be correct.
|
|
||||||
If you don't know the amount of arguments upfront, you may declare a variadic closure with `...`: `{some, arguments... -> body}`
|
|
||||||
where `arguments` would be the variadic argument, which is always a list of excess parameters.
|
|
||||||
In closures (or multi-expression scripts for that matter), you may assign variables as follows: `name = value`.
|
|
||||||
Please note that you cannot assign values to fields of objects.
|
|
||||||
You may also assign a closure to a variable to use it like a named function.
|
|
||||||
This could look as follows: `someFunction = {arg -> arg * arg}`
|
|
||||||
Please also be aware that μScript does NOT allow you to modify variables of outer scopes from inner scopes.
|
|
||||||
|
|
||||||
## Embedding μScript
|
## Embedding μScript
|
||||||
|
|
||||||
|
@ -0,0 +1,58 @@
|
|||||||
|
package io.gitlab.jfronny.muscript.test;
|
||||||
|
|
||||||
|
import io.gitlab.jfronny.commons.StringFormatter;
|
||||||
|
import io.gitlab.jfronny.muscript.compiler.Parser;
|
||||||
|
import io.gitlab.jfronny.muscript.error.LocationalException;
|
||||||
|
import org.junit.jupiter.api.Test;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Objects;
|
||||||
|
|
||||||
|
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs;
|
||||||
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
|
|
||||||
|
class ValidExampleTest {
|
||||||
|
@Test
|
||||||
|
void assertValidExample() throws IOException {
|
||||||
|
final String source;
|
||||||
|
try (InputStream is = Objects.requireNonNull(ValidExampleTest.class.getClassLoader().getResourceAsStream("example.md"))) {
|
||||||
|
source = new String(is.readAllBytes());
|
||||||
|
}
|
||||||
|
String[] split = source.lines().toArray(String[]::new);
|
||||||
|
for (int i = 0; i < split.length; i++) {
|
||||||
|
String s = split[i];
|
||||||
|
if (s.equals("```mu")) {
|
||||||
|
StringBuilder blockBuilder = new StringBuilder();
|
||||||
|
while (!split[++i].equals("```")) {
|
||||||
|
blockBuilder.append('\n').append(split[i]);
|
||||||
|
}
|
||||||
|
assertEquals("```", split[i++]);
|
||||||
|
assertEquals("<details>", split[i++]);
|
||||||
|
assertEquals("<summary>Result</summary>", split[i++]);
|
||||||
|
assertEquals("<br>", split[i++]);
|
||||||
|
assertEquals("", split[i++]);
|
||||||
|
assertEquals("```", split[i]);
|
||||||
|
StringBuilder resultBuilder = new StringBuilder();
|
||||||
|
while (!split[++i].equals("```")) {
|
||||||
|
resultBuilder.append('\n').append(split[i]);
|
||||||
|
}
|
||||||
|
i++;
|
||||||
|
assertEquals("</details>", split[i++]);
|
||||||
|
final String block = blockBuilder.substring(1);
|
||||||
|
final String expectedResult = resultBuilder.substring(1);
|
||||||
|
String result = null;
|
||||||
|
try {
|
||||||
|
result = Parser.parseScript(block).asExpr().asStringExpr().get(makeArgs());
|
||||||
|
} catch (Throwable t) {
|
||||||
|
assertEquals(expectedResult, StringFormatter.toString(t, e ->
|
||||||
|
e instanceof LocationalException le
|
||||||
|
? le.asPrintable(block).toString()
|
||||||
|
: e.toString()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if (result != null) assertEquals(expectedResult, result);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
218
muscript/src/test/resources/example.md
Normal file
218
muscript/src/test/resources/example.md
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
# μScript
|
||||||
|
|
||||||
|
The purpose of this document is to be a guide to using the μScript language.
|
||||||
|
It is not intended to explain any history of the language, implementation details or details on how to embed μScript in programs.
|
||||||
|
For information on that, look [here](../../../README.md).
|
||||||
|
|
||||||
|
## Types
|
||||||
|
μScript supports a couple of basic types, namely:
|
||||||
|
- [Numbers](#numbers) (double-precision floating-point)
|
||||||
|
- [Booleans](#booleans)
|
||||||
|
- [Strings](#strings)
|
||||||
|
- [Objects](#objects)
|
||||||
|
- [Lists](#lists)
|
||||||
|
- [Functions](#functions)
|
||||||
|
|
||||||
|
Numbers, booleans and strings can be created just like in java.
|
||||||
|
|
||||||
|
## Numbers
|
||||||
|
|
||||||
|
You can use the most common math operations on numbers.
|
||||||
|
That includes addition, subtraction, multiplication, division, modulo, pow, and your standard comparison operators.
|
||||||
|
|
||||||
|
Order of operation is also supported.
|
||||||
|
|
||||||
|
The StdLib comes with additional constants and functions, namely: `PI`, `E`, `round`, `floor`, `ceil`, `abs`, `random`
|
||||||
|
|
||||||
|
For example:
|
||||||
|
|
||||||
|
```mu
|
||||||
|
listOf(
|
||||||
|
2 + 5,
|
||||||
|
2 - 5,
|
||||||
|
0.5 * 4,
|
||||||
|
2 + 2 * 3,
|
||||||
|
(2 + 2) * 3,
|
||||||
|
4 / 2,
|
||||||
|
5 % 3, // this is modulo
|
||||||
|
2 ^ 10, // two to the power of ten
|
||||||
|
12 > 4,
|
||||||
|
5 < 3,
|
||||||
|
4 >= 4,
|
||||||
|
5 != 2,
|
||||||
|
8 == 8,
|
||||||
|
round(PI, 2) + floor(E) // round also accepts a second argument: precision, but it can be ommitted to round to a whole number
|
||||||
|
)
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>Result</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```
|
||||||
|
[7, -3, 2, 8, 12, 2, 2, 1024, true, false, true, true, true, 5.1400]
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Booleans
|
||||||
|
|
||||||
|
Your standard logic operators are supported.
|
||||||
|
XOR/XNOR are just your normal `!=`/`==` operators.
|
||||||
|
Make sure not to use `||`, as that is the string concatenation operator, not OR.
|
||||||
|
You can also use ternary conditional operators as you would in java.
|
||||||
|
|
||||||
|
```mu
|
||||||
|
listOf(
|
||||||
|
1 < 3 ? "this is correct" : "it is not", // if the condition is true, do the first thing. Else, do the second
|
||||||
|
!true, // not
|
||||||
|
true & false, // and
|
||||||
|
true & true,
|
||||||
|
true | false, // or
|
||||||
|
false | false
|
||||||
|
)
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>Result</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```
|
||||||
|
[this is correct, false, false, true, true, false]
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Strings
|
||||||
|
|
||||||
|
Strings only support concatenation via `||`.
|
||||||
|
You can also concatenate other things to a string.
|
||||||
|
Values of all other types can also be coerced into strings.
|
||||||
|
|
||||||
|
Equality operations are supported.
|
||||||
|
|
||||||
|
The StdLib comes with some additional functions, namely: `toUpper`, `toLower`, `contains` and `replace`
|
||||||
|
|
||||||
|
```mu
|
||||||
|
15 || "string one"::toUpper() || 'string Two'::toLower() || "example"::contains("thing") || "yxamply"::replace("y", "e")
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>Result</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```
|
||||||
|
15STRING ONEstring twofalseexample
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Objects
|
||||||
|
|
||||||
|
You cannot create your own objects, but objects may be passed to your script from functions or as parameters.
|
||||||
|
You can read (but not write) their fields via `.` or `[]`.
|
||||||
|
|
||||||
|
In the following example, the objects `object` and `object2` is passed to the script.
|
||||||
|
|
||||||
|
The StdLib also contains two objects, namely `date` and `time` which allow reading the current date and time.
|
||||||
|
They also allow creating date/time objects and comparing them.
|
||||||
|
|
||||||
|
```mu
|
||||||
|
listOf(
|
||||||
|
object2.valuename, // This is how you would normally do this
|
||||||
|
object2['valuename'], // You can also use []
|
||||||
|
this['object2']['valuename'], // 'this' is always an object representing the current scope
|
||||||
|
object2['value' || 'name'], // [] can contain any expression
|
||||||
|
object[object2['sub'].val / 10], // [] even references to other field
|
||||||
|
object2.sub['val'] * 2, // You can do anything with the value of a field
|
||||||
|
object.subvalue / (object2.sub.val + 6),
|
||||||
|
object2.stringfunc("some parameter"), // Objects can also contain functions
|
||||||
|
date(2023, 5, 13),
|
||||||
|
time(23, 55, 10),
|
||||||
|
date(2020, 5, 10) > date.today
|
||||||
|
)
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>Result</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```
|
||||||
|
[subvalue, subvalue, subvalue, subvalue, One, 20, 64, some parameter, 2023-05-13, 23:55:10, false]
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Lists
|
||||||
|
|
||||||
|
Lists can be created with the stdlib function `listOf` (as seen in other examples).
|
||||||
|
You can access their entries with `[]`, just like you would access fields for objects.
|
||||||
|
In function calls, you can use the spread operator (`...`) to use all elements of the list as parameters.
|
||||||
|
The StdLib also comes with some additional functions, namely `len`, `isEmpty`, `concat`, `filter`, `map`, `flatMap`, `fold` and `forEach`.
|
||||||
|
|
||||||
|
```mu
|
||||||
|
listOf(
|
||||||
|
len(listOf(1, 2, 3)),
|
||||||
|
isEmpty(listOf()),
|
||||||
|
concat(listOf(1, 2), listOf(3, 4)),
|
||||||
|
listOf(1, 2)::concat(listOf(3, 4))::len(), // Don't forget using the bind operator for readability in longer chains
|
||||||
|
time(listOf(23, 55)..., 10), // You can use the spread operator in any call, not just variadic ones
|
||||||
|
listOf(1, 2, 3)[1], // Lists are 0-indexed
|
||||||
|
listOf(1, 2, 3, 4)::filter({n->n%2==0})::map({n->n/2}) // you can chain the additional functions for proper functional programming
|
||||||
|
)
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>Result</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```
|
||||||
|
[3, true, [1, 2, 3, 4], 4, 23:55:10, 2, [1, 2]]
|
||||||
|
```
|
||||||
|
</details>
|
||||||
|
|
||||||
|
## Functions
|
||||||
|
|
||||||
|
μScript is functional as in functions being treated like any other data type.
|
||||||
|
This means you can pass them to methods as arguments, store them in variables or do whatever else you feel like.
|
||||||
|
A closure consists of a list of arguments (of which the last may be variadic) and some instructions.
|
||||||
|
|
||||||
|
```mu
|
||||||
|
someFunction = {n -> n * 2} // By assigning a closure to a variable, you get an equivalent to a normal java function
|
||||||
|
|
||||||
|
someVal = 1
|
||||||
|
$someVal = 1
|
||||||
|
|
||||||
|
{->someVal = 12}() // If you try to change a variable from inside a closure, you create a copy of it. The outer scope doesn't change
|
||||||
|
{->$someVal = 12}() // The only exception to that are variables prefixed with a dollar sign
|
||||||
|
// These work like mutable variables in other languages
|
||||||
|
/*
|
||||||
|
Use `...` to mark the last argument of a closure as variadic
|
||||||
|
All arguments that don't fit in the previous parameters will be packed in it as a list
|
||||||
|
Note that the length of that list may be 0
|
||||||
|
*/
|
||||||
|
someFunction2 = { a, b... ->
|
||||||
|
isEmpty(b) ? a : a * someFunction2(b...) // you can use the spread operator on that list like any other
|
||||||
|
}
|
||||||
|
|
||||||
|
listOf(
|
||||||
|
someFunction(1), // Closures are ran with parentheses
|
||||||
|
{->"some expression(s)"}(), // You don't have to store them to call them once...
|
||||||
|
{f1, f2 -> f1(f2(5))}(someFunction, {n->n}), // ... or to pass them to a higher order function
|
||||||
|
{->
|
||||||
|
12 + 3
|
||||||
|
10 + 2
|
||||||
|
5 + 2 // The last expression in a closure is its result. This is the same behavior as scripts (like the one this runs in)
|
||||||
|
}(),
|
||||||
|
{->
|
||||||
|
12 + 3; // You can (and probably should) use semicolons to seperate expressions
|
||||||
|
10 + 2;
|
||||||
|
5 + 2;
|
||||||
|
}(),
|
||||||
|
1::someFunction(), // You can use the bind operator '::' to create a new closure using the value to the left as the first argument
|
||||||
|
// In this case, this new closure is executed immediately using the parentheses, but doing that isn't necessary
|
||||||
|
1::({n->n})(), // Instead of a name, you can also use any expression as the right part of a bind operator (if it is in parentheses)
|
||||||
|
someVal,
|
||||||
|
$someVal,
|
||||||
|
someFunction2(1, 2, 3, 4)
|
||||||
|
)
|
||||||
|
```
|
||||||
|
<details>
|
||||||
|
<summary>Result</summary>
|
||||||
|
<br>
|
||||||
|
|
||||||
|
```
|
||||||
|
[2, some expression(s), 10, 7, 7, 2, 1, 1, 12, 24]
|
||||||
|
```
|
||||||
|
</details>
|
Loading…
Reference in New Issue
Block a user