package goja
import (
"fmt"
"github.com/dop251/goja/parser"
"regexp"
"strings"
"unicode/utf16"
"unicode/utf8"
)
func (r *Runtime ) newRegexpObject (proto *Object ) *regexpObject {
v := &Object {runtime : r }
o := ®expObject {}
o .class = classRegExp
o .val = v
o .extensible = true
v .self = o
o .prototype = proto
o .init ()
return o
}
func (r *Runtime ) newRegExpp (pattern *regexpPattern , patternStr String , proto *Object ) *regexpObject {
o := r .newRegexpObject (proto )
o .pattern = pattern
o .source = patternStr
return o
}
func decodeHex(s string ) (int , bool ) {
var hex int
for i := 0 ; i < len (s ); i ++ {
var n byte
chr := s [i ]
switch {
case '0' <= chr && chr <= '9' :
n = chr - '0'
case 'a' <= chr && chr <= 'f' :
n = chr - 'a' + 10
case 'A' <= chr && chr <= 'F' :
n = chr - 'A' + 10
default :
return 0 , false
}
hex = hex *16 + int (n )
}
return hex , true
}
func writeHex4(b *strings .Builder , i int ) {
b .WriteByte (hex [i >>12 ])
b .WriteByte (hex [(i >>8 )&0xF ])
b .WriteByte (hex [(i >>4 )&0xF ])
b .WriteByte (hex [i &0xF ])
}
func convertRegexpToUnicode(patternStr string ) string {
var sb strings .Builder
pos := 0
for i := 0 ; i < len (patternStr )-11 ; {
r , size := utf8 .DecodeRuneInString (patternStr [i :])
if r == '\\' {
i ++
if patternStr [i ] == 'u' && patternStr [i +5 ] == '\\' && patternStr [i +6 ] == 'u' {
if first , ok := decodeHex (patternStr [i +1 : i +5 ]); ok {
if isUTF16FirstSurrogate (uint16 (first )) {
if second , ok := decodeHex (patternStr [i +7 : i +11 ]); ok {
if isUTF16SecondSurrogate (uint16 (second )) {
r = utf16 .DecodeRune (rune (first ), rune (second ))
sb .WriteString (patternStr [pos : i -1 ])
sb .WriteRune (r )
i += 11
pos = i
continue
}
}
}
}
}
i ++
} else {
i += size
}
}
if pos > 0 {
sb .WriteString (patternStr [pos :])
return sb .String ()
}
return patternStr
}
func convertRegexpToUtf16(patternStr string ) string {
var sb strings .Builder
pos := 0
var prevRune rune
for i := 0 ; i < len (patternStr ); {
r , size := utf8 .DecodeRuneInString (patternStr [i :])
if r > 0xFFFF {
sb .WriteString (patternStr [pos :i ])
if prevRune == '\\' {
sb .WriteRune ('\\' )
}
first , second := utf16 .EncodeRune (r )
sb .WriteString (`\u` )
writeHex4 (&sb , int (first ))
sb .WriteString (`\u` )
writeHex4 (&sb , int (second ))
pos = i + size
}
i += size
prevRune = r
}
if pos > 0 {
sb .WriteString (patternStr [pos :])
return sb .String ()
}
return patternStr
}
func escapeInvalidUtf16(s String ) string {
if imported , ok := s .(*importedString ); ok {
return imported .s
}
if ascii , ok := s .(asciiString ); ok {
return ascii .String ()
}
var sb strings .Builder
rd := &lenientUtf16Decoder {utf16Reader : s .utf16Reader ()}
pos := 0
utf8Size := 0
var utf8Buf [utf8 .UTFMax ]byte
for {
c , size , err := rd .ReadRune ()
if err != nil {
break
}
if utf16 .IsSurrogate (c ) {
if sb .Len () == 0 {
sb .Grow (utf8Size + 7 )
hrd := s .Reader ()
var c rune
for p := 0 ; p < pos ; {
var size int
var err error
c , size , err = hrd .ReadRune ()
if err != nil {
panic (fmt .Errorf ("error while reading string head %q, pos: %d: %w" , s .String (), pos , err ))
}
sb .WriteRune (c )
p += size
}
if c == '\\' {
sb .WriteRune (c )
}
}
sb .WriteString (`\u` )
writeHex4 (&sb , int (c ))
} else {
if sb .Len () > 0 {
sb .WriteRune (c )
} else {
utf8Size += utf8 .EncodeRune (utf8Buf [:], c )
pos += size
}
}
}
if sb .Len () > 0 {
return sb .String ()
}
return s .String ()
}
func compileRegexpFromValueString(patternStr String , flags string ) (*regexpPattern , error ) {
return compileRegexp (escapeInvalidUtf16 (patternStr ), flags )
}
func compileRegexp(patternStr , flags string ) (p *regexpPattern , err error ) {
var global , ignoreCase , multiline , dotAll , sticky , unicode bool
var wrapper *regexpWrapper
var wrapper2 *regexp2Wrapper
if flags != "" {
invalidFlags := func () {
err = fmt .Errorf ("Invalid flags supplied to RegExp constructor '%s'" , flags )
}
for _ , chr := range flags {
switch chr {
case 'g' :
if global {
invalidFlags ()
return
}
global = true
case 'm' :
if multiline {
invalidFlags ()
return
}
multiline = true
case 's' :
if dotAll {
invalidFlags ()
return
}
dotAll = true
case 'i' :
if ignoreCase {
invalidFlags ()
return
}
ignoreCase = true
case 'y' :
if sticky {
invalidFlags ()
return
}
sticky = true
case 'u' :
if unicode {
invalidFlags ()
}
unicode = true
default :
invalidFlags ()
return
}
}
}
if unicode {
patternStr = convertRegexpToUnicode (patternStr )
} else {
patternStr = convertRegexpToUtf16 (patternStr )
}
re2Str , err1 := parser .TransformRegExp (patternStr , dotAll , unicode )
if err1 == nil {
re2flags := ""
if multiline {
re2flags += "m"
}
if dotAll {
re2flags += "s"
}
if ignoreCase {
re2flags += "i"
}
if len (re2flags ) > 0 {
re2Str = fmt .Sprintf ("(?%s:%s)" , re2flags , re2Str )
}
pattern , err1 := regexp .Compile (re2Str )
if err1 != nil {
err = fmt .Errorf ("Invalid regular expression (re2): %s (%v)" , re2Str , err1 )
return
}
wrapper = (*regexpWrapper )(pattern )
} else {
if _ , incompat := err1 .(parser .RegexpErrorIncompatible ); !incompat {
err = err1
return
}
wrapper2 , err = compileRegexp2 (patternStr , multiline , dotAll , ignoreCase , unicode )
if err != nil {
err = fmt .Errorf ("Invalid regular expression (regexp2): %s (%v)" , patternStr , err )
return
}
}
p = ®expPattern {
src : patternStr ,
regexpWrapper : wrapper ,
regexp2Wrapper : wrapper2 ,
global : global ,
ignoreCase : ignoreCase ,
multiline : multiline ,
dotAll : dotAll ,
sticky : sticky ,
unicode : unicode ,
}
return
}
func (r *Runtime ) _newRegExp (patternStr String , flags string , proto *Object ) *regexpObject {
pattern , err := compileRegexpFromValueString (patternStr , flags )
if err != nil {
panic (r .newSyntaxError (err .Error(), -1 ))
}
return r .newRegExpp (pattern , patternStr , proto )
}
func (r *Runtime ) builtin_newRegExp (args []Value , proto *Object ) *Object {
var patternVal , flagsVal Value
if len (args ) > 0 {
patternVal = args [0 ]
}
if len (args ) > 1 {
flagsVal = args [1 ]
}
return r .newRegExp (patternVal , flagsVal , proto ).val
}
func (r *Runtime ) newRegExp (patternVal , flagsVal Value , proto *Object ) *regexpObject {
var pattern String
var flags string
if isRegexp (patternVal ) {
if obj , ok := patternVal .(*Object ); ok {
if rx , ok := obj .self .(*regexpObject ); ok {
if flagsVal == nil || flagsVal == _undefined {
return rx .clone ()
} else {
return r ._newRegExp (rx .source , flagsVal .toString ().String (), proto )
}
} else {
pattern = nilSafe (obj .self .getStr ("source" , nil )).toString ()
if flagsVal == nil || flagsVal == _undefined {
flags = nilSafe (obj .self .getStr ("flags" , nil )).toString ().String ()
} else {
flags = flagsVal .toString ().String ()
}
goto exit
}
}
}
if patternVal != nil && patternVal != _undefined {
pattern = patternVal .toString ()
}
if flagsVal != nil && flagsVal != _undefined {
flags = flagsVal .toString ().String ()
}
if pattern == nil {
pattern = stringEmpty
}
exit :
return r ._newRegExp (pattern , flags , proto )
}
func (r *Runtime ) builtin_RegExp (call FunctionCall ) Value {
pattern := call .Argument (0 )
patternIsRegExp := isRegexp (pattern )
flags := call .Argument (1 )
if patternIsRegExp && flags == _undefined {
if obj , ok := call .Argument (0 ).(*Object ); ok {
patternConstructor := obj .self .getStr ("constructor" , nil )
if patternConstructor == r .global .RegExp {
return pattern
}
}
}
return r .newRegExp (pattern , flags , r .getRegExpPrototype ()).val
}
func (r *Runtime ) regexpproto_compile (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
var (
pattern *regexpPattern
source String
flags string
err error
)
patternVal := call .Argument (0 )
flagsVal := call .Argument (1 )
if o , ok := patternVal .(*Object ); ok {
if p , ok := o .self .(*regexpObject ); ok {
if flagsVal != _undefined {
panic (r .NewTypeError ("Cannot supply flags when constructing one RegExp from another" ))
}
this .pattern = p .pattern
this .source = p .source
goto exit
}
}
if patternVal != _undefined {
source = patternVal .toString ()
} else {
source = stringEmpty
}
if flagsVal != _undefined {
flags = flagsVal .toString ().String ()
}
pattern , err = compileRegexpFromValueString (source , flags )
if err != nil {
panic (r .newSyntaxError (err .Error(), -1 ))
}
this .pattern = pattern
this .source = source
exit :
this .setOwnStr ("lastIndex" , intToValue (0 ), true )
return call .This
}
panic (r .NewTypeError ("Method RegExp.prototype.compile called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
func (r *Runtime ) regexpproto_exec (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
return this .exec (call .Argument (0 ).toString ())
} else {
r .typeErrorResult (true , "Method RegExp.prototype.exec called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This }))
return nil
}
}
func (r *Runtime ) regexpproto_test (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .test (call .Argument (0 ).toString ()) {
return valueTrue
} else {
return valueFalse
}
} else {
panic (r .NewTypeError ("Method RegExp.prototype.test called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_toString (call FunctionCall ) Value {
obj := r .toObject (call .This )
if this := r .checkStdRegexp (obj ); this != nil {
var sb StringBuilder
sb .WriteRune ('/' )
if !this .writeEscapedSource (&sb ) {
sb .WriteString (this .source )
}
sb .WriteRune ('/' )
if this .pattern .global {
sb .WriteRune ('g' )
}
if this .pattern .ignoreCase {
sb .WriteRune ('i' )
}
if this .pattern .multiline {
sb .WriteRune ('m' )
}
if this .pattern .dotAll {
sb .WriteRune ('s' )
}
if this .pattern .unicode {
sb .WriteRune ('u' )
}
if this .pattern .sticky {
sb .WriteRune ('y' )
}
return sb .String ()
}
pattern := nilSafe (obj .self .getStr ("source" , nil )).toString ()
flags := nilSafe (obj .self .getStr ("flags" , nil )).toString ()
var sb StringBuilder
sb .WriteRune ('/' )
sb .WriteString (pattern )
sb .WriteRune ('/' )
sb .WriteString (flags )
return sb .String ()
}
func (r *regexpObject ) writeEscapedSource (sb *StringBuilder ) bool {
if r .source .Length () == 0 {
sb .WriteString (asciiString ("(?:)" ))
return true
}
pos := 0
lastPos := 0
rd := &lenientUtf16Decoder {utf16Reader : r .source .utf16Reader ()}
L :
for {
c , size , err := rd .ReadRune ()
if err != nil {
break
}
switch c {
case '\\' :
pos ++
_, size , err = rd .ReadRune ()
if err != nil {
break L
}
case '/' , '\u000a' , '\u000d' , '\u2028' , '\u2029' :
sb .WriteSubstring (r .source , lastPos , pos )
sb .WriteRune ('\\' )
switch c {
case '\u000a' :
sb .WriteRune ('n' )
case '\u000d' :
sb .WriteRune ('r' )
default :
sb .WriteRune ('u' )
sb .WriteRune (rune (hex [c >>12 ]))
sb .WriteRune (rune (hex [(c >>8 )&0xF ]))
sb .WriteRune (rune (hex [(c >>4 )&0xF ]))
sb .WriteRune (rune (hex [c &0xF ]))
}
lastPos = pos + size
}
pos += size
}
if lastPos > 0 {
sb .WriteSubstring (r .source , lastPos , r .source .Length ())
return true
}
return false
}
func (r *Runtime ) regexpproto_getSource (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
var sb StringBuilder
if this .writeEscapedSource (&sb ) {
return sb .String ()
}
return this .source
} else if call .This == r .global .RegExpPrototype {
return asciiString ("(?:)" )
} else {
panic (r .NewTypeError ("Method RegExp.prototype.source getter called on incompatible receiver" ))
}
}
func (r *Runtime ) regexpproto_getGlobal (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .pattern .global {
return valueTrue
} else {
return valueFalse
}
} else if call .This == r .global .RegExpPrototype {
return _undefined
} else {
panic (r .NewTypeError ("Method RegExp.prototype.global getter called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_getMultiline (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .pattern .multiline {
return valueTrue
} else {
return valueFalse
}
} else if call .This == r .global .RegExpPrototype {
return _undefined
} else {
panic (r .NewTypeError ("Method RegExp.prototype.multiline getter called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_getDotAll (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .pattern .dotAll {
return valueTrue
} else {
return valueFalse
}
} else if call .This == r .global .RegExpPrototype {
return _undefined
} else {
panic (r .NewTypeError ("Method RegExp.prototype.dotAll getter called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_getIgnoreCase (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .pattern .ignoreCase {
return valueTrue
} else {
return valueFalse
}
} else if call .This == r .global .RegExpPrototype {
return _undefined
} else {
panic (r .NewTypeError ("Method RegExp.prototype.ignoreCase getter called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_getUnicode (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .pattern .unicode {
return valueTrue
} else {
return valueFalse
}
} else if call .This == r .global .RegExpPrototype {
return _undefined
} else {
panic (r .NewTypeError ("Method RegExp.prototype.unicode getter called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_getSticky (call FunctionCall ) Value {
if this , ok := r .toObject (call .This ).self .(*regexpObject ); ok {
if this .pattern .sticky {
return valueTrue
} else {
return valueFalse
}
} else if call .This == r .global .RegExpPrototype {
return _undefined
} else {
panic (r .NewTypeError ("Method RegExp.prototype.sticky getter called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : call .This })))
}
}
func (r *Runtime ) regexpproto_getFlags (call FunctionCall ) Value {
var global , ignoreCase , multiline , dotAll , sticky , unicode bool
thisObj := r .toObject (call .This )
size := 0
if v := thisObj .self .getStr ("global" , nil ); v != nil {
global = v .ToBoolean ()
if global {
size ++
}
}
if v := thisObj .self .getStr ("ignoreCase" , nil ); v != nil {
ignoreCase = v .ToBoolean ()
if ignoreCase {
size ++
}
}
if v := thisObj .self .getStr ("multiline" , nil ); v != nil {
multiline = v .ToBoolean ()
if multiline {
size ++
}
}
if v := thisObj .self .getStr ("dotAll" , nil ); v != nil {
dotAll = v .ToBoolean ()
if dotAll {
size ++
}
}
if v := thisObj .self .getStr ("sticky" , nil ); v != nil {
sticky = v .ToBoolean ()
if sticky {
size ++
}
}
if v := thisObj .self .getStr ("unicode" , nil ); v != nil {
unicode = v .ToBoolean ()
if unicode {
size ++
}
}
var sb strings .Builder
sb .Grow (size )
if global {
sb .WriteByte ('g' )
}
if ignoreCase {
sb .WriteByte ('i' )
}
if multiline {
sb .WriteByte ('m' )
}
if dotAll {
sb .WriteByte ('s' )
}
if unicode {
sb .WriteByte ('u' )
}
if sticky {
sb .WriteByte ('y' )
}
return asciiString (sb .String ())
}
func (r *Runtime ) regExpExec (execFn func (FunctionCall ) Value , rxObj *Object , arg Value ) Value {
res := execFn (FunctionCall {
This : rxObj ,
Arguments : []Value {arg },
})
if res != _null {
if _ , ok := res .(*Object ); !ok {
panic (r .NewTypeError ("RegExp exec method returned something other than an Object or null" ))
}
}
return res
}
func (r *Runtime ) getGlobalRegexpMatches (rxObj *Object , s String , fullUnicode bool ) []Value {
rxObj .self .setOwnStr ("lastIndex" , intToValue (0 ), true )
execFn , ok := r .toObject (rxObj .self .getStr ("exec" , nil )).self .assertCallable ()
if !ok {
panic (r .NewTypeError ("exec is not a function" ))
}
var a []Value
for {
res := r .regExpExec (execFn , rxObj , s )
if res == _null {
break
}
a = append (a , res )
matchStr := nilSafe (r .toObject (res ).self .getIdx (valueInt (0 ), nil )).toString ()
if matchStr .Length () == 0 {
thisIndex := toLength (rxObj .self .getStr ("lastIndex" , nil ))
rxObj .self .setOwnStr ("lastIndex" , valueInt (advanceStringIndex64 (s , thisIndex , fullUnicode )), true )
}
}
return a
}
func (r *Runtime ) regexpproto_stdMatcherGeneric (rxObj *Object , s String ) Value {
rx := rxObj .self
flags := nilSafe (rx .getStr ("flags" , nil )).String ()
global := strings .ContainsRune (flags , 'g' )
if global {
a := r .getGlobalRegexpMatches (rxObj , s , strings .ContainsRune (flags , 'u' ))
if len (a ) == 0 {
return _null
}
ar := make ([]Value , 0 , len (a ))
for _ , result := range a {
obj := r .toObject (result )
matchStr := nilSafe (obj .self .getIdx (valueInt (0 ), nil )).ToString ()
ar = append (ar , matchStr )
}
return r .newArrayValues (ar )
}
execFn , ok := r .toObject (rx .getStr ("exec" , nil )).self .assertCallable ()
if !ok {
panic (r .NewTypeError ("exec is not a function" ))
}
return r .regExpExec (execFn , rxObj , s )
}
func (r *Runtime ) checkStdRegexp (rxObj *Object ) *regexpObject {
if deoptimiseRegexp {
return nil
}
rx , ok := rxObj .self .(*regexpObject )
if !ok {
return nil
}
if !rx .standard || rx .prototype == nil || rx .prototype .self != r .global .stdRegexpProto {
return nil
}
return rx
}
func (r *Runtime ) regexpproto_stdMatcher (call FunctionCall ) Value {
thisObj := r .toObject (call .This )
s := call .Argument (0 ).toString ()
rx := r .checkStdRegexp (thisObj )
if rx == nil {
return r .regexpproto_stdMatcherGeneric (thisObj , s )
}
if rx .pattern .global {
res := rx .pattern .findAllSubmatchIndex (s , 0 , -1 , rx .pattern .sticky )
if len (res ) == 0 {
rx .setOwnStr ("lastIndex" , intToValue (0 ), true )
return _null
}
a := make ([]Value , 0 , len (res ))
for _ , result := range res {
a = append (a , s .Substring (result [0 ], result [1 ]))
}
rx .setOwnStr ("lastIndex" , intToValue (int64 (res [len (res )-1 ][1 ])), true )
return r .newArrayValues (a )
} else {
return rx .exec (s )
}
}
func (r *Runtime ) regexpproto_stdSearchGeneric (rxObj *Object , arg String ) Value {
rx := rxObj .self
previousLastIndex := nilSafe (rx .getStr ("lastIndex" , nil ))
zero := intToValue (0 )
if !previousLastIndex .SameAs (zero ) {
rx .setOwnStr ("lastIndex" , zero , true )
}
execFn , ok := r .toObject (rx .getStr ("exec" , nil )).self .assertCallable ()
if !ok {
panic (r .NewTypeError ("exec is not a function" ))
}
result := r .regExpExec (execFn , rxObj , arg )
currentLastIndex := nilSafe (rx .getStr ("lastIndex" , nil ))
if !currentLastIndex .SameAs (previousLastIndex ) {
rx .setOwnStr ("lastIndex" , previousLastIndex , true )
}
if result == _null {
return intToValue (-1 )
}
return r .toObject (result ).self .getStr ("index" , nil )
}
func (r *Runtime ) regexpproto_stdMatcherAll (call FunctionCall ) Value {
thisObj := r .toObject (call .This )
s := call .Argument (0 ).toString ()
flags := nilSafe (thisObj .self .getStr ("flags" , nil )).toString ()
c := r .speciesConstructorObj (call .This .(*Object ), r .getRegExp ())
matcher := r .toConstructor (c )([]Value {call .This , flags }, nil )
matcher .self .setOwnStr ("lastIndex" , valueInt (toLength (thisObj .self .getStr ("lastIndex" , nil ))), true )
flagsStr := flags .String ()
global := strings .Contains (flagsStr , "g" )
fullUnicode := strings .Contains (flagsStr , "u" )
return r .createRegExpStringIterator (matcher , s , global , fullUnicode )
}
func (r *Runtime ) createRegExpStringIterator (matcher *Object , s String , global , fullUnicode bool ) Value {
o := &Object {runtime : r }
ri := ®ExpStringIterObject {
matcher : matcher ,
s : s ,
global : global ,
fullUnicode : fullUnicode ,
}
ri .class = classObject
ri .val = o
ri .extensible = true
o .self = ri
ri .prototype = r .getRegExpStringIteratorPrototype ()
ri .init ()
return o
}
type regExpStringIterObject struct {
baseObject
matcher *Object
s String
global, fullUnicode, done bool
}
func regExpExec(r *Object , s String ) Value {
exec := r .self .getStr ("exec" , nil )
if execObject , ok := exec .(*Object ); ok {
if execFn , ok := execObject .self .assertCallable (); ok {
return r .runtime .regExpExec (execFn , r , s )
}
}
if rx , ok := r .self .(*regexpObject ); ok {
return rx .exec (s )
}
panic (r .runtime .NewTypeError ("no RegExpMatcher internal slot" ))
}
func (ri *regExpStringIterObject ) next () (v Value ) {
if ri .done {
return ri .val .runtime .createIterResultObject (_undefined , true )
}
match := regExpExec (ri .matcher , ri .s )
if IsNull (match ) {
ri .done = true
return ri .val .runtime .createIterResultObject (_undefined , true )
}
if !ri .global {
ri .done = true
return ri .val .runtime .createIterResultObject (match , false )
}
matchStr := nilSafe (ri .val .runtime .toObject (match ).self .getIdx (valueInt (0 ), nil )).toString ()
if matchStr .Length () == 0 {
thisIndex := toLength (ri .matcher .self .getStr ("lastIndex" , nil ))
ri .matcher .self .setOwnStr ("lastIndex" , valueInt (advanceStringIndex64 (ri .s , thisIndex , ri .fullUnicode )), true )
}
return ri .val .runtime .createIterResultObject (match , false )
}
func (r *Runtime ) regexpproto_stdSearch (call FunctionCall ) Value {
thisObj := r .toObject (call .This )
s := call .Argument (0 ).toString ()
rx := r .checkStdRegexp (thisObj )
if rx == nil {
return r .regexpproto_stdSearchGeneric (thisObj , s )
}
previousLastIndex := rx .getStr ("lastIndex" , nil )
rx .setOwnStr ("lastIndex" , intToValue (0 ), true )
match , result := rx .execRegexp (s )
rx .setOwnStr ("lastIndex" , previousLastIndex , true )
if !match {
return intToValue (-1 )
}
return intToValue (int64 (result [0 ]))
}
func (r *Runtime ) regexpproto_stdSplitterGeneric (splitter *Object , s String , limit Value , unicodeMatching bool ) Value {
var a []Value
var lim int64
if limit == nil || limit == _undefined {
lim = maxInt - 1
} else {
lim = toLength (limit )
}
if lim == 0 {
return r .newArrayValues (a )
}
size := s .Length ()
p := 0
execFn := toMethod (splitter .ToObject (r ).self .getStr ("exec" , nil ))
if size == 0 {
if r .regExpExec (execFn , splitter , s ) == _null {
a = append (a , s )
}
return r .newArrayValues (a )
}
q := p
for q < size {
splitter .self .setOwnStr ("lastIndex" , intToValue (int64 (q )), true )
z := r .regExpExec (execFn , splitter , s )
if z == _null {
q = advanceStringIndex (s , q , unicodeMatching )
} else {
z := r .toObject (z )
e := toLength (splitter .self .getStr ("lastIndex" , nil ))
if e == int64 (p ) {
q = advanceStringIndex (s , q , unicodeMatching )
} else {
a = append (a , s .Substring (p , q ))
if int64 (len (a )) == lim {
return r .newArrayValues (a )
}
if e > int64 (size ) {
p = size
} else {
p = int (e )
}
numberOfCaptures := max (toLength (z .self .getStr ("length" , nil ))-1 , 0 )
for i := int64 (1 ); i <= numberOfCaptures ; i ++ {
a = append (a , nilSafe (z .self .getIdx (valueInt (i ), nil )))
if int64 (len (a )) == lim {
return r .newArrayValues (a )
}
}
q = p
}
}
}
a = append (a , s .Substring (p , size ))
return r .newArrayValues (a )
}
func advanceStringIndex(s String , pos int , unicode bool ) int {
next := pos + 1
if !unicode {
return next
}
l := s .Length ()
if next >= l {
return next
}
if !isUTF16FirstSurrogate (s .CharAt (pos )) {
return next
}
if !isUTF16SecondSurrogate (s .CharAt (next )) {
return next
}
return next + 1
}
func advanceStringIndex64(s String , pos int64 , unicode bool ) int64 {
next := pos + 1
if !unicode {
return next
}
l := int64 (s .Length ())
if next >= l {
return next
}
if !isUTF16FirstSurrogate (s .CharAt (int (pos ))) {
return next
}
if !isUTF16SecondSurrogate (s .CharAt (int (next ))) {
return next
}
return next + 1
}
func (r *Runtime ) regexpproto_stdSplitter (call FunctionCall ) Value {
rxObj := r .toObject (call .This )
s := call .Argument (0 ).toString ()
limitValue := call .Argument (1 )
var splitter *Object
search := r .checkStdRegexp (rxObj )
c := r .speciesConstructorObj (rxObj , r .getRegExp ())
if search == nil || c != r .global .RegExp {
flags := nilSafe (rxObj .self .getStr ("flags" , nil )).toString ()
flagsStr := flags .String ()
if !strings .Contains (flagsStr , "y" ) {
flags = flags .Concat (asciiString ("y" ))
}
splitter = r .toConstructor (c )([]Value {rxObj , flags }, nil )
search = r .checkStdRegexp (splitter )
if search == nil {
return r .regexpproto_stdSplitterGeneric (splitter , s , limitValue , strings .Contains (flagsStr , "u" ))
}
}
pattern := search .pattern
limit := -1
if limitValue != _undefined {
limit = int (toUint32 (limitValue ))
}
if limit == 0 {
return r .newArrayValues (nil )
}
targetLength := s .Length ()
var valueArray []Value
lastIndex := 0
found := 0
result := pattern .findAllSubmatchIndex (s , 0 , -1 , false )
if targetLength == 0 {
if result == nil {
valueArray = append (valueArray , s )
}
goto RETURN
}
for _ , match := range result {
if match [0 ] == match [1 ] {
if match [0 ] == 0 || match [0 ] == targetLength {
continue
}
}
if lastIndex != match [0 ] {
valueArray = append (valueArray , s .Substring (lastIndex , match [0 ]))
found ++
} else if lastIndex == match [0 ] {
if lastIndex != -1 {
valueArray = append (valueArray , stringEmpty )
found ++
}
}
lastIndex = match [1 ]
if found == limit {
goto RETURN
}
captureCount := len (match ) / 2
for index := 1 ; index < captureCount ; index ++ {
offset := index * 2
var value Value
if match [offset ] != -1 {
value = s .Substring (match [offset ], match [offset +1 ])
} else {
value = _undefined
}
valueArray = append (valueArray , value )
found ++
if found == limit {
goto RETURN
}
}
}
if found != limit {
if lastIndex != targetLength {
valueArray = append (valueArray , s .Substring (lastIndex , targetLength ))
} else {
valueArray = append (valueArray , stringEmpty )
}
}
RETURN :
return r .newArrayValues (valueArray )
}
func (r *Runtime ) regexpproto_stdReplacerGeneric (rxObj *Object , s , replaceStr String , rcall func (FunctionCall ) Value ) Value {
var results []Value
flags := nilSafe (rxObj .self .getStr ("flags" , nil )).String ()
isGlobal := strings .ContainsRune (flags , 'g' )
isUnicode := strings .ContainsRune (flags , 'u' )
if isGlobal {
results = r .getGlobalRegexpMatches (rxObj , s , isUnicode )
} else {
execFn := toMethod (rxObj .self .getStr ("exec" , nil ))
result := r .regExpExec (execFn , rxObj , s )
if result != _null {
results = append (results , result )
}
}
lengthS := s .Length ()
nextSourcePosition := 0
var resultBuf StringBuilder
for _ , result := range results {
obj := r .toObject (result )
nCaptures := max (toLength (obj .self .getStr ("length" , nil ))-1 , 0 )
matched := nilSafe (obj .self .getIdx (valueInt (0 ), nil )).toString ()
matchLength := matched .Length ()
position := toIntStrict (max (min (nilSafe (obj .self .getStr ("index" , nil )).ToInteger (), int64 (lengthS )), 0 ))
var captures []Value
if rcall != nil {
captures = make ([]Value , 0 , nCaptures +3 )
} else {
captures = make ([]Value , 0 , nCaptures +1 )
}
captures = append (captures , matched )
for n := int64 (1 ); n <= nCaptures ; n ++ {
capN := nilSafe (obj .self .getIdx (valueInt (n ), nil ))
if capN != _undefined {
capN = capN .ToString ()
}
captures = append (captures , capN )
}
var replacement String
if rcall != nil {
captures = append (captures , intToValue (int64 (position )), s )
replacement = rcall (FunctionCall {
This : _undefined ,
Arguments : captures ,
}).toString ()
if position >= nextSourcePosition {
resultBuf .WriteString (s .Substring (nextSourcePosition , position ))
resultBuf .WriteString (replacement )
nextSourcePosition = position + matchLength
}
} else {
if position >= nextSourcePosition {
resultBuf .WriteString (s .Substring (nextSourcePosition , position ))
writeSubstitution (s , position , len (captures ), func (idx int ) String {
capture := captures [idx ]
if capture != _undefined {
return capture .toString ()
}
return stringEmpty
}, replaceStr , &resultBuf )
nextSourcePosition = position + matchLength
}
}
}
if nextSourcePosition < lengthS {
resultBuf .WriteString (s .Substring (nextSourcePosition , lengthS ))
}
return resultBuf .String ()
}
func writeSubstitution(s String , position int , numCaptures int , getCapture func (int ) String , replaceStr String , buf *StringBuilder ) {
l := s .Length ()
rl := replaceStr .Length ()
matched := getCapture (0 )
tailPos := position + matched .Length ()
for i := 0 ; i < rl ; i ++ {
c := replaceStr .CharAt (i )
if c == '$' && i < rl -1 {
ch := replaceStr .CharAt (i + 1 )
switch ch {
case '$' :
buf .WriteRune ('$' )
case '`' :
buf .WriteString (s .Substring (0 , position ))
case '\'' :
if tailPos < l {
buf .WriteString (s .Substring (tailPos , l ))
}
case '&' :
buf .WriteString (matched )
default :
matchNumber := 0
j := i + 1
for j < rl {
ch := replaceStr .CharAt (j )
if ch >= '0' && ch <= '9' {
m := matchNumber *10 + int (ch -'0' )
if m >= numCaptures {
break
}
matchNumber = m
j ++
} else {
break
}
}
if matchNumber > 0 {
buf .WriteString (getCapture (matchNumber ))
i = j - 1
continue
} else {
buf .WriteRune ('$' )
buf .WriteRune (rune (ch ))
}
}
i ++
} else {
buf .WriteRune (rune (c ))
}
}
}
func (r *Runtime ) regexpproto_stdReplacer (call FunctionCall ) Value {
rxObj := r .toObject (call .This )
s := call .Argument (0 ).toString ()
replaceStr , rcall := getReplaceValue (call .Argument (1 ))
rx := r .checkStdRegexp (rxObj )
if rx == nil {
return r .regexpproto_stdReplacerGeneric (rxObj , s , replaceStr , rcall )
}
var index int64
find := 1
if rx .pattern .global {
find = -1
rx .setOwnStr ("lastIndex" , intToValue (0 ), true )
} else {
index = rx .getLastIndex ()
}
found := rx .pattern .findAllSubmatchIndex (s , toIntStrict (index ), find , rx .pattern .sticky )
if len (found ) > 0 {
if !rx .updateLastIndex (index , found [0 ], found [len (found )-1 ]) {
found = nil
}
} else {
rx .updateLastIndex (index , nil , nil )
}
return stringReplace (s , found , replaceStr , rcall )
}
func (r *Runtime ) regExpStringIteratorProto_next (call FunctionCall ) Value {
thisObj := r .toObject (call .This )
if iter , ok := thisObj .self .(*regExpStringIterObject ); ok {
return iter .next ()
}
panic (r .NewTypeError ("Method RegExp String Iterator.prototype.next called on incompatible receiver %s" , r .objectproto_toString (FunctionCall {This : thisObj })))
}
func (r *Runtime ) createRegExpStringIteratorPrototype (val *Object ) objectImpl {
o := newBaseObjectObj (val , r .getIteratorPrototype (), classObject )
o ._putProp ("next" , r .newNativeFunc (r .regExpStringIteratorProto_next , "next" , 0 ), true , false , true )
o ._putSym (SymToStringTag , valueProp (asciiString (classRegExpStringIterator ), false , false , true ))
return o
}
func (r *Runtime ) getRegExpStringIteratorPrototype () *Object {
var o *Object
if o = r .global .RegExpStringIteratorPrototype ; o == nil {
o = &Object {runtime : r }
r .global .RegExpStringIteratorPrototype = o
o .self = r .createRegExpStringIteratorPrototype (o )
}
return o
}
func (r *Runtime ) getRegExp () *Object {
ret := r .global .RegExp
if ret == nil {
ret = &Object {runtime : r }
r .global .RegExp = ret
proto := r .getRegExpPrototype ()
r .newNativeFuncAndConstruct (ret , r .builtin_RegExp ,
r .wrapNativeConstruct (r .builtin_newRegExp , ret , proto ), proto , "RegExp" , intToValue (2 ))
rx := ret .self
r .putSpeciesReturnThis (rx )
}
return ret
}
func (r *Runtime ) getRegExpPrototype () *Object {
ret := r .global .RegExpPrototype
if ret == nil {
o := r .newGuardedObject (r .global .ObjectPrototype , classObject )
ret = o .val
r .global .RegExpPrototype = ret
r .global .stdRegexpProto = o
o ._putProp ("constructor" , r .getRegExp (), true , false , true )
o ._putProp ("compile" , r .newNativeFunc (r .regexpproto_compile , "compile" , 2 ), true , false , true )
o ._putProp ("exec" , r .newNativeFunc (r .regexpproto_exec , "exec" , 1 ), true , false , true )
o ._putProp ("test" , r .newNativeFunc (r .regexpproto_test , "test" , 1 ), true , false , true )
o ._putProp ("toString" , r .newNativeFunc (r .regexpproto_toString , "toString" , 0 ), true , false , true )
o .setOwnStr ("source" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getSource , "get source" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("global" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getGlobal , "get global" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("multiline" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getMultiline , "get multiline" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("dotAll" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getDotAll , "get dotAll" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("ignoreCase" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getIgnoreCase , "get ignoreCase" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("unicode" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getUnicode , "get unicode" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("sticky" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getSticky , "get sticky" , 0 ),
accessor : true ,
}, false )
o .setOwnStr ("flags" , &valueProperty {
configurable : true ,
getterFunc : r .newNativeFunc (r .regexpproto_getFlags , "get flags" , 0 ),
accessor : true ,
}, false )
o ._putSym (SymMatch , valueProp (r .newNativeFunc (r .regexpproto_stdMatcher , "[Symbol.match]" , 1 ), true , false , true ))
o ._putSym (SymMatchAll , valueProp (r .newNativeFunc (r .regexpproto_stdMatcherAll , "[Symbol.matchAll]" , 1 ), true , false , true ))
o ._putSym (SymSearch , valueProp (r .newNativeFunc (r .regexpproto_stdSearch , "[Symbol.search]" , 1 ), true , false , true ))
o ._putSym (SymSplit , valueProp (r .newNativeFunc (r .regexpproto_stdSplitter , "[Symbol.split]" , 2 ), true , false , true ))
o ._putSym (SymReplace , valueProp (r .newNativeFunc (r .regexpproto_stdReplacer , "[Symbol.replace]" , 2 ), true , false , true ))
o .guard ("exec" , "global" , "multiline" , "ignoreCase" , "unicode" , "sticky" )
}
return ret
}
The pages are generated with Golds v0.8.2 . (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu .
PR and bug reports are welcome and can be submitted to the issue list .
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds .