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
5 changes: 3 additions & 2 deletions src/main/java/leekscript/compiler/LexicalParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ private boolean tryParseCommaLike() {
return false;
}

private boolean tryParseOperator() {
private boolean tryParseOperator() {
// Order is important, the first operator found is returned
// So operators starting with the same characters must be ordered by length
var operators = new String[] {
Expand All @@ -77,7 +77,8 @@ private boolean tryParseOperator() {
">>>=", /* ">>>", ">>=", ">>", */ ">=", ">",
"^=", "^",
"~", "@",
"?", "\\"
"??=", "??", "?",
"\\"
};

if (tryParseExact("=>", TokenType.ARROW)) {
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/leekscript/compiler/expression/Expression.java
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,10 @@ public void compileShiftUnsignedRightEq(MainLeekBlock mainblock, JavaWriter writ
throw new RuntimeException("Abstract method");
}

public void compileCoalesceEq(MainLeekBlock mainblock, JavaWriter writer, Expression expr, boolean parenthesis) {
throw new RuntimeException("Abstract method");
}

public abstract boolean validExpression(WordCompiler compiler, MainLeekBlock mainblock) throws LeekExpressionException;

public Expression trim() {
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/leekscript/compiler/expression/LeekArrayAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -586,6 +586,18 @@ public void compileShiftUnsignedRightEq(MainLeekBlock mainblock, JavaWriter writ
writer.addCode(", " + mainblock.getWordCompiler().getCurrentClassVariable() + ")");
}

@Override
public void compileCoalesceEq(MainLeekBlock mainblock, JavaWriter writer, Expression expr, boolean parenthesis) {
// a[index] ??= b
writer.addCode("put_coalesce_eq(");
mTabular.writeJavaCode(mainblock, writer, false);
writer.addCode(", ");
mCase.writeJavaCode(mainblock, writer, false);
writer.addCode(", ");
expr.writeJavaCode(mainblock, writer, false);
writer.addCode(", " + mainblock.getWordCompiler().getCurrentClassVariable() + ")");
}

public void setLeftValue(boolean b) {
mLeftValue = b;
}
Expand Down
30 changes: 28 additions & 2 deletions src/main/java/leekscript/compiler/expression/LeekExpression.java
Original file line number Diff line number Diff line change
Expand Up @@ -797,6 +797,23 @@ public void writeJavaCode(MainLeekBlock mainblock, JavaWriter writer, boolean pa
}
if (parenthesis) writer.addCode(")");
return;
case Operators.COALESCE:
// a ?? b == (a != null) ? a : b
if (!mExpression1.getType().canBeNull()) {
// a can never be null, so just return a
if (parenthesis) writer.addCode("(");
mExpression1.writeJavaCode(mainblock, writer, !(mExpression1 instanceof LeekExpression));
if (parenthesis) writer.addCode(")");
return;
}
if (parenthesis) writer.addCode("(");
writer.compileLoad(mainblock, mExpression1, true);
writer.addCode(" != null ? ");
mExpression1.writeJavaCode(mainblock, writer, true);
writer.addCode(" : ");
mExpression2.writeJavaCode(mainblock, writer, true);
if (parenthesis) writer.addCode(")");
return;
case Operators.XOR:
writer.addCode("xor(");
writer.getBoolean(mainblock, mExpression1, false);
Expand Down Expand Up @@ -872,6 +889,9 @@ public void writeJavaCode(MainLeekBlock mainblock, JavaWriter writer, boolean pa
mExpression1.compileSetCopy(mainblock, writer, mExpression2, parenthesis);
}
return;
case Operators.COALESCE_ASSIGN:
mExpression1.compileCoalesceEq(mainblock, writer, mExpression2, parenthesis);
return;
case Operators.ADDASSIGN:
mExpression1.compileAddEq(mainblock, writer, mExpression2, type, parenthesis);
return;
Expand Down Expand Up @@ -1006,7 +1026,7 @@ public void analyze(WordCompiler compiler) throws LeekCompilerException {

// Si on a affaire à une assignation, incrémentation ou autre du genre
// on doit vérifier qu'on a bien une variable (l-value)
if (mOperator == Operators.ADDASSIGN || mOperator == Operators.MINUSASSIGN || mOperator == Operators.DIVIDEASSIGN || mOperator == Operators.ASSIGN || mOperator == Operators.MODULUSASSIGN || mOperator == Operators.MULTIPLIEASSIGN || mOperator == Operators.POWERASSIGN || mOperator == Operators.BITOR_ASSIGN || mOperator == Operators.BITAND_ASSIGN || mOperator == Operators.BITXOR_ASSIGN || mOperator == Operators.SHIFT_LEFT_ASSIGN || mOperator == Operators.SHIFT_RIGHT_ASSIGN || mOperator == Operators.SHIFT_UNSIGNED_RIGHT_ASSIGN) {
if (mOperator == Operators.ADDASSIGN || mOperator == Operators.MINUSASSIGN || mOperator == Operators.DIVIDEASSIGN || mOperator == Operators.ASSIGN || mOperator == Operators.MODULUSASSIGN || mOperator == Operators.MULTIPLIEASSIGN || mOperator == Operators.POWERASSIGN || mOperator == Operators.BITOR_ASSIGN || mOperator == Operators.BITAND_ASSIGN || mOperator == Operators.BITXOR_ASSIGN || mOperator == Operators.SHIFT_LEFT_ASSIGN || mOperator == Operators.SHIFT_RIGHT_ASSIGN || mOperator == Operators.SHIFT_UNSIGNED_RIGHT_ASSIGN || mOperator == Operators.COALESCE_ASSIGN) {
if (mExpression1.isFinal()) {
if (mExpression1 instanceof LeekObjectAccess) {
if (!compiler.isInConstructor()) {
Expand Down Expand Up @@ -1065,14 +1085,15 @@ public void analyze(WordCompiler compiler) throws LeekCompilerException {
}

// Type compatible ?
if (mOperator == Operators.ASSIGN || mOperator == Operators.ADDASSIGN || mOperator == Operators.MINUSASSIGN || mOperator == Operators.MULTIPLIEASSIGN || mOperator == Operators.DIVIDEASSIGN || mOperator == Operators.POWERASSIGN) {
if (mOperator == Operators.ASSIGN || mOperator == Operators.ADDASSIGN || mOperator == Operators.MINUSASSIGN || mOperator == Operators.MULTIPLIEASSIGN || mOperator == Operators.DIVIDEASSIGN || mOperator == Operators.POWERASSIGN || mOperator == Operators.COALESCE_ASSIGN) {

var expressionType = mExpression2.getType();
if (mOperator == Operators.ADDASSIGN) expressionType = mExpression1.getType().add(mExpression2.getType());
if (mOperator == Operators.MINUSASSIGN) expressionType = mExpression1.getType().sub(mExpression2.getType());
if (mOperator == Operators.MULTIPLIEASSIGN) expressionType = mExpression1.getType().mul(mExpression2.getType());
if (mOperator == Operators.DIVIDEASSIGN) expressionType = mExpression1.getType().div(mExpression2.getType());
if (mOperator == Operators.POWERASSIGN) expressionType = mExpression1.getType().pow(mExpression2.getType());
if (mOperator == Operators.COALESCE_ASSIGN) expressionType = Type.compound(mExpression1.getType(), mExpression2.getType());

var cast = mExpression1.getType().accepts(expressionType);
if (cast == CastType.INCOMPATIBLE) {
Expand Down Expand Up @@ -1173,6 +1194,11 @@ public void analyze(WordCompiler compiler) throws LeekCompilerException {
else if (mOperator == Operators.BITAND || mOperator == Operators.BITNOT || mOperator == Operators.BITOR || mOperator == Operators.BITXOR || mOperator == Operators.SHIFT_LEFT || mOperator == Operators.SHIFT_RIGHT || mOperator == Operators.SHIFT_UNSIGNED_RIGHT || mOperator == Operators.INTEGER_DIVISION) {
type = Type.INT;
}
else if (mOperator == Operators.COALESCE) {
type = Type.compound(mExpression1.getType(), mExpression2.getType());
} else if (mOperator == Operators.COALESCE_ASSIGN) {
type = mExpression1.getType();
}
else if (mOperator == Operators.ADD) {
type = mExpression1.getType().add(mExpression2.getType());
}
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/leekscript/compiler/expression/LeekObjectAccess.java
Original file line number Diff line number Diff line change
Expand Up @@ -490,6 +490,16 @@ public void compileShiftUnsignedRightEq(MainLeekBlock mainblock, JavaWriter writ
writer.addCode(", " + mainblock.getWordCompiler().getCurrentClassVariable() + ")");
}

@Override
public void compileCoalesceEq(MainLeekBlock mainblock, JavaWriter writer, Expression expr, boolean parenthesis) {
// object.field ??= value
writer.addCode("field_coalesce_eq(");
object.writeJavaCode(mainblock, writer, false);
writer.addCode(", \"" + field.getWord() + "\", ");
expr.writeJavaCode(mainblock, writer, false);
writer.addCode(", " + mainblock.getWordCompiler().getCurrentClassVariable() + ")");
}

@Override
public Location getLocation() {
if (field == null) {
Expand Down
43 changes: 43 additions & 0 deletions src/main/java/leekscript/compiler/expression/LeekVariable.java
Original file line number Diff line number Diff line change
Expand Up @@ -1241,6 +1241,49 @@ public void compileShiftUnsignedRightEq(MainLeekBlock mainblock, JavaWriter writ
}
}

@Override
public void compileCoalesceEq(MainLeekBlock mainblock, JavaWriter writer, Expression expr, boolean parenthesis) {
// a ??= b => a = (a != null) ? a : b
if (type == VariableType.GLOBAL || type == VariableType.LOCAL) {
if (isBox()) {
// Box-based variable: use Box.coalesce_eq
if (type == VariableType.GLOBAL) {
writer.addCode("g_" + token.getWord() + ".coalesce_eq(");
expr.writeJavaCode(mainblock, writer, false);
writer.addCode(")");
} else {
writer.addCode("u_" + token.getWord() + ".coalesce_eq(");
expr.writeJavaCode(mainblock, writer, false);
writer.addCode(")");
}
return;
}
}

// Fallback: explicit ternary assignment on the underlying storage
String varName;
if (type == VariableType.FIELD) {
varName = token.getWord();
} else if (type == VariableType.STATIC_FIELD) {
varName = mainblock.getWordCompiler().getCurrentClassVariable() + "." + token.getWord();
} else if (type == VariableType.GLOBAL) {
varName = "g_" + token.getWord();
} else {
// LOCAL or others
varName = "u_" + token.getWord();
}

if (parenthesis) writer.addCode("(");
writer.addCode(varName + " = ");
if (this.variableType != Type.ANY) {
writer.addCode("(" + this.variableType.getJavaPrimitiveName(mainblock.getVersion()) + ") ");
}
writer.addCode("(" + varName + " != null ? " + varName + " : ");
expr.writeJavaCode(mainblock, writer, false);
writer.addCode(")");
if (parenthesis) writer.addCode(")");
}

@Override
public Location getLocation() {
return token.getLocation();
Expand Down
11 changes: 11 additions & 0 deletions src/main/java/leekscript/compiler/expression/Operators.java
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,8 @@ public class Operators {
public final static int XOR = 55;
public final static int IN = 56;
public final static int NOT_IN = 57;
public final static int COALESCE = 58;
public final static int COALESCE_ASSIGN = 59;

public final static int getOperator(String operator, int version) {
if(operator.equals("[")) return CROCHET;
Expand Down Expand Up @@ -120,6 +122,8 @@ public final static int getOperator(String operator, int version) {
if(operator.equals("as")) return AS;
if(operator.equals("in")) return IN;
if(operator.equals("not in")) return NOT_IN;
if(operator.equals("??")) return COALESCE;
if(operator.equals("??=")) return COALESCE_ASSIGN;

return -1;
}
Expand Down Expand Up @@ -180,6 +184,8 @@ public static int getPriority(int operator) {
return 3;
case OR:
return 2;
case COALESCE:
return 2;
case TERNAIRE:
case DOUBLE_POINT:
return 1;
Expand All @@ -197,6 +203,7 @@ public static int getPriority(int operator) {
case BITAND_ASSIGN:
case BITOR_ASSIGN:
case BITXOR_ASSIGN:
case COALESCE_ASSIGN:
return 0;
default:
return -1;
Expand Down Expand Up @@ -331,6 +338,10 @@ public static String getString(int operator) {
return "not in";
case XOR:
return "xor";
case COALESCE:
return "??";
case COALESCE_ASSIGN:
return "??=";
}
return "null";
}
Expand Down
62 changes: 62 additions & 0 deletions src/main/java/leekscript/runner/AI.java
Original file line number Diff line number Diff line change
Expand Up @@ -1888,6 +1888,33 @@ public Object field_add_eq(Object object, String field, Object value, ClassLeekV
return null;
}

public Object field_coalesce_eq(Object object, String field, Object value, ClassLeekValue fromClass) throws LeekRunException {
if (object instanceof ObjectLeekValue) {
return ((ObjectLeekValue) object).field_coalesce_eq(field, value);
}
if (object instanceof ClassLeekValue) {
return ((ClassLeekValue) object).field_coalesce_eq(field, value);
}
try {
var f = getFieldCached(object.getClass(), field);
if (!checkFieldAccessLevel(f, object, fromClass)) {
return null;
}
if (f.isAnnotationPresent(Final.class)) {
this.addSystemLog(AILog.ERROR, Error.CANNOT_ASSIGN_FINAL_FIELD, new String[] { object.getClass().getName(), field });
return null;
}
var current = f.get(object);
var v = current != null ? current : value;
f.set(object, v);
return v;
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
addSystemLog(AILog.ERROR, e);
}
addSystemLog(AILog.ERROR, Error.UNKNOWN_FIELD, new Object[] { object, field });
return null;
}

public Object field_sub_eq(Object object, String field, Object value, ClassLeekValue fromClass) throws LeekRunException {
if (object instanceof ObjectLeekValue) {
return ((ObjectLeekValue) object).field_sub_eq(field, value);
Expand Down Expand Up @@ -2809,6 +2836,41 @@ public Object put_bxor_eq(Object array, Object key, Object value, ClassLeekValue
return null;
}

public Object put_coalesce_eq(Object array, Object key, Object value, ClassLeekValue fromClass) throws LeekRunException {
if (array instanceof LegacyArrayLeekValue) {
return ((LegacyArrayLeekValue) array).put_coalesce_eq(this, key, value);
}
if (array instanceof ArrayLeekValue) {
return ((ArrayLeekValue) array).put_coalesce_eq(this, key, value);
}
if (array instanceof MapLeekValue) {
return ((MapLeekValue) array).put_coalesce_eq(this, key, value);
}
if (array instanceof ObjectLeekValue) {
var field = string(key);
return ((ObjectLeekValue) array).field_coalesce_eq(field, value);
}
if (array instanceof ClassLeekValue) {
var field = string(key);
return ((ClassLeekValue) array).field_coalesce_eq(field, value);
}
if (array instanceof NativeObjectLeekValue) {
try {
var f = getWriteableField(array, string(key), fromClass);
if (f == null) return null;
var current = f.get(array);
var v = current != null ? current : value;
f.set(array, v);
return v;
} catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException | SecurityException e) {
addSystemLog(AILog.ERROR, e);
}
}
if (version >= 3)
addSystemLog(AILog.ERROR, Error.VALUE_IS_NOT_AN_ARRAY, new Object[] { array });
return null;
}

public Object set(Object variable, Object value) throws LeekRunException {
if (variable instanceof Box) {
return ((Box) variable).set(value);
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/leekscript/runner/values/ArrayLeekValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,21 @@ public Object put_add_eq(AI ai, Object key, Object value) throws LeekRunExceptio
}
}

public Object put_coalesce_eq(AI ai, Object key, Object value) throws LeekRunException {
ai.opsNoCheck(ArrayLeekValue.WRITE_OPERATIONS);
int i = (int) ai.integer(key);
if (i < 0) i += size();
try {
var current = get(i);
var new_value = current != null ? current : value;
set(i, new_value);
return new_value;
} catch (IndexOutOfBoundsException e) {
wrongIndexError(ai, i);
return null;
}
}

public Object put_sub_eq(AI ai, Object key, Object value) throws LeekRunException {
ai.opsNoCheck(ArrayLeekValue.WRITE_OPERATIONS);
int i = (int) ai.integer(key);
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/leekscript/runner/values/Box.java
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,14 @@ public Object mod_eq(Object val) throws LeekRunException {
return mValue = mUAI.mod(mValue, val);
}

public Object coalesce_eq(Object val) throws LeekRunException {
// a ??= b => a = (a != null) ? a : b
if (mValue != null) {
return mValue;
}
return mValue = val;
}

public Object get(Object index, ClassLeekValue fromClass) throws LeekRunException {
return mUAI.get(mValue, index, fromClass);
}
Expand Down
5 changes: 5 additions & 0 deletions src/main/java/leekscript/runner/values/ClassLeekValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,11 @@ public long field_ushr_eq(String field, Object value) throws LeekRunException {
return result.ushr_eq(value);
}

public Object field_coalesce_eq(String field, Object value) throws LeekRunException {
var result = getFieldL(field);
return result.coalesce_eq(value);
}

public Object callMethod(String method, ClassLeekValue fromClass, Object... arguments) throws LeekRunException {
ai.ops(1);
var result = getStaticMethod(ai, method, fromClass);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -448,6 +448,11 @@ public long put_ushr_eq(AI ai, Object key, Object value) throws LeekRunException
return getOrCreate(ai, key).ushr_eq(value);
}

public Object put_coalesce_eq(AI ai, Object key, Object value) throws LeekRunException {
var box = getOrCreate(ai, key);
return box.coalesce_eq(value);
}

private Object transformKey(AI ai, Object key) throws LeekRunException {
if (key instanceof String || key instanceof ObjectLeekValue || key instanceof NativeObjectLeekValue) {
return key;
Expand Down
8 changes: 8 additions & 0 deletions src/main/java/leekscript/runner/values/MapLeekValue.java
Original file line number Diff line number Diff line change
Expand Up @@ -205,6 +205,14 @@ public Object put_bxor_eq(AI ai, Object key, Object value) throws LeekRunExcepti
return v;
}

public Object put_coalesce_eq(AI ai, Object key, Object value) throws LeekRunException {
ai.opsNoCheck(MapLeekValue.WRITE_OPERATIONS);
var current = get(key);
var v = current != null ? current : value;
put(key, v);
return v;
}

public Object put_shr_eq(AI ai, Object key, Object value) throws LeekRunException {
ai.opsNoCheck(MapLeekValue.WRITE_OPERATIONS);
var v = ai.shr(get(key), value);
Expand Down
Loading
Loading