cue: allow # and disallow #0 as identifiers
At a later point, we will probably rewrite `#: "x": y` to `#("x"): y`. This
change will make that easier. At that time, we may prohibit # again.
We also disallow #<num> for now. We may want to assign special meaning to that later, so we will reserve them for now.
See https://github.com/cuelang/cue/discussions/388
Change-Id: I9095df572db1da34525267b06c7ed0b021de1ca9
Reviewed-on: https://cue-review.googlesource.com/c/cue/+/6101
Reviewed-by: Marcel van Lohuizen <mpvl@golang.org>
diff --git a/cue/ast/ident.go b/cue/ast/ident.go
index 35f1ca3..86b50ed 100644
--- a/cue/ast/ident.go
+++ b/cue/ast/ident.go
@@ -39,27 +39,25 @@
return false
}
- consumed := false
+ // TODO: use consumed again to allow #0.
+ // consumed := false
if strings.HasPrefix(ident, "_") {
ident = ident[1:]
- consumed = true
+ // consumed = true
if len(ident) == 0 {
return true
}
}
if strings.HasPrefix(ident, "#") {
ident = ident[1:]
- consumed = true
- if len(ident) == 0 {
- return false
- }
+ // consumed = true
}
- if !consumed {
- if r, _ := utf8.DecodeRuneInString(ident); isDigit(r) {
- return false
- }
+ // if !consumed {
+ if r, _ := utf8.DecodeRuneInString(ident); isDigit(r) {
+ return false
}
+ // }
for _, r := range ident {
if isLetter(r) || isDigit(r) || r == '_' || r == '$' {
@@ -133,13 +131,13 @@
}
if strings.HasPrefix(ident[p:], "#") {
p++
- if len(ident) == p {
- return "", errors.Newf(pos, "invalid identifier '_#'")
- }
+ // if len(ident) == p {
+ // return "", errors.Newf(pos, "invalid identifier '_#'")
+ // }
}
- if p == 0 {
- if r, _ := utf8.DecodeRuneInString(ident); isDigit(r) {
+ if p == 0 || ident[p-1] == '#' {
+ if r, _ := utf8.DecodeRuneInString(ident[p:]); isDigit(r) {
return "", errors.Newf(pos, "invalid character '%s' in identifier", string(r))
}
}
diff --git a/cue/ast/ident_test.go b/cue/ast/ident_test.go
index 3bbef90..b137dc7 100644
--- a/cue/ast/ident_test.go
+++ b/cue/ast/ident_test.go
@@ -56,14 +56,18 @@
err: true,
}, {
in: &ast.Ident{Name: "#"},
+ out: "#",
+ isIdent: true,
+ }, {
+ in: &ast.Ident{Name: "#0"},
out: "",
isIdent: false,
err: true,
}, {
in: &ast.Ident{Name: "_#"},
- out: "",
- isIdent: false,
- err: true,
+ out: "_#",
+ isIdent: true,
+ err: false,
}, {
in: &ast.Ident{Name: "_"},
out: "_",
diff --git a/cue/scanner/scanner.go b/cue/scanner/scanner.go
index c988256..5210b4f 100644
--- a/cue/scanner/scanner.go
+++ b/cue/scanner/scanner.go
@@ -273,6 +273,10 @@
}
if s.ch == '#' {
s.next()
+ // TODO: remove this block to allow #<num>
+ if isDigit(s.ch) {
+ return string(s.src[offs:s.offset])
+ }
}
for isLetter(s.ch) || isDigit(s.ch) || s.ch == '_' || s.ch == '$' {
s.next()
@@ -778,7 +782,8 @@
tok = token.Lookup(lit)
insertEOL = true
break
- } else if ch != '#' {
+ }
+ if ch != '#' || (s.ch != '\'' && s.ch != '"' && s.ch != '#') {
tok = token.IDENT
insertEOL = true
break
diff --git a/cue/scanner/scanner_test.go b/cue/scanner/scanner_test.go
index 123962e..d12f751 100644
--- a/cue/scanner/scanner_test.go
+++ b/cue/scanner/scanner_test.go
@@ -77,7 +77,8 @@
{token.IDENT, "foobar", literal},
{token.IDENT, "$foobar", literal},
{token.IDENT, "#foobar", literal},
- {token.IDENT, "#0", literal},
+ // {token.IDENT, "#0", literal},
+ {token.IDENT, "#", literal},
{token.IDENT, "_foobar", literal},
{token.IDENT, "__foobar", literal},
{token.IDENT, "#_foobar", literal},
diff --git a/doc/ref/spec.md b/doc/ref/spec.md
index 122b133..851714e 100644
--- a/doc/ref/spec.md
+++ b/doc/ref/spec.md
@@ -179,8 +179,9 @@
Identifiers name entities such as fields and aliases.
An identifier is a sequence of one or more letters (which includes `_` and `$`)
and digits, optionally preceded by `#` or `_#`.
-It may not be `_`, `$`, `#`, or `_#`.
-The first character in an identifier must be a letter or `#`.
+It may not be `_` or `$`.
+The first character in an identifier, or after an `#` if it contains one,
+must be a letter.
Identifiers starting with a `#` or `_` are reserved for definitions and hidden
fields.
diff --git a/encoding/jsonschema/decode.go b/encoding/jsonschema/decode.go
index 70d4338..f1c7f58 100644
--- a/encoding/jsonschema/decode.go
+++ b/encoding/jsonschema/decode.go
@@ -35,7 +35,7 @@
// have a valid identifier name.
//
// TODO: find something more principled, like allowing #."a-b" or `#a-b`.
-const rootDefs = "#def"
+const rootDefs = "#"
// A decoder converts JSON schema to CUE.
type decoder struct {
diff --git a/encoding/jsonschema/jsonschema.go b/encoding/jsonschema/jsonschema.go
index 398cbbf..00027c6 100644
--- a/encoding/jsonschema/jsonschema.go
+++ b/encoding/jsonschema/jsonschema.go
@@ -74,8 +74,8 @@
//
// The default mapping is
// {} {}
- // {"definitions", foo} {#foo} or {#def, foo}
- // {"$defs", foo} {#foo} or {#def, foo}
+ // {"definitions", foo} {#foo} or {#, foo}
+ // {"$defs", foo} {#foo} or {#, foo}
Map func(pos token.Pos, path []string) ([]ast.Label, error)
// TODO: configurability to make it compatible with OpenAPI, such as
diff --git a/encoding/jsonschema/testdata/def.txtar b/encoding/jsonschema/testdata/def.txtar
index 064c4e7..9b47661 100644
--- a/encoding/jsonschema/testdata/def.txtar
+++ b/encoding/jsonschema/testdata/def.txtar
@@ -40,7 +40,7 @@
-- out.cue --
@jsonschema(schema="http://json-schema.org/draft-07/schema#",id="http://cuelang.org/go/encoding/openapi/testdata/order.json")
-person?: #def["per-son"]
+person?: #["per-son"]
billing_address?: #address
shipping_address?: #address
@@ -51,9 +51,9 @@
...
}
-#def: "per-son": {
+#: "per-son": {
name?: string
- children?: [...#def["per-son"]]
+ children?: [...#["per-son"]]
...
}
...