cue: fix number parsing

- allow 0. for floats
- allow old-school octal numbers (just too common)
- fix productions in spec.

Change-Id: Ifaf9bbc8f221c637875a26ab02d379865d4af151
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/2164
Reviewed-by: Marcel van Lohuizen <mpvl@google.com>
diff --git a/cue/lit.go b/cue/lit.go
index 8e5b3e0..0d8e4f6 100644
--- a/cue/lit.go
+++ b/cue/lit.go
@@ -131,8 +131,10 @@
 	p.ch = p.src[p.p]
 	p.p++
 	if p.ch == '.' {
+		if len(p.buf) == 0 {
+			p.buf = append(p.buf, '0')
+		}
 		p.buf = append(p.buf, '.')
-
 	}
 	return true
 }
@@ -229,7 +231,6 @@
 }
 
 func (p *litParser) scanNumber(seenDecimalPoint bool) value {
-	// digitVal(s.ch) < 10
 	isFloat := false
 	base := 10
 
@@ -242,7 +243,8 @@
 	if p.ch == '0' {
 		// int or float
 		p.next()
-		if p.ch == 'x' || p.ch == 'X' {
+		switch p.ch {
+		case 'x', 'X':
 			base = 16
 			// hexadecimal int
 			p.next()
@@ -251,7 +253,7 @@
 				// only scanned "0x" or "0X"
 				return p.error(p.node, "illegal hexadecimal number %q", p.src)
 			}
-		} else if p.ch == 'b' {
+		case 'b':
 			base = 2
 			// binary int
 			p.next()
@@ -260,7 +262,7 @@
 				// only scanned "0b"
 				return p.error(p.node, "illegal binary number %q", p.src)
 			}
-		} else if p.ch == 'o' {
+		case 'o':
 			base = 8
 			// octal int
 			p.next()
@@ -269,12 +271,19 @@
 				// only scanned "0o"
 				return p.error(p.node, "illegal octal number %q", p.src)
 			}
-		} else {
-			// int or float
-			p.scanMantissa(10)
-			if p.ch == '.' || p.ch == 'e' {
+		default:
+			// int (base 8 or 10) or float
+			p.scanMantissa(8)
+			if p.ch == '.' || p.ch == 'e' || p.ch == '8' || p.ch == '9' {
+				p.scanMantissa(10)
+				if p.ch != '.' && p.ch != 'e' {
+					return p.error(p.node, "illegal octal number %q", p.src)
+				}
 				goto fraction
 			}
+			if len(p.buf) > 0 {
+				base = 8
+			}
 		}
 		goto exit
 	}
diff --git a/cue/lit_test.go b/cue/lit_test.go
index 6ad6905..b98c892 100644
--- a/cue/lit_test.go
+++ b/cue/lit_test.go
@@ -121,7 +121,12 @@
 		{"1.", mkFloat("1")},
 		{"0.0", mkFloat("0.0")},
 		{".0", mkFloat(".0")},
+		{"012.34", mkFloat("012.34")},
+		{".01", mkFloat(".01")},
+		{".01e2", mkFloat("1")},
+		{"0.", mkFloat("0.")},
 		{"1K", mkMul(1000, mulK, 10)},
+		{".5K", mkMul(500, mulK, 10)},
 		{"1Mi", mkMul(1024*1024, mulMi, 10)},
 		{"1.5Mi", mkMul((1024+512)*1024, mulMi, 10)},
 		{"1.3Mi", &bottom{}}, // Cannot be accurately represented.
@@ -134,6 +139,7 @@
 		{"0b11001000", mkMul(0xc8, 0, 2)},
 		{"0b1", mkMul(1, 0, 2)},
 		{"0o755", mkMul(0755, 0, 8)},
+		{"0755", mkMul(0755, 0, 8)},
 	}
 	p := litParser{
 		ctx: &context{Context: &apd.BaseContext},
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 5263f29..0e3439b 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -293,14 +293,20 @@
 towards zero if it is not an integer.
 
 ```
-int_lit     = decimal_lit | octal_lit | binary_lit | hex_lit .
-decimals  = ( "0" … "9" ) { [ "_" ] decimal_digit } .
-decimal_lit = ( "1" … "9" ) { [ "_" ] decimal_digit } [ [ "." decimals ] multiplier ] |
-            "." decimals multiplier.
+int_lit     = decimal_lit | si_lit | octal_lit | binary_lit | hex_lit .
+decimal_lit = ( "1" … "9" ) { [ "_" ] decimal_digit } .
+decimals    = decimal_digit { [ "_" ] decimal_digit } .
+si_it       = decimals [ "." decimals ] multiplier |
+              "." decimals  multiplier .
 binary_lit  = "0b" binary_digit { binary_digit } .
 hex_lit     = "0" ( "x" | "X" ) hex_digit { [ "_" ] hex_digit } .
-octal_lit   = "0o" octal_digit { [ "_" ] octal_digit } .
+octal_lit   = "0" [ "o" ] octal_digit { [ "_" ] octal_digit } .
 multiplier  = ( "K" | "M" | "G" | "T" | "P" | "E" | "Y" | "Z" ) [ "i" ]
+
+float_lit   = decimals "." [ decimals ] [ exponent ] |
+              decimals exponent |
+              "." decimals [ exponent ].
+exponent  = ( "e" | "E" ) [ "+" | "-" ] decimals .
 ```
 
 ```