:1:1",
134 | })
135 |
136 | return UndefinedValue()
137 | })
138 | require.NoError(t, err)
139 |
140 | _, err = vm.Run(`functionA()`)
141 | require.NoError(t, err)
142 | })
143 | }
144 |
--------------------------------------------------------------------------------
/testing_test.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "errors"
5 | "strings"
6 | "testing"
7 | "time"
8 |
9 | "github.com/robertkrimen/otto/terst"
10 | )
11 |
12 | func tt(t *testing.T, arguments ...func()) {
13 | t.Helper()
14 | halt := errors.New("A test was taking too long")
15 | timer := time.AfterFunc(20*time.Second, func() {
16 | panic(halt)
17 | })
18 | defer func() {
19 | timer.Stop()
20 | }()
21 | terst.Terst(t, arguments...)
22 | }
23 |
24 | func is(arguments ...interface{}) bool {
25 | var got, expect interface{}
26 |
27 | switch len(arguments) {
28 | case 0, 1:
29 | return terst.Is(arguments...)
30 | case 2:
31 | got, expect = arguments[0], arguments[1]
32 | default:
33 | got, expect = arguments[0], arguments[2]
34 | }
35 |
36 | switch value := got.(type) {
37 | case Value:
38 | if value.value != nil {
39 | got = value.value
40 | }
41 | case *Error:
42 | if value != nil {
43 | got = value.Error()
44 | }
45 | if expect == nil {
46 | // FIXME This is weird
47 | expect = ""
48 | }
49 | }
50 |
51 | if len(arguments) == 2 {
52 | arguments[0] = got
53 | arguments[1] = expect
54 | } else {
55 | arguments[0] = got
56 | arguments[2] = expect
57 | }
58 |
59 | return terst.Is(arguments...)
60 | }
61 |
62 | func test(arguments ...interface{}) (func(string, ...interface{}) Value, *_tester) {
63 | tester := newTester()
64 | if len(arguments) > 0 {
65 | tester.test(arguments[0].(string))
66 | }
67 | return tester.test, tester
68 | }
69 |
70 | type _tester struct {
71 | vm *Otto
72 | }
73 |
74 | func newTester() *_tester {
75 | return &_tester{
76 | vm: New(),
77 | }
78 | }
79 |
80 | func (te *_tester) Get(name string) (Value, error) {
81 | return te.vm.Get(name)
82 | }
83 |
84 | func (te *_tester) Set(name string, value interface{}) Value {
85 | err := te.vm.Set(name, value)
86 | is(err, nil)
87 | if err != nil {
88 | terst.Caller().T().FailNow()
89 | }
90 | return te.vm.getValue(name)
91 | }
92 |
93 | func (te *_tester) Run(src interface{}) (Value, error) {
94 | return te.vm.Run(src)
95 | }
96 |
97 | func (te *_tester) test(name string, expect ...interface{}) Value {
98 | vm := te.vm
99 | raise := false
100 | defer func() {
101 | if caught := recover(); caught != nil {
102 | if exception, ok := caught.(*exception); ok {
103 | caught = exception.eject()
104 | }
105 | if raise {
106 | if len(expect) > 0 {
107 | is(caught, expect[0])
108 | }
109 | } else {
110 | dbg("Panic, caught:", caught)
111 | panic(caught)
112 | }
113 | }
114 | }()
115 | var value Value
116 | var err error
117 | if isIdentifier(name) {
118 | value = vm.getValue(name)
119 | } else {
120 | source := name
121 | index := strings.Index(source, "raise:")
122 | if index == 0 {
123 | raise = true
124 | source = source[6:]
125 | source = strings.TrimLeft(source, " ")
126 | }
127 | value, err = vm.runtime.cmplRun(source, nil)
128 | if err != nil {
129 | panic(err)
130 | }
131 | }
132 | value = value.resolve()
133 | if len(expect) > 0 {
134 | is(value, expect[0])
135 | }
136 | return value
137 | }
138 |
--------------------------------------------------------------------------------
/token/generate.go:
--------------------------------------------------------------------------------
1 | package token
2 |
3 | //go:generate go run ../tools/gen-tokens -output token_const.go
4 |
--------------------------------------------------------------------------------
/token/token.go:
--------------------------------------------------------------------------------
1 | // Package token defines constants representing the lexical tokens of JavaScript (ECMA5).
2 | package token
3 |
4 | import (
5 | "strconv"
6 | )
7 |
8 | // Token is the set of lexical tokens in JavaScript (ECMA5).
9 | type Token int
10 |
11 | // String returns the string corresponding to the token.
12 | // For operators, delimiters, and keywords the string is the actual
13 | // token string (e.g., for the token PLUS, the String() is
14 | // "+"). For all other tokens the string corresponds to the token
15 | // name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
16 | func (tkn Token) String() string {
17 | switch {
18 | case tkn == 0:
19 | return "UNKNOWN"
20 | case tkn < Token(len(token2string)):
21 | return token2string[tkn]
22 | default:
23 | return "token(" + strconv.Itoa(int(tkn)) + ")"
24 | }
25 | }
26 |
27 | type keyword struct {
28 | token Token
29 | futureKeyword bool
30 | strict bool
31 | }
32 |
33 | // IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token
34 | // if the literal is a future keyword (const, let, class, super, ...), or 0 if the literal is not a keyword.
35 | //
36 | // If the literal is a keyword, IsKeyword returns a second value indicating if the literal
37 | // is considered a future keyword in strict-mode only.
38 | //
39 | // 7.6.1.2 Future Reserved Words:
40 | //
41 | // const
42 | // class
43 | // enum
44 | // export
45 | // extends
46 | // import
47 | // super
48 | //
49 | // 7.6.1.2 Future Reserved Words (strict):
50 | //
51 | // implements
52 | // interface
53 | // let
54 | // package
55 | // private
56 | // protected
57 | // public
58 | // static
59 | func IsKeyword(literal string) (Token, bool) {
60 | if kw, exists := keywordTable[literal]; exists {
61 | if kw.futureKeyword {
62 | return KEYWORD, kw.strict
63 | }
64 | return kw.token, false
65 | }
66 | return 0, false
67 | }
68 |
--------------------------------------------------------------------------------
/tools/gen-jscore/helpers.go:
--------------------------------------------------------------------------------
1 | package main
2 |
3 | import (
4 | "fmt"
5 | "unicode"
6 | )
7 |
8 | // ucfirst converts the first rune of val to uppercase an returns the result.
9 | func ucfirst(val string) string {
10 | r := []rune(val)
11 | r[0] = unicode.ToUpper(r[0])
12 |
13 | return string(r)
14 | }
15 |
16 | // dict is a template helper returns a map created from alternating values.
17 | // Values must be passed as key, value pairs with key being a string.
18 | // It can be used to pass the combination of multiple values to a template.
19 | func dict(values ...interface{}) (map[string]interface{}, error) {
20 | if len(values)%2 != 0 {
21 | return nil, fmt.Errorf("map requires parameters which are multiple of 2 got %d", len(values))
22 | }
23 |
24 | m := make(map[string]interface{}, len(values)/2)
25 | for i := 0; i < len(values); i += 2 {
26 | key, ok := values[i].(string)
27 | if !ok {
28 | return nil, fmt.Errorf("map keys must be strings got %T", values[i])
29 | }
30 | m[key] = values[i+1]
31 | }
32 |
33 | return m, nil
34 | }
35 |
--------------------------------------------------------------------------------
/tools/gen-jscore/main.go:
--------------------------------------------------------------------------------
1 | // Command gen-jscore generates go representations of JavaScript core types file.
2 | package main
3 |
4 | import (
5 | "embed"
6 | "flag"
7 | "fmt"
8 | "log"
9 | "os"
10 | "os/exec"
11 | "strings"
12 | "text/template"
13 |
14 | "gopkg.in/yaml.v3"
15 | )
16 |
17 | //go:embed .gen-jscore.yaml
18 | var configData []byte
19 |
20 | //go:embed templates/*
21 | var templates embed.FS
22 |
23 | // jsType represents JavaScript type to generate.
24 | type jsType struct {
25 | Prototype *prototype `yaml:"prototype"`
26 | Name string `yaml:"name"`
27 | ObjectClass string `yaml:"objectClass"`
28 | ObjectPrototype string `yaml:"objectPrototype"`
29 | Class string `yaml:"class"`
30 | Value string `yaml:"value"`
31 | Properties []property `yaml:"properties"`
32 | Core bool `yaml:"core"`
33 | }
34 |
35 | // BlankConstructor is a default fallback returning false for templates.
36 | func (t jsType) BlankConstructor() bool {
37 | return false
38 | }
39 |
40 | // prototype represents a JavaScript prototype to generate.
41 | type prototype struct {
42 | Value string `yaml:"value"`
43 | ObjectClass string `yaml:"objectClass"`
44 | Prototype string `yaml:"prototype"`
45 | Properties []property `yaml:"properties"`
46 | }
47 |
48 | // Property returns the property with the given name.
49 | func (p prototype) Property(name string) (*property, error) {
50 | for _, prop := range p.Properties {
51 | if prop.Name == name {
52 | return &prop, nil
53 | }
54 | }
55 |
56 | return nil, fmt.Errorf("missing property %q", name)
57 | }
58 |
59 | // property represents a JavaScript property to generate.
60 | type property struct {
61 | Name string `yaml:"name"`
62 | Call string `yaml:"call"`
63 | Mode string `yaml:"mode"`
64 | Value string `yaml:"value"`
65 | Kind string `yaml:"kind"`
66 | Function int `yaml:"function"`
67 | }
68 |
69 | // value represents a JavaScript value to generate a Value creator for.
70 | type value struct {
71 | Name string `yaml:"name"`
72 | Type string `yaml:"type"`
73 | }
74 |
75 | // config represents our configuration.
76 | type config struct {
77 | Types []jsType `yaml:"types"`
78 | Values []value `yaml:"values"`
79 | Log jsType `yaml:"log"`
80 | }
81 |
82 | // Type returns the type for name.
83 | func (c config) Type(name string) (*jsType, error) {
84 | for _, t := range c.Types {
85 | if t.Name == name {
86 | return &t, nil
87 | }
88 | }
89 |
90 | return nil, fmt.Errorf("missing type %q", name)
91 | }
92 |
93 | // generate generates the context file writing the output to filename.
94 | func generate(filename string) (err error) {
95 | var cfg config
96 | if err = yaml.Unmarshal(configData, &cfg); err != nil {
97 | return fmt.Errorf("decode config: %w", err)
98 | }
99 |
100 | tmpl := template.New("base").Funcs(template.FuncMap{
101 | "ucfirst": ucfirst,
102 | "dict": dict,
103 | "contains": strings.Contains,
104 | })
105 |
106 | tmpl, err = tmpl.ParseFS(templates, "templates/*.tmpl")
107 | if err != nil {
108 | return fmt.Errorf("parse templates: %w", err)
109 | }
110 |
111 | output, err := os.Create(filename) //nolint:gosec
112 | if err != nil {
113 | return fmt.Errorf("open output: %w", err)
114 | }
115 |
116 | defer func() {
117 | if errc := output.Close(); err == nil && errc != nil {
118 | err = errc
119 | }
120 | }()
121 |
122 | if err = tmpl.ExecuteTemplate(output, "root.tmpl", cfg); err != nil {
123 | return fmt.Errorf("execute template: %w", err)
124 | }
125 |
126 | cmd := exec.Command("gofmt", "-w", filename)
127 | buf, err := cmd.CombinedOutput()
128 | if err != nil {
129 | return fmt.Errorf("format output %q: %w", string(buf), err)
130 | }
131 |
132 | return nil
133 | }
134 |
135 | func main() {
136 | var filename string
137 | flag.StringVar(&filename, "output", "inline.go", "the filename to write the generated code to")
138 | if err := generate(filename); err != nil {
139 | log.Fatal(err)
140 | }
141 | }
142 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/constructor.tmpl:
--------------------------------------------------------------------------------
1 | {{- with .Prototype.Property "constructor"}}
2 | // {{$.Name}} constructor definition.
3 | rt.global.{{$.Name}}Prototype.property[{{template "name.tmpl" .Name}}] = property{{template "property-value.tmpl" dict "Name" $.Name "Core" true "Property" .}}
4 | {{- end}}
5 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/core-prototype-property.tmpl:
--------------------------------------------------------------------------------
1 | {{/* Expects .(jsType) */}}
2 |
3 | // {{.Name}} prototype property definition.
4 | rt.global.{{.Name}}Prototype.property = {{template "property.tmpl" dict "Name" .Name "BlankConstructor" true "Properties" .Prototype.Properties}}
5 | rt.global.{{.Name}}Prototype.propertyOrder = {{template "property-order.tmpl" .Prototype}}{{/* No newline. */}}
6 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/definition.tmpl:
--------------------------------------------------------------------------------
1 | &object{
2 | runtime: rt,
3 | class: class{{or .Class "Function"}}Name,
4 | objectClass: class{{or .ObjectClass "Object"}},
5 | prototype: rt.global.{{or .ObjectPrototype "Function"}}Prototype,
6 | extensible: true,
7 | {{- if not .Class}}
8 | value: nativeFunctionObject{
9 | name: class{{or .Value .Name}}Name,
10 | call: builtin{{or .Value .Name}},
11 | construct: builtinNew{{or .Value .Name}},
12 | },
13 | {{- end}}
14 | {{- template "property-fields.tmpl" .}}
15 | }{{/* No newline */ -}}
16 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/function.tmpl:
--------------------------------------------------------------------------------
1 | &object{
2 | runtime: rt,
3 | class: classFunctionName,
4 | objectClass: classObject,
5 | prototype: rt.global.FunctionPrototype,
6 | extensible: true,
7 | property: map[string]property{
8 | propertyLength: {
9 | mode: 0,
10 | value: Value{
11 | kind: valueNumber,
12 | value: {{if eq .Property.Function -1}}0{{else}}{{.Property.Function}}{{end}},
13 | },
14 | },
15 | propertyName: {
16 | mode: 0,
17 | value: Value{
18 | kind: valueString,
19 | value: "{{.Property.Name}}",
20 | },
21 | },
22 | },
23 | propertyOrder: []string{
24 | propertyLength,
25 | propertyName,
26 | },
27 | value: nativeFunctionObject{
28 | name: {{template "name.tmpl" .Property.Name}},
29 | call: builtin{{if .Property.Call}}{{.Property.Call}}{{else}}{{.Name}}{{.Property.Name | ucfirst}}{{end}},
30 | },
31 | }{{/* No newline. */ -}}
32 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/global.tmpl:
--------------------------------------------------------------------------------
1 | // {{.Name}} properties.
2 | rt.globalObject.property = {{template "property.tmpl" .}}
3 |
4 | // {{.Name}} property order.
5 | rt.globalObject.propertyOrder = {{template "property-order.tmpl" . -}}
6 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/name.tmpl:
--------------------------------------------------------------------------------
1 | {{- if eq . "length" "prototype" "constructor" -}}
2 | property{{ucfirst .}}
3 | {{- else if eq . "toString" -}}
4 | methodToString
5 | {{- else if eq . "Object" "Function" "Array" "String" "Boolean" "Number" "Math" "Date" "RegExp"
6 | "Error" "EvalError" "TypeError" "RangeError" "ReferenceError" "SyntaxError" "URIError" "JSON" -}}
7 | class{{.}}Name
8 | {{- else -}}
9 | "{{.}}"
10 | {{- end -}}
11 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/property-entry.tmpl:
--------------------------------------------------------------------------------
1 | {{- template "name.tmpl" .Property.Name}}: {{- template "property-value.tmpl" .}},{{/* No newline */ -}}
2 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/property-fields.tmpl:
--------------------------------------------------------------------------------
1 | {{- /* expects .Name and .Properties */ -}}
2 | {{if .Properties}}
3 | property: {{template "property.tmpl" .}},
4 | propertyOrder: {{template "property-order.tmpl" .}},
5 | {{end -}}
6 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/property-order.tmpl:
--------------------------------------------------------------------------------
1 | {{- /* expects .Properties */ -}}
2 | []string{
3 | {{range .Properties -}}
4 | {{template "name.tmpl" .Name}},
5 | {{end -}}
6 | }{{/* No newline */ -}}
7 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/property-value.tmpl:
--------------------------------------------------------------------------------
1 | {{with .Property}} {
2 | mode: {{if .Mode}}{{.Mode}}{{else if or .Function (eq .Name "constructor")}}0o101{{else}}0{{end}},
3 | {{- if eq .Name "constructor" | and $.BlankConstructor}}
4 | value: Value{},
5 | {{- else}}
6 | value: Value{
7 | kind: {{if .Kind}}{{.Kind}}{{else if eq .Name "length"}}valueNumber{{else}}valueObject{{end}},
8 | {{- if .Function}}
9 | value: {{template "function.tmpl" $}},
10 | {{- else if .Value}}
11 | value: {{.Value}},
12 | {{- end}}
13 | },
14 | {{- end}}
15 | }{{end -}}
16 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/property.tmpl:
--------------------------------------------------------------------------------
1 | {{- /* Expects .Name and .Properties */ -}}
2 | map[string]property{
3 | {{range .Properties -}}
4 | {{- /* Skip constructor which is output later. */}}
5 | {{- if eq .Name "constructor" | not -}}
6 | {{template "property-entry.tmpl" dict "Name" $.Name "BlankConstructor" $.BlankConstructor "Property" .}}
7 | {{end -}}
8 | {{end -}}
9 | }{{/* No newline */ -}}
10 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/prototype.tmpl:
--------------------------------------------------------------------------------
1 | {{/* Expects .Name.(jsType.Name), .Prototype and optional .BlankConstructor */}}
2 | {{- with .Prototype}}
3 | // {{$.Name}} prototype.
4 | rt.global.{{$.Name}}Prototype = &object{
5 | runtime: rt,
6 | class: class{{$.Name}}Name,
7 | objectClass: class{{or .ObjectClass "Object"}},
8 | prototype: {{if .Prototype}}rt.global.{{.Prototype}}Prototype{{else}}nil{{end}},
9 | extensible: true,
10 | value: {{or .Value (print "prototypeValue" $.Name)}},
11 | {{- if not $.Core}}
12 | {{- template "property-fields.tmpl" dict "Name" $.Name "BlankConstructor" $.BlankConstructor "Properties" .Properties}}
13 | {{- end}}
14 | }
15 | {{- end -}}
16 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/root.tmpl:
--------------------------------------------------------------------------------
1 | // Code generated by tools/gen-jscore. DO NOT EDIT.
2 |
3 | package otto
4 |
5 | import (
6 | "math"
7 | )
8 |
9 | func (rt *runtime) newContext() {
10 | // Order here is import as definitions depend on each other.
11 | {{- $object := .Type "Object"}}
12 | {{- $function := .Type "Function"}}
13 | {{template "prototype.tmpl" $object}}
14 | {{template "prototype.tmpl" $function}}
15 |
16 | {{- template "core-prototype-property.tmpl" $object}}
17 | {{- template "core-prototype-property.tmpl" $function}}
18 |
19 | {{- template "type.tmpl" $object}}
20 | {{- template "type.tmpl" $function}}
21 |
22 | {{- range .Types}}
23 | {{- if eq .Name "Global"}}
24 | {{template "global.tmpl" . }}
25 | {{- else if not .Core}}
26 | {{template "type.tmpl" .}}
27 | {{- end}}
28 | {{- end}}
29 | }
30 |
31 | func (rt *runtime) newConsole() *object {
32 | return {{template "definition.tmpl" .Log}}
33 | }
34 |
35 | {{range .Values}}
36 | {{template "value.tmpl" .}}
37 | {{- end}}
38 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/type.tmpl:
--------------------------------------------------------------------------------
1 | {{if not .Core | and .Prototype}}
2 | {{template "prototype.tmpl" dict "Name" .Name "Prototype" .Prototype}}
3 | {{- end}}
4 |
5 | // {{.Name}} definition.
6 | rt.global.{{.Name}} = {{template "definition.tmpl" .}}
7 |
8 | {{- if .Prototype}}
9 | {{template "constructor.tmpl" .}}
10 | {{- end}}
11 |
--------------------------------------------------------------------------------
/tools/gen-jscore/templates/value.tmpl:
--------------------------------------------------------------------------------
1 | func {{.Name}}Value(value {{or .Type .Name}}) Value {
2 | return Value{
3 | kind:
4 | {{- if contains .Name "string"}}
5 | valueString
6 | {{- else if contains .Name "bool"}}
7 | valueBoolean
8 | {{- else if contains .Name "object"}}
9 | valueObject
10 | {{- else}}
11 | valueNumber
12 | {{- end}},
13 | value: value,
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/tools/gen-tokens/.gen-tokens.yaml:
--------------------------------------------------------------------------------
1 | tokens:
2 | - group: Control
3 | - name: ILLEGAL
4 | - name: EOF
5 | - name: COMMENT
6 | - name: KEYWORD
7 |
8 | - group: Types
9 | - name: STRING
10 | - name: BOOLEAN
11 | - name: "NULL"
12 | - name: NUMBER
13 | - name: IDENTIFIER
14 |
15 | - group: Maths
16 | - name: PLUS
17 | symbol: "+"
18 | - name: MINUS
19 | symbol: "-"
20 | - name: MULTIPLY
21 | symbol: "*"
22 | - name: SLASH
23 | symbol: "/"
24 | - name: REMAINDER
25 | symbol: "%"
26 |
27 | - group: Logical and bitwise operators
28 | - name: AND
29 | symbol: "&"
30 | - name: OR
31 | symbol: "|"
32 | - name: EXCLUSIVE_OR
33 | symbol: "^"
34 | - name: SHIFT_LEFT
35 | symbol: "<<"
36 | - name: SHIFT_RIGHT
37 | symbol: ">>"
38 | - name: UNSIGNED_SHIFT_RIGHT
39 | symbol: ">>>"
40 | - name: AND_NOT
41 | symbol: "&^"
42 |
43 | - group: Math assignments
44 | - name: ADD_ASSIGN
45 | symbol: "+="
46 | - name: SUBTRACT_ASSIGN
47 | symbol: "-="
48 | - name: MULTIPLY_ASSIGN
49 | symbol: "*="
50 | - name: QUOTIENT_ASSIGN
51 | symbol: "/="
52 | - name: REMAINDER_ASSIGN
53 | symbol: "%="
54 |
55 | - group: Math and bitwise assignments
56 | - name: AND_ASSIGN
57 | symbol: "&="
58 | - name: OR_ASSIGN
59 | symbol: "|="
60 | - name: EXCLUSIVE_OR_ASSIGN
61 | symbol: "^="
62 | - name: SHIFT_LEFT_ASSIGN
63 | symbol: "<<="
64 | - name: SHIFT_RIGHT_ASSIGN
65 | symbol: ">>="
66 | - name: UNSIGNED_SHIFT_RIGHT_ASSIGN
67 | symbol: ">>>="
68 | - name: AND_NOT_ASSIGN
69 | symbol: "&^="
70 |
71 | - group: Logical operators and decrement / increment
72 | - name: LOGICAL_AND
73 | symbol: "&&"
74 | - name: LOGICAL_OR
75 | symbol: "||"
76 | - name: INCREMENT
77 | symbol: "++"
78 | - name: DECREMENT
79 | symbol: "--"
80 |
81 | - group: Comparison operators
82 | - name: EQUAL
83 | symbol: "=="
84 | - name: STRICT_EQUAL
85 | symbol: "==="
86 | - name: LESS
87 | symbol: "<"
88 | - name: GREATER
89 | symbol: ">"
90 | - name: ASSIGN
91 | symbol: "="
92 | - name: NOT
93 | symbol: "!"
94 |
95 | - group: Bitwise not
96 | - name: BITWISE_NOT
97 | symbol: "~"
98 |
99 | - group: Comparison operators
100 | - name: NOT_EQUAL
101 | symbol: "!="
102 | - name: STRICT_NOT_EQUAL
103 | symbol: "!=="
104 | - name: LESS_OR_EQUAL
105 | symbol: "<="
106 | - name: GREATER_OR_EQUAL
107 | symbol: ">="
108 |
109 | - group: Left operators
110 | - name: LEFT_PARENTHESIS
111 | symbol: "("
112 | - name: LEFT_BRACKET
113 | symbol: "["
114 | - name: LEFT_BRACE
115 | symbol: "{"
116 | - name: COMMA
117 | symbol: ","
118 | - name: PERIOD
119 | symbol: "."
120 |
121 | - group: Right operators
122 | - name: RIGHT_PARENTHESIS
123 | symbol: ")"
124 | - name: RIGHT_BRACKET
125 | symbol: "]"
126 | - name: RIGHT_BRACE
127 | symbol: "}"
128 | - name: SEMICOLON
129 | symbol: ";"
130 | - name: COLON
131 | symbol: ":"
132 | - name: QUESTION_MARK
133 | symbol: "?"
134 |
135 | - group: Basic flow - keywords below here
136 | - name: _
137 | - name: IF
138 | - name: IN
139 | - name: DO
140 |
141 | - group: Declarations
142 | - name: VAR
143 | - name: FOR
144 | - name: NEW
145 | - name: TRY
146 |
147 | - group: Advanced flow
148 | - name: THIS
149 | - name: ELSE
150 | - name: CASE
151 | - name: VOID
152 | - name: WITH
153 |
154 | - group: Loops
155 | - name: WHILE
156 | - name: BREAK
157 | - name: CATCH
158 | - name: THROW
159 |
160 | - group: Functions
161 | - name: RETURN
162 | - name: TYPEOF
163 | - name: DELETE
164 | - name: SWITCH
165 |
166 | - group: Fallback identifiers
167 | - name: DEFAULT
168 | - name: FINALLY
169 |
170 | - group: Miscellaneous
171 | - name: FUNCTION
172 | - name: CONTINUE
173 | - name: DEBUGGER
174 |
175 | - group: Instance of
176 | - name: INSTANCEOF
177 |
178 | # Future
179 | - name: const
180 | future: true
181 | - name: class
182 | future: true
183 | - name: enum
184 | future: true
185 | - name: export
186 | future: true
187 | - name: extends
188 | future: true
189 | - name: import
190 | future: true
191 | - name: super
192 | future: true
193 |
194 | # Future Strict items
195 | - name: implements
196 | future: true
197 | strict: true
198 | - name: interface
199 | future: true
200 | strict: true
201 | - name: let
202 | future: true
203 | strict: true
204 | - name: package
205 | future: true
206 | strict: true
207 | - name: private
208 | future: true
209 | strict: true
210 | - name: protected
211 | future: true
212 | strict: true
213 | - name: public
214 | future: true
215 | strict: true
216 | - name: static
217 | future: true
218 | strict: true
219 |
--------------------------------------------------------------------------------
/tools/gen-tokens/main.go:
--------------------------------------------------------------------------------
1 | // Command gen-tokens generates go representations of JavaScript tokens.
2 | package main
3 |
4 | import (
5 | "embed"
6 | "flag"
7 | "fmt"
8 | "log"
9 | "os"
10 | "os/exec"
11 | "strings"
12 | "text/template"
13 |
14 | "gopkg.in/yaml.v3"
15 | )
16 |
17 | //go:embed .gen-tokens.yaml
18 | var configData []byte
19 |
20 | //go:embed templates/*
21 | var templates embed.FS
22 |
23 | // token represents a JavaScript token.
24 | type token struct {
25 | Group string `yaml:"group"`
26 | Name string `yaml:"name"`
27 | Symbol string `yaml:"symbol"`
28 | Future bool `yaml:"future"`
29 | Strict bool `yaml:"strict"`
30 | }
31 |
32 | // config represents our configuration.
33 | type config struct {
34 | Tokens []token `yaml:"tokens"`
35 | }
36 |
37 | // generate generates the context file writing the output to filename.
38 | func generate(filename string) (err error) {
39 | var cfg config
40 | if err = yaml.Unmarshal(configData, &cfg); err != nil {
41 | return fmt.Errorf("decode config: %w", err)
42 | }
43 |
44 | tmpl := template.New("base").Funcs(template.FuncMap{
45 | "toLower": strings.ToLower,
46 | })
47 |
48 | tmpl, err = tmpl.ParseFS(templates, "templates/*.tmpl")
49 | if err != nil {
50 | return fmt.Errorf("parse templates: %w", err)
51 | }
52 |
53 | output, err := os.Create(filename) //nolint:gosec
54 | if err != nil {
55 | return fmt.Errorf("open output: %w", err)
56 | }
57 |
58 | defer func() {
59 | if errc := output.Close(); err == nil && errc != nil {
60 | err = errc
61 | }
62 | }()
63 |
64 | if err = tmpl.ExecuteTemplate(output, "root.tmpl", cfg); err != nil {
65 | return fmt.Errorf("execute template: %w", err)
66 | }
67 |
68 | cmd := exec.Command("gofmt", "-w", filename)
69 | buf, err := cmd.CombinedOutput()
70 | if err != nil {
71 | return fmt.Errorf("format output %q: %w", string(buf), err)
72 | }
73 |
74 | return nil
75 | }
76 |
77 | func main() {
78 | var filename string
79 | flag.StringVar(&filename, "output", "token_const.go", "the filename to write the generated code to")
80 | if err := generate(filename); err != nil {
81 | log.Fatal(err)
82 | }
83 | }
84 |
--------------------------------------------------------------------------------
/tools/gen-tokens/templates/root.tmpl:
--------------------------------------------------------------------------------
1 | // Code generated by tools/gen-tokens. DO NOT EDIT.
2 |
3 | package token
4 |
5 | const (
6 | _ Token = iota
7 | {{range .Tokens}}
8 | {{- if not .Future}}
9 | {{- if .Group}}
10 | // {{.Group}}.
11 | {{- else}}
12 | {{.Name}} {{- if .Symbol}}// {{.Symbol}}{{end}}
13 | {{- end}}
14 | {{- end}}
15 | {{- end}}
16 | )
17 |
18 |
19 | var token2string = [...]string{
20 | {{- $lc := false -}}
21 | {{- range .Tokens -}}
22 | {{if or .Future .Group | not -}}
23 | {{- if eq .Name "_" -}}
24 | {{$lc = true}}
25 | {{- else}}
26 | {{- $symbol := or .Symbol .Name}}
27 | {{.Name}}: "{{if $lc}}{{toLower $symbol}}{{else}}{{$symbol}}{{end}}",
28 | {{- end}}
29 | {{- end -}}
30 | {{- end}}
31 | }
32 |
33 | var keywordTable = map[string]keyword{
34 | {{- $keyword := false -}}
35 | {{range .Tokens}}
36 | {{- /* First keyword follows _ */ -}}
37 | {{- if eq .Name "_"}}{{$keyword = true}}{{continue}}{{end}}
38 | {{- if $keyword}}
39 | {{- if or .Symbol .Group | not}}
40 | "{{toLower .Name}}": {
41 | {{- if .Future}}
42 | token: KEYWORD,
43 | futureKeyword: true,
44 | {{- else}}
45 | token: {{.Name}},
46 | {{- end}}
47 | {{- if .Strict}}
48 | strict: true,
49 | {{- end}}
50 | },
51 | {{- end -}}
52 | {{end -}}
53 | {{- end}}
54 | }
55 |
--------------------------------------------------------------------------------
/type_arguments.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "strconv"
5 | )
6 |
7 | func (rt *runtime) newArgumentsObject(indexOfParameterName []string, stash stasher, length int) *object {
8 | obj := rt.newClassObject("Arguments")
9 |
10 | for index := range indexOfParameterName {
11 | name := strconv.FormatInt(int64(index), 10)
12 | objectDefineOwnProperty(obj, name, property{Value{}, 0o111}, false)
13 | }
14 |
15 | obj.objectClass = classArguments
16 | obj.value = argumentsObject{
17 | indexOfParameterName: indexOfParameterName,
18 | stash: stash,
19 | }
20 |
21 | obj.prototype = rt.global.ObjectPrototype
22 |
23 | obj.defineProperty(propertyLength, intValue(length), 0o101, false)
24 |
25 | return obj
26 | }
27 |
28 | type argumentsObject struct {
29 | stash stasher
30 | indexOfParameterName []string
31 | }
32 |
33 | func (o argumentsObject) clone(c *cloner) argumentsObject {
34 | indexOfParameterName := make([]string, len(o.indexOfParameterName))
35 | copy(indexOfParameterName, o.indexOfParameterName)
36 | return argumentsObject{
37 | indexOfParameterName: indexOfParameterName,
38 | stash: c.stash(o.stash),
39 | }
40 | }
41 |
42 | func (o argumentsObject) get(name string) (Value, bool) {
43 | index := stringToArrayIndex(name)
44 | if index >= 0 && index < int64(len(o.indexOfParameterName)) {
45 | if name = o.indexOfParameterName[index]; name == "" {
46 | return Value{}, false
47 | }
48 | return o.stash.getBinding(name, false), true
49 | }
50 | return Value{}, false
51 | }
52 |
53 | func (o argumentsObject) put(name string, value Value) {
54 | index := stringToArrayIndex(name)
55 | name = o.indexOfParameterName[index]
56 | o.stash.setBinding(name, value, false)
57 | }
58 |
59 | func (o argumentsObject) delete(name string) {
60 | index := stringToArrayIndex(name)
61 | o.indexOfParameterName[index] = ""
62 | }
63 |
64 | func argumentsGet(obj *object, name string) Value {
65 | if value, exists := obj.value.(argumentsObject).get(name); exists {
66 | return value
67 | }
68 | return objectGet(obj, name)
69 | }
70 |
71 | func argumentsGetOwnProperty(obj *object, name string) *property {
72 | prop := objectGetOwnProperty(obj, name)
73 | if value, exists := obj.value.(argumentsObject).get(name); exists {
74 | prop.value = value
75 | }
76 | return prop
77 | }
78 |
79 | func argumentsDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
80 | if _, exists := obj.value.(argumentsObject).get(name); exists {
81 | if !objectDefineOwnProperty(obj, name, descriptor, false) {
82 | return obj.runtime.typeErrorResult(throw)
83 | }
84 | if value, valid := descriptor.value.(Value); valid {
85 | obj.value.(argumentsObject).put(name, value)
86 | }
87 | return true
88 | }
89 | return objectDefineOwnProperty(obj, name, descriptor, throw)
90 | }
91 |
92 | func argumentsDelete(obj *object, name string, throw bool) bool {
93 | if !objectDelete(obj, name, throw) {
94 | return false
95 | }
96 | if _, exists := obj.value.(argumentsObject).get(name); exists {
97 | obj.value.(argumentsObject).delete(name)
98 | }
99 | return true
100 | }
101 |
--------------------------------------------------------------------------------
/type_array.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "strconv"
5 | )
6 |
7 | func (rt *runtime) newArrayObject(length uint32) *object {
8 | obj := rt.newObject()
9 | obj.class = classArrayName
10 | obj.defineProperty(propertyLength, uint32Value(length), 0o100, false)
11 | obj.objectClass = classArray
12 | return obj
13 | }
14 |
15 | func isArray(obj *object) bool {
16 | if obj == nil {
17 | return false
18 | }
19 |
20 | switch obj.class {
21 | case classArrayName, classGoArrayName, classGoSliceName:
22 | return true
23 | default:
24 | return false
25 | }
26 | }
27 |
28 | func objectLength(obj *object) uint32 {
29 | if obj == nil {
30 | return 0
31 | }
32 | switch obj.class {
33 | case classArrayName:
34 | return obj.get(propertyLength).value.(uint32)
35 | case classStringName:
36 | return uint32(obj.get(propertyLength).value.(int))
37 | case classGoArrayName, classGoSliceName:
38 | return uint32(obj.get(propertyLength).value.(int))
39 | }
40 | return 0
41 | }
42 |
43 | func arrayUint32(rt *runtime, value Value) uint32 {
44 | nm := value.number()
45 | if nm.kind != numberInteger || !isUint32(nm.int64) {
46 | // FIXME
47 | panic(rt.panicRangeError())
48 | }
49 | return uint32(nm.int64)
50 | }
51 |
52 | func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
53 | lengthProperty := obj.getOwnProperty(propertyLength)
54 | lengthValue, valid := lengthProperty.value.(Value)
55 | if !valid {
56 | panic("Array.length != Value{}")
57 | }
58 |
59 | reject := func(reason string) bool {
60 | if throw {
61 | panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %s", reason))
62 | }
63 | return false
64 | }
65 | length := lengthValue.value.(uint32)
66 | if name == propertyLength {
67 | if descriptor.value == nil {
68 | return objectDefineOwnProperty(obj, name, descriptor, throw)
69 | }
70 | newLengthValue, isValue := descriptor.value.(Value)
71 | if !isValue {
72 | panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %q is not a value", descriptor.value))
73 | }
74 | newLength := arrayUint32(obj.runtime, newLengthValue)
75 | descriptor.value = uint32Value(newLength)
76 | if newLength > length {
77 | return objectDefineOwnProperty(obj, name, descriptor, throw)
78 | }
79 | if !lengthProperty.writable() {
80 | return reject("property length for not writable")
81 | }
82 | newWritable := true
83 | if descriptor.mode&0o700 == 0 {
84 | // If writable is off
85 | newWritable = false
86 | descriptor.mode |= 0o100
87 | }
88 | if !objectDefineOwnProperty(obj, name, descriptor, throw) {
89 | return false
90 | }
91 | for newLength < length {
92 | length--
93 | if !obj.delete(strconv.FormatInt(int64(length), 10), false) {
94 | descriptor.value = uint32Value(length + 1)
95 | if !newWritable {
96 | descriptor.mode &= 0o077
97 | }
98 | objectDefineOwnProperty(obj, name, descriptor, false)
99 | return reject("delete failed")
100 | }
101 | }
102 | if !newWritable {
103 | descriptor.mode &= 0o077
104 | objectDefineOwnProperty(obj, name, descriptor, false)
105 | }
106 | } else if index := stringToArrayIndex(name); index >= 0 {
107 | if index >= int64(length) && !lengthProperty.writable() {
108 | return reject("property length not writable")
109 | }
110 | if !objectDefineOwnProperty(obj, strconv.FormatInt(index, 10), descriptor, false) {
111 | return reject("Object.DefineOwnProperty failed")
112 | }
113 | if index >= int64(length) {
114 | lengthProperty.value = uint32Value(uint32(index + 1))
115 | objectDefineOwnProperty(obj, propertyLength, *lengthProperty, false)
116 | return true
117 | }
118 | }
119 | return objectDefineOwnProperty(obj, name, descriptor, throw)
120 | }
121 |
--------------------------------------------------------------------------------
/type_boolean.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | func (rt *runtime) newBooleanObject(value Value) *object {
4 | return rt.newPrimitiveObject(classBooleanName, boolValue(value.bool()))
5 | }
6 |
--------------------------------------------------------------------------------
/type_error.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | func (rt *runtime) newErrorObject(name string, message Value, stackFramesToPop int) *object {
4 | obj := rt.newClassObject(classErrorName)
5 | if message.IsDefined() {
6 | err := newError(rt, name, stackFramesToPop, "%s", message.string())
7 | obj.defineProperty("message", err.messageValue(), 0o111, false)
8 | obj.value = err
9 | } else {
10 | obj.value = newError(rt, name, stackFramesToPop)
11 | }
12 |
13 | obj.defineOwnProperty("stack", property{
14 | value: propertyGetSet{
15 | rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value {
16 | return stringValue(obj.value.(ottoError).formatWithStack())
17 | }),
18 | &nilGetSetObject,
19 | },
20 | mode: modeConfigureMask & modeOnMask,
21 | }, false)
22 |
23 | return obj
24 | }
25 |
26 | func (rt *runtime) newErrorObjectError(err ottoError) *object {
27 | obj := rt.newClassObject(classErrorName)
28 | obj.defineProperty("message", err.messageValue(), 0o111, false)
29 | obj.value = err
30 | switch err.name {
31 | case "EvalError":
32 | obj.prototype = rt.global.EvalErrorPrototype
33 | case "TypeError":
34 | obj.prototype = rt.global.TypeErrorPrototype
35 | case "RangeError":
36 | obj.prototype = rt.global.RangeErrorPrototype
37 | case "ReferenceError":
38 | obj.prototype = rt.global.ReferenceErrorPrototype
39 | case "SyntaxError":
40 | obj.prototype = rt.global.SyntaxErrorPrototype
41 | case "URIError":
42 | obj.prototype = rt.global.URIErrorPrototype
43 | default:
44 | obj.prototype = rt.global.ErrorPrototype
45 | }
46 |
47 | obj.defineOwnProperty("stack", property{
48 | value: propertyGetSet{
49 | rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value {
50 | return stringValue(obj.value.(ottoError).formatWithStack())
51 | }),
52 | &nilGetSetObject,
53 | },
54 | mode: modeConfigureMask & modeOnMask,
55 | }, false)
56 |
57 | return obj
58 | }
59 |
--------------------------------------------------------------------------------
/type_go_array.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "reflect"
5 | "strconv"
6 | )
7 |
8 | func (rt *runtime) newGoArrayObject(value reflect.Value) *object {
9 | o := rt.newObject()
10 | o.class = classGoArrayName
11 | o.objectClass = classGoArray
12 | o.value = newGoArrayObject(value)
13 | return o
14 | }
15 |
16 | type goArrayObject struct {
17 | value reflect.Value
18 | writable bool
19 | propertyMode propertyMode
20 | }
21 |
22 | func newGoArrayObject(value reflect.Value) *goArrayObject {
23 | writable := value.Kind() == reflect.Ptr || value.CanSet() // The Array is addressable (like a Slice)
24 | mode := propertyMode(0o010)
25 | if writable {
26 | mode = 0o110
27 | }
28 |
29 | return &goArrayObject{
30 | value: value,
31 | writable: writable,
32 | propertyMode: mode,
33 | }
34 | }
35 |
36 | func (o goArrayObject) getValue(name string) (reflect.Value, bool) { //nolint:unused
37 | if index, err := strconv.ParseInt(name, 10, 64); err != nil {
38 | v, ok := o.getValueIndex(index)
39 | if ok {
40 | return v, ok
41 | }
42 | }
43 |
44 | if m := o.value.MethodByName(name); m.IsValid() {
45 | return m, true
46 | }
47 |
48 | return reflect.Value{}, false
49 | }
50 |
51 | func (o goArrayObject) getValueIndex(index int64) (reflect.Value, bool) {
52 | value := reflect.Indirect(o.value)
53 | if index < int64(value.Len()) {
54 | return value.Index(int(index)), true
55 | }
56 |
57 | return reflect.Value{}, false
58 | }
59 |
60 | func (o goArrayObject) setValue(index int64, value Value) bool {
61 | indexValue, exists := o.getValueIndex(index)
62 | if !exists {
63 | return false
64 | }
65 | reflectValue, err := value.toReflectValue(reflect.Indirect(o.value).Type().Elem())
66 | if err != nil {
67 | panic(err)
68 | }
69 | indexValue.Set(reflectValue)
70 | return true
71 | }
72 |
73 | func goArrayGetOwnProperty(obj *object, name string) *property {
74 | // length
75 | if name == propertyLength {
76 | return &property{
77 | value: toValue(reflect.Indirect(obj.value.(*goArrayObject).value).Len()),
78 | mode: 0,
79 | }
80 | }
81 |
82 | // .0, .1, .2, ...
83 | if index := stringToArrayIndex(name); index >= 0 {
84 | goObj := obj.value.(*goArrayObject)
85 | value := Value{}
86 | reflectValue, exists := goObj.getValueIndex(index)
87 | if exists {
88 | value = obj.runtime.toValue(reflectValue.Interface())
89 | }
90 | return &property{
91 | value: value,
92 | mode: goObj.propertyMode,
93 | }
94 | }
95 |
96 | if method := obj.value.(*goArrayObject).value.MethodByName(name); method.IsValid() {
97 | return &property{
98 | obj.runtime.toValue(method.Interface()),
99 | 0o110,
100 | }
101 | }
102 |
103 | return objectGetOwnProperty(obj, name)
104 | }
105 |
106 | func goArrayEnumerate(obj *object, all bool, each func(string) bool) {
107 | goObj := obj.value.(*goArrayObject)
108 | // .0, .1, .2, ...
109 |
110 | for index, length := 0, goObj.value.Len(); index < length; index++ {
111 | name := strconv.FormatInt(int64(index), 10)
112 | if !each(name) {
113 | return
114 | }
115 | }
116 |
117 | objectEnumerate(obj, all, each)
118 | }
119 |
120 | func goArrayDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
121 | if name == propertyLength {
122 | return obj.runtime.typeErrorResult(throw)
123 | } else if index := stringToArrayIndex(name); index >= 0 {
124 | goObj := obj.value.(*goArrayObject)
125 | if goObj.writable {
126 | if obj.value.(*goArrayObject).setValue(index, descriptor.value.(Value)) {
127 | return true
128 | }
129 | }
130 | return obj.runtime.typeErrorResult(throw)
131 | }
132 | return objectDefineOwnProperty(obj, name, descriptor, throw)
133 | }
134 |
135 | func goArrayDelete(obj *object, name string, throw bool) bool {
136 | // length
137 | if name == propertyLength {
138 | return obj.runtime.typeErrorResult(throw)
139 | }
140 |
141 | // .0, .1, .2, ...
142 | index := stringToArrayIndex(name)
143 | if index >= 0 {
144 | goObj := obj.value.(*goArrayObject)
145 | if goObj.writable {
146 | indexValue, exists := goObj.getValueIndex(index)
147 | if exists {
148 | indexValue.Set(reflect.Zero(reflect.Indirect(goObj.value).Type().Elem()))
149 | return true
150 | }
151 | }
152 | return obj.runtime.typeErrorResult(throw)
153 | }
154 |
155 | return obj.delete(name, throw)
156 | }
157 |
--------------------------------------------------------------------------------
/type_go_map.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "reflect"
5 | )
6 |
7 | func (rt *runtime) newGoMapObject(value reflect.Value) *object {
8 | obj := rt.newObject()
9 | obj.class = classObjectName // TODO Should this be something else?
10 | obj.objectClass = classGoMap
11 | obj.value = newGoMapObject(value)
12 | return obj
13 | }
14 |
15 | type goMapObject struct {
16 | keyType reflect.Type
17 | valueType reflect.Type
18 | value reflect.Value
19 | }
20 |
21 | func newGoMapObject(value reflect.Value) *goMapObject {
22 | if value.Kind() != reflect.Map {
23 | dbgf("%/panic//%@: %v != reflect.Map", value.Kind())
24 | }
25 | return &goMapObject{
26 | value: value,
27 | keyType: value.Type().Key(),
28 | valueType: value.Type().Elem(),
29 | }
30 | }
31 |
32 | func (o goMapObject) toKey(name string) reflect.Value {
33 | reflectValue, err := stringToReflectValue(name, o.keyType.Kind())
34 | if err != nil {
35 | panic(err)
36 | }
37 | return reflectValue
38 | }
39 |
40 | func (o goMapObject) toValue(value Value) reflect.Value {
41 | reflectValue, err := value.toReflectValue(o.valueType)
42 | if err != nil {
43 | panic(err)
44 | }
45 | return reflectValue
46 | }
47 |
48 | func goMapGetOwnProperty(obj *object, name string) *property {
49 | goObj := obj.value.(*goMapObject)
50 |
51 | // an error here means that the key referenced by `name` could not possibly
52 | // be a property of this object, so it should be safe to ignore this error
53 | //
54 | // TODO: figure out if any cases from
55 | // https://go.dev/ref/spec#Comparison_operators meet the criteria of 1)
56 | // being possible to represent as a string, 2) being possible to reconstruct
57 | // from a string, and 3) having a meaningful failure case in this context
58 | // other than "key does not exist"
59 | key, err := stringToReflectValue(name, goObj.keyType.Kind())
60 | if err != nil {
61 | return nil
62 | }
63 |
64 | value := goObj.value.MapIndex(key)
65 | if value.IsValid() {
66 | return &property{obj.runtime.toValue(value.Interface()), 0o111}
67 | }
68 |
69 | // Other methods
70 | if method := obj.value.(*goMapObject).value.MethodByName(name); method.IsValid() {
71 | return &property{
72 | value: obj.runtime.toValue(method.Interface()),
73 | mode: 0o110,
74 | }
75 | }
76 |
77 | return nil
78 | }
79 |
80 | func goMapEnumerate(obj *object, all bool, each func(string) bool) {
81 | goObj := obj.value.(*goMapObject)
82 | keys := goObj.value.MapKeys()
83 | for _, key := range keys {
84 | if !each(toValue(key).String()) {
85 | return
86 | }
87 | }
88 | }
89 |
90 | func goMapDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
91 | goObj := obj.value.(*goMapObject)
92 | // TODO ...or 0222
93 | if descriptor.mode != 0o111 {
94 | return obj.runtime.typeErrorResult(throw)
95 | }
96 | if !descriptor.isDataDescriptor() {
97 | return obj.runtime.typeErrorResult(throw)
98 | }
99 | goObj.value.SetMapIndex(goObj.toKey(name), goObj.toValue(descriptor.value.(Value)))
100 | return true
101 | }
102 |
103 | func goMapDelete(obj *object, name string, throw bool) bool {
104 | goObj := obj.value.(*goMapObject)
105 | goObj.value.SetMapIndex(goObj.toKey(name), reflect.Value{})
106 | // FIXME
107 | return true
108 | }
109 |
--------------------------------------------------------------------------------
/type_go_map_test.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "sort"
5 | "strconv"
6 | "testing"
7 | )
8 |
9 | type GoMapTest map[string]int
10 |
11 | func (s GoMapTest) Join() string {
12 | joinedStr := ""
13 |
14 | // Ordering the map takes some effort
15 | // because map iterators in golang are unordered by definition.
16 | // So we need to extract keys, sort them, and then generate K/V pairs
17 | // All of this is meant to ensure that the test is predictable.
18 | keys := make([]string, len(s))
19 | i := 0
20 | for key := range s {
21 | keys[i] = key
22 | i++
23 | }
24 |
25 | sort.Strings(keys)
26 |
27 | for _, key := range keys {
28 | joinedStr += key + ": " + strconv.Itoa(s[key]) + " "
29 | }
30 | return joinedStr
31 | }
32 |
33 | func TestGoMap(t *testing.T) {
34 | tt(t, func() {
35 | test, vm := test()
36 | vm.Set("TestMap", GoMapTest{"one": 1, "two": 2, "three": 3})
37 | is(test(`TestMap["one"]`).export(), 1)
38 | is(test(`TestMap.Join()`).export(), "one: 1 three: 3 two: 2 ")
39 | })
40 | }
41 |
--------------------------------------------------------------------------------
/type_go_slice.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "reflect"
5 | "strconv"
6 | )
7 |
8 | func (rt *runtime) newGoSliceObject(value reflect.Value) *object {
9 | o := rt.newObject()
10 | o.class = classGoSliceName
11 | o.objectClass = classGoSlice
12 | o.value = newGoSliceObject(value)
13 | return o
14 | }
15 |
16 | type goSliceObject struct {
17 | value reflect.Value
18 | }
19 |
20 | func newGoSliceObject(value reflect.Value) *goSliceObject {
21 | return &goSliceObject{
22 | value: value,
23 | }
24 | }
25 |
26 | func (o goSliceObject) getValue(index int64) (reflect.Value, bool) {
27 | if index < int64(o.value.Len()) {
28 | return o.value.Index(int(index)), true
29 | }
30 | return reflect.Value{}, false
31 | }
32 |
33 | func (o *goSliceObject) setLength(value Value) {
34 | want, err := value.ToInteger()
35 | if err != nil {
36 | panic(err)
37 | }
38 |
39 | wantInt := int(want)
40 | switch {
41 | case wantInt == o.value.Len():
42 | // No change needed.
43 | case wantInt < o.value.Cap():
44 | // Fits in current capacity.
45 | o.value.SetLen(wantInt)
46 | default:
47 | // Needs expanding.
48 | newSlice := reflect.MakeSlice(o.value.Type(), wantInt, wantInt)
49 | reflect.Copy(newSlice, o.value)
50 | o.value = newSlice
51 | }
52 | }
53 |
54 | func (o *goSliceObject) setValue(index int64, value Value) bool {
55 | reflectValue, err := value.toReflectValue(o.value.Type().Elem())
56 | if err != nil {
57 | panic(err)
58 | }
59 |
60 | indexValue, exists := o.getValue(index)
61 | if !exists {
62 | if int64(o.value.Len()) == index {
63 | // Trying to append e.g. slice.push(...), allow it.
64 | o.value = reflect.Append(o.value, reflectValue)
65 | return true
66 | }
67 | return false
68 | }
69 |
70 | indexValue.Set(reflectValue)
71 | return true
72 | }
73 |
74 | func goSliceGetOwnProperty(obj *object, name string) *property {
75 | // length
76 | if name == propertyLength {
77 | return &property{
78 | value: toValue(obj.value.(*goSliceObject).value.Len()),
79 | mode: 0o110,
80 | }
81 | }
82 |
83 | // .0, .1, .2, ...
84 | if index := stringToArrayIndex(name); index >= 0 {
85 | value := Value{}
86 | reflectValue, exists := obj.value.(*goSliceObject).getValue(index)
87 | if exists {
88 | value = obj.runtime.toValue(reflectValue.Interface())
89 | }
90 | return &property{
91 | value: value,
92 | mode: 0o110,
93 | }
94 | }
95 |
96 | // Other methods
97 | if method := obj.value.(*goSliceObject).value.MethodByName(name); method.IsValid() {
98 | return &property{
99 | value: obj.runtime.toValue(method.Interface()),
100 | mode: 0o110,
101 | }
102 | }
103 |
104 | return objectGetOwnProperty(obj, name)
105 | }
106 |
107 | func goSliceEnumerate(obj *object, all bool, each func(string) bool) {
108 | goObj := obj.value.(*goSliceObject)
109 | // .0, .1, .2, ...
110 |
111 | for index, length := 0, goObj.value.Len(); index < length; index++ {
112 | name := strconv.FormatInt(int64(index), 10)
113 | if !each(name) {
114 | return
115 | }
116 | }
117 |
118 | objectEnumerate(obj, all, each)
119 | }
120 |
121 | func goSliceDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
122 | if name == propertyLength {
123 | obj.value.(*goSliceObject).setLength(descriptor.value.(Value))
124 | return true
125 | } else if index := stringToArrayIndex(name); index >= 0 {
126 | if obj.value.(*goSliceObject).setValue(index, descriptor.value.(Value)) {
127 | return true
128 | }
129 | return obj.runtime.typeErrorResult(throw)
130 | }
131 | return objectDefineOwnProperty(obj, name, descriptor, throw)
132 | }
133 |
134 | func goSliceDelete(obj *object, name string, throw bool) bool {
135 | // length
136 | if name == propertyLength {
137 | return obj.runtime.typeErrorResult(throw)
138 | }
139 |
140 | // .0, .1, .2, ...
141 | index := stringToArrayIndex(name)
142 | if index >= 0 {
143 | goObj := obj.value.(*goSliceObject)
144 | indexValue, exists := goObj.getValue(index)
145 | if exists {
146 | indexValue.Set(reflect.Zero(goObj.value.Type().Elem()))
147 | return true
148 | }
149 | return obj.runtime.typeErrorResult(throw)
150 | }
151 |
152 | return obj.delete(name, throw)
153 | }
154 |
--------------------------------------------------------------------------------
/type_go_slice_test.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import "testing"
4 |
5 | type GoSliceTest []int
6 |
7 | func (s GoSliceTest) Sum() int {
8 | sum := 0
9 | for _, v := range s {
10 | sum += v
11 | }
12 | return sum
13 | }
14 |
15 | func TestGoSlice(t *testing.T) {
16 | tt(t, func() {
17 | test, vm := test()
18 | vm.Set("TestSlice", GoSliceTest{1, 2, 3})
19 | is(test(`TestSlice.length`).export(), 3)
20 | is(test(`TestSlice[1]`).export(), 2)
21 | is(test(`TestSlice.Sum()`).export(), 6)
22 | })
23 | }
24 |
--------------------------------------------------------------------------------
/type_go_struct.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "encoding/json"
5 | "reflect"
6 | )
7 |
8 | // FIXME Make a note about not being able to modify a struct unless it was
9 | // passed as a pointer-to: &struct{ ... }
10 | // This seems to be a limitation of the reflect package.
11 | // This goes for the other Go constructs too.
12 | // I guess we could get around it by either:
13 | // 1. Creating a new struct every time
14 | // 2. Creating an addressable? struct in the constructor
15 |
16 | func (rt *runtime) newGoStructObject(value reflect.Value) *object {
17 | o := rt.newObject()
18 | o.class = classObjectName // TODO Should this be something else?
19 | o.objectClass = classGoStruct
20 | o.value = newGoStructObject(value)
21 | return o
22 | }
23 |
24 | type goStructObject struct {
25 | value reflect.Value
26 | }
27 |
28 | func newGoStructObject(value reflect.Value) *goStructObject {
29 | if reflect.Indirect(value).Kind() != reflect.Struct {
30 | dbgf("%/panic//%@: %v != reflect.Struct", value.Kind())
31 | }
32 | return &goStructObject{
33 | value: value,
34 | }
35 | }
36 |
37 | func (o goStructObject) getValue(name string) reflect.Value {
38 | if idx := fieldIndexByName(reflect.Indirect(o.value).Type(), name); len(idx) > 0 {
39 | return reflect.Indirect(o.value).FieldByIndex(idx)
40 | }
41 |
42 | if validGoStructName(name) {
43 | // Do not reveal hidden or unexported fields.
44 | if field := reflect.Indirect(o.value).FieldByName(name); field.IsValid() {
45 | return field
46 | }
47 |
48 | if method := o.value.MethodByName(name); method.IsValid() {
49 | return method
50 | }
51 | }
52 |
53 | return reflect.Value{}
54 | }
55 |
56 | func (o goStructObject) fieldIndex(name string) []int { //nolint:unused
57 | return fieldIndexByName(reflect.Indirect(o.value).Type(), name)
58 | }
59 |
60 | func (o goStructObject) method(name string) (reflect.Method, bool) { //nolint:unused
61 | return reflect.Indirect(o.value).Type().MethodByName(name)
62 | }
63 |
64 | func (o goStructObject) setValue(rt *runtime, name string, value Value) bool {
65 | if idx := fieldIndexByName(reflect.Indirect(o.value).Type(), name); len(idx) == 0 {
66 | return false
67 | }
68 |
69 | fieldValue := o.getValue(name)
70 | converted, err := rt.convertCallParameter(value, fieldValue.Type())
71 | if err != nil {
72 | panic(rt.panicTypeError("Object.setValue convertCallParameter: %s", err))
73 | }
74 | fieldValue.Set(converted)
75 |
76 | return true
77 | }
78 |
79 | func goStructGetOwnProperty(obj *object, name string) *property {
80 | goObj := obj.value.(*goStructObject)
81 | value := goObj.getValue(name)
82 | if value.IsValid() {
83 | return &property{obj.runtime.toValue(value), 0o110}
84 | }
85 |
86 | return objectGetOwnProperty(obj, name)
87 | }
88 |
89 | func validGoStructName(name string) bool {
90 | if name == "" {
91 | return false
92 | }
93 | return 'A' <= name[0] && name[0] <= 'Z' // TODO What about Unicode?
94 | }
95 |
96 | func goStructEnumerate(obj *object, all bool, each func(string) bool) {
97 | goObj := obj.value.(*goStructObject)
98 |
99 | // Enumerate fields
100 | for index := range reflect.Indirect(goObj.value).NumField() {
101 | name := reflect.Indirect(goObj.value).Type().Field(index).Name
102 | if validGoStructName(name) {
103 | if !each(name) {
104 | return
105 | }
106 | }
107 | }
108 |
109 | // Enumerate methods
110 | for index := range goObj.value.NumMethod() {
111 | name := goObj.value.Type().Method(index).Name
112 | if validGoStructName(name) {
113 | if !each(name) {
114 | return
115 | }
116 | }
117 | }
118 |
119 | objectEnumerate(obj, all, each)
120 | }
121 |
122 | func goStructCanPut(obj *object, name string) bool {
123 | goObj := obj.value.(*goStructObject)
124 | value := goObj.getValue(name)
125 | if value.IsValid() {
126 | return true
127 | }
128 |
129 | return objectCanPut(obj, name)
130 | }
131 |
132 | func goStructPut(obj *object, name string, value Value, throw bool) {
133 | goObj := obj.value.(*goStructObject)
134 | if goObj.setValue(obj.runtime, name, value) {
135 | return
136 | }
137 |
138 | objectPut(obj, name, value, throw)
139 | }
140 |
141 | func goStructMarshalJSON(obj *object) json.Marshaler {
142 | goObj := obj.value.(*goStructObject)
143 | goValue := reflect.Indirect(goObj.value).Interface()
144 | marshaler, _ := goValue.(json.Marshaler)
145 | return marshaler
146 | }
147 |
--------------------------------------------------------------------------------
/type_go_struct_test.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | func TestGoStructEmbeddedFields(t *testing.T) {
8 | type A struct {
9 | A1 string `json:"a1"`
10 | A2 string `json:"a2"`
11 | A3 string `json:"a3"`
12 | }
13 |
14 | type B struct {
15 | A
16 | B1 string `json:"b1"`
17 | }
18 |
19 | tt(t, func() {
20 | test, vm := test()
21 |
22 | vm.Set("v", B{A{"a1", "a2", "a3"}, "b1"})
23 |
24 | test(`[v.a1,v.a2,v.a3,v.b1]`, "a1,a2,a3,b1")
25 | })
26 | }
27 |
28 | func TestGoStructNilBoolPointerField(t *testing.T) {
29 | type S struct {
30 | C interface{} `json:"c"`
31 | B *bool `json:"b"`
32 | A int `json:"a"`
33 | }
34 |
35 | tt(t, func() {
36 | test, vm := test()
37 | vm.Set("s", S{A: 1, B: nil, C: nil})
38 | test(`'a' in s`, true)
39 | test(`typeof s.a`, "number")
40 | test(`'b' in s`, true)
41 | test(`typeof s.b`, "undefined")
42 | test(`'c' in s`, true)
43 | test(`typeof s.c`, "undefined")
44 | })
45 | }
46 |
47 | func TestGoStructError(t *testing.T) {
48 | type S1 struct {
49 | A string `json:"a"`
50 | B string `json:"b"`
51 | }
52 |
53 | type S2 struct {
54 | B S1 `json:"b"`
55 | A []S1 `json:"a"`
56 | }
57 |
58 | type S3 struct {
59 | B S2 `json:"b"`
60 | A []S2 `json:"a"`
61 | }
62 |
63 | tt(t, func() {
64 | test, vm := test()
65 | vm.Set("fn", func(s *S3) string { return "cool" })
66 | test(
67 | `(function() { try { fn({a:[{a:[{c:"x"}]}]}) } catch (ex) { return ex } })()`,
68 | `TypeError: can't convert to *otto.S3: couldn't convert property "a" of otto.S3: couldn't convert element 0 of []otto.S2: couldn't convert property "a" of otto.S2: couldn't convert element 0 of []otto.S1: can't convert property "c" of otto.S1: field does not exist`,
69 | )
70 | })
71 | }
72 |
--------------------------------------------------------------------------------
/type_number.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | func (rt *runtime) newNumberObject(value Value) *object {
4 | return rt.newPrimitiveObject(classNumberName, value.numberValue())
5 | }
6 |
--------------------------------------------------------------------------------
/type_reference.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | type referencer interface {
4 | invalid() bool // IsUnresolvableReference
5 | getValue() Value // getValue
6 | putValue(value Value) string // PutValue
7 | delete() bool
8 | }
9 |
10 | // PropertyReference
11 |
12 | type propertyReference struct {
13 | base *object
14 | runtime *runtime
15 | name string
16 | at at
17 | strict bool
18 | }
19 |
20 | func newPropertyReference(rt *runtime, base *object, name string, strict bool, atv at) *propertyReference {
21 | return &propertyReference{
22 | runtime: rt,
23 | name: name,
24 | strict: strict,
25 | base: base,
26 | at: atv,
27 | }
28 | }
29 |
30 | func (pr *propertyReference) invalid() bool {
31 | return pr.base == nil
32 | }
33 |
34 | func (pr *propertyReference) getValue() Value {
35 | if pr.base == nil {
36 | panic(pr.runtime.panicReferenceError("'%s' is not defined", pr.name, pr.at))
37 | }
38 | return pr.base.get(pr.name)
39 | }
40 |
41 | func (pr *propertyReference) putValue(value Value) string {
42 | if pr.base == nil {
43 | return pr.name
44 | }
45 | pr.base.put(pr.name, value, pr.strict)
46 | return ""
47 | }
48 |
49 | func (pr *propertyReference) delete() bool {
50 | if pr.base == nil {
51 | // TODO Throw an error if strict
52 | return true
53 | }
54 | return pr.base.delete(pr.name, pr.strict)
55 | }
56 |
57 | type stashReference struct {
58 | base stasher
59 | name string
60 | strict bool
61 | }
62 |
63 | func (sr *stashReference) invalid() bool {
64 | return false // The base (an environment) will never be nil
65 | }
66 |
67 | func (sr *stashReference) getValue() Value {
68 | return sr.base.getBinding(sr.name, sr.strict)
69 | }
70 |
71 | func (sr *stashReference) putValue(value Value) string {
72 | sr.base.setValue(sr.name, value, sr.strict)
73 | return ""
74 | }
75 |
76 | func (sr *stashReference) delete() bool {
77 | if sr.base == nil {
78 | // This should never be reached, but just in case
79 | return false
80 | }
81 | return sr.base.deleteBinding(sr.name)
82 | }
83 |
84 | // getIdentifierReference.
85 | func getIdentifierReference(rt *runtime, stash stasher, name string, strict bool, atv at) referencer {
86 | if stash == nil {
87 | return newPropertyReference(rt, nil, name, strict, atv)
88 | }
89 | if stash.hasBinding(name) {
90 | return stash.newReference(name, strict, atv)
91 | }
92 | return getIdentifierReference(rt, stash.outer(), name, strict, atv)
93 | }
94 |
--------------------------------------------------------------------------------
/type_regexp.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "fmt"
5 | "regexp"
6 |
7 | "github.com/robertkrimen/otto/parser"
8 | )
9 |
10 | type regExpObject struct {
11 | regularExpression *regexp.Regexp
12 | source string
13 | flags string
14 | global bool
15 | ignoreCase bool
16 | multiline bool
17 | }
18 |
19 | func (rt *runtime) newRegExpObject(pattern string, flags string) *object {
20 | o := rt.newObject()
21 | o.class = classRegExpName
22 |
23 | global := false
24 | ignoreCase := false
25 | multiline := false
26 | re2flags := ""
27 |
28 | // TODO Maybe clean up the panicking here... TypeError, SyntaxError, ?
29 |
30 | for _, chr := range flags {
31 | switch chr {
32 | case 'g':
33 | if global {
34 | panic(rt.panicSyntaxError("newRegExpObject: %s %s", pattern, flags))
35 | }
36 | global = true
37 | case 'm':
38 | if multiline {
39 | panic(rt.panicSyntaxError("newRegExpObject: %s %s", pattern, flags))
40 | }
41 | multiline = true
42 | re2flags += "m"
43 | case 'i':
44 | if ignoreCase {
45 | panic(rt.panicSyntaxError("newRegExpObject: %s %s", pattern, flags))
46 | }
47 | ignoreCase = true
48 | re2flags += "i"
49 | }
50 | }
51 |
52 | re2pattern, err := parser.TransformRegExp(pattern)
53 | if err != nil {
54 | panic(rt.panicTypeError("Invalid regular expression: %s", err.Error()))
55 | }
56 | if len(re2flags) > 0 {
57 | re2pattern = fmt.Sprintf("(?%s:%s)", re2flags, re2pattern)
58 | }
59 |
60 | regularExpression, err := regexp.Compile(re2pattern)
61 | if err != nil {
62 | panic(rt.panicSyntaxError("Invalid regular expression: %s", err.Error()[22:]))
63 | }
64 |
65 | o.value = regExpObject{
66 | regularExpression: regularExpression,
67 | global: global,
68 | ignoreCase: ignoreCase,
69 | multiline: multiline,
70 | source: pattern,
71 | flags: flags,
72 | }
73 | o.defineProperty("global", boolValue(global), 0, false)
74 | o.defineProperty("ignoreCase", boolValue(ignoreCase), 0, false)
75 | o.defineProperty("multiline", boolValue(multiline), 0, false)
76 | o.defineProperty("lastIndex", intValue(0), 0o100, false)
77 | o.defineProperty("source", stringValue(pattern), 0, false)
78 | return o
79 | }
80 |
81 | func (o *object) regExpValue() regExpObject {
82 | value, _ := o.value.(regExpObject)
83 | return value
84 | }
85 |
86 | func execRegExp(this *object, target string) (bool, []int) {
87 | if this.class != classRegExpName {
88 | panic(this.runtime.panicTypeError("Calling RegExp.exec on a non-RegExp object"))
89 | }
90 | lastIndex := this.get("lastIndex").number().int64
91 | index := lastIndex
92 | global := this.get("global").bool()
93 | if !global {
94 | index = 0
95 | }
96 |
97 | var result []int
98 | if 0 > index || index > int64(len(target)) {
99 | } else {
100 | result = this.regExpValue().regularExpression.FindStringSubmatchIndex(target[index:])
101 | }
102 |
103 | if result == nil {
104 | this.put("lastIndex", intValue(0), true)
105 | return false, nil
106 | }
107 |
108 | startIndex := index
109 | endIndex := int(lastIndex) + result[1]
110 | // We do this shift here because the .FindStringSubmatchIndex above
111 | // was done on a local subordinate slice of the string, not the whole string
112 | for index, offset := range result {
113 | if offset != -1 {
114 | result[index] += int(startIndex)
115 | }
116 | }
117 | if global {
118 | this.put("lastIndex", intValue(endIndex), true)
119 | }
120 |
121 | return true, result
122 | }
123 |
124 | func execResultToArray(rt *runtime, target string, result []int) *object {
125 | captureCount := len(result) / 2
126 | valueArray := make([]Value, captureCount)
127 | for index := range captureCount {
128 | offset := 2 * index
129 | if result[offset] != -1 {
130 | valueArray[index] = stringValue(target[result[offset]:result[offset+1]])
131 | } else {
132 | valueArray[index] = Value{}
133 | }
134 | }
135 | matchIndex := result[0]
136 | if matchIndex != 0 {
137 | // Find the utf16 index in the string, not the byte index.
138 | matchIndex = utf16Length(target[:matchIndex])
139 | }
140 | match := rt.newArrayOf(valueArray)
141 | match.defineProperty("input", stringValue(target), 0o111, false)
142 | match.defineProperty("index", intValue(matchIndex), 0o111, false)
143 | return match
144 | }
145 |
--------------------------------------------------------------------------------
/type_string.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "strconv"
5 | "unicode/utf16"
6 | "unicode/utf8"
7 | )
8 |
9 | type stringObjecter interface {
10 | Length() int
11 | At(at int) rune
12 | String() string
13 | }
14 |
15 | type stringASCII string
16 |
17 | func (str stringASCII) Length() int {
18 | return len(str)
19 | }
20 |
21 | func (str stringASCII) At(at int) rune {
22 | return rune(str[at])
23 | }
24 |
25 | func (str stringASCII) String() string {
26 | return string(str)
27 | }
28 |
29 | type stringWide struct {
30 | string string
31 | value16 []uint16
32 | }
33 |
34 | func (str stringWide) Length() int {
35 | if str.value16 == nil {
36 | str.value16 = utf16.Encode([]rune(str.string))
37 | }
38 | return len(str.value16)
39 | }
40 |
41 | func (str stringWide) At(at int) rune {
42 | if str.value16 == nil {
43 | str.value16 = utf16.Encode([]rune(str.string))
44 | }
45 | return rune(str.value16[at])
46 | }
47 |
48 | func (str stringWide) String() string {
49 | return str.string
50 | }
51 |
52 | func newStringObject(str string) stringObjecter {
53 | for i := range len(str) {
54 | if str[i] >= utf8.RuneSelf {
55 | goto wide
56 | }
57 | }
58 |
59 | return stringASCII(str)
60 |
61 | wide:
62 | return &stringWide{
63 | string: str,
64 | }
65 | }
66 |
67 | func stringAt(str stringObjecter, index int) rune {
68 | if 0 <= index && index < str.Length() {
69 | return str.At(index)
70 | }
71 | return utf8.RuneError
72 | }
73 |
74 | func (rt *runtime) newStringObject(value Value) *object {
75 | str := newStringObject(value.string())
76 |
77 | obj := rt.newClassObject(classStringName)
78 | obj.defineProperty(propertyLength, intValue(str.Length()), 0, false)
79 | obj.objectClass = classString
80 | obj.value = str
81 | return obj
82 | }
83 |
84 | func (o *object) stringValue() stringObjecter {
85 | if str, ok := o.value.(stringObjecter); ok {
86 | return str
87 | }
88 | return nil
89 | }
90 |
91 | func stringEnumerate(obj *object, all bool, each func(string) bool) {
92 | if str := obj.stringValue(); str != nil {
93 | length := str.Length()
94 | for index := range length {
95 | if !each(strconv.FormatInt(int64(index), 10)) {
96 | return
97 | }
98 | }
99 | }
100 | objectEnumerate(obj, all, each)
101 | }
102 |
103 | func stringGetOwnProperty(obj *object, name string) *property {
104 | if prop := objectGetOwnProperty(obj, name); prop != nil {
105 | return prop
106 | }
107 | // TODO Test a string of length >= +int32 + 1?
108 | if index := stringToArrayIndex(name); index >= 0 {
109 | if chr := stringAt(obj.stringValue(), int(index)); chr != utf8.RuneError {
110 | return &property{stringValue(string(chr)), 0}
111 | }
112 | }
113 | return nil
114 | }
115 |
--------------------------------------------------------------------------------
/underscore/LICENSE.underscorejs:
--------------------------------------------------------------------------------
1 | Copyright (c) 2009-2022 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors
2 |
3 | Permission is hereby granted, free of charge, to any person
4 | obtaining a copy of this software and associated documentation
5 | files (the "Software"), to deal in the Software without
6 | restriction, including without limitation the rights to use,
7 | copy, modify, merge, publish, distribute, sublicense, and/or sell
8 | copies of the Software, and to permit persons to whom the
9 | Software is furnished to do so, subject to the following
10 | conditions:
11 |
12 | The above copyright notice and this permission notice shall be
13 | included in all copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
16 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
17 | OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
18 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
19 | HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
20 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
21 | FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
22 | OTHER DEALINGS IN THE SOFTWARE.
23 |
--------------------------------------------------------------------------------
/underscore/README.md:
--------------------------------------------------------------------------------
1 | # underscore
2 |
3 | [](https://pkg.go.dev/github.com/robertkrimen/otto/underscore) [](https://opensource.org/licenses/MIT)
4 |
5 | To update the version of underscore run:
6 |
7 | ```shell
8 | go generate
9 | ```
10 |
--------------------------------------------------------------------------------
/underscore/download.go:
--------------------------------------------------------------------------------
1 | //go:build generate
2 |
3 | package main
4 |
5 | import (
6 | "context"
7 | "flag"
8 | "fmt"
9 | "io"
10 | "log"
11 | "net/http"
12 | "os"
13 | "time"
14 | )
15 |
16 | var (
17 | url = flag.String("url", "", "url to read from")
18 | output = flag.String("output", "", "output file to write the result too")
19 | )
20 |
21 | func download(url, output string) (err error) {
22 | ctx, cancel := context.WithTimeout(context.Background(), time.Second*10)
23 | defer cancel()
24 |
25 | req, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
26 | if err != nil {
27 | return fmt.Errorf("new request failed: %w", err)
28 | }
29 |
30 | resp, err := http.DefaultClient.Do(req)
31 | if err != nil {
32 | return fmt.Errorf("request failed: %w", err)
33 | }
34 | defer resp.Body.Close()
35 |
36 | var f *os.File
37 | if output != "" {
38 | if f, err = os.Create(output); err != nil {
39 | return fmt.Errorf("create file %q failed: %w", output, err)
40 | }
41 |
42 | defer f.Close()
43 | } else {
44 | f = os.Stdout
45 | }
46 |
47 | if _, err := io.Copy(f, resp.Body); err != nil {
48 | return fmt.Errorf("body save: %w", err)
49 | }
50 |
51 | return nil
52 | }
53 |
54 | func main() {
55 | flag.Parse()
56 |
57 | switch {
58 | case len(*url) == 0:
59 | log.Fatal("missing required --url parameter")
60 | }
61 |
62 | if err := download(*url, *output); err != nil {
63 | log.Fatal(err)
64 | }
65 | }
66 |
--------------------------------------------------------------------------------
/underscore/generate.go:
--------------------------------------------------------------------------------
1 | package underscore
2 |
3 | //go:generate go run download.go --url https://underscorejs.org/underscore-min.js --output underscore-min.js
4 | //go:generate go run download.go --url https://raw.githubusercontent.com/jashkenas/underscore/master/LICENSE --output LICENSE.underscorejs
5 |
--------------------------------------------------------------------------------
/underscore/testify:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env perl
2 |
3 | use strict;
4 | use warnings;
5 |
6 | my $underscore_test = shift @ARGV || "";
7 | if (!-d $underscore_test) {
8 | print <<_END_;
9 | Usage:
10 |
11 | testify ./underscore/test
12 |
13 | # Should look something like:
14 | arrays.js
15 | chaining.js
16 | collections.js
17 | functions.js
18 | index.html
19 | objects.js
20 | speed.js
21 | utility.js
22 | vendor
23 |
24 | _END_
25 | if ($underscore_test) {
26 | die "!: Not a directory: $underscore_test\n"
27 | }
28 | exit;
29 | }
30 |
31 | chdir $underscore_test or die "!: $!";
32 |
33 | my @js = <*.js>;
34 |
35 | for my $file (@js) {
36 | open my $fh, '<', $file or die "!: $!";
37 | my $tests = join "", <$fh>;
38 | my @tests = $tests =~ m/
39 | ^(\s{2}test\(.*?
40 | ^\s{2}}\);)$
41 | /mgxs;
42 | close $fh;
43 | next unless @tests;
44 | print "$file: ", scalar(@tests), "\n";
45 | my $underscore_name = "underscore_$file";
46 | $underscore_name =~ s/.js$//;
47 | my $go_file = "${underscore_name}_test.go";
48 | $go_file =~ s/.js$/.go/;
49 | open $fh, '>', $go_file or die "!: $!";
50 |
51 | $fh->print(<<_END_);
52 | package otto
53 |
54 | import (
55 | "testing"
56 | )
57 |
58 | _END_
59 |
60 | my $count = 0;
61 | for my $test (@tests) {
62 | $test =~ s/`([^`]+)`/<$1>/g;
63 | my ($name) = $test =~ m/^\s*test\(['"]([^'"]+)['"]/;
64 | $fh->print(<<_END_);
65 | // $name
66 | func Test_${underscore_name}_$count(t *testing.T) {
67 | tt(t, func(){
68 | test := underscoreTest()
69 |
70 | test(`
71 | $test
72 | `)
73 | })
74 | }
75 |
76 | _END_
77 | $count++;
78 | }
79 | }
80 |
81 | # test('#779 - delimeters are applied to unescaped text.', 1, function() {
82 | # var template = _.template('<<\nx\n>>', null, {evaluate: /<<(.*?)>>/g});
83 | # strictEqual(template(), '<<\nx\n>>');
84 | # });
85 |
--------------------------------------------------------------------------------
/underscore/underscore.go:
--------------------------------------------------------------------------------
1 | // Package underscore contains the source for the JavaScript utility-belt library.
2 | //
3 | // import (
4 | // _ "github.com/robertkrimen/otto/underscore"
5 | // )
6 | //
7 | // Every Otto runtime will now include [underscore] for more information see the [underscore docs]
8 | //
9 | // By importing this package, you'll automatically load underscore every time you create a new Otto runtime.
10 | //
11 | // To prevent this behavior, you can do the following:
12 | //
13 | // import (
14 | // "github.com/robertkrimen/otto/underscore"
15 | // )
16 | //
17 | // func init() {
18 | // underscore.Disable()
19 | // }
20 | //
21 | // [underscore]: http://underscorejs.org
22 | // [underscore docs]: https://github.com/documentcloud/underscore
23 | package underscore
24 |
25 | import (
26 | _ "embed" // Embed underscore.
27 |
28 | "github.com/robertkrimen/otto/registry"
29 | )
30 |
31 | //go:embed underscore-min.js
32 | var underscore string
33 | var entry *registry.Entry = registry.Register(Source)
34 |
35 | // Enable underscore runtime inclusion.
36 | func Enable() {
37 | entry.Enable()
38 | }
39 |
40 | // Disable underscore runtime inclusion.
41 | func Disable() {
42 | entry.Disable()
43 | }
44 |
45 | // Source returns the underscore source.
46 | func Source() string {
47 | return underscore
48 | }
49 |
--------------------------------------------------------------------------------
/underscore_chaining_test.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "testing"
5 | )
6 |
7 | // map/flatten/reduce.
8 | func Test_underscore_chaining_0(t *testing.T) {
9 | tt(t, func() {
10 | test := underscoreTest()
11 |
12 | test(`
13 | test("map/flatten/reduce", function() {
14 | var lyrics = [
15 | "I'm a lumberjack and I'm okay",
16 | "I sleep all night and I work all day",
17 | "He's a lumberjack and he's okay",
18 | "He sleeps all night and he works all day"
19 | ];
20 | var counts = _(lyrics).chain()
21 | .map(function(line) { return line.split(''); })
22 | .flatten()
23 | .reduce(function(hash, l) {
24 | hash[l] = hash[l] || 0;
25 | hash[l]++;
26 | return hash;
27 | }, {}).value();
28 | ok(counts['a'] == 16 && counts['e'] == 10, 'counted all the letters in the song');
29 | });
30 | `)
31 | })
32 | }
33 |
34 | // select/reject/sortBy.
35 | func Test_underscore_chaining_1(t *testing.T) {
36 | tt(t, func() {
37 | test := underscoreTest()
38 |
39 | test(`
40 | test("select/reject/sortBy", function() {
41 | var numbers = [1,2,3,4,5,6,7,8,9,10];
42 | numbers = _(numbers).chain().select(function(n) {
43 | return n % 2 == 0;
44 | }).reject(function(n) {
45 | return n % 4 == 0;
46 | }).sortBy(function(n) {
47 | return -n;
48 | }).value();
49 | equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
50 | });
51 | `)
52 | })
53 | }
54 |
55 | // select/reject/sortBy in functional style.
56 | func Test_underscore_chaining_2(t *testing.T) {
57 | tt(t, func() {
58 | test := underscoreTest()
59 |
60 | test(`
61 | test("select/reject/sortBy in functional style", function() {
62 | var numbers = [1,2,3,4,5,6,7,8,9,10];
63 | numbers = _.chain(numbers).select(function(n) {
64 | return n % 2 == 0;
65 | }).reject(function(n) {
66 | return n % 4 == 0;
67 | }).sortBy(function(n) {
68 | return -n;
69 | }).value();
70 | equal(numbers.join(', '), "10, 6, 2", "filtered and reversed the numbers");
71 | });
72 | `)
73 | })
74 | }
75 |
76 | // reverse/concat/unshift/pop/map.
77 | func Test_underscore_chaining_3(t *testing.T) {
78 | tt(t, func() {
79 | test := underscoreTest()
80 |
81 | test(`
82 | test("reverse/concat/unshift/pop/map", function() {
83 | var numbers = [1,2,3,4,5];
84 | numbers = _(numbers).chain()
85 | .reverse()
86 | .concat([5, 5, 5])
87 | .unshift(17)
88 | .pop()
89 | .map(function(n){ return n * 2; })
90 | .value();
91 | equal(numbers.join(', '), "34, 10, 8, 6, 4, 2, 10, 10", 'can chain together array functions.');
92 | });
93 | `)
94 | })
95 | }
96 |
--------------------------------------------------------------------------------
/underscore_test.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "sync"
5 | "testing"
6 |
7 | "github.com/robertkrimen/otto/terst"
8 | "github.com/robertkrimen/otto/underscore"
9 | )
10 |
11 | func init() {
12 | underscore.Disable()
13 | }
14 |
15 | var (
16 | // A persistent handle for the underscore tester
17 | // We do not run underscore tests in parallel, so it is okay to stash globally.
18 | tester *_tester
19 | once sync.Once
20 | )
21 |
22 | // A tester for underscore: underscoreTest => test(underscore) :).
23 | func underscoreTest() func(string, ...interface{}) Value {
24 | setTester := func() {
25 | tester = newTester()
26 | tester.underscore() // Load underscore and testing shim, etc.
27 | }
28 | once.Do(setTester)
29 |
30 | return tester.test
31 | }
32 |
33 | func (te *_tester) underscore() {
34 | vm := te.vm
35 | _, err := vm.Run(underscore.Source())
36 | if err != nil {
37 | panic(err)
38 | }
39 |
40 | err = vm.Set("assert", func(call FunctionCall) Value {
41 | if !call.Argument(0).bool() {
42 | message := "Assertion failed"
43 | if len(call.ArgumentList) > 1 {
44 | message = call.ArgumentList[1].string()
45 | }
46 | t := terst.Caller().T()
47 | is(message, nil)
48 | t.Fail()
49 | return falseValue
50 | }
51 | return trueValue
52 | })
53 | if err != nil {
54 | panic(err)
55 | }
56 |
57 | _, err = vm.Run(`
58 | var templateSettings;
59 |
60 | function _setup() {
61 | templateSettings = _.clone(_.templateSettings);
62 | }
63 |
64 | function _teardown() {
65 | _.templateSettings = templateSettings;
66 | }
67 |
68 | function module() {
69 | /* Nothing happens. */
70 | }
71 |
72 | function equals(a, b, emit) {
73 | assert(a == b, emit + ", <" + a + "> != <" + b + ">");
74 | }
75 | var equal = equals;
76 |
77 | function notStrictEqual(a, b, emit) {
78 | assert(a !== b, emit);
79 | }
80 |
81 | function strictEqual(a, b, emit) {
82 | assert(a === b, emit);
83 | }
84 |
85 | function ok(a, emit) {
86 | assert(a, emit);
87 | }
88 |
89 | function raises(fn, want, emit) {
90 | var have, _ok = false;
91 | if (typeof want === "string") {
92 | emit = want;
93 | want = null;
94 | }
95 |
96 | try {
97 | fn();
98 | } catch(tmp) {
99 | have = tmp;
100 | }
101 |
102 | if (have) {
103 | if (!want) {
104 | _ok = true;
105 | }
106 | else if (want instanceof RegExp) {
107 | _ok = want.test(have);
108 | }
109 | else if (have instanceof want) {
110 | _ok = true
111 | }
112 | else if (want.call({}, have) === true) {
113 | _ok = true;
114 | }
115 | }
116 |
117 | ok(_ok, emit);
118 | }
119 |
120 | function test(name){
121 | _setup()
122 | try {
123 | templateSettings = _.clone(_.templateSettings);
124 | if (arguments.length == 3) {
125 | count = 0
126 | for (count = 0; count < arguments[1]; count++) {
127 | arguments[2]()
128 | }
129 | } else {
130 | // For now.
131 | arguments[1]()
132 | }
133 | }
134 | finally {
135 | _teardown()
136 | }
137 | }
138 |
139 | function deepEqual(a, b, emit) {
140 | // Also, for now.
141 | assert(_.isEqual(a, b), emit)
142 | }
143 | `)
144 | if err != nil {
145 | panic(err)
146 | }
147 | }
148 |
149 | func Test_underscore(t *testing.T) {
150 | tt(t, func() {
151 | test := underscoreTest()
152 |
153 | test(`
154 | _.map([1, 2, 3], function(value){
155 | return value + 1
156 | })
157 | `, "2,3,4")
158 |
159 | test(`
160 | abc = _.find([1, 2, 3, -1], function(value) { return value == -1 })
161 | `, -1)
162 |
163 | test(`_.isEqual(1, 1)`, true)
164 | test(`_.isEqual([], [])`, true)
165 | test(`_.isEqual(['b', 'd'], ['b', 'd'])`, true)
166 | test(`_.isEqual(['b', 'd', 'c'], ['b', 'd', 'e'])`, false)
167 | test(`_.isFunction(function(){})`, true)
168 | test(`_.template('\u2028<%= "\\u2028\\u2029" %>\u2029
')()`, "\u2028\u2028\u2029\u2029
")
169 | })
170 | }
171 |
172 | // TODO Test: typeof An argument reference
173 | // TODO Test: abc = {}; abc == Object(abc)
174 |
--------------------------------------------------------------------------------
/value_boolean.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "reflect"
7 | "unicode/utf16"
8 | )
9 |
10 | func (v Value) bool() bool {
11 | if v.kind == valueBoolean {
12 | return v.value.(bool)
13 | }
14 | if v.IsUndefined() || v.IsNull() {
15 | return false
16 | }
17 | switch value := v.value.(type) {
18 | case bool:
19 | return value
20 | case int, int8, int16, int32, int64:
21 | return reflect.ValueOf(value).Int() != 0
22 | case uint, uint8, uint16, uint32, uint64:
23 | return reflect.ValueOf(value).Uint() != 0
24 | case float32:
25 | return value != 0
26 | case float64:
27 | if math.IsNaN(value) || value == 0 {
28 | return false
29 | }
30 | return true
31 | case string:
32 | return len(value) != 0
33 | case []uint16:
34 | return len(utf16.Decode(value)) != 0
35 | }
36 | if v.IsObject() {
37 | return true
38 | }
39 | panic(fmt.Sprintf("unexpected boolean type %T", v.value))
40 | }
41 |
--------------------------------------------------------------------------------
/value_kind.gen.go:
--------------------------------------------------------------------------------
1 | // Code generated by "stringer -type=valueKind -trimprefix=value -output=value_kind.gen.go"; DO NOT EDIT.
2 |
3 | package otto
4 |
5 | import "strconv"
6 |
7 | func _() {
8 | // An "invalid array index" compiler error signifies that the constant values have changed.
9 | // Re-run the stringer command to generate them again.
10 | var x [1]struct{}
11 | _ = x[valueUndefined-0]
12 | _ = x[valueNull-1]
13 | _ = x[valueNumber-2]
14 | _ = x[valueString-3]
15 | _ = x[valueBoolean-4]
16 | _ = x[valueObject-5]
17 | _ = x[valueEmpty-6]
18 | _ = x[valueResult-7]
19 | _ = x[valueReference-8]
20 | }
21 |
22 | const _valueKind_name = "UndefinedNullNumberStringBooleanObjectEmptyResultReference"
23 |
24 | var _valueKind_index = [...]uint8{0, 9, 13, 19, 25, 32, 38, 43, 49, 58}
25 |
26 | func (i valueKind) String() string {
27 | if i < 0 || i >= valueKind(len(_valueKind_index)-1) {
28 | return "valueKind(" + strconv.FormatInt(int64(i), 10) + ")"
29 | }
30 | return _valueKind_name[_valueKind_index[i]:_valueKind_index[i+1]]
31 | }
32 |
--------------------------------------------------------------------------------
/value_primitive.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | func toNumberPrimitive(value Value) Value {
4 | return toPrimitive(value, defaultValueHintNumber)
5 | }
6 |
7 | func toPrimitiveValue(value Value) Value {
8 | return toPrimitive(value, defaultValueNoHint)
9 | }
10 |
11 | func toPrimitive(value Value, hint defaultValueHint) Value {
12 | switch value.kind {
13 | case valueNull, valueUndefined, valueNumber, valueString, valueBoolean:
14 | return value
15 | case valueObject:
16 | return value.object().DefaultValue(hint)
17 | default:
18 | panic(hereBeDragons(value.kind, value))
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/value_string.go:
--------------------------------------------------------------------------------
1 | package otto
2 |
3 | import (
4 | "fmt"
5 | "math"
6 | "regexp"
7 | "strconv"
8 | "unicode/utf16"
9 | )
10 |
11 | var matchLeading0Exponent = regexp.MustCompile(`([eE][\+\-])0+([1-9])`) // 1e-07 => 1e-7
12 |
13 | // FIXME
14 | // https://code.google.com/p/v8/source/browse/branches/bleeding_edge/src/conversions.cc?spec=svn18082&r=18082
15 | func floatToString(value float64, bitsize int) string {
16 | // TODO Fit to ECMA-262 9.8.1 specification
17 | if math.IsNaN(value) {
18 | return "NaN"
19 | } else if math.IsInf(value, 0) {
20 | if math.Signbit(value) {
21 | return "-Infinity"
22 | }
23 | return "Infinity"
24 | }
25 | exponent := math.Log10(math.Abs(value))
26 | if exponent >= 21 || exponent < -6 {
27 | return matchLeading0Exponent.ReplaceAllString(strconv.FormatFloat(value, 'g', -1, bitsize), "$1$2")
28 | }
29 | return strconv.FormatFloat(value, 'f', -1, bitsize)
30 | }
31 |
32 | func numberToStringRadix(value Value, radix int) string {
33 | float := value.float64()
34 | switch {
35 | case math.IsNaN(float):
36 | return "NaN"
37 | case math.IsInf(float, 1):
38 | return "Infinity"
39 | case math.IsInf(float, -1):
40 | return "-Infinity"
41 | case float == 0:
42 | return "0"
43 | }
44 | // FIXME This is very broken
45 | // Need to do proper radix conversion for floats, ...
46 | // This truncates large floats (so bad).
47 | return strconv.FormatInt(int64(float), radix)
48 | }
49 |
50 | func (v Value) string() string {
51 | if v.kind == valueString {
52 | switch value := v.value.(type) {
53 | case string:
54 | return value
55 | case []uint16:
56 | return string(utf16.Decode(value))
57 | }
58 | }
59 | if v.IsUndefined() {
60 | return "undefined"
61 | }
62 | if v.IsNull() {
63 | return "null"
64 | }
65 | switch value := v.value.(type) {
66 | case bool:
67 | return strconv.FormatBool(value)
68 | case int:
69 | return strconv.FormatInt(int64(value), 10)
70 | case int8:
71 | return strconv.FormatInt(int64(value), 10)
72 | case int16:
73 | return strconv.FormatInt(int64(value), 10)
74 | case int32:
75 | return strconv.FormatInt(int64(value), 10)
76 | case int64:
77 | return strconv.FormatInt(value, 10)
78 | case uint:
79 | return strconv.FormatUint(uint64(value), 10)
80 | case uint8:
81 | return strconv.FormatUint(uint64(value), 10)
82 | case uint16:
83 | return strconv.FormatUint(uint64(value), 10)
84 | case uint32:
85 | return strconv.FormatUint(uint64(value), 10)
86 | case uint64:
87 | return strconv.FormatUint(value, 10)
88 | case float32:
89 | if value == 0 {
90 | return "0" // Take care not to return -0
91 | }
92 | return floatToString(float64(value), 32)
93 | case float64:
94 | if value == 0 {
95 | return "0" // Take care not to return -0
96 | }
97 | return floatToString(value, 64)
98 | case []uint16:
99 | return string(utf16.Decode(value))
100 | case string:
101 | return value
102 | case *object:
103 | return value.DefaultValue(defaultValueHintString).string()
104 | }
105 | panic(fmt.Errorf("%v.string( %T)", v.value, v.value))
106 | }
107 |
--------------------------------------------------------------------------------