diff --git a/CSharp/Interpreter/ArcscriptExpression.cs b/CSharp/Interpreter/ArcscriptExpression.cs index bb9b67c..eb450ce 100644 --- a/CSharp/Interpreter/ArcscriptExpression.cs +++ b/CSharp/Interpreter/ArcscriptExpression.cs @@ -93,6 +93,28 @@ public class Expression : ArcscriptExpressionBase, IComparable } return new Expression(result); } + + public static Expression operator %(Expression first, Expression second) + { + if (first.Type() == typeof(string) || second.Type() == typeof(string)) + { + throw new InvalidOperationException("Modulo is not supported for string types."); + } + var doubleValues = GetDoubleValues(first.Value, second.Value); + if (doubleValues.Value2 == 0) + { + throw new DivideByZeroException("Modulo by zero."); + } + if (!doubleValues.HasDouble) + { + var intValue = (int)(doubleValues.Value1 % doubleValues.Value2); + return new Expression(intValue); + } + else + { + return new Expression(doubleValues.Value1 % doubleValues.Value2); + } + } public static bool operator ==(Expression first, Expression second) { @@ -113,8 +135,12 @@ public class Expression : ArcscriptExpressionBase, IComparable return false; } - public int CompareTo(object other) + public int CompareTo(object? other) { + if (other == null || !(other is Expression)) + { + throw new ArgumentException("Object is not an Expression"); + } Expression o = (Expression)other; DoubleValues fValues = GetDoubleValues(this.Value, o.Value); double result = fValues.Value1 - fValues.Value2; @@ -128,7 +154,7 @@ public int CompareTo(object other) return 0; } - public override bool Equals(object obj) + public override bool Equals(object? obj) { if (obj == null || !(obj is Expression)) { diff --git a/CSharp/Interpreter/ArcscriptVisitor.cs b/CSharp/Interpreter/ArcscriptVisitor.cs index 741050a..785cd95 100644 --- a/CSharp/Interpreter/ArcscriptVisitor.cs +++ b/CSharp/Interpreter/ArcscriptVisitor.cs @@ -167,6 +167,8 @@ public override object VisitStatement_assignment([NotNull] ArcscriptParser.State variableValue *= compound_condition_or; } else if ( context.ASSIGNDIV() != null ) { variableValue /= compound_condition_or; + } else if (context.ASSIGNMOD() != null) { + variableValue %= compound_condition_or; } this.state.SetVarValue(variableName, variableValue.Value); @@ -281,8 +283,13 @@ public override object VisitMultiplicative_numeric_expression([NotNull] Arcscrip if ( context.MUL() != null ) { return result * signed_unary_num_expr; } - // Else DIV - return result / signed_unary_num_expr; + + if (context.DIV() != null) + { + return result / signed_unary_num_expr; + } + // Else MOD + return result % signed_unary_num_expr; } return (Expression)this.VisitSigned_unary_numeric_expression(context.signed_unary_numeric_expression()); diff --git a/CSharp/__tests__/runtimeErrors.json b/CSharp/__tests__/runtimeErrors.json index 2f4924a..9adb85e 100644 --- a/CSharp/__tests__/runtimeErrors.json +++ b/CSharp/__tests__/runtimeErrors.json @@ -67,6 +67,18 @@ { "code": "
w -= _a
", "error": "runtime" + }, + { + "code": "
z = 10 % 0
", + "error": "runtime" + }, + { + "code": "
x %= 0
", + "error": "runtime" + }, + { + "code": "
y = 5 % \"test\"
", + "error": "runtime" } ] } \ No newline at end of file diff --git a/CSharp/__tests__/valid.json b/CSharp/__tests__/valid.json index 6df3bb8..ea3214d 100644 --- a/CSharp/__tests__/valid.json +++ b/CSharp/__tests__/valid.json @@ -99,6 +99,42 @@ "var1": 8 } }, + { + "code": "
x = 10 % 3
", + "changes": { + "var1": 1 + } + }, + { + "code": "
x = 10 % 3 * 2
", + "changes": { + "var1": 2 + } + }, + { + "code": "
x = 10 % (3 * 2)
", + "changes": { + "var1": 4 + } + }, + { + "code": "
x = 5 + 6 % 3
", + "changes": { + "var1": 5 + } + }, + { + "code": "
x %= 6
", + "changes": { + "var1": 2 + } + }, + { + "code": "
x = 10 % 7 % 2
", + "changes": { + "var1": 1 + } + }, { "code": "
x = 3 / 4 * 5
", "changes": { diff --git a/Cpp/demo/tests/runtimeErrors.json b/Cpp/demo/tests/runtimeErrors.json index 2f4924a..9adb85e 100644 --- a/Cpp/demo/tests/runtimeErrors.json +++ b/Cpp/demo/tests/runtimeErrors.json @@ -67,6 +67,18 @@ { "code": "
w -= _a
", "error": "runtime" + }, + { + "code": "
z = 10 % 0
", + "error": "runtime" + }, + { + "code": "
x %= 0
", + "error": "runtime" + }, + { + "code": "
y = 5 % \"test\"
", + "error": "runtime" } ] } \ No newline at end of file diff --git a/Cpp/demo/tests/valid.json b/Cpp/demo/tests/valid.json index 6df3bb8..ea3214d 100644 --- a/Cpp/demo/tests/valid.json +++ b/Cpp/demo/tests/valid.json @@ -99,6 +99,42 @@ "var1": 8 } }, + { + "code": "
x = 10 % 3
", + "changes": { + "var1": 1 + } + }, + { + "code": "
x = 10 % 3 * 2
", + "changes": { + "var1": 2 + } + }, + { + "code": "
x = 10 % (3 * 2)
", + "changes": { + "var1": 4 + } + }, + { + "code": "
x = 5 + 6 % 3
", + "changes": { + "var1": 5 + } + }, + { + "code": "
x %= 6
", + "changes": { + "var1": 2 + } + }, + { + "code": "
x = 10 % 7 % 2
", + "changes": { + "var1": 1 + } + }, { "code": "
x = 3 / 4 * 5
", "changes": { diff --git a/Cpp/src/ArcscriptExpression.cpp b/Cpp/src/ArcscriptExpression.cpp index ca13bfb..4b75888 100755 --- a/Cpp/src/ArcscriptExpression.cpp +++ b/Cpp/src/ArcscriptExpression.cpp @@ -1,5 +1,6 @@ #include "ArcscriptExpression.h" #include +#include #include "ArcscriptErrorExceptions.h" @@ -31,11 +32,10 @@ std::string Expression::valueToString(std::any value) Expression::NumberValues Expression::doubleValues(std::any value1, std::any value2) { - int intValue1, intValue2; - double dblValue1, dblValue2; + double dblValue1 = 0, dblValue2 = 0; bool isDouble = false; if (value1.type() == typeid(int)) { - intValue1 = std::any_cast(value1); + const int intValue1 = std::any_cast(value1); dblValue1 = intValue1; } else if (value1.type() == typeid(double)){ // type double; isDouble = true; @@ -49,7 +49,7 @@ Expression::NumberValues Expression::doubleValues(std::any value1, std::any valu } } if (value2.type() == typeid(int)) { - intValue2 = std::any_cast(value2); + const int intValue2 = std::any_cast(value2); dblValue2 = intValue2; } else if (value2.type() == typeid(double)) { isDouble = true; @@ -77,15 +77,15 @@ bool Expression::valueToBool(std::any value) { return (std::any_cast(value) > 0); } if (value.type() == typeid(std::string)) { - return (std::any_cast(value) != ""); + return (!std::any_cast(value).empty()); } return (std::any_cast(value)); } -Expression Expression::operator+ (const Expression &other) { +Expression Expression::operator+ (const Expression &other) const { if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { - return Expression(valueToString(value) + valueToString(other.value)); + return {valueToString(value) + valueToString(other.value)}; } NumberValues values = doubleValues(value, other.value); Expression* result; @@ -98,7 +98,7 @@ Expression Expression::operator+ (const Expression &other) { return *result; } -Expression Expression::operator- (const Expression &other) { +Expression Expression::operator- (const Expression &other) const { if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { throw RuntimeErrorException("Cannot subtract strings"); } @@ -113,7 +113,7 @@ Expression Expression::operator- (const Expression &other) { return *result; } -Expression Expression::operator* (const Expression &other) { +Expression Expression::operator* (const Expression &other) const { NumberValues values = doubleValues(value, other.value); Expression* result; if (!values.hasDoubles) { @@ -125,7 +125,7 @@ Expression Expression::operator* (const Expression &other) { return *result; } -Expression Expression::operator* (const int other) { +Expression Expression::operator* (const int other) const { NumberValues values = doubleValues(value, other); Expression* result; if (!values.hasDoubles) { @@ -137,7 +137,7 @@ Expression Expression::operator* (const int other) { return *result; } -Expression Expression::operator/ (const Expression &other) { +Expression Expression::operator/ (const Expression &other) const { NumberValues values = doubleValues(value, other.value); Expression* result; @@ -150,6 +150,24 @@ Expression Expression::operator/ (const Expression &other) { return *result; } +Expression Expression::operator% (const Expression &other) const { + NumberValues values = doubleValues(value, other.value); + Expression* result; + + if (values.value2 == 0) { + throw RuntimeErrorException("Modulo by zero is not allowed."); + } + + if (!values.hasDoubles) { + int intValue = static_cast(static_cast(values.value1) % static_cast(values.value2)); + result = new Expression(intValue); + } else { + double modValue = std::fmod(values.value1, values.value2); + result = new Expression(modValue); + } + return *result; +} + Expression Expression::operator+= (const Expression &other) { if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { auto val1 = valueToString(value); @@ -216,7 +234,24 @@ Expression Expression::operator/= (const Expression &other) { return *this; } -bool Expression::operator== (const Expression &other) { +Expression Expression::operator%= (const Expression &other) { + NumberValues values = doubleValues(value, other.value); + + if (values.value2 == 0) { + throw RuntimeErrorException("Modulo by zero is not allowed."); + } + + if (!values.hasDoubles) { + int intValue = static_cast(static_cast(values.value1) % static_cast(values.value2)); + value = new Expression(intValue); + } else { + double modValue = std::fmod(values.value1, values.value2); + value = new Expression(modValue); + } + return *this; +} + +bool Expression::operator== (const Expression &other) const { if (value.type() == typeid(int) || value.type() == typeid(double)) { NumberValues values = doubleValues(value, other.value); return values.value1 == values.value2; @@ -227,28 +262,28 @@ bool Expression::operator== (const Expression &other) { return std::any_cast(value) == std::any_cast(other.value); } -bool Expression::operator== (double other) { +bool Expression::operator== (double other) const { NumberValues values = doubleValues(value, other); return values.value1 == values.value2; } -bool Expression::operator== (int other) { +bool Expression::operator== (int other) const { NumberValues values = doubleValues(value, other); return values.value1 == values.value2; } -bool Expression::operator== (std::string other) { +bool Expression::operator== (const std::string& other) const { return std::any_cast(value) == other; } -bool Expression::operator== (bool other) { +bool Expression::operator== (const bool other) const { return valueToBool(value) == other; } -bool Expression::operator!= (const Expression &other) { +bool Expression::operator!= (const Expression &other) const { if (value.type() == typeid(std::string) || other.value.type() == typeid(std::string)) { if (value.type() != other.value.type()) { - return true; // Different types, cannot be equal + return true; // Different types cannot be equal } else { return std::any_cast(value) != std::any_cast(other.value); } @@ -263,63 +298,63 @@ bool Expression::operator!= (const Expression &other) { return std::any_cast(value) != std::any_cast(other.value); } -bool Expression::operator!= (double other) { +bool Expression::operator!= (double other) const { NumberValues values = doubleValues(value, other); return values.value1 != values.value2; } -bool Expression::operator!= (int other) { +bool Expression::operator!= (int other) const { NumberValues values = doubleValues(value, other); return values.value1 != values.value2; } -bool Expression::operator!= (std::string other) { +bool Expression::operator!= (const std::string& other) const { return std::any_cast(value) != other; } -bool Expression::operator!= (const char other[]) { +bool Expression::operator!= (const char other[]) const { return strcmp(std::any_cast(value).c_str(), other) == 0; } -bool Expression::operator> (const Expression &other) { +bool Expression::operator> (const Expression &other) const { NumberValues values = doubleValues(value, other.value); return values.value1 > values.value2; } -bool Expression::operator> (int other) { +bool Expression::operator> (int other) const { NumberValues values = doubleValues(value, other); return values.value1 > values.value2; } -bool Expression::operator> (double other) { +bool Expression::operator> (double other) const { NumberValues values = doubleValues(value, other); return values.value1 > values.value2; } -bool Expression::operator>= (const Expression &other) { +bool Expression::operator>= (const Expression &other) const { NumberValues values = doubleValues(value, other.value); return values.value1 >= values.value2; } -bool Expression::operator< (const Expression &other) { +bool Expression::operator< (const Expression &other) const { NumberValues values = doubleValues(value, other.value); return values.value1 < values.value2; } -bool Expression::operator<= (const Expression &other) { +bool Expression::operator<= (const Expression &other) const { NumberValues values = doubleValues(value, other.value); return values.value1 <= values.value2; } -bool Expression::operator! () { +bool Expression::operator! () const { return !(valueToBool(value)); } -bool Expression::operator&& (const Expression &other) { +bool Expression::operator&& (const Expression &other) const { return valueToBool(value) && valueToBool(other.value); } -bool Expression::operator|| (const Expression &other) { +bool Expression::operator|| (const Expression &other) const { return valueToBool(value) || valueToBool(other.value); } } diff --git a/Cpp/src/ArcscriptExpression.h b/Cpp/src/ArcscriptExpression.h index 09d5c00..0bcf11e 100755 --- a/Cpp/src/ArcscriptExpression.h +++ b/Cpp/src/ArcscriptExpression.h @@ -1,7 +1,8 @@ +#pragma once + #include #include #include -#include namespace Arcweave { @@ -46,35 +47,37 @@ class Expression { return value.type(); } - Expression operator+ (const Expression &other); - Expression operator- (const Expression &other); - Expression operator* (const Expression &other); - Expression operator* (const int other); - Expression operator/ (const Expression &other); + Expression operator+ (const Expression &other) const; + Expression operator- (const Expression &other) const; + Expression operator* (const Expression &other) const; + Expression operator* (const int other) const; + Expression operator/ (const Expression &other) const; + Expression operator% (const Expression &other) const; Expression operator+= (const Expression &other); Expression operator-= (const Expression &other); Expression operator*= (const Expression &other); Expression operator/= (const Expression &other); + Expression operator%= (const Expression &other); - bool operator== (const Expression &other); - bool operator== (double other); - bool operator== (int other); - bool operator== (std::string other); - bool operator== (bool other); - bool operator!= (const Expression &other); - bool operator!= (double other); - bool operator!= (int other); - bool operator!= (std::string other); - bool operator!= (const char other[]); - bool operator> (const Expression &other); - bool operator> (int other); - bool operator> (double other); - bool operator>= (const Expression &other); - bool operator< (const Expression &other); - bool operator<= (const Expression &other); - bool operator! (); - bool operator&& (const Expression &other); - bool operator|| (const Expression &other); + bool operator== (const Expression &other) const; + bool operator== (double other) const; + bool operator== (int other) const; + bool operator== (const std::string& other) const; + bool operator== (bool other) const; + bool operator!= (const Expression &other) const; + bool operator!= (double other) const; + bool operator!= (int other) const; + bool operator!= (const std::string& other) const; + bool operator!= (const char other[]) const; + bool operator> (const Expression &other) const; + bool operator> (int other) const; + bool operator> (double other) const; + bool operator>= (const Expression &other) const; + bool operator< (const Expression &other) const; + bool operator<= (const Expression &other) const; + bool operator! () const; + bool operator&& (const Expression &other) const; + bool operator|| (const Expression &other) const; }; } diff --git a/Cpp/src/ArcscriptVisitor.cpp b/Cpp/src/ArcscriptVisitor.cpp index f849517..da05015 100755 --- a/Cpp/src/ArcscriptVisitor.cpp +++ b/Cpp/src/ArcscriptVisitor.cpp @@ -154,6 +154,9 @@ std::any ArcscriptVisitor::visitStatement_assignment(ArcscriptParser::Statement_ if (ctx->ASSIGNDIV() != NULL) { varValue /= compound_condition_or; } + if (ctx->ASSIGNMOD() != NULL) { + varValue %= compound_condition_or; + } state->setVarValue(variableName, varValue.value); return std::any(); @@ -277,6 +280,8 @@ std::any ArcscriptVisitor::visitMultiplicative_numeric_expression(ArcscriptParse } else if (ctx->DIV() != NULL) { signed_unary_num_expr = result / signed_unary_num_expr; + } else if (ctx->MOD() != NULL) { + signed_unary_num_expr = result % signed_unary_num_expr; } return signed_unary_num_expr; } diff --git a/Cpp/src/ArcscriptVisitor.h b/Cpp/src/ArcscriptVisitor.h index 2b07166..41ec406 100755 --- a/Cpp/src/ArcscriptVisitor.h +++ b/Cpp/src/ArcscriptVisitor.h @@ -15,7 +15,7 @@ class ArcscriptVisitor : public ArcscriptParserBaseVisitor { ArcscriptState *state; ArcscriptFunctions *functions; - ArcscriptVisitor(ArcscriptState* _state) : state(_state) { + explicit ArcscriptVisitor(ArcscriptState* _state) : state(_state) { functions = new ArcscriptFunctions(state); } diff --git a/JavaScript/__tests__/runtimeErrors.json b/JavaScript/__tests__/runtimeErrors.json index 2f4924a..9adb85e 100644 --- a/JavaScript/__tests__/runtimeErrors.json +++ b/JavaScript/__tests__/runtimeErrors.json @@ -67,6 +67,18 @@ { "code": "
w -= _a
", "error": "runtime" + }, + { + "code": "
z = 10 % 0
", + "error": "runtime" + }, + { + "code": "
x %= 0
", + "error": "runtime" + }, + { + "code": "
y = 5 % \"test\"
", + "error": "runtime" } ] } \ No newline at end of file diff --git a/JavaScript/__tests__/valid.json b/JavaScript/__tests__/valid.json index 86ea21a..2459060 100644 --- a/JavaScript/__tests__/valid.json +++ b/JavaScript/__tests__/valid.json @@ -99,6 +99,42 @@ "var1": 8 } }, + { + "code": "
x = 10 % 3
", + "changes": { + "var1": 1 + } + }, + { + "code": "
x = 10 % 3 * 2
", + "changes": { + "var1": 2 + } + }, + { + "code": "
x = 10 % (3 * 2)
", + "changes": { + "var1": 4 + } + }, + { + "code": "
x = 5 + 6 % 3
", + "changes": { + "var1": 5 + } + }, + { + "code": "
x %= 6
", + "changes": { + "var1": 2 + } + }, + { + "code": "
x = 10 % 7 % 2
", + "changes": { + "var1": 1 + } + }, { "code": "
x = 3 / 4 * 5
", "changes": { diff --git a/JavaScript/src/ArcscriptVisitor.js b/JavaScript/src/ArcscriptVisitor.js index 68742a1..53d7a2c 100644 --- a/JavaScript/src/ArcscriptVisitor.js +++ b/JavaScript/src/ArcscriptVisitor.js @@ -163,6 +163,7 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { ctx.ASSIGNADD() || ctx.ASSIGNSUB() || ctx.ASSIGNMUL() || + ctx.ASSIGNMOD() || ctx.ASSIGNDIV() ) { if (typeof variableValue === 'boolean') { @@ -191,6 +192,13 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { if (!Number.isFinite(result)) { throw new RuntimeError(`Invalid division by zero`); } + } else if (ctx.ASSIGNMOD()) { + result = new BigNumber(variableValue) + .modulo(new BigNumber(compound_condition_or)) + .toNumber(); + if (!Number.isFinite(result)) { + throw new RuntimeError(`Invalid division by zero`); + } } else if (ctx.ASSIGN()) { result = compound_condition_or; } @@ -396,9 +404,18 @@ export default class ArcscriptVisitor extends ArcscriptParserVisitor { .multipliedBy(new BigNumber(signed_unary_numeric_expression)) .toNumber(); } - // else DIV + if (ctx.DIV()) { + const result = new BigNumber(multiplicative_numeric_expression) + .dividedBy(new BigNumber(signed_unary_numeric_expression)) + .toNumber(); + if (!Number.isFinite(result)) { + throw new RuntimeError(`Invalid division by zero`); + } + return result; + } + // else MOD const result = new BigNumber(multiplicative_numeric_expression) - .dividedBy(new BigNumber(signed_unary_numeric_expression)) + .modulo(new BigNumber(signed_unary_numeric_expression)) .toNumber(); if (!Number.isFinite(result)) { throw new RuntimeError(`Invalid division by zero`); diff --git a/grammar/ArcscriptLexer.g4 b/grammar/ArcscriptLexer.g4 index 3b97474..b4d1ac8 100644 --- a/grammar/ArcscriptLexer.g4 +++ b/grammar/ArcscriptLexer.g4 @@ -37,11 +37,13 @@ ASSIGNMUL: '*='; ASSIGNDIV: '/='; ASSIGNADD: '+='; ASSIGNSUB: '-='; +ASSIGNMOD: '%='; MUL: '*'; DIV: '/'; ADD: '+'; SUB: '-'; +MOD: '%'; GE: GT '='; GT: '>' | '>'; diff --git a/grammar/ArcscriptParser.g4 b/grammar/ArcscriptParser.g4 index 957e352..6b8a569 100644 --- a/grammar/ArcscriptParser.g4 +++ b/grammar/ArcscriptParser.g4 @@ -63,6 +63,7 @@ statement_assignment: | ASSIGNSUB | ASSIGNMUL | ASSIGNDIV + | ASSIGNMOD | ASSIGN ) compound_condition_or {this.assertVariable($VARIABLE);}; @@ -84,7 +85,7 @@ additive_numeric_expression: multiplicative_numeric_expression: signed_unary_numeric_expression - | multiplicative_numeric_expression (MUL | DIV) signed_unary_numeric_expression; + | multiplicative_numeric_expression (MUL | DIV | MOD) signed_unary_numeric_expression; signed_unary_numeric_expression: sign unary_numeric_expression