Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 14 additions & 16 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
name: 'build'
on: [ 'workflow_dispatch' ]
on:
push:
branches: [ 'main' ]
pull_request:

permissions:
contents: read

jobs:
build:
Expand All @@ -8,26 +14,18 @@ jobs:
- name: Checkout Branch
uses: actions/checkout@v7

- name: Restore gradle.properties
shell: bash
env:
MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
run: |
mkdir -p ~/.gradle/
echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV
echo "${MAVEN_TOKEN}" > ~/.gradle/gradle.properties

- name: Setup Java
uses: actions/setup-java@v6
with:
distribution: microsoft
java-version: 25
cache: gradle

- name: Grant Execute Permission
run: chmod +x gradlew
# the tests compare with expectations.txt, which MolangJS generates from tests.txt
- name: Check MolangJS Expectations
run: |
./gradlew :runtime-compiler:generateExpectations
git diff --exit-code -- runtime-compiler/src/test/resources/expectations.txt

- name: Build
run: |
./gradlew publish
env:
MAVEN_PASSWORD: ${{ secrets.REDLANCE_PASSWORD }}
run: ./gradlew build
34 changes: 34 additions & 0 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
name: 'publish'
on: [ 'workflow_dispatch' ]

jobs:
publish:
runs-on: ubuntu-latest
steps:
- name: Checkout Branch
uses: actions/checkout@v7

- name: Restore gradle.properties
shell: bash
env:
MAVEN_TOKEN: ${{ secrets.MAVEN_TOKEN }}
run: |
mkdir -p ~/.gradle/
echo "GRADLE_USER_HOME=${HOME}/.gradle" >> $GITHUB_ENV
echo "${MAVEN_TOKEN}" > ~/.gradle/gradle.properties

- name: Setup Java
uses: actions/setup-java@v6
with:
distribution: microsoft
java-version: 25

- name: Grant Execute Permission
run: chmod +x gradlew

# build runs the tests and the license checks, so a failing build is never published
- name: Build and Publish
run: |
./gradlew build publish
env:
MAVEN_PASSWORD: ${{ secrets.REDLANCE_PASSWORD }}
26 changes: 0 additions & 26 deletions docs/bindings.md

This file was deleted.

37 changes: 0 additions & 37 deletions docs/getting-started.md

This file was deleted.

4 changes: 0 additions & 4 deletions docs/index.txt

This file was deleted.

27 changes: 0 additions & 27 deletions docs/installation.md

This file was deleted.

85 changes: 0 additions & 85 deletions docs/usage.md

This file was deleted.

4 changes: 2 additions & 2 deletions gradle.properties
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
group=team.unnamed
version=6.0.2
group=org.redlance.mochafloats
version=6.1.0
description=A lightweight, fast and efficient Molang lexer, parser, interpreter and compiler for Java 24+
kotlin.stdlib.default.dependency=false
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,15 @@ final class MolangParserImpl implements MolangParser {
lexer.next();
List<Expression> expressions = new ArrayList<>();
while (true) {
// empty statements are skipped, like Bedrock does with "{ v.a = 1;; }"
while (lexer.current().kind() == TokenKind.SEMICOLON) {
lexer.next();
}
// "{}" and a semicolon after the last statement, as in "{ v.a = 1; }"
if (lexer.current().kind() == TokenKind.RBRACE) {
lexer.next();
break;
}
Expression compoundExpression = MolangParserImpl.parseCompoundExpression(lexer, 0);
if (compoundExpression != null) expressions.add(compoundExpression);
token = lexer.current();
Expand Down Expand Up @@ -155,7 +164,12 @@ final class MolangParserImpl implements MolangParser {
return new UnaryExpression(UnaryExpression.Op.RETURN, compoundExpression);
}

return null;
// an empty statement (";;"), a missing operand ("1 +") or argument ("f(1,)"):
// returning null here made the caller stop parsing and drop the rest of the script
if (token.kind() == TokenKind.ERROR) {
throw new ParseException("Found error token: " + token.value(), lexer.cursor());
}
throw new ParseException("Expected an expression, but found " + token.kind(), lexer.cursor());
}

//
Expand Down Expand Up @@ -277,7 +291,9 @@ final class MolangParserImpl implements MolangParser {
// then it's a ternary expression, since there is a ':', indicating the next expression
lexer.next();
if (trueValue == null) return null;
Expression compoundExpression = MolangParserImpl.parseCompoundExpression(lexer, ternaryPrecedence);
// the false branch may hold another conditional, so "a ? 1 : b ? 2 : 3"
// is "a ? 1 : (b ? 2 : 3)", like in MolangJS and most other implementations
Expression compoundExpression = MolangParserImpl.parseCompoundExpression(lexer, ternaryPrecedence - 1);
if (compoundExpression == null || left == null) return null;
return new TernaryConditionalExpression(left, trueValue, compoundExpression);
} else {
Expand Down Expand Up @@ -318,7 +334,10 @@ final class MolangParserImpl implements MolangParser {
}

lexer.next();
Expression compoundExpression = MolangParserImpl.parseCompoundExpression(lexer, precedence);
// assignment takes everything on its right, so "v.x = a ? 1 : 2" assigns the
// conditional and "v.a = v.b = 1" assigns both, the other operators group from the left
final int rightPrecedence = op == BinaryExpression.Op.ASSIGN ? precedence - 1 : precedence;
Expression compoundExpression = MolangParserImpl.parseCompoundExpression(lexer, rightPrecedence);
if (compoundExpression == null || left == null) return null;
return new BinaryExpression(op, left, compoundExpression);
}
Expand Down Expand Up @@ -350,6 +369,11 @@ final class MolangParserImpl implements MolangParser {
private @Nullable Expression next0() throws IOException {
Token token = lexer.next();

// empty statements are skipped, like Bedrock does with "v.a = 1;; v.b = 2"
while (token.kind() == TokenKind.SEMICOLON) {
token = lexer.next();
}

if (token.kind() == TokenKind.EOF) {
// reached end-of-file!
return null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ public void write(ByteBuf buf) {

@Override
public @NotNull String toString() {
return String.format("%s[%s]", array, index);
return BinaryExpression.parenthesize(array, !CallExpression.isPostfixOperand(array)) + "[" + index + "]";
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,37 @@ public void write(ByteBuf buf) {

@Override
public String toString() {
return String.format("%s%s%s", this.left, this.op.toString(), this.right);
return parenthesize(left, leftNeedsParentheses()) + op + parenthesize(right, rightNeedsParentheses());
}

// "=" and "?" take everything on their right, the other operators group from the left
private boolean rightAssociative() {
return op == Op.ASSIGN || op == Op.CONDITIONAL;
}

// parentheses are only printed where the parser would otherwise group differently
boolean leftNeedsParentheses() {
final int leftPrecedence = printPrecedence(left);
return leftPrecedence < op.precedence() || (rightAssociative() && leftPrecedence == op.precedence());
}

boolean rightNeedsParentheses() {
final int rightPrecedence = printPrecedence(right);
return rightPrecedence < op.precedence() || (!rightAssociative() && rightPrecedence == op.precedence());
}

// how tightly an expression binds when printed, everything but the operators binds tightest
static int printPrecedence(final @NotNull Expression expression) {
if (expression instanceof BinaryExpression binary) {
return binary.op.precedence();
} else if (expression instanceof TernaryConditionalExpression) {
return Op.CONDITIONAL.precedence();
}
return Integer.MAX_VALUE;
}

static @NotNull String parenthesize(final @NotNull Expression expression, final boolean parentheses) {
return parentheses ? "(" + expression + ")" : expression.toString();
}

@Override
Expand Down
Loading
Loading