273 lines
7.1 KiB
Go
273 lines
7.1 KiB
Go
package goja
|
|
|
|
import (
|
|
"fmt"
|
|
"github.com/dlclark/regexp2"
|
|
"github.com/dop251/goja/parser"
|
|
"regexp"
|
|
)
|
|
|
|
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 valueString, global, ignoreCase, multiline bool, proto *Object) *Object {
|
|
o := r.newRegexpObject(proto)
|
|
|
|
o.pattern = pattern
|
|
o.source = patternStr
|
|
o.global = global
|
|
o.ignoreCase = ignoreCase
|
|
o.multiline = multiline
|
|
|
|
return o.val
|
|
}
|
|
|
|
func compileRegexp(patternStr, flags string) (p regexpPattern, global, ignoreCase, multiline bool, err error) {
|
|
|
|
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 'i':
|
|
if ignoreCase {
|
|
invalidFlags()
|
|
return
|
|
}
|
|
ignoreCase = true
|
|
default:
|
|
invalidFlags()
|
|
return
|
|
}
|
|
}
|
|
}
|
|
|
|
re2Str, err1 := parser.TransformRegExp(patternStr)
|
|
if /*false &&*/ err1 == nil {
|
|
re2flags := ""
|
|
if multiline {
|
|
re2flags += "m"
|
|
}
|
|
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
|
|
}
|
|
|
|
p = (*regexpWrapper)(pattern)
|
|
} else {
|
|
var opts regexp2.RegexOptions = regexp2.ECMAScript
|
|
if multiline {
|
|
opts |= regexp2.Multiline
|
|
}
|
|
if ignoreCase {
|
|
opts |= regexp2.IgnoreCase
|
|
}
|
|
regexp2Pattern, err1 := regexp2.Compile(patternStr, opts)
|
|
if err1 != nil {
|
|
err = fmt.Errorf("Invalid regular expression (regexp2): %s (%v)", patternStr, err1)
|
|
return
|
|
}
|
|
p = (*regexp2Wrapper)(regexp2Pattern)
|
|
}
|
|
return
|
|
}
|
|
|
|
func (r *Runtime) newRegExp(patternStr valueString, flags string, proto *Object) *Object {
|
|
pattern, global, ignoreCase, multiline, err := compileRegexp(patternStr.String(), flags)
|
|
if err != nil {
|
|
panic(r.newSyntaxError(err.Error(), -1))
|
|
}
|
|
return r.newRegExpp(pattern, patternStr, global, ignoreCase, multiline, proto)
|
|
}
|
|
|
|
func (r *Runtime) builtin_newRegExp(args []Value) *Object {
|
|
var pattern valueString
|
|
var flags string
|
|
if len(args) > 0 {
|
|
if obj, ok := args[0].(*Object); ok {
|
|
if regexp, ok := obj.self.(*regexpObject); ok {
|
|
if len(args) < 2 || args[1] == _undefined {
|
|
return regexp.clone()
|
|
} else {
|
|
return r.newRegExp(regexp.source, args[1].String(), r.global.RegExpPrototype)
|
|
}
|
|
}
|
|
}
|
|
if args[0] != _undefined {
|
|
pattern = args[0].ToString()
|
|
}
|
|
}
|
|
if len(args) > 1 {
|
|
if a := args[1]; a != _undefined {
|
|
flags = a.String()
|
|
}
|
|
}
|
|
if pattern == nil {
|
|
pattern = stringEmpty
|
|
}
|
|
return r.newRegExp(pattern, flags, r.global.RegExpPrototype)
|
|
}
|
|
|
|
func (r *Runtime) builtin_RegExp(call FunctionCall) Value {
|
|
flags := call.Argument(1)
|
|
if flags == _undefined {
|
|
if obj, ok := call.Argument(0).(*Object); ok {
|
|
if _, ok := obj.self.(*regexpObject); ok {
|
|
return call.Arguments[0]
|
|
}
|
|
}
|
|
}
|
|
return r.builtin_newRegExp(call.Arguments)
|
|
}
|
|
|
|
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", call.This.ToString())
|
|
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 {
|
|
r.typeErrorResult(true, "Method RegExp.prototype.test called on incompatible receiver %s", call.This.ToString())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) regexpproto_toString(call FunctionCall) Value {
|
|
if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
|
|
var g, i, m string
|
|
if this.global {
|
|
g = "g"
|
|
}
|
|
if this.ignoreCase {
|
|
i = "i"
|
|
}
|
|
if this.multiline {
|
|
m = "m"
|
|
}
|
|
return newStringValue(fmt.Sprintf("/%s/%s%s%s", this.source.String(), g, i, m))
|
|
} else {
|
|
r.typeErrorResult(true, "Method RegExp.prototype.toString called on incompatible receiver %s", call.This)
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) regexpproto_getSource(call FunctionCall) Value {
|
|
if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
|
|
return this.source
|
|
} else {
|
|
r.typeErrorResult(true, "Method RegExp.prototype.source getter called on incompatible receiver %s", call.This.ToString())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) regexpproto_getGlobal(call FunctionCall) Value {
|
|
if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
|
|
if this.global {
|
|
return valueTrue
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
} else {
|
|
r.typeErrorResult(true, "Method RegExp.prototype.global getter called on incompatible receiver %s", call.This.ToString())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) regexpproto_getMultiline(call FunctionCall) Value {
|
|
if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
|
|
if this.multiline {
|
|
return valueTrue
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
} else {
|
|
r.typeErrorResult(true, "Method RegExp.prototype.multiline getter called on incompatible receiver %s", call.This.ToString())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) regexpproto_getIgnoreCase(call FunctionCall) Value {
|
|
if this, ok := r.toObject(call.This).self.(*regexpObject); ok {
|
|
if this.ignoreCase {
|
|
return valueTrue
|
|
} else {
|
|
return valueFalse
|
|
}
|
|
} else {
|
|
r.typeErrorResult(true, "Method RegExp.prototype.ignoreCase getter called on incompatible receiver %s", call.This.ToString())
|
|
return nil
|
|
}
|
|
}
|
|
|
|
func (r *Runtime) initRegExp() {
|
|
r.global.RegExpPrototype = r.NewObject()
|
|
o := r.global.RegExpPrototype.self
|
|
o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, nil, "exec", nil, 1), true, false, true)
|
|
o._putProp("test", r.newNativeFunc(r.regexpproto_test, nil, "test", nil, 1), true, false, true)
|
|
o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, nil, "toString", nil, 0), true, false, true)
|
|
o.putStr("source", &valueProperty{
|
|
configurable: true,
|
|
getterFunc: r.newNativeFunc(r.regexpproto_getSource, nil, "get source", nil, 0),
|
|
accessor: true,
|
|
}, false)
|
|
o.putStr("global", &valueProperty{
|
|
configurable: true,
|
|
getterFunc: r.newNativeFunc(r.regexpproto_getGlobal, nil, "get global", nil, 0),
|
|
accessor: true,
|
|
}, false)
|
|
o.putStr("multiline", &valueProperty{
|
|
configurable: true,
|
|
getterFunc: r.newNativeFunc(r.regexpproto_getMultiline, nil, "get multiline", nil, 0),
|
|
accessor: true,
|
|
}, false)
|
|
o.putStr("ignoreCase", &valueProperty{
|
|
configurable: true,
|
|
getterFunc: r.newNativeFunc(r.regexpproto_getIgnoreCase, nil, "get ignoreCase", nil, 0),
|
|
accessor: true,
|
|
}, false)
|
|
|
|
r.global.RegExp = r.newNativeFunc(r.builtin_RegExp, r.builtin_newRegExp, "RegExp", r.global.RegExpPrototype, 2)
|
|
r.addToGlobal("RegExp", r.global.RegExp)
|
|
}
|