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"
"os"
"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/models"
rumblelivestreamlib "github.com/tylertravisty/rumble-livestream-lib-go"
"github.com/wailsapp/wails/v2/pkg/runtime"
_ "github.com/mattn/go-sqlite3"
)
const (
AccountType = "Account"
ChannelType = "Channel"
)
// App struct
type App struct {
api *api.Api
clients map[string]*rumblelivestreamlib.Client
clientsMu sync.Mutex
ctx context.Context
services *models.Services
logError *log.Logger
@ -36,6 +46,8 @@ func NewApp() *App {
log.Fatal("error initializing log: ", err)
}
app.api = api.NewApi(app.logError, app.logInfo)
return app
}
@ -58,6 +70,7 @@ func (a *App) log() error {
// so we can call the runtime methods
func (a *App) startup(ctx context.Context) {
a.ctx = ctx
a.api.Startup(ctx)
db, err := config.Database()
if err != nil {
@ -85,6 +98,13 @@ func (a *App) startup(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 {
err := a.services.Close()
if err != nil {
@ -102,7 +122,7 @@ func (a *App) shutdown(ctx context.Context) {
a.logFileMu.Unlock()
}
func (a *App) AddChannel(apiKey string) error {
func (a *App) AddPage(apiKey string) error {
client := rumblelivestreamlib.Client{StreamKey: apiKey}
resp, err := client.Request()
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)
}
if acct == nil {
err = a.services.AccountS.Create(&models.Account{
_, err = a.services.AccountS.Create(&models.Account{
UID: &uid,
Username: &username,
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 {
var err error
a.clientsMu.Lock()
defer a.clientsMu.Unlock()
client, exists := a.clients[username]
if exists && client != nil {
err = client.Logout()
@ -207,27 +229,107 @@ func (a *App) Login(username string, password string) error {
}
cookiesS := string(cookiesB)
act, err := a.services.AccountS.ByUsername(username)
acct, err := a.services.AccountS.ByUsername(username)
if err != nil {
a.logError.Println("error getting account by username:", err)
return fmt.Errorf("Error logging in. Try again.")
}
if act == nil {
act = &models.Account{nil, nil, &username, &cookiesS, nil, nil}
err = a.services.AccountS.Create(act)
if acct == nil {
acct = &models.Account{nil, nil, &username, &cookiesS, nil, nil}
id, err := a.services.AccountS.Create(acct)
if err != nil {
a.logError.Println("error creating account:", err)
return fmt.Errorf("Error logging in. Try again.")
}
acct.ID = &id
} else {
act.Cookies = &cookiesS
err = a.services.AccountS.Update(act)
acct.Cookies = &cookiesS
err = a.services.AccountS.Update(acct)
if err != nil {
a.logError.Println("error updating account:", err)
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
}
@ -247,18 +349,26 @@ type Account struct {
}
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{}
accountChannels, err := a.services.AccountChannelS.All()
if err != nil {
a.logError.Println("error getting all account channels:", err)
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
return nil, fmt.Errorf("error querying all account channels: %v", err)
}
for _, ac := range accountChannels {
if ac.Account.Username == nil {
a.logError.Println("account-channel contains nil account username")
return nil, fmt.Errorf("Error retrieving accounts and channels. Try restarting.")
return nil, fmt.Errorf("account-channel contains nil account username")
}
act, exists := list[*ac.Account.Username]
@ -274,3 +384,366 @@ func (a *App) AccountList() (map[string]*Account, error) {
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 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_red from './icons/eye-red.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 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 logo from './logo/logo.png';
export const ChevronRight = chevron_right;
export const CircleGreenBackground = circle_green_background;
export const CircleRedBackground = circle_red_background;
export const Eye = eye;
export const EyeRed = eye_red;
export const EyeSlash = eye_slash;
export const Gear = gear_fill;
export const Heart = heart;
export const Logo = logo;
export const Pause = pause;
export const Play = play;
export const PlusCircle = plus_circle;
export const Star = star;
export const ThumbsDown = thumbs_down;
export const ThumbsUp = thumbs_up;
export const XLg = x_lg;

View file

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

View file

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

View file

@ -1,17 +1,38 @@
import { useEffect, useState } from 'react';
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 './ChannelSideBar.css';
import {
ChevronRight,
CircleGreenBackground,
CircleRedBackground,
Eye,
EyeSlash,
PlusCircle,
} from '../assets';
import './PageSideBar.css';
function ChannelSideBar(props) {
function PageSideBar(props) {
const [accounts, setAccounts] = useState({});
const [error, setError] = useState('');
const [addOpen, setAddOpen] = useState(false);
const [refresh, setRefresh] = useState(false);
const [scrollY, setScrollY] = useState(0);
useEffect(() => {
EventsOn('PageSideBarAccounts', (event) => {
setAccounts(event);
});
}, []);
useEffect(() => {
AccountList()
.then((response) => {
@ -38,27 +59,39 @@ function ChannelSideBar(props) {
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 (
<>
<ModalAdd
onClose={() => setAddOpen(false)}
onRefresh={() => {
setRefresh(!refresh);
}}
show={addOpen}
/>
<div className='channel-sidebar'>
<div className='channel-sidebar-body' onScroll={handleScroll}>
{addOpen && (
<ModalAdd
onClose={() => setAddOpen(false)}
onRefresh={() => {
setRefresh(!refresh);
}}
show={addOpen}
/>
)}
<div className='page-sidebar'>
<div className='page-sidebar-body' onScroll={handleScroll}>
{sortAccounts().map((account, index) => (
<AccountChannels
account={accounts[account]}
key={index}
openAccount={openAccount}
openChannel={openChannel}
scrollY={scrollY}
top={index === 0}
/>
))}
</div>
<div className='channel-sidebar-footer'>
<div className='page-sidebar-footer'>
<ButtonIcon
hoverText={'Add an account/channel'}
onClick={() => setAddOpen(true)}
@ -70,7 +103,7 @@ function ChannelSideBar(props) {
);
}
export default ChannelSideBar;
export default PageSideBar;
function AccountChannels(props) {
const sortChannels = () => {
@ -84,12 +117,24 @@ function AccountChannels(props) {
if (props.account.account !== undefined) {
return (
<div
className='channel-sidebar-account-list'
className='page-sidebar-account-list'
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) => (
<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>
);
@ -97,25 +142,82 @@ function AccountChannels(props) {
}
function AccountIcon(props) {
const [apiActive, setApiActive] = 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 (
<div
className='channel-sidebar-icon'
className='page-sidebar-icon'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{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()}
</span>
) : (
<img className='channel-sidebar-icon-image' src={props.account.profile_image} />
)}
<img className='channel-sidebar-icon-account' src={CircleGreenBackground} />
{hover && (
<HoverName name={'/user/' + props.account.username} scrollY={props.scrollY} />
<img
className='page-sidebar-icon-image'
src={props.account.profile_image}
style={{ border: iconBorder() }}
/>
)}
<img
className='page-sidebar-icon-account'
src={loggedIn ? CircleGreenBackground : CircleRedBackground}
/>
{hover && <HoverName name={pageName(username)} scrollY={props.scrollY} />}
</div>
);
}
@ -125,12 +227,12 @@ function ButtonIcon(props) {
return (
<div
className='channel-sidebar-icon'
className='page-sidebar-icon'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
<button className='channel-sidebar-button' onClick={props.onClick}>
<img className='channel-sidebar-button-icon' src={PlusCircle} />
<button className='page-sidebar-button' onClick={props.onClick}>
<img className='page-sidebar-button-icon' src={PlusCircle} />
</button>
{hover && <HoverName name={props.hoverText} scrollY={props.scrollY} />}
</div>
@ -138,26 +240,69 @@ function ButtonIcon(props) {
}
function ChannelIcon(props) {
const [apiActive, setApiActive] = useState(false);
const [channelName, setChannelName] = useState(props.channel.name);
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 (
<div
className='channel-sidebar-icon'
className='page-sidebar-icon'
onMouseEnter={() => setHover(true)}
onMouseLeave={() => setHover(false)}
>
{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()}
</span>
) : (
<img className='channel-sidebar-icon-image' src={props.channel.profile_image} />
)}
{hover && (
<HoverName
name={'/c/' + props.channel.name.replace(/\s/g, '')}
scrollY={props.scrollY}
<img
className='page-sidebar-icon-image'
src={props.channel.profile_image}
style={{ border: iconBorder() }}
/>
)}
{hover && <HoverName name={pageName(channelName)} scrollY={props.scrollY} />}
</div>
);
}
@ -165,10 +310,10 @@ function ChannelIcon(props) {
function HoverName(props) {
return (
<div
className='channel-sidebar-icon-hover'
className='page-sidebar-icon-hover'
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>
);
}
@ -220,7 +365,7 @@ function ModalAdd(props) {
useEffect(() => {
if (addChannelLoading) {
AddChannel(channelKey)
AddPage(channelKey)
.then(() => {
reset();
props.onClose();
@ -318,15 +463,17 @@ function ModalAdd(props) {
return (
<>
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
{error !== '' && (
<SmallModal
onClose={() => setError('')}
show={error !== ''}
style={{ minWidth: '300px', maxWidth: '200px', maxHeight: '200px' }}
title={'Error'}
message={error}
submitButton={'OK'}
onSubmit={() => setError('')}
/>
)}
<Modal
cancelButton={stage !== 'start' ? 'Back' : ''}
onCancel={back}

View file

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

View file

@ -6,7 +6,7 @@ toolchain go1.22.0
require (
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
)
@ -28,7 +28,7 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/r3labs/sse/v2 v2.10.0 // 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/tkrajina/go-reflector v0.5.6 // 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/wailsapp/go-webview2 v1.0.10 // 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/net v0.20.0 // indirect
golang.org/x/sys v0.16.0 // indirect
golang.org/x/net v0.22.0 // indirect
golang.org/x/sys v0.18.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/cenkalti/backoff.v1 v1.1.0 // 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.4.4 h1:8TfxU8dW6PdqD27gjM8MVNuicgxIjxpm4K7x4jp8sis=
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.2.1/go.mod h1:UPwtJ1Xu7JrLcZjNWN8orJaM5n5YEtqL//farB5FlRY=
github.com/robertkrimen/otto v0.3.0 h1:5RI+8860NSxvXywDY9ddF5HcPw0puRsd8EgbXV0oqRE=
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/go.mod h1:+m/ZKRl6ClXCE2Lgf3MsQlWfh4bn1bz6CXEOxnEXnEA=
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/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/rumble-livestream-lib-go v0.3.4 h1:VPKelrC3hesJlbqdByMkUhbEubFx80T5FNC60JKrEfw=
github.com/tylertravisty/rumble-livestream-lib-go v0.3.4/go.mod h1:rUET5uInouMfB4ekqdGiYeoN5ibOdzU9cCgRE0i57Wg=
github.com/tylertravisty/rumble-livestream-lib-go v0.3.5 h1:mAf4oYuQ55pXTPsIMVztOlYM8oGsBgsNMJvel2VLgsQ=
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/go.mod h1:6bBcMArwyJ5K/AmCkWv1jt77kVWyCJ6HpOuEn7z0Csc=
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/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.18.0 h1:PGVlW0xEltQnzFZ55hkuX5+KLyrMYhHld1YHO4AKcdc=
golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1mg=
golang.org/x/crypto v0.21.0 h1:X31++rzVUdKhX5sWmSOFZxx8UW/ldWx55cbf08iNAMA=
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/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-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y=
golang.org/x/net v0.20.0 h1:aCL9BSgETF1k+blQaYUBx9hJ9LOGP3gAVemcZlf1Kpo=
golang.org/x/net v0.20.0/go.mod h1:z8BVo6PvndSri0LbOE3hAn0apkU+1YvI6E70E9jsnvY=
golang.org/x/net v0.22.0 h1:9sGLhx7iRIHEiX0oAJ3MRZMUCElJgy7Br1nO+AMN3Tc=
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-20190916202348-b4ddaad3f8a3/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-20220811171246-fbc7d0a398ab/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.16.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
golang.org/x/sys v0.18.0 h1:DBdB3niSjOA/O0blCZBqDefyWNYveAYMNF1Wum0DYQ4=
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/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ=
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"`
}
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 {
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 {
All() ([]Account, error)
AutoMigrate() error
ByID(id int64) (*Account, error)
ByUsername(username string) (*Account, error)
Create(a *Account) error
Create(a *Account) (int64, error)
Delete(a *Account) error
DestructiveReset() error
Update(a *Account) error
}
@ -138,6 +169,34 @@ func (as *accountService) createAccountTable() error {
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) {
err := runAccountValFuncs(
&Account{Username: &username},
@ -166,24 +225,50 @@ func (as *accountService) ByUsername(username string) (*Account, error) {
return sa.toAccount(), nil
}
func (as *accountService) Create(a *Account) error {
func (as *accountService) Create(a *Account) (int64, error) {
err := runAccountValFuncs(
a,
accountRequireUsername,
)
if err != nil {
return pkgErr("invalid account", err)
return -1, pkgErr("invalid account", err)
}
columns := columnsNoID(accountColumns)
insertQ := fmt.Sprintf(`
INSERT INTO "%s" (%s)
VALUES (%s)
RETURNING id
`, 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 {
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
@ -230,7 +315,7 @@ func (as *accountService) Update(a *Account) error {
_, err = as.Database.Exec(updateQ, a.valuesEndID()...)
if err != nil {
return pkgErr(fmt.Sprintf("error executing update query", accountTable), err)
return pkgErr("error executing update query", err)
}
return nil

View file

@ -3,6 +3,7 @@ package models
import (
"database/sql"
"fmt"
"strings"
)
const (
@ -19,6 +20,35 @@ type Channel struct {
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 {
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 {
AutoMigrate() error
ByAccount(a *Account) ([]Channel, error)
ByID(id int64) (*Channel, error)
ByName(name string) (*Channel, error)
Create(c *Channel) error
Delete(c *Channel) error
DestructiveReset() error
Update(c *Channel) error
}
func NewChannelService(db *sql.DB) ChannelService {
@ -106,6 +140,74 @@ func (cs *channelService) createChannelTable() error {
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) {
err := runChannelValFuncs(
&Channel{Name: &name},
@ -160,6 +262,28 @@ func (cs *channelService) Create(c *Channel) error {
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 {
err := cs.dropChannelTable()
if err != nil {
@ -182,6 +306,33 @@ func (cs *channelService) dropChannelTable() error {
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
func runChannelValFuncs(c *Channel, fns ...channelValFunc) error {
@ -215,6 +366,14 @@ func channelRequireApiKey(c *Channel) error {
return nil
}
func channelRequireID(c *Channel) error {
if c.ID == nil || *c.ID < 1 {
return ErrChannelInvalidID
}
return nil
}
func channelRequireCID(c *Channel) error {
if c.CID == nil || *c.CID == "" {
return ErrChannelInvalidCID

View file

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

View file

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

View file

@ -4,6 +4,7 @@ run:
- terst
skip-files:
- dbg/dbg.go
- token/token_const.go
linters-settings:
govet:
@ -11,25 +12,17 @@ linters-settings:
goconst:
min-len: 2
min-occurrences: 4
revive:
enable-all-rules: false
rules:
- name: var-naming
disabled: true
linters:
enable-all: true
disable:
- dupl
- gas
- errcheck
- gofmt
- gosimple
- interfacer
- megacheck
- maligned
- structcheck
- staticcheck
- unconvert
- unparam
- varcheck
- lll
- prealloc
- gochecknoglobals
- gochecknoinits
- scopelint
@ -39,35 +32,34 @@ linters:
- goerr113
- wsl
- nlreturn
- tagliatelle
- gomnd
- paralleltest
- wrapcheck
- testpackage
- golint
- gofumpt
- forbidigo
- gocognit
- gocritic
- godot
- nakedret
- nestif
- revive
- errorlint
- exhaustive
- forcetypeassert
- ifshort
- stylecheck
- gocyclo
- misspell
- cyclop
- varnamelen
- nonamedreturns
- maintidx
- ireturn
- exhaustruct
- nosnakecase
- deadcode
- 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"
)
// CommentPosition determines where the comment is in a given context
// CommentPosition determines where the comment is in a given context.
type CommentPosition int
// Available comment positions.
const (
_ CommentPosition = iota
LEADING // Before the pertinent expression
TRAILING // After the pertinent expression
KEY // Before a key in an object
COLON // After a colon in a field declaration
FINAL // Final comments in a block, not belonging to a specific expression or the comment after a trailing , in an array or object literal
IF // After an if keyword
WHILE // After a while keyword
DO // After do keyword
FOR // After a for keyword
WITH // After a with keyword
_ CommentPosition = iota
// LEADING is before the pertinent expression.
LEADING
// TRAILING is after the pertinent expression.
TRAILING
// KEY is before a key in an object.
KEY
// COLON is after a colon in a field declaration.
COLON
// 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.
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
)
// Comment contains the data of the comment
// Comment contains the data of the comment.
type Comment struct {
Begin file.Idx
Text string
Position CommentPosition
}
// NewComment creates a new comment
// NewComment creates a new comment.
func NewComment(text string, idx file.Idx) *Comment {
comment := &Comment{
Begin: idx,
@ -42,7 +54,7 @@ func NewComment(text string, idx file.Idx) *Comment {
return comment
}
// String returns a stringified version of the position
// String returns a stringified version of the position.
func (cp CommentPosition) String() string {
switch cp {
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 {
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 {
// CommentMap is a reference to the parser comment map
CommentMap CommentMap
// Comments lists the comments scanned, not linked to a node yet
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 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
// primary determines whether or not processing a primary expression
primary bool
@ -94,6 +106,7 @@ type Comments struct {
afterBlock bool
}
// NewComments returns a new Comments.
func NewComments() *Comments {
comments := &Comments{
CommentMap: CommentMap{},
@ -107,7 +120,7 @@ func (c *Comments) String() string {
}
// FetchAll returns all the currently scanned comments,
// including those from the next line
// including those from the next line.
func (c *Comments) FetchAll() []*Comment {
defer func() {
c.Comments = nil
@ -117,7 +130,7 @@ func (c *Comments) FetchAll() []*Comment {
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 {
defer func() {
c.Comments = nil
@ -126,12 +139,12 @@ func (c *Comments) Fetch() []*Comment {
return c.Comments
}
// ResetLineBreak marks the beginning of a new statement
// ResetLineBreak marks the beginning of a new statement.
func (c *Comments) ResetLineBreak() {
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() {
c.primary = true
c.wasLineBreak = false
@ -205,7 +218,7 @@ func (c *Comments) SetExpression(node Expression) {
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) {
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() {
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
// AddComment adds a single comment to the map
// AddComment adds a single comment to the map.
func (cm CommentMap) AddComment(node Node, comment *Comment) {
list := cm[node]
list = append(list, comment)
@ -244,7 +257,7 @@ func (cm CommentMap) AddComment(node Node, comment *Comment) {
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) {
for _, comment := range comments {
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 {
size := 0
for _, comments := range cm {
@ -264,7 +277,7 @@ func (cm CommentMap) Size() int {
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) {
for i, c := range cm[from] {
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)
}
case *BadExpression:
case *BadStatement:
case *BinaryExpression:
if n != nil {
Walk(v, n.Left)

View file

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

View file

@ -8,92 +8,93 @@ import (
// Array
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 {
return toValue_object(builtinNewArrayNative(self.runtime, argumentList))
func builtinNewArray(obj *object, argumentList []Value) Value {
return objectValue(builtinNewArrayNative(obj.runtime, argumentList))
}
func builtinNewArrayNative(runtime *_runtime, argumentList []Value) *_object {
func builtinNewArrayNative(rt *runtime, argumentList []Value) *object {
if len(argumentList) == 1 {
firstArgument := argumentList[0]
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()
join := thisObject.get("join")
if join.isCallable() {
join := join._object()
join := join.object()
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 := ","
thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength)))
if length == 0 {
return toValue_string("")
return stringValue("")
}
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))
stringValue := ""
switch value.kind {
case valueEmpty, valueUndefined, valueNull:
default:
object := call.runtime.toObject(value)
toLocaleString := object.get("toLocaleString")
obj := call.runtime.toObject(value)
toLocaleString := obj.get("toLocaleString")
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)
}
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()
valueArray := []Value{}
source := append([]Value{toValue_object(thisObject)}, call.ArgumentList...)
source := append([]Value{objectValue(thisObject)}, call.ArgumentList...)
for _, item := range source {
switch item.kind {
case valueObject:
object := item._object()
if isArray(object) {
length := object.get(propertyLength).number().int64
for index := int64(0); index < length; index += 1 {
obj := item.object()
if isArray(obj) {
length := obj.get(propertyLength).number().int64
for index := int64(0); index < length; index++ {
name := strconv.FormatInt(index, 10)
if object.hasProperty(name) {
valueArray = append(valueArray, object.get(name))
if obj.hasProperty(name) {
valueArray = append(valueArray, obj.get(name))
} else {
valueArray = append(valueArray, Value{})
}
}
continue
}
fallthrough
default:
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()
length := int64(toUint32(thisObject.get(propertyLength)))
if 0 == length {
thisObject.put(propertyLength, toValue_int64(0), true)
if length == 0 {
thisObject.put(propertyLength, int64Value(0), true)
return Value{}
}
first := thisObject.get("0")
@ -107,52 +108,50 @@ func builtinArray_shift(call FunctionCall) Value {
}
}
thisObject.delete(arrayIndexToString(length-1), true)
thisObject.put(propertyLength, toValue_int64(length-1), true)
thisObject.put(propertyLength, int64Value(length-1), true)
return first
}
func builtinArray_push(call FunctionCall) Value {
func builtinArrayPush(call FunctionCall) Value {
thisObject := call.thisObject()
itemList := call.ArgumentList
index := int64(toUint32(thisObject.get(propertyLength)))
for len(itemList) > 0 {
thisObject.put(arrayIndexToString(index), itemList[0], true)
itemList = itemList[1:]
index += 1
index++
}
length := toValue_int64(index)
length := int64Value(index)
thisObject.put(propertyLength, length, true)
return length
}
func builtinArray_pop(call FunctionCall) Value {
func builtinArrayPop(call FunctionCall) Value {
thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength)))
if 0 == length {
thisObject.put(propertyLength, toValue_uint32(0), true)
if length == 0 {
thisObject.put(propertyLength, uint32Value(0), true)
return Value{}
}
last := thisObject.get(arrayIndexToString(length - 1))
thisObject.delete(arrayIndexToString(length-1), true)
thisObject.put(propertyLength, toValue_int64(length-1), true)
thisObject.put(propertyLength, int64Value(length-1), true)
return last
}
func builtinArray_join(call FunctionCall) Value {
func builtinArrayJoin(call FunctionCall) Value {
separator := ","
{
argument := call.Argument(0)
if argument.IsDefined() {
separator = argument.string()
}
argument := call.Argument(0)
if argument.IsDefined() {
separator = argument.string()
}
thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength)))
if length == 0 {
return toValue_string("")
return stringValue("")
}
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))
stringValue := ""
switch value.kind {
@ -162,10 +161,10 @@ func builtinArray_join(call FunctionCall) Value {
}
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()
length := int64(toUint32(thisObject.get(propertyLength)))
@ -177,7 +176,7 @@ func builtinArray_splice(call FunctionCall) Value {
valueArray := make([]Value, deleteCount)
for index := int64(0); index < deleteCount; index++ {
indexString := arrayIndexToString(int64(start + index))
indexString := arrayIndexToString(start + index)
if thisObject.hasProperty(indexString) {
valueArray[index] = thisObject.get(indexString)
}
@ -197,7 +196,7 @@ func builtinArray_splice(call FunctionCall) Value {
}
if itemCount < deleteCount {
// The Object/Array is shrinking
stop := int64(length) - deleteCount
stop := length - deleteCount
// The new length of the Object/Array before
// appending the itemList remainder
// Stopping at the lower bound of the insertion:
@ -215,7 +214,7 @@ func builtinArray_splice(call FunctionCall) Value {
// Delete off the end
// We don't bother to delete below <stop + itemCount> (if any) since those
// 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)
}
} else if itemCount > deleteCount {
@ -226,7 +225,7 @@ func builtinArray_splice(call FunctionCall) Value {
// Starting from the upper bound of the deletion:
// Move an item from the after the deleted 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)
to := arrayIndexToString(index + itemCount - 1)
if thisObject.hasProperty(from) {
@ -240,12 +239,12 @@ func builtinArray_splice(call FunctionCall) Value {
for index := int64(0); index < itemCount; index++ {
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()
length := int64(toUint32(thisObject.get(propertyLength)))
@ -253,7 +252,7 @@ func builtinArray_slice(call FunctionCall) Value {
if start >= end {
// Always an empty array
return toValue_object(call.runtime.newArray(0))
return objectValue(call.runtime.newArray(0))
}
sliceLength := end - start
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()
length := int64(toUint32(thisObject.get(propertyLength)))
itemList := call.ArgumentList
@ -288,12 +287,12 @@ func builtinArray_unshift(call FunctionCall) Value {
thisObject.put(arrayIndexToString(index), itemList[index], true)
}
newLength := toValue_int64(length + itemCount)
newLength := int64Value(length + itemCount)
thisObject.put(propertyLength, newLength, true)
return newLength
}
func builtinArray_reverse(call FunctionCall) Value {
func builtinArrayReverse(call FunctionCall) Value {
thisObject := call.thisObject()
length := int64(toUint32(thisObject.get(propertyLength)))
@ -315,30 +314,29 @@ func builtinArray_reverse(call FunctionCall) Value {
lower.exists = thisObject.hasProperty(lower.name)
upper.exists = thisObject.hasProperty(upper.name)
if lower.exists && upper.exists {
switch {
case lower.exists && upper.exists:
lowerValue := thisObject.get(lower.name)
upperValue := thisObject.get(upper.name)
thisObject.put(lower.name, upperValue, true)
thisObject.put(upper.name, lowerValue, true)
} else if !lower.exists && upper.exists {
case !lower.exists && upper.exists:
value := thisObject.get(upper.name)
thisObject.delete(upper.name, true)
thisObject.put(lower.name, value, true)
} else if lower.exists && !upper.exists {
case lower.exists && !upper.exists:
value := thisObject.get(lower.name)
thisObject.delete(lower.name, true)
thisObject.put(upper.name, value, true)
} else {
// Nothing happens.
}
lower.index += 1
lower.index++
}
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 {
name string
exists bool
@ -351,11 +349,12 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
k.name = arrayIndexToString(int64(index1))
k.exists = thisObject.hasProperty(k.name)
if !j.exists && !k.exists {
switch {
case !j.exists && !k.exists:
return 0
} else if !j.exists {
case !j.exists:
return 1
} else if !k.exists {
case !k.exists:
return -1
}
@ -364,11 +363,12 @@ func sortCompare(thisObject *_object, index0, index1 uint, compare *_object) int
j.defined = x.IsDefined()
k.defined = y.IsDefined()
if !j.defined && !k.defined {
switch {
case !j.defined && !k.defined:
return 0
} else if !j.defined {
case !j.defined:
return 1
} else if !k.defined {
case !k.defined:
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))
}
func arraySortSwap(thisObject *_object, index0, index1 uint) {
func arraySortSwap(thisObject *object, index0, index1 uint) {
j := struct {
name string
exists bool
@ -400,25 +400,24 @@ func arraySortSwap(thisObject *_object, index0, index1 uint) {
k.name = arrayIndexToString(int64(index1))
k.exists = thisObject.hasProperty(k.name)
if j.exists && k.exists {
jValue := thisObject.get(j.name)
kValue := thisObject.get(k.name)
thisObject.put(j.name, kValue, true)
thisObject.put(k.name, jValue, true)
} else if !j.exists && k.exists {
switch {
case j.exists && k.exists:
jv := thisObject.get(j.name)
kv := thisObject.get(k.name)
thisObject.put(j.name, kv, true)
thisObject.put(k.name, jv, true)
case !j.exists && k.exists:
value := thisObject.get(k.name)
thisObject.delete(k.name, true)
thisObject.put(j.name, value, true)
} else if j.exists && !k.exists {
case j.exists && !k.exists:
value := thisObject.get(j.name)
thisObject.delete(j.name, 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
cursor := left
cursor2 := left
@ -429,18 +428,18 @@ func arraySortQuickPartition(thisObject *_object, left, right, pivot uint, compa
if cursor < cursor2 {
arraySortSwap(thisObject, index, cursor2)
}
cursor += 1
cursor2 += 1
cursor++
cursor2++
} else if comparison == 0 {
arraySortSwap(thisObject, index, cursor2)
cursor2 += 1
cursor2++
}
}
arraySortSwap(thisObject, cursor2, right)
return cursor, cursor2
}
func arraySortQuickSort(thisObject *_object, left, right uint, compare *_object) {
func arraySortQuickSort(thisObject *object, left, right uint, compare *object) {
if left < right {
middle := left + (right-left)/2
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()
length := uint(toUint32(thisObject.get(propertyLength)))
compareValue := call.Argument(0)
compare := compareValue._object()
compare := compareValue.object()
if compareValue.IsUndefined() {
} else if !compareValue.isCallable() {
panic(call.runtime.panicTypeError())
panic(call.runtime.panicTypeError("Array.sort value %q is not callable", compareValue))
}
if length > 1 {
arraySortQuickSort(thisObject, 0, length-1, compare)
@ -466,11 +465,11 @@ func builtinArray_sort(call FunctionCall) Value {
return call.This
}
func builtinArray_isArray(call FunctionCall) Value {
return toValue_bool(isArray(call.Argument(0)._object()))
func builtinArrayIsArray(call FunctionCall) Value {
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)
if length := int64(toUint32(thisObject.get(propertyLength))); length > 0 {
index := int64(0)
@ -485,20 +484,20 @@ func builtinArray_indexOf(call FunctionCall) Value {
index = -1
}
for ; index >= 0 && index < length; index++ {
name := arrayIndexToString(int64(index))
name := arrayIndexToString(index)
if !thisObject.hasProperty(name) {
continue
}
value := thisObject.get(name)
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)
length := int64(toUint32(thisObject.get(propertyLength)))
index := length - 1
@ -511,30 +510,30 @@ func builtinArray_lastIndexOf(call FunctionCall) Value {
if index > length {
index = length - 1
} else if 0 > index {
return toValue_int(-1)
return intValue(-1)
}
for ; index >= 0; index-- {
name := arrayIndexToString(int64(index))
name := arrayIndexToString(index)
if !thisObject.hasProperty(name) {
continue
}
value := thisObject.get(name)
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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1)
for index := int64(0); index < length; index++ {
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
}
return falseValue
@ -542,46 +541,46 @@ func builtinArray_every(call FunctionCall) Value {
}
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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1)
for index := int64(0); index < length; index++ {
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 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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1)
for index := int64(0); index < length; index++ {
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{}
}
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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength)))
callThis := call.Argument(1)
@ -593,14 +592,14 @@ func builtinArray_map(call FunctionCall) 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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
length := int64(toUint32(thisObject.get(propertyLength)))
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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
initial := len(call.ArgumentList) > 1
start := call.Argument(1)
@ -633,6 +632,7 @@ func builtinArray_reduce(call FunctionCall) Value {
if key := arrayIndexToString(index); thisObject.hasProperty(key) {
accumulator = thisObject.get(key)
index++
break
}
}
@ -647,12 +647,12 @@ func builtinArray_reduce(call FunctionCall) Value {
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()
this := toValue_object(thisObject)
this := objectValue(thisObject)
if iterator := call.Argument(0); iterator.isCallable() {
initial := len(call.ArgumentList) > 1
start := call.Argument(1)
@ -679,5 +679,5 @@ func builtinArray_reduceRight(call FunctionCall) Value {
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
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 {
return toValue_object(self.runtime.newBoolean(valueOfArrayIndex(argumentList, 0)))
func builtinNewBoolean(obj *object, argumentList []Value) Value {
return objectValue(obj.runtime.newBoolean(valueOfArrayIndex(argumentList, 0)))
}
func builtinBoolean_toString(call FunctionCall) Value {
func builtinBooleanToString(call FunctionCall) Value {
value := call.This
if !value.IsBoolean() {
// 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
if !value.IsBoolean() {
value = call.thisClassObject(classBoolean).primitiveValue()
value = call.thisClassObject(classBooleanName).primitiveValue()
}
return value
}

View file

@ -2,121 +2,118 @@ package otto
import (
"math"
Time "time"
"time"
)
// Date
const (
// TODO Be like V8?
// builtinDate_goDateTimeLayout = "Mon Jan 2 2006 15:04:05 GMT-0700 (MST)"
builtinDate_goDateTimeLayout = Time.RFC1123 // "Mon, 02 Jan 2006 15:04:05 MST"
builtinDate_goDateLayout = "Mon, 02 Jan 2006"
builtinDate_goTimeLayout = "15:04:05 MST"
// builtinDateDateTimeLayout = "Mon Jan 2 2006 15:04:05 GMT-0700 (MST)".
builtinDateDateTimeLayout = time.RFC1123 // "Mon, 02 Jan 2006 15:04:05 MST"
builtinDateDateLayout = "Mon, 02 Jan 2006"
builtinDateTimeLayout = "15:04:05 MST"
)
var (
// utcTimeZone is the time zone used for UTC calculations.
// It is GMT not UTC as that's what Javascript does because toUTCString is
// actually an alias to toGMTString.
utcTimeZone = Time.FixedZone("GMT", 0)
)
// utcTimeZone is the time zone used for UTC calculations.
// It is GMT not UTC as that's what Javascript does because toUTCString is
// actually an alias to toGMTString.
var utcTimeZone = time.FixedZone("GMT", 0)
func builtinDate(call FunctionCall) Value {
date := &_dateObject{}
date.Set(newDateTime([]Value{}, Time.Local))
return toValue_string(date.Time().Format(builtinDate_goDateTimeLayout))
date := &dateObject{}
date.Set(newDateTime([]Value{}, time.Local)) //nolint: gosmopolitan
return stringValue(date.Time().Format(builtinDateDateTimeLayout))
}
func builtinNewDate(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newDate(newDateTime(argumentList, Time.Local)))
func builtinNewDate(obj *object, argumentList []Value) Value {
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())
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())
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())
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())
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())
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 {
object := call.thisObject()
value := object.DefaultValue(defaultValueHintNumber) // FIXME object.primitiveNumberValue
{ // FIXME value.isFinite
value := value.float64()
if math.IsNaN(value) || math.IsInf(value, 0) {
return nullValue
}
func builtinDateToJSON(call FunctionCall) Value {
obj := call.thisObject()
value := obj.DefaultValue(defaultValueHintNumber) // FIXME object.primitiveNumberValue
// FIXME fv.isFinite
if fv := value.float64(); math.IsNaN(fv) || math.IsInf(fv, 0) {
return nullValue
}
toISOString := object.get("toISOString")
toISOString := obj.get("toISOString")
if !toISOString.isCallable() {
// 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())
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())
if date.isNaN {
return NaNValue()
}
// We do this (convert away from a float) so the user
// does not get something back in exponential notation
return toValue_int64(int64(date.Epoch()))
return int64Value(date.Epoch())
}
func builtinDate_setTime(call FunctionCall) Value {
object := call.thisObject()
func builtinDateSetTime(call FunctionCall) Value {
obj := call.thisObject()
date := dateObjectOf(call.runtime, call.thisObject())
date.Set(call.Argument(0).float64())
object.value = date
obj.value = date
return date.Value()
}
func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool) (*_object, *_dateObject, *_ecmaTime, []int) {
object := call.thisObject()
func builtinDateBeforeSet(call FunctionCall, argumentLimit int, timeLocal bool) (*object, *dateObject, *ecmaTime, []int) {
obj := call.thisObject()
date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN {
return nil, nil, nil, nil
@ -127,7 +124,7 @@ func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool
}
if argumentLimit == 0 {
object.value = invalidDateObject
obj.value = invalidDateObject
return nil, nil, nil, nil
}
@ -138,61 +135,61 @@ func _builtinDate_beforeSet(call FunctionCall, argumentLimit int, timeLocal bool
switch nm.kind {
case numberInteger, numberFloat:
default:
object.value = invalidDateObject
obj.value = invalidDateObject
return nil, nil, nil, nil
}
valueList[index] = int(nm.int64)
}
baseTime := date.Time()
if timeLocal {
baseTime = baseTime.Local()
baseTime = baseTime.Local() //nolint: gosmopolitan
}
ecmaTime := ecmaTime(baseTime)
return object, &date, &ecmaTime, valueList
ecmaTime := newEcmaTime(baseTime)
return obj, &date, &ecmaTime, valueList
}
func builtinDate_parse(call FunctionCall) Value {
func builtinDateParse(call FunctionCall) Value {
date := call.Argument(0).string()
return toValue_float64(dateParse(date))
return float64Value(dateParse(date))
}
func builtinDate_UTC(call FunctionCall) Value {
return toValue_float64(newDateTime(call.ArgumentList, Time.UTC))
func builtinDateUTC(call FunctionCall) Value {
return float64Value(newDateTime(call.ArgumentList, time.UTC))
}
func builtinDate_now(call FunctionCall) Value {
func builtinDateNow(call FunctionCall) Value {
call.ArgumentList = []Value(nil)
return builtinDate_UTC(call)
return builtinDateUTC(call)
}
// This is a placeholder
func builtinDate_toLocaleString(call FunctionCall) Value {
// This is a placeholder.
func builtinDateToLocaleString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject())
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
func builtinDate_toLocaleDateString(call FunctionCall) Value {
// This is a placeholder.
func builtinDateToLocaleDateString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject())
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
func builtinDate_toLocaleTimeString(call FunctionCall) Value {
// This is a placeholder.
func builtinDateToLocaleTimeString(call FunctionCall) Value {
date := dateObjectOf(call.runtime, call.thisObject())
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())
if date.isNaN {
return NaNValue()
@ -200,155 +197,155 @@ func builtinDate_valueOf(call FunctionCall) Value {
return date.Value()
}
func builtinDate_getYear(call FunctionCall) Value {
func builtinDateGetYear(call FunctionCall) Value {
// Will throw a TypeError is ThisObject is nil or
// does not have Class of "Date"
date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN {
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
// does not have Class of "Date"
date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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
date := dateObjectOf(call.runtime, call.thisObject())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
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())
if date.isNaN {
return NaNValue()
}
timeLocal := date.Time().Local()
timeLocal := date.Time().Local() //nolint: gosmopolitan
// Is this kosher?
timeLocalAsUTC := Time.Date(
timeLocalAsUTC := time.Date(
timeLocal.Year(),
timeLocal.Month(),
timeLocal.Day(),
@ -356,13 +353,13 @@ func builtinDate_getTimezoneOffset(call FunctionCall) Value {
timeLocal.Minute(),
timeLocal.Second(),
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 {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, true)
func builtinDateSetMilliseconds(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, true)
if ecmaTime == nil {
return NaNValue()
}
@ -370,12 +367,12 @@ func builtinDate_setMilliseconds(call FunctionCall) Value {
ecmaTime.millisecond = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCMilliseconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, false)
func builtinDateSetUTCMilliseconds(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, false)
if ecmaTime == nil {
return NaNValue()
}
@ -383,12 +380,12 @@ func builtinDate_setUTCMilliseconds(call FunctionCall) Value {
ecmaTime.millisecond = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setSeconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, true)
func builtinDateSetSeconds(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, true)
if ecmaTime == nil {
return NaNValue()
}
@ -399,12 +396,12 @@ func builtinDate_setSeconds(call FunctionCall) Value {
ecmaTime.second = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCSeconds(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, false)
func builtinDateSetUTCSeconds(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, false)
if ecmaTime == nil {
return NaNValue()
}
@ -415,12 +412,12 @@ func builtinDate_setUTCSeconds(call FunctionCall) Value {
ecmaTime.second = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setMinutes(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, true)
func builtinDateSetMinutes(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, true)
if ecmaTime == nil {
return NaNValue()
}
@ -434,12 +431,12 @@ func builtinDate_setMinutes(call FunctionCall) Value {
ecmaTime.minute = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCMinutes(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, false)
func builtinDateSetUTCMinutes(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, false)
if ecmaTime == nil {
return NaNValue()
}
@ -453,58 +450,58 @@ func builtinDate_setUTCMinutes(call FunctionCall) Value {
ecmaTime.minute = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setHours(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 4, true)
func builtinDateSetHours(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 4, true)
if ecmaTime == nil {
return NaNValue()
}
if len(value) > 3 {
switch {
case len(value) > 3:
ecmaTime.millisecond = value[3]
fallthrough
case len(value) > 2:
ecmaTime.second = value[2]
ecmaTime.minute = value[1]
} else if len(value) > 2 {
ecmaTime.second = value[2]
ecmaTime.minute = value[1]
} else if len(value) > 1 {
fallthrough
case len(value) > 1:
ecmaTime.minute = value[1]
}
ecmaTime.hour = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCHours(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 4, false)
func builtinDateSetUTCHours(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 4, false)
if ecmaTime == nil {
return NaNValue()
}
if len(value) > 3 {
switch {
case len(value) > 3:
ecmaTime.millisecond = value[3]
fallthrough
case len(value) > 2:
ecmaTime.second = value[2]
ecmaTime.minute = value[1]
} else if len(value) > 2 {
ecmaTime.second = value[2]
ecmaTime.minute = value[1]
} else if len(value) > 1 {
fallthrough
case len(value) > 1:
ecmaTime.minute = value[1]
}
ecmaTime.hour = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setDate(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, true)
func builtinDateSetDate(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, true)
if ecmaTime == nil {
return NaNValue()
}
@ -512,12 +509,12 @@ func builtinDate_setDate(call FunctionCall) Value {
ecmaTime.day = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCDate(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, false)
func builtinDateSetUTCDate(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, false)
if ecmaTime == nil {
return NaNValue()
}
@ -525,12 +522,12 @@ func builtinDate_setUTCDate(call FunctionCall) Value {
ecmaTime.day = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setMonth(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, true)
func builtinDateSetMonth(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, true)
if ecmaTime == nil {
return NaNValue()
}
@ -541,12 +538,12 @@ func builtinDate_setMonth(call FunctionCall) Value {
ecmaTime.month = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCMonth(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 2, false)
func builtinDateSetUTCMonth(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 2, false)
if ecmaTime == nil {
return NaNValue()
}
@ -557,12 +554,12 @@ func builtinDate_setUTCMonth(call FunctionCall) Value {
ecmaTime.month = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setYear(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 1, true)
func builtinDateSetYear(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 1, true)
if ecmaTime == nil {
return NaNValue()
}
@ -574,12 +571,12 @@ func builtinDate_setYear(call FunctionCall) Value {
ecmaTime.year = year
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setFullYear(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, true)
func builtinDateSetFullYear(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, true)
if ecmaTime == nil {
return NaNValue()
}
@ -593,12 +590,12 @@ func builtinDate_setFullYear(call FunctionCall) Value {
ecmaTime.year = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}
func builtinDate_setUTCFullYear(call FunctionCall) Value {
object, date, ecmaTime, value := _builtinDate_beforeSet(call, 3, false)
func builtinDateSetUTCFullYear(call FunctionCall) Value {
obj, date, ecmaTime, value := builtinDateBeforeSet(call, 3, false)
if ecmaTime == nil {
return NaNValue()
}
@ -612,7 +609,7 @@ func builtinDate_setUTCFullYear(call FunctionCall) Value {
ecmaTime.year = value[0]
date.SetTime(ecmaTime.goTime())
object.value = *date
obj.value = *date
return date.Value()
}

View file

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

View file

@ -11,11 +11,11 @@ import (
// Function
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 {
return toValue_object(builtinNewFunctionNative(self.runtime, argumentList))
func builtinNewFunction(obj *object, argumentList []Value) Value {
return objectValue(builtinNewFunctionNative(obj.runtime, argumentList))
}
func argumentList2parameterList(argumentList []Value) []string {
@ -29,10 +29,9 @@ func argumentList2parameterList(argumentList []Value) []string {
return parameterList
}
func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object {
func builtinNewFunctionNative(rt *runtime, argumentList []Value) *object {
var parameterList, body string
count := len(argumentList)
if count > 0 {
if count := len(argumentList); count > 0 {
tmp := make([]string, 0, count-1)
for _, value := range argumentList[0 : count-1] {
tmp = append(tmp, value.string())
@ -43,35 +42,35 @@ func builtinNewFunctionNative(runtime *_runtime, argumentList []Value) *_object
// FIXME
function, err := parser.ParseFunction(parameterList, body)
runtime.parseThrow(err) // Will panic/throw appropriately
cmpl := _compiler{}
cmpl_function := cmpl.parseExpression(function)
rt.parseThrow(err) // Will panic/throw appropriately
cmpl := compiler{}
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 {
object := call.thisClassObject(classFunction) // Should throw a TypeError unless Function
switch fn := object.value.(type) {
case _nativeFunctionObject:
return toValue_string(fmt.Sprintf("function %s() { [native code] }", fn.name))
case _nodeFunctionObject:
return toValue_string(fn.node.source)
case _bindFunctionObject:
return toValue_string("function () { [native code] }")
func builtinFunctionToString(call FunctionCall) Value {
obj := call.thisClassObject(classFunctionName) // Should throw a TypeError unless Function
switch fn := obj.value.(type) {
case nativeFunctionObject:
return stringValue(fmt.Sprintf("function %s() { [native code] }", fn.name))
case nodeFunctionObject:
return stringValue(fn.node.source)
case bindFunctionObject:
return stringValue("function () { [native code] }")
default:
panic(call.runtime.panicTypeError("Function.toString unknown type %T", obj.value))
}
panic(call.runtime.panicTypeError("Function.toString()"))
}
func builtinFunction_apply(call FunctionCall) Value {
func builtinFunctionApply(call FunctionCall) Value {
if !call.This.isCallable() {
panic(call.runtime.panicTypeError())
panic(call.runtime.panicTypeError("Function.apply %q is not callable", call.This))
}
this := call.Argument(0)
if this.IsUndefined() {
// FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject)
this = objectValue(call.runtime.globalObject)
}
argumentList := call.Argument(1)
switch argumentList.kind {
@ -79,10 +78,10 @@ func builtinFunction_apply(call FunctionCall) Value {
return call.thisObject().call(this, nil, false, nativeFrame)
case valueObject:
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()
length := int64(toUint32(arrayObject.get(propertyLength)))
valueArray := make([]Value, length)
@ -92,15 +91,15 @@ func builtinFunction_apply(call FunctionCall) Value {
return thisObject.call(this, valueArray, false, nativeFrame)
}
func builtinFunction_call(call FunctionCall) Value {
func builtinFunctionCall(call FunctionCall) Value {
if !call.This.isCallable() {
panic(call.runtime.panicTypeError())
panic(call.runtime.panicTypeError("Function.call %q is not callable", call.This))
}
thisObject := call.thisObject()
this := call.Argument(0)
if this.IsUndefined() {
// FIXME Not ECMA5
this = toValue_object(call.runtime.globalObject)
this = objectValue(call.runtime.globalObject)
}
if len(call.ArgumentList) >= 1 {
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)
}
func builtinFunction_bind(call FunctionCall) Value {
func builtinFunctionBind(call FunctionCall) Value {
target := call.This
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)
argumentList := call.slice(1)
if this.IsUndefined() {
// 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"
)
type _builtinJSON_parseContext struct {
type builtinJSONParseContext struct {
call FunctionCall
reviver Value
}
func builtinJSON_parse(call FunctionCall) Value {
ctx := _builtinJSON_parseContext{
func builtinJSONParse(call FunctionCall) Value {
ctx := builtinJSONParseContext{
call: call,
}
revive := false
@ -27,109 +27,107 @@ func builtinJSON_parse(call FunctionCall) Value {
if err != nil {
panic(call.runtime.panicSyntaxError(err.Error()))
}
value, exists := builtinJSON_parseWalk(ctx, root)
value, exists := builtinJSONParseWalk(ctx, root)
if !exists {
value = Value{}
}
if revive {
root := ctx.call.runtime.newObject()
root.put("", value, false)
return builtinJSON_reviveWalk(ctx, root, "")
return builtinJSONReviveWalk(ctx, root, "")
}
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)
if object := value._object(); object != nil {
if isArray(object) {
length := int64(objectLength(object))
for index := int64(0); index < length; index += 1 {
if obj := value.object(); obj != nil {
if isArray(obj) {
length := int64(objectLength(obj))
for index := int64(0); index < length; index++ {
name := arrayIndexToString(index)
value := builtinJSON_reviveWalk(ctx, object, name)
value := builtinJSONReviveWalk(ctx, obj, name)
if value.IsUndefined() {
object.delete(name, false)
obj.delete(name, false)
} else {
object.defineProperty(name, value, 0111, false)
obj.defineProperty(name, value, 0o111, false)
}
}
} else {
object.enumerate(false, func(name string) bool {
value := builtinJSON_reviveWalk(ctx, object, name)
obj.enumerate(false, func(name string) bool {
value := builtinJSONReviveWalk(ctx, obj, name)
if value.IsUndefined() {
object.delete(name, false)
obj.delete(name, false)
} else {
object.defineProperty(name, value, 0111, false)
obj.defineProperty(name, value, 0o111, false)
}
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) {
case nil:
return nullValue, true
case bool:
return toValue_bool(value), true
return boolValue(value), true
case string:
return toValue_string(value), true
return stringValue(value), true
case float64:
return toValue_float64(value), true
return float64Value(value), true
case []interface{}:
arrayValue := make([]Value, len(value))
for index, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
if value, exists := builtinJSONParseWalk(ctx, rawValue); exists {
arrayValue[index] = value
}
}
return toValue_object(ctx.call.runtime.newArrayOf(arrayValue)), true
return objectValue(ctx.call.runtime.newArrayOf(arrayValue)), true
case map[string]interface{}:
object := ctx.call.runtime.newObject()
obj := ctx.call.runtime.newObject()
for name, rawValue := range value {
if value, exists := builtinJSON_parseWalk(ctx, rawValue); exists {
object.put(name, value, false)
if value, exists := builtinJSONParseWalk(ctx, rawValue); exists {
obj.put(name, value, false)
}
}
return toValue_object(object), true
return objectValue(obj), true
}
return Value{}, false
}
type _builtinJSON_stringifyContext struct {
type builtinJSONStringifyContext struct {
call FunctionCall
stack []*_object
stack []*object
propertyList []string
replacerFunction *Value
gap string
}
func builtinJSON_stringify(call FunctionCall) Value {
ctx := _builtinJSON_stringifyContext{
func builtinJSONStringify(call FunctionCall) Value {
ctx := builtinJSONStringifyContext{
call: call,
stack: []*_object{nil},
stack: []*object{nil},
}
replacer := call.Argument(1)._object()
replacer := call.Argument(1).object()
if replacer != nil {
if isArray(replacer) {
length := objectLength(replacer)
seen := map[string]bool{}
propertyList := make([]string, length)
length = 0
for index, _ := range propertyList {
for index := range propertyList {
value := replacer.get(arrayIndexToString(int64(index)))
switch value.kind {
case valueObject:
switch value.value.(*_object).class {
case classString:
case classNumber:
switch value.value.(*object).class {
case classStringName, classNumberName:
default:
continue
}
case valueString:
case valueNumber:
case valueString, valueNumber:
default:
continue
}
@ -138,21 +136,21 @@ func builtinJSON_stringify(call FunctionCall) Value {
continue
}
seen[name] = true
length += 1
length++
propertyList[index] = name
}
ctx.propertyList = propertyList[0:length]
} else if replacer.class == classFunction {
value := toValue_object(replacer)
} else if replacer.class == classFunctionName {
value := objectValue(replacer)
ctx.replacerFunction = &value
}
}
if spaceValue, exists := call.getArgument(2); exists {
if spaceValue.kind == valueObject {
switch spaceValue.value.(*_object).class {
case classString:
spaceValue = toValue_string(spaceValue.string())
case classNumber:
switch spaceValue.value.(*object).class {
case classStringName:
spaceValue = stringValue(spaceValue.string())
case classNumberName:
spaceValue = spaceValue.numberValue()
}
}
@ -176,51 +174,51 @@ func builtinJSON_stringify(call FunctionCall) Value {
}
holder := call.runtime.newObject()
holder.put("", call.Argument(0), false)
value, exists := builtinJSON_stringifyWalk(ctx, "", holder)
value, exists := builtinJSONStringifyWalk(ctx, "", holder)
if !exists {
return Value{}
}
valueJSON, err := json.Marshal(value)
if err != nil {
panic(call.runtime.panicTypeError(err.Error()))
panic(call.runtime.panicTypeError("JSON.stringify marshal: %s", err))
}
if ctx.gap != "" {
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()
}
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)
if value.IsObject() {
object := value._object()
if toJSON := object.get("toJSON"); toJSON.IsFunction() {
obj := value.object()
if toJSON := obj.get("toJSON"); toJSON.IsFunction() {
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 object.objectClass.marshalJSON != nil {
marshaler := object.objectClass.marshalJSON(object)
if marshaler != nil {
return marshaler, true
}
marshaler := obj.objectClass.marshalJSON(obj)
if marshaler != nil {
return marshaler, true
}
}
}
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 {
switch value.value.(*_object).class {
case classBoolean:
value = value._object().value.(Value)
case classString:
value = toValue_string(value.string())
case classNumber:
switch value.value.(*object).class {
case classBooleanName:
value = value.object().value.(Value)
case classStringName:
value = stringValue(value.string())
case classNumberName:
value = value.numberValue()
}
}
@ -243,10 +241,10 @@ func builtinJSON_stringifyWalk(ctx _builtinJSON_stringifyContext, key string, ho
case valueNull:
return nil, true
case valueObject:
holder := value._object()
if value := value._object(); nil != value {
for _, object := range ctx.stack {
if holder == object {
holder := value.object()
if value := value.object(); nil != value {
for _, obj := range ctx.stack {
if holder == obj {
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)))
}
array := make([]interface{}, length)
for index, _ := range array {
for index := range array {
name := arrayIndexToString(int64(index))
value, _ := builtinJSON_stringifyWalk(ctx, name, holder)
value, _ := builtinJSONStringifyWalk(ctx, name, holder)
array[index] = value
}
return array, true
} else if holder.class != classFunction {
object := map[string]interface{}{}
} else if holder.class != classFunctionName {
obj := map[string]interface{}{}
if ctx.propertyList != nil {
for _, name := range ctx.propertyList {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
value, exists := builtinJSONStringifyWalk(ctx, name, holder)
if exists {
object[name] = value
obj[name] = value
}
}
} else {
// Go maps are without order, so this doesn't conform to the ECMA ordering
// standard, but oh well...
holder.enumerate(false, func(name string) bool {
value, exists := builtinJSON_stringifyWalk(ctx, name, holder)
value, exists := builtinJSONStringifyWalk(ctx, name, holder)
if exists {
object[name] = value
obj[name] = value
}
return true
})
}
return object, true
return obj, true
}
}
return nil, false

View file

@ -7,27 +7,37 @@ import (
// Math
func builtinMath_abs(call FunctionCall) Value {
func builtinMathAbs(call FunctionCall) Value {
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()
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()
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()
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()
if math.IsNaN(y) {
return NaNValue()
@ -36,40 +46,75 @@ func builtinMath_atan2(call FunctionCall) Value {
if math.IsNaN(x) {
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()
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()
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()
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()
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()
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) {
case 0:
return negativeInfinityValue()
case 1:
return toValue_float64(call.ArgumentList[0].float64())
return float64Value(call.ArgumentList[0].float64())
}
result := call.ArgumentList[0].float64()
if math.IsNaN(result) {
@ -82,15 +127,15 @@ func builtinMath_max(call FunctionCall) 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) {
case 0:
return positiveInfinityValue()
case 1:
return toValue_float64(call.ArgumentList[0].float64())
return float64Value(call.ArgumentList[0].float64())
}
result := call.ArgumentList[0].float64()
if math.IsNaN(result) {
@ -103,49 +148,64 @@ func builtinMath_min(call FunctionCall) 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)
x := call.Argument(0).float64()
y := call.Argument(1).float64()
if math.Abs(x) == 1 && math.IsInf(y, 0) {
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
if call.runtime.random != nil {
v = call.runtime.random()
} 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()
value := math.Floor(number + 0.5)
if value == 0 {
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()
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()
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()
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 {
return argumentList[0].numberValue()
}
return toValue_int(0)
return intValue(0)
}
func builtinNumber(call FunctionCall) Value {
return numberValueFromNumberArgumentList(call.ArgumentList)
}
func builtinNewNumber(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newNumber(numberValueFromNumberArgumentList(argumentList)))
func builtinNewNumber(obj *object, argumentList []Value) Value {
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
value := call.thisClassObject(classNumber).primitiveValue()
value := call.thisClassObject(classNumberName).primitiveValue()
radix := 10
radixArgument := call.Argument(0)
if radixArgument.IsDefined() {
@ -39,33 +39,32 @@ func builtinNumber_toString(call FunctionCall) Value {
radix = int(integer)
}
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 {
return call.thisClassObject(classNumber).primitiveValue()
func builtinNumberValueOf(call FunctionCall) Value {
return call.thisClassObject(classNumberName).primitiveValue()
}
func builtinNumber_toFixed(call FunctionCall) Value {
func builtinNumberToFixed(call FunctionCall) Value {
precision := toIntegerFloat(call.Argument(0))
if 20 < precision || 0 > precision {
panic(call.runtime.panicRangeError("toFixed() precision must be between 0 and 20"))
}
if call.This.IsNaN() {
return toValue_string("NaN")
return stringValue("NaN")
}
value := call.This.float64()
if math.Abs(value) >= 1e21 {
return toValue_string(floatToString(value, 64))
if value := call.This.float64(); math.Abs(value) >= 1e21 {
return stringValue(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() {
return toValue_string("NaN")
return stringValue("NaN")
}
precision := float64(-1)
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"))
}
}
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() {
return toValue_string("NaN")
return stringValue("NaN")
}
value := call.Argument(0)
if value.IsUndefined() {
return toValue_string(call.This.string())
return stringValue(call.This.string())
}
precision := toIntegerFloat(value)
if 1 > precision {
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 {
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 {
value := call.thisClassObject(classNumber).primitiveValue()
func builtinNumberToLocaleString(call FunctionCall) Value {
value := call.thisClassObject(classNumberName).primitiveValue()
locale := call.Argument(0)
lang := defaultLanguage
if locale.IsDefined() {
@ -108,5 +107,5 @@ func builtinNumber_toLocaleString(call FunctionCall) Value {
}
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)
switch value.kind {
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)
switch value.kind {
case valueNull, valueUndefined:
case valueNumber, valueString, valueBoolean:
return toValue_object(self.runtime.toObject(value))
return objectValue(obj.runtime.toObject(value))
case valueObject:
return value
default:
}
return toValue_object(self.runtime.newObject())
return objectValue(obj.runtime.newObject())
}
func builtinObject_valueOf(call FunctionCall) Value {
return toValue_object(call.thisObject())
func builtinObjectValueOf(call FunctionCall) Value {
return objectValue(call.thisObject())
}
func builtinObject_hasOwnProperty(call FunctionCall) Value {
func builtinObjectHasOwnProperty(call FunctionCall) Value {
propertyName := call.Argument(0).string()
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)
if !value.IsObject() {
return falseValue
@ -55,235 +55,235 @@ func builtinObject_isPrototypeOf(call FunctionCall) Value {
return falseValue
}
func builtinObject_propertyIsEnumerable(call FunctionCall) Value {
func builtinObjectPropertyIsEnumerable(call FunctionCall) Value {
propertyName := call.Argument(0).string()
thisObject := call.thisObject()
property := thisObject.getOwnProperty(propertyName)
if property != nil && property.enumerable() {
prop := thisObject.getOwnProperty(propertyName)
if prop != nil && prop.enumerable() {
return trueValue
}
return falseValue
}
func builtinObject_toString(call FunctionCall) Value {
func builtinObjectToString(call FunctionCall) Value {
var result string
if call.This.IsUndefined() {
switch {
case call.This.IsUndefined():
result = "[object Undefined]"
} else if call.This.IsNull() {
case call.This.IsNull():
result = "[object Null]"
} else {
default:
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")
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)
}
func builtinObject_getPrototypeOf(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
func builtinObjectGetPrototypeOf(call FunctionCall) Value {
val := call.Argument(0)
obj := val.object()
if obj == nil {
panic(call.runtime.panicTypeError("Object.GetPrototypeOf is nil"))
}
if object.prototype == nil {
if obj.prototype == nil {
return nullValue
}
return toValue_object(object.prototype)
return objectValue(obj.prototype)
}
func builtinObject_getOwnPropertyDescriptor(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
func builtinObjectGetOwnPropertyDescriptor(call FunctionCall) Value {
val := call.Argument(0)
obj := val.object()
if obj == nil {
panic(call.runtime.panicTypeError("Object.GetOwnPropertyDescriptor is nil"))
}
name := call.Argument(1).string()
descriptor := object.getOwnProperty(name)
descriptor := obj.getOwnProperty(name)
if descriptor == nil {
return Value{}
}
return toValue_object(call.runtime.fromPropertyDescriptor(*descriptor))
return objectValue(call.runtime.fromPropertyDescriptor(*descriptor))
}
func builtinObject_defineProperty(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
func builtinObjectDefineProperty(call FunctionCall) Value {
val := call.Argument(0)
obj := val.object()
if obj == nil {
panic(call.runtime.panicTypeError("Object.DefineProperty is nil"))
}
name := call.Argument(1).string()
descriptor := toPropertyDescriptor(call.runtime, call.Argument(2))
object.defineOwnProperty(name, descriptor, true)
return objectValue
obj.defineOwnProperty(name, descriptor, true)
return val
}
func builtinObject_defineProperties(call FunctionCall) Value {
objectValue := call.Argument(0)
object := objectValue._object()
if object == nil {
panic(call.runtime.panicTypeError())
func builtinObjectDefineProperties(call FunctionCall) Value {
val := call.Argument(0)
obj := val.object()
if obj == nil {
panic(call.runtime.panicTypeError("Object.DefineProperties is nil"))
}
properties := call.runtime.toObject(call.Argument(1))
properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true)
obj.defineOwnProperty(name, descriptor, true)
return true
})
return objectValue
return val
}
func builtinObject_create(call FunctionCall) Value {
func builtinObjectCreate(call FunctionCall) Value {
prototypeValue := call.Argument(0)
if !prototypeValue.IsNull() && !prototypeValue.IsObject() {
panic(call.runtime.panicTypeError())
panic(call.runtime.panicTypeError("Object.Create is nil"))
}
object := call.runtime.newObject()
object.prototype = prototypeValue._object()
obj := call.runtime.newObject()
obj.prototype = prototypeValue.object()
propertiesValue := call.Argument(1)
if propertiesValue.IsDefined() {
properties := call.runtime.toObject(propertiesValue)
properties.enumerate(false, func(name string) bool {
descriptor := toPropertyDescriptor(call.runtime, properties.get(name))
object.defineOwnProperty(name, descriptor, true)
obj.defineOwnProperty(name, descriptor, true)
return true
})
}
return toValue_object(object)
return objectValue(obj)
}
func builtinObject_isExtensible(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
return toValue_bool(object.extensible)
func builtinObjectIsExtensible(call FunctionCall) Value {
val := call.Argument(0)
if obj := val.object(); obj != nil {
return boolValue(obj.extensible)
}
panic(call.runtime.panicTypeError())
panic(call.runtime.panicTypeError("Object.IsExtensible is nil"))
}
func builtinObject_preventExtensions(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
func builtinObjectPreventExtensions(call FunctionCall) Value {
val := call.Argument(0)
if obj := val.object(); obj != nil {
obj.extensible = false
return val
}
return object
panic(call.runtime.panicTypeError("Object.PreventExtensions is nil"))
}
func builtinObject_isSealed(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
if object.extensible {
return toValue_bool(false)
func builtinObjectIsSealed(call FunctionCall) Value {
val := call.Argument(0)
if obj := val.object(); obj != nil {
if obj.extensible {
return boolValue(false)
}
result := true
object.enumerate(true, func(name string) bool {
property := object.getProperty(name)
if property.configurable() {
obj.enumerate(true, func(name string) bool {
prop := obj.getProperty(name)
if prop.configurable() {
result = false
}
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 {
object := call.Argument(0)
if object := object._object(); object != nil {
object.enumerate(true, func(name string) bool {
if property := object.getOwnProperty(name); nil != property && property.configurable() {
property.configureOff()
object.defineOwnProperty(name, *property, true)
func builtinObjectSeal(call FunctionCall) Value {
val := call.Argument(0)
if obj := val.object(); obj != nil {
obj.enumerate(true, func(name string) bool {
if prop := obj.getOwnProperty(name); nil != prop && prop.configurable() {
prop.configureOff()
obj.defineOwnProperty(name, *prop, true)
}
return true
})
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
obj.extensible = false
return val
}
return object
panic(call.runtime.panicTypeError("Object.Seal is nil"))
}
func builtinObject_isFrozen(call FunctionCall) Value {
object := call.Argument(0)
if object := object._object(); object != nil {
if object.extensible {
return toValue_bool(false)
func builtinObjectIsFrozen(call FunctionCall) Value {
val := call.Argument(0)
if obj := val.object(); obj != nil {
if obj.extensible {
return boolValue(false)
}
result := true
object.enumerate(true, func(name string) bool {
property := object.getProperty(name)
if property.configurable() || property.writable() {
obj.enumerate(true, func(name string) bool {
prop := obj.getProperty(name)
if prop.configurable() || prop.writable() {
result = false
}
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 {
object := call.Argument(0)
if object := object._object(); object != nil {
object.enumerate(true, func(name string) bool {
if property, update := object.getOwnProperty(name), false; nil != property {
if property.isDataDescriptor() && property.writable() {
property.writeOff()
func builtinObjectFreeze(call FunctionCall) Value {
val := call.Argument(0)
if obj := val.object(); obj != nil {
obj.enumerate(true, func(name string) bool {
if prop, update := obj.getOwnProperty(name), false; nil != prop {
if prop.isDataDescriptor() && prop.writable() {
prop.writeOff()
update = true
}
if property.configurable() {
property.configureOff()
if prop.configurable() {
prop.configureOff()
update = true
}
if update {
object.defineOwnProperty(name, *property, true)
obj.defineOwnProperty(name, *prop, true)
}
}
return true
})
object.extensible = false
} else {
panic(call.runtime.panicTypeError())
obj.extensible = false
return val
}
return object
panic(call.runtime.panicTypeError("Object.Freeze is nil"))
}
func builtinObject_keys(call FunctionCall) Value {
if object, keys := call.Argument(0)._object(), []Value(nil); nil != object {
object.enumerate(false, func(name string) bool {
keys = append(keys, toValue_string(name))
func builtinObjectKeys(call FunctionCall) Value {
if obj, keys := call.Argument(0).object(), []Value(nil); nil != obj {
obj.enumerate(false, func(name string) bool {
keys = append(keys, stringValue(name))
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 {
if object, propertyNames := call.Argument(0)._object(), []Value(nil); nil != object {
object.enumerate(true, func(name string) bool {
if object.hasOwnProperty(name) {
propertyNames = append(propertyNames, toValue_string(name))
func builtinObjectGetOwnPropertyNames(call FunctionCall) Value {
if obj, propertyNames := call.Argument(0).object(), []Value(nil); nil != obj {
obj.enumerate(true, func(name string) bool {
if obj.hasOwnProperty(name) {
propertyNames = append(propertyNames, stringValue(name))
}
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 {
pattern := call.Argument(0)
flags := call.Argument(1)
if object := pattern._object(); object != nil {
if object.class == classRegExp && flags.IsUndefined() {
if obj := pattern.object(); obj != nil {
if obj.class == classRegExpName && flags.IsUndefined() {
return pattern
}
}
return toValue_object(call.runtime.newRegExp(pattern, flags))
return objectValue(call.runtime.newRegExp(pattern, flags))
}
func builtinNewRegExp(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newRegExp(
func builtinNewRegExp(obj *object, argumentList []Value) Value {
return objectValue(obj.runtime.newRegExp(
valueOfArrayIndex(argumentList, 0),
valueOfArrayIndex(argumentList, 1),
))
}
func builtinRegExp_toString(call FunctionCall) Value {
func builtinRegExpToString(call FunctionCall) Value {
thisObject := call.thisObject()
source := thisObject.get("source").string()
flags := []byte{}
@ -37,41 +37,47 @@ func builtinRegExp_toString(call FunctionCall) Value {
if thisObject.get("multiline").bool() {
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()
target := call.Argument(0).string()
match, result := execRegExp(thisObject, target)
if !match {
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()
target := call.Argument(0).string()
match, result := execRegExp(thisObject, target)
if !match {
return toValue_bool(match)
return boolValue(match)
}
// Match extract and assign input, $_ and $1 -> $9 on global RegExp.
input := toValue_string(target)
call.runtime.global.RegExp.defineProperty("$_", input, 0100, false)
call.runtime.global.RegExp.defineProperty("input", input, 0100, false)
input := stringValue(target)
call.runtime.global.RegExp.defineProperty("$_", input, 0o100, false)
call.runtime.global.RegExp.defineProperty("input", input, 0o100, false)
var start int
n := 1
re := call.runtime.global.RegExp
empty := stringValue("")
for i, v := range result[2:] {
if i%2 == 0 {
start = v
} 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++
if n == 10 {
break
@ -81,16 +87,15 @@ func builtinRegExp_test(call FunctionCall) Value {
if n <= 9 {
// Erase remaining.
empty := toValue_string("")
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
// semblance of compatibility.
// Caveat emptor: it may not be around for long.

View file

@ -13,62 +13,63 @@ import (
func stringValueFromStringArgumentList(argumentList []Value) Value {
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 {
return stringValueFromStringArgumentList(call.ArgumentList)
}
func builtinNewString(self *_object, argumentList []Value) Value {
return toValue_object(self.runtime.newString(stringValueFromStringArgumentList(argumentList)))
func builtinNewString(obj *object, argumentList []Value) Value {
return objectValue(obj.runtime.newString(stringValueFromStringArgumentList(argumentList)))
}
func builtinString_toString(call FunctionCall) Value {
return call.thisClassObject(classString).primitiveValue()
}
func builtinString_valueOf(call FunctionCall) Value {
return call.thisClassObject(classString).primitiveValue()
func builtinStringToString(call FunctionCall) Value {
return call.thisClassObject(classStringName).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))
for index, value := range call.ArgumentList {
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)
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 {
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)
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 {
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)
var value bytes.Buffer
value.WriteString(call.This.string())
for _, item := range call.ArgumentList {
value.WriteString(item.string())
}
return toValue_string(value.String())
return stringValue(value.String())
}
func lastIndexRune(s, substr string) int {
@ -89,44 +90,44 @@ func utf16Length(s string) int {
return len(utf16.Encode([]rune(s)))
}
func builtinString_indexOf(call FunctionCall) Value {
func builtinStringIndexOf(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
value := call.This.string()
target := call.Argument(0).string()
if 2 > len(call.ArgumentList) {
return toValue_int(indexRune(value, target))
return intValue(indexRune(value, target))
}
start := toIntegerFloat(call.Argument(1))
if 0 > start {
start = 0
} else if start >= float64(len(value)) {
if target == "" {
return toValue_int(len(value))
return intValue(len(value))
}
return toValue_int(-1)
return intValue(-1)
}
index := indexRune(value[int(start):], target)
if index >= 0 {
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)
value := call.This.string()
target := call.Argument(0).string()
if 2 > len(call.ArgumentList) || call.ArgumentList[1].IsUndefined() {
return toValue_int(lastIndexRune(value, target))
return intValue(lastIndexRune(value, target))
}
length := len(value)
if length == 0 {
return toValue_int(lastIndexRune(value, target))
return intValue(lastIndexRune(value, target))
}
start := call.ArgumentList[1].number()
if start.kind == numberInfinity { // FIXME
// 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 {
start.int64 = 0
@ -135,15 +136,15 @@ func builtinString_lastIndexOf(call FunctionCall) Value {
if 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)
target := call.This.string()
matcherValue := call.Argument(0)
matcher := matcherValue._object()
if !matcherValue.IsObject() || matcher.class != classRegExp {
matcher := matcherValue.object()
if !matcherValue.IsObject() || matcher.class != classRegExpName {
matcher = call.runtime.newRegExp(matcherValue, Value{})
}
global := matcher.get("global").bool()
@ -152,34 +153,32 @@ func builtinString_match(call FunctionCall) Value {
if !match {
return nullValue
}
return toValue_object(execResultToArray(call.runtime, target, result))
return objectValue(execResultToArray(call.runtime, target, result))
}
{
result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
if result == nil {
matcher.put("lastIndex", toValue_int(0), true)
return Value{} // !match
}
matchCount := len(result)
valueArray := make([]Value, matchCount)
for index := 0; index < matchCount; index++ {
valueArray[index] = toValue_string(target[result[index][0]:result[index][1]])
}
matcher.put("lastIndex", toValue_int(result[matchCount-1][1]), true)
return toValue_object(call.runtime.newArrayOf(valueArray))
result := matcher.regExpValue().regularExpression.FindAllStringIndex(target, -1)
if result == nil {
matcher.put("lastIndex", intValue(0), true)
return Value{} // !match
}
matchCount := len(result)
valueArray := make([]Value, matchCount)
for index := 0; index < matchCount; index++ {
valueArray[index] = stringValue(target[result[index][0]:result[index][1]])
}
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
output = input
output := input
if match[0] != lastIndex {
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
switch part[1] {
case '$':
@ -193,37 +192,38 @@ func builtinString_findAndReplaceString(input []byte, lastIndex int, match []int
}
matchNumberParse, err := strconv.ParseInt(string(part[1:]), 10, 64)
if err != nil {
return []byte{}
return nil
}
matchNumber := int(matchNumberParse)
if matchNumber >= matchCount {
return []byte{}
return nil
}
offset := 2 * matchNumber
if 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)
target := []byte(call.This.string())
searchValue := call.Argument(0)
searchObject := searchValue._object()
searchObject := searchValue.object()
// TODO If a capture is -1?
var search *regexp.Regexp
global := false
find := 1
if searchValue.IsObject() && searchObject.class == classRegExp {
if searchValue.IsObject() && searchObject.class == classRegExpName {
regExp := searchObject.regExpValue()
search = regExp.regularExpression
if regExp.global {
find = -1
global = true
}
} else {
search = regexp.MustCompile(regexp.QuoteMeta(searchValue.string()))
@ -231,73 +231,70 @@ func builtinString_replace(call FunctionCall) Value {
found := search.FindAllSubmatchIndex(target, find)
if found == nil {
return toValue_string(string(target)) // !match
return stringValue(string(target)) // !match
}
{
lastIndex := 0
result := []byte{}
replaceValue := call.Argument(1)
if replaceValue.isCallable() {
target := string(target)
replace := replaceValue._object()
for _, match := range found {
if match[0] != lastIndex {
result = append(result, target[lastIndex:match[0]]...)
}
matchCount := len(match) / 2
argumentList := make([]Value, matchCount+2)
for index := 0; index < matchCount; index++ {
offset := 2 * index
if match[offset] != -1 {
argumentList[index] = toValue_string(target[match[offset]:match[offset+1]])
} else {
argumentList[index] = Value{}
}
}
argumentList[matchCount+0] = toValue_int(match[0])
argumentList[matchCount+1] = toValue_string(target)
replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
result = append(result, []byte(replacement)...)
lastIndex = match[1]
lastIndex := 0
result := []byte{}
replaceValue := call.Argument(1)
if replaceValue.isCallable() {
target := string(target)
replace := replaceValue.object()
for _, match := range found {
if match[0] != lastIndex {
result = append(result, target[lastIndex:match[0]]...)
}
} else {
replace := []byte(replaceValue.string())
for _, match := range found {
result = builtinString_findAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1]
matchCount := len(match) / 2
argumentList := make([]Value, matchCount+2)
for index := 0; index < matchCount; index++ {
offset := 2 * index
if match[offset] != -1 {
argumentList[index] = stringValue(target[match[offset]:match[offset+1]])
} else {
argumentList[index] = Value{}
}
}
argumentList[matchCount+0] = intValue(match[0])
argumentList[matchCount+1] = stringValue(target)
replacement := replace.call(Value{}, argumentList, false, nativeFrame).string()
result = append(result, []byte(replacement)...)
lastIndex = match[1]
}
if lastIndex != len(target) {
result = append(result, target[lastIndex:]...)
} else {
replace := []byte(replaceValue.string())
for _, match := range found {
result = builtinStringFindAndReplaceString(result, lastIndex, match, target, replace)
lastIndex = match[1]
}
if global && searchObject != nil {
searchObject.put("lastIndex", toValue_int(lastIndex), true)
}
return toValue_string(string(result))
}
if lastIndex != len(target) {
result = append(result, target[lastIndex:]...)
}
if global && searchObject != nil {
searchObject.put("lastIndex", intValue(lastIndex), true)
}
return stringValue(string(result))
}
func builtinString_search(call FunctionCall) Value {
func builtinStringSearch(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
target := call.This.string()
searchValue := call.Argument(0)
search := searchValue._object()
if !searchValue.IsObject() || search.class != classRegExp {
search := searchValue.object()
if !searchValue.IsObject() || search.class != classRegExpName {
search = call.runtime.newRegExp(searchValue, Value{})
}
result := search.regExpValue().regularExpression.FindStringIndex(target)
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)
target := call.This.string()
@ -309,16 +306,16 @@ func builtinString_split(call FunctionCall) Value {
}
if limit == 0 {
return toValue_object(call.runtime.newArray(0))
return objectValue(call.runtime.newArray(0))
}
if separatorValue.IsUndefined() {
return toValue_object(call.runtime.newArrayOf([]Value{toValue_string(target)}))
return objectValue(call.runtime.newArrayOf([]Value{stringValue(target)}))
}
if separatorValue.isRegExp() {
targetLength := len(target)
search := separatorValue._object().regExpValue().regularExpression
search := separatorValue.object().regExpValue().regularExpression
valueArray := []Value{}
result := search.FindAllStringSubmatchIndex(target, -1)
lastIndex := 0
@ -333,11 +330,11 @@ func builtinString_split(call FunctionCall) Value {
}
if lastIndex != match[0] {
valueArray = append(valueArray, toValue_string(target[lastIndex:match[0]]))
valueArray = append(valueArray, stringValue(target[lastIndex:match[0]]))
found++
} else if lastIndex == match[0] {
if lastIndex != -1 {
valueArray = append(valueArray, toValue_string(""))
valueArray = append(valueArray, stringValue(""))
found++
}
}
@ -352,7 +349,7 @@ func builtinString_split(call FunctionCall) Value {
offset := index * 2
value := Value{}
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)
found++
@ -364,14 +361,14 @@ func builtinString_split(call FunctionCall) Value {
if found != limit {
if lastIndex != targetLength {
valueArray = append(valueArray, toValue_string(target[lastIndex:targetLength]))
valueArray = append(valueArray, stringValue(target[lastIndex:targetLength]))
} else {
valueArray = append(valueArray, toValue_string(""))
valueArray = append(valueArray, stringValue(""))
}
}
RETURN:
return toValue_object(call.runtime.newArrayOf(valueArray))
return objectValue(call.runtime.newArrayOf(valueArray))
} else {
separator := separatorValue.string()
@ -390,26 +387,26 @@ func builtinString_split(call FunctionCall) Value {
valueArray := make([]Value, len(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)
target := call.This.string()
length := int64(len(target))
start, end := rangeStartEnd(call.ArgumentList, length, false)
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)
target := []rune(call.This.string())
@ -418,21 +415,21 @@ func builtinString_substring(call FunctionCall) Value {
if start > end {
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())
size := int64(len(target))
start, length := rangeStartLength(call.ArgumentList, size)
if start >= size {
return toValue_string("")
return stringValue("")
}
if length <= 0 {
return toValue_string("")
return stringValue("")
}
if start+length >= size {
@ -443,66 +440,69 @@ func builtinString_substr(call FunctionCall) Value {
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)
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)
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
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"
func builtinStringToUpperCase(call FunctionCall) Value {
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)
return toValue(strings.Trim(call.This.string(),
builtinString_trim_whitespace))
builtinStringTrimWhitespace))
}
// Mozilla extension, not ECMAScript 5
func builtinString_trimLeft(call FunctionCall) Value {
// Mozilla extension, not ECMAScript 5.
func builtinStringTrimLeft(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
return toValue(strings.TrimLeft(call.This.string(),
builtinString_trim_whitespace))
builtinStringTrimWhitespace))
}
// Mozilla extension, not ECMAScript 5
func builtinString_trimRight(call FunctionCall) Value {
// Mozilla extension, not ECMAScript 5.
func builtinStringTrimRight(call FunctionCall) Value {
checkObjectCoercible(call.runtime, call.This)
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)
this := call.This.string()
this := call.This.string() //nolint: ifshort
that := call.Argument(0).string()
if this < that {
return toValue_int(-1)
return intValue(-1)
} else if this == that {
return toValue_int(0)
return intValue(0)
}
return toValue_int(1)
return intValue(1)
}
/*
An alternate version of String.trim
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 builtinStringToLocaleLowerCase(call FunctionCall) Value {
return builtinStringToLowerCase(call)
}
func builtinString_toLocaleUpperCase(call FunctionCall) Value {
return builtinString_toUpperCase(call)
func builtinStringToLocaleUpperCase(call FunctionCall) Value {
return builtinStringToUpperCase(call)
}

View file

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

View file

@ -5,14 +5,7 @@ import (
"github.com/robertkrimen/otto/file"
)
type _compiler struct {
type compiler struct {
file *file.File
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"
)
func (self *_runtime) cmpl_evaluate_nodeProgram(node *_nodeProgram, eval bool) Value {
func (rt *runtime) cmplEvaluateNodeProgram(node *nodeProgram, eval bool) Value {
if !eval {
self.enterGlobalScope()
defer func() {
self.leaveScope()
}()
rt.enterGlobalScope()
defer rt.leaveScope()
}
self.cmpl_functionDeclaration(node.functionList)
self.cmpl_variableDeclaration(node.varList)
self.scope.frame.file = node.file
return self.cmpl_evaluate_nodeStatementList(node.body)
rt.cmplFunctionDeclaration(node.functionList)
rt.cmplVariableDeclaration(node.varList)
rt.scope.frame.file = node.file
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))
// function(abc, def, ghi)
// indexOfParameterName[0] = "abc"
@ -36,28 +34,28 @@ func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash,
indexOfParameterName[index] = name
}
// strict = false
self.scope.lexical.setValue(name, value, false)
rt.scope.lexical.setValue(name, value, false)
}
if !argumentsFound {
arguments := self.newArgumentsObject(indexOfParameterName, stash, len(argumentList))
arguments.defineProperty("callee", toValue_object(function), 0101, false)
arguments := rt.newArgumentsObject(indexOfParameterName, stash, len(argumentList))
arguments.defineProperty("callee", objectValue(function), 0o101, false)
stash.arguments = arguments
// strict = false
self.scope.lexical.setValue("arguments", toValue_object(arguments), false)
for index, _ := range argumentList {
rt.scope.lexical.setValue("arguments", objectValue(arguments), false)
for index := range argumentList {
if index < len(node.parameterList) {
continue
}
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)
self.cmpl_variableDeclaration(node.varList)
rt.cmplFunctionDeclaration(node.functionList)
rt.cmplVariableDeclaration(node.varList)
result := self.cmpl_evaluate_nodeStatement(node.body)
result := rt.cmplEvaluateNodeStatement(node.body)
if result.kind == valueResult {
return result
}
@ -65,16 +63,16 @@ func (self *_runtime) cmpl_call_nodeFunction(function *_object, stash *_fnStash,
return Value{}
}
func (self *_runtime) cmpl_functionDeclaration(list []*_nodeFunctionLiteral) {
executionContext := self.scope
func (rt *runtime) cmplFunctionDeclaration(list []*nodeFunctionLiteral) {
executionContext := rt.scope
eval := executionContext.eval
stash := executionContext.variable
for _, function := range list {
name := function.name
value := self.cmpl_evaluate_nodeExpression(function)
value := rt.cmplEvaluateNodeExpression(function)
if !stash.hasBinding(name) {
stash.createBinding(name, eval == true, value)
stash.createBinding(name, eval, value)
} else {
// TODO 10.5.5.e
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) {
executionContext := self.scope
func (rt *runtime) cmplVariableDeclaration(list []string) {
executionContext := rt.scope
eval := executionContext.eval
stash := executionContext.variable
for _, name := range list {
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 (
"fmt"
"math"
"runtime"
goruntime "runtime"
"github.com/robertkrimen/otto/token"
)
func (self *_runtime) cmpl_evaluate_nodeExpression(node _nodeExpression) Value {
func (rt *runtime) cmplEvaluateNodeExpression(node nodeExpression) Value {
// Allow interpreter interruption
// If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this
if self.otto.Interrupt != nil {
runtime.Gosched()
if rt.otto.Interrupt != nil {
goruntime.Gosched()
select {
case value := <-self.otto.Interrupt:
case value := <-rt.otto.Interrupt:
value()
default:
}
}
switch node := node.(type) {
case *_nodeArrayLiteral:
return self.cmpl_evaluate_nodeArrayLiteral(node)
case *nodeArrayLiteral:
return rt.cmplEvaluateNodeArrayLiteral(node)
case *_nodeAssignExpression:
return self.cmpl_evaluate_nodeAssignExpression(node)
case *nodeAssignExpression:
return rt.cmplEvaluateNodeAssignExpression(node)
case *_nodeBinaryExpression:
case *nodeBinaryExpression:
if node.comparison {
return self.cmpl_evaluate_nodeBinaryExpression_comparison(node)
} else {
return self.cmpl_evaluate_nodeBinaryExpression(node)
return rt.cmplEvaluateNodeBinaryExpressionComparison(node)
}
return rt.cmplEvaluateNodeBinaryExpression(node)
case *_nodeBracketExpression:
return self.cmpl_evaluate_nodeBracketExpression(node)
case *nodeBracketExpression:
return rt.cmplEvaluateNodeBracketExpression(node)
case *_nodeCallExpression:
return self.cmpl_evaluate_nodeCallExpression(node, nil)
case *nodeCallExpression:
return rt.cmplEvaluateNodeCallExpression(node, nil)
case *_nodeConditionalExpression:
return self.cmpl_evaluate_nodeConditionalExpression(node)
case *nodeConditionalExpression:
return rt.cmplEvaluateNodeConditionalExpression(node)
case *_nodeDotExpression:
return self.cmpl_evaluate_nodeDotExpression(node)
case *nodeDotExpression:
return rt.cmplEvaluateNodeDotExpression(node)
case *_nodeFunctionLiteral:
var local = self.scope.lexical
case *nodeFunctionLiteral:
local := rt.scope.lexical
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 != "" {
local.createBinding(node.name, false, value)
}
return value
case *_nodeIdentifier:
case *nodeIdentifier:
name := node.name
// TODO Should be true or false (strictness) depending on context
// getIdentifierReference should not return nil, but we check anyway and panic
// 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 {
// Should never get here!
panic(hereBeDragons("referenceError == nil: " + name))
}
return toValue(reference)
case *_nodeLiteral:
case *nodeLiteral:
return node.value
case *_nodeNewExpression:
return self.cmpl_evaluate_nodeNewExpression(node)
case *nodeNewExpression:
return rt.cmplEvaluateNodeNewExpression(node)
case *_nodeObjectLiteral:
return self.cmpl_evaluate_nodeObjectLiteral(node)
case *nodeObjectLiteral:
return rt.cmplEvaluateNodeObjectLiteral(node)
case *_nodeRegExpLiteral:
return toValue_object(self._newRegExp(node.pattern, node.flags))
case *nodeRegExpLiteral:
return objectValue(rt.newRegExpDirect(node.pattern, node.flags))
case *_nodeSequenceExpression:
return self.cmpl_evaluate_nodeSequenceExpression(node)
case *nodeSequenceExpression:
return rt.cmplEvaluateNodeSequenceExpression(node)
case *_nodeThisExpression:
return toValue_object(self.scope.this)
case *nodeThisExpression:
return objectValue(rt.scope.this)
case *_nodeUnaryExpression:
return self.cmpl_evaluate_nodeUnaryExpression(node)
case *nodeUnaryExpression:
return rt.cmplEvaluateNodeUnaryExpression(node)
case *_nodeVariableExpression:
return self.cmpl_evaluate_nodeVariableExpression(node)
case *nodeVariableExpression:
return rt.cmplEvaluateNodeVariableExpression(node)
default:
panic(fmt.Sprintf("unknown node type: %T", node))
}
panic(fmt.Errorf("Here be dragons: evaluate_nodeExpression(%T)", node))
}
func (self *_runtime) cmpl_evaluate_nodeArrayLiteral(node *_nodeArrayLiteral) Value {
func (rt *runtime) cmplEvaluateNodeArrayLiteral(node *nodeArrayLiteral) Value {
valueArray := []Value{}
for _, node := range node.value {
if node == nil {
valueArray = append(valueArray, emptyValue)
} 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 {
left := self.cmpl_evaluate_nodeExpression(node.left)
right := self.cmpl_evaluate_nodeExpression(node.right)
func (rt *runtime) cmplEvaluateNodeAssignExpression(node *nodeAssignExpression) Value {
left := rt.cmplEvaluateNodeExpression(node.left)
right := rt.cmplEvaluateNodeExpression(node.right)
rightValue := right.resolve()
result := rightValue
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
}
func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpression) Value {
left := self.cmpl_evaluate_nodeExpression(node.left)
func (rt *runtime) cmplEvaluateNodeBinaryExpression(node *nodeBinaryExpression) Value {
left := rt.cmplEvaluateNodeExpression(node.left)
leftValue := left.resolve()
switch node.operator {
@ -141,214 +140,206 @@ func (self *_runtime) cmpl_evaluate_nodeBinaryExpression(node *_nodeBinaryExpres
if !leftValue.bool() {
return leftValue
}
right := self.cmpl_evaluate_nodeExpression(node.right)
right := rt.cmplEvaluateNodeExpression(node.right)
return right.resolve()
case token.LOGICAL_OR:
if leftValue.bool() {
return leftValue
}
right := self.cmpl_evaluate_nodeExpression(node.right)
right := rt.cmplEvaluateNodeExpression(node.right)
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 {
left := self.cmpl_evaluate_nodeExpression(node.left).resolve()
right := self.cmpl_evaluate_nodeExpression(node.right).resolve()
func (rt *runtime) cmplEvaluateNodeBinaryExpressionComparison(node *nodeBinaryExpression) Value {
left := rt.cmplEvaluateNodeExpression(node.left).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 {
target := self.cmpl_evaluate_nodeExpression(node.left)
func (rt *runtime) cmplEvaluateNodeBracketExpression(node *nodeBracketExpression) Value {
target := rt.cmplEvaluateNodeExpression(node.left)
targetValue := target.resolve()
member := self.cmpl_evaluate_nodeExpression(node.member)
member := rt.cmplEvaluateNodeExpression(node.member)
memberValue := member.resolve()
// 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 {
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 {
rt := self
func (rt *runtime) cmplEvaluateNodeCallExpression(node *nodeCallExpression, withArgumentList []interface{}) Value {
this := Value{}
callee := self.cmpl_evaluate_nodeExpression(node.callee)
callee := rt.cmplEvaluateNodeExpression(node.callee)
argumentList := []Value{}
if withArgumentList != nil {
argumentList = self.toValueArray(withArgumentList...)
argumentList = rt.toValueArray(withArgumentList...)
} else {
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
name := ""
if rf != nil {
if rf := callee.reference(); rf != nil {
switch rf := rf.(type) {
case *_propertyReference:
case *propertyReference:
name = rf.name
object := rf.base
this = toValue_object(object)
this = objectValue(rf.base)
eval = rf.name == "eval" // Possible direct eval
case *_stashReference:
case *stashReference:
// TODO ImplicitThisValue
name = rf.name
eval = rf.name == "eval" // Possible direct eval
default:
// 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) {
case *_nodeIdentifier:
at = _at(callee.idx)
case *_nodeDotExpression:
at = _at(callee.idx)
case *_nodeBracketExpression:
at = _at(callee.idx)
case *nodeIdentifier:
atv = at(callee.idx)
case *nodeDotExpression:
atv = at(callee.idx)
case *nodeBracketExpression:
atv = at(callee.idx)
}
frame := _frame{
frm := frame{
callee: name,
file: self.scope.frame.file,
file: rt.scope.frame.file,
}
vl := callee.resolve()
if !vl.IsFunction() {
if name == "" {
// 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 {
test := self.cmpl_evaluate_nodeExpression(node.test)
func (rt *runtime) cmplEvaluateNodeConditionalExpression(node *nodeConditionalExpression) Value {
test := rt.cmplEvaluateNodeExpression(node.test)
testValue := test.resolve()
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 {
target := self.cmpl_evaluate_nodeExpression(node.left)
func (rt *runtime) cmplEvaluateNodeDotExpression(node *nodeDotExpression) Value {
target := rt.cmplEvaluateNodeExpression(node.left)
targetValue := target.resolve()
// 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 {
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 {
rt := self
callee := self.cmpl_evaluate_nodeExpression(node.callee)
func (rt *runtime) cmplEvaluateNodeNewExpression(node *nodeNewExpression) Value {
callee := rt.cmplEvaluateNodeExpression(node.callee)
argumentList := []Value{}
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()
name := ""
if rf != nil {
var name string
if rf := callee.reference(); rf != nil {
switch rf := rf.(type) {
case *_propertyReference:
case *propertyReference:
name = rf.name
case *_stashReference:
case *stashReference:
name = rf.name
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) {
case *_nodeIdentifier:
at = _at(callee.idx)
case *_nodeDotExpression:
at = _at(callee.idx)
case *_nodeBracketExpression:
at = _at(callee.idx)
case *nodeIdentifier:
atv = at(callee.idx)
case *nodeDotExpression:
atv = at(callee.idx)
case *nodeBracketExpression:
atv = at(callee.idx)
}
vl := callee.resolve()
if !vl.IsFunction() {
if name == "" {
// 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 {
result := self.newObject()
for _, property := range node.value {
switch property.kind {
func (rt *runtime) cmplEvaluateNodeObjectLiteral(node *nodeObjectLiteral) Value {
result := rt.newObject()
for _, prop := range node.value {
switch prop.kind {
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":
getter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical)
descriptor := _property{}
descriptor.mode = 0211
descriptor.value = _propertyGetSet{getter, nil}
result.defineOwnProperty(property.key, descriptor, false)
getter := rt.newNodeFunction(prop.value.(*nodeFunctionLiteral), rt.scope.lexical)
descriptor := property{}
descriptor.mode = 0o211
descriptor.value = propertyGetSet{getter, nil}
result.defineOwnProperty(prop.key, descriptor, false)
case "set":
setter := self.newNodeFunction(property.value.(*_nodeFunctionLiteral), self.scope.lexical)
descriptor := _property{}
descriptor.mode = 0211
descriptor.value = _propertyGetSet{nil, setter}
result.defineOwnProperty(property.key, descriptor, false)
setter := rt.newNodeFunction(prop.value.(*nodeFunctionLiteral), rt.scope.lexical)
descriptor := property{}
descriptor.mode = 0o211
descriptor.value = propertyGetSet{nil, setter}
result.defineOwnProperty(prop.key, descriptor, false)
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
for _, node := range node.sequence {
result = self.cmpl_evaluate_nodeExpression(node)
result = rt.cmplEvaluateNodeExpression(node)
result = result.resolve()
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpression) Value {
target := self.cmpl_evaluate_nodeExpression(node.operand)
func (rt *runtime) cmplEvaluateNodeUnaryExpression(node *nodeUnaryExpression) Value {
target := rt.cmplEvaluateNodeExpression(node.operand)
switch node.operator {
case token.TYPEOF, token.DELETE:
if target.kind == valueReference && target.reference().invalid() {
if node.operator == token.TYPEOF {
return toValue_string("undefined")
return stringValue("undefined")
}
return trueValue
}
@ -364,10 +355,10 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
case token.BITWISE_NOT:
targetValue := target.resolve()
integerValue := toInt32(targetValue)
return toValue_int32(^integerValue)
return int32Value(^integerValue)
case token.PLUS:
targetValue := target.resolve()
return toValue_float64(targetValue.float64())
return float64Value(targetValue.float64())
case token.MINUS:
targetValue := target.resolve()
value := targetValue.float64()
@ -376,35 +367,35 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
if math.Signbit(value) {
sign = 1
}
return toValue_float64(math.Copysign(value, sign))
return float64Value(math.Copysign(value, sign))
case token.INCREMENT:
targetValue := target.resolve()
if node.postfix {
// Postfix++
oldValue := targetValue.float64()
newValue := toValue_float64(+1 + oldValue)
self.putValue(target.reference(), newValue)
return toValue_float64(oldValue)
} else {
// ++Prefix
newValue := toValue_float64(+1 + targetValue.float64())
self.putValue(target.reference(), newValue)
return newValue
newValue := float64Value(+1 + oldValue)
rt.putValue(target.reference(), newValue)
return float64Value(oldValue)
}
// ++Prefix
newValue := float64Value(+1 + targetValue.float64())
rt.putValue(target.reference(), newValue)
return newValue
case token.DECREMENT:
targetValue := target.resolve()
if node.postfix {
// Postfix--
oldValue := targetValue.float64()
newValue := toValue_float64(-1 + oldValue)
self.putValue(target.reference(), newValue)
return toValue_float64(oldValue)
} else {
// --Prefix
newValue := toValue_float64(-1 + targetValue.float64())
self.putValue(target.reference(), newValue)
return newValue
newValue := float64Value(-1 + oldValue)
rt.putValue(target.reference(), newValue)
return float64Value(oldValue)
}
// --Prefix
newValue := float64Value(-1 + targetValue.float64())
rt.putValue(target.reference(), newValue)
return newValue
case token.VOID:
target.resolve() // FIXME Side effect?
return Value{}
@ -413,25 +404,25 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
if reference == nil {
return trueValue
}
return toValue_bool(target.reference().delete())
return boolValue(target.reference().delete())
case token.TYPEOF:
targetValue := target.resolve()
switch targetValue.kind {
case valueUndefined:
return toValue_string("undefined")
return stringValue("undefined")
case valueNull:
return toValue_string("object")
return stringValue("object")
case valueBoolean:
return toValue_string("boolean")
return stringValue("boolean")
case valueNumber:
return toValue_string("number")
return stringValue("number")
case valueString:
return toValue_string("string")
return stringValue("string")
case valueObject:
if targetValue._object().isCall() {
return toValue_string("function")
if targetValue.object().isCall() {
return stringValue("function")
}
return toValue_string("object")
return stringValue("object")
default:
// FIXME ?
}
@ -440,14 +431,14 @@ func (self *_runtime) cmpl_evaluate_nodeUnaryExpression(node *_nodeUnaryExpressi
panic(hereBeDragons())
}
func (self *_runtime) cmpl_evaluate_nodeVariableExpression(node *_nodeVariableExpression) Value {
func (rt *runtime) cmplEvaluateNodeVariableExpression(node *nodeVariableExpression) Value {
if node.initializer != nil {
// FIXME If reference is nil
left := getIdentifierReference(self, self.scope.lexical, node.name, false, _at(node.idx))
right := self.cmpl_evaluate_nodeExpression(node.initializer)
left := getIdentifierReference(rt, rt.scope.lexical, node.name, false, at(node.idx))
right := rt.cmplEvaluateNodeExpression(node.initializer)
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 (
"fmt"
"runtime"
goruntime "runtime"
"github.com/robertkrimen/otto/token"
)
func (self *_runtime) cmpl_evaluate_nodeStatement(node _nodeStatement) Value {
func (rt *runtime) cmplEvaluateNodeStatement(node nodeStatement) Value {
// Allow interpreter interruption
// If the Interrupt channel is nil, then
// we avoid runtime.Gosched() overhead (if any)
// FIXME: Test this
if self.otto.Interrupt != nil {
runtime.Gosched()
if rt.otto.Interrupt != nil {
goruntime.Gosched()
select {
case value := <-self.otto.Interrupt:
case value := <-rt.otto.Interrupt:
value()
default:
}
}
switch node := node.(type) {
case *_nodeBlockStatement:
labels := self.labels
self.labels = nil
case *nodeBlockStatement:
labels := rt.labels
rt.labels = nil
value := self.cmpl_evaluate_nodeStatementList(node.list)
switch value.kind {
case valueResult:
switch value.evaluateBreak(labels) {
case resultBreak:
value := rt.cmplEvaluateNodeStatementList(node.list)
if value.kind == valueResult {
if value.evaluateBreak(labels) == resultBreak {
return emptyValue
}
}
return value
case *_nodeBranchStatement:
case *nodeBranchStatement:
target := node.label
switch node.branch { // FIXME Maybe node.kind? node.operator?
case token.BREAK:
return toValue(newBreakResult(target))
case token.CONTINUE:
return toValue(newContinueResult(target))
default:
panic(fmt.Errorf("unknown node branch token %T", node))
}
case *_nodeDebuggerStatement:
if self.debugger != nil {
self.debugger(self.otto)
case *nodeDebuggerStatement:
if rt.debugger != nil {
rt.debugger(rt.otto)
}
return emptyValue // Nothing happens.
case *_nodeDoWhileStatement:
return self.cmpl_evaluate_nodeDoWhileStatement(node)
case *nodeDoWhileStatement:
return rt.cmplEvaluateNodeDoWhileStatement(node)
case *_nodeEmptyStatement:
case *nodeEmptyStatement:
return emptyValue
case *_nodeExpressionStatement:
return self.cmpl_evaluate_nodeExpression(node.expression)
case *nodeExpressionStatement:
return rt.cmplEvaluateNodeExpression(node.expression)
case *_nodeForInStatement:
return self.cmpl_evaluate_nodeForInStatement(node)
case *nodeForInStatement:
return rt.cmplEvaluateNodeForInStatement(node)
case *_nodeForStatement:
return self.cmpl_evaluate_nodeForStatement(node)
case *nodeForStatement:
return rt.cmplEvaluateNodeForStatement(node)
case *_nodeIfStatement:
return self.cmpl_evaluate_nodeIfStatement(node)
case *nodeIfStatement:
return rt.cmplEvaluateNodeIfStatement(node)
case *_nodeLabelledStatement:
self.labels = append(self.labels, node.label)
case *nodeLabelledStatement:
rt.labels = append(rt.labels, node.label)
defer func() {
if len(self.labels) > 0 {
self.labels = self.labels[:len(self.labels)-1] // Pop the label
if len(rt.labels) > 0 {
rt.labels = rt.labels[:len(rt.labels)-1] // Pop the label
} 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 {
return toValue(newReturnResult(self.cmpl_evaluate_nodeExpression(node.argument).resolve()))
return toValue(newReturnResult(rt.cmplEvaluateNodeExpression(node.argument).resolve()))
}
return toValue(newReturnResult(Value{}))
case *_nodeSwitchStatement:
return self.cmpl_evaluate_nodeSwitchStatement(node)
case *nodeSwitchStatement:
return rt.cmplEvaluateNodeSwitchStatement(node)
case *_nodeThrowStatement:
value := self.cmpl_evaluate_nodeExpression(node.argument).resolve()
case *nodeThrowStatement:
value := rt.cmplEvaluateNodeExpression(node.argument).resolve()
panic(newException(value))
case *_nodeTryStatement:
return self.cmpl_evaluate_nodeTryStatement(node)
case *nodeTryStatement:
return rt.cmplEvaluateNodeTryStatement(node)
case *_nodeVariableStatement:
case *nodeVariableStatement:
// Variables are already defined, this is initialization only
for _, variable := range node.list {
self.cmpl_evaluate_nodeVariableExpression(variable.(*_nodeVariableExpression))
rt.cmplEvaluateNodeVariableExpression(variable.(*nodeVariableExpression))
}
return emptyValue
case *_nodeWhileStatement:
return self.cmpl_evaluate_nodeWhileStatement(node)
case *nodeWhileStatement:
return rt.cmplEvaluateModeWhileStatement(node)
case *_nodeWithStatement:
return self.cmpl_evaluate_nodeWithStatement(node)
case *nodeWithStatement:
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 (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Value {
func (rt *runtime) cmplEvaluateNodeStatementList(list []nodeStatement) Value {
var result Value
for _, node := range list {
value := self.cmpl_evaluate_nodeStatement(node)
value := rt.cmplEvaluateNodeStatement(node)
switch value.kind {
case valueResult:
return value
@ -133,9 +133,9 @@ func (self *_runtime) cmpl_evaluate_nodeStatementList(list []_nodeStatement) Val
return result
}
func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileStatement) Value {
labels := append(self.labels, "")
self.labels = nil
func (rt *runtime) cmplEvaluateNodeDoWhileStatement(node *nodeDoWhileStatement) Value {
labels := append(rt.labels, "") //nolint: gocritic
rt.labels = nil
test := node.test
@ -143,7 +143,7 @@ func (self *_runtime) cmpl_evaluate_nodeDoWhileStatement(node *_nodeDoWhileState
resultBreak:
for {
for _, node := range node.body {
value := self.cmpl_evaluate_nodeStatement(node)
value := rt.cmplEvaluateNodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
@ -160,7 +160,7 @@ resultBreak:
}
}
resultContinue:
if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() {
if !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
// Stahp: do ... while (false)
break
}
@ -168,11 +168,11 @@ resultBreak:
return result
}
func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement) Value {
labels := append(self.labels, "")
self.labels = nil
func (rt *runtime) cmplEvaluateNodeForInStatement(node *nodeForInStatement) Value {
labels := append(rt.labels, "") //nolint: gocritic
rt.labels = nil
source := self.cmpl_evaluate_nodeExpression(node.source)
source := rt.cmplEvaluateNodeExpression(node.source)
sourceValue := source.resolve()
switch sourceValue.kind {
@ -180,26 +180,26 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
return emptyValue
}
sourceObject := self.toObject(sourceValue)
sourceObject := rt.toObject(sourceValue)
into := node.into
body := node.body
result := emptyValue
object := sourceObject
for object != nil {
obj := sourceObject
for obj != nil {
enumerateValue := emptyValue
object.enumerate(false, func(name string) bool {
into := self.cmpl_evaluate_nodeExpression(into)
obj.enumerate(false, func(name string) bool {
into := rt.cmplEvaluateNodeExpression(into)
// In the case of: for (var abc in def) ...
if into.reference() == nil {
identifier := into.string()
// 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 {
value := self.cmpl_evaluate_nodeStatement(node)
value := rt.cmplEvaluateNodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
@ -207,7 +207,7 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
enumerateValue = value
return false
case resultBreak:
object = nil
obj = nil
return false
case resultContinue:
return true
@ -219,10 +219,10 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
}
return true
})
if object == nil {
if obj == nil {
break
}
object = object.prototype
obj = obj.prototype
if !enumerateValue.isEmpty() {
result = enumerateValue
}
@ -230,9 +230,9 @@ func (self *_runtime) cmpl_evaluate_nodeForInStatement(node *_nodeForInStatement
return result
}
func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Value {
labels := append(self.labels, "")
self.labels = nil
func (rt *runtime) cmplEvaluateNodeForStatement(node *nodeForStatement) Value {
labels := append(rt.labels, "") //nolint: gocritic
rt.labels = nil
initializer := node.initializer
test := node.test
@ -240,7 +240,7 @@ func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Va
body := node.body
if initializer != nil {
initialResult := self.cmpl_evaluate_nodeExpression(initializer)
initialResult := rt.cmplEvaluateNodeExpression(initializer)
initialResult.resolve() // Side-effect trigger
}
@ -248,25 +248,25 @@ func (self *_runtime) cmpl_evaluate_nodeForStatement(node *_nodeForStatement) Va
resultBreak:
for {
if test != nil {
testResult := self.cmpl_evaluate_nodeExpression(test)
testResult := rt.cmplEvaluateNodeExpression(test)
testResultValue := testResult.resolve()
if testResultValue.bool() == false {
if !testResultValue.bool() {
break
}
}
// this is to prevent for cycles with no body from running forever
if len(body) == 0 && self.otto.Interrupt != nil {
runtime.Gosched()
if len(body) == 0 && rt.otto.Interrupt != nil {
goruntime.Gosched()
select {
case value := <-self.otto.Interrupt:
case value := <-rt.otto.Interrupt:
value()
default:
}
}
for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node)
value := rt.cmplEvaluateNodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
@ -284,36 +284,36 @@ resultBreak:
}
resultContinue:
if update != nil {
updateResult := self.cmpl_evaluate_nodeExpression(update)
updateResult := rt.cmplEvaluateNodeExpression(update)
updateResult.resolve() // Side-effect trigger
}
}
return result
}
func (self *_runtime) cmpl_evaluate_nodeIfStatement(node *_nodeIfStatement) Value {
test := self.cmpl_evaluate_nodeExpression(node.test)
func (rt *runtime) cmplEvaluateNodeIfStatement(node *nodeIfStatement) Value {
test := rt.cmplEvaluateNodeExpression(node.test)
testValue := test.resolve()
if testValue.bool() {
return self.cmpl_evaluate_nodeStatement(node.consequent)
return rt.cmplEvaluateNodeStatement(node.consequent)
} else if node.alternate != nil {
return self.cmpl_evaluate_nodeStatement(node.alternate)
return rt.cmplEvaluateNodeStatement(node.alternate)
}
return emptyValue
}
func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStatement) Value {
labels := append(self.labels, "")
self.labels = nil
func (rt *runtime) cmplEvaluateNodeSwitchStatement(node *nodeSwitchStatement) Value {
labels := append(rt.labels, "") //nolint: gocritic
rt.labels = nil
discriminantResult := self.cmpl_evaluate_nodeExpression(node.discriminant)
target := node.default_
discriminantResult := rt.cmplEvaluateNodeExpression(node.discriminant)
target := node.defaultIdx
for index, clause := range node.body {
test := clause.test
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
break
}
@ -324,7 +324,7 @@ func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStateme
if target != -1 {
for _, clause := range node.body[target:] {
for _, statement := range clause.consequent {
value := self.cmpl_evaluate_nodeStatement(statement)
value := rt.cmplEvaluateNodeStatement(statement)
switch value.kind {
case valueResult:
switch value.evaluateBreak(labels) {
@ -344,58 +344,58 @@ func (self *_runtime) cmpl_evaluate_nodeSwitchStatement(node *_nodeSwitchStateme
return result
}
func (self *_runtime) cmpl_evaluate_nodeTryStatement(node *_nodeTryStatement) Value {
tryCatchValue, exception := self.tryCatchEvaluate(func() Value {
return self.cmpl_evaluate_nodeStatement(node.body)
func (rt *runtime) cmplEvaluateNodeTryStatement(node *nodeTryStatement) Value {
tryCatchValue, exep := rt.tryCatchEvaluate(func() Value {
return rt.cmplEvaluateNodeStatement(node.body)
})
if exception && node.catch != nil {
outer := self.scope.lexical
self.scope.lexical = self.newDeclarationStash(outer)
if exep && node.catch != nil {
outer := rt.scope.lexical
rt.scope.lexical = rt.newDeclarationStash(outer)
defer func() {
self.scope.lexical = outer
rt.scope.lexical = outer
}()
// TODO If necessary, convert TypeError<runtime> => TypeError
// That, is, such errors can be thrown despite not being JavaScript "native"
// 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.Catch
tryCatchValue, exception = self.tryCatchEvaluate(func() Value {
return self.cmpl_evaluate_nodeStatement(node.catch.body)
tryCatchValue, exep = rt.tryCatchEvaluate(func() Value {
return rt.cmplEvaluateNodeStatement(node.catch.body)
})
}
if node.finally != nil {
finallyValue := self.cmpl_evaluate_nodeStatement(node.finally)
finallyValue := rt.cmplEvaluateNodeStatement(node.finally)
if finallyValue.kind == valueResult {
return finallyValue
}
}
if exception {
if exep {
panic(newException(tryCatchValue))
}
return tryCatchValue
}
func (self *_runtime) cmpl_evaluate_nodeWhileStatement(node *_nodeWhileStatement) Value {
func (rt *runtime) cmplEvaluateModeWhileStatement(node *nodeWhileStatement) Value {
test := node.test
body := node.body
labels := append(self.labels, "")
self.labels = nil
labels := append(rt.labels, "") //nolint: gocritic
rt.labels = nil
result := emptyValue
resultBreakContinue:
for {
if !self.cmpl_evaluate_nodeExpression(test).resolve().bool() {
if !rt.cmplEvaluateNodeExpression(test).resolve().bool() {
// Stahp: while (false) ...
break
}
for _, node := range body {
value := self.cmpl_evaluate_nodeStatement(node)
value := rt.cmplEvaluateNodeStatement(node)
switch value.kind {
case valueResult:
switch value.evaluateBreakContinue(labels) {
@ -415,14 +415,14 @@ resultBreakContinue:
return result
}
func (self *_runtime) cmpl_evaluate_nodeWithStatement(node *_nodeWithStatement) Value {
object := self.cmpl_evaluate_nodeExpression(node.object)
outer := self.scope.lexical
lexical := self.newObjectStash(self.toObject(object.resolve()), outer)
self.scope.lexical = lexical
func (rt *runtime) cmplEvaluateNodeWithStatement(node *nodeWithStatement) Value {
obj := rt.cmplEvaluateNodeExpression(node.object)
outer := rt.scope.lexical
lexical := rt.newObjectStash(rt.toObject(obj.resolve()), outer)
rt.scope.lexical = lexical
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 (
"fmt"
"regexp"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
"github.com/robertkrimen/otto/token"
)
var trueLiteral = &_nodeLiteral{value: toValue_bool(true)}
var falseLiteral = &_nodeLiteral{value: toValue_bool(false)}
var nullLiteral = &_nodeLiteral{value: nullValue}
var emptyStatement = &_nodeEmptyStatement{}
var (
trueLiteral = &nodeLiteral{value: boolValue(true)}
falseLiteral = &nodeLiteral{value: boolValue(false)}
nullLiteral = &nodeLiteral{value: nullValue}
emptyStatement = &nodeEmptyStatement{}
)
func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
if in == nil {
func (cmpl *compiler) parseExpression(expr ast.Expression) nodeExpression {
if expr == nil {
return nil
}
switch in := in.(type) {
switch expr := expr.(type) {
case *ast.ArrayLiteral:
out := &_nodeArrayLiteral{
value: make([]_nodeExpression, len(in.Value)),
out := &nodeArrayLiteral{
value: make([]nodeExpression, len(expr.Value)),
}
for i, value := range in.Value {
for i, value := range expr.Value {
out.value[i] = cmpl.parseExpression(value)
}
return out
case *ast.AssignExpression:
return &_nodeAssignExpression{
operator: in.Operator,
left: cmpl.parseExpression(in.Left),
right: cmpl.parseExpression(in.Right),
return &nodeAssignExpression{
operator: expr.Operator,
left: cmpl.parseExpression(expr.Left),
right: cmpl.parseExpression(expr.Right),
}
case *ast.BinaryExpression:
return &_nodeBinaryExpression{
operator: in.Operator,
left: cmpl.parseExpression(in.Left),
right: cmpl.parseExpression(in.Right),
comparison: in.Comparison,
return &nodeBinaryExpression{
operator: expr.Operator,
left: cmpl.parseExpression(expr.Left),
right: cmpl.parseExpression(expr.Right),
comparison: expr.Comparison,
}
case *ast.BooleanLiteral:
if in.Value {
if expr.Value {
return trueLiteral
}
return falseLiteral
case *ast.BracketExpression:
return &_nodeBracketExpression{
idx: in.Left.Idx0(),
left: cmpl.parseExpression(in.Left),
member: cmpl.parseExpression(in.Member),
return &nodeBracketExpression{
idx: expr.Left.Idx0(),
left: cmpl.parseExpression(expr.Left),
member: cmpl.parseExpression(expr.Member),
}
case *ast.CallExpression:
out := &_nodeCallExpression{
callee: cmpl.parseExpression(in.Callee),
argumentList: make([]_nodeExpression, len(in.ArgumentList)),
out := &nodeCallExpression{
callee: cmpl.parseExpression(expr.Callee),
argumentList: make([]nodeExpression, len(expr.ArgumentList)),
}
for i, value := range in.ArgumentList {
for i, value := range expr.ArgumentList {
out.argumentList[i] = cmpl.parseExpression(value)
}
return out
case *ast.ConditionalExpression:
return &_nodeConditionalExpression{
test: cmpl.parseExpression(in.Test),
consequent: cmpl.parseExpression(in.Consequent),
alternate: cmpl.parseExpression(in.Alternate),
return &nodeConditionalExpression{
test: cmpl.parseExpression(expr.Test),
consequent: cmpl.parseExpression(expr.Consequent),
alternate: cmpl.parseExpression(expr.Alternate),
}
case *ast.DotExpression:
return &_nodeDotExpression{
idx: in.Left.Idx0(),
left: cmpl.parseExpression(in.Left),
identifier: in.Identifier.Name,
return &nodeDotExpression{
idx: expr.Left.Idx0(),
left: cmpl.parseExpression(expr.Left),
identifier: expr.Identifier.Name,
}
case *ast.EmptyExpression:
@ -86,48 +87,48 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
case *ast.FunctionLiteral:
name := ""
if in.Name != nil {
name = in.Name.Name
if expr.Name != nil {
name = expr.Name.Name
}
out := &_nodeFunctionLiteral{
out := &nodeFunctionLiteral{
name: name,
body: cmpl.parseStatement(in.Body),
source: in.Source,
body: cmpl.parseStatement(expr.Body),
source: expr.Source,
file: cmpl.file,
}
if in.ParameterList != nil {
list := in.ParameterList.List
if expr.ParameterList != nil {
list := expr.ParameterList.List
out.parameterList = make([]string, len(list))
for i, value := range list {
out.parameterList[i] = value.Name
}
}
for _, value := range in.DeclarationList {
for _, value := range expr.DeclarationList {
switch value := value.(type) {
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:
for _, value := range value.List {
out.varList = append(out.varList, value.Name)
}
default:
panic(fmt.Errorf("Here be dragons: parseProgram.declaration(%T)", value))
panic(fmt.Sprintf("parse expression unknown function declaration type %T", value))
}
}
return out
case *ast.Identifier:
return &_nodeIdentifier{
idx: in.Idx,
name: in.Name,
return &nodeIdentifier{
idx: expr.Idx,
name: expr.Name,
}
case *ast.NewExpression:
out := &_nodeNewExpression{
callee: cmpl.parseExpression(in.Callee),
argumentList: make([]_nodeExpression, len(in.ArgumentList)),
out := &nodeNewExpression{
callee: cmpl.parseExpression(expr.Callee),
argumentList: make([]nodeExpression, len(expr.ArgumentList)),
}
for i, value := range in.ArgumentList {
for i, value := range expr.ArgumentList {
out.argumentList[i] = cmpl.parseExpression(value)
}
return out
@ -136,16 +137,16 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
return nullLiteral
case *ast.NumberLiteral:
return &_nodeLiteral{
value: toValue(in.Value),
return &nodeLiteral{
value: toValue(expr.Value),
}
case *ast.ObjectLiteral:
out := &_nodeObjectLiteral{
value: make([]_nodeProperty, len(in.Value)),
out := &nodeObjectLiteral{
value: make([]nodeProperty, len(expr.Value)),
}
for i, value := range in.Value {
out.value[i] = _nodeProperty{
for i, value := range expr.Value {
out.value[i] = nodeProperty{
key: value.Key,
kind: value.Kind,
value: cmpl.parseExpression(value.Value),
@ -154,79 +155,79 @@ func (cmpl *_compiler) parseExpression(in ast.Expression) _nodeExpression {
return out
case *ast.RegExpLiteral:
return &_nodeRegExpLiteral{
flags: in.Flags,
pattern: in.Pattern,
return &nodeRegExpLiteral{
flags: expr.Flags,
pattern: expr.Pattern,
}
case *ast.SequenceExpression:
out := &_nodeSequenceExpression{
sequence: make([]_nodeExpression, len(in.Sequence)),
out := &nodeSequenceExpression{
sequence: make([]nodeExpression, len(expr.Sequence)),
}
for i, value := range in.Sequence {
for i, value := range expr.Sequence {
out.sequence[i] = cmpl.parseExpression(value)
}
return out
case *ast.StringLiteral:
return &_nodeLiteral{
value: toValue_string(in.Value),
return &nodeLiteral{
value: stringValue(expr.Value),
}
case *ast.ThisExpression:
return &_nodeThisExpression{}
return &nodeThisExpression{}
case *ast.UnaryExpression:
return &_nodeUnaryExpression{
operator: in.Operator,
operand: cmpl.parseExpression(in.Operand),
postfix: in.Postfix,
return &nodeUnaryExpression{
operator: expr.Operator,
operand: cmpl.parseExpression(expr.Operand),
postfix: expr.Postfix,
}
case *ast.VariableExpression:
return &_nodeVariableExpression{
idx: in.Idx0(),
name: in.Name,
initializer: cmpl.parseExpression(in.Initializer),
return &nodeVariableExpression{
idx: expr.Idx0(),
name: expr.Name,
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(in ast.Statement) _nodeStatement {
if in == nil {
func (cmpl *compiler) parseStatement(stmt ast.Statement) nodeStatement {
if stmt == nil {
return nil
}
switch in := in.(type) {
switch stmt := stmt.(type) {
case *ast.BlockStatement:
out := &_nodeBlockStatement{
list: make([]_nodeStatement, len(in.List)),
out := &nodeBlockStatement{
list: make([]nodeStatement, len(stmt.List)),
}
for i, value := range in.List {
for i, value := range stmt.List {
out.list[i] = cmpl.parseStatement(value)
}
return out
case *ast.BranchStatement:
out := &_nodeBranchStatement{
branch: in.Token,
out := &nodeBranchStatement{
branch: stmt.Token,
}
if in.Label != nil {
out.label = in.Label.Name
if stmt.Label != nil {
out.label = stmt.Label.Name
}
return out
case *ast.DebuggerStatement:
return &_nodeDebuggerStatement{}
return &nodeDebuggerStatement{}
case *ast.DoWhileStatement:
out := &_nodeDoWhileStatement{
test: cmpl.parseExpression(in.Test),
out := &nodeDoWhileStatement{
test: cmpl.parseExpression(stmt.Test),
}
body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok {
body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list
} else {
out.body = append(out.body, body)
@ -237,17 +238,17 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return emptyStatement
case *ast.ExpressionStatement:
return &_nodeExpressionStatement{
expression: cmpl.parseExpression(in.Expression),
return &nodeExpressionStatement{
expression: cmpl.parseExpression(stmt.Expression),
}
case *ast.ForInStatement:
out := &_nodeForInStatement{
into: cmpl.parseExpression(in.Into),
source: cmpl.parseExpression(in.Source),
out := &nodeForInStatement{
into: cmpl.parseExpression(stmt.Into),
source: cmpl.parseExpression(stmt.Source),
}
body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok {
body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list
} else {
out.body = append(out.body, body)
@ -255,13 +256,13 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return out
case *ast.ForStatement:
out := &_nodeForStatement{
initializer: cmpl.parseExpression(in.Initializer),
update: cmpl.parseExpression(in.Update),
test: cmpl.parseExpression(in.Test),
out := &nodeForStatement{
initializer: cmpl.parseExpression(stmt.Initializer),
update: cmpl.parseExpression(stmt.Update),
test: cmpl.parseExpression(stmt.Test),
}
body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok {
body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list
} else {
out.body = append(out.body, body)
@ -272,33 +273,33 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return emptyStatement
case *ast.IfStatement:
return &_nodeIfStatement{
test: cmpl.parseExpression(in.Test),
consequent: cmpl.parseStatement(in.Consequent),
alternate: cmpl.parseStatement(in.Alternate),
return &nodeIfStatement{
test: cmpl.parseExpression(stmt.Test),
consequent: cmpl.parseStatement(stmt.Consequent),
alternate: cmpl.parseStatement(stmt.Alternate),
}
case *ast.LabelledStatement:
return &_nodeLabelledStatement{
label: in.Label.Name,
statement: cmpl.parseStatement(in.Statement),
return &nodeLabelledStatement{
label: stmt.Label.Name,
statement: cmpl.parseStatement(stmt.Statement),
}
case *ast.ReturnStatement:
return &_nodeReturnStatement{
argument: cmpl.parseExpression(in.Argument),
return &nodeReturnStatement{
argument: cmpl.parseExpression(stmt.Argument),
}
case *ast.SwitchStatement:
out := &_nodeSwitchStatement{
discriminant: cmpl.parseExpression(in.Discriminant),
default_: in.Default,
body: make([]*_nodeCaseStatement, len(in.Body)),
out := &nodeSwitchStatement{
discriminant: cmpl.parseExpression(stmt.Discriminant),
defaultIdx: stmt.Default,
body: make([]*nodeCaseStatement, len(stmt.Body)),
}
for i, clause := range in.Body {
out.body[i] = &_nodeCaseStatement{
for i, clause := range stmt.Body {
out.body[i] = &nodeCaseStatement{
test: cmpl.parseExpression(clause.Test),
consequent: make([]_nodeStatement, len(clause.Consequent)),
consequent: make([]nodeStatement, len(clause.Consequent)),
}
for j, value := range clause.Consequent {
out.body[i].consequent[j] = cmpl.parseStatement(value)
@ -307,38 +308,38 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return out
case *ast.ThrowStatement:
return &_nodeThrowStatement{
argument: cmpl.parseExpression(in.Argument),
return &nodeThrowStatement{
argument: cmpl.parseExpression(stmt.Argument),
}
case *ast.TryStatement:
out := &_nodeTryStatement{
body: cmpl.parseStatement(in.Body),
finally: cmpl.parseStatement(in.Finally),
out := &nodeTryStatement{
body: cmpl.parseStatement(stmt.Body),
finally: cmpl.parseStatement(stmt.Finally),
}
if in.Catch != nil {
out.catch = &_nodeCatchStatement{
parameter: in.Catch.Parameter.Name,
body: cmpl.parseStatement(in.Catch.Body),
if stmt.Catch != nil {
out.catch = &nodeCatchStatement{
parameter: stmt.Catch.Parameter.Name,
body: cmpl.parseStatement(stmt.Catch.Body),
}
}
return out
case *ast.VariableStatement:
out := &_nodeVariableStatement{
list: make([]_nodeExpression, len(in.List)),
out := &nodeVariableStatement{
list: make([]nodeExpression, len(stmt.List)),
}
for i, value := range in.List {
for i, value := range stmt.List {
out.list[i] = cmpl.parseExpression(value)
}
return out
case *ast.WhileStatement:
out := &_nodeWhileStatement{
test: cmpl.parseExpression(in.Test),
out := &nodeWhileStatement{
test: cmpl.parseExpression(stmt.Test),
}
body := cmpl.parseStatement(in.Body)
if block, ok := body.(*_nodeBlockStatement); ok {
body := cmpl.parseStatement(stmt.Body)
if block, ok := body.(*nodeBlockStatement); ok {
out.body = block.list
} else {
out.body = append(out.body, body)
@ -346,307 +347,298 @@ func (cmpl *_compiler) parseStatement(in ast.Statement) _nodeStatement {
return out
case *ast.WithStatement:
return &_nodeWithStatement{
object: cmpl.parseExpression(in.Object),
body: cmpl.parseStatement(in.Body),
return &nodeWithStatement{
object: cmpl.parseExpression(stmt.Object),
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 cmpl_parse(in *ast.Program) *_nodeProgram {
cmpl := _compiler{
func cmplParse(in *ast.Program) *nodeProgram {
cmpl := compiler{
program: in,
}
if cmpl.program != nil {
cmpl.file = cmpl.program.File
}
return cmpl.parse()
}
func (cmpl *_compiler) _parse(in *ast.Program) *_nodeProgram {
out := &_nodeProgram{
body: make([]_nodeStatement, len(in.Body)),
file: in.File,
func (cmpl *compiler) parse() *nodeProgram {
out := &nodeProgram{
body: make([]nodeStatement, len(cmpl.program.Body)),
file: cmpl.program.File,
}
for i, value := range in.Body {
for i, value := range cmpl.program.Body {
out.body[i] = cmpl.parseStatement(value)
}
for _, value := range in.DeclarationList {
for _, value := range cmpl.program.DeclarationList {
switch value := value.(type) {
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:
for _, value := range value.List {
out.varList = append(out.varList, value.Name)
}
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
}
type _nodeProgram struct {
body []_nodeStatement
type nodeProgram struct {
body []nodeStatement
varList []string
functionList []*_nodeFunctionLiteral
variableList []_nodeDeclaration
functionList []*nodeFunctionLiteral
file *file.File
}
type _nodeDeclaration struct {
name string
definition _node
}
type _node interface {
}
type node interface{}
type (
_nodeExpression interface {
_node
_expressionNode()
nodeExpression interface {
node
expressionNode()
}
_nodeArrayLiteral struct {
value []_nodeExpression
nodeArrayLiteral struct {
value []nodeExpression
}
_nodeAssignExpression struct {
nodeAssignExpression struct {
operator token.Token
left _nodeExpression
right _nodeExpression
left nodeExpression
right nodeExpression
}
_nodeBinaryExpression struct {
nodeBinaryExpression struct {
operator token.Token
left _nodeExpression
right _nodeExpression
left nodeExpression
right nodeExpression
comparison bool
}
_nodeBracketExpression struct {
nodeBracketExpression struct {
idx file.Idx
left _nodeExpression
member _nodeExpression
left nodeExpression
member nodeExpression
}
_nodeCallExpression struct {
callee _nodeExpression
argumentList []_nodeExpression
nodeCallExpression struct {
callee nodeExpression
argumentList []nodeExpression
}
_nodeConditionalExpression struct {
test _nodeExpression
consequent _nodeExpression
alternate _nodeExpression
nodeConditionalExpression struct {
test nodeExpression
consequent nodeExpression
alternate nodeExpression
}
_nodeDotExpression struct {
nodeDotExpression struct {
idx file.Idx
left _nodeExpression
left nodeExpression
identifier string
}
_nodeFunctionLiteral struct {
nodeFunctionLiteral struct {
name string
body _nodeStatement
body nodeStatement
source string
parameterList []string
varList []string
functionList []*_nodeFunctionLiteral
functionList []*nodeFunctionLiteral
file *file.File
}
_nodeIdentifier struct {
nodeIdentifier struct {
idx file.Idx
name string
}
_nodeLiteral struct {
nodeLiteral struct {
value Value
}
_nodeNewExpression struct {
callee _nodeExpression
argumentList []_nodeExpression
nodeNewExpression struct {
callee nodeExpression
argumentList []nodeExpression
}
_nodeObjectLiteral struct {
value []_nodeProperty
nodeObjectLiteral struct {
value []nodeProperty
}
_nodeProperty struct {
nodeProperty struct {
key string
kind string
value _nodeExpression
value nodeExpression
}
_nodeRegExpLiteral struct {
nodeRegExpLiteral struct {
flags string
pattern string // Value?
regexp *regexp.Regexp
}
_nodeSequenceExpression struct {
sequence []_nodeExpression
nodeSequenceExpression struct {
sequence []nodeExpression
}
_nodeThisExpression struct {
}
nodeThisExpression struct{}
_nodeUnaryExpression struct {
nodeUnaryExpression struct {
operator token.Token
operand _nodeExpression
operand nodeExpression
postfix bool
}
_nodeVariableExpression struct {
nodeVariableExpression struct {
idx file.Idx
name string
initializer _nodeExpression
initializer nodeExpression
}
)
type (
_nodeStatement interface {
_node
_statementNode()
nodeStatement interface {
node
statementNode()
}
_nodeBlockStatement struct {
list []_nodeStatement
nodeBlockStatement struct {
list []nodeStatement
}
_nodeBranchStatement struct {
nodeBranchStatement struct {
branch token.Token
label string
}
_nodeCaseStatement struct {
test _nodeExpression
consequent []_nodeStatement
nodeCaseStatement struct {
test nodeExpression
consequent []nodeStatement
}
_nodeCatchStatement struct {
nodeCatchStatement struct {
parameter string
body _nodeStatement
body nodeStatement
}
_nodeDebuggerStatement struct {
nodeDebuggerStatement struct{}
nodeDoWhileStatement struct {
test nodeExpression
body []nodeStatement
}
_nodeDoWhileStatement struct {
test _nodeExpression
body []_nodeStatement
nodeEmptyStatement struct{}
nodeExpressionStatement struct {
expression nodeExpression
}
_nodeEmptyStatement struct {
nodeForInStatement struct {
into nodeExpression
source nodeExpression
body []nodeStatement
}
_nodeExpressionStatement struct {
expression _nodeExpression
nodeForStatement struct {
initializer nodeExpression
update nodeExpression
test nodeExpression
body []nodeStatement
}
_nodeForInStatement struct {
into _nodeExpression
source _nodeExpression
body []_nodeStatement
nodeIfStatement struct {
test nodeExpression
consequent nodeStatement
alternate nodeStatement
}
_nodeForStatement struct {
initializer _nodeExpression
update _nodeExpression
test _nodeExpression
body []_nodeStatement
}
_nodeIfStatement struct {
test _nodeExpression
consequent _nodeStatement
alternate _nodeStatement
}
_nodeLabelledStatement struct {
nodeLabelledStatement struct {
label string
statement _nodeStatement
statement nodeStatement
}
_nodeReturnStatement struct {
argument _nodeExpression
nodeReturnStatement struct {
argument nodeExpression
}
_nodeSwitchStatement struct {
discriminant _nodeExpression
default_ int
body []*_nodeCaseStatement
nodeSwitchStatement struct {
discriminant nodeExpression
defaultIdx int
body []*nodeCaseStatement
}
_nodeThrowStatement struct {
argument _nodeExpression
nodeThrowStatement struct {
argument nodeExpression
}
_nodeTryStatement struct {
body _nodeStatement
catch *_nodeCatchStatement
finally _nodeStatement
nodeTryStatement struct {
body nodeStatement
catch *nodeCatchStatement
finally nodeStatement
}
_nodeVariableStatement struct {
list []_nodeExpression
nodeVariableStatement struct {
list []nodeExpression
}
_nodeWhileStatement struct {
test _nodeExpression
body []_nodeStatement
nodeWhileStatement struct {
test nodeExpression
body []nodeStatement
}
_nodeWithStatement struct {
object _nodeExpression
body _nodeStatement
nodeWithStatement struct {
object nodeExpression
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() {}
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
// _statementNode
func (*_nodeBlockStatement) _statementNode() {}
func (*_nodeBranchStatement) _statementNode() {}
func (*_nodeCaseStatement) _statementNode() {}
func (*_nodeCatchStatement) _statementNode() {}
func (*_nodeDebuggerStatement) _statementNode() {}
func (*_nodeDoWhileStatement) _statementNode() {}
func (*_nodeEmptyStatement) _statementNode() {}
func (*_nodeExpressionStatement) _statementNode() {}
func (*_nodeForInStatement) _statementNode() {}
func (*_nodeForStatement) _statementNode() {}
func (*_nodeIfStatement) _statementNode() {}
func (*_nodeLabelledStatement) _statementNode() {}
func (*_nodeReturnStatement) _statementNode() {}
func (*_nodeSwitchStatement) _statementNode() {}
func (*_nodeThrowStatement) _statementNode() {}
func (*_nodeTryStatement) _statementNode() {}
func (*_nodeVariableStatement) _statementNode() {}
func (*_nodeWhileStatement) _statementNode() {}
func (*_nodeWithStatement) _statementNode() {}
func (*nodeBlockStatement) statementNode() {}
func (*nodeBranchStatement) statementNode() {}
func (*nodeCaseStatement) statementNode() {}
func (*nodeCatchStatement) statementNode() {}
func (*nodeDebuggerStatement) statementNode() {}
func (*nodeDoWhileStatement) statementNode() {}
func (*nodeEmptyStatement) statementNode() {}
func (*nodeExpressionStatement) statementNode() {}
func (*nodeForInStatement) statementNode() {}
func (*nodeForStatement) statementNode() {}
func (*nodeIfStatement) statementNode() {}
func (*nodeLabelledStatement) statementNode() {}
func (*nodeReturnStatement) statementNode() {}
func (*nodeSwitchStatement) statementNode() {}
func (*nodeThrowStatement) statementNode() {}
func (*nodeTryStatement) statementNode() {}
func (*nodeVariableStatement) statementNode() {}
func (*nodeWhileStatement) statementNode() {}
func (*nodeWithStatement) statementNode() {}

View file

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

View file

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

View file

@ -3,56 +3,55 @@
/*
Package dbg is a println/printf/log-debugging utility library.
import (
Dbg "github.com/robertkrimen/dbg"
)
import (
Dbg "github.com/robertkrimen/dbg"
)
dbg, dbgf := Dbg.New()
dbg, dbgf := Dbg.New()
dbg("Emit some debug stuff", []byte{120, 121, 122, 122, 121}, math.Pi)
# "2013/01/28 16:50:03 Emit some debug stuff [120 121 122 122 121] 3.141592653589793"
dbg("Emit some debug stuff", []byte{120, 121, 122, 122, 121}, math.Pi)
# "2013/01/28 16:50:03 Emit some debug stuff [120 121 122 122 121] 3.141592653589793"
dbgf("With a %s formatting %.2f", "little", math.Pi)
# "2013/01/28 16:51:55 With a little formatting (3.14)"
dbgf("With a %s formatting %.2f", "little", math.Pi)
# "2013/01/28 16:51:55 With a little formatting (3.14)"
dbgf("%/fatal//A fatal debug statement: should not be here")
# "A fatal debug statement: should not be here"
# ...and then, os.Exit(1)
dbgf("%/fatal//A fatal debug statement: should not be here")
# "A fatal debug statement: should not be here"
# ...and then, os.Exit(1)
dbgf("%/panic//Can also panic %s", "this")
# "Can also panic this"
# ...as a panic, equivalent to: panic("Can also panic this")
dbgf("%/panic//Can also panic %s", "this")
# "Can also panic this"
# ...as a panic, equivalent to: panic("Can also panic this")
dbgf("Any %s arguments without a corresponding %%", "extra", "are treated like arguments to dbg()")
# "2013/01/28 17:14:40 Any extra arguments (without a corresponding %) are treated like arguments to dbg()"
dbgf("Any %s arguments without a corresponding %%", "extra", "are treated like arguments to dbg()")
# "2013/01/28 17:14:40 Any extra arguments (without a corresponding %) are treated like arguments to dbg()"
dbgf("%d %d", 1, 2, 3, 4, 5)
# "2013/01/28 17:16:32 Another example: 1 2 3 4 5"
dbgf("%d %d", 1, 2, 3, 4, 5)
# "2013/01/28 17:16:32 Another example: 1 2 3 4 5"
dbgf("%@: Include the function name for a little context (via %s)", "%@")
# "2013... github.com/robertkrimen/dbg.TestSynopsis: Include the function name for a little context (via %@)"
dbgf("%@: Include the function name for a little context (via %s)", "%@")
# "2013... github.com/robertkrimen/dbg.TestSynopsis: Include the function name for a little context (via %@)"
By default, dbg uses log (log.Println, log.Printf, log.Panic, etc.) for output.
However, you can also provide your own output destination by invoking dbg.New with
a customization function:
import (
"bytes"
Dbg "github.com/robertkrimen/dbg"
"os"
)
import (
"bytes"
Dbg "github.com/robertkrimen/dbg"
"os"
)
# dbg to os.Stderr
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr)
})
# A slightly contrived example:
var buffer bytes.Buffer
dbg, dbgf := New(func(dbgr *Dbgr) {
dbgr.SetOutput(&buffer)
})
# dbg to os.Stderr
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr)
})
# A slightly contrived example:
var buffer bytes.Buffer
dbg, dbgf := New(func(dbgr *Dbgr) {
dbgr.SetOutput(&buffer)
})
*/
package dbg
@ -63,7 +62,7 @@ import (
"log"
"os"
"regexp"
"runtime"
goruntime "runtime"
"strings"
"unicode"
)
@ -130,30 +129,28 @@ func parseFormat(format string) (frmt _frmt) {
}
type Dbgr struct {
emit _emit
emit emit
}
type DbgFunction func(values ...interface{})
func NewDbgr() *Dbgr {
self := &Dbgr{}
return self
return &Dbgr{}
}
/*
New will create and return a pair of debugging functions. You can customize where
they output to by passing in an (optional) customization function:
import (
Dbg "github.com/robertkrimen/dbg"
"os"
)
# dbg to os.Stderr
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr)
})
import (
Dbg "github.com/robertkrimen/dbg"
"os"
)
# dbg to os.Stderr
dbg, dbgf := Dbg.New(func(dbgr *Dbgr) {
dbgr.SetOutput(os.Stderr)
})
*/
func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) {
dbgr := NewDbgr()
@ -165,26 +162,25 @@ func New(options ...interface{}) (dbg DbgFunction, dbgf DbgFunction) {
return dbgr.DbgDbgf()
}
func (self Dbgr) Dbg(values ...interface{}) {
self.getEmit().emit(_frmt{}, "", values...)
func (d Dbgr) Dbg(values ...interface{}) {
d.getEmit().emit(_frmt{}, "", values...)
}
func (self Dbgr) Dbgf(values ...interface{}) {
self.dbgf(values...)
func (d Dbgr) Dbgf(values ...interface{}) {
d.dbgf(values...)
}
func (self Dbgr) DbgDbgf() (dbg DbgFunction, dbgf DbgFunction) {
func (d Dbgr) DbgDbgf() (dbg DbgFunction, dbgf DbgFunction) {
dbg = func(vl ...interface{}) {
self.Dbg(vl...)
d.Dbg(vl...)
}
dbgf = func(vl ...interface{}) {
self.dbgf(vl...)
d.dbgf(vl...)
}
return dbg, dbgf // Redundant, but...
}
func (self Dbgr) dbgf(values ...interface{}) {
func (d Dbgr) dbgf(values ...interface{}) {
var frmt _frmt
if len(values) > 0 {
tmp := fmt.Sprint(values[0])
@ -192,7 +188,7 @@ func (self Dbgr) dbgf(values ...interface{}) {
values = values[1:]
}
buffer_f := bytes.Buffer{}
buf := bytes.Buffer{}
format := frmt.format
end := len(format)
for at := 0; at < end; {
@ -201,7 +197,7 @@ func (self Dbgr) dbgf(values ...interface{}) {
at++
}
if at > last {
buffer_f.WriteString(format[last:at])
buf.WriteString(format[last:at])
}
if at >= end {
break
@ -211,100 +207,97 @@ func (self Dbgr) dbgf(values ...interface{}) {
// format[at] == ?
if format[at] == '@' {
depth := 2
pc, _, _, _ := runtime.Caller(depth)
name := runtime.FuncForPC(pc).Name()
buffer_f.WriteString(name)
pc, _, _, _ := goruntime.Caller(depth)
name := goruntime.FuncForPC(pc).Name()
buf.WriteString(name)
} else {
buffer_f.WriteString(format[at-1 : at+1])
buf.WriteString(format[at-1 : at+1])
}
at++
}
//values_f := append([]interface{}{}, values[0:frmt.operandCount]...)
values_f := values[0:frmt.operandCount]
values_dbg := values[frmt.operandCount:]
if len(values_dbg) > 0 {
//valuesF := append([]interface{}{}, values[0:frmt.operandCount]...)
valuesF := values[0:frmt.operandCount]
valuesDbg := values[frmt.operandCount:]
if len(valuesDbg) > 0 {
// Adjust frmt.format:
// (%v instead of %s because: frmt.check)
{
tmp := format
if len(tmp) > 0 {
if unicode.IsSpace(rune(tmp[len(tmp)-1])) {
buffer_f.WriteString("%v")
} else {
buffer_f.WriteString(" %v")
}
} else if frmt.check {
// Performing a check, so no output
tmp := format
if len(tmp) > 0 {
if unicode.IsSpace(rune(tmp[len(tmp)-1])) {
buf.WriteString("%v")
} else {
buffer_f.WriteString("%v")
buf.WriteString(" %v")
}
} else if frmt.check {
// Performing a check, so no output
} else {
buf.WriteString("%v")
}
// Adjust values_f:
// Adjust valuesF:
if !frmt.check {
tmp := []string{}
for _, value := range values_dbg {
for _, value := range valuesDbg {
tmp = append(tmp, fmt.Sprintf("%v", value))
}
// First, make a copy of values_f, so we avoid overwriting values_dbg when appending
values_f = append([]interface{}{}, values_f...)
values_f = append(values_f, strings.Join(tmp, " "))
// First, make a copy of valuesF, so we avoid overwriting valuesDbg when appending
valuesF = append([]interface{}{}, valuesF...)
valuesF = append(valuesF, strings.Join(tmp, " "))
}
}
format = buffer_f.String()
format = buf.String()
if frmt.check {
// We do not actually emit to the log, but panic if
// 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 format == "" {
panic(value)
} else {
panic(fmt.Sprintf(format, append(values_f, value)...))
panic(fmt.Sprintf(format, append(valuesF, value)...))
}
}
}
} else {
self.getEmit().emit(frmt, format, values_f...)
d.getEmit().emit(frmt, format, valuesF...)
}
}
// Idiot-proof &Dbgr{}, etc.
func (self *Dbgr) getEmit() _emit {
if self.emit == nil {
self.emit = standardEmit()
func (d *Dbgr) getEmit() emit {
if d.emit == nil {
d.emit = standardEmit()
}
return self.emit
return d.emit
}
// SetOutput will accept the following as a destination for output:
//
// *log.Logger Print*/Panic*/Fatal* of the logger
// io.Writer -
// nil Reset to the default output (os.Stderr)
// "log" Print*/Panic*/Fatal* via the "log" package
//
func (self *Dbgr) SetOutput(output interface{}) {
// *log.Logger Print*/Panic*/Fatal* of the logger
// io.Writer -
// nil Reset to the default output (os.Stderr)
// "log" Print*/Panic*/Fatal* via the "log" package
func (d *Dbgr) SetOutput(output interface{}) {
if output == nil {
self.emit = standardEmit()
d.emit = standardEmit()
return
}
switch output := output.(type) {
case *log.Logger:
self.emit = _emitLogger{
d.emit = emitLogger{
logger: output,
}
return
case io.Writer:
self.emit = _emitWriter{
d.emit = emitWriter{
writer: output,
}
return
case string:
if output == "log" {
self.emit = _emitLog{}
d.emit = emitLog{}
return
}
}
@ -315,8 +308,8 @@ func (self *Dbgr) SetOutput(output interface{}) {
// = emit = //
// ======== //
func standardEmit() _emit {
return _emitWriter{
func standardEmit() emit {
return emitWriter{
writer: os.Stderr,
}
}
@ -329,50 +322,50 @@ func ln(tmp string) string {
return tmp
}
type _emit interface {
type emit interface {
emit(_frmt, string, ...interface{})
}
type _emitWriter struct {
type emitWriter struct {
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 == "" {
fmt.Fprintln(self.writer, values...)
fmt.Fprintln(ew.writer, values...)
} else {
if frmt.panic {
panic(fmt.Sprintf(format, values...))
}
fmt.Fprintf(self.writer, ln(format), values...)
fmt.Fprintf(ew.writer, ln(format), values...)
if frmt.fatal {
os.Exit(1)
}
}
}
type _emitLogger struct {
type emitLogger struct {
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 == "" {
self.logger.Println(values...)
el.logger.Println(values...)
} else {
if frmt.panic {
self.logger.Panicf(format, values...)
el.logger.Panicf(format, values...)
} else if frmt.fatal {
self.logger.Fatalf(format, values...)
el.logger.Fatalf(format, values...)
} 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 == "" {
log.Println(values...)
} else {

View file

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

View file

@ -8,12 +8,12 @@ import (
"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
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) {
return NaNValue()
}
@ -26,61 +26,57 @@ func (self *_runtime) evaluateDivide(left float64, right float64) Value {
if math.IsInf(left, 0) {
if math.Signbit(left) == math.Signbit(right) {
return positiveInfinityValue()
} else {
return negativeInfinityValue()
}
return negativeInfinityValue()
}
if math.IsInf(right, 0) {
if math.Signbit(left) == math.Signbit(right) {
return positiveZeroValue()
} else {
return negativeZeroValue()
}
return negativeZeroValue()
}
if right == 0 {
if math.Signbit(left) == math.Signbit(right) {
return positiveInfinityValue()
} else {
return negativeInfinityValue()
}
return negativeInfinityValue()
}
return toValue_float64(left / right)
return float64Value(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
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()
switch operator {
// Additive
case token.PLUS:
leftValue = toPrimitive(leftValue)
leftValue = toPrimitiveValue(leftValue)
rightValue := right.resolve()
rightValue = toPrimitive(rightValue)
rightValue = toPrimitiveValue(rightValue)
if leftValue.IsString() || rightValue.IsString() {
return toValue_string(strings.Join([]string{leftValue.string(), rightValue.string()}, ""))
} else {
return toValue_float64(leftValue.float64() + rightValue.float64())
return stringValue(strings.Join([]string{leftValue.string(), rightValue.string()}, ""))
}
return float64Value(leftValue.float64() + rightValue.float64())
case token.MINUS:
rightValue := right.resolve()
return toValue_float64(leftValue.float64() - rightValue.float64())
return float64Value(leftValue.float64() - rightValue.float64())
// Multiplicative
case token.MULTIPLY:
rightValue := right.resolve()
return toValue_float64(leftValue.float64() * rightValue.float64())
return float64Value(leftValue.float64() * rightValue.float64())
case token.SLASH:
rightValue := right.resolve()
return self.evaluateDivide(leftValue.float64(), rightValue.float64())
return rt.evaluateDivide(leftValue.float64(), rightValue.float64())
case token.REMAINDER:
rightValue := right.resolve()
return toValue_float64(math.Mod(leftValue.float64(), rightValue.float64()))
return float64Value(math.Mod(leftValue.float64(), rightValue.float64()))
// Logical
case token.LOGICAL_AND:
@ -88,65 +84,65 @@ func (self *_runtime) calculateBinaryExpression(operator token.Token, left Value
if !left {
return falseValue
}
return toValue_bool(right.resolve().bool())
return boolValue(right.resolve().bool())
case token.LOGICAL_OR:
left := leftValue.bool()
if left {
return trueValue
}
return toValue_bool(right.resolve().bool())
return boolValue(right.resolve().bool())
// Bitwise
case token.AND:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) & toInt32(rightValue))
return int32Value(toInt32(leftValue) & toInt32(rightValue))
case token.OR:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) | toInt32(rightValue))
return int32Value(toInt32(leftValue) | toInt32(rightValue))
case token.EXCLUSIVE_OR:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) ^ toInt32(rightValue))
return int32Value(toInt32(leftValue) ^ toInt32(rightValue))
// Shift
// (Masking of 0x1f is to restrict the shift to a maximum of 31 places)
case token.SHIFT_LEFT:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) << (toUint32(rightValue) & 0x1f))
return int32Value(toInt32(leftValue) << (toUint32(rightValue) & 0x1f))
case token.SHIFT_RIGHT:
rightValue := right.resolve()
return toValue_int32(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f))
return int32Value(toInt32(leftValue) >> (toUint32(rightValue) & 0x1f))
case token.UNSIGNED_SHIFT_RIGHT:
rightValue := right.resolve()
// 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:
rightValue := right.resolve()
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:
rightValue := right.resolve()
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))
}
type _lessThanResult int
type lessThanResult int
const (
lessThanFalse _lessThanResult = iota
lessThanFalse lessThanResult = iota
lessThanTrue
lessThanUndefined
)
func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult {
func calculateLessThan(left Value, right Value, leftFirst bool) lessThanResult {
var x, y Value
if leftFirst {
x = toNumberPrimitive(left)
@ -175,46 +171,46 @@ func calculateLessThan(left Value, right Value, leftFirst bool) _lessThanResult
return lessThanFalse
}
// FIXME Probably a map is not the most efficient way to do this
var lessThanTable [4](map[_lessThanResult]bool) = [4](map[_lessThanResult]bool){
// FIXME Probably a map is not the most efficient way to do this.
var lessThanTable [4](map[lessThanResult]bool) = [4](map[lessThanResult]bool){
// <
map[_lessThanResult]bool{
map[lessThanResult]bool{
lessThanFalse: false,
lessThanTrue: true,
lessThanUndefined: false,
},
// >
map[_lessThanResult]bool{
map[lessThanResult]bool{
lessThanFalse: false,
lessThanTrue: true,
lessThanUndefined: false,
},
// <=
map[_lessThanResult]bool{
map[lessThanResult]bool{
lessThanFalse: true,
lessThanTrue: false,
lessThanUndefined: false,
},
// >=
map[_lessThanResult]bool{
map[lessThanResult]bool{
lessThanFalse: true,
lessThanTrue: 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?
// TODO This might be redundant now (with regards to evaluateComparison)
x := left.resolve()
y := right.resolve()
kindEqualKind := false
var kindEqualKind bool
var negate bool
result := true
negate := false
switch comparator {
case token.LESS:
@ -238,27 +234,28 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
negate = true
fallthrough
case token.EQUAL:
if x.kind == y.kind {
switch {
case x.kind == y.kind:
kindEqualKind = true
} else if x.kind <= valueNull && y.kind <= valueNull {
case x.kind <= valueNull && y.kind <= valueNull:
result = true
} else if x.kind <= valueNull || y.kind <= valueNull {
case x.kind <= valueNull || y.kind <= valueNull:
result = false
} else if x.kind <= valueString && y.kind <= valueString {
case x.kind <= valueString && y.kind <= valueString:
result = x.float64() == y.float64()
} else if x.kind == valueBoolean {
result = self.calculateComparison(token.EQUAL, toValue_float64(x.float64()), y)
} else if y.kind == valueBoolean {
result = self.calculateComparison(token.EQUAL, x, toValue_float64(y.float64()))
} else if x.kind == valueObject {
result = self.calculateComparison(token.EQUAL, toPrimitive(x), y)
} else if y.kind == valueObject {
result = self.calculateComparison(token.EQUAL, x, toPrimitive(y))
} else {
panic(hereBeDragons("Unable to test for equality: %v ==? %v", x, y))
case x.kind == valueBoolean:
result = rt.calculateComparison(token.EQUAL, float64Value(x.float64()), y)
case y.kind == valueBoolean:
result = rt.calculateComparison(token.EQUAL, x, float64Value(y.float64()))
case x.kind == valueObject:
result = rt.calculateComparison(token.EQUAL, toPrimitiveValue(x), y)
case y.kind == valueObject:
result = rt.calculateComparison(token.EQUAL, x, toPrimitiveValue(y))
default:
panic(fmt.Sprintf("unknown types for equal: %v ==? %v", x, y))
}
default:
panic(fmt.Errorf("Unknown comparator %s", comparator.String()))
panic(fmt.Sprintf("unknown comparator %s", comparator.String()))
}
if kindEqualKind {
@ -278,7 +275,7 @@ func (self *_runtime) calculateComparison(comparator token.Token, left Value, ri
case valueBoolean:
result = x.bool() == y.bool()
case valueObject:
result = x._object() == y._object()
result = x.object() == y.object()
default:
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
Line int // The line number, starting at 1
Column int // The column number, starting at 1 (The character count)
}
// A Position is valid if the line number is > 0.
func (self *Position) isValid() bool {
return self.Line > 0
func (p *Position) isValid() bool {
return p.Line > 0
}
// 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
// file An invalid position with filename
// - An invalid position without filename
func (self *Position) String() string {
str := self.Filename
if self.isValid() {
func (p *Position) String() string {
str := p.Filename
if p.isValid() {
if str != "" {
str += ":"
}
str += fmt.Sprintf("%d:%d", self.Line, self.Column)
str += fmt.Sprintf("%d:%d", p.Line, p.Column)
}
if str == "" {
str = "-"
@ -49,10 +48,8 @@ func (self *Position) String() string {
return str
}
// FileSet
// A FileSet represents a set of source files.
type FileSet struct {
type FileSet struct { //nolint: golint
files []*File
last *File
}
@ -60,27 +57,28 @@ type FileSet struct {
// AddFile adds a new file with the given filename and src.
//
// This an internal method, but exported for cross-package use.
func (self *FileSet) AddFile(filename, src string) int {
base := self.nextBase()
func (fs *FileSet) AddFile(filename, src string) int {
base := fs.nextBase()
file := &File{
name: filename,
src: src,
base: base,
}
self.files = append(self.files, file)
self.last = file
fs.files = append(fs.files, file)
fs.last = file
return base
}
func (self *FileSet) nextBase() int {
if self.last == nil {
func (fs *FileSet) nextBase() int {
if fs.last == nil {
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 {
for _, file := range self.files {
// File returns the File at idx or nil if not found.
func (fs *FileSet) File(idx Idx) *File {
for _, file := range fs.files {
if idx <= Idx(file.base+len(file.src)) {
return file
}
@ -89,8 +87,8 @@ func (self *FileSet) File(idx Idx) *File {
}
// Position converts an Idx in the FileSet into a Position.
func (self *FileSet) Position(idx Idx) *Position {
for _, file := range self.files {
func (fs *FileSet) Position(idx Idx) *Position {
for _, file := range fs.files {
if idx <= Idx(file.base+len(file.src)) {
return file.Position(idx - Idx(file.base))
}
@ -99,6 +97,7 @@ func (self *FileSet) Position(idx Idx) *Position {
return nil
}
// File represents a file to parse.
type File struct {
name string
src string
@ -106,6 +105,7 @@ type File struct {
sm *sourcemap.Consumer
}
// NewFile returns a new file with the given filename, src and base.
func NewFile(filename, src string, base int) *File {
return &File{
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 {
fl.sm = sm
return fl
}
// Name returns the name of fl.
func (fl *File) Name() string {
return fl.name
}
// Source returns the source of fl.
func (fl *File) Source() string {
return fl.src
}
// Base returns the base of fl.
func (fl *File) Base() int {
return fl.base
}
// Position returns the position at idx or nil if not valid.
func (fl *File) Position(idx Idx) *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 (
prototypeValueObject = interface{}(nil)
prototypeValueFunction = _nativeFunctionObject{
prototypeValueFunction = nativeFunctionObject{
call: func(_ FunctionCall) Value {
return Value{}
},
}
prototypeValueString = _stringASCII("")
prototypeValueString = stringASCII("")
// TODO Make this just false?
prototypeValueBoolean = Value{
kind: valueBoolean,
@ -22,7 +22,7 @@ var (
kind: valueNumber,
value: 0,
}
prototypeValueDate = _dateObject{
prototypeValueDate = dateObject{
epoch: 0,
isNaN: false,
time: time.Unix(0, 0).UTC(),
@ -31,7 +31,7 @@ var (
value: 0,
},
}
prototypeValueRegExp = _regExpObject{
prototypeValueRegExp = regExpObject{
regularExpression: nil,
global: false,
ignoreCase: false,
@ -41,102 +41,101 @@ var (
}
)
func newContext() *_runtime {
self := &_runtime{}
func newContext() *runtime {
rt := &runtime{}
self.globalStash = self.newObjectStash(nil, nil)
self.globalObject = self.globalStash.object
rt.globalStash = rt.newObjectStash(nil, nil)
rt.globalObject = rt.globalStash.object
_newContext(self)
rt.newContext()
self.eval = self.globalObject.property["eval"].value.(Value).value.(*_object)
self.globalObject.prototype = self.global.ObjectPrototype
rt.eval = rt.globalObject.property["eval"].value.(Value).value.(*object)
rt.globalObject.prototype = rt.global.ObjectPrototype
return self
return rt
}
func (runtime *_runtime) newBaseObject() *_object {
self := newObject(runtime, "")
return self
func (rt *runtime) newBaseObject() *object {
return newObject(rt, "")
}
func (runtime *_runtime) newClassObject(class string) *_object {
return newObject(runtime, class)
func (rt *runtime) newClassObject(class string) *object {
return newObject(rt, class)
}
func (runtime *_runtime) newPrimitiveObject(class string, value Value) *_object {
self := runtime.newClassObject(class)
self.value = value
return self
func (rt *runtime) newPrimitiveObject(class string, value Value) *object {
o := rt.newClassObject(class)
o.value = value
return o
}
func (self *_object) primitiveValue() Value {
switch value := self.value.(type) {
func (o *object) primitiveValue() Value {
switch value := o.value.(type) {
case Value:
return value
case _stringObject:
return toValue_string(value.String())
case stringObjecter:
return stringValue(value.String())
}
return Value{}
}
func (self *_object) hasPrimitive() bool {
switch self.value.(type) {
case Value, _stringObject:
func (o *object) hasPrimitive() bool { //nolint: unused
switch o.value.(type) {
case Value, stringObjecter:
return true
}
return false
}
func (runtime *_runtime) newObject() *_object {
self := runtime.newClassObject(classObject)
self.prototype = runtime.global.ObjectPrototype
return self
func (rt *runtime) newObject() *object {
o := rt.newClassObject(classObjectName)
o.prototype = rt.global.ObjectPrototype
return o
}
func (runtime *_runtime) newArray(length uint32) *_object {
self := runtime.newArrayObject(length)
self.prototype = runtime.global.ArrayPrototype
return self
func (rt *runtime) newArray(length uint32) *object {
o := rt.newArrayObject(length)
o.prototype = rt.global.ArrayPrototype
return o
}
func (runtime *_runtime) newArrayOf(valueArray []Value) *_object {
self := runtime.newArray(uint32(len(valueArray)))
func (rt *runtime) newArrayOf(valueArray []Value) *object {
o := rt.newArray(uint32(len(valueArray)))
for index, value := range valueArray {
if value.isEmpty() {
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 {
self := runtime.newStringObject(value)
self.prototype = runtime.global.StringPrototype
return self
func (rt *runtime) newString(value Value) *object {
o := rt.newStringObject(value)
o.prototype = rt.global.StringPrototype
return o
}
func (runtime *_runtime) newBoolean(value Value) *_object {
self := runtime.newBooleanObject(value)
self.prototype = runtime.global.BooleanPrototype
return self
func (rt *runtime) newBoolean(value Value) *object {
o := rt.newBooleanObject(value)
o.prototype = rt.global.BooleanPrototype
return o
}
func (runtime *_runtime) newNumber(value Value) *_object {
self := runtime.newNumberObject(value)
self.prototype = runtime.global.NumberPrototype
return self
func (rt *runtime) newNumber(value Value) *object {
o := rt.newNumberObject(value)
o.prototype = rt.global.NumberPrototype
return o
}
func (runtime *_runtime) newRegExp(patternValue Value, flagsValue Value) *_object {
func (rt *runtime) newRegExp(patternValue Value, flagsValue Value) *object {
pattern := ""
flags := ""
if object := patternValue._object(); object != nil && object.class == classRegExp {
if obj := patternValue.object(); obj != nil && obj.class == classRegExpName {
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
flags = regExp.flags
} 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 {
self := runtime.newRegExpObject(pattern, flags)
self.prototype = runtime.global.RegExpPrototype
return self
func (rt *runtime) newRegExpDirect(pattern string, flags string) *object {
o := rt.newRegExpObject(pattern, flags)
o.prototype = rt.global.RegExpPrototype
return o
}
// TODO Should (probably) be one argument, right? This is redundant
func (runtime *_runtime) newDate(epoch float64) *_object {
self := runtime.newDateObject(epoch)
self.prototype = runtime.global.DatePrototype
return self
// TODO Should (probably) be one argument, right? This is redundant.
func (rt *runtime) newDate(epoch float64) *object {
o := rt.newDateObject(epoch)
o.prototype = rt.global.DatePrototype
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 {
case "EvalError":
return runtime.newEvalError(message)
return rt.newEvalError(message)
case "TypeError":
return runtime.newTypeError(message)
return rt.newTypeError(message)
case "RangeError":
return runtime.newRangeError(message)
return rt.newRangeError(message)
case "ReferenceError":
return runtime.newReferenceError(message)
return rt.newReferenceError(message)
case "SyntaxError":
return runtime.newSyntaxError(message)
return rt.newSyntaxError(message)
case "URIError":
return runtime.newURIError(message)
return rt.newURIError(message)
}
self := runtime.newErrorObject(name, message, stackFramesToPop)
self.prototype = runtime.global.ErrorPrototype
obj := rt.newErrorObject(name, message, stackFramesToPop)
obj.prototype = rt.global.ErrorPrototype
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 {
self := runtime.newNativeFunctionObject(name, file, line, _nativeFunction, 0)
self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false)
return self
func (rt *runtime) newNativeFunction(name, file string, line int, fn nativeFunction) *object {
o := rt.newNativeFunctionObject(name, file, line, fn, 0)
o.prototype = rt.global.FunctionPrototype
prototype := rt.newObject()
o.defineProperty("prototype", objectValue(prototype), 0o100, false)
prototype.defineProperty("constructor", objectValue(o), 0o100, false)
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
self := runtime.newNodeFunctionObject(node, scopeEnvironment)
self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0101, false)
return self
o := rt.newNodeFunctionObject(node, scopeEnvironment)
o.prototype = rt.global.FunctionPrototype
prototype := rt.newObject()
o.defineProperty("prototype", objectValue(prototype), 0o100, false)
prototype.defineProperty("constructor", objectValue(o), 0o101, false)
return o
}
// FIXME Only in one place...
func (runtime *_runtime) newBoundFunction(target *_object, this Value, argumentList []Value) *_object {
self := runtime.newBoundFunctionObject(target, this, argumentList)
self.prototype = runtime.global.FunctionPrototype
prototype := runtime.newObject()
self.defineProperty("prototype", toValue_object(prototype), 0100, false)
prototype.defineProperty("constructor", toValue_object(self), 0100, false)
return self
func (rt *runtime) newBoundFunction(target *object, this Value, argumentList []Value) *object {
o := rt.newBoundFunctionObject(target, this, argumentList)
o.prototype = rt.global.FunctionPrototype
prototype := rt.newObject()
o.defineProperty("prototype", objectValue(prototype), 0o100, false)
prototype.defineProperty("constructor", objectValue(o), 0o100, false)
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"
var (
defaultLanguage = language.MustParse("en-US")
)
var defaultLanguage = language.MustParse("en-US")

View file

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

View file

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

View file

@ -39,10 +39,8 @@ Set a string
Get the value of an expression
value, _ = vm.Run("xyzzy.length")
{
// value is an int64 with a value of 16
value, _ := value.ToInteger()
}
// iv is an int64 with a value of 16
iv, _ := value.ToInteger()
An error happens
@ -216,9 +214,7 @@ http://github.com/robertkrimen/natto
Here is some more discussion of the issue:
* http://book.mixu.net/node/ch2.html
* http://en.wikipedia.org/wiki/Reentrancy_%28computing%29
* http://aaroncrane.co.uk/2009/02/perl_safe_signals/
*/
package otto
@ -232,36 +228,41 @@ import (
"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 {
// 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.
Interrupt chan func()
runtime *_runtime
runtime *runtime
}
// New will allocate a new JavaScript runtime
// New will allocate a new JavaScript runtime.
func New() *Otto {
self := &Otto{
o := &Otto{
runtime: newContext(),
}
self.runtime.otto = self
self.runtime.traceLimit = 10
self.Set("console", self.runtime.newConsole())
o.runtime.otto = o
o.runtime.traceLimit = 10
if err := o.Set("console", o.runtime.newConsole()); err != nil {
panic(err)
}
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 {
self := &Otto{
runtime: otto.runtime.clone(),
func (o *Otto) clone() *Otto {
n := &Otto{
runtime: o.runtime.clone(),
}
self.runtime.otto = self
return self
n.runtime.otto = n
return n
}
// 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 Program, but if the AST has been modified, then runtime behavior is undefined.
func (self Otto) Run(src interface{}) (Value, error) {
value, err := self.runtime.cmpl_run(src, nil)
func (o Otto) Run(src interface{}) (Value, error) {
value, err := o.runtime.cmplRun(src, nil)
if !value.safe() {
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
// already defined in the current stack frame. This is most useful in, for
// example, a debugger call.
func (self Otto) Eval(src interface{}) (Value, error) {
if self.runtime.scope == nil {
self.runtime.enterGlobalScope()
defer self.runtime.leaveScope()
func (o Otto) Eval(src interface{}) (Value, error) {
if o.runtime.scope == nil {
o.runtime.enterGlobalScope()
defer o.runtime.leaveScope()
}
value, err := self.runtime.cmpl_eval(src, nil)
value, err := o.runtime.cmplEval(src, nil)
if !value.safe() {
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
// will be undefined.
func (self Otto) Get(name string) (Value, error) {
func (o Otto) Get(name string) (Value, error) {
value := Value{}
err := catchPanic(func() {
value = self.getValue(name)
value = o.getValue(name)
})
if !value.safe() {
value = Value{}
@ -330,8 +331,8 @@ func (self Otto) Get(name string) (Value, error) {
return value, err
}
func (self Otto) getValue(name string) Value {
return self.runtime.globalStash.getBinding(name, false)
func (o Otto) getValue(name string) Value {
return o.runtime.globalStash.getBinding(name, false)
}
// 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.
//
// If the top-level binding does not exist, it will be created.
func (self Otto) Set(name string, value interface{}) error {
{
value, err := self.ToValue(value)
if err != nil {
return err
}
err = catchPanic(func() {
self.setValue(name, value)
})
func (o Otto) Set(name string, value interface{}) error {
val, err := o.ToValue(value)
if err != nil {
return err
}
return catchPanic(func() {
o.setValue(name, val)
})
}
func (self Otto) setValue(name string, value Value) {
self.runtime.globalStash.setValue(name, value, false)
func (o Otto) setValue(name string, value Value) {
o.runtime.globalStash.setValue(name, value, false)
}
func (self Otto) SetDebuggerHandler(fn func(vm *Otto)) {
self.runtime.debugger = fn
// SetDebuggerHandler sets the debugger handler to fn.
func (o Otto) SetDebuggerHandler(fn func(vm *Otto)) {
o.runtime.debugger = fn
}
func (self Otto) SetRandomSource(fn func() float64) {
self.runtime.random = fn
// SetRandomSource sets the random source to fn.
func (o Otto) SetRandomSource(fn func() float64) {
o.runtime.random = fn
}
// 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
// happens outside the interpreter. So if your Go function is infinitely
// recursive, you're still in trouble.
func (self Otto) SetStackDepthLimit(limit int) {
self.runtime.stackLimit = limit
func (o Otto) SetStackDepthLimit(limit int) {
o.runtime.stackLimit = limit
}
// 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
// is 10. This is consistent with V8 and SpiderMonkey.
//
// TODO: expose via `Error.stackTraceLimit`
func (self Otto) SetStackTraceLimit(limit int) {
self.runtime.traceLimit = limit
// TODO: expose via `Error.stackTraceLimit`.
func (o Otto) SetStackTraceLimit(limit int) {
o.runtime.traceLimit = limit
}
// MakeCustomError creates a new Error object with the given name and message,
// returning it as a Value.
func (self Otto) MakeCustomError(name, message string) Value {
return self.runtime.toValue(self.runtime.newError(name, self.runtime.toValue(message), 0))
func (o Otto) MakeCustomError(name, message string) Value {
return o.runtime.toValue(o.runtime.newError(name, o.runtime.toValue(message), 0))
}
// MakeRangeError creates a new RangeError object with the given message,
// returning it as a Value.
func (self Otto) MakeRangeError(message string) Value {
return self.runtime.toValue(self.runtime.newRangeError(self.runtime.toValue(message)))
func (o Otto) MakeRangeError(message string) Value {
return o.runtime.toValue(o.runtime.newRangeError(o.runtime.toValue(message)))
}
// MakeSyntaxError creates a new SyntaxError object with the given message,
// returning it as a Value.
func (self Otto) MakeSyntaxError(message string) Value {
return self.runtime.toValue(self.runtime.newSyntaxError(self.runtime.toValue(message)))
func (o Otto) MakeSyntaxError(message string) Value {
return o.runtime.toValue(o.runtime.newSyntaxError(o.runtime.toValue(message)))
}
// MakeTypeError creates a new TypeError object with the given message,
// returning it as a Value.
func (self Otto) MakeTypeError(message string) Value {
return self.runtime.toValue(self.runtime.newTypeError(self.runtime.toValue(message)))
func (o Otto) MakeTypeError(message string) Value {
return o.runtime.toValue(o.runtime.newTypeError(o.runtime.toValue(message)))
}
// 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
// ten stack frames, and skipping any innermost native function stack frames.
func (self Otto) Context() Context {
return self.ContextSkip(10, true)
func (o Otto) Context() Context {
return o.ContextSkip(10, true)
}
// ContextLimit returns the current execution context of the vm, with a
// specific limit on the number of stack frames to traverse, skipping any
// innermost native function stack frames.
func (self Otto) ContextLimit(limit int) Context {
return self.ContextSkip(limit, true)
func (o Otto) ContextLimit(limit int) Context {
return o.ContextSkip(limit, true)
}
// ContextSkip returns the current execution context of the vm, with a
// specific limit on the number of stack frames to traverse, optionally
// 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
if self.runtime.scope == nil {
self.runtime.enterGlobalScope()
defer self.runtime.leaveScope()
if o.runtime.scope == nil {
o.runtime.enterGlobalScope()
defer o.runtime.leaveScope()
}
scope := self.runtime.scope
frame := scope.frame
curScope := o.runtime.scope
frm := curScope.frame
for skipNative && frame.native && scope.outer != nil {
scope = scope.outer
frame = scope.frame
for skipNative && frm.native && curScope.outer != nil {
curScope = curScope.outer
frm = curScope.frame
}
// Get location information
var ctx Context
ctx.Filename = "<unknown>"
ctx.Callee = frame.callee
ctx.Callee = frm.callee
switch {
case frame.native:
ctx.Filename = frame.nativeFile
ctx.Line = frame.nativeLine
case frm.native:
ctx.Filename = frm.nativeFile
ctx.Line = frm.nativeLine
ctx.Column = 0
case frame.file != nil:
case frm.file != nil:
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.Column = p.Column
@ -479,14 +481,14 @@ func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) {
}
// Get the current scope this Value
ctx.This = toValue_object(scope.this)
ctx.This = objectValue(curScope.this)
// Build stacktrace (up to 10 levels deep)
ctx.Symbols = make(map[string]Value)
ctx.Stacktrace = append(ctx.Stacktrace, frame.location())
ctx.Stacktrace = append(ctx.Stacktrace, frm.location())
for limit != 0 {
// Get variables
stash := scope.lexical
stash := curScope.lexical
for {
for _, name := range getStashProperties(stash) {
if _, ok := ctx.Symbols[name]; !ok {
@ -499,17 +501,17 @@ func (self Otto) ContextSkip(limit int, skipNative bool) (ctx Context) {
}
}
scope = scope.outer
if scope == nil {
curScope = curScope.outer
if curScope == nil {
break
}
if scope.frame.offset >= 0 {
ctx.Stacktrace = append(ctx.Stacktrace, scope.frame.location())
if curScope.frame.offset >= 0 {
ctx.Stacktrace = append(ctx.Stacktrace, curScope.frame.location())
}
limit--
}
return
return ctx
}
// 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
// // 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")
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{}
construct := false
@ -541,19 +543,19 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
}
// FIXME enterGlobalScope
self.runtime.enterGlobalScope()
o.runtime.enterGlobalScope()
defer func() {
self.runtime.leaveScope()
o.runtime.leaveScope()
}()
if !construct && this == nil {
program, err := self.runtime.cmpl_parse("", source+"()", nil)
program, err := o.runtime.cmplParse("", source+"()", nil)
if err == nil {
if node, ok := program.body[0].(*_nodeExpressionStatement); ok {
if node, ok := node.expression.(*_nodeCallExpression); ok {
if node, ok := program.body[0].(*nodeExpressionStatement); ok {
if node, ok := node.expression.(*nodeCallExpression); ok {
var value Value
err := catchPanic(func() {
value = self.runtime.cmpl_evaluate_nodeCallExpression(node, argumentList)
value = o.runtime.cmplEvaluateNodeCallExpression(node, argumentList)
})
if err != nil {
return Value{}, err
@ -563,35 +565,32 @@ func (self Otto) Call(source string, this interface{}, argumentList ...interface
}
}
} else {
value, err := self.ToValue(this)
value, err := o.ToValue(this)
if err != nil {
return Value{}, err
}
thisValue = value
}
{
this := thisValue
val := thisValue
fn, err := o.Run(source)
if err != nil {
return Value{}, err
}
fn, err := self.Run(source)
if err != nil {
return Value{}, err
}
if construct {
result, err := fn.constructSafe(self.runtime, this, argumentList...)
if err != nil {
return Value{}, err
}
return result, nil
}
result, err := fn.Call(this, argumentList...)
if construct {
result, err := fn.constructSafe(o.runtime, val, argumentList...)
if err != nil {
return Value{}, err
}
return result, nil
}
result, err := fn.Call(val, argumentList...)
if err != nil {
return Value{}, err
}
return result, nil
}
// 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
// nil and an error is returned.
func (self Otto) Object(source string) (*Object, error) {
value, err := self.runtime.cmpl_run(source, nil)
func (o Otto) Object(source string) (*Object, error) {
value, err := o.runtime.cmplRun(source, nil)
if err != nil {
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.
func (self Otto) ToValue(value interface{}) (Value, error) {
return self.runtime.safeToValue(value)
func (o Otto) ToValue(value interface{}) (Value, error) {
return o.runtime.safeToValue(value)
}
// 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.
//
// Be on the lookout for memory leaks or inadvertent sharing of resources.
func (in *Otto) Copy() *Otto {
func (o *Otto) Copy() *Otto {
out := &Otto{
runtime: in.runtime.clone(),
runtime: o.runtime.clone(),
}
out.runtime.otto = out
return out
}
// Object{}
// Object is the representation of a JavaScript object.
type Object struct {
object *_object
object *object
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.
//
// 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
// 2. The property is not actually a function
// 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...
// e.g., Object("JSON").Call("stringify", ...)
function, err := self.Get(name)
function, err := o.Get(name)
if err != nil {
return Value{}, err
}
return function.Call(self.Value(), argumentList...)
return function.Call(o.Value(), argumentList...)
}
// Value will return self as a value.
func (self Object) Value() Value {
return self.value
// Value returns the value of o.
func (o Object) Value() Value {
return o.value
}
// 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{}
err := catchPanic(func() {
value = self.object.get(name)
value = o.object.get(name)
})
if !value.safe() {
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),
// or there is an error during conversion of the given value.
func (self Object) Set(name string, value interface{}) error {
{
value, err := self.object.runtime.safeToValue(value)
if err != nil {
return err
}
err = catchPanic(func() {
self.object.put(name, value, true)
})
func (o Object) Set(name string, value interface{}) error {
val, err := o.object.runtime.safeToValue(value)
if err != nil {
return err
}
return catchPanic(func() {
o.object.put(name, val, true)
})
}
// Keys gets the keys for the given object.
//
// Equivalent to calling Object.keys on the object.
func (self Object) Keys() []string {
func (o Object) 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)
return true
})
@ -730,10 +717,10 @@ func (self Object) Keys() []string {
// KeysByParent gets the keys (and those of the parents) for the given object,
// in order of "closest" to "furthest".
func (self Object) KeysByParent() [][]string {
func (o Object) KeysByParent() [][]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
o.enumerate(false, func(name string) bool {
@ -759,28 +746,29 @@ func (self Object) KeysByParent() [][]string {
// Boolean
// Date
// RegExp
func (self Object) Class() string {
return self.object.class
func (o Object) Class() string {
return o.object.class
}
func (self Object) MarshalJSON() ([]byte, error) {
// MarshalJSON implements json.Marshaller.
func (o Object) MarshalJSON() ([]byte, error) {
var goValue interface{}
switch value := self.object.value.(type) {
case *_goStructObject:
switch value := o.object.value.(type) {
case *goStructObject:
goValue = value.value.Interface()
case *_goMapObject:
case *goMapObject:
goValue = value.value.Interface()
case *_goArrayObject:
case *goArrayObject:
goValue = value.value.Interface()
case *_goSliceObject:
case *goSliceObject:
goValue = value.value.Interface()
default:
// It's a JS object; pass it to JSON.stringify:
var result []byte
err := catchPanic(func() {
resultVal := builtinJSON_stringify(FunctionCall{
runtime: self.object.runtime,
ArgumentList: []Value{self.value},
resultVal := builtinJSONStringify(FunctionCall{
runtime: o.object.runtime,
ArgumentList: []Value{o.value},
})
result = []byte(resultVal.String())
})

View file

@ -3,28 +3,28 @@ package otto
import (
"fmt"
"regexp"
runtime_ "runtime"
goruntime "runtime"
"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 {
return isIdentifier_Regexp.MatchString(string_)
func isIdentifier(value string) bool {
return isIdentifierRegexp.MatchString(value)
}
func (self *_runtime) toValueArray(arguments ...interface{}) []Value {
func (rt *runtime) toValueArray(arguments ...interface{}) []Value {
length := len(arguments)
if length == 1 {
if valueArray, ok := arguments[0].([]Value); ok {
return valueArray
}
return []Value{self.toValue(arguments[0])}
return []Value{rt.toValue(arguments[0])}
}
valueArray := make([]Value, length)
for index, value := range arguments {
valueArray[index] = self.toValue(value)
valueArray[index] = rt.toValue(value)
}
return valueArray
@ -89,15 +89,13 @@ func valueToRangeIndex(indexValue Value, length int64, negativeIsZero bool) int6
if index < 0 {
index = 0
}
} else {
if index > length {
index = length
}
} else if index > length {
index = length
}
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)
if len(array) == 1 {
// 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
}
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)
// Assume the second argument is missing or undefined
length = int64(size)
length = size
if len(source) == 1 {
// If there is only the start argument, then length = size
return
return start, length
}
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
length = lengthValue.number().int64
}
return
return start, length
}
func hereBeDragons(arguments ...interface{}) string {
pc, _, _, _ := runtime_.Caller(1) //nolint: dogsled
name := runtime_.FuncForPC(pc).Name()
pc, _, _, _ := goruntime.Caller(1) //nolint: dogsled
name := goruntime.FuncForPC(pc).Name()
message := fmt.Sprintf("Here be dragons -- %s", name)
if len(arguments) > 0 {
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 (
err_UnexpectedToken = "Unexpected token %v"
err_UnexpectedEndOfInput = "Unexpected end of input"
errUnexpectedToken = "Unexpected token %v"
errUnexpectedEndOfInput = "Unexpected end of input"
)
// UnexpectedNumber: 'Unexpected number',
@ -20,7 +20,7 @@ const (
// NewlineAfterThrow: 'Illegal newline after throw',
// InvalidRegExp: 'Invalid regular expression',
// 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',
// MultipleDefaultsInSwitch: 'More than one default clause in switch statement',
// NoCatchOrFinally: 'Missing catch or finally after try',
@ -55,27 +55,27 @@ type Error struct {
// FIXME Should this be "SyntaxError"?
func (self Error) Error() string {
filename := self.Position.Filename
func (e Error) Error() string {
filename := e.Position.Filename
if filename == "" {
filename = "(anonymous)"
}
return fmt.Sprintf("%s: Line %d:%d %s",
filename,
self.Position.Line,
self.Position.Column,
self.Message,
e.Position.Line,
e.Position.Column,
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
switch place := place.(type) {
case int:
idx = self.idxOf(place)
idx = p.idxOf(place)
case file.Idx:
if place == 0 {
idx = self.idxOf(self.chrOffset)
idx = p.idxOf(p.chrOffset)
} else {
idx = place
}
@ -83,57 +83,69 @@ func (self *_parser) error(place interface{}, msg string, msgValues ...interface
panic(fmt.Errorf("error(%T, ...)", place))
}
position := self.position(idx)
position := p.position(idx)
msg = fmt.Sprintf(msg, msgValues...)
self.errors.Add(position, msg)
return self.errors[len(self.errors)-1]
p.errors.Add(position, msg)
}
func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error {
func (p *parser) errorUnexpected(idx file.Idx, chr rune) {
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 {
switch tkn {
case token.EOF:
return self.error(file.Idx(0), err_UnexpectedEndOfInput)
func (p *parser) errorUnexpectedToken(tkn token.Token) {
if tkn == token.EOF {
p.error(file.Idx(0), errUnexpectedEndOfInput)
return
}
value := tkn.String()
switch tkn {
case token.BOOLEAN, token.NULL:
value = self.literal
p.error(p.idx, errUnexpectedToken, p.literal)
case token.IDENTIFIER:
return self.error(self.idx, "Unexpected identifier")
p.error(p.idx, "Unexpected identifier")
case token.KEYWORD:
// 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:
return self.error(self.idx, "Unexpected number")
p.error(p.idx, "Unexpected number")
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.
type ErrorList []*Error //nolint: errname
// Add adds an Error with given position and message to an ErrorList.
func (self *ErrorList) Add(position file.Position, msg string) {
*self = append(*self, &Error{position, msg})
func (el *ErrorList) Add(position file.Position, msg string) {
*el = append(*el, &Error{position, msg})
}
// 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) }
func (self ErrorList) Swap(i, j int) { self[i], self[j] = self[j], self[i] }
func (self ErrorList) Less(i, j int) bool {
x := &self[i].Position
y := &self[j].Position
// Len implement sort.Interface.
func (el *ErrorList) Len() int {
return len(*el)
}
// 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 {
return true
}
@ -148,26 +160,28 @@ func (self ErrorList) Less(i, j int) bool {
return false
}
func (self ErrorList) Sort() {
sort.Sort(self)
// Sort sorts el.
func (el *ErrorList) Sort() {
sort.Sort(el)
}
// Error implements the Error interface.
func (self ErrorList) Error() string {
switch len(self) {
func (el *ErrorList) Error() string {
switch len(*el) {
case 0:
return "no errors"
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.
// If the list is empty, Err returns nil.
func (self ErrorList) Err() error {
if len(self) == 0 {
func (el *ErrorList) Err() error {
if len(*el) == 0 {
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"
)
type _chr struct {
type chr struct { //nolint: unused
value rune
width int
}
@ -55,49 +55,50 @@ func isIdentifierPart(chr rune) bool {
chr >= utf8.RuneSelf && (unicode.IsLetter(chr) || unicode.IsDigit(chr))
}
func (self *_parser) scanIdentifier() (string, error) {
offset := self.chrOffset
func (p *parser) scanIdentifier() (string, error) {
offset := p.chrOffset
parse := false
for isIdentifierPart(self.chr) {
if self.chr == '\\' {
distance := self.chrOffset - offset
self.read()
if self.chr != 'u' {
return "", fmt.Errorf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr))
for isIdentifierPart(p.chr) {
if p.chr == '\\' {
distance := p.chrOffset - offset
p.read()
if p.chr != 'u' {
return "", fmt.Errorf("invalid identifier escape character: %c (%s)", p.chr, string(p.chr))
}
parse = true
var value rune
for j := 0; j < 4; j++ {
self.read()
decimal, ok := hex2decimal(byte(self.chr))
p.read()
decimal, ok := hex2decimal(byte(p.chr))
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
}
if value == '\\' {
return "", fmt.Errorf("Invalid identifier escape value: %c (%s)", value, string(value))
} else if distance == 0 {
switch {
case value == '\\':
return "", fmt.Errorf("invalid identifier escape value: %c (%s)", value, string(value))
case distance == 0:
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) {
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 {
return parseStringLiteral(literal)
}
return literal, nil
}
// 7.2
func isLineWhiteSpace(chr rune) bool {
// 7.2.
func isLineWhiteSpace(chr rune) bool { //nolint: unused, deadcode
switch chr {
case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff':
return true
@ -109,7 +110,7 @@ func isLineWhiteSpace(chr rune) bool {
return unicode.IsSpace(chr)
}
// 7.3
// 7.3.
func isLineTerminator(chr rune) bool {
switch chr {
case '\u000a', '\u000d', '\u2028', '\u2029':
@ -118,19 +119,19 @@ func isLineTerminator(chr rune) bool {
return false
}
func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
self.implicitSemicolon = false
func (p *parser) scan() (tkn token.Token, literal string, idx file.Idx) { //nolint: nonamedreturns
p.implicitSemicolon = false
for {
self.skipWhiteSpace()
p.skipWhiteSpace()
idx = self.idxOf(self.chrOffset)
idx = p.idxOf(p.chrOffset)
insertSemicolon := false
switch chr := self.chr; {
switch chr := p.chr; {
case isIdentifierStart(chr):
var err error
literal, err = self.scanIdentifier()
literal, err = p.scanIdentifier()
if err != nil {
tkn = token.ILLEGAL
break
@ -142,23 +143,20 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
switch tkn {
case 0: // Not a keyword
if literal == "true" || literal == "false" {
self.insertSemicolon = true
tkn = token.BOOLEAN
return
} else if literal == "null" {
self.insertSemicolon = true
tkn = token.NULL
return
switch literal {
case "true", "false":
p.insertSemicolon = true
return token.BOOLEAN, literal, idx
case "null":
p.insertSemicolon = true
return token.NULL, literal, idx
}
case token.KEYWORD:
tkn = token.KEYWORD
if strict {
// TODO If strict and in strict mode, then this is not a break
break
}
return
return token.KEYWORD, literal, idx
case
token.THIS,
@ -167,40 +165,39 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
token.RETURN,
token.CONTINUE,
token.DEBUGGER:
self.insertSemicolon = true
return
p.insertSemicolon = true
return tkn, literal, idx
default:
return
return tkn, literal, idx
}
}
self.insertSemicolon = true
tkn = token.IDENTIFIER
return
p.insertSemicolon = true
return token.IDENTIFIER, literal, idx
case '0' <= chr && chr <= '9':
self.insertSemicolon = true
tkn, literal = self.scanNumericLiteral(false)
return
p.insertSemicolon = true
tkn, literal = p.scanNumericLiteral(false)
return tkn, literal, idx
default:
self.read()
p.read()
switch chr {
case -1:
if self.insertSemicolon {
self.insertSemicolon = false
self.implicitSemicolon = true
if p.insertSemicolon {
p.insertSemicolon = false
p.implicitSemicolon = true
}
tkn = token.EOF
case '\r', '\n', '\u2028', '\u2029':
self.insertSemicolon = false
self.implicitSemicolon = true
self.comments.AtLineBreak()
p.insertSemicolon = false
p.implicitSemicolon = true
p.comments.AtLineBreak()
continue
case ':':
tkn = token.COLON
case '.':
if digitValue(self.chr) < 10 {
if digitValue(p.chr) < 10 {
insertSemicolon = true
tkn, literal = self.scanNumericLiteral(true)
tkn, literal = p.scanNumericLiteral(true)
} else {
tkn = token.PERIOD
}
@ -224,68 +221,69 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
tkn = token.RIGHT_BRACE
insertSemicolon = true
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 {
insertSemicolon = true
}
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 {
insertSemicolon = true
}
case '*':
tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
tkn = p.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN)
case '/':
if self.chr == '/' {
if self.mode&StoreComments != 0 {
literal := string(self.readSingleLineComment())
self.comments.AddComment(ast.NewComment(literal, self.idx))
switch p.chr {
case '/':
if p.mode&StoreComments != 0 {
literal := string(p.readSingleLineComment())
p.comments.AddComment(ast.NewComment(literal, idx))
continue
}
self.skipSingleLineComment()
p.skipSingleLineComment()
continue
} else if self.chr == '*' {
if self.mode&StoreComments != 0 {
literal = string(self.readMultiLineComment())
self.comments.AddComment(ast.NewComment(literal, self.idx))
case '*':
if p.mode&StoreComments != 0 {
literal = string(p.readMultiLineComment())
p.comments.AddComment(ast.NewComment(literal, idx))
continue
}
self.skipMultiLineComment()
p.skipMultiLineComment()
continue
} else {
default:
// 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
}
case '%':
tkn = self.switch2(token.REMAINDER, token.REMAINDER_ASSIGN)
tkn = p.switch2(token.REMAINDER, token.REMAINDER_ASSIGN)
case '^':
tkn = self.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN)
tkn = p.switch2(token.EXCLUSIVE_OR, token.EXCLUSIVE_OR_ASSIGN)
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 '>':
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 '=':
tkn = self.switch2(token.ASSIGN, token.EQUAL)
if tkn == token.EQUAL && self.chr == '=' {
self.read()
tkn = p.switch2(token.ASSIGN, token.EQUAL)
if tkn == token.EQUAL && p.chr == '=' {
p.read()
tkn = token.STRICT_EQUAL
}
case '!':
tkn = self.switch2(token.NOT, token.NOT_EQUAL)
if tkn == token.NOT_EQUAL && self.chr == '=' {
self.read()
tkn = p.switch2(token.NOT, token.NOT_EQUAL)
if tkn == token.NOT_EQUAL && p.chr == '=' {
p.read()
tkn = token.STRICT_NOT_EQUAL
}
case '&':
if self.chr == '^' {
self.read()
tkn = self.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
if p.chr == '^' {
p.read()
tkn = p.switch2(token.AND_NOT, token.AND_NOT_ASSIGN)
} else {
tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
tkn = p.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND)
}
case '|':
tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
tkn = p.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR)
case '~':
tkn = token.BITWISE_NOT
case '?':
@ -294,49 +292,49 @@ func (self *_parser) scan() (tkn token.Token, literal string, idx file.Idx) {
insertSemicolon = true
tkn = token.STRING
var err error
literal, err = self.scanString(self.chrOffset - 1)
literal, err = p.scanString(p.chrOffset - 1)
if err != nil {
tkn = token.ILLEGAL
}
default:
self.errorUnexpected(idx, chr)
p.errorUnexpected(idx, chr)
tkn = token.ILLEGAL
}
}
self.insertSemicolon = insertSemicolon
return
p.insertSemicolon = insertSemicolon
return tkn, literal, idx
}
}
func (self *_parser) switch2(tkn0, tkn1 token.Token) token.Token {
if self.chr == '=' {
self.read()
func (p *parser) switch2(tkn0, tkn1 token.Token) token.Token {
if p.chr == '=' {
p.read()
return tkn1
}
return tkn0
}
func (self *_parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token {
if self.chr == '=' {
self.read()
func (p *parser) switch3(tkn0, tkn1 token.Token, chr2 rune, tkn2 token.Token) token.Token {
if p.chr == '=' {
p.read()
return tkn1
}
if self.chr == chr2 {
self.read()
if p.chr == chr2 {
p.read()
return tkn2
}
return tkn0
}
func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token {
if self.chr == '=' {
self.read()
func (p *parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token) token.Token {
if p.chr == '=' {
p.read()
return tkn1
}
if self.chr == chr2 {
self.read()
if self.chr == '=' {
self.read()
if p.chr == chr2 {
p.read()
if p.chr == '=' {
p.read()
return tkn3
}
return tkn2
@ -344,21 +342,21 @@ func (self *_parser) switch4(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token
return tkn0
}
func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token {
if self.chr == '=' {
self.read()
func (p *parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token.Token, chr3 rune, tkn4, tkn5 token.Token) token.Token {
if p.chr == '=' {
p.read()
return tkn1
}
if self.chr == chr2 {
self.read()
if self.chr == '=' {
self.read()
if p.chr == chr2 {
p.read()
if p.chr == '=' {
p.read()
return tkn3
}
if self.chr == chr3 {
self.read()
if self.chr == '=' {
self.read()
if p.chr == chr3 {
p.read()
if p.chr == '=' {
p.read()
return tkn5
}
return tkn4
@ -368,137 +366,137 @@ func (self *_parser) switch6(tkn0, tkn1 token.Token, chr2 rune, tkn2, tkn3 token
return tkn0
}
func (self *_parser) chrAt(index int) _chr {
value, width := utf8.DecodeRuneInString(self.str[index:])
return _chr{
func (p *parser) chrAt(index int) chr { //nolint: unused
value, width := utf8.DecodeRuneInString(p.str[index:])
return chr{
value: value,
width: width,
}
}
func (self *_parser) _peek() rune {
if self.offset+1 < self.length {
return rune(self.str[self.offset+1])
func (p *parser) peek() rune {
if p.offset+1 < p.length {
return rune(p.str[p.offset+1])
}
return -1
}
func (self *_parser) read() {
if self.offset < self.length {
self.chrOffset = self.offset
chr, width := rune(self.str[self.offset]), 1
func (p *parser) read() {
if p.offset < p.length {
p.chrOffset = p.offset
chr, width := rune(p.str[p.offset]), 1
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 {
self.error(self.chrOffset, "Invalid UTF-8 character")
p.error(p.chrOffset, "Invalid UTF-8 character")
}
}
self.offset += width
self.chr = chr
p.offset += width
p.chr = chr
} else {
self.chrOffset = self.length
self.chr = -1 // EOF
p.chrOffset = p.length
p.chr = -1 // EOF
}
}
// This is here since the functions are so similar
func (self *_RegExp_parser) read() {
if self.offset < self.length {
self.chrOffset = self.offset
chr, width := rune(self.str[self.offset]), 1
// This is here since the functions are so similar.
func (p *regExpParser) read() {
if p.offset < p.length {
p.chrOffset = p.offset
chr, width := rune(p.str[p.offset]), 1
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 {
self.error(self.chrOffset, "Invalid UTF-8 character")
p.error(p.chrOffset, "Invalid UTF-8 character")
}
}
self.offset += width
self.chr = chr
p.offset += width
p.chr = chr
} else {
self.chrOffset = self.length
self.chr = -1 // EOF
p.chrOffset = p.length
p.chr = -1 // EOF
}
}
func (self *_parser) readSingleLineComment() (result []rune) {
for self.chr != -1 {
self.read()
if isLineTerminator(self.chr) {
return
func (p *parser) readSingleLineComment() []rune {
var result []rune
for p.chr != -1 {
p.read()
if isLineTerminator(p.chr) {
return result
}
result = append(result, self.chr)
result = append(result, p.chr)
}
// Get rid of the trailing -1
result = result[:len(result)-1]
return
return result[:len(result)-1]
}
func (self *_parser) readMultiLineComment() (result []rune) {
self.read()
for self.chr >= 0 {
chr := self.chr
self.read()
if chr == '*' && self.chr == '/' {
self.read()
return
func (p *parser) readMultiLineComment() []rune {
var result []rune
p.read()
for p.chr >= 0 {
chr := p.chr
p.read()
if chr == '*' && p.chr == '/' {
p.read()
return result
}
result = append(result, chr)
}
self.errorUnexpected(0, self.chr)
p.errorUnexpected(0, p.chr)
return
return result
}
func (self *_parser) skipSingleLineComment() {
for self.chr != -1 {
self.read()
if isLineTerminator(self.chr) {
func (p *parser) skipSingleLineComment() {
for p.chr != -1 {
p.read()
if isLineTerminator(p.chr) {
return
}
}
}
func (self *_parser) skipMultiLineComment() {
self.read()
for self.chr >= 0 {
chr := self.chr
self.read()
if chr == '*' && self.chr == '/' {
self.read()
func (p *parser) skipMultiLineComment() {
p.read()
for p.chr >= 0 {
chr := p.chr
p.read()
if chr == '*' && p.chr == '/' {
p.read()
return
}
}
self.errorUnexpected(0, self.chr)
p.errorUnexpected(0, p.chr)
}
func (self *_parser) skipWhiteSpace() {
func (p *parser) skipWhiteSpace() {
for {
switch self.chr {
switch p.chr {
case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff':
self.read()
p.read()
continue
case '\r':
if self._peek() == '\n' {
self.comments.AtLineBreak()
self.read()
if p.peek() == '\n' {
p.comments.AtLineBreak()
p.read()
}
fallthrough
case '\u2028', '\u2029', '\n':
if self.insertSemicolon {
if p.insertSemicolon {
return
}
self.comments.AtLineBreak()
self.read()
p.comments.AtLineBreak()
p.read()
continue
}
if self.chr >= utf8.RuneSelf {
if unicode.IsSpace(self.chr) {
self.read()
if p.chr >= utf8.RuneSelf {
if unicode.IsSpace(p.chr) {
p.read()
continue
}
}
@ -506,121 +504,114 @@ func (self *_parser) skipWhiteSpace() {
}
}
func (self *_parser) skipLineWhiteSpace() {
for isLineWhiteSpace(self.chr) {
self.read()
func (p *parser) scanMantissa(base int) {
for digitValue(p.chr) < base {
p.read()
}
}
func (self *_parser) scanMantissa(base int) {
for digitValue(self.chr) < base {
self.read()
}
}
func (self *_parser) scanEscape(quote rune) {
func (p *parser) scanEscape(quote rune) {
var length, base uint32
switch self.chr {
//case '0', '1', '2', '3', '4', '5', '6', '7':
switch p.chr {
// Octal:
// length, base, limit = 3, 8, 255
case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'', '0':
self.read()
p.read()
return
case '\r', '\n', '\u2028', '\u2029':
self.scanNewline()
p.scanNewline()
return
case 'x':
self.read()
p.read()
length, base = 2, 16
case 'u':
self.read()
p.read()
length, base = 4, 16
default:
self.read() // Always make progress
p.read() // Always make progress
return
}
var value uint32
for ; length > 0 && self.chr != quote && self.chr >= 0; length-- {
digit := uint32(digitValue(self.chr))
for ; length > 0 && p.chr != quote && p.chr >= 0; length-- {
digit := uint32(digitValue(p.chr))
if digit >= base {
break
}
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 {
chr := self.chr
for p.chr != quote {
chr := p.chr
if chr == '\n' || chr == '\r' || chr == '\u2028' || chr == '\u2029' || chr < 0 {
goto newline
}
self.read()
if chr == '\\' {
p.read()
switch {
case chr == '\\':
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
}
self.read()
p.read()
} else {
self.scanEscape(quote)
p.scanEscape(quote)
}
} else if chr == '[' && quote == '/' {
case chr == '[' && quote == '/':
// Allow a slash (/) in a bracket character class ([...])
// TODO Fix this, this is hacky...
quote = -1
} else if chr == ']' && quote == -1 {
case chr == ']' && quote == -1:
quote = '/'
}
}
// " ' /
self.read()
p.read()
return string(self.str[offset:self.chrOffset]), nil
return p.str[offset:p.chrOffset], nil
newline:
self.scanNewline()
p.scanNewline()
err := "String not terminated"
if quote == '/' {
err = "Invalid regular expression: missing /"
self.error(self.idxOf(offset), err)
p.error(p.idxOf(offset), err)
}
return "", errors.New(err)
}
func (self *_parser) scanNewline() {
if self.chr == '\r' {
self.read()
if self.chr != '\n' {
func (p *parser) scanNewline() {
if p.chr == '\r' {
p.read()
if p.chr != '\n' {
return
}
}
self.read()
p.read()
}
func hex2decimal(chr byte) (value rune, ok bool) {
{
chr := rune(chr)
switch {
case '0' <= chr && chr <= '9':
return chr - '0', true
case 'a' <= chr && chr <= 'f':
return chr - 'a' + 10, true
case 'A' <= chr && chr <= 'F':
return chr - 'A' + 10, true
}
return
func hex2decimal(chr byte) (rune, bool) {
r := rune(chr)
switch {
case '0' <= r && r <= '9':
return r - '0', true
case 'a' <= r && r <= 'f':
return r - 'a' + 10, true
case 'A' <= r && r <= 'F':
return r - 'A' + 10, true
default:
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
value, err = strconv.ParseInt(literal, 0, 64)
if err == nil {
@ -632,14 +623,16 @@ func parseNumberLiteral(literal string) (value interface{}, err error) {
value, err = strconv.ParseFloat(literal, 64)
if err == nil {
return value, nil
} else if err.(*strconv.NumError).Err == strconv.ErrRange {
} else if errors.Is(err, strconv.ErrRange) {
// Infinity, etc.
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
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') {
// Could just be a very large number (e.g. 0x8000000000000000)
var value float64
@ -647,7 +640,7 @@ func parseNumberLiteral(literal string) (value interface{}, err error) {
for _, chr := range literal {
digit := digitValue(chr)
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)
}
@ -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) {
@ -783,78 +776,79 @@ func parseStringLiteral(literal string) (string, error) {
return buffer.String(), nil
}
func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {
offset := self.chrOffset
func (p *parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) {
offset := p.chrOffset
tkn := token.NUMBER
if decimalPoint {
offset--
self.scanMantissa(10)
p.scanMantissa(10)
goto exponent
}
if self.chr == '0' {
offset := self.chrOffset
self.read()
if self.chr == 'x' || self.chr == 'X' {
if p.chr == '0' {
offset := p.chrOffset
p.read()
switch p.chr {
case 'x', 'X':
// Hexadecimal
self.read()
if isDigit(self.chr, 16) {
self.read()
p.read()
if isDigit(p.chr, 16) {
p.read()
} 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"
self.error(0, "Illegal hexadecimal number")
p.error(0, "Illegal hexadecimal number")
}
goto hexadecimal
} else if self.chr == '.' {
case '.':
// Float
goto float
} else {
default:
// Octal, Float
if self.chr == 'e' || self.chr == 'E' {
if p.chr == 'e' || p.chr == 'E' {
goto exponent
}
self.scanMantissa(8)
if self.chr == '8' || self.chr == '9' {
return token.ILLEGAL, self.str[offset:self.chrOffset]
p.scanMantissa(8)
if p.chr == '8' || p.chr == '9' {
return token.ILLEGAL, p.str[offset:p.chrOffset]
}
goto octal
}
}
self.scanMantissa(10)
p.scanMantissa(10)
float:
if self.chr == '.' {
self.read()
self.scanMantissa(10)
if p.chr == '.' {
p.read()
p.scanMantissa(10)
}
exponent:
if self.chr == 'e' || self.chr == 'E' {
self.read()
if self.chr == '-' || self.chr == '+' {
self.read()
if p.chr == 'e' || p.chr == 'E' {
p.read()
if p.chr == '-' || p.chr == '+' {
p.read()
}
if isDecimalDigit(self.chr) {
self.read()
self.scanMantissa(10)
if isDecimalDigit(p.chr) {
p.read()
p.scanMantissa(10)
} else {
return token.ILLEGAL, self.str[offset:self.chrOffset]
return token.ILLEGAL, p.str[offset:p.chrOffset]
}
}
hexadecimal:
octal:
if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) {
return token.ILLEGAL, self.str[offset:self.chrOffset]
if isIdentifierStart(p.chr) || isDecimalDigit(p.chr) {
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 (
"bytes"
"encoding/base64"
"errors"
"fmt"
"io"
"io/ioutil"
"os"
"github.com/robertkrimen/otto/ast"
"github.com/robertkrimen/otto/file"
@ -49,11 +49,14 @@ import (
type Mode uint
const (
IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
StoreComments // Store the comments from source to the comments map
// IgnoreRegExpErrors ignores RegExp compatibility errors (allow backtracking).
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
length int
base int
@ -66,7 +69,7 @@ type _parser struct {
token token.Token // The token
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
implicitSemicolon bool // An implicit semicolon exists
@ -85,12 +88,13 @@ type _parser struct {
comments *ast.Comments
}
// Parser is implemented by types which can parse JavaScript Code.
type Parser interface {
Scan() (tkn token.Token, literal string, idx file.Idx)
}
func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser {
return &_parser{
func newParser(filename, src string, base int, sm *sourcemap.Consumer) *parser {
return &parser{
chr: ' ', // This is set so we can start scanning by skipping whitespace
str: 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 {
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) {
if src != nil {
switch src := src.(type) {
@ -122,12 +127,14 @@ func ReadSource(filename string, src interface{}) ([]byte, error) {
return nil, err
}
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) {
if src == nil {
return nil, nil //nolint: nilnil
@ -139,9 +146,7 @@ func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error
case []byte:
return sourcemap.Parse(filename, src)
case *bytes.Buffer:
if src != nil {
return sourcemap.Parse(filename, src.Bytes())
}
return sourcemap.Parse(filename, src.Bytes())
case io.Reader:
var bfr bytes.Buffer
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())
case *sourcemap.Consumer:
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) {
src, err := ReadSource(filename, javascriptSource)
if err != nil {
@ -184,10 +190,10 @@ func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSo
base = fileSet.AddFile(filename, string(src))
}
parser := _newParser(filename, string(src), base, sm)
parser.mode = mode
program, err := parser.parse()
program.Comments = parser.comments.CommentMap
p := newParser(filename, string(src), base, sm)
p.mode = mode
program, err := p.parse()
program.Comments = p.comments.CommentMap
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) {
src := "(function(" + parameterList + ") {\n" + body + "\n})"
parser := _newParser("", src, 1, nil)
program, err := parser.parse()
p := newParser("", src, 1, nil)
program, err := p.parse()
if err != nil {
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
// returns the token.Token token, a string literal representing the value of the token (if applicable)
// and it's current file.Idx index.
func (self *_parser) Scan() (tkn token.Token, literal string, idx file.Idx) {
return self.scan()
func (p *parser) Scan() (token.Token, string, file.Idx) {
return p.scan()
}
func (self *_parser) slice(idx0, idx1 file.Idx) string {
from := int(idx0) - self.base
to := int(idx1) - self.base
if from >= 0 && to <= len(self.str) {
return self.str[from:to]
func (p *parser) slice(idx0, idx1 file.Idx) string {
from := int(idx0) - p.base
to := int(idx1) - p.base
if from >= 0 && to <= len(p.str) {
return p.str[from:to]
}
return ""
}
func (self *_parser) parse() (*ast.Program, error) {
self.next()
program := self.parseProgram()
func (p *parser) parse() (*ast.Program, error) {
p.next()
program := p.parseProgram()
if false {
self.errors.Sort()
p.errors.Sort()
}
if self.mode&StoreComments != 0 {
self.comments.CommentMap.AddComments(program, self.comments.FetchAll(), ast.TRAILING)
if p.mode&StoreComments != 0 {
p.comments.CommentMap.AddComments(program, p.comments.FetchAll(), ast.TRAILING)
}
return program, self.errors.Err()
return program, p.errors.Err()
}
func (self *_parser) next() {
self.token, self.literal, self.idx = self.scan()
func (p *parser) next() {
p.token, p.literal, p.idx = p.scan()
}
func (self *_parser) optionalSemicolon() {
if self.token == token.SEMICOLON {
self.next()
func (p *parser) optionalSemicolon() {
if p.token == token.SEMICOLON {
p.next()
return
}
if self.implicitSemicolon {
self.implicitSemicolon = false
if p.implicitSemicolon {
p.implicitSemicolon = false
return
}
if self.token != token.EOF && self.token != token.RIGHT_BRACE {
self.expect(token.SEMICOLON)
if p.token != token.EOF && p.token != token.RIGHT_BRACE {
p.expect(token.SEMICOLON)
}
}
func (self *_parser) semicolon() {
if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
if self.implicitSemicolon {
self.implicitSemicolon = false
func (p *parser) semicolon() {
if p.token != token.RIGHT_PARENTHESIS && p.token != token.RIGHT_BRACE {
if p.implicitSemicolon {
p.implicitSemicolon = false
return
}
self.expect(token.SEMICOLON)
p.expect(token.SEMICOLON)
}
}
func (self *_parser) idxOf(offset int) file.Idx {
return file.Idx(self.base + offset)
func (p *parser) idxOf(offset int) file.Idx {
return file.Idx(p.base + offset)
}
func (self *_parser) expect(value token.Token) file.Idx {
idx := self.idx
if self.token != value {
self.errorUnexpectedToken(self.token)
func (p *parser) expect(value token.Token) file.Idx {
idx := p.idx
if p.token != value {
p.errorUnexpectedToken(p.token)
}
self.next()
p.next()
return idx
}
@ -305,17 +311,17 @@ func lineCount(str string) (int, int) {
for index, chr := range str {
switch chr {
case '\r':
line += 1
line++
last = index
pair = true
continue
case '\n':
if !pair {
line += 1
line++
}
last = index
case '\u2028', '\u2029':
line += 1
line++
last = index + 2
}
pair = false
@ -323,11 +329,11 @@ func lineCount(str string) (int, int) {
return line, last
}
func (self *_parser) position(idx file.Idx) file.Position {
func (p *parser) position(idx file.Idx) file.Position {
position := file.Position{}
offset := int(idx) - self.base
str := self.str[:offset]
position.Filename = self.file.Name()
offset := int(idx) - p.base
str := p.str[:offset]
position.Filename = p.file.Name()
line, last := lineCount(str)
position.Line = 1 + line
if last >= 0 {

View file

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

View file

@ -4,8 +4,8 @@ import (
"github.com/robertkrimen/otto/ast"
)
type _scope struct {
outer *_scope
type scope struct {
outer *scope
allowIn bool
inIteration bool
inSwitch bool
@ -15,30 +15,30 @@ type _scope struct {
labels []string
}
func (self *_parser) openScope() {
self.scope = &_scope{
outer: self.scope,
func (p *parser) openScope() {
p.scope = &scope{
outer: p.scope,
allowIn: true,
}
}
func (self *_parser) closeScope() {
self.scope = self.scope.outer
func (p *parser) closeScope() {
p.scope = p.scope.outer
}
func (self *_scope) declare(declaration ast.Declaration) {
self.declarationList = append(self.declarationList, declaration)
func (p *scope) declare(declaration ast.Declaration) {
p.declarationList = append(p.declarationList, declaration)
}
func (self *_scope) hasLabel(name string) bool {
for _, label := range self.labels {
func (p *scope) hasLabel(name string) bool {
for _, label := range p.labels {
if label == name {
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
return self.outer.hasLabel(name)
return p.outer.hasLabel(name)
}
return false
}

File diff suppressed because it is too large Load diff

View file

@ -2,87 +2,87 @@ package otto
// property
type _propertyMode int
type propertyMode int
const (
modeWriteMask _propertyMode = 0700
modeEnumerateMask = 0070
modeConfigureMask = 0007
modeOnMask = 0111
modeSetMask = 0222 // If value is 2, then mode is neither "On" nor "Off"
modeWriteMask propertyMode = 0o700
modeEnumerateMask propertyMode = 0o070
modeConfigureMask propertyMode = 0o007
modeOnMask propertyMode = 0o111
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{}
mode _propertyMode
mode propertyMode
}
func (self _property) writable() bool {
return self.mode&modeWriteMask == modeWriteMask&modeOnMask
func (p property) writable() bool {
return p.mode&modeWriteMask == modeWriteMask&modeOnMask
}
func (self *_property) writeOn() {
self.mode = (self.mode & ^modeWriteMask) | (modeWriteMask & modeOnMask)
func (p *property) writeOn() {
p.mode = (p.mode & ^modeWriteMask) | (modeWriteMask & modeOnMask)
}
func (self *_property) writeOff() {
self.mode &= ^modeWriteMask
func (p *property) writeOff() {
p.mode &= ^modeWriteMask
}
func (self *_property) writeClear() {
self.mode = (self.mode & ^modeWriteMask) | (modeWriteMask & modeSetMask)
func (p *property) writeClear() {
p.mode = (p.mode & ^modeWriteMask) | (modeWriteMask & modeSetMask)
}
func (self _property) writeSet() bool {
return 0 == self.mode&modeWriteMask&modeSetMask
func (p property) writeSet() bool {
return p.mode&modeWriteMask&modeSetMask == 0
}
func (self _property) enumerable() bool {
return self.mode&modeEnumerateMask == modeEnumerateMask&modeOnMask
func (p property) enumerable() bool {
return p.mode&modeEnumerateMask == modeEnumerateMask&modeOnMask
}
func (self *_property) enumerateOn() {
self.mode = (self.mode & ^modeEnumerateMask) | (modeEnumerateMask & modeOnMask)
func (p *property) enumerateOn() {
p.mode = (p.mode & ^modeEnumerateMask) | (modeEnumerateMask & modeOnMask)
}
func (self *_property) enumerateOff() {
self.mode &= ^modeEnumerateMask
func (p *property) enumerateOff() {
p.mode &= ^modeEnumerateMask
}
func (self _property) enumerateSet() bool {
return 0 == self.mode&modeEnumerateMask&modeSetMask
func (p property) enumerateSet() bool {
return p.mode&modeEnumerateMask&modeSetMask == 0
}
func (self _property) configurable() bool {
return self.mode&modeConfigureMask == modeConfigureMask&modeOnMask
func (p property) configurable() bool {
return p.mode&modeConfigureMask == modeConfigureMask&modeOnMask
}
func (self *_property) configureOn() {
self.mode = (self.mode & ^modeConfigureMask) | (modeConfigureMask & modeOnMask)
func (p *property) configureOn() {
p.mode = (p.mode & ^modeConfigureMask) | (modeConfigureMask & modeOnMask)
}
func (self *_property) configureOff() {
self.mode &= ^modeConfigureMask
func (p *property) configureOff() {
p.mode &= ^modeConfigureMask
}
func (self _property) configureSet() bool {
return 0 == self.mode&modeConfigureMask&modeSetMask
func (p property) configureSet() bool { //nolint: unused
return p.mode&modeConfigureMask&modeSetMask == 0
}
func (self _property) copy() *_property {
property := self
return &property
func (p property) copy() *property { //nolint: unused
cpy := p
return &cpy
}
func (self _property) get(this *_object) Value {
switch value := self.value.(type) {
func (p property) get(this *object) Value {
switch value := p.value.(type) {
case Value:
return value
case _propertyGetSet:
case propertyGetSet:
if value[0] != nil {
return value[0].call(toValue(this), nil, false, nativeFrame)
}
@ -90,76 +90,75 @@ func (self _property) get(this *_object) Value {
return Value{}
}
func (self _property) isAccessorDescriptor() bool {
setGet, test := self.value.(_propertyGetSet)
func (p property) isAccessorDescriptor() bool {
setGet, test := p.value.(propertyGetSet)
return test && (setGet[0] != nil || setGet[1] != nil)
}
func (self _property) isDataDescriptor() bool {
if self.writeSet() { // Either "On" or "Off"
func (p property) isDataDescriptor() bool {
if p.writeSet() { // Either "On" or "Off"
return true
}
value, valid := self.value.(Value)
value, valid := p.value.(Value)
return valid && !value.isEmpty()
}
func (self _property) isGenericDescriptor() bool {
return !(self.isDataDescriptor() || self.isAccessorDescriptor())
func (p property) isGenericDescriptor() bool {
return !(p.isDataDescriptor() || p.isAccessorDescriptor())
}
func (self _property) isEmpty() bool {
return self.mode == 0222 && self.isGenericDescriptor()
func (p property) isEmpty() bool {
return p.mode == 0o222 && p.isGenericDescriptor()
}
// _enumerableValue, _enumerableTrue, _enumerableFalse?
// .enumerableValue() .enumerableExists()
func toPropertyDescriptor(rt *_runtime, value Value) (descriptor _property) {
objectDescriptor := value._object()
func toPropertyDescriptor(rt *runtime, value Value) property {
objectDescriptor := value.object()
if objectDescriptor == nil {
panic(rt.panicTypeError())
panic(rt.panicTypeError("toPropertyDescriptor on nil"))
}
{
descriptor.mode = modeSetMask // Initially nothing is set
if objectDescriptor.hasProperty("enumerable") {
if objectDescriptor.get("enumerable").bool() {
descriptor.enumerateOn()
} else {
descriptor.enumerateOff()
}
}
if objectDescriptor.hasProperty("configurable") {
if objectDescriptor.get("configurable").bool() {
descriptor.configureOn()
} else {
descriptor.configureOff()
}
}
if objectDescriptor.hasProperty("writable") {
if objectDescriptor.get("writable").bool() {
descriptor.writeOn()
} else {
descriptor.writeOff()
}
var descriptor property
descriptor.mode = modeSetMask // Initially nothing is set
if objectDescriptor.hasProperty("enumerable") {
if objectDescriptor.get("enumerable").bool() {
descriptor.enumerateOn()
} else {
descriptor.enumerateOff()
}
}
var getter, setter *_object
if objectDescriptor.hasProperty("configurable") {
if objectDescriptor.get("configurable").bool() {
descriptor.configureOn()
} else {
descriptor.configureOff()
}
}
if objectDescriptor.hasProperty("writable") {
if objectDescriptor.get("writable").bool() {
descriptor.writeOn()
} else {
descriptor.writeOff()
}
}
var getter, setter *object
getterSetter := false
if objectDescriptor.hasProperty("get") {
value := objectDescriptor.get("get")
if value.IsDefined() {
if !value.isCallable() {
panic(rt.panicTypeError())
panic(rt.panicTypeError("toPropertyDescriptor get not callable"))
}
getter = value._object()
getter = value.object()
getterSetter = true
} else {
getter = &_nilGetSetObject
getter = &nilGetSetObject
getterSetter = true
}
}
@ -168,52 +167,52 @@ func toPropertyDescriptor(rt *_runtime, value Value) (descriptor _property) {
value := objectDescriptor.get("set")
if value.IsDefined() {
if !value.isCallable() {
panic(rt.panicTypeError())
panic(rt.panicTypeError("toPropertyDescriptor set not callable"))
}
setter = value._object()
setter = value.object()
getterSetter = true
} else {
setter = &_nilGetSetObject
setter = &nilGetSetObject
getterSetter = true
}
}
if getterSetter {
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 getterSetter {
panic(rt.panicTypeError())
panic(rt.panicTypeError("toPropertyDescriptor value getterSetter"))
}
descriptor.value = objectDescriptor.get("value")
}
return
return descriptor
}
func (self *_runtime) fromPropertyDescriptor(descriptor _property) *_object {
object := self.newObject()
func (rt *runtime) fromPropertyDescriptor(descriptor property) *object {
obj := rt.newObject()
if descriptor.isDataDescriptor() {
object.defineProperty("value", descriptor.value.(Value), 0111, false)
object.defineProperty("writable", toValue_bool(descriptor.writable()), 0111, false)
obj.defineProperty("value", descriptor.value.(Value), 0o111, false)
obj.defineProperty("writable", boolValue(descriptor.writable()), 0o111, false)
} else if descriptor.isAccessorDescriptor() {
getSet := descriptor.value.(_propertyGetSet)
getSet := descriptor.value.(propertyGetSet)
get := Value{}
if getSet[0] != nil {
get = toValue_object(getSet[0])
get = objectValue(getSet[0])
}
set := Value{}
if getSet[1] != nil {
set = toValue_object(getSet[1])
set = objectValue(getSet[1])
}
object.defineProperty("get", get, 0111, false)
object.defineProperty("set", set, 0111, false)
obj.defineProperty("get", get, 0o111, false)
obj.defineProperty("set", set, 0o111, false)
}
object.defineProperty("enumerable", toValue_bool(descriptor.enumerable()), 0111, false)
object.defineProperty("configurable", toValue_bool(descriptor.configurable()), 0111, false)
return object
obj.defineProperty("enumerable", boolValue(descriptor.enumerable()), 0o111, false)
obj.defineProperty("configurable", boolValue(descriptor.configurable()), 0o111, false)
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 expirmental package to facillitate altering the otto runtime via import.
This interface can change at any time.
*/
// Package registry is an experimental package to facilitate altering the otto runtime via import.
//
// This interface can change at any time.
package registry
var registry []*Entry = make([]*Entry, 0)
// Entry represents a registry entry.
type Entry struct {
active bool
source func() string
}
// newEntry returns a new Entry for source.
func newEntry(source func() string) *Entry {
return &Entry{
active: true,
@ -19,18 +19,22 @@ func newEntry(source func() string) *Entry {
}
}
func (self *Entry) Enable() {
self.active = true
// Enable enables the entry.
func (e *Entry) Enable() {
e.active = true
}
func (self *Entry) Disable() {
self.active = false
// Disable disables the entry.
func (e *Entry) Disable() {
e.active = false
}
func (self Entry) Source() string {
return self.source()
// Source returns the source of the entry.
func (e Entry) Source() string {
return e.source()
}
// Apply applies callback to all registry entries.
func Apply(callback func(Entry)) {
for _, entry := range registry {
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 {
entry := newEntry(source)
registry = append(registry, entry)

View file

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

View file

@ -8,7 +8,7 @@ import (
"math"
"path"
"reflect"
"runtime"
goruntime "runtime"
"strconv"
"strings"
"sync"
@ -17,49 +17,49 @@ import (
"github.com/robertkrimen/otto/parser"
)
type _global struct {
Object *_object // Object( ... ), new Object( ... ) - 1 (length)
Function *_object // Function( ... ), new Function( ... ) - 1
Array *_object // Array( ... ), new Array( ... ) - 1
String *_object // String( ... ), new String( ... ) - 1
Boolean *_object // Boolean( ... ), new Boolean( ... ) - 1
Number *_object // Number( ... ), new Number( ... ) - 1
Math *_object
Date *_object // Date( ... ), new Date( ... ) - 7
RegExp *_object // RegExp( ... ), new RegExp( ... ) - 2
Error *_object // Error( ... ), new Error( ... ) - 1
EvalError *_object
TypeError *_object
RangeError *_object
ReferenceError *_object
SyntaxError *_object
URIError *_object
JSON *_object
type global struct {
Object *object // Object( ... ), new Object( ... ) - 1 (length)
Function *object // Function( ... ), new Function( ... ) - 1
Array *object // Array( ... ), new Array( ... ) - 1
String *object // String( ... ), new String( ... ) - 1
Boolean *object // Boolean( ... ), new Boolean( ... ) - 1
Number *object // Number( ... ), new Number( ... ) - 1
Math *object
Date *object // Date( ... ), new Date( ... ) - 7
RegExp *object // RegExp( ... ), new RegExp( ... ) - 2
Error *object // Error( ... ), new Error( ... ) - 1
EvalError *object
TypeError *object
RangeError *object
ReferenceError *object
SyntaxError *object
URIError *object
JSON *object
ObjectPrototype *_object // Object.prototype
FunctionPrototype *_object // Function.prototype
ArrayPrototype *_object // Array.prototype
StringPrototype *_object // String.prototype
BooleanPrototype *_object // Boolean.prototype
NumberPrototype *_object // Number.prototype
DatePrototype *_object // Date.prototype
RegExpPrototype *_object // RegExp.prototype
ErrorPrototype *_object // Error.prototype
EvalErrorPrototype *_object
TypeErrorPrototype *_object
RangeErrorPrototype *_object
ReferenceErrorPrototype *_object
SyntaxErrorPrototype *_object
URIErrorPrototype *_object
ObjectPrototype *object // Object.prototype
FunctionPrototype *object // Function.prototype
ArrayPrototype *object // Array.prototype
StringPrototype *object // String.prototype
BooleanPrototype *object // Boolean.prototype
NumberPrototype *object // Number.prototype
DatePrototype *object // Date.prototype
RegExpPrototype *object // RegExp.prototype
ErrorPrototype *object // Error.prototype
EvalErrorPrototype *object
TypeErrorPrototype *object
RangeErrorPrototype *object
ReferenceErrorPrototype *object
SyntaxErrorPrototype *object
URIErrorPrototype *object
}
type _runtime struct {
global _global
globalObject *_object
globalStash *_objectStash
scope *_scope
type runtime struct {
global global
globalObject *object
globalStash *objectStash
scope *scope
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)
random func() float64
stackLimit int
@ -69,54 +69,54 @@ type _runtime struct {
lck sync.Mutex
}
func (self *_runtime) enterScope(scope *_scope) {
scope.outer = self.scope
if self.scope != nil {
if self.stackLimit != 0 && self.scope.depth+1 >= self.stackLimit {
panic(self.panicRangeError("Maximum call stack size exceeded"))
func (rt *runtime) enterScope(scop *scope) {
scop.outer = rt.scope
if rt.scope != nil {
if rt.stackLimit != 0 && rt.scope.depth+1 >= rt.stackLimit {
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() {
self.scope = self.scope.outer
func (rt *runtime) leaveScope() {
rt.scope = rt.scope.outer
}
// FIXME This is used in two places (cloning)
func (self *_runtime) enterGlobalScope() {
self.enterScope(newScope(self.globalStash, self.globalStash, self.globalObject))
// FIXME This is used in two places (cloning).
func (rt *runtime) enterGlobalScope() {
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 {
outer = self.globalStash
outer = rt.globalStash
}
stash := self.newFunctionStash(outer)
var thisObject *_object
stash := rt.newFunctionStash(outer)
var thisObject *object
switch this.kind {
case valueUndefined, valueNull:
thisObject = self.globalObject
thisObject = rt.globalObject
default:
thisObject = self.toObject(this)
thisObject = rt.toObject(this)
}
self.enterScope(newScope(stash, stash, thisObject))
rt.enterScope(newScope(stash, stash, thisObject))
return stash
}
func (self *_runtime) putValue(reference _reference, value Value) {
func (rt *runtime) putValue(reference referencer, value Value) {
name := reference.putValue(value)
if name != "" {
// Why? -- If reference.base == nil
// 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)
// throw = Something 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.
defer func() {
if caught := recover(); caught != nil {
if exception, ok := caught.(*_exception); ok {
caught = exception.eject()
if excep, ok := caught.(*exception); ok {
caught = excep.eject()
}
switch caught := caught.(type) {
case _error:
exception = true
tryValue = toValue_object(self.newErrorObjectError(caught))
case ottoError:
isException = true
tryValue = objectValue(rt.newErrorObjectError(caught))
case Value:
exception = true
isException = true
tryValue = caught
default:
exception = true
isException = true
tryValue = toValue(caught)
}
}
@ -144,52 +144,51 @@ func (self *_runtime) tryCatchEvaluate(inner func() Value) (tryValue Value, exce
return inner(), false
}
// toObject
func (self *_runtime) toObject(value Value) *_object {
func (rt *runtime) toObject(value Value) *object {
switch value.kind {
case valueEmpty, valueUndefined, valueNull:
panic(self.panicTypeError())
panic(rt.panicTypeError("toObject unsupported kind %s", value.kind))
case valueBoolean:
return self.newBoolean(value)
return rt.newBoolean(value)
case valueString:
return self.newString(value)
return rt.newString(value)
case valueNumber:
return self.newNumber(value)
return rt.newNumber(value)
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 {
case valueUndefined:
return nil, errors.New("undefined")
case valueNull:
return nil, errors.New("null")
case valueBoolean:
return self.newBoolean(value), nil
return rt.newBoolean(value), nil
case valueString:
return self.newString(value), nil
return rt.newString(value), nil
case valueNumber:
return self.newNumber(value), nil
return rt.newNumber(value), nil
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)
if !isObject && !mustCoerce {
panic(rt.panicTypeError())
panic(rt.panicTypeError("checkObjectCoercible not object or mustCoerce"))
}
}
// testObjectCoercible
func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
// testObjectCoercible.
func testObjectCoercible(value Value) (isObject, mustCoerce bool) { //nolint: nonamedreturns
switch value.kind {
case valueReference, valueEmpty, valueNull, valueUndefined:
return false, false
@ -198,21 +197,21 @@ func testObjectCoercible(value Value) (isObject bool, mustCoerce bool) {
case valueObject:
return true, false
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{}
err := catchPanic(func() {
result = self.toValue(value)
result = rt.toValue(value)
})
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.
// 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())
if val.Kind() == t.Kind() {
@ -231,20 +230,20 @@ func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
return reflect.ValueOf(f64)
case reflect.Float32:
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)
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
i64 := int64(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
val = reflect.ValueOf(i64)
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() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
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)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
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)) {
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)
case reflect.Float32, reflect.Float64:
@ -274,12 +273,12 @@ func (self *_runtime) convertNumeric(v Value, t reflect.Type) reflect.Value {
switch t.Kind() {
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
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)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
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)
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 {
@ -332,13 +331,15 @@ func fieldIndexByName(t reflect.Type, name string) []int {
return nil
}
var typeOfValue = reflect.TypeOf(Value{})
var typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{})
var (
typeOfValue = reflect.TypeOf(Value{})
typeOfJSONRawMessage = reflect.TypeOf(json.RawMessage{})
)
// convertCallParameter converts request val to type t if possible.
// 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.
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 {
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 gso, ok := v._object().value.(*_goStructObject); ok {
if gso, ok := v.object().value.(*goStructObject); ok {
if gso.value.Type().AssignableTo(t) {
// please see TestDynamicFunctionReturningInterface for why this exists
if t.Kind() == reflect.Interface && gso.value.Type().ConvertibleTo(t) {
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) {
// please see TestDynamicFunctionReturningInterface for why this exists
if t.Kind() == reflect.Interface && gao.value.Type().ConvertibleTo(t) {
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
default:
var vv reflect.Value
vv, err := self.convertCallParameter(v, t.Elem())
vv, err := rt.convertCallParameter(v, t.Elem())
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() {
@ -418,12 +417,11 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
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:
switch v.kind {
case valueNumber:
return self.convertNumeric(v, t), nil
if v.kind == valueNumber {
return rt.convertNumeric(v, t), nil
}
case reflect.Slice:
if o := v._object(); o != nil {
if o := v.object(); o != nil {
if lv := o.get(propertyLength); lv.IsNumber() {
l := lv.number().int64
@ -432,7 +430,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
tt := t.Elem()
switch o.class {
case classArray:
case classArrayName:
for i := int64(0); i < l; i++ {
p, ok := o.property[strconv.FormatInt(i, 10)]
if !ok {
@ -444,24 +442,24 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
continue
}
ev, err := self.convertCallParameter(e, tt)
ev, err := rt.convertCallParameter(e, tt)
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)
}
case classGoArray, classGoSlice:
case classGoArrayName, classGoSliceName:
var gslice bool
switch o.value.(type) {
case *_goSliceObject:
case *goSliceObject:
gslice = true
case *_goArrayObject:
case *goArrayObject:
gslice = false
}
for i := int64(0); i < l; i++ {
var p *_property
var p *property
if gslice {
p = goSliceGetOwnProperty(o, strconv.FormatInt(i, 10))
} else {
@ -476,9 +474,9 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
continue
}
ev, err := self.convertCallParameter(e, tt)
ev, err := rt.convertCallParameter(e, tt)
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)
@ -489,15 +487,15 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
}
}
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)
var err error
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 {
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
}
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")
}
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 {
l := make([]interface{}, len(args))
for i, a := range args {
@ -533,16 +531,16 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
return nil
}
r, err := self.convertCallParameter(rv, t.Out(0))
r, err := rt.convertCallParameter(rv, t.Out(0))
if err != nil {
panic(self.panicTypeError(err.Error()))
panic(rt.panicTypeError("convertCallParameter Func: %s", err))
}
return []reflect.Value{r}
}), nil
}
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)
for _, k := range o.propertyOrder {
@ -570,9 +568,9 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
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 {
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)
@ -583,16 +581,16 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
}
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() {
sv, err := fn.Call(v)
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 {
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
}
@ -608,7 +606,7 @@ func (self *_runtime) convertCallParameter(v Value, t reflect.Type) (reflect.Val
r := reflect.New(t)
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
@ -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)
}
func (self *_runtime) toValue(value interface{}) Value {
func (rt *runtime) toValue(value interface{}) Value {
rv, ok := value.(reflect.Value)
if ok {
value = rv.Interface()
@ -647,173 +645,171 @@ func (self *_runtime) toValue(value interface{}) Value {
var name, file string
var line int
pc := reflect.ValueOf(value).Pointer()
fn := runtime.FuncForPC(pc)
fn := goruntime.FuncForPC(pc)
if fn != nil {
name = fn.Name()
file, line = fn.FileLine(pc)
file = path.Base(file)
}
return toValue_object(self.newNativeFunction(name, file, line, value))
case _nativeFunction:
return objectValue(rt.newNativeFunction(name, file, line, value))
case nativeFunction:
var name, file string
var line int
pc := reflect.ValueOf(value).Pointer()
fn := runtime.FuncForPC(pc)
fn := goruntime.FuncForPC(pc)
if fn != nil {
name = fn.Name()
file, line = fn.FileLine(pc)
file = path.Base(file)
}
return toValue_object(self.newNativeFunction(name, file, line, value))
case Object, *Object, _object, *_object:
return objectValue(rt.newNativeFunction(name, file, line, value))
case Object, *Object, object, *object:
// Nothing happens.
// FIXME We should really figure out what can come here.
// This catch-all is ugly.
default:
{
value := reflect.ValueOf(value)
if ok && value.Kind() == rv.Kind() {
// Use passed in rv which may be writable.
value = rv
}
val := reflect.ValueOf(value)
if ok && val.Kind() == rv.Kind() {
// Use passed in rv which may be writable.
val = rv
}
switch value.Kind() {
case reflect.Ptr:
switch reflect.Indirect(value).Kind() {
case reflect.Struct:
return toValue_object(self.newGoStructObject(value))
case reflect.Array:
return toValue_object(self.newGoArray(value))
}
switch val.Kind() {
case reflect.Ptr:
switch reflect.Indirect(val).Kind() {
case reflect.Struct:
return toValue_object(self.newGoStructObject(value))
case reflect.Map:
return toValue_object(self.newGoMapObject(value))
case reflect.Slice:
return toValue_object(self.newGoSlice(value))
return objectValue(rt.newGoStructObject(val))
case reflect.Array:
return toValue_object(self.newGoArray(value))
case reflect.Func:
var name, file string
var line int
if v := reflect.ValueOf(value); v.Kind() == reflect.Ptr {
pc := v.Pointer()
fn := runtime.FuncForPC(pc)
if fn != nil {
name = fn.Name()
file, line = fn.FileLine(pc)
file = path.Base(file)
return objectValue(rt.newGoArray(val))
}
case reflect.Struct:
return objectValue(rt.newGoStructObject(val))
case reflect.Map:
return objectValue(rt.newGoMapObject(val))
case reflect.Slice:
return objectValue(rt.newGoSlice(val))
case reflect.Array:
return objectValue(rt.newGoArray(val))
case reflect.Func:
var name, file string
var line int
if v := reflect.ValueOf(val); v.Kind() == reflect.Ptr {
pc := v.Pointer()
fn := goruntime.FuncForPC(pc)
if fn != nil {
name = fn.Name()
file, line = fn.FileLine(pc)
file = path.Base(file)
}
}
typ := val.Type()
return objectValue(rt.newNativeFunction(name, file, line, func(c FunctionCall) Value {
nargs := typ.NumIn()
if len(c.ArgumentList) != nargs {
if typ.IsVariadic() {
if len(c.ArgumentList) < nargs-1 {
panic(rt.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList))))
}
} else {
panic(rt.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList))))
}
}
typ := value.Type()
in := make([]reflect.Value, len(c.ArgumentList))
return toValue_object(self.newNativeFunction(name, file, line, func(c FunctionCall) Value {
nargs := typ.NumIn()
callSlice := false
if len(c.ArgumentList) != nargs {
if typ.IsVariadic() {
if len(c.ArgumentList) < nargs-1 {
panic(self.panicRangeError(fmt.Sprintf("expected at least %d arguments; got %d", nargs-1, len(c.ArgumentList))))
}
} else {
panic(self.panicRangeError(fmt.Sprintf("expected %d argument(s); got %d", nargs, len(c.ArgumentList))))
}
}
for i, a := range c.ArgumentList {
var t reflect.Type
in := make([]reflect.Value, len(c.ArgumentList))
callSlice := false
for i, a := range c.ArgumentList {
var t reflect.Type
n := i
if n >= nargs-1 && typ.IsVariadic() {
if n > nargs-1 {
n = nargs - 1
}
t = typ.In(n).Elem()
} else {
t = typ.In(n)
n := i
if n >= nargs-1 && typ.IsVariadic() {
if n > nargs-1 {
n = nargs - 1
}
// if this is a variadic Go function, and the caller has supplied
// exactly the number of JavaScript arguments required, and this
// is the last JavaScript argument, try treating the it as the
// actual set of variadic Go arguments. if that succeeds, break
// out of the loop.
if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 {
if v, err := self.convertCallParameter(a, typ.In(n)); err == nil {
in[i] = v
callSlice = true
break
}
}
v, err := self.convertCallParameter(a, t)
if err != nil {
panic(self.panicTypeError(err.Error()))
}
in[i] = v
}
var out []reflect.Value
if callSlice {
out = value.CallSlice(in)
t = typ.In(n).Elem()
} else {
out = value.Call(in)
t = typ.In(n)
}
switch len(out) {
case 0:
return Value{}
case 1:
return self.toValue(out[0].Interface())
default:
s := make([]interface{}, len(out))
for i, v := range out {
s[i] = self.toValue(v.Interface())
// if this is a variadic Go function, and the caller has supplied
// exactly the number of JavaScript arguments required, and this
// is the last JavaScript argument, try treating the it as the
// actual set of variadic Go arguments. if that succeeds, break
// out of the loop.
if typ.IsVariadic() && len(c.ArgumentList) == nargs && i == nargs-1 {
if v, err := rt.convertCallParameter(a, typ.In(n)); err == nil {
in[i] = v
callSlice = true
break
}
return self.toValue(s)
}
}))
}
v, err := rt.convertCallParameter(a, t)
if err != nil {
panic(rt.panicTypeError(err.Error()))
}
in[i] = v
}
var out []reflect.Value
if callSlice {
out = val.CallSlice(in)
} else {
out = val.Call(in)
}
switch len(out) {
case 0:
return Value{}
case 1:
return rt.toValue(out[0].Interface())
default:
s := make([]interface{}, len(out))
for i, v := range out {
s[i] = rt.toValue(v.Interface())
}
return rt.toValue(s)
}
}))
}
}
return toValue(value)
}
func (runtime *_runtime) newGoSlice(value reflect.Value) *_object {
self := runtime.newGoSliceObject(value)
self.prototype = runtime.global.ArrayPrototype
return self
func (rt *runtime) newGoSlice(value reflect.Value) *object {
obj := rt.newGoSliceObject(value)
obj.prototype = rt.global.ArrayPrototype
return obj
}
func (runtime *_runtime) newGoArray(value reflect.Value) *_object {
self := runtime.newGoArrayObject(value)
self.prototype = runtime.global.ArrayPrototype
return self
func (rt *runtime) newGoArray(value reflect.Value) *object {
obj := rt.newGoArrayObject(value)
obj.prototype = rt.global.ArrayPrototype
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)
}
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)
if err != nil {
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) {
case *ast.Program:
return nil, src, nil
@ -821,22 +817,22 @@ func (self *_runtime) parseSource(src, sm interface{}) (*_nodeProgram, *ast.Prog
return src.program, nil, nil
}
program, err := self.parse("", src, sm)
program, err := rt.parse("", src, sm)
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{}
cmpl_program, program, err := self.parseSource(src, sm)
node, program, err := rt.parseSource(src, sm)
if err != nil {
return result, err
}
if cmpl_program == nil {
cmpl_program = cmpl_parse(program)
if node == nil {
node = cmplParse(program)
}
err = catchPanic(func() {
result = self.cmpl_evaluate_nodeProgram(cmpl_program, eval)
result = rt.cmplEvaluateNodeProgram(node, eval)
})
switch result.kind {
case valueEmpty:
@ -847,33 +843,32 @@ func (self *_runtime) cmpl_runOrEval(src, sm interface{}, eval bool) (Value, err
return result, err
}
func (self *_runtime) cmpl_run(src, sm interface{}) (Value, error) {
return self.cmpl_runOrEval(src, sm, false)
func (rt *runtime) cmplRun(src, sm interface{}) (Value, error) {
return rt.cmplRunOrEval(src, sm, false)
}
func (self *_runtime) cmpl_eval(src, sm interface{}) (Value, error) {
return self.cmpl_runOrEval(src, sm, true)
func (rt *runtime) cmplEval(src, sm interface{}) (Value, error) {
return rt.cmplRunOrEval(src, sm, true)
}
func (self *_runtime) parseThrow(err error) {
func (rt *runtime) parseThrow(err error) {
if err == nil {
return
}
switch err := err.(type) {
case parser.ErrorList:
{
err := err[0]
if err.Message == "Invalid left-hand side in assignment" {
panic(self.panicReferenceError(err.Message))
}
panic(self.panicSyntaxError(err.Message))
var errl parser.ErrorList
if errors.Is(err, &errl) {
err := errl[0]
if err.Message == "invalid left-hand side in assignment" {
panic(rt.panicReferenceError(err.Message))
}
panic(rt.panicSyntaxError(err.Message))
}
panic(self.panicSyntaxError(err.Error()))
panic(rt.panicSyntaxError(err.Error()))
}
func (self *_runtime) cmpl_parseOrThrow(src, sm interface{}) *_nodeProgram {
program, err := self.cmpl_parse("", src, sm)
self.parseThrow(err) // Will panic/throw appropriately
func (rt *runtime) cmplParseOrThrow(src, sm interface{}) *nodeProgram {
program, err := rt.cmplParse("", src, sm)
rt.parseThrow(err) // Will panic/throw appropriately
return program
}

View file

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

View file

@ -6,6 +6,7 @@ import (
"errors"
)
// ErrVersion is an error which represents a version mismatch.
var ErrVersion = errors.New("version mismatch")
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.
type Script struct {
version string
program *_nodeProgram
program *nodeProgram
filename string
src string
}
@ -24,23 +25,22 @@ type Script struct {
//
// script, err := vm.Compile("", `var abc; if (!abc) abc = 0; abc += 2; abc;`)
// vm.Run(script)
func (self *Otto) Compile(filename string, src interface{}) (*Script, error) {
return self.CompileWithSourceMap(filename, src, nil)
func (o *Otto) Compile(filename string, src interface{}) (*Script, error) {
return o.CompileWithSourceMap(filename, src, nil)
}
// CompileWithSourceMap does the same thing as Compile, but with the obvious
// difference of applying a source map.
func (self *Otto) CompileWithSourceMap(filename string, src, sm interface{}) (*Script, error) {
program, err := self.runtime.parse(filename, src, sm)
func (o *Otto) CompileWithSourceMap(filename string, src, sm interface{}) (*Script, error) {
program, err := o.runtime.parse(filename, src, sm)
if err != nil {
return nil, err
}
cmpl_program := cmpl_parse(program)
node := cmplParse(program)
script := &Script{
version: scriptVersion,
program: cmpl_program,
program: node,
filename: filename,
src: program.File.Source(),
}
@ -48,30 +48,30 @@ func (self *Otto) CompileWithSourceMap(filename string, src, sm interface{}) (*S
return script, nil
}
func (self *Script) String() string {
return "// " + self.filename + "\n" + self.src
func (s *Script) String() string {
return "// " + s.filename + "\n" + s.src
}
// 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.
//
// 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
encoder := gob.NewEncoder(&bfr)
err := encoder.Encode(self.version)
err := encoder.Encode(s.version)
if err != nil {
return nil, err
}
err = encoder.Encode(self.program)
err = encoder.Encode(s.program)
if err != nil {
return nil, err
}
err = encoder.Encode(self.filename)
err = encoder.Encode(s.filename)
if err != nil {
return nil, err
}
err = encoder.Encode(self.src)
err = encoder.Encode(s.src)
if err != nil {
return nil, err
}
@ -83,27 +83,27 @@ func (self *Script) marshalBinary() ([]byte, error) {
// will return an error.
//
// 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))
defer func() {
if err != nil {
self.version = ""
self.program = nil
self.filename = ""
self.src = ""
s.version = ""
s.program = nil
s.filename = ""
s.src = ""
}
}()
if err = decoder.Decode(&self.version); err != nil {
if err = decoder.Decode(&s.version); err != nil {
return err
}
if self.version != scriptVersion {
if s.version != scriptVersion {
return ErrVersion
}
if err = decoder.Decode(&self.program); err != nil {
if err = decoder.Decode(&s.program); err != nil {
return err
}
if err = decoder.Decode(&self.filename); err != nil {
if err = decoder.Decode(&s.filename); err != nil {
return err
}
return decoder.Decode(&self.src)
return decoder.Decode(&s.src)
}

View file

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

View file

@ -1,25 +1,28 @@
// Code generated by tools/gen-tokens. DO NOT EDIT.
package token
const (
_ Token = iota
// Control.
ILLEGAL
EOF
COMMENT
KEYWORD
// Types.
STRING
BOOLEAN
NULL
NUMBER
IDENTIFIER
// Maths.
PLUS // +
MINUS // -
MULTIPLY // *
SLASH // /
REMAINDER // %
// Logical and bitwise operators.
AND // &
OR // |
EXCLUSIVE_OR // ^
@ -27,13 +30,13 @@ const (
SHIFT_RIGHT // >>
UNSIGNED_SHIFT_RIGHT // >>>
AND_NOT // &^
// Math assignments.
ADD_ASSIGN // +=
SUBTRACT_ASSIGN // -=
MULTIPLY_ASSIGN // *=
QUOTIENT_ASSIGN // /=
REMAINDER_ASSIGN // %=
// Math and bitwise assignments.
AND_ASSIGN // &=
OR_ASSIGN // |=
EXCLUSIVE_OR_ASSIGN // ^=
@ -41,74 +44,73 @@ const (
SHIFT_RIGHT_ASSIGN // >>=
UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>=
AND_NOT_ASSIGN // &^=
// Logical operators and decrement / increment.
LOGICAL_AND // &&
LOGICAL_OR // ||
INCREMENT // ++
DECREMENT // --
// Comparison operators.
EQUAL // ==
STRICT_EQUAL // ===
LESS // <
GREATER // >
ASSIGN // =
NOT // !
// Bitwise not.
BITWISE_NOT // ~
// Comparison operators.
NOT_EQUAL // !=
STRICT_NOT_EQUAL // !==
LESS_OR_EQUAL // <=
GREATER_OR_EQUAL // >=
// Left operators.
LEFT_PARENTHESIS // (
LEFT_BRACKET // [
LEFT_BRACE // {
COMMA // ,
PERIOD // .
// Right operators.
RIGHT_PARENTHESIS // )
RIGHT_BRACKET // ]
RIGHT_BRACE // }
SEMICOLON // ;
COLON // :
QUESTION_MARK // ?
firstKeyword //nolint: deadcode
// Basic flow - keywords below here.
_
IF
IN
DO
// Declarations.
VAR
FOR
NEW
TRY
// Advanced flow.
THIS
ELSE
CASE
VOID
WITH
// Loops.
WHILE
BREAK
CATCH
THROW
// Functions.
RETURN
TYPEOF
DELETE
SWITCH
// Fallback identifiers.
DEFAULT
FINALLY
// Miscellaneous.
FUNCTION
CONTINUE
DEBUGGER
// Instance of.
INSTANCEOF
lastKeyword //nolint: deadcode
)
var token2string = [...]string{
@ -199,149 +201,149 @@ var token2string = [...]string{
INSTANCEOF: "instanceof",
}
var keywordTable = map[string]_keyword{
"if": _keyword{
var keywordTable = map[string]keyword{
"if": {
token: IF,
},
"in": _keyword{
"in": {
token: IN,
},
"do": _keyword{
"do": {
token: DO,
},
"var": _keyword{
"var": {
token: VAR,
},
"for": _keyword{
"for": {
token: FOR,
},
"new": _keyword{
"new": {
token: NEW,
},
"try": _keyword{
"try": {
token: TRY,
},
"this": _keyword{
"this": {
token: THIS,
},
"else": _keyword{
"else": {
token: ELSE,
},
"case": _keyword{
"case": {
token: CASE,
},
"void": _keyword{
"void": {
token: VOID,
},
"with": _keyword{
"with": {
token: WITH,
},
"while": _keyword{
"while": {
token: WHILE,
},
"break": _keyword{
"break": {
token: BREAK,
},
"catch": _keyword{
"catch": {
token: CATCH,
},
"throw": _keyword{
"throw": {
token: THROW,
},
"return": _keyword{
"return": {
token: RETURN,
},
"typeof": _keyword{
"typeof": {
token: TYPEOF,
},
"delete": _keyword{
"delete": {
token: DELETE,
},
"switch": _keyword{
"switch": {
token: SWITCH,
},
"default": _keyword{
"default": {
token: DEFAULT,
},
"finally": _keyword{
"finally": {
token: FINALLY,
},
"function": _keyword{
"function": {
token: FUNCTION,
},
"continue": _keyword{
"continue": {
token: CONTINUE,
},
"debugger": _keyword{
"debugger": {
token: DEBUGGER,
},
"instanceof": _keyword{
"instanceof": {
token: INSTANCEOF,
},
"const": _keyword{
"const": {
token: KEYWORD,
futureKeyword: true,
},
"class": _keyword{
"class": {
token: KEYWORD,
futureKeyword: true,
},
"enum": _keyword{
"enum": {
token: KEYWORD,
futureKeyword: true,
},
"export": _keyword{
"export": {
token: KEYWORD,
futureKeyword: true,
},
"extends": _keyword{
"extends": {
token: KEYWORD,
futureKeyword: true,
},
"import": _keyword{
"import": {
token: KEYWORD,
futureKeyword: true,
},
"super": _keyword{
"super": {
token: KEYWORD,
futureKeyword: true,
},
"implements": _keyword{
"implements": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"interface": _keyword{
"interface": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"let": _keyword{
"let": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"package": _keyword{
"package": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"private": _keyword{
"private": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"protected": _keyword{
"protected": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"public": _keyword{
"public": {
token: KEYWORD,
futureKeyword: true,
strict: true,
},
"static": _keyword{
"static": {
token: KEYWORD,
futureKeyword: 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"
)
func (runtime *_runtime) newArgumentsObject(indexOfParameterName []string, stash _stash, length int) *_object {
self := runtime.newClassObject("Arguments")
func (rt *runtime) newArgumentsObject(indexOfParameterName []string, stash stasher, length int) *object {
obj := rt.newClassObject("Arguments")
for index, _ := range indexOfParameterName {
for index := range indexOfParameterName {
name := strconv.FormatInt(int64(index), 10)
objectDefineOwnProperty(self, name, _property{Value{}, 0111}, false)
objectDefineOwnProperty(obj, name, property{Value{}, 0o111}, false)
}
self.objectClass = _classArguments
self.value = _argumentsObject{
obj.objectClass = classArguments
obj.value = argumentsObject{
indexOfParameterName: indexOfParameterName,
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
// function(abc, def, ghi)
// indexOfParameterName[0] = "abc"
// indexOfParameterName[1] = "def"
// indexOfParameterName[2] = "ghi"
// ...
stash _stash
stash stasher
}
func (in _argumentsObject) clone(clone *_clone) _argumentsObject {
indexOfParameterName := make([]string, len(in.indexOfParameterName))
copy(indexOfParameterName, in.indexOfParameterName)
return _argumentsObject{
func (o argumentsObject) clone(c *cloner) argumentsObject {
indexOfParameterName := make([]string, len(o.indexOfParameterName))
copy(indexOfParameterName, o.indexOfParameterName)
return argumentsObject{
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)
if index >= 0 && index < int64(len(self.indexOfParameterName)) {
name := self.indexOfParameterName[index]
if index >= 0 && index < int64(len(o.indexOfParameterName)) {
name := o.indexOfParameterName[index]
if name == "" {
return Value{}, false
}
return self.stash.getBinding(name, false), true
return o.stash.getBinding(name, false), true
}
return Value{}, false
}
func (self _argumentsObject) put(name string, value Value) {
func (o argumentsObject) put(name string, value Value) {
index := stringToArrayIndex(name)
name = self.indexOfParameterName[index]
self.stash.setBinding(name, value, false)
name = o.indexOfParameterName[index]
o.stash.setBinding(name, value, false)
}
func (self _argumentsObject) delete(name string) {
func (o argumentsObject) delete(name string) {
index := stringToArrayIndex(name)
self.indexOfParameterName[index] = ""
o.indexOfParameterName[index] = ""
}
func argumentsGet(self *_object, name string) Value {
if value, exists := self.value.(_argumentsObject).get(name); exists {
func argumentsGet(obj *object, name string) Value {
if value, exists := obj.value.(argumentsObject).get(name); exists {
return value
}
return objectGet(self, name)
return objectGet(obj, name)
}
func argumentsGetOwnProperty(self *_object, name string) *_property {
property := objectGetOwnProperty(self, name)
if value, exists := self.value.(_argumentsObject).get(name); exists {
property.value = value
func argumentsGetOwnProperty(obj *object, name string) *property {
prop := objectGetOwnProperty(obj, name)
if value, exists := obj.value.(argumentsObject).get(name); exists {
prop.value = value
}
return property
return prop
}
func argumentsDefineOwnProperty(self *_object, name string, descriptor _property, throw bool) bool {
if _, exists := self.value.(_argumentsObject).get(name); exists {
if !objectDefineOwnProperty(self, name, descriptor, false) {
return self.runtime.typeErrorResult(throw)
func argumentsDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
if _, exists := obj.value.(argumentsObject).get(name); exists {
if !objectDefineOwnProperty(obj, name, descriptor, false) {
return obj.runtime.typeErrorResult(throw)
}
if value, valid := descriptor.value.(Value); valid {
self.value.(_argumentsObject).put(name, value)
obj.value.(argumentsObject).put(name, value)
}
return true
}
return objectDefineOwnProperty(self, name, descriptor, throw)
return objectDefineOwnProperty(obj, name, descriptor, throw)
}
func argumentsDelete(self *_object, name string, throw bool) bool {
if !objectDelete(self, name, throw) {
func argumentsDelete(obj *object, name string, throw bool) bool {
if !objectDelete(obj, name, throw) {
return false
}
if _, exists := self.value.(_argumentsObject).get(name); exists {
self.value.(_argumentsObject).delete(name)
if _, exists := obj.value.(argumentsObject).get(name); exists {
obj.value.(argumentsObject).delete(name)
}
return true
}

View file

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

View file

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

View file

@ -7,23 +7,21 @@ import (
Time "time"
)
type _dateObject struct {
type dateObject struct {
time Time.Time // Time from the "time" package, a cached version of time
epoch int64
value Value
isNaN bool
}
var (
invalidDateObject = _dateObject{
time: Time.Time{},
epoch: -1,
value: NaNValue(),
isNaN: true,
}
)
var invalidDateObject = dateObject{
time: Time.Time{},
epoch: -1,
value: NaNValue(),
isNaN: true,
}
type _ecmaTime struct {
type ecmaTime struct {
year int
month int
day int
@ -34,8 +32,8 @@ type _ecmaTime struct {
location *Time.Location // Basically, either local or UTC
}
func ecmaTime(goTime Time.Time) _ecmaTime {
return _ecmaTime{
func newEcmaTime(goTime Time.Time) ecmaTime {
return ecmaTime{
goTime.Year(),
dateFromGoMonth(goTime.Month()),
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(
self.year,
dateToGoMonth(self.month),
self.day,
self.hour,
self.minute,
self.second,
self.millisecond*(100*100*100),
self.location,
t.year,
dateToGoMonth(t.month),
t.day,
t.hour,
t.minute,
t.second,
t.millisecond*(100*100*100),
t.location,
)
}
func (self *_dateObject) Time() Time.Time {
return self.time
func (d *dateObject) Time() Time.Time {
return d.time
}
func (self *_dateObject) Epoch() int64 {
return self.epoch
func (d *dateObject) Epoch() int64 {
return d.epoch
}
func (self *_dateObject) Value() Value {
return self.value
func (d *dateObject) Value() 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
func (self *_dateObject) SetNaN() {
self.time = Time.Time{}
self.epoch = -1
self.value = NaNValue()
self.isNaN = true
// FIXME A date should only be in the range of -100,000,000 to +100,000,000 (1970): 15.9.1.1.
func (d *dateObject) SetNaN() {
d.time = Time.Time{}
d.epoch = -1
d.value = NaNValue()
d.isNaN = true
}
func (self *_dateObject) SetTime(time Time.Time) {
self.Set(timeToEpoch(time))
func (d *dateObject) SetTime(time Time.Time) {
d.Set(timeToEpoch(time))
}
func (self *_dateObject) Set(epoch float64) {
func (d *dateObject) Set(epoch float64) {
// epoch
self.epoch = epochToInteger(epoch)
d.epoch = epochToInteger(epoch)
// time
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
if err != nil {
self.isNaN = true
self.epoch = -1
self.value = NaNValue()
d.isNaN = true
d.epoch = -1
d.value = NaNValue()
} 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))
}
func epochToTime(value float64) (time Time.Time, err error) {
func epochToTime(value float64) (Time.Time, error) {
epochWithMilli := value
if math.IsNaN(epochWithMilli) || math.IsInf(epochWithMilli, 0) {
err = fmt.Errorf("Invalid time %v", value)
return
return Time.Time{}, fmt.Errorf("invalid time %v", value)
}
epoch := int64(epochWithMilli / 1000)
milli := int64(epochWithMilli) % 1000
time = Time.Unix(int64(epoch), milli*1000000).In(utcTimeZone)
return
return Time.Unix(epoch, milli*1000000).In(utcTimeZone), nil
}
func timeToEpoch(time Time.Time) float64 {
return float64(time.UnixMilli())
}
func (runtime *_runtime) newDateObject(epoch float64) *_object {
self := runtime.newObject()
self.class = classDate
func (rt *runtime) newDateObject(epoch float64) *object {
obj := rt.newObject()
obj.class = classDateName
// FIXME This is ugly...
date := _dateObject{}
date := dateObject{}
date.Set(epoch)
self.value = date
return self
obj.value = date
return obj
}
func (self *_object) dateValue() _dateObject {
value, _ := self.value.(_dateObject)
func (o *object) dateValue() dateObject {
value, _ := o.value.(dateObject)
return value
}
func dateObjectOf(rt *_runtime, _dateObject *_object) _dateObject {
if _dateObject == nil || _dateObject.class != classDate {
panic(rt.panicTypeError())
func dateObjectOf(rt *runtime, date *object) dateObject {
if date == nil {
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 {
return Time.Month(month + 1)
}
@ -163,7 +162,8 @@ func dateFromGoDay(day Time.Weekday) int {
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) {
if index >= len(argumentList) {
return default_, false
@ -175,29 +175,41 @@ func newDateTime(argumentList []Value, location *Time.Location) (epoch float64)
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 invalid bool
if year, invalid = pick(0, 1900.0); invalid {
goto INVALID
return math.NaN()
}
if month, invalid = pick(1, 0.0); invalid {
goto INVALID
return math.NaN()
}
if day, invalid = pick(2, 1.0); invalid {
goto INVALID
return math.NaN()
}
if hour, invalid = pick(3, 0.0); invalid {
goto INVALID
return math.NaN()
}
if minute, invalid = pick(4, 0.0); invalid {
goto INVALID
return math.NaN()
}
if second, invalid = pick(5, 0.0); invalid {
goto INVALID
return math.NaN()
}
if millisecond, invalid = pick(6, 0.0); invalid {
goto INVALID
return math.NaN()
}
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)
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 (
@ -238,6 +235,10 @@ var (
"2006-01T15:04:05",
"2006-01-02T15:04:05",
"2006/01",
"2006/01/02",
"2006/01/02 15:04:05",
"2006T15:04:05.000",
"2006-01T15:04:05.000",
"2006-01-02T15:04:05.000",
@ -259,7 +260,8 @@ var (
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
var time Time.Time
var err error

View file

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

View file

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

View file

@ -5,51 +5,51 @@ import (
"strconv"
)
func (runtime *_runtime) newGoArrayObject(value reflect.Value) *_object {
self := runtime.newObject()
self.class = classGoArray
self.objectClass = _classGoArray
self.value = _newGoArrayObject(value)
return self
func (rt *runtime) newGoArrayObject(value reflect.Value) *object {
o := rt.newObject()
o.class = classGoArrayName
o.objectClass = classGoArray
o.value = newGoArrayObject(value)
return o
}
type _goArrayObject struct {
type goArrayObject struct {
value reflect.Value
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)
mode := _propertyMode(0010)
mode := propertyMode(0o010)
if writable {
mode = 0110
mode = 0o110
}
self := &_goArrayObject{
return &goArrayObject{
value: value,
writable: writable,
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 {
v, ok := self.getValueIndex(index)
v, ok := o.getValueIndex(index)
if 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 reflect.Value{}, false
}
func (self _goArrayObject) getValueIndex(index int64) (reflect.Value, bool) {
value := reflect.Indirect(self.value)
func (o goArrayObject) getValueIndex(index int64) (reflect.Value, bool) {
value := reflect.Indirect(o.value)
if index < int64(value.Len()) {
return value.Index(int(index)), true
}
@ -57,12 +57,12 @@ func (self _goArrayObject) getValueIndex(index int64) (reflect.Value, bool) {
return reflect.Value{}, false
}
func (self _goArrayObject) setValue(index int64, value Value) bool {
indexValue, exists := self.getValueIndex(index)
func (o goArrayObject) setValue(index int64, value Value) bool {
indexValue, exists := o.getValueIndex(index)
if !exists {
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 {
panic(err)
}
@ -70,87 +70,87 @@ func (self _goArrayObject) setValue(index int64, value Value) bool {
return true
}
func goArrayGetOwnProperty(self *_object, name string) *_property {
func goArrayGetOwnProperty(obj *object, name string) *property {
// length
if name == propertyLength {
return &_property{
value: toValue(reflect.Indirect(self.value.(*_goArrayObject).value).Len()),
return &property{
value: toValue(reflect.Indirect(obj.value.(*goArrayObject).value).Len()),
mode: 0,
}
}
// .0, .1, .2, ...
if index := stringToArrayIndex(name); index >= 0 {
object := self.value.(*_goArrayObject)
goObj := obj.value.(*goArrayObject)
value := Value{}
reflectValue, exists := object.getValueIndex(index)
reflectValue, exists := goObj.getValueIndex(index)
if exists {
value = self.runtime.toValue(reflectValue.Interface())
value = obj.runtime.toValue(reflectValue.Interface())
}
return &_property{
return &property{
value: value,
mode: object.propertyMode,
mode: goObj.propertyMode,
}
}
if method := self.value.(*_goArrayObject).value.MethodByName(name); method != (reflect.Value{}) {
return &_property{
self.runtime.toValue(method.Interface()),
0110,
if method := obj.value.(*goArrayObject).value.MethodByName(name); method != (reflect.Value{}) {
return &property{
obj.runtime.toValue(method.Interface()),
0o110,
}
}
return objectGetOwnProperty(self, name)
return objectGetOwnProperty(obj, name)
}
func goArrayEnumerate(self *_object, all bool, each func(string) bool) {
object := self.value.(*_goArrayObject)
func goArrayEnumerate(obj *object, all bool, each func(string) bool) {
goObj := obj.value.(*goArrayObject)
// .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)
if !each(name) {
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 {
return self.runtime.typeErrorResult(throw)
return obj.runtime.typeErrorResult(throw)
} else if index := stringToArrayIndex(name); index >= 0 {
object := self.value.(*_goArrayObject)
if object.writable {
if self.value.(*_goArrayObject).setValue(index, descriptor.value.(Value)) {
goObj := obj.value.(*goArrayObject)
if goObj.writable {
if obj.value.(*goArrayObject).setValue(index, descriptor.value.(Value)) {
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
if name == propertyLength {
return self.runtime.typeErrorResult(throw)
return obj.runtime.typeErrorResult(throw)
}
// .0, .1, .2, ...
index := stringToArrayIndex(name)
if index >= 0 {
object := self.value.(*_goArrayObject)
if object.writable {
indexValue, exists := object.getValueIndex(index)
goObj := obj.value.(*goArrayObject)
if goObj.writable {
indexValue, exists := goObj.getValueIndex(index)
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 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"
)
func (runtime *_runtime) newGoMapObject(value reflect.Value) *_object {
self := runtime.newObject()
self.class = classObject // TODO Should this be something else?
self.objectClass = _classGoMap
self.value = _newGoMapObject(value)
return self
func (rt *runtime) newGoMapObject(value reflect.Value) *object {
obj := rt.newObject()
obj.class = classObjectName // TODO Should this be something else?
obj.objectClass = classGoMap
obj.value = newGoMapObject(value)
return obj
}
type _goMapObject struct {
type goMapObject struct {
value reflect.Value
keyType reflect.Type
valueType reflect.Type
}
func _newGoMapObject(value reflect.Value) *_goMapObject {
func newGoMapObject(value reflect.Value) *goMapObject {
if value.Kind() != reflect.Map {
dbgf("%/panic//%@: %v != reflect.Map", value.Kind())
}
self := &_goMapObject{
return &goMapObject{
value: value,
keyType: value.Type().Key(),
valueType: value.Type().Elem(),
}
return self
}
func (self _goMapObject) toKey(name string) reflect.Value {
reflectValue, err := stringToReflectValue(name, self.keyType.Kind())
func (o goMapObject) toKey(name string) reflect.Value {
reflectValue, err := stringToReflectValue(name, o.keyType.Kind())
if err != nil {
panic(err)
}
return reflectValue
}
func (self _goMapObject) toValue(value Value) reflect.Value {
reflectValue, err := value.toReflectValue(self.valueType)
func (o goMapObject) toValue(value Value) reflect.Value {
reflectValue, err := value.toReflectValue(o.valueType)
if err != nil {
panic(err)
}
return reflectValue
}
func goMapGetOwnProperty(self *_object, name string) *_property {
object := self.value.(*_goMapObject)
value := object.value.MapIndex(object.toKey(name))
func goMapGetOwnProperty(obj *object, name string) *property {
goObj := obj.value.(*goMapObject)
// 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() {
return &_property{self.runtime.toValue(value.Interface()), 0111}
return &property{obj.runtime.toValue(value.Interface()), 0o111}
}
// Other methods
if method := self.value.(*_goMapObject).value.MethodByName(name); method.IsValid() {
return &_property{
value: self.runtime.toValue(method.Interface()),
mode: 0110,
if method := obj.value.(*goMapObject).value.MethodByName(name); method.IsValid() {
return &property{
value: obj.runtime.toValue(method.Interface()),
mode: 0o110,
}
}
return nil
}
func goMapEnumerate(self *_object, all bool, each func(string) bool) {
object := self.value.(*_goMapObject)
keys := object.value.MapKeys()
func goMapEnumerate(obj *object, all bool, each func(string) bool) {
goObj := obj.value.(*goMapObject)
keys := goObj.value.MapKeys()
for _, key := range keys {
if !each(toValue(key).String()) {
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 {
object := self.value.(*_goMapObject)
func goMapDefineOwnProperty(obj *object, name string, descriptor property, throw bool) bool {
goObj := obj.value.(*goMapObject)
// TODO ...or 0222
if descriptor.mode != 0111 {
return self.runtime.typeErrorResult(throw)
if descriptor.mode != 0o111 {
return obj.runtime.typeErrorResult(throw)
}
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
}
func goMapDelete(self *_object, name string, throw bool) bool {
object := self.value.(*_goMapObject)
object.value.SetMapIndex(object.toKey(name), reflect.Value{})
func goMapDelete(obj *object, name string, throw bool) bool {
goObj := obj.value.(*goMapObject)
goObj.value.SetMapIndex(goObj.toKey(name), reflect.Value{})
// FIXME
return true
}

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