Browse Source

Support 0o and 0b number literals. Closes #335

Dmitry Panov 4 years ago
parent
commit
d99e4b8cbf
4 changed files with 40 additions and 51 deletions
  1. 1 7
      compiler_expr.go
  2. 29 43
      parser/lexer.go
  3. 4 0
      parser/parser_test.go
  4. 6 1
      tc39_test.go

+ 1 - 7
compiler_expr.go

@@ -2,18 +2,12 @@ package goja
 
 import (
 	"fmt"
-	"regexp"
-
 	"github.com/dop251/goja/ast"
 	"github.com/dop251/goja/file"
 	"github.com/dop251/goja/token"
 	"github.com/dop251/goja/unistring"
 )
 
-var (
-	octalRegexp = regexp.MustCompile(`^0[0-7]`)
-)
-
 type compiledExpr interface {
 	emitGetter(putOnStack bool)
 	emitSetter(valueExpr compiledExpr, putOnStack bool)
@@ -2050,7 +2044,7 @@ func (c *compiler) compileIdentifierExpression(v *ast.Identifier) compiledExpr {
 }
 
 func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr {
-	if c.scope.strict && octalRegexp.MatchString(v.Literal) {
+	if c.scope.strict && len(v.Literal) > 1 && v.Literal[0] == '0' && v.Literal[1] <= '7' && v.Literal[1] >= '0' {
 		c.throwSyntaxError(int(v.Idx)-1, "Octal literals are not allowed in strict mode")
 		panic("Unreachable")
 	}

+ 29 - 43
parser/lexer.go

@@ -1055,53 +1055,41 @@ func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string)
 	if decimalPoint {
 		offset--
 		self.scanMantissa(10)
-		goto exponent
-	}
-
-	if self.chr == '0' {
-		offset := self.chrOffset
-		self.read()
-		if self.chr == 'x' || self.chr == 'X' {
-			// Hexadecimal
+	} else {
+		if self.chr == '0' {
 			self.read()
-			if isDigit(self.chr, 16) {
-				self.read()
-			} else {
-				return token.ILLEGAL, self.str[offset:self.chrOffset]
+			base := 0
+			switch self.chr {
+			case 'x', 'X':
+				base = 16
+			case 'o', 'O':
+				base = 8
+			case 'b', 'B':
+				base = 2
+			case '.', 'e', 'E':
+				// no-op
+			default:
+				// legacy octal
+				self.scanMantissa(8)
+				goto end
 			}
-			self.scanMantissa(16)
-
-			if self.chrOffset-offset <= 2 {
-				// Only "0x" or "0X"
-				self.error(0, "Illegal hexadecimal number")
+			if base > 0 {
+				self.read()
+				if !isDigit(self.chr, base) {
+					return token.ILLEGAL, self.str[offset:self.chrOffset]
+				}
+				self.scanMantissa(base)
+				goto end
 			}
-
-			goto hexadecimal
-		} else if self.chr == '.' {
-			// Float
-			goto float
 		} else {
-			// Octal, Float
-			if self.chr == 'e' || self.chr == 'E' {
-				goto exponent
-			}
-			self.scanMantissa(8)
-			if self.chr == '8' || self.chr == '9' {
-				return token.ILLEGAL, self.str[offset:self.chrOffset]
-			}
-			goto octal
+			self.scanMantissa(10)
+		}
+		if self.chr == '.' {
+			self.read()
+			self.scanMantissa(10)
 		}
 	}
 
-	self.scanMantissa(10)
-
-float:
-	if self.chr == '.' {
-		self.read()
-		self.scanMantissa(10)
-	}
-
-exponent:
 	if self.chr == 'e' || self.chr == 'E' {
 		self.read()
 		if self.chr == '-' || self.chr == '+' {
@@ -1114,9 +1102,7 @@ exponent:
 			return token.ILLEGAL, self.str[offset:self.chrOffset]
 		}
 	}
-
-hexadecimal:
-octal:
+end:
 	if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) {
 		return token.ILLEGAL, self.str[offset:self.chrOffset]
 	}

+ 4 - 0
parser/parser_test.go

@@ -133,6 +133,10 @@ func TestParserErr(t *testing.T) {
 
 		test("01.0", "(anonymous): Line 1:3 Unexpected number")
 
+		test(".0.9", "(anonymous): Line 1:3 Unexpected number")
+
+		test("0o3e1", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
+
 		test("01a", "(anonymous): Line 1:1 Unexpected token ILLEGAL")
 
 		test("0x3in[]", "(anonymous): Line 1:1 Unexpected token ILLEGAL")

+ 6 - 1
tc39_test.go

@@ -298,6 +298,7 @@ var (
 	es6IdWhiteList = []string{
 		"8.1.2.1",
 		"9.5",
+		"11.8.3",
 		"11.8.6",
 		"12.1",
 		"12.2.1",
@@ -406,6 +407,9 @@ var (
 		"sec-tagged-templates-runtime-semantics-evaluation",
 		"sec-template-literal-lexical-components",
 		"sec-template-literals",
+		"sec-literals-numeric-literals",
+		"sec-literals-string-literals",
+		"sec-additional-syntax-numeric-literals",
 	}
 )
 
@@ -799,7 +803,8 @@ func TestTC39(t *testing.T) {
 		ctx.runTC39Tests("test/language/global-code")
 		ctx.runTC39Tests("test/language/identifier-resolution")
 		ctx.runTC39Tests("test/language/identifiers")
-		//ctx.runTC39Tests("test/language/literals") // octal sequences in strict mode
+		//ctx.runTC39Tests("test/language/literals") // legacy octal escape in strings in strict mode and regexp
+		ctx.runTC39Tests("test/language/literals/numeric")
 		ctx.runTC39Tests("test/language/punctuators")
 		ctx.runTC39Tests("test/language/reserved-words")
 		ctx.runTC39Tests("test/language/source-text")