Allow underscores in numeric constants
diff --git a/go.mod b/go.mod
index d3f2fc8..bfed505 100644
--- a/go.mod
+++ b/go.mod
@@ -1,6 +1,6 @@
 module github.com/google/wuffs
 
-go 1.12
+go 1.13
 
 require (
 	github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
diff --git a/lang/token/token.go b/lang/token/token.go
index c1e6ced..b46e4dc 100644
--- a/lang/token/token.go
+++ b/lang/token/token.go
@@ -89,14 +89,18 @@
 	return ('A' <= c && c <= 'Z') || ('a' <= c && c <= 'z') || (c == '_') || ('0' <= c && c <= '9')
 }
 
-func hexaNumeric(c byte) bool {
-	return ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f') || ('0' <= c && c <= '9')
+func hexaNumericUnderscore(c byte) bool {
+	return ('A' <= c && c <= 'F') || ('a' <= c && c <= 'f') || (c == '_') || ('0' <= c && c <= '9')
 }
 
 func numeric(c byte) bool {
 	return ('0' <= c && c <= '9')
 }
 
+func numericUnderscore(c byte) bool {
+	return (c == '_') || ('0' <= c && c <= '9')
+}
+
 func hasPrefix(a []byte, s string) bool {
 	if len(s) == 0 {
 		return true
@@ -113,6 +117,19 @@
 	return true
 }
 
+// checkNumericUnderscores rejects consecutive or trailing underscores.
+func checkNumericUnderscores(a []byte) bool {
+	prevUnderscore := false
+	for _, c := range a {
+		currUnderscore := c == '_'
+		if prevUnderscore && currUnderscore {
+			return false
+		}
+		prevUnderscore = currUnderscore
+	}
+	return !prevUnderscore
+}
+
 func Tokenize(m *Map, filename string, src []byte) (tokens []Token, comments []string, retErr error) {
 	line := uint32(1)
 loop:
@@ -185,12 +202,10 @@
 
 		if numeric(c) {
 			// TODO: 0b11 binary numbers.
-			//
-			// TODO: allow underscores like 0b1000_0000_1111?
-			j, isDigit := i+1, numeric
+			j, isDigit := i+1, numericUnderscore
 			if c == '0' && j < len(src) {
 				if next := src[j]; next == 'x' || next == 'X' {
-					j, isDigit = j+1, hexaNumeric
+					j, isDigit = j+1, hexaNumericUnderscore
 				} else if numeric(next) {
 					return nil, nil, fmt.Errorf("token: legacy octal syntax at %s:%d", filename, line)
 				}
@@ -200,6 +215,9 @@
 					return nil, nil, fmt.Errorf("token: constant too long at %s:%d", filename, line)
 				}
 			}
+			if !checkNumericUnderscores(src[i:j]) {
+				return nil, nil, fmt.Errorf("token: invalid numeric literal at %s:%d", filename, line)
+			}
 			id, err := m.Insert(string(src[i:j]))
 			if err != nil {
 				return nil, nil, err