Implemented dashboard and login/logout

This commit is contained in:
tyler 2024-04-04 10:46:14 -04:00
parent e68567c010
commit b97012ed21
159 changed files with 17979 additions and 15788 deletions

497
v1/app.go
View file

@ -7,17 +7,27 @@ import (
"log" "log"
"os" "os"
"sync" "sync"
"time"
"github.com/tylertravisty/rum-goggles/v1/internal/api"
"github.com/tylertravisty/rum-goggles/v1/internal/config" "github.com/tylertravisty/rum-goggles/v1/internal/config"
"github.com/tylertravisty/rum-goggles/v1/internal/models" "github.com/tylertravisty/rum-goggles/v1/internal/models"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go" rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
"github.com/wailsapp/wails/v2/pkg/runtime"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
) )
const (
AccountType = "Account"
ChannelType = "Channel"
)
// App struct // App struct
type App struct { type App struct {
api *api.Api
clients map[string]*rumblelivestreamlib.Client clients map[string]*rumblelivestreamlib.Client
clientsMu sync.Mutex
ctx context.Context ctx context.Context
services *models.Services services *models.Services
logError *log.Logger logError *log.Logger
@ -36,6 +46,8 @@ func NewApp() *App {
log.Fatal("error initializing log: ", err) log.Fatal("error initializing log: ", err)
} }
app.api = api.NewApi(app.logError, app.logInfo)
return app return app
} }
@ -58,6 +70,7 @@ func (a *App) log() error {
// so we can call the runtime methods // so we can call the runtime methods
func (a *App) startup(ctx context.Context) { func (a *App) startup(ctx context.Context) {
a.ctx = ctx a.ctx = ctx
a.api.Startup(ctx)
db, err := config.Database() db, err := config.Database()
if err != nil { if err != nil {
@ -85,6 +98,13 @@ func (a *App) startup(ctx context.Context) {
} }
func (a *App) shutdown(ctx context.Context) { func (a *App) shutdown(ctx context.Context) {
if a.api != nil {
err := a.api.Shutdown()
if err != nil {
a.logError.Println("error shutting down api:", err)
}
}
if a.services != nil { if a.services != nil {
err := a.services.Close() err := a.services.Close()
if err != nil { if err != nil {
@ -102,7 +122,7 @@ func (a *App) shutdown(ctx context.Context) {
a.logFileMu.Unlock() a.logFileMu.Unlock()
} }
func (a *App) AddChannel(apiKey string) error { func (a *App) AddPage(apiKey string) error {
client := rumblelivestreamlib.Client{StreamKey: apiKey} client := rumblelivestreamlib.Client{StreamKey: apiKey}
resp, err := client.Request() resp, err := client.Request()
if err != nil { if err != nil {
@ -140,7 +160,7 @@ func (a *App) addAccountNotExist(uid string, username string, apiKey string) err
return fmt.Errorf("error querying account by username: %v", err) return fmt.Errorf("error querying account by username: %v", err)
} }
if acct == nil { if acct == nil {
err = a.services.AccountS.Create(&models.Account{ _, err = a.services.AccountS.Create(&models.Account{
UID: &uid, UID: &uid,
Username: &username, Username: &username,
ApiKey: &apiKey, ApiKey: &apiKey,
@ -182,6 +202,8 @@ func (a *App) addChannelNotExist(username string, cid string, name string, apiKe
func (a *App) Login(username string, password string) error { func (a *App) Login(username string, password string) error {
var err error var err error
a.clientsMu.Lock()
defer a.clientsMu.Unlock()
client, exists := a.clients[username] client, exists := a.clients[username]
if exists && client != nil { if exists && client != nil {
err = client.Logout() err = client.Logout()
@ -207,27 +229,107 @@ func (a *App) Login(username string, password string) error {
} }
cookiesS := string(cookiesB) cookiesS := string(cookiesB)
act, err := a.services.AccountS.ByUsername(username) acct, err := a.services.AccountS.ByUsername(username)
if err != nil { if err != nil {
a.logError.Println("error getting account by username:", err) a.logError.Println("error getting account by username:", err)
return fmt.Errorf("Error logging in. Try again.") return fmt.Errorf("Error logging in. Try again.")
} }
if act == nil { if acct == nil {
act = &models.Account{nil, nil, &username, &cookiesS, nil, nil} acct = &models.Account{nil, nil, &username, &cookiesS, nil, nil}
err = a.services.AccountS.Create(act) id, err := a.services.AccountS.Create(acct)
if err != nil { if err != nil {
a.logError.Println("error creating account:", err) a.logError.Println("error creating account:", err)
return fmt.Errorf("Error logging in. Try again.") return fmt.Errorf("Error logging in. Try again.")
} }
acct.ID = &id
} else { } else {
act.Cookies = &cookiesS acct.Cookies = &cookiesS
err = a.services.AccountS.Update(act) err = a.services.AccountS.Update(acct)
if err != nil { if err != nil {
a.logError.Println("error updating account:", err) a.logError.Println("error updating account:", err)
return fmt.Errorf("Error logging in. Try again.") return fmt.Errorf("Error logging in. Try again.")
} }
} }
name := acct.String()
if name == nil {
a.logError.Println("account name is nil")
return fmt.Errorf("Error logging in. Try again.")
}
runtime.EventsEmit(a.ctx, "LoggedIn-"+*name, true)
list, err := a.accountList()
if err != nil {
a.logError.Println("error getting account list:", err)
return fmt.Errorf("Error logging in. Try again.")
}
runtime.EventsEmit(a.ctx, "PageSideBarAccounts", list)
err = a.openDetails(acct)
if err != nil {
a.logError.Println("error opening account details:", err)
return fmt.Errorf("Error logging in. Try again.")
}
return nil
}
func (a *App) Logout(id int64) error {
acct, err := a.services.AccountS.ByID(id)
if err != nil {
a.logError.Println("error querying account by ID:", err)
return fmt.Errorf("Error logging out. Try again.")
}
if acct == nil {
return fmt.Errorf("Did not find account. Try again.")
}
if acct.Username == nil {
a.logError.Println("account username is nil")
return fmt.Errorf("Error logging out. Try again.")
}
a.clientsMu.Lock()
defer a.clientsMu.Unlock()
client, exists := a.clients[*acct.Username]
if exists {
err = client.Logout()
if err != nil {
a.logError.Println("error logging out:", err)
return fmt.Errorf("Error logging out. Try again.")
}
delete(a.clients, *acct.Username)
}
if acct.Cookies != nil {
acct.Cookies = nil
err = a.services.AccountS.Update(acct)
if err != nil {
a.logError.Println("error updating account:", err)
return fmt.Errorf("Error logging out. Try again.")
}
}
name := acct.String()
if name == nil {
a.logError.Println("account name is nil")
return fmt.Errorf("Error logging out. Try again.")
}
runtime.EventsEmit(a.ctx, "LoggedIn-"+*name, false)
list, err := a.accountList()
if err != nil {
a.logError.Println("error getting account list:", err)
return fmt.Errorf("Error logging out. Try again.")
}
runtime.EventsEmit(a.ctx, "PageSideBarAccounts", list)
err = a.openDetails(acct)
if err != nil {
a.logError.Println("error opening account details:", err)
return fmt.Errorf("Error logging out. Try again.")
}
return nil return nil
} }
@ -247,18 +349,26 @@ type Account struct {
} }
func (a *App) AccountList() (map[string]*Account, error) { func (a *App) AccountList() (map[string]*Account, error) {
list, err := a.accountList()
if err != nil {
a.logError.Println("error getting account list:", err)
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
}
return list, nil
}
func (a *App) accountList() (map[string]*Account, error) {
list := map[string]*Account{} list := map[string]*Account{}
accountChannels, err := a.services.AccountChannelS.All() accountChannels, err := a.services.AccountChannelS.All()
if err != nil { if err != nil {
a.logError.Println("error getting all account channels:", err) return nil, fmt.Errorf("error querying all account channels: %v", err)
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
} }
for _, ac := range accountChannels { for _, ac := range accountChannels {
if ac.Account.Username == nil { if ac.Account.Username == nil {
a.logError.Println("account-channel contains nil account username") return nil, fmt.Errorf("account-channel contains nil account username")
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
} }
act, exists := list[*ac.Account.Username] act, exists := list[*ac.Account.Username]
@ -274,3 +384,366 @@ func (a *App) AccountList() (map[string]*Account, error) {
return list, nil return list, nil
} }
func (a *App) OpenAccount(id int64) error {
acct, err := a.services.AccountS.ByID(id)
if err != nil {
a.logError.Println("error querying account by ID:", err)
return fmt.Errorf("Error opening account. Try again.")
}
if acct == nil {
return fmt.Errorf("Did not find account. Try again.")
}
err = a.openDetails(acct)
if err != nil {
a.logError.Println("error opening account details:", err)
return fmt.Errorf("Error opening account. Try again.")
}
return nil
}
func (a *App) OpenChannel(id int64) error {
channel, err := a.services.ChannelS.ByID(id)
if err != nil {
a.logError.Println("error querying channel by ID:", err)
return fmt.Errorf("Error opening channel. Try again.")
}
if channel == nil {
return fmt.Errorf("Did not find channel. Try again.")
}
err = a.openDetails(channel)
if err != nil {
a.logError.Println("error opening channel details:", err)
return fmt.Errorf("Error opening channel. Try again.")
}
return nil
}
type Page interface {
Id() *int64
KeyUrl() *string
LoggedIn() bool
String() *string
Title() *string
Type() string
}
type PageDetails struct {
ID int64 `json:"id"`
HasApi bool `json:"has_api"`
LoggedIn bool `json:"logged_in"`
Title string `json:"title"`
Type string `json:"type"`
}
func (a *App) openDetails(p Page) error {
id := p.Id()
if id == nil {
return fmt.Errorf("page id is nil")
}
hasApi := true
key := p.KeyUrl()
if key == nil || *key == "" {
hasApi = false
}
name := p.String()
if name == nil {
return fmt.Errorf("page name is nil")
}
title := p.Title()
if title == nil {
return fmt.Errorf("page title is nil")
}
runtime.EventsEmit(a.ctx, "PageDetails", PageDetails{
ID: *id,
HasApi: hasApi,
LoggedIn: p.LoggedIn(),
Title: *title,
Type: p.Type(),
})
err := a.api.Display(*name)
if err != nil {
return fmt.Errorf("error displaying api for %s: %v", *name, err)
}
return nil
}
func (a *App) ActivateAccount(id int64) error {
acct, err := a.services.AccountS.ByID(id)
if err != nil {
a.logError.Println("error querying account by ID:", err)
return fmt.Errorf("Error activating account. Try again.")
}
if acct == nil {
return fmt.Errorf("Did not find account. Try again.")
}
err = a.activatePage(acct)
if err != nil {
a.logError.Println("error activating account:", err)
return fmt.Errorf("Error activating account. Try again.")
}
return nil
}
// ActivateChannel activates an inactivate page and deactivates an active page.
func (a *App) ActivateChannel(id int64) error {
channel, err := a.services.ChannelS.ByID(id)
if err != nil {
a.logError.Println("error querying channel by ID:", err)
return fmt.Errorf("Error activating channel. Try again.")
}
if channel == nil {
return fmt.Errorf("Did not find channel. Try again.")
}
err = a.activatePage(channel)
if err != nil {
a.logError.Println("error activating channel:", err)
return fmt.Errorf("Error activating channel. Try again.")
}
return nil
}
// If page is inactivate, activate.
// If page is active, deactivate.
func (a *App) activatePage(p Page) error {
name := p.String()
if name == nil {
return fmt.Errorf("page name is nil")
}
url := p.KeyUrl()
if url == nil {
return fmt.Errorf("page key url is nil")
}
if a.api.Active(*name) {
err := a.api.Stop(*name)
if err != nil {
return fmt.Errorf("error stopping api: %v", err)
}
return nil
}
err := a.api.Start(*name, *url, 10*time.Second)
if err != nil {
return fmt.Errorf("error starting api: %v", err)
}
err = a.api.Display(*name)
if err != nil {
return fmt.Errorf("error displaying api: %v", err)
}
return nil
}
func (a *App) DeleteAccount(id int64) error {
acct, err := a.services.AccountS.ByID(id)
if err != nil {
a.logError.Println("error querying account by ID:", err)
return fmt.Errorf("Error deleting account. Try again.")
}
if acct == nil {
return fmt.Errorf("Did not find account. Try again.")
}
channels, err := a.services.ChannelS.ByAccount(acct)
if err != nil {
a.logError.Println("error querying channels by account:", err)
return fmt.Errorf("Error deleting account. Try again.")
}
if len(channels) != 0 {
return fmt.Errorf("You must delete all channels associated with the account before it can be deleted.")
}
name := acct.String()
if name == nil {
a.logError.Println("account name is nil")
return fmt.Errorf("Error deleting account. Try again.")
}
if a.api.Active(*name) {
err := a.api.Stop(*name)
if err != nil {
a.logError.Println("error stopping api:", err)
return fmt.Errorf("Error deleting account. Try again.")
}
}
err = a.services.AccountS.Delete(acct)
if err != nil {
a.logError.Println("error deleting account:", err)
return fmt.Errorf("Error deleting account. Try again.")
}
runtime.EventsEmit(a.ctx, "PageDetails", nil)
list, err := a.accountList()
if err != nil {
a.logError.Println("error getting account list:", err)
return fmt.Errorf("Error deleting account. Try again.")
}
runtime.EventsEmit(a.ctx, "PageSideBarAccounts", list)
return nil
}
func (a *App) DeleteChannel(id int64) error {
channel, err := a.services.ChannelS.ByID(id)
if err != nil {
a.logError.Println("error querying channel by ID:", err)
return fmt.Errorf("Error deleting channel. Try again.")
}
if channel == nil {
return fmt.Errorf("Did not find channel. Try again.")
}
name := channel.String()
if name == nil {
a.logError.Println("channel name is nil")
return fmt.Errorf("Error deleting channel. Try again.")
}
if a.api.Active(*name) {
err := a.api.Stop(*name)
if err != nil {
a.logError.Println("error stopping api:", err)
return fmt.Errorf("Error deleting channel. Try again.")
}
}
err = a.services.ChannelS.Delete(channel)
if err != nil {
a.logError.Println("error deleting channel:", err)
return fmt.Errorf("Error deleting channel. Try again.")
}
runtime.EventsEmit(a.ctx, "PageDetails", nil)
list, err := a.accountList()
if err != nil {
a.logError.Println("error getting account list:", err)
return fmt.Errorf("Error deleting channel. Try again.")
}
runtime.EventsEmit(a.ctx, "PageSideBarAccounts", list)
return nil
}
func (a *App) PageStatus(name string) {
active := false
isLive := false
resp := a.api.Response(name)
if resp != nil {
active = true
isLive = len(resp.Livestreams) > 0
}
runtime.EventsEmit(a.ctx, "ApiActive-"+name, active)
runtime.EventsEmit(a.ctx, "PageLive-"+name, isLive)
}
func (a *App) UpdateAccountApi(id int64, apiKey string) error {
acct, err := a.services.AccountS.ByID(id)
if err != nil {
a.logError.Println("error querying account by ID:", err)
return fmt.Errorf("Error updating account. Try again.")
}
if acct == nil {
return fmt.Errorf("Did not find account. Try again.")
}
name := acct.String()
if name == nil {
a.logError.Println("account name is nil")
return fmt.Errorf("Error updating account. Try again.")
}
if a.api.Active(*name) {
err := a.api.Stop(*name)
if err != nil {
a.logError.Println("error stopping api:", err)
return fmt.Errorf("Error updating account. Try again.")
}
}
client := rumblelivestreamlib.Client{StreamKey: apiKey}
resp, err := client.Request()
if err != nil {
a.logError.Println("error executing api request:", err)
return fmt.Errorf("Error querying API. Verify key and try again.")
}
if resp.ChannelName != "" || resp.Username != *acct.Username {
return fmt.Errorf("API key does not belong to account. Verify key and try again.")
}
acct.ApiKey = &apiKey
err = a.services.AccountS.Update(acct)
if err != nil {
a.logError.Println("error updating account:", err)
return fmt.Errorf("Error updating account. Try again.")
}
return nil
}
func (a *App) UpdateChannelApi(id int64, apiKey string) error {
channel, err := a.services.ChannelS.ByID(id)
if err != nil {
a.logError.Println("error querying channel by ID:", err)
return fmt.Errorf("Error updating channel. Try again.")
}
if channel == nil {
return fmt.Errorf("Did not find channel. Try again.")
}
name := channel.String()
if name == nil {
a.logError.Println("channel name is nil")
return fmt.Errorf("Error updating channel. Try again.")
}
if a.api.Active(*name) {
err := a.api.Stop(*name)
if err != nil {
a.logError.Println("error stopping api:", err)
return fmt.Errorf("Error updating channel. Try again.")
}
}
client := rumblelivestreamlib.Client{StreamKey: apiKey}
resp, err := client.Request()
if err != nil {
a.logError.Println("error executing api request:", err)
return fmt.Errorf("Error querying API. Verify key and try again.")
}
if resp.ChannelName != *channel.Name {
return fmt.Errorf("API key does not belong to channel. Verify key and try again.")
}
*channel.ApiKey = apiKey
err = a.services.ChannelS.Update(channel)
if err != nil {
a.logError.Println("error updating channel:", err)
return fmt.Errorf("Error updating channel. Try again.")
}
return nil
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

@ -1,17 +1,33 @@
import chevron_right from './icons/chevron-right.png'; import chevron_right from './icons/chevron-right.png';
import circle_green_background from './icons/circle-green-background.png'; import circle_green_background from './icons/circle-green-background.png';
import circle_red_background from './icons/circle-red-background.png';
import eye from './icons/eye.png'; import eye from './icons/eye.png';
import eye_red from './icons/eye-red.png';
import eye_slash from './icons/eye-slash.png'; import eye_slash from './icons/eye-slash.png';
import gear_fill from './icons/gear-fill.png';
import heart from './icons/heart-fill.png'; import heart from './icons/heart-fill.png';
import plus_circle from './icons/plus-circle-fill.png' import pause from './icons/pause-circle-green.png';
import play from './icons/play-circle-green.png';
import plus_circle from './icons/plus-circle-fill.png';
import star from './icons/star-fill.png';
import thumbs_down from './icons/hand-thumbs-down-fill.png';
import thumbs_up from './icons/hand-thumbs-up-fill.png';
import x_lg from './icons/x-lg.png'; import x_lg from './icons/x-lg.png';
import logo from './logo/logo.png'; import logo from './logo/logo.png';
export const ChevronRight = chevron_right; export const ChevronRight = chevron_right;
export const CircleGreenBackground = circle_green_background; export const CircleGreenBackground = circle_green_background;
export const CircleRedBackground = circle_red_background;
export const Eye = eye; export const Eye = eye;
export const EyeRed = eye_red;
export const EyeSlash = eye_slash; export const EyeSlash = eye_slash;
export const Gear = gear_fill;
export const Heart = heart; export const Heart = heart;
export const Logo = logo; export const Logo = logo;
export const Pause = pause;
export const Play = play;
export const PlusCircle = plus_circle; export const PlusCircle = plus_circle;
export const Star = star;
export const ThumbsDown = thumbs_down;
export const ThumbsUp = thumbs_up;
export const XLg = x_lg; export const XLg = x_lg;

View file

@ -31,7 +31,7 @@
text-decoration: none; text-decoration: none;
/* width: 20%; */ /* width: 20%; */
height: 40px; height: 40px;
width: 70px; min-width: 70px;
} }
.modal-button-cancel { .modal-button-cancel {
@ -46,21 +46,35 @@
text-decoration: none; text-decoration: none;
/* width: 20%; */ /* width: 20%; */
height: 40px; height: 40px;
width: 70px; min-width: 70px;
} }
.modal-button-delete { .modal-button-delete {
background-color: transparent; background-color: transparent;
border: 1px solid red; background-color: #f23160;
border: 1px solid #f23160;
border-radius: 5px; border-radius: 5px;
color: red; color: #eee;
cursor: pointer; cursor: pointer;
font-size: 18px; font-size: 18px;
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
/* width: 20%; */
height: 40px; height: 40px;
width: 70px; min-width: 70px;
}
.modal-button-delete-inactive {
background-color: transparent;
background-color: #d6e0ea80;
border: 1px solid #d6e0ea80;
border-radius: 5px;
color: #eee;
cursor: pointer;
font-size: 18px;
font-weight: bold;
text-decoration: none;
height: 40px;
min-width: 70px;
} }
.modal-close { .modal-close {
@ -121,6 +135,20 @@
font-size: 24px; font-size: 24px;
} }
.small-modal-button-cancel {
background-color: transparent;
border: 1px solid #495a6a;
border-radius: 5px;
color: #495a6a;
cursor: pointer;
font-size: 18px;
font-weight: bold;
text-decoration: none;
/* width: 20%; */
height: 40px;
min-width: 70px;
}
.small-modal-button-delete { .small-modal-button-delete {
background-color: red; background-color: red;
border: none; border: none;
@ -131,7 +159,7 @@
font-weight: bold; font-weight: bold;
text-decoration: none; text-decoration: none;
/* width: 20%; */ /* width: 20%; */
width: 70px; min-width: 70px;
} }
.small-modal-container { .small-modal-container {

View file

@ -27,7 +27,14 @@ export function Modal(props) {
</button> </button>
)} )}
{props.deleteButton && ( {props.deleteButton && (
<button className='modal-button-delete' onClick={props.onDelete}> <button
className={
props.deleteActive
? 'modal-button-delete'
: 'modal-button-delete-inactive'
}
onClick={props.onDelete}
>
{props.deleteButton} {props.deleteButton}
</button> </button>
)} )}
@ -70,7 +77,7 @@ export function SmallModal(props) {
</div> </div>
<div className='small-modal-footer'> <div className='small-modal-footer'>
{props.cancelButton && ( {props.cancelButton && (
<button className='modal-button-cancel' onClick={props.onCancel}> <button className='small-modal-button-cancel' onClick={props.onCancel}>
{props.cancelButton} {props.cancelButton}
</button> </button>
)} )}

View file

@ -0,0 +1,356 @@
.modal-delete-page {
align-items: center;
display: flex;
flex-direction: column;
height: 100%;
justify-content: space-evenly;
width: 100%;
}
.modal-delete-page-header {
align-items: center;
display: flex;
flex-direction: column;
}
.modal-delete-page-input {
display: flex;
flex-direction: row;
justify-content: space-between;
margin-bottom: 10px;
width: 100%;
}
.modal-delete-page-input-text {
background-color: #061726;
border: none;
border-radius: 5px;
box-sizing: border-box;
color: white;
font-family: monospace;
font-size: 16px;
outline: none;
padding: 10px;
resize: none;
width: 100%;
}
.modal-delete-page-subtitle {
color: white;
font-family: sans-serif;
font-size: 14px;
margin-top: 10px;
text-align: center;
}
.modal-delete-page-title {
color: white;
font-family: sans-serif;
font-size: 24px;
font-weight: bold;
text-align: center;
}
.modal-delete-page-body {
align-items: center;
display: flex;
flex-direction: column;
width: 100%;
}
.page-activity {
height: 100%;
overflow-y: auto;
width: 100%;
}
.page-activity-header {
align-items: center;
border-bottom: 1px solid #061726;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: space-evenly;
padding: 10px 20px;
width: 100%;
}
.page-activity-stat {
align-items: center;
display: flex;
flex-direction: row;
justify-content: space-between;
width: 175px;
}
.page-activity-stat-text {
color: white;
font-family: monospace;
font-size: 16px;
}
.page-activity-stat-title {
color: white;
font-family: sans-serif;
font-size: 16px;
}
.page-details {
align-items: center;
background-color: #1f2e3c;
display: flex;
flex-direction: column;
justify-content: space-between;
height: 100%;
/* padding: 0px 10px; */
width: 300px;
min-width: 300px;
}
.page-details-footer {
align-items: start;
border-top: 1px solid #061726;
box-sizing: border-box;
display: flex;
flex-direction: column;
/* height: 50px; */
justify-content: center;
padding: 10px 0px;
width: 100%;
}
.page-details-footer-title {
color: #eee;
font-family: sans-serif;
font-weight: bold;
padding: 0px 10px 10px 10px;
}
.page-details-footer-categories {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: column;
justify-content: start;
padding: 0px 10px;
width: 100%;
}
.page-details-footer-category {
align-items: center;
box-sizing: border-box;
color: #eee;
display: flex;
flex-direction: row;
font-family: sans-serif;
font-weight: bold;
justify-content: center;
padding-bottom: 5px;
text-align: center;
width: 100%;
}
.page-details-footer-stats {
align-items: center;
box-sizing: border-box;
display: flex;
flex-direction: row;
justify-content: space-evenly;
padding-bottom: 10px;
width: 100%;
}
.page-details-footer-stat {
align-items: center;
display: flex;
flex-direction: row;
justify-content: center;
width: 100px;
}
.page-details-footer-stat-icon {
height: 20px;
width: 20px;
}
.page-details-footer-stat-text {
color: white;
font-family: monospace;
font-size: 16px;
padding-left: 5px;
}
.page-details-footer-stat-text-red {
color: #f23160;
font-family: monospace;
font-size: 16px;
padding-left: 5px;
}
.page-details-header {
align-items: center;
border-bottom: 1px solid #061726;
box-sizing: border-box;
display: flex;
flex-direction: row;
/* min-height: 80px; */
justify-content: space-between;
padding: 10px 20px;
width: 100%;
}
.page-details-header-left {
align-items: start;
display: flex;
flex-direction: column;
justify-content: center;
}
.page-details-header-right {
align-items: end;
display: flex;
flex-direction: row;
justify-content: end;
}
.page-details-header-button {
align-items: center;
background-color: #1f2e3c;
border: none;
display: flex;
justify-content: center;
padding-left: 10px;
padding-right: 0px;
}
.page-details-header-button:hover {
cursor: pointer;
}
.page-details-header-icon {
height: 24px;
width: 24px;
}
.page-details-header-title {
color: #eee;
font-family: sans-serif;
font-weight: bold;
}
.page-details-header-type {
color: #eee;
font-family: sans-serif;
font-size: 12px;
}
.page-details-settings {
background-color: #000312;
border-radius: 3px;
padding: 5px;
position: fixed;
transform: translate(0px, 60px);
transition: all 1s;
width: 270px;
z-index: 10;
}
.page-details-settings-background {
align-items: center;
display: flex;
height: 100vh;
justify-content: center;
left: 0;
opacity: 0;
position: absolute;
top: 0;
width: 100vw;
z-index: 8;
}
.page-details-settings-button {
color: #eee;
font-family: sans-serif;
font-size: 16px;
background-color: #000312;
border: none;
border-radius: 3px;
box-sizing: border-box;
padding: 5px;
width: 100%;
}
.page-details-settings-button:hover {
background-color: #77b23b;
color: #000312;
cursor: pointer;
}
.page-details-settings-text {
color: #eee;
font-family: sans-serif;
font-size: 16px;
}
.page-event {
border-bottom: 1px solid #061726;
color: white;
display: flex;
flex-direction: row;
font-family: sans-serif;
justify-content: space-between;
overflow-x: wrap;
padding: 10px 20px;
}
.page-event-left {
align-items: center;
display: flex;
flex-direction: row;
}
.page-event-icon {
width: 20px;
height: 20px;
padding-right: 10px;
}
.page-event-left-text {
display: flex;
flex-direction: column;
}
.page-event-username {
font-size: 14px;
font-weight: bold;
max-width: 150px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.page-event-description {
color: #88a0b8;
font-size: 14px;
}
.page-event-date {
align-items: center;
display: flex;
font-family: monospace;
text-align: end;
}
.page-inactive {
align-items: center;
display: flex;
flex-direction: column;
justify-content: center;
height: 100%;
width: 100%;
}
.page-inactive-text {
/* border: 1px solid #eee;
border-radius: 30px; */
color: #eee;
padding: 20px;
}

View file

@ -0,0 +1,795 @@
import { useEffect, useState } from 'react';
import { EventsOn } from '../../wailsjs/runtime/runtime';
import {
Eye,
EyeRed,
EyeSlash,
Gear,
Heart,
Play,
Pause,
Star,
ThumbsDown,
ThumbsUp,
} from '../assets';
import './PageDetails.css';
import {
ActivateAccount,
ActivateChannel,
DeleteAccount,
DeleteChannel,
Login,
Logout,
UpdateAccountApi,
UpdateChannelApi,
} from '../../wailsjs/go/main/App';
import { Modal, SmallModal } from './Modal';
function countString(value) {
switch (true) {
case value <= 0 || value == undefined:
return '0';
case value < 1000:
return value;
case value < 1000000:
return (value / 1000).toFixed(3).slice(0, -2) + 'K';
case value < 1000000000:
return (value / 1000000).toFixed(6).slice(0, -5) + 'M';
default:
return 'Inf';
}
}
function PageDetails(props) {
const [activate, setActivate] = useState(false);
const [active, setActive] = useState(false);
const [activity, setActivity] = useState(null);
const [openApi, setOpenApi] = useState(false);
const [apiValid, setApiValid] = useState(true);
const [editingApi, setEditingApi] = useState(false);
const [editApi, setEditApi] = useState('');
const updateEditApi = (event) => {
setEditApi(event.target.value);
};
const [openDelete, setOpenDelete] = useState(false);
const [deleting, setDeleting] = useState(false);
const [deleteName, setDeleteName] = useState('');
const updateDeleteName = (event) => {
if (deleting) {
return;
}
setDeleteName(event.target.value);
};
const [details, setDetails] = useState(null);
const [error, setError] = useState('');
const [live, setLive] = useState(false);
const [liveTitle, setLiveTitle] = useState('');
const [openLogin, setOpenLogin] = useState(false);
const [loggingIn, setLoggingIn] = useState(false);
const [loginUsername, setLoginUsername] = useState('');
const updateLoginUsername = (event) => {
if (loggingIn) {
return;
}
setLoginUsername(event.target.value);
};
const [loginUsernameValid, setLoginUsernameValid] = useState(true);
const [loginPassword, setLoginPassword] = useState('');
const updateLoginPassword = (event) => {
if (loggingIn) {
return;
}
setLoginPassword(event.target.value);
};
const [loginPasswordValid, setLoginPasswordValid] = useState(true);
const [openLogout, setOpenLogout] = useState(false);
const [loggingOut, setLoggingOut] = useState(false);
const [settings, setSettings] = useState(false);
const triggerSettings = () => setSettings(!settings);
useEffect(() => {
EventsOn('PageDetails', (event) => {
setDetails(event);
// TODO: do I need to reset all editing/logging out/etc. values?
});
EventsOn('PageActivity', (event) => {
setActivity(event);
setActive(true);
if (event.livestreams.length > 0) {
setLive(true);
} else {
setLive(false);
}
});
EventsOn('PageActive', (event) => {
if (event) {
setActive(true);
} else {
setActive(false);
setActivity(null);
setLive(false);
}
});
}, []);
useEffect(() => {
if (deleting) {
switch (true) {
case details.type === 'Channel':
DeleteChannel(details.id)
.then(() => resetDelete())
.catch((error) => {
setDeleting(false);
setError(error);
});
return;
case details.type === 'Account':
DeleteAccount(details.id)
.then(() => resetDelete())
.catch((error) => {
setDeleting(false);
setError(error);
});
return;
}
}
}, [deleting]);
useEffect(() => {
if (editingApi) {
switch (true) {
case details.type === 'Channel':
UpdateChannelApi(details.id, editApi)
.then(() => resetEditApi())
.catch((error) => {
setEditingApi(false);
setError(error);
});
return;
case details.type === 'Account':
UpdateAccountApi(details.id, editApi)
.then(() => resetEditApi())
.catch((error) => {
setEditingApi(false);
setError(error);
});
return;
}
}
}, [editingApi]);
useEffect(() => {
if (loggingIn && details.type === 'Account') {
Login(loginUsername, loginPassword)
.then(() => {
resetLogin();
})
.catch((error) => {
setLoggingIn(false);
setError(error);
});
} else if (loggingIn && details.type === 'Channel') {
resetLogin();
}
}, [loggingIn]);
useEffect(() => {
if (loggingOut && details.type === 'Account') {
Logout(details.id)
.catch((error) => {
setError(error);
})
.finally(() => resetLogout());
} else if (loggingOut && details.type === 'Channel') {
resetLogout();
}
}, [loggingOut]);
const activatePage = () => {
switch (true) {
case details.type === 'Channel':
ActivateChannel(details.id).catch((error) => {
setError(error);
});
return;
case details.type === 'Account':
ActivateAccount(details.id).catch((error) => {
setError(error);
});
return;
}
};
const deletePage = () => {
if (deleting || details.title !== deleteName) {
return;
}
setDeleting(true);
};
const resetDelete = () => {
setDeleteName('');
setDeleting(false);
setOpenDelete(false);
};
const submitEditApi = () => {
if (editingApi) {
return;
}
if (editApi === '') {
setApiValid(false);
return;
}
setEditingApi(true);
};
const closeEditApi = () => {
if (editingApi) {
return;
}
resetEditApi();
};
const resetEditApi = () => {
setOpenApi(false);
setApiValid(true);
setEditApi('');
setEditingApi(false);
};
const login = () => {
if (loginUsername === '') {
setLoginUsernameValid(false);
return;
}
if (loginPassword === '') {
setLoginPasswordValid(false);
return;
}
setLoggingIn(true);
};
const closeLogin = () => {
if (loggingIn) {
return;
}
setOpenLogin(false);
};
const resetLogin = () => {
setLoggingIn(false);
setOpenLogin(false);
};
const logout = () => {
setLoggingOut(true);
};
const closeLogout = () => {
if (loggingOut) {
return;
}
setOpenLogout(false);
};
const resetLogout = () => {
setLoggingOut(false);
setOpenLogout(false);
};
return (
<>
{openLogin && (
<Modal
cancelButton={'Cancel'}
onCancel={closeLogin}
onClose={closeLogin}
show={openLogin}
style={{
height: '480px',
minHeight: '480px',
width: '360px',
minWidth: '360px',
}}
submitButton={'Login'}
submitLoading={loggingIn}
onSubmit={login}
>
<ModalLogin
password={loginPassword}
passwordValid={loginPasswordValid}
updatePassword={updateLoginPassword}
username={loginUsername}
usernameValid={loginUsernameValid}
updateUsername={updateLoginUsername}
/>
</Modal>
)}
{openLogout && (
<SmallModal
cancelButton={'Cancel'}
onCancel={closeLogout}
onClose={closeLogout}
show={openLogout}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
title={'Logout'}
message={'Are you sure you want to log out of ' + details.title + '?'}
submitButton={loggingOut ? 'Logging out...' : 'Logout'}
onSubmit={logout}
/>
)}
{error !== '' && (
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
)}
{openDelete && (
<Modal
cancelButton={'Cancel'}
onCancel={resetDelete}
onClose={resetDelete}
deleteButton={deleting ? 'Deleting' : 'Delete'}
onDelete={deletePage}
deleteActive={details.title === deleteName}
pageName={details.title}
show={openDelete}
style={{
height: '350px',
minHeight: '350px',
width: '350px',
minWidth: '350px',
}}
>
<div className='modal-delete-page'>
<div className='modal-delete-page-header'>
<span className='modal-delete-page-title'>Delete page</span>
<span className='modal-delete-page-subtitle'>
Are you sure you want to delete <b>{details.title}</b>? This cannot
be undone. You must type '{details.title}' into the box to delete.
</span>
</div>
<div className='modal-delete-page-body'>
<div className='modal-delete-page-input'>
<input
className='modal-delete-page-input-text'
onChange={updateDeleteName}
placeholder={details.title}
type={'text'}
value={deleteName}
></input>
</div>
</div>
</div>
</Modal>
)}
{openApi && (
<Modal
cancelButton={'Cancel'}
onCancel={closeEditApi}
onClose={closeEditApi}
show={openApi}
style={{
height: '480px',
minHeight: '480px',
width: '360px',
minWidth: '360px',
}}
submitButton={'Submit'}
submitLoading={editingApi}
onSubmit={submitEditApi}
>
<ModalEditApi
apiKey={editApi}
updateApiKey={updateEditApi}
apiValid={apiValid}
/>
</Modal>
)}
<div className='page-details'>
{details !== null && (
<>
<div className='page-details-header'>
<div className='page-details-header-left'>
<span className='page-details-header-title'>{details.title}</span>
<span className='page-details-header-type'>{details.type}</span>
</div>
<div className='page-details-header-right'>
{details.has_api && (
<button
className='page-details-header-button'
onClick={activatePage}
>
<img
className='page-details-header-icon'
src={active ? Pause : Play}
/>
</button>
)}
<button
className='page-details-header-button'
onClick={triggerSettings}
>
<img className='page-details-header-icon' src={Gear} />
</button>
</div>
</div>
{settings && (
<>
<div
className='page-details-settings-background'
onClick={triggerSettings}
></div>
<div className='page-details-settings'>
{details.type === 'Account' && (
<button
className='page-details-settings-button'
onClick={() => {
triggerSettings();
if (details.logged_in) {
setOpenLogout(true);
} else {
setOpenLogin(true);
}
}}
>
{details.logged_in ? 'Logout' : 'Login'}
</button>
)}
<button
className='page-details-settings-button'
onClick={() => {
triggerSettings();
setOpenApi(true);
}}
>
Edit API key
</button>
<button
className='page-details-settings-button'
onClick={() => {
triggerSettings();
setOpenDelete(true);
}}
>
Delete
</button>
</div>
</>
)}
{active && activity !== null && (
<>
<PageActivity activity={activity} />
{live && (
<DetailsFooter
categories={activity.livestreams[0].categories}
dislikes={activity.livestreams[0].dislikes}
likes={activity.livestreams[0].likes}
title={activity.livestreams[0].title}
viewers={activity.livestreams[0].watching_now}
/>
)}
</>
)}
{!active && (
<div className='page-inactive'>
<span className='page-inactive-text'>
{details.has_api
? 'Press play to start API'
: 'Open settings to add API key'}
</span>
</div>
)}
</>
)}
</div>
</>
);
}
export default PageDetails;
function PageActivity(props) {
const eventDate = (event) => {
if (event.followed_on) {
return event.followed_on;
}
if (event.subscribed_on) {
return event.subscribed_on;
}
};
const sortEvents = () => {
let sorted = [
...props.activity.followers.recent_followers,
...props.activity.subscribers.recent_subscribers,
].sort((a, b) => (eventDate(a) < eventDate(b) ? 1 : -1));
return sorted;
};
return (
<>
<div className='page-activity-header'>
<div className='page-activity-stat'>
<span className='page-activity-stat-title'>Followers:</span>
<span className='page-activity-stat-text'>
{countString(props.activity.followers.num_followers)}
</span>
</div>
<div className='page-activity-stat'>
<span className='page-activity-stat-title'>Subscribers:</span>
<span className='page-activity-stat-text'>
{countString(props.activity.subscribers.num_subscribers)}
</span>
</div>
</div>
<div className='page-activity'>
<div className='page-activity-list'>
{sortEvents().map((event, index) => (
<PageEvent event={event} key={index} />
))}
</div>
</div>
</>
);
}
function PageEvent(props) {
const dateDate = (date) => {
const options = { month: 'short' };
let month = new Intl.DateTimeFormat('en-US', options).format(date);
let day = date.getDate();
return month + ' ' + day;
};
const dateDay = (date) => {
let now = new Date();
let today = now.getDay();
switch (date.getDay()) {
case 0:
return 'Sunday';
case 1:
return 'Monday';
case 2:
return 'Tuesday';
case 3:
return 'Wednesday';
case 4:
return 'Thursday';
case 5:
return 'Friday';
case 6:
return 'Saturday';
}
};
const dateTime = (date) => {
let now = new Date();
let today = now.getDay();
let day = date.getDay();
if (today !== day) {
return dateDay(date);
}
let hours24 = date.getHours();
let hours = hours24 % 12 || 12;
let minutes = date.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
let mer = 'pm';
if (hours24 < 12) {
mer = 'am';
}
return hours + ':' + minutes + ' ' + mer;
};
const dateString = (d) => {
if (isNaN(Date.parse(d))) {
return 'Who knows?';
}
let now = new Date();
let date = new Date(d);
// Fix Rumble's timezone problem
date.setHours(date.getHours() - 4);
let diff = now - date;
switch (true) {
case diff < 0:
return 'In the future!?';
case diff < 60000:
return 'Now';
case diff < 3600000:
let minutes = Math.floor(diff / 1000 / 60);
let postfix = ' mins ago';
if (minutes == 1) {
postfix = ' min ago';
}
return minutes + postfix;
case diff < 86400000:
return dateTime(date);
case diff < 604800000:
return dateDay(date);
default:
return dateDate(date);
}
};
return (
<div className='page-event'>
<div className='page-event-left'>
{props.event.followed_on && <img className='page-event-icon' src={Heart}></img>}
{props.event.subscribed_on && <img className='page-event-icon' src={Star}></img>}
<div className='page-event-left-text'>
<span className='page-event-username'>{props.event.username}</span>
<span className='page-event-description'>
{props.event.followed_on && 'Followed you'}
{props.event.subscribed_on && 'Subscribed'}
</span>
</div>
</div>
<span className='page-event-date'>
{props.event.followed_on && dateString(props.event.followed_on)}
{props.event.subscribed_on && dateString(props.event.subscribed_on)}
</span>
</div>
);
}
function DetailsFooter(props) {
return (
<div className='page-details-footer'>
<span className='page-details-footer-title'>{props.title}</span>
<div className='page-details-footer-stats'>
<div className='page-details-footer-stat'>
<img className='page-details-footer-stat-icon' src={EyeRed} />
<span className='page-details-footer-stat-text-red'>
{countString(props.viewers)}
</span>
</div>
<div className='page-details-footer-stat'>
<img className='page-details-footer-stat-icon' src={ThumbsUp} />
<span className='page-details-footer-stat-text'>
{countString(props.likes)}
</span>
</div>
<div className='page-details-footer-stat'>
<img className='page-details-footer-stat-icon' src={ThumbsDown} />
<span className='page-details-footer-stat-text'>
{countString(props.dislikes)}
</span>
</div>
</div>
<div className='page-details-footer-categories'>
<span className='page-details-footer-category'>
{props.categories.primary.title}
</span>
<span className='page-details-footer-category'>
{props.categories.secondary.title}
</span>
</div>
</div>
);
}
function ModalEditApi(props) {
const [showKey, setShowKey] = useState(false);
const updateShowKey = () => setShowKey(!showKey);
return (
<div className='modal-add-account-channel'>
<div className='modal-add-account-channel-header'>
<span className='modal-add-account-channel-title'>Edit API Key</span>
<span className='modal-add-account-channel-subtitle'>Enter new API key below</span>
</div>
<div className='modal-add-account-channel-body'>
{props.apiValid === false ? (
<label className='modal-add-channel-label-warning'>
API KEY - Please enter a valid API key
</label>
) : (
<label className='modal-add-channel-label'>API KEY</label>
)}
<div className='modal-add-channel-key'>
<input
className='modal-add-channel-key-input'
onChange={props.updateApiKey}
placeholder={'Enter API key'}
type={showKey ? 'text' : 'password'}
value={props.apiKey}
></input>
<button className='modal-add-channel-key-show' onClick={updateShowKey}>
<img
className='modal-add-channel-key-show-icon'
src={showKey ? EyeSlash : Eye}
/>
</button>
</div>
<span className='modal-add-channel-description'>API KEYS SHOULD LOOK LIKE</span>
<span className='modal-add-channel-description-subtext'>
https://rumble.com/-livestream-api/get-data?key=really-long_string-of_random-characters
</span>
</div>
<div></div>
</div>
);
}
function ModalLogin(props) {
const [showPassword, setShowPassword] = useState(false);
const updateShowPassword = () => setShowPassword(!showPassword);
return (
<div className='modal-add-account-channel'>
<div className='modal-add-account-channel-header'>
<span className='modal-add-account-channel-title'>Login</span>
<span className='modal-add-account-channel-subtitle'>
Log into your Rumble account
</span>
</div>
<div className='modal-add-account-channel-body'>
{props.usernameValid === false ? (
<label className='modal-add-account-channel-label-warning'>
USERNAME - Please enter a valid username
</label>
) : (
<label className='modal-add-account-channel-label'>USERNAME</label>
)}
<div className='modal-add-account-channel-input'>
<input
className='modal-add-account-channel-input-text'
onChange={!props.loading && props.updateUsername}
placeholder={'Username'}
type={'text'}
value={props.username}
></input>
</div>
{props.passwordValid === false ? (
<label className='modal-add-account-channel-label-warning'>
PASSWORD - Please enter a valid password
</label>
) : (
<label className='modal-add-account-channel-label'>PASSWORD</label>
)}
<div className='modal-add-account-channel-input'>
<input
className='modal-add-account-channel-input-password'
onChange={!props.loading && props.updatePassword}
placeholder={'Password'}
type={showPassword ? 'text' : 'password'}
value={props.password}
></input>
<button
className='modal-add-account-channel-input-show'
onClick={updateShowPassword}
>
<img
className='modal-add-account-channel-input-show-icon'
src={showPassword ? EyeSlash : Eye}
/>
</button>
</div>
</div>
<div></div>
</div>
);
}

View file

@ -1,5 +1,4 @@
.page-sidebar {
.channel-sidebar {
align-items: center; align-items: center;
background-color: #061726; background-color: #061726;
display: flex; display: flex;
@ -7,18 +6,19 @@
justify-content: space-between; justify-content: space-between;
height: 100%; height: 100%;
padding: 0px 10px; padding: 0px 10px;
width: 60px;
} }
.channel-sidebar-account-list { .page-sidebar-account-list {
border-top: 2px solid #273848; border-top: 2px solid #273848;
padding-bottom: 10px; padding-bottom: 10px;
} }
.channel-sidebar-body { .page-sidebar-body {
overflow-y: auto; overflow-y: auto;
} }
.channel-sidebar-button { .page-sidebar-button {
align-items: center; align-items: center;
background-color: #061726; background-color: #061726;
border: none; border: none;
@ -27,27 +27,31 @@
padding: 0px; padding: 0px;
} }
.channel-sidebar-button:hover { .page-sidebar-button:hover {
cursor: pointer; cursor: pointer;
} }
.channel-sidebar-button-icon { .page-sidebar-button-icon {
height: 60px; height: 60px;
width: 60px; width: 60px;
} }
.channel-sidebar-footer { .page-sidebar-footer {
padding-bottom: 10px; padding-bottom: 10px;
} }
.channel-sidebar-icon { .page-sidebar-icon {
height: 60px; height: 60px;
margin-top: 10px; margin-top: 10px;
position: relative; position: relative;
width: 60px; width: 60px;
} }
.channel-sidebar-icon-account { .page-sidebar-icon:hover {
cursor: pointer;
}
.page-sidebar-icon-account {
bottom: 0px; bottom: 0px;
height: 24px; height: 24px;
left: 36px; left: 36px;
@ -55,7 +59,7 @@
width: 24px; width: 24px;
} }
.channel-sidebar-icon-hover { .page-sidebar-icon-hover {
background-color: #061726; background-color: #061726;
border-radius: 5px; border-radius: 5px;
color: black; color: black;
@ -64,7 +68,7 @@
/* transform: translate(75px, -50px); */ /* transform: translate(75px, -50px); */
z-index: 10; z-index: 10;
} }
.channel-sidebar-icon-hover:before { .page-sidebar-icon-hover:before {
content:""; content:"";
position: absolute; position: absolute;
width: 0; width: 0;
@ -75,34 +79,35 @@
margin: 7px 0 0 -13px; margin: 7px 0 0 -13px;
} }
.channel-sidebar-icon-hover-text { .page-sidebar-icon-hover-text {
color: white; color: white;
font-family: sans-serif; font-family: sans-serif;
font-weight: bold; font-weight: bold;
font-size: 16px; font-size: 16px;
} }
.channel-sidebar-icon-image { .page-sidebar-icon-image {
/* border: 3px solid #85c742; */ /* border: 3px solid #85c742; */
/* border: 3px solid #ec0; */ /* border: 3px solid #ec0; */
border: 3px solid #f23160; /* border: 3px solid #f23160; */
border-radius: 50%; border-radius: 50%;
height: 54px; height: 54px;
transition: border-radius 0.25s; transition: border-radius 0.25s;
width: 54px; width: 54px;
} }
.channel-sidebar-icon-image:hover { .page-sidebar-icon-image:hover {
border-radius: 30%; border-radius: 30%;
transition: border-radius 0.25s; transition: border-radius 0.25s;
} }
.channel-sidebar-icon-initial { .page-sidebar-icon-initial {
align-items: center; align-items: center;
background-color: #3377cc; background-color: #3377cc;
/* border: 3px solid #3377cc; */
/* border: 3px solid #85c742; */ /* border: 3px solid #85c742; */
/* border: 3px solid #ec0; */ /* border: 3px solid #ec0; */
border: 3px solid #f23160; /* border: 3px solid #f23160; */
border-radius: 50%; border-radius: 50%;
color: #eee; color: #eee;
display: flex; display: flex;
@ -115,7 +120,7 @@
width: 54px; width: 54px;
} }
.channel-sidebar-icon-initial:hover { .page-sidebar-icon-initial:hover {
border-radius: 30%; border-radius: 30%;
transition: border-radius 0.25s; transition: border-radius 0.25s;
} }

View file

@ -1,17 +1,38 @@
import { useEffect, useState } from 'react'; import { useEffect, useState } from 'react';
import { Modal, SmallModal } from './Modal'; import { Modal, SmallModal } from './Modal';
import { AccountList, AddChannel, Login } from '../../wailsjs/go/main/App'; import {
AccountList,
AddPage,
Login,
OpenAccount,
OpenChannel,
PageStatus,
} from '../../wailsjs/go/main/App';
import { EventsOff, EventsOn } from '../../wailsjs/runtime/runtime';
import { ChevronRight, CircleGreenBackground, Eye, EyeSlash, PlusCircle } from '../assets'; import {
import './ChannelSideBar.css'; ChevronRight,
CircleGreenBackground,
CircleRedBackground,
Eye,
EyeSlash,
PlusCircle,
} from '../assets';
import './PageSideBar.css';
function ChannelSideBar(props) { function PageSideBar(props) {
const [accounts, setAccounts] = useState({}); const [accounts, setAccounts] = useState({});
const [error, setError] = useState(''); const [error, setError] = useState('');
const [addOpen, setAddOpen] = useState(false); const [addOpen, setAddOpen] = useState(false);
const [refresh, setRefresh] = useState(false); const [refresh, setRefresh] = useState(false);
const [scrollY, setScrollY] = useState(0); const [scrollY, setScrollY] = useState(0);
useEffect(() => {
EventsOn('PageSideBarAccounts', (event) => {
setAccounts(event);
});
}, []);
useEffect(() => { useEffect(() => {
AccountList() AccountList()
.then((response) => { .then((response) => {
@ -38,8 +59,17 @@ function ChannelSideBar(props) {
setScrollY(event.target.scrollTop); setScrollY(event.target.scrollTop);
}; };
const openAccount = (account) => {
OpenAccount(account.id).catch((error) => setError(error));
};
const openChannel = (channel) => {
OpenChannel(channel.id).catch((error) => setError(error));
};
return ( return (
<> <>
{addOpen && (
<ModalAdd <ModalAdd
onClose={() => setAddOpen(false)} onClose={() => setAddOpen(false)}
onRefresh={() => { onRefresh={() => {
@ -47,18 +77,21 @@ function ChannelSideBar(props) {
}} }}
show={addOpen} show={addOpen}
/> />
<div className='channel-sidebar'> )}
<div className='channel-sidebar-body' onScroll={handleScroll}> <div className='page-sidebar'>
<div className='page-sidebar-body' onScroll={handleScroll}>
{sortAccounts().map((account, index) => ( {sortAccounts().map((account, index) => (
<AccountChannels <AccountChannels
account={accounts[account]} account={accounts[account]}
key={index} key={index}
openAccount={openAccount}
openChannel={openChannel}
scrollY={scrollY} scrollY={scrollY}
top={index === 0} top={index === 0}
/> />
))} ))}
</div> </div>
<div className='channel-sidebar-footer'> <div className='page-sidebar-footer'>
<ButtonIcon <ButtonIcon
hoverText={'Add an account/channel'} hoverText={'Add an account/channel'}
onClick={() => setAddOpen(true)} onClick={() => setAddOpen(true)}
@ -70,7 +103,7 @@ function ChannelSideBar(props) {
); );
} }
export default ChannelSideBar; export default PageSideBar;
function AccountChannels(props) { function AccountChannels(props) {
const sortChannels = () => { const sortChannels = () => {
@ -84,12 +117,24 @@ function AccountChannels(props) {
if (props.account.account !== undefined) { if (props.account.account !== undefined) {
return ( return (
<div <div
className='channel-sidebar-account-list' className='page-sidebar-account-list'
style={props.top ? { borderTop: 'none' } : {}} style={props.top ? { borderTop: 'none' } : {}}
> >
<AccountIcon account={props.account.account} key={0} scrollY={props.scrollY} /> <button
className='page-sidebar-button'
key={0}
onClick={() => props.openAccount(props.account.account)}
>
<AccountIcon account={props.account.account} scrollY={props.scrollY} />
</button>
{sortChannels().map((channel, index) => ( {sortChannels().map((channel, index) => (
<ChannelIcon channel={channel} key={index + 1} scrollY={props.scrollY} /> <button
className='page-sidebar-button'
key={index + 1}
onClick={() => props.openChannel(channel)}
>
<ChannelIcon channel={channel} scrollY={props.scrollY} />
</button>
))} ))}
</div> </div>
); );
@ -97,25 +142,82 @@ function AccountChannels(props) {
} }
function AccountIcon(props) { function AccountIcon(props) {
const [apiActive, setApiActive] = useState(false);
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const [isLive, setIsLive] = useState(false);
const [loggedIn, setLoggedIn] = useState(props.account.cookies !== null);
const [username, setUsername] = useState(props.account.username);
const iconBorder = () => {
if (!apiActive) {
return '3px solid #3377cc';
}
if (isLive) {
return '3px solid #85c742';
} else {
return '3px solid #f23160';
}
};
const pageName = (name) => {
if (name === undefined) return;
return '/user/' + name;
};
useEffect(() => {
if (username !== props.account.username) {
EventsOff(
'ApiActive-' + pageName(username),
'LoggedIn-' + pageName(username),
'PageLive-' + pageName(username)
);
setApiActive(false);
setIsLive(false);
}
EventsOn('ApiActive-' + pageName(props.account.username), (event) => {
setApiActive(event);
});
EventsOn('LoggedIn-' + pageName(props.account.username), (event) => {
setLoggedIn(event);
});
EventsOn('PageLive-' + pageName(props.account.username), (event) => {
setIsLive(event);
});
setUsername(props.account.username);
}, [props.account.username]);
useEffect(() => {
if (username !== '') {
PageStatus(pageName(username));
}
}, [username]);
return ( return (
<div <div
className='channel-sidebar-icon' className='page-sidebar-icon'
onMouseEnter={() => setHover(true)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)} onMouseLeave={() => setHover(false)}
> >
{props.account.profile_image === null ? ( {props.account.profile_image === null ? (
<span className='channel-sidebar-icon-initial'> <span className='page-sidebar-icon-initial' style={{ border: iconBorder() }}>
{props.account.username[0].toUpperCase()} {props.account.username[0].toUpperCase()}
</span> </span>
) : ( ) : (
<img className='channel-sidebar-icon-image' src={props.account.profile_image} /> <img
)} className='page-sidebar-icon-image'
<img className='channel-sidebar-icon-account' src={CircleGreenBackground} /> src={props.account.profile_image}
{hover && ( style={{ border: iconBorder() }}
<HoverName name={'/user/' + props.account.username} scrollY={props.scrollY} /> />
)} )}
<img
className='page-sidebar-icon-account'
src={loggedIn ? CircleGreenBackground : CircleRedBackground}
/>
{hover && <HoverName name={pageName(username)} scrollY={props.scrollY} />}
</div> </div>
); );
} }
@ -125,12 +227,12 @@ function ButtonIcon(props) {
return ( return (
<div <div
className='channel-sidebar-icon' className='page-sidebar-icon'
onMouseEnter={() => setHover(true)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)} onMouseLeave={() => setHover(false)}
> >
<button className='channel-sidebar-button' onClick={props.onClick}> <button className='page-sidebar-button' onClick={props.onClick}>
<img className='channel-sidebar-button-icon' src={PlusCircle} /> <img className='page-sidebar-button-icon' src={PlusCircle} />
</button> </button>
{hover && <HoverName name={props.hoverText} scrollY={props.scrollY} />} {hover && <HoverName name={props.hoverText} scrollY={props.scrollY} />}
</div> </div>
@ -138,26 +240,69 @@ function ButtonIcon(props) {
} }
function ChannelIcon(props) { function ChannelIcon(props) {
const [apiActive, setApiActive] = useState(false);
const [channelName, setChannelName] = useState(props.channel.name);
const [hover, setHover] = useState(false); const [hover, setHover] = useState(false);
const [isLive, setIsLive] = useState(false);
const iconBorder = () => {
if (!apiActive) {
return '3px solid #3377cc';
}
if (isLive) {
return '3px solid #85c742';
} else {
return '3px solid #f23160';
}
};
const pageName = (name) => {
if (name === undefined) return;
return '/c/' + name.replace(/\s/g, '');
};
useEffect(() => {
if (channelName !== props.channel.name) {
EventsOff('PageLive-' + pageName(channelName), 'ApiActive-' + pageName(channelName));
setApiActive(false);
setIsLive(false);
}
EventsOn('PageLive-' + pageName(props.channel.name), (event) => {
setIsLive(event);
});
EventsOn('ApiActive-' + pageName(props.channel.name), (event) => {
setApiActive(event);
});
setChannelName(props.channel.name);
}, [props.channel.name]);
useEffect(() => {
if (channelName !== '') {
PageStatus(pageName(channelName));
}
}, [channelName]);
return ( return (
<div <div
className='channel-sidebar-icon' className='page-sidebar-icon'
onMouseEnter={() => setHover(true)} onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)} onMouseLeave={() => setHover(false)}
> >
{props.channel.profile_image === null ? ( {props.channel.profile_image === null ? (
<span className='channel-sidebar-icon-initial'> <span className='page-sidebar-icon-initial' style={{ border: iconBorder() }}>
{props.channel.name[0].toUpperCase()} {props.channel.name[0].toUpperCase()}
</span> </span>
) : ( ) : (
<img className='channel-sidebar-icon-image' src={props.channel.profile_image} /> <img
)} className='page-sidebar-icon-image'
{hover && ( src={props.channel.profile_image}
<HoverName style={{ border: iconBorder() }}
name={'/c/' + props.channel.name.replace(/\s/g, '')}
scrollY={props.scrollY}
/> />
)} )}
{hover && <HoverName name={pageName(channelName)} scrollY={props.scrollY} />}
</div> </div>
); );
} }
@ -165,10 +310,10 @@ function ChannelIcon(props) {
function HoverName(props) { function HoverName(props) {
return ( return (
<div <div
className='channel-sidebar-icon-hover' className='page-sidebar-icon-hover'
style={{ transform: 'translate(75px, -' + (50 + props.scrollY) + 'px)' }} style={{ transform: 'translate(75px, -' + (50 + props.scrollY) + 'px)' }}
> >
<span className='channel-sidebar-icon-hover-text'>{props.name}</span> <span className='page-sidebar-icon-hover-text'>{props.name}</span>
</div> </div>
); );
} }
@ -220,7 +365,7 @@ function ModalAdd(props) {
useEffect(() => { useEffect(() => {
if (addChannelLoading) { if (addChannelLoading) {
AddChannel(channelKey) AddPage(channelKey)
.then(() => { .then(() => {
reset(); reset();
props.onClose(); props.onClose();
@ -318,6 +463,7 @@ function ModalAdd(props) {
return ( return (
<> <>
{error !== '' && (
<SmallModal <SmallModal
onClose={() => setError('')} onClose={() => setError('')}
show={error !== ''} show={error !== ''}
@ -327,6 +473,7 @@ function ModalAdd(props) {
submitButton={'OK'} submitButton={'OK'}
onSubmit={() => setError('')} onSubmit={() => setError('')}
/> />
)}
<Modal <Modal
cancelButton={stage !== 'start' ? 'Back' : ''} cancelButton={stage !== 'start' ? 'Back' : ''}
onCancel={back} onCancel={back}

View file

@ -1,12 +1,16 @@
import { useState } from 'react';
import { CircleGreenBackground, Heart } from '../assets'; import { CircleGreenBackground, Heart } from '../assets';
import ChannelSideBar from '../components/ChannelSideBar'; import PageDetails from '../components/PageDetails';
import PageSideBar from '../components/PageSideBar';
import './Dashboard.css'; import './Dashboard.css';
function Dashboard() { function Dashboard() {
return ( return (
<div className='dashboard'> <div className='dashboard'>
<ChannelSideBar /> <PageSideBar />
<div style={{ backgroundColor: '#1f2e3c', width: '100%', height: '100%' }}></div> <PageDetails />
<div style={{ backgroundColor: '#344453', width: '100%', height: '100%' }}></div>
</div> </div>
); );
} }

View file

@ -6,7 +6,7 @@ toolchain go1.22.0
require ( require (
github.com/mattn/go-sqlite3 v1.14.22 github.com/mattn/go-sqlite3 v1.14.22
github.com/tylertravisty/rumble-livestream-lib-go v0.3.4 github.com/tylertravisty/rumble-livestream-lib-go v0.3.5
github.com/wailsapp/wails/v2 v2.8.0 github.com/wailsapp/wails/v2 v2.8.0
) )
@ -28,7 +28,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect github.com/pkg/errors v0.9.1 // indirect
github.com/r3labs/sse/v2 v2.10.0 // indirect github.com/r3labs/sse/v2 v2.10.0 // indirect
github.com/rivo/uniseg v0.4.4 // indirect github.com/rivo/uniseg v0.4.4 // indirect
github.com/robertkrimen/otto v0.2.1 // indirect github.com/robertkrimen/otto v0.3.0 // indirect
github.com/samber/lo v1.38.1 // indirect github.com/samber/lo v1.38.1 // indirect
github.com/tkrajina/go-reflector v0.5.6 // indirect github.com/tkrajina/go-reflector v0.5.6 // indirect
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 // indirect github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 // indirect
@ -36,10 +36,10 @@ require (
github.com/valyala/fasttemplate v1.2.2 // indirect github.com/valyala/fasttemplate v1.2.2 // indirect
github.com/wailsapp/go-webview2 v1.0.10 // indirect github.com/wailsapp/go-webview2 v1.0.10 // indirect
github.com/wailsapp/mimetype v1.4.1 // indirect github.com/wailsapp/mimetype v1.4.1 // indirect
golang.org/x/crypto v0.18.0 // indirect golang.org/x/crypto v0.21.0 // indirect
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 // indirect
golang.org/x/net v0.20.0 // indirect golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.16.0 // indirect golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect golang.org/x/text v0.14.0 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect gopkg.in/cenkalti/backoff.v1 v1.1.0 // indirect
gopkg.in/sourcemap.v1 v1.0.5 // indirect gopkg.in/sourcemap.v1 v1.0.5 // indirect

View file

@ -48,8 +48,8 @@ github.com/r3labs/sse/v2 v2.10.0/go.mod h1:Igau6Whc+F17QUgML1fYe1VPZzTV6EMCnYktE
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis= github.com/rivo/uniseg v0.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rivo/uniseg v0.4.4/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
github.com/robertkrimen/otto v0.2.1 h1:FVP0PJ0AHIjC+N4pKCG9yCDz6LHNPCwi/GKID5pGGF0= github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oqRE=
github.com/robertkrimen/otto v0.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY= github.com/robertkrimen/otto v0.3.0/go.mod h1:uW9yN1CYflmUQYvAMS0m+ZiNo3dMzRUDQJX0jWbzgxw=
github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM= github.com/samber/lo v1.38.1 h1:j2XEAqXKb09Am4ebOg31SpvzUTTs6EN3VfgeLUhPdXM=
github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA= github.com/samber/lo v1.38.1/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
@ -60,8 +60,8 @@ github.com/tkrajina/go-reflector v0.5.6 h1:hKQ0gyocG7vgMD2M3dRlYN6WBBOmdoOzJ6njQ
github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4= github.com/tkrajina/go-reflector v0.5.6/go.mod h1:ECbqLgccecY5kPmPmXg1MrHW585yMcDkVl6IvJe64T4=
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g= github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909 h1:xrjIFqzGQXlCrCdMPpW6+SodGFSlrQ3ZNUCr3f5tF1g=
github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk= github.com/tylertravisty/go-utils v0.0.0-20230524204414-6893ae548909/go.mod h1:2W31Jhs9YSy7y500wsCOW0bcamGi9foQV1CKrfvfTxk=
github.com/tylertravisty/rumble-livestream-lib-go v0.3.4 h1:VPKelrC3hesJlbqdByMkUhbEubFx80T5FNC60JKrEfw= github.com/tylertravisty/rumble-livestream-lib-go v0.3.5 h1:mAf4oYuQ55pXTPsIMVztOlYM8oGsBgsNMJvel2VLgsQ=
github.com/tylertravisty/rumble-livestream-lib-go v0.3.4/go.mod h1:rUET5uInouMfB4ekqdGiYeoN5ibOdzU9cCgRE0i57Wg= github.com/tylertravisty/rumble-livestream-lib-go v0.3.5/go.mod h1:rUET5uInouMfB4ekqdGiYeoN5ibOdzU9cCgRE0i57Wg=
github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw= github.com/valyala/bytebufferpool v1.0.0 h1:GqA5TC/0021Y/b9FG4Oi9Mr3q7XYx6KllzawFIhcdPw=
github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc= github.com/valyala/bytebufferpool v1.0.0/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ= github.com/valyala/fasttemplate v1.2.1/go.mod h1:KHLXt3tVN2HBp8eijSv/kGJopbvo7S+qRAEEKiv+SiQ=
@ -74,14 +74,14 @@ github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4X
github.com/wailsapp/wails/v2 v2.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4= github.com/wailsapp/wails/v2 v2.8.0 h1:b2NNn99uGPiN6P5bDsnPwOJZWtAOUhNLv7Vl+YxMTr4=
github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0= github.com/wailsapp/wails/v2 v2.8.0/go.mod h1:EFUGWkUX3KofO4fmKR/GmsLy3HhPH7NbyOEaMt8lBF0=
golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w=
golang.org/x/crypto v0.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc= golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg= golang.org/x/crypto v0.21.0/go.mod h1:0BP7YvVV9gBbVKyeTG0Gyn+gZm94bibOW5BjDEYAOMs=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1 h1:k/i9J1pBpvlfR+9QsetwPyERsqu1GIbi967PQMq3Ivc=
golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= golang.org/x/exp v0.0.0-20230522175609-2e198f4a06a1/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w=
golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20191116160921-f9c825593386/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s=
golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo= golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY= golang.org/x/net v0.22.0/go.mod h1:JKghWKKOSdJwpW2GEx0Ja7fmaKnMsbu+MWVZTokSYmg=
golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
@ -93,8 +93,8 @@ golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBc
golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20211103235746-7861aae1554b/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
golang.org/x/sys v0.16.0 h1:xWw16ngr6ZMtmxDyKyIgsE93KNKz5HKmMa3b8ALHidU= golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
golang.org/x/sys v0.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.18.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo=
golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=

257
v1/internal/api/api.go Normal file
View file

@ -0,0 +1,257 @@
package api
import (
"context"
"fmt"
"log"
"sync"
"time"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type Api struct {
callers map[string]*caller
callersMu sync.Mutex
ctx context.Context
display string
displayMu sync.Mutex
logError *log.Logger
logInfo *log.Logger
}
type caller struct {
cancel context.CancelFunc
cancelMu sync.Mutex
display bool
displayMu sync.Mutex
interval time.Duration
name string
response *rumblelivestreamlib.LivestreamResponse
responseMu sync.Mutex
url string
}
type event struct {
close bool
err error
name string
resp *rumblelivestreamlib.LivestreamResponse
}
func NewApi(logError *log.Logger, logInfo *log.Logger) *Api {
return &Api{logError: logError, logInfo: logInfo}
}
func (a *Api) Response(name string) *rumblelivestreamlib.LivestreamResponse {
a.callersMu.Lock()
defer a.callersMu.Unlock()
caller, exists := a.callers[name]
if !exists {
return nil
}
caller.responseMu.Lock()
defer caller.responseMu.Unlock()
copy := *caller.response
return &copy
}
func (a *Api) Display(name string) error {
a.displayMu.Lock()
defer a.displayMu.Unlock()
if name == a.display {
return nil
}
a.callersMu.Lock()
defer a.callersMu.Unlock()
if a.display != "" {
displaying, exists := a.callers[a.display]
if !exists {
return pkgErr("", fmt.Errorf("displaying caller does not exist: %s", a.display))
}
displaying.displayMu.Lock()
displaying.display = false
displaying.displayMu.Unlock()
a.display = ""
}
caller, exists := a.callers[name]
if !exists {
// return pkgErr("", fmt.Errorf("caller does not exist: %s", name))
runtime.EventsEmit(a.ctx, "PageActive", false)
return nil
}
caller.displayMu.Lock()
caller.display = true
caller.displayMu.Unlock()
a.display = name
a.handleResponse(caller)
return nil
}
func (a *Api) Startup(ctx context.Context) {
a.ctx = ctx
a.callers = map[string]*caller{}
}
func (a *Api) Shutdown() error {
for _, caller := range a.callers {
caller.cancelMu.Lock()
if caller.cancel != nil {
caller.cancel()
}
caller.cancelMu.Unlock()
}
return nil
}
func (a *Api) Start(name string, url string, interval time.Duration) error {
if name == "" {
return fmt.Errorf("name is empty")
}
if url == "" {
return fmt.Errorf("url is empty")
}
a.callersMu.Lock()
defer a.callersMu.Unlock()
if _, active := a.callers[name]; active {
return nil
}
ctx, cancel := context.WithCancel(context.Background())
caller := &caller{
cancel: cancel,
interval: interval,
name: name,
url: url,
}
a.callers[name] = caller
go a.run(ctx, caller)
return nil
}
func (a *Api) run(ctx context.Context, caller *caller) {
client := &rumblelivestreamlib.Client{StreamKey: caller.url}
for {
runtime.EventsEmit(a.ctx, "ApiActive-"+caller.name, true)
caller.displayMu.Lock()
if caller.display {
runtime.EventsEmit(a.ctx, "PageActive", true)
}
caller.displayMu.Unlock()
resp, err := a.query(client)
if err != nil {
a.logError.Println(pkgErr("error querying api", err))
// runtime.EventsEmit(a.ctx, "ApiActive-"+caller.name, false)
a.stop(caller)
return
}
caller.responseMu.Lock()
caller.response = resp
caller.responseMu.Unlock()
a.handleResponse(caller)
timer := time.NewTimer(caller.interval)
select {
case <-ctx.Done():
timer.Stop()
// runtime.EventsEmit(a.ctx, "ApiActive-"+caller.name, false)
a.stop(caller)
return
case <-timer.C:
}
}
}
func (a *Api) handleResponse(c *caller) {
if c == nil {
return
}
c.responseMu.Lock()
defer c.responseMu.Unlock()
if c.response == nil {
return
}
c.displayMu.Lock()
if c.display {
runtime.EventsEmit(a.ctx, "PageActivity", c.response)
}
c.displayMu.Unlock()
isLive := len(c.response.Livestreams) > 0
runtime.EventsEmit(a.ctx, "PageLive-"+c.name, isLive)
}
func (a *Api) stop(c *caller) {
if c == nil {
return
}
runtime.EventsEmit(a.ctx, "ApiActive-"+c.name, false)
c.displayMu.Lock()
if c.display {
c.display = false
runtime.EventsEmit(a.ctx, "PageActive", false)
}
c.displayMu.Unlock()
a.displayMu.Lock()
if a.display == c.name {
a.display = ""
}
a.displayMu.Unlock()
a.callersMu.Lock()
delete(a.callers, c.name)
a.callersMu.Unlock()
return
}
func (a *Api) Active(name string) bool {
a.callersMu.Lock()
defer a.callersMu.Unlock()
_, active := a.callers[name]
return active
}
func (a *Api) Stop(name string) error {
a.callersMu.Lock()
caller, exists := a.callers[name]
if !exists {
return pkgErr("", fmt.Errorf("caller does not exist: %s", name))
}
a.callersMu.Unlock()
caller.cancelMu.Lock()
if caller.cancel != nil {
caller.cancel()
}
caller.cancelMu.Unlock()
return nil
}
func (a *Api) query(client *rumblelivestreamlib.Client) (*rumblelivestreamlib.LivestreamResponse, error) {
resp, err := client.Request()
if err != nil {
return nil, fmt.Errorf("error executing client request: %v", err)
}
return resp, nil
}

14
v1/internal/api/error.go Normal file
View file

@ -0,0 +1,14 @@
package api
import "fmt"
const pkgName = "api"
func pkgErr(prefix string, err error) error {
pkgErr := pkgName
if prefix != "" {
pkgErr = fmt.Sprintf("%s: %s", pkgErr, prefix)
}
return fmt.Errorf("%s: %v", pkgErr, err)
}

View file

@ -19,6 +19,35 @@ type Account struct {
ApiKey *string `json:"api_key"` ApiKey *string `json:"api_key"`
} }
func (a *Account) Id() *int64 {
return a.ID
}
func (a *Account) KeyUrl() *string {
return a.ApiKey
}
func (a *Account) LoggedIn() bool {
return a.Cookies != nil
}
func (a *Account) String() *string {
if a.Username == nil {
return nil
}
s := "/user/" + *a.Username
return &s
}
func (a *Account) Title() *string {
return a.Username
}
func (a *Account) Type() string {
return "Account"
}
func (a *Account) values() []any { func (a *Account) values() []any {
return []any{a.ID, a.UID, a.Username, a.Cookies, a.ProfileImage, a.ApiKey} return []any{a.ID, a.UID, a.Username, a.Cookies, a.ProfileImage, a.ApiKey}
} }
@ -60,8 +89,10 @@ func (sa sqlAccount) toAccount() *Account {
type AccountService interface { type AccountService interface {
All() ([]Account, error) All() ([]Account, error)
AutoMigrate() error AutoMigrate() error
ByID(id int64) (*Account, error)
ByUsername(username string) (*Account, error) ByUsername(username string) (*Account, error)
Create(a *Account) error Create(a *Account) (int64, error)
Delete(a *Account) error
DestructiveReset() error DestructiveReset() error
Update(a *Account) error Update(a *Account) error
} }
@ -138,6 +169,34 @@ func (as *accountService) createAccountTable() error {
return nil return nil
} }
func (as *accountService) ByID(id int64) (*Account, error) {
err := runAccountValFuncs(
&Account{ID: &id},
accountRequireID,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE id=?
`, accountColumns, accountTable)
var sa sqlAccount
row := as.Database.QueryRow(selectQ, id)
err = sa.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sa.toAccount(), nil
}
func (as *accountService) ByUsername(username string) (*Account, error) { func (as *accountService) ByUsername(username string) (*Account, error) {
err := runAccountValFuncs( err := runAccountValFuncs(
&Account{Username: &username}, &Account{Username: &username},
@ -166,24 +225,50 @@ func (as *accountService) ByUsername(username string) (*Account, error) {
return sa.toAccount(), nil return sa.toAccount(), nil
} }
func (as *accountService) Create(a *Account) error { func (as *accountService) Create(a *Account) (int64, error) {
err := runAccountValFuncs( err := runAccountValFuncs(
a, a,
accountRequireUsername, accountRequireUsername,
) )
if err != nil { if err != nil {
return pkgErr("invalid account", err) return -1, pkgErr("invalid account", err)
} }
columns := columnsNoID(accountColumns) columns := columnsNoID(accountColumns)
insertQ := fmt.Sprintf(` insertQ := fmt.Sprintf(`
INSERT INTO "%s" (%s) INSERT INTO "%s" (%s)
VALUES (%s) VALUES (%s)
RETURNING id
`, accountTable, columns, values(columns)) `, accountTable, columns, values(columns))
_, err = as.Database.Exec(insertQ, a.valuesNoID()...) // _, err = as.Database.Exec(insertQ, a.valuesNoID()...)
var id int64
row := as.Database.QueryRow(insertQ, a.valuesNoID()...)
err = row.Scan(&id)
if err != nil { if err != nil {
return pkgErr("error executing insert query", err) return -1, pkgErr("error executing insert query", err)
}
return id, nil
}
func (as *accountService) Delete(a *Account) error {
err := runAccountValFuncs(
a,
accountRequireID,
)
if err != nil {
return pkgErr("invalid account", err)
}
deleteQ := fmt.Sprintf(`
DELETE FROM "%s"
WHERE id=?
`, accountTable)
_, err = as.Database.Exec(deleteQ, a.ID)
if err != nil {
return pkgErr("error executing delete query", err)
} }
return nil return nil
@ -230,7 +315,7 @@ func (as *accountService) Update(a *Account) error {
_, err = as.Database.Exec(updateQ, a.valuesEndID()...) _, err = as.Database.Exec(updateQ, a.valuesEndID()...)
if err != nil { if err != nil {
return pkgErr(fmt.Sprintf("error executing update query", accountTable), err) return pkgErr("error executing update query", err)
} }
return nil return nil

View file

@ -3,6 +3,7 @@ package models
import ( import (
"database/sql" "database/sql"
"fmt" "fmt"
"strings"
) )
const ( const (
@ -19,6 +20,35 @@ type Channel struct {
ApiKey *string `json:"api_key"` ApiKey *string `json:"api_key"`
} }
func (c *Channel) Id() *int64 {
return c.ID
}
func (c *Channel) KeyUrl() *string {
return c.ApiKey
}
func (c *Channel) LoggedIn() bool {
return false
}
func (c *Channel) String() *string {
if c.Name == nil {
return nil
}
s := "/c/" + strings.ReplaceAll(*c.Name, " ", "")
return &s
}
func (c *Channel) Title() *string {
return c.Name
}
func (c *Channel) Type() string {
return "Channel"
}
func (c *Channel) values() []any { func (c *Channel) values() []any {
return []any{c.ID, c.AccountID, c.CID, c.Name, c.ProfileImage, c.ApiKey} return []any{c.ID, c.AccountID, c.CID, c.Name, c.ProfileImage, c.ApiKey}
} }
@ -59,9 +89,13 @@ func (sc sqlChannel) toChannel() *Channel {
type ChannelService interface { type ChannelService interface {
AutoMigrate() error AutoMigrate() error
ByAccount(a *Account) ([]Channel, error)
ByID(id int64) (*Channel, error)
ByName(name string) (*Channel, error) ByName(name string) (*Channel, error)
Create(c *Channel) error Create(c *Channel) error
Delete(c *Channel) error
DestructiveReset() error DestructiveReset() error
Update(c *Channel) error
} }
func NewChannelService(db *sql.DB) ChannelService { func NewChannelService(db *sql.DB) ChannelService {
@ -106,6 +140,74 @@ func (cs *channelService) createChannelTable() error {
return nil return nil
} }
func (cs *channelService) ByAccount(a *Account) ([]Channel, error) {
err := runAccountValFuncs(
a,
accountRequireID,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE account_id=?
`, channelColumns, channelTable)
rows, err := cs.Database.Query(selectQ, a.ID)
if err != nil {
return nil, pkgErr("error executing select query", err)
}
defer rows.Close()
channels := []Channel{}
for rows.Next() {
sc := &sqlChannel{}
err = sc.scan(rows)
if err != nil {
return nil, pkgErr("error scanning row", err)
}
channels = append(channels, *sc.toChannel())
}
err = rows.Err()
if err != nil && err != sql.ErrNoRows {
return nil, pkgErr("error iterating over rows", err)
}
return channels, nil
}
func (cs *channelService) ByID(id int64) (*Channel, error) {
err := runChannelValFuncs(
&Channel{ID: &id},
channelRequireID,
)
if err != nil {
return nil, pkgErr("", err)
}
selectQ := fmt.Sprintf(`
SELECT %s
FROM "%s"
WHERE id=?
`, channelColumns, channelTable)
var sc sqlChannel
row := cs.Database.QueryRow(selectQ, id)
err = sc.scan(row)
if err != nil {
if err == sql.ErrNoRows {
return nil, nil
}
return nil, pkgErr("error executing select query", err)
}
return sc.toChannel(), nil
}
func (cs *channelService) ByName(name string) (*Channel, error) { func (cs *channelService) ByName(name string) (*Channel, error) {
err := runChannelValFuncs( err := runChannelValFuncs(
&Channel{Name: &name}, &Channel{Name: &name},
@ -160,6 +262,28 @@ func (cs *channelService) Create(c *Channel) error {
return nil return nil
} }
func (cs *channelService) Delete(c *Channel) error {
err := runChannelValFuncs(
c,
channelRequireID,
)
if err != nil {
return pkgErr("invalid channel", err)
}
deleteQ := fmt.Sprintf(`
DELETE FROM "%s"
WHERE id=?
`, channelTable)
_, err = cs.Database.Exec(deleteQ, c.ID)
if err != nil {
return pkgErr("error executing delete query", err)
}
return nil
}
func (cs *channelService) DestructiveReset() error { func (cs *channelService) DestructiveReset() error {
err := cs.dropChannelTable() err := cs.dropChannelTable()
if err != nil { if err != nil {
@ -182,6 +306,33 @@ func (cs *channelService) dropChannelTable() error {
return nil return nil
} }
func (cs *channelService) Update(c *Channel) error {
err := runChannelValFuncs(
c,
channelRequireAccountID,
channelRequireApiKey,
channelRequireCID,
channelRequireName,
)
if err != nil {
return pkgErr("invalid channel", err)
}
columns := columnsNoID(channelColumns)
updateQ := fmt.Sprintf(`
UPDATE "%s"
SET %s
WHERE id=?
`, channelTable, set(columns))
_, err = cs.Database.Exec(updateQ, c.valuesEndID()...)
if err != nil {
return pkgErr("error executing update query", err)
}
return nil
}
type channelValFunc func(*Channel) error type channelValFunc func(*Channel) error
func runChannelValFuncs(c *Channel, fns ...channelValFunc) error { func runChannelValFuncs(c *Channel, fns ...channelValFunc) error {
@ -215,6 +366,14 @@ func channelRequireApiKey(c *Channel) error {
return nil return nil
} }
func channelRequireID(c *Channel) error {
if c.ID == nil || *c.ID < 1 {
return ErrChannelInvalidID
}
return nil
}
func channelRequireCID(c *Channel) error { func channelRequireCID(c *Channel) error {
if c.CID == nil || *c.CID == "" { if c.CID == nil || *c.CID == "" {
return ErrChannelInvalidCID return ErrChannelInvalidCID

View file

@ -11,6 +11,7 @@ const (
ErrChannelInvalidAccountID ValidatorError = "invalid channel account id" ErrChannelInvalidAccountID ValidatorError = "invalid channel account id"
ErrChannelInvalidApiKey ValidatorError = "invalid channel API key" ErrChannelInvalidApiKey ValidatorError = "invalid channel API key"
ErrChannelInvalidCID ValidatorError = "invalid channel CID" ErrChannelInvalidCID ValidatorError = "invalid channel CID"
ErrChannelInvalidID ValidatorError = "invalid channel ID"
ErrChannelInvalidName ValidatorError = "invalid channel name" ErrChannelInvalidName ValidatorError = "invalid channel name"
) )

View file

@ -1,7 +1,10 @@
/.test .test
/otto/otto otto/otto
/otto/otto-* otto/otto-*
/test/test-*.js tools/tester/testdata/
/test/tester tools/tester/tester
tools/gen-jscore/gen-jscore
tools/gen-tokens/gen-tokens
.idea .idea
dist/ dist/
.vscode/

View file

@ -4,6 +4,7 @@ run:
- terst - terst
skip-files: skip-files:
- dbg/dbg.go - dbg/dbg.go
- token/token_const.go
linters-settings: linters-settings:
govet: govet:
@ -11,25 +12,17 @@ linters-settings:
goconst: goconst:
min-len: 2 min-len: 2
min-occurrences: 4 min-occurrences: 4
revive:
enable-all-rules: false
rules:
- name: var-naming
disabled: true
linters: linters:
enable-all: true enable-all: true
disable: disable:
- dupl - dupl
- gas
- errcheck
- gofmt
- gosimple
- interfacer
- megacheck
- maligned
- structcheck
- staticcheck
- unconvert
- unparam
- varcheck
- lll - lll
- prealloc
- gochecknoglobals - gochecknoglobals
- gochecknoinits - gochecknoinits
- scopelint - scopelint
@ -39,35 +32,34 @@ linters:
- goerr113 - goerr113
- wsl - wsl
- nlreturn - nlreturn
- tagliatelle
- gomnd - gomnd
- paralleltest - paralleltest
- wrapcheck - wrapcheck
- testpackage - testpackage
- golint
- gofumpt
- forbidigo
- gocognit - gocognit
- gocritic
- godot
- nakedret
- nestif - nestif
- revive
- errorlint
- exhaustive - exhaustive
- forcetypeassert - forcetypeassert
- ifshort
- stylecheck
- gocyclo - gocyclo
- misspell
- cyclop - cyclop
- varnamelen - varnamelen
- nonamedreturns
- maintidx - maintidx
- ireturn - ireturn
- exhaustruct - exhaustruct
- nosnakecase - nosnakecase
- deadcode
- dupword - dupword
- gci - structcheck
- deadcode
- golint
- varcheck
- ifshort
- interfacer
- maligned
# Just causes noise
- depguard
issues:
exclude-use-default: false
max-same-issues: 0
exclude:
- Deferring unsafe method "Close" on type "io\.ReadCloser"

View file

@ -1,63 +0,0 @@
.PHONY: test test-race test-release release release-check test-262
.PHONY: parser
.PHONY: otto assets underscore
TESTS := \
~
TEST := -v --run
TEST := -v
TEST := -v --run Test\($(subst $(eval) ,\|,$(TESTS))\)
TEST := .
test: parser inline.go
go test -i
go test $(TEST)
@echo PASS
parser:
$(MAKE) -C parser
inline.go: inline.pl
./$< > $@
#################
# release, test #
#################
release: test-race test-release
for package in . parser token ast file underscore registry; do (cd $$package && godocdown --signature > README.markdown); done
@echo \*\*\* make release-check
@echo PASS
release-check: .test
$(MAKE) -C test build test
$(MAKE) -C .test/test262 build test
@echo PASS
test-262: .test
$(MAKE) -C .test/test262 build test
@echo PASS
test-release:
go test -i
go test
test-race:
go test -race -i
go test -race
#################################
# otto, assets, underscore, ... #
#################################
otto:
$(MAKE) -C otto
assets:
mkdir -p .assets
for file in underscore/test/*.js; do tr "\`" "_" < $$file > .assets/`basename $$file`; done
underscore:
$(MAKE) -C $@

File diff suppressed because it is too large Load diff

View file

@ -6,32 +6,44 @@ import (
"github.com/robertkrimen/otto/file" "github.com/robertkrimen/otto/file"
) )
// CommentPosition determines where the comment is in a given context // CommentPosition determines where the comment is in a given context.
type CommentPosition int type CommentPosition int
// Available comment positions.
const ( const (
_ CommentPosition = iota _ CommentPosition = iota
LEADING // Before the pertinent expression // LEADING is before the pertinent expression.
TRAILING // After the pertinent expression LEADING
KEY // Before a key in an object // TRAILING is after the pertinent expression.
COLON // After a colon in a field declaration TRAILING
FINAL // Final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal // KEY is before a key in an object.
IF // After an if keyword KEY
WHILE // After a while keyword // COLON is after a colon in a field declaration.
DO // After do keyword COLON
FOR // After a for keyword // FINAL is the final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal.
WITH // After a with keyword FINAL
// IF is after an if keyword.
IF
// WHILE is after a while keyword.
WHILE
// DO is after do keyword.
DO
// FOR is after a for keyword.
FOR
// WITH is after a with keyword.
WITH
// TBD is unknown.
TBD TBD
) )
// Comment contains the data of the comment // Comment contains the data of the comment.
type Comment struct { type Comment struct {
Begin file.Idx Begin file.Idx
Text string Text string
Position CommentPosition Position CommentPosition
} }
// NewComment creates a new comment // NewComment creates a new comment.
func NewComment(text string, idx file.Idx) *Comment { func NewComment(text string, idx file.Idx) *Comment {
comment := &Comment{ comment := &Comment{
Begin: idx, Begin: idx,
@ -42,7 +54,7 @@ func NewComment(text string, idx file.Idx) *Comment {
return comment return comment
} }
// String returns a stringified version of the position // String returns a stringified version of the position.
func (cp CommentPosition) String() string { func (cp CommentPosition) String() string {
switch cp { switch cp {
case LEADING: case LEADING:
@ -70,23 +82,23 @@ func (cp CommentPosition) String() string {
} }
} }
// String returns a stringified version of the comment // String returns a stringified version of the comment.
func (c Comment) String() string { func (c Comment) String() string {
return fmt.Sprintf("Comment: %v", c.Text) return fmt.Sprintf("Comment: %v", c.Text)
} }
// Comments defines the current view of comments from the parser // Comments defines the current view of comments from the parser.
type Comments struct { type Comments struct {
// CommentMap is a reference to the parser comment map // CommentMap is a reference to the parser comment map
CommentMap CommentMap CommentMap CommentMap
// Comments lists the comments scanned, not linked to a node yet // Comments lists the comments scanned, not linked to a node yet
Comments []*Comment Comments []*Comment
// future lists the comments after a line break during a sequence of comments
future []*Comment
// Current is node for which comments are linked to // Current is node for which comments are linked to
Current Expression Current Expression
// wasLineBreak determines if a line break occured while scanning for comments // future lists the comments after a line break during a sequence of comments
future []*Comment
// wasLineBreak determines if a line break occurred while scanning for comments
wasLineBreak bool wasLineBreak bool
// primary determines whether or not processing a primary expression // primary determines whether or not processing a primary expression
primary bool primary bool
@ -94,6 +106,7 @@ type Comments struct {
afterBlock bool afterBlock bool
} }
// NewComments returns a new Comments.
func NewComments() *Comments { func NewComments() *Comments {
comments := &Comments{ comments := &Comments{
CommentMap: CommentMap{}, CommentMap: CommentMap{},
@ -107,7 +120,7 @@ func (c *Comments) String() string {
} }
// FetchAll returns all the currently scanned comments, // FetchAll returns all the currently scanned comments,
// including those from the next line // including those from the next line.
func (c *Comments) FetchAll() []*Comment { func (c *Comments) FetchAll() []*Comment {
defer func() { defer func() {
c.Comments = nil c.Comments = nil
@ -117,7 +130,7 @@ func (c *Comments) FetchAll() []*Comment {
return append(c.Comments, c.future...) return append(c.Comments, c.future...)
} }
// Fetch returns all the currently scanned comments // Fetch returns all the currently scanned comments.
func (c *Comments) Fetch() []*Comment { func (c *Comments) Fetch() []*Comment {
defer func() { defer func() {
c.Comments = nil c.Comments = nil
@ -126,12 +139,12 @@ func (c *Comments) Fetch() []*Comment {
return c.Comments return c.Comments
} }
// ResetLineBreak marks the beginning of a new statement // ResetLineBreak marks the beginning of a new statement.
func (c *Comments) ResetLineBreak() { func (c *Comments) ResetLineBreak() {
c.wasLineBreak = false c.wasLineBreak = false
} }
// MarkPrimary will mark the context as processing a primary expression // MarkPrimary will mark the context as processing a primary expression.
func (c *Comments) MarkPrimary() { func (c *Comments) MarkPrimary() {
c.primary = true c.primary = true
c.wasLineBreak = false c.wasLineBreak = false
@ -205,7 +218,7 @@ func (c *Comments) SetExpression(node Expression) {
c.applyComments(node, previous, TRAILING) c.applyComments(node, previous, TRAILING)
} }
// PostProcessNode applies all found comments to the given node // PostProcessNode applies all found comments to the given node.
func (c *Comments) PostProcessNode(node Node) { func (c *Comments) PostProcessNode(node Node) {
c.applyComments(node, nil, TRAILING) c.applyComments(node, nil, TRAILING)
} }
@ -228,15 +241,15 @@ func (c *Comments) applyComments(node, previous Node, position CommentPosition)
} }
} }
// AtLineBreak will mark a line break // AtLineBreak will mark a line break.
func (c *Comments) AtLineBreak() { func (c *Comments) AtLineBreak() {
c.wasLineBreak = true c.wasLineBreak = true
} }
// CommentMap is the data structure where all found comments are stored // CommentMap is the data structure where all found comments are stored.
type CommentMap map[Node][]*Comment type CommentMap map[Node][]*Comment
// AddComment adds a single comment to the map // AddComment adds a single comment to the map.
func (cm CommentMap) AddComment(node Node, comment *Comment) { func (cm CommentMap) AddComment(node Node, comment *Comment) {
list := cm[node] list := cm[node]
list = append(list, comment) list = append(list, comment)
@ -244,7 +257,7 @@ func (cm CommentMap) AddComment(node Node, comment *Comment) {
cm[node] = list cm[node] = list
} }
// AddComments adds a slice of comments, given a node and an updated position // AddComments adds a slice of comments, given a node and an updated position.
func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) { func (cm CommentMap) AddComments(node Node, comments []*Comment, position CommentPosition) {
for _, comment := range comments { for _, comment := range comments {
if comment.Position == TBD { if comment.Position == TBD {
@ -254,7 +267,7 @@ func (cm CommentMap) AddComments(node Node, comments []*Comment, position Commen
} }
} }
// Size returns the size of the map // Size returns the size of the map.
func (cm CommentMap) Size() int { func (cm CommentMap) Size() int {
size := 0 size := 0
for _, comments := range cm { for _, comments := range cm {
@ -264,7 +277,7 @@ func (cm CommentMap) Size() int {
return size return size
} }
// MoveComments moves comments with a given position from a node to another // MoveComments moves comments with a given position from a node to another.
func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) { func (cm CommentMap) MoveComments(from, to Node, position CommentPosition) {
for i, c := range cm[from] { for i, c := range cm[from] {
if c.Position == position { if c.Position == position {

File diff suppressed because it is too large Load diff

View file

@ -38,6 +38,7 @@ func Walk(v Visitor, n Node) {
Walk(v, n.Right) Walk(v, n.Right)
} }
case *BadExpression: case *BadExpression:
case *BadStatement:
case *BinaryExpression: case *BinaryExpression:
if n != nil { if n != nil {
Walk(v, n.Left) Walk(v, n.Left)

View file

@ -2,6 +2,7 @@ package otto
import ( import (
"encoding/hex" "encoding/hex"
"errors"
"math" "math"
"net/url" "net/url"
"regexp" "regexp"
@ -11,34 +12,34 @@ import (
"unicode/utf8" "unicode/utf8"
) )
// Global // Global.
func builtinGlobal_eval(call FunctionCall) Value { func builtinGlobalEval(call FunctionCall) Value {
src := call.Argument(0) src := call.Argument(0)
if !src.IsString() { if !src.IsString() {
return src return src
} }
runtime := call.runtime rt := call.runtime
program := runtime.cmpl_parseOrThrow(src.string(), nil) program := rt.cmplParseOrThrow(src.string(), nil)
if !call.eval { if !call.eval {
// Not a direct call to eval, so we enter the global ExecutionContext // Not a direct call to eval, so we enter the global ExecutionContext
runtime.enterGlobalScope() rt.enterGlobalScope()
defer runtime.leaveScope() defer rt.leaveScope()
} }
returnValue := runtime.cmpl_evaluate_nodeProgram(program, true) returnValue := rt.cmplEvaluateNodeProgram(program, true)
if returnValue.isEmpty() { if returnValue.isEmpty() {
return Value{} return Value{}
} }
return returnValue return returnValue
} }
func builtinGlobal_isNaN(call FunctionCall) Value { func builtinGlobalIsNaN(call FunctionCall) Value {
value := call.Argument(0).float64() value := call.Argument(0).float64()
return toValue_bool(math.IsNaN(value)) return boolValue(math.IsNaN(value))
} }
func builtinGlobal_isFinite(call FunctionCall) Value { func builtinGlobalIsFinite(call FunctionCall) Value {
value := call.Argument(0).float64() value := call.Argument(0).float64()
return toValue_bool(!math.IsNaN(value) && !math.IsInf(value, 0)) return boolValue(!math.IsNaN(value) && !math.IsInf(value, 0))
} }
func digitValue(chr rune) int { func digitValue(chr rune) int {
@ -53,8 +54,8 @@ func digitValue(chr rune) int {
return 36 // Larger than any legal digit value return 36 // Larger than any legal digit value
} }
func builtinGlobal_parseInt(call FunctionCall) Value { func builtinGlobalParseInt(call FunctionCall) Value {
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace) input := strings.Trim(call.Argument(0).string(), builtinStringTrimWhitespace)
if len(input) == 0 { if len(input) == 0 {
return NaNValue() return NaNValue()
} }
@ -106,7 +107,7 @@ func builtinGlobal_parseInt(call FunctionCall) Value {
value, err := strconv.ParseInt(input, radix, 64) value, err := strconv.ParseInt(input, radix, 64)
if err != nil { if err != nil {
if err.(*strconv.NumError).Err == strconv.ErrRange { if errors.Is(err, strconv.ErrRange) {
base := float64(base) base := float64(base)
// Could just be a very large number (e.g. 0x8000000000000000) // Could just be a very large number (e.g. 0x8000000000000000)
var value float64 var value float64
@ -120,7 +121,7 @@ func builtinGlobal_parseInt(call FunctionCall) Value {
if negative { if negative {
value *= -1 value *= -1
} }
return toValue_float64(value) return float64Value(value)
} }
return NaNValue() return NaNValue()
} }
@ -128,24 +129,26 @@ func builtinGlobal_parseInt(call FunctionCall) Value {
value *= -1 value *= -1
} }
return toValue_int64(value) return int64Value(value)
} }
var parseFloat_matchBadSpecial = regexp.MustCompile(`[\+\-]?(?:[Ii]nf$|infinity)`) var (
var parseFloat_matchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`) parseFloatMatchBadSpecial = regexp.MustCompile(`[\+\-]?(?:[Ii]nf$|infinity)`)
parseFloatMatchValid = regexp.MustCompile(`[0-9eE\+\-\.]|Infinity`)
)
func builtinGlobal_parseFloat(call FunctionCall) Value { func builtinGlobalParseFloat(call FunctionCall) Value {
// Caveat emptor: This implementation does NOT match the specification // Caveat emptor: This implementation does NOT match the specification
input := strings.Trim(call.Argument(0).string(), builtinString_trim_whitespace) input := strings.Trim(call.Argument(0).string(), builtinStringTrimWhitespace)
if parseFloat_matchBadSpecial.MatchString(input) { if parseFloatMatchBadSpecial.MatchString(input) {
return NaNValue() return NaNValue()
} }
value, err := strconv.ParseFloat(input, 64) value, err := strconv.ParseFloat(input, 64)
if err != nil { if err != nil {
for end := len(input); end > 0; end-- { for end := len(input); end > 0; end-- {
input := input[0:end] input := input[0:end]
if !parseFloat_matchValid.MatchString(input) { if !parseFloatMatchValid.MatchString(input) {
return NaNValue() return NaNValue()
} }
value, err = strconv.ParseFloat(input, 64) value, err = strconv.ParseFloat(input, 64)
@ -157,12 +160,12 @@ func builtinGlobal_parseFloat(call FunctionCall) Value {
return NaNValue() return NaNValue()
} }
} }
return toValue_float64(value) return float64Value(value)
} }
// encodeURI/decodeURI // encodeURI/decodeURI
func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value { func encodeDecodeURI(call FunctionCall, escape *regexp.Regexp) Value {
value := call.Argument(0) value := call.Argument(0)
var input []uint16 var input []uint16
switch vl := value.value.(type) { switch vl := value.value.(type) {
@ -172,7 +175,7 @@ func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
input = utf16.Encode([]rune(value.string())) input = utf16.Encode([]rune(value.string()))
} }
if len(input) == 0 { if len(input) == 0 {
return toValue_string("") return stringValue("")
} }
output := []byte{} output := []byte{}
length := len(input) length := len(input)
@ -184,7 +187,7 @@ func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
panic(call.runtime.panicURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
if value >= 0xD800 && value <= 0xDBFF { if value >= 0xD800 && value <= 0xDBFF {
index += 1 index++
if index >= length { if index >= length {
panic(call.runtime.panicURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
@ -195,43 +198,42 @@ func _builtinGlobal_encodeURI(call FunctionCall, escape *regexp.Regexp) Value {
} }
decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000} decode = []rune{((rune(value) - 0xD800) * 0x400) + (rune(value1) - 0xDC00) + 0x10000}
} }
index += 1 index++
size := utf8.EncodeRune(encode, decode[0]) size := utf8.EncodeRune(encode, decode[0])
encode := encode[0:size] encode := encode[0:size]
output = append(output, encode...) output = append(output, encode...)
} }
{
value := escape.ReplaceAllFunc(output, func(target []byte) []byte { bytes := escape.ReplaceAllFunc(output, func(target []byte) []byte {
// Probably a better way of doing this // Probably a better way of doing this
if target[0] == ' ' { if target[0] == ' ' {
return []byte("%20") return []byte("%20")
} }
return []byte(url.QueryEscape(string(target))) return []byte(url.QueryEscape(string(target)))
}) })
return toValue_string(string(value)) return stringValue(string(bytes))
}
} }
var encodeURI_Regexp = regexp.MustCompile(`([^~!@#$&*()=:/,;?+'])`) var encodeURIRegexp = regexp.MustCompile(`([^~!@#$&*()=:/,;?+'])`)
func builtinGlobal_encodeURI(call FunctionCall) Value { func builtinGlobalEncodeURI(call FunctionCall) Value {
return _builtinGlobal_encodeURI(call, encodeURI_Regexp) return encodeDecodeURI(call, encodeURIRegexp)
} }
var encodeURIComponent_Regexp = regexp.MustCompile(`([^~!*()'])`) var encodeURIComponentRegexp = regexp.MustCompile(`([^~!*()'])`)
func builtinGlobal_encodeURIComponent(call FunctionCall) Value { func builtinGlobalEncodeURIComponent(call FunctionCall) Value {
return _builtinGlobal_encodeURI(call, encodeURIComponent_Regexp) return encodeDecodeURI(call, encodeURIComponentRegexp)
} }
// 3B/2F/3F/3A/40/26/3D/2B/24/2C/23 // 3B/2F/3F/3A/40/26/3D/2B/24/2C/23.
var decodeURI_guard = regexp.MustCompile(`(?i)(?:%)(3B|2F|3F|3A|40|26|3D|2B|24|2C|23)`) var decodeURIGuard = regexp.MustCompile(`(?i)(?:%)(3B|2F|3F|3A|40|26|3D|2B|24|2C|23)`)
func _decodeURI(input string, reserve bool) (string, bool) { func decodeURI(input string, reserve bool) (string, bool) {
if reserve { if reserve {
input = decodeURI_guard.ReplaceAllString(input, "%25$1") input = decodeURIGuard.ReplaceAllString(input, "%25$1")
} }
input = strings.Replace(input, "+", "%2B", -1) // Ugly hack to make QueryUnescape work with our use case input = strings.ReplaceAll(input, "+", "%2B") // Ugly hack to make QueryUnescape work with our use case
output, err := url.QueryUnescape(input) output, err := url.QueryUnescape(input)
if err != nil || !utf8.ValidString(output) { if err != nil || !utf8.ValidString(output) {
return "", true return "", true
@ -239,25 +241,25 @@ func _decodeURI(input string, reserve bool) (string, bool) {
return output, false return output, false
} }
func builtinGlobal_decodeURI(call FunctionCall) Value { func builtinGlobalDecodeURI(call FunctionCall) Value {
output, err := _decodeURI(call.Argument(0).string(), true) output, err := decodeURI(call.Argument(0).string(), true)
if err { if err {
panic(call.runtime.panicURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
return toValue_string(output) return stringValue(output)
} }
func builtinGlobal_decodeURIComponent(call FunctionCall) Value { func builtinGlobalDecodeURIComponent(call FunctionCall) Value {
output, err := _decodeURI(call.Argument(0).string(), false) output, err := decodeURI(call.Argument(0).string(), false)
if err { if err {
panic(call.runtime.panicURIError("URI malformed")) panic(call.runtime.panicURIError("URI malformed"))
} }
return toValue_string(output) return stringValue(output)
} }
// escape/unescape // escape/unescape
func builtin_shouldEscape(chr byte) bool { func builtinShouldEscape(chr byte) bool {
if 'A' <= chr && chr <= 'Z' || 'a' <= chr && chr <= 'z' || '0' <= chr && chr <= '9' { if 'A' <= chr && chr <= 'Z' || 'a' <= chr && chr <= 'z' || '0' <= chr && chr <= '9' {
return false return false
} }
@ -266,11 +268,11 @@ func builtin_shouldEscape(chr byte) bool {
const escapeBase16 = "0123456789ABCDEF" const escapeBase16 = "0123456789ABCDEF"
func builtin_escape(input string) string { func builtinEscape(input string) string {
output := make([]byte, 0, len(input)) output := make([]byte, 0, len(input))
length := len(input) length := len(input)
for index := 0; index < length; { for index := 0; index < length; {
if builtin_shouldEscape(input[index]) { if builtinShouldEscape(input[index]) {
chr, width := utf8.DecodeRuneInString(input[index:]) chr, width := utf8.DecodeRuneInString(input[index:])
chr16 := utf16.Encode([]rune{chr})[0] chr16 := utf16.Encode([]rune{chr})[0]
if 256 > chr16 { if 256 > chr16 {
@ -289,13 +291,13 @@ func builtin_escape(input string) string {
index += width index += width
} else { } else {
output = append(output, input[index]) output = append(output, input[index])
index += 1 index++
} }
} }
return string(output) return string(output)
} }
func builtin_unescape(input string) string { func builtinUnescape(input string) string {
output := make([]rune, 0, len(input)) output := make([]rune, 0, len(input))
length := len(input) length := len(input)
for index := 0; index < length; { for index := 0; index < length; {
@ -322,15 +324,15 @@ func builtin_unescape(input string) string {
} }
} }
output = append(output, rune(input[index])) output = append(output, rune(input[index]))
index += 1 index++
} }
return string(output) return string(output)
} }
func builtinGlobal_escape(call FunctionCall) Value { func builtinGlobalEscape(call FunctionCall) Value {
return toValue_string(builtin_escape(call.Argument(0).string())) return stringValue(builtinEscape(call.Argument(0).string()))
} }
func builtinGlobal_unescape(call FunctionCall) Value { func builtinGlobalUnescape(call FunctionCall) Value {
return toValue_string(builtin_unescape(call.Argument(0).string())) return stringValue(builtinUnescape(call.Argument(0).string()))
} }

View file

@ -8,92 +8,93 @@ import (
// Array // Array
func builtinArray(call FunctionCall) Value { func builtinArray(call FunctionCall) Value {
return toValue_object(builtinNewArrayNative(call.runtime, call.ArgumentList)) return objectValue(builtinNewArrayNative(call.runtime, call.ArgumentList))
} }
func builtinNewArray(self *_object, argumentList []Value) Value { func builtinNewArray(obj *object, argumentList []Value) Value {
return toValue_object(builtinNewArrayNative(self.runtime, argumentList)) return objectValue(builtinNewArrayNative(obj.runtime, argumentList))
} }
func builtinNewArrayNative(runtime *_runtime, argumentList []Value) *_object { func builtinNewArrayNative(rt *runtime, argumentList []Value) *object {
if len(argumentList) == 1 { if len(argumentList) == 1 {
firstArgument := argumentList[0] firstArgument := argumentList[0]
if firstArgument.IsNumber() { if firstArgument.IsNumber() {
return runtime.newArray(arrayUint32(runtime, firstArgument)) return rt.newArray(arrayUint32(rt, firstArgument))
} }
} }
return runtime.newArrayOf(argumentList) return rt.newArrayOf(argumentList)
} }
func builtinArray_toString(call FunctionCall) Value { func builtinArrayToString(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
join := thisObject.get("join") join := thisObject.get("join")
if join.isCallable() { if join.isCallable() {
join := join._object() join := join.object()
return join.call(call.This, call.ArgumentList, false, nativeFrame) return join.call(call.This, call.ArgumentList, false, nativeFrame)
} }
return builtinObject_toString(call) return builtinObjectToString(call)
} }
func builtinArray_toLocaleString(call FunctionCall) Value { func builtinArrayToLocaleString(call FunctionCall) Value {
separator := "," separator := ","
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
if length == 0 { if length == 0 {
return toValue_string("") return stringValue("")
} }
stringList := make([]string, 0, length) stringList := make([]string, 0, length)
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index++ {
value := thisObject.get(arrayIndexToString(index)) value := thisObject.get(arrayIndexToString(index))
stringValue := "" stringValue := ""
switch value.kind { switch value.kind {
case valueEmpty, valueUndefined, valueNull: case valueEmpty, valueUndefined, valueNull:
default: default:
object := call.runtime.toObject(value) obj := call.runtime.toObject(value)
toLocaleString := object.get("toLocaleString") toLocaleString := obj.get("toLocaleString")
if !toLocaleString.isCallable() { if !toLocaleString.isCallable() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.toLocaleString index[%d] %q is not callable", index, toLocaleString))
} }
stringValue = toLocaleString.call(call.runtime, toValue_object(object)).string() stringValue = toLocaleString.call(call.runtime, objectValue(obj)).string()
} }
stringList = append(stringList, stringValue) stringList = append(stringList, stringValue)
} }
return toValue_string(strings.Join(stringList, separator)) return stringValue(strings.Join(stringList, separator))
} }
func builtinArray_concat(call FunctionCall) Value { func builtinArrayConcat(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
valueArray := []Value{} valueArray := []Value{}
source := append([]Value{toValue_object(thisObject)}, call.ArgumentList...) source := append([]Value{objectValue(thisObject)}, call.ArgumentList...)
for _, item := range source { for _, item := range source {
switch item.kind { switch item.kind {
case valueObject: case valueObject:
object := item._object() obj := item.object()
if isArray(object) { if isArray(obj) {
length := object.get(propertyLength).number().int64 length := obj.get(propertyLength).number().int64
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index++ {
name := strconv.FormatInt(index, 10) name := strconv.FormatInt(index, 10)
if object.hasProperty(name) { if obj.hasProperty(name) {
valueArray = append(valueArray, object.get(name)) valueArray = append(valueArray, obj.get(name))
} else { } else {
valueArray = append(valueArray, Value{}) valueArray = append(valueArray, Value{})
} }
} }
continue continue
} }
fallthrough fallthrough
default: default:
valueArray = append(valueArray, item) valueArray = append(valueArray, item)
} }
} }
return toValue_object(call.runtime.newArrayOf(valueArray)) return objectValue(call.runtime.newArrayOf(valueArray))
} }
func builtinArray_shift(call FunctionCall) Value { func builtinArrayShift(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
if 0 == length { if length == 0 {
thisObject.put(propertyLength, toValue_int64(0), true) thisObject.put(propertyLength, int64Value(0), true)
return Value{} return Value{}
} }
first := thisObject.get("0") first := thisObject.get("0")
@ -107,52 +108,50 @@ func builtinArray_shift(call FunctionCall) Value {
} }
} }
thisObject.delete(arrayIndexToString(length-1), true) thisObject.delete(arrayIndexToString(length-1), true)
thisObject.put(propertyLength, toValue_int64(length-1), true) thisObject.put(propertyLength, int64Value(length-1), true)
return first return first
} }
func builtinArray_push(call FunctionCall) Value { func builtinArrayPush(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
itemList := call.ArgumentList itemList := call.ArgumentList
index := int64(toUint32(thisObject.get(propertyLength))) index := int64(toUint32(thisObject.get(propertyLength)))
for len(itemList) > 0 { for len(itemList) > 0 {
thisObject.put(arrayIndexToString(index), itemList[0], true) thisObject.put(arrayIndexToString(index), itemList[0], true)
itemList = itemList[1:] itemList = itemList[1:]
index += 1 index++
} }
length := toValue_int64(index) length := int64Value(index)
thisObject.put(propertyLength, length, true) thisObject.put(propertyLength, length, true)
return length return length
} }
func builtinArray_pop(call FunctionCall) Value { func builtinArrayPop(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
if 0 == length { if length == 0 {
thisObject.put(propertyLength, toValue_uint32(0), true) thisObject.put(propertyLength, uint32Value(0), true)
return Value{} return Value{}
} }
last := thisObject.get(arrayIndexToString(length - 1)) last := thisObject.get(arrayIndexToString(length - 1))
thisObject.delete(arrayIndexToString(length-1), true) thisObject.delete(arrayIndexToString(length-1), true)
thisObject.put(propertyLength, toValue_int64(length-1), true) thisObject.put(propertyLength, int64Value(length-1), true)
return last return last
} }
func builtinArray_join(call FunctionCall) Value { func builtinArrayJoin(call FunctionCall) Value {
separator := "," separator := ","
{
argument := call.Argument(0) argument := call.Argument(0)
if argument.IsDefined() { if argument.IsDefined() {
separator = argument.string() separator = argument.string()
} }
}
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
if length == 0 { if length == 0 {
return toValue_string("") return stringValue("")
} }
stringList := make([]string, 0, length) stringList := make([]string, 0, length)
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index++ {
value := thisObject.get(arrayIndexToString(index)) value := thisObject.get(arrayIndexToString(index))
stringValue := "" stringValue := ""
switch value.kind { switch value.kind {
@ -162,10 +161,10 @@ func builtinArray_join(call FunctionCall) Value {
} }
stringList = append(stringList, stringValue) stringList = append(stringList, stringValue)
} }
return toValue_string(strings.Join(stringList, separator)) return stringValue(strings.Join(stringList, separator))
} }
func builtinArray_splice(call FunctionCall) Value { func builtinArraySplice(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
@ -177,7 +176,7 @@ func builtinArray_splice(call FunctionCall) Value {
valueArray := make([]Value, deleteCount) valueArray := make([]Value, deleteCount)
for index := int64(0); index < deleteCount; index++ { for index := int64(0); index < deleteCount; index++ {
indexString := arrayIndexToString(int64(start + index)) indexString := arrayIndexToString(start + index)
if thisObject.hasProperty(indexString) { if thisObject.hasProperty(indexString) {
valueArray[index] = thisObject.get(indexString) valueArray[index] = thisObject.get(indexString)
} }
@ -197,7 +196,7 @@ func builtinArray_splice(call FunctionCall) Value {
} }
if itemCount < deleteCount { if itemCount < deleteCount {
// The Object/Array is shrinking // The Object/Array is shrinking
stop := int64(length) - deleteCount stop := length - deleteCount
// The new length of the Object/Array before // The new length of the Object/Array before
// appending the itemList remainder // appending the itemList remainder
// Stopping at the lower bound of the insertion: // Stopping at the lower bound of the insertion:
@ -215,7 +214,7 @@ func builtinArray_splice(call FunctionCall) Value {
// Delete off the end // Delete off the end
// We don't bother to delete below <stop + itemCount> (if any) since those // We don't bother to delete below <stop + itemCount> (if any) since those
// will be overwritten anyway // will be overwritten anyway
for index := int64(length); index > (stop + itemCount); index-- { for index := length; index > (stop + itemCount); index-- {
thisObject.delete(arrayIndexToString(index-1), true) thisObject.delete(arrayIndexToString(index-1), true)
} }
} else if itemCount > deleteCount { } else if itemCount > deleteCount {
@ -226,7 +225,7 @@ func builtinArray_splice(call FunctionCall) Value {
// Starting from the upper bound of the deletion: // Starting from the upper bound of the deletion:
// Move an item from the after the deleted portion // Move an item from the after the deleted portion
// to a position after the inserted portion // to a position after the inserted portion
for index := int64(length) - deleteCount; index > start; index-- { for index := length - deleteCount; index > start; index-- {
from := arrayIndexToString(index + deleteCount - 1) from := arrayIndexToString(index + deleteCount - 1)
to := arrayIndexToString(index + itemCount - 1) to := arrayIndexToString(index + itemCount - 1)
if thisObject.hasProperty(from) { if thisObject.hasProperty(from) {
@ -240,12 +239,12 @@ func builtinArray_splice(call FunctionCall) Value {
for index := int64(0); index < itemCount; index++ { for index := int64(0); index < itemCount; index++ {
thisObject.put(arrayIndexToString(index+start), itemList[index], true) thisObject.put(arrayIndexToString(index+start), itemList[index], true)
} }
thisObject.put(propertyLength, toValue_int64(int64(length)+itemCount-deleteCount), true) thisObject.put(propertyLength, int64Value(length+itemCount-deleteCount), true)
return toValue_object(call.runtime.newArrayOf(valueArray)) return objectValue(call.runtime.newArrayOf(valueArray))
} }
func builtinArray_slice(call FunctionCall) Value { func builtinArraySlice(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
@ -253,7 +252,7 @@ func builtinArray_slice(call FunctionCall) Value {
if start >= end { if start >= end {
// Always an empty array // Always an empty array
return toValue_object(call.runtime.newArray(0)) return objectValue(call.runtime.newArray(0))
} }
sliceLength := end - start sliceLength := end - start
sliceValueArray := make([]Value, sliceLength) sliceValueArray := make([]Value, sliceLength)
@ -265,10 +264,10 @@ func builtinArray_slice(call FunctionCall) Value {
} }
} }
return toValue_object(call.runtime.newArrayOf(sliceValueArray)) return objectValue(call.runtime.newArrayOf(sliceValueArray))
} }
func builtinArray_unshift(call FunctionCall) Value { func builtinArrayUnshift(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
itemList := call.ArgumentList itemList := call.ArgumentList
@ -288,12 +287,12 @@ func builtinArray_unshift(call FunctionCall) Value {
thisObject.put(arrayIndexToString(index), itemList[index], true) thisObject.put(arrayIndexToString(index), itemList[index], true)
} }
newLength := toValue_int64(length + itemCount) newLength := int64Value(length + itemCount)
thisObject.put(propertyLength, newLength, true) thisObject.put(propertyLength, newLength, true)
return newLength return newLength
} }
func builtinArray_reverse(call FunctionCall) Value { func builtinArrayReverse(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
@ -315,30 +314,29 @@ func builtinArray_reverse(call FunctionCall) Value {
lower.exists = thisObject.hasProperty(lower.name) lower.exists = thisObject.hasProperty(lower.name)
upper.exists = thisObject.hasProperty(upper.name) upper.exists = thisObject.hasProperty(upper.name)
if lower.exists && upper.exists { switch {
case lower.exists && upper.exists:
lowerValue := thisObject.get(lower.name) lowerValue := thisObject.get(lower.name)
upperValue := thisObject.get(upper.name) upperValue := thisObject.get(upper.name)
thisObject.put(lower.name, upperValue, true) thisObject.put(lower.name, upperValue, true)
thisObject.put(upper.name, lowerValue, true) thisObject.put(upper.name, lowerValue, true)
} else if !lower.exists && upper.exists { case !lower.exists && upper.exists:
value := thisObject.get(upper.name) value := thisObject.get(upper.name)
thisObject.delete(upper.name, true) thisObject.delete(upper.name, true)
thisObject.put(lower.name, value, true) thisObject.put(lower.name, value, true)
} else if lower.exists && !upper.exists { case lower.exists && !upper.exists:
value := thisObject.get(lower.name) value := thisObject.get(lower.name)
thisObject.delete(lower.name, true) thisObject.delete(lower.name, true)
thisObject.put(upper.name, value, true) thisObject.put(upper.name, value, true)
} else {
// Nothing happens.
} }
lower.index += 1 lower.index++
} }
return call.This return call.This
} }
func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int { func sortCompare(thisObject *object, index0, index1 uint, compare *object) int {
j := struct { j := struct {
name string name string
exists bool exists bool
@ -351,11 +349,12 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
k.name = arrayIndexToString(int64(index1)) k.name = arrayIndexToString(int64(index1))
k.exists = thisObject.hasProperty(k.name) k.exists = thisObject.hasProperty(k.name)
if !j.exists && !k.exists { switch {
case !j.exists && !k.exists:
return 0 return 0
} else if !j.exists { case !j.exists:
return 1 return 1
} else if !k.exists { case !k.exists:
return -1 return -1
} }
@ -364,11 +363,12 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
j.defined = x.IsDefined() j.defined = x.IsDefined()
k.defined = y.IsDefined() k.defined = y.IsDefined()
if !j.defined && !k.defined { switch {
case !j.defined && !k.defined:
return 0 return 0
} else if !j.defined { case !j.defined:
return 1 return 1
} else if !k.defined { case !k.defined:
return -1 return -1
} }
@ -388,7 +388,7 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
return toIntSign(compare.call(Value{}, []Value{x, y}, false, nativeFrame)) return toIntSign(compare.call(Value{}, []Value{x, y}, false, nativeFrame))
} }
func arraySortSwap(thisObject *_object, index0, index1 uint) { func arraySortSwap(thisObject *object, index0, index1 uint) {
j := struct { j := struct {
name string name string
exists bool exists bool
@ -400,25 +400,24 @@ func arraySortSwap(thisObject *_object, index0, index1 uint) {
k.name = arrayIndexToString(int64(index1)) k.name = arrayIndexToString(int64(index1))
k.exists = thisObject.hasProperty(k.name) k.exists = thisObject.hasProperty(k.name)
if j.exists && k.exists { switch {
jValue := thisObject.get(j.name) case j.exists && k.exists:
kValue := thisObject.get(k.name) jv := thisObject.get(j.name)
thisObject.put(j.name, kValue, true) kv := thisObject.get(k.name)
thisObject.put(k.name, jValue, true) thisObject.put(j.name, kv, true)
} else if !j.exists && k.exists { thisObject.put(k.name, jv, true)
case !j.exists && k.exists:
value := thisObject.get(k.name) value := thisObject.get(k.name)
thisObject.delete(k.name, true) thisObject.delete(k.name, true)
thisObject.put(j.name, value, true) thisObject.put(j.name, value, true)
} else if j.exists && !k.exists { case j.exists && !k.exists:
value := thisObject.get(j.name) value := thisObject.get(j.name)
thisObject.delete(j.name, true) thisObject.delete(j.name, true)
thisObject.put(k.name, value, true) thisObject.put(k.name, value, true)
} else {
// Nothing happens.
} }
} }
func arraySortQuickPartition(thisObject *_object, left, right, pivot uint, compare *_object) (uint, uint) { func arraySortQuickPartition(thisObject *object, left, right, pivot uint, compare *object) (uint, uint) {
arraySortSwap(thisObject, pivot, right) // Right is now the pivot value arraySortSwap(thisObject, pivot, right) // Right is now the pivot value
cursor := left cursor := left
cursor2 := left cursor2 := left
@ -429,18 +428,18 @@ func arraySortQuickPartition(thisObject *_object, left, right, pivot uint, compa
if cursor < cursor2 { if cursor < cursor2 {
arraySortSwap(thisObject, index, cursor2) arraySortSwap(thisObject, index, cursor2)
} }
cursor += 1 cursor++
cursor2 += 1 cursor2++
} else if comparison == 0 { } else if comparison == 0 {
arraySortSwap(thisObject, index, cursor2) arraySortSwap(thisObject, index, cursor2)
cursor2 += 1 cursor2++
} }
} }
arraySortSwap(thisObject, cursor2, right) arraySortSwap(thisObject, cursor2, right)
return cursor, cursor2 return cursor, cursor2
} }
func arraySortQuickSort(thisObject *_object, left, right uint, compare *_object) { func arraySortQuickSort(thisObject *object, left, right uint, compare *object) {
if left < right { if left < right {
middle := left + (right-left)/2 middle := left + (right-left)/2
pivot, pivot2 := arraySortQuickPartition(thisObject, left, right, middle, compare) pivot, pivot2 := arraySortQuickPartition(thisObject, left, right, middle, compare)
@ -451,14 +450,14 @@ func arraySortQuickSort(thisObject *_object, left, right uint, compare *_object)
} }
} }
func builtinArray_sort(call FunctionCall) Value { func builtinArraySort(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
length := uint(toUint32(thisObject.get(propertyLength))) length := uint(toUint32(thisObject.get(propertyLength)))
compareValue := call.Argument(0) compareValue := call.Argument(0)
compare := compareValue._object() compare := compareValue.object()
if compareValue.IsUndefined() { if compareValue.IsUndefined() {
} else if !compareValue.isCallable() { } else if !compareValue.isCallable() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.sort value %q is not callable", compareValue))
} }
if length > 1 { if length > 1 {
arraySortQuickSort(thisObject, 0, length-1, compare) arraySortQuickSort(thisObject, 0, length-1, compare)
@ -466,11 +465,11 @@ func builtinArray_sort(call FunctionCall) Value {
return call.This return call.This
} }
func builtinArray_isArray(call FunctionCall) Value { func builtinArrayIsArray(call FunctionCall) Value {
return toValue_bool(isArray(call.Argument(0)._object())) return boolValue(isArray(call.Argument(0).object()))
} }
func builtinArray_indexOf(call FunctionCall) Value { func builtinArrayIndexOf(call FunctionCall) Value {
thisObject, matchValue := call.thisObject(), call.Argument(0) thisObject, matchValue := call.thisObject(), call.Argument(0)
if length := int64(toUint32(thisObject.get(propertyLength))); length > 0 { if length := int64(toUint32(thisObject.get(propertyLength))); length > 0 {
index := int64(0) index := int64(0)
@ -485,20 +484,20 @@ func builtinArray_indexOf(call FunctionCall) Value {
index = -1 index = -1
} }
for ; index >= 0 && index < length; index++ { for ; index >= 0 && index < length; index++ {
name := arrayIndexToString(int64(index)) name := arrayIndexToString(index)
if !thisObject.hasProperty(name) { if !thisObject.hasProperty(name) {
continue continue
} }
value := thisObject.get(name) value := thisObject.get(name)
if strictEqualityComparison(matchValue, value) { if strictEqualityComparison(matchValue, value) {
return toValue_uint32(uint32(index)) return uint32Value(uint32(index))
} }
} }
} }
return toValue_int(-1) return intValue(-1)
} }
func builtinArray_lastIndexOf(call FunctionCall) Value { func builtinArrayLastIndexOf(call FunctionCall) Value {
thisObject, matchValue := call.thisObject(), call.Argument(0) thisObject, matchValue := call.thisObject(), call.Argument(0)
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
index := length - 1 index := length - 1
@ -511,30 +510,30 @@ func builtinArray_lastIndexOf(call FunctionCall) Value {
if index > length { if index > length {
index = length - 1 index = length - 1
} else if 0 > index { } else if 0 > index {
return toValue_int(-1) return intValue(-1)
} }
for ; index >= 0; index-- { for ; index >= 0; index-- {
name := arrayIndexToString(int64(index)) name := arrayIndexToString(index)
if !thisObject.hasProperty(name) { if !thisObject.hasProperty(name) {
continue continue
} }
value := thisObject.get(name) value := thisObject.get(name)
if strictEqualityComparison(matchValue, value) { if strictEqualityComparison(matchValue, value) {
return toValue_uint32(uint32(index)) return uint32Value(uint32(index))
} }
} }
return toValue_int(-1) return intValue(-1)
} }
func builtinArray_every(call FunctionCall) Value { func builtinArrayEvery(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1) callThis := call.Argument(1)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, toValue_int64(index), this).bool() { if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, int64Value(index), this).bool() {
continue continue
} }
return falseValue return falseValue
@ -542,46 +541,46 @@ func builtinArray_every(call FunctionCall) Value {
} }
return trueValue return trueValue
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.every argument %q is not callable", call.Argument(0)))
} }
func builtinArray_some(call FunctionCall) Value { func builtinArraySome(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1) callThis := call.Argument(1)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, toValue_int64(index), this).bool() { if value := thisObject.get(key); iterator.call(call.runtime, callThis, value, int64Value(index), this).bool() {
return trueValue return trueValue
} }
} }
} }
return falseValue return falseValue
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.some %q if not callable", call.Argument(0)))
} }
func builtinArray_forEach(call FunctionCall) Value { func builtinArrayForEach(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1) callThis := call.Argument(1)
for index := int64(0); index < length; index++ { for index := int64(0); index < length; index++ {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
iterator.call(call.runtime, callThis, thisObject.get(key), toValue_int64(index), this) iterator.call(call.runtime, callThis, thisObject.get(key), int64Value(index), this)
} }
} }
return Value{} return Value{}
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.foreach %q if not callable", call.Argument(0)))
} }
func builtinArray_map(call FunctionCall) Value { func builtinArrayMap(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1) callThis := call.Argument(1)
@ -593,14 +592,14 @@ func builtinArray_map(call FunctionCall) Value {
values[index] = Value{} values[index] = Value{}
} }
} }
return toValue_object(call.runtime.newArrayOf(values)) return objectValue(call.runtime.newArrayOf(values))
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.foreach %q if not callable", call.Argument(0)))
} }
func builtinArray_filter(call FunctionCall) Value { func builtinArrayFilter(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength))) length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1) callThis := call.Argument(1)
@ -613,14 +612,14 @@ func builtinArray_filter(call FunctionCall) Value {
} }
} }
} }
return toValue_object(call.runtime.newArrayOf(values)) return objectValue(call.runtime.newArrayOf(values))
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.filter %q if not callable", call.Argument(0)))
} }
func builtinArray_reduce(call FunctionCall) Value { func builtinArrayReduce(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
initial := len(call.ArgumentList) > 1 initial := len(call.ArgumentList) > 1
start := call.Argument(1) start := call.Argument(1)
@ -633,6 +632,7 @@ func builtinArray_reduce(call FunctionCall) Value {
if key := arrayIndexToString(index); thisObject.hasProperty(key) { if key := arrayIndexToString(index); thisObject.hasProperty(key) {
accumulator = thisObject.get(key) accumulator = thisObject.get(key)
index++ index++
break break
} }
} }
@ -647,12 +647,12 @@ func builtinArray_reduce(call FunctionCall) Value {
return accumulator return accumulator
} }
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.reduce %q if not callable", call.Argument(0)))
} }
func builtinArray_reduceRight(call FunctionCall) Value { func builtinArrayReduceRight(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
this := toValue_object(thisObject) this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() { if iterator := call.Argument(0); iterator.isCallable() {
initial := len(call.ArgumentList) > 1 initial := len(call.ArgumentList) > 1
start := call.Argument(1) start := call.Argument(1)
@ -679,5 +679,5 @@ func builtinArray_reduceRight(call FunctionCall) Value {
return accumulator return accumulator
} }
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Array.reduceRight %q if not callable", call.Argument(0)))
} }

View file

@ -3,26 +3,26 @@ package otto
// Boolean // Boolean
func builtinBoolean(call FunctionCall) Value { func builtinBoolean(call FunctionCall) Value {
return toValue_bool(call.Argument(0).bool()) return boolValue(call.Argument(0).bool())
} }
func builtinNewBoolean(self *_object, argumentList []Value) Value { func builtinNewBoolean(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newBoolean(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newBoolean(valueOfArrayIndex(argumentList, 0)))
} }
func builtinBoolean_toString(call FunctionCall) Value { func builtinBooleanToString(call FunctionCall) Value {
value := call.This value := call.This
if !value.IsBoolean() { if !value.IsBoolean() {
// Will throw a TypeError if ThisObject is not a Boolean // Will throw a TypeError if ThisObject is not a Boolean
value = call.thisClassObject(classBoolean).primitiveValue() value = call.thisClassObject(classBooleanName).primitiveValue()
} }
return toValue_string(value.string()) return stringValue(value.string())
} }
func builtinBoolean_valueOf(call FunctionCall) Value { func builtinBooleanValueOf(call FunctionCall) Value {
value := call.This value := call.This
if !value.IsBoolean() { if !value.IsBoolean() {
value = call.thisClassObject(classBoolean).primitiveValue() value = call.thisClassObject(classBooleanName).primitiveValue()
} }
return value return value
} }

View file

@ -2,121 +2,118 @@ package otto
import ( import (
"math" "math"
Time "time" "time"
) )
// Date // Date
const ( const (
// TODO Be like V8? // TODO Be like V8?
// builtinDate_goDateTimeLayout = "Mon Jan 2 2006 15:04:05 GMT-0700 (MST)" // builtinDateDateTimeLayout = "Mon Jan 2 2006 15:04:05 GMT-0700 (MST)".
builtinDate_goDateTimeLayout = Time.RFC1123 // "Mon, 02 Jan 2006 15:04:05 MST" builtinDateDateTimeLayout = time.RFC1123 // "Mon, 02 Jan 2006 15:04:05 MST"
builtinDate_goDateLayout = "Mon, 02 Jan 2006" builtinDateDateLayout = "Mon, 02 Jan 2006"
builtinDate_goTimeLayout = "15:04:05 MST" builtinDateTimeLayout = "15:04:05 MST"
) )
var (
// utcTimeZone is the time zone used for UTC calculations. // utcTimeZone is the time zone used for UTC calculations.
// It is GMT not UTC as that's what Javascript does because toUTCString is // It is GMT not UTC as that's what Javascript does because toUTCString is
// actually an alias to toGMTString. // actually an alias to toGMTString.
utcTimeZone = Time.FixedZone("GMT", 0) var utcTimeZone = time.FixedZone("GMT", 0)
)
func builtinDate(call FunctionCall) Value { func builtinDate(call FunctionCall) Value {
date := &_dateObject{} date := &dateObject{}
date.Set(newDateTime([]Value{}, Time.Local)) date.Set(newDateTime([]Value{}, time.Local)) //nolint: gosmopolitan
return toValue_string(date.Time().Format(builtinDate_goDateTimeLayout)) return stringValue(date.Time().Format(builtinDateDateTimeLayout))
} }
func builtinNewDate(self *_object, argumentList []Value) Value { func builtinNewDate(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newDate(newDateTime(argumentList, Time.Local))) return objectValue(obj.runtime.newDate(newDateTime(argumentList, time.Local))) //nolint: gosmopolitan
} }
func builtinDate_toString(call FunctionCall) Value { func builtinDateToString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Local().Format(builtinDate_goDateTimeLayout)) return stringValue(date.Time().Local().Format(builtinDateDateTimeLayout)) //nolint: gosmopolitan
} }
func builtinDate_toDateString(call FunctionCall) Value { func builtinDateToDateString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Local().Format(builtinDate_goDateLayout)) return stringValue(date.Time().Local().Format(builtinDateDateLayout)) //nolint: gosmopolitan
} }
func builtinDate_toTimeString(call FunctionCall) Value { func builtinDateToTimeString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Local().Format(builtinDate_goTimeLayout)) return stringValue(date.Time().Local().Format(builtinDateTimeLayout)) //nolint: gosmopolitan
} }
func builtinDate_toUTCString(call FunctionCall) Value { func builtinDateToUTCString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().In(utcTimeZone).Format(builtinDate_goDateTimeLayout)) return stringValue(date.Time().In(utcTimeZone).Format(builtinDateDateTimeLayout))
} }
func builtinDate_toISOString(call FunctionCall) Value { func builtinDateToISOString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Format("2006-01-02T15:04:05.000Z")) return stringValue(date.Time().Format("2006-01-02T15:04:05.000Z"))
} }
func builtinDate_toJSON(call FunctionCall) Value { func builtinDateToJSON(call FunctionCall) Value {
object := call.thisObject() obj := call.thisObject()
value := object.DefaultValue(defaultValueHintNumber) // FIXME object.primitiveNumberValue value := obj.DefaultValue(defaultValueHintNumber) // FIXME object.primitiveNumberValue
{ // FIXME value.isFinite // FIXME fv.isFinite
value := value.float64() if fv := value.float64(); math.IsNaN(fv) || math.IsInf(fv, 0) {
if math.IsNaN(value) || math.IsInf(value, 0) {
return nullValue return nullValue
} }
}
toISOString := object.get("toISOString") toISOString := obj.get("toISOString")
if !toISOString.isCallable() { if !toISOString.isCallable() {
// FIXME // FIXME
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Date.toJSON toISOString %q is not callable", toISOString))
} }
return toISOString.call(call.runtime, toValue_object(object), []Value{}) return toISOString.call(call.runtime, objectValue(obj), []Value{})
} }
func builtinDate_toGMTString(call FunctionCall) Value { func builtinDateToGMTString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Format("Mon, 02 Jan 2006 15:04:05 GMT")) return stringValue(date.Time().Format("Mon, 02 Jan 2006 15:04:05 GMT"))
} }
func builtinDate_getTime(call FunctionCall) Value { func builtinDateGetTime(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
// We do this (convert away from a float) so the user // We do this (convert away from a float) so the user
// does not get something back in exponential notation // does not get something back in exponential notation
return toValue_int64(int64(date.Epoch())) return int64Value(date.Epoch())
} }
func builtinDate_setTime(call FunctionCall) Value { func builtinDateSetTime(call FunctionCall) Value {
object := call.thisObject() obj := call.thisObject()
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
date.Set(call.Argument(0).float64()) date.Set(call.Argument(0).float64())
object.value = date obj.value = date
return date.Value() return date.Value()
} }
func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool) (*_object, *_dateObject, *_ecmaTime, []int) { func builtinDateBeforeSet(call FunctionCall, argumentLimit int, timeLocal bool) (*object, *dateObject, *ecmaTime, []int) {
object := call.thisObject() obj := call.thisObject()
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return nil, nil, nil, nil return nil, nil, nil, nil
@ -127,7 +124,7 @@ func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool
} }
if argumentLimit == 0 { if argumentLimit == 0 {
object.value = invalidDateObject obj.value = invalidDateObject
return nil, nil, nil, nil return nil, nil, nil, nil
} }
@ -138,61 +135,61 @@ func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool
switch nm.kind { switch nm.kind {
case numberInteger, numberFloat: case numberInteger, numberFloat:
default: default:
object.value = invalidDateObject obj.value = invalidDateObject
return nil, nil, nil, nil return nil, nil, nil, nil
} }
valueList[index] = int(nm.int64) valueList[index] = int(nm.int64)
} }
baseTime := date.Time() baseTime := date.Time()
if timeLocal { if timeLocal {
baseTime = baseTime.Local() baseTime = baseTime.Local() //nolint: gosmopolitan
} }
ecmaTime := ecmaTime(baseTime) ecmaTime := newEcmaTime(baseTime)
return object, &date, &ecmaTime, valueList return obj, &date, &ecmaTime, valueList
} }
func builtinDate_parse(call FunctionCall) Value { func builtinDateParse(call FunctionCall) Value {
date := call.Argument(0).string() date := call.Argument(0).string()
return toValue_float64(dateParse(date)) return float64Value(dateParse(date))
} }
func builtinDate_UTC(call FunctionCall) Value { func builtinDateUTC(call FunctionCall) Value {
return toValue_float64(newDateTime(call.ArgumentList, Time.UTC)) return float64Value(newDateTime(call.ArgumentList, time.UTC))
} }
func builtinDate_now(call FunctionCall) Value { func builtinDateNow(call FunctionCall) Value {
call.ArgumentList = []Value(nil) call.ArgumentList = []Value(nil)
return builtinDate_UTC(call) return builtinDateUTC(call)
} }
// This is a placeholder // This is a placeholder.
func builtinDate_toLocaleString(call FunctionCall) Value { func builtinDateToLocaleString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Local().Format("2006-01-02 15:04:05")) return stringValue(date.Time().Local().Format("2006-01-02 15:04:05")) //nolint: gosmopolitan
} }
// This is a placeholder // This is a placeholder.
func builtinDate_toLocaleDateString(call FunctionCall) Value { func builtinDateToLocaleDateString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Local().Format("2006-01-02")) return stringValue(date.Time().Local().Format("2006-01-02")) //nolint: gosmopolitan
} }
// This is a placeholder // This is a placeholder.
func builtinDate_toLocaleTimeString(call FunctionCall) Value { func builtinDateToLocaleTimeString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return toValue_string("Invalid Date") return stringValue("Invalid Date")
} }
return toValue_string(date.Time().Local().Format("15:04:05")) return stringValue(date.Time().Local().Format("15:04:05")) //nolint: gosmopolitan
} }
func builtinDate_valueOf(call FunctionCall) Value { func builtinDateValueOf(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
@ -200,155 +197,155 @@ func builtinDate_valueOf(call FunctionCall) Value {
return date.Value() return date.Value()
} }
func builtinDate_getYear(call FunctionCall) Value { func builtinDateGetYear(call FunctionCall) Value {
// Will throw a TypeError is ThisObject is nil or // Will throw a TypeError is ThisObject is nil or
// does not have Class of "Date" // does not have Class of "Date"
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Year() - 1900) return intValue(date.Time().Local().Year() - 1900) //nolint: gosmopolitan
} }
func builtinDate_getFullYear(call FunctionCall) Value { func builtinDateGetFullYear(call FunctionCall) Value {
// Will throw a TypeError is ThisObject is nil or // Will throw a TypeError is ThisObject is nil or
// does not have Class of "Date" // does not have Class of "Date"
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Year()) return intValue(date.Time().Local().Year()) //nolint: gosmopolitan
} }
func builtinDate_getUTCFullYear(call FunctionCall) Value { func builtinDateGetUTCFullYear(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Year()) return intValue(date.Time().Year())
} }
func builtinDate_getMonth(call FunctionCall) Value { func builtinDateGetMonth(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(dateFromGoMonth(date.Time().Local().Month())) return intValue(dateFromGoMonth(date.Time().Local().Month())) //nolint: gosmopolitan
} }
func builtinDate_getUTCMonth(call FunctionCall) Value { func builtinDateGetUTCMonth(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(dateFromGoMonth(date.Time().Month())) return intValue(dateFromGoMonth(date.Time().Month()))
} }
func builtinDate_getDate(call FunctionCall) Value { func builtinDateGetDate(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Day()) return intValue(date.Time().Local().Day()) //nolint: gosmopolitan
} }
func builtinDate_getUTCDate(call FunctionCall) Value { func builtinDateGetUTCDate(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Day()) return intValue(date.Time().Day())
} }
func builtinDate_getDay(call FunctionCall) Value { func builtinDateGetDay(call FunctionCall) Value {
// Actually day of the week // Actually day of the week
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(dateFromGoDay(date.Time().Local().Weekday())) return intValue(dateFromGoDay(date.Time().Local().Weekday())) //nolint: gosmopolitan
} }
func builtinDate_getUTCDay(call FunctionCall) Value { func builtinDateGetUTCDay(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(dateFromGoDay(date.Time().Weekday())) return intValue(dateFromGoDay(date.Time().Weekday()))
} }
func builtinDate_getHours(call FunctionCall) Value { func builtinDateGetHours(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Hour()) return intValue(date.Time().Local().Hour()) //nolint: gosmopolitan
} }
func builtinDate_getUTCHours(call FunctionCall) Value { func builtinDateGetUTCHours(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Hour()) return intValue(date.Time().Hour())
} }
func builtinDate_getMinutes(call FunctionCall) Value { func builtinDateGetMinutes(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Minute()) return intValue(date.Time().Local().Minute()) //nolint: gosmopolitan
} }
func builtinDate_getUTCMinutes(call FunctionCall) Value { func builtinDateGetUTCMinutes(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Minute()) return intValue(date.Time().Minute())
} }
func builtinDate_getSeconds(call FunctionCall) Value { func builtinDateGetSeconds(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Second()) return intValue(date.Time().Local().Second()) //nolint: gosmopolitan
} }
func builtinDate_getUTCSeconds(call FunctionCall) Value { func builtinDateGetUTCSeconds(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Second()) return intValue(date.Time().Second())
} }
func builtinDate_getMilliseconds(call FunctionCall) Value { func builtinDateGetMilliseconds(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Local().Nanosecond() / (100 * 100 * 100)) return intValue(date.Time().Local().Nanosecond() / (100 * 100 * 100)) //nolint: gosmopolitan
} }
func builtinDate_getUTCMilliseconds(call FunctionCall) Value { func builtinDateGetUTCMilliseconds(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
return toValue_int(date.Time().Nanosecond() / (100 * 100 * 100)) return intValue(date.Time().Nanosecond() / (100 * 100 * 100))
} }
func builtinDate_getTimezoneOffset(call FunctionCall) Value { func builtinDateGetTimezoneOffset(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject()) date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN { if date.isNaN {
return NaNValue() return NaNValue()
} }
timeLocal := date.Time().Local() timeLocal := date.Time().Local() //nolint: gosmopolitan
// Is this kosher? // Is this kosher?
timeLocalAsUTC := Time.Date( timeLocalAsUTC := time.Date(
timeLocal.Year(), timeLocal.Year(),
timeLocal.Month(), timeLocal.Month(),
timeLocal.Day(), timeLocal.Day(),
@ -356,13 +353,13 @@ func builtinDate_getTimezoneOffset(call FunctionCall) Value {
timeLocal.Minute(), timeLocal.Minute(),
timeLocal.Second(), timeLocal.Second(),
timeLocal.Nanosecond(), timeLocal.Nanosecond(),
Time.UTC, time.UTC,
) )
return toValue_float64(date.Time().Sub(timeLocalAsUTC).Seconds() / 60) return float64Value(date.Time().Sub(timeLocalAsUTC).Seconds() / 60)
} }
func builtinDate_setMilliseconds(call FunctionCall) Value { func builtinDateSetMilliseconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -370,12 +367,12 @@ func builtinDate_setMilliseconds(call FunctionCall) Value {
ecmaTime.millisecond = value[0] ecmaTime.millisecond = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCMilliseconds(call FunctionCall) Value { func builtinDateSetUTCMilliseconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -383,12 +380,12 @@ func builtinDate_setUTCMilliseconds(call FunctionCall) Value {
ecmaTime.millisecond = value[0] ecmaTime.millisecond = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setSeconds(call FunctionCall) Value { func builtinDateSetSeconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -399,12 +396,12 @@ func builtinDate_setSeconds(call FunctionCall) Value {
ecmaTime.second = value[0] ecmaTime.second = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCSeconds(call FunctionCall) Value { func builtinDateSetUTCSeconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -415,12 +412,12 @@ func builtinDate_setUTCSeconds(call FunctionCall) Value {
ecmaTime.second = value[0] ecmaTime.second = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setMinutes(call FunctionCall) Value { func builtinDateSetMinutes(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -434,12 +431,12 @@ func builtinDate_setMinutes(call FunctionCall) Value {
ecmaTime.minute = value[0] ecmaTime.minute = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCMinutes(call FunctionCall) Value { func builtinDateSetUTCMinutes(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -453,58 +450,58 @@ func builtinDate_setUTCMinutes(call FunctionCall) Value {
ecmaTime.minute = value[0] ecmaTime.minute = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setHours(call FunctionCall) Value { func builtinDateSetHours(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 4, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 4, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
if len(value) > 3 { switch {
case len(value) > 3:
ecmaTime.millisecond = value[3] ecmaTime.millisecond = value[3]
fallthrough
case len(value) > 2:
ecmaTime.second = value[2] ecmaTime.second = value[2]
ecmaTime.minute = value[1] fallthrough
} else if len(value) > 2 { case len(value) > 1:
ecmaTime.second = value[2]
ecmaTime.minute = value[1]
} else if len(value) > 1 {
ecmaTime.minute = value[1] ecmaTime.minute = value[1]
} }
ecmaTime.hour = value[0] ecmaTime.hour = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCHours(call FunctionCall) Value { func builtinDateSetUTCHours(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 4, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 4, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
if len(value) > 3 { switch {
case len(value) > 3:
ecmaTime.millisecond = value[3] ecmaTime.millisecond = value[3]
fallthrough
case len(value) > 2:
ecmaTime.second = value[2] ecmaTime.second = value[2]
ecmaTime.minute = value[1] fallthrough
} else if len(value) > 2 { case len(value) > 1:
ecmaTime.second = value[2]
ecmaTime.minute = value[1]
} else if len(value) > 1 {
ecmaTime.minute = value[1] ecmaTime.minute = value[1]
} }
ecmaTime.hour = value[0] ecmaTime.hour = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setDate(call FunctionCall) Value { func builtinDateSetDate(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -512,12 +509,12 @@ func builtinDate_setDate(call FunctionCall) Value {
ecmaTime.day = value[0] ecmaTime.day = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCDate(call FunctionCall) Value { func builtinDateSetUTCDate(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -525,12 +522,12 @@ func builtinDate_setUTCDate(call FunctionCall) Value {
ecmaTime.day = value[0] ecmaTime.day = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setMonth(call FunctionCall) Value { func builtinDateSetMonth(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -541,12 +538,12 @@ func builtinDate_setMonth(call FunctionCall) Value {
ecmaTime.month = value[0] ecmaTime.month = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCMonth(call FunctionCall) Value { func builtinDateSetUTCMonth(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -557,12 +554,12 @@ func builtinDate_setUTCMonth(call FunctionCall) Value {
ecmaTime.month = value[0] ecmaTime.month = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setYear(call FunctionCall) Value { func builtinDateSetYear(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -574,12 +571,12 @@ func builtinDate_setYear(call FunctionCall) Value {
ecmaTime.year = year ecmaTime.year = year
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setFullYear(call FunctionCall) Value { func builtinDateSetFullYear(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, true) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, true)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -593,12 +590,12 @@ func builtinDate_setFullYear(call FunctionCall) Value {
ecmaTime.year = value[0] ecmaTime.year = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }
func builtinDate_setUTCFullYear(call FunctionCall) Value { func builtinDateSetUTCFullYear(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, false) obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, false)
if ecmaTime == nil { if ecmaTime == nil {
return NaNValue() return NaNValue()
} }
@ -612,7 +609,7 @@ func builtinDate_setUTCFullYear(call FunctionCall) Value {
ecmaTime.year = value[0] ecmaTime.year = value[0]
date.SetTime(ecmaTime.goTime()) date.SetTime(ecmaTime.goTime())
object.value = *date obj.value = *date
return date.Value() return date.Value()
} }

View file

@ -5,20 +5,20 @@ import (
) )
func builtinError(call FunctionCall) Value { func builtinError(call FunctionCall) Value {
return toValue_object(call.runtime.newError(classError, call.Argument(0), 1)) return objectValue(call.runtime.newError(classErrorName, call.Argument(0), 1))
} }
func builtinNewError(self *_object, argumentList []Value) Value { func builtinNewError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newError(classError, valueOfArrayIndex(argumentList, 0), 0)) return objectValue(obj.runtime.newError(classErrorName, valueOfArrayIndex(argumentList, 0), 0))
} }
func builtinError_toString(call FunctionCall) Value { func builtinErrorToString(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
if thisObject == nil { if thisObject == nil {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Error.toString is nil"))
} }
name := classError name := classErrorName
nameValue := thisObject.get("name") nameValue := thisObject.get("name")
if nameValue.IsDefined() { if nameValue.IsDefined() {
name = nameValue.string() name = nameValue.string()
@ -31,96 +31,96 @@ func builtinError_toString(call FunctionCall) Value {
} }
if len(name) == 0 { if len(name) == 0 {
return toValue_string(message) return stringValue(message)
} }
if len(message) == 0 { if len(message) == 0 {
return toValue_string(name) return stringValue(name)
} }
return toValue_string(fmt.Sprintf("%s: %s", name, message)) return stringValue(fmt.Sprintf("%s: %s", name, message))
} }
func (runtime *_runtime) newEvalError(message Value) *_object { func (rt *runtime) newEvalError(message Value) *object {
self := runtime.newErrorObject("EvalError", message, 0) o := rt.newErrorObject("EvalError", message, 0)
self.prototype = runtime.global.EvalErrorPrototype o.prototype = rt.global.EvalErrorPrototype
return self return o
} }
func builtinEvalError(call FunctionCall) Value { func builtinEvalError(call FunctionCall) Value {
return toValue_object(call.runtime.newEvalError(call.Argument(0))) return objectValue(call.runtime.newEvalError(call.Argument(0)))
} }
func builtinNewEvalError(self *_object, argumentList []Value) Value { func builtinNewEvalError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newEvalError(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newEvalError(valueOfArrayIndex(argumentList, 0)))
} }
func (runtime *_runtime) newTypeError(message Value) *_object { func (rt *runtime) newTypeError(message Value) *object {
self := runtime.newErrorObject("TypeError", message, 0) o := rt.newErrorObject("TypeError", message, 0)
self.prototype = runtime.global.TypeErrorPrototype o.prototype = rt.global.TypeErrorPrototype
return self return o
} }
func builtinTypeError(call FunctionCall) Value { func builtinTypeError(call FunctionCall) Value {
return toValue_object(call.runtime.newTypeError(call.Argument(0))) return objectValue(call.runtime.newTypeError(call.Argument(0)))
} }
func builtinNewTypeError(self *_object, argumentList []Value) Value { func builtinNewTypeError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newTypeError(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newTypeError(valueOfArrayIndex(argumentList, 0)))
} }
func (runtime *_runtime) newRangeError(message Value) *_object { func (rt *runtime) newRangeError(message Value) *object {
self := runtime.newErrorObject("RangeError", message, 0) o := rt.newErrorObject("RangeError", message, 0)
self.prototype = runtime.global.RangeErrorPrototype o.prototype = rt.global.RangeErrorPrototype
return self return o
} }
func builtinRangeError(call FunctionCall) Value { func builtinRangeError(call FunctionCall) Value {
return toValue_object(call.runtime.newRangeError(call.Argument(0))) return objectValue(call.runtime.newRangeError(call.Argument(0)))
} }
func builtinNewRangeError(self *_object, argumentList []Value) Value { func builtinNewRangeError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newRangeError(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newRangeError(valueOfArrayIndex(argumentList, 0)))
} }
func (runtime *_runtime) newURIError(message Value) *_object { func (rt *runtime) newURIError(message Value) *object {
self := runtime.newErrorObject("URIError", message, 0) o := rt.newErrorObject("URIError", message, 0)
self.prototype = runtime.global.URIErrorPrototype o.prototype = rt.global.URIErrorPrototype
return self return o
} }
func (runtime *_runtime) newReferenceError(message Value) *_object { func (rt *runtime) newReferenceError(message Value) *object {
self := runtime.newErrorObject("ReferenceError", message, 0) o := rt.newErrorObject("ReferenceError", message, 0)
self.prototype = runtime.global.ReferenceErrorPrototype o.prototype = rt.global.ReferenceErrorPrototype
return self return o
} }
func builtinReferenceError(call FunctionCall) Value { func builtinReferenceError(call FunctionCall) Value {
return toValue_object(call.runtime.newReferenceError(call.Argument(0))) return objectValue(call.runtime.newReferenceError(call.Argument(0)))
} }
func builtinNewReferenceError(self *_object, argumentList []Value) Value { func builtinNewReferenceError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newReferenceError(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newReferenceError(valueOfArrayIndex(argumentList, 0)))
} }
func (runtime *_runtime) newSyntaxError(message Value) *_object { func (rt *runtime) newSyntaxError(message Value) *object {
self := runtime.newErrorObject("SyntaxError", message, 0) o := rt.newErrorObject("SyntaxError", message, 0)
self.prototype = runtime.global.SyntaxErrorPrototype o.prototype = rt.global.SyntaxErrorPrototype
return self return o
} }
func builtinSyntaxError(call FunctionCall) Value { func builtinSyntaxError(call FunctionCall) Value {
return toValue_object(call.runtime.newSyntaxError(call.Argument(0))) return objectValue(call.runtime.newSyntaxError(call.Argument(0)))
} }
func builtinNewSyntaxError(self *_object, argumentList []Value) Value { func builtinNewSyntaxError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newSyntaxError(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newSyntaxError(valueOfArrayIndex(argumentList, 0)))
} }
func builtinURIError(call FunctionCall) Value { func builtinURIError(call FunctionCall) Value {
return toValue_object(call.runtime.newURIError(call.Argument(0))) return objectValue(call.runtime.newURIError(call.Argument(0)))
} }
func builtinNewURIError(self *_object, argumentList []Value) Value { func builtinNewURIError(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newURIError(valueOfArrayIndex(argumentList, 0))) return objectValue(obj.runtime.newURIError(valueOfArrayIndex(argumentList, 0)))
} }

View file

@ -11,11 +11,11 @@ import (
// Function // Function
func builtinFunction(call FunctionCall) Value { func builtinFunction(call FunctionCall) Value {
return toValue_object(builtinNewFunctionNative(call.runtime, call.ArgumentList)) return objectValue(builtinNewFunctionNative(call.runtime, call.ArgumentList))
} }
func builtinNewFunction(self *_object, argumentList []Value) Value { func builtinNewFunction(obj *object, argumentList []Value) Value {
return toValue_object(builtinNewFunctionNative(self.runtime, argumentList)) return objectValue(builtinNewFunctionNative(obj.runtime, argumentList))
} }
func argumentList2parameterList(argumentList []Value) []string { func argumentList2parameterList(argumentList []Value) []string {
@ -29,10 +29,9 @@ func argumentList2parameterList(argumentList []Value) []string {
return parameterList return parameterList
} }
func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object { func builtinNewFunctionNative(rt *runtime, argumentList []Value) *object {
var parameterList, body string var parameterList, body string
count := len(argumentList) if count := len(argumentList); count > 0 {
if count > 0 {
tmp := make([]string, 0, count-1) tmp := make([]string, 0, count-1)
for _, value := range argumentList[0 : count-1] { for _, value := range argumentList[0 : count-1] {
tmp = append(tmp, value.string()) tmp = append(tmp, value.string())
@ -43,35 +42,35 @@ func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object
// FIXME // FIXME
function, err := parser.ParseFunction(parameterList, body) function, err := parser.ParseFunction(parameterList, body)
runtime.parseThrow(err) // Will panic/throw appropriately rt.parseThrow(err) // Will panic/throw appropriately
cmpl := _compiler{} cmpl := compiler{}
cmpl_function := cmpl.parseExpression(function) cmplFunction := cmpl.parseExpression(function)
return runtime.newNodeFunction(cmpl_function.(*_nodeFunctionLiteral), runtime.globalStash) return rt.newNodeFunction(cmplFunction.(*nodeFunctionLiteral), rt.globalStash)
} }
func builtinFunction_toString(call FunctionCall) Value { func builtinFunctionToString(call FunctionCall) Value {
object := call.thisClassObject(classFunction) // Should throw a TypeError unless Function obj := call.thisClassObject(classFunctionName) // Should throw a TypeError unless Function
switch fn := object.value.(type) { switch fn := obj.value.(type) {
case _nativeFunctionObject: case nativeFunctionObject:
return toValue_string(fmt.Sprintf("function %s() { [native code] }", fn.name)) return stringValue(fmt.Sprintf("function %s() { [native code] }", fn.name))
case _nodeFunctionObject: case nodeFunctionObject:
return toValue_string(fn.node.source) return stringValue(fn.node.source)
case _bindFunctionObject: case bindFunctionObject:
return toValue_string("function () { [native code] }") return stringValue("function () { [native code] }")
default:
panic(call.runtime.panicTypeError("Function.toString unknown type %T", obj.value))
}
} }
panic(call.runtime.panicTypeError("Function.toString()")) func builtinFunctionApply(call FunctionCall) Value {
}
func builtinFunction_apply(call FunctionCall) Value {
if !call.This.isCallable() { if !call.This.isCallable() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Function.apply %q is not callable", call.This))
} }
this := call.Argument(0) this := call.Argument(0)
if this.IsUndefined() { if this.IsUndefined() {
// FIXME Not ECMA5 // FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject) this = objectValue(call.runtime.globalObject)
} }
argumentList := call.Argument(1) argumentList := call.Argument(1)
switch argumentList.kind { switch argumentList.kind {
@ -79,10 +78,10 @@ func builtinFunction_apply(call FunctionCall) Value {
return call.thisObject().call(this, nil, false, nativeFrame) return call.thisObject().call(this, nil, false, nativeFrame)
case valueObject: case valueObject:
default: default:
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Function.apply unknown type %T for second argument"))
} }
arrayObject := argumentList._object() arrayObject := argumentList.object()
thisObject := call.thisObject() thisObject := call.thisObject()
length := int64(toUint32(arrayObject.get(propertyLength))) length := int64(toUint32(arrayObject.get(propertyLength)))
valueArray := make([]Value, length) valueArray := make([]Value, length)
@ -92,15 +91,15 @@ func builtinFunction_apply(call FunctionCall) Value {
return thisObject.call(this, valueArray, false, nativeFrame) return thisObject.call(this, valueArray, false, nativeFrame)
} }
func builtinFunction_call(call FunctionCall) Value { func builtinFunctionCall(call FunctionCall) Value {
if !call.This.isCallable() { if !call.This.isCallable() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Function.call %q is not callable", call.This))
} }
thisObject := call.thisObject() thisObject := call.thisObject()
this := call.Argument(0) this := call.Argument(0)
if this.IsUndefined() { if this.IsUndefined() {
// FIXME Not ECMA5 // FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject) this = objectValue(call.runtime.globalObject)
} }
if len(call.ArgumentList) >= 1 { if len(call.ArgumentList) >= 1 {
return thisObject.call(this, call.ArgumentList[1:], false, nativeFrame) return thisObject.call(this, call.ArgumentList[1:], false, nativeFrame)
@ -108,19 +107,19 @@ func builtinFunction_call(call FunctionCall) Value {
return thisObject.call(this, nil, false, nativeFrame) return thisObject.call(this, nil, false, nativeFrame)
} }
func builtinFunction_bind(call FunctionCall) Value { func builtinFunctionBind(call FunctionCall) Value {
target := call.This target := call.This
if !target.isCallable() { if !target.isCallable() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Function.bind %q is not callable", call.This))
} }
targetObject := target._object() targetObject := target.object()
this := call.Argument(0) this := call.Argument(0)
argumentList := call.slice(1) argumentList := call.slice(1)
if this.IsUndefined() { if this.IsUndefined() {
// FIXME Do this elsewhere? // FIXME Do this elsewhere?
this = toValue_object(call.runtime.globalObject) this = objectValue(call.runtime.globalObject)
} }
return toValue_object(call.runtime.newBoundFunction(targetObject, this, argumentList)) return objectValue(call.runtime.newBoundFunction(targetObject, this, argumentList))
} }

View file

@ -7,13 +7,13 @@ import (
"strings" "strings"
) )
type _builtinJSON_parseContext struct { type builtinJSONParseContext struct {
call FunctionCall call FunctionCall
reviver Value reviver Value
} }
func builtinJSON_parse(call FunctionCall) Value { func builtinJSONParse(call FunctionCall) Value {
ctx := _builtinJSON_parseContext{ ctx := builtinJSONParseContext{
call: call, call: call,
} }
revive := false revive := false
@ -27,109 +27,107 @@ func builtinJSON_parse(call FunctionCall) Value {
if err != nil { if err != nil {
panic(call.runtime.panicSyntaxError(err.Error())) panic(call.runtime.panicSyntaxError(err.Error()))
} }
value, exists := builtinJSON_parseWalk(ctx, root) value, exists := builtinJSONParseWalk(ctx, root)
if !exists { if !exists {
value = Value{} value = Value{}
} }
if revive { if revive {
root := ctx.call.runtime.newObject() root := ctx.call.runtime.newObject()
root.put("", value, false) root.put("", value, false)
return builtinJSON_reviveWalk(ctx, root, "") return builtinJSONReviveWalk(ctx, root, "")
} }
return value return value
} }
func builtinJSON_reviveWalk(ctx _builtinJSON_parseContext, holder *_object, name string) Value { func builtinJSONReviveWalk(ctx builtinJSONParseContext, holder *object, name string) Value {
value := holder.get(name) value := holder.get(name)
if object := value._object(); object != nil { if obj := value.object(); obj != nil {
if isArray(object) { if isArray(obj) {
length := int64(objectLength(object)) length := int64(objectLength(obj))
for index := int64(0); index < length; index += 1 { for index := int64(0); index < length; index++ {
name := arrayIndexToString(index) name := arrayIndexToString(index)
value := builtinJSON_reviveWalk(ctx, object, name) value := builtinJSONReviveWalk(ctx, obj, name)
if value.IsUndefined() { if value.IsUndefined() {
object.delete(name, false) obj.delete(name, false)
} else { } else {
object.defineProperty(name, value, 0111, false) obj.defineProperty(name, value, 0o111, false)
} }
} }
} else { } else {
object.enumerate(false, func(name string) bool { obj.enumerate(false, func(name string) bool {
value := builtinJSON_reviveWalk(ctx, object, name) value := builtinJSONReviveWalk(ctx, obj, name)
if value.IsUndefined() { if value.IsUndefined() {
object.delete(name, false) obj.delete(name, false)
} else { } else {
object.defineProperty(name, value, 0111, false) obj.defineProperty(name, value, 0o111, false)
} }
return true return true
}) })
} }
} }
return ctx.reviver.call(ctx.call.runtime, toValue_object(holder), name, value) return ctx.reviver.call(ctx.call.runtime, objectValue(holder), name, value)
} }
func builtinJSON_parseWalk(ctx _builtinJSON_parseContext, rawValue interface{}) (Value, bool) { func builtinJSONParseWalk(ctx builtinJSONParseContext, rawValue interface{}) (Value, bool) {
switch value := rawValue.(type) { switch value := rawValue.(type) {
case nil: case nil:
return nullValue, true return nullValue, true
case bool: case bool:
return toValue_bool(value), true return boolValue(value), true
case string: case string:
return toValue_string(value), true return stringValue(value), true
case float64: case float64:
return toValue_float64(value), true return float64Value(value), true
case []interface{}: case []interface{}:
arrayValue := make([]Value, len(value)) arrayValue := make([]Value, len(value))
for index, rawValue := range value { for index, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists { if value, exists := builtinJSONParseWalk(ctx, rawValue); exists {
arrayValue[index] = value arrayValue[index] = value
} }
} }
return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true return objectValue(ctx.call.runtime.newArrayOf(arrayValue)), true
case map[string]interface{}: case map[string]interface{}:
object := ctx.call.runtime.newObject() obj := ctx.call.runtime.newObject()
for name, rawValue := range value { for name, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists { if value, exists := builtinJSONParseWalk(ctx, rawValue); exists {
object.put(name, value, false) obj.put(name, value, false)
} }
} }
return toValue_object(object), true return objectValue(obj), true
} }
return Value{}, false return Value{}, false
} }
type _builtinJSON_stringifyContext struct { type builtinJSONStringifyContext struct {
call FunctionCall call FunctionCall
stack []*_object stack []*object
propertyList []string propertyList []string
replacerFunction *Value replacerFunction *Value
gap string gap string
} }
func builtinJSON_stringify(call FunctionCall) Value { func builtinJSONStringify(call FunctionCall) Value {
ctx := _builtinJSON_stringifyContext{ ctx := builtinJSONStringifyContext{
call: call, call: call,
stack: []*_object{nil}, stack: []*object{nil},
} }
replacer := call.Argument(1)._object() replacer := call.Argument(1).object()
if replacer != nil { if replacer != nil {
if isArray(replacer) { if isArray(replacer) {
length := objectLength(replacer) length := objectLength(replacer)
seen := map[string]bool{} seen := map[string]bool{}
propertyList := make([]string, length) propertyList := make([]string, length)
length = 0 length = 0
for index, _ := range propertyList { for index := range propertyList {
value := replacer.get(arrayIndexToString(int64(index))) value := replacer.get(arrayIndexToString(int64(index)))
switch value.kind { switch value.kind {
case valueObject: case valueObject:
switch value.value.(*_object).class { switch value.value.(*object).class {
case classString: case classStringName, classNumberName:
case classNumber:
default: default:
continue continue
} }
case valueString: case valueString, valueNumber:
case valueNumber:
default: default:
continue continue
} }
@ -138,21 +136,21 @@ func builtinJSON_stringify(call FunctionCall) Value {
continue continue
} }
seen[name] = true seen[name] = true
length += 1 length++
propertyList[index] = name propertyList[index] = name
} }
ctx.propertyList = propertyList[0:length] ctx.propertyList = propertyList[0:length]
} else if replacer.class == classFunction { } else if replacer.class == classFunctionName {
value := toValue_object(replacer) value := objectValue(replacer)
ctx.replacerFunction = &value ctx.replacerFunction = &value
} }
} }
if spaceValue, exists := call.getArgument(2); exists { if spaceValue, exists := call.getArgument(2); exists {
if spaceValue.kind == valueObject { if spaceValue.kind == valueObject {
switch spaceValue.value.(*_object).class { switch spaceValue.value.(*object).class {
case classString: case classStringName:
spaceValue = toValue_string(spaceValue.string()) spaceValue = stringValue(spaceValue.string())
case classNumber: case classNumberName:
spaceValue = spaceValue.numberValue() spaceValue = spaceValue.numberValue()
} }
} }
@ -176,51 +174,51 @@ func builtinJSON_stringify(call FunctionCall) Value {
} }
holder := call.runtime.newObject() holder := call.runtime.newObject()
holder.put("", call.Argument(0), false) holder.put("", call.Argument(0), false)
value, exists := builtinJSON_stringifyWalk(ctx, "", holder) value, exists := builtinJSONStringifyWalk(ctx, "", holder)
if !exists { if !exists {
return Value{} return Value{}
} }
valueJSON, err := json.Marshal(value) valueJSON, err := json.Marshal(value)
if err != nil { if err != nil {
panic(call.runtime.panicTypeError(err.Error())) panic(call.runtime.panicTypeError("JSON.stringify marshal: %s", err))
} }
if ctx.gap != "" { if ctx.gap != "" {
valueJSON1 := bytes.Buffer{} valueJSON1 := bytes.Buffer{}
json.Indent(&valueJSON1, valueJSON, "", ctx.gap) if err = json.Indent(&valueJSON1, valueJSON, "", ctx.gap); err != nil {
panic(call.runtime.panicTypeError("JSON.stringify indent: %s", err))
}
valueJSON = valueJSON1.Bytes() valueJSON = valueJSON1.Bytes()
} }
return toValue_string(string(valueJSON)) return stringValue(string(valueJSON))
} }
func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, holder *_object) (interface{}, bool) { func builtinJSONStringifyWalk(ctx builtinJSONStringifyContext, key string, holder *object) (interface{}, bool) {
value := holder.get(key) value := holder.get(key)
if value.IsObject() { if value.IsObject() {
object := value._object() obj := value.object()
if toJSON := object.get("toJSON"); toJSON.IsFunction() { if toJSON := obj.get("toJSON"); toJSON.IsFunction() {
value = toJSON.call(ctx.call.runtime, value, key) value = toJSON.call(ctx.call.runtime, value, key)
} else { } else if obj.objectClass.marshalJSON != nil {
// If the object is a GoStruct or something that implements json.Marshaler // If the object is a GoStruct or something that implements json.Marshaler
if object.objectClass.marshalJSON != nil { marshaler := obj.objectClass.marshalJSON(obj)
marshaler := object.objectClass.marshalJSON(object)
if marshaler != nil { if marshaler != nil {
return marshaler, true return marshaler, true
} }
} }
} }
}
if ctx.replacerFunction != nil { if ctx.replacerFunction != nil {
value = ctx.replacerFunction.call(ctx.call.runtime, toValue_object(holder), key, value) value = ctx.replacerFunction.call(ctx.call.runtime, objectValue(holder), key, value)
} }
if value.kind == valueObject { if value.kind == valueObject {
switch value.value.(*_object).class { switch value.value.(*object).class {
case classBoolean: case classBooleanName:
value = value._object().value.(Value) value = value.object().value.(Value)
case classString: case classStringName:
value = toValue_string(value.string()) value = stringValue(value.string())
case classNumber: case classNumberName:
value = value.numberValue() value = value.numberValue()
} }
} }
@ -243,10 +241,10 @@ func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, ho
case valueNull: case valueNull:
return nil, true return nil, true
case valueObject: case valueObject:
holder := value._object() holder := value.object()
if value := value._object(); nil != value { if value := value.object(); nil != value {
for _, object := range ctx.stack { for _, obj := range ctx.stack {
if holder == object { if holder == obj {
panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON")) panic(ctx.call.runtime.panicTypeError("Converting circular structure to JSON"))
} }
} }
@ -266,33 +264,33 @@ func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, ho
panic(ctx.call.runtime.panicTypeError(fmt.Sprintf("JSON.stringify: invalid length: %v (%[1]T)", value))) panic(ctx.call.runtime.panicTypeError(fmt.Sprintf("JSON.stringify: invalid length: %v (%[1]T)", value)))
} }
array := make([]interface{}, length) array := make([]interface{}, length)
for index, _ := range array { for index := range array {
name := arrayIndexToString(int64(index)) name := arrayIndexToString(int64(index))
value, _ := builtinJSON_stringifyWalk(ctx, name, holder) value, _ := builtinJSONStringifyWalk(ctx, name, holder)
array[index] = value array[index] = value
} }
return array, true return array, true
} else if holder.class != classFunction { } else if holder.class != classFunctionName {
object := map[string]interface{}{} obj := map[string]interface{}{}
if ctx.propertyList != nil { if ctx.propertyList != nil {
for _, name := range ctx.propertyList { for _, name := range ctx.propertyList {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder) value, exists := builtinJSONStringifyWalk(ctx, name, holder)
if exists { if exists {
object[name] = value obj[name] = value
} }
} }
} else { } else {
// Go maps are without order, so this doesn't conform to the ECMA ordering // Go maps are without order, so this doesn't conform to the ECMA ordering
// standard, but oh well... // standard, but oh well...
holder.enumerate(false, func(name string) bool { holder.enumerate(false, func(name string) bool {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder) value, exists := builtinJSONStringifyWalk(ctx, name, holder)
if exists { if exists {
object[name] = value obj[name] = value
} }
return true return true
}) })
} }
return object, true return obj, true
} }
} }
return nil, false return nil, false

View file

@ -7,27 +7,37 @@ import (
// Math // Math
func builtinMath_abs(call FunctionCall) Value { func builtinMathAbs(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Abs(number)) return float64Value(math.Abs(number))
} }
func builtinMath_acos(call FunctionCall) Value { func builtinMathAcos(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Acos(number)) return float64Value(math.Acos(number))
} }
func builtinMath_asin(call FunctionCall) Value { func builtinMathAcosh(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Asin(number)) return float64Value(math.Acosh(number))
} }
func builtinMath_atan(call FunctionCall) Value { func builtinMathAsin(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Atan(number)) return float64Value(math.Asin(number))
} }
func builtinMath_atan2(call FunctionCall) Value { func builtinMathAsinh(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Asinh(number))
}
func builtinMathAtan(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Atan(number))
}
func builtinMathAtan2(call FunctionCall) Value {
y := call.Argument(0).float64() y := call.Argument(0).float64()
if math.IsNaN(y) { if math.IsNaN(y) {
return NaNValue() return NaNValue()
@ -36,40 +46,75 @@ func builtinMath_atan2(call FunctionCall) Value {
if math.IsNaN(x) { if math.IsNaN(x) {
return NaNValue() return NaNValue()
} }
return toValue_float64(math.Atan2(y, x)) return float64Value(math.Atan2(y, x))
} }
func builtinMath_cos(call FunctionCall) Value { func builtinMathAtanh(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Cos(number)) return float64Value(math.Atanh(number))
} }
func builtinMath_ceil(call FunctionCall) Value { func builtinMathCbrt(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Ceil(number)) return float64Value(math.Cbrt(number))
} }
func builtinMath_exp(call FunctionCall) Value { func builtinMathCos(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Exp(number)) return float64Value(math.Cos(number))
} }
func builtinMath_floor(call FunctionCall) Value { func builtinMathCeil(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Floor(number)) return float64Value(math.Ceil(number))
} }
func builtinMath_log(call FunctionCall) Value { func builtinMathCosh(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Log(number)) return float64Value(math.Cosh(number))
} }
func builtinMath_max(call FunctionCall) Value { func builtinMathExp(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Exp(number))
}
func builtinMathExpm1(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Expm1(number))
}
func builtinMathFloor(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Floor(number))
}
func builtinMathLog(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Log(number))
}
func builtinMathLog10(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Log10(number))
}
func builtinMathLog1p(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Log1p(number))
}
func builtinMathLog2(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Log2(number))
}
func builtinMathMax(call FunctionCall) Value {
switch len(call.ArgumentList) { switch len(call.ArgumentList) {
case 0: case 0:
return negativeInfinityValue() return negativeInfinityValue()
case 1: case 1:
return toValue_float64(call.ArgumentList[0].float64()) return float64Value(call.ArgumentList[0].float64())
} }
result := call.ArgumentList[0].float64() result := call.ArgumentList[0].float64()
if math.IsNaN(result) { if math.IsNaN(result) {
@ -82,15 +127,15 @@ func builtinMath_max(call FunctionCall) Value {
} }
result = math.Max(result, value) result = math.Max(result, value)
} }
return toValue_float64(result) return float64Value(result)
} }
func builtinMath_min(call FunctionCall) Value { func builtinMathMin(call FunctionCall) Value {
switch len(call.ArgumentList) { switch len(call.ArgumentList) {
case 0: case 0:
return positiveInfinityValue() return positiveInfinityValue()
case 1: case 1:
return toValue_float64(call.ArgumentList[0].float64()) return float64Value(call.ArgumentList[0].float64())
} }
result := call.ArgumentList[0].float64() result := call.ArgumentList[0].float64()
if math.IsNaN(result) { if math.IsNaN(result) {
@ -103,49 +148,64 @@ func builtinMath_min(call FunctionCall) Value {
} }
result = math.Min(result, value) result = math.Min(result, value)
} }
return toValue_float64(result) return float64Value(result)
} }
func builtinMath_pow(call FunctionCall) Value { func builtinMathPow(call FunctionCall) Value {
// TODO Make sure this works according to the specification (15.8.2.13) // TODO Make sure this works according to the specification (15.8.2.13)
x := call.Argument(0).float64() x := call.Argument(0).float64()
y := call.Argument(1).float64() y := call.Argument(1).float64()
if math.Abs(x) == 1 && math.IsInf(y, 0) { if math.Abs(x) == 1 && math.IsInf(y, 0) {
return NaNValue() return NaNValue()
} }
return toValue_float64(math.Pow(x, y)) return float64Value(math.Pow(x, y))
} }
func builtinMath_random(call FunctionCall) Value { func builtinMathRandom(call FunctionCall) Value {
var v float64 var v float64
if call.runtime.random != nil { if call.runtime.random != nil {
v = call.runtime.random() v = call.runtime.random()
} else { } else {
v = rand.Float64() v = rand.Float64() //nolint: gosec
} }
return toValue_float64(v) return float64Value(v)
} }
func builtinMath_round(call FunctionCall) Value { func builtinMathRound(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
value := math.Floor(number + 0.5) value := math.Floor(number + 0.5)
if value == 0 { if value == 0 {
value = math.Copysign(0, number) value = math.Copysign(0, number)
} }
return toValue_float64(value) return float64Value(value)
} }
func builtinMath_sin(call FunctionCall) Value { func builtinMathSin(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Sin(number)) return float64Value(math.Sin(number))
} }
func builtinMath_sqrt(call FunctionCall) Value { func builtinMathSinh(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Sqrt(number)) return float64Value(math.Sinh(number))
} }
func builtinMath_tan(call FunctionCall) Value { func builtinMathSqrt(call FunctionCall) Value {
number := call.Argument(0).float64() number := call.Argument(0).float64()
return toValue_float64(math.Tan(number)) return float64Value(math.Sqrt(number))
}
func builtinMathTan(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Tan(number))
}
func builtinMathTanh(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Tanh(number))
}
func builtinMathTrunc(call FunctionCall) Value {
number := call.Argument(0).float64()
return float64Value(math.Trunc(number))
} }

View file

@ -15,20 +15,20 @@ func numberValueFromNumberArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 { if len(argumentList) > 0 {
return argumentList[0].numberValue() return argumentList[0].numberValue()
} }
return toValue_int(0) return intValue(0)
} }
func builtinNumber(call FunctionCall) Value { func builtinNumber(call FunctionCall) Value {
return numberValueFromNumberArgumentList(call.ArgumentList) return numberValueFromNumberArgumentList(call.ArgumentList)
} }
func builtinNewNumber(self *_object, argumentList []Value) Value { func builtinNewNumber(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newNumber(numberValueFromNumberArgumentList(argumentList))) return objectValue(obj.runtime.newNumber(numberValueFromNumberArgumentList(argumentList)))
} }
func builtinNumber_toString(call FunctionCall) Value { func builtinNumberToString(call FunctionCall) Value {
// Will throw a TypeError if ThisObject is not a Number // Will throw a TypeError if ThisObject is not a Number
value := call.thisClassObject(classNumber).primitiveValue() value := call.thisClassObject(classNumberName).primitiveValue()
radix := 10 radix := 10
radixArgument := call.Argument(0) radixArgument := call.Argument(0)
if radixArgument.IsDefined() { if radixArgument.IsDefined() {
@ -39,33 +39,32 @@ func builtinNumber_toString(call FunctionCall) Value {
radix = int(integer) radix = int(integer)
} }
if radix == 10 { if radix == 10 {
return toValue_string(value.string()) return stringValue(value.string())
} }
return toValue_string(numberToStringRadix(value, radix)) return stringValue(numberToStringRadix(value, radix))
} }
func builtinNumber_valueOf(call FunctionCall) Value { func builtinNumberValueOf(call FunctionCall) Value {
return call.thisClassObject(classNumber).primitiveValue() return call.thisClassObject(classNumberName).primitiveValue()
} }
func builtinNumber_toFixed(call FunctionCall) Value { func builtinNumberToFixed(call FunctionCall) Value {
precision := toIntegerFloat(call.Argument(0)) precision := toIntegerFloat(call.Argument(0))
if 20 < precision || 0 > precision { if 20 < precision || 0 > precision {
panic(call.runtime.panicRangeError("toFixed() precision must be between 0 and 20")) panic(call.runtime.panicRangeError("toFixed() precision must be between 0 and 20"))
} }
if call.This.IsNaN() { if call.This.IsNaN() {
return toValue_string("NaN") return stringValue("NaN")
} }
value := call.This.float64() if value := call.This.float64(); math.Abs(value) >= 1e21 {
if math.Abs(value) >= 1e21 { return stringValue(floatToString(value, 64))
return toValue_string(floatToString(value, 64))
} }
return toValue_string(strconv.FormatFloat(call.This.float64(), 'f', int(precision), 64)) return stringValue(strconv.FormatFloat(call.This.float64(), 'f', int(precision), 64))
} }
func builtinNumber_toExponential(call FunctionCall) Value { func builtinNumberToExponential(call FunctionCall) Value {
if call.This.IsNaN() { if call.This.IsNaN() {
return toValue_string("NaN") return stringValue("NaN")
} }
precision := float64(-1) precision := float64(-1)
if value := call.Argument(0); value.IsDefined() { if value := call.Argument(0); value.IsDefined() {
@ -74,33 +73,33 @@ func builtinNumber_toExponential(call FunctionCall) Value {
panic(call.runtime.panicRangeError("toString() radix must be between 2 and 36")) panic(call.runtime.panicRangeError("toString() radix must be between 2 and 36"))
} }
} }
return toValue_string(strconv.FormatFloat(call.This.float64(), 'e', int(precision), 64)) return stringValue(strconv.FormatFloat(call.This.float64(), 'e', int(precision), 64))
} }
func builtinNumber_toPrecision(call FunctionCall) Value { func builtinNumberToPrecision(call FunctionCall) Value {
if call.This.IsNaN() { if call.This.IsNaN() {
return toValue_string("NaN") return stringValue("NaN")
} }
value := call.Argument(0) value := call.Argument(0)
if value.IsUndefined() { if value.IsUndefined() {
return toValue_string(call.This.string()) return stringValue(call.This.string())
} }
precision := toIntegerFloat(value) precision := toIntegerFloat(value)
if 1 > precision { if 1 > precision {
panic(call.runtime.panicRangeError("toPrecision() precision must be greater than 1")) panic(call.runtime.panicRangeError("toPrecision() precision must be greater than 1"))
} }
return toValue_string(strconv.FormatFloat(call.This.float64(), 'g', int(precision), 64)) return stringValue(strconv.FormatFloat(call.This.float64(), 'g', int(precision), 64))
} }
func builtinNumber_isNaN(call FunctionCall) Value { func builtinNumberIsNaN(call FunctionCall) Value {
if len(call.ArgumentList) < 1 { if len(call.ArgumentList) < 1 {
return toValue_bool(false) return boolValue(false)
} }
return toValue_bool(call.Argument(0).IsNaN()) return boolValue(call.Argument(0).IsNaN())
} }
func builtinNumber_toLocaleString(call FunctionCall) Value { func builtinNumberToLocaleString(call FunctionCall) Value {
value := call.thisClassObject(classNumber).primitiveValue() value := call.thisClassObject(classNumberName).primitiveValue()
locale := call.Argument(0) locale := call.Argument(0)
lang := defaultLanguage lang := defaultLanguage
if locale.IsDefined() { if locale.IsDefined() {
@ -108,5 +107,5 @@ func builtinNumber_toLocaleString(call FunctionCall) Value {
} }
p := message.NewPrinter(lang) p := message.NewPrinter(lang)
return toValue_string(p.Sprintf("%v", number.Decimal(value.value))) return stringValue(p.Sprintf("%v", number.Decimal(value.value)))
} }

View file

@ -10,36 +10,36 @@ func builtinObject(call FunctionCall) Value {
value := call.Argument(0) value := call.Argument(0)
switch value.kind { switch value.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
return toValue_object(call.runtime.newObject()) return objectValue(call.runtime.newObject())
} }
return toValue_object(call.runtime.toObject(value)) return objectValue(call.runtime.toObject(value))
} }
func builtinNewObject(self *_object, argumentList []Value) Value { func builtinNewObject(obj *object, argumentList []Value) Value {
value := valueOfArrayIndex(argumentList, 0) value := valueOfArrayIndex(argumentList, 0)
switch value.kind { switch value.kind {
case valueNull, valueUndefined: case valueNull, valueUndefined:
case valueNumber, valueString, valueBoolean: case valueNumber, valueString, valueBoolean:
return toValue_object(self.runtime.toObject(value)) return objectValue(obj.runtime.toObject(value))
case valueObject: case valueObject:
return value return value
default: default:
} }
return toValue_object(self.runtime.newObject()) return objectValue(obj.runtime.newObject())
} }
func builtinObject_valueOf(call FunctionCall) Value { func builtinObjectValueOf(call FunctionCall) Value {
return toValue_object(call.thisObject()) return objectValue(call.thisObject())
} }
func builtinObject_hasOwnProperty(call FunctionCall) Value { func builtinObjectHasOwnProperty(call FunctionCall) Value {
propertyName := call.Argument(0).string() propertyName := call.Argument(0).string()
thisObject := call.thisObject() thisObject := call.thisObject()
return toValue_bool(thisObject.hasOwnProperty(propertyName)) return boolValue(thisObject.hasOwnProperty(propertyName))
} }
func builtinObject_isPrototypeOf(call FunctionCall) Value { func builtinObjectIsPrototypeOf(call FunctionCall) Value {
value := call.Argument(0) value := call.Argument(0)
if !value.IsObject() { if !value.IsObject() {
return falseValue return falseValue
@ -55,235 +55,235 @@ func builtinObject_isPrototypeOf(call FunctionCall) Value {
return falseValue return falseValue
} }
func builtinObject_propertyIsEnumerable(call FunctionCall) Value { func builtinObjectPropertyIsEnumerable(call FunctionCall) Value {
propertyName := call.Argument(0).string() propertyName := call.Argument(0).string()
thisObject := call.thisObject() thisObject := call.thisObject()
property := thisObject.getOwnProperty(propertyName) prop := thisObject.getOwnProperty(propertyName)
if property != nil && property.enumerable() { if prop != nil && prop.enumerable() {
return trueValue return trueValue
} }
return falseValue return falseValue
} }
func builtinObject_toString(call FunctionCall) Value { func builtinObjectToString(call FunctionCall) Value {
var result string var result string
if call.This.IsUndefined() { switch {
case call.This.IsUndefined():
result = "[object Undefined]" result = "[object Undefined]"
} else if call.This.IsNull() { case call.This.IsNull():
result = "[object Null]" result = "[object Null]"
} else { default:
result = fmt.Sprintf("[object %s]", call.thisObject().class) result = fmt.Sprintf("[object %s]", call.thisObject().class)
} }
return toValue_string(result) return stringValue(result)
} }
func builtinObject_toLocaleString(call FunctionCall) Value { func builtinObjectToLocaleString(call FunctionCall) Value {
toString := call.thisObject().get("toString") toString := call.thisObject().get("toString")
if !toString.isCallable() { if !toString.isCallable() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.toLocaleString %q is not callable", toString))
} }
return toString.call(call.runtime, call.This) return toString.call(call.runtime, call.This)
} }
func builtinObject_getPrototypeOf(call FunctionCall) Value { func builtinObjectGetPrototypeOf(call FunctionCall) Value {
objectValue := call.Argument(0) val := call.Argument(0)
object := objectValue._object() obj := val.object()
if object == nil { if obj == nil {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.GetPrototypeOf is nil"))
} }
if object.prototype == nil { if obj.prototype == nil {
return nullValue return nullValue
} }
return toValue_object(object.prototype) return objectValue(obj.prototype)
} }
func builtinObject_getOwnPropertyDescriptor(call FunctionCall) Value { func builtinObjectGetOwnPropertyDescriptor(call FunctionCall) Value {
objectValue := call.Argument(0) val := call.Argument(0)
object := objectValue._object() obj := val.object()
if object == nil { if obj == nil {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.GetOwnPropertyDescriptor is nil"))
} }
name := call.Argument(1).string() name := call.Argument(1).string()
descriptor := object.getOwnProperty(name) descriptor := obj.getOwnProperty(name)
if descriptor == nil { if descriptor == nil {
return Value{} return Value{}
} }
return toValue_object(call.runtime.fromPropertyDescriptor(*descriptor)) return objectValue(call.runtime.fromPropertyDescriptor(*descriptor))
} }
func builtinObject_defineProperty(call FunctionCall) Value { func builtinObjectDefineProperty(call FunctionCall) Value {
objectValue := call.Argument(0) val := call.Argument(0)
object := objectValue._object() obj := val.object()
if object == nil { if obj == nil {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.DefineProperty is nil"))
} }
name := call.Argument(1).string() name := call.Argument(1).string()
descriptor := toPropertyDescriptor(call.runtime, call.Argument(2)) descriptor := toPropertyDescriptor(call.runtime, call.Argument(2))
object.defineOwnProperty(name, descriptor, true) obj.defineOwnProperty(name, descriptor, true)
return objectValue return val
} }
func builtinObject_defineProperties(call FunctionCall) Value { func builtinObjectDefineProperties(call FunctionCall) Value {
objectValue := call.Argument(0) val := call.Argument(0)
object := objectValue._object() obj := val.object()
if object == nil { if obj == nil {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.DefineProperties is nil"))
} }
properties := call.runtime.toObject(call.Argument(1)) properties := call.runtime.toObject(call.Argument(1))
properties.enumerate(false, func(name string) bool { properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name)) descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true) obj.defineOwnProperty(name, descriptor, true)
return true return true
}) })
return objectValue return val
} }
func builtinObject_create(call FunctionCall) Value { func builtinObjectCreate(call FunctionCall) Value {
prototypeValue := call.Argument(0) prototypeValue := call.Argument(0)
if !prototypeValue.IsNull() && !prototypeValue.IsObject() { if !prototypeValue.IsNull() && !prototypeValue.IsObject() {
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.Create is nil"))
} }
object := call.runtime.newObject() obj := call.runtime.newObject()
object.prototype = prototypeValue._object() obj.prototype = prototypeValue.object()
propertiesValue := call.Argument(1) propertiesValue := call.Argument(1)
if propertiesValue.IsDefined() { if propertiesValue.IsDefined() {
properties := call.runtime.toObject(propertiesValue) properties := call.runtime.toObject(propertiesValue)
properties.enumerate(false, func(name string) bool { properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name)) descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true) obj.defineOwnProperty(name, descriptor, true)
return true return true
}) })
} }
return toValue_object(object) return objectValue(obj)
} }
func builtinObject_isExtensible(call FunctionCall) Value { func builtinObjectIsExtensible(call FunctionCall) Value {
object := call.Argument(0) val := call.Argument(0)
if object := object._object(); object != nil { if obj := val.object(); obj != nil {
return toValue_bool(object.extensible) return boolValue(obj.extensible)
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.IsExtensible is nil"))
} }
func builtinObject_preventExtensions(call FunctionCall) Value { func builtinObjectPreventExtensions(call FunctionCall) Value {
object := call.Argument(0) val := call.Argument(0)
if object := object._object(); object != nil { if obj := val.object(); obj != nil {
object.extensible = false obj.extensible = false
} else { return val
panic(call.runtime.panicTypeError())
} }
return object panic(call.runtime.panicTypeError("Object.PreventExtensions is nil"))
} }
func builtinObject_isSealed(call FunctionCall) Value { func builtinObjectIsSealed(call FunctionCall) Value {
object := call.Argument(0) val := call.Argument(0)
if object := object._object(); object != nil { if obj := val.object(); obj != nil {
if object.extensible { if obj.extensible {
return toValue_bool(false) return boolValue(false)
} }
result := true result := true
object.enumerate(true, func(name string) bool { obj.enumerate(true, func(name string) bool {
property := object.getProperty(name) prop := obj.getProperty(name)
if property.configurable() { if prop.configurable() {
result = false result = false
} }
return true return true
}) })
return toValue_bool(result) return boolValue(result)
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.IsSealed is nil"))
} }
func builtinObject_seal(call FunctionCall) Value { func builtinObjectSeal(call FunctionCall) Value {
object := call.Argument(0) val := call.Argument(0)
if object := object._object(); object != nil { if obj := val.object(); obj != nil {
object.enumerate(true, func(name string) bool { obj.enumerate(true, func(name string) bool {
if property := object.getOwnProperty(name); nil != property && property.configurable() { if prop := obj.getOwnProperty(name); nil != prop && prop.configurable() {
property.configureOff() prop.configureOff()
object.defineOwnProperty(name, *property, true) obj.defineOwnProperty(name, *prop, true)
} }
return true return true
}) })
object.extensible = false obj.extensible = false
} else { return val
panic(call.runtime.panicTypeError())
} }
return object panic(call.runtime.panicTypeError("Object.Seal is nil"))
} }
func builtinObject_isFrozen(call FunctionCall) Value { func builtinObjectIsFrozen(call FunctionCall) Value {
object := call.Argument(0) val := call.Argument(0)
if object := object._object(); object != nil { if obj := val.object(); obj != nil {
if object.extensible { if obj.extensible {
return toValue_bool(false) return boolValue(false)
} }
result := true result := true
object.enumerate(true, func(name string) bool { obj.enumerate(true, func(name string) bool {
property := object.getProperty(name) prop := obj.getProperty(name)
if property.configurable() || property.writable() { if prop.configurable() || prop.writable() {
result = false result = false
} }
return true return true
}) })
return toValue_bool(result) return boolValue(result)
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.IsFrozen is nil"))
} }
func builtinObject_freeze(call FunctionCall) Value { func builtinObjectFreeze(call FunctionCall) Value {
object := call.Argument(0) val := call.Argument(0)
if object := object._object(); object != nil { if obj := val.object(); obj != nil {
object.enumerate(true, func(name string) bool { obj.enumerate(true, func(name string) bool {
if property, update := object.getOwnProperty(name), false; nil != property { if prop, update := obj.getOwnProperty(name), false; nil != prop {
if property.isDataDescriptor() && property.writable() { if prop.isDataDescriptor() && prop.writable() {
property.writeOff() prop.writeOff()
update = true update = true
} }
if property.configurable() { if prop.configurable() {
property.configureOff() prop.configureOff()
update = true update = true
} }
if update { if update {
object.defineOwnProperty(name, *property, true) obj.defineOwnProperty(name, *prop, true)
} }
} }
return true return true
}) })
object.extensible = false obj.extensible = false
} else { return val
panic(call.runtime.panicTypeError())
} }
return object panic(call.runtime.panicTypeError("Object.Freeze is nil"))
} }
func builtinObject_keys(call FunctionCall) Value { func builtinObjectKeys(call FunctionCall) Value {
if object, keys := call.Argument(0)._object(), []Value(nil); nil != object { if obj, keys := call.Argument(0).object(), []Value(nil); nil != obj {
object.enumerate(false, func(name string) bool { obj.enumerate(false, func(name string) bool {
keys = append(keys, toValue_string(name)) keys = append(keys, stringValue(name))
return true return true
}) })
return toValue_object(call.runtime.newArrayOf(keys)) return objectValue(call.runtime.newArrayOf(keys))
} }
panic(call.runtime.panicTypeError()) panic(call.runtime.panicTypeError("Object.Keys is nil"))
} }
func builtinObject_getOwnPropertyNames(call FunctionCall) Value { func builtinObjectGetOwnPropertyNames(call FunctionCall) Value {
if object, propertyNames := call.Argument(0)._object(), []Value(nil); nil != object { if obj, propertyNames := call.Argument(0).object(), []Value(nil); nil != obj {
object.enumerate(true, func(name string) bool { obj.enumerate(true, func(name string) bool {
if object.hasOwnProperty(name) { if obj.hasOwnProperty(name) {
propertyNames = append(propertyNames, toValue_string(name)) propertyNames = append(propertyNames, stringValue(name))
} }
return true return true
}) })
return toValue_object(call.runtime.newArrayOf(propertyNames)) return objectValue(call.runtime.newArrayOf(propertyNames))
} }
panic(call.runtime.panicTypeError())
// Default to empty array for non object types.
return objectValue(call.runtime.newArray(0))
} }

View file

@ -9,22 +9,22 @@ import (
func builtinRegExp(call FunctionCall) Value { func builtinRegExp(call FunctionCall) Value {
pattern := call.Argument(0) pattern := call.Argument(0)
flags := call.Argument(1) flags := call.Argument(1)
if object := pattern._object(); object != nil { if obj := pattern.object(); obj != nil {
if object.class == classRegExp && flags.IsUndefined() { if obj.class == classRegExpName && flags.IsUndefined() {
return pattern return pattern
} }
} }
return toValue_object(call.runtime.newRegExp(pattern, flags)) return objectValue(call.runtime.newRegExp(pattern, flags))
} }
func builtinNewRegExp(self *_object, argumentList []Value) Value { func builtinNewRegExp(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newRegExp( return objectValue(obj.runtime.newRegExp(
valueOfArrayIndex(argumentList, 0), valueOfArrayIndex(argumentList, 0),
valueOfArrayIndex(argumentList, 1), valueOfArrayIndex(argumentList, 1),
)) ))
} }
func builtinRegExp_toString(call FunctionCall) Value { func builtinRegExpToString(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
source := thisObject.get("source").string() source := thisObject.get("source").string()
flags := []byte{} flags := []byte{}
@ -37,41 +37,47 @@ func builtinRegExp_toString(call FunctionCall) Value {
if thisObject.get("multiline").bool() { if thisObject.get("multiline").bool() {
flags = append(flags, 'm') flags = append(flags, 'm')
} }
return toValue_string(fmt.Sprintf("/%s/%s", source, flags)) return stringValue(fmt.Sprintf("/%s/%s", source, flags))
} }
func builtinRegExp_exec(call FunctionCall) Value { func builtinRegExpExec(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
target := call.Argument(0).string() target := call.Argument(0).string()
match, result := execRegExp(thisObject, target) match, result := execRegExp(thisObject, target)
if !match { if !match {
return nullValue return nullValue
} }
return toValue_object(execResultToArray(call.runtime, target, result)) return objectValue(execResultToArray(call.runtime, target, result))
} }
func builtinRegExp_test(call FunctionCall) Value { func builtinRegExpTest(call FunctionCall) Value {
thisObject := call.thisObject() thisObject := call.thisObject()
target := call.Argument(0).string() target := call.Argument(0).string()
match, result := execRegExp(thisObject, target) match, result := execRegExp(thisObject, target)
if !match { if !match {
return toValue_bool(match) return boolValue(match)
} }
// Match extract and assign input, $_ and $1 -> $9 on global RegExp. // Match extract and assign input, $_ and $1 -> $9 on global RegExp.
input := toValue_string(target) input := stringValue(target)
call.runtime.global.RegExp.defineProperty("$_", input, 0100, false) call.runtime.global.RegExp.defineProperty("$_", input, 0o100, false)
call.runtime.global.RegExp.defineProperty("input", input, 0100, false) call.runtime.global.RegExp.defineProperty("input", input, 0o100, false)
var start int var start int
n := 1 n := 1
re := call.runtime.global.RegExp re := call.runtime.global.RegExp
empty := stringValue("")
for i, v := range result[2:] { for i, v := range result[2:] {
if i%2 == 0 { if i%2 == 0 {
start = v start = v
} else { } else {
re.defineProperty(fmt.Sprintf("$%d", n), toValue_string(target[start:v]), 0100, false) if v == -1 {
// No match for this part.
re.defineProperty(fmt.Sprintf("$%d", n), empty, 0o100, false)
} else {
re.defineProperty(fmt.Sprintf("$%d", n), stringValue(target[start:v]), 0o100, false)
}
n++ n++
if n == 10 { if n == 10 {
break break
@ -81,16 +87,15 @@ func builtinRegExp_test(call FunctionCall) Value {
if n <= 9 { if n <= 9 {
// Erase remaining. // Erase remaining.
empty := toValue_string("")
for i := n; i <= 9; i++ { for i := n; i <= 9; i++ {
re.defineProperty(fmt.Sprintf("$%d", i), empty, 0100, false) re.defineProperty(fmt.Sprintf("$%d", i), empty, 0o100, false)
} }
} }
return toValue_bool(match) return boolValue(match)
} }
func builtinRegExp_compile(call FunctionCall) Value { func builtinRegExpCompile(call FunctionCall) Value {
// This (useless) function is deprecated, but is here to provide some // This (useless) function is deprecated, but is here to provide some
// semblance of compatibility. // semblance of compatibility.
// Caveat emptor: it may not be around for long. // Caveat emptor: it may not be around for long.

View file

@ -13,62 +13,63 @@ import (
func stringValueFromStringArgumentList(argumentList []Value) Value { func stringValueFromStringArgumentList(argumentList []Value) Value {
if len(argumentList) > 0 { if len(argumentList) > 0 {
return toValue_string(argumentList[0].string()) return stringValue(argumentList[0].string())
} }
return toValue_string("") return stringValue("")
} }
func builtinString(call FunctionCall) Value { func builtinString(call FunctionCall) Value {
return stringValueFromStringArgumentList(call.ArgumentList) return stringValueFromStringArgumentList(call.ArgumentList)
} }
func builtinNewString(self *_object, argumentList []Value) Value { func builtinNewString(obj *object, argumentList []Value) Value {
return toValue_object(self.runtime.newString(stringValueFromStringArgumentList(argumentList))) return objectValue(obj.runtime.newString(stringValueFromStringArgumentList(argumentList)))
} }
func builtinString_toString(call FunctionCall) Value { func builtinStringToString(call FunctionCall) Value {
return call.thisClassObject(classString).primitiveValue() return call.thisClassObject(classStringName).primitiveValue()
}
func builtinString_valueOf(call FunctionCall) Value {
return call.thisClassObject(classString).primitiveValue()
} }
func builtinString_fromCharCode(call FunctionCall) Value { func builtinStringValueOf(call FunctionCall) Value {
return call.thisClassObject(classStringName).primitiveValue()
}
func builtinStringFromCharCode(call FunctionCall) Value {
chrList := make([]uint16, len(call.ArgumentList)) chrList := make([]uint16, len(call.ArgumentList))
for index, value := range call.ArgumentList { for index, value := range call.ArgumentList {
chrList[index] = toUint16(value) chrList[index] = toUint16(value)
} }
return toValue_string16(chrList) return string16Value(chrList)
} }
func builtinString_charAt(call FunctionCall) Value { func builtinStringCharAt(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
idx := int(call.Argument(0).number().int64) idx := int(call.Argument(0).number().int64)
chr := stringAt(call.This._object().stringValue(), idx) chr := stringAt(call.This.object().stringValue(), idx)
if chr == utf8.RuneError { if chr == utf8.RuneError {
return toValue_string("") return stringValue("")
} }
return toValue_string(string(chr)) return stringValue(string(chr))
} }
func builtinString_charCodeAt(call FunctionCall) Value { func builtinStringCharCodeAt(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
idx := int(call.Argument(0).number().int64) idx := int(call.Argument(0).number().int64)
chr := stringAt(call.This._object().stringValue(), idx) chr := stringAt(call.This.object().stringValue(), idx)
if chr == utf8.RuneError { if chr == utf8.RuneError {
return NaNValue() return NaNValue()
} }
return toValue_uint16(uint16(chr)) return uint16Value(uint16(chr))
} }
func builtinString_concat(call FunctionCall) Value { func builtinStringConcat(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
var value bytes.Buffer var value bytes.Buffer
value.WriteString(call.This.string()) value.WriteString(call.This.string())
for _, item := range call.ArgumentList { for _, item := range call.ArgumentList {
value.WriteString(item.string()) value.WriteString(item.string())
} }
return toValue_string(value.String()) return stringValue(value.String())
} }
func lastIndexRune(s, substr string) int { func lastIndexRune(s, substr string) int {
@ -89,44 +90,44 @@ func utf16Length(s string) int {
return len(utf16.Encode([]rune(s))) return len(utf16.Encode([]rune(s)))
} }
func builtinString_indexOf(call FunctionCall) Value { func builtinStringIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
value := call.This.string() value := call.This.string()
target := call.Argument(0).string() target := call.Argument(0).string()
if 2 > len(call.ArgumentList) { if 2 > len(call.ArgumentList) {
return toValue_int(indexRune(value, target)) return intValue(indexRune(value, target))
} }
start := toIntegerFloat(call.Argument(1)) start := toIntegerFloat(call.Argument(1))
if 0 > start { if 0 > start {
start = 0 start = 0
} else if start >= float64(len(value)) { } else if start >= float64(len(value)) {
if target == "" { if target == "" {
return toValue_int(len(value)) return intValue(len(value))
} }
return toValue_int(-1) return intValue(-1)
} }
index := indexRune(value[int(start):], target) index := indexRune(value[int(start):], target)
if index >= 0 { if index >= 0 {
index += int(start) index += int(start)
} }
return toValue_int(index) return intValue(index)
} }
func builtinString_lastIndexOf(call FunctionCall) Value { func builtinStringLastIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
value := call.This.string() value := call.This.string()
target := call.Argument(0).string() target := call.Argument(0).string()
if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() { if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
return toValue_int(lastIndexRune(value, target)) return intValue(lastIndexRune(value, target))
} }
length := len(value) length := len(value)
if length == 0 { if length == 0 {
return toValue_int(lastIndexRune(value, target)) return intValue(lastIndexRune(value, target))
} }
start := call.ArgumentList[1].number() start := call.ArgumentList[1].number()
if start.kind == numberInfinity { // FIXME if start.kind == numberInfinity { // FIXME
// startNumber is infinity, so start is the end of string (start = length) // startNumber is infinity, so start is the end of string (start = length)
return toValue_int(lastIndexRune(value, target)) return intValue(lastIndexRune(value, target))
} }
if 0 > start.int64 { if 0 > start.int64 {
start.int64 = 0 start.int64 = 0
@ -135,15 +136,15 @@ func builtinString_lastIndexOf(call FunctionCall) Value {
if end > length { if end > length {
end = length end = length
} }
return toValue_int(lastIndexRune(value[:end], target)) return intValue(lastIndexRune(value[:end], target))
} }
func builtinString_match(call FunctionCall) Value { func builtinStringMatch(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
target := call.This.string() target := call.This.string()
matcherValue := call.Argument(0) matcherValue := call.Argument(0)
matcher := matcherValue._object() matcher := matcherValue.object()
if !matcherValue.IsObject() || matcher.class != classRegExp { if !matcherValue.IsObject() || matcher.class != classRegExpName {
matcher = call.runtime.newRegExp(matcherValue, Value{}) matcher = call.runtime.newRegExp(matcherValue, Value{})
} }
global := matcher.get("global").bool() global := matcher.get("global").bool()
@ -152,34 +153,32 @@ func builtinString_match(call FunctionCall) Value {
if !match { if !match {
return nullValue return nullValue
} }
return toValue_object(execResultToArray(call.runtime, target, result)) return objectValue(execResultToArray(call.runtime, target, result))
} }
{
result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1) result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
if result == nil { if result == nil {
matcher.put("lastIndex", toValue_int(0), true) matcher.put("lastIndex", intValue(0), true)
return Value{} // !match return Value{} // !match
} }
matchCount := len(result) matchCount := len(result)
valueArray := make([]Value, matchCount) valueArray := make([]Value, matchCount)
for index := 0; index < matchCount; index++ { for index := 0; index < matchCount; index++ {
valueArray[index] = toValue_string(target[result[index][0]:result[index][1]]) valueArray[index] = stringValue(target[result[index][0]:result[index][1]])
}
matcher.put("lastIndex", toValue_int(result[matchCount-1][1]), true)
return toValue_object(call.runtime.newArrayOf(valueArray))
} }
matcher.put("lastIndex", intValue(result[matchCount-1][1]), true)
return objectValue(call.runtime.newArrayOf(valueArray))
} }
var builtinString_replace_Regexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])") var builtinStringReplaceRegexp = regexp.MustCompile("\\$(?:[\\$\\&\\'\\`1-9]|0[1-9]|[1-9][0-9])")
func builtinString_findAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) (output []byte) { func builtinStringFindAndReplaceString(input []byte, lastIndex int, match []int, target []byte, replaceValue []byte) []byte {
matchCount := len(match) / 2 matchCount := len(match) / 2
output = input output := input
if match[0] != lastIndex { if match[0] != lastIndex {
output = append(output, target[lastIndex:match[0]]...) output = append(output, target[lastIndex:match[0]]...)
} }
replacement := builtinString_replace_Regexp.ReplaceAllFunc(replaceValue, func(part []byte) []byte { replacement := builtinStringReplaceRegexp.ReplaceAllFunc(replaceValue, func(part []byte) []byte {
// TODO Check if match[0] or match[1] can be -1 in this scenario // TODO Check if match[0] or match[1] can be -1 in this scenario
switch part[1] { switch part[1] {
case '$': case '$':
@ -193,37 +192,38 @@ func builtinString_findAndReplaceString(input []byte, lastIndex int, match []int
} }
matchNumberParse, err := strconv.ParseInt(string(part[1:]), 10, 64) matchNumberParse, err := strconv.ParseInt(string(part[1:]), 10, 64)
if err != nil { if err != nil {
return []byte{} return nil
} }
matchNumber := int(matchNumberParse) matchNumber := int(matchNumberParse)
if matchNumber >= matchCount { if matchNumber >= matchCount {
return []byte{} return nil
} }
offset := 2 * matchNumber offset := 2 * matchNumber
if match[offset] != -1 { if match[offset] != -1 {
return target[match[offset]:match[offset+1]] return target[match[offset]:match[offset+1]]
} }
return []byte{} // The empty string return nil // The empty string
}) })
output = append(output, replacement...)
return output return append(output, replacement...)
} }
func builtinString_replace(call FunctionCall) Value { func builtinStringReplace(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
target := []byte(call.This.string()) target := []byte(call.This.string())
searchValue := call.Argument(0) searchValue := call.Argument(0)
searchObject := searchValue._object() searchObject := searchValue.object()
// TODO If a capture is -1? // TODO If a capture is -1?
var search *regexp.Regexp var search *regexp.Regexp
global := false global := false
find := 1 find := 1
if searchValue.IsObject() && searchObject.class == classRegExp { if searchValue.IsObject() && searchObject.class == classRegExpName {
regExp := searchObject.regExpValue() regExp := searchObject.regExpValue()
search = regExp.regularExpression search = regExp.regularExpression
if regExp.global { if regExp.global {
find = -1 find = -1
global = true
} }
} else { } else {
search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string())) search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
@ -231,17 +231,15 @@ func builtinString_replace(call FunctionCall) Value {
found := search.FindAllSubmatchIndex(target, find) found := search.FindAllSubmatchIndex(target, find)
if found == nil { if found == nil {
return toValue_string(string(target)) // !match return stringValue(string(target)) // !match
} }
{
lastIndex := 0 lastIndex := 0
result := []byte{} result := []byte{}
replaceValue := call.Argument(1) replaceValue := call.Argument(1)
if replaceValue.isCallable() { if replaceValue.isCallable() {
target := string(target) target := string(target)
replace := replaceValue._object() replace := replaceValue.object()
for _, match := range found { for _, match := range found {
if match[0] != lastIndex { if match[0] != lastIndex {
result = append(result, target[lastIndex:match[0]]...) result = append(result, target[lastIndex:match[0]]...)
@ -251,13 +249,13 @@ func builtinString_replace(call FunctionCall) Value {
for index := 0; index < matchCount; index++ { for index := 0; index < matchCount; index++ {
offset := 2 * index offset := 2 * index
if match[offset] != -1 { if match[offset] != -1 {
argumentList[index] = toValue_string(target[match[offset]:match[offset+1]]) argumentList[index] = stringValue(target[match[offset]:match[offset+1]])
} else { } else {
argumentList[index] = Value{} argumentList[index] = Value{}
} }
} }
argumentList[matchCount+0] = toValue_int(match[0]) argumentList[matchCount+0] = intValue(match[0])
argumentList[matchCount+1] = toValue_string(target) argumentList[matchCount+1] = stringValue(target)
replacement := replace.call(Value{}, argumentList, false, nativeFrame).string() replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
result = append(result, []byte(replacement)...) result = append(result, []byte(replacement)...)
lastIndex = match[1] lastIndex = match[1]
@ -265,7 +263,7 @@ func builtinString_replace(call FunctionCall) Value {
} else { } else {
replace := []byte(replaceValue.string()) replace := []byte(replaceValue.string())
for _, match := range found { for _, match := range found {
result = builtinString_findAndReplaceString(result, lastIndex, match, target, replace) result = builtinStringFindAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1] lastIndex = match[1]
} }
} }
@ -275,29 +273,28 @@ func builtinString_replace(call FunctionCall) Value {
} }
if global && searchObject != nil { if global && searchObject != nil {
searchObject.put("lastIndex", toValue_int(lastIndex), true) searchObject.put("lastIndex", intValue(lastIndex), true)
} }
return toValue_string(string(result)) return stringValue(string(result))
}
} }
func builtinString_search(call FunctionCall) Value { func builtinStringSearch(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
target := call.This.string() target := call.This.string()
searchValue := call.Argument(0) searchValue := call.Argument(0)
search := searchValue._object() search := searchValue.object()
if !searchValue.IsObject() || search.class != classRegExp { if !searchValue.IsObject() || search.class != classRegExpName {
search = call.runtime.newRegExp(searchValue, Value{}) search = call.runtime.newRegExp(searchValue, Value{})
} }
result := search.regExpValue().regularExpression.FindStringIndex(target) result := search.regExpValue().regularExpression.FindStringIndex(target)
if result == nil { if result == nil {
return toValue_int(-1) return intValue(-1)
} }
return toValue_int(result[0]) return intValue(result[0])
} }
func builtinString_split(call FunctionCall) Value { func builtinStringSplit(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
target := call.This.string() target := call.This.string()
@ -309,16 +306,16 @@ func builtinString_split(call FunctionCall) Value {
} }
if limit == 0 { if limit == 0 {
return toValue_object(call.runtime.newArray(0)) return objectValue(call.runtime.newArray(0))
} }
if separatorValue.IsUndefined() { if separatorValue.IsUndefined() {
return toValue_object(call.runtime.newArrayOf([]Value{toValue_string(target)})) return objectValue(call.runtime.newArrayOf([]Value{stringValue(target)}))
} }
if separatorValue.isRegExp() { if separatorValue.isRegExp() {
targetLength := len(target) targetLength := len(target)
search := separatorValue._object().regExpValue().regularExpression search := separatorValue.object().regExpValue().regularExpression
valueArray := []Value{} valueArray := []Value{}
result := search.FindAllStringSubmatchIndex(target, -1) result := search.FindAllStringSubmatchIndex(target, -1)
lastIndex := 0 lastIndex := 0
@ -333,11 +330,11 @@ func builtinString_split(call FunctionCall) Value {
} }
if lastIndex != match[0] { if lastIndex != match[0] {
valueArray = append(valueArray, toValue_string(target[lastIndex:match[0]])) valueArray = append(valueArray, stringValue(target[lastIndex:match[0]]))
found++ found++
} else if lastIndex == match[0] { } else if lastIndex == match[0] {
if lastIndex != -1 { if lastIndex != -1 {
valueArray = append(valueArray, toValue_string("")) valueArray = append(valueArray, stringValue(""))
found++ found++
} }
} }
@ -352,7 +349,7 @@ func builtinString_split(call FunctionCall) Value {
offset := index * 2 offset := index * 2
value := Value{} value := Value{}
if match[offset] != -1 { if match[offset] != -1 {
value = toValue_string(target[match[offset]:match[offset+1]]) value = stringValue(target[match[offset]:match[offset+1]])
} }
valueArray = append(valueArray, value) valueArray = append(valueArray, value)
found++ found++
@ -364,14 +361,14 @@ func builtinString_split(call FunctionCall) Value {
if found != limit { if found != limit {
if lastIndex != targetLength { if lastIndex != targetLength {
valueArray = append(valueArray, toValue_string(target[lastIndex:targetLength])) valueArray = append(valueArray, stringValue(target[lastIndex:targetLength]))
} else { } else {
valueArray = append(valueArray, toValue_string("")) valueArray = append(valueArray, stringValue(""))
} }
} }
RETURN: RETURN:
return toValue_object(call.runtime.newArrayOf(valueArray)) return objectValue(call.runtime.newArrayOf(valueArray))
} else { } else {
separator := separatorValue.string() separator := separatorValue.string()
@ -390,26 +387,26 @@ func builtinString_split(call FunctionCall) Value {
valueArray := make([]Value, len(split)) valueArray := make([]Value, len(split))
for index, value := range split { for index, value := range split {
valueArray[index] = toValue_string(value) valueArray[index] = stringValue(value)
} }
return toValue_object(call.runtime.newArrayOf(valueArray)) return objectValue(call.runtime.newArrayOf(valueArray))
} }
} }
func builtinString_slice(call FunctionCall) Value { func builtinStringSlice(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
target := call.This.string() target := call.This.string()
length := int64(len(target)) length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, false) start, end := rangeStartEnd(call.ArgumentList, length, false)
if end-start <= 0 { if end-start <= 0 {
return toValue_string("") return stringValue("")
} }
return toValue_string(string(target[start:end])) return stringValue(target[start:end])
} }
func builtinString_substring(call FunctionCall) Value { func builtinStringSubstring(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
target := []rune(call.This.string()) target := []rune(call.This.string())
@ -418,21 +415,21 @@ func builtinString_substring(call FunctionCall) Value {
if start > end { if start > end {
start, end = end, start start, end = end, start
} }
return toValue_string(string(target[start:end])) return stringValue(string(target[start:end]))
} }
func builtinString_substr(call FunctionCall) Value { func builtinStringSubstr(call FunctionCall) Value {
target := []rune(call.This.string()) target := []rune(call.This.string())
size := int64(len(target)) size := int64(len(target))
start, length := rangeStartLength(call.ArgumentList, size) start, length := rangeStartLength(call.ArgumentList, size)
if start >= size { if start >= size {
return toValue_string("") return stringValue("")
} }
if length <= 0 { if length <= 0 {
return toValue_string("") return stringValue("")
} }
if start+length >= size { if start+length >= size {
@ -443,66 +440,69 @@ func builtinString_substr(call FunctionCall) Value {
length = size - start length = size - start
} }
return toValue_string(string(target[start : start+length])) return stringValue(string(target[start : start+length]))
} }
func builtinString_toLowerCase(call FunctionCall) Value { func builtinStringStartsWith(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
return toValue_string(strings.ToLower(call.This.string())) target := call.This.string()
search := call.Argument(0).string()
length := len(search)
if length > len(target) {
return boolValue(false)
}
return boolValue(target[:length] == search)
} }
func builtinString_toUpperCase(call FunctionCall) Value { func builtinStringToLowerCase(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
return toValue_string(strings.ToUpper(call.This.string())) return stringValue(strings.ToLower(call.This.string()))
} }
// 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters func builtinStringToUpperCase(call FunctionCall) Value {
const builtinString_trim_whitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF" checkObjectCoercible(call.runtime, call.This)
return stringValue(strings.ToUpper(call.This.string()))
}
func builtinString_trim(call FunctionCall) Value { // 7.2 Table 2 — Whitespace Characters & 7.3 Table 3 - Line Terminator Characters.
const builtinStringTrimWhitespace = "\u0009\u000A\u000B\u000C\u000D\u0020\u00A0\u1680\u180E\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200A\u2028\u2029\u202F\u205F\u3000\uFEFF"
func builtinStringTrim(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
return toValue(strings.Trim(call.This.string(), return toValue(strings.Trim(call.This.string(),
builtinString_trim_whitespace)) builtinStringTrimWhitespace))
} }
// Mozilla extension, not ECMAScript 5 // Mozilla extension, not ECMAScript 5.
func builtinString_trimLeft(call FunctionCall) Value { func builtinStringTrimLeft(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimLeft(call.This.string(), return toValue(strings.TrimLeft(call.This.string(),
builtinString_trim_whitespace)) builtinStringTrimWhitespace))
} }
// Mozilla extension, not ECMAScript 5 // Mozilla extension, not ECMAScript 5.
func builtinString_trimRight(call FunctionCall) Value { func builtinStringTrimRight(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimRight(call.This.string(), return toValue(strings.TrimRight(call.This.string(),
builtinString_trim_whitespace)) builtinStringTrimWhitespace))
} }
func builtinString_localeCompare(call FunctionCall) Value { func builtinStringLocaleCompare(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This) checkObjectCoercible(call.runtime, call.This)
this := call.This.string() this := call.This.string() //nolint: ifshort
that := call.Argument(0).string() that := call.Argument(0).string()
if this < that { if this < that {
return toValue_int(-1) return intValue(-1)
} else if this == that { } else if this == that {
return toValue_int(0) return intValue(0)
} }
return toValue_int(1) return intValue(1)
} }
/* func builtinStringToLocaleLowerCase(call FunctionCall) Value {
An alternate version of String.trim return builtinStringToLowerCase(call)
func builtinString_trim(call FunctionCall) Value {
checkObjectCoercible(call.This)
return toValue_string(strings.TrimFunc(call.string(.This), isWhiteSpaceOrLineTerminator))
}
*/
func builtinString_toLocaleLowerCase(call FunctionCall) Value {
return builtinString_toLowerCase(call)
} }
func builtinString_toLocaleUpperCase(call FunctionCall) Value { func builtinStringToLocaleUpperCase(call FunctionCall) Value {
return builtinString_toUpperCase(call) return builtinStringToUpperCase(call)
} }

View file

@ -4,158 +4,157 @@ import (
"fmt" "fmt"
) )
type _clone struct { type cloner struct {
runtime *_runtime runtime *runtime
_object map[*_object]*_object obj map[*object]*object
_objectStash map[*_objectStash]*_objectStash objectstash map[*objectStash]*objectStash
_dclStash map[*_dclStash]*_dclStash dclstash map[*dclStash]*dclStash
_fnStash map[*_fnStash]*_fnStash fnstash map[*fnStash]*fnStash
} }
func (in *_runtime) clone() *_runtime { func (rt *runtime) clone() *runtime {
in.lck.Lock() rt.lck.Lock()
defer in.lck.Unlock() defer rt.lck.Unlock()
out := &_runtime{ out := &runtime{
debugger: in.debugger, debugger: rt.debugger,
random: in.random, random: rt.random,
stackLimit: in.stackLimit, stackLimit: rt.stackLimit,
traceLimit: in.traceLimit, traceLimit: rt.traceLimit,
} }
clone := _clone{ c := cloner{
runtime: out, runtime: out,
_object: make(map[*_object]*_object), obj: make(map[*object]*object),
_objectStash: make(map[*_objectStash]*_objectStash), objectstash: make(map[*objectStash]*objectStash),
_dclStash: make(map[*_dclStash]*_dclStash), dclstash: make(map[*dclStash]*dclStash),
_fnStash: make(map[*_fnStash]*_fnStash), fnstash: make(map[*fnStash]*fnStash),
} }
globalObject := clone.object(in.globalObject) globalObject := c.object(rt.globalObject)
out.globalStash = out.newObjectStash(globalObject, nil) out.globalStash = out.newObjectStash(globalObject, nil)
out.globalObject = globalObject out.globalObject = globalObject
out.global = _global{ out.global = global{
clone.object(in.global.Object), c.object(rt.global.Object),
clone.object(in.global.Function), c.object(rt.global.Function),
clone.object(in.global.Array), c.object(rt.global.Array),
clone.object(in.global.String), c.object(rt.global.String),
clone.object(in.global.Boolean), c.object(rt.global.Boolean),
clone.object(in.global.Number), c.object(rt.global.Number),
clone.object(in.global.Math), c.object(rt.global.Math),
clone.object(in.global.Date), c.object(rt.global.Date),
clone.object(in.global.RegExp), c.object(rt.global.RegExp),
clone.object(in.global.Error), c.object(rt.global.Error),
clone.object(in.global.EvalError), c.object(rt.global.EvalError),
clone.object(in.global.TypeError), c.object(rt.global.TypeError),
clone.object(in.global.RangeError), c.object(rt.global.RangeError),
clone.object(in.global.ReferenceError), c.object(rt.global.ReferenceError),
clone.object(in.global.SyntaxError), c.object(rt.global.SyntaxError),
clone.object(in.global.URIError), c.object(rt.global.URIError),
clone.object(in.global.JSON), c.object(rt.global.JSON),
clone.object(in.global.ObjectPrototype), c.object(rt.global.ObjectPrototype),
clone.object(in.global.FunctionPrototype), c.object(rt.global.FunctionPrototype),
clone.object(in.global.ArrayPrototype), c.object(rt.global.ArrayPrototype),
clone.object(in.global.StringPrototype), c.object(rt.global.StringPrototype),
clone.object(in.global.BooleanPrototype), c.object(rt.global.BooleanPrototype),
clone.object(in.global.NumberPrototype), c.object(rt.global.NumberPrototype),
clone.object(in.global.DatePrototype), c.object(rt.global.DatePrototype),
clone.object(in.global.RegExpPrototype), c.object(rt.global.RegExpPrototype),
clone.object(in.global.ErrorPrototype), c.object(rt.global.ErrorPrototype),
clone.object(in.global.EvalErrorPrototype), c.object(rt.global.EvalErrorPrototype),
clone.object(in.global.TypeErrorPrototype), c.object(rt.global.TypeErrorPrototype),
clone.object(in.global.RangeErrorPrototype), c.object(rt.global.RangeErrorPrototype),
clone.object(in.global.ReferenceErrorPrototype), c.object(rt.global.ReferenceErrorPrototype),
clone.object(in.global.SyntaxErrorPrototype), c.object(rt.global.SyntaxErrorPrototype),
clone.object(in.global.URIErrorPrototype), c.object(rt.global.URIErrorPrototype),
} }
out.eval = out.globalObject.property["eval"].value.(Value).value.(*_object) out.eval = out.globalObject.property["eval"].value.(Value).value.(*object)
out.globalObject.prototype = out.global.ObjectPrototype out.globalObject.prototype = out.global.ObjectPrototype
// Not sure if this is necessary, but give some help to the GC // Not sure if this is necessary, but give some help to the GC
clone.runtime = nil c.runtime = nil
clone._object = nil c.obj = nil
clone._objectStash = nil c.objectstash = nil
clone._dclStash = nil c.dclstash = nil
clone._fnStash = nil c.fnstash = nil
return out return out
} }
func (clone *_clone) object(in *_object) *_object { func (c *cloner) object(in *object) *object {
if out, exists := clone._object[in]; exists { if out, exists := c.obj[in]; exists {
return out return out
} }
out := &_object{} out := &object{}
clone._object[in] = out c.obj[in] = out
return in.objectClass.clone(in, out, clone) return in.objectClass.clone(in, out, c)
} }
func (clone *_clone) dclStash(in *_dclStash) (*_dclStash, bool) { func (c *cloner) dclStash(in *dclStash) (*dclStash, bool) {
if out, exists := clone._dclStash[in]; exists { if out, exists := c.dclstash[in]; exists {
return out, true return out, true
} }
out := &_dclStash{} out := &dclStash{}
clone._dclStash[in] = out c.dclstash[in] = out
return out, false return out, false
} }
func (clone *_clone) objectStash(in *_objectStash) (*_objectStash, bool) { func (c *cloner) objectStash(in *objectStash) (*objectStash, bool) {
if out, exists := clone._objectStash[in]; exists { if out, exists := c.objectstash[in]; exists {
return out, true return out, true
} }
out := &_objectStash{} out := &objectStash{}
clone._objectStash[in] = out c.objectstash[in] = out
return out, false return out, false
} }
func (clone *_clone) fnStash(in *_fnStash) (*_fnStash, bool) { func (c *cloner) fnStash(in *fnStash) (*fnStash, bool) {
if out, exists := clone._fnStash[in]; exists { if out, exists := c.fnstash[in]; exists {
return out, true return out, true
} }
out := &_fnStash{} out := &fnStash{}
clone._fnStash[in] = out c.fnstash[in] = out
return out, false return out, false
} }
func (clone *_clone) value(in Value) Value { func (c *cloner) value(in Value) Value {
out := in out := in
switch value := in.value.(type) { if value, ok := in.value.(*object); ok {
case *_object: out.value = c.object(value)
out.value = clone.object(value)
} }
return out return out
} }
func (clone *_clone) valueArray(in []Value) []Value { func (c *cloner) valueArray(in []Value) []Value {
out := make([]Value, len(in)) out := make([]Value, len(in))
for index, value := range in { for index, value := range in {
out[index] = clone.value(value) out[index] = c.value(value)
} }
return out return out
} }
func (clone *_clone) stash(in _stash) _stash { func (c *cloner) stash(in stasher) stasher {
if in == nil { if in == nil {
return nil return nil
} }
return in.clone(clone) return in.clone(c)
} }
func (clone *_clone) property(in _property) _property { func (c *cloner) property(in property) property {
out := in out := in
switch value := in.value.(type) { switch value := in.value.(type) {
case Value: case Value:
out.value = clone.value(value) out.value = c.value(value)
case _propertyGetSet: case propertyGetSet:
p := _propertyGetSet{} p := propertyGetSet{}
if value[0] != nil { if value[0] != nil {
p[0] = clone.object(value[0]) p[0] = c.object(value[0])
} }
if value[1] != nil { if value[1] != nil {
p[1] = clone.object(value[1]) p[1] = c.object(value[1])
} }
out.value = p out.value = p
default: default:
@ -165,8 +164,8 @@ func (clone *_clone) property(in _property) _property {
return out return out
} }
func (clone *_clone) dclProperty(in _dclProperty) _dclProperty { func (c *cloner) dclProperty(in dclProperty) dclProperty {
out := in out := in
out.value = clone.value(in.value) out.value = c.value(in.value)
return out return out
} }

View file

@ -5,14 +5,7 @@ import (
"github.com/robertkrimen/otto/file" "github.com/robertkrimen/otto/file"
) )
type _compiler struct { type compiler struct {
file *file.File file *file.File
program *ast.Program program *ast.Program
} }
func (cmpl *_compiler) parse() *_nodeProgram {
if cmpl.program != nil {
cmpl.file = cmpl.program.File
}
return cmpl._parse(cmpl.program)
}

View file

@ -4,20 +4,18 @@ import (
"strconv" "strconv"
) )
func (self *_runtime) cmpl_evaluate_nodeProgram(node *_nodeProgram, eval bool) Value { func (rt *runtime) cmplEvaluateNodeProgram(node *nodeProgram, eval bool) Value {
if !eval { if !eval {
self.enterGlobalScope() rt.enterGlobalScope()
defer func() { defer rt.leaveScope()
self.leaveScope()
}()
} }
self.cmpl_functionDeclaration(node.functionList) rt.cmplFunctionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList) rt.cmplVariableDeclaration(node.varList)
self.scope.frame.file = node.file rt.scope.frame.file = node.file
return self.cmpl_evaluate_nodeStatementList(node.body) return rt.cmplEvaluateNodeStatementList(node.body)
} }
func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash, node *_nodeFunctionLiteral, this Value, argumentList []Value) Value { func (rt *runtime) cmplCallNodeFunction(function *object, stash *fnStash, node *nodeFunctionLiteral, argumentList []Value) Value {
indexOfParameterName := make([]string, len(argumentList)) indexOfParameterName := make([]string, len(argumentList))
// function(abc, def, ghi) // function(abc, def, ghi)
// indexOfParameterName[0] = "abc" // indexOfParameterName[0] = "abc"
@ -36,28 +34,28 @@ func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash,
indexOfParameterName[index] = name indexOfParameterName[index] = name
} }
// strict = false // strict = false
self.scope.lexical.setValue(name, value, false) rt.scope.lexical.setValue(name, value, false)
} }
if !argumentsFound { if !argumentsFound {
arguments := self.newArgumentsObject(indexOfParameterName, stash, len(argumentList)) arguments := rt.newArgumentsObject(indexOfParameterName, stash, len(argumentList))
arguments.defineProperty("callee", toValue_object(function), 0101, false) arguments.defineProperty("callee", objectValue(function), 0o101, false)
stash.arguments = arguments stash.arguments = arguments
// strict = false // strict = false
self.scope.lexical.setValue("arguments", toValue_object(arguments), false) rt.scope.lexical.setValue("arguments", objectValue(arguments), false)
for index, _ := range argumentList { for index := range argumentList {
if index < len(node.parameterList) { if index < len(node.parameterList) {
continue continue
} }
indexAsString := strconv.FormatInt(int64(index), 10) indexAsString := strconv.FormatInt(int64(index), 10)
arguments.defineProperty(indexAsString, argumentList[index], 0111, false) arguments.defineProperty(indexAsString, argumentList[index], 0o111, false)
} }
} }
self.cmpl_functionDeclaration(node.functionList) rt.cmplFunctionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList) rt.cmplVariableDeclaration(node.varList)
result := self.cmpl_evaluate_nodeStatement(node.body) result := rt.cmplEvaluateNodeStatement(node.body)
if result.kind == valueResult { if result.kind == valueResult {
return result return result
} }
@ -65,16 +63,16 @@ func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash,
return Value{} return Value{}
} }
func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) { func (rt *runtime) cmplFunctionDeclaration(list []*nodeFunctionLiteral) {
executionContext := self.scope executionContext := rt.scope
eval := executionContext.eval eval := executionContext.eval
stash := executionContext.variable stash := executionContext.variable
for _, function := range list { for _, function := range list {
name := function.name name := function.name
value := self.cmpl_evaluate_nodeExpression(function) value := rt.cmplEvaluateNodeExpression(function)
if !stash.hasBinding(name) { if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, value) stash.createBinding(name, eval, value)
} else { } else {
// TODO 10.5.5.e // TODO 10.5.5.e
stash.setBinding(name, value, false) // TODO strict stash.setBinding(name, value, false) // TODO strict
@ -82,14 +80,14 @@ func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) {
} }
} }
func (self *_runtime) cmpl_variableDeclaration(list []string) { func (rt *runtime) cmplVariableDeclaration(list []string) {
executionContext := self.scope executionContext := rt.scope
eval := executionContext.eval eval := executionContext.eval
stash := executionContext.variable stash := executionContext.variable
for _, name := range list { for _, name := range list {
if !stash.hasBinding(name) { if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, Value{}) // TODO strict? stash.createBinding(name, eval, Value{}) // TODO strict?
} }
} }
} }

View file

@ -3,136 +3,135 @@ package otto
import ( import (
"fmt" "fmt"
"math" "math"
"runtime" goruntime "runtime"
"github.com/robertkrimen/otto/token" "github.com/robertkrimen/otto/token"
) )
func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value { func (rt *runtime) cmplEvaluateNodeExpression(node nodeExpression) Value {
// Allow interpreter interruption // Allow interpreter interruption
// If the Interrupt channel is nil, then // If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any) // we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this // FIXME: Test this
if self.otto.Interrupt != nil { if rt.otto.Interrupt != nil {
runtime.Gosched() goruntime.Gosched()
select { select {
case value := <-self.otto.Interrupt: case value := <-rt.otto.Interrupt:
value() value()
default: default:
} }
} }
switch node := node.(type) { switch node := node.(type) {
case *_nodeArrayLiteral: case *nodeArrayLiteral:
return self.cmpl_evaluate_nodeArrayLiteral(node) return rt.cmplEvaluateNodeArrayLiteral(node)
case *_nodeAssignExpression: case *nodeAssignExpression:
return self.cmpl_evaluate_nodeAssignExpression(node) return rt.cmplEvaluateNodeAssignExpression(node)
case *_nodeBinaryExpression: case *nodeBinaryExpression:
if node.comparison { if node.comparison {
return self.cmpl_evaluate_nodeBinaryExpression_comparison(node) return rt.cmplEvaluateNodeBinaryExpressionComparison(node)
} else {
return self.cmpl_evaluate_nodeBinaryExpression(node)
} }
return rt.cmplEvaluateNodeBinaryExpression(node)
case *_nodeBracketExpression: case *nodeBracketExpression:
return self.cmpl_evaluate_nodeBracketExpression(node) return rt.cmplEvaluateNodeBracketExpression(node)
case *_nodeCallExpression: case *nodeCallExpression:
return self.cmpl_evaluate_nodeCallExpression(node, nil) return rt.cmplEvaluateNodeCallExpression(node, nil)
case *_nodeConditionalExpression: case *nodeConditionalExpression:
return self.cmpl_evaluate_nodeConditionalExpression(node) return rt.cmplEvaluateNodeConditionalExpression(node)
case *_nodeDotExpression: case *nodeDotExpression:
return self.cmpl_evaluate_nodeDotExpression(node) return rt.cmplEvaluateNodeDotExpression(node)
case *_nodeFunctionLiteral: case *nodeFunctionLiteral:
var local = self.scope.lexical local := rt.scope.lexical
if node.name != "" { if node.name != "" {
local = self.newDeclarationStash(local) local = rt.newDeclarationStash(local)
} }
value := toValue_object(self.newNodeFunction(node, local)) value := objectValue(rt.newNodeFunction(node, local))
if node.name != "" { if node.name != "" {
local.createBinding(node.name, false, value) local.createBinding(node.name, false, value)
} }
return value return value
case *_nodeIdentifier: case *nodeIdentifier:
name := node.name name := node.name
// TODO Should be true or false (strictness) depending on context // TODO Should be true or false (strictness) depending on context
// getIdentifierReference should not return nil, but we check anyway and panic // getIdentifierReference should not return nil, but we check anyway and panic
// so as not to propagate the nil into something else // so as not to propagate the nil into something else
reference := getIdentifierReference(self, self.scope.lexical, name, false, _at(node.idx)) reference := getIdentifierReference(rt, rt.scope.lexical, name, false, at(node.idx))
if reference == nil { if reference == nil {
// Should never get here! // Should never get here!
panic(hereBeDragons("referenceError == nil: " + name)) panic(hereBeDragons("referenceError == nil: " + name))
} }
return toValue(reference) return toValue(reference)
case *_nodeLiteral: case *nodeLiteral:
return node.value return node.value
case *_nodeNewExpression: case *nodeNewExpression:
return self.cmpl_evaluate_nodeNewExpression(node) return rt.cmplEvaluateNodeNewExpression(node)
case *_nodeObjectLiteral: case *nodeObjectLiteral:
return self.cmpl_evaluate_nodeObjectLiteral(node) return rt.cmplEvaluateNodeObjectLiteral(node)
case *_nodeRegExpLiteral: case *nodeRegExpLiteral:
return toValue_object(self._newRegExp(node.pattern, node.flags)) return objectValue(rt.newRegExpDirect(node.pattern, node.flags))
case *_nodeSequenceExpression: case *nodeSequenceExpression:
return self.cmpl_evaluate_nodeSequenceExpression(node) return rt.cmplEvaluateNodeSequenceExpression(node)
case *_nodeThisExpression: case *nodeThisExpression:
return toValue_object(self.scope.this) return objectValue(rt.scope.this)
case *_nodeUnaryExpression: case *nodeUnaryExpression:
return self.cmpl_evaluate_nodeUnaryExpression(node) return rt.cmplEvaluateNodeUnaryExpression(node)
case *_nodeVariableExpression: case *nodeVariableExpression:
return self.cmpl_evaluate_nodeVariableExpression(node) return rt.cmplEvaluateNodeVariableExpression(node)
default:
panic(fmt.Sprintf("unknown node type: %T", node))
}
} }
panic(fmt.Errorf("Here be dragons: evaluate_nodeExpression(%T)", node)) func (rt *runtime) cmplEvaluateNodeArrayLiteral(node *nodeArrayLiteral) Value {
}
func (self *_runtime) cmpl_evaluate_nodeArrayLiteral(node *_nodeArrayLiteral) Value {
valueArray := []Value{} valueArray := []Value{}
for _, node := range node.value { for _, node := range node.value {
if node == nil { if node == nil {
valueArray = append(valueArray, emptyValue) valueArray = append(valueArray, emptyValue)
} else { } else {
valueArray = append(valueArray, self.cmpl_evaluate_nodeExpression(node).resolve()) valueArray = append(valueArray, rt.cmplEvaluateNodeExpression(node).resolve())
} }
} }
result := self.newArrayOf(valueArray) result := rt.newArrayOf(valueArray)
return toValue_object(result) return objectValue(result)
} }
func (self *_runtime) cmpl_evaluate_nodeAssignExpression(node *_nodeAssignExpression) Value { func (rt *runtime) cmplEvaluateNodeAssignExpression(node *nodeAssignExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left) left := rt.cmplEvaluateNodeExpression(node.left)
right := self.cmpl_evaluate_nodeExpression(node.right) right := rt.cmplEvaluateNodeExpression(node.right)
rightValue := right.resolve() rightValue := right.resolve()
result := rightValue result := rightValue
if node.operator != token.ASSIGN { if node.operator != token.ASSIGN {
result = self.calculateBinaryExpression(node.operator, left, rightValue) result = rt.calculateBinaryExpression(node.operator, left, rightValue)
} }
self.putValue(left.reference(), result) rt.putValue(left.reference(), result)
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpression) Value { func (rt *runtime) cmplEvaluateNodeBinaryExpression(node *nodeBinaryExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left) left := rt.cmplEvaluateNodeExpression(node.left)
leftValue := left.resolve() leftValue := left.resolve()
switch node.operator { switch node.operator {
@ -141,214 +140,206 @@ func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpres
if !leftValue.bool() { if !leftValue.bool() {
return leftValue return leftValue
} }
right := self.cmpl_evaluate_nodeExpression(node.right) right := rt.cmplEvaluateNodeExpression(node.right)
return right.resolve() return right.resolve()
case token.LOGICAL_OR: case token.LOGICAL_OR:
if leftValue.bool() { if leftValue.bool() {
return leftValue return leftValue
} }
right := self.cmpl_evaluate_nodeExpression(node.right) right := rt.cmplEvaluateNodeExpression(node.right)
return right.resolve() return right.resolve()
} }
return self.calculateBinaryExpression(node.operator, leftValue, self.cmpl_evaluate_nodeExpression(node.right)) return rt.calculateBinaryExpression(node.operator, leftValue, rt.cmplEvaluateNodeExpression(node.right))
} }
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression_comparison(node *_nodeBinaryExpression) Value { func (rt *runtime) cmplEvaluateNodeBinaryExpressionComparison(node *nodeBinaryExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left).resolve() left := rt.cmplEvaluateNodeExpression(node.left).resolve()
right := self.cmpl_evaluate_nodeExpression(node.right).resolve() right := rt.cmplEvaluateNodeExpression(node.right).resolve()
return toValue_bool(self.calculateComparison(node.operator, left, right)) return boolValue(rt.calculateComparison(node.operator, left, right))
} }
func (self *_runtime) cmpl_evaluate_nodeBracketExpression(node *_nodeBracketExpression) Value { func (rt *runtime) cmplEvaluateNodeBracketExpression(node *nodeBracketExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.left) target := rt.cmplEvaluateNodeExpression(node.left)
targetValue := target.resolve() targetValue := target.resolve()
member := self.cmpl_evaluate_nodeExpression(node.member) member := rt.cmplEvaluateNodeExpression(node.member)
memberValue := member.resolve() memberValue := member.resolve()
// TODO Pass in base value as-is, and defer toObject till later? // TODO Pass in base value as-is, and defer toObject till later?
object, err := self.objectCoerce(targetValue) obj, err := rt.objectCoerce(targetValue)
if err != nil { if err != nil {
panic(self.panicTypeError("Cannot access member '%s' of %s", memberValue.string(), err.Error(), _at(node.idx))) panic(rt.panicTypeError("Cannot access member %q of %s", memberValue.string(), err, at(node.idx)))
} }
return toValue(newPropertyReference(self, object, memberValue.string(), false, _at(node.idx))) return toValue(newPropertyReference(rt, obj, memberValue.string(), false, at(node.idx)))
} }
func (self *_runtime) cmpl_evaluate_nodeCallExpression(node *_nodeCallExpression, withArgumentList []interface{}) Value { func (rt *runtime) cmplEvaluateNodeCallExpression(node *nodeCallExpression, withArgumentList []interface{}) Value {
rt := self
this := Value{} this := Value{}
callee := self.cmpl_evaluate_nodeExpression(node.callee) callee := rt.cmplEvaluateNodeExpression(node.callee)
argumentList := []Value{} argumentList := []Value{}
if withArgumentList != nil { if withArgumentList != nil {
argumentList = self.toValueArray(withArgumentList...) argumentList = rt.toValueArray(withArgumentList...)
} else { } else {
for _, argumentNode := range node.argumentList { for _, argumentNode := range node.argumentList {
argumentList = append(argumentList, self.cmpl_evaluate_nodeExpression(argumentNode).resolve()) argumentList = append(argumentList, rt.cmplEvaluateNodeExpression(argumentNode).resolve())
} }
} }
rf := callee.reference()
vl := callee.resolve()
eval := false // Whether this call is a (candidate for) direct call to eval eval := false // Whether this call is a (candidate for) direct call to eval
name := "" name := ""
if rf != nil { if rf := callee.reference(); rf != nil {
switch rf := rf.(type) { switch rf := rf.(type) {
case *_propertyReference: case *propertyReference:
name = rf.name name = rf.name
object := rf.base this = objectValue(rf.base)
this = toValue_object(object)
eval = rf.name == "eval" // Possible direct eval eval = rf.name == "eval" // Possible direct eval
case *_stashReference: case *stashReference:
// TODO ImplicitThisValue // TODO ImplicitThisValue
name = rf.name name = rf.name
eval = rf.name == "eval" // Possible direct eval eval = rf.name == "eval" // Possible direct eval
default: default:
// FIXME? // FIXME?
panic(rt.panicTypeError("Here be dragons")) panic(rt.panicTypeError("unexpected callee type %T to node call expression", rf))
} }
} }
at := _at(-1) atv := at(-1)
switch callee := node.callee.(type) { switch callee := node.callee.(type) {
case *_nodeIdentifier: case *nodeIdentifier:
at = _at(callee.idx) atv = at(callee.idx)
case *_nodeDotExpression: case *nodeDotExpression:
at = _at(callee.idx) atv = at(callee.idx)
case *_nodeBracketExpression: case *nodeBracketExpression:
at = _at(callee.idx) atv = at(callee.idx)
} }
frame := _frame{ frm := frame{
callee: name, callee: name,
file: self.scope.frame.file, file: rt.scope.frame.file,
} }
vl := callee.resolve()
if !vl.IsFunction() { if !vl.IsFunction() {
if name == "" { if name == "" {
// FIXME Maybe typeof? // FIXME Maybe typeof?
panic(rt.panicTypeError("%v is not a function", vl, at)) panic(rt.panicTypeError("%v is not a function", vl, atv))
} }
panic(rt.panicTypeError("'%s' is not a function", name, at)) panic(rt.panicTypeError("%q is not a function", name, atv))
} }
self.scope.frame.offset = int(at) rt.scope.frame.offset = int(atv)
return vl._object().call(this, argumentList, eval, frame) return vl.object().call(this, argumentList, eval, frm)
} }
func (self *_runtime) cmpl_evaluate_nodeConditionalExpression(node *_nodeConditionalExpression) Value { func (rt *runtime) cmplEvaluateNodeConditionalExpression(node *nodeConditionalExpression) Value {
test := self.cmpl_evaluate_nodeExpression(node.test) test := rt.cmplEvaluateNodeExpression(node.test)
testValue := test.resolve() testValue := test.resolve()
if testValue.bool() { if testValue.bool() {
return self.cmpl_evaluate_nodeExpression(node.consequent) return rt.cmplEvaluateNodeExpression(node.consequent)
} }
return self.cmpl_evaluate_nodeExpression(node.alternate) return rt.cmplEvaluateNodeExpression(node.alternate)
} }
func (self *_runtime) cmpl_evaluate_nodeDotExpression(node *_nodeDotExpression) Value { func (rt *runtime) cmplEvaluateNodeDotExpression(node *nodeDotExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.left) target := rt.cmplEvaluateNodeExpression(node.left)
targetValue := target.resolve() targetValue := target.resolve()
// TODO Pass in base value as-is, and defer toObject till later? // TODO Pass in base value as-is, and defer toObject till later?
object, err := self.objectCoerce(targetValue) obj, err := rt.objectCoerce(targetValue)
if err != nil { if err != nil {
panic(self.panicTypeError("Cannot access member '%s' of %s", node.identifier, err.Error(), _at(node.idx))) panic(rt.panicTypeError("Cannot access member %q of %s", node.identifier, err, at(node.idx)))
} }
return toValue(newPropertyReference(self, object, node.identifier, false, _at(node.idx))) return toValue(newPropertyReference(rt, obj, node.identifier, false, at(node.idx)))
} }
func (self *_runtime) cmpl_evaluate_nodeNewExpression(node *_nodeNewExpression) Value { func (rt *runtime) cmplEvaluateNodeNewExpression(node *nodeNewExpression) Value {
rt := self callee := rt.cmplEvaluateNodeExpression(node.callee)
callee := self.cmpl_evaluate_nodeExpression(node.callee)
argumentList := []Value{} argumentList := []Value{}
for _, argumentNode := range node.argumentList { for _, argumentNode := range node.argumentList {
argumentList = append(argumentList, self.cmpl_evaluate_nodeExpression(argumentNode).resolve()) argumentList = append(argumentList, rt.cmplEvaluateNodeExpression(argumentNode).resolve())
} }
rf := callee.reference() var name string
vl := callee.resolve() if rf := callee.reference(); rf != nil {
name := ""
if rf != nil {
switch rf := rf.(type) { switch rf := rf.(type) {
case *_propertyReference: case *propertyReference:
name = rf.name name = rf.name
case *_stashReference: case *stashReference:
name = rf.name name = rf.name
default: default:
panic(rt.panicTypeError("Here be dragons")) panic(rt.panicTypeError("node new expression unexpected callee type %T", rf))
} }
} }
at := _at(-1) atv := at(-1)
switch callee := node.callee.(type) { switch callee := node.callee.(type) {
case *_nodeIdentifier: case *nodeIdentifier:
at = _at(callee.idx) atv = at(callee.idx)
case *_nodeDotExpression: case *nodeDotExpression:
at = _at(callee.idx) atv = at(callee.idx)
case *_nodeBracketExpression: case *nodeBracketExpression:
at = _at(callee.idx) atv = at(callee.idx)
} }
vl := callee.resolve()
if !vl.IsFunction() { if !vl.IsFunction() {
if name == "" { if name == "" {
// FIXME Maybe typeof? // FIXME Maybe typeof?
panic(rt.panicTypeError("%v is not a function", vl, at)) panic(rt.panicTypeError("%v is not a function", vl, atv))
} }
panic(rt.panicTypeError("'%s' is not a function", name, at)) panic(rt.panicTypeError("'%s' is not a function", name, atv))
} }
self.scope.frame.offset = int(at) rt.scope.frame.offset = int(atv)
return vl._object().construct(argumentList) return vl.object().construct(argumentList)
} }
func (self *_runtime) cmpl_evaluate_nodeObjectLiteral(node *_nodeObjectLiteral) Value { func (rt *runtime) cmplEvaluateNodeObjectLiteral(node *nodeObjectLiteral) Value {
result := self.newObject() result := rt.newObject()
for _, prop := range node.value {
for _, property := range node.value { switch prop.kind {
switch property.kind {
case "value": case "value":
result.defineProperty(property.key, self.cmpl_evaluate_nodeExpression(property.value).resolve(), 0111, false) result.defineProperty(prop.key, rt.cmplEvaluateNodeExpression(prop.value).resolve(), 0o111, false)
case "get": case "get":
getter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical) getter := rt.newNodeFunction(prop.value.(*nodeFunctionLiteral), rt.scope.lexical)
descriptor := _property{} descriptor := property{}
descriptor.mode = 0211 descriptor.mode = 0o211
descriptor.value = _propertyGetSet{getter, nil} descriptor.value = propertyGetSet{getter, nil}
result.defineOwnProperty(property.key, descriptor, false) result.defineOwnProperty(prop.key, descriptor, false)
case "set": case "set":
setter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical) setter := rt.newNodeFunction(prop.value.(*nodeFunctionLiteral), rt.scope.lexical)
descriptor := _property{} descriptor := property{}
descriptor.mode = 0211 descriptor.mode = 0o211
descriptor.value = _propertyGetSet{nil, setter} descriptor.value = propertyGetSet{nil, setter}
result.defineOwnProperty(property.key, descriptor, false) result.defineOwnProperty(prop.key, descriptor, false)
default: default:
panic(fmt.Errorf("Here be dragons: evaluate_nodeObjectLiteral: invalid property.Kind: %v", property.kind)) panic(fmt.Sprintf("unknown node object literal property kind %T", prop.kind))
} }
} }
return toValue_object(result) return objectValue(result)
} }
func (self *_runtime) cmpl_evaluate_nodeSequenceExpression(node *_nodeSequenceExpression) Value { func (rt *runtime) cmplEvaluateNodeSequenceExpression(node *nodeSequenceExpression) Value {
var result Value var result Value
for _, node := range node.sequence { for _, node := range node.sequence {
result = self.cmpl_evaluate_nodeExpression(node) result = rt.cmplEvaluateNodeExpression(node)
result = result.resolve() result = result.resolve()
} }
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpression) Value { func (rt *runtime) cmplEvaluateNodeUnaryExpression(node *nodeUnaryExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.operand) target := rt.cmplEvaluateNodeExpression(node.operand)
switch node.operator { switch node.operator {
case token.TYPEOF, token.DELETE: case token.TYPEOF, token.DELETE:
if target.kind == valueReference && target.reference().invalid() { if target.kind == valueReference && target.reference().invalid() {
if node.operator == token.TYPEOF { if node.operator == token.TYPEOF {
return toValue_string("undefined") return stringValue("undefined")
} }
return trueValue return trueValue
} }
@ -364,10 +355,10 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
case token.BITWISE_NOT: case token.BITWISE_NOT:
targetValue := target.resolve() targetValue := target.resolve()
integerValue := toInt32(targetValue) integerValue := toInt32(targetValue)
return toValue_int32(^integerValue) return int32Value(^integerValue)
case token.PLUS: case token.PLUS:
targetValue := target.resolve() targetValue := target.resolve()
return toValue_float64(targetValue.float64()) return float64Value(targetValue.float64())
case token.MINUS: case token.MINUS:
targetValue := target.resolve() targetValue := target.resolve()
value := targetValue.float64() value := targetValue.float64()
@ -376,35 +367,35 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
if math.Signbit(value) { if math.Signbit(value) {
sign = 1 sign = 1
} }
return toValue_float64(math.Copysign(value, sign)) return float64Value(math.Copysign(value, sign))
case token.INCREMENT: case token.INCREMENT:
targetValue := target.resolve() targetValue := target.resolve()
if node.postfix { if node.postfix {
// Postfix++ // Postfix++
oldValue := targetValue.float64() oldValue := targetValue.float64()
newValue := toValue_float64(+1 + oldValue) newValue := float64Value(+1 + oldValue)
self.putValue(target.reference(), newValue) rt.putValue(target.reference(), newValue)
return toValue_float64(oldValue) return float64Value(oldValue)
} else {
// ++Prefix
newValue := toValue_float64(+1 + targetValue.float64())
self.putValue(target.reference(), newValue)
return newValue
} }
// ++Prefix
newValue := float64Value(+1 + targetValue.float64())
rt.putValue(target.reference(), newValue)
return newValue
case token.DECREMENT: case token.DECREMENT:
targetValue := target.resolve() targetValue := target.resolve()
if node.postfix { if node.postfix {
// Postfix-- // Postfix--
oldValue := targetValue.float64() oldValue := targetValue.float64()
newValue := toValue_float64(-1 + oldValue) newValue := float64Value(-1 + oldValue)
self.putValue(target.reference(), newValue) rt.putValue(target.reference(), newValue)
return toValue_float64(oldValue) return float64Value(oldValue)
} else {
// --Prefix
newValue := toValue_float64(-1 + targetValue.float64())
self.putValue(target.reference(), newValue)
return newValue
} }
// --Prefix
newValue := float64Value(-1 + targetValue.float64())
rt.putValue(target.reference(), newValue)
return newValue
case token.VOID: case token.VOID:
target.resolve() // FIXME Side effect? target.resolve() // FIXME Side effect?
return Value{} return Value{}
@ -413,25 +404,25 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
if reference == nil { if reference == nil {
return trueValue return trueValue
} }
return toValue_bool(target.reference().delete()) return boolValue(target.reference().delete())
case token.TYPEOF: case token.TYPEOF:
targetValue := target.resolve() targetValue := target.resolve()
switch targetValue.kind { switch targetValue.kind {
case valueUndefined: case valueUndefined:
return toValue_string("undefined") return stringValue("undefined")
case valueNull: case valueNull:
return toValue_string("object") return stringValue("object")
case valueBoolean: case valueBoolean:
return toValue_string("boolean") return stringValue("boolean")
case valueNumber: case valueNumber:
return toValue_string("number") return stringValue("number")
case valueString: case valueString:
return toValue_string("string") return stringValue("string")
case valueObject: case valueObject:
if targetValue._object().isCall() { if targetValue.object().isCall() {
return toValue_string("function") return stringValue("function")
} }
return toValue_string("object") return stringValue("object")
default: default:
// FIXME ? // FIXME ?
} }
@ -440,14 +431,14 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
panic(hereBeDragons()) panic(hereBeDragons())
} }
func (self *_runtime) cmpl_evaluate_nodeVariableExpression(node *_nodeVariableExpression) Value { func (rt *runtime) cmplEvaluateNodeVariableExpression(node *nodeVariableExpression) Value {
if node.initializer != nil { if node.initializer != nil {
// FIXME If reference is nil // FIXME If reference is nil
left := getIdentifierReference(self, self.scope.lexical, node.name, false, _at(node.idx)) left := getIdentifierReference(rt, rt.scope.lexical, node.name, false, at(node.idx))
right := self.cmpl_evaluate_nodeExpression(node.initializer) right := rt.cmplEvaluateNodeExpression(node.initializer)
rightValue := right.resolve() rightValue := right.resolve()
self.putValue(left, rightValue) rt.putValue(left, rightValue)
} }
return toValue_string(node.name) return stringValue(node.name)
} }

View file

@ -2,121 +2,121 @@ package otto
import ( import (
"fmt" "fmt"
"runtime" goruntime "runtime"
"github.com/robertkrimen/otto/token" "github.com/robertkrimen/otto/token"
) )
func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value { func (rt *runtime) cmplEvaluateNodeStatement(node nodeStatement) Value {
// Allow interpreter interruption // Allow interpreter interruption
// If the Interrupt channel is nil, then // If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any) // we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this // FIXME: Test this
if self.otto.Interrupt != nil { if rt.otto.Interrupt != nil {
runtime.Gosched() goruntime.Gosched()
select { select {
case value := <-self.otto.Interrupt: case value := <-rt.otto.Interrupt:
value() value()
default: default:
} }
} }
switch node := node.(type) { switch node := node.(type) {
case *_nodeBlockStatement: case *nodeBlockStatement:
labels := self.labels labels := rt.labels
self.labels = nil rt.labels = nil
value := self.cmpl_evaluate_nodeStatementList(node.list) value := rt.cmplEvaluateNodeStatementList(node.list)
switch value.kind { if value.kind == valueResult {
case valueResult: if value.evaluateBreak(labels) == resultBreak {
switch value.evaluateBreak(labels) {
case resultBreak:
return emptyValue return emptyValue
} }
} }
return value return value
case *_nodeBranchStatement: case *nodeBranchStatement:
target := node.label target := node.label
switch node.branch { // FIXME Maybe node.kind? node.operator? switch node.branch { // FIXME Maybe node.kind? node.operator?
case token.BREAK: case token.BREAK:
return toValue(newBreakResult(target)) return toValue(newBreakResult(target))
case token.CONTINUE: case token.CONTINUE:
return toValue(newContinueResult(target)) return toValue(newContinueResult(target))
default:
panic(fmt.Errorf("unknown node branch token %T", node))
} }
case *_nodeDebuggerStatement: case *nodeDebuggerStatement:
if self.debugger != nil { if rt.debugger != nil {
self.debugger(self.otto) rt.debugger(rt.otto)
} }
return emptyValue // Nothing happens. return emptyValue // Nothing happens.
case *_nodeDoWhileStatement: case *nodeDoWhileStatement:
return self.cmpl_evaluate_nodeDoWhileStatement(node) return rt.cmplEvaluateNodeDoWhileStatement(node)
case *_nodeEmptyStatement: case *nodeEmptyStatement:
return emptyValue return emptyValue
case *_nodeExpressionStatement: case *nodeExpressionStatement:
return self.cmpl_evaluate_nodeExpression(node.expression) return rt.cmplEvaluateNodeExpression(node.expression)
case *_nodeForInStatement: case *nodeForInStatement:
return self.cmpl_evaluate_nodeForInStatement(node) return rt.cmplEvaluateNodeForInStatement(node)
case *_nodeForStatement: case *nodeForStatement:
return self.cmpl_evaluate_nodeForStatement(node) return rt.cmplEvaluateNodeForStatement(node)
case *_nodeIfStatement: case *nodeIfStatement:
return self.cmpl_evaluate_nodeIfStatement(node) return rt.cmplEvaluateNodeIfStatement(node)
case *_nodeLabelledStatement: case *nodeLabelledStatement:
self.labels = append(self.labels, node.label) rt.labels = append(rt.labels, node.label)
defer func() { defer func() {
if len(self.labels) > 0 { if len(rt.labels) > 0 {
self.labels = self.labels[:len(self.labels)-1] // Pop the label rt.labels = rt.labels[:len(rt.labels)-1] // Pop the label
} else { } else {
self.labels = nil rt.labels = nil
} }
}() }()
return self.cmpl_evaluate_nodeStatement(node.statement) return rt.cmplEvaluateNodeStatement(node.statement)
case *_nodeReturnStatement: case *nodeReturnStatement:
if node.argument != nil { if node.argument != nil {
return toValue(newReturnResult(self.cmpl_evaluate_nodeExpression(node.argument).resolve())) return toValue(newReturnResult(rt.cmplEvaluateNodeExpression(node.argument).resolve()))
} }
return toValue(newReturnResult(Value{})) return toValue(newReturnResult(Value{}))
case *_nodeSwitchStatement: case *nodeSwitchStatement:
return self.cmpl_evaluate_nodeSwitchStatement(node) return rt.cmplEvaluateNodeSwitchStatement(node)
case *_nodeThrowStatement: case *nodeThrowStatement:
value := self.cmpl_evaluate_nodeExpression(node.argument).resolve() value := rt.cmplEvaluateNodeExpression(node.argument).resolve()
panic(newException(value)) panic(newException(value))
case *_nodeTryStatement: case *nodeTryStatement:
return self.cmpl_evaluate_nodeTryStatement(node) return rt.cmplEvaluateNodeTryStatement(node)
case *_nodeVariableStatement: case *nodeVariableStatement:
// Variables are already defined, this is initialization only // Variables are already defined, this is initialization only
for _, variable := range node.list { for _, variable := range node.list {
self.cmpl_evaluate_nodeVariableExpression(variable.(*_nodeVariableExpression)) rt.cmplEvaluateNodeVariableExpression(variable.(*nodeVariableExpression))
} }
return emptyValue return emptyValue
case *_nodeWhileStatement: case *nodeWhileStatement:
return self.cmpl_evaluate_nodeWhileStatement(node) return rt.cmplEvaluateModeWhileStatement(node)
case *_nodeWithStatement: case *nodeWithStatement:
return self.cmpl_evaluate_nodeWithStatement(node) return rt.cmplEvaluateNodeWithStatement(node)
default:
panic(fmt.Errorf("unknown node statement type %T", node))
}
} }
panic(fmt.Errorf("Here be dragons: evaluate_nodeStatement(%T)", node)) func (rt *runtime) cmplEvaluateNodeStatementList(list []nodeStatement) Value {
}
func (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Value {
var result Value var result Value
for _, node := range list { for _, node := range list {
value := self.cmpl_evaluate_nodeStatement(node) value := rt.cmplEvaluateNodeStatement(node)
switch value.kind { switch value.kind {
case valueResult: case valueResult:
return value return value
@ -133,9 +133,9 @@ func (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Val
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileStatement) Value { func (rt *runtime) cmplEvaluateNodeDoWhileStatement(node *nodeDoWhileStatement) Value {
labels := append(self.labels, "") labels := append(rt.labels, "") //nolint: gocritic
self.labels = nil rt.labels = nil
test := node.test test := node.test
@ -143,7 +143,7 @@ func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileState
resultBreak: resultBreak:
for { for {
for _, node := range node.body { for _, node := range node.body {
value := self.cmpl_evaluate_nodeStatement(node) value := rt.cmplEvaluateNodeStatement(node)
switch value.kind { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
@ -160,7 +160,7 @@ resultBreak:
} }
} }
resultContinue: resultContinue:
if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() { if !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
// Stahp: do ... while (false) // Stahp: do ... while (false)
break break
} }
@ -168,11 +168,11 @@ resultBreak:
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement) Value { func (rt *runtime) cmplEvaluateNodeForInStatement(node *nodeForInStatement) Value {
labels := append(self.labels, "") labels := append(rt.labels, "") //nolint: gocritic
self.labels = nil rt.labels = nil
source := self.cmpl_evaluate_nodeExpression(node.source) source := rt.cmplEvaluateNodeExpression(node.source)
sourceValue := source.resolve() sourceValue := source.resolve()
switch sourceValue.kind { switch sourceValue.kind {
@ -180,26 +180,26 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
return emptyValue return emptyValue
} }
sourceObject := self.toObject(sourceValue) sourceObject := rt.toObject(sourceValue)
into := node.into into := node.into
body := node.body body := node.body
result := emptyValue result := emptyValue
object := sourceObject obj := sourceObject
for object != nil { for obj != nil {
enumerateValue := emptyValue enumerateValue := emptyValue
object.enumerate(false, func(name string) bool { obj.enumerate(false, func(name string) bool {
into := self.cmpl_evaluate_nodeExpression(into) into := rt.cmplEvaluateNodeExpression(into)
// In the case of: for (var abc in def) ... // In the case of: for (var abc in def) ...
if into.reference() == nil { if into.reference() == nil {
identifier := into.string() identifier := into.string()
// TODO Should be true or false (strictness) depending on context // TODO Should be true or false (strictness) depending on context
into = toValue(getIdentifierReference(self, self.scope.lexical, identifier, false, -1)) into = toValue(getIdentifierReference(rt, rt.scope.lexical, identifier, false, -1))
} }
self.putValue(into.reference(), toValue_string(name)) rt.putValue(into.reference(), stringValue(name))
for _, node := range body { for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node) value := rt.cmplEvaluateNodeStatement(node)
switch value.kind { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
@ -207,7 +207,7 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
enumerateValue = value enumerateValue = value
return false return false
case resultBreak: case resultBreak:
object = nil obj = nil
return false return false
case resultContinue: case resultContinue:
return true return true
@ -219,10 +219,10 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
} }
return true return true
}) })
if object == nil { if obj == nil {
break break
} }
object = object.prototype obj = obj.prototype
if !enumerateValue.isEmpty() { if !enumerateValue.isEmpty() {
result = enumerateValue result = enumerateValue
} }
@ -230,9 +230,9 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Value { func (rt *runtime) cmplEvaluateNodeForStatement(node *nodeForStatement) Value {
labels := append(self.labels, "") labels := append(rt.labels, "") //nolint: gocritic
self.labels = nil rt.labels = nil
initializer := node.initializer initializer := node.initializer
test := node.test test := node.test
@ -240,7 +240,7 @@ func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Va
body := node.body body := node.body
if initializer != nil { if initializer != nil {
initialResult := self.cmpl_evaluate_nodeExpression(initializer) initialResult := rt.cmplEvaluateNodeExpression(initializer)
initialResult.resolve() // Side-effect trigger initialResult.resolve() // Side-effect trigger
} }
@ -248,25 +248,25 @@ func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Va
resultBreak: resultBreak:
for { for {
if test != nil { if test != nil {
testResult := self.cmpl_evaluate_nodeExpression(test) testResult := rt.cmplEvaluateNodeExpression(test)
testResultValue := testResult.resolve() testResultValue := testResult.resolve()
if testResultValue.bool() == false { if !testResultValue.bool() {
break break
} }
} }
// this is to prevent for cycles with no body from running forever // this is to prevent for cycles with no body from running forever
if len(body) == 0 && self.otto.Interrupt != nil { if len(body) == 0 && rt.otto.Interrupt != nil {
runtime.Gosched() goruntime.Gosched()
select { select {
case value := <-self.otto.Interrupt: case value := <-rt.otto.Interrupt:
value() value()
default: default:
} }
} }
for _, node := range body { for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node) value := rt.cmplEvaluateNodeStatement(node)
switch value.kind { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
@ -284,36 +284,36 @@ resultBreak:
} }
resultContinue: resultContinue:
if update != nil { if update != nil {
updateResult := self.cmpl_evaluate_nodeExpression(update) updateResult := rt.cmplEvaluateNodeExpression(update)
updateResult.resolve() // Side-effect trigger updateResult.resolve() // Side-effect trigger
} }
} }
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeIfStatement(node *_nodeIfStatement) Value { func (rt *runtime) cmplEvaluateNodeIfStatement(node *nodeIfStatement) Value {
test := self.cmpl_evaluate_nodeExpression(node.test) test := rt.cmplEvaluateNodeExpression(node.test)
testValue := test.resolve() testValue := test.resolve()
if testValue.bool() { if testValue.bool() {
return self.cmpl_evaluate_nodeStatement(node.consequent) return rt.cmplEvaluateNodeStatement(node.consequent)
} else if node.alternate != nil { } else if node.alternate != nil {
return self.cmpl_evaluate_nodeStatement(node.alternate) return rt.cmplEvaluateNodeStatement(node.alternate)
} }
return emptyValue return emptyValue
} }
func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStatement) Value { func (rt *runtime) cmplEvaluateNodeSwitchStatement(node *nodeSwitchStatement) Value {
labels := append(self.labels, "") labels := append(rt.labels, "") //nolint: gocritic
self.labels = nil rt.labels = nil
discriminantResult := self.cmpl_evaluate_nodeExpression(node.discriminant) discriminantResult := rt.cmplEvaluateNodeExpression(node.discriminant)
target := node.default_ target := node.defaultIdx
for index, clause := range node.body { for index, clause := range node.body {
test := clause.test test := clause.test
if test != nil { if test != nil {
if self.calculateComparison(token.STRICT_EQUAL, discriminantResult, self.cmpl_evaluate_nodeExpression(test)) { if rt.calculateComparison(token.STRICT_EQUAL, discriminantResult, rt.cmplEvaluateNodeExpression(test)) {
target = index target = index
break break
} }
@ -324,7 +324,7 @@ func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStateme
if target != -1 { if target != -1 {
for _, clause := range node.body[target:] { for _, clause := range node.body[target:] {
for _, statement := range clause.consequent { for _, statement := range clause.consequent {
value := self.cmpl_evaluate_nodeStatement(statement) value := rt.cmplEvaluateNodeStatement(statement)
switch value.kind { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreak(labels) { switch value.evaluateBreak(labels) {
@ -344,58 +344,58 @@ func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStateme
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeTryStatement(node *_nodeTryStatement) Value { func (rt *runtime) cmplEvaluateNodeTryStatement(node *nodeTryStatement) Value {
tryCatchValue, exception := self.tryCatchEvaluate(func() Value { tryCatchValue, exep := rt.tryCatchEvaluate(func() Value {
return self.cmpl_evaluate_nodeStatement(node.body) return rt.cmplEvaluateNodeStatement(node.body)
}) })
if exception && node.catch != nil { if exep && node.catch != nil {
outer := self.scope.lexical outer := rt.scope.lexical
self.scope.lexical = self.newDeclarationStash(outer) rt.scope.lexical = rt.newDeclarationStash(outer)
defer func() { defer func() {
self.scope.lexical = outer rt.scope.lexical = outer
}() }()
// TODO If necessary, convert TypeError<runtime> => TypeError // TODO If necessary, convert TypeError<runtime> => TypeError
// That, is, such errors can be thrown despite not being JavaScript "native" // That, is, such errors can be thrown despite not being JavaScript "native"
// strict = false // strict = false
self.scope.lexical.setValue(node.catch.parameter, tryCatchValue, false) rt.scope.lexical.setValue(node.catch.parameter, tryCatchValue, false)
// FIXME node.CatchParameter // FIXME node.CatchParameter
// FIXME node.Catch // FIXME node.Catch
tryCatchValue, exception = self.tryCatchEvaluate(func() Value { tryCatchValue, exep = rt.tryCatchEvaluate(func() Value {
return self.cmpl_evaluate_nodeStatement(node.catch.body) return rt.cmplEvaluateNodeStatement(node.catch.body)
}) })
} }
if node.finally != nil { if node.finally != nil {
finallyValue := self.cmpl_evaluate_nodeStatement(node.finally) finallyValue := rt.cmplEvaluateNodeStatement(node.finally)
if finallyValue.kind == valueResult { if finallyValue.kind == valueResult {
return finallyValue return finallyValue
} }
} }
if exception { if exep {
panic(newException(tryCatchValue)) panic(newException(tryCatchValue))
} }
return tryCatchValue return tryCatchValue
} }
func (self *_runtime) cmpl_evaluate_nodeWhileStatement(node *_nodeWhileStatement) Value { func (rt *runtime) cmplEvaluateModeWhileStatement(node *nodeWhileStatement) Value {
test := node.test test := node.test
body := node.body body := node.body
labels := append(self.labels, "") labels := append(rt.labels, "") //nolint: gocritic
self.labels = nil rt.labels = nil
result := emptyValue result := emptyValue
resultBreakContinue: resultBreakContinue:
for { for {
if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() { if !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
// Stahp: while (false) ... // Stahp: while (false) ...
break break
} }
for _, node := range body { for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node) value := rt.cmplEvaluateNodeStatement(node)
switch value.kind { switch value.kind {
case valueResult: case valueResult:
switch value.evaluateBreakContinue(labels) { switch value.evaluateBreakContinue(labels) {
@ -415,14 +415,14 @@ resultBreakContinue:
return result return result
} }
func (self *_runtime) cmpl_evaluate_nodeWithStatement(node *_nodeWithStatement) Value { func (rt *runtime) cmplEvaluateNodeWithStatement(node *nodeWithStatement) Value {
object := self.cmpl_evaluate_nodeExpression(node.object) obj := rt.cmplEvaluateNodeExpression(node.object)
outer := self.scope.lexical outer := rt.scope.lexical
lexical := self.newObjectStash(self.toObject(object.resolve()), outer) lexical := rt.newObjectStash(rt.toObject(obj.resolve()), outer)
self.scope.lexical = lexical rt.scope.lexical = lexical
defer func() { defer func() {
self.scope.lexical = outer rt.scope.lexical = outer
}() }()
return self.cmpl_evaluate_nodeStatement(node.body) return rt.cmplEvaluateNodeStatement(node.body)
} }

View file

@ -2,83 +2,84 @@ package otto
import ( import (
"fmt" "fmt"
"regexp"
"github.com/robertkrimen/otto/ast" "github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file" "github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token" "github.com/robertkrimen/otto/token"
) )
var trueLiteral = &_nodeLiteral{value: toValue_bool(true)} var (
var falseLiteral = &_nodeLiteral{value: toValue_bool(false)} trueLiteral = &nodeLiteral{value: boolValue(true)}
var nullLiteral = &_nodeLiteral{value: nullValue} falseLiteral = &nodeLiteral{value: boolValue(false)}
var emptyStatement = &_nodeEmptyStatement{} nullLiteral = &nodeLiteral{value: nullValue}
emptyStatement = &nodeEmptyStatement{}
)
func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression { func (cmpl *compiler) parseExpression(expr ast.Expression) nodeExpression {
if in == nil { if expr == nil {
return nil return nil
} }
switch in := in.(type) { switch expr := expr.(type) {
case *ast.ArrayLiteral: case *ast.ArrayLiteral:
out := &_nodeArrayLiteral{ out := &nodeArrayLiteral{
value: make([]_nodeExpression, len(in.Value)), value: make([]nodeExpression, len(expr.Value)),
} }
for i, value := range in.Value { for i, value := range expr.Value {
out.value[i] = cmpl.parseExpression(value) out.value[i] = cmpl.parseExpression(value)
} }
return out return out
case *ast.AssignExpression: case *ast.AssignExpression:
return &_nodeAssignExpression{ return &nodeAssignExpression{
operator: in.Operator, operator: expr.Operator,
left: cmpl.parseExpression(in.Left), left: cmpl.parseExpression(expr.Left),
right: cmpl.parseExpression(in.Right), right: cmpl.parseExpression(expr.Right),
} }
case *ast.BinaryExpression: case *ast.BinaryExpression:
return &_nodeBinaryExpression{ return &nodeBinaryExpression{
operator: in.Operator, operator: expr.Operator,
left: cmpl.parseExpression(in.Left), left: cmpl.parseExpression(expr.Left),
right: cmpl.parseExpression(in.Right), right: cmpl.parseExpression(expr.Right),
comparison: in.Comparison, comparison: expr.Comparison,
} }
case *ast.BooleanLiteral: case *ast.BooleanLiteral:
if in.Value { if expr.Value {
return trueLiteral return trueLiteral
} }
return falseLiteral return falseLiteral
case *ast.BracketExpression: case *ast.BracketExpression:
return &_nodeBracketExpression{ return &nodeBracketExpression{
idx: in.Left.Idx0(), idx: expr.Left.Idx0(),
left: cmpl.parseExpression(in.Left), left: cmpl.parseExpression(expr.Left),
member: cmpl.parseExpression(in.Member), member: cmpl.parseExpression(expr.Member),
} }
case *ast.CallExpression: case *ast.CallExpression:
out := &_nodeCallExpression{ out := &nodeCallExpression{
callee: cmpl.parseExpression(in.Callee), callee: cmpl.parseExpression(expr.Callee),
argumentList: make([]_nodeExpression, len(in.ArgumentList)), argumentList: make([]nodeExpression, len(expr.ArgumentList)),
} }
for i, value := range in.ArgumentList { for i, value := range expr.ArgumentList {
out.argumentList[i] = cmpl.parseExpression(value) out.argumentList[i] = cmpl.parseExpression(value)
} }
return out return out
case *ast.ConditionalExpression: case *ast.ConditionalExpression:
return &_nodeConditionalExpression{ return &nodeConditionalExpression{
test: cmpl.parseExpression(in.Test), test: cmpl.parseExpression(expr.Test),
consequent: cmpl.parseExpression(in.Consequent), consequent: cmpl.parseExpression(expr.Consequent),
alternate: cmpl.parseExpression(in.Alternate), alternate: cmpl.parseExpression(expr.Alternate),
} }
case *ast.DotExpression: case *ast.DotExpression:
return &_nodeDotExpression{ return &nodeDotExpression{
idx: in.Left.Idx0(), idx: expr.Left.Idx0(),
left: cmpl.parseExpression(in.Left), left: cmpl.parseExpression(expr.Left),
identifier: in.Identifier.Name, identifier: expr.Identifier.Name,
} }
case *ast.EmptyExpression: case *ast.EmptyExpression:
@ -86,48 +87,48 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
case *ast.FunctionLiteral: case *ast.FunctionLiteral:
name := "" name := ""
if in.Name != nil { if expr.Name != nil {
name = in.Name.Name name = expr.Name.Name
} }
out := &_nodeFunctionLiteral{ out := &nodeFunctionLiteral{
name: name, name: name,
body: cmpl.parseStatement(in.Body), body: cmpl.parseStatement(expr.Body),
source: in.Source, source: expr.Source,
file: cmpl.file, file: cmpl.file,
} }
if in.ParameterList != nil { if expr.ParameterList != nil {
list := in.ParameterList.List list := expr.ParameterList.List
out.parameterList = make([]string, len(list)) out.parameterList = make([]string, len(list))
for i, value := range list { for i, value := range list {
out.parameterList[i] = value.Name out.parameterList[i] = value.Name
} }
} }
for _, value := range in.DeclarationList { for _, value := range expr.DeclarationList {
switch value := value.(type) { switch value := value.(type) {
case *ast.FunctionDeclaration: case *ast.FunctionDeclaration:
out.functionList = append(out.functionList, cmpl.parseExpression(value.Function).(*_nodeFunctionLiteral)) out.functionList = append(out.functionList, cmpl.parseExpression(value.Function).(*nodeFunctionLiteral))
case *ast.VariableDeclaration: case *ast.VariableDeclaration:
for _, value := range value.List { for _, value := range value.List {
out.varList = append(out.varList, value.Name) out.varList = append(out.varList, value.Name)
} }
default: default:
panic(fmt.Errorf("Here be dragons: parseProgram.declaration(%T)", value)) panic(fmt.Sprintf("parse expression unknown function declaration type %T", value))
} }
} }
return out return out
case *ast.Identifier: case *ast.Identifier:
return &_nodeIdentifier{ return &nodeIdentifier{
idx: in.Idx, idx: expr.Idx,
name: in.Name, name: expr.Name,
} }
case *ast.NewExpression: case *ast.NewExpression:
out := &_nodeNewExpression{ out := &nodeNewExpression{
callee: cmpl.parseExpression(in.Callee), callee: cmpl.parseExpression(expr.Callee),
argumentList: make([]_nodeExpression, len(in.ArgumentList)), argumentList: make([]nodeExpression, len(expr.ArgumentList)),
} }
for i, value := range in.ArgumentList { for i, value := range expr.ArgumentList {
out.argumentList[i] = cmpl.parseExpression(value) out.argumentList[i] = cmpl.parseExpression(value)
} }
return out return out
@ -136,16 +137,16 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
return nullLiteral return nullLiteral
case *ast.NumberLiteral: case *ast.NumberLiteral:
return &_nodeLiteral{ return &nodeLiteral{
value: toValue(in.Value), value: toValue(expr.Value),
} }
case *ast.ObjectLiteral: case *ast.ObjectLiteral:
out := &_nodeObjectLiteral{ out := &nodeObjectLiteral{
value: make([]_nodeProperty, len(in.Value)), value: make([]nodeProperty, len(expr.Value)),
} }
for i, value := range in.Value { for i, value := range expr.Value {
out.value[i] = _nodeProperty{ out.value[i] = nodeProperty{
key: value.Key, key: value.Key,
kind: value.Kind, kind: value.Kind,
value: cmpl.parseExpression(value.Value), value: cmpl.parseExpression(value.Value),
@ -154,79 +155,79 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
return out return out
case *ast.RegExpLiteral: case *ast.RegExpLiteral:
return &_nodeRegExpLiteral{ return &nodeRegExpLiteral{
flags: in.Flags, flags: expr.Flags,
pattern: in.Pattern, pattern: expr.Pattern,
} }
case *ast.SequenceExpression: case *ast.SequenceExpression:
out := &_nodeSequenceExpression{ out := &nodeSequenceExpression{
sequence: make([]_nodeExpression, len(in.Sequence)), sequence: make([]nodeExpression, len(expr.Sequence)),
} }
for i, value := range in.Sequence { for i, value := range expr.Sequence {
out.sequence[i] = cmpl.parseExpression(value) out.sequence[i] = cmpl.parseExpression(value)
} }
return out return out
case *ast.StringLiteral: case *ast.StringLiteral:
return &_nodeLiteral{ return &nodeLiteral{
value: toValue_string(in.Value), value: stringValue(expr.Value),
} }
case *ast.ThisExpression: case *ast.ThisExpression:
return &_nodeThisExpression{} return &nodeThisExpression{}
case *ast.UnaryExpression: case *ast.UnaryExpression:
return &_nodeUnaryExpression{ return &nodeUnaryExpression{
operator: in.Operator, operator: expr.Operator,
operand: cmpl.parseExpression(in.Operand), operand: cmpl.parseExpression(expr.Operand),
postfix: in.Postfix, postfix: expr.Postfix,
} }
case *ast.VariableExpression: case *ast.VariableExpression:
return &_nodeVariableExpression{ return &nodeVariableExpression{
idx: in.Idx0(), idx: expr.Idx0(),
name: in.Name, name: expr.Name,
initializer: cmpl.parseExpression(in.Initializer), initializer: cmpl.parseExpression(expr.Initializer),
}
default:
panic(fmt.Errorf("parse expression unknown node type %T", expr))
} }
} }
panic(fmt.Errorf("Here be dragons: cmpl.parseExpression(%T)", in)) func (cmpl *compiler) parseStatement(stmt ast.Statement) nodeStatement {
} if stmt == nil {
func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
if in == nil {
return nil return nil
} }
switch in := in.(type) { switch stmt := stmt.(type) {
case *ast.BlockStatement: case *ast.BlockStatement:
out := &_nodeBlockStatement{ out := &nodeBlockStatement{
list: make([]_nodeStatement, len(in.List)), list: make([]nodeStatement, len(stmt.List)),
} }
for i, value := range in.List { for i, value := range stmt.List {
out.list[i] = cmpl.parseStatement(value) out.list[i] = cmpl.parseStatement(value)
} }
return out return out
case *ast.BranchStatement: case *ast.BranchStatement:
out := &_nodeBranchStatement{ out := &nodeBranchStatement{
branch: in.Token, branch: stmt.Token,
} }
if in.Label != nil { if stmt.Label != nil {
out.label = in.Label.Name out.label = stmt.Label.Name
} }
return out return out
case *ast.DebuggerStatement: case *ast.DebuggerStatement:
return &_nodeDebuggerStatement{} return &nodeDebuggerStatement{}
case *ast.DoWhileStatement: case *ast.DoWhileStatement:
out := &_nodeDoWhileStatement{ out := &nodeDoWhileStatement{
test: cmpl.parseExpression(in.Test), test: cmpl.parseExpression(stmt.Test),
} }
body := cmpl.parseStatement(in.Body) body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list out.body = block.list
} else { } else {
out.body = append(out.body, body) out.body = append(out.body, body)
@ -237,17 +238,17 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return emptyStatement return emptyStatement
case *ast.ExpressionStatement: case *ast.ExpressionStatement:
return &_nodeExpressionStatement{ return &nodeExpressionStatement{
expression: cmpl.parseExpression(in.Expression), expression: cmpl.parseExpression(stmt.Expression),
} }
case *ast.ForInStatement: case *ast.ForInStatement:
out := &_nodeForInStatement{ out := &nodeForInStatement{
into: cmpl.parseExpression(in.Into), into: cmpl.parseExpression(stmt.Into),
source: cmpl.parseExpression(in.Source), source: cmpl.parseExpression(stmt.Source),
} }
body := cmpl.parseStatement(in.Body) body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list out.body = block.list
} else { } else {
out.body = append(out.body, body) out.body = append(out.body, body)
@ -255,13 +256,13 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return out return out
case *ast.ForStatement: case *ast.ForStatement:
out := &_nodeForStatement{ out := &nodeForStatement{
initializer: cmpl.parseExpression(in.Initializer), initializer: cmpl.parseExpression(stmt.Initializer),
update: cmpl.parseExpression(in.Update), update: cmpl.parseExpression(stmt.Update),
test: cmpl.parseExpression(in.Test), test: cmpl.parseExpression(stmt.Test),
} }
body := cmpl.parseStatement(in.Body) body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list out.body = block.list
} else { } else {
out.body = append(out.body, body) out.body = append(out.body, body)
@ -272,33 +273,33 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return emptyStatement return emptyStatement
case *ast.IfStatement: case *ast.IfStatement:
return &_nodeIfStatement{ return &nodeIfStatement{
test: cmpl.parseExpression(in.Test), test: cmpl.parseExpression(stmt.Test),
consequent: cmpl.parseStatement(in.Consequent), consequent: cmpl.parseStatement(stmt.Consequent),
alternate: cmpl.parseStatement(in.Alternate), alternate: cmpl.parseStatement(stmt.Alternate),
} }
case *ast.LabelledStatement: case *ast.LabelledStatement:
return &_nodeLabelledStatement{ return &nodeLabelledStatement{
label: in.Label.Name, label: stmt.Label.Name,
statement: cmpl.parseStatement(in.Statement), statement: cmpl.parseStatement(stmt.Statement),
} }
case *ast.ReturnStatement: case *ast.ReturnStatement:
return &_nodeReturnStatement{ return &nodeReturnStatement{
argument: cmpl.parseExpression(in.Argument), argument: cmpl.parseExpression(stmt.Argument),
} }
case *ast.SwitchStatement: case *ast.SwitchStatement:
out := &_nodeSwitchStatement{ out := &nodeSwitchStatement{
discriminant: cmpl.parseExpression(in.Discriminant), discriminant: cmpl.parseExpression(stmt.Discriminant),
default_: in.Default, defaultIdx: stmt.Default,
body: make([]*_nodeCaseStatement, len(in.Body)), body: make([]*nodeCaseStatement, len(stmt.Body)),
} }
for i, clause := range in.Body { for i, clause := range stmt.Body {
out.body[i] = &_nodeCaseStatement{ out.body[i] = &nodeCaseStatement{
test: cmpl.parseExpression(clause.Test), test: cmpl.parseExpression(clause.Test),
consequent: make([]_nodeStatement, len(clause.Consequent)), consequent: make([]nodeStatement, len(clause.Consequent)),
} }
for j, value := range clause.Consequent { for j, value := range clause.Consequent {
out.body[i].consequent[j] = cmpl.parseStatement(value) out.body[i].consequent[j] = cmpl.parseStatement(value)
@ -307,38 +308,38 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return out return out
case *ast.ThrowStatement: case *ast.ThrowStatement:
return &_nodeThrowStatement{ return &nodeThrowStatement{
argument: cmpl.parseExpression(in.Argument), argument: cmpl.parseExpression(stmt.Argument),
} }
case *ast.TryStatement: case *ast.TryStatement:
out := &_nodeTryStatement{ out := &nodeTryStatement{
body: cmpl.parseStatement(in.Body), body: cmpl.parseStatement(stmt.Body),
finally: cmpl.parseStatement(in.Finally), finally: cmpl.parseStatement(stmt.Finally),
} }
if in.Catch != nil { if stmt.Catch != nil {
out.catch = &_nodeCatchStatement{ out.catch = &nodeCatchStatement{
parameter: in.Catch.Parameter.Name, parameter: stmt.Catch.Parameter.Name,
body: cmpl.parseStatement(in.Catch.Body), body: cmpl.parseStatement(stmt.Catch.Body),
} }
} }
return out return out
case *ast.VariableStatement: case *ast.VariableStatement:
out := &_nodeVariableStatement{ out := &nodeVariableStatement{
list: make([]_nodeExpression, len(in.List)), list: make([]nodeExpression, len(stmt.List)),
} }
for i, value := range in.List { for i, value := range stmt.List {
out.list[i] = cmpl.parseExpression(value) out.list[i] = cmpl.parseExpression(value)
} }
return out return out
case *ast.WhileStatement: case *ast.WhileStatement:
out := &_nodeWhileStatement{ out := &nodeWhileStatement{
test: cmpl.parseExpression(in.Test), test: cmpl.parseExpression(stmt.Test),
} }
body := cmpl.parseStatement(in.Body) body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*_nodeBlockStatement); ok { if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list out.body = block.list
} else { } else {
out.body = append(out.body, body) out.body = append(out.body, body)
@ -346,307 +347,298 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return out return out
case *ast.WithStatement: case *ast.WithStatement:
return &_nodeWithStatement{ return &nodeWithStatement{
object: cmpl.parseExpression(in.Object), object: cmpl.parseExpression(stmt.Object),
body: cmpl.parseStatement(in.Body), body: cmpl.parseStatement(stmt.Body),
}
default:
panic(fmt.Sprintf("parse statement: unknown type %T", stmt))
} }
} }
panic(fmt.Errorf("Here be dragons: cmpl.parseStatement(%T)", in)) func cmplParse(in *ast.Program) *nodeProgram {
} cmpl := compiler{
func cmpl_parse(in *ast.Program) *_nodeProgram {
cmpl := _compiler{
program: in, program: in,
} }
if cmpl.program != nil {
cmpl.file = cmpl.program.File
}
return cmpl.parse() return cmpl.parse()
} }
func (cmpl *_compiler) _parse(in *ast.Program) *_nodeProgram { func (cmpl *compiler) parse() *nodeProgram {
out := &_nodeProgram{ out := &nodeProgram{
body: make([]_nodeStatement, len(in.Body)), body: make([]nodeStatement, len(cmpl.program.Body)),
file: in.File, file: cmpl.program.File,
} }
for i, value := range in.Body { for i, value := range cmpl.program.Body {
out.body[i] = cmpl.parseStatement(value) out.body[i] = cmpl.parseStatement(value)
} }
for _, value := range in.DeclarationList { for _, value := range cmpl.program.DeclarationList {
switch value := value.(type) { switch value := value.(type) {
case *ast.FunctionDeclaration: case *ast.FunctionDeclaration:
out.functionList = append(out.functionList, cmpl.parseExpression(value.Function).(*_nodeFunctionLiteral)) out.functionList = append(out.functionList, cmpl.parseExpression(value.Function).(*nodeFunctionLiteral))
case *ast.VariableDeclaration: case *ast.VariableDeclaration:
for _, value := range value.List { for _, value := range value.List {
out.varList = append(out.varList, value.Name) out.varList = append(out.varList, value.Name)
} }
default: default:
panic(fmt.Errorf("Here be dragons: cmpl.parseProgram.DeclarationList(%T)", value)) panic(fmt.Sprintf("Here be dragons: cmpl.parseProgram.DeclarationList(%T)", value))
} }
} }
return out return out
} }
type _nodeProgram struct { type nodeProgram struct {
body []_nodeStatement body []nodeStatement
varList []string varList []string
functionList []*_nodeFunctionLiteral functionList []*nodeFunctionLiteral
variableList []_nodeDeclaration
file *file.File file *file.File
} }
type _nodeDeclaration struct { type node interface{}
name string
definition _node
}
type _node interface {
}
type ( type (
_nodeExpression interface { nodeExpression interface {
_node node
_expressionNode() expressionNode()
} }
_nodeArrayLiteral struct { nodeArrayLiteral struct {
value []_nodeExpression value []nodeExpression
} }
_nodeAssignExpression struct { nodeAssignExpression struct {
operator token.Token operator token.Token
left _nodeExpression left nodeExpression
right _nodeExpression right nodeExpression
} }
_nodeBinaryExpression struct { nodeBinaryExpression struct {
operator token.Token operator token.Token
left _nodeExpression left nodeExpression
right _nodeExpression right nodeExpression
comparison bool comparison bool
} }
_nodeBracketExpression struct { nodeBracketExpression struct {
idx file.Idx idx file.Idx
left _nodeExpression left nodeExpression
member _nodeExpression member nodeExpression
} }
_nodeCallExpression struct { nodeCallExpression struct {
callee _nodeExpression callee nodeExpression
argumentList []_nodeExpression argumentList []nodeExpression
} }
_nodeConditionalExpression struct { nodeConditionalExpression struct {
test _nodeExpression test nodeExpression
consequent _nodeExpression consequent nodeExpression
alternate _nodeExpression alternate nodeExpression
} }
_nodeDotExpression struct { nodeDotExpression struct {
idx file.Idx idx file.Idx
left _nodeExpression left nodeExpression
identifier string identifier string
} }
_nodeFunctionLiteral struct { nodeFunctionLiteral struct {
name string name string
body _nodeStatement body nodeStatement
source string source string
parameterList []string parameterList []string
varList []string varList []string
functionList []*_nodeFunctionLiteral functionList []*nodeFunctionLiteral
file *file.File file *file.File
} }
_nodeIdentifier struct { nodeIdentifier struct {
idx file.Idx idx file.Idx
name string name string
} }
_nodeLiteral struct { nodeLiteral struct {
value Value value Value
} }
_nodeNewExpression struct { nodeNewExpression struct {
callee _nodeExpression callee nodeExpression
argumentList []_nodeExpression argumentList []nodeExpression
} }
_nodeObjectLiteral struct { nodeObjectLiteral struct {
value []_nodeProperty value []nodeProperty
} }
_nodeProperty struct { nodeProperty struct {
key string key string
kind string kind string
value _nodeExpression value nodeExpression
} }
_nodeRegExpLiteral struct { nodeRegExpLiteral struct {
flags string flags string
pattern string // Value? pattern string // Value?
regexp *regexp.Regexp
} }
_nodeSequenceExpression struct { nodeSequenceExpression struct {
sequence []_nodeExpression sequence []nodeExpression
} }
_nodeThisExpression struct { nodeThisExpression struct{}
}
_nodeUnaryExpression struct { nodeUnaryExpression struct {
operator token.Token operator token.Token
operand _nodeExpression operand nodeExpression
postfix bool postfix bool
} }
_nodeVariableExpression struct { nodeVariableExpression struct {
idx file.Idx idx file.Idx
name string name string
initializer _nodeExpression initializer nodeExpression
} }
) )
type ( type (
_nodeStatement interface { nodeStatement interface {
_node node
_statementNode() statementNode()
} }
_nodeBlockStatement struct { nodeBlockStatement struct {
list []_nodeStatement list []nodeStatement
} }
_nodeBranchStatement struct { nodeBranchStatement struct {
branch token.Token branch token.Token
label string label string
} }
_nodeCaseStatement struct { nodeCaseStatement struct {
test _nodeExpression test nodeExpression
consequent []_nodeStatement consequent []nodeStatement
} }
_nodeCatchStatement struct { nodeCatchStatement struct {
parameter string parameter string
body _nodeStatement body nodeStatement
} }
_nodeDebuggerStatement struct { nodeDebuggerStatement struct{}
nodeDoWhileStatement struct {
test nodeExpression
body []nodeStatement
} }
_nodeDoWhileStatement struct { nodeEmptyStatement struct{}
test _nodeExpression
body []_nodeStatement nodeExpressionStatement struct {
expression nodeExpression
} }
_nodeEmptyStatement struct { nodeForInStatement struct {
into nodeExpression
source nodeExpression
body []nodeStatement
} }
_nodeExpressionStatement struct { nodeForStatement struct {
expression _nodeExpression initializer nodeExpression
update nodeExpression
test nodeExpression
body []nodeStatement
} }
_nodeForInStatement struct { nodeIfStatement struct {
into _nodeExpression test nodeExpression
source _nodeExpression consequent nodeStatement
body []_nodeStatement alternate nodeStatement
} }
_nodeForStatement struct { nodeLabelledStatement struct {
initializer _nodeExpression
update _nodeExpression
test _nodeExpression
body []_nodeStatement
}
_nodeIfStatement struct {
test _nodeExpression
consequent _nodeStatement
alternate _nodeStatement
}
_nodeLabelledStatement struct {
label string label string
statement _nodeStatement statement nodeStatement
} }
_nodeReturnStatement struct { nodeReturnStatement struct {
argument _nodeExpression argument nodeExpression
} }
_nodeSwitchStatement struct { nodeSwitchStatement struct {
discriminant _nodeExpression discriminant nodeExpression
default_ int defaultIdx int
body []*_nodeCaseStatement body []*nodeCaseStatement
} }
_nodeThrowStatement struct { nodeThrowStatement struct {
argument _nodeExpression argument nodeExpression
} }
_nodeTryStatement struct { nodeTryStatement struct {
body _nodeStatement body nodeStatement
catch *_nodeCatchStatement catch *nodeCatchStatement
finally _nodeStatement finally nodeStatement
} }
_nodeVariableStatement struct { nodeVariableStatement struct {
list []_nodeExpression list []nodeExpression
} }
_nodeWhileStatement struct { nodeWhileStatement struct {
test _nodeExpression test nodeExpression
body []_nodeStatement body []nodeStatement
} }
_nodeWithStatement struct { nodeWithStatement struct {
object _nodeExpression object nodeExpression
body _nodeStatement body nodeStatement
} }
) )
// _expressionNode // expressionNode.
func (*nodeArrayLiteral) expressionNode() {}
func (*nodeAssignExpression) expressionNode() {}
func (*nodeBinaryExpression) expressionNode() {}
func (*nodeBracketExpression) expressionNode() {}
func (*nodeCallExpression) expressionNode() {}
func (*nodeConditionalExpression) expressionNode() {}
func (*nodeDotExpression) expressionNode() {}
func (*nodeFunctionLiteral) expressionNode() {}
func (*nodeIdentifier) expressionNode() {}
func (*nodeLiteral) expressionNode() {}
func (*nodeNewExpression) expressionNode() {}
func (*nodeObjectLiteral) expressionNode() {}
func (*nodeRegExpLiteral) expressionNode() {}
func (*nodeSequenceExpression) expressionNode() {}
func (*nodeThisExpression) expressionNode() {}
func (*nodeUnaryExpression) expressionNode() {}
func (*nodeVariableExpression) expressionNode() {}
func (*_nodeArrayLiteral) _expressionNode() {} // statementNode
func (*_nodeAssignExpression) _expressionNode() {}
func (*_nodeBinaryExpression) _expressionNode() {}
func (*_nodeBracketExpression) _expressionNode() {}
func (*_nodeCallExpression) _expressionNode() {}
func (*_nodeConditionalExpression) _expressionNode() {}
func (*_nodeDotExpression) _expressionNode() {}
func (*_nodeFunctionLiteral) _expressionNode() {}
func (*_nodeIdentifier) _expressionNode() {}
func (*_nodeLiteral) _expressionNode() {}
func (*_nodeNewExpression) _expressionNode() {}
func (*_nodeObjectLiteral) _expressionNode() {}
func (*_nodeRegExpLiteral) _expressionNode() {}
func (*_nodeSequenceExpression) _expressionNode() {}
func (*_nodeThisExpression) _expressionNode() {}
func (*_nodeUnaryExpression) _expressionNode() {}
func (*_nodeVariableExpression) _expressionNode() {}
// _statementNode func (*nodeBlockStatement) statementNode() {}
func (*nodeBranchStatement) statementNode() {}
func (*_nodeBlockStatement) _statementNode() {} func (*nodeCaseStatement) statementNode() {}
func (*_nodeBranchStatement) _statementNode() {} func (*nodeCatchStatement) statementNode() {}
func (*_nodeCaseStatement) _statementNode() {} func (*nodeDebuggerStatement) statementNode() {}
func (*_nodeCatchStatement) _statementNode() {} func (*nodeDoWhileStatement) statementNode() {}
func (*_nodeDebuggerStatement) _statementNode() {} func (*nodeEmptyStatement) statementNode() {}
func (*_nodeDoWhileStatement) _statementNode() {} func (*nodeExpressionStatement) statementNode() {}
func (*_nodeEmptyStatement) _statementNode() {} func (*nodeForInStatement) statementNode() {}
func (*_nodeExpressionStatement) _statementNode() {} func (*nodeForStatement) statementNode() {}
func (*_nodeForInStatement) _statementNode() {} func (*nodeIfStatement) statementNode() {}
func (*_nodeForStatement) _statementNode() {} func (*nodeLabelledStatement) statementNode() {}
func (*_nodeIfStatement) _statementNode() {} func (*nodeReturnStatement) statementNode() {}
func (*_nodeLabelledStatement) _statementNode() {} func (*nodeSwitchStatement) statementNode() {}
func (*_nodeReturnStatement) _statementNode() {} func (*nodeThrowStatement) statementNode() {}
func (*_nodeSwitchStatement) _statementNode() {} func (*nodeTryStatement) statementNode() {}
func (*_nodeThrowStatement) _statementNode() {} func (*nodeVariableStatement) statementNode() {}
func (*_nodeTryStatement) _statementNode() {} func (*nodeWhileStatement) statementNode() {}
func (*_nodeVariableStatement) _statementNode() {} func (*nodeWithStatement) statementNode() {}
func (*_nodeWhileStatement) _statementNode() {}
func (*_nodeWithStatement) _statementNode() {}

View file

@ -14,37 +14,33 @@ func formatForConsole(argumentList []Value) string {
return strings.Join(output, " ") return strings.Join(output, " ")
} }
func builtinConsole_log(call FunctionCall) Value { func builtinConsoleLog(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList)) fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return Value{} return Value{}
} }
func builtinConsole_error(call FunctionCall) Value { func builtinConsoleError(call FunctionCall) Value {
fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList)) fmt.Fprintln(os.Stdout, formatForConsole(call.ArgumentList))
return Value{} return Value{}
} }
// Nothing happens. // Nothing happens.
func builtinConsole_dir(call FunctionCall) Value { func builtinConsoleDir(call FunctionCall) Value {
return Value{} return Value{}
} }
func builtinConsole_time(call FunctionCall) Value { func builtinConsoleTime(call FunctionCall) Value {
return Value{} return Value{}
} }
func builtinConsole_timeEnd(call FunctionCall) Value { func builtinConsoleTimeEnd(call FunctionCall) Value {
return Value{} return Value{}
} }
func builtinConsole_trace(call FunctionCall) Value { func builtinConsoleTrace(call FunctionCall) Value {
return Value{} return Value{}
} }
func builtinConsole_assert(call FunctionCall) Value { func builtinConsoleAssert(call FunctionCall) Value {
return Value{} return Value{}
} }
func (runtime *_runtime) newConsole() *_object {
return newConsoleObject(runtime)
}

View file

@ -2,18 +2,34 @@ package otto
const ( const (
// Common classes. // Common classes.
classString = "String" classStringName = "String"
classGoArray = "GoArray" classGoArrayName = "GoArray"
classGoSlice = "GoSlice" classGoSliceName = "GoSlice"
classNumber = "Number" classNumberName = "Number"
classDate = "Date" classDateName = "Date"
classArray = "Array" classArrayName = "Array"
classFunction = "Function" classFunctionName = "Function"
classObject = "Object" classObjectName = "Object"
classRegExp = "RegExp" classRegExpName = "RegExp"
classBoolean = "Boolean" classBooleanName = "Boolean"
classError = "Error" classMathName = "Math"
classJSONName = "JSON"
// Error classes.
classErrorName = "Error"
classEvalErrorName = "EvalError"
classTypeErrorName = "TypeError"
classRangeErrorName = "RangeError"
classReferenceErrorName = "ReferenceError"
classSyntaxErrorName = "SyntaxError"
classURIErrorName = "URIError"
// Common properties. // Common properties.
propertyName = "name"
propertyLength = "length" propertyLength = "length"
propertyPrototype = "prototype"
propertyConstructor = "constructor"
// Common methods.
methodToString = "toString"
) )

View file

@ -52,7 +52,6 @@ a customization function:
dbg, dbgf := New(func(dbgr *Dbgr) { dbg, dbgf := New(func(dbgr *Dbgr) {
dbgr.SetOutput(&buffer) dbgr.SetOutput(&buffer)
}) })
*/ */
package dbg package dbg
@ -63,7 +62,7 @@ import (
"log" "log"
"os" "os"
"regexp" "regexp"
"runtime" goruntime "runtime"
"strings" "strings"
"unicode" "unicode"
) )
@ -130,14 +129,13 @@ func parseFormat(format string) (frmt _frmt) {
} }
type Dbgr struct { type Dbgr struct {
emit _emit emit emit
} }
type DbgFunction func(values ...interface{}) type DbgFunction func(values ...interface{})
func NewDbgr() *Dbgr { func NewDbgr() *Dbgr {
self := &Dbgr{} return &Dbgr{}
return self
} }
/* /*
@ -153,7 +151,6 @@ they output to by passing in an (optional) customization function:
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) { dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr) dbgr.SetOutput(os.Stderr)
}) })
*/ */
func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) { func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) {
dbgr := NewDbgr() dbgr := NewDbgr()
@ -165,26 +162,25 @@ func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) {
return dbgr.DbgDbgf() return dbgr.DbgDbgf()
} }
func (self Dbgr) Dbg(values ...interface{}) { func (d Dbgr) Dbg(values ...interface{}) {
self.getEmit().emit(_frmt{}, "", values...) d.getEmit().emit(_frmt{}, "", values...)
} }
func (self Dbgr) Dbgf(values ...interface{}) { func (d Dbgr) Dbgf(values ...interface{}) {
self.dbgf(values...) d.dbgf(values...)
} }
func (self Dbgr) DbgDbgf() (dbg DbgFunction, dbgf DbgFunction) { func (d Dbgr) DbgDbgf() (dbg DbgFunction, dbgf DbgFunction) {
dbg = func(vl ...interface{}) { dbg = func(vl ...interface{}) {
self.Dbg(vl...) d.Dbg(vl...)
} }
dbgf = func(vl ...interface{}) { dbgf = func(vl ...interface{}) {
self.dbgf(vl...) d.dbgf(vl...)
} }
return dbg, dbgf // Redundant, but... return dbg, dbgf // Redundant, but...
} }
func (self Dbgr) dbgf(values ...interface{}) { func (d Dbgr) dbgf(values ...interface{}) {
var frmt _frmt var frmt _frmt
if len(values) > 0 { if len(values) > 0 {
tmp := fmt.Sprint(values[0]) tmp := fmt.Sprint(values[0])
@ -192,7 +188,7 @@ func (self Dbgr) dbgf(values ...interface{}) {
values = values[1:] values = values[1:]
} }
buffer_f := bytes.Buffer{} buf := bytes.Buffer{}
format := frmt.format format := frmt.format
end := len(format) end := len(format)
for at := 0; at < end; { for at := 0; at < end; {
@ -201,7 +197,7 @@ func (self Dbgr) dbgf(values ...interface{}) {
at++ at++
} }
if at > last { if at > last {
buffer_f.WriteString(format[last:at]) buf.WriteString(format[last:at])
} }
if at >= end { if at >= end {
break break
@ -211,72 +207,70 @@ func (self Dbgr) dbgf(values ...interface{}) {
// format[at] == ? // format[at] == ?
if format[at] == '@' { if format[at] == '@' {
depth := 2 depth := 2
pc, _, _, _ := runtime.Caller(depth) pc, _, _, _ := goruntime.Caller(depth)
name := runtime.FuncForPC(pc).Name() name := goruntime.FuncForPC(pc).Name()
buffer_f.WriteString(name) buf.WriteString(name)
} else { } else {
buffer_f.WriteString(format[at-1 : at+1]) buf.WriteString(format[at-1 : at+1])
} }
at++ at++
} }
//values_f := append([]interface{}{}, values[0:frmt.operandCount]...) //valuesF := append([]interface{}{}, values[0:frmt.operandCount]...)
values_f := values[0:frmt.operandCount] valuesF := values[0:frmt.operandCount]
values_dbg := values[frmt.operandCount:] valuesDbg := values[frmt.operandCount:]
if len(values_dbg) > 0 { if len(valuesDbg) > 0 {
// Adjust frmt.format: // Adjust frmt.format:
// (%v instead of %s because: frmt.check) // (%v instead of %s because: frmt.check)
{
tmp := format tmp := format
if len(tmp) > 0 { if len(tmp) > 0 {
if unicode.IsSpace(rune(tmp[len(tmp)-1])) { if unicode.IsSpace(rune(tmp[len(tmp)-1])) {
buffer_f.WriteString("%v") buf.WriteString("%v")
} else { } else {
buffer_f.WriteString(" %v") buf.WriteString(" %v")
} }
} else if frmt.check { } else if frmt.check {
// Performing a check, so no output // Performing a check, so no output
} else { } else {
buffer_f.WriteString("%v") buf.WriteString("%v")
}
} }
// Adjust values_f: // Adjust valuesF:
if !frmt.check { if !frmt.check {
tmp := []string{} tmp := []string{}
for _, value := range values_dbg { for _, value := range valuesDbg {
tmp = append(tmp, fmt.Sprintf("%v", value)) tmp = append(tmp, fmt.Sprintf("%v", value))
} }
// First, make a copy of values_f, so we avoid overwriting values_dbg when appending // First, make a copy of valuesF, so we avoid overwriting valuesDbg when appending
values_f = append([]interface{}{}, values_f...) valuesF = append([]interface{}{}, valuesF...)
values_f = append(values_f, strings.Join(tmp, " ")) valuesF = append(valuesF, strings.Join(tmp, " "))
} }
} }
format = buffer_f.String() format = buf.String()
if frmt.check { if frmt.check {
// We do not actually emit to the log, but panic if // We do not actually emit to the log, but panic if
// a non-nil value is detected (e.g. a non-nil error) // a non-nil value is detected (e.g. a non-nil error)
for _, value := range values_dbg { for _, value := range valuesDbg {
if value != nil { if value != nil {
if format == "" { if format == "" {
panic(value) panic(value)
} else { } else {
panic(fmt.Sprintf(format, append(values_f, value)...)) panic(fmt.Sprintf(format, append(valuesF, value)...))
} }
} }
} }
} else { } else {
self.getEmit().emit(frmt, format, values_f...) d.getEmit().emit(frmt, format, valuesF...)
} }
} }
// Idiot-proof &Dbgr{}, etc. // Idiot-proof &Dbgr{}, etc.
func (self *Dbgr) getEmit() _emit { func (d *Dbgr) getEmit() emit {
if self.emit == nil { if d.emit == nil {
self.emit = standardEmit() d.emit = standardEmit()
} }
return self.emit return d.emit
} }
// SetOutput will accept the following as a destination for output: // SetOutput will accept the following as a destination for output:
@ -285,26 +279,25 @@ func (self *Dbgr) getEmit() _emit {
// io.Writer - // io.Writer -
// nil Reset to the default output (os.Stderr) // nil Reset to the default output (os.Stderr)
// "log" Print*/Panic*/Fatal* via the "log" package // "log" Print*/Panic*/Fatal* via the "log" package
// func (d *Dbgr) SetOutput(output interface{}) {
func (self *Dbgr) SetOutput(output interface{}) {
if output == nil { if output == nil {
self.emit = standardEmit() d.emit = standardEmit()
return return
} }
switch output := output.(type) { switch output := output.(type) {
case *log.Logger: case *log.Logger:
self.emit = _emitLogger{ d.emit = emitLogger{
logger: output, logger: output,
} }
return return
case io.Writer: case io.Writer:
self.emit = _emitWriter{ d.emit = emitWriter{
writer: output, writer: output,
} }
return return
case string: case string:
if output == "log" { if output == "log" {
self.emit = _emitLog{} d.emit = emitLog{}
return return
} }
} }
@ -315,8 +308,8 @@ func (self *Dbgr) SetOutput(output interface{}) {
// = emit = // // = emit = //
// ======== // // ======== //
func standardEmit() _emit { func standardEmit() emit {
return _emitWriter{ return emitWriter{
writer: os.Stderr, writer: os.Stderr,
} }
} }
@ -329,50 +322,50 @@ func ln(tmp string) string {
return tmp return tmp
} }
type _emit interface { type emit interface {
emit(_frmt, string, ...interface{}) emit(_frmt, string, ...interface{})
} }
type _emitWriter struct { type emitWriter struct {
writer io.Writer writer io.Writer
} }
func (self _emitWriter) emit(frmt _frmt, format string, values ...interface{}) { func (ew emitWriter) emit(frmt _frmt, format string, values ...interface{}) {
if format == "" { if format == "" {
fmt.Fprintln(self.writer, values...) fmt.Fprintln(ew.writer, values...)
} else { } else {
if frmt.panic { if frmt.panic {
panic(fmt.Sprintf(format, values...)) panic(fmt.Sprintf(format, values...))
} }
fmt.Fprintf(self.writer, ln(format), values...) fmt.Fprintf(ew.writer, ln(format), values...)
if frmt.fatal { if frmt.fatal {
os.Exit(1) os.Exit(1)
} }
} }
} }
type _emitLogger struct { type emitLogger struct {
logger *log.Logger logger *log.Logger
} }
func (self _emitLogger) emit(frmt _frmt, format string, values ...interface{}) { func (el emitLogger) emit(frmt _frmt, format string, values ...interface{}) {
if format == "" { if format == "" {
self.logger.Println(values...) el.logger.Println(values...)
} else { } else {
if frmt.panic { if frmt.panic {
self.logger.Panicf(format, values...) el.logger.Panicf(format, values...)
} else if frmt.fatal { } else if frmt.fatal {
self.logger.Fatalf(format, values...) el.logger.Fatalf(format, values...)
} else { } else {
self.logger.Printf(format, values...) el.logger.Printf(format, values...)
} }
} }
} }
type _emitLog struct { type emitLog struct {
} }
func (self _emitLog) emit(frmt _frmt, format string, values ...interface{}) { func (el emitLog) emit(frmt _frmt, format string, values ...interface{}) {
if format == "" { if format == "" {
log.Println(values...) log.Println(values...)
} else { } else {

View file

@ -7,49 +7,49 @@ import (
"github.com/robertkrimen/otto/file" "github.com/robertkrimen/otto/file"
) )
type _exception struct { type exception struct {
value interface{} value interface{}
} }
func newException(value interface{}) *_exception { func newException(value interface{}) *exception {
return &_exception{ return &exception{
value: value, value: value,
} }
} }
func (self *_exception) eject() interface{} { func (e *exception) eject() interface{} {
value := self.value value := e.value
self.value = nil // Prevent Go from holding on to the value, whatever it is e.value = nil // Prevent Go from holding on to the value, whatever it is
return value return value
} }
type _error struct { type ottoError struct {
name string name string
message string message string
trace []_frame trace []frame
offset int offset int
} }
func (err _error) format() string { func (e ottoError) format() string {
if len(err.name) == 0 { if len(e.name) == 0 {
return err.message return e.message
} }
if len(err.message) == 0 { if len(e.message) == 0 {
return err.name return e.name
} }
return fmt.Sprintf("%s: %s", err.name, err.message) return fmt.Sprintf("%s: %s", e.name, e.message)
} }
func (err _error) formatWithStack() string { func (e ottoError) formatWithStack() string {
str := err.format() + "\n" str := e.format() + "\n"
for _, frame := range err.trace { for _, frm := range e.trace {
str += " at " + frame.location() + "\n" str += " at " + frm.location() + "\n"
} }
return str return str
} }
type _frame struct { type frame struct {
native bool native bool
nativeFile string nativeFile string
nativeLine int nativeLine int
@ -59,13 +59,11 @@ type _frame struct {
fn interface{} fn interface{}
} }
var ( var nativeFrame = frame{}
nativeFrame = _frame{}
)
type _at int type at int
func (fr _frame) location() string { func (fr frame) location() string {
str := "<unknown>" str := "<unknown>"
switch { switch {
@ -95,14 +93,14 @@ func (fr _frame) location() string {
// An Error represents a runtime error, e.g. a TypeError, a ReferenceError, etc. // An Error represents a runtime error, e.g. a TypeError, a ReferenceError, etc.
type Error struct { type Error struct {
_error ottoError
} }
// Error returns a description of the error // Error returns a description of the error
// //
// TypeError: 'def' is not a function // TypeError: 'def' is not a function
func (err Error) Error() string { func (e Error) Error() string {
return err.format() return e.format()
} }
// String returns a description of the error and a trace of where the // String returns a description of the error and a trace of where the
@ -111,36 +109,36 @@ func (err Error) Error() string {
// TypeError: 'def' is not a function // TypeError: 'def' is not a function
// at xyz (<anonymous>:3:9) // at xyz (<anonymous>:3:9)
// at <anonymous>:7:1/ // at <anonymous>:7:1/
func (err Error) String() string { func (e Error) String() string {
return err.formatWithStack() return e.formatWithStack()
} }
// GoString returns a description of the error and a trace of where the // GoString returns a description of the error and a trace of where the
// error occurred. Printing with %#v will trigger this behaviour. // error occurred. Printing with %#v will trigger this behaviour.
func (err Error) GoString() string { func (e Error) GoString() string {
return err.formatWithStack() return e.formatWithStack()
} }
func (err _error) describe(format string, in ...interface{}) string { func (e ottoError) describe(format string, in ...interface{}) string {
return fmt.Sprintf(format, in...) return fmt.Sprintf(format, in...)
} }
func (self _error) messageValue() Value { func (e ottoError) messageValue() Value {
if self.message == "" { if e.message == "" {
return Value{} return Value{}
} }
return toValue_string(self.message) return stringValue(e.message)
} }
func (rt *_runtime) typeErrorResult(throw bool) bool { func (rt *runtime) typeErrorResult(throw bool) bool {
if throw { if throw {
panic(rt.panicTypeError()) panic(rt.panicTypeError())
} }
return false return false
} }
func newError(rt *_runtime, name string, stackFramesToPop int, in ...interface{}) _error { func newError(rt *runtime, name string, stackFramesToPop int, in ...interface{}) ottoError {
err := _error{ err := ottoError{
name: name, name: name,
offset: -1, offset: -1,
} }
@ -148,21 +146,21 @@ func newError(rt *_runtime, name string, stackFramesToPop int, in ...interface{}
length := len(in) length := len(in)
if rt != nil && rt.scope != nil { if rt != nil && rt.scope != nil {
scope := rt.scope curScope := rt.scope
for i := 0; i < stackFramesToPop; i++ { for i := 0; i < stackFramesToPop; i++ {
if scope.outer != nil { if curScope.outer != nil {
scope = scope.outer curScope = curScope.outer
} }
} }
frame := scope.frame frm := curScope.frame
if length > 0 { if length > 0 {
if at, ok := in[length-1].(_at); ok { if atv, ok := in[length-1].(at); ok {
in = in[0 : length-1] in = in[0 : length-1]
if scope != nil { if curScope != nil {
frame.offset = int(at) frm.offset = int(atv)
} }
length-- length--
} }
@ -173,54 +171,52 @@ func newError(rt *_runtime, name string, stackFramesToPop int, in ...interface{}
limit := rt.traceLimit limit := rt.traceLimit
err.trace = append(err.trace, frame) err.trace = append(err.trace, frm)
if scope != nil { if curScope != nil {
for scope = scope.outer; scope != nil; scope = scope.outer { for curScope = curScope.outer; curScope != nil; curScope = curScope.outer {
if limit--; limit == 0 { if limit--; limit == 0 {
break break
} }
if scope.frame.offset >= 0 { if curScope.frame.offset >= 0 {
err.trace = append(err.trace, scope.frame) err.trace = append(err.trace, curScope.frame)
} }
} }
} }
} else { } else if length > 0 {
if length > 0 {
description, in = in[0].(string), in[1:] description, in = in[0].(string), in[1:]
} }
}
err.message = err.describe(description, in...) err.message = err.describe(description, in...)
return err return err
} }
func (rt *_runtime) panicTypeError(argumentList ...interface{}) *_exception { func (rt *runtime) panicTypeError(argumentList ...interface{}) *exception {
return &_exception{ return &exception{
value: newError(rt, "TypeError", 0, argumentList...), value: newError(rt, "TypeError", 0, argumentList...),
} }
} }
func (rt *_runtime) panicReferenceError(argumentList ...interface{}) *_exception { func (rt *runtime) panicReferenceError(argumentList ...interface{}) *exception {
return &_exception{ return &exception{
value: newError(rt, "ReferenceError", 0, argumentList...), value: newError(rt, "ReferenceError", 0, argumentList...),
} }
} }
func (rt *_runtime) panicURIError(argumentList ...interface{}) *_exception { func (rt *runtime) panicURIError(argumentList ...interface{}) *exception {
return &_exception{ return &exception{
value: newError(rt, "URIError", 0, argumentList...), value: newError(rt, "URIError", 0, argumentList...),
} }
} }
func (rt *_runtime) panicSyntaxError(argumentList ...interface{}) *_exception { func (rt *runtime) panicSyntaxError(argumentList ...interface{}) *exception {
return &_exception{ return &exception{
value: newError(rt, "SyntaxError", 0, argumentList...), value: newError(rt, "SyntaxError", 0, argumentList...),
} }
} }
func (rt *_runtime) panicRangeError(argumentList ...interface{}) *_exception { func (rt *runtime) panicRangeError(argumentList ...interface{}) *exception {
return &_exception{ return &exception{
value: newError(rt, "RangeError", 0, argumentList...), value: newError(rt, "RangeError", 0, argumentList...),
} }
} }
@ -228,20 +224,19 @@ func (rt *_runtime) panicRangeError(argumentList ...interface{}) *_exception {
func catchPanic(function func()) (err error) { func catchPanic(function func()) (err error) {
defer func() { defer func() {
if caught := recover(); caught != nil { if caught := recover(); caught != nil {
if exception, ok := caught.(*_exception); ok { if excep, ok := caught.(*exception); ok {
caught = exception.eject() caught = excep.eject()
} }
switch caught := caught.(type) { switch caught := caught.(type) {
case *Error: case *Error:
err = caught err = caught
return return
case _error: case ottoError:
err = &Error{caught} err = &Error{caught}
return return
case Value: case Value:
if vl := caught._object(); vl != nil { if vl := caught.object(); vl != nil {
switch vl := vl.value.(type) { if vl, ok := vl.value.(ottoError); ok {
case _error:
err = &Error{vl} err = &Error{vl}
return return
} }

View file

@ -8,12 +8,12 @@ import (
"github.com/robertkrimen/otto/token" "github.com/robertkrimen/otto/token"
) )
func (self *_runtime) evaluateMultiply(left float64, right float64) Value { func (rt *runtime) evaluateMultiply(left float64, right float64) Value { //nolint: unused
// TODO 11.5.1 // TODO 11.5.1
return Value{} return Value{}
} }
func (self *_runtime) evaluateDivide(left float64, right float64) Value { func (rt *runtime) evaluateDivide(left float64, right float64) Value {
if math.IsNaN(left) || math.IsNaN(right) { if math.IsNaN(left) || math.IsNaN(right) {
return NaNValue() return NaNValue()
} }
@ -26,61 +26,57 @@ func (self *_runtime) evaluateDivide(left float64, right float64) Value {
if math.IsInf(left, 0) { if math.IsInf(left, 0) {
if math.Signbit(left) == math.Signbit(right) { if math.Signbit(left) == math.Signbit(right) {
return positiveInfinityValue() return positiveInfinityValue()
} else {
return negativeInfinityValue()
} }
return negativeInfinityValue()
} }
if math.IsInf(right, 0) { if math.IsInf(right, 0) {
if math.Signbit(left) == math.Signbit(right) { if math.Signbit(left) == math.Signbit(right) {
return positiveZeroValue() return positiveZeroValue()
} else {
return negativeZeroValue()
} }
return negativeZeroValue()
} }
if right == 0 { if right == 0 {
if math.Signbit(left) == math.Signbit(right) { if math.Signbit(left) == math.Signbit(right) {
return positiveInfinityValue() return positiveInfinityValue()
} else { }
return negativeInfinityValue() return negativeInfinityValue()
} }
} return float64Value(left / right)
return toValue_float64(left / right)
} }
func (self *_runtime) evaluateModulo(left float64, right float64) Value { func (rt *runtime) evaluateModulo(left float64, right float64) Value { //nolint: unused
// TODO 11.5.3 // TODO 11.5.3
return Value{} return Value{}
} }
func (self *_runtime) calculateBinaryExpression(operator token.Token, left Value, right Value) Value { func (rt *runtime) calculateBinaryExpression(operator token.Token, left Value, right Value) Value {
leftValue := left.resolve() leftValue := left.resolve()
switch operator { switch operator {
// Additive // Additive
case token.PLUS: case token.PLUS:
leftValue = toPrimitive(leftValue) leftValue = toPrimitiveValue(leftValue)
rightValue := right.resolve() rightValue := right.resolve()
rightValue = toPrimitive(rightValue) rightValue = toPrimitiveValue(rightValue)
if leftValue.IsString() || rightValue.IsString() { if leftValue.IsString() || rightValue.IsString() {
return toValue_string(strings.Join([]string{leftValue.string(), rightValue.string()}, "")) return stringValue(strings.Join([]string{leftValue.string(), rightValue.string()}, ""))
} else {
return toValue_float64(leftValue.float64() + rightValue.float64())
} }
return float64Value(leftValue.float64() + rightValue.float64())
case token.MINUS: case token.MINUS:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_float64(leftValue.float64() - rightValue.float64()) return float64Value(leftValue.float64() - rightValue.float64())
// Multiplicative // Multiplicative
case token.MULTIPLY: case token.MULTIPLY:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_float64(leftValue.float64() * rightValue.float64()) return float64Value(leftValue.float64() * rightValue.float64())
case token.SLASH: case token.SLASH:
rightValue := right.resolve() rightValue := right.resolve()
return self.evaluateDivide(leftValue.float64(), rightValue.float64()) return rt.evaluateDivide(leftValue.float64(), rightValue.float64())
case token.REMAINDER: case token.REMAINDER:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_float64(math.Mod(leftValue.float64(), rightValue.float64())) return float64Value(math.Mod(leftValue.float64(), rightValue.float64()))
// Logical // Logical
case token.LOGICAL_AND: case token.LOGICAL_AND:
@ -88,65 +84,65 @@ func (self *_runtime) calculateBinaryExpression(operator token.Token, left Value
if !left { if !left {
return falseValue return falseValue
} }
return toValue_bool(right.resolve().bool()) return boolValue(right.resolve().bool())
case token.LOGICAL_OR: case token.LOGICAL_OR:
left := leftValue.bool() left := leftValue.bool()
if left { if left {
return trueValue return trueValue
} }
return toValue_bool(right.resolve().bool()) return boolValue(right.resolve().bool())
// Bitwise // Bitwise
case token.AND: case token.AND:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) & toInt32(rightValue)) return int32Value(toInt32(leftValue) & toInt32(rightValue))
case token.OR: case token.OR:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) | toInt32(rightValue)) return int32Value(toInt32(leftValue) | toInt32(rightValue))
case token.EXCLUSIVE_OR: case token.EXCLUSIVE_OR:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) ^ toInt32(rightValue)) return int32Value(toInt32(leftValue) ^ toInt32(rightValue))
// Shift // Shift
// (Masking of 0x1f is to restrict the shift to a maximum of 31 places) // (Masking of 0x1f is to restrict the shift to a maximum of 31 places)
case token.SHIFT_LEFT: case token.SHIFT_LEFT:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) << (toUint32(rightValue) & 0x1f)) return int32Value(toInt32(leftValue) << (toUint32(rightValue) & 0x1f))
case token.SHIFT_RIGHT: case token.SHIFT_RIGHT:
rightValue := right.resolve() rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f)) return int32Value(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.UNSIGNED_SHIFT_RIGHT: case token.UNSIGNED_SHIFT_RIGHT:
rightValue := right.resolve() rightValue := right.resolve()
// Shifting an unsigned integer is a logical shift // Shifting an unsigned integer is a logical shift
return toValue_uint32(toUint32(leftValue) >> (toUint32(rightValue) & 0x1f)) return uint32Value(toUint32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.INSTANCEOF: case token.INSTANCEOF:
rightValue := right.resolve() rightValue := right.resolve()
if !rightValue.IsObject() { if !rightValue.IsObject() {
panic(self.panicTypeError("Expecting a function in instanceof check, but got: %v", rightValue)) panic(rt.panicTypeError("invalid kind %s for instanceof (expected object)", rightValue.kind))
} }
return toValue_bool(rightValue._object().hasInstance(leftValue)) return boolValue(rightValue.object().hasInstance(leftValue))
case token.IN: case token.IN:
rightValue := right.resolve() rightValue := right.resolve()
if !rightValue.IsObject() { if !rightValue.IsObject() {
panic(self.panicTypeError()) panic(rt.panicTypeError("invalid kind %s for in (expected object)", rightValue.kind))
} }
return toValue_bool(rightValue._object().hasProperty(leftValue.string())) return boolValue(rightValue.object().hasProperty(leftValue.string()))
} }
panic(hereBeDragons(operator)) panic(hereBeDragons(operator))
} }
type _lessThanResult int type lessThanResult int
const ( const (
lessThanFalse _lessThanResult = iota lessThanFalse lessThanResult = iota
lessThanTrue lessThanTrue
lessThanUndefined lessThanUndefined
) )
func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult { func calculateLessThan(left Value, right Value, leftFirst bool) lessThanResult {
var x, y Value var x, y Value
if leftFirst { if leftFirst {
x = toNumberPrimitive(left) x = toNumberPrimitive(left)
@ -175,46 +171,46 @@ func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult
return lessThanFalse return lessThanFalse
} }
// FIXME Probably a map is not the most efficient way to do this // FIXME Probably a map is not the most efficient way to do this.
var lessThanTable [4](map[_lessThanResult]bool) = [4](map[_lessThanResult]bool){ var lessThanTable [4](map[lessThanResult]bool) = [4](map[lessThanResult]bool){
// < // <
map[_lessThanResult]bool{ map[lessThanResult]bool{
lessThanFalse: false, lessThanFalse: false,
lessThanTrue: true, lessThanTrue: true,
lessThanUndefined: false, lessThanUndefined: false,
}, },
// > // >
map[_lessThanResult]bool{ map[lessThanResult]bool{
lessThanFalse: false, lessThanFalse: false,
lessThanTrue: true, lessThanTrue: true,
lessThanUndefined: false, lessThanUndefined: false,
}, },
// <= // <=
map[_lessThanResult]bool{ map[lessThanResult]bool{
lessThanFalse: true, lessThanFalse: true,
lessThanTrue: false, lessThanTrue: false,
lessThanUndefined: false, lessThanUndefined: false,
}, },
// >= // >=
map[_lessThanResult]bool{ map[lessThanResult]bool{
lessThanFalse: true, lessThanFalse: true,
lessThanTrue: false, lessThanTrue: false,
lessThanUndefined: false, lessThanUndefined: false,
}, },
} }
func (self *_runtime) calculateComparison(comparator token.Token, left Value, right Value) bool { func (rt *runtime) calculateComparison(comparator token.Token, left Value, right Value) bool {
// FIXME Use strictEqualityComparison? // FIXME Use strictEqualityComparison?
// TODO This might be redundant now (with regards to evaluateComparison) // TODO This might be redundant now (with regards to evaluateComparison)
x := left.resolve() x := left.resolve()
y := right.resolve() y := right.resolve()
kindEqualKind := false var kindEqualKind bool
var negate bool
result := true result := true
negate := false
switch comparator { switch comparator {
case token.LESS: case token.LESS:
@ -238,27 +234,28 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
negate = true negate = true
fallthrough fallthrough
case token.EQUAL: case token.EQUAL:
if x.kind == y.kind { switch {
case x.kind == y.kind:
kindEqualKind = true kindEqualKind = true
} else if x.kind <= valueNull && y.kind <= valueNull { case x.kind <= valueNull && y.kind <= valueNull:
result = true result = true
} else if x.kind <= valueNull || y.kind <= valueNull { case x.kind <= valueNull || y.kind <= valueNull:
result = false result = false
} else if x.kind <= valueString && y.kind <= valueString { case x.kind <= valueString && y.kind <= valueString:
result = x.float64() == y.float64() result = x.float64() == y.float64()
} else if x.kind == valueBoolean { case x.kind == valueBoolean:
result = self.calculateComparison(token.EQUAL, toValue_float64(x.float64()), y) result = rt.calculateComparison(token.EQUAL, float64Value(x.float64()), y)
} else if y.kind == valueBoolean { case y.kind == valueBoolean:
result = self.calculateComparison(token.EQUAL, x, toValue_float64(y.float64())) result = rt.calculateComparison(token.EQUAL, x, float64Value(y.float64()))
} else if x.kind == valueObject { case x.kind == valueObject:
result = self.calculateComparison(token.EQUAL, toPrimitive(x), y) result = rt.calculateComparison(token.EQUAL, toPrimitiveValue(x), y)
} else if y.kind == valueObject { case y.kind == valueObject:
result = self.calculateComparison(token.EQUAL, x, toPrimitive(y)) result = rt.calculateComparison(token.EQUAL, x, toPrimitiveValue(y))
} else { default:
panic(hereBeDragons("Unable to test for equality: %v ==? %v", x, y)) panic(fmt.Sprintf("unknown types for equal: %v ==? %v", x, y))
} }
default: default:
panic(fmt.Errorf("Unknown comparator %s", comparator.String())) panic(fmt.Sprintf("unknown comparator %s", comparator.String()))
} }
if kindEqualKind { if kindEqualKind {
@ -278,7 +275,7 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
case valueBoolean: case valueBoolean:
result = x.bool() == y.bool() result = x.bool() == y.bool()
case valueObject: case valueObject:
result = x._object() == y._object() result = x.object() == y.object()
default: default:
goto ERROR goto ERROR
} }

View file

@ -1,110 +0,0 @@
# file
--
import "github.com/robertkrimen/otto/file"
Package file encapsulates the file abstractions used by the ast & parser.
## Usage
#### type File
```go
type File struct {
}
```
#### func NewFile
```go
func NewFile(filename, src string, base int) *File
```
#### func (*File) Base
```go
func (fl *File) Base() int
```
#### func (*File) Name
```go
func (fl *File) Name() string
```
#### func (*File) Source
```go
func (fl *File) Source() string
```
#### type FileSet
```go
type FileSet struct {
}
```
A FileSet represents a set of source files.
#### func (*FileSet) AddFile
```go
func (self *FileSet) AddFile(filename, src string) int
```
AddFile adds a new file with the given filename and src.
This an internal method, but exported for cross-package use.
#### func (*FileSet) File
```go
func (self *FileSet) File(idx Idx) *File
```
#### func (*FileSet) Position
```go
func (self *FileSet) Position(idx Idx) *Position
```
Position converts an Idx in the FileSet into a Position.
#### type Idx
```go
type Idx int
```
Idx is a compact encoding of a source position within a file set. It can be
converted into a Position for a more convenient, but much larger,
representation.
#### type Position
```go
type Position struct {
Filename string // The filename where the error occurred, if any
Offset int // The src offset
Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count)
}
```
Position describes an arbitrary source position including the filename, line,
and column location.
#### func (*Position) String
```go
func (self *Position) String() string
```
String returns a string in one of several forms:
file:line:column A valid position with filename
line:column A valid position without filename
file An invalid position with filename
- An invalid position without filename
--
**godocdown** http://github.com/robertkrimen/godocdown

View file

@ -20,13 +20,12 @@ type Position struct {
Offset int // The src offset Offset int // The src offset
Line int // The line number, starting at 1 Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count) Column int // The column number, starting at 1 (The character count)
} }
// A Position is valid if the line number is > 0. // A Position is valid if the line number is > 0.
func (self *Position) isValid() bool { func (p *Position) isValid() bool {
return self.Line > 0 return p.Line > 0
} }
// String returns a string in one of several forms: // String returns a string in one of several forms:
@ -35,13 +34,13 @@ func (self *Position) isValid() bool {
// line:column A valid position without filename // line:column A valid position without filename
// file An invalid position with filename // file An invalid position with filename
// - An invalid position without filename // - An invalid position without filename
func (self *Position) String() string { func (p *Position) String() string {
str := self.Filename str := p.Filename
if self.isValid() { if p.isValid() {
if str != "" { if str != "" {
str += ":" str += ":"
} }
str += fmt.Sprintf("%d:%d", self.Line, self.Column) str += fmt.Sprintf("%d:%d", p.Line, p.Column)
} }
if str == "" { if str == "" {
str = "-" str = "-"
@ -49,10 +48,8 @@ func (self *Position) String() string {
return str return str
} }
// FileSet
// A FileSet represents a set of source files. // A FileSet represents a set of source files.
type FileSet struct { type FileSet struct { //nolint: golint
files []*File files []*File
last *File last *File
} }
@ -60,27 +57,28 @@ type FileSet struct {
// AddFile adds a new file with the given filename and src. // AddFile adds a new file with the given filename and src.
// //
// This an internal method, but exported for cross-package use. // This an internal method, but exported for cross-package use.
func (self *FileSet) AddFile(filename, src string) int { func (fs *FileSet) AddFile(filename, src string) int {
base := self.nextBase() base := fs.nextBase()
file := &File{ file := &File{
name: filename, name: filename,
src: src, src: src,
base: base, base: base,
} }
self.files = append(self.files, file) fs.files = append(fs.files, file)
self.last = file fs.last = file
return base return base
} }
func (self *FileSet) nextBase() int { func (fs *FileSet) nextBase() int {
if self.last == nil { if fs.last == nil {
return 1 return 1
} }
return self.last.base + len(self.last.src) + 1 return fs.last.base + len(fs.last.src) + 1
} }
func (self *FileSet) File(idx Idx) *File { // File returns the File at idx or nil if not found.
for _, file := range self.files { func (fs *FileSet) File(idx Idx) *File {
for _, file := range fs.files {
if idx <= Idx(file.base+len(file.src)) { if idx <= Idx(file.base+len(file.src)) {
return file return file
} }
@ -89,8 +87,8 @@ func (self *FileSet) File(idx Idx) *File {
} }
// Position converts an Idx in the FileSet into a Position. // Position converts an Idx in the FileSet into a Position.
func (self *FileSet) Position(idx Idx) *Position { func (fs *FileSet) Position(idx Idx) *Position {
for _, file := range self.files { for _, file := range fs.files {
if idx <= Idx(file.base+len(file.src)) { if idx <= Idx(file.base+len(file.src)) {
return file.Position(idx - Idx(file.base)) return file.Position(idx - Idx(file.base))
} }
@ -99,6 +97,7 @@ func (self *FileSet) Position(idx Idx) *Position {
return nil return nil
} }
// File represents a file to parse.
type File struct { type File struct {
name string name string
src string src string
@ -106,6 +105,7 @@ type File struct {
sm *sourcemap.Consumer sm *sourcemap.Consumer
} }
// NewFile returns a new file with the given filename, src and base.
func NewFile(filename, src string, base int) *File { func NewFile(filename, src string, base int) *File {
return &File{ return &File{
name: filename, name: filename,
@ -114,23 +114,28 @@ func NewFile(filename, src string, base int) *File {
} }
} }
// WithSourceMap sets the source map of fl.
func (fl *File) WithSourceMap(sm *sourcemap.Consumer) *File { func (fl *File) WithSourceMap(sm *sourcemap.Consumer) *File {
fl.sm = sm fl.sm = sm
return fl return fl
} }
// Name returns the name of fl.
func (fl *File) Name() string { func (fl *File) Name() string {
return fl.name return fl.name
} }
// Source returns the source of fl.
func (fl *File) Source() string { func (fl *File) Source() string {
return fl.src return fl.src
} }
// Base returns the base of fl.
func (fl *File) Base() int { func (fl *File) Base() int {
return fl.base return fl.base
} }
// Position returns the position at idx or nil if not valid.
func (fl *File) Position(idx Idx) *Position { func (fl *File) Position(idx Idx) *Position {
position := &Position{} position := &Position{}

4
v1/vendor/github.com/robertkrimen/otto/generate.go generated vendored Normal file
View file

@ -0,0 +1,4 @@
package otto
//go:generate go run ./tools/gen-jscore -output inline.go
//go:generate stringer -type=valueKind -trimprefix=value -output=value_kind.gen.go

View file

@ -7,12 +7,12 @@ import (
var ( var (
prototypeValueObject = interface{}(nil) prototypeValueObject = interface{}(nil)
prototypeValueFunction = _nativeFunctionObject{ prototypeValueFunction = nativeFunctionObject{
call: func(_ FunctionCall) Value { call: func(_ FunctionCall) Value {
return Value{} return Value{}
}, },
} }
prototypeValueString = _stringASCII("") prototypeValueString = stringASCII("")
// TODO Make this just false? // TODO Make this just false?
prototypeValueBoolean = Value{ prototypeValueBoolean = Value{
kind: valueBoolean, kind: valueBoolean,
@ -22,7 +22,7 @@ var (
kind: valueNumber, kind: valueNumber,
value: 0, value: 0,
} }
prototypeValueDate = _dateObject{ prototypeValueDate = dateObject{
epoch: 0, epoch: 0,
isNaN: false, isNaN: false,
time: time.Unix(0, 0).UTC(), time: time.Unix(0, 0).UTC(),
@ -31,7 +31,7 @@ var (
value: 0, value: 0,
}, },
} }
prototypeValueRegExp = _regExpObject{ prototypeValueRegExp = regExpObject{
regularExpression: nil, regularExpression: nil,
global: false, global: false,
ignoreCase: false, ignoreCase: false,
@ -41,102 +41,101 @@ var (
} }
) )
func newContext() *_runtime { func newContext() *runtime {
self := &_runtime{} rt := &runtime{}
self.globalStash = self.newObjectStash(nil, nil) rt.globalStash = rt.newObjectStash(nil, nil)
self.globalObject = self.globalStash.object rt.globalObject = rt.globalStash.object
_newContext(self) rt.newContext()
self.eval = self.globalObject.property["eval"].value.(Value).value.(*_object) rt.eval = rt.globalObject.property["eval"].value.(Value).value.(*object)
self.globalObject.prototype = self.global.ObjectPrototype rt.globalObject.prototype = rt.global.ObjectPrototype
return self return rt
} }
func (runtime *_runtime) newBaseObject() *_object { func (rt *runtime) newBaseObject() *object {
self := newObject(runtime, "") return newObject(rt, "")
return self
} }
func (runtime *_runtime) newClassObject(class string) *_object { func (rt *runtime) newClassObject(class string) *object {
return newObject(runtime, class) return newObject(rt, class)
} }
func (runtime *_runtime) newPrimitiveObject(class string, value Value) *_object { func (rt *runtime) newPrimitiveObject(class string, value Value) *object {
self := runtime.newClassObject(class) o := rt.newClassObject(class)
self.value = value o.value = value
return self return o
} }
func (self *_object) primitiveValue() Value { func (o *object) primitiveValue() Value {
switch value := self.value.(type) { switch value := o.value.(type) {
case Value: case Value:
return value return value
case _stringObject: case stringObjecter:
return toValue_string(value.String()) return stringValue(value.String())
} }
return Value{} return Value{}
} }
func (self *_object) hasPrimitive() bool { func (o *object) hasPrimitive() bool { //nolint: unused
switch self.value.(type) { switch o.value.(type) {
case Value, _stringObject: case Value, stringObjecter:
return true return true
} }
return false return false
} }
func (runtime *_runtime) newObject() *_object { func (rt *runtime) newObject() *object {
self := runtime.newClassObject(classObject) o := rt.newClassObject(classObjectName)
self.prototype = runtime.global.ObjectPrototype o.prototype = rt.global.ObjectPrototype
return self return o
} }
func (runtime *_runtime) newArray(length uint32) *_object { func (rt *runtime) newArray(length uint32) *object {
self := runtime.newArrayObject(length) o := rt.newArrayObject(length)
self.prototype = runtime.global.ArrayPrototype o.prototype = rt.global.ArrayPrototype
return self return o
} }
func (runtime *_runtime) newArrayOf(valueArray []Value) *_object { func (rt *runtime) newArrayOf(valueArray []Value) *object {
self := runtime.newArray(uint32(len(valueArray))) o := rt.newArray(uint32(len(valueArray)))
for index, value := range valueArray { for index, value := range valueArray {
if value.isEmpty() { if value.isEmpty() {
continue continue
} }
self.defineProperty(strconv.FormatInt(int64(index), 10), value, 0111, false) o.defineProperty(strconv.FormatInt(int64(index), 10), value, 0o111, false)
} }
return self return o
} }
func (runtime *_runtime) newString(value Value) *_object { func (rt *runtime) newString(value Value) *object {
self := runtime.newStringObject(value) o := rt.newStringObject(value)
self.prototype = runtime.global.StringPrototype o.prototype = rt.global.StringPrototype
return self return o
} }
func (runtime *_runtime) newBoolean(value Value) *_object { func (rt *runtime) newBoolean(value Value) *object {
self := runtime.newBooleanObject(value) o := rt.newBooleanObject(value)
self.prototype = runtime.global.BooleanPrototype o.prototype = rt.global.BooleanPrototype
return self return o
} }
func (runtime *_runtime) newNumber(value Value) *_object { func (rt *runtime) newNumber(value Value) *object {
self := runtime.newNumberObject(value) o := rt.newNumberObject(value)
self.prototype = runtime.global.NumberPrototype o.prototype = rt.global.NumberPrototype
return self return o
} }
func (runtime *_runtime) newRegExp(patternValue Value, flagsValue Value) *_object { func (rt *runtime) newRegExp(patternValue Value, flagsValue Value) *object {
pattern := "" pattern := ""
flags := "" flags := ""
if object := patternValue._object(); object != nil && object.class == classRegExp { if obj := patternValue.object(); obj != nil && obj.class == classRegExpName {
if flagsValue.IsDefined() { if flagsValue.IsDefined() {
panic(runtime.panicTypeError("Cannot supply flags when constructing one RegExp from another")) panic(rt.panicTypeError("Cannot supply flags when constructing one RegExp from another"))
} }
regExp := object.regExpValue() regExp := obj.regExpValue()
pattern = regExp.source pattern = regExp.source
flags = regExp.flags flags = regExp.flags
} else { } else {
@ -148,71 +147,71 @@ func (runtime *_runtime) newRegExp(patternValue Value, flagsValue Value) *_objec
} }
} }
return runtime._newRegExp(pattern, flags) return rt.newRegExpDirect(pattern, flags)
} }
func (runtime *_runtime) _newRegExp(pattern string, flags string) *_object { func (rt *runtime) newRegExpDirect(pattern string, flags string) *object {
self := runtime.newRegExpObject(pattern, flags) o := rt.newRegExpObject(pattern, flags)
self.prototype = runtime.global.RegExpPrototype o.prototype = rt.global.RegExpPrototype
return self return o
} }
// TODO Should (probably) be one argument, right? This is redundant // TODO Should (probably) be one argument, right? This is redundant.
func (runtime *_runtime) newDate(epoch float64) *_object { func (rt *runtime) newDate(epoch float64) *object {
self := runtime.newDateObject(epoch) o := rt.newDateObject(epoch)
self.prototype = runtime.global.DatePrototype o.prototype = rt.global.DatePrototype
return self return o
} }
func (runtime *_runtime) newError(name string, message Value, stackFramesToPop int) *_object { func (rt *runtime) newError(name string, message Value, stackFramesToPop int) *object {
switch name { switch name {
case "EvalError": case "EvalError":
return runtime.newEvalError(message) return rt.newEvalError(message)
case "TypeError": case "TypeError":
return runtime.newTypeError(message) return rt.newTypeError(message)
case "RangeError": case "RangeError":
return runtime.newRangeError(message) return rt.newRangeError(message)
case "ReferenceError": case "ReferenceError":
return runtime.newReferenceError(message) return rt.newReferenceError(message)
case "SyntaxError": case "SyntaxError":
return runtime.newSyntaxError(message) return rt.newSyntaxError(message)
case "URIError": case "URIError":
return runtime.newURIError(message) return rt.newURIError(message)
} }
self := runtime.newErrorObject(name, message, stackFramesToPop) obj := rt.newErrorObject(name, message, stackFramesToPop)
self.prototype = runtime.global.ErrorPrototype obj.prototype = rt.global.ErrorPrototype
if name != "" { if name != "" {
self.defineProperty("name", toValue_string(name), 0111, false) obj.defineProperty("name", stringValue(name), 0o111, false)
} }
return self return obj
} }
func (runtime *_runtime) newNativeFunction(name, file string, line int, _nativeFunction _nativeFunction) *_object { func (rt *runtime) newNativeFunction(name, file string, line int, fn nativeFunction) *object {
self := runtime.newNativeFunctionObject(name, file, line, _nativeFunction, 0) o := rt.newNativeFunctionObject(name, file, line, fn, 0)
self.prototype = runtime.global.FunctionPrototype o.prototype = rt.global.FunctionPrototype
prototype := runtime.newObject() prototype := rt.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false) o.defineProperty("prototype", objectValue(prototype), 0o100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false) prototype.defineProperty("constructor", objectValue(o), 0o100, false)
return self return o
} }
func (runtime *_runtime) newNodeFunction(node *_nodeFunctionLiteral, scopeEnvironment _stash) *_object { func (rt *runtime) newNodeFunction(node *nodeFunctionLiteral, scopeEnvironment stasher) *object {
// TODO Implement 13.2 fully // TODO Implement 13.2 fully
self := runtime.newNodeFunctionObject(node, scopeEnvironment) o := rt.newNodeFunctionObject(node, scopeEnvironment)
self.prototype = runtime.global.FunctionPrototype o.prototype = rt.global.FunctionPrototype
prototype := runtime.newObject() prototype := rt.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false) o.defineProperty("prototype", objectValue(prototype), 0o100, false)
prototype.defineProperty("constructor", toValue_object(self), 0101, false) prototype.defineProperty("constructor", objectValue(o), 0o101, false)
return self return o
} }
// FIXME Only in one place... // FIXME Only in one place...
func (runtime *_runtime) newBoundFunction(target *_object, this Value, argumentList []Value) *_object { func (rt *runtime) newBoundFunction(target *object, this Value, argumentList []Value) *object {
self := runtime.newBoundFunctionObject(target, this, argumentList) o := rt.newBoundFunctionObject(target, this, argumentList)
self.prototype = runtime.global.FunctionPrototype o.prototype = rt.global.FunctionPrototype
prototype := runtime.newObject() prototype := rt.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false) o.defineProperty("prototype", objectValue(prototype), 0o100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false) prototype.defineProperty("constructor", objectValue(o), 0o100, false)
return self return o
} }

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -2,6 +2,4 @@ package otto
import "golang.org/x/text/language" import "golang.org/x/text/language"
var ( var defaultLanguage = language.MustParse("en-US")
defaultLanguage = language.MustParse("en-US")
)

View file

@ -1,78 +1,78 @@
package otto package otto
type _object struct { type object struct {
runtime *_runtime runtime *runtime
class string class string
objectClass *_objectClass objectClass *objectClass
value interface{} value interface{}
prototype *_object prototype *object
extensible bool extensible bool
property map[string]_property property map[string]property
propertyOrder []string propertyOrder []string
} }
func newObject(runtime *_runtime, class string) *_object { func newObject(rt *runtime, class string) *object {
self := &_object{ o := &object{
runtime: runtime, runtime: rt,
class: class, class: class,
objectClass: _classObject, objectClass: classObject,
property: make(map[string]_property), property: make(map[string]property),
extensible: true, extensible: true,
} }
return self return o
} }
// 8.12 // 8.12
// 8.12.1 // 8.12.1.
func (self *_object) getOwnProperty(name string) *_property { func (o *object) getOwnProperty(name string) *property {
return self.objectClass.getOwnProperty(self, name) return o.objectClass.getOwnProperty(o, name)
} }
// 8.12.2 // 8.12.2.
func (self *_object) getProperty(name string) *_property { func (o *object) getProperty(name string) *property {
return self.objectClass.getProperty(self, name) return o.objectClass.getProperty(o, name)
} }
// 8.12.3 // 8.12.3.
func (self *_object) get(name string) Value { func (o *object) get(name string) Value {
return self.objectClass.get(self, name) return o.objectClass.get(o, name)
} }
// 8.12.4 // 8.12.4.
func (self *_object) canPut(name string) bool { func (o *object) canPut(name string) bool {
return self.objectClass.canPut(self, name) return o.objectClass.canPut(o, name)
} }
// 8.12.5 // 8.12.5.
func (self *_object) put(name string, value Value, throw bool) { func (o *object) put(name string, value Value, throw bool) {
self.objectClass.put(self, name, value, throw) o.objectClass.put(o, name, value, throw)
} }
// 8.12.6 // 8.12.6.
func (self *_object) hasProperty(name string) bool { func (o *object) hasProperty(name string) bool {
return self.objectClass.hasProperty(self, name) return o.objectClass.hasProperty(o, name)
} }
func (self *_object) hasOwnProperty(name string) bool { func (o *object) hasOwnProperty(name string) bool {
return self.objectClass.hasOwnProperty(self, name) return o.objectClass.hasOwnProperty(o, name)
} }
type _defaultValueHint int type defaultValueHint int
const ( const (
defaultValueNoHint _defaultValueHint = iota defaultValueNoHint defaultValueHint = iota
defaultValueHintString defaultValueHintString
defaultValueHintNumber defaultValueHintNumber
) )
// 8.12.8 // 8.12.8.
func (self *_object) DefaultValue(hint _defaultValueHint) Value { func (o *object) DefaultValue(hint defaultValueHint) Value {
if hint == defaultValueNoHint { if hint == defaultValueNoHint {
if self.class == classDate { if o.class == classDateName {
// Date exception // Date exception
hint = defaultValueHintString hint = defaultValueHintString
} else { } else {
@ -84,72 +84,67 @@ func (self *_object) DefaultValue(hint _defaultValueHint) Value {
methodSequence = []string{"toString", "valueOf"} methodSequence = []string{"toString", "valueOf"}
} }
for _, methodName := range methodSequence { for _, methodName := range methodSequence {
method := self.get(methodName) method := o.get(methodName)
// FIXME This is redundant... // FIXME This is redundant...
if method.isCallable() { if method.isCallable() {
result := method._object().call(toValue_object(self), nil, false, nativeFrame) result := method.object().call(objectValue(o), nil, false, nativeFrame)
if result.IsPrimitive() { if result.IsPrimitive() {
return result return result
} }
} }
} }
panic(self.runtime.panicTypeError()) panic(o.runtime.panicTypeError("Object.DefaultValue unknown"))
} }
func (self *_object) String() string { func (o *object) String() string {
return self.DefaultValue(defaultValueHintString).string() return o.DefaultValue(defaultValueHintString).string()
} }
func (self *_object) defineProperty(name string, value Value, mode _propertyMode, throw bool) bool { func (o *object) defineProperty(name string, value Value, mode propertyMode, throw bool) bool { //nolint: unparam
return self.defineOwnProperty(name, _property{value, mode}, throw) return o.defineOwnProperty(name, property{value, mode}, throw)
} }
// 8.12.9 // 8.12.9.
func (self *_object) defineOwnProperty(name string, descriptor _property, throw bool) bool { func (o *object) defineOwnProperty(name string, descriptor property, throw bool) bool {
return self.objectClass.defineOwnProperty(self, name, descriptor, throw) return o.objectClass.defineOwnProperty(o, name, descriptor, throw)
} }
func (self *_object) delete(name string, throw bool) bool { func (o *object) delete(name string, throw bool) bool {
return self.objectClass.delete(self, name, throw) return o.objectClass.delete(o, name, throw)
} }
func (self *_object) enumerate(all bool, each func(string) bool) { func (o *object) enumerate(all bool, each func(string) bool) {
self.objectClass.enumerate(self, all, each) o.objectClass.enumerate(o, all, each)
} }
func (self *_object) _exists(name string) bool { func (o *object) readProperty(name string) (property, bool) {
_, exists := self.property[name] prop, exists := o.property[name]
return exists return prop, exists
} }
func (self *_object) _read(name string) (_property, bool) { func (o *object) writeProperty(name string, value interface{}, mode propertyMode) {
property, exists := self.property[name]
return property, exists
}
func (self *_object) _write(name string, value interface{}, mode _propertyMode) {
if value == nil { if value == nil {
value = Value{} value = Value{}
} }
_, exists := self.property[name] if _, exists := o.property[name]; !exists {
self.property[name] = _property{value, mode} o.propertyOrder = append(o.propertyOrder, name)
if !exists {
self.propertyOrder = append(self.propertyOrder, name)
} }
o.property[name] = property{value, mode}
} }
func (self *_object) _delete(name string) { func (o *object) deleteProperty(name string) {
_, exists := self.property[name] if _, exists := o.property[name]; !exists {
delete(self.property, name) return
if exists { }
for index, property := range self.propertyOrder {
if name == property { delete(o.property, name)
if index == len(self.propertyOrder)-1 { for index, prop := range o.propertyOrder {
self.propertyOrder = self.propertyOrder[:index] if name == prop {
if index == len(o.propertyOrder)-1 {
o.propertyOrder = o.propertyOrder[:index]
} else { } else {
self.propertyOrder = append(self.propertyOrder[:index], self.propertyOrder[index+1:]...) o.propertyOrder = append(o.propertyOrder[:index], o.propertyOrder[index+1:]...)
}
} }
} }
} }

View file

@ -4,24 +4,24 @@ import (
"encoding/json" "encoding/json"
) )
type _objectClass struct { type objectClass struct {
getOwnProperty func(*_object, string) *_property getOwnProperty func(*object, string) *property
getProperty func(*_object, string) *_property getProperty func(*object, string) *property
get func(*_object, string) Value get func(*object, string) Value
canPut func(*_object, string) bool canPut func(*object, string) bool
put func(*_object, string, Value, bool) put func(*object, string, Value, bool)
hasProperty func(*_object, string) bool hasProperty func(*object, string) bool
hasOwnProperty func(*_object, string) bool hasOwnProperty func(*object, string) bool
defineOwnProperty func(*_object, string, _property, bool) bool defineOwnProperty func(*object, string, property, bool) bool
delete func(*_object, string, bool) bool delete func(*object, string, bool) bool
enumerate func(*_object, bool, func(string) bool) enumerate func(*object, bool, func(string) bool)
clone func(*_object, *_object, *_clone) *_object clone func(*object, *object, *cloner) *object
marshalJSON func(*_object) json.Marshaler marshalJSON func(*object) json.Marshaler
} }
func objectEnumerate(self *_object, all bool, each func(string) bool) { func objectEnumerate(obj *object, all bool, each func(string) bool) {
for _, name := range self.propertyOrder { for _, name := range obj.propertyOrder {
if all || self.property[name].enumerable() { if all || obj.property[name].enumerable() {
if !each(name) { if !each(name) {
return return
} }
@ -29,20 +29,17 @@ func objectEnumerate(self *_object, all bool, each func(string) bool) {
} }
} }
var ( var classObject,
_classObject, classArray,
_classArray, classString,
_classString, classArguments,
_classArguments, classGoStruct,
_classGoStruct, classGoMap,
_classGoMap, classGoArray,
_classGoArray, classGoSlice *objectClass
_classGoSlice,
_ *_objectClass
)
func init() { func init() {
_classObject = &_objectClass{ classObject = &objectClass{
objectGetOwnProperty, objectGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -57,7 +54,7 @@ func init() {
nil, nil,
} }
_classArray = &_objectClass{ classArray = &objectClass{
objectGetOwnProperty, objectGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -72,7 +69,7 @@ func init() {
nil, nil,
} }
_classString = &_objectClass{ classString = &objectClass{
stringGetOwnProperty, stringGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -87,7 +84,7 @@ func init() {
nil, nil,
} }
_classArguments = &_objectClass{ classArguments = &objectClass{
argumentsGetOwnProperty, argumentsGetOwnProperty,
objectGetProperty, objectGetProperty,
argumentsGet, argumentsGet,
@ -102,7 +99,7 @@ func init() {
nil, nil,
} }
_classGoStruct = &_objectClass{ classGoStruct = &objectClass{
goStructGetOwnProperty, goStructGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -117,7 +114,7 @@ func init() {
goStructMarshalJSON, goStructMarshalJSON,
} }
_classGoMap = &_objectClass{ classGoMap = &objectClass{
goMapGetOwnProperty, goMapGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -132,7 +129,7 @@ func init() {
nil, nil,
} }
_classGoArray = &_objectClass{ classGoArray = &objectClass{
goArrayGetOwnProperty, goArrayGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -147,7 +144,7 @@ func init() {
nil, nil,
} }
_classGoSlice = &_objectClass{ classGoSlice = &objectClass{
goSliceGetOwnProperty, goSliceGetOwnProperty,
objectGetProperty, objectGetProperty,
objectGet, objectGet,
@ -165,85 +162,81 @@ func init() {
// Allons-y // Allons-y
// 8.12.1 // 8.12.1.
func objectGetOwnProperty(self *_object, name string) *_property { func objectGetOwnProperty(obj *object, name string) *property {
// Return a _copy_ of the property // Return a _copy_ of the prop
property, exists := self._read(name) prop, exists := obj.readProperty(name)
if !exists { if !exists {
return nil return nil
} }
return &property return &prop
} }
// 8.12.2 // 8.12.2.
func objectGetProperty(self *_object, name string) *_property { func objectGetProperty(obj *object, name string) *property {
property := self.getOwnProperty(name) prop := obj.getOwnProperty(name)
if property != nil { if prop != nil {
return property return prop
} }
if self.prototype != nil { if obj.prototype != nil {
return self.prototype.getProperty(name) return obj.prototype.getProperty(name)
} }
return nil return nil
} }
// 8.12.3 // 8.12.3.
func objectGet(self *_object, name string) Value { func objectGet(obj *object, name string) Value {
property := self.getProperty(name) if prop := obj.getProperty(name); prop != nil {
if property != nil { return prop.get(obj)
return property.get(self)
} }
return Value{} return Value{}
} }
// 8.12.4 // 8.12.4.
func objectCanPut(self *_object, name string) bool { func objectCanPut(obj *object, name string) bool {
canPut, _, _ := _objectCanPut(self, name) canPut, _, _ := objectCanPutDetails(obj, name)
return canPut return canPut
} }
func _objectCanPut(self *_object, name string) (canPut bool, property *_property, setter *_object) { func objectCanPutDetails(obj *object, name string) (canPut bool, prop *property, setter *object) { //nolint: nonamedreturns
property = self.getOwnProperty(name) prop = obj.getOwnProperty(name)
if property != nil { if prop != nil {
switch propertyValue := property.value.(type) { switch propertyValue := prop.value.(type) {
case Value: case Value:
canPut = property.writable() return prop.writable(), prop, nil
return case propertyGetSet:
case _propertyGetSet:
setter = propertyValue[1] setter = propertyValue[1]
canPut = setter != nil return setter != nil, prop, setter
return
default: default:
panic(self.runtime.panicTypeError()) panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value))
} }
} }
if self.prototype == nil { if obj.prototype == nil {
return self.extensible, nil, nil return obj.extensible, nil, nil
} }
property = self.prototype.getProperty(name) prop = obj.prototype.getProperty(name)
if property == nil { if prop == nil {
return self.extensible, nil, nil return obj.extensible, nil, nil
} }
switch propertyValue := property.value.(type) { switch propertyValue := prop.value.(type) {
case Value: case Value:
if !self.extensible { if !obj.extensible {
return false, nil, nil return false, nil, nil
} }
return property.writable(), nil, nil return prop.writable(), nil, nil
case _propertyGetSet: case propertyGetSet:
setter = propertyValue[1] setter = propertyValue[1]
canPut = setter != nil return setter != nil, prop, setter
return
default: default:
panic(self.runtime.panicTypeError()) panic(obj.runtime.panicTypeError("unexpected type %T to Object.CanPutDetails", prop.value))
} }
} }
// 8.12.5 // 8.12.5.
func objectPut(self *_object, name string, value Value, throw bool) { func objectPut(obj *object, name string, value Value, throw bool) {
if true { if true {
// Shortcut... // Shortcut...
// //
@ -253,16 +246,17 @@ func objectPut(self *_object, name string, value Value, throw bool) {
// If that were to no longer be the case, we would have to have // If that were to no longer be the case, we would have to have
// something to detect that here, so that we do not use an // something to detect that here, so that we do not use an
// incompatible canPut routine // incompatible canPut routine
canPut, property, setter := _objectCanPut(self, name) canPut, prop, setter := objectCanPutDetails(obj, name)
if !canPut { switch {
self.runtime.typeErrorResult(throw) case !canPut:
} else if setter != nil { obj.runtime.typeErrorResult(throw)
setter.call(toValue(self), []Value{value}, false, nativeFrame) case setter != nil:
} else if property != nil { setter.call(toValue(obj), []Value{value}, false, nativeFrame)
property.value = value case prop != nil:
self.defineOwnProperty(name, *property, throw) prop.value = value
} else { obj.defineOwnProperty(name, *prop, throw)
self.defineProperty(name, value, 0111, throw) default:
obj.defineProperty(name, value, 0o111, throw)
} }
return return
} }
@ -270,69 +264,77 @@ func objectPut(self *_object, name string, value Value, throw bool) {
// The long way... // The long way...
// //
// Right now, code should never get here, see above // Right now, code should never get here, see above
if !self.canPut(name) { if !obj.canPut(name) {
self.runtime.typeErrorResult(throw) obj.runtime.typeErrorResult(throw)
return return
} }
property := self.getOwnProperty(name) prop := obj.getOwnProperty(name)
if property == nil { if prop == nil {
property = self.getProperty(name) prop = obj.getProperty(name)
if property != nil { if prop != nil {
if getSet, isAccessor := property.value.(_propertyGetSet); isAccessor { if getSet, isAccessor := prop.value.(propertyGetSet); isAccessor {
getSet[1].call(toValue(self), []Value{value}, false, nativeFrame) getSet[1].call(toValue(obj), []Value{value}, false, nativeFrame)
return return
} }
} }
self.defineProperty(name, value, 0111, throw) obj.defineProperty(name, value, 0o111, throw)
} else { return
switch propertyValue := property.value.(type) { }
switch propertyValue := prop.value.(type) {
case Value: case Value:
property.value = value prop.value = value
self.defineOwnProperty(name, *property, throw) obj.defineOwnProperty(name, *prop, throw)
case _propertyGetSet: case propertyGetSet:
if propertyValue[1] != nil { if propertyValue[1] != nil {
propertyValue[1].call(toValue(self), []Value{value}, false, nativeFrame) propertyValue[1].call(toValue(obj), []Value{value}, false, nativeFrame)
return return
} }
if throw { if throw {
panic(self.runtime.panicTypeError()) panic(obj.runtime.panicTypeError("Object.Put nil second parameter to propertyGetSet"))
} }
default: default:
panic(self.runtime.panicTypeError()) panic(obj.runtime.panicTypeError("Object.Put unexpected type %T", prop.value))
}
} }
} }
// 8.12.6 // 8.12.6.
func objectHasProperty(self *_object, name string) bool { func objectHasProperty(obj *object, name string) bool {
return self.getProperty(name) != nil return obj.getProperty(name) != nil
} }
func objectHasOwnProperty(self *_object, name string) bool { func objectHasOwnProperty(obj *object, name string) bool {
return self.getOwnProperty(name) != nil return obj.getOwnProperty(name) != nil
} }
// 8.12.9 // 8.12.9.
func objectDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool { func objectDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
property, exists := self._read(name) reject := func(reason string) bool {
{ if throw {
panic(obj.runtime.panicTypeError("Object.DefineOwnProperty: %s", reason))
}
return false
}
prop, exists := obj.readProperty(name)
if !exists { if !exists {
if !self.extensible { if !obj.extensible {
goto Reject return reject("not exists and not extensible")
} }
if newGetSet, isAccessor := descriptor.value.(_propertyGetSet); isAccessor { if newGetSet, isAccessor := descriptor.value.(propertyGetSet); isAccessor {
if newGetSet[0] == &_nilGetSetObject { if newGetSet[0] == &nilGetSetObject {
newGetSet[0] = nil newGetSet[0] = nil
} }
if newGetSet[1] == &_nilGetSetObject { if newGetSet[1] == &nilGetSetObject {
newGetSet[1] = nil newGetSet[1] = nil
} }
descriptor.value = newGetSet descriptor.value = newGetSet
} }
self._write(name, descriptor.value, descriptor.mode) obj.writeProperty(name, descriptor.value, descriptor.mode)
return true return true
} }
if descriptor.isEmpty() { if descriptor.isEmpty() {
return true return true
} }
@ -340,43 +342,45 @@ func objectDefineOwnProperty(self *_object, name string, descriptor _property, t
// TODO Per 8.12.9.6 - We should shortcut here (returning true) if // TODO Per 8.12.9.6 - We should shortcut here (returning true) if
// the current and new (define) properties are the same // the current and new (define) properties are the same
configurable := property.configurable() configurable := prop.configurable()
if !configurable { if !configurable {
if descriptor.configurable() { if descriptor.configurable() {
goto Reject return reject("property and descriptor not configurable")
} }
// Test that, if enumerable is set on the property descriptor, then it should // Test that, if enumerable is set on the property descriptor, then it should
// be the same as the existing property // be the same as the existing property
if descriptor.enumerateSet() && descriptor.enumerable() != property.enumerable() { if descriptor.enumerateSet() && descriptor.enumerable() != prop.enumerable() {
goto Reject return reject("property not configurable and enumerable miss match")
} }
} }
value, isDataDescriptor := property.value.(Value)
getSet, _ := property.value.(_propertyGetSet) value, isDataDescriptor := prop.value.(Value)
if descriptor.isGenericDescriptor() { getSet, _ := prop.value.(propertyGetSet)
switch {
case descriptor.isGenericDescriptor():
// GenericDescriptor // GenericDescriptor
} else if isDataDescriptor != descriptor.isDataDescriptor() { case isDataDescriptor != descriptor.isDataDescriptor():
// DataDescriptor <=> AccessorDescriptor // DataDescriptor <=> AccessorDescriptor
if !configurable { if !configurable {
goto Reject return reject("property descriptor not configurable")
} }
} else if isDataDescriptor && descriptor.isDataDescriptor() { case isDataDescriptor && descriptor.isDataDescriptor():
// DataDescriptor <=> DataDescriptor // DataDescriptor <=> DataDescriptor
if !configurable { if !configurable {
if !property.writable() && descriptor.writable() { if !prop.writable() && descriptor.writable() {
goto Reject return reject("property not configurable or writeable and descriptor not writeable")
} }
if !property.writable() { if !prop.writable() {
if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) { if descriptor.value != nil && !sameValue(value, descriptor.value.(Value)) {
goto Reject return reject("property not configurable or writeable and descriptor not the same")
} }
} }
} }
} else { default:
// AccessorDescriptor <=> AccessorDescriptor // AccessorDescriptor <=> AccessorDescriptor
newGetSet, _ := descriptor.value.(_propertyGetSet) newGetSet, _ := descriptor.value.(propertyGetSet)
presentGet, presentSet := true, true presentGet, presentSet := true, true
if newGetSet[0] == &_nilGetSetObject { if newGetSet[0] == &nilGetSetObject {
// Present, but nil // Present, but nil
newGetSet[0] = nil newGetSet[0] = nil
} else if newGetSet[0] == nil { } else if newGetSet[0] == nil {
@ -384,7 +388,7 @@ func objectDefineOwnProperty(self *_object, name string, descriptor _property, t
newGetSet[0] = getSet[0] newGetSet[0] = getSet[0]
presentGet = false presentGet = false
} }
if newGetSet[1] == &_nilGetSetObject { if newGetSet[1] == &nilGetSetObject {
// Present, but nil // Present, but nil
newGetSet[1] = nil newGetSet[1] = nil
} else if newGetSet[1] == nil { } else if newGetSet[1] == nil {
@ -394,97 +398,91 @@ func objectDefineOwnProperty(self *_object, name string, descriptor _property, t
} }
if !configurable { if !configurable {
if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) { if (presentGet && (getSet[0] != newGetSet[0])) || (presentSet && (getSet[1] != newGetSet[1])) {
goto Reject return reject("access descriptor not configurable")
} }
} }
descriptor.value = newGetSet descriptor.value = newGetSet
} }
{
// This section will preserve attributes of // This section will preserve attributes of
// the original property, if necessary // the original property, if necessary
value1 := descriptor.value value1 := descriptor.value
if value1 == nil { if value1 == nil {
value1 = property.value value1 = prop.value
} else if newGetSet, isAccessor := descriptor.value.(_propertyGetSet); isAccessor { } else if newGetSet, isAccessor := descriptor.value.(propertyGetSet); isAccessor {
if newGetSet[0] == &_nilGetSetObject { if newGetSet[0] == &nilGetSetObject {
newGetSet[0] = nil newGetSet[0] = nil
} }
if newGetSet[1] == &_nilGetSetObject { if newGetSet[1] == &nilGetSetObject {
newGetSet[1] = nil newGetSet[1] = nil
} }
value1 = newGetSet value1 = newGetSet
} }
mode1 := descriptor.mode mode1 := descriptor.mode
if mode1&0222 != 0 { if mode1&0o222 != 0 {
// TODO Factor this out into somewhere testable // TODO Factor this out into somewhere testable
// (Maybe put into switch ...) // (Maybe put into switch ...)
mode0 := property.mode mode0 := prop.mode
if mode1&0200 != 0 { if mode1&0o200 != 0 {
if descriptor.isDataDescriptor() { if descriptor.isDataDescriptor() {
mode1 &= ^0200 // Turn off "writable" missing mode1 &= ^0o200 // Turn off "writable" missing
mode1 |= (mode0 & 0100) mode1 |= (mode0 & 0o100)
} }
} }
if mode1&020 != 0 { if mode1&0o20 != 0 {
mode1 |= (mode0 & 010) mode1 |= (mode0 & 0o10)
} }
if mode1&02 != 0 { if mode1&0o2 != 0 {
mode1 |= (mode0 & 01) mode1 |= (mode0 & 0o1)
} }
mode1 &= 0311 // 0311 to preserve the non-setting on "writable" mode1 &= 0o311 // 0311 to preserve the non-setting on "writable"
}
self._write(name, value1, mode1)
} }
obj.writeProperty(name, value1, mode1)
return true return true
} }
Reject:
if throw {
panic(self.runtime.panicTypeError())
}
return false
}
func objectDelete(self *_object, name string, throw bool) bool { func objectDelete(obj *object, name string, throw bool) bool {
property_ := self.getOwnProperty(name) prop := obj.getOwnProperty(name)
if property_ == nil { if prop == nil {
return true return true
} }
if property_.configurable() { if prop.configurable() {
self._delete(name) obj.deleteProperty(name)
return true return true
} }
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
func objectClone(in *_object, out *_object, clone *_clone) *_object { func objectClone(in *object, out *object, clone *cloner) *object {
*out = *in *out = *in
out.runtime = clone.runtime out.runtime = clone.runtime
if out.prototype != nil { if out.prototype != nil {
out.prototype = clone.object(in.prototype) out.prototype = clone.object(in.prototype)
} }
out.property = make(map[string]_property, len(in.property)) out.property = make(map[string]property, len(in.property))
out.propertyOrder = make([]string, len(in.propertyOrder)) out.propertyOrder = make([]string, len(in.propertyOrder))
copy(out.propertyOrder, in.propertyOrder) copy(out.propertyOrder, in.propertyOrder)
for index, property := range in.property { for index, prop := range in.property {
out.property[index] = clone.property(property) out.property[index] = clone.property(prop)
} }
switch value := in.value.(type) { switch value := in.value.(type) {
case _nativeFunctionObject: case nativeFunctionObject:
out.value = value out.value = value
case _bindFunctionObject: case bindFunctionObject:
out.value = _bindFunctionObject{ out.value = bindFunctionObject{
target: clone.object(value.target), target: clone.object(value.target),
this: clone.value(value.this), this: clone.value(value.this),
argumentList: clone.valueArray(value.argumentList), argumentList: clone.valueArray(value.argumentList),
} }
case _nodeFunctionObject: case nodeFunctionObject:
out.value = _nodeFunctionObject{ out.value = nodeFunctionObject{
node: value.node, node: value.node,
stash: clone.stash(value.stash), stash: clone.stash(value.stash),
} }
case _argumentsObject: case argumentsObject:
out.value = value.clone(clone) out.value = value.clone(clone)
} }

View file

@ -39,10 +39,8 @@ Set a string
Get the value of an expression Get the value of an expression
value, _ = vm.Run("xyzzy.length") value, _ = vm.Run("xyzzy.length")
{ // iv is an int64 with a value of 16
// value is an int64 with a value of 16 iv, _ := value.ToInteger()
value, _ := value.ToInteger()
}
An error happens An error happens
@ -216,9 +214,7 @@ http://github.com/robertkrimen/natto
Here is some more discussion of the issue: Here is some more discussion of the issue:
* http://book.mixu.net/node/ch2.html * http://book.mixu.net/node/ch2.html
* http://en.wikipedia.org/wiki/Reentrancy_%28computing%29 * http://en.wikipedia.org/wiki/Reentrancy_%28computing%29
* http://aaroncrane.co.uk/2009/02/perl_safe_signals/ * http://aaroncrane.co.uk/2009/02/perl_safe_signals/
*/ */
package otto package otto
@ -232,36 +228,41 @@ import (
"github.com/robertkrimen/otto/registry" "github.com/robertkrimen/otto/registry"
) )
// Otto is the representation of the JavaScript runtime. Each instance of Otto has a self-contained namespace. // Otto is the representation of the JavaScript runtime.
// Each instance of Otto has a self-contained namespace.
type Otto struct { type Otto struct {
// Interrupt is a channel for interrupting the runtime. You can use this to halt a long running execution, for example. // Interrupt is a channel for interrupting the runtime. You can use this to halt a long running execution, for example.
// See "Halting Problem" for more information. // See "Halting Problem" for more information.
Interrupt chan func() Interrupt chan func()
runtime *_runtime runtime *runtime
} }
// New will allocate a new JavaScript runtime // New will allocate a new JavaScript runtime.
func New() *Otto { func New() *Otto {
self := &Otto{ o := &Otto{
runtime: newContext(), runtime: newContext(),
} }
self.runtime.otto = self o.runtime.otto = o
self.runtime.traceLimit = 10 o.runtime.traceLimit = 10
self.Set("console", self.runtime.newConsole()) if err := o.Set("console", o.runtime.newConsole()); err != nil {
panic(err)
}
registry.Apply(func(entry registry.Entry) { registry.Apply(func(entry registry.Entry) {
self.Run(entry.Source()) if _, err := o.Run(entry.Source()); err != nil {
panic(err)
}
}) })
return self return o
} }
func (otto *Otto) clone() *Otto { func (o *Otto) clone() *Otto {
self := &Otto{ n := &Otto{
runtime: otto.runtime.clone(), runtime: o.runtime.clone(),
} }
self.runtime.otto = self n.runtime.otto = n
return self return n
} }
// Run will allocate a new JavaScript runtime, run the given source // Run will allocate a new JavaScript runtime, run the given source
@ -289,8 +290,8 @@ func Run(src interface{}) (*Otto, Value, error) {
// src may also be a Script. // src may also be a Script.
// //
// src may also be a Program, but if the AST has been modified, then runtime behavior is undefined. // src may also be a Program, but if the AST has been modified, then runtime behavior is undefined.
func (self Otto) Run(src interface{}) (Value, error) { func (o Otto) Run(src interface{}) (Value, error) {
value, err := self.runtime.cmpl_run(src, nil) value, err := o.runtime.cmplRun(src, nil)
if !value.safe() { if !value.safe() {
value = Value{} value = Value{}
} }
@ -302,13 +303,13 @@ func (self Otto) Run(src interface{}) (Value, error) {
// By staying in the same scope, the code evaluated has access to everything // By staying in the same scope, the code evaluated has access to everything
// already defined in the current stack frame. This is most useful in, for // already defined in the current stack frame. This is most useful in, for
// example, a debugger call. // example, a debugger call.
func (self Otto) Eval(src interface{}) (Value, error) { func (o Otto) Eval(src interface{}) (Value, error) {
if self.runtime.scope == nil { if o.runtime.scope == nil {
self.runtime.enterGlobalScope() o.runtime.enterGlobalScope()
defer self.runtime.leaveScope() defer o.runtime.leaveScope()
} }
value, err := self.runtime.cmpl_eval(src, nil) value, err := o.runtime.cmplEval(src, nil)
if !value.safe() { if !value.safe() {
value = Value{} value = Value{}
} }
@ -319,10 +320,10 @@ func (self Otto) Eval(src interface{}) (Value, error) {
// //
// If there is an error (like the binding does not exist), then the value // If there is an error (like the binding does not exist), then the value
// will be undefined. // will be undefined.
func (self Otto) Get(name string) (Value, error) { func (o Otto) Get(name string) (Value, error) {
value := Value{} value := Value{}
err := catchPanic(func() { err := catchPanic(func() {
value = self.getValue(name) value = o.getValue(name)
}) })
if !value.safe() { if !value.safe() {
value = Value{} value = Value{}
@ -330,8 +331,8 @@ func (self Otto) Get(name string) (Value, error) {
return value, err return value, err
} }
func (self Otto) getValue(name string) Value { func (o Otto) getValue(name string) Value {
return self.runtime.globalStash.getBinding(name, false) return o.runtime.globalStash.getBinding(name, false)
} }
// Set the top-level binding of the given name to the given value. // Set the top-level binding of the given name to the given value.
@ -343,29 +344,29 @@ func (self Otto) getValue(name string) Value {
// fails), then an error is returned. // fails), then an error is returned.
// //
// If the top-level binding does not exist, it will be created. // If the top-level binding does not exist, it will be created.
func (self Otto) Set(name string, value interface{}) error { func (o Otto) Set(name string, value interface{}) error {
{ val, err := o.ToValue(value)
value, err := self.ToValue(value)
if err != nil { if err != nil {
return err return err
} }
err = catchPanic(func() {
self.setValue(name, value) return catchPanic(func() {
o.setValue(name, val)
}) })
return err
}
} }
func (self Otto) setValue(name string, value Value) { func (o Otto) setValue(name string, value Value) {
self.runtime.globalStash.setValue(name, value, false) o.runtime.globalStash.setValue(name, value, false)
} }
func (self Otto) SetDebuggerHandler(fn func(vm *Otto)) { // SetDebuggerHandler sets the debugger handler to fn.
self.runtime.debugger = fn func (o Otto) SetDebuggerHandler(fn func(vm *Otto)) {
o.runtime.debugger = fn
} }
func (self Otto) SetRandomSource(fn func() float64) { // SetRandomSource sets the random source to fn.
self.runtime.random = fn func (o Otto) SetRandomSource(fn func() float64) {
o.runtime.random = fn
} }
// SetStackDepthLimit sets an upper limit to the depth of the JavaScript // SetStackDepthLimit sets an upper limit to the depth of the JavaScript
@ -376,41 +377,41 @@ func (self Otto) SetRandomSource(fn func() float64) {
// JavaScript makes a call to a Go function, otto won't keep track of what // 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 // happens outside the interpreter. So if your Go function is infinitely
// recursive, you're still in trouble. // recursive, you're still in trouble.
func (self Otto) SetStackDepthLimit(limit int) { func (o Otto) SetStackDepthLimit(limit int) {
self.runtime.stackLimit = limit o.runtime.stackLimit = limit
} }
// SetStackTraceLimit sets an upper limit to the number of stack frames that // SetStackTraceLimit sets an upper limit to the number of stack frames that
// otto will use when formatting an error's stack trace. By default, the limit // otto will use when formatting an error's stack trace. By default, the limit
// is 10. This is consistent with V8 and SpiderMonkey. // is 10. This is consistent with V8 and SpiderMonkey.
// //
// TODO: expose via `Error.stackTraceLimit` // TODO: expose via `Error.stackTraceLimit`.
func (self Otto) SetStackTraceLimit(limit int) { func (o Otto) SetStackTraceLimit(limit int) {
self.runtime.traceLimit = limit o.runtime.traceLimit = limit
} }
// MakeCustomError creates a new Error object with the given name and message, // MakeCustomError creates a new Error object with the given name and message,
// returning it as a Value. // returning it as a Value.
func (self Otto) MakeCustomError(name, message string) Value { func (o Otto) MakeCustomError(name, message string) Value {
return self.runtime.toValue(self.runtime.newError(name, self.runtime.toValue(message), 0)) return o.runtime.toValue(o.runtime.newError(name, o.runtime.toValue(message), 0))
} }
// MakeRangeError creates a new RangeError object with the given message, // MakeRangeError creates a new RangeError object with the given message,
// returning it as a Value. // returning it as a Value.
func (self Otto) MakeRangeError(message string) Value { func (o Otto) MakeRangeError(message string) Value {
return self.runtime.toValue(self.runtime.newRangeError(self.runtime.toValue(message))) return o.runtime.toValue(o.runtime.newRangeError(o.runtime.toValue(message)))
} }
// MakeSyntaxError creates a new SyntaxError object with the given message, // MakeSyntaxError creates a new SyntaxError object with the given message,
// returning it as a Value. // returning it as a Value.
func (self Otto) MakeSyntaxError(message string) Value { func (o Otto) MakeSyntaxError(message string) Value {
return self.runtime.toValue(self.runtime.newSyntaxError(self.runtime.toValue(message))) return o.runtime.toValue(o.runtime.newSyntaxError(o.runtime.toValue(message)))
} }
// MakeTypeError creates a new TypeError object with the given message, // MakeTypeError creates a new TypeError object with the given message,
// returning it as a Value. // returning it as a Value.
func (self Otto) MakeTypeError(message string) Value { func (o Otto) MakeTypeError(message string) Value {
return self.runtime.toValue(self.runtime.newTypeError(self.runtime.toValue(message))) return o.runtime.toValue(o.runtime.newTypeError(o.runtime.toValue(message)))
} }
// Context is a structure that contains information about the current execution // Context is a structure that contains information about the current execution
@ -427,48 +428,49 @@ type Context struct {
// Context returns the current execution context of the vm, traversing up to // Context returns the current execution context of the vm, traversing up to
// ten stack frames, and skipping any innermost native function stack frames. // ten stack frames, and skipping any innermost native function stack frames.
func (self Otto) Context() Context { func (o Otto) Context() Context {
return self.ContextSkip(10, true) return o.ContextSkip(10, true)
} }
// ContextLimit returns the current execution context of the vm, with a // ContextLimit returns the current execution context of the vm, with a
// specific limit on the number of stack frames to traverse, skipping any // specific limit on the number of stack frames to traverse, skipping any
// innermost native function stack frames. // innermost native function stack frames.
func (self Otto) ContextLimit(limit int) Context { func (o Otto) ContextLimit(limit int) Context {
return self.ContextSkip(limit, true) return o.ContextSkip(limit, true)
} }
// ContextSkip returns the current execution context of the vm, with a // ContextSkip returns the current execution context of the vm, with a
// specific limit on the number of stack frames to traverse, optionally // specific limit on the number of stack frames to traverse, optionally
// skipping any innermost native function stack frames. // skipping any innermost native function stack frames.
func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) { func (o Otto) ContextSkip(limit int, skipNative bool) Context {
// Ensure we are operating in a scope // Ensure we are operating in a scope
if self.runtime.scope == nil { if o.runtime.scope == nil {
self.runtime.enterGlobalScope() o.runtime.enterGlobalScope()
defer self.runtime.leaveScope() defer o.runtime.leaveScope()
} }
scope := self.runtime.scope curScope := o.runtime.scope
frame := scope.frame frm := curScope.frame
for skipNative && frame.native && scope.outer != nil { for skipNative && frm.native && curScope.outer != nil {
scope = scope.outer curScope = curScope.outer
frame = scope.frame frm = curScope.frame
} }
// Get location information // Get location information
var ctx Context
ctx.Filename = "<unknown>" ctx.Filename = "<unknown>"
ctx.Callee = frame.callee ctx.Callee = frm.callee
switch { switch {
case frame.native: case frm.native:
ctx.Filename = frame.nativeFile ctx.Filename = frm.nativeFile
ctx.Line = frame.nativeLine ctx.Line = frm.nativeLine
ctx.Column = 0 ctx.Column = 0
case frame.file != nil: case frm.file != nil:
ctx.Filename = "<anonymous>" ctx.Filename = "<anonymous>"
if p := frame.file.Position(file.Idx(frame.offset)); p != nil { if p := frm.file.Position(file.Idx(frm.offset)); p != nil {
ctx.Line = p.Line ctx.Line = p.Line
ctx.Column = p.Column ctx.Column = p.Column
@ -479,14 +481,14 @@ func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) {
} }
// Get the current scope this Value // Get the current scope this Value
ctx.This = toValue_object(scope.this) ctx.This = objectValue(curScope.this)
// Build stacktrace (up to 10 levels deep) // Build stacktrace (up to 10 levels deep)
ctx.Symbols = make(map[string]Value) ctx.Symbols = make(map[string]Value)
ctx.Stacktrace = append(ctx.Stacktrace, frame.location()) ctx.Stacktrace = append(ctx.Stacktrace, frm.location())
for limit != 0 { for limit != 0 {
// Get variables // Get variables
stash := scope.lexical stash := curScope.lexical
for { for {
for _, name := range getStashProperties(stash) { for _, name := range getStashProperties(stash) {
if _, ok := ctx.Symbols[name]; !ok { if _, ok := ctx.Symbols[name]; !ok {
@ -499,17 +501,17 @@ func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) {
} }
} }
scope = scope.outer curScope = curScope.outer
if scope == nil { if curScope == nil {
break break
} }
if scope.frame.offset >= 0 { if curScope.frame.offset >= 0 {
ctx.Stacktrace = append(ctx.Stacktrace, scope.frame.location()) ctx.Stacktrace = append(ctx.Stacktrace, curScope.frame.location())
} }
limit-- limit--
} }
return return ctx
} }
// Call the given JavaScript with a given this and arguments. // Call the given JavaScript with a given this and arguments.
@ -531,7 +533,7 @@ func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) {
// // This will perform a concat on the given array and return the result // // This will perform a concat on the given array and return the result
// // value is [ 1, 2, 3, undefined, 4, 5, 6, 7, "abc" ] // // value is [ 1, 2, 3, undefined, 4, 5, 6, 7, "abc" ]
// value, _ := vm.Call(`[ 1, 2, 3, undefined, 4 ].concat`, nil, 5, 6, 7, "abc") // value, _ := vm.Call(`[ 1, 2, 3, undefined, 4 ].concat`, nil, 5, 6, 7, "abc")
func (self Otto) Call(source string, this interface{}, argumentList ...interface{}) (Value, error) { func (o Otto) Call(source string, this interface{}, argumentList ...interface{}) (Value, error) {
thisValue := Value{} thisValue := Value{}
construct := false construct := false
@ -541,19 +543,19 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
} }
// FIXME enterGlobalScope // FIXME enterGlobalScope
self.runtime.enterGlobalScope() o.runtime.enterGlobalScope()
defer func() { defer func() {
self.runtime.leaveScope() o.runtime.leaveScope()
}() }()
if !construct && this == nil { if !construct && this == nil {
program, err := self.runtime.cmpl_parse("", source+"()", nil) program, err := o.runtime.cmplParse("", source+"()", nil)
if err == nil { if err == nil {
if node, ok := program.body[0].(*_nodeExpressionStatement); ok { if node, ok := program.body[0].(*nodeExpressionStatement); ok {
if node, ok := node.expression.(*_nodeCallExpression); ok { if node, ok := node.expression.(*nodeCallExpression); ok {
var value Value var value Value
err := catchPanic(func() { err := catchPanic(func() {
value = self.runtime.cmpl_evaluate_nodeCallExpression(node, argumentList) value = o.runtime.cmplEvaluateNodeCallExpression(node, argumentList)
}) })
if err != nil { if err != nil {
return Value{}, err return Value{}, err
@ -563,36 +565,33 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
} }
} }
} else { } else {
value, err := self.ToValue(this) value, err := o.ToValue(this)
if err != nil { if err != nil {
return Value{}, err return Value{}, err
} }
thisValue = value thisValue = value
} }
{ val := thisValue
this := thisValue fn, err := o.Run(source)
fn, err := self.Run(source)
if err != nil { if err != nil {
return Value{}, err return Value{}, err
} }
if construct { if construct {
result, err := fn.constructSafe(self.runtime, this, argumentList...) result, err := fn.constructSafe(o.runtime, val, argumentList...)
if err != nil { if err != nil {
return Value{}, err return Value{}, err
} }
return result, nil return result, nil
} }
result, err := fn.Call(this, argumentList...) result, err := fn.Call(val, argumentList...)
if err != nil { if err != nil {
return Value{}, err return Value{}, err
} }
return result, nil return result, nil
} }
}
// Object will run the given source and return the result as an object. // Object will run the given source and return the result as an object.
// //
@ -611,8 +610,8 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
// //
// If there is an error (like the source does not result in an object), then // If there is an error (like the source does not result in an object), then
// nil and an error is returned. // nil and an error is returned.
func (self Otto) Object(source string) (*Object, error) { func (o Otto) Object(source string) (*Object, error) {
value, err := self.runtime.cmpl_run(source, nil) value, err := o.runtime.cmplRun(source, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -623,8 +622,8 @@ func (self Otto) Object(source string) (*Object, error) {
} }
// ToValue will convert an interface{} value to a value digestible by otto/JavaScript. // ToValue will convert an interface{} value to a value digestible by otto/JavaScript.
func (self Otto) ToValue(value interface{}) (Value, error) { func (o Otto) ToValue(value interface{}) (Value, error) {
return self.runtime.safeToValue(value) return o.runtime.safeToValue(value)
} }
// Copy will create a copy/clone of the runtime. // Copy will create a copy/clone of the runtime.
@ -635,30 +634,20 @@ func (self Otto) ToValue(value interface{}) (Value, error) {
// etc. into a new runtime. // etc. into a new runtime.
// //
// Be on the lookout for memory leaks or inadvertent sharing of resources. // Be on the lookout for memory leaks or inadvertent sharing of resources.
func (in *Otto) Copy() *Otto { func (o *Otto) Copy() *Otto {
out := &Otto{ out := &Otto{
runtime: in.runtime.clone(), runtime: o.runtime.clone(),
} }
out.runtime.otto = out out.runtime.otto = out
return out return out
} }
// Object{}
// Object is the representation of a JavaScript object. // Object is the representation of a JavaScript object.
type Object struct { type Object struct {
object *_object object *object
value Value value Value
} }
func _newObject(object *_object, value Value) *Object {
// value MUST contain object!
return &Object{
object: object,
value: value,
}
}
// Call a method on the object. // Call a method on the object.
// //
// It is essentially equivalent to: // It is essentially equivalent to:
@ -671,27 +660,27 @@ func _newObject(object *_object, value Value) *Object {
// 1. There is an error during conversion of the argument list // 1. There is an error during conversion of the argument list
// 2. The property is not actually a function // 2. The property is not actually a function
// 3. An (uncaught) exception is thrown // 3. An (uncaught) exception is thrown
func (self Object) Call(name string, argumentList ...interface{}) (Value, error) { func (o Object) Call(name string, argumentList ...interface{}) (Value, error) {
// TODO: Insert an example using JavaScript below... // TODO: Insert an example using JavaScript below...
// e.g., Object("JSON").Call("stringify", ...) // e.g., Object("JSON").Call("stringify", ...)
function, err := self.Get(name) function, err := o.Get(name)
if err != nil { if err != nil {
return Value{}, err return Value{}, err
} }
return function.Call(self.Value(), argumentList...) return function.Call(o.Value(), argumentList...)
} }
// Value will return self as a value. // Value returns the value of o.
func (self Object) Value() Value { func (o Object) Value() Value {
return self.value return o.value
} }
// Get the value of the property with the given name. // Get the value of the property with the given name.
func (self Object) Get(name string) (Value, error) { func (o Object) Get(name string) (Value, error) {
value := Value{} value := Value{}
err := catchPanic(func() { err := catchPanic(func() {
value = self.object.get(name) value = o.object.get(name)
}) })
if !value.safe() { if !value.safe() {
value = Value{} value = Value{}
@ -703,25 +692,23 @@ func (self Object) Get(name string) (Value, error) {
// //
// An error will result if the setting the property triggers an exception (i.e. read-only), // An error will result if the setting the property triggers an exception (i.e. read-only),
// or there is an error during conversion of the given value. // or there is an error during conversion of the given value.
func (self Object) Set(name string, value interface{}) error { func (o Object) Set(name string, value interface{}) error {
{ val, err := o.object.runtime.safeToValue(value)
value, err := self.object.runtime.safeToValue(value)
if err != nil { if err != nil {
return err return err
} }
err = catchPanic(func() {
self.object.put(name, value, true) return catchPanic(func() {
o.object.put(name, val, true)
}) })
return err
}
} }
// Keys gets the keys for the given object. // Keys gets the keys for the given object.
// //
// Equivalent to calling Object.keys on the object. // Equivalent to calling Object.keys on the object.
func (self Object) Keys() []string { func (o Object) Keys() []string {
var keys []string var keys []string
self.object.enumerate(false, func(name string) bool { o.object.enumerate(false, func(name string) bool {
keys = append(keys, name) keys = append(keys, name)
return true return true
}) })
@ -730,10 +717,10 @@ func (self Object) Keys() []string {
// KeysByParent gets the keys (and those of the parents) for the given object, // KeysByParent gets the keys (and those of the parents) for the given object,
// in order of "closest" to "furthest". // in order of "closest" to "furthest".
func (self Object) KeysByParent() [][]string { func (o Object) KeysByParent() [][]string {
var a [][]string var a [][]string
for o := self.object; o != nil; o = o.prototype { for o := o.object; o != nil; o = o.prototype {
var l []string var l []string
o.enumerate(false, func(name string) bool { o.enumerate(false, func(name string) bool {
@ -759,28 +746,29 @@ func (self Object) KeysByParent() [][]string {
// Boolean // Boolean
// Date // Date
// RegExp // RegExp
func (self Object) Class() string { func (o Object) Class() string {
return self.object.class return o.object.class
} }
func (self Object) MarshalJSON() ([]byte, error) { // MarshalJSON implements json.Marshaller.
func (o Object) MarshalJSON() ([]byte, error) {
var goValue interface{} var goValue interface{}
switch value := self.object.value.(type) { switch value := o.object.value.(type) {
case *_goStructObject: case *goStructObject:
goValue = value.value.Interface() goValue = value.value.Interface()
case *_goMapObject: case *goMapObject:
goValue = value.value.Interface() goValue = value.value.Interface()
case *_goArrayObject: case *goArrayObject:
goValue = value.value.Interface() goValue = value.value.Interface()
case *_goSliceObject: case *goSliceObject:
goValue = value.value.Interface() goValue = value.value.Interface()
default: default:
// It's a JS object; pass it to JSON.stringify: // It's a JS object; pass it to JSON.stringify:
var result []byte var result []byte
err := catchPanic(func() { err := catchPanic(func() {
resultVal := builtinJSON_stringify(FunctionCall{ resultVal := builtinJSONStringify(FunctionCall{
runtime: self.object.runtime, runtime: o.object.runtime,
ArgumentList: []Value{self.value}, ArgumentList: []Value{o.value},
}) })
result = []byte(resultVal.String()) result = []byte(resultVal.String())
}) })

View file

@ -3,28 +3,28 @@ package otto
import ( import (
"fmt" "fmt"
"regexp" "regexp"
runtime_ "runtime" goruntime "runtime"
"strconv" "strconv"
) )
var isIdentifier_Regexp *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z\$][a-zA-Z0-9\$]*$`) var isIdentifierRegexp *regexp.Regexp = regexp.MustCompile(`^[a-zA-Z\$][a-zA-Z0-9\$]*$`)
func isIdentifier(string_ string) bool { func isIdentifier(value string) bool {
return isIdentifier_Regexp.MatchString(string_) return isIdentifierRegexp.MatchString(value)
} }
func (self *_runtime) toValueArray(arguments ...interface{}) []Value { func (rt *runtime) toValueArray(arguments ...interface{}) []Value {
length := len(arguments) length := len(arguments)
if length == 1 { if length == 1 {
if valueArray, ok := arguments[0].([]Value); ok { if valueArray, ok := arguments[0].([]Value); ok {
return valueArray return valueArray
} }
return []Value{self.toValue(arguments[0])} return []Value{rt.toValue(arguments[0])}
} }
valueArray := make([]Value, length) valueArray := make([]Value, length)
for index, value := range arguments { for index, value := range arguments {
valueArray[index] = self.toValue(value) valueArray[index] = rt.toValue(value)
} }
return valueArray return valueArray
@ -89,15 +89,13 @@ func valueToRangeIndex(indexValue Value, length int64, negativeIsZero bool) int6
if index < 0 { if index < 0 {
index = 0 index = 0
} }
} else { } else if index > length {
if index > length {
index = length index = length
} }
}
return index return index
} }
func rangeStartEnd(array []Value, size int64, negativeIsZero bool) (start, end int64) { func rangeStartEnd(array []Value, size int64, negativeIsZero bool) (start, end int64) { //nolint: nonamedreturns
start = valueToRangeIndex(valueOfArrayIndex(array, 0), size, negativeIsZero) start = valueToRangeIndex(valueOfArrayIndex(array, 0), size, negativeIsZero)
if len(array) == 1 { if len(array) == 1 {
// If there is only the start argument, then end = size // If there is only the start argument, then end = size
@ -115,14 +113,14 @@ func rangeStartEnd(array []Value, size int64, negativeIsZero bool) (start, end i
return return
} }
func rangeStartLength(source []Value, size int64) (start, length int64) { func rangeStartLength(source []Value, size int64) (start, length int64) { //nolint: nonamedreturns
start = valueToRangeIndex(valueOfArrayIndex(source, 0), size, false) start = valueToRangeIndex(valueOfArrayIndex(source, 0), size, false)
// Assume the second argument is missing or undefined // Assume the second argument is missing or undefined
length = int64(size) length = size
if len(source) == 1 { if len(source) == 1 {
// If there is only the start argument, then length = size // If there is only the start argument, then length = size
return return start, length
} }
lengthValue := valueOfArrayIndex(source, 1) lengthValue := valueOfArrayIndex(source, 1)
@ -130,12 +128,12 @@ func rangeStartLength(source []Value, size int64) (start, length int64) {
// Which it is not, so get the value as an array index // Which it is not, so get the value as an array index
length = lengthValue.number().int64 length = lengthValue.number().int64
} }
return return start, length
} }
func hereBeDragons(arguments ...interface{}) string { func hereBeDragons(arguments ...interface{}) string {
pc, _, _, _ := runtime_.Caller(1) //nolint: dogsled pc, _, _, _ := goruntime.Caller(1) //nolint: dogsled
name := runtime_.FuncForPC(pc).Name() name := goruntime.FuncForPC(pc).Name()
message := fmt.Sprintf("Here be dragons -- %s", name) message := fmt.Sprintf("Here be dragons -- %s", name)
if len(arguments) > 0 { if len(arguments) > 0 {
message += ": " message += ": "

View file

@ -1,4 +0,0 @@
.PHONY: test
test:
go test

View file

@ -1,190 +0,0 @@
# parser
--
import "github.com/robertkrimen/otto/parser"
Package parser implements a parser for JavaScript.
import (
"github.com/robertkrimen/otto/parser"
)
Parse and return an AST
filename := "" // A filename is optional
src := `
// Sample xyzzy example
(function(){
if (3.14159 > 0) {
console.log("Hello, World.");
return;
}
var xyzzy = NaN;
console.log("Nothing happens.");
return xyzzy;
})();
`
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
program, err := parser.ParseFile(nil, filename, src, 0)
### Warning
The parser and AST interfaces are still works-in-progress (particularly where
node types are concerned) and may change in the future.
## Usage
#### func ParseFile
```go
func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error)
```
ParseFile parses the source code of a single JavaScript/ECMAScript source file
and returns the corresponding ast.Program node.
If fileSet == nil, ParseFile parses source without a FileSet. If fileSet != nil,
ParseFile first adds filename and src to fileSet.
The filename argument is optional and is used for labelling errors, etc.
src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST
always be in UTF-8.
// Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
#### func ParseFunction
```go
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error)
```
ParseFunction parses a given parameter list and body as a function and returns
the corresponding ast.FunctionLiteral node.
The parameter list, if any, should be a comma-separated list of identifiers.
#### func ReadSource
```go
func ReadSource(filename string, src interface{}) ([]byte, error)
```
#### func TransformRegExp
```go
func TransformRegExp(pattern string) (string, error)
```
TransformRegExp transforms a JavaScript pattern into a Go "regexp" pattern.
re2 (Go) cannot do backtracking, so the presence of a lookahead (?=) (?!) or
backreference (\1, \2, ...) will cause an error.
re2 (Go) has a different definition for \s: [\t\n\f\r ]. The JavaScript
definition, on the other hand, also includes \v, Unicode "Separator, Space",
etc.
If the pattern is invalid (not valid even in JavaScript), then this function
returns the empty string and an error.
If the pattern is valid, but incompatible (contains a lookahead or
backreference), then this function returns the transformation (a non-empty
string) AND an error.
#### type Error
```go
type Error struct {
Position file.Position
Message string
}
```
An Error represents a parsing error. It includes the position where the error
occurred and a message/description.
#### func (Error) Error
```go
func (self Error) Error() string
```
#### type ErrorList
```go
type ErrorList []*Error
```
ErrorList is a list of *Errors.
#### func (*ErrorList) Add
```go
func (self *ErrorList) Add(position file.Position, msg string)
```
Add adds an Error with given position and message to an ErrorList.
#### func (ErrorList) Err
```go
func (self ErrorList) Err() error
```
Err returns an error equivalent to this ErrorList. If the list is empty, Err
returns nil.
#### func (ErrorList) Error
```go
func (self ErrorList) Error() string
```
Error implements the Error interface.
#### func (ErrorList) Len
```go
func (self ErrorList) Len() int
```
#### func (ErrorList) Less
```go
func (self ErrorList) Less(i, j int) bool
```
#### func (*ErrorList) Reset
```go
func (self *ErrorList) Reset()
```
Reset resets an ErrorList to no errors.
#### func (ErrorList) Sort
```go
func (self ErrorList) Sort()
```
#### func (ErrorList) Swap
```go
func (self ErrorList) Swap(i, j int)
```
#### type Mode
```go
type Mode uint
```
A Mode value is a set of flags (or 0). They control optional parser
functionality.
```go
const (
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
)
```
--
**godocdown** http://github.com/robertkrimen/godocdown

View file

@ -9,8 +9,8 @@ import (
) )
const ( const (
err_UnexpectedToken = "Unexpected token %v" errUnexpectedToken = "Unexpected token %v"
err_UnexpectedEndOfInput = "Unexpected end of input" errUnexpectedEndOfInput = "Unexpected end of input"
) )
// UnexpectedNumber: 'Unexpected number', // UnexpectedNumber: 'Unexpected number',
@ -20,7 +20,7 @@ const (
// NewlineAfterThrow: 'Illegal newline after throw', // NewlineAfterThrow: 'Illegal newline after throw',
// InvalidRegExp: 'Invalid regular expression', // InvalidRegExp: 'Invalid regular expression',
// UnterminatedRegExp: 'Invalid regular expression: missing /', // UnterminatedRegExp: 'Invalid regular expression: missing /',
// InvalidLHSInAssignment: 'Invalid left-hand side in assignment', // InvalidLHSInAssignment: 'invalid left-hand side in assignment',
// InvalidLHSInForIn: 'Invalid left-hand side in for-in', // InvalidLHSInForIn: 'Invalid left-hand side in for-in',
// MultipleDefaultsInSwitch: 'More than one default clause in switch statement', // MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
// NoCatchOrFinally: 'Missing catch or finally after try', // NoCatchOrFinally: 'Missing catch or finally after try',
@ -55,27 +55,27 @@ type Error struct {
// FIXME Should this be "SyntaxError"? // FIXME Should this be "SyntaxError"?
func (self Error) Error() string { func (e Error) Error() string {
filename := self.Position.Filename filename := e.Position.Filename
if filename == "" { if filename == "" {
filename = "(anonymous)" filename = "(anonymous)"
} }
return fmt.Sprintf("%s: Line %d:%d %s", return fmt.Sprintf("%s: Line %d:%d %s",
filename, filename,
self.Position.Line, e.Position.Line,
self.Position.Column, e.Position.Column,
self.Message, e.Message,
) )
} }
func (self *_parser) error(place interface{}, msg string, msgValues ...interface{}) *Error { func (p *parser) error(place interface{}, msg string, msgValues ...interface{}) {
var idx file.Idx var idx file.Idx
switch place := place.(type) { switch place := place.(type) {
case int: case int:
idx = self.idxOf(place) idx = p.idxOf(place)
case file.Idx: case file.Idx:
if place == 0 { if place == 0 {
idx = self.idxOf(self.chrOffset) idx = p.idxOf(p.chrOffset)
} else { } else {
idx = place idx = place
} }
@ -83,57 +83,69 @@ func (self *_parser) error(place interface{}, msg string, msgValues ...interface
panic(fmt.Errorf("error(%T, ...)", place)) panic(fmt.Errorf("error(%T, ...)", place))
} }
position := self.position(idx) position := p.position(idx)
msg = fmt.Sprintf(msg, msgValues...) msg = fmt.Sprintf(msg, msgValues...)
self.errors.Add(position, msg) p.errors.Add(position, msg)
return self.errors[len(self.errors)-1]
} }
func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error { func (p *parser) errorUnexpected(idx file.Idx, chr rune) {
if chr == -1 { if chr == -1 {
return self.error(idx, err_UnexpectedEndOfInput) p.error(idx, errUnexpectedEndOfInput)
return
} }
return self.error(idx, err_UnexpectedToken, token.ILLEGAL) p.error(idx, errUnexpectedToken, token.ILLEGAL)
} }
func (self *_parser) errorUnexpectedToken(tkn token.Token) error { func (p *parser) errorUnexpectedToken(tkn token.Token) {
switch tkn { if tkn == token.EOF {
case token.EOF: p.error(file.Idx(0), errUnexpectedEndOfInput)
return self.error(file.Idx(0), err_UnexpectedEndOfInput) return
} }
value := tkn.String() value := tkn.String()
switch tkn { switch tkn {
case token.BOOLEAN, token.NULL: case token.BOOLEAN, token.NULL:
value = self.literal p.error(p.idx, errUnexpectedToken, p.literal)
case token.IDENTIFIER: case token.IDENTIFIER:
return self.error(self.idx, "Unexpected identifier") p.error(p.idx, "Unexpected identifier")
case token.KEYWORD: case token.KEYWORD:
// TODO Might be a future reserved word // TODO Might be a future reserved word
return self.error(self.idx, "Unexpected reserved word") p.error(p.idx, "Unexpected reserved word")
case token.NUMBER: case token.NUMBER:
return self.error(self.idx, "Unexpected number") p.error(p.idx, "Unexpected number")
case token.STRING: case token.STRING:
return self.error(self.idx, "Unexpected string") p.error(p.idx, "Unexpected string")
default:
p.error(p.idx, errUnexpectedToken, value)
} }
return self.error(self.idx, err_UnexpectedToken, value)
} }
// ErrorList is a list of *Errors. // ErrorList is a list of *Errors.
type ErrorList []*Error //nolint: errname type ErrorList []*Error //nolint: errname
// Add adds an Error with given position and message to an ErrorList. // Add adds an Error with given position and message to an ErrorList.
func (self *ErrorList) Add(position file.Position, msg string) { func (el *ErrorList) Add(position file.Position, msg string) {
*self = append(*self, &Error{position, msg}) *el = append(*el, &Error{position, msg})
} }
// Reset resets an ErrorList to no errors. // Reset resets an ErrorList to no errors.
func (self *ErrorList) Reset() { *self = (*self)[0:0] } func (el *ErrorList) Reset() {
*el = (*el)[0:0]
}
func (self ErrorList) Len() int { return len(self) } // Len implement sort.Interface.
func (self ErrorList) Swap(i, j int) { self[i], self[j] = self[j], self[i] } func (el *ErrorList) Len() int {
func (self ErrorList) Less(i, j int) bool { return len(*el)
x := &self[i].Position }
y := &self[j].Position
// Swap implement sort.Interface.
func (el *ErrorList) Swap(i, j int) {
(*el)[i], (*el)[j] = (*el)[j], (*el)[i]
}
// Less implement sort.Interface.
func (el *ErrorList) Less(i, j int) bool {
x := (*el)[i].Position
y := (*el)[j].Position
if x.Filename < y.Filename { if x.Filename < y.Filename {
return true return true
} }
@ -148,26 +160,28 @@ func (self ErrorList) Less(i, j int) bool {
return false return false
} }
func (self ErrorList) Sort() { // Sort sorts el.
sort.Sort(self) func (el *ErrorList) Sort() {
sort.Sort(el)
} }
// Error implements the Error interface. // Error implements the Error interface.
func (self ErrorList) Error() string { func (el *ErrorList) Error() string {
switch len(self) { switch len(*el) {
case 0: case 0:
return "no errors" return "no errors"
case 1: case 1:
return self[0].Error() return (*el)[0].Error()
default:
return fmt.Sprintf("%s (and %d more errors)", (*el)[0].Error(), len(*el)-1)
} }
return fmt.Sprintf("%s (and %d more errors)", self[0].Error(), len(self)-1)
} }
// Err returns an error equivalent to this ErrorList. // Err returns an error equivalent to this ErrorList.
// If the list is empty, Err returns nil. // If the list is empty, Err returns nil.
func (self ErrorList) Err() error { func (el *ErrorList) Err() error {
if len(self) == 0 { if len(*el) == 0 {
return nil return nil
} }
return self return el
} }

File diff suppressed because it is too large Load diff

View file

@ -15,7 +15,7 @@ import (
"github.com/robertkrimen/otto/token" "github.com/robertkrimen/otto/token"
) )
type _chr struct { type chr struct { //nolint: unused
value rune value rune
width int width int
} }
@ -55,49 +55,50 @@ func isIdentifierPart(chr rune) bool {
chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr)) chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
} }
func (self *_parser) scanIdentifier() (string, error) { func (p *parser) scanIdentifier() (string, error) {
offset := self.chrOffset offset := p.chrOffset
parse := false parse := false
for isIdentifierPart(self.chr) { for isIdentifierPart(p.chr) {
if self.chr == '\\' { if p.chr == '\\' {
distance := self.chrOffset - offset distance := p.chrOffset - offset
self.read() p.read()
if self.chr != 'u' { if p.chr != 'u' {
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) return "", fmt.Errorf("invalid identifier escape character: %c (%s)", p.chr, string(p.chr))
} }
parse = true parse = true
var value rune var value rune
for j := 0; j < 4; j++ { for j := 0; j < 4; j++ {
self.read() p.read()
decimal, ok := hex2decimal(byte(self.chr)) decimal, ok := hex2decimal(byte(p.chr))
if !ok { if !ok {
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) return "", fmt.Errorf("invalid identifier escape character: %c (%s)", p.chr, string(p.chr))
} }
value = value<<4 | decimal value = value<<4 | decimal
} }
if value == '\\' { switch {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) case value == '\\':
} else if distance == 0 { return "", fmt.Errorf("invalid identifier escape value: %c (%s)", value, string(value))
case distance == 0:
if !isIdentifierStart(value) { if !isIdentifierStart(value) {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) return "", fmt.Errorf("invalid identifier escape value: %c (%s)", value, string(value))
} }
} else if distance > 0 { case distance > 0:
if !isIdentifierPart(value) { if !isIdentifierPart(value) {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value)) return "", fmt.Errorf("invalid identifier escape value: %c (%s)", value, string(value))
} }
} }
} }
self.read() p.read()
} }
literal := string(self.str[offset:self.chrOffset]) literal := p.str[offset:p.chrOffset]
if parse { if parse {
return parseStringLiteral(literal) return parseStringLiteral(literal)
} }
return literal, nil return literal, nil
} }
// 7.2 // 7.2.
func isLineWhiteSpace(chr rune) bool { func isLineWhiteSpace(chr rune) bool { //nolint: unused, deadcode
switch chr { switch chr {
case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff': case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff':
return true return true
@ -109,7 +110,7 @@ func isLineWhiteSpace(chr rune) bool {
return unicode.IsSpace(chr) return unicode.IsSpace(chr)
} }
// 7.3 // 7.3.
func isLineTerminator(chr rune) bool { func isLineTerminator(chr rune) bool {
switch chr { switch chr {
case '\u000a', '\u000d', '\u2028', '\u2029': case '\u000a', '\u000d', '\u2028', '\u2029':
@ -118,19 +119,19 @@ func isLineTerminator(chr rune) bool {
return false return false
} }
func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) { func (p *parser) scan() (tkn token.Token, literal string, idx file.Idx) { //nolint: nonamedreturns
self.implicitSemicolon = false p.implicitSemicolon = false
for { for {
self.skipWhiteSpace() p.skipWhiteSpace()
idx = self.idxOf(self.chrOffset) idx = p.idxOf(p.chrOffset)
insertSemicolon := false insertSemicolon := false
switch chr := self.chr; { switch chr := p.chr; {
case isIdentifierStart(chr): case isIdentifierStart(chr):
var err error var err error
literal, err = self.scanIdentifier() literal, err = p.scanIdentifier()
if err != nil { if err != nil {
tkn = token.ILLEGAL tkn = token.ILLEGAL
break break
@ -142,23 +143,20 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
switch tkn { switch tkn {
case 0: // Not a keyword case 0: // Not a keyword
if literal == "true" || literal == "false" { switch literal {
self.insertSemicolon = true case "true", "false":
tkn = token.BOOLEAN p.insertSemicolon = true
return return token.BOOLEAN, literal, idx
} else if literal == "null" { case "null":
self.insertSemicolon = true p.insertSemicolon = true
tkn = token.NULL return token.NULL, literal, idx
return
} }
case token.KEYWORD: case token.KEYWORD:
tkn = token.KEYWORD
if strict { if strict {
// TODO If strict and in strict mode, then this is not a break // TODO If strict and in strict mode, then this is not a break
break break
} }
return return token.KEYWORD, literal, idx
case case
token.THIS, token.THIS,
@ -167,40 +165,39 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
token.RETURN, token.RETURN,
token.CONTINUE, token.CONTINUE,
token.DEBUGGER: token.DEBUGGER:
self.insertSemicolon = true p.insertSemicolon = true
return return tkn, literal, idx
default: default:
return return tkn, literal, idx
} }
} }
self.insertSemicolon = true p.insertSemicolon = true
tkn = token.IDENTIFIER return token.IDENTIFIER, literal, idx
return
case '0' <= chr && chr <= '9': case '0' <= chr && chr <= '9':
self.insertSemicolon = true p.insertSemicolon = true
tkn, literal = self.scanNumericLiteral(false) tkn, literal = p.scanNumericLiteral(false)
return return tkn, literal, idx
default: default:
self.read() p.read()
switch chr { switch chr {
case -1: case -1:
if self.insertSemicolon { if p.insertSemicolon {
self.insertSemicolon = false p.insertSemicolon = false
self.implicitSemicolon = true p.implicitSemicolon = true
} }
tkn = token.EOF tkn = token.EOF
case '\r', '\n', '\u2028', '\u2029': case '\r', '\n', '\u2028', '\u2029':
self.insertSemicolon = false p.insertSemicolon = false
self.implicitSemicolon = true p.implicitSemicolon = true
self.comments.AtLineBreak() p.comments.AtLineBreak()
continue continue
case ':': case ':':
tkn = token.COLON tkn = token.COLON
case '.': case '.':
if digitValue(self.chr) < 10 { if digitValue(p.chr) < 10 {
insertSemicolon = true insertSemicolon = true
tkn, literal = self.scanNumericLiteral(true) tkn, literal = p.scanNumericLiteral(true)
} else { } else {
tkn = token.PERIOD tkn = token.PERIOD
} }
@ -224,68 +221,69 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
tkn = token.RIGHT_BRACE tkn = token.RIGHT_BRACE
insertSemicolon = true insertSemicolon = true
case '+': case '+':
tkn = self.switch3(token.PLUS, token.ADD_ASSIGN, '+', token.INCREMENT) tkn = p.switch3(token.PLUS, token.ADD_ASSIGN, '+', token.INCREMENT)
if tkn == token.INCREMENT { if tkn == token.INCREMENT {
insertSemicolon = true insertSemicolon = true
} }
case '-': case '-':
tkn = self.switch3(token.MINUS, token.SUBTRACT_ASSIGN, '-', token.DECREMENT) tkn = p.switch3(token.MINUS, token.SUBTRACT_ASSIGN, '-', token.DECREMENT)
if tkn == token.DECREMENT { if tkn == token.DECREMENT {
insertSemicolon = true insertSemicolon = true
} }
case '*': case '*':
tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN) tkn = p.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
case '/': case '/':
if self.chr == '/' { switch p.chr {
if self.mode&StoreComments != 0 { case '/':
literal := string(self.readSingleLineComment()) if p.mode&StoreComments != 0 {
self.comments.AddComment(ast.NewComment(literal, self.idx)) literal := string(p.readSingleLineComment())
p.comments.AddComment(ast.NewComment(literal, idx))
continue continue
} }
self.skipSingleLineComment() p.skipSingleLineComment()
continue continue
} else if self.chr == '*' { case '*':
if self.mode&StoreComments != 0 { if p.mode&StoreComments != 0 {
literal = string(self.readMultiLineComment()) literal = string(p.readMultiLineComment())
self.comments.AddComment(ast.NewComment(literal, self.idx)) p.comments.AddComment(ast.NewComment(literal, idx))
continue continue
} }
self.skipMultiLineComment() p.skipMultiLineComment()
continue continue
} else { default:
// Could be division, could be RegExp literal // Could be division, could be RegExp literal
tkn = self.switch2(token.SLASH, token.QUOTIENT_ASSIGN) tkn = p.switch2(token.SLASH, token.QUOTIENT_ASSIGN)
insertSemicolon = true insertSemicolon = true
} }
case '%': case '%':
tkn = self.switch2(token.REMAINDER, token.REMAINDER_ASSIGN) tkn = p.switch2(token.REMAINDER, token.REMAINDER_ASSIGN)
case '^': case '^':
tkn = self.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN) tkn = p.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN)
case '<': case '<':
tkn = self.switch4(token.LESS, token.LESS_OR_EQUAL, '<', token.SHIFT_LEFT, token.SHIFT_LEFT_ASSIGN) tkn = p.switch4(token.LESS, token.LESS_OR_EQUAL, '<', token.SHIFT_LEFT, token.SHIFT_LEFT_ASSIGN)
case '>': case '>':
tkn = self.switch6(token.GREATER, token.GREATER_OR_EQUAL, '>', token.SHIFT_RIGHT, token.SHIFT_RIGHT_ASSIGN, '>', token.UNSIGNED_SHIFT_RIGHT, token.UNSIGNED_SHIFT_RIGHT_ASSIGN) tkn = p.switch6(token.GREATER, token.GREATER_OR_EQUAL, '>', token.SHIFT_RIGHT, token.SHIFT_RIGHT_ASSIGN, '>', token.UNSIGNED_SHIFT_RIGHT, token.UNSIGNED_SHIFT_RIGHT_ASSIGN)
case '=': case '=':
tkn = self.switch2(token.ASSIGN, token.EQUAL) tkn = p.switch2(token.ASSIGN, token.EQUAL)
if tkn == token.EQUAL && self.chr == '=' { if tkn == token.EQUAL && p.chr == '=' {
self.read() p.read()
tkn = token.STRICT_EQUAL tkn = token.STRICT_EQUAL
} }
case '!': case '!':
tkn = self.switch2(token.NOT, token.NOT_EQUAL) tkn = p.switch2(token.NOT, token.NOT_EQUAL)
if tkn == token.NOT_EQUAL && self.chr == '=' { if tkn == token.NOT_EQUAL && p.chr == '=' {
self.read() p.read()
tkn = token.STRICT_NOT_EQUAL tkn = token.STRICT_NOT_EQUAL
} }
case '&': case '&':
if self.chr == '^' { if p.chr == '^' {
self.read() p.read()
tkn = self.switch2(token.AND_NOT, token.AND_NOT_ASSIGN) tkn = p.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
} else { } else {
tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND) tkn = p.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
} }
case '|': case '|':
tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR) tkn = p.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
case '~': case '~':
tkn = token.BITWISE_NOT tkn = token.BITWISE_NOT
case '?': case '?':
@ -294,49 +292,49 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
insertSemicolon = true insertSemicolon = true
tkn = token.STRING tkn = token.STRING
var err error var err error
literal, err = self.scanString(self.chrOffset - 1) literal, err = p.scanString(p.chrOffset - 1)
if err != nil { if err != nil {
tkn = token.ILLEGAL tkn = token.ILLEGAL
} }
default: default:
self.errorUnexpected(idx, chr) p.errorUnexpected(idx, chr)
tkn = token.ILLEGAL tkn = token.ILLEGAL
} }
} }
self.insertSemicolon = insertSemicolon p.insertSemicolon = insertSemicolon
return return tkn, literal, idx
} }
} }
func (self *_parser) switch2(tkn0, tkn1 token.Token) token.Token { func (p *parser) switch2(tkn0, tkn1 token.Token) token.Token {
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn1 return tkn1
} }
return tkn0 return tkn0
} }
func (self *_parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token { func (p *parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token {
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn1 return tkn1
} }
if self.chr == chr2 { if p.chr == chr2 {
self.read() p.read()
return tkn2 return tkn2
} }
return tkn0 return tkn0
} }
func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token { func (p *parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token {
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn1 return tkn1
} }
if self.chr == chr2 { if p.chr == chr2 {
self.read() p.read()
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn3 return tkn3
} }
return tkn2 return tkn2
@ -344,21 +342,21 @@ func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token
return tkn0 return tkn0
} }
func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token { func (p *parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token {
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn1 return tkn1
} }
if self.chr == chr2 { if p.chr == chr2 {
self.read() p.read()
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn3 return tkn3
} }
if self.chr == chr3 { if p.chr == chr3 {
self.read() p.read()
if self.chr == '=' { if p.chr == '=' {
self.read() p.read()
return tkn5 return tkn5
} }
return tkn4 return tkn4
@ -368,137 +366,137 @@ func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token
return tkn0 return tkn0
} }
func (self *_parser) chrAt(index int) _chr { func (p *parser) chrAt(index int) chr { //nolint: unused
value, width := utf8.DecodeRuneInString(self.str[index:]) value, width := utf8.DecodeRuneInString(p.str[index:])
return _chr{ return chr{
value: value, value: value,
width: width, width: width,
} }
} }
func (self *_parser) _peek() rune { func (p *parser) peek() rune {
if self.offset+1 < self.length { if p.offset+1 < p.length {
return rune(self.str[self.offset+1]) return rune(p.str[p.offset+1])
} }
return -1 return -1
} }
func (self *_parser) read() { func (p *parser) read() {
if self.offset < self.length { if p.offset < p.length {
self.chrOffset = self.offset p.chrOffset = p.offset
chr, width := rune(self.str[self.offset]), 1 chr, width := rune(p.str[p.offset]), 1
if chr >= utf8.RuneSelf { // !ASCII if chr >= utf8.RuneSelf { // !ASCII
chr, width = utf8.DecodeRuneInString(self.str[self.offset:]) chr, width = utf8.DecodeRuneInString(p.str[p.offset:])
if chr == utf8.RuneError && width == 1 { if chr == utf8.RuneError && width == 1 {
self.error(self.chrOffset, "Invalid UTF-8 character") p.error(p.chrOffset, "Invalid UTF-8 character")
} }
} }
self.offset += width p.offset += width
self.chr = chr p.chr = chr
} else { } else {
self.chrOffset = self.length p.chrOffset = p.length
self.chr = -1 // EOF p.chr = -1 // EOF
} }
} }
// This is here since the functions are so similar // This is here since the functions are so similar.
func (self *_RegExp_parser) read() { func (p *regExpParser) read() {
if self.offset < self.length { if p.offset < p.length {
self.chrOffset = self.offset p.chrOffset = p.offset
chr, width := rune(self.str[self.offset]), 1 chr, width := rune(p.str[p.offset]), 1
if chr >= utf8.RuneSelf { // !ASCII if chr >= utf8.RuneSelf { // !ASCII
chr, width = utf8.DecodeRuneInString(self.str[self.offset:]) chr, width = utf8.DecodeRuneInString(p.str[p.offset:])
if chr == utf8.RuneError && width == 1 { if chr == utf8.RuneError && width == 1 {
self.error(self.chrOffset, "Invalid UTF-8 character") p.error(p.chrOffset, "Invalid UTF-8 character")
} }
} }
self.offset += width p.offset += width
self.chr = chr p.chr = chr
} else { } else {
self.chrOffset = self.length p.chrOffset = p.length
self.chr = -1 // EOF p.chr = -1 // EOF
} }
} }
func (self *_parser) readSingleLineComment() (result []rune) { func (p *parser) readSingleLineComment() []rune {
for self.chr != -1 { var result []rune
self.read() for p.chr != -1 {
if isLineTerminator(self.chr) { p.read()
return if isLineTerminator(p.chr) {
return result
} }
result = append(result, self.chr) result = append(result, p.chr)
} }
// Get rid of the trailing -1 // Get rid of the trailing -1
result = result[:len(result)-1] return result[:len(result)-1]
return
} }
func (self *_parser) readMultiLineComment() (result []rune) { func (p *parser) readMultiLineComment() []rune {
self.read() var result []rune
for self.chr >= 0 { p.read()
chr := self.chr for p.chr >= 0 {
self.read() chr := p.chr
if chr == '*' && self.chr == '/' { p.read()
self.read() if chr == '*' && p.chr == '/' {
return p.read()
return result
} }
result = append(result, chr) result = append(result, chr)
} }
self.errorUnexpected(0, self.chr) p.errorUnexpected(0, p.chr)
return return result
} }
func (self *_parser) skipSingleLineComment() { func (p *parser) skipSingleLineComment() {
for self.chr != -1 { for p.chr != -1 {
self.read() p.read()
if isLineTerminator(self.chr) { if isLineTerminator(p.chr) {
return return
} }
} }
} }
func (self *_parser) skipMultiLineComment() { func (p *parser) skipMultiLineComment() {
self.read() p.read()
for self.chr >= 0 { for p.chr >= 0 {
chr := self.chr chr := p.chr
self.read() p.read()
if chr == '*' && self.chr == '/' { if chr == '*' && p.chr == '/' {
self.read() p.read()
return return
} }
} }
self.errorUnexpected(0, self.chr) p.errorUnexpected(0, p.chr)
} }
func (self *_parser) skipWhiteSpace() { func (p *parser) skipWhiteSpace() {
for { for {
switch self.chr { switch p.chr {
case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff': case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff':
self.read() p.read()
continue continue
case '\r': case '\r':
if self._peek() == '\n' { if p.peek() == '\n' {
self.comments.AtLineBreak() p.comments.AtLineBreak()
self.read() p.read()
} }
fallthrough fallthrough
case '\u2028', '\u2029', '\n': case '\u2028', '\u2029', '\n':
if self.insertSemicolon { if p.insertSemicolon {
return return
} }
self.comments.AtLineBreak() p.comments.AtLineBreak()
self.read() p.read()
continue continue
} }
if self.chr >= utf8.RuneSelf { if p.chr >= utf8.RuneSelf {
if unicode.IsSpace(self.chr) { if unicode.IsSpace(p.chr) {
self.read() p.read()
continue continue
} }
} }
@ -506,121 +504,114 @@ func (self *_parser) skipWhiteSpace() {
} }
} }
func (self *_parser) skipLineWhiteSpace() { func (p *parser) scanMantissa(base int) {
for isLineWhiteSpace(self.chr) { for digitValue(p.chr) < base {
self.read() p.read()
} }
} }
func (self *_parser) scanMantissa(base int) { func (p *parser) scanEscape(quote rune) {
for digitValue(self.chr) < base {
self.read()
}
}
func (self *_parser) scanEscape(quote rune) {
var length, base uint32 var length, base uint32
switch self.chr { switch p.chr {
//case '0', '1', '2', '3', '4', '5', '6', '7':
// Octal: // Octal:
// length, base, limit = 3, 8, 255 // length, base, limit = 3, 8, 255
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0': case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0':
self.read() p.read()
return return
case '\r', '\n', '\u2028', '\u2029': case '\r', '\n', '\u2028', '\u2029':
self.scanNewline() p.scanNewline()
return return
case 'x': case 'x':
self.read() p.read()
length, base = 2, 16 length, base = 2, 16
case 'u': case 'u':
self.read() p.read()
length, base = 4, 16 length, base = 4, 16
default: default:
self.read() // Always make progress p.read() // Always make progress
return return
} }
var value uint32 var value uint32
for ; length > 0 && self.chr != quote && self.chr >= 0; length-- { for ; length > 0 && p.chr != quote && p.chr >= 0; length-- {
digit := uint32(digitValue(self.chr)) digit := uint32(digitValue(p.chr))
if digit >= base { if digit >= base {
break break
} }
value = value*base + digit value = value*base + digit
self.read() p.read()
} }
} }
func (self *_parser) scanString(offset int) (string, error) { func (p *parser) scanString(offset int) (string, error) {
// " ' / // " ' /
quote := rune(self.str[offset]) quote := rune(p.str[offset])
for self.chr != quote { for p.chr != quote {
chr := self.chr chr := p.chr
if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 { if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
goto newline goto newline
} }
self.read() p.read()
if chr == '\\' { switch {
case chr == '\\':
if quote == '/' { if quote == '/' {
if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 { if p.chr == '\n' || p.chr == '\r' || p.chr == '\u2028' || p.chr == '\u2029' || p.chr < 0 {
goto newline goto newline
} }
self.read() p.read()
} else { } else {
self.scanEscape(quote) p.scanEscape(quote)
} }
} else if chr == '[' && quote == '/' { case chr == '[' && quote == '/':
// Allow a slash (/) in a bracket character class ([...]) // Allow a slash (/) in a bracket character class ([...])
// TODO Fix this, this is hacky... // TODO Fix this, this is hacky...
quote = -1 quote = -1
} else if chr == ']' && quote == -1 { case chr == ']' && quote == -1:
quote = '/' quote = '/'
} }
} }
// " ' / // " ' /
self.read() p.read()
return string(self.str[offset:self.chrOffset]), nil return p.str[offset:p.chrOffset], nil
newline: newline:
self.scanNewline() p.scanNewline()
err := "String not terminated" err := "String not terminated"
if quote == '/' { if quote == '/' {
err = "Invalid regular expression: missing /" err = "Invalid regular expression: missing /"
self.error(self.idxOf(offset), err) p.error(p.idxOf(offset), err)
} }
return "", errors.New(err) return "", errors.New(err)
} }
func (self *_parser) scanNewline() { func (p *parser) scanNewline() {
if self.chr == '\r' { if p.chr == '\r' {
self.read() p.read()
if self.chr != '\n' { if p.chr != '\n' {
return return
} }
} }
self.read() p.read()
} }
func hex2decimal(chr byte) (value rune, ok bool) { func hex2decimal(chr byte) (rune, bool) {
{ r := rune(chr)
chr := rune(chr)
switch { switch {
case '0' <= chr && chr <= '9': case '0' <= r && r <= '9':
return chr - '0', true return r - '0', true
case 'a' <= chr && chr <= 'f': case 'a' <= r && r <= 'f':
return chr - 'a' + 10, true return r - 'a' + 10, true
case 'A' <= chr && chr <= 'F': case 'A' <= r && r <= 'F':
return chr - 'A' + 10, true return r - 'A' + 10, true
} default:
return return 0, false
} }
} }
func parseNumberLiteral(literal string) (value interface{}, err error) { func parseNumberLiteral(literal string) (value interface{}, err error) { //nolint: nonamedreturns
// TODO Is Uint okay? What about -MAX_UINT // TODO Is Uint okay? What about -MAX_UINT
value, err = strconv.ParseInt(literal, 0, 64) value, err = strconv.ParseInt(literal, 0, 64)
if err == nil { if err == nil {
@ -632,14 +623,16 @@ func parseNumberLiteral(literal string) (value interface{}, err error) {
value, err = strconv.ParseFloat(literal, 64) value, err = strconv.ParseFloat(literal, 64)
if err == nil { if err == nil {
return value, nil return value, nil
} else if err.(*strconv.NumError).Err == strconv.ErrRange { } else if errors.Is(err, strconv.ErrRange) {
// Infinity, etc. // Infinity, etc.
return value, nil return value, nil
} }
// TODO(steve): Fix as this is assigning to err so we know the type.
// Need to understand what this was trying to do?
err = parseIntErr err = parseIntErr
if err.(*strconv.NumError).Err == strconv.ErrRange { if errors.Is(err, strconv.ErrRange) {
if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') { if len(literal) > 2 && literal[0] == '0' && (literal[1] == 'X' || literal[1] == 'x') {
// Could just be a very large number (e.g. 0x8000000000000000) // Could just be a very large number (e.g. 0x8000000000000000)
var value float64 var value float64
@ -647,7 +640,7 @@ func parseNumberLiteral(literal string) (value interface{}, err error) {
for _, chr := range literal { for _, chr := range literal {
digit := digitValue(chr) digit := digitValue(chr)
if digit >= 16 { if digit >= 16 {
return nil, errors.New("Illegal numeric literal") return nil, fmt.Errorf("illegal numeric literal: %v (>= 16)", digit)
} }
value = value*16 + float64(digit) value = value*16 + float64(digit)
} }
@ -655,7 +648,7 @@ func parseNumberLiteral(literal string) (value interface{}, err error) {
} }
} }
return nil, errors.New("Illegal numeric literal") return nil, errors.New("illegal numeric literal")
} }
func parseStringLiteral(literal string) (string, error) { func parseStringLiteral(literal string) (string, error) {
@ -783,78 +776,79 @@ func parseStringLiteral(literal string) (string, error) {
return buffer.String(), nil return buffer.String(), nil
} }
func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) { func (p *parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {
offset := self.chrOffset offset := p.chrOffset
tkn := token.NUMBER tkn := token.NUMBER
if decimalPoint { if decimalPoint {
offset-- offset--
self.scanMantissa(10) p.scanMantissa(10)
goto exponent goto exponent
} }
if self.chr == '0' { if p.chr == '0' {
offset := self.chrOffset offset := p.chrOffset
self.read() p.read()
if self.chr == 'x' || self.chr == 'X' { switch p.chr {
case 'x', 'X':
// Hexadecimal // Hexadecimal
self.read() p.read()
if isDigit(self.chr, 16) { if isDigit(p.chr, 16) {
self.read() p.read()
} else { } else {
return token.ILLEGAL, self.str[offset:self.chrOffset] return token.ILLEGAL, p.str[offset:p.chrOffset]
} }
self.scanMantissa(16) p.scanMantissa(16)
if self.chrOffset-offset <= 2 { if p.chrOffset-offset <= 2 {
// Only "0x" or "0X" // Only "0x" or "0X"
self.error(0, "Illegal hexadecimal number") p.error(0, "Illegal hexadecimal number")
} }
goto hexadecimal goto hexadecimal
} else if self.chr == '.' { case '.':
// Float // Float
goto float goto float
} else { default:
// Octal, Float // Octal, Float
if self.chr == 'e' || self.chr == 'E' { if p.chr == 'e' || p.chr == 'E' {
goto exponent goto exponent
} }
self.scanMantissa(8) p.scanMantissa(8)
if self.chr == '8' || self.chr == '9' { if p.chr == '8' || p.chr == '9' {
return token.ILLEGAL, self.str[offset:self.chrOffset] return token.ILLEGAL, p.str[offset:p.chrOffset]
} }
goto octal goto octal
} }
} }
self.scanMantissa(10) p.scanMantissa(10)
float: float:
if self.chr == '.' { if p.chr == '.' {
self.read() p.read()
self.scanMantissa(10) p.scanMantissa(10)
} }
exponent: exponent:
if self.chr == 'e' || self.chr == 'E' { if p.chr == 'e' || p.chr == 'E' {
self.read() p.read()
if self.chr == '-' || self.chr == '+' { if p.chr == '-' || p.chr == '+' {
self.read() p.read()
} }
if isDecimalDigit(self.chr) { if isDecimalDigit(p.chr) {
self.read() p.read()
self.scanMantissa(10) p.scanMantissa(10)
} else { } else {
return token.ILLEGAL, self.str[offset:self.chrOffset] return token.ILLEGAL, p.str[offset:p.chrOffset]
} }
} }
hexadecimal: hexadecimal:
octal: octal:
if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) { if isIdentifierStart(p.chr) || isDecimalDigit(p.chr) {
return token.ILLEGAL, self.str[offset:self.chrOffset] return token.ILLEGAL, p.str[offset:p.chrOffset]
} }
return tkn, self.str[offset:self.chrOffset] return tkn, p.str[offset:p.chrOffset]
} }

View file

@ -35,9 +35,9 @@ package parser
import ( import (
"bytes" "bytes"
"encoding/base64" "encoding/base64"
"errors" "fmt"
"io" "io"
"io/ioutil" "os"
"github.com/robertkrimen/otto/ast" "github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file" "github.com/robertkrimen/otto/file"
@ -49,11 +49,14 @@ import (
type Mode uint type Mode uint
const ( const (
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking) // IgnoreRegExpErrors ignores RegExp compatibility errors (allow backtracking).
StoreComments // Store the comments from source to the comments map IgnoreRegExpErrors Mode = 1 << iota
// StoreComments stores the comments from source to the comments map.
StoreComments
) )
type _parser struct { type parser struct { //nolint: maligned
str string str string
length int length int
base int base int
@ -66,7 +69,7 @@ type _parser struct {
token token.Token // The token token token.Token // The token
literal string // The literal of the token, if any literal string // The literal of the token, if any
scope *_scope scope *scope
insertSemicolon bool // If we see a newline, then insert an implicit semicolon insertSemicolon bool // If we see a newline, then insert an implicit semicolon
implicitSemicolon bool // An implicit semicolon exists implicitSemicolon bool // An implicit semicolon exists
@ -85,12 +88,13 @@ type _parser struct {
comments *ast.Comments comments *ast.Comments
} }
// Parser is implemented by types which can parse JavaScript Code.
type Parser interface { type Parser interface {
Scan() (tkn token.Token, literal string, idx file.Idx) Scan() (tkn token.Token, literal string, idx file.Idx)
} }
func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser { func newParser(filename, src string, base int, sm *sourcemap.Consumer) *parser {
return &_parser{ return &parser{
chr: ' ', // This is set so we can start scanning by skipping whitespace chr: ' ', // This is set so we can start scanning by skipping whitespace
str: src, str: src,
length: len(src), length: len(src),
@ -100,11 +104,12 @@ func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser
} }
} }
// Returns a new Parser. // NewParser returns a new Parser.
func NewParser(filename, src string) Parser { func NewParser(filename, src string) Parser {
return _newParser(filename, src, 1, nil) return newParser(filename, src, 1, nil)
} }
// ReadSource reads code from src if not nil, otherwise reads from filename.
func ReadSource(filename string, src interface{}) ([]byte, error) { func ReadSource(filename string, src interface{}) ([]byte, error) {
if src != nil { if src != nil {
switch src := src.(type) { switch src := src.(type) {
@ -122,12 +127,14 @@ func ReadSource(filename string, src interface{}) ([]byte, error) {
return nil, err return nil, err
} }
return bfr.Bytes(), nil return bfr.Bytes(), nil
default:
return nil, fmt.Errorf("invalid src type %T", src)
} }
return nil, errors.New("invalid source")
} }
return ioutil.ReadFile(filename) return os.ReadFile(filename) //nolint: gosec
} }
// ReadSourceMap reads the source map from src if not nil, otherwise is a noop.
func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) { func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) {
if src == nil { if src == nil {
return nil, nil //nolint: nilnil return nil, nil //nolint: nilnil
@ -139,9 +146,7 @@ func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error
case []byte: case []byte:
return sourcemap.Parse(filename, src) return sourcemap.Parse(filename, src)
case *bytes.Buffer: case *bytes.Buffer:
if src != nil {
return sourcemap.Parse(filename, src.Bytes()) return sourcemap.Parse(filename, src.Bytes())
}
case io.Reader: case io.Reader:
var bfr bytes.Buffer var bfr bytes.Buffer
if _, err := io.Copy(&bfr, src); err != nil { if _, err := io.Copy(&bfr, src); err != nil {
@ -150,11 +155,12 @@ func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error
return sourcemap.Parse(filename, bfr.Bytes()) return sourcemap.Parse(filename, bfr.Bytes())
case *sourcemap.Consumer: case *sourcemap.Consumer:
return src, nil return src, nil
default:
return nil, fmt.Errorf("invalid sourcemap type %T", src)
}
} }
return nil, errors.New("invalid sourcemap type") // ParseFileWithSourceMap parses the sourcemap returning the resulting Program.
}
func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) { func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) {
src, err := ReadSource(filename, javascriptSource) src, err := ReadSource(filename, javascriptSource)
if err != nil { if err != nil {
@ -184,10 +190,10 @@ func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSo
base = fileSet.AddFile(filename, string(src)) base = fileSet.AddFile(filename, string(src))
} }
parser := _newParser(filename, string(src), base, sm) p := newParser(filename, string(src), base, sm)
parser.mode = mode p.mode = mode
program, err := parser.parse() program, err := p.parse()
program.Comments = parser.comments.CommentMap program.Comments = p.comments.CommentMap
return program, err return program, err
} }
@ -215,8 +221,8 @@ func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mod
func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) { func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
src := "(function(" + parameterList + ") {\n" + body + "\n})" src := "(function(" + parameterList + ") {\n" + body + "\n})"
parser := _newParser("", src, 1, nil) p := newParser("", src, 1, nil)
program, err := parser.parse() program, err := p.parse()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -227,75 +233,75 @@ func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
// Scan reads a single token from the source at the current offset, increments the offset and // Scan reads a single token from the source at the current offset, increments the offset and
// returns the token.Token token, a string literal representing the value of the token (if applicable) // returns the token.Token token, a string literal representing the value of the token (if applicable)
// and it's current file.Idx index. // and it's current file.Idx index.
func (self *_parser) Scan() (tkn token.Token, literal string, idx file.Idx) { func (p *parser) Scan() (token.Token, string, file.Idx) {
return self.scan() return p.scan()
} }
func (self *_parser) slice(idx0, idx1 file.Idx) string { func (p *parser) slice(idx0, idx1 file.Idx) string {
from := int(idx0) - self.base from := int(idx0) - p.base
to := int(idx1) - self.base to := int(idx1) - p.base
if from >= 0 && to <= len(self.str) { if from >= 0 && to <= len(p.str) {
return self.str[from:to] return p.str[from:to]
} }
return "" return ""
} }
func (self *_parser) parse() (*ast.Program, error) { func (p *parser) parse() (*ast.Program, error) {
self.next() p.next()
program := self.parseProgram() program := p.parseProgram()
if false { if false {
self.errors.Sort() p.errors.Sort()
} }
if self.mode&StoreComments != 0 { if p.mode&StoreComments != 0 {
self.comments.CommentMap.AddComments(program, self.comments.FetchAll(), ast.TRAILING) p.comments.CommentMap.AddComments(program, p.comments.FetchAll(), ast.TRAILING)
} }
return program, self.errors.Err() return program, p.errors.Err()
} }
func (self *_parser) next() { func (p *parser) next() {
self.token, self.literal, self.idx = self.scan() p.token, p.literal, p.idx = p.scan()
} }
func (self *_parser) optionalSemicolon() { func (p *parser) optionalSemicolon() {
if self.token == token.SEMICOLON { if p.token == token.SEMICOLON {
self.next() p.next()
return return
} }
if self.implicitSemicolon { if p.implicitSemicolon {
self.implicitSemicolon = false p.implicitSemicolon = false
return return
} }
if self.token != token.EOF && self.token != token.RIGHT_BRACE { if p.token != token.EOF && p.token != token.RIGHT_BRACE {
self.expect(token.SEMICOLON) p.expect(token.SEMICOLON)
} }
} }
func (self *_parser) semicolon() { func (p *parser) semicolon() {
if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE { if p.token != token.RIGHT_PARENTHESIS && p.token != token.RIGHT_BRACE {
if self.implicitSemicolon { if p.implicitSemicolon {
self.implicitSemicolon = false p.implicitSemicolon = false
return return
} }
self.expect(token.SEMICOLON) p.expect(token.SEMICOLON)
} }
} }
func (self *_parser) idxOf(offset int) file.Idx { func (p *parser) idxOf(offset int) file.Idx {
return file.Idx(self.base + offset) return file.Idx(p.base + offset)
} }
func (self *_parser) expect(value token.Token) file.Idx { func (p *parser) expect(value token.Token) file.Idx {
idx := self.idx idx := p.idx
if self.token != value { if p.token != value {
self.errorUnexpectedToken(self.token) p.errorUnexpectedToken(p.token)
} }
self.next() p.next()
return idx return idx
} }
@ -305,17 +311,17 @@ func lineCount(str string) (int, int) {
for index, chr := range str { for index, chr := range str {
switch chr { switch chr {
case '\r': case '\r':
line += 1 line++
last = index last = index
pair = true pair = true
continue continue
case '\n': case '\n':
if !pair { if !pair {
line += 1 line++
} }
last = index last = index
case '\u2028', '\u2029': case '\u2028', '\u2029':
line += 1 line++
last = index + 2 last = index + 2
} }
pair = false pair = false
@ -323,11 +329,11 @@ func lineCount(str string) (int, int) {
return line, last return line, last
} }
func (self *_parser) position(idx file.Idx) file.Position { func (p *parser) position(idx file.Idx) file.Position {
position := file.Position{} position := file.Position{}
offset := int(idx) - self.base offset := int(idx) - p.base
str := self.str[:offset] str := p.str[:offset]
position.Filename = self.file.Name() position.Filename = p.file.Name()
line, last := lineCount(str) line, last := lineCount(str)
position.Line = 1 + line position.Line = 1 + line
if last >= 0 { if last >= 0 {

View file

@ -6,7 +6,7 @@ import (
"strconv" "strconv"
) )
type _RegExp_parser struct { type regExpParser struct { //nolint: maligned
str string str string
length int length int
@ -40,128 +40,128 @@ func TransformRegExp(pattern string) (string, error) {
// TODO If without \, if without (?=, (?!, then another shortcut // TODO If without \, if without (?=, (?!, then another shortcut
parser := _RegExp_parser{ p := regExpParser{
str: pattern, str: pattern,
length: len(pattern), length: len(pattern),
goRegexp: bytes.NewBuffer(make([]byte, 0, 3*len(pattern)/2)), goRegexp: bytes.NewBuffer(make([]byte, 0, 3*len(pattern)/2)),
} }
parser.read() // Pull in the first character p.read() // Pull in the first character
parser.scan() p.scan()
var err error var err error
if len(parser.errors) > 0 { if len(p.errors) > 0 {
err = parser.errors[0] err = p.errors[0]
} }
if parser.invalid { if p.invalid {
return "", err return "", err
} }
// Might not be re2 compatible, but is still a valid JavaScript RegExp // Might not be re2 compatible, but is still a valid JavaScript RegExp
return parser.goRegexp.String(), err return p.goRegexp.String(), err
} }
func (self *_RegExp_parser) scan() { func (p *regExpParser) scan() {
for self.chr != -1 { for p.chr != -1 {
switch self.chr { switch p.chr {
case '\\': case '\\':
self.read() p.read()
self.scanEscape(false) p.scanEscape(false)
case '(': case '(':
self.pass() p.pass()
self.scanGroup() p.scanGroup()
case '[': case '[':
self.pass() p.pass()
self.scanBracket() p.scanBracket()
case ')': case ')':
self.error(-1, "Unmatched ')'") p.error(-1, "Unmatched ')'")
self.invalid = true p.invalid = true
self.pass() p.pass()
default: default:
self.pass() p.pass()
} }
} }
} }
// (...) // (...)
func (self *_RegExp_parser) scanGroup() { func (p *regExpParser) scanGroup() {
str := self.str[self.chrOffset:] str := p.str[p.chrOffset:]
if len(str) > 1 { // A possibility of (?= or (?! if len(str) > 1 { // A possibility of (?= or (?!
if str[0] == '?' { if str[0] == '?' {
if str[1] == '=' || str[1] == '!' { if str[1] == '=' || str[1] == '!' {
self.error(-1, "re2: Invalid (%s) <lookahead>", self.str[self.chrOffset:self.chrOffset+2]) p.error(-1, "re2: Invalid (%s) <lookahead>", p.str[p.chrOffset:p.chrOffset+2])
} }
} }
} }
for self.chr != -1 && self.chr != ')' { for p.chr != -1 && p.chr != ')' {
switch self.chr { switch p.chr {
case '\\': case '\\':
self.read() p.read()
self.scanEscape(false) p.scanEscape(false)
case '(': case '(':
self.pass() p.pass()
self.scanGroup() p.scanGroup()
case '[': case '[':
self.pass() p.pass()
self.scanBracket() p.scanBracket()
default: default:
self.pass() p.pass()
continue continue
} }
} }
if self.chr != ')' { if p.chr != ')' {
self.error(-1, "Unterminated group") p.error(-1, "Unterminated group")
self.invalid = true p.invalid = true
return return
} }
self.pass() p.pass()
} }
// [...] // [...].
func (self *_RegExp_parser) scanBracket() { func (p *regExpParser) scanBracket() {
for self.chr != -1 { for p.chr != -1 {
if self.chr == ']' { if p.chr == ']' {
break break
} else if self.chr == '\\' { } else if p.chr == '\\' {
self.read() p.read()
self.scanEscape(true) p.scanEscape(true)
continue continue
} }
self.pass() p.pass()
} }
if self.chr != ']' { if p.chr != ']' {
self.error(-1, "Unterminated character class") p.error(-1, "Unterminated character class")
self.invalid = true p.invalid = true
return return
} }
self.pass() p.pass()
} }
// \... // \...
func (self *_RegExp_parser) scanEscape(inClass bool) { func (p *regExpParser) scanEscape(inClass bool) {
offset := self.chrOffset offset := p.chrOffset
var length, base uint32 var length, base uint32
switch self.chr { switch p.chr {
case '0', '1', '2', '3', '4', '5', '6', '7': case '0', '1', '2', '3', '4', '5', '6', '7':
var value int64 var value int64
size := 0 size := 0
for { for {
digit := int64(digitValue(self.chr)) digit := int64(digitValue(p.chr))
if digit >= 8 { if digit >= 8 {
// Not a valid digit // Not a valid digit
break break
} }
value = value*8 + digit value = value*8 + digit
self.read() p.read()
size += 1 size++
} }
if size == 1 { // The number of characters read if size == 1 { // The number of characters read
_, err := self.goRegexp.Write([]byte{'\\', byte(value) + '0'}) _, err := p.goRegexp.Write([]byte{'\\', byte(value) + '0'})
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
if value != 0 { if value != 0 {
// An invalid backreference // An invalid backreference
self.error(-1, "re2: Invalid \\%d <backreference>", value) p.error(-1, "re2: Invalid \\%d <backreference>", value)
} }
return return
} }
@ -172,49 +172,49 @@ func (self *_RegExp_parser) scanEscape(inClass bool) {
tmp = tmp[0:3] tmp = tmp[0:3]
} }
tmp = strconv.AppendInt(tmp, value, 16) tmp = strconv.AppendInt(tmp, value, 16)
_, err := self.goRegexp.Write(tmp) _, err := p.goRegexp.Write(tmp)
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
return return
case '8', '9': case '8', '9':
size := 0 size := 0
for { for {
digit := digitValue(self.chr) digit := digitValue(p.chr)
if digit >= 10 { if digit >= 10 {
// Not a valid digit // Not a valid digit
break break
} }
self.read() p.read()
size += 1 size++
} }
err := self.goRegexp.WriteByte('\\') err := p.goRegexp.WriteByte('\\')
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
_, err = self.goRegexp.WriteString(self.str[offset:self.chrOffset]) _, err = p.goRegexp.WriteString(p.str[offset:p.chrOffset])
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
self.error(-1, "re2: Invalid \\%s <backreference>", self.str[offset:self.chrOffset]) p.error(-1, "re2: Invalid \\%s <backreference>", p.str[offset:p.chrOffset])
return return
case 'x': case 'x':
self.read() p.read()
length, base = 2, 16 length, base = 2, 16
case 'u': case 'u':
self.read() p.read()
length, base = 4, 16 length, base = 4, 16
case 'b': case 'b':
if inClass { if inClass {
_, err := self.goRegexp.Write([]byte{'\\', 'x', '0', '8'}) _, err := p.goRegexp.Write([]byte{'\\', 'x', '0', '8'})
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
self.read() p.read()
return return
} }
fallthrough fallthrough
@ -231,24 +231,25 @@ func (self *_RegExp_parser) scanEscape(inClass bool) {
fallthrough fallthrough
case 'f', 'n', 'r', 't', 'v': case 'f', 'n', 'r', 't', 'v':
err := self.goRegexp.WriteByte('\\') err := p.goRegexp.WriteByte('\\')
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
self.pass() p.pass()
return return
case 'c': case 'c':
self.read() p.read()
var value int64 var value int64
if 'a' <= self.chr && self.chr <= 'z' { switch {
value = int64(self.chr) - 'a' + 1 case 'a' <= p.chr && p.chr <= 'z':
} else if 'A' <= self.chr && self.chr <= 'Z' { value = int64(p.chr) - 'a' + 1
value = int64(self.chr) - 'A' + 1 case 'A' <= p.chr && p.chr <= 'Z':
} else { value = int64(p.chr) - 'A' + 1
err := self.goRegexp.WriteByte('c') default:
err := p.goRegexp.WriteByte('c')
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
return return
} }
@ -259,98 +260,93 @@ func (self *_RegExp_parser) scanEscape(inClass bool) {
tmp = tmp[0:3] tmp = tmp[0:3]
} }
tmp = strconv.AppendInt(tmp, value, 16) tmp = strconv.AppendInt(tmp, value, 16)
_, err := self.goRegexp.Write(tmp) _, err := p.goRegexp.Write(tmp)
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
self.read() p.read()
return return
default: default:
// $ is an identifier character, so we have to have // $ is an identifier character, so we have to have
// a special case for it here // a special case for it here
if self.chr == '$' || !isIdentifierPart(self.chr) { if p.chr == '$' || !isIdentifierPart(p.chr) {
// A non-identifier character needs escaping // A non-identifier character needs escaping
err := self.goRegexp.WriteByte('\\') err := p.goRegexp.WriteByte('\\')
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
} else { } else { //nolint: staticcheck
// Unescape the character for re2 // Unescape the character for re2
} }
self.pass() p.pass()
return return
} }
// Otherwise, we're a \u.... or \x... // Otherwise, we're a \u.... or \x...
valueOffset := self.chrOffset valueOffset := p.chrOffset
var value uint32 var value uint32
{ for length := length; length > 0; length-- {
length := length digit := uint32(digitValue(p.chr))
for ; length > 0; length-- {
digit := uint32(digitValue(self.chr))
if digit >= base { if digit >= base {
// Not a valid digit // Not a valid digit
goto skip goto skip
} }
value = value*base + digit value = value*base + digit
self.read() p.read()
}
} }
if length == 4 { switch length {
_, err := self.goRegexp.Write([]byte{ case 4:
if _, err := p.goRegexp.Write([]byte{
'\\', '\\',
'x', 'x',
'{', '{',
self.str[valueOffset+0], p.str[valueOffset+0],
self.str[valueOffset+1], p.str[valueOffset+1],
self.str[valueOffset+2], p.str[valueOffset+2],
self.str[valueOffset+3], p.str[valueOffset+3],
'}', '}',
}) }); err != nil {
if err != nil { p.errors = append(p.errors, err)
self.errors = append(self.errors, err)
} }
} else if length == 2 { case 2:
_, err := self.goRegexp.Write([]byte{ if _, err := p.goRegexp.Write([]byte{
'\\', '\\',
'x', 'x',
self.str[valueOffset+0], p.str[valueOffset+0],
self.str[valueOffset+1], p.str[valueOffset+1],
}) }); err != nil {
if err != nil { p.errors = append(p.errors, err)
self.errors = append(self.errors, err)
} }
} else { default:
// Should never, ever get here... // Should never, ever get here...
self.error(-1, "re2: Illegal branch in scanEscape") p.error(-1, "re2: Illegal branch in scanEscape")
goto skip goto skip
} }
return return
skip: skip:
_, err := self.goRegexp.WriteString(self.str[offset:self.chrOffset]) _, err := p.goRegexp.WriteString(p.str[offset:p.chrOffset])
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
} }
func (self *_RegExp_parser) pass() { func (p *regExpParser) pass() {
if self.chr != -1 { if p.chr != -1 {
_, err := self.goRegexp.WriteRune(self.chr) _, err := p.goRegexp.WriteRune(p.chr)
if err != nil { if err != nil {
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
} }
} }
self.read() p.read()
} }
// TODO Better error reporting, use the offset, etc. // TODO Better error reporting, use the offset, etc.
func (self *_RegExp_parser) error(offset int, msg string, msgValues ...interface{}) error { func (p *regExpParser) error(offset int, msg string, msgValues ...interface{}) { //nolint: unparam
err := fmt.Errorf(msg, msgValues...) err := fmt.Errorf(msg, msgValues...)
self.errors = append(self.errors, err) p.errors = append(p.errors, err)
return err
} }

View file

@ -4,8 +4,8 @@ import (
"github.com/robertkrimen/otto/ast" "github.com/robertkrimen/otto/ast"
) )
type _scope struct { type scope struct {
outer *_scope outer *scope
allowIn bool allowIn bool
inIteration bool inIteration bool
inSwitch bool inSwitch bool
@ -15,30 +15,30 @@ type _scope struct {
labels []string labels []string
} }
func (self *_parser) openScope() { func (p *parser) openScope() {
self.scope = &_scope{ p.scope = &scope{
outer: self.scope, outer: p.scope,
allowIn: true, allowIn: true,
} }
} }
func (self *_parser) closeScope() { func (p *parser) closeScope() {
self.scope = self.scope.outer p.scope = p.scope.outer
} }
func (self *_scope) declare(declaration ast.Declaration) { func (p *scope) declare(declaration ast.Declaration) {
self.declarationList = append(self.declarationList, declaration) p.declarationList = append(p.declarationList, declaration)
} }
func (self *_scope) hasLabel(name string) bool { func (p *scope) hasLabel(name string) bool {
for _, label := range self.labels { for _, label := range p.labels {
if label == name { if label == name {
return true return true
} }
} }
if self.outer != nil && !self.inFunction { if p.outer != nil && !p.inFunction {
// Crossing a function boundary to look for a label is verboten // Crossing a function boundary to look for a label is verboten
return self.outer.hasLabel(name) return p.outer.hasLabel(name)
} }
return false return false
} }

File diff suppressed because it is too large Load diff

View file

@ -2,87 +2,87 @@ package otto
// property // property
type _propertyMode int type propertyMode int
const ( const (
modeWriteMask _propertyMode = 0700 modeWriteMask propertyMode = 0o700
modeEnumerateMask = 0070 modeEnumerateMask propertyMode = 0o070
modeConfigureMask = 0007 modeConfigureMask propertyMode = 0o007
modeOnMask = 0111 modeOnMask propertyMode = 0o111
modeSetMask = 0222 // If value is 2, then mode is neither "On" nor "Off" modeSetMask propertyMode = 0o222 // If value is 2, then mode is neither "On" nor "Off"
) )
type _propertyGetSet [2]*_object type propertyGetSet [2]*object
var _nilGetSetObject _object = _object{} var nilGetSetObject = object{}
type _property struct { type property struct {
value interface{} value interface{}
mode _propertyMode mode propertyMode
} }
func (self _property) writable() bool { func (p property) writable() bool {
return self.mode&modeWriteMask == modeWriteMask&modeOnMask return p.mode&modeWriteMask == modeWriteMask&modeOnMask
} }
func (self *_property) writeOn() { func (p *property) writeOn() {
self.mode = (self.mode & ^modeWriteMask) | (modeWriteMask & modeOnMask) p.mode = (p.mode & ^modeWriteMask) | (modeWriteMask & modeOnMask)
} }
func (self *_property) writeOff() { func (p *property) writeOff() {
self.mode &= ^modeWriteMask p.mode &= ^modeWriteMask
} }
func (self *_property) writeClear() { func (p *property) writeClear() {
self.mode = (self.mode & ^modeWriteMask) | (modeWriteMask & modeSetMask) p.mode = (p.mode & ^modeWriteMask) | (modeWriteMask & modeSetMask)
} }
func (self _property) writeSet() bool { func (p property) writeSet() bool {
return 0 == self.mode&modeWriteMask&modeSetMask return p.mode&modeWriteMask&modeSetMask == 0
} }
func (self _property) enumerable() bool { func (p property) enumerable() bool {
return self.mode&modeEnumerateMask == modeEnumerateMask&modeOnMask return p.mode&modeEnumerateMask == modeEnumerateMask&modeOnMask
} }
func (self *_property) enumerateOn() { func (p *property) enumerateOn() {
self.mode = (self.mode & ^modeEnumerateMask) | (modeEnumerateMask & modeOnMask) p.mode = (p.mode & ^modeEnumerateMask) | (modeEnumerateMask & modeOnMask)
} }
func (self *_property) enumerateOff() { func (p *property) enumerateOff() {
self.mode &= ^modeEnumerateMask p.mode &= ^modeEnumerateMask
} }
func (self _property) enumerateSet() bool { func (p property) enumerateSet() bool {
return 0 == self.mode&modeEnumerateMask&modeSetMask return p.mode&modeEnumerateMask&modeSetMask == 0
} }
func (self _property) configurable() bool { func (p property) configurable() bool {
return self.mode&modeConfigureMask == modeConfigureMask&modeOnMask return p.mode&modeConfigureMask == modeConfigureMask&modeOnMask
} }
func (self *_property) configureOn() { func (p *property) configureOn() {
self.mode = (self.mode & ^modeConfigureMask) | (modeConfigureMask & modeOnMask) p.mode = (p.mode & ^modeConfigureMask) | (modeConfigureMask & modeOnMask)
} }
func (self *_property) configureOff() { func (p *property) configureOff() {
self.mode &= ^modeConfigureMask p.mode &= ^modeConfigureMask
} }
func (self _property) configureSet() bool { func (p property) configureSet() bool { //nolint: unused
return 0 == self.mode&modeConfigureMask&modeSetMask return p.mode&modeConfigureMask&modeSetMask == 0
} }
func (self _property) copy() *_property { func (p property) copy() *property { //nolint: unused
property := self cpy := p
return &property return &cpy
} }
func (self _property) get(this *_object) Value { func (p property) get(this *object) Value {
switch value := self.value.(type) { switch value := p.value.(type) {
case Value: case Value:
return value return value
case _propertyGetSet: case propertyGetSet:
if value[0] != nil { if value[0] != nil {
return value[0].call(toValue(this), nil, false, nativeFrame) return value[0].call(toValue(this), nil, false, nativeFrame)
} }
@ -90,37 +90,37 @@ func (self _property) get(this *_object) Value {
return Value{} return Value{}
} }
func (self _property) isAccessorDescriptor() bool { func (p property) isAccessorDescriptor() bool {
setGet, test := self.value.(_propertyGetSet) setGet, test := p.value.(propertyGetSet)
return test && (setGet[0] != nil || setGet[1] != nil) return test && (setGet[0] != nil || setGet[1] != nil)
} }
func (self _property) isDataDescriptor() bool { func (p property) isDataDescriptor() bool {
if self.writeSet() { // Either "On" or "Off" if p.writeSet() { // Either "On" or "Off"
return true return true
} }
value, valid := self.value.(Value) value, valid := p.value.(Value)
return valid && !value.isEmpty() return valid && !value.isEmpty()
} }
func (self _property) isGenericDescriptor() bool { func (p property) isGenericDescriptor() bool {
return !(self.isDataDescriptor() || self.isAccessorDescriptor()) return !(p.isDataDescriptor() || p.isAccessorDescriptor())
} }
func (self _property) isEmpty() bool { func (p property) isEmpty() bool {
return self.mode == 0222 && self.isGenericDescriptor() return p.mode == 0o222 && p.isGenericDescriptor()
} }
// _enumerableValue, _enumerableTrue, _enumerableFalse? // _enumerableValue, _enumerableTrue, _enumerableFalse?
// .enumerableValue() .enumerableExists() // .enumerableValue() .enumerableExists()
func toPropertyDescriptor(rt *_runtime, value Value) (descriptor _property) { func toPropertyDescriptor(rt *runtime, value Value) property {
objectDescriptor := value._object() objectDescriptor := value.object()
if objectDescriptor == nil { if objectDescriptor == nil {
panic(rt.panicTypeError()) panic(rt.panicTypeError("toPropertyDescriptor on nil"))
} }
{ var descriptor property
descriptor.mode = modeSetMask // Initially nothing is set descriptor.mode = modeSetMask // Initially nothing is set
if objectDescriptor.hasProperty("enumerable") { if objectDescriptor.hasProperty("enumerable") {
if objectDescriptor.get("enumerable").bool() { if objectDescriptor.get("enumerable").bool() {
@ -145,21 +145,20 @@ func toPropertyDescriptor(rt *_runtime, value Value) (descriptor _property) {
descriptor.writeOff() descriptor.writeOff()
} }
} }
}
var getter, setter *_object var getter, setter *object
getterSetter := false getterSetter := false
if objectDescriptor.hasProperty("get") { if objectDescriptor.hasProperty("get") {
value := objectDescriptor.get("get") value := objectDescriptor.get("get")
if value.IsDefined() { if value.IsDefined() {
if !value.isCallable() { if !value.isCallable() {
panic(rt.panicTypeError()) panic(rt.panicTypeError("toPropertyDescriptor get not callable"))
} }
getter = value._object() getter = value.object()
getterSetter = true getterSetter = true
} else { } else {
getter = &_nilGetSetObject getter = &nilGetSetObject
getterSetter = true getterSetter = true
} }
} }
@ -168,52 +167,52 @@ func toPropertyDescriptor(rt *_runtime, value Value) (descriptor _property) {
value := objectDescriptor.get("set") value := objectDescriptor.get("set")
if value.IsDefined() { if value.IsDefined() {
if !value.isCallable() { if !value.isCallable() {
panic(rt.panicTypeError()) panic(rt.panicTypeError("toPropertyDescriptor set not callable"))
} }
setter = value._object() setter = value.object()
getterSetter = true getterSetter = true
} else { } else {
setter = &_nilGetSetObject setter = &nilGetSetObject
getterSetter = true getterSetter = true
} }
} }
if getterSetter { if getterSetter {
if descriptor.writeSet() { if descriptor.writeSet() {
panic(rt.panicTypeError()) panic(rt.panicTypeError("toPropertyDescriptor descriptor writeSet"))
} }
descriptor.value = _propertyGetSet{getter, setter} descriptor.value = propertyGetSet{getter, setter}
} }
if objectDescriptor.hasProperty("value") { if objectDescriptor.hasProperty("value") {
if getterSetter { if getterSetter {
panic(rt.panicTypeError()) panic(rt.panicTypeError("toPropertyDescriptor value getterSetter"))
} }
descriptor.value = objectDescriptor.get("value") descriptor.value = objectDescriptor.get("value")
} }
return return descriptor
} }
func (self *_runtime) fromPropertyDescriptor(descriptor _property) *_object { func (rt *runtime) fromPropertyDescriptor(descriptor property) *object {
object := self.newObject() obj := rt.newObject()
if descriptor.isDataDescriptor() { if descriptor.isDataDescriptor() {
object.defineProperty("value", descriptor.value.(Value), 0111, false) obj.defineProperty("value", descriptor.value.(Value), 0o111, false)
object.defineProperty("writable", toValue_bool(descriptor.writable()), 0111, false) obj.defineProperty("writable", boolValue(descriptor.writable()), 0o111, false)
} else if descriptor.isAccessorDescriptor() { } else if descriptor.isAccessorDescriptor() {
getSet := descriptor.value.(_propertyGetSet) getSet := descriptor.value.(propertyGetSet)
get := Value{} get := Value{}
if getSet[0] != nil { if getSet[0] != nil {
get = toValue_object(getSet[0]) get = objectValue(getSet[0])
} }
set := Value{} set := Value{}
if getSet[1] != nil { if getSet[1] != nil {
set = toValue_object(getSet[1]) set = objectValue(getSet[1])
} }
object.defineProperty("get", get, 0111, false) obj.defineProperty("get", get, 0o111, false)
object.defineProperty("set", set, 0111, false) obj.defineProperty("set", set, 0o111, false)
} }
object.defineProperty("enumerable", toValue_bool(descriptor.enumerable()), 0111, false) obj.defineProperty("enumerable", boolValue(descriptor.enumerable()), 0o111, false)
object.defineProperty("configurable", toValue_bool(descriptor.configurable()), 0111, false) obj.defineProperty("configurable", boolValue(descriptor.configurable()), 0o111, false)
return object return obj
} }

View file

@ -1,51 +0,0 @@
# registry
--
import "github.com/robertkrimen/otto/registry"
Package registry is an expirmental package to facillitate altering the otto
runtime via import.
This interface can change at any time.
## Usage
#### func Apply
```go
func Apply(callback func(Entry))
```
#### type Entry
```go
type Entry struct {
}
```
#### func Register
```go
func Register(source func() string) *Entry
```
#### func (*Entry) Disable
```go
func (self *Entry) Disable()
```
#### func (*Entry) Enable
```go
func (self *Entry) Enable()
```
#### func (Entry) Source
```go
func (self Entry) Source() string
```
--
**godocdown** http://github.com/robertkrimen/godocdown

View file

@ -1,17 +1,17 @@
/* // Package registry is an experimental package to facilitate altering the otto runtime via import.
Package registry is an expirmental package to facillitate altering the otto runtime via import. //
// This interface can change at any time.
This interface can change at any time.
*/
package registry package registry
var registry []*Entry = make([]*Entry, 0) var registry []*Entry = make([]*Entry, 0)
// Entry represents a registry entry.
type Entry struct { type Entry struct {
active bool active bool
source func() string source func() string
} }
// newEntry returns a new Entry for source.
func newEntry(source func() string) *Entry { func newEntry(source func() string) *Entry {
return &Entry{ return &Entry{
active: true, active: true,
@ -19,18 +19,22 @@ func newEntry(source func() string) *Entry {
} }
} }
func (self *Entry) Enable() { // Enable enables the entry.
self.active = true func (e *Entry) Enable() {
e.active = true
} }
func (self *Entry) Disable() { // Disable disables the entry.
self.active = false func (e *Entry) Disable() {
e.active = false
} }
func (self Entry) Source() string { // Source returns the source of the entry.
return self.source() func (e Entry) Source() string {
return e.source()
} }
// Apply applies callback to all registry entries.
func Apply(callback func(Entry)) { func Apply(callback func(Entry)) {
for _, entry := range registry { for _, entry := range registry {
if !entry.active { if !entry.active {
@ -40,6 +44,7 @@ func Apply(callback func(Entry)) {
} }
} }
// Register registers a new Entry for source.
func Register(source func() string) *Entry { func Register(source func() string) *Entry {
entry := newEntry(source) entry := newEntry(source)
registry = append(registry, entry) registry = append(registry, entry)

View file

@ -1,28 +1,28 @@
package otto package otto
type _resultKind int type resultKind int
const ( const (
_ _resultKind = iota _ resultKind = iota
resultReturn resultReturn
resultBreak resultBreak
resultContinue resultContinue
) )
type _result struct { type result struct {
kind _resultKind kind resultKind
value Value value Value
target string target string
} }
func newReturnResult(value Value) _result { func newReturnResult(value Value) result {
return _result{resultReturn, value, ""} return result{resultReturn, value, ""}
} }
func newContinueResult(target string) _result { func newContinueResult(target string) result {
return _result{resultContinue, emptyValue, target} return result{resultContinue, emptyValue, target}
} }
func newBreakResult(target string) _result { func newBreakResult(target string) result {
return _result{resultBreak, emptyValue, target} return result{resultBreak, emptyValue, target}
} }

View file

@ -8,7 +8,7 @@ import (
"math" "math"
"path" "path"
"reflect" "reflect"
"runtime" goruntime "runtime"
"strconv" "strconv"
"strings" "strings"
"sync" "sync"
@ -17,49 +17,49 @@ import (
"github.com/robertkrimen/otto/parser" "github.com/robertkrimen/otto/parser"
) )
type _global struct { type global struct {
Object *_object // Object( ... ), new Object( ... ) - 1 (length) Object *object // Object( ... ), new Object( ... ) - 1 (length)
Function *_object // Function( ... ), new Function( ... ) - 1 Function *object // Function( ... ), new Function( ... ) - 1
Array *_object // Array( ... ), new Array( ... ) - 1 Array *object // Array( ... ), new Array( ... ) - 1
String *_object // String( ... ), new String( ... ) - 1 String *object // String( ... ), new String( ... ) - 1
Boolean *_object // Boolean( ... ), new Boolean( ... ) - 1 Boolean *object // Boolean( ... ), new Boolean( ... ) - 1
Number *_object // Number( ... ), new Number( ... ) - 1 Number *object // Number( ... ), new Number( ... ) - 1
Math *_object Math *object
Date *_object // Date( ... ), new Date( ... ) - 7 Date *object // Date( ... ), new Date( ... ) - 7
RegExp *_object // RegExp( ... ), new RegExp( ... ) - 2 RegExp *object // RegExp( ... ), new RegExp( ... ) - 2
Error *_object // Error( ... ), new Error( ... ) - 1 Error *object // Error( ... ), new Error( ... ) - 1
EvalError *_object EvalError *object
TypeError *_object TypeError *object
RangeError *_object RangeError *object
ReferenceError *_object ReferenceError *object
SyntaxError *_object SyntaxError *object
URIError *_object URIError *object
JSON *_object JSON *object
ObjectPrototype *_object // Object.prototype ObjectPrototype *object // Object.prototype
FunctionPrototype *_object // Function.prototype FunctionPrototype *object // Function.prototype
ArrayPrototype *_object // Array.prototype ArrayPrototype *object // Array.prototype
StringPrototype *_object // String.prototype StringPrototype *object // String.prototype
BooleanPrototype *_object // Boolean.prototype BooleanPrototype *object // Boolean.prototype
NumberPrototype *_object // Number.prototype NumberPrototype *object // Number.prototype
DatePrototype *_object // Date.prototype DatePrototype *object // Date.prototype
RegExpPrototype *_object // RegExp.prototype RegExpPrototype *object // RegExp.prototype
ErrorPrototype *_object // Error.prototype ErrorPrototype *object // Error.prototype
EvalErrorPrototype *_object EvalErrorPrototype *object
TypeErrorPrototype *_object TypeErrorPrototype *object
RangeErrorPrototype *_object RangeErrorPrototype *object
ReferenceErrorPrototype *_object ReferenceErrorPrototype *object
SyntaxErrorPrototype *_object SyntaxErrorPrototype *object
URIErrorPrototype *_object URIErrorPrototype *object
} }
type _runtime struct { type runtime struct {
global _global global global
globalObject *_object globalObject *object
globalStash *_objectStash globalStash *objectStash
scope *_scope scope *scope
otto *Otto otto *Otto
eval *_object // The builtin eval, for determine indirect versus direct invocation eval *object // The builtin eval, for determine indirect versus direct invocation
debugger func(*Otto) debugger func(*Otto)
random func() float64 random func() float64
stackLimit int stackLimit int
@ -69,54 +69,54 @@ type _runtime struct {
lck sync.Mutex lck sync.Mutex
} }
func (self *_runtime) enterScope(scope *_scope) { func (rt *runtime) enterScope(scop *scope) {
scope.outer = self.scope scop.outer = rt.scope
if self.scope != nil { if rt.scope != nil {
if self.stackLimit != 0 && self.scope.depth+1 >= self.stackLimit { if rt.stackLimit != 0 && rt.scope.depth+1 >= rt.stackLimit {
panic(self.panicRangeError("Maximum call stack size exceeded")) panic(rt.panicRangeError("Maximum call stack size exceeded"))
} }
scope.depth = self.scope.depth + 1 scop.depth = rt.scope.depth + 1
} }
self.scope = scope rt.scope = scop
} }
func (self *_runtime) leaveScope() { func (rt *runtime) leaveScope() {
self.scope = self.scope.outer rt.scope = rt.scope.outer
} }
// FIXME This is used in two places (cloning) // FIXME This is used in two places (cloning).
func (self *_runtime) enterGlobalScope() { func (rt *runtime) enterGlobalScope() {
self.enterScope(newScope(self.globalStash, self.globalStash, self.globalObject)) rt.enterScope(newScope(rt.globalStash, rt.globalStash, rt.globalObject))
} }
func (self *_runtime) enterFunctionScope(outer _stash, this Value) *_fnStash { func (rt *runtime) enterFunctionScope(outer stasher, this Value) *fnStash {
if outer == nil { if outer == nil {
outer = self.globalStash outer = rt.globalStash
} }
stash := self.newFunctionStash(outer) stash := rt.newFunctionStash(outer)
var thisObject *_object var thisObject *object
switch this.kind { switch this.kind {
case valueUndefined, valueNull: case valueUndefined, valueNull:
thisObject = self.globalObject thisObject = rt.globalObject
default: default:
thisObject = self.toObject(this) thisObject = rt.toObject(this)
} }
self.enterScope(newScope(stash, stash, thisObject)) rt.enterScope(newScope(stash, stash, thisObject))
return stash return stash
} }
func (self *_runtime) putValue(reference _reference, value Value) { func (rt *runtime) putValue(reference referencer, value Value) {
name := reference.putValue(value) name := reference.putValue(value)
if name != "" { if name != "" {
// Why? -- If reference.base == nil // Why? -- If reference.base == nil
// strict = false // strict = false
self.globalObject.defineProperty(name, value, 0111, false) rt.globalObject.defineProperty(name, value, 0o111, false)
} }
} }
func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exception bool) { func (rt *runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, isException bool) { //nolint: nonamedreturns
// resultValue = The value of the block (e.g. the last statement) // resultValue = The value of the block (e.g. the last statement)
// throw = Something was thrown // throw = Something was thrown
// throwValue = The value of what was thrown // throwValue = The value of what was thrown
@ -124,18 +124,18 @@ func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exce
// Otherwise, some sort of unknown panic happened, we'll just propagate it. // Otherwise, some sort of unknown panic happened, we'll just propagate it.
defer func() { defer func() {
if caught := recover(); caught != nil { if caught := recover(); caught != nil {
if exception, ok := caught.(*_exception); ok { if excep, ok := caught.(*exception); ok {
caught = exception.eject() caught = excep.eject()
} }
switch caught := caught.(type) { switch caught := caught.(type) {
case _error: case ottoError:
exception = true isException = true
tryValue = toValue_object(self.newErrorObjectError(caught)) tryValue = objectValue(rt.newErrorObjectError(caught))
case Value: case Value:
exception = true isException = true
tryValue = caught tryValue = caught
default: default:
exception = true isException = true
tryValue = toValue(caught) tryValue = toValue(caught)
} }
} }
@ -144,52 +144,51 @@ func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exce
return inner(), false return inner(), false
} }
// toObject func (rt *runtime) toObject(value Value) *object {
func (self *_runtime) toObject(value Value) *_object {
switch value.kind { switch value.kind {
case valueEmpty, valueUndefined, valueNull: case valueEmpty, valueUndefined, valueNull:
panic(self.panicTypeError()) panic(rt.panicTypeError("toObject unsupported kind %s", value.kind))
case valueBoolean: case valueBoolean:
return self.newBoolean(value) return rt.newBoolean(value)
case valueString: case valueString:
return self.newString(value) return rt.newString(value)
case valueNumber: case valueNumber:
return self.newNumber(value) return rt.newNumber(value)
case valueObject: case valueObject:
return value._object() return value.object()
default:
panic(rt.panicTypeError("toObject unknown kind %s", value.kind))
} }
panic(self.panicTypeError())
} }
func (self *_runtime) objectCoerce(value Value) (*_object, error) { func (rt *runtime) objectCoerce(value Value) (*object, error) {
switch value.kind { switch value.kind {
case valueUndefined: case valueUndefined:
return nil, errors.New("undefined") return nil, errors.New("undefined")
case valueNull: case valueNull:
return nil, errors.New("null") return nil, errors.New("null")
case valueBoolean: case valueBoolean:
return self.newBoolean(value), nil return rt.newBoolean(value), nil
case valueString: case valueString:
return self.newString(value), nil return rt.newString(value), nil
case valueNumber: case valueNumber:
return self.newNumber(value), nil return rt.newNumber(value), nil
case valueObject: case valueObject:
return value._object(), nil return value.object(), nil
default:
panic(rt.panicTypeError("objectCoerce unknown kind %s", value.kind))
} }
panic(self.panicTypeError())
} }
func checkObjectCoercible(rt *_runtime, value Value) { func checkObjectCoercible(rt *runtime, value Value) {
isObject, mustCoerce := testObjectCoercible(value) isObject, mustCoerce := testObjectCoercible(value)
if !isObject && !mustCoerce { if !isObject && !mustCoerce {
panic(rt.panicTypeError()) panic(rt.panicTypeError("checkObjectCoercible not object or mustCoerce"))
} }
} }
// testObjectCoercible // testObjectCoercible.
func testObjectCoercible(value Value) (isObject, mustCoerce bool) { //nolint: nonamedreturns
func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
switch value.kind { switch value.kind {
case valueReference, valueEmpty, valueNull, valueUndefined: case valueReference, valueEmpty, valueNull, valueUndefined:
return false, false return false, false
@ -198,21 +197,21 @@ func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
case valueObject: case valueObject:
return true, false return true, false
default: default:
panic("this should never happen") panic(fmt.Sprintf("testObjectCoercible unknown kind %s", value.kind))
} }
} }
func (self *_runtime) safeToValue(value interface{}) (Value, error) { func (rt *runtime) safeToValue(value interface{}) (Value, error) {
result := Value{} result := Value{}
err := catchPanic(func() { err := catchPanic(func() {
result = self.toValue(value) result = rt.toValue(value)
}) })
return result, err return result, err
} }
// convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics. // convertNumeric converts numeric parameter val from js to that of type t if it is safe to do so, otherwise it panics.
// This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily. // This allows literals (int64), bitwise values (int32) and the general form (float64) of javascript numerics to be passed as parameters to go functions easily.
func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value { func (rt *runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
val := reflect.ValueOf(v.export()) val := reflect.ValueOf(v.export())
if val.Kind() == t.Kind() { if val.Kind() == t.Kind() {
@ -231,20 +230,20 @@ func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
return reflect.ValueOf(f64) return reflect.ValueOf(f64)
case reflect.Float32: case reflect.Float32:
if reflect.Zero(t).OverflowFloat(f64) { if reflect.Zero(t).OverflowFloat(f64) {
panic(self.panicRangeError("converting float64 to float32 would overflow")) panic(rt.panicRangeError("converting float64 to float32 would overflow"))
} }
return val.Convert(t) return val.Convert(t)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i64 := int64(f64) i64 := int64(f64)
if float64(i64) != f64 { if float64(i64) != f64 {
panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t))) panic(rt.panicRangeError(fmt.Sprintf("converting %v to %v would cause loss of precision", val.Type(), t)))
} }
// The float represents an integer // The float represents an integer
val = reflect.ValueOf(i64) val = reflect.ValueOf(i64)
default: default:
panic(self.panicTypeError(fmt.Sprintf("cannot convert %v to %v", val.Type(), t))) panic(rt.panicTypeError(fmt.Sprintf("cannot convert %v to %v", val.Type(), t)))
} }
} }
@ -254,15 +253,15 @@ func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
switch t.Kind() { switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if reflect.Zero(t).OverflowInt(i64) { if reflect.Zero(t).OverflowInt(i64) {
panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) panic(rt.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
} }
return val.Convert(t) return val.Convert(t)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if i64 < 0 { if i64 < 0 {
panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t))) panic(rt.panicRangeError(fmt.Sprintf("converting %v to %v would underflow", val.Type(), t)))
} }
if reflect.Zero(t).OverflowUint(uint64(i64)) { if reflect.Zero(t).OverflowUint(uint64(i64)) {
panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) panic(rt.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
} }
return val.Convert(t) return val.Convert(t)
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
@ -274,12 +273,12 @@ func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
switch t.Kind() { switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) { if u64 > math.MaxInt64 || reflect.Zero(t).OverflowInt(int64(u64)) {
panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) panic(rt.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
} }
return val.Convert(t) return val.Convert(t)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
if reflect.Zero(t).OverflowUint(u64) { if reflect.Zero(t).OverflowUint(u64) {
panic(self.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t))) panic(rt.panicRangeError(fmt.Sprintf("converting %v to %v would overflow", val.Type(), t)))
} }
return val.Convert(t) return val.Convert(t)
case reflect.Float32, reflect.Float64: case reflect.Float32, reflect.Float64:
@ -287,7 +286,7 @@ func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
} }
} }
panic(self.panicTypeError(fmt.Sprintf("unsupported type %v -> %v for numeric conversion", val.Type(), t))) panic(rt.panicTypeError(fmt.Sprintf("unsupported type %v -> %v for numeric conversion", val.Type(), t)))
} }
func fieldIndexByName(t reflect.Type, name string) []int { func fieldIndexByName(t reflect.Type, name string) []int {
@ -332,13 +331,15 @@ func fieldIndexByName(t reflect.Type, name string) []int {
return nil return nil
} }
var typeOfValue = reflect.TypeOf(Value{}) var (
var typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{}) typeOfValue = reflect.TypeOf(Value{})
typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{})
)
// convertCallParameter converts request val to type t if possible. // convertCallParameter converts request val to type t if possible.
// If the conversion fails due to overflow or type miss-match then it panics. // If the conversion fails due to overflow or type miss-match then it panics.
// If no conversion is known then the original value is returned. // If no conversion is known then the original value is returned.
func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Value, error) { func (rt *runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Value, error) {
if t == typeOfValue { if t == typeOfValue {
return reflect.ValueOf(v), nil return reflect.ValueOf(v), nil
} }
@ -350,25 +351,23 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
} }
if v.kind == valueObject { if v.kind == valueObject {
if gso, ok := v._object().value.(*_goStructObject); ok { if gso, ok := v.object().value.(*goStructObject); ok {
if gso.value.Type().AssignableTo(t) { if gso.value.Type().AssignableTo(t) {
// please see TestDynamicFunctionReturningInterface for why this exists // please see TestDynamicFunctionReturningInterface for why this exists
if t.Kind() == reflect.Interface && gso.value.Type().ConvertibleTo(t) { if t.Kind() == reflect.Interface && gso.value.Type().ConvertibleTo(t) {
return gso.value.Convert(t), nil return gso.value.Convert(t), nil
} else { }
return gso.value, nil return gso.value, nil
} }
} }
}
if gao, ok := v._object().value.(*_goArrayObject); ok { if gao, ok := v.object().value.(*goArrayObject); ok {
if gao.value.Type().AssignableTo(t) { if gao.value.Type().AssignableTo(t) {
// please see TestDynamicFunctionReturningInterface for why this exists // please see TestDynamicFunctionReturningInterface for why this exists
if t.Kind() == reflect.Interface && gao.value.Type().ConvertibleTo(t) { if t.Kind() == reflect.Interface && gao.value.Type().ConvertibleTo(t) {
return gao.value.Convert(t), nil return gao.value.Convert(t), nil
} else {
return gao.value, nil
} }
return gao.value, nil
} }
} }
} }
@ -392,9 +391,9 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
return reflect.Zero(t), nil return reflect.Zero(t), nil
default: default:
var vv reflect.Value var vv reflect.Value
vv, err := self.convertCallParameter(v, t.Elem()) vv, err := rt.convertCallParameter(v, t.Elem())
if err != nil { if err != nil {
return reflect.Zero(t), fmt.Errorf("can't convert to %s: %s", t, err.Error()) return reflect.Zero(t), fmt.Errorf("can't convert to %s: %w", t, err)
} }
if vv.CanAddr() { if vv.CanAddr() {
@ -418,12 +417,11 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
return reflect.ValueOf(fmt.Sprintf("%v", v.value)), nil return reflect.ValueOf(fmt.Sprintf("%v", v.value)), nil
} }
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64: case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Float32, reflect.Float64:
switch v.kind { if v.kind == valueNumber {
case valueNumber: return rt.convertNumeric(v, t), nil
return self.convertNumeric(v, t), nil
} }
case reflect.Slice: case reflect.Slice:
if o := v._object(); o != nil { if o := v.object(); o != nil {
if lv := o.get(propertyLength); lv.IsNumber() { if lv := o.get(propertyLength); lv.IsNumber() {
l := lv.number().int64 l := lv.number().int64
@ -432,7 +430,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
tt := t.Elem() tt := t.Elem()
switch o.class { switch o.class {
case classArray: case classArrayName:
for i := int64(0); i < l; i++ { for i := int64(0); i < l; i++ {
p, ok := o.property[strconv.FormatInt(i, 10)] p, ok := o.property[strconv.FormatInt(i, 10)]
if !ok { if !ok {
@ -444,24 +442,24 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
continue continue
} }
ev, err := self.convertCallParameter(e, tt) ev, err := rt.convertCallParameter(e, tt)
if err != nil { if err != nil {
return reflect.Zero(t), fmt.Errorf("couldn't convert element %d of %s: %s", i, t, err.Error()) return reflect.Zero(t), fmt.Errorf("couldn't convert element %d of %s: %w", i, t, err)
} }
s.Index(int(i)).Set(ev) s.Index(int(i)).Set(ev)
} }
case classGoArray, classGoSlice: case classGoArrayName, classGoSliceName:
var gslice bool var gslice bool
switch o.value.(type) { switch o.value.(type) {
case *_goSliceObject: case *goSliceObject:
gslice = true gslice = true
case *_goArrayObject: case *goArrayObject:
gslice = false gslice = false
} }
for i := int64(0); i < l; i++ { for i := int64(0); i < l; i++ {
var p *_property var p *property
if gslice { if gslice {
p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10)) p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10))
} else { } else {
@ -476,9 +474,9 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
continue continue
} }
ev, err := self.convertCallParameter(e, tt) ev, err := rt.convertCallParameter(e, tt)
if err != nil { if err != nil {
return reflect.Zero(t), fmt.Errorf("couldn't convert element %d of %s: %s", i, t, err.Error()) return reflect.Zero(t), fmt.Errorf("couldn't convert element %d of %s: %w", i, t, err)
} }
s.Index(int(i)).Set(ev) s.Index(int(i)).Set(ev)
@ -489,15 +487,15 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
} }
} }
case reflect.Map: case reflect.Map:
if o := v._object(); o != nil && t.Key().Kind() == reflect.String { if o := v.object(); o != nil && t.Key().Kind() == reflect.String {
m := reflect.MakeMap(t) m := reflect.MakeMap(t)
var err error var err error
o.enumerate(false, func(k string) bool { o.enumerate(false, func(k string) bool {
v, verr := self.convertCallParameter(o.get(k), t.Elem()) v, verr := rt.convertCallParameter(o.get(k), t.Elem())
if verr != nil { if verr != nil {
err = fmt.Errorf("couldn't convert property %q of %s: %s", k, t, verr.Error()) err = fmt.Errorf("couldn't convert property %q of %s: %w", k, t, verr)
return false return false
} }
m.SetMapIndex(reflect.ValueOf(k), v) m.SetMapIndex(reflect.ValueOf(k), v)
@ -515,7 +513,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
return reflect.Zero(t), fmt.Errorf("converting JavaScript values to Go functions with more than one return value is currently not supported") return reflect.Zero(t), fmt.Errorf("converting JavaScript values to Go functions with more than one return value is currently not supported")
} }
if o := v._object(); o != nil && o.class == classFunction { if o := v.object(); o != nil && o.class == classFunctionName {
return reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value { return reflect.MakeFunc(t, func(args []reflect.Value) []reflect.Value {
l := make([]interface{}, len(args)) l := make([]interface{}, len(args))
for i, a := range args { for i, a := range args {
@ -533,16 +531,16 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
return nil return nil
} }
r, err := self.convertCallParameter(rv, t.Out(0)) r, err := rt.convertCallParameter(rv, t.Out(0))
if err != nil { if err != nil {
panic(self.panicTypeError(err.Error())) panic(rt.panicTypeError("convertCallParameter Func: %s", err))
} }
return []reflect.Value{r} return []reflect.Value{r}
}), nil }), nil
} }
case reflect.Struct: case reflect.Struct:
if o := v._object(); o != nil && o.class == classObject { if o := v.object(); o != nil && o.class == classObjectName {
s := reflect.New(t) s := reflect.New(t)
for _, k := range o.propertyOrder { for _, k := range o.propertyOrder {
@ -570,9 +568,9 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
ss = ss.Field(i) ss = ss.Field(i)
} }
v, err := self.convertCallParameter(o.get(k), ss.Type()) v, err := rt.convertCallParameter(o.get(k), ss.Type())
if err != nil { if err != nil {
return reflect.Zero(t), fmt.Errorf("couldn't convert property %q of %s: %s", k, t, err.Error()) return reflect.Zero(t), fmt.Errorf("couldn't convert property %q of %s: %w", k, t, err)
} }
ss.Set(v) ss.Set(v)
@ -583,16 +581,16 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
} }
if tk == reflect.String { if tk == reflect.String {
if o := v._object(); o != nil && o.hasProperty("toString") { if o := v.object(); o != nil && o.hasProperty("toString") {
if fn := o.get("toString"); fn.IsFunction() { if fn := o.get("toString"); fn.IsFunction() {
sv, err := fn.Call(v) sv, err := fn.Call(v)
if err != nil { if err != nil {
return reflect.Zero(t), fmt.Errorf("couldn't call toString: %s", err.Error()) return reflect.Zero(t), fmt.Errorf("couldn't call toString: %w", err)
} }
r, err := self.convertCallParameter(sv, t) r, err := rt.convertCallParameter(sv, t)
if err != nil { if err != nil {
return reflect.Zero(t), fmt.Errorf("couldn't convert toString result: %s", err.Error()) return reflect.Zero(t), fmt.Errorf("couldn't convert toString result: %w", err)
} }
return r, nil return r, nil
} }
@ -608,7 +606,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
r := reflect.New(t) r := reflect.New(t)
if err := r.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(v.string())); err != nil { if err := r.Interface().(encoding.TextUnmarshaler).UnmarshalText([]byte(v.string())); err != nil {
return reflect.Zero(t), fmt.Errorf("can't convert to %s as TextUnmarshaller: %s", t.String(), err.Error()) return reflect.Zero(t), fmt.Errorf("can't convert to %s as TextUnmarshaller: %w", t.String(), err)
} }
return r.Elem(), nil return r.Elem(), nil
@ -634,7 +632,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
return reflect.Zero(t), fmt.Errorf("can't convert from %q to %q", s, t) return reflect.Zero(t), fmt.Errorf("can't convert from %q to %q", s, t)
} }
func (self *_runtime) toValue(value interface{}) Value { func (rt *runtime) toValue(value interface{}) Value {
rv, ok := value.(reflect.Value) rv, ok := value.(reflect.Value)
if ok { if ok {
value = rv.Interface() value = rv.Interface()
@ -647,58 +645,57 @@ func (self *_runtime) toValue(value interface{}) Value {
var name, file string var name, file string
var line int var line int
pc := reflect.ValueOf(value).Pointer() pc := reflect.ValueOf(value).Pointer()
fn := runtime.FuncForPC(pc) fn := goruntime.FuncForPC(pc)
if fn != nil { if fn != nil {
name = fn.Name() name = fn.Name()
file, line = fn.FileLine(pc) file, line = fn.FileLine(pc)
file = path.Base(file) file = path.Base(file)
} }
return toValue_object(self.newNativeFunction(name, file, line, value)) return objectValue(rt.newNativeFunction(name, file, line, value))
case _nativeFunction: case nativeFunction:
var name, file string var name, file string
var line int var line int
pc := reflect.ValueOf(value).Pointer() pc := reflect.ValueOf(value).Pointer()
fn := runtime.FuncForPC(pc) fn := goruntime.FuncForPC(pc)
if fn != nil { if fn != nil {
name = fn.Name() name = fn.Name()
file, line = fn.FileLine(pc) file, line = fn.FileLine(pc)
file = path.Base(file) file = path.Base(file)
} }
return toValue_object(self.newNativeFunction(name, file, line, value)) return objectValue(rt.newNativeFunction(name, file, line, value))
case Object, *Object, _object, *_object: case Object, *Object, object, *object:
// Nothing happens. // Nothing happens.
// FIXME We should really figure out what can come here. // FIXME We should really figure out what can come here.
// This catch-all is ugly. // This catch-all is ugly.
default: default:
{ val := reflect.ValueOf(value)
value := reflect.ValueOf(value) if ok && val.Kind() == rv.Kind() {
if ok && value.Kind() == rv.Kind() {
// Use passed in rv which may be writable. // Use passed in rv which may be writable.
value = rv val = rv
} }
switch value.Kind() { switch val.Kind() {
case reflect.Ptr: case reflect.Ptr:
switch reflect.Indirect(value).Kind() { switch reflect.Indirect(val).Kind() {
case reflect.Struct: case reflect.Struct:
return toValue_object(self.newGoStructObject(value)) return objectValue(rt.newGoStructObject(val))
case reflect.Array: case reflect.Array:
return toValue_object(self.newGoArray(value)) return objectValue(rt.newGoArray(val))
} }
case reflect.Struct: case reflect.Struct:
return toValue_object(self.newGoStructObject(value)) return objectValue(rt.newGoStructObject(val))
case reflect.Map: case reflect.Map:
return toValue_object(self.newGoMapObject(value)) return objectValue(rt.newGoMapObject(val))
case reflect.Slice: case reflect.Slice:
return toValue_object(self.newGoSlice(value)) return objectValue(rt.newGoSlice(val))
case reflect.Array: case reflect.Array:
return toValue_object(self.newGoArray(value)) return objectValue(rt.newGoArray(val))
case reflect.Func: case reflect.Func:
var name, file string var name, file string
var line int var line int
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr { if v := reflect.ValueOf(val); v.Kind() == reflect.Ptr {
pc := v.Pointer() pc := v.Pointer()
fn := runtime.FuncForPC(pc) fn := goruntime.FuncForPC(pc)
if fn != nil { if fn != nil {
name = fn.Name() name = fn.Name()
file, line = fn.FileLine(pc) file, line = fn.FileLine(pc)
@ -706,18 +703,18 @@ func (self *_runtime) toValue(value interface{}) Value {
} }
} }
typ := value.Type() typ := val.Type()
return toValue_object(self.newNativeFunction(name, file, line, func(c FunctionCall) Value { return objectValue(rt.newNativeFunction(name, file, line, func(c FunctionCall) Value {
nargs := typ.NumIn() nargs := typ.NumIn()
if len(c.ArgumentList) != nargs { if len(c.ArgumentList) != nargs {
if typ.IsVariadic() { if typ.IsVariadic() {
if len(c.ArgumentList) < nargs-1 { if len(c.ArgumentList) < nargs-1 {
panic(self.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList)))) panic(rt.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList))))
} }
} else { } else {
panic(self.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList)))) panic(rt.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList))))
} }
} }
@ -745,16 +742,16 @@ func (self *_runtime) toValue(value interface{}) Value {
// actual set of variadic Go arguments. if that succeeds, break // actual set of variadic Go arguments. if that succeeds, break
// out of the loop. // out of the loop.
if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 { if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 {
if v, err := self.convertCallParameter(a, typ.In(n)); err == nil { if v, err := rt.convertCallParameter(a, typ.In(n)); err == nil {
in[i] = v in[i] = v
callSlice = true callSlice = true
break break
} }
} }
v, err := self.convertCallParameter(a, t) v, err := rt.convertCallParameter(a, t)
if err != nil { if err != nil {
panic(self.panicTypeError(err.Error())) panic(rt.panicTypeError(err.Error()))
} }
in[i] = v in[i] = v
@ -762,58 +759,57 @@ func (self *_runtime) toValue(value interface{}) Value {
var out []reflect.Value var out []reflect.Value
if callSlice { if callSlice {
out = value.CallSlice(in) out = val.CallSlice(in)
} else { } else {
out = value.Call(in) out = val.Call(in)
} }
switch len(out) { switch len(out) {
case 0: case 0:
return Value{} return Value{}
case 1: case 1:
return self.toValue(out[0].Interface()) return rt.toValue(out[0].Interface())
default: default:
s := make([]interface{}, len(out)) s := make([]interface{}, len(out))
for i, v := range out { for i, v := range out {
s[i] = self.toValue(v.Interface()) s[i] = rt.toValue(v.Interface())
} }
return self.toValue(s) return rt.toValue(s)
} }
})) }))
} }
} }
}
return toValue(value) return toValue(value)
} }
func (runtime *_runtime) newGoSlice(value reflect.Value) *_object { func (rt *runtime) newGoSlice(value reflect.Value) *object {
self := runtime.newGoSliceObject(value) obj := rt.newGoSliceObject(value)
self.prototype = runtime.global.ArrayPrototype obj.prototype = rt.global.ArrayPrototype
return self return obj
} }
func (runtime *_runtime) newGoArray(value reflect.Value) *_object { func (rt *runtime) newGoArray(value reflect.Value) *object {
self := runtime.newGoArrayObject(value) obj := rt.newGoArrayObject(value)
self.prototype = runtime.global.ArrayPrototype obj.prototype = rt.global.ArrayPrototype
return self return obj
} }
func (runtime *_runtime) parse(filename string, src, sm interface{}) (*ast.Program, error) { func (rt *runtime) parse(filename string, src, sm interface{}) (*ast.Program, error) {
return parser.ParseFileWithSourceMap(nil, filename, src, sm, 0) return parser.ParseFileWithSourceMap(nil, filename, src, sm, 0)
} }
func (runtime *_runtime) cmpl_parse(filename string, src, sm interface{}) (*_nodeProgram, error) { func (rt *runtime) cmplParse(filename string, src, sm interface{}) (*nodeProgram, error) {
program, err := parser.ParseFileWithSourceMap(nil, filename, src, sm, 0) program, err := parser.ParseFileWithSourceMap(nil, filename, src, sm, 0)
if err != nil { if err != nil {
return nil, err return nil, err
} }
return cmpl_parse(program), nil return cmplParse(program), nil
} }
func (self *_runtime) parseSource(src, sm interface{}) (*_nodeProgram, *ast.Program, error) { func (rt *runtime) parseSource(src, sm interface{}) (*nodeProgram, *ast.Program, error) {
switch src := src.(type) { switch src := src.(type) {
case *ast.Program: case *ast.Program:
return nil, src, nil return nil, src, nil
@ -821,22 +817,22 @@ func (self *_runtime) parseSource(src, sm interface{}) (*_nodeProgram, *ast.Prog
return src.program, nil, nil return src.program, nil, nil
} }
program, err := self.parse("", src, sm) program, err := rt.parse("", src, sm)
return nil, program, err return nil, program, err
} }
func (self *_runtime) cmpl_runOrEval(src, sm interface{}, eval bool) (Value, error) { func (rt *runtime) cmplRunOrEval(src, sm interface{}, eval bool) (Value, error) {
result := Value{} result := Value{}
cmpl_program, program, err := self.parseSource(src, sm) node, program, err := rt.parseSource(src, sm)
if err != nil { if err != nil {
return result, err return result, err
} }
if cmpl_program == nil { if node == nil {
cmpl_program = cmpl_parse(program) node = cmplParse(program)
} }
err = catchPanic(func() { err = catchPanic(func() {
result = self.cmpl_evaluate_nodeProgram(cmpl_program, eval) result = rt.cmplEvaluateNodeProgram(node, eval)
}) })
switch result.kind { switch result.kind {
case valueEmpty: case valueEmpty:
@ -847,33 +843,32 @@ func (self *_runtime) cmpl_runOrEval(src, sm interface{}, eval bool) (Value, err
return result, err return result, err
} }
func (self *_runtime) cmpl_run(src, sm interface{}) (Value, error) { func (rt *runtime) cmplRun(src, sm interface{}) (Value, error) {
return self.cmpl_runOrEval(src, sm, false) return rt.cmplRunOrEval(src, sm, false)
} }
func (self *_runtime) cmpl_eval(src, sm interface{}) (Value, error) { func (rt *runtime) cmplEval(src, sm interface{}) (Value, error) {
return self.cmpl_runOrEval(src, sm, true) return rt.cmplRunOrEval(src, sm, true)
} }
func (self *_runtime) parseThrow(err error) { func (rt *runtime) parseThrow(err error) {
if err == nil { if err == nil {
return return
} }
switch err := err.(type) {
case parser.ErrorList: var errl parser.ErrorList
{ if errors.Is(err, &errl) {
err := err[0] err := errl[0]
if err.Message == "Invalid left-hand side in assignment" { if err.Message == "invalid left-hand side in assignment" {
panic(self.panicReferenceError(err.Message)) panic(rt.panicReferenceError(err.Message))
} }
panic(self.panicSyntaxError(err.Message)) panic(rt.panicSyntaxError(err.Message))
} }
} panic(rt.panicSyntaxError(err.Error()))
panic(self.panicSyntaxError(err.Error()))
} }
func (self *_runtime) cmpl_parseOrThrow(src, sm interface{}) *_nodeProgram { func (rt *runtime) cmplParseOrThrow(src, sm interface{}) *nodeProgram {
program, err := self.cmpl_parse("", src, sm) program, err := rt.cmplParse("", src, sm)
self.parseThrow(err) // Will panic/throw appropriately rt.parseThrow(err) // Will panic/throw appropriately
return program return program
} }

View file

@ -1,33 +1,19 @@
package otto package otto
// _scope: // An ECMA-262 ExecutionContext.
// entryFile type scope struct {
// entryIdx lexical stasher
// top? variable stasher
// outer => nil this *object
// _stash:
// lexical
// variable
//
// _thisStash (ObjectEnvironment)
// _fnStash
// _dclStash
// An ECMA-262 ExecutionContext
type _scope struct {
lexical _stash
variable _stash
this *_object
eval bool // Replace this with kind? eval bool // Replace this with kind?
outer *_scope outer *scope
depth int depth int
frame _frame frame frame
} }
func newScope(lexical _stash, variable _stash, this *_object) *_scope { func newScope(lexical stasher, variable stasher, this *object) *scope {
return &_scope{ return &scope{
lexical: lexical, lexical: lexical,
variable: variable, variable: variable,
this: this, this: this,

View file

@ -6,6 +6,7 @@ import (
"errors" "errors"
) )
// ErrVersion is an error which represents a version mismatch.
var ErrVersion = errors.New("version mismatch") var ErrVersion = errors.New("version mismatch")
var scriptVersion = "2014-04-13/1" var scriptVersion = "2014-04-13/1"
@ -14,7 +15,7 @@ var scriptVersion = "2014-04-13/1"
// Passing a Script value to a run method will evaluate the JavaScript. // Passing a Script value to a run method will evaluate the JavaScript.
type Script struct { type Script struct {
version string version string
program *_nodeProgram program *nodeProgram
filename string filename string
src string src string
} }
@ -24,23 +25,22 @@ type Script struct {
// //
// script, err := vm.Compile("", `var abc; if (!abc) abc = 0; abc += 2; abc;`) // script, err := vm.Compile("", `var abc; if (!abc) abc = 0; abc += 2; abc;`)
// vm.Run(script) // vm.Run(script)
func (self *Otto) Compile(filename string, src interface{}) (*Script, error) { func (o *Otto) Compile(filename string, src interface{}) (*Script, error) {
return self.CompileWithSourceMap(filename, src, nil) return o.CompileWithSourceMap(filename, src, nil)
} }
// CompileWithSourceMap does the same thing as Compile, but with the obvious // CompileWithSourceMap does the same thing as Compile, but with the obvious
// difference of applying a source map. // difference of applying a source map.
func (self *Otto) CompileWithSourceMap(filename string, src, sm interface{}) (*Script, error) { func (o *Otto) CompileWithSourceMap(filename string, src, sm interface{}) (*Script, error) {
program, err := self.runtime.parse(filename, src, sm) program, err := o.runtime.parse(filename, src, sm)
if err != nil { if err != nil {
return nil, err return nil, err
} }
cmpl_program := cmpl_parse(program) node := cmplParse(program)
script := &Script{ script := &Script{
version: scriptVersion, version: scriptVersion,
program: cmpl_program, program: node,
filename: filename, filename: filename,
src: program.File.Source(), src: program.File.Source(),
} }
@ -48,30 +48,30 @@ func (self *Otto) CompileWithSourceMap(filename string, src, sm interface{}) (*S
return script, nil return script, nil
} }
func (self *Script) String() string { func (s *Script) String() string {
return "// " + self.filename + "\n" + self.src return "// " + s.filename + "\n" + s.src
} }
// MarshalBinary will marshal a script into a binary form. A marshalled script // MarshalBinary will marshal a script into a binary form. A marshalled script
// that is later unmarshalled can be executed on the same version of the otto runtime. // that is later unmarshalled can be executed on the same version of the otto runtime.
// //
// The binary format can change at any time and should be considered unspecified and opaque. // The binary format can change at any time and should be considered unspecified and opaque.
func (self *Script) marshalBinary() ([]byte, error) { func (s *Script) marshalBinary() ([]byte, error) {
var bfr bytes.Buffer var bfr bytes.Buffer
encoder := gob.NewEncoder(&bfr) encoder := gob.NewEncoder(&bfr)
err := encoder.Encode(self.version) err := encoder.Encode(s.version)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = encoder.Encode(self.program) err = encoder.Encode(s.program)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = encoder.Encode(self.filename) err = encoder.Encode(s.filename)
if err != nil { if err != nil {
return nil, err return nil, err
} }
err = encoder.Encode(self.src) err = encoder.Encode(s.src)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -83,27 +83,27 @@ func (self *Script) marshalBinary() ([]byte, error) {
// will return an error. // will return an error.
// //
// The binary format can change at any time and should be considered unspecified and opaque. // The binary format can change at any time and should be considered unspecified and opaque.
func (self *Script) unmarshalBinary(data []byte) (err error) { func (s *Script) unmarshalBinary(data []byte) (err error) { //nolint: nonamedreturns
decoder := gob.NewDecoder(bytes.NewReader(data)) decoder := gob.NewDecoder(bytes.NewReader(data))
defer func() { defer func() {
if err != nil { if err != nil {
self.version = "" s.version = ""
self.program = nil s.program = nil
self.filename = "" s.filename = ""
self.src = "" s.src = ""
} }
}() }()
if err = decoder.Decode(&self.version); err != nil { if err = decoder.Decode(&s.version); err != nil {
return err return err
} }
if self.version != scriptVersion { if s.version != scriptVersion {
return ErrVersion return ErrVersion
} }
if err = decoder.Decode(&self.program); err != nil { if err = decoder.Decode(&s.program); err != nil {
return err return err
} }
if err = decoder.Decode(&self.filename); err != nil { if err = decoder.Decode(&s.filename); err != nil {
return err return err
} }
return decoder.Decode(&self.src) return decoder.Decode(&s.src)
} }

View file

@ -4,11 +4,8 @@ import (
"fmt" "fmt"
) )
// ====== // stasher is implemented by types which can stash data.
// _stash type stasher interface {
// ======
type _stash interface {
hasBinding(string) bool // hasBinding(string) bool //
createBinding(string, bool, Value) // CreateMutableBinding createBinding(string, bool, Value) // CreateMutableBinding
setBinding(string, Value, bool) // SetMutableBinding setBinding(string, Value, bool) // SetMutableBinding
@ -16,160 +13,151 @@ type _stash interface {
deleteBinding(string) bool // deleteBinding(string) bool //
setValue(string, Value, bool) // createBinding + setBinding setValue(string, Value, bool) // createBinding + setBinding
outer() _stash outer() stasher
runtime() *_runtime runtime() *runtime
newReference(string, bool, _at) _reference newReference(string, bool, at) referencer
clone(clone *_clone) _stash clone(*cloner) stasher
} }
// ========== type objectStash struct {
// _objectStash rt *runtime
// ========== outr stasher
object *object
type _objectStash struct {
_runtime *_runtime
_outer _stash
object *_object
} }
func (self *_objectStash) runtime() *_runtime { func (s *objectStash) runtime() *runtime {
return self._runtime return s.rt
} }
func (runtime *_runtime) newObjectStash(object *_object, outer _stash) *_objectStash { func (rt *runtime) newObjectStash(obj *object, outer stasher) *objectStash {
if object == nil { if obj == nil {
object = runtime.newBaseObject() obj = rt.newBaseObject()
object.class = "environment" obj.class = "environment"
} }
return &_objectStash{ return &objectStash{
_runtime: runtime, rt: rt,
_outer: outer, outr: outer,
object: object, object: obj,
} }
} }
func (in *_objectStash) clone(clone *_clone) _stash { func (s *objectStash) clone(c *cloner) stasher {
out, exists := clone.objectStash(in) out, exists := c.objectStash(s)
if exists { if exists {
return out return out
} }
*out = _objectStash{ *out = objectStash{
clone.runtime, c.runtime,
clone.stash(in._outer), c.stash(s.outr),
clone.object(in.object), c.object(s.object),
} }
return out return out
} }
func (self *_objectStash) hasBinding(name string) bool { func (s *objectStash) hasBinding(name string) bool {
return self.object.hasProperty(name) return s.object.hasProperty(name)
} }
func (self *_objectStash) createBinding(name string, deletable bool, value Value) { func (s *objectStash) createBinding(name string, deletable bool, value Value) {
if self.object.hasProperty(name) { if s.object.hasProperty(name) {
panic(hereBeDragons()) panic(hereBeDragons())
} }
mode := _propertyMode(0111) mode := propertyMode(0o111)
if !deletable { if !deletable {
mode = _propertyMode(0110) mode = propertyMode(0o110)
} }
// TODO False? // TODO False?
self.object.defineProperty(name, value, mode, false) s.object.defineProperty(name, value, mode, false)
} }
func (self *_objectStash) setBinding(name string, value Value, strict bool) { func (s *objectStash) setBinding(name string, value Value, strict bool) {
self.object.put(name, value, strict) s.object.put(name, value, strict)
} }
func (self *_objectStash) setValue(name string, value Value, throw bool) { func (s *objectStash) setValue(name string, value Value, throw bool) {
if !self.hasBinding(name) { if !s.hasBinding(name) {
self.createBinding(name, true, value) // Configurable by default s.createBinding(name, true, value) // Configurable by default
} else { } else {
self.setBinding(name, value, throw) s.setBinding(name, value, throw)
} }
} }
func (self *_objectStash) getBinding(name string, throw bool) Value { func (s *objectStash) getBinding(name string, throw bool) Value {
if self.object.hasProperty(name) { if s.object.hasProperty(name) {
return self.object.get(name) return s.object.get(name)
} }
if throw { // strict? if throw { // strict?
panic(self._runtime.panicReferenceError("Not Defined", name)) panic(s.rt.panicReferenceError("Not Defined", name))
} }
return Value{} return Value{}
} }
func (self *_objectStash) deleteBinding(name string) bool { func (s *objectStash) deleteBinding(name string) bool {
return self.object.delete(name, false) return s.object.delete(name, false)
} }
func (self *_objectStash) outer() _stash { func (s *objectStash) outer() stasher {
return self._outer return s.outr
} }
func (self *_objectStash) newReference(name string, strict bool, at _at) _reference { func (s *objectStash) newReference(name string, strict bool, atv at) referencer {
return newPropertyReference(self._runtime, self.object, name, strict, at) return newPropertyReference(s.rt, s.object, name, strict, atv)
} }
// ========= type dclStash struct {
// _dclStash rt *runtime
// ========= outr stasher
property map[string]dclProperty
type _dclStash struct {
_runtime *_runtime
_outer _stash
property map[string]_dclProperty
} }
type _dclProperty struct { type dclProperty struct {
value Value value Value
mutable bool mutable bool
deletable bool deletable bool
readable bool readable bool
} }
func (runtime *_runtime) newDeclarationStash(outer _stash) *_dclStash { func (rt *runtime) newDeclarationStash(outer stasher) *dclStash {
return &_dclStash{ return &dclStash{
_runtime: runtime, rt: rt,
_outer: outer, outr: outer,
property: map[string]_dclProperty{}, property: map[string]dclProperty{},
} }
} }
func (in *_dclStash) clone(clone *_clone) _stash { func (s *dclStash) clone(c *cloner) stasher {
out, exists := clone.dclStash(in) out, exists := c.dclStash(s)
if exists { if exists {
return out return out
} }
property := make(map[string]_dclProperty, len(in.property)) prop := make(map[string]dclProperty, len(s.property))
for index, value := range in.property { for index, value := range s.property {
property[index] = clone.dclProperty(value) prop[index] = c.dclProperty(value)
} }
*out = _dclStash{ *out = dclStash{
clone.runtime, c.runtime,
clone.stash(in._outer), c.stash(s.outr),
property, prop,
} }
return out return out
} }
func (self *_dclStash) hasBinding(name string) bool { func (s *dclStash) hasBinding(name string) bool {
_, exists := self.property[name] _, exists := s.property[name]
return exists return exists
} }
func (self *_dclStash) runtime() *_runtime { func (s *dclStash) runtime() *runtime {
return self._runtime return s.rt
} }
func (self *_dclStash) createBinding(name string, deletable bool, value Value) { func (s *dclStash) createBinding(name string, deletable bool, value Value) {
_, exists := self.property[name] if _, exists := s.property[name]; exists {
if exists {
panic(fmt.Errorf("createBinding: %s: already exists", name)) panic(fmt.Errorf("createBinding: %s: already exists", name))
} }
self.property[name] = _dclProperty{ s.property[name] = dclProperty{
value: value, value: value,
mutable: true, mutable: true,
deletable: deletable, deletable: deletable,
@ -177,62 +165,62 @@ func (self *_dclStash) createBinding(name string, deletable bool, value Value) {
} }
} }
func (self *_dclStash) setBinding(name string, value Value, strict bool) { func (s *dclStash) setBinding(name string, value Value, strict bool) {
property, exists := self.property[name] prop, exists := s.property[name]
if !exists { if !exists {
panic(fmt.Errorf("setBinding: %s: missing", name)) panic(fmt.Errorf("setBinding: %s: missing", name))
} }
if property.mutable { if prop.mutable {
property.value = value prop.value = value
self.property[name] = property s.property[name] = prop
} else { } else {
self._runtime.typeErrorResult(strict) s.rt.typeErrorResult(strict)
} }
} }
func (self *_dclStash) setValue(name string, value Value, throw bool) { func (s *dclStash) setValue(name string, value Value, throw bool) {
if !self.hasBinding(name) { if !s.hasBinding(name) {
self.createBinding(name, false, value) // NOT deletable by default s.createBinding(name, false, value) // NOT deletable by default
} else { } else {
self.setBinding(name, value, throw) s.setBinding(name, value, throw)
} }
} }
// FIXME This is called a __lot__ // FIXME This is called a __lot__.
func (self *_dclStash) getBinding(name string, throw bool) Value { func (s *dclStash) getBinding(name string, throw bool) Value {
property, exists := self.property[name] prop, exists := s.property[name]
if !exists { if !exists {
panic(fmt.Errorf("getBinding: %s: missing", name)) panic(fmt.Errorf("getBinding: %s: missing", name))
} }
if !property.mutable && !property.readable { if !prop.mutable && !prop.readable {
if throw { // strict? if throw { // strict?
panic(self._runtime.panicTypeError()) panic(s.rt.panicTypeError("getBinding property %s not mutable and not readable", name))
} }
return Value{} return Value{}
} }
return property.value return prop.value
} }
func (self *_dclStash) deleteBinding(name string) bool { func (s *dclStash) deleteBinding(name string) bool {
property, exists := self.property[name] prop, exists := s.property[name]
if !exists { if !exists {
return true return true
} }
if !property.deletable { if !prop.deletable {
return false return false
} }
delete(self.property, name) delete(s.property, name)
return true return true
} }
func (self *_dclStash) outer() _stash { func (s *dclStash) outer() stasher {
return self._outer return s.outr
} }
func (self *_dclStash) newReference(name string, strict bool, _ _at) _reference { func (s *dclStash) newReference(name string, strict bool, _ at) referencer {
return &_stashReference{ return &stashReference{
name: name, name: name,
base: self, base: s,
} }
} }
@ -240,57 +228,62 @@ func (self *_dclStash) newReference(name string, strict bool, _ _at) _reference
// _fnStash // _fnStash
// ======== // ========
type _fnStash struct { type fnStash struct {
_dclStash dclStash
arguments *_object arguments *object
indexOfArgumentName map[string]string indexOfArgumentName map[string]string
} }
func (runtime *_runtime) newFunctionStash(outer _stash) *_fnStash { func (rt *runtime) newFunctionStash(outer stasher) *fnStash {
return &_fnStash{ return &fnStash{
_dclStash: _dclStash{ dclStash: dclStash{
_runtime: runtime, rt: rt,
_outer: outer, outr: outer,
property: map[string]_dclProperty{}, property: map[string]dclProperty{},
}, },
} }
} }
func (in *_fnStash) clone(clone *_clone) _stash { func (s *fnStash) clone(c *cloner) stasher {
out, exists := clone.fnStash(in) out, exists := c.fnStash(s)
if exists { if exists {
return out return out
} }
dclStash := in._dclStash.clone(clone).(*_dclStash) dclStash := s.dclStash.clone(c).(*dclStash)
index := make(map[string]string, len(in.indexOfArgumentName)) index := make(map[string]string, len(s.indexOfArgumentName))
for name, value := range in.indexOfArgumentName { for name, value := range s.indexOfArgumentName {
index[name] = value index[name] = value
} }
*out = _fnStash{ *out = fnStash{
_dclStash: *dclStash, dclStash: *dclStash,
arguments: clone.object(in.arguments), arguments: c.object(s.arguments),
indexOfArgumentName: index, indexOfArgumentName: index,
} }
return out return out
} }
func getStashProperties(stash _stash) (keys []string) { // getStashProperties returns the properties from stash.
func getStashProperties(stash stasher) []string {
switch vars := stash.(type) { switch vars := stash.(type) {
case *_dclStash: case *dclStash:
keys := make([]string, 0, len(vars.property))
for k := range vars.property { for k := range vars.property {
keys = append(keys, k) keys = append(keys, k)
} }
case *_fnStash: return keys
case *fnStash:
keys := make([]string, 0, len(vars.property))
for k := range vars.property { for k := range vars.property {
keys = append(keys, k) keys = append(keys, k)
} }
case *_objectStash: return keys
case *objectStash:
keys := make([]string, 0, len(vars.object.property))
for k := range vars.object.property { for k := range vars.object.property {
keys = append(keys, k) keys = append(keys, k)
} }
return keys
default: default:
panic("unknown stash type") panic("unknown stash type")
} }
return
} }

View file

@ -1,2 +0,0 @@
token_const.go: tokenfmt
./$^ | gofmt > $@

View file

@ -1,171 +0,0 @@
# token
--
import "github.com/robertkrimen/otto/token"
Package token defines constants representing the lexical tokens of JavaScript
(ECMA5).
## Usage
```go
const (
ILLEGAL
EOF
COMMENT
KEYWORD
STRING
BOOLEAN
NULL
NUMBER
IDENTIFIER
PLUS // +
MINUS // -
MULTIPLY // *
SLASH // /
REMAINDER // %
AND // &
OR // |
EXCLUSIVE_OR // ^
SHIFT_LEFT // <<
SHIFT_RIGHT // >>
UNSIGNED_SHIFT_RIGHT // >>>
AND_NOT // &^
ADD_ASSIGN // +=
SUBTRACT_ASSIGN // -=
MULTIPLY_ASSIGN // *=
QUOTIENT_ASSIGN // /=
REMAINDER_ASSIGN // %=
AND_ASSIGN // &=
OR_ASSIGN // |=
EXCLUSIVE_OR_ASSIGN // ^=
SHIFT_LEFT_ASSIGN // <<=
SHIFT_RIGHT_ASSIGN // >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
AND_NOT_ASSIGN // &^=
LOGICAL_AND // &&
LOGICAL_OR // ||
INCREMENT // ++
DECREMENT // --
EQUAL // ==
STRICT_EQUAL // ===
LESS // <
GREATER // >
ASSIGN // =
NOT // !
BITWISE_NOT // ~
NOT_EQUAL // !=
STRICT_NOT_EQUAL // !==
LESS_OR_EQUAL // <=
GREATER_OR_EQUAL // >=
LEFT_PARENTHESIS // (
LEFT_BRACKET // [
LEFT_BRACE // {
COMMA // ,
PERIOD // .
RIGHT_PARENTHESIS // )
RIGHT_BRACKET // ]
RIGHT_BRACE // }
SEMICOLON // ;
COLON // :
QUESTION_MARK // ?
IF
IN
DO
VAR
FOR
NEW
TRY
THIS
ELSE
CASE
VOID
WITH
WHILE
BREAK
CATCH
THROW
RETURN
TYPEOF
DELETE
SWITCH
DEFAULT
FINALLY
FUNCTION
CONTINUE
DEBUGGER
INSTANCEOF
)
```
#### type Token
```go
type Token int
```
Token is the set of lexical tokens in JavaScript (ECMA5).
#### func IsKeyword
```go
func IsKeyword(literal string) (Token, bool)
```
IsKeyword returns the keyword token if literal is a keyword, a KEYWORD token if
the literal is a future keyword (const, let, class, super, ...), or 0 if the
literal is not a keyword.
If the literal is a keyword, IsKeyword returns a second value indicating if the
literal is considered a future keyword in strict-mode only.
7.6.1.2 Future Reserved Words:
const
class
enum
export
extends
import
super
7.6.1.2 Future Reserved Words (strict):
implements
interface
let
package
private
protected
public
static
#### func (Token) String
```go
func (tkn Token) String() string
```
String returns the string corresponding to the token. For operators, delimiters,
and keywords the string is the actual token string (e.g., for the token PLUS,
the String() is "+"). For all other tokens the string corresponds to the token
name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
--
**godocdown** http://github.com/robertkrimen/godocdown

View file

@ -0,0 +1,3 @@
package token
//go:generate go run ../tools/gen-tokens -output token_const.go

View file

@ -14,63 +14,17 @@ type Token int
// "+"). For all other tokens the string corresponds to the token // "+"). For all other tokens the string corresponds to the token
// name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER"). // name (e.g. for the token IDENTIFIER, the string is "IDENTIFIER").
func (tkn Token) String() string { func (tkn Token) String() string {
if 0 == tkn { switch {
case tkn == 0:
return "UNKNOWN" return "UNKNOWN"
} case tkn < Token(len(token2string)):
if tkn < Token(len(token2string)) {
return token2string[tkn] return token2string[tkn]
} default:
return "token(" + strconv.Itoa(int(tkn)) + ")" return "token(" + strconv.Itoa(int(tkn)) + ")"
} }
// This is not used for anything
func (tkn Token) precedence(in bool) int {
switch tkn {
case LOGICAL_OR:
return 1
case LOGICAL_AND:
return 2
case OR, OR_ASSIGN:
return 3
case EXCLUSIVE_OR:
return 4
case AND, AND_ASSIGN, AND_NOT, AND_NOT_ASSIGN:
return 5
case EQUAL,
NOT_EQUAL,
STRICT_EQUAL,
STRICT_NOT_EQUAL:
return 6
case LESS, GREATER, LESS_OR_EQUAL, GREATER_OR_EQUAL, INSTANCEOF:
return 7
case IN:
if in {
return 7
}
return 0
case SHIFT_LEFT, SHIFT_RIGHT, UNSIGNED_SHIFT_RIGHT:
fallthrough
case SHIFT_LEFT_ASSIGN, SHIFT_RIGHT_ASSIGN, UNSIGNED_SHIFT_RIGHT_ASSIGN:
return 8
case PLUS, MINUS, ADD_ASSIGN, SUBTRACT_ASSIGN:
return 9
case MULTIPLY, SLASH, REMAINDER, MULTIPLY_ASSIGN, QUOTIENT_ASSIGN, REMAINDER_ASSIGN:
return 11
}
return 0
} }
type _keyword struct { type keyword struct {
token Token token Token
futureKeyword bool futureKeyword bool
strict bool strict bool

View file

@ -1,25 +1,28 @@
// Code generated by tools/gen-tokens. DO NOT EDIT.
package token package token
const ( const (
_ Token = iota _ Token = iota
// Control.
ILLEGAL ILLEGAL
EOF EOF
COMMENT COMMENT
KEYWORD KEYWORD
// Types.
STRING STRING
BOOLEAN BOOLEAN
NULL NULL
NUMBER NUMBER
IDENTIFIER IDENTIFIER
// Maths.
PLUS // + PLUS // +
MINUS // - MINUS // -
MULTIPLY // * MULTIPLY // *
SLASH // / SLASH // /
REMAINDER // % REMAINDER // %
// Logical and bitwise operators.
AND // & AND // &
OR // | OR // |
EXCLUSIVE_OR // ^ EXCLUSIVE_OR // ^
@ -27,13 +30,13 @@ const (
SHIFT_RIGHT // >> SHIFT_RIGHT // >>
UNSIGNED_SHIFT_RIGHT // >>> UNSIGNED_SHIFT_RIGHT // >>>
AND_NOT // &^ AND_NOT // &^
// Math assignments.
ADD_ASSIGN // += ADD_ASSIGN // +=
SUBTRACT_ASSIGN // -= SUBTRACT_ASSIGN // -=
MULTIPLY_ASSIGN // *= MULTIPLY_ASSIGN // *=
QUOTIENT_ASSIGN // /= QUOTIENT_ASSIGN // /=
REMAINDER_ASSIGN // %= REMAINDER_ASSIGN // %=
// Math and bitwise assignments.
AND_ASSIGN // &= AND_ASSIGN // &=
OR_ASSIGN // |= OR_ASSIGN // |=
EXCLUSIVE_OR_ASSIGN // ^= EXCLUSIVE_OR_ASSIGN // ^=
@ -41,74 +44,73 @@ const (
SHIFT_RIGHT_ASSIGN // >>= SHIFT_RIGHT_ASSIGN // >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>= UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
AND_NOT_ASSIGN // &^= AND_NOT_ASSIGN // &^=
// Logical operators and decrement / increment.
LOGICAL_AND // && LOGICAL_AND // &&
LOGICAL_OR // || LOGICAL_OR // ||
INCREMENT // ++ INCREMENT // ++
DECREMENT // -- DECREMENT // --
// Comparison operators.
EQUAL // == EQUAL // ==
STRICT_EQUAL // === STRICT_EQUAL // ===
LESS // < LESS // <
GREATER // > GREATER // >
ASSIGN // = ASSIGN // =
NOT // ! NOT // !
// Bitwise not.
BITWISE_NOT // ~ BITWISE_NOT // ~
// Comparison operators.
NOT_EQUAL // != NOT_EQUAL // !=
STRICT_NOT_EQUAL // !== STRICT_NOT_EQUAL // !==
LESS_OR_EQUAL // <= LESS_OR_EQUAL // <=
GREATER_OR_EQUAL // >= GREATER_OR_EQUAL // >=
// Left operators.
LEFT_PARENTHESIS // ( LEFT_PARENTHESIS // (
LEFT_BRACKET // [ LEFT_BRACKET // [
LEFT_BRACE // { LEFT_BRACE // {
COMMA // , COMMA // ,
PERIOD // . PERIOD // .
// Right operators.
RIGHT_PARENTHESIS // ) RIGHT_PARENTHESIS // )
RIGHT_BRACKET // ] RIGHT_BRACKET // ]
RIGHT_BRACE // } RIGHT_BRACE // }
SEMICOLON // ; SEMICOLON // ;
COLON // : COLON // :
QUESTION_MARK // ? QUESTION_MARK // ?
// Basic flow - keywords below here.
firstKeyword //nolint: deadcode _
IF IF
IN IN
DO DO
// Declarations.
VAR VAR
FOR FOR
NEW NEW
TRY TRY
// Advanced flow.
THIS THIS
ELSE ELSE
CASE CASE
VOID VOID
WITH WITH
// Loops.
WHILE WHILE
BREAK BREAK
CATCH CATCH
THROW THROW
// Functions.
RETURN RETURN
TYPEOF TYPEOF
DELETE DELETE
SWITCH SWITCH
// Fallback identifiers.
DEFAULT DEFAULT
FINALLY FINALLY
// Miscellaneous.
FUNCTION FUNCTION
CONTINUE CONTINUE
DEBUGGER DEBUGGER
// Instance of.
INSTANCEOF INSTANCEOF
lastKeyword //nolint: deadcode
) )
var token2string = [...]string{ var token2string = [...]string{
@ -199,149 +201,149 @@ var token2string = [...]string{
INSTANCEOF: "instanceof", INSTANCEOF: "instanceof",
} }
var keywordTable = map[string]_keyword{ var keywordTable = map[string]keyword{
"if": _keyword{ "if": {
token: IF, token: IF,
}, },
"in": _keyword{ "in": {
token: IN, token: IN,
}, },
"do": _keyword{ "do": {
token: DO, token: DO,
}, },
"var": _keyword{ "var": {
token: VAR, token: VAR,
}, },
"for": _keyword{ "for": {
token: FOR, token: FOR,
}, },
"new": _keyword{ "new": {
token: NEW, token: NEW,
}, },
"try": _keyword{ "try": {
token: TRY, token: TRY,
}, },
"this": _keyword{ "this": {
token: THIS, token: THIS,
}, },
"else": _keyword{ "else": {
token: ELSE, token: ELSE,
}, },
"case": _keyword{ "case": {
token: CASE, token: CASE,
}, },
"void": _keyword{ "void": {
token: VOID, token: VOID,
}, },
"with": _keyword{ "with": {
token: WITH, token: WITH,
}, },
"while": _keyword{ "while": {
token: WHILE, token: WHILE,
}, },
"break": _keyword{ "break": {
token: BREAK, token: BREAK,
}, },
"catch": _keyword{ "catch": {
token: CATCH, token: CATCH,
}, },
"throw": _keyword{ "throw": {
token: THROW, token: THROW,
}, },
"return": _keyword{ "return": {
token: RETURN, token: RETURN,
}, },
"typeof": _keyword{ "typeof": {
token: TYPEOF, token: TYPEOF,
}, },
"delete": _keyword{ "delete": {
token: DELETE, token: DELETE,
}, },
"switch": _keyword{ "switch": {
token: SWITCH, token: SWITCH,
}, },
"default": _keyword{ "default": {
token: DEFAULT, token: DEFAULT,
}, },
"finally": _keyword{ "finally": {
token: FINALLY, token: FINALLY,
}, },
"function": _keyword{ "function": {
token: FUNCTION, token: FUNCTION,
}, },
"continue": _keyword{ "continue": {
token: CONTINUE, token: CONTINUE,
}, },
"debugger": _keyword{ "debugger": {
token: DEBUGGER, token: DEBUGGER,
}, },
"instanceof": _keyword{ "instanceof": {
token: INSTANCEOF, token: INSTANCEOF,
}, },
"const": _keyword{ "const": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"class": _keyword{ "class": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"enum": _keyword{ "enum": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"export": _keyword{ "export": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"extends": _keyword{ "extends": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"import": _keyword{ "import": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"super": _keyword{ "super": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
}, },
"implements": _keyword{ "implements": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"interface": _keyword{ "interface": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"let": _keyword{ "let": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"package": _keyword{ "package": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"private": _keyword{ "private": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"protected": _keyword{ "protected": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"public": _keyword{ "public": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,
}, },
"static": _keyword{ "static": {
token: KEYWORD, token: KEYWORD,
futureKeyword: true, futureKeyword: true,
strict: true, strict: true,

View file

@ -1,222 +0,0 @@
#!/usr/bin/env perl
use strict;
use warnings;
my (%token, @order, @keywords);
{
my $keywords;
my @const;
push @const, <<_END_;
package token
const(
_ Token = iota
_END_
for (split m/\n/, <<_END_) {
ILLEGAL
EOF
COMMENT
KEYWORD
STRING
BOOLEAN
NULL
NUMBER
IDENTIFIER
PLUS +
MINUS -
MULTIPLY *
SLASH /
REMAINDER %
AND &
OR |
EXCLUSIVE_OR ^
SHIFT_LEFT <<
SHIFT_RIGHT >>
UNSIGNED_SHIFT_RIGHT >>>
AND_NOT &^
ADD_ASSIGN +=
SUBTRACT_ASSIGN -=
MULTIPLY_ASSIGN *=
QUOTIENT_ASSIGN /=
REMAINDER_ASSIGN %=
AND_ASSIGN &=
OR_ASSIGN |=
EXCLUSIVE_OR_ASSIGN ^=
SHIFT_LEFT_ASSIGN <<=
SHIFT_RIGHT_ASSIGN >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN >>>=
AND_NOT_ASSIGN &^=
LOGICAL_AND &&
LOGICAL_OR ||
INCREMENT ++
DECREMENT --
EQUAL ==
STRICT_EQUAL ===
LESS <
GREATER >
ASSIGN =
NOT !
BITWISE_NOT ~
NOT_EQUAL !=
STRICT_NOT_EQUAL !==
LESS_OR_EQUAL <=
GREATER_OR_EQUAL <=
LEFT_PARENTHESIS (
LEFT_BRACKET [
LEFT_BRACE {
COMMA ,
PERIOD .
RIGHT_PARENTHESIS )
RIGHT_BRACKET ]
RIGHT_BRACE }
SEMICOLON ;
COLON :
QUESTION_MARK ?
firstKeyword
IF
IN
DO
VAR
FOR
NEW
TRY
THIS
ELSE
CASE
VOID
WITH
WHILE
BREAK
CATCH
THROW
RETURN
TYPEOF
DELETE
SWITCH
DEFAULT
FINALLY
FUNCTION
CONTINUE
DEBUGGER
INSTANCEOF
lastKeyword
_END_
chomp;
next if m/^\s*#/;
my ($name, $symbol) = m/(\w+)\s*(\S+)?/;
if (defined $symbol) {
push @order, $name;
push @const, "$name // $symbol";
$token{$name} = $symbol;
} elsif (defined $name) {
$keywords ||= $name eq 'firstKeyword';
push @const, $name;
#$const[-1] .= " Token = iota" if 2 == @const;
if ($name =~ m/^([A-Z]+)/) {
push @keywords, $name if $keywords;
push @order, $name;
if ($token{SEMICOLON}) {
$token{$name} = lc $1;
} else {
$token{$name} = $name;
}
}
} else {
push @const, "";
}
}
push @const, ")";
print join "\n", @const, "";
}
{
print <<_END_;
var token2string = [...]string{
_END_
for my $name (@order) {
print "$name: \"$token{$name}\",\n";
}
print <<_END_;
}
_END_
print <<_END_;
var keywordTable = map[string]_keyword{
_END_
for my $name (@keywords) {
print <<_END_
"@{[ lc $name ]}": _keyword{
token: $name,
},
_END_
}
for my $name (qw/
const
class
enum
export
extends
import
super
/) {
print <<_END_
"$name": _keyword{
token: KEYWORD,
futureKeyword: true,
},
_END_
}
for my $name (qw/
implements
interface
let
package
private
protected
public
static
/) {
print <<_END_
"$name": _keyword{
token: KEYWORD,
futureKeyword: true,
strict: true,
},
_END_
}
print <<_END_;
}
_END_
}

View file

@ -4,103 +4,103 @@ import (
"strconv" "strconv"
) )
func (runtime *_runtime) newArgumentsObject(indexOfParameterName []string, stash _stash, length int) *_object { func (rt *runtime) newArgumentsObject(indexOfParameterName []string, stash stasher, length int) *object {
self := runtime.newClassObject("Arguments") obj := rt.newClassObject("Arguments")
for index, _ := range indexOfParameterName { for index := range indexOfParameterName {
name := strconv.FormatInt(int64(index), 10) name := strconv.FormatInt(int64(index), 10)
objectDefineOwnProperty(self, name, _property{Value{}, 0111}, false) objectDefineOwnProperty(obj, name, property{Value{}, 0o111}, false)
} }
self.objectClass = _classArguments obj.objectClass = classArguments
self.value = _argumentsObject{ obj.value = argumentsObject{
indexOfParameterName: indexOfParameterName, indexOfParameterName: indexOfParameterName,
stash: stash, stash: stash,
} }
self.prototype = runtime.global.ObjectPrototype obj.prototype = rt.global.ObjectPrototype
self.defineProperty(propertyLength, toValue_int(length), 0101, false) obj.defineProperty(propertyLength, intValue(length), 0o101, false)
return self return obj
} }
type _argumentsObject struct { type argumentsObject struct {
indexOfParameterName []string indexOfParameterName []string
// function(abc, def, ghi) // function(abc, def, ghi)
// indexOfParameterName[0] = "abc" // indexOfParameterName[0] = "abc"
// indexOfParameterName[1] = "def" // indexOfParameterName[1] = "def"
// indexOfParameterName[2] = "ghi" // indexOfParameterName[2] = "ghi"
// ... // ...
stash _stash stash stasher
} }
func (in _argumentsObject) clone(clone *_clone) _argumentsObject { func (o argumentsObject) clone(c *cloner) argumentsObject {
indexOfParameterName := make([]string, len(in.indexOfParameterName)) indexOfParameterName := make([]string, len(o.indexOfParameterName))
copy(indexOfParameterName, in.indexOfParameterName) copy(indexOfParameterName, o.indexOfParameterName)
return _argumentsObject{ return argumentsObject{
indexOfParameterName, indexOfParameterName,
clone.stash(in.stash), c.stash(o.stash),
} }
} }
func (self _argumentsObject) get(name string) (Value, bool) { func (o argumentsObject) get(name string) (Value, bool) {
index := stringToArrayIndex(name) index := stringToArrayIndex(name)
if index >= 0 && index < int64(len(self.indexOfParameterName)) { if index >= 0 && index < int64(len(o.indexOfParameterName)) {
name := self.indexOfParameterName[index] name := o.indexOfParameterName[index]
if name == "" { if name == "" {
return Value{}, false return Value{}, false
} }
return self.stash.getBinding(name, false), true return o.stash.getBinding(name, false), true
} }
return Value{}, false return Value{}, false
} }
func (self _argumentsObject) put(name string, value Value) { func (o argumentsObject) put(name string, value Value) {
index := stringToArrayIndex(name) index := stringToArrayIndex(name)
name = self.indexOfParameterName[index] name = o.indexOfParameterName[index]
self.stash.setBinding(name, value, false) o.stash.setBinding(name, value, false)
} }
func (self _argumentsObject) delete(name string) { func (o argumentsObject) delete(name string) {
index := stringToArrayIndex(name) index := stringToArrayIndex(name)
self.indexOfParameterName[index] = "" o.indexOfParameterName[index] = ""
} }
func argumentsGet(self *_object, name string) Value { func argumentsGet(obj *object, name string) Value {
if value, exists := self.value.(_argumentsObject).get(name); exists { if value, exists := obj.value.(argumentsObject).get(name); exists {
return value return value
} }
return objectGet(self, name) return objectGet(obj, name)
} }
func argumentsGetOwnProperty(self *_object, name string) *_property { func argumentsGetOwnProperty(obj *object, name string) *property {
property := objectGetOwnProperty(self, name) prop := objectGetOwnProperty(obj, name)
if value, exists := self.value.(_argumentsObject).get(name); exists { if value, exists := obj.value.(argumentsObject).get(name); exists {
property.value = value prop.value = value
} }
return property return prop
} }
func argumentsDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool { func argumentsDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
if _, exists := self.value.(_argumentsObject).get(name); exists { if _, exists := obj.value.(argumentsObject).get(name); exists {
if !objectDefineOwnProperty(self, name, descriptor, false) { if !objectDefineOwnProperty(obj, name, descriptor, false) {
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
if value, valid := descriptor.value.(Value); valid { if value, valid := descriptor.value.(Value); valid {
self.value.(_argumentsObject).put(name, value) obj.value.(argumentsObject).put(name, value)
} }
return true return true
} }
return objectDefineOwnProperty(self, name, descriptor, throw) return objectDefineOwnProperty(obj, name, descriptor, throw)
} }
func argumentsDelete(self *_object, name string, throw bool) bool { func argumentsDelete(obj *object, name string, throw bool) bool {
if !objectDelete(self, name, throw) { if !objectDelete(obj, name, throw) {
return false return false
} }
if _, exists := self.value.(_argumentsObject).get(name); exists { if _, exists := obj.value.(argumentsObject).get(name); exists {
self.value.(_argumentsObject).delete(name) obj.value.(argumentsObject).delete(name)
} }
return true return true
} }

View file

@ -4,43 +4,43 @@ import (
"strconv" "strconv"
) )
func (runtime *_runtime) newArrayObject(length uint32) *_object { func (rt *runtime) newArrayObject(length uint32) *object {
self := runtime.newObject() obj := rt.newObject()
self.class = classArray obj.class = classArrayName
self.defineProperty(propertyLength, toValue_uint32(length), 0100, false) obj.defineProperty(propertyLength, uint32Value(length), 0o100, false)
self.objectClass = _classArray obj.objectClass = classArray
return self return obj
} }
func isArray(object *_object) bool { func isArray(obj *object) bool {
if object == nil { if obj == nil {
return false return false
} }
switch object.class { switch obj.class {
case classArray, classGoArray, classGoSlice: case classArrayName, classGoArrayName, classGoSliceName:
return true return true
default: default:
return false return false
} }
} }
func objectLength(object *_object) uint32 { func objectLength(obj *object) uint32 {
if object == nil { if obj == nil {
return 0 return 0
} }
switch object.class { switch obj.class {
case classArray: case classArrayName:
return object.get(propertyLength).value.(uint32) return obj.get(propertyLength).value.(uint32)
case classString: case classStringName:
return uint32(object.get(propertyLength).value.(int)) return uint32(obj.get(propertyLength).value.(int))
case classGoArray, classGoSlice: case classGoArrayName, classGoSliceName:
return uint32(object.get(propertyLength).value.(int)) return uint32(obj.get(propertyLength).value.(int))
} }
return 0 return 0
} }
func arrayUint32(rt *_runtime, value Value) uint32 { func arrayUint32(rt *runtime, value Value) uint32 {
nm := value.number() nm := value.number()
if nm.kind != numberInteger || !isUint32(nm.int64) { if nm.kind != numberInteger || !isUint32(nm.int64) {
// FIXME // FIXME
@ -49,70 +49,72 @@ func arrayUint32(rt *_runtime, value Value) uint32 {
return uint32(nm.int64) return uint32(nm.int64)
} }
func arrayDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool { func arrayDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
lengthProperty := self.getOwnProperty(propertyLength) lengthProperty := obj.getOwnProperty(propertyLength)
lengthValue, valid := lengthProperty.value.(Value) lengthValue, valid := lengthProperty.value.(Value)
if !valid { if !valid {
panic("Array.length != Value{}") panic("Array.length != Value{}")
} }
reject := func(reason string) bool {
if throw {
panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %s", reason))
}
return false
}
length := lengthValue.value.(uint32) length := lengthValue.value.(uint32)
if name == propertyLength { if name == propertyLength {
if descriptor.value == nil { if descriptor.value == nil {
return objectDefineOwnProperty(self, name, descriptor, throw) return objectDefineOwnProperty(obj, name, descriptor, throw)
} }
newLengthValue, isValue := descriptor.value.(Value) newLengthValue, isValue := descriptor.value.(Value)
if !isValue { if !isValue {
panic(self.runtime.panicTypeError()) panic(obj.runtime.panicTypeError("Array.DefineOwnProperty %q is not a value", descriptor.value))
} }
newLength := arrayUint32(self.runtime, newLengthValue) newLength := arrayUint32(obj.runtime, newLengthValue)
descriptor.value = toValue_uint32(newLength) descriptor.value = uint32Value(newLength)
if newLength > length { if newLength > length {
return objectDefineOwnProperty(self, name, descriptor, throw) return objectDefineOwnProperty(obj, name, descriptor, throw)
} }
if !lengthProperty.writable() { if !lengthProperty.writable() {
goto Reject return reject("property length for not writable")
} }
newWritable := true newWritable := true
if descriptor.mode&0700 == 0 { if descriptor.mode&0o700 == 0 {
// If writable is off // If writable is off
newWritable = false newWritable = false
descriptor.mode |= 0100 descriptor.mode |= 0o100
} }
if !objectDefineOwnProperty(self, name, descriptor, throw) { if !objectDefineOwnProperty(obj, name, descriptor, throw) {
return false return false
} }
for newLength < length { for newLength < length {
length-- length--
if !self.delete(strconv.FormatInt(int64(length), 10), false) { if !obj.delete(strconv.FormatInt(int64(length), 10), false) {
descriptor.value = toValue_uint32(length + 1) descriptor.value = uint32Value(length + 1)
if !newWritable { if !newWritable {
descriptor.mode &= 0077 descriptor.mode &= 0o077
} }
objectDefineOwnProperty(self, name, descriptor, false) objectDefineOwnProperty(obj, name, descriptor, false)
goto Reject return reject("delete failed")
} }
} }
if !newWritable { if !newWritable {
descriptor.mode &= 0077 descriptor.mode &= 0o077
objectDefineOwnProperty(self, name, descriptor, false) objectDefineOwnProperty(obj, name, descriptor, false)
} }
} else if index := stringToArrayIndex(name); index >= 0 { } else if index := stringToArrayIndex(name); index >= 0 {
if index >= int64(length) && !lengthProperty.writable() { if index >= int64(length) && !lengthProperty.writable() {
goto Reject return reject("property length not writable")
} }
if !objectDefineOwnProperty(self, strconv.FormatInt(index, 10), descriptor, false) { if !objectDefineOwnProperty(obj, strconv.FormatInt(index, 10), descriptor, false) {
goto Reject return reject("Object.DefineOwnProperty failed")
} }
if index >= int64(length) { if index >= int64(length) {
lengthProperty.value = toValue_uint32(uint32(index + 1)) lengthProperty.value = uint32Value(uint32(index + 1))
objectDefineOwnProperty(self, propertyLength, *lengthProperty, false) objectDefineOwnProperty(obj, propertyLength, *lengthProperty, false)
return true return true
} }
} }
return objectDefineOwnProperty(self, name, descriptor, throw) return objectDefineOwnProperty(obj, name, descriptor, throw)
Reject:
if throw {
panic(self.runtime.panicTypeError())
}
return false
} }

View file

@ -1,5 +1,5 @@
package otto package otto
func (runtime *_runtime) newBooleanObject(value Value) *_object { func (rt *runtime) newBooleanObject(value Value) *object {
return runtime.newPrimitiveObject(classBoolean, toValue_bool(value.bool())) return rt.newPrimitiveObject(classBooleanName, boolValue(value.bool()))
} }

View file

@ -7,23 +7,21 @@ import (
Time "time" Time "time"
) )
type _dateObject struct { type dateObject struct {
time Time.Time // Time from the "time" package, a cached version of time time Time.Time // Time from the "time" package, a cached version of time
epoch int64 epoch int64
value Value value Value
isNaN bool isNaN bool
} }
var ( var invalidDateObject = dateObject{
invalidDateObject = _dateObject{
time: Time.Time{}, time: Time.Time{},
epoch: -1, epoch: -1,
value: NaNValue(), value: NaNValue(),
isNaN: true, isNaN: true,
} }
)
type _ecmaTime struct { type ecmaTime struct {
year int year int
month int month int
day int day int
@ -34,8 +32,8 @@ type _ecmaTime struct {
location *Time.Location // Basically, either local or UTC location *Time.Location // Basically, either local or UTC
} }
func ecmaTime(goTime Time.Time) _ecmaTime { func newEcmaTime(goTime Time.Time) ecmaTime {
return _ecmaTime{ return ecmaTime{
goTime.Year(), goTime.Year(),
dateFromGoMonth(goTime.Month()), dateFromGoMonth(goTime.Month()),
goTime.Day(), goTime.Day(),
@ -47,58 +45,58 @@ func ecmaTime(goTime Time.Time) _ecmaTime {
} }
} }
func (self *_ecmaTime) goTime() Time.Time { func (t *ecmaTime) goTime() Time.Time {
return Time.Date( return Time.Date(
self.year, t.year,
dateToGoMonth(self.month), dateToGoMonth(t.month),
self.day, t.day,
self.hour, t.hour,
self.minute, t.minute,
self.second, t.second,
self.millisecond*(100*100*100), t.millisecond*(100*100*100),
self.location, t.location,
) )
} }
func (self *_dateObject) Time() Time.Time { func (d *dateObject) Time() Time.Time {
return self.time return d.time
} }
func (self *_dateObject) Epoch() int64 { func (d *dateObject) Epoch() int64 {
return self.epoch return d.epoch
} }
func (self *_dateObject) Value() Value { func (d *dateObject) Value() Value {
return self.value return d.value
} }
// FIXME A date should only be in the range of -100,000,000 to +100,000,000 (1970): 15.9.1.1 // FIXME A date should only be in the range of -100,000,000 to +100,000,000 (1970): 15.9.1.1.
func (self *_dateObject) SetNaN() { func (d *dateObject) SetNaN() {
self.time = Time.Time{} d.time = Time.Time{}
self.epoch = -1 d.epoch = -1
self.value = NaNValue() d.value = NaNValue()
self.isNaN = true d.isNaN = true
} }
func (self *_dateObject) SetTime(time Time.Time) { func (d *dateObject) SetTime(time Time.Time) {
self.Set(timeToEpoch(time)) d.Set(timeToEpoch(time))
} }
func (self *_dateObject) Set(epoch float64) { func (d *dateObject) Set(epoch float64) {
// epoch // epoch
self.epoch = epochToInteger(epoch) d.epoch = epochToInteger(epoch)
// time // time
time, err := epochToTime(epoch) time, err := epochToTime(epoch)
self.time = time // Is either a valid time, or the zero-value for time.Time d.time = time // Is either a valid time, or the zero-value for time.Time
// value & isNaN // value & isNaN
if err != nil { if err != nil {
self.isNaN = true d.isNaN = true
self.epoch = -1 d.epoch = -1
self.value = NaNValue() d.value = NaNValue()
} else { } else {
self.value = toValue_int64(self.epoch) d.value = int64Value(d.epoch)
} }
} }
@ -109,48 +107,49 @@ func epochToInteger(value float64) int64 {
return int64(math.Ceil(value)) return int64(math.Ceil(value))
} }
func epochToTime(value float64) (time Time.Time, err error) { func epochToTime(value float64) (Time.Time, error) {
epochWithMilli := value epochWithMilli := value
if math.IsNaN(epochWithMilli) || math.IsInf(epochWithMilli, 0) { if math.IsNaN(epochWithMilli) || math.IsInf(epochWithMilli, 0) {
err = fmt.Errorf("Invalid time %v", value) return Time.Time{}, fmt.Errorf("invalid time %v", value)
return
} }
epoch := int64(epochWithMilli / 1000) epoch := int64(epochWithMilli / 1000)
milli := int64(epochWithMilli) % 1000 milli := int64(epochWithMilli) % 1000
time = Time.Unix(int64(epoch), milli*1000000).In(utcTimeZone) return Time.Unix(epoch, milli*1000000).In(utcTimeZone), nil
return
} }
func timeToEpoch(time Time.Time) float64 { func timeToEpoch(time Time.Time) float64 {
return float64(time.UnixMilli()) return float64(time.UnixMilli())
} }
func (runtime *_runtime) newDateObject(epoch float64) *_object { func (rt *runtime) newDateObject(epoch float64) *object {
self := runtime.newObject() obj := rt.newObject()
self.class = classDate obj.class = classDateName
// FIXME This is ugly... // FIXME This is ugly...
date := _dateObject{} date := dateObject{}
date.Set(epoch) date.Set(epoch)
self.value = date obj.value = date
return self return obj
} }
func (self *_object) dateValue() _dateObject { func (o *object) dateValue() dateObject {
value, _ := self.value.(_dateObject) value, _ := o.value.(dateObject)
return value return value
} }
func dateObjectOf(rt *_runtime, _dateObject *_object) _dateObject { func dateObjectOf(rt *runtime, date *object) dateObject {
if _dateObject == nil || _dateObject.class != classDate { if date == nil {
panic(rt.panicTypeError()) panic(rt.panicTypeError("Date.ObjectOf is nil"))
} }
return _dateObject.dateValue() if date.class != classDateName {
panic(rt.panicTypeError("Date.ObjectOf %q != %q", date.class, classDateName))
}
return date.dateValue()
} }
// JavaScript is 0-based, Go is 1-based (15.9.1.4) // JavaScript is 0-based, Go is 1-based (15.9.1.4).
func dateToGoMonth(month int) Time.Month { func dateToGoMonth(month int) Time.Month {
return Time.Month(month + 1) return Time.Month(month + 1)
} }
@ -163,7 +162,8 @@ func dateFromGoDay(day Time.Weekday) int {
return int(day) return int(day)
} }
func newDateTime(argumentList []Value, location *Time.Location) (epoch float64) { // newDateTime returns the epoch of date contained in argumentList for location.
func newDateTime(argumentList []Value, location *Time.Location) float64 {
pick := func(index int, default_ float64) (float64, bool) { pick := func(index int, default_ float64) (float64, bool) {
if index >= len(argumentList) { if index >= len(argumentList) {
return default_, false return default_, false
@ -175,29 +175,41 @@ func newDateTime(argumentList []Value, location *Time.Location) (epoch float64)
return value, false return value, false
} }
if len(argumentList) >= 2 { // 2-argument, 3-argument, ... switch len(argumentList) {
case 0: // 0-argument
time := Time.Now().In(utcTimeZone)
return timeToEpoch(time)
case 1: // 1-argument
value := valueOfArrayIndex(argumentList, 0)
value = toPrimitiveValue(value)
if value.IsString() {
return dateParse(value.string())
}
return value.float64()
default: // 2-argument, 3-argument, ...
var year, month, day, hour, minute, second, millisecond float64 var year, month, day, hour, minute, second, millisecond float64
var invalid bool var invalid bool
if year, invalid = pick(0, 1900.0); invalid { if year, invalid = pick(0, 1900.0); invalid {
goto INVALID return math.NaN()
} }
if month, invalid = pick(1, 0.0); invalid { if month, invalid = pick(1, 0.0); invalid {
goto INVALID return math.NaN()
} }
if day, invalid = pick(2, 1.0); invalid { if day, invalid = pick(2, 1.0); invalid {
goto INVALID return math.NaN()
} }
if hour, invalid = pick(3, 0.0); invalid { if hour, invalid = pick(3, 0.0); invalid {
goto INVALID return math.NaN()
} }
if minute, invalid = pick(4, 0.0); invalid { if minute, invalid = pick(4, 0.0); invalid {
goto INVALID return math.NaN()
} }
if second, invalid = pick(5, 0.0); invalid { if second, invalid = pick(5, 0.0); invalid {
goto INVALID return math.NaN()
} }
if millisecond, invalid = pick(6, 0.0); invalid { if millisecond, invalid = pick(6, 0.0); invalid {
goto INVALID return math.NaN()
} }
if year >= 0 && year <= 99 { if year >= 0 && year <= 99 {
@ -206,22 +218,7 @@ func newDateTime(argumentList []Value, location *Time.Location) (epoch float64)
time := Time.Date(int(year), dateToGoMonth(int(month)), int(day), int(hour), int(minute), int(second), int(millisecond)*1000*1000, location) time := Time.Date(int(year), dateToGoMonth(int(month)), int(day), int(hour), int(minute), int(second), int(millisecond)*1000*1000, location)
return timeToEpoch(time) return timeToEpoch(time)
} else if len(argumentList) == 0 { // 0-argument
time := Time.Now().In(utcTimeZone)
return timeToEpoch(time)
} else { // 1-argument
value := valueOfArrayIndex(argumentList, 0)
value = toPrimitive(value)
if value.IsString() {
return dateParse(value.string())
} }
return value.float64()
}
INVALID:
epoch = math.NaN()
return
} }
var ( var (
@ -238,6 +235,10 @@ var (
"2006-01T15:04:05", "2006-01T15:04:05",
"2006-01-02T15:04:05", "2006-01-02T15:04:05",
"2006/01",
"2006/01/02",
"2006/01/02 15:04:05",
"2006T15:04:05.000", "2006T15:04:05.000",
"2006-01T15:04:05.000", "2006-01T15:04:05.000",
"2006-01-02T15:04:05.000", "2006-01-02T15:04:05.000",
@ -259,7 +260,8 @@ var (
matchDateTimeZone = regexp.MustCompile(`^(.*)(?:(Z)|([\+\-]\d{2}):(\d{2}))$`) matchDateTimeZone = regexp.MustCompile(`^(.*)(?:(Z)|([\+\-]\d{2}):(\d{2}))$`)
) )
func dateParse(date string) (epoch float64) { // dateParse returns the epoch of the parsed date.
func dateParse(date string) float64 {
// YYYY-MM-DDTHH:mm:ss.sssZ // YYYY-MM-DDTHH:mm:ss.sssZ
var time Time.Time var time Time.Time
var err error var err error

View file

@ -1,58 +1,58 @@
package otto package otto
func (rt *_runtime) newErrorObject(name string, message Value, stackFramesToPop int) *_object { func (rt *runtime) newErrorObject(name string, message Value, stackFramesToPop int) *object {
self := rt.newClassObject(classError) obj := rt.newClassObject(classErrorName)
if message.IsDefined() { if message.IsDefined() {
msg := message.string() err := newError(rt, name, stackFramesToPop, "%s", message.string())
self.defineProperty("message", toValue_string(msg), 0111, false) obj.defineProperty("message", err.messageValue(), 0o111, false)
self.value = newError(rt, name, stackFramesToPop, msg) obj.value = err
} else { } else {
self.value = newError(rt, name, stackFramesToPop) obj.value = newError(rt, name, stackFramesToPop)
} }
self.defineOwnProperty("stack", _property{ obj.defineOwnProperty("stack", property{
value: _propertyGetSet{ value: propertyGetSet{
rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value { rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value {
return toValue_string(self.value.(_error).formatWithStack()) return stringValue(obj.value.(ottoError).formatWithStack())
}), }),
&_nilGetSetObject, &nilGetSetObject,
}, },
mode: modeConfigureMask & modeOnMask, mode: modeConfigureMask & modeOnMask,
}, false) }, false)
return self return obj
} }
func (rt *_runtime) newErrorObjectError(err _error) *_object { func (rt *runtime) newErrorObjectError(err ottoError) *object {
self := rt.newClassObject(classError) obj := rt.newClassObject(classErrorName)
self.defineProperty("message", err.messageValue(), 0111, false) obj.defineProperty("message", err.messageValue(), 0o111, false)
self.value = err obj.value = err
switch err.name { switch err.name {
case "EvalError": case "EvalError":
self.prototype = rt.global.EvalErrorPrototype obj.prototype = rt.global.EvalErrorPrototype
case "TypeError": case "TypeError":
self.prototype = rt.global.TypeErrorPrototype obj.prototype = rt.global.TypeErrorPrototype
case "RangeError": case "RangeError":
self.prototype = rt.global.RangeErrorPrototype obj.prototype = rt.global.RangeErrorPrototype
case "ReferenceError": case "ReferenceError":
self.prototype = rt.global.ReferenceErrorPrototype obj.prototype = rt.global.ReferenceErrorPrototype
case "SyntaxError": case "SyntaxError":
self.prototype = rt.global.SyntaxErrorPrototype obj.prototype = rt.global.SyntaxErrorPrototype
case "URIError": case "URIError":
self.prototype = rt.global.URIErrorPrototype obj.prototype = rt.global.URIErrorPrototype
default: default:
self.prototype = rt.global.ErrorPrototype obj.prototype = rt.global.ErrorPrototype
} }
self.defineOwnProperty("stack", _property{ obj.defineOwnProperty("stack", property{
value: _propertyGetSet{ value: propertyGetSet{
rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value { rt.newNativeFunction("get", "internal", 0, func(FunctionCall) Value {
return toValue_string(self.value.(_error).formatWithStack()) return stringValue(obj.value.(ottoError).formatWithStack())
}), }),
&_nilGetSetObject, &nilGetSetObject,
}, },
mode: modeConfigureMask & modeOnMask, mode: modeConfigureMask & modeOnMask,
}, false) }, false)
return self return obj
} }

View file

@ -1,20 +1,20 @@
package otto package otto
// _constructFunction // constructFunction.
type _constructFunction func(*_object, []Value) Value type constructFunction func(*object, []Value) Value
// 13.2.2 [[Construct]] // 13.2.2 [[Construct]].
func defaultConstruct(fn *_object, argumentList []Value) Value { func defaultConstruct(fn *object, argumentList []Value) Value {
object := fn.runtime.newObject() obj := fn.runtime.newObject()
object.class = classObject obj.class = classObjectName
prototype := fn.get("prototype") prototype := fn.get("prototype")
if prototype.kind != valueObject { if prototype.kind != valueObject {
prototype = toValue_object(fn.runtime.global.ObjectPrototype) prototype = objectValue(fn.runtime.global.ObjectPrototype)
} }
object.prototype = prototype._object() obj.prototype = prototype.object()
this := toValue_object(object) this := objectValue(obj)
value := fn.call(this, argumentList, false, nativeFrame) value := fn.call(this, argumentList, false, nativeFrame)
if value.kind == valueObject { if value.kind == valueObject {
return value return value
@ -22,72 +22,66 @@ func defaultConstruct(fn *_object, argumentList []Value) Value {
return this return this
} }
// _nativeFunction // nativeFunction.
type _nativeFunction func(FunctionCall) Value type nativeFunction func(FunctionCall) Value
// ===================== // // nativeFunctionObject.
// _nativeFunctionObject // type nativeFunctionObject struct {
// ===================== //
type _nativeFunctionObject struct {
name string name string
file string file string
line int line int
call _nativeFunction // [[Call]] call nativeFunction // [[Call]]
construct _constructFunction // [[Construct]] construct constructFunction // [[Construct]]
} }
func (runtime *_runtime) _newNativeFunctionObject(name, file string, line int, native _nativeFunction, length int) *_object { func (rt *runtime) newNativeFunctionProperty(name, file string, line int, native nativeFunction, length int) *object {
self := runtime.newClassObject(classFunction) o := rt.newClassObject(classFunctionName)
self.value = _nativeFunctionObject{ o.value = nativeFunctionObject{
name: name, name: name,
file: file, file: file,
line: line, line: line,
call: native, call: native,
construct: defaultConstruct, construct: defaultConstruct,
} }
self.defineProperty("name", toValue_string(name), 0000, false) o.defineProperty("name", stringValue(name), 0o000, false)
self.defineProperty(propertyLength, toValue_int(length), 0000, false) o.defineProperty(propertyLength, intValue(length), 0o000, false)
return self return o
} }
func (runtime *_runtime) newNativeFunctionObject(name, file string, line int, native _nativeFunction, length int) *_object { func (rt *runtime) newNativeFunctionObject(name, file string, line int, native nativeFunction, length int) *object {
self := runtime._newNativeFunctionObject(name, file, line, native, length) o := rt.newNativeFunctionProperty(name, file, line, native, length)
self.defineOwnProperty("caller", _property{ o.defineOwnProperty("caller", property{
value: _propertyGetSet{ value: propertyGetSet{
runtime._newNativeFunctionObject("get", "internal", 0, func(fc FunctionCall) Value { rt.newNativeFunctionProperty("get", "internal", 0, func(fc FunctionCall) Value {
for sc := runtime.scope; sc != nil; sc = sc.outer { for sc := rt.scope; sc != nil; sc = sc.outer {
if sc.frame.fn == self { if sc.frame.fn == o {
if sc.outer == nil || sc.outer.frame.fn == nil { if sc.outer == nil || sc.outer.frame.fn == nil {
return nullValue return nullValue
} }
return runtime.toValue(sc.outer.frame.fn) return rt.toValue(sc.outer.frame.fn)
} }
} }
return nullValue return nullValue
}, 0), }, 0),
&_nilGetSetObject, &nilGetSetObject,
}, },
mode: 0000, mode: 0o000,
}, false) }, false)
return self return o
} }
// =================== // // bindFunctionObject.
// _bindFunctionObject // type bindFunctionObject struct {
// =================== // target *object
type _bindFunctionObject struct {
target *_object
this Value this Value
argumentList []Value argumentList []Value
} }
func (runtime *_runtime) newBoundFunctionObject(target *_object, this Value, argumentList []Value) *_object { func (rt *runtime) newBoundFunctionObject(target *object, this Value, argumentList []Value) *object {
self := runtime.newClassObject(classFunction) o := rt.newClassObject(classFunctionName)
self.value = _bindFunctionObject{ o.value = bindFunctionObject{
target: target, target: target,
this: this, this: this,
argumentList: argumentList, argumentList: argumentList,
@ -97,100 +91,96 @@ func (runtime *_runtime) newBoundFunctionObject(target *_object, this Value, arg
if length < 0 { if length < 0 {
length = 0 length = 0
} }
self.defineProperty("name", toValue_string("bound "+target.get("name").String()), 0000, false) o.defineProperty("name", stringValue("bound "+target.get("name").String()), 0o000, false)
self.defineProperty(propertyLength, toValue_int(length), 0000, false) o.defineProperty(propertyLength, intValue(length), 0o000, false)
self.defineProperty("caller", Value{}, 0000, false) // TODO Should throw a TypeError o.defineProperty("caller", Value{}, 0o000, false) // TODO Should throw a TypeError
self.defineProperty("arguments", Value{}, 0000, false) // TODO Should throw a TypeError o.defineProperty("arguments", Value{}, 0o000, false) // TODO Should throw a TypeError
return self return o
} }
// [[Construct]] // [[Construct]].
func (fn _bindFunctionObject) construct(argumentList []Value) Value { func (fn bindFunctionObject) construct(argumentList []Value) Value {
object := fn.target obj := fn.target
switch value := object.value.(type) { switch value := obj.value.(type) {
case _nativeFunctionObject: case nativeFunctionObject:
return value.construct(object, fn.argumentList) return value.construct(obj, fn.argumentList)
case _nodeFunctionObject: case nodeFunctionObject:
argumentList = append(fn.argumentList, argumentList...) argumentList = append(fn.argumentList, argumentList...)
return object.construct(argumentList) return obj.construct(argumentList)
default:
panic(fn.target.runtime.panicTypeError("construct unknown type %T", obj.value))
} }
panic(fn.target.runtime.panicTypeError())
} }
// =================== // // nodeFunctionObject.
// _nodeFunctionObject // type nodeFunctionObject struct {
// =================== // node *nodeFunctionLiteral
stash stasher
type _nodeFunctionObject struct {
node *_nodeFunctionLiteral
stash _stash
} }
func (runtime *_runtime) newNodeFunctionObject(node *_nodeFunctionLiteral, stash _stash) *_object { func (rt *runtime) newNodeFunctionObject(node *nodeFunctionLiteral, stash stasher) *object {
self := runtime.newClassObject(classFunction) o := rt.newClassObject(classFunctionName)
self.value = _nodeFunctionObject{ o.value = nodeFunctionObject{
node: node, node: node,
stash: stash, stash: stash,
} }
self.defineProperty("name", toValue_string(node.name), 0000, false) o.defineProperty("name", stringValue(node.name), 0o000, false)
self.defineProperty(propertyLength, toValue_int(len(node.parameterList)), 0000, false) o.defineProperty(propertyLength, intValue(len(node.parameterList)), 0o000, false)
self.defineOwnProperty("caller", _property{ o.defineOwnProperty("caller", property{
value: _propertyGetSet{ value: propertyGetSet{
runtime.newNativeFunction("get", "internal", 0, func(fc FunctionCall) Value { rt.newNativeFunction("get", "internal", 0, func(fc FunctionCall) Value {
for sc := runtime.scope; sc != nil; sc = sc.outer { for sc := rt.scope; sc != nil; sc = sc.outer {
if sc.frame.fn == self { if sc.frame.fn == o {
if sc.outer == nil || sc.outer.frame.fn == nil { if sc.outer == nil || sc.outer.frame.fn == nil {
return nullValue return nullValue
} }
return runtime.toValue(sc.outer.frame.fn) return rt.toValue(sc.outer.frame.fn)
} }
} }
return nullValue return nullValue
}), }),
&_nilGetSetObject, &nilGetSetObject,
}, },
mode: 0000, mode: 0o000,
}, false) }, false)
return self return o
} }
// ======= // // _object.
// _object // func (o *object) isCall() bool {
// ======= // switch fn := o.value.(type) {
case nativeFunctionObject:
func (self *_object) isCall() bool {
switch fn := self.value.(type) {
case _nativeFunctionObject:
return fn.call != nil return fn.call != nil
case _bindFunctionObject: case bindFunctionObject:
return true return true
case _nodeFunctionObject: case nodeFunctionObject:
return true return true
} default:
return false return false
} }
}
func (self *_object) call(this Value, argumentList []Value, eval bool, frame _frame) Value { func (o *object) call(this Value, argumentList []Value, eval bool, frm frame) Value { //nolint: unparam // Isn't currently used except in recursive self.
switch fn := self.value.(type) { switch fn := o.value.(type) {
case _nativeFunctionObject: case nativeFunctionObject:
// Since eval is a native function, we only have to check for it here // Since eval is a native function, we only have to check for it here
if eval { if eval {
eval = self == self.runtime.eval // If eval is true, then it IS a direct eval eval = o == o.runtime.eval // If eval is true, then it IS a direct eval
} }
// Enter a scope, name from the native object... // Enter a scope, name from the native object...
rt := self.runtime rt := o.runtime
if rt.scope != nil && !eval { if rt.scope != nil && !eval {
rt.enterFunctionScope(rt.scope.lexical, this) rt.enterFunctionScope(rt.scope.lexical, this)
rt.scope.frame = _frame{ rt.scope.frame = frame{
native: true, native: true,
nativeFile: fn.file, nativeFile: fn.file,
nativeLine: fn.line, nativeLine: fn.line,
callee: fn.name, callee: fn.name,
file: nil, file: nil,
fn: self, fn: o,
} }
defer func() { defer func() {
rt.leaveScope() rt.leaveScope()
@ -198,77 +188,77 @@ func (self *_object) call(this Value, argumentList []Value, eval bool, frame _fr
} }
return fn.call(FunctionCall{ return fn.call(FunctionCall{
runtime: self.runtime, runtime: o.runtime,
eval: eval, eval: eval,
This: this, This: this,
ArgumentList: argumentList, ArgumentList: argumentList,
Otto: self.runtime.otto, Otto: o.runtime.otto,
}) })
case _bindFunctionObject: case bindFunctionObject:
// TODO Passthrough site, do not enter a scope // TODO Passthrough site, do not enter a scope
argumentList = append(fn.argumentList, argumentList...) argumentList = append(fn.argumentList, argumentList...)
return fn.target.call(fn.this, argumentList, false, frame) return fn.target.call(fn.this, argumentList, false, frm)
case _nodeFunctionObject: case nodeFunctionObject:
rt := self.runtime rt := o.runtime
stash := rt.enterFunctionScope(fn.stash, this) stash := rt.enterFunctionScope(fn.stash, this)
rt.scope.frame = _frame{ rt.scope.frame = frame{
callee: fn.node.name, callee: fn.node.name,
file: fn.node.file, file: fn.node.file,
fn: self, fn: o,
} }
defer func() { defer func() {
rt.leaveScope() rt.leaveScope()
}() }()
callValue := rt.cmpl_call_nodeFunction(self, stash, fn.node, this, argumentList) callValue := rt.cmplCallNodeFunction(o, stash, fn.node, argumentList)
if value, valid := callValue.value.(_result); valid { if value, valid := callValue.value.(result); valid {
return value.value return value.value
} }
return callValue return callValue
} }
panic(self.runtime.panicTypeError("%v is not a function", toValue_object(self))) panic(o.runtime.panicTypeError("%v is not a function", objectValue(o)))
} }
func (self *_object) construct(argumentList []Value) Value { func (o *object) construct(argumentList []Value) Value {
switch fn := self.value.(type) { switch fn := o.value.(type) {
case _nativeFunctionObject: case nativeFunctionObject:
if fn.call == nil { if fn.call == nil {
panic(self.runtime.panicTypeError("%v is not a function", toValue_object(self))) panic(o.runtime.panicTypeError("%v is not a function", objectValue(o)))
} }
if fn.construct == nil { if fn.construct == nil {
panic(self.runtime.panicTypeError("%v is not a constructor", toValue_object(self))) panic(o.runtime.panicTypeError("%v is not a constructor", objectValue(o)))
} }
return fn.construct(self, argumentList) return fn.construct(o, argumentList)
case _bindFunctionObject: case bindFunctionObject:
return fn.construct(argumentList) return fn.construct(argumentList)
case _nodeFunctionObject: case nodeFunctionObject:
return defaultConstruct(self, argumentList) return defaultConstruct(o, argumentList)
} }
panic(self.runtime.panicTypeError("%v is not a function", toValue_object(self))) panic(o.runtime.panicTypeError("%v is not a function", objectValue(o)))
} }
// 15.3.5.3 // 15.3.5.3.
func (self *_object) hasInstance(of Value) bool { func (o *object) hasInstance(of Value) bool {
if !self.isCall() { if !o.isCall() {
// We should not have a hasInstance method // We should not have a hasInstance method
panic(self.runtime.panicTypeError()) panic(o.runtime.panicTypeError("Object.hasInstance not callable"))
} }
if !of.IsObject() { if !of.IsObject() {
return false return false
} }
prototype := self.get("prototype") prototype := o.get("prototype")
if !prototype.IsObject() { if !prototype.IsObject() {
panic(self.runtime.panicTypeError()) panic(o.runtime.panicTypeError("Object.hasInstance prototype %q is not an object", prototype))
} }
prototypeObject := prototype._object() prototypeObject := prototype.object()
value := of._object().prototype value := of.object().prototype
for value != nil { for value != nil {
if value == prototypeObject { if value == prototypeObject {
return true return true
@ -278,14 +268,10 @@ func (self *_object) hasInstance(of Value) bool {
return false return false
} }
// ============ //
// FunctionCall //
// ============ //
// FunctionCall is an encapsulation of a JavaScript function call. // FunctionCall is an encapsulation of a JavaScript function call.
type FunctionCall struct { type FunctionCall struct {
runtime *_runtime runtime *runtime
_thisObject *_object thisObj *object
eval bool // This call is a direct call to eval eval bool // This call is a direct call to eval
This Value This Value
@ -296,43 +282,42 @@ type FunctionCall struct {
// Argument will return the value of the argument at the given index. // Argument will return the value of the argument at the given index.
// //
// If no such argument exists, undefined is returned. // If no such argument exists, undefined is returned.
func (self FunctionCall) Argument(index int) Value { func (f FunctionCall) Argument(index int) Value {
return valueOfArrayIndex(self.ArgumentList, index) return valueOfArrayIndex(f.ArgumentList, index)
} }
func (self FunctionCall) getArgument(index int) (Value, bool) { func (f FunctionCall) getArgument(index int) (Value, bool) {
return getValueOfArrayIndex(self.ArgumentList, index) return getValueOfArrayIndex(f.ArgumentList, index)
} }
func (self FunctionCall) slice(index int) []Value { func (f FunctionCall) slice(index int) []Value {
if index < len(self.ArgumentList) { if index < len(f.ArgumentList) {
return self.ArgumentList[index:] return f.ArgumentList[index:]
} }
return []Value{} return []Value{}
} }
func (self *FunctionCall) thisObject() *_object { func (f *FunctionCall) thisObject() *object {
if self._thisObject == nil { if f.thisObj == nil {
this := self.This.resolve() // FIXME Is this right? this := f.This.resolve() // FIXME Is this right?
self._thisObject = self.runtime.toObject(this) f.thisObj = f.runtime.toObject(this)
} }
return self._thisObject return f.thisObj
} }
func (self *FunctionCall) thisClassObject(class string) *_object { func (f *FunctionCall) thisClassObject(class string) *object {
thisObject := self.thisObject() if o := f.thisObject(); o.class != class {
if thisObject.class != class { panic(f.runtime.panicTypeError("Function.Class %s != %s", o.class, class))
panic(self.runtime.panicTypeError())
} }
return self._thisObject return f.thisObj
} }
func (self FunctionCall) toObject(value Value) *_object { func (f FunctionCall) toObject(value Value) *object {
return self.runtime.toObject(value) return f.runtime.toObject(value)
} }
// CallerLocation will return file location information (file:line:pos) where this function is being called. // CallerLocation will return file location information (file:line:pos) where this function is being called.
func (self FunctionCall) CallerLocation() string { func (f FunctionCall) CallerLocation() string {
// see error.go for location() // see error.go for location()
return self.runtime.scope.outer.frame.location() return f.runtime.scope.outer.frame.location()
} }

View file

@ -5,51 +5,51 @@ import (
"strconv" "strconv"
) )
func (runtime *_runtime) newGoArrayObject(value reflect.Value) *_object { func (rt *runtime) newGoArrayObject(value reflect.Value) *object {
self := runtime.newObject() o := rt.newObject()
self.class = classGoArray o.class = classGoArrayName
self.objectClass = _classGoArray o.objectClass = classGoArray
self.value = _newGoArrayObject(value) o.value = newGoArrayObject(value)
return self return o
} }
type _goArrayObject struct { type goArrayObject struct {
value reflect.Value value reflect.Value
writable bool writable bool
propertyMode _propertyMode propertyMode propertyMode
} }
func _newGoArrayObject(value reflect.Value) *_goArrayObject { func newGoArrayObject(value reflect.Value) *goArrayObject {
writable := value.Kind() == reflect.Ptr || value.CanSet() // The Array is addressable (like a Slice) writable := value.Kind() == reflect.Ptr || value.CanSet() // The Array is addressable (like a Slice)
mode := _propertyMode(0010) mode := propertyMode(0o010)
if writable { if writable {
mode = 0110 mode = 0o110
} }
self := &_goArrayObject{
return &goArrayObject{
value: value, value: value,
writable: writable, writable: writable,
propertyMode: mode, propertyMode: mode,
} }
return self
} }
func (self _goArrayObject) getValue(name string) (reflect.Value, bool) { func (o goArrayObject) getValue(name string) (reflect.Value, bool) { //nolint: unused
if index, err := strconv.ParseInt(name, 10, 64); err != nil { if index, err := strconv.ParseInt(name, 10, 64); err != nil {
v, ok := self.getValueIndex(index) v, ok := o.getValueIndex(index)
if ok { if ok {
return v, ok return v, ok
} }
} }
if m := self.value.MethodByName(name); m != (reflect.Value{}) { if m := o.value.MethodByName(name); m != (reflect.Value{}) {
return m, true return m, true
} }
return reflect.Value{}, false return reflect.Value{}, false
} }
func (self _goArrayObject) getValueIndex(index int64) (reflect.Value, bool) { func (o goArrayObject) getValueIndex(index int64) (reflect.Value, bool) {
value := reflect.Indirect(self.value) value := reflect.Indirect(o.value)
if index < int64(value.Len()) { if index < int64(value.Len()) {
return value.Index(int(index)), true return value.Index(int(index)), true
} }
@ -57,12 +57,12 @@ func (self _goArrayObject) getValueIndex(index int64) (reflect.Value, bool) {
return reflect.Value{}, false return reflect.Value{}, false
} }
func (self _goArrayObject) setValue(index int64, value Value) bool { func (o goArrayObject) setValue(index int64, value Value) bool {
indexValue, exists := self.getValueIndex(index) indexValue, exists := o.getValueIndex(index)
if !exists { if !exists {
return false return false
} }
reflectValue, err := value.toReflectValue(reflect.Indirect(self.value).Type().Elem()) reflectValue, err := value.toReflectValue(reflect.Indirect(o.value).Type().Elem())
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -70,87 +70,87 @@ func (self _goArrayObject) setValue(index int64, value Value) bool {
return true return true
} }
func goArrayGetOwnProperty(self *_object, name string) *_property { func goArrayGetOwnProperty(obj *object, name string) *property {
// length // length
if name == propertyLength { if name == propertyLength {
return &_property{ return &property{
value: toValue(reflect.Indirect(self.value.(*_goArrayObject).value).Len()), value: toValue(reflect.Indirect(obj.value.(*goArrayObject).value).Len()),
mode: 0, mode: 0,
} }
} }
// .0, .1, .2, ... // .0, .1, .2, ...
if index := stringToArrayIndex(name); index >= 0 { if index := stringToArrayIndex(name); index >= 0 {
object := self.value.(*_goArrayObject) goObj := obj.value.(*goArrayObject)
value := Value{} value := Value{}
reflectValue, exists := object.getValueIndex(index) reflectValue, exists := goObj.getValueIndex(index)
if exists { if exists {
value = self.runtime.toValue(reflectValue.Interface()) value = obj.runtime.toValue(reflectValue.Interface())
} }
return &_property{ return &property{
value: value, value: value,
mode: object.propertyMode, mode: goObj.propertyMode,
} }
} }
if method := self.value.(*_goArrayObject).value.MethodByName(name); method != (reflect.Value{}) { if method := obj.value.(*goArrayObject).value.MethodByName(name); method != (reflect.Value{}) {
return &_property{ return &property{
self.runtime.toValue(method.Interface()), obj.runtime.toValue(method.Interface()),
0110, 0o110,
} }
} }
return objectGetOwnProperty(self, name) return objectGetOwnProperty(obj, name)
} }
func goArrayEnumerate(self *_object, all bool, each func(string) bool) { func goArrayEnumerate(obj *object, all bool, each func(string) bool) {
object := self.value.(*_goArrayObject) goObj := obj.value.(*goArrayObject)
// .0, .1, .2, ... // .0, .1, .2, ...
for index, length := 0, object.value.Len(); index < length; index++ { for index, length := 0, goObj.value.Len(); index < length; index++ {
name := strconv.FormatInt(int64(index), 10) name := strconv.FormatInt(int64(index), 10)
if !each(name) { if !each(name) {
return return
} }
} }
objectEnumerate(self, all, each) objectEnumerate(obj, all, each)
} }
func goArrayDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool { func goArrayDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
if name == propertyLength { if name == propertyLength {
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} else if index := stringToArrayIndex(name); index >= 0 { } else if index := stringToArrayIndex(name); index >= 0 {
object := self.value.(*_goArrayObject) goObj := obj.value.(*goArrayObject)
if object.writable { if goObj.writable {
if self.value.(*_goArrayObject).setValue(index, descriptor.value.(Value)) { if obj.value.(*goArrayObject).setValue(index, descriptor.value.(Value)) {
return true return true
} }
} }
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
return objectDefineOwnProperty(self, name, descriptor, throw) return objectDefineOwnProperty(obj, name, descriptor, throw)
} }
func goArrayDelete(self *_object, name string, throw bool) bool { func goArrayDelete(obj *object, name string, throw bool) bool {
// length // length
if name == propertyLength { if name == propertyLength {
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
// .0, .1, .2, ... // .0, .1, .2, ...
index := stringToArrayIndex(name) index := stringToArrayIndex(name)
if index >= 0 { if index >= 0 {
object := self.value.(*_goArrayObject) goObj := obj.value.(*goArrayObject)
if object.writable { if goObj.writable {
indexValue, exists := object.getValueIndex(index) indexValue, exists := goObj.getValueIndex(index)
if exists { if exists {
indexValue.Set(reflect.Zero(reflect.Indirect(object.value).Type().Elem())) indexValue.Set(reflect.Zero(reflect.Indirect(goObj.value).Type().Elem()))
return true return true
} }
} }
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
return self.delete(name, throw) return obj.delete(name, throw)
} }

View file

@ -4,69 +4,82 @@ import (
"reflect" "reflect"
) )
func (runtime *_runtime) newGoMapObject(value reflect.Value) *_object { func (rt *runtime) newGoMapObject(value reflect.Value) *object {
self := runtime.newObject() obj := rt.newObject()
self.class = classObject // TODO Should this be something else? obj.class = classObjectName // TODO Should this be something else?
self.objectClass = _classGoMap obj.objectClass = classGoMap
self.value = _newGoMapObject(value) obj.value = newGoMapObject(value)
return self return obj
} }
type _goMapObject struct { type goMapObject struct {
value reflect.Value value reflect.Value
keyType reflect.Type keyType reflect.Type
valueType reflect.Type valueType reflect.Type
} }
func _newGoMapObject(value reflect.Value) *_goMapObject { func newGoMapObject(value reflect.Value) *goMapObject {
if value.Kind() != reflect.Map { if value.Kind() != reflect.Map {
dbgf("%/panic//%@: %v != reflect.Map", value.Kind()) dbgf("%/panic//%@: %v != reflect.Map", value.Kind())
} }
self := &_goMapObject{ return &goMapObject{
value: value, value: value,
keyType: value.Type().Key(), keyType: value.Type().Key(),
valueType: value.Type().Elem(), valueType: value.Type().Elem(),
} }
return self
} }
func (self _goMapObject) toKey(name string) reflect.Value { func (o goMapObject) toKey(name string) reflect.Value {
reflectValue, err := stringToReflectValue(name, self.keyType.Kind()) reflectValue, err := stringToReflectValue(name, o.keyType.Kind())
if err != nil { if err != nil {
panic(err) panic(err)
} }
return reflectValue return reflectValue
} }
func (self _goMapObject) toValue(value Value) reflect.Value { func (o goMapObject) toValue(value Value) reflect.Value {
reflectValue, err := value.toReflectValue(self.valueType) reflectValue, err := value.toReflectValue(o.valueType)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return reflectValue return reflectValue
} }
func goMapGetOwnProperty(self *_object, name string) *_property { func goMapGetOwnProperty(obj *object, name string) *property {
object := self.value.(*_goMapObject) goObj := obj.value.(*goMapObject)
value := object.value.MapIndex(object.toKey(name))
// an error here means that the key referenced by `name` could not possibly
// be a property of this object, so it should be safe to ignore this error
//
// TODO: figure out if any cases from
// https://go.dev/ref/spec#Comparison_operators meet the criteria of 1)
// being possible to represent as a string, 2) being possible to reconstruct
// from a string, and 3) having a meaningful failure case in this context
// other than "key does not exist"
key, err := stringToReflectValue(name, goObj.keyType.Kind())
if err != nil {
return nil
}
value := goObj.value.MapIndex(key)
if value.IsValid() { if value.IsValid() {
return &_property{self.runtime.toValue(value.Interface()), 0111} return &property{obj.runtime.toValue(value.Interface()), 0o111}
} }
// Other methods // Other methods
if method := self.value.(*_goMapObject).value.MethodByName(name); method.IsValid() { if method := obj.value.(*goMapObject).value.MethodByName(name); method.IsValid() {
return &_property{ return &property{
value: self.runtime.toValue(method.Interface()), value: obj.runtime.toValue(method.Interface()),
mode: 0110, mode: 0o110,
} }
} }
return nil return nil
} }
func goMapEnumerate(self *_object, all bool, each func(string) bool) { func goMapEnumerate(obj *object, all bool, each func(string) bool) {
object := self.value.(*_goMapObject) goObj := obj.value.(*goMapObject)
keys := object.value.MapKeys() keys := goObj.value.MapKeys()
for _, key := range keys { for _, key := range keys {
if !each(toValue(key).String()) { if !each(toValue(key).String()) {
return return
@ -74,22 +87,22 @@ func goMapEnumerate(self *_object, all bool, each func(string) bool) {
} }
} }
func goMapDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool { func goMapDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
object := self.value.(*_goMapObject) goObj := obj.value.(*goMapObject)
// TODO ...or 0222 // TODO ...or 0222
if descriptor.mode != 0111 { if descriptor.mode != 0o111 {
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
if !descriptor.isDataDescriptor() { if !descriptor.isDataDescriptor() {
return self.runtime.typeErrorResult(throw) return obj.runtime.typeErrorResult(throw)
} }
object.value.SetMapIndex(object.toKey(name), object.toValue(descriptor.value.(Value))) goObj.value.SetMapIndex(goObj.toKey(name), goObj.toValue(descriptor.value.(Value)))
return true return true
} }
func goMapDelete(self *_object, name string, throw bool) bool { func goMapDelete(obj *object, name string, throw bool) bool {
object := self.value.(*_goMapObject) goObj := obj.value.(*goMapObject)
object.value.SetMapIndex(object.toKey(name), reflect.Value{}) goObj.value.SetMapIndex(goObj.toKey(name), reflect.Value{})
// FIXME // FIXME
return true return true
} }

Some files were not shown because too many files have changed in this diff Show more