muScript: better documentation
ci/woodpecker/push/woodpecker Pipeline was successful Details

This commit is contained in:
Johannes Frohnmeyer 2023-03-11 17:03:17 +01:00
parent cf49056104
commit 516fcea3a1
Signed by: Johannes
GPG Key ID: E76429612C2929F4
3 changed files with 281 additions and 74 deletions

View File

@ -2,83 +2,14 @@
μ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.
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.
μ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
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.
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).
## Embedding μScript

View File

@ -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);
}
}
}
}

View 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>