Skip to content

Commit aae39d1

Browse files
committed
Add compound assignment operators (#17)
1 parent 54e44a2 commit aae39d1

5 files changed

Lines changed: 183 additions & 1 deletion

File tree

lexer/lexer.go

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ const (
2626

2727
// Operators.
2828
ASSIGN_OPERATOR
29+
COMPOUND_ASSIGN_OPERATOR
2930
UNARY_OPERATOR
3031
BINARY_OPERATOR
3132
COMPARE_OPERATOR
@@ -134,6 +135,12 @@ var nonAlphabeticTokens = []tokenMapping{
134135
{"&&", LOGICAL_OPERATOR},
135136
{"||", LOGICAL_OPERATOR},
136137

138+
{"+=", COMPOUND_ASSIGN_OPERATOR},
139+
{"-=", COMPOUND_ASSIGN_OPERATOR},
140+
{"*=", COMPOUND_ASSIGN_OPERATOR},
141+
{"/=", COMPOUND_ASSIGN_OPERATOR},
142+
{"%=", COMPOUND_ASSIGN_OPERATOR},
143+
137144
{"=", ASSIGN_OPERATOR},
138145

139146
{":=", SHORT_INIT_OPERATOR},

parser/parser.go

Lines changed: 76 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1154,6 +1154,79 @@ func (p *Parser) evaluateVarDefinition(ctx context) (Statement, error) {
11541154
return variable, nil
11551155
}
11561156

1157+
func (p *Parser) evaluateCompoundAssignment(ctx context) (Statement, error) {
1158+
nameTokens, err := p.evaluateVarNames()
1159+
1160+
if err != nil {
1161+
return nil, err
1162+
}
1163+
nameToken := nameTokens[0]
1164+
namesLen := len(nameTokens)
1165+
1166+
if namesLen > 1 {
1167+
return nil, p.expectedError("a single variable on the left side", nameToken)
1168+
}
1169+
assignToken := p.eat()
1170+
1171+
// Check assign token.
1172+
if assignToken.Type() != lexer.COMPOUND_ASSIGN_OPERATOR {
1173+
return nil, p.expectedError(`"+=", "-=", "*=", "/=" or "%="`, assignToken)
1174+
}
1175+
valuesToken := p.peek()
1176+
evaluatedVals, err := p.evaluateValues(ctx)
1177+
1178+
if err != nil {
1179+
return nil, err
1180+
}
1181+
isMultiReturnFuncCall, call := evaluatedVals.isMultiReturnFuncCall()
1182+
values := evaluatedVals.values
1183+
valuesTypes := []ValueType{}
1184+
1185+
// If it's a multi return function call evaluate how many values are returned by the function.
1186+
if isMultiReturnFuncCall {
1187+
valuesTypes = call.ReturnTypes()
1188+
} else {
1189+
for _, value := range values {
1190+
valuesTypes = append(valuesTypes, value.ValueType())
1191+
}
1192+
}
1193+
valuesTypesLen := len(valuesTypes)
1194+
1195+
if valuesTypesLen > 1 {
1196+
return nil, p.expectedError("a single value on the right side", valuesToken)
1197+
}
1198+
name := nameToken.Value()
1199+
1200+
// Make sure variable has been defined.
1201+
definedVariable, exists := ctx.findVariable(name, p.prefix, ctx.global())
1202+
1203+
if !exists {
1204+
return nil, p.atError(fmt.Sprintf("variable %s has not been defined", name), nameToken)
1205+
}
1206+
valueType := valuesTypes[0]
1207+
expectedValueType := definedVariable.ValueType()
1208+
1209+
if valueType != expectedValueType {
1210+
return nil, p.expectedError(fmt.Sprintf("%s but got %s", expectedValueType.ToString(), valueType.ToString()), valuesToken)
1211+
}
1212+
assignOperator := assignToken.Value()
1213+
binaryOperator := string(assignOperator[0])
1214+
1215+
if !slices.Contains([]BinaryOperator{BINARY_OPERATOR_ADDITION, BINARY_OPERATOR_SUBTRACTION, BINARY_OPERATOR_MULTIPLICATION, BINARY_OPERATOR_DIVISION, BINARY_OPERATOR_MODULO}, binaryOperator) {
1216+
return nil, p.expectedError(fmt.Sprintf(`valid compound assignment operator but got "%s"`, assignOperator), assignToken)
1217+
}
1218+
return VariableAssignment{
1219+
variables: []Variable{definedVariable},
1220+
values: []Expression{
1221+
BinaryOperation{
1222+
left: VariableEvaluation{definedVariable},
1223+
operator: binaryOperator,
1224+
right: values[0],
1225+
},
1226+
},
1227+
}, nil
1228+
}
1229+
11571230
func (p *Parser) evaluateVarAssignment(ctx context) (Statement, error) {
11581231
nameTokens, err := p.evaluateVarNames()
11591232

@@ -1948,6 +2021,8 @@ func (p *Parser) evaluateStatement(ctx context) (Statement, error) {
19482021
switch p.peekAt(1).Type() {
19492022
case lexer.INCREMENT_OPERATOR, lexer.DECREMENT_OPERATOR:
19502023
stmt, err = p.evaluateIncrementDecrement(ctx)
2024+
case lexer.COMPOUND_ASSIGN_OPERATOR:
2025+
stmt, err = p.evaluateCompoundAssignment(ctx)
19512026
case lexer.ASSIGN_OPERATOR, lexer.COMMA:
19522027
stmt, err = p.evaluateVarAssignment(ctx)
19532028
default:
@@ -2067,7 +2142,7 @@ func (p *Parser) evaluateLogicalOperation(ctx context, operator LogicalOperator,
20672142
if operatorToken.Type() != lexer.LOGICAL_OPERATOR || operatorToken.Value() != operator {
20682143
break
20692144
}
2070-
2145+
20712146
if !leftExpression.ValueType().IsBool() {
20722147
return nil, p.expectedError("boolean value", conditionToken)
20732148
}

tests/binaryoperation.go

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,3 +112,63 @@ func testComplexCalculationSuccess(t *testing.T, transpilerFunc transpilerFunc)
112112
require.Equal(t, strconv.Itoa((145+37*2)-18+64/4*3-(250%23+11)+7*(81-9)/6+999), output)
113113
})
114114
}
115+
116+
func testCompoundAssignmentAdditionSuccess(t *testing.T, transpilerFunc transpilerFunc) {
117+
transpilerFunc(t, `
118+
var a = 2
119+
a += 2
120+
121+
print(a)
122+
`, func(output string, err error) {
123+
require.Nil(t, err)
124+
require.Equal(t, "4", output)
125+
})
126+
}
127+
128+
func testCompoundAssignmentSubtractionSuccess(t *testing.T, transpilerFunc transpilerFunc) {
129+
transpilerFunc(t, `
130+
var a = 2
131+
a -= 2
132+
133+
print(a)
134+
`, func(output string, err error) {
135+
require.Nil(t, err)
136+
require.Equal(t, "0", output)
137+
})
138+
}
139+
140+
func testCompoundAssignmentMultiplicationSuccess(t *testing.T, transpilerFunc transpilerFunc) {
141+
transpilerFunc(t, `
142+
var a = 2
143+
a *= 2
144+
145+
print(a)
146+
`, func(output string, err error) {
147+
require.Nil(t, err)
148+
require.Equal(t, "4", output)
149+
})
150+
}
151+
152+
func testCompoundAssignmentDivisionSuccess(t *testing.T, transpilerFunc transpilerFunc) {
153+
transpilerFunc(t, `
154+
var a = 4
155+
a /= 2
156+
157+
print(a)
158+
`, func(output string, err error) {
159+
require.Nil(t, err)
160+
require.Equal(t, "2", output)
161+
})
162+
}
163+
164+
func testCompoundAssignmentModuloSuccess(t *testing.T, transpilerFunc transpilerFunc) {
165+
transpilerFunc(t, `
166+
var a = 4
167+
a %= 2
168+
169+
print(a)
170+
`, func(output string, err error) {
171+
require.Nil(t, err)
172+
require.Equal(t, "0", output)
173+
})
174+
}

tests/binaryoperation_linux_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,23 @@ func TestMoreComplexCalculationWithBracketsSuccess(t *testing.T) {
3535
func TestComplexCalculationSuccess(t *testing.T) {
3636
testComplexCalculationSuccess(t, transpileBash)
3737
}
38+
39+
func TestCompoundAssignmentAdditionSuccess(t *testing.T) {
40+
testCompoundAssignmentAdditionSuccess(t, transpileBash)
41+
}
42+
43+
func TestCompoundAssignmentSubtractionSuccess(t *testing.T) {
44+
testCompoundAssignmentSubtractionSuccess(t, transpileBash)
45+
}
46+
47+
func TestCompoundAssignmentMultiplicationSuccess(t *testing.T) {
48+
testCompoundAssignmentMultiplicationSuccess(t, transpileBash)
49+
}
50+
51+
func TestCompoundAssignmentDivisionSuccess(t *testing.T) {
52+
testCompoundAssignmentDivisionSuccess(t, transpileBash)
53+
}
54+
55+
func TestCompoundAssignmentModuloSuccess(t *testing.T) {
56+
testCompoundAssignmentModuloSuccess(t, transpileBash)
57+
}

tests/binaryoperation_windows_test.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,23 @@ func TestMoreComplexCalculationWithBracketsSuccess(t *testing.T) {
3535
func TestComplexCalculationSuccess(t *testing.T) {
3636
testComplexCalculationSuccess(t, transpileBatch)
3737
}
38+
39+
func TestCompoundAssignmentAdditionSuccess(t *testing.T) {
40+
testCompoundAssignmentAdditionSuccess(t, transpileBatch)
41+
}
42+
43+
func TestCompoundAssignmentSubtractionSuccess(t *testing.T) {
44+
testCompoundAssignmentSubtractionSuccess(t, transpileBatch)
45+
}
46+
47+
func TestCompoundAssignmentMultiplicationSuccess(t *testing.T) {
48+
testCompoundAssignmentMultiplicationSuccess(t, transpileBatch)
49+
}
50+
51+
func TestCompoundAssignmentDivisionSuccess(t *testing.T) {
52+
testCompoundAssignmentDivisionSuccess(t, transpileBatch)
53+
}
54+
55+
func TestCompoundAssignmentModuloSuccess(t *testing.T) {
56+
testCompoundAssignmentModuloSuccess(t, transpileBatch)
57+
}

0 commit comments

Comments
 (0)