chore(muscript): copy tests and documentation over to new implementation
This commit is contained in:
parent
ab6c3b5b2d
commit
beaba5cb02
|
@ -0,0 +1,67 @@
|
|||
# μScript
|
||||
|
||||
μ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.
|
||||
|
||||
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).
|
||||
|
||||
For details on the standard library provided by μScript, look [here](StandardLib.md).
|
||||
|
||||
## Embedding μScript
|
||||
|
||||
μScript is available as a [maven package](https://maven.frohnmeyer-wds.de/#/artifacts/io/gitlab/jfronny/muscript) which you can add to your
|
||||
project.
|
||||
To use it, first parse an expression via `Parser.parse(String script)` and convert the returned generic expression to a typed
|
||||
one by calling `as(Bool|String|Number|Dynamic)Expr`.
|
||||
This process may throw a ParseException.
|
||||
You may also use `Parser.parseScript(String script)` for multi-expression scripts.
|
||||
You can call `get(Dynamic<?> dataRoot)` on the result to execute the script on the provided data, which should be a
|
||||
`Scope` which you created with `StandardLib.createScope()` to add standard methods.
|
||||
This is also where you can add custom data to be accessed by your script.
|
||||
The execution of a script can throw a LocationalException which may be converted to a LocationalError for printing
|
||||
using the source of the expression if available.
|
||||
You may also call `StarScriptIngester.starScriptToMu()` to generate μScript code from StarScript code.
|
||||
|
||||
A full example could look as follows:
|
||||
|
||||
```java
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
String source = String.join(" ", args);
|
||||
Expr<?> parsed;
|
||||
try {
|
||||
parsed = Parser.parse(source); // or Parser.parse(StarScriptIngester.starScriptToMu(source))
|
||||
} catch (Parser.ParseException e) { // Could not parse
|
||||
System.err.println(e.error);
|
||||
return;
|
||||
}
|
||||
BoolExpr typed;
|
||||
try {
|
||||
typed = parsed.asBoolExpr();
|
||||
} catch (LocationalException e) {
|
||||
System.err.println(e.asPrintable(source));
|
||||
return;
|
||||
}
|
||||
Scope scope = StandardLib.createScope()
|
||||
.set("someValue", 15)
|
||||
.set("someOther", Map.of(
|
||||
"subValue", DFinal.of(true)
|
||||
));
|
||||
boolean result;
|
||||
try {
|
||||
result = typed.get(scope);
|
||||
} catch (LocationalException e) {
|
||||
System.err.println(e.asPrintable(source));
|
||||
return;
|
||||
}
|
||||
System.out.println("Result: " + result);
|
||||
}
|
||||
}
|
||||
```
|
|
@ -0,0 +1,77 @@
|
|||
# μScript Standard Library
|
||||
|
||||
The purpose of this document is to be a guide to the standard library provided by μScript.
|
||||
It is not intended to explain the syntax of the language or provide information on its history, implementation, or how to embed it in programs.
|
||||
|
||||
For information on that, look at the [language guide](src/test/resources/example.md) and the [developer info](README.md).
|
||||
|
||||
## Types
|
||||
| name | description |
|
||||
|----------|-----------------------------------------------------------------------------------------------------|
|
||||
| number | Equivalent to a java double, the only number type in muScript |
|
||||
| bool | A boolean. Either true or false |
|
||||
| null | Equivalent to null in java or Unit in kotlin |
|
||||
| string | A sequence of characters |
|
||||
| list | An ordered sequence of items of any type |
|
||||
| object | A mapping between keys (string) and values (any) |
|
||||
| callable | A value that can be invoked via `()` |
|
||||
| date | Represents a calendar date. Can be represented as string, number (days since 01-01-1970) and object |
|
||||
| time | Represents a time of day. Can be represented as a string, number (second of day) and object |
|
||||
| enum | Details below |
|
||||
| try | Details below |
|
||||
|
||||
### Enum
|
||||
A map between names and values of any type. Can also be represented as a list of names.
|
||||
One entry may be selected, its index will be accessible as the number representation and its name as the string representation of the enum.
|
||||
|
||||
### Try
|
||||
An `object` returned by the `try` function. Contains the following entries:
|
||||
- `result`: The result of executing the closure, the result of `catch` if it failed and `catch` was called, or `null`
|
||||
- `catch(handler: {string -> any})`: If the execution of the closure failed,
|
||||
`handler` will be executed with the error message and its result will be provided as `result`.
|
||||
No more than one `catch` may exist per `try`.
|
||||
|
||||
## Functions
|
||||
The standard library also provides a number of default utility functions.
|
||||
Below, these are listed alongside their arguments.
|
||||
The syntax used for signatures in this list is not used elsewhere and not part of the language.
|
||||
|
||||
### Signature syntax
|
||||
- If a parameter is suffixed with `?`, it can be omitted. If omission is equivalent to a default value, it will be listed with the default behind an equals sign
|
||||
- If a parameter is suffixed with `...`, it will be variadic (support 0 or more arguments)
|
||||
- If multiple types are possible, they are listed as `T1 / T2`, where T1 is preferred over T2
|
||||
- If an object supports multiple representations, it will be denoted as `T1 & T2`
|
||||
- Callables are represented like closures but with types instead of names
|
||||
|
||||
### Supported functions
|
||||
| signature | description |
|
||||
|---------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `round(value: number, decimalPlaces?: number = 0): number` | Rounds `value` while retaining `decimalPlaces` places after the comma |
|
||||
| `floor(value: number): number` | Returns the largest integer lower than or equal to `value` |
|
||||
| `ceil(value: number): number` | Returns the smallest integer larger than or equal to `value` |
|
||||
| `abs(value: number): number` | Returns the absolute value of `value` (always positive or zero) |
|
||||
| `random(min?: number = 0, max?: number = 1): number` | Returns a random number between `min` and `max`. If `min` is provided, `max` cannot be omitted. |
|
||||
| `toUpper(value: string): string` | Converts all characters in `value` to upper case |
|
||||
| `toLower(value: string): string` | Converts all characters in `value` to lower case |
|
||||
| `contains(in: list / object / string, find: any): bool` | Checks whether `find` is an element of the list, a key of the object or a substring of the string provided as `in` |
|
||||
| `replace(source: string, target: string, replacement: string): string` | Replaces all instances of `target` in `source` with `replacement` and returns the result |
|
||||
| `listOf(elements: any...): list` | Creates a list with the provided elements |
|
||||
| `len(toCheck: string / object / list): number` | Gets the amount of characters in the string or entries in the object or list provided as `toCheck` |
|
||||
| `isEmpty(toCheck: object / list): bool` | Checks whether the object or list provided as `toCheck` contain any entries |
|
||||
| `concat(lists: list...): list` | Returns a list containing all elements of all provided lists in their original order |
|
||||
| `filter(in: list, with: {any -> bool}): list` | Returns a list of all elements `el` of `in` for which `with(el)` returned true |
|
||||
| `allMatch(in: list, with: {any -> bool}): bool` | Whether `with(el)` is true for all elements `el` of `in` |
|
||||
| `anyMatch(in: list, with: {any -> bool}): bool` | Whether `with(el)` is true for any element `el` of `in` |
|
||||
| `map(in: list, with: {any -> any}): list` | Returns a list of the results of applying `with` to all elements of `in` |
|
||||
| `flatMap(in: list, with: {any -> list}): list` | Returns the concatenated list of the results of applying `with` to all elements of `in` |
|
||||
| `fold(in: list, identity: any, operator: {any, any -> any}): any` | Runs `operator(previous, current)` for every element in `in` where `previous` is the previous result of `operator`, starting with `identity` and `current` is the current element |
|
||||
| `forEach(in: list, with: {any -> any}): any` | Runs `with` for each element in `in`, returning the last result or `null` for empty lists |
|
||||
| `toObject(from: list, keyMapper: {any -> string}, valueMapper: {any -> any}): object` | Creates an object mapping the results of `keyMapper` to the results of `valueMapper` for every element in `from` |
|
||||
| `callableObject(source: object, action: callable): object & callable` | Returns an object equivalent to `source` that can be called, resulting in the invocation of `action` |
|
||||
| `enum(values: object, selected?: string): enum` | Creates an enum with the values provided as `values`, optionally marking the one with the key `selected` as such |
|
||||
| `keys(ob: object): list` | Returns the list of names of entries of the object `ob` |
|
||||
| `values(ob: object): list` | Returns the list of values of entries of the object `ob` |
|
||||
| `try(closure: {any... -> any}, args: any...): try` | Attempts to execute the closure with the provided args and returns a `try`. For details on what to do with that, look above. |
|
||||
|
||||
Other project-specific functions (like [μScript-gson](../muscript-gson/README.md)) will likely also be available to your scripts.
|
||||
Please consult the documentation for the project using μScript for more information on what is available or use `this::keys()` to view everything.
|
|
@ -11,6 +11,8 @@ dependencies {
|
|||
api(projects.muscriptDataAdditional)
|
||||
|
||||
testImplementation(libs.junit.jupiter.api)
|
||||
testImplementation(projects.muscriptParser)
|
||||
testImplementation(projects.muscriptSerialize)
|
||||
testRuntimeOnly(libs.junit.jupiter.engine)
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,37 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.ast.StringExpr;
|
||||
import io.gitlab.jfronny.muscript.ast.dynamic.DynamicAssign;
|
||||
import io.gitlab.jfronny.muscript.ast.string.StringLiteral;
|
||||
import io.gitlab.jfronny.muscript.core.CodeLocation;
|
||||
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
|
||||
import io.gitlab.jfronny.muscript.serialize.Decompiler;
|
||||
import io.gitlab.jfronny.muscript.test.util.UnforkableScope;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asDynamic;
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asString;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.evaluate;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.number;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parse;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class AssignTest {
|
||||
@Test
|
||||
void testAssignSimple() {
|
||||
String source = "someval = 'test'";
|
||||
StringExpr expr = asString(parse(source));
|
||||
assertEquals(asString(new DynamicAssign(new CodeLocation(0, 6, source, null), "someval", asDynamic(new StringLiteral(new CodeLocation(10, 15, source, null), "test")))), expr);
|
||||
assertEquals("someval = 'test'", Decompiler.decompile(expr));
|
||||
Scope scope = new UnforkableScope();
|
||||
assertEquals("test", evaluate(expr, scope));
|
||||
assertEquals("test", scope.getValue().get("someval").asString().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testAssignInner() {
|
||||
assertEquals(2, number("{->some = other = 2; other}()"));
|
||||
assertEquals(2, number("{->some = other = 2; some}()"));
|
||||
assertEquals(2, number("{->some = 2 + 4 * (other = 4 / 2); other}()"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asNumber;
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asString;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.evaluate;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class BindTest {
|
||||
@Test
|
||||
void simpleBind() {
|
||||
assertEquals(30, run(parseScript( """
|
||||
fn = {n, b -> n*b*2}
|
||||
5::fn(3)
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void lists() {
|
||||
assertEquals("[1, 9, 25, 49, 81]", evaluate(asString(parseScript("""
|
||||
numbers::map({n->n::function(2)})::filter({n->n%2!=0})
|
||||
""").content()), makeArgs()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void complexBind() {
|
||||
// fn = {b -> 2 * b}
|
||||
assertEquals(6, evaluate(asNumber(parseScript("""
|
||||
fn = 2::({a, b -> a * b})
|
||||
fn(3)
|
||||
""").content()), makeArgs()));
|
||||
// {a, b, c -> a(b(c))}(fn, {a -> a + 5}, 3)
|
||||
assertEquals(16, evaluate(asNumber(parseScript("""
|
||||
fn = 2::({a, b -> a * b})
|
||||
fn::({a, b, c -> a(b(c))})({a -> a + 5}, 3)
|
||||
""").content()), makeArgs()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,26 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.bool;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.number;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class BooleanTest {
|
||||
@Test
|
||||
void simpleLogic() {
|
||||
assertTrue(bool("false | true"));
|
||||
assertTrue(bool("false != true"));
|
||||
assertTrue(bool("!false != false"));
|
||||
assertFalse(bool("!false & false"));
|
||||
assertTrue(bool("!false & true"));
|
||||
assertTrue(bool("true == true"));
|
||||
assertTrue(bool("false != true"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void conditional() {
|
||||
assertEquals(3, number("true ? 3 : 4"));
|
||||
assertEquals(4, number("false ? 3 : 4"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,19 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CallableTest {
|
||||
@Test
|
||||
void basicFunctionTest() {
|
||||
assertEquals(3, number("object.subfunc(0, 1, 2)"));
|
||||
assertEquals(18, number("object.subfunc(0, object.subfunc(1, 2, 3), 4)"));
|
||||
assertTrue(bool("repeatArgs().repeatArgs().boolean"));
|
||||
assertEquals(32, number("function(object.subfunc(0, 1), 5)"));
|
||||
assertEquals("<=1.16.5\"", string("object2.stringfunc('<=1.16.5\"')"));
|
||||
assertEquals("minecraft", string("object2.stringfunc('minecraft', '<=1.16.5')"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,48 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.data.additional.DFinal;
|
||||
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
|
||||
import io.gitlab.jfronny.muscript.parser.Parser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asDynamic;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.evaluate;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ClosureTest {
|
||||
@Test
|
||||
void testScript() {
|
||||
assertEquals(8, run(parseScript("function(2, 1); function(2, 2); function(2, 3)"), makeArgs())
|
||||
.asNumber()
|
||||
.getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testClosure() {
|
||||
assertEquals(2, number("{->2}()"));
|
||||
assertEquals(2, number("{n->n}(2)"));
|
||||
assertEquals(2, number("{n->n()}({->2})"));
|
||||
assertThrows(Parser.ParseException.class, () -> number("{->num = 2 num = num * 2 num = num - 2}()"));
|
||||
assertEquals(2, number("{->num = 2; num = num * 2; num = num - 2}()"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fizzbuzzInμScriptByEmbeddingADomainSpecificLanguage() {
|
||||
var fn = evaluate(asDynamic(parse("""
|
||||
{ n ->
|
||||
test = { b, s, o -> n % b == 0 ? { _ -> s || o('')} : o }
|
||||
fizz = { o -> test(3, 'Fizz', o) }
|
||||
buzz = { o -> test(5, 'Buzz', o) }
|
||||
fizz(buzz({n -> n}))(n)
|
||||
}
|
||||
""")), new Scope())
|
||||
.asCallable();
|
||||
assertEquals("8", fn.call(DFinal.of(8)).asString().getValue());
|
||||
assertEquals("Buzz", fn.call(DFinal.of(10)).asString().getValue());
|
||||
assertEquals("Fizz", fn.call(DFinal.of(12)).asString().getValue());
|
||||
assertEquals("FizzBuzz", fn.call(DFinal.of(15)).asString().getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,17 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.bool;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.string;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class CombinationTest {
|
||||
@Test
|
||||
void simpleTest() {
|
||||
assertTrue(bool("1 + 5 < 12 & (true != false)"));
|
||||
assertEquals("59", string("15 / 3 || 2 + 7"));
|
||||
assertEquals("7yes5", string("2 + 5 || (true ? 'yes' : 'no') || 15 / 3"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class CommentTest {
|
||||
@Test
|
||||
void singleLine() {
|
||||
assertEquals(2, run(parseScript("""
|
||||
n = 5
|
||||
n = 2
|
||||
// n = 3
|
||||
n
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void multiLine() {
|
||||
assertEquals(2, run(parseScript("""
|
||||
n = 2
|
||||
/* n = 3 */
|
||||
/*
|
||||
n = 3
|
||||
n = 4
|
||||
n = 5
|
||||
*/
|
||||
n
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void commentWithStuff() {
|
||||
assertDoesNotThrow(() -> run(parseScript("""
|
||||
ob = {
|
||||
k1 = 'Yes',
|
||||
`1` = "One"
|
||||
}
|
||||
"""), makeArgs()));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,35 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.serialize.Decompiler;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class DecompileTest {
|
||||
private static final String script = """
|
||||
clientVersion = challenge({ ->
|
||||
mod('better-whitelist').version;
|
||||
});
|
||||
assert(mod('better-whitelist').version == clientVersion, 'You have the wrong mod version');
|
||||
assert(challenge({ arg ->
|
||||
allMatch(arg, { v ->
|
||||
anyMatch(mods, { m ->
|
||||
v.name == m.name & v.version == m.version;
|
||||
});
|
||||
});
|
||||
}, map(filter(mods, { v ->
|
||||
v.environment != 'server';
|
||||
}), { v ->
|
||||
{
|
||||
name = v.name,
|
||||
version = v.version
|
||||
};
|
||||
})));
|
||||
""";
|
||||
|
||||
@Test
|
||||
void testDecompile() {
|
||||
assertEquals(script, Decompiler.decompile(parseScript(script)));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.core.LocationalException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class ExceptionHandlingTest {
|
||||
@Test
|
||||
void catchStdlib() {
|
||||
assertEquals("Signature mismatch for isEmpty: expected <(collection: string | [any] | {any}) -> bool> but got <() -> any>", assertThrows(LocationalException.class, () -> string("isEmpty()")).getMessage());
|
||||
assertEquals("Signature mismatch for isEmpty: expected <(collection: string | [any] | {any}) -> bool> but got <() -> any>", string("try({->isEmpty()}).catch({e->e.message}).result"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void fail() {
|
||||
assertEquals("Failed", assertThrows(LocationalException.class, () -> string("fail()")).getMessage());
|
||||
assertEquals("Joe", assertThrows(LocationalException.class, () -> string("fail('Joe')")).getMessage());
|
||||
}
|
||||
|
||||
@Test
|
||||
void catchFail() {
|
||||
assertEquals("Cought Joe", string("try({->fail('Joe')}).catch({e->'Cought ' || e.message}).result"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void catchInner() {
|
||||
assertEquals("Got Signature mismatch for isEmpty: expected <(collection: string | [any] | {any}) -> bool> but got <() -> any>", assertThrows(LocationalException.class, () -> run(parseScript("""
|
||||
inner = {-> isEmpty()}
|
||||
outer = {-> inner()}
|
||||
outer2 = {-> try({a->a()}, outer).catch({e -> fail('Got ' || e.message)}).result}
|
||||
outer2()
|
||||
"""), makeArgs())).getMessage());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.core.LocationalException;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class GlobalTest {
|
||||
@Test
|
||||
void notAll() {
|
||||
assertEquals(12, run(parseScript("""
|
||||
v = 12
|
||||
{->v = 5}()
|
||||
v
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void global() {
|
||||
assertEquals(5, run(parseScript("""
|
||||
$v = 12
|
||||
{->$v = 5}()
|
||||
$v
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
|
||||
@Test
|
||||
void innermost() {
|
||||
assertThrows(LocationalException.class,
|
||||
() -> run(parseScript("""
|
||||
{->$v = 12}()
|
||||
$v
|
||||
"""), makeArgs()).asNumber().getValue(),
|
||||
"This object doesn't contain '$v'");
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.bool;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ListTest {
|
||||
@Test
|
||||
void listAccess() {
|
||||
assertTrue(bool("list[0]"));
|
||||
assertTrue(bool("list[1] < 3"));
|
||||
assertTrue(bool("list[2] || list[0] == '3true'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void contains() {
|
||||
assertFalse(bool("numbers::contains(',')"));
|
||||
assertFalse(bool("numbers::contains('1,')"));
|
||||
assertFalse(bool("numbers::contains('1')"));
|
||||
assertTrue(bool("numbers::contains(1)"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.parser.Parser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parse;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class LocationalErrorTest {
|
||||
@Test
|
||||
void invalidCode() {
|
||||
assertEquals("""
|
||||
Error at 't' (character 6): Expected number expression but got boolean
|
||||
1 | 15 + true
|
||||
^--^-- Here""",
|
||||
assertThrows(Parser.ParseException.class, () -> parse("15 + true")).error.toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void invalidCode2() {
|
||||
assertEquals("""
|
||||
Error at ''' (character 6): Expected number expression but got string
|
||||
1 | 15 + 'yes'
|
||||
^---^-- Here""",
|
||||
assertThrows(Parser.ParseException.class, () -> parse("15 + 'yes'")).error.toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.core.MuScriptVersion;
|
||||
import io.gitlab.jfronny.muscript.core.SourceFS;
|
||||
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
|
||||
import io.gitlab.jfronny.muscript.data.additional.libs.StandardLib;
|
||||
import io.gitlab.jfronny.muscript.parser.Parser;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class MultiScriptTest {
|
||||
final SourceFS FS = new SourceFS.Immutable(Map.of(
|
||||
"main.mu", """
|
||||
#include import1.mu
|
||||
func(4)""",
|
||||
"import1.mu", """
|
||||
#include import2.mu
|
||||
func = {c->fn2(c-1, c)}""",
|
||||
"import2.mu", """
|
||||
#include import3.mu
|
||||
fn = {a->a}""",
|
||||
"import3.mu", """
|
||||
#include import2.mu
|
||||
fn = {a->a*2}
|
||||
fn2 = {a, b -> fn(a)+b}"""
|
||||
));
|
||||
|
||||
final Scope scope = StandardLib.createScope(MuScriptVersion.DEFAULT)
|
||||
.set("throw", args -> {
|
||||
throw new IllegalArgumentException("No");
|
||||
});
|
||||
|
||||
@Test
|
||||
void simpleMultifile() {
|
||||
assertEquals(7, run(Parser.parseMultiScript(MuScriptVersion.DEFAULT, "main.mu", FS), scope).asNumber().getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,38 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.bool;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.number;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class NumberTest {
|
||||
@Test
|
||||
void simpleMath() {
|
||||
assertEquals(12, number("7 + 5"));
|
||||
assertEquals(12, number("14 - 2"));
|
||||
assertEquals(12, number("4* 3"));
|
||||
assertEquals(12, number("24 /2"));
|
||||
assertEquals(12, number("92 % 20"));
|
||||
assertEquals(12, number("2^2*3"));
|
||||
assertEquals(-12, number("-12"));
|
||||
assertEquals(12, number("-1 * -12 + 2 + (-2)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void compare() {
|
||||
assertTrue(bool("12 < 10 * 2"));
|
||||
assertTrue(bool("12 > 14 / 2"));
|
||||
assertTrue(bool("12 == 10 + 2"));
|
||||
assertTrue(bool("12 >= 10 + 2"));
|
||||
assertTrue(bool("10 <= 10 + 2"));
|
||||
assertTrue(bool("12 != 10 * 2"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void orderOfOperations() {
|
||||
assertEquals(12, number("2 + 5 * 2"));
|
||||
assertEquals(12, number("3*2^2"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,39 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.*;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
||||
class ObjectTest {
|
||||
@Test
|
||||
void valueAccess() {
|
||||
assertTrue(bool("boolean"));
|
||||
assertTrue(bool("object.subvalue > 1000"));
|
||||
assertTrue(bool("object.subfunc(2, 4, 8) == 12"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void arrayAccess() {
|
||||
assertTrue(bool("object[1] == 'One'"));
|
||||
assertTrue(bool("object['subvalue'] == 1024"));
|
||||
assertTrue(bool("object[object2.valuename] == 1024"));
|
||||
assertTrue(bool("object2['sub'].val == 10"));
|
||||
assertTrue(bool("object2.sub['val'] == 10"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void objectLiteral() {
|
||||
assertEquals(12, run(parseScript("""
|
||||
ob = {}
|
||||
ob = {test = 2, test2 = 3}
|
||||
t = ob.test
|
||||
ob = {test2 = 3, test = 2}
|
||||
t = t * ob.test
|
||||
ob = {test = 3}
|
||||
t * ob.test
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,56 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.core.LocationalException;
|
||||
import io.gitlab.jfronny.muscript.core.MuScriptVersion;
|
||||
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
|
||||
import io.gitlab.jfronny.muscript.data.additional.libs.IntentionalException;
|
||||
import io.gitlab.jfronny.muscript.data.additional.libs.StandardLib;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||
|
||||
class StackTraceTest {
|
||||
final String source = """
|
||||
someInner = { ->
|
||||
throw()
|
||||
}
|
||||
|
||||
someOuter = { ->
|
||||
someInner()
|
||||
}
|
||||
|
||||
someOuter()
|
||||
""";
|
||||
|
||||
final Scope scope = StandardLib.createScope(MuScriptVersion.DEFAULT)
|
||||
.set("throw", args -> {
|
||||
throw new IntentionalException("Expected Exception");
|
||||
});
|
||||
|
||||
@Test
|
||||
void stackTrace() {
|
||||
assertEquals("""
|
||||
Error at '(' (character 8): Expected Exception
|
||||
1 | throw()
|
||||
^-- Here
|
||||
at someInner (call: line 5)
|
||||
at someOuter (call: line 8)""",
|
||||
assertThrows(LocationalException.class, () -> run(parseScript(source), scope))
|
||||
.asPrintable().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void stackTrace2() {
|
||||
assertEquals("""
|
||||
Error at '(' (character 8): Expected Exception
|
||||
1 | throw()
|
||||
^-- Here
|
||||
at someInner (call: line 5 in some/file.mu)
|
||||
at someOuter (call: line 8 in some/file.mu)""",
|
||||
assertThrows(LocationalException.class, () -> run(parseScript(source, "some/file.mu"), scope))
|
||||
.asPrintable().toString());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,15 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.muscript.parser.StarScriptIngester;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class StarScriptIngesterTest {
|
||||
@Test
|
||||
void ingestionTest() {
|
||||
assertEquals("'Hello ' || (name) || '!'", StarScriptIngester.starScriptToMu("Hello {name}!"));
|
||||
assertEquals("('Only \"Content')", StarScriptIngester.starScriptToMu("{'Only \"Content'}"));
|
||||
assertEquals("(a + c / 75) || ' equals ' || (b)", StarScriptIngester.starScriptToMu("{a + c / 75} equals {b}"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.bool;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.string;
|
||||
import static org.junit.jupiter.api.Assertions.*;
|
||||
|
||||
class StringTest {
|
||||
@Test
|
||||
void operators() {
|
||||
assertEquals("Hello, world!", string("'Hello, ' || 'world!'"));
|
||||
assertEquals("Yes 15 hello", string("'Yes ' || 16 - 1 || ' hello'"));
|
||||
assertEquals("Value", string("string"));
|
||||
assertTrue(bool("string == 'Value'"));
|
||||
assertFalse(bool("string == 'Something else'"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void concatComparison() {
|
||||
assertEquals("Hellotrue", string("'Hello' || -12 < 5"));
|
||||
assertEquals("trueHello", string("-12 < 5 || 'Hello'"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,21 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.run;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.makeArgs;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class TernaryTest {
|
||||
@Test
|
||||
void testRootTernary() {
|
||||
assertEquals(2, run(parseScript("""
|
||||
value = 0;
|
||||
value == 1 ? joe() : value = 1;
|
||||
value == 1 ? value = 2 : joe();
|
||||
value == 1 ? value = 3 : value;
|
||||
value;
|
||||
"""), makeArgs()).asNumber().getValue());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,71 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import io.gitlab.jfronny.commons.StringFormatter;
|
||||
import io.gitlab.jfronny.muscript.core.LocationalException;
|
||||
import io.gitlab.jfronny.muscript.core.MuScriptVersion;
|
||||
import io.gitlab.jfronny.muscript.data.additional.libs.StandardLib;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.function.Executable;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.asString;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.evaluate;
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.parseScript;
|
||||
import static org.junit.jupiter.api.Assertions.assertAll;
|
||||
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);
|
||||
List<Executable> testCases = new LinkedList<>();
|
||||
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("Result:", split[i++]);
|
||||
assertEquals("```", split[i]);
|
||||
StringBuilder resultBuilder = new StringBuilder();
|
||||
while (!split[++i].equals("```")) {
|
||||
resultBuilder.append('\n').append(split[i]);
|
||||
}
|
||||
i++;
|
||||
final String block = blockBuilder.substring(1);
|
||||
final String expectedResult = resultBuilder.substring(1);
|
||||
testCases.add(new TestCase(block, expectedResult));
|
||||
}
|
||||
}
|
||||
|
||||
assertAll(testCases);
|
||||
}
|
||||
|
||||
record TestCase(String source, String expectedResult) implements Executable {
|
||||
@Override
|
||||
public void execute() {
|
||||
String result = null;
|
||||
try {
|
||||
result = evaluate(asString(parseScript(source).content()), StandardLib.createScope(MuScriptVersion.DEFAULT));
|
||||
} catch (Throwable t) {
|
||||
assertEquals(expectedResult, StringFormatter.toString(t, e ->
|
||||
e instanceof LocationalException le
|
||||
? le.asPrintable().toString()
|
||||
: e.toString()
|
||||
));
|
||||
}
|
||||
if (result != null) assertEquals(expectedResult, result);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,28 @@
|
|||
package io.gitlab.jfronny.muscript.test;
|
||||
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.test.util.MuTestUtil.number;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
class VaragsTest {
|
||||
@Test
|
||||
void basicListOf() {
|
||||
assertEquals(2, number("listOf(1, 2, 3, 4)[2] - 1"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void variadicFunction() {
|
||||
assertEquals(2, number("{joe...->joe[2] - 1}(1, 2, 3, 4)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void variadicParameter() {
|
||||
assertEquals(2, number("{n...->n[2] - 1}(listOf(1, 2, 3, 4)...)"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void variadicFootgun() {
|
||||
assertEquals(2, number("{a, b, c, d -> c - 1}(listOf(1, 2, 3, 4)...)"));
|
||||
}
|
||||
}
|
|
@ -0,0 +1,74 @@
|
|||
package io.gitlab.jfronny.muscript.test.util;
|
||||
|
||||
import io.gitlab.jfronny.muscript.ast.Expr;
|
||||
import io.gitlab.jfronny.muscript.ast.context.Script;
|
||||
import io.gitlab.jfronny.muscript.core.MuScriptVersion;
|
||||
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
|
||||
import io.gitlab.jfronny.muscript.data.additional.impl.ObjectGraphPrinter;
|
||||
import io.gitlab.jfronny.muscript.data.additional.libs.StandardLib;
|
||||
import io.gitlab.jfronny.muscript.parser.Parser;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static io.gitlab.jfronny.muscript.ast.context.ExprUtils.*;
|
||||
import static io.gitlab.jfronny.muscript.data.additional.DFinal.of;
|
||||
import static io.gitlab.jfronny.muscript.runtime.Runtime.evaluate;
|
||||
|
||||
public class MuTestUtil {
|
||||
public static double number(String source) {
|
||||
return evaluate(asNumber(parse(source)), makeArgs());
|
||||
}
|
||||
|
||||
public static boolean bool(String source) {
|
||||
Expr tree = parse(source);
|
||||
try {
|
||||
return evaluate(asBool(tree), makeArgs());
|
||||
} catch (RuntimeException e) {
|
||||
try {
|
||||
System.out.println("Caught error with tree:\n" + ObjectGraphPrinter.printGraph(tree));
|
||||
} catch (IllegalAccessException ex) {
|
||||
throw new RuntimeException(ex);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
}
|
||||
|
||||
public static String string(String source) {
|
||||
return evaluate(asString(parse(source)), makeArgs());
|
||||
}
|
||||
|
||||
public static Expr parse(String source) {
|
||||
return Parser.parse(MuScriptVersion.DEFAULT, source);
|
||||
}
|
||||
|
||||
public static Script parseScript(String source) {
|
||||
return Parser.parseScript(MuScriptVersion.DEFAULT, source);
|
||||
}
|
||||
|
||||
public static Script parseScript(String source, String file) {
|
||||
return Parser.parseScript(MuScriptVersion.DEFAULT, source, file);
|
||||
}
|
||||
|
||||
public static Scope makeArgs() {
|
||||
return StandardLib.createScope(MuScriptVersion.DEFAULT)
|
||||
.set("boolean", true)
|
||||
.set("number", 15)
|
||||
.set("string", "Value")
|
||||
.set("object", Map.of(
|
||||
"subvalue", of(1024),
|
||||
"subfunc", of(v -> of(v.get(1).asNumber().getValue() * v.size()), "object.subfunc"),
|
||||
"1", of("One")
|
||||
))
|
||||
.set("object2", Map.of(
|
||||
"valuename", of("subvalue"),
|
||||
"sub", of(Map.of(
|
||||
"val", of(10)
|
||||
)),
|
||||
"stringfunc", of(v -> of(v.get(0).asString().getValue()), "object2.stringfunc")
|
||||
))
|
||||
.set("list", of(of(true), of(2), of("3")))
|
||||
.set("numbers", of(of(1), of(2), of(3), of(4), of(5), of(6), of(7), of(8), of(9), of(10)))
|
||||
.set("function", v -> of(Math.pow(v.get(0).asNumber().getValue(), v.get(1).asNumber().getValue())))
|
||||
.set("repeatArgs", v -> makeArgs());
|
||||
}
|
||||
}
|
|
@ -0,0 +1,10 @@
|
|||
package io.gitlab.jfronny.muscript.test.util;
|
||||
|
||||
import io.gitlab.jfronny.muscript.data.additional.context.Scope;
|
||||
|
||||
public class UnforkableScope extends Scope {
|
||||
@Override
|
||||
public Scope fork() {
|
||||
return this;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,265 @@
|
|||
# μ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).
|
||||
|
||||
For information on the standard library provided by μScript, look [here](../../../StandardLib.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`
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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, floor(E)) // round also accepts a second argument: decimalPlaces, but it can be ommitted to round to a whole number
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[7, -3, 2, 8, 12, 2, 2, 1024, true, false, true, true, true, 3.14]
|
||||
```
|
||||
</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.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
['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: `len`, `toUpper`, `toLower`, `contains` and `replace`
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```mu
|
||||
15 || "string one"::toUpper() || 'string Two'::toLower() || "example"::contains("thing") || "yxamply"::replace("y", "e")
|
||||
```
|
||||
Result:
|
||||
```
|
||||
15STRING ONEstring twofalseexample
|
||||
```
|
||||
</details>
|
||||
|
||||
## Objects
|
||||
|
||||
You can read (but not write) the fields of objects via `.` or `[]`.
|
||||
|
||||
The StdLib comes with some additional functions, namely: `isEmpty`, `len`, `keys`, `values` and `contains`
|
||||
It 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.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```mu
|
||||
object2 = {
|
||||
valuename = 'subvalue',
|
||||
sub = {
|
||||
val = 10
|
||||
},
|
||||
stringfunc = {text -> text}
|
||||
}
|
||||
|
||||
object = {
|
||||
subvalue = 1024,
|
||||
`1` = "One" // you can escape any identifier (not just object keys) with '`'
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
['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`, `contains`, `isEmpty`, `concat`, `filter`, `allMatch`, `anyMatch`, `map`, `flatMap`, `fold`, `forEach` and `toObject`.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[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.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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)
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[2, 'some expression(s)', 10, 7, 7, 2, 1, 1, 12, 24]
|
||||
```
|
||||
</details>
|
||||
|
||||
## Exception Handling
|
||||
|
||||
μScript supports basic exception handling.
|
||||
This means you can throw and catch exceptions, both those you throw in μScript and those in your Java code.
|
||||
The function `fail` throws an exception with the supplied message (optional) or "Failed" if it is omitted.
|
||||
You may use the function `try` to catch exceptions with `catch`.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```mu
|
||||
someFunction = { -> fail("You did something wrong") } // This is an example. Usually, you'd do this after some checks.
|
||||
|
||||
listOf(
|
||||
try(someFunction).result, // You can access the result of your method (or null if it failed) directly like this
|
||||
try({->"Yay"}).result, // if it succeeds, you get its result
|
||||
try(someFunction).catch({e->"Cought!"}).result, // if a catch is present and it fails, the result of catch will be used instead
|
||||
try(someFunction).catch({e->e.message}).result, // the parameter to catch represents the cought exception
|
||||
try({a-> "ABC" || a}, "DEF").result // try also supports adding parameters to your first arg
|
||||
)
|
||||
// Note: You may not catch twice. Doing so will lead to an exception
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[null, 'Yay', 'Cought!', 'You did something wrong', 'ABCDEF']
|
||||
```
|
||||
</details>
|
|
@ -1,67 +1,3 @@
|
|||
# μScript
|
||||
|
||||
μ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.
|
||||
|
||||
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).
|
||||
|
||||
For details on the standard library provided by μScript, look [here](StandardLib.md).
|
||||
|
||||
## Embedding μScript
|
||||
|
||||
μScript is available as a [maven package](https://maven.frohnmeyer-wds.de/#/artifacts/io/gitlab/jfronny/muscript) which you can add to your
|
||||
project.
|
||||
To use it, first parse an expression via `Parser.parse(String script)` and convert the returned generic expression to a typed
|
||||
one by calling `as(Bool|String|Number|Dynamic)Expr`.
|
||||
This process may throw a ParseException.
|
||||
You may also use `Parser.parseScript(String script)` for multi-expression scripts.
|
||||
You can call `get(Dynamic<?> dataRoot)` on the result to execute the script on the provided data, which should be a
|
||||
`Scope` which you created with `StandardLib.createScope()` to add standard methods.
|
||||
This is also where you can add custom data to be accessed by your script.
|
||||
The execution of a script can throw a LocationalException which may be converted to a LocationalError for printing
|
||||
using the source of the expression if available.
|
||||
You may also call `StarScriptIngester.starScriptToMu()` to generate μScript code from StarScript code.
|
||||
|
||||
A full example could look as follows:
|
||||
|
||||
```java
|
||||
public class Example {
|
||||
public static void main(String[] args) {
|
||||
String source = String.join(" ", args);
|
||||
Expr<?> parsed;
|
||||
try {
|
||||
parsed = Parser.parse(source); // or Parser.parse(StarScriptIngester.starScriptToMu(source))
|
||||
} catch (Parser.ParseException e) { // Could not parse
|
||||
System.err.println(e.error);
|
||||
return;
|
||||
}
|
||||
BoolExpr typed;
|
||||
try {
|
||||
typed = parsed.asBoolExpr();
|
||||
} catch (LocationalException e) {
|
||||
System.err.println(e.asPrintable(source));
|
||||
return;
|
||||
}
|
||||
Scope scope = StandardLib.createScope()
|
||||
.set("someValue", 15)
|
||||
.set("someOther", Map.of(
|
||||
"subValue", DFinal.of(true)
|
||||
));
|
||||
boolean result;
|
||||
try {
|
||||
result = typed.get(scope);
|
||||
} catch (LocationalException e) {
|
||||
System.err.println(e.asPrintable(source));
|
||||
return;
|
||||
}
|
||||
System.out.println("Result: " + result);
|
||||
}
|
||||
}
|
||||
```
|
||||
This document was moved [here](../muscript-runtime/README.md).
|
|
@ -1,77 +1,3 @@
|
|||
# μScript Standard Library
|
||||
|
||||
The purpose of this document is to be a guide to the standard library provided by μScript.
|
||||
It is not intended to explain the syntax of the language or provide information on its history, implementation, or how to embed it in programs.
|
||||
|
||||
For information on that, look at the [language guide](src/test/resources/example.md) and the [developer info](README.md).
|
||||
|
||||
## Types
|
||||
| name | description |
|
||||
|----------|-----------------------------------------------------------------------------------------------------|
|
||||
| number | Equivalent to a java double, the only number type in muScript |
|
||||
| bool | A boolean. Either true or false |
|
||||
| null | Equivalent to null in java or Unit in kotlin |
|
||||
| string | A sequence of characters |
|
||||
| list | An ordered sequence of items of any type |
|
||||
| object | A mapping between keys (string) and values (any) |
|
||||
| callable | A value that can be invoked via `()` |
|
||||
| date | Represents a calendar date. Can be represented as string, number (days since 01-01-1970) and object |
|
||||
| time | Represents a time of day. Can be represented as a string, number (second of day) and object |
|
||||
| enum | Details below |
|
||||
| try | Details below |
|
||||
|
||||
### Enum
|
||||
A map between names and values of any type. Can also be represented as a list of names.
|
||||
One entry may be selected, its index will be accessible as the number representation and its name as the string representation of the enum.
|
||||
|
||||
### Try
|
||||
An `object` returned by the `try` function. Contains the following entries:
|
||||
- `result`: The result of executing the closure, the result of `catch` if it failed and `catch` was called, or `null`
|
||||
- `catch(handler: {string -> any})`: If the execution of the closure failed,
|
||||
`handler` will be executed with the error message and its result will be provided as `result`.
|
||||
No more than one `catch` may exist per `try`.
|
||||
|
||||
## Functions
|
||||
The standard library also provides a number of default utility functions.
|
||||
Below, these are listed alongside their arguments.
|
||||
The syntax used for signatures in this list is not used elsewhere and not part of the language.
|
||||
|
||||
### Signature syntax
|
||||
- If a parameter is suffixed with `?`, it can be omitted. If omission is equivalent to a default value, it will be listed with the default behind an equals sign
|
||||
- If a parameter is suffixed with `...`, it will be variadic (support 0 or more arguments)
|
||||
- If multiple types are possible, they are listed as `T1 / T2`, where T1 is preferred over T2
|
||||
- If an object supports multiple representations, it will be denoted as `T1 & T2`
|
||||
- Callables are represented like closures but with types instead of names
|
||||
|
||||
### Supported functions
|
||||
| signature | description |
|
||||
|---------------------------------------------------------------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
|
||||
| `round(value: number, decimalPlaces?: number = 0): number` | Rounds `value` while retaining `decimalPlaces` places after the comma |
|
||||
| `floor(value: number): number` | Returns the largest integer lower than or equal to `value` |
|
||||
| `ceil(value: number): number` | Returns the smallest integer larger than or equal to `value` |
|
||||
| `abs(value: number): number` | Returns the absolute value of `value` (always positive or zero) |
|
||||
| `random(min?: number = 0, max?: number = 1): number` | Returns a random number between `min` and `max`. If `min` is provided, `max` cannot be omitted. |
|
||||
| `toUpper(value: string): string` | Converts all characters in `value` to upper case |
|
||||
| `toLower(value: string): string` | Converts all characters in `value` to lower case |
|
||||
| `contains(in: list / object / string, find: any): bool` | Checks whether `find` is an element of the list, a key of the object or a substring of the string provided as `in` |
|
||||
| `replace(source: string, target: string, replacement: string): string` | Replaces all instances of `target` in `source` with `replacement` and returns the result |
|
||||
| `listOf(elements: any...): list` | Creates a list with the provided elements |
|
||||
| `len(toCheck: string / object / list): number` | Gets the amount of characters in the string or entries in the object or list provided as `toCheck` |
|
||||
| `isEmpty(toCheck: object / list): bool` | Checks whether the object or list provided as `toCheck` contain any entries |
|
||||
| `concat(lists: list...): list` | Returns a list containing all elements of all provided lists in their original order |
|
||||
| `filter(in: list, with: {any -> bool}): list` | Returns a list of all elements `el` of `in` for which `with(el)` returned true |
|
||||
| `allMatch(in: list, with: {any -> bool}): bool` | Whether `with(el)` is true for all elements `el` of `in` |
|
||||
| `anyMatch(in: list, with: {any -> bool}): bool` | Whether `with(el)` is true for any element `el` of `in` |
|
||||
| `map(in: list, with: {any -> any}): list` | Returns a list of the results of applying `with` to all elements of `in` |
|
||||
| `flatMap(in: list, with: {any -> list}): list` | Returns the concatenated list of the results of applying `with` to all elements of `in` |
|
||||
| `fold(in: list, identity: any, operator: {any, any -> any}): any` | Runs `operator(previous, current)` for every element in `in` where `previous` is the previous result of `operator`, starting with `identity` and `current` is the current element |
|
||||
| `forEach(in: list, with: {any -> any}): any` | Runs `with` for each element in `in`, returning the last result or `null` for empty lists |
|
||||
| `toObject(from: list, keyMapper: {any -> string}, valueMapper: {any -> any}): object` | Creates an object mapping the results of `keyMapper` to the results of `valueMapper` for every element in `from` |
|
||||
| `callableObject(source: object, action: callable): object & callable` | Returns an object equivalent to `source` that can be called, resulting in the invocation of `action` |
|
||||
| `enum(values: object, selected?: string): enum` | Creates an enum with the values provided as `values`, optionally marking the one with the key `selected` as such |
|
||||
| `keys(ob: object): list` | Returns the list of names of entries of the object `ob` |
|
||||
| `values(ob: object): list` | Returns the list of values of entries of the object `ob` |
|
||||
| `try(closure: {any... -> any}, args: any...): try` | Attempts to execute the closure with the provided args and returns a `try`. For details on what to do with that, look above. |
|
||||
|
||||
Other project-specific functions (like [μScript-gson](../muscript-gson/README.md)) will likely also be available to your scripts.
|
||||
Please consult the documentation for the project using μScript for more information on what is available or use `this::keys()` to view everything.
|
||||
This document was moved [here](../muscript-runtime/StandardLib.md).
|
|
@ -1,265 +1,3 @@
|
|||
# μ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).
|
||||
|
||||
For information on the standard library provided by μScript, look [here](../../../StandardLib.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`
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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, floor(E)) // round also accepts a second argument: decimalPlaces, but it can be ommitted to round to a whole number
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[7, -3, 2, 8, 12, 2, 2, 1024, true, false, true, true, true, 3.14]
|
||||
```
|
||||
</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.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
['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: `len`, `toUpper`, `toLower`, `contains` and `replace`
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```mu
|
||||
15 || "string one"::toUpper() || 'string Two'::toLower() || "example"::contains("thing") || "yxamply"::replace("y", "e")
|
||||
```
|
||||
Result:
|
||||
```
|
||||
15STRING ONEstring twofalseexample
|
||||
```
|
||||
</details>
|
||||
|
||||
## Objects
|
||||
|
||||
You can read (but not write) the fields of objects via `.` or `[]`.
|
||||
|
||||
The StdLib comes with some additional functions, namely: `isEmpty`, `len`, `keys`, `values` and `contains`
|
||||
It 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.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```mu
|
||||
object2 = {
|
||||
valuename = 'subvalue',
|
||||
sub = {
|
||||
val = 10
|
||||
},
|
||||
stringfunc = {text -> text}
|
||||
}
|
||||
|
||||
object = {
|
||||
subvalue = 1024,
|
||||
`1` = "One" // you can escape any identifier (not just object keys) with '`'
|
||||
}
|
||||
|
||||
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
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
['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`, `contains`, `isEmpty`, `concat`, `filter`, `allMatch`, `anyMatch`, `map`, `flatMap`, `fold`, `forEach` and `toObject`.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[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.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```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)
|
||||
)
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[2, 'some expression(s)', 10, 7, 7, 2, 1, 1, 12, 24]
|
||||
```
|
||||
</details>
|
||||
|
||||
## Exception Handling
|
||||
|
||||
μScript supports basic exception handling.
|
||||
This means you can throw and catch exceptions, both those you throw in μScript and those in your Java code.
|
||||
The function `fail` throws an exception with the supplied message (optional) or "Failed" if it is omitted.
|
||||
You may use the function `try` to catch exceptions with `catch`.
|
||||
|
||||
<details>
|
||||
<summary>Example</summary>
|
||||
<br>
|
||||
|
||||
```mu
|
||||
someFunction = { -> fail("You did something wrong") } // This is an example. Usually, you'd do this after some checks.
|
||||
|
||||
listOf(
|
||||
try(someFunction).result, // You can access the result of your method (or null if it failed) directly like this
|
||||
try({->"Yay"}).result, // if it succeeds, you get its result
|
||||
try(someFunction).catch({e->"Cought!"}).result, // if a catch is present and it fails, the result of catch will be used instead
|
||||
try(someFunction).catch({e->e.message}).result, // the parameter to catch represents the cought exception
|
||||
try({a-> "ABC" || a}, "DEF").result // try also supports adding parameters to your first arg
|
||||
)
|
||||
// Note: You may not catch twice. Doing so will lead to an exception
|
||||
```
|
||||
Result:
|
||||
```
|
||||
[null, 'Yay', 'Cought!', 'You did something wrong', 'ABCDEF']
|
||||
```
|
||||
</details>
|
||||
This document was moved [here](../../../../muscript-runtime/src/test/resources/example.md).
|
Loading…
Reference in New Issue