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"]]
 	...
 }
 ...