Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions builtin_string.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,13 @@ package otto

import (
"bytes"
"regexp"
restd "regexp"
"strconv"
"strings"
"unicode/utf16"
"unicode/utf8"

"github.com/robertkrimen/otto/regexp"
)

// String
Expand Down Expand Up @@ -170,7 +172,7 @@ func builtinStringMatch(call FunctionCall) Value {
return objectValue(call.runtime.newArrayOf(valueArray))
}

var builtinStringReplaceRegexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")
var builtinStringReplaceRegexp = restd.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")

func builtinStringFindAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) []byte {
matchCount := len(match) / 2
Expand Down Expand Up @@ -215,7 +217,7 @@ func builtinStringReplace(call FunctionCall) Value {
searchObject := searchValue.object()

// TODO If a capture is -1?
var search *regexp.Regexp
var search regexp.Finder
global := false
find := 1
if searchValue.IsObject() && searchObject.class == classRegExpName {
Expand All @@ -226,7 +228,7 @@ func builtinStringReplace(call FunctionCall) Value {
global = true
}
} else {
search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
search = call.runtime.regExp.MustCompile(restd.QuoteMeta(searchValue.string()))
}

found := search.FindAllSubmatchIndex(target, find)
Expand Down
1 change: 1 addition & 0 deletions clone.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ func (rt *runtime) clone() *runtime {
defer rt.lck.Unlock()

out := &runtime{
regExp: rt.regExp,
debugger: rt.debugger,
random: rt.random,
stackLimit: rt.stackLimit,
Expand Down
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ module github.com/robertkrimen/otto
go 1.18

require (
github.com/dlclark/regexp2 v1.7.0
github.com/stretchr/testify v1.8.1
golang.org/x/text v0.4.0
gopkg.in/readline.v1 v1.0.0-20160726135117-62c6fe619375
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ github.com/chzyer/test v1.0.0/go.mod h1:2JlltgoNkt4TW/z9V/IzDdFaMTM2JPIi26O1pF38
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/dlclark/regexp2 v1.7.0 h1:7lJfhqlPssTb1WQx4yvTHN0uElPEv52sbaECrAQxjAo=
github.com/dlclark/regexp2 v1.7.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
Expand Down
56 changes: 55 additions & 1 deletion otto.go
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,7 @@ import (
"strings"

"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/regexp"
"github.com/robertkrimen/otto/registry"
)

Expand All @@ -237,11 +238,60 @@ type Otto struct {
runtime *runtime
}

// Option represents a Otto configuration option.
type Option func(*Otto)

// Debugger is an option that sets the debugger handler to fn.
func Debugger(fn func(vm *Otto)) Option {
return func(o *Otto) {
o.runtime.debugger = fn
}
}

// RandomSource is an option that sets the random source to fn.
func RandomSource(fn func() float64) Option {
return func(o *Otto) {
o.runtime.random = fn
}
}

// Stack is an option that sets the stack depth to limit.
// Default: 0 (unlimited).
func Stack(limit int) Option {
return func(o *Otto) {
o.runtime.stackLimit = limit
}
}

// StackTrace is an option that sets the stack trace depth to limit.
// Default: 10.
func StackTrace(limit int) Option {
return func(o *Otto) {
o.runtime.traceLimit = limit
}
}

// RegExp is an option which sets the regular expression engine.
// This can be used to enable ECMAScript compatible regular expression
// processing using regexp2.Creator.
// Default: standard regexp library using ECMA to RE2 transformation
// which provides constant order matching but doesn't support backtracking.
func RegExp(creator regexp.Creator) Option {
return func(o *Otto) {
o.runtime.regExp = creator
}
}

// New will allocate a new JavaScript runtime.
func New() *Otto {
func New(options ...Option) *Otto {
o := &Otto{
runtime: newContext(),
}
o.runtime.regExp = &regexpStd{}

for _, f := range options {
f(o)
}
o.runtime.otto = o
o.runtime.traceLimit = 10
if err := o.Set("console", o.runtime.newConsole()); err != nil {
Expand Down Expand Up @@ -360,11 +410,13 @@ func (o Otto) setValue(name string, value Value) {
}

// SetDebuggerHandler sets the debugger handler to fn.
// Deprecated: Use Debugger option.
func (o Otto) SetDebuggerHandler(fn func(vm *Otto)) {
o.runtime.debugger = fn
}

// SetRandomSource sets the random source to fn.
// Deprecated: Use RandomSource option.
func (o Otto) SetRandomSource(fn func() float64) {
o.runtime.random = fn
}
Expand All @@ -377,6 +429,7 @@ func (o Otto) SetRandomSource(fn func() float64) {
// JavaScript makes a call to a Go function, otto won't keep track of what
// happens outside the interpreter. So if your Go function is infinitely
// recursive, you're still in trouble.
// Deprecated: Use Stack option.
func (o Otto) SetStackDepthLimit(limit int) {
o.runtime.stackLimit = limit
}
Expand All @@ -386,6 +439,7 @@ func (o Otto) SetStackDepthLimit(limit int) {
// is 10. This is consistent with V8 and SpiderMonkey.
//
// TODO: expose via `Error.stackTraceLimit`.
// Deprecated: Use StackTrace option.
func (o Otto) SetStackTraceLimit(limit int) {
o.runtime.traceLimit = limit
}
Expand Down
27 changes: 15 additions & 12 deletions parser/expression.go
Original file line number Diff line number Diff line change
Expand Up @@ -142,21 +142,24 @@ func (p *parser) parseRegExpLiteral() *ast.RegExpLiteral {

var value string
// TODO 15.10
// Test during parsing that this is a valid regular expression
// Sorry, (?=) and (?!) are invalid (for now)
pat, err := TransformRegExp(pattern)
if err != nil {
if pat == "" || p.mode&IgnoreRegExpErrors == 0 {
p.error(idx, "Invalid regular expression: %s", err.Error())
}
} else {
_, err = regexp.Compile(pat)
if p.mode&NoRegExpTransform == 0 {
// Test during parsing that this is a valid regular expression
// Sorry, (?=) and (?!) are invalid (for now)
pat, err := TransformRegExp(pattern)
if err != nil {
// We should not get here, ParseRegExp should catch any errors
p.error(idx, "Invalid regular expression: %s", err.Error()[22:]) // Skip redundant "parse regexp error"
if pat == "" || p.mode&IgnoreRegExpErrors == 0 {
p.error(idx, "Invalid regular expression: %s", err.Error())
}
} else {
value = pat
if _, err = regexp.Compile(pat); err != nil {
// We should not get here, ParseRegExp should catch any errors
p.error(idx, "Invalid regular expression: %s", err.Error()[22:]) // Skip redundant "parse regexp error"
} else {
value = pat
}
}
} else {
value = pattern
}

literal := p.str[offset:endOffset]
Expand Down
3 changes: 3 additions & 0 deletions parser/parser.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ const (

// StoreComments stores the comments from source to the comments map.
StoreComments

// NoRegExpTransform disables RegExp transformation.
NoRegExpTransform
)

type parser struct { //nolint: maligned
Expand Down
1 change: 0 additions & 1 deletion parser/regexp.go
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,6 @@ func TransformRegExp(pattern string) (string, error) {
}

// TODO If without \, if without (?=, (?!, then another shortcut

p := regExpParser{
str: pattern,
length: len(pattern),
Expand Down
45 changes: 45 additions & 0 deletions regexp/interfaces.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
// Package regexp defines Regular Expression interfaces used for otto.
package regexp

// Finder is implemented by types which can act as a regular expression finder.
type Finder interface {
// FindStringIndex returns a two-element slice of integers defining the location of the
// leftmost match in s of the regular expression. The match itself is at s[loc[0]:loc[1]].
// A return value of nil indicates no match.
FindStringIndex(s string) []int

// FindAllStringIndex is the 'All' version of FindStringIndex; it returns a slice of all
// successive matches of the expression, as defined by the 'All' description in the package
// comment. A return value of nil indicates no match.
FindAllStringIndex(s string, n int) [][]int

// FindStringSubmatchIndex returns a slice holding the index pairs identifying the leftmost
// match of the regular expression in s and the matches, if any, of its sub expressions, as
// defined by the 'Submatch' and 'Index' descriptions in the package comment. A return value
// of nil indicates no match.
FindStringSubmatchIndex(s string) []int

// FindAllStringSubmatchIndex is the 'All' version of FindStringSubmatchIndex; it returns a
// slice of all successive matches of the expression, as defined by the 'All' description in
// the package comment. A return value of nil indicates no match.
FindAllStringSubmatchIndex(s string, n int) [][]int

// FindAllSubmatchIndex is the 'All' version of FindSubmatchIndex; it returns a slice of all
// successive matches of the expression, as defined by the 'All' description in the package
// comment. A return value of nil indicates no match.
FindAllSubmatchIndex(b []byte, n int) [][]int
}

// Creator is implemented by types which can operate a RegExp provider for otto.
type Creator interface {
// Compile parses a regular expression and returns, if successful, a Finder that
// can be used to match against text.
Compile(str string) (Finder, error)

// MustCompile is like Compile but panics if the expression cannot be parsed. It simplifies safe
// initialization of global variables holding compiled regular expressions.
MustCompile(str string) Finder

// NoTransform returns true if no RegExp transformation should be done.
NoTransform() bool
}
Loading