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