cue: use 0o notation for octal numbers
instead of Go style octals
Fixes cuelang/cue#4.
Also fixes bug where `0` is interpreted as octal, and
thus only has type int and not float as well
Change-Id: I038bfdd02cecd2c78c8151530d9b8ef2b16e9a8c
diff --git a/cue/lit.go b/cue/lit.go
index 21f1829..7632f9a 100644
--- a/cue/lit.go
+++ b/cue/lit.go
@@ -557,33 +557,30 @@
// only scanned "0x" or "0X"
return p.error(p.node, "illegal hexadecimal number %q", p.src)
}
- } else if p.ch == 'b' || p.ch == 'B' {
+ } else if p.ch == 'b' {
base = 2
// binary int
p.next()
p.scanMantissa(2)
if p.p <= 2 {
- // only scanned "0b" or "0B"
+ // only scanned "0b"
return p.error(p.node, "illegal binary number %q", p.src)
}
- } else {
+ } else if p.ch == 'o' {
base = 8
- // octal int or float
- seenDecimalDigit := false
+ // octal int
+ p.next()
p.scanMantissa(8)
- if p.ch == '8' || p.ch == '9' {
- // illegal octal int or float
- seenDecimalDigit = true
- p.scanMantissa(10)
+ if p.p <= 2 {
+ // only scanned "0o"
+ return p.error(p.node, "illegal octal number %q", p.src)
}
- // TODO: disallow complex
+ } else {
+ // int or float
+ p.scanMantissa(10)
if p.ch == '.' || p.ch == 'e' {
goto fraction
}
- // octal int
- if seenDecimalDigit {
- return p.error(p.node, "illegal octal number %q", p.src)
- }
}
goto exit
}
diff --git a/cue/lit_test.go b/cue/lit_test.go
index 4ed82fc..6287bfa 100644
--- a/cue/lit_test.go
+++ b/cue/lit_test.go
@@ -126,6 +126,7 @@
lit string
node value
}{
+ {"0", mkInt(0)},
{"null", nullSentinel},
{"true", trueSentinel},
{"false", falseSentinel},
@@ -153,6 +154,7 @@
{"1", mkInt(1)},
{"100_000", hk},
{"1.", mkFloat("1")},
+ {"0.0", mkFloat("0.0")},
{".0", mkFloat(".0")},
{"1K", mkMul(1000, mulK, 10)},
{"1Mi", mkMul(1024*1024, mulMi, 10)},
@@ -166,7 +168,7 @@
{"0xABCD", mkMul(0xABCD, 0, 16)},
{"0b11001000", mkMul(0xc8, 0, 2)},
{"0b1", mkMul(1, 0, 2)},
- {"0755", mkMul(0755, 0, 8)},
+ {"0o755", mkMul(0755, 0, 8)},
}
p := litParser{
ctx: &context{Context: &apd.BaseContext},
@@ -195,7 +197,7 @@
// not allowed in string literal, only binary
{`"foo\x00"`},
{`0x`},
- {`09`},
+ {`0o`},
{`0_`},
{``},
{`"`},
diff --git a/cue/scanner/scanner.go b/cue/scanner/scanner.go
index 89493e5..f5f0881 100644
--- a/cue/scanner/scanner.go
+++ b/cue/scanner/scanner.go
@@ -324,30 +324,35 @@
// only scanned "0x" or "0X"
s.error(offs, "illegal hexadecimal number")
}
- } else if s.ch == 'b' || s.ch == 'B' {
+ } else if s.ch == 'b' {
// binary int
s.next()
s.scanMantissa(2)
if s.offset-offs <= 2 {
- // only scanned "0b" or "0B"
+ // only scanned "0b"
s.error(offs, "illegal binary number")
}
- } else {
- // octal int or float
- seenDecimalDigit := false
+ } else if s.ch == 'o' {
+ // octal int
+ s.next()
s.scanMantissa(8)
- if s.ch == '8' || s.ch == '9' {
- // illegal octal int or float
- seenDecimalDigit = true
+ if s.offset-offs <= 2 {
+ // only scanned "0o"
+ s.error(offs, "illegal octal number")
+ }
+ } else {
+ // 0 or float
+ seenDigits := false
+ if s.ch >= '0' && s.ch <= '9' {
+ seenDigits = true
s.scanMantissa(10)
}
- // TODO: disallow complex.
if s.ch == '.' || s.ch == 'e' {
goto fraction
}
- // octal int
- if seenDecimalDigit {
- s.error(offs, "illegal octal number")
+ if seenDigits {
+ // integer other than 0 may not start with 0
+ s.error(offs, "illegal integer number")
}
}
goto exit
diff --git a/cue/scanner/scanner_test.go b/cue/scanner/scanner_test.go
index 2faf296..45e73a5 100644
--- a/cue/scanner/scanner_test.go
+++ b/cue/scanner/scanner_test.go
@@ -79,11 +79,12 @@
{token.INT, "12345_67890_12345_6788_90", literal},
{token.INT, "1234567M", literal},
{token.INT, "1234567Mi", literal},
- {token.INT, "01234567", literal},
+ {token.INT, "1234567", literal},
{token.INT, ".3Mi", literal},
{token.INT, "3.3Mi", literal},
{token.INT, "0xcafebabe", literal},
{token.INT, "0b1100_1001", literal},
+ {token.INT, "0o1234567", literal},
{token.FLOAT, "0.", literal},
{token.FLOAT, ".0", literal},
{token.FLOAT, "3.14159265", literal},
@@ -758,17 +759,18 @@
{"'", token.STRING, 0, "'", "string literal not terminated"},
{"/**/", token.COMMENT, 0, "/**/", ""},
{"/*", token.COMMENT, 0, "/*", "comment not terminated"},
- {"077", token.INT, 0, "077", ""},
+ {"0", token.INT, 0, "0", ""},
+ {"077", token.INT, 0, "077", "illegal integer number"},
{"078.", token.FLOAT, 0, "078.", ""},
{"07801234567.", token.FLOAT, 0, "07801234567.", ""},
{"078e0", token.FLOAT, 0, "078e0", ""},
- {"078", token.INT, 0, "078", "illegal octal number"},
- {"07800000009", token.INT, 0, "07800000009", "illegal octal number"},
+ {"078", token.INT, 0, "078", "illegal integer number"},
+ {"07800000009", token.INT, 0, "07800000009", "illegal integer number"},
{"0x", token.INT, 0, "0x", "illegal hexadecimal number"},
{"0X", token.INT, 0, "0X", "illegal hexadecimal number"},
{"0Xbeef_", token.INT, 6, "0Xbeef_", "illegal '_' in number"},
{"0b", token.INT, 0, "0b", "illegal binary number"},
- {"0B", token.INT, 0, "0B", "illegal binary number"},
+ {"0o", token.INT, 0, "0o", "illegal octal number"},
// {"123456789012345678890_i", IMAG, 21, "123456789012345678890_i", "illegal '_' in number"},
{"\"abc\x00def\"", token.STRING, 4, "\"abc\x00def\"", "illegal character NUL"},
{"\"abc\x80def\"", token.STRING, 4, "\"abc\x80def\"", "illegal UTF-8 encoding"},
diff --git a/cue/types_test.go b/cue/types_test.go
index 7eeae0b..fca29ae 100644
--- a/cue/types_test.go
+++ b/cue/types_test.go
@@ -925,7 +925,7 @@
}, {
config: config,
path: strList("b", "d", "lookup in non-struct"),
- str: "not of right kind (int vs struct)",
+ str: "not of right kind (number vs struct)",
}}
for _, tc := range testCases {
t.Run(tc.str, func(t *testing.T) {
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index c0ea20a..8fba6dd 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -298,20 +298,17 @@
"." 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 } .
multiplier = ( "K" | "M" | "G" | "T" | "P" | "E" | "Y" | "Z" ) [ "i" ]
```
-<!--
-octal_lit = "0" octal_digit { [ "_" ] octal_digit } .
-TODO: consider 0o766 notation for octal.
---->
-
```
42
1.5Gi
-0600
-0xBad_Face
170_141_183_460_469_231_731_687_303_715_884_105_727
+0xBad_Face
+0o755
+0b0101_0001
```
### Decimal floating-point literals
diff --git a/internal/third_party/yaml/decode.go b/internal/third_party/yaml/decode.go
index 5a970d2..bc42da9 100644
--- a/internal/third_party/yaml/decode.go
+++ b/internal/third_party/yaml/decode.go
@@ -456,7 +456,13 @@
return d.ident(n, str)
case yaml_INT_TAG:
- return d.makeNum(n, n.value, token.INT)
+ // Convert YAML octal to CUE octal. If YAML accepted an invalid
+ // integer, just convert it as well to ensure CUE will fail.
+ s := n.value
+ if len(s) > 1 && s[0] == '0' && s[1] <= '9' {
+ s = "0o" + s[1:]
+ }
+ return d.makeNum(n, s, token.INT)
case yaml_FLOAT_TAG:
value := n.value
diff --git a/internal/third_party/yaml/decode_test.go b/internal/third_party/yaml/decode_test.go
index be4d034..2e2122d 100644
--- a/internal/third_party/yaml/decode_test.go
+++ b/internal/third_party/yaml/decode_test.go
@@ -122,7 +122,7 @@
"decimal: +685_230",
}, {
"octal: 02472256",
- "octal: 02472256",
+ "octal: 0o2472256",
}, {
"hexa: 0x_0A_74_AE",
"hexa: 0x_0A_74_AE",