diff --git a/compiler/lexer/lexer.go b/compiler/lexer/lexer.go index a41d96133..99df9eb23 100644 --- a/compiler/lexer/lexer.go +++ b/compiler/lexer/lexer.go @@ -264,12 +264,29 @@ func (l *Lexer) resetNosymbol() { } +// prevent double underscores and underscore at the end such as 1__2, 3_ +func (l *Lexer) checkNumberError() bool { + + return l.ch == '_' && (l.readPosition >= len(l.input) || !isDigit(l.input[l.readPosition])) + +} + func (l *Lexer) readNumber() []rune { - position := l.position - for isDigit(l.ch) { + var input []rune + + for isDigit(l.ch) || l.ch == '_' { + + if isDigit(l.ch) { + input = append(input, l.ch) + } + + if l.checkNumberError() { + return input + } + l.readChar() } - return l.input[position:l.position] + return input } func (l *Lexer) readIdentifier() []rune { diff --git a/compiler/lexer/lexer_test.go b/compiler/lexer/lexer_test.go index 1864193e2..f9709c83a 100644 --- a/compiler/lexer/lexer_test.go +++ b/compiler/lexer/lexer_test.go @@ -125,6 +125,8 @@ func TestNextToken(t *testing.T) { '\"string\"' "\'string\'" '\'string\'' + 1_23 + 12_3.45_6 ` tests := []struct { @@ -438,8 +440,11 @@ func TestNextToken(t *testing.T) { {token.String, "\\\"string\\\"", 116}, {token.String, "'string'", 117}, {token.String, "'string'", 118}, - - {token.EOF, "", 119}, + {token.Int, "123", 119}, + {token.Int, "123", 120}, + {token.Dot, ".", 120}, + {token.Int, "456", 120}, + {token.EOF, "", 121}, } l := New(input) diff --git a/vm/float_test.go b/vm/float_test.go index 27f4423b5..68878692c 100644 --- a/vm/float_test.go +++ b/vm/float_test.go @@ -266,3 +266,38 @@ func TestFloatZeroDivisionFail(t *testing.T) { v.checkSP(t, i, 1) } } + +func TestFloatWithWrongUnderscore(t *testing.T) { + testsFail := []errorTestCase{ + {`1._2`, "UndefinedMethodError: Undefined Method '_2' for 1", 1}, + {`1.2_`, "UndefinedMethodError: Undefined Method '_' for ", 1}, + } + for i, tt := range testsFail { + v := initTestVM() + evaluated := v.testEval(t, tt.input, getFilename()) + checkErrorMsg(t, i, evaluated, tt.expected) + v.checkCFP(t, i, tt.expectedCFP) + v.checkSP(t, i, 1) + } +} + +func TestFloatWithCorrectUnderscore(t *testing.T) { + + tests := []struct { + input string + expected interface{} + }{ + {`1_2.34`, 12.34}, + {`12.3_4`, 12.34}, + {`1_2.3_4`, 12.34}, + {`1_2_3.4_5_6`, 123.456}, + } + + for i, tt := range tests { + v := initTestVM() + evaluated := v.testEval(t, tt.input, getFilename()) + verifyExpected(t, i, evaluated, tt.expected) + v.checkCFP(t, i, 0) + v.checkSP(t, i, 1) + } +} diff --git a/vm/integer_test.go b/vm/integer_test.go index e8dfe1c34..b2e4a30bd 100644 --- a/vm/integer_test.go +++ b/vm/integer_test.go @@ -390,3 +390,41 @@ func TestIntegerZeroDivisionFail(t *testing.T) { v.checkSP(t, i, 1) } } + +func TestIntegerWithWrongUnderscore(t *testing.T) { + testsFail := []errorTestCase{ + {`1_`, "UndefinedMethodError: Undefined Method '_' for ", 1}, + {`1__2`, "UndefinedMethodError: Undefined Method '__2' for ", 1}, + {`_1`, "UndefinedMethodError: Undefined Method '_1' for ", 1}, + } + for i, tt := range testsFail { + v := initTestVM() + evaluated := v.testEval(t, tt.input, getFilename()) + checkErrorMsg(t, i, evaluated, tt.expected) + v.checkCFP(t, i, tt.expectedCFP) + v.checkSP(t, i, 1) + } + +} + +func TestIntegerWithCorrectUnderscore(t *testing.T) { + + tests := []struct { + input string + expected interface{} + }{ + {`1_234`, 1234}, + {`1_2_34`, 1234}, + {`1_23_4`, 1234}, + {`1_2_3_4`, 1234}, + } + + for i, tt := range tests { + v := initTestVM() + evaluated := v.testEval(t, tt.input, getFilename()) + verifyExpected(t, i, evaluated, tt.expected) + v.checkCFP(t, i, 0) + v.checkSP(t, i, 1) + } + +}