Repository: jensneuse/graphql-go-tools (go)

Issue:
Lexer missing scientific notation float support - exponent part
http://spec.graphql.org/draft/#sec-Float-Value

In order to fully implement the spec, and be useful for particularly large or small values, we should be able to handle floats expressed in scientific notation:

We should support exponents expressed in both lower and upper case `e` or `E`.

I think the below tests cover all / most of the cases we need to support.

```
t.Run("read plancks constant", func(t *testing.T) {
	run("6.63E-34", mustRead(keyword.FLOAT, "6.63E-34"))
})
t.Run("read electron mass kg", func(t *testing.T) {
	run("9.10938356e-3", mustRead(keyword.FLOAT, "9.10938356e-3"))
})
t.Run("read earth mass kg", func(t *testing.T) {
	run("5.9724e24", mustRead(keyword.FLOAT, "5.9724e24"))
})
t.Run("read earth circumference m", func(t *testing.T) {
	run("4E7", mustRead(keyword.FLOAT, "4E7"))
})
t.Run("read an inch in mm", func(t *testing.T) {
	run("2.54E+1", mustRead(keyword.FLOAT, "2.54E+1"))
})
t.Run("read electron charge/mass ratio", func(t *testing.T) {
	run("-1.758E11", mustRead(keyword.FLOAT, "-1.758E11"))
})
```


Pull request description:
Feature/lexer add scientific exponential notation
This PR will implement parsing numbers with exponential notations correctly.
This PR will close #160 
Thanks @asoorm for mentioning the missing implementation.

Patch:
diff --git a/pkg/lexer/lexer.go b/pkg/lexer/lexer.go
index 157aeced..240dd08f 100644
--- a/pkg/lexer/lexer.go
+++ b/pkg/lexer/lexer.go
@@ -208,11 +208,12 @@ func (l *Lexer) readDigit(tok *token.Token) {
 		l.readRune()
 	}
 
-	isFloat := r == runes.DOT
+	hasExponent := r == runes.EXPONENT_LOWER || r == runes.EXPONENT_UPPER
+	isFloat := r == runes.DOT || hasExponent
 
 	if isFloat {
-		l.swallowAmount(1)
-		l.readFloat(tok)
+		l.readRune()
+		l.readFloat(hasExponent,tok)
 		return
 	}
 
@@ -220,7 +221,7 @@ func (l *Lexer) readDigit(tok *token.Token) {
 	tok.SetEnd(l.input.InputPosition, l.input.TextPosition)
 }
 
-func (l *Lexer) readFloat(tok *token.Token) {
+func (l *Lexer) readFloat(hasReadExponentAlready bool,tok *token.Token) {
 
 	var r byte
 	for {
@@ -231,7 +232,33 @@ func (l *Lexer) readFloat(tok *token.Token) {
 		l.readRune()
 	}
 
-	tok.Keyword = keyword.FLOAT
+	if hasReadExponentAlready {
+		float := keyword.FLOAT
+		tok.Keyword = float
+		tok.SetEnd(l.input.InputPosition, l.input.TextPosition)
+		return
+	}
+
+	optionalExponent := l.peekRune(false)
+	if optionalExponent  == runes.EXPONENT_LOWER || optionalExponent == runes.EXPONENT_UPPER {
+		l.readRune()
+	}
+
+	optionalPlusMinus := l.peekRune(false)
+	if optionalPlusMinus == runes.SUB || optionalPlusMinus == runes.ADD {
+		l.readRune()
+	}
+
+	for {
+		r = l.peekRune(false)
+		if !runeIsDigit(r) {
+			break
+		}
+		l.readRune()
+	}
+
+	float := keyword.FLOAT
+	tok.Keyword = float
 	tok.SetEnd(l.input.InputPosition, l.input.TextPosition)
 }
 
diff --git a/pkg/lexer/runes/runes.go b/pkg/lexer/runes/runes.go
index 54ac745a..af1fe2fe 100644
--- a/pkg/lexer/runes/runes.go
+++ b/pkg/lexer/runes/runes.go
@@ -14,12 +14,15 @@ const (
 	QUOTE          = '"'
 	BACKSLASH      = '\\'
 	DOT            = '.'
+	EXPONENT_LOWER = 'e'
+	EXPONENT_UPPER = 'E'
 	AT             = '@'
 	DOLLAR         = '$'
 	PIPE           = '|'
 	SLASH          = '/'
 	EQUALS         = '='
 	SUB            = '-'
+	ADD            = '+'
 	AND            = '&'
 	UNDERSCORE     = '_'
 


Test patch:
diff --git a/pkg/lexer/lexer_test.go b/pkg/lexer/lexer_test.go
index d37e056a..713e6b8d 100644
--- a/pkg/lexer/lexer_test.go
+++ b/pkg/lexer/lexer_test.go
@@ -153,6 +153,25 @@ func TestLexer_Peek_Read(t *testing.T) {
 	t.Run("fail reading incomplete float", func(t *testing.T) {
 		run("13.", mustRead(keyword.FLOAT, "13."))
 	})
+	t.Run("read plancks constant", func(t *testing.T) {
+		run("6.63E-34", mustRead(keyword.FLOAT, "6.63E-34"))
+	})
+	t.Run("read electron mass kg", func(t *testing.T) {
+		run("9.10938356e-3", mustRead(keyword.FLOAT, "9.10938356e-3"))
+	})
+	t.Run("read earth mass kg", func(t *testing.T) {
+		run("5.9724e24", mustRead(keyword.FLOAT, "5.9724e24"))
+	})
+	t.Run("read earth circumference m", func(t *testing.T) {
+		run("4E7", mustRead(keyword.FLOAT, "4E7"))
+	})
+	t.Run("read an inch in mm", func(t *testing.T) {
+		run("2.54E+1", mustRead(keyword.FLOAT, "2.54E+1"))
+	})
+	t.Run("read electron charge/mass ratio", func(t *testing.T) {
+		run("-1.758E11",mustRead(keyword.SUB, "-"),
+			mustRead(keyword.FLOAT, "1.758E11"))
+	})
 	t.Run("read single line string", func(t *testing.T) {
 		run("\"foo\"", mustRead(keyword.STRING, "foo"))
 	})