commit e9e409d958d0f46ad5cfcb37beef04631bc76c4d Author: ❀ » Cato Sweeney. ❀ » Console@the.bb Date: Wed Dec 3 12:53:19 2025 +0800 * diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..ac2f1d8 --- /dev/null +++ b/.gitignore @@ -0,0 +1,17 @@ +# Binaries for programs and plugins +main +*.exe +*.exe~ +*.dll +*.so +*.dylib +*.idea +# Test binary, built with `go test -c` +*.test +__debug* + +*.git + +/fatal +# Output of the go coverage tool, specifically when used with LiteIDE +*.out diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..8eea20a --- /dev/null +++ b/Dockerfile @@ -0,0 +1,18 @@ +FROM alpine:latest +LABEL MAINTAINER="PandaX" + +WORKDIR /go/src/panda +COPY ./pandax ./ +COPY ./config.yml ./ +COPY ./resource ./resource +COPY ./uploads ./uploads + +RUN chmod 755 ./pandax + +EXPOSE 7788 +EXPOSE 9001 +EXPOSE 9002 +EXPOSE 9003 +EXPOSE 9003/udp + +ENTRYPOINT ./pandax \ No newline at end of file diff --git a/api/function.js b/api/function.js new file mode 100644 index 0000000..b6e4cb5 --- /dev/null +++ b/api/function.js @@ -0,0 +1,12 @@ +function add(a, b) { + return a+b +} + +function main() { + let ip = payload.get().headers['Remote-Host'] + ip = ip.toString() + return JSON.stringify({ + status: add(199,1), + addr: ip, + }) +} \ No newline at end of file diff --git a/apps/develop/api/gen.go b/apps/develop/api/gen.go new file mode 100644 index 0000000..318b9e4 --- /dev/null +++ b/apps/develop/api/gen.go @@ -0,0 +1,30 @@ +package api + +import ( + "pandax/apps/develop/gen" + "pandax/apps/develop/services" + "pandax/kit/restfulx" +) + +type GenApi struct { + GenTableApp services.SysGenTableModel +} + +// Preview 代码视图 +func (e *GenApi) Preview(rc *restfulx.ReqCtx) { + tableId := restfulx.PathParamInt(rc, "tableId") + rc.ResData = gen.Preview(int64(tableId)) +} + +// GenCode 代码生成 +func (e *GenApi) GenCode(rc *restfulx.ReqCtx) { + tableId := restfulx.PathParamInt(rc, "tableId") + gen.GenCode(int64(tableId)) +} + +// GenConfigure 配置生成 +func (e *GenApi) GenConfigure(rc *restfulx.ReqCtx) { + tableId := restfulx.PathParamInt(rc, "tableId") + menuId := restfulx.QueryInt(rc, "menuId", 0) + gen.GenConfigure(tableId, menuId) +} diff --git a/apps/develop/api/table.go b/apps/develop/api/table.go new file mode 100644 index 0000000..ed3dec1 --- /dev/null +++ b/apps/develop/api/table.go @@ -0,0 +1,131 @@ +package api + +import ( + "pandax/apps/develop/api/vo" + "pandax/apps/develop/entity" + "pandax/apps/develop/gen" + "pandax/apps/develop/services" + "pandax/kit/biz" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" + "strings" + "sync" +) + +type GenTableApi struct { + GenTableApp services.SysGenTableModel +} + +// GetDBTableList 获取数据库表列表 +func (g *GenTableApi) GetDBTableList(rc *restfulx.ReqCtx) { + dbTables := entity.DBTables{} + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + dbTables.TableName = restfulx.QueryParam(rc, "tableName") + + list, total, err := g.GenTableApp.FindDbTablesListPage(pageNum, pageSize, dbTables) + biz.ErrIsNil(err, "查询配置分页列表信息失败") + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +// GetTablePage 获取表页列表数据 +func (g *GenTableApi) GetTablePage(rc *restfulx.ReqCtx) { + devGenTable := entity.DevGenTable{} + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + devGenTable.TableName = restfulx.QueryParam(rc, "tableName") + devGenTable.TableComment = restfulx.QueryParam(rc, "tableComment") + devGenTable.RoleId = rc.LoginAccount.RoleId + devGenTable.Owner = rc.LoginAccount.UserName + + list, total, err := g.GenTableApp.FindListPage(pageNum, pageSize, devGenTable) + biz.ErrIsNil(err, "分页获取表失败") + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +// GetTableInfo 获取表信息 +func (g *GenTableApi) GetTableInfo(rc *restfulx.ReqCtx) { + devGenTable := entity.DevGenTable{} + devGenTable.TableId = int64(restfulx.PathParamInt(rc, "tableId")) + devGenTable.RoleId = rc.LoginAccount.RoleId + result, err := g.GenTableApp.FindOne(devGenTable, true) + biz.ErrIsNil(err, "分页获取表信息失败") + rc.ResData = vo.TableInfoVo{ + List: result.Columns, + Info: *result, + } +} + +// GetTableInfoByName 获取表信息 +func (g *GenTableApi) GetTableInfoByName(rc *restfulx.ReqCtx) { + devGenTable := entity.DevGenTable{} + devGenTable.TableName = restfulx.QueryParam(rc, "tableName") + devGenTable.RoleId = rc.LoginAccount.RoleId + result, err := g.GenTableApp.FindOne(devGenTable, true) + biz.ErrIsNil(err, "分页获取表信息失败") + rc.ResData = vo.TableInfoVo{ + List: result.Columns, + Info: *result, + } +} + +// GetTableTree 获取树表信息 +func (g *GenTableApi) GetTableTree(rc *restfulx.ReqCtx) { + devGenTable := entity.DevGenTable{} + devGenTable.RoleId = rc.LoginAccount.RoleId + devGenTable.Owner = rc.LoginAccount.UserName + tree, err := g.GenTableApp.FindTree(devGenTable) + biz.ErrIsNil(err, "获取树表信息失败") + rc.ResData = tree +} + +// Insert 添加表结构 +func (g *GenTableApi) Insert(rc *restfulx.ReqCtx) { + tables := strings.Split(restfulx.QueryParam(rc, "tables"), ",") + + var wg sync.WaitGroup + for _, table := range tables { + wg.Add(1) + go func(table string) { + defer wg.Done() + var tg = gen.Generator{ + TableName: table, + } + genTable, err := tg.Generate() + if err != nil { + biz.ErrIsNil(err, "创建表结构") + } + genTable.OrgId = rc.LoginAccount.OrganizationId + genTable.Owner = rc.LoginAccount.UserName + g.GenTableApp.Insert(genTable) + }(table) + } + wg.Wait() +} + +// Update 修改表结构 +func (g *GenTableApi) Update(rc *restfulx.ReqCtx) { + var data entity.DevGenTable + restfulx.BindJsonAndValid(rc, &data) + _, err := g.GenTableApp.Update(data) + biz.ErrIsNil(err, "修改表结构") +} + +// Delete 删除表结构 +func (g *GenTableApi) Delete(rc *restfulx.ReqCtx) { + tableIds := restfulx.PathParam(rc, "tableId") + group := utils.IdsStrToIdsIntGroup(tableIds) + err := g.GenTableApp.Delete(group) + biz.ErrIsNil(err, "删除表结构") +} diff --git a/apps/develop/api/vo/tableVo.go b/apps/develop/api/vo/tableVo.go new file mode 100644 index 0000000..6fa2c50 --- /dev/null +++ b/apps/develop/api/vo/tableVo.go @@ -0,0 +1,14 @@ +package vo + +import "pandax/apps/develop/entity" + +/** + * @Description + * @Author 熊猫 + * @Date 2022/8/4 15:52 + **/ + +type TableInfoVo struct { + List []entity.DevGenTableColumn `json:"list"` + Info entity.DevGenTable `json:"info"` +} diff --git a/apps/develop/entity/dev_gen_table.go b/apps/develop/entity/dev_gen_table.go new file mode 100644 index 0000000..815468f --- /dev/null +++ b/apps/develop/entity/dev_gen_table.go @@ -0,0 +1,36 @@ +package entity + +import "pandax/kit/model" + +type DevGenTable struct { + TableId int64 `gorm:"primaryKey;autoIncrement" json:"tableId"` // 编号 + OrgId int64 `json:"orgId" gorm:"type:int;comment:机构ID"` + Owner string `json:"owner" gorm:"type:varchar(64);comment:创建者,所有者"` + TableName string `gorm:"table_name" json:"tableName"` // 表名称 + TableComment string `gorm:"table_comment" json:"tableComment"` // 表描述 + ClassName string `gorm:"class_name" json:"className"` // 实体类名称 + TplCategory string `gorm:"tpl_category" json:"tplCategory"` // 使用的模板(crud单表操作 tree树表操作) + PackageName string `gorm:"package_name" json:"packageName"` // 生成包路径 + ModuleName string `gorm:"module_name" json:"moduleName"` // 生成模块名 + BusinessName string `gorm:"business_name" json:"businessName"` // 生成业务名 + FunctionName string `gorm:"function_name" json:"functionName"` // 生成功能名 + FunctionAuthor string `gorm:"function_author" json:"functionAuthor"` // 生成功能作者 + Options string `gorm:"options" json:"options"` // 其它生成选项 + Remark string `gorm:"remark" json:"remark"` // 备注 + PkColumn string `gorm:"pk_column;" json:"pkColumn"` + PkJsonField string `gorm:"pk_json_field" json:"pkJsonField"` + Columns []DevGenTableColumn `gorm:"-" json:"columns"` // 字段信息 + model.BaseModel + + RoleId int64 `gorm:"-"` // 角色数据权限 +} + +type DBTables struct { + TableName string `gorm:"column:TABLE_NAME" json:"tableName"` + Engine string `gorm:"column:ENGINE" json:"engine"` + TableRows string `gorm:"column:TABLE_ROWS" json:"tableRows"` + TableCollation string `gorm:"column:TABLE_COLLATION" json:"tableCollation"` + CreateTime string `gorm:"column:CREATE_TIME" json:"createTime"` + UpdateTime string `gorm:"column:UPDATE_TIME" json:"updateTime"` + TableComment string `gorm:"column:TABLE_COMMENT" json:"tableComment"` +} diff --git a/apps/develop/entity/dev_gen_table_column.go b/apps/develop/entity/dev_gen_table_column.go new file mode 100644 index 0000000..a677a56 --- /dev/null +++ b/apps/develop/entity/dev_gen_table_column.go @@ -0,0 +1,64 @@ +package entity + +type DevGenTableColumn struct { + ColumnId int64 `gorm:"primaryKey;autoIncrement" json:"columnId"` // 编号 + OrgId int64 `json:"orgId" gorm:"type:int;comment:机构ID"` + Owner string `json:"owner" gorm:"type:varchar(64);comment:创建者,所有者"` + TableId int64 `gorm:"table_id" json:"tableId"` // 归属表编号 + TableName string `gorm:"table_name" json:"tableName"` // 归属表名字 + ColumnName string `gorm:"column_name" json:"columnName"` // 列名称 + ColumnComment string `gorm:"column_comment" json:"columnComment"` // 列描述 + ColumnType string `gorm:"column_type" json:"columnType"` // 列类型 + ColumnKey string `gorm:"column_key" json:"columnKey"` + JsonField string `gorm:"json_field;" json:"jsonField"` + HtmlField string `gorm:"html_field" json:"htmlField"` // html字段名 + IsPk string `gorm:"is_pk" json:"isPk"` // 是否主键(1是) + IsIncrement string `gorm:"is_increment" json:"isIncrement"` // 是否自增(1是) + IsRequired string `gorm:"is_required" json:"isRequired"` // 是否必填(1是) + IsInsert string `gorm:"is_insert" json:"isInsert"` // 是否为插入字段(1是) + IsEdit string `gorm:"is_edit" json:"isEdit"` // 是否编辑字段(1是) + IsList string `gorm:"is_list" json:"isList"` // 是否列表字段(1是) + IsQuery string `gorm:"is_query" json:"isQuery"` // 是否查询字段(1是) + QueryType string `gorm:"query_type" json:"queryType"` // 查询方式(等于、不等于、大于、小于、范围) + HtmlType string `gorm:"html_type" json:"htmlType"` // 显示类型(文本框、文本域、下拉框、复选框、单选框、日期控件) + DictType string `gorm:"dict_type" json:"dictType"` // 字典类型 + Sort int `gorm:"sort" json:"sort"` // 排序 + LinkTableId int64 `gorm:"link_table_id" json:"linkTableId"` // 关联编号 + LinkTableName string `gorm:"link_table_name" json:"linkTableName"` // 关联表名 + LinkTableClass string `gorm:"link_table_class" json:"linkTableClass"` // 关联表类名 + LinkTablePackage string `gorm:"link_table_package" json:"linkTablePackage"` // 关联表包名 + LinkLabelId string `gorm:"link_label_id" json:"linkLabelId"` // 关联表键名 + LinkLabelName string `gorm:"link_label_name" json:"linkLabelName"` // 关联表字段值 + + RoleId int64 `gorm:"-"` // 角色数据权限 +} + +type DBColumns struct { + TableSchema string `gorm:"column:TABLE_SCHEMA" json:"tableSchema"` + TableName string `gorm:"column:TABLE_NAME" json:"tableName"` + ColumnName string `gorm:"column:COLUMN_NAME" json:"columnName"` + ColumnDefault string `gorm:"column:COLUMN_DEFAULT" json:"columnDefault"` + IsNullable string `gorm:"column:IS_NULLABLE" json:"isNullable"` + DataType string `gorm:"column:DATA_TYPE" json:"dataType"` + CharacterMaximumLength string `gorm:"column:CHARACTER_MAXIMUM_LENGTH" json:"characterMaximumLength"` + CharacterSetName string `gorm:"column:CHARACTER_SET_NAME" json:"characterSetName"` + ColumnType string `gorm:"column:COLUMN_TYPE" json:"columnType"` + ColumnKey string `gorm:"column:COLUMN_KEY" json:"columnKey"` + Extra string `gorm:"column:EXTRA" json:"extra"` + ColumnComment string `gorm:"column:COLUMN_COMMENT" json:"columnComment"` +} + +type DBColumnsP struct { + TableSchema string `gorm:"column:table_schema" json:"tableSchema"` + TableName string `gorm:"column:table_name" json:"tableName"` + ColumnName string `gorm:"column:column_name" json:"columnName"` + ColumnDefault string `gorm:"column:column_default" json:"columnDefault"` + IsNullable string `gorm:"column:is_nullable" json:"isNullable"` + DataType string `gorm:"column:data_type" json:"dataType"` + CharacterMaximumLength string `gorm:"column:character_maximum_length" json:"characterMaximumLength"` + CharacterSetName string `gorm:"column:character_set_name" json:"characterSetName"` + ColumnType string `gorm:"column:udt_name" json:"columnType"` + ColumnKey string ` json:"columnKey"` // 判断自增比较困难 + Extra string ` json:"extra"` + ColumnComment string ` json:"columnComment"` +} diff --git a/apps/develop/gen/gen.go b/apps/develop/gen/gen.go new file mode 100644 index 0000000..bae250a --- /dev/null +++ b/apps/develop/gen/gen.go @@ -0,0 +1,445 @@ +package gen + +import ( + "bytes" + "fmt" + "os" + "strconv" + "strings" + "sync" + "text/template" + + "pandax/apps/develop/entity" + "pandax/apps/develop/services" + sysEntity "pandax/apps/system/entity" + sysServices "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/pkg/global" + + "github.com/kakuilan/kgo" +) + +var ToolsGenTableColumn = toolsGenTableColumn{} + +type toolsGenTableColumn struct{} + +var ( + ColumnTypeStr = []string{"char", "varchar", "narchar", "varchar2", "tinytext", "text", "mediumtext", "longtext"} + ColumnTypeTime = []string{"datetime", "time", "date", "timestamp", "timestamptz"} + ColumnTypeNumber = []string{"tinyint", "smallint", "mediumint", "int", "int2", "int4", "int8", "number", "integer", "numeric", "bigint", "float", "float4", "float8", "double", "decimal"} + ColumnNameNotEdit = []string{"create_by", "update_by", "create_time", "update_time", "delete_time"} + ColumnNameNotList = []string{"create_by", "update_by", "update_time", "delete_time"} + ColumnNameNotQuery = []string{"create_by", "update_by", "create_time", "update_time", "delete_time", "remark"} +) + +func (s *toolsGenTableColumn) GetDbType(columnType string) string { + if strings.Index(columnType, "(") > 0 { + return columnType[0:strings.Index(columnType, "(")] + } else { + return columnType + } +} + +func (s *toolsGenTableColumn) IsExistInArray(value string, array []string) bool { + for _, v := range array { + if strings.Contains(value, v) { + return true + } + } + return false +} + +func (s *toolsGenTableColumn) GetColumnLength(columnType string) int { + start := strings.LastIndex(columnType, "(") + end := strings.LastIndex(columnType, ")") + if start >= 0 && end >= 0 { + result := columnType[start+1 : end] + i, _ := strconv.Atoi(result) + return i + } + return 0 +} + +func (s *toolsGenTableColumn) IsStringObject(dataType string) bool { + return s.IsExistInArray(dataType, ColumnTypeStr) +} + +func (s *toolsGenTableColumn) IsTimeObject(dataType string) bool { + return s.IsExistInArray(dataType, ColumnTypeTime) +} + +func (s *toolsGenTableColumn) IsNumberObject(dataType string) bool { + return s.IsExistInArray(dataType, ColumnTypeNumber) +} + +func (s *toolsGenTableColumn) IsNotEdit(name string) bool { + if strings.Contains(name, "id") { + return true + } + return s.IsExistInArray(name, ColumnNameNotEdit) +} + +func (s *toolsGenTableColumn) IsNotList(name string) bool { + return s.IsExistInArray(name, ColumnNameNotList) +} + +func (s *toolsGenTableColumn) IsNotQuery(name string) bool { + return s.IsExistInArray(name, ColumnNameNotQuery) +} + +func (s *toolsGenTableColumn) CheckNameColumn(columnName string) bool { + if len(columnName) >= 4 { + tmp := columnName[len(columnName)-4:] + return tmp == "name" + } + return false +} + +func (s *toolsGenTableColumn) CheckStatusColumn(columnName string) bool { + if len(columnName) >= 6 { + tmp := columnName[len(columnName)-6:] + return tmp == "status" + + } + return false +} + +func (s *toolsGenTableColumn) CheckTypeColumn(columnName string) bool { + if len(columnName) >= 4 { + tmp := columnName[len(columnName)-4:] + return tmp == "type" + } + return false +} + +func (s *toolsGenTableColumn) CheckSexColumn(columnName string) bool { + if len(columnName) >= 3 { + tmp := columnName[len(columnName)-4:] + return tmp == "sex" + } + return false +} + +type Generator struct { + TableName string +} + +func (g *Generator) Generate() (entity.DevGenTable, error) { + var data entity.DevGenTable + data.TableName = g.TableName + tableNameList := strings.Split(g.TableName, "_") + for i := 0; i < len(tableNameList); i++ { + strStart := string([]byte(tableNameList[i])[:1]) + strEnd := string([]byte(tableNameList[i])[1:]) + data.ClassName += strings.ToUpper(strStart) + strEnd + if i >= 1 { + data.BusinessName += strings.ToLower(strStart) + strEnd + data.FunctionName = strings.ToUpper(strStart) + strEnd + } + } + data.PackageName = "system" + data.TplCategory = "crud" + data.ModuleName = strings.Replace(g.TableName, "_", "-", -1) + + dbColumn, err := services.DevTableColumnModelDao.FindDbTableColumnList(g.TableName) + if err != nil { + return data, err + } + data.TableComment = data.ClassName + data.FunctionAuthor = "xScript_Engine" + + var wg sync.WaitGroup + columnChan := make(chan entity.DevGenTableColumn, len(dbColumn)) // 创建带缓冲的通道 + + for x := 0; x < len(dbColumn); x++ { + index := x + wg.Add(1) + go func(wg *sync.WaitGroup, y int) { + defer wg.Done() + var column entity.DevGenTableColumn + column.ColumnComment = dbColumn[y].ColumnComment + column.ColumnName = dbColumn[y].ColumnName + column.ColumnType = dbColumn[y].ColumnType + column.Sort = y + 1 + column.IsPk = "0" + + nameList := strings.Split(dbColumn[y].ColumnName, "_") + for i := 0; i < len(nameList); i++ { + strStart := string([]byte(nameList[i])[:1]) + strend := string([]byte(nameList[i])[1:]) + if i == 0 { + column.JsonField = strings.ToLower(strStart) + strend + } else { + column.JsonField += strings.ToUpper(strStart) + strend + } + } + + if column.ColumnComment == "" { + column.ColumnComment = column.JsonField + } + + dataType := strings.ToLower(column.ColumnType) + if ToolsGenTableColumn.IsStringObject(dataType) { + columnLength := ToolsGenTableColumn.GetColumnLength(column.ColumnType) + if columnLength >= 500 { + column.HtmlType = "textarea" + } else { + column.HtmlType = "input" + } + } else if ToolsGenTableColumn.IsTimeObject(dataType) { + column.HtmlType = "datetime" + } else if ToolsGenTableColumn.IsNumberObject(dataType) { + column.HtmlType = "input" + } else { + switch dataType { + case "bool": + column.HtmlType = "switch" + } + } + + if strings.Contains(dbColumn[y].ColumnKey, "PR") { + column.IsPk = "1" + data.PkColumn = dbColumn[y].ColumnName + data.PkJsonField = column.JsonField + if dbColumn[y].Extra == "auto_increment" { + column.IsIncrement = "1" + } + } + + if ToolsGenTableColumn.IsNotEdit(column.ColumnName) { + column.IsRequired = "0" + column.IsInsert = "0" + } else { + column.IsRequired = "0" + column.IsInsert = "1" + if strings.Contains(column.ColumnName, "name") || strings.Contains(column.ColumnName, "status") { + column.IsRequired = "1" + } + } + + if ToolsGenTableColumn.IsNotEdit(column.ColumnName) { + column.IsEdit = "0" + } else { + if column.IsPk == "1" { + column.IsEdit = "0" + } else { + column.IsEdit = "1" + } + } + + if ToolsGenTableColumn.IsNotList(column.ColumnName) { + column.IsList = "0" + } else { + column.IsList = "1" + } + + if ToolsGenTableColumn.IsNotQuery(column.ColumnName) { + column.IsQuery = "0" + } else { + column.IsQuery = "1" + } + + if ToolsGenTableColumn.CheckNameColumn(column.ColumnName) { + column.QueryType = "LIKE" + } else { + column.QueryType = "EQ" + } + + if ToolsGenTableColumn.CheckStatusColumn(column.ColumnName) { + column.HtmlType = "radio" + } else if ToolsGenTableColumn.CheckTypeColumn(column.ColumnName) || ToolsGenTableColumn.CheckSexColumn(column.ColumnName) { + column.HtmlType = "select" + } + + columnChan <- column // 将处理结果放入通道 + }(&wg, index) + } + + wg.Wait() + close(columnChan) // 关闭通道 + + for column := range columnChan { + data.Columns = append(data.Columns, column) // 将通道中的结果放入data.Columns + } + + return data, nil +} + +func Preview(tableId int64) map[string]interface{} { + defer func() { + if err := recover(); err != nil { + global.Log.Error(err) + } + }() + + t1, err := template.ParseFiles("resource/template/js/function.template") + biz.ErrIsNil(err, "function模版读取失败") + + t5, err := template.ParseFiles("resource/template/js/api.template") + biz.ErrIsNil(err, "api模版读取失败") + + t6, err := template.ParseFiles("resource/template/vue/list-vue.template") + biz.ErrIsNil(err, "vue列表模版读取失败!") + + t7, err := template.ParseFiles("resource/template/vue/edit-vue.template") + biz.ErrIsNil(err, "vue编辑模版读取失败!") + + tab, _ := services.DevGenTableModelDao.FindOne(entity.DevGenTable{TableId: tableId}, false) + + var b1 bytes.Buffer + t1.Execute(&b1, tab) + var b5 bytes.Buffer + t5.Execute(&b5, tab) + var b6 bytes.Buffer + t6.Execute(&b6, tab) + var b7 bytes.Buffer + t7.Execute(&b7, tab) + + mp := make(map[string]interface{}) + mp["template/function.template"] = b1.String() + mp["template/jsApi.template"] = b5.String() + mp["template/listVue.template"] = b6.String() + mp["template/editVue.template"] = b7.String() + return mp +} + +func GenCode(tableId int64) { + defer func() { + if err := recover(); err != nil { + global.Log.Error(err) + } + }() + + tab, err := services.DevGenTableModelDao.FindOne(entity.DevGenTable{TableId: tableId}, false) + biz.ErrIsNil(err, "读取表信息失败!") + tab.ModuleName = strings.Replace(tab.TableName, "_", "-", -1) + + t1, err := template.ParseFiles("resource/template/js/function.template") + biz.ErrIsNil(err, "function模版读取失败!") + + t5, err := template.ParseFiles("resource/template/js/api.template") + biz.ErrIsNil(err, "js模版读取失败!") + + t6, err := template.ParseFiles("resource/template/vue/list-vue.template") + biz.ErrIsNil(err, "vue列表模版读取失败!") + t7, err := template.ParseFiles("resource/template/vue/edit-vue.template") + biz.ErrIsNil(err, "vue编辑模版读取失败!") + + kgo.KFile.Mkdir("./"+tab.PackageName+"/"+tab.BusinessName+"/", os.ModePerm) + kgo.KFile.Mkdir(global.Conf.Gen.Frontpath+"/views/"+tab.PackageName+"/"+tab.BusinessName+"/", os.ModePerm) + kgo.KFile.Mkdir(global.Conf.Gen.Frontpath+"/views/"+tab.PackageName+"/"+tab.BusinessName+"/"+"component"+"/", os.ModePerm) + + var b1 bytes.Buffer + t1.Execute(&b1, tab) + var b5 bytes.Buffer + t5.Execute(&b5, tab) + var b6 bytes.Buffer + t6.Execute(&b6, tab) + var b7 bytes.Buffer + t7.Execute(&b7, tab) + + kgo.KFile.WriteFile("./"+tab.PackageName+"/"+tab.BusinessName+"/"+tab.ModuleName+"/function.js", b1.Bytes()) + kgo.KFile.WriteFile(global.Conf.Gen.Frontpath+"/api/"+tab.PackageName+"/"+tab.BusinessName+".ts", b5.Bytes()) + kgo.KFile.WriteFile(global.Conf.Gen.Frontpath+"/views/"+tab.PackageName+"/"+tab.BusinessName+"/index.vue", b6.Bytes()) + kgo.KFile.WriteFile(global.Conf.Gen.Frontpath+"/views/"+tab.PackageName+"/"+tab.BusinessName+"/"+"component"+"/editModule.vue", b7.Bytes()) +} + +func GenConfigure(tableId, parentId int) { + tab, err := services.DevGenTableModelDao.FindOne(entity.DevGenTable{TableId: int64(tableId)}, false) + if err != nil { + return + } + component := "Layout" + if parentId != 0 { + component = fmt.Sprintf("/%s/%s/%s/index", tab.PackageName, tab.BusinessName, tab.ModuleName) + } + menu := sysEntity.SysMenu{ + ParentId: int64(parentId), + MenuName: tab.TableComment, + MenuType: "C", + Sort: 1, + Icon: "elementSetting", + Path: fmt.Sprintf("/%s/%s/%s", tab.PackageName, tab.BusinessName, tab.ModuleName), + Component: component, + IsIframe: "1", + IsHide: "0", + IsKeepAlive: "1", + IsAffix: "1", + Permission: fmt.Sprintf("%s:%s:%s:list", tab.PackageName, tab.BusinessName, tab.ModuleName), + Status: "0", + CreateBy: "admin", + } + insert := sysServices.SysMenuModelDao.Insert(menu) + menuA := sysEntity.SysMenu{ + ParentId: insert.MenuId, + MenuName: "新增" + tab.TableComment, + MenuType: "F", + Sort: 1, + Permission: fmt.Sprintf("%s:%s:%s:add", tab.PackageName, tab.BusinessName, tab.ModuleName), + Status: "0", + CreateBy: "gen", + } + go sysServices.SysMenuModelDao.Insert(menuA) + menuE := sysEntity.SysMenu{ + ParentId: insert.MenuId, + MenuName: "修改" + tab.TableComment, + MenuType: "F", + Sort: 2, + Permission: fmt.Sprintf("%s:%s:%s:edit", tab.PackageName, tab.BusinessName, tab.ModuleName), + Status: "0", + CreateBy: "gen", + } + go sysServices.SysMenuModelDao.Insert(menuE) + menuD := sysEntity.SysMenu{ + ParentId: insert.MenuId, + MenuName: "删除" + tab.TableComment, + MenuType: "F", + Sort: 3, + Permission: fmt.Sprintf("%s:%s:%s:delete", tab.PackageName, tab.BusinessName, tab.ModuleName), + Status: "0", + CreateBy: "gen", + } + go sysServices.SysMenuModelDao.Insert(menuD) + apiG := sysEntity.SysApi{ + Path: fmt.Sprintf("/%s/%s/%s", tab.PackageName, tab.BusinessName, tab.ModuleName), + Description: fmt.Sprintf("获取%s信息(列表)", tab.TableComment), + ApiGroup: tab.BusinessName, + Method: "GET", + } + go sysServices.SysApiModelDao.Insert(apiG) + apiB := sysEntity.SysApi{ + Path: fmt.Sprintf("/%s/%s/%s/:filter", tab.PackageName, tab.BusinessName, tab.ModuleName), + Description: fmt.Sprintf("获取%s信息", tab.TableComment), + ApiGroup: tab.BusinessName, + Method: "GET", + } + go sysServices.SysApiModelDao.Insert(apiB) + apiA := sysEntity.SysApi{ + Path: fmt.Sprintf("/%s/%s/%s", tab.PackageName, tab.BusinessName, tab.ModuleName), + Description: fmt.Sprintf("添加%s信息", tab.TableComment), + ApiGroup: tab.BusinessName, + Method: "POST", + } + go sysServices.SysApiModelDao.Insert(apiA) + apiE := sysEntity.SysApi{ + Path: fmt.Sprintf("/%s/%s/%s", tab.PackageName, tab.BusinessName, tab.ModuleName), + Description: fmt.Sprintf("修改%s信息", tab.TableComment), + ApiGroup: tab.BusinessName, + Method: "PUT", + } + go sysServices.SysApiModelDao.Insert(apiE) + apiD := sysEntity.SysApi{ + Path: fmt.Sprintf("/%s/%s/%s/:filter", tab.PackageName, tab.BusinessName, tab.ModuleName), + Description: fmt.Sprintf("修改%s信息", tab.TableComment), + ApiGroup: tab.BusinessName, + Method: "PUT", + } + go sysServices.SysApiModelDao.Insert(apiD) + apiF := sysEntity.SysApi{ + Path: fmt.Sprintf("/%s/%s/%s", tab.PackageName, tab.BusinessName, tab.ModuleName), + Description: fmt.Sprintf("删除%s信息", tab.TableComment), + ApiGroup: tab.BusinessName, + Method: "DELETE", + } + go sysServices.SysApiModelDao.Insert(apiF) +} diff --git a/apps/develop/router/gen.go b/apps/develop/router/gen.go new file mode 100644 index 0000000..880b316 --- /dev/null +++ b/apps/develop/router/gen.go @@ -0,0 +1,51 @@ +package router + +import ( + "pandax/apps/develop/api" + "pandax/apps/develop/services" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitGenRouter(container *restful.Container) { + + // 登录日志 + s := &api.GenApi{ + GenTableApp: services.DevGenTableModelDao, + } + + ws := new(restful.WebService) + ws.Path("/develop/code/gen").Produces(restful.MIME_JSON) + tags := []string{"代码生成"} + + ws.Route(ws.GET("/preview/{tableId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取生成代码视图").Handle(s.Preview) + }). + Doc("获取生成代码视图"). + Param(ws.PathParameter("tableId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", map[string]any{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/code/{tableId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("生成代码").Handle(s.GenCode) + }). + Doc("生成代码"). + Param(ws.PathParameter("tableId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", map[string]any{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/configure/{tableId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("生成配置").Handle(s.GenConfigure) + }). + Doc("生成配置"). + Param(ws.PathParameter("tableId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", map[string]any{}). + Returns(404, "Not Found", nil)) + + container.Add(ws) +} diff --git a/apps/develop/router/table.go b/apps/develop/router/table.go new file mode 100644 index 0000000..6a158fb --- /dev/null +++ b/apps/develop/router/table.go @@ -0,0 +1,82 @@ +package router + +import ( + "pandax/apps/develop/api" + "pandax/apps/develop/api/vo" + "pandax/apps/develop/entity" + "pandax/apps/develop/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitGenTableRouter(container *restful.Container) { + // 登录日志 + s := &api.GenTableApi{ + GenTableApp: services.DevGenTableModelDao, + } + + ws := new(restful.WebService) + ws.Path("/develop/code/table").Produces(restful.MIME_JSON) + tags := []string{"codetable"} + + ws.Route(ws.GET("/db/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取数据库列表").Handle(s.GetDBTableList) + }). + Doc("获取数据库列表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取表列表").Handle(s.GetTablePage) + }). + Doc("获取表列表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/info/tableName").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取表信息By tableName").Handle(s.GetTableInfoByName) + }). + Doc("获取表信息By tableName"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", vo.TableInfoVo{})) + + ws.Route(ws.GET("/info/{tableId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取表信息").Handle(s.GetTableInfo) + }). + Doc("获取表信息"). + Param(ws.PathParameter("tenantId", "租户Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", vo.TableInfoVo{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/tableTree").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取表树").Handle(s.GetTableTree) + }). + Doc("获取表树"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", []entity.DevGenTable{})) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("新增表").Handle(s.Insert) + }). + Doc("新增表"). + Metadata(restfulspec.KeyOpenAPITags, tags)) // from the request + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改表").Handle(s.Update) + }). + Doc("修改表"). + Metadata(restfulspec.KeyOpenAPITags, tags)) // from the request + + ws.Route(ws.DELETE("/{tableId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除表").Handle(s.Delete) + }). + Doc("删除表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("tableId", "多id 1,2,3").DataType("string"))) + + container.Add(ws) +} diff --git a/apps/develop/services/gen_table.go b/apps/develop/services/gen_table.go new file mode 100644 index 0000000..738b22d --- /dev/null +++ b/apps/develop/services/gen_table.go @@ -0,0 +1,257 @@ +package services + +import ( + "errors" + "pandax/apps/develop/entity" + "pandax/kit/biz" + "pandax/kit/utils" + "pandax/pkg/global" + "pandax/pkg/global/model" +) + +/** + * @Description + * @Author Panda + * @Date 2021/12/31 8:58 + **/ + +type ( + SysGenTableModel interface { + FindDbTablesListPage(page, pageSize int, data entity.DBTables) (*[]entity.DBTables, int64, error) + FindDbTableOne(tableName string) (*entity.DBTables, error) + + // 导入表数据 + Insert(data entity.DevGenTable) error + FindOne(data entity.DevGenTable, exclude bool) (*entity.DevGenTable, error) + FindTree(data entity.DevGenTable) (*[]entity.DevGenTable, error) + FindListPage(page, pageSize int, data entity.DevGenTable) (*[]entity.DevGenTable, int64, error) + Update(data entity.DevGenTable) (*entity.DevGenTable, error) + Delete(tableIds []int64) error + } + + devGenTableModelImpl struct { + table string + } +) + +var DevGenTableModelDao SysGenTableModel = &devGenTableModelImpl{ + table: "dev_gen_tables", +} + +func (m *devGenTableModelImpl) FindDbTablesListPage(page, pageSize int, data entity.DBTables) (*[]entity.DBTables, int64, error) { + list := make([]entity.DBTables, 0) + pgdata := make([]map[string]any, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + if global.Conf.Server.DbType != "mysql" && global.Conf.Server.DbType != "postgresql" { + biz.ErrIsNil(errors.New("只支持mysql和postgresql数据库"), "只支持mysql和postgresql数据库") + } + + db := global.Db.Table("information_schema.tables") + if global.Conf.Server.DbType == "mysql" { + db = db.Where("table_schema= ? ", global.Conf.Gen.Dbname) + } + if global.Conf.Server.DbType == "postgresql" { + db = db.Where("table_schema = ? ", "public") + } + //db = db.Where("table_name NOT LIKE 'dev_%'") + if data.TableName != "" { + db = db.Where("table_name like ?", "%"+data.TableName+"%") + } + if global.Conf.Server.DbType == "mysql" { + err := db.Limit(pageSize).Offset(offset).Find(&list).Count(&total).Error + return &list, total, err + } else { + err := db.Limit(pageSize).Offset(offset).Find(&pgdata).Count(&total).Error + for _, pd := range pgdata { + list = append(list, entity.DBTables{TableName: utils.B2S(pd["table_name"].([]uint8))}) + } + return &list, total, err + } +} + +func (m *devGenTableModelImpl) FindDbTableOne(tableName string) (*entity.DBTables, error) { + resData := new(entity.DBTables) + if global.Conf.Server.DbType != "mysql" && global.Conf.Server.DbType != "postgresql" { + return nil, errors.New("只支持mysql和postgresql数据库") + } + db := global.Db.Table("information_schema.tables") + if global.Conf.Server.DbType == "mysql" { + db = db.Where("table_schema= ? ", global.Conf.Gen.Dbname) + } + if global.Conf.Server.DbType == "postgresql" { + db = db.Where("table_schema= ? ", "public") + } + db = db.Where("table_name = ?", tableName) + err := db.First(&resData).Error + return resData, err +} + +func (m *devGenTableModelImpl) Insert(dgt entity.DevGenTable) error { + err := global.Db.Table(m.table).Create(&dgt).Error + if err != nil { + return err + } + + var columnsToInsert []entity.DevGenTableColumn + for i := 0; i < len(dgt.Columns); i++ { + dgt.Columns[i].TableId = dgt.TableId + columns := dgt.Columns[i] + columns.OrgId = dgt.OrgId + columns.Owner = dgt.Owner + columnsToInsert = append(columnsToInsert, columns) + } + + DevTableColumnModelDao.InsertBatch(columnsToInsert) + + return nil +} + +func (m *devGenTableModelImpl) FindOne(data entity.DevGenTable, exclude bool) (*entity.DevGenTable, error) { + resData := new(entity.DevGenTable) + db := global.Db.Table(m.table) + if data.TableName != "" { + db = db.Where("table_name = ?", data.TableName) + } + if data.TableId != 0 { + db = db.Where("table_id = ?", data.TableId) + } + if data.TableComment != "" { + db = db.Where("table_comment = ?", data.TableComment) + } + err := db.First(resData).Error + if err != nil { + return resData, err + } + list, err := DevTableColumnModelDao.FindList(entity.DevGenTableColumn{TableId: resData.TableId}, exclude) + resData.Columns = *list + return resData, err +} + +func (m *devGenTableModelImpl) FindTree(data entity.DevGenTable) (*[]entity.DevGenTable, error) { + resData := make([]entity.DevGenTable, 0) + db := global.Db.Table(m.table) + + if data.TableName != "" { + db = db.Where("table_name = ?", data.TableName) + } + if data.TableId != 0 { + db = db.Where("table_id = ?", data.TableId) + } + if data.TableComment != "" { + db = db.Where("table_comment = ?", data.TableComment) + } + // 组织数据访问权限 + if err := model.OrgAuthSet(db, data.RoleId, data.Owner); err != nil { + return nil, err + } + if err := db.Find(&resData).Error; err != nil { + return nil, err + } + for i := 0; i < len(resData); i++ { + var col entity.DevGenTableColumn + col.TableId = resData[i].TableId + col.RoleId = data.RoleId + columns, _ := DevTableColumnModelDao.FindList(col, false) + resData[i].Columns = *columns + } + return &resData, nil +} + +func (m *devGenTableModelImpl) FindListPage(page, pageSize int, data entity.DevGenTable) (*[]entity.DevGenTable, int64, error) { + list := make([]entity.DevGenTable, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.TableName != "" { + db = db.Where("table_name = ?", data.TableName) + } + if data.TableComment != "" { + db = db.Where("table_comment = ?", data.TableComment) + } + // 组织数据访问权限 + if err := model.OrgAuthSet(db, data.RoleId, data.Owner); err != nil { + return &list, total, err + } + db.Where("delete_time IS NULL") + if err := db.Count(&total).Error; err != nil { + return &list, total, err + } + if err := db.Limit(pageSize).Offset(offset).Find(&list).Error; err != nil { + return &list, total, err + } + return &list, total, nil +} + +func (m *devGenTableModelImpl) Update(data entity.DevGenTable) (*entity.DevGenTable, error) { + err := global.Db.Table(m.table).Model(&data).Updates(&data).Error + if err != nil { + return nil, err + } + + tableNames := make([]string, 0) + for i := range data.Columns { + if data.Columns[i].LinkTableName != "" { + tableNames = append(tableNames, data.Columns[i].LinkTableName) + } + } + + tables := make([]entity.DevGenTable, 0) + tableMap := make(map[string]*entity.DevGenTable) + if len(tableNames) > 0 { + err = global.Db.Table(m.table).Where("table_name in (?)", tableNames).Find(&tables).Error + if err != nil { + return nil, err + } + for i := range tables { + tableMap[tables[i].TableName] = &tables[i] + } + } + + for i := 0; i < len(data.Columns); i++ { + if data.Columns[i].LinkTableName != "" { + t, ok := tableMap[data.Columns[i].LinkTableName] + if ok { + data.Columns[i].LinkTableId = t.TableId + data.Columns[i].LinkTableClass = t.ClassName + data.Columns[i].LinkTablePackage = t.BusinessName + data.Columns[i].LinkLabelId = t.PkColumn + data.Columns[i].LinkLabelName = t.PkJsonField + } + } + DevTableColumnModelDao.Update(data.Columns[i]) + } + return &data, nil +} + +func (e *devGenTableModelImpl) DeleteTables(tableId int64) (bool, error) { + var err error + success := false + tx := global.Db.Begin() + defer func() { + if err != nil { + tx.Rollback() + } else { + tx.Commit() + } + }() + if err = tx.Table("sys_tables").Delete(entity.DevGenTable{}, "table_id = ?", tableId).Error; err != nil { + return success, err + } + if err = tx.Table("sys_columns").Delete(entity.DevGenTableColumn{}, "table_id = ?", tableId).Error; err != nil { + return success, err + } + success = true + return success, nil +} + +func (m *devGenTableModelImpl) Delete(configIds []int64) error { + err := global.Db.Table(m.table).Delete(&entity.DevGenTable{}, "table_id in (?)", configIds).Error + if err != nil { + return errors.New("删除生成代码信息失败") + } + DevTableColumnModelDao.Delete(configIds) + return nil +} diff --git a/apps/develop/services/gen_table_column.go b/apps/develop/services/gen_table_column.go new file mode 100644 index 0000000..a0c32e6 --- /dev/null +++ b/apps/develop/services/gen_table_column.go @@ -0,0 +1,185 @@ +package services + +import ( + "errors" + "pandax/apps/develop/entity" + "pandax/pkg/global" +) + +/** + * @Description + * @Author Panda + * @Date 2021/12/31 14:44 + **/ + +type ( + SysGenTableColumnModel interface { + FindDbTablesColumnListPage(page, pageSize int, data entity.DBColumns) (*[]entity.DBColumns, int64, error) + FindDbTableColumnList(tableName string) ([]entity.DBColumns, error) + + Insert(data entity.DevGenTableColumn) (*entity.DevGenTableColumn, error) + InsertBatch(data []entity.DevGenTableColumn) error + FindList(data entity.DevGenTableColumn, exclude bool) (*[]entity.DevGenTableColumn, error) + Update(data entity.DevGenTableColumn) (*entity.DevGenTableColumn, error) + Delete(tableId []int64) error + } + + devTableColumnModelImpl struct { + table string + } +) + +var DevTableColumnModelDao SysGenTableColumnModel = &devTableColumnModelImpl{ + table: "dev_gen_table_columns", +} + +func (m *devTableColumnModelImpl) FindDbTablesColumnListPage(page, pageSize int, data entity.DBColumns) (*[]entity.DBColumns, int64, error) { + list := make([]entity.DBColumns, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + if global.Conf.Server.DbType != "mysql" && global.Conf.Server.DbType != "postgresql" { + return nil, 0, errors.New("只支持mysql和postgresql数据库") + } + db := global.Db.Table("information_schema.COLUMNS") + if global.Conf.Server.DbType == "mysql" { + db = db.Where("table_schema= ? ", global.Conf.Gen.Dbname) + } + if global.Conf.Server.DbType == "postgresql" { + db = db.Where("table_schema = ? ", "public") + } + + if data.TableName != "" { + db = db.Where("table_name = ?", data.TableName) + } + + err := db.Count(&total).Error + err = db.Limit(pageSize).Offset(offset).Find(&list).Error + return &list, total, err +} + +func (m *devTableColumnModelImpl) FindDbTableColumnList(tableName string) ([]entity.DBColumns, error) { + + if global.Conf.Server.DbType != "mysql" && global.Conf.Server.DbType != "postgresql" { + return nil, errors.New("只支持mysql和postgresql数据库") + } + db := global.Db.Table("information_schema.columns") + if global.Conf.Server.DbType == "mysql" { + db = db.Where("table_schema= ? ", global.Conf.Gen.Dbname) + } + if global.Conf.Server.DbType == "postgresql" { + db = db.Where("table_schema = ? ", "public") + } + if tableName == "" { + return nil, errors.New("table name cannot be empty!") + } + + db = db.Where("table_name = ?", tableName) + resData := make([]entity.DBColumns, 0) + if global.Conf.Server.DbType == "mysql" { + err := db.Find(&resData).Error + return resData, err + } + if global.Conf.Server.DbType == "postgresql" { + pr, err := getPgPR(tableName) + if err != nil { + return resData, errors.New("查询PG表主键字段失败") + } + resDataP := make([]entity.DBColumnsP, 0) + err = db.Find(&resDataP).Error + if err != nil { + return resData, errors.New("查询表字段失败") + } + for _, data := range resDataP { + dbc := entity.DBColumns{ + TableSchema: data.TableSchema, + TableName: data.TableName, + ColumnName: data.ColumnName, + ColumnDefault: data.ColumnDefault, + IsNullable: data.IsNullable, + DataType: data.DataType, + CharacterMaximumLength: data.CharacterMaximumLength, + CharacterSetName: data.CharacterSetName, + ColumnType: data.ColumnType, + ColumnKey: data.ColumnKey, + Extra: data.Extra, + ColumnComment: data.ColumnComment, + } + // 设置为主键 + if pr == data.ColumnName { + dbc.ColumnKey = "PRIMARY KEY" + } + resData = append(resData, dbc) + } + + return resData, nil + } + return resData, nil +} + +func getPgPR(tableName string) (string, error) { + sql := `SELECT + kcu.column_name +FROM + information_schema.table_constraints AS tc + JOIN information_schema.key_column_usage AS kcu ON tc.constraint_name = kcu.constraint_name +WHERE + tc.constraint_type = 'PRIMARY KEY' + AND tc.table_schema = 'public' + AND tc.table_name = ?;` + var pkname string + err := global.Db.Raw(sql, tableName).Scan(&pkname).Error + return pkname, err +} + +func (m *devTableColumnModelImpl) Insert(dgt entity.DevGenTableColumn) (*entity.DevGenTableColumn, error) { + err := global.Db.Table(m.table).Create(&dgt).Error + if err != nil { + global.Log.Error(err) + } + return &dgt, err +} + +func (m *devTableColumnModelImpl) InsertBatch(columns []entity.DevGenTableColumn) error { + tx := global.Db.Begin() + for _, column := range columns { + err := tx.Table(m.table).Create(&column).Error + if err != nil { + tx.Rollback() + global.Log.Error(err) + return err + } + } + err := tx.Commit().Error + if err != nil { + global.Log.Error(err) + return err + } + return nil +} + +func (m *devTableColumnModelImpl) FindList(data entity.DevGenTableColumn, exclude bool) (*[]entity.DevGenTableColumn, error) { + list := make([]entity.DevGenTableColumn, 0) + db := global.Db.Table(m.table).Where("table_id = ?", data.TableId) + if exclude { + notIn := make([]string, 6) + notIn = append(notIn, "id") + notIn = append(notIn, "create_by") + notIn = append(notIn, "update_by") + notIn = append(notIn, "create_time") + notIn = append(notIn, "update_time") + notIn = append(notIn, "delete_time") + db = db.Where("column_name not in(?)", notIn) + } + err := db.Find(&list).Error + return &list, err +} + +func (m *devTableColumnModelImpl) Update(data entity.DevGenTableColumn) (*entity.DevGenTableColumn, error) { + err := global.Db.Table(m.table).Model(&data).Updates(&data).Error + return &data, err +} + +func (m *devTableColumnModelImpl) Delete(tableId []int64) error { + err := global.Db.Table(m.table).Delete(&entity.DevGenTableColumn{}, "table_id in (?)", tableId).Error + return err +} diff --git a/apps/log/api/log_login.go b/apps/log/api/log_login.go new file mode 100644 index 0000000..c339b4e --- /dev/null +++ b/apps/log/api/log_login.go @@ -0,0 +1,48 @@ +package api + +import ( + "pandax/apps/log/entity" + "pandax/apps/log/services" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" +) + +type LogLoginApi struct { + LogLoginApp services.LogLoginModel +} + +func (l *LogLoginApi) GetLoginLogList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + loginLocation := restfulx.QueryParam(rc, "loginLocation") + username := restfulx.QueryParam(rc, "username") + list, total := l.LogLoginApp.FindListPage(pageNum, pageSize, entity.LogLogin{LoginLocation: loginLocation, Username: username}) + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +func (l *LogLoginApi) GetLoginLog(rc *restfulx.ReqCtx) { + infoId := restfulx.PathParamInt(rc, "infoId") + rc.ResData = l.LogLoginApp.FindOne(int64(infoId)) +} + +func (l *LogLoginApi) UpdateLoginLog(rc *restfulx.ReqCtx) { + var log entity.LogLogin + restfulx.BindQuery(rc, &log) + l.LogLoginApp.Update(log) +} + +func (l *LogLoginApi) DeleteLoginLog(rc *restfulx.ReqCtx) { + infoIds := restfulx.PathParam(rc, "infoId") + group := utils.IdsStrToIdsIntGroup(infoIds) + l.LogLoginApp.Delete(group) +} + +func (l *LogLoginApi) DeleteAll(rc *restfulx.ReqCtx) { + l.LogLoginApp.DeleteAll() +} diff --git a/apps/log/api/log_oper.go b/apps/log/api/log_oper.go new file mode 100644 index 0000000..ca17053 --- /dev/null +++ b/apps/log/api/log_oper.go @@ -0,0 +1,43 @@ +package api + +import ( + "pandax/apps/log/entity" + "pandax/apps/log/services" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" +) + +type LogOperApi struct { + LogOperApp services.LogOperModel +} + +func (l *LogOperApi) GetOperLogList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + businessType := restfulx.QueryParam(rc, "businessType") + operName := restfulx.QueryParam(rc, "operName") + title := restfulx.QueryParam(rc, "title") + list, total := l.LogOperApp.FindListPage(pageNum, pageSize, entity.LogOper{BusinessType: businessType, OperName: operName, Title: title}) + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +func (l *LogOperApi) GetOperLog(rc *restfulx.ReqCtx) { + operId := restfulx.PathParamInt(rc, "operId") + rc.ResData = l.LogOperApp.FindOne(int64(operId)) +} + +func (l *LogOperApi) DeleteOperLog(rc *restfulx.ReqCtx) { + operIds := restfulx.PathParam(rc, "operId") + group := utils.IdsStrToIdsIntGroup(operIds) + l.LogOperApp.Delete(group) +} + +func (l *LogOperApi) DeleteAll(rc *restfulx.ReqCtx) { + l.LogOperApp.DeleteAll() +} diff --git a/apps/log/entity/log_login.go b/apps/log/entity/log_login.go new file mode 100644 index 0000000..3aee1a4 --- /dev/null +++ b/apps/log/entity/log_login.go @@ -0,0 +1,24 @@ +package entity + +import ( + "pandax/kit/model" + "time" +) + +type LogLogin struct { + InfoId int64 `json:"infoId" gorm:"primary_key;AUTO_INCREMENT"` //主键 + Username string `json:"username" gorm:"type:varchar(128);comment:用户名"` + Status string `json:"status" gorm:"type:varchar(1);comment:状态"` + Ipaddr string `json:"ipaddr" gorm:"type:varchar(255);comment:ip地址"` + LoginLocation string `json:"loginLocation" gorm:"type:varchar(255);comment:归属地"` + Browser string `json:"browser" gorm:"type:varchar(255);comment:浏览器"` + Os string `json:"os" gorm:"type:varchar(255);comment:系统"` + Platform string `json:"platform" gorm:"type:varchar(255);comment:固件"` + LoginTime time.Time `json:"loginTime" gorm:"type:timestamp;comment:登录时间"` + CreateBy string `json:"createBy" gorm:"type:varchar(128);comment:创建人"` + UpdateBy string `json:"updateBy" gorm:"type:varchar(128);comment:更新者"` + Params string `json:"params" gorm:"-"` + Remark string `json:"remark" gorm:"type:varchar(255);"` //备注 + Msg string `json:"msg" gorm:"type:varchar(255);"` + model.BaseModel +} diff --git a/apps/log/entity/log_oper.go b/apps/log/entity/log_oper.go new file mode 100644 index 0000000..4eeb2be --- /dev/null +++ b/apps/log/entity/log_oper.go @@ -0,0 +1,19 @@ +package entity + +import ( + "pandax/kit/model" +) + +type LogOper struct { + OperId int64 `json:"operId" gorm:"primary_key;AUTO_INCREMENT"` //主键 + Title string `json:"title" gorm:"type:varchar(128);comment:操作的模块"` + BusinessType string `json:"businessType" gorm:"type:varchar(1);comment:0其它 1新增 2修改 3删除"` + Method string `json:"method" gorm:"type:varchar(255);comment:请求方法"` + OperName string `json:"operName" gorm:"type:varchar(255);comment:操作人员"` + OperUrl string `json:"operUrl" gorm:"type:varchar(255);comment:操作url"` + OperIp string `json:"operIp" gorm:"type:varchar(255);comment:操作IP"` + OperLocation string `json:"operLocation" gorm:"type:varchar(255);comment:操作地点"` + OperParam string `json:"operParam" gorm:"type:varchar(255);comment:请求参数"` // + Status string `json:"status" gorm:"type:varchar(1);comment:0=正常,1=异常"` + model.BaseModel +} diff --git a/apps/log/router/login_log.go b/apps/log/router/login_log.go new file mode 100644 index 0000000..e4bbfa2 --- /dev/null +++ b/apps/log/router/login_log.go @@ -0,0 +1,68 @@ +package router + +import ( + "pandax/apps/log/api" + "pandax/apps/log/entity" + "pandax/apps/log/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitLoginLogRouter(container *restful.Container) { + // 登录日志 + s := &api.LogLoginApi{ + LogLoginApp: services.LogLoginModelDao, + } + + ws := new(restful.WebService) + ws.Path("/log/logLogin").Produces(restful.MIME_JSON) + tags := []string{"logLogin"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取登录日志列表").Handle(s.GetLoginLogList) + }). + Doc("获取登录日志列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("loginLocation", "loginLocation").DataType("string")). + Param(ws.QueryParameter("username", "username").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/{infoId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取登录日志信息").Handle(s.GetLoginLog) + }). + Doc("获取登录日志信息"). + Param(ws.PathParameter("infoId", "Id").DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.LogLogin{}). // on the response + Returns(200, "OK", entity.LogLogin{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改登录日志信息").Handle(s.UpdateLoginLog) + }). + Doc("修改登录日志信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.LogLogin{})) + + ws.Route(ws.DELETE("/{infoId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除登录日志信息").Handle(s.DeleteLoginLog) + }). + Doc("删除登录日志信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("infoId", "多id 1,2,3").DataType("string"))) + + ws.Route(ws.DELETE("/all").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("清空登录日志信息").Handle(s.DeleteAll) + }). + Doc("清空登录日志信息"). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + container.Add(ws) + +} diff --git a/apps/log/router/oper_log.go b/apps/log/router/oper_log.go new file mode 100644 index 0000000..e5ad808 --- /dev/null +++ b/apps/log/router/oper_log.go @@ -0,0 +1,61 @@ +package router + +import ( + "pandax/apps/log/api" + "pandax/apps/log/entity" + "pandax/apps/log/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitOperLogRouter(container *restful.Container) { + // 操作日志 + s := &api.LogOperApi{ + LogOperApp: services.LogOperModelDao, + } + + ws := new(restful.WebService) + ws.Path("/log/logOper").Produces(restful.MIME_JSON) + tags := []string{"logOper"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取操作日志列表").Handle(s.GetOperLogList) + }). + Doc("获取操作日志列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("businessType", "businessType").DataType("string")). + Param(ws.QueryParameter("operName", "operName").DataType("string")). + Param(ws.QueryParameter("title", "title").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/{operId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取操作日志信息").Handle(s.GetOperLog) + }). + Doc("获取操作日志信息"). + Param(ws.PathParameter("operId", "Id").DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.LogOper{}). // on the response + Returns(200, "OK", entity.LogOper{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.DELETE("/{operId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除操作日志信息").Handle(s.DeleteOperLog) + }). + Doc("删除操作日志信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("operId", "多id 1,2,3").DataType("string"))) + + ws.Route(ws.DELETE("/all").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("清空操作日志信息").Handle(s.DeleteAll) + }). + Doc("清空操作日志信息"). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + container.Add(ws) +} diff --git a/apps/log/services/log_login.go b/apps/log/services/log_login.go new file mode 100644 index 0000000..474a5df --- /dev/null +++ b/apps/log/services/log_login.go @@ -0,0 +1,76 @@ +package services + +import ( + "pandax/apps/log/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + LogLoginModel interface { + Insert(data entity.LogLogin) *entity.LogLogin + FindOne(infoId int64) *entity.LogLogin + FindListPage(page, pageSize int, data entity.LogLogin) (*[]entity.LogLogin, int64) + Update(data entity.LogLogin) *entity.LogLogin + Delete(infoId []int64) + DeleteAll() + } + + logLoginModelImpl struct { + table string + } +) + +var LogLoginModelDao LogLoginModel = &logLoginModelImpl{ + table: `log_logins`, +} + +func (m *logLoginModelImpl) Insert(data entity.LogLogin) *entity.LogLogin { + global.Db.Table(m.table).Create(&data) + return &data +} + +func (m *logLoginModelImpl) FindOne(infoId int64) *entity.LogLogin { + resData := new(entity.LogLogin) + err := global.Db.Table(m.table).Where("info_id = ?", infoId).First(resData).Error + biz.ErrIsNil(err, "查询登录日志信息失败") + return resData +} + +func (m *logLoginModelImpl) FindListPage(page, pageSize int, data entity.LogLogin) (*[]entity.LogLogin, int64) { + list := make([]entity.LogLogin, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + if data.LoginLocation != "" { + db = db.Where("login_location like ?", "%"+data.LoginLocation+"%") + } + if data.Username != "" { + db = db.Where("username like ?", "%"+data.Username+"%") + } + err := db.Where("delete_time IS NULL").Count(&total).Error + err = db.Order("info_id desc").Limit(pageSize).Offset(offset).Find(&list).Error + + biz.ErrIsNil(err, "查询登录分页日志信息失败") + return &list, total +} + +func (m *logLoginModelImpl) Update(data entity.LogLogin) *entity.LogLogin { + err := global.Db.Table(m.table).Updates(&data).Error + biz.ErrIsNil(err, "修改登录日志信息失败") + return &data +} + +func (m *logLoginModelImpl) Delete(infoIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.LogLogin{}, "info_id in (?)", infoIds).Error + biz.ErrIsNil(err, "删除登录日志信息失败") + return +} + +func (m *logLoginModelImpl) DeleteAll() { + global.Db.Exec("DELETE FROM log_logins") +} diff --git a/apps/log/services/log_oper.go b/apps/log/services/log_oper.go new file mode 100644 index 0000000..33d8938 --- /dev/null +++ b/apps/log/services/log_oper.go @@ -0,0 +1,72 @@ +package services + +import ( + "pandax/apps/log/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + LogOperModel interface { + Insert(data entity.LogOper) *entity.LogOper + FindOne(infoId int64) *entity.LogOper + FindListPage(page, pageSize int, data entity.LogOper) (*[]entity.LogOper, int64) + Delete(infoId []int64) + DeleteAll() + } + + logLogOperModelImpl struct { + table string + } +) + +var LogOperModelDao LogOperModel = &logLogOperModelImpl{ + table: `log_opers`, +} + +func (m *logLogOperModelImpl) Insert(data entity.LogOper) *entity.LogOper { + global.Db.Table(m.table).Create(&data) + return &data +} + +func (m *logLogOperModelImpl) FindOne(operId int64) *entity.LogOper { + resData := new(entity.LogOper) + err := global.Db.Table(m.table).Where("oper_id = ?", operId).First(resData).Error + biz.ErrIsNil(err, "查询操作日志信息失败") + return resData +} + +func (m *logLogOperModelImpl) FindListPage(page, pageSize int, data entity.LogOper) (*[]entity.LogOper, int64) { + list := make([]entity.LogOper, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.BusinessType != "" { + db = db.Where("business_type = ?", data.BusinessType) + } + if data.OperLocation != "" { + db = db.Where("oper_location like ?", "%"+data.OperLocation+"%") + } + if data.Title != "" { + db = db.Where("title like ?", "%"+data.Title+"%") + } + if data.OperName != "" { + db = db.Where("oper_name like ?", "%"+data.OperName+"%") + } + err := db.Where("delete_time IS NULL").Count(&total).Error + err = db.Order("create_time desc").Limit(pageSize).Offset(offset).Find(&list).Error + + biz.ErrIsNil(err, "查询操作分页日志信息失败") + return &list, total +} + +func (m *logLogOperModelImpl) Delete(operIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.LogOper{}, "oper_id in (?)", operIds).Error + biz.ErrIsNil(err, "删除操作日志信息失败") + return +} + +func (m *logLogOperModelImpl) DeleteAll() { + global.Db.Exec("DELETE FROM log_opers") +} diff --git a/apps/script/call.go b/apps/script/call.go new file mode 100644 index 0000000..d49d740 --- /dev/null +++ b/apps/script/call.go @@ -0,0 +1,1066 @@ +package script + +import ( + "context" + "fmt" + "io" + "net/http" + "os" + "path/filepath" + "strings" + "sync" + "sync/atomic" + "time" + + "pandax/pkg/xscript/engine" +) + +func responeCall(vm *engine.Runtime, wr http.ResponseWriter, r *http.Request, _ string) { + payload := extractRequestInfo(vm, r) + vm.Set("runtime", map[string]any{ + "version": "xScript Enging for xMagic 1.15.1.0", + "call": func(call engine.FunctionCall) engine.Value { + var args []string + for _, arg := range call.Arguments[1:] { + args = append(args, arg.String()) + } + return executeScript(false, wr, r, call.Argument(0).String(), args) + }, + "exec": func(call engine.FunctionCall) engine.Value { + var args []string + for _, arg := range call.Arguments[1:] { + args = append(args, arg.String()) + } + return executeScript(true, wr, r, call.Argument(0).String(), args) + }, + "thread": map[string]any{ + "start": func(call engine.FunctionCall) engine.Value { + scriptPath := call.Argument(0).String() + payload.Lock() + defer payload.Unlock() + ctx, _ := context.WithCancel(context.Background()) + threadPool = fmt.Sprintf("%s;", threadPool) + var args []string + for _, arg := range call.Arguments[1:] { + args = append(args, arg.String()) + } + go func() { + defer strings.ReplaceAll(threadPool, fmt.Sprintf("%s;", scriptPath), "") + for { + select { + case <-ctx.Done(): + fmt.Println("线程执行完成 -->", scriptPath) + return + default: + executeScript(true, wr, r, scriptPath, args) + return + } + } + }() + return vm.ToValue(scriptPath) + }, + "startWithSecret": func(call engine.FunctionCall) engine.Value { + scriptPath := call.Argument(0).String() + secret := call.Argument(1).String() + if secret == "" { + secret = payload.Header["Signet"][0] + } + payload.Lock() + defer payload.Unlock() + ctx, _ := context.WithCancel(context.Background()) + threadPool = fmt.Sprintf("%s;", threadPool) + var args []string + for _, arg := range call.Arguments[2:] { + args = append(args, arg.String()) + } + go func() { + defer strings.ReplaceAll(threadPool, fmt.Sprintf("%s;", scriptPath), "") + for { + select { + case <-ctx.Done(): + fmt.Println("线程执行完成 -->", scriptPath) + return + default: + executeScriptWithSecret(true, wr, r, scriptPath, secret, args) + return + } + } + }() + return vm.ToValue(scriptPath) + }, + // "stop": func(call engine.FunctionCall) engine.Value { + // tid := call.Argument(0).String() + // payload.Lock() + // defer payload.Unlock() + // if cancel, ok := threadPool.Load(tid); ok { + // threadPool.Delete(tid) + // cancel.(context.CancelFunc)() + // } + // return vm.ToValue(true) + // }, + // "stopAll": func(call engine.FunctionCall) engine.Value { + // payload.Lock() + // defer payload.Unlock() + // threadPool.Range( + // func(key, value any) bool { + // threadPool.Delete(key) + // value.(context.CancelFunc)() + // return true + // }, + // ) + // return vm.ToValue(true) + // }, + "list": func() engine.Value { + return vm.ToValue(strings.Split(threadPool, ";")) + }, + "match": func(call engine.FunctionCall) engine.Value { + reference := call.Argument(0).String() + total := len(call.Arguments) + if total < 2 { + fmt.Println("至少需要传入两个调用进行结果对比") + return vm.ToValue(false) + } + + resultChan := make(chan engine.Value, total-1) + done := make(chan bool) + + // 使用协程池控制并发数量 + // pool := make(chan struct{}, 10) // 假设最多同时执行10个goroutine + + // 计数器,用于跟踪已完成的goroutine数量 + var counter int32 + + for i := 1; i < total; i++ { + // pool <- struct{}{} // 协程池中获取一个空闲的位置 + atomic.AddInt32(&counter, 1) // 增加计数器 + go func(script string) { + defer func() { + // <-pool // 执行完毕后释放协程池的位置 + if atomic.AddInt32(&counter, -1) == 0 { + close(done) // 所有goroutine完成后关闭done通道 + } + }() + r := executeScript(true, wr, r, script, nil) + if strings.Contains(r.String(), reference) { + select { + case resultChan <- r: + default: + } + } + }(call.Argument(i).String()) + } + + go func() { + <-done // 等待所有goroutine完成 + close(resultChan) + }() + + select { + case <-done: + return vm.ToValue(true) + case <-time.After(3 * time.Second): + close(resultChan) + return engine.Null() + } + }, + "getOne": func(call engine.FunctionCall) engine.Value { + total := len(call.Arguments) + if total < 2 { + fmt.Println("至少需要传入两个调用进行结果取最快返回") + return vm.ToValue(false) + } + + resultChan := make(chan engine.Value) + done := make(chan bool) + + for i := 1; i < total; i++ { + go func(script string) { + r := executeScript(true, wr, r, script, nil) + if r.String() != "" { + select { + case resultChan <- r: + done <- true + default: + } + } + }(call.Argument(i).String()) + } + + go func() { + for i := 1; i < total; i++ { + <-done + } + close(resultChan) + }() + + select { + case result := <-resultChan: + return result + case <-time.After(10 * time.Second): + close(resultChan) + return engine.Null() + } + }, + "getAll": func(call engine.FunctionCall) engine.Value { + total := len(call.Arguments) + if total < 2 { + fmt.Println("至少需要传入两个调用以获取全部结果,如果只调用一个请使用call方式更高效") + return vm.ToValue(false) + } + resultChan := make(chan engine.Value, total) + var wg sync.WaitGroup + for i := range call.Arguments { + wg.Add(1) + go func(path string) { + defer wg.Done() + result := executeScript(true, wr, r, path, nil) + if result.String() != "" { + resultChan <- result + } + }(call.Argument(i).String()) + } + wg.Wait() + close(resultChan) + var results []string + for res := range resultChan { + results = append(results, res.String()) + } + return vm.ToValue(results) + }, + }, + "cache": map[string]any{ + "list": func(call engine.FunctionCall) engine.Value { + return vm.ToValue(scriptCache.List()) + }, + "remove": func(call engine.FunctionCall) engine.Value { + scriptPath := call.Argument(0).String() + if scriptPath != "" { + scriptCache.Remove(scriptPath) + } + return vm.ToValue(true) + }, + "purge": func(call engine.FunctionCall) engine.Value { + scriptCache.Purge() + return vm.ToValue(true) + }, + }, + }) + vm.Set("payload", map[string]any{ + "get": func() engine.Value { + return vm.ToValue(payload) + }, + "toCURL": func() engine.Value { + var cmdBuilder strings.Builder + if payload.Method == "" { + payload.Method = "GET" + } + cmdBuilder.WriteString(fmt.Sprintf("curl -L -X %s '%s%s'", payload.Method, payload.Host, payload.URL)) + for key, values := range payload.Header { + if key == "Accept-Encoding" { + continue + } + value := fmt.Sprintf("%s", values) + cmdBuilder.WriteString(fmt.Sprintf(" -H '%s: %s'", key, value[1:len(value)-1])) + } + if payload.Body != nil { + cmdBuilder.WriteString(" -d '" + payload.Body.String() + "'") + } + return vm.ToValue(cmdBuilder.String()) + }, + "set": map[string]any{ + "header": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1).String() + if key != "" && value != "" { + payload.Lock() + defer payload.Unlock() + r.Header.Set(key, value) + } + return vm.ToValue(true) + }, + "cookie": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1).String() + if key != "" && value != "" { + payload.Lock() + defer payload.Unlock() + http.SetCookie(wr, &http.Cookie{ + Name: key, + Value: value, + }) + } + return vm.ToValue(true) + }, + "body": func(call engine.FunctionCall) engine.Value { + value := call.Argument(0).String() + if value != "" { + payload.Lock() + defer payload.Unlock() + r.Body = io.NopCloser(strings.NewReader(value)) + } + return vm.ToValue(true) + }, + }, + }) + vm.Set("response", map[string]any{ + "headers": map[string]any{ + "get": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + if key != "" && wr != nil { + return vm.ToValue(wr.Header().Get(key)) + } + return vm.ToValue(nil) + }, + "set": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1).String() + if key != "" && value != "" && wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set(key, value) + } + return vm.ToValue(true) + }, + "add": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1).String() + if key != "" && value != "" && wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Add(key, value) + } + return vm.ToValue(true) + }, + "del": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + if key != "" && wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Del(key) + } + return vm.ToValue(true) + }, + }, + "contentType": map[string]any{ + "html": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "text/html; charset=utf-8") + } + return vm.ToValue(true) + }, + "json": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "application/json; charset=utf-8") + } + return vm.ToValue(true) + }, + "xml": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "application/xml; charset=utf-8") + } + return vm.ToValue(true) + }, + "plain": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "text/plain; charset=utf-8") + } + return vm.ToValue(true) + }, + "javascript": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "application/javascript; charset=utf-8") + } + return vm.ToValue(true) + }, + "css": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "text/css; charset=utf-8") + } + return vm.ToValue(true) + }, + "gif": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "image/gif") + } + return vm.ToValue(true) + }, + "jpeg": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "image/jpeg") + } + return vm.ToValue(true) + }, + "png": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "image/png") + } + return vm.ToValue(true) + }, + "svg": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "image/svg+xml") + } + return vm.ToValue(true) + }, + "webp": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "image/webp") + } + return vm.ToValue(true) + }, + "ico": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Set("Content-Type", "image/x-icon") + } + return vm.ToValue(true) + }, + }, + "cookies": map[string]any{ + "get": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + if key != "" && wr != nil { + cookie, err := r.Cookie(key) + if err == nil { + return vm.ToValue(cookie.Value) + } + } + return vm.ToValue(nil) + }, + "set": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1).String() + if key != "" && value != "" && wr != nil { + payload.Lock() + defer payload.Unlock() + http.SetCookie(wr, &http.Cookie{ + Name: key, + Value: value, + }) + } + return vm.ToValue(true) + }, + "setRaw": func(call engine.FunctionCall) engine.Value { + input := call.Argument(0).String() + if input != "" && wr != nil { + payload.Lock() + defer payload.Unlock() + http.SetCookie(wr, &http.Cookie{ + Raw: input, + }) + } + return vm.ToValue(true) + }, + "del": func(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + if key != "" && wr != nil { + payload.Lock() + defer payload.Unlock() + http.SetCookie(wr, &http.Cookie{ + Name: key, + MaxAge: -1, + }) + } + return vm.ToValue(true) + }, + }, + "status": map[string]any{ + "code": func(call engine.FunctionCall) engine.Value { + code := call.Argument(0).ToInteger() + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(int(code)) + } + return vm.ToValue(code) + }, + "continue": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusContinue) + } + return vm.ToValue(http.StatusContinue) + }, + "switchingProtocols": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusSwitchingProtocols) + } + return vm.ToValue(http.StatusSwitchingProtocols) + }, + "processing": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusProcessing) + } + return vm.ToValue(http.StatusProcessing) + }, + "earlyHints": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusEarlyHints) + } + return vm.ToValue(http.StatusEarlyHints) + }, + "ok": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusOK) + } + return vm.ToValue(http.StatusOK) + }, + "created": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusCreated) + } + return vm.ToValue(http.StatusCreated) + }, + "accepted": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusAccepted) + } + return vm.ToValue(http.StatusAccepted) + }, + "nonAuthoritativeInfo": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNonAuthoritativeInfo) + } + return vm.ToValue(http.StatusNonAuthoritativeInfo) + }, + "noContent": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNoContent) + } + return vm.ToValue(http.StatusNoContent) + }, + "resetContent": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusResetContent) + } + return vm.ToValue(http.StatusResetContent) + }, + "partialContent": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusPartialContent) + } + return vm.ToValue(http.StatusPartialContent) + }, + "multiStatus": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusMultiStatus) + } + return vm.ToValue(http.StatusMultiStatus) + }, + "alreadyReported": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusAlreadyReported) + } + return vm.ToValue(http.StatusAlreadyReported) + }, + "imUsed": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusIMUsed) + } + return vm.ToValue(http.StatusIMUsed) + }, + "movedPermanently": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusMovedPermanently) + } + return vm.ToValue(http.StatusMovedPermanently) + }, + "found": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusFound) + } + return vm.ToValue(http.StatusFound) + }, + "seeOther": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusSeeOther) + } + return vm.ToValue(http.StatusSeeOther) + }, + "notModified": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNotModified) + } + return vm.ToValue(http.StatusNotModified) + }, + "useProxy": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusUseProxy) + } + return vm.ToValue(http.StatusUseProxy) + }, + "temporaryRedirect": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusTemporaryRedirect) + } + return vm.ToValue(http.StatusTemporaryRedirect) + }, + "permanentRedirect": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusPermanentRedirect) + } + return vm.ToValue(http.StatusPermanentRedirect) + }, + "badRequest": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusBadRequest) + } + return vm.ToValue(http.StatusBadRequest) + }, + "unauthorized": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusUnauthorized) + } + return vm.ToValue(http.StatusUnauthorized) + }, + "paymentRequired": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusPaymentRequired) + } + return vm.ToValue(http.StatusPaymentRequired) + }, + "forbidden": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusForbidden) + } + return vm.ToValue(http.StatusForbidden) + }, + "notFound": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNotFound) + } + return vm.ToValue(http.StatusNotFound) + }, + "methodNotAllowed": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusMethodNotAllowed) + } + return vm.ToValue(http.StatusMethodNotAllowed) + }, + "notAcceptable": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNotAcceptable) + } + return vm.ToValue(http.StatusNotAcceptable) + }, + "proxyAuthenticationRequired": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusProxyAuthRequired) + } + return vm.ToValue(http.StatusProxyAuthRequired) + }, + "requestTimeout": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusRequestTimeout) + } + return vm.ToValue(http.StatusRequestTimeout) + }, + "conflict": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusConflict) + } + return vm.ToValue(http.StatusConflict) + }, + "gone": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusGone) + } + return vm.ToValue(http.StatusGone) + }, + "lengthRequired": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusLengthRequired) + } + return vm.ToValue(http.StatusLengthRequired) + }, + "preconditionFailed": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusPreconditionFailed) + } + return vm.ToValue(http.StatusPreconditionFailed) + }, + "requestEntityTooLarge": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusRequestEntityTooLarge) + } + return vm.ToValue(http.StatusRequestEntityTooLarge) + }, + "requestURITooLong": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusRequestURITooLong) + } + return vm.ToValue(http.StatusRequestURITooLong) + }, + "unsupportedMediaType": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusUnsupportedMediaType) + } + return vm.ToValue(http.StatusUnsupportedMediaType) + }, + "requestedRangeNotSatisfiable": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusRequestedRangeNotSatisfiable) + } + return vm.ToValue(http.StatusRequestedRangeNotSatisfiable) + }, + "expectationFailed": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusExpectationFailed) + } + return vm.ToValue(http.StatusExpectationFailed) + }, + "teapot": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusTeapot) + } + return vm.ToValue(http.StatusTeapot) + }, + "misdirectedRequest": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusMisdirectedRequest) + } + return vm.ToValue(http.StatusMisdirectedRequest) + }, + "unprocessableEntity": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusUnprocessableEntity) + } + return vm.ToValue(http.StatusUnprocessableEntity) + }, + "locked": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusLocked) + } + return vm.ToValue(http.StatusLocked) + }, + "failedDependency": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusFailedDependency) + } + return vm.ToValue(http.StatusFailedDependency) + }, + "tooEarly": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusTooEarly) + } + return vm.ToValue(http.StatusTooEarly) + }, + "upgradeRequired": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusUpgradeRequired) + } + return vm.ToValue(http.StatusUpgradeRequired) + }, + "preconditionRequired": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusPreconditionRequired) + } + return vm.ToValue(http.StatusPreconditionRequired) + }, + "tooManyRequests": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusTooManyRequests) + } + return vm.ToValue(http.StatusTooManyRequests) + }, + "requestHeaderFieldsTooLarge": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusRequestHeaderFieldsTooLarge) + } + return vm.ToValue(http.StatusRequestHeaderFieldsTooLarge) + }, + "unavailableForLegalReasons": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusUnavailableForLegalReasons) + } + return vm.ToValue(http.StatusUnavailableForLegalReasons) + }, + "internalServerError": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusInternalServerError) + } + return vm.ToValue(http.StatusInternalServerError) + }, + "notImplemented": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNotImplemented) + } + return vm.ToValue(http.StatusNotImplemented) + }, + "badGateway": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusBadGateway) + } + return vm.ToValue(http.StatusBadGateway) + }, + "serviceUnavailable": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusServiceUnavailable) + } + return vm.ToValue(http.StatusServiceUnavailable) + }, + "gatewayTimeout": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusGatewayTimeout) + } + return vm.ToValue(http.StatusGatewayTimeout) + }, + "httpVersionNotSupported": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusHTTPVersionNotSupported) + } + return vm.ToValue(http.StatusHTTPVersionNotSupported) + }, + "variantAlsoNegotiates": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusVariantAlsoNegotiates) + } + return vm.ToValue(http.StatusVariantAlsoNegotiates) + }, + "insufficientStorage": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusInsufficientStorage) + } + return vm.ToValue(http.StatusInsufficientStorage) + }, + "loopDetected": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusLoopDetected) + } + return vm.ToValue(http.StatusLoopDetected) + }, + "notExtended": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNotExtended) + } + return vm.ToValue(http.StatusNotExtended) + }, + "networkAuthenticationRequired": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNetworkAuthenticationRequired) + } + return vm.ToValue(http.StatusNetworkAuthenticationRequired) + }, + }, + "cache": map[string]any{ + "check": func(call engine.FunctionCall) engine.Value { + content, ok := call.Argument(0).Export().([]byte) + if !ok { + if str, ok := call.Argument(0).Export().(string); ok { + content = []byte(str) + } else { + return vm.ToValue(false) + } + } + if checkChace(wr, content) { + return vm.ToValue(true) + } + return vm.ToValue(false) + }, + "clear": func() engine.Value { + if wr != nil { + payload.Lock() + defer payload.Unlock() + wr.Header().Add("Clear-Site-Data", "cache, cookies, storage, executionContexts") + } + return vm.ToValue(true) + }, + }, + "write": func(call engine.FunctionCall) engine.Value { + value := call.Argument(0) + fmt.Fprint(wr, value) + return vm.ToValue(true) + }, + "upload": func(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + if path == "" || path == "null" || path == "undefined" { + path = filepath.Join(rootPath, r.URL.Path) + } + + if wr == nil { + return vm.ToValue(false) + } + fileName, fileType, fileSize := uploadFile(wr, r, path) + wr.Write([]byte(fmt.Sprintf("event: done\ndata: {id: %s, name: %s, type: %s, size: %d}\n\n", filepath.Base(path), fileName, fileType, fileSize))) + res := map[string]any{ + "name": fileName, + "type": fileType, + "size": fileSize, + "path": path, + } + return vm.ToValue(res) + }, + "file": func(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + if path == "" { + path = filepath.Join(rootPath, r.URL.Path) + } + + if wr == nil { + return vm.ToValue(false) + } + + _, err := os.Stat(path) + if os.IsNotExist(err) && wr != nil { + payload.Lock() + defer payload.Unlock() + wr.WriteHeader(http.StatusNotFound) + + return vm.ToValue(false) + } + + if wr != nil { + payload.Lock() + defer payload.Unlock() + // metaData := []byte(fmt.Sprintf("name=%s,size=%d,time=%v", fileInfo.Name(), fileInfo.Size(), fileInfo.ModTime())) + // checkChace(wr, metaData) + http.ServeFile(wr, r, path) + } + return vm.ToValue(true) + }, + }) +} diff --git a/apps/script/checkChace.go b/apps/script/checkChace.go new file mode 100644 index 0000000..2672b64 --- /dev/null +++ b/apps/script/checkChace.go @@ -0,0 +1,46 @@ +package script + +import ( + "crypto/sha1" + "fmt" + "net/http" + _ "net/http/pprof" + "os" + "time" +) + +var threadPool string +var rootPath, _ = os.Getwd() + +// func init() { +// rootPath, _ = os.Executable() +// rootPath = filepath.Dir(rootPath) +// } + +func checkChace(wr http.ResponseWriter, data []byte) bool { + if wr == nil { + return false + } + hash := sha1.New() + hash.Write(data) + hashString := fmt.Sprintf("\"%x\"", hash.Sum(nil)) + wr.Header().Set("Date", time.Now().Format(time.RFC1123)) + wr.Header().Set("ETag", hashString) + + if match := wr.Header().Get("If-Match"); match != "" { + wr.Header().Del("If-Match") + if match != hashString { + wr.WriteHeader(http.StatusPreconditionFailed) + return true + } + } + + if match := wr.Header().Get("If-None-Match"); match != "" { + wr.Header().Del("If-None-Match") + if match == hashString { + wr.WriteHeader(http.StatusNotModified) + return true + } + } + return false +} diff --git a/apps/script/gzip.go b/apps/script/gzip.go new file mode 100644 index 0000000..82d25ad --- /dev/null +++ b/apps/script/gzip.go @@ -0,0 +1,21 @@ +package script + +import ( + "mime" + "path/filepath" +) + +func GetMimeType(filename string) string { + extension := filepath.Ext(filename) + mimeType := mime.TypeByExtension(extension) + return mimeType +} + +// type gzipResponseWriter struct { +// http.ResponseWriter +// io.Writer +// } + +// func (w *gzipResponseWriter) Write(data []byte) (int, error) { +// return w.Writer.Write(data) +// } diff --git a/apps/script/handle.go b/apps/script/handle.go new file mode 100644 index 0000000..1359148 --- /dev/null +++ b/apps/script/handle.go @@ -0,0 +1,74 @@ +package script + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "time" + + "pandax/kit/restfulx" +) + +type ScriptApi struct{} + +func (up *ScriptApi) Call(rc *restfulx.ReqCtx) { + rc.Response.Header().Set("Alt-Svc", "h2=:7788; ma=2592000; v=\"46,43,39,35\";") + rc.Response.Header().Set("Accept-Ch", "Sec-CH-UA-Arch, Sec-CH-UA-Bitness, Sec-CH-UA-Full-Version-List, Sec-CH-UA-Mobile, Sec-CH-UA-Model, Sec-CH-UA-Platform, Sec-CH-UA-Platform-Version, DPR, Viewport-Width, Width, ETag, If-Match, If-None-Match") + rc.Response.Header().Set("Access-Control-Max-Age", "31536000") + rc.Response.Header().Set("Cache-Control", "no-transform,no-siteapp,max-age=31536000") + + rc.Response.Header().Set("Date", time.Now().Format(time.RFC1123)) + rc.Response.Header().Set("X-Request-Receive-Date", time.Now().Format(time.RFC1123)) + rc.Response.Header().Set("X-Content-Type-Options", "nosniff") + + rc.Response.Header().Set("Save-Data", "off") + rc.Response.Header().Set("Vary", "ETag") + rc.Response.Header().Set("X-Runtime-Info", "xScript Enging for xMagic 1.15.1.0") + // 判断是否支持gzip压缩 + if strings.Contains(rc.Request.Request.Header.Get("Accept-Encoding"), "gzip") { + rc.Response.Header().Set("Content-Encoding", "gzip") + } + + if clear := strings.Contains(rc.Request.Request.Header.Get("Cache-Control"), "clear"); clear { + rc.Response.Header().Add("Clear-Site-Data", "cache, cookies, storage, executionContexts") + } + + if cacheMatch := rc.Request.Request.Header.Get("If-Match"); cacheMatch != "" { + rc.Response.Header().Set("If-Match", cacheMatch) + } + if cacheMatch := rc.Request.Request.Header.Get("If-None-Match"); cacheMatch != "" { + rc.Response.Header().Set("If-None-Match", cacheMatch) + } + + path := filepath.Join(rootPath, rc.Request.Request.URL.Path) + + // 默认的 Content-Type + rc.Response.Header().Set("Content-Type", GetMimeType(rc.Request.Request.URL.Path)) + if rc.Response.Header().Get("Content-Type") == "" { + rc.Response.Header().Set("Content-Type", "application/json;charset=utf-8") + } + + // 如果path末尾被浏览器追加了#,则去掉 + path = strings.TrimSuffix(path, "#") + + info, err := os.Stat(path) + if err == nil { + if info.IsDir() { + if !strings.HasSuffix(path, "/") { + path += "/" + } + } + } + + // 判断s.Path下是否存在文件core.js, 如果存在则执行core.js + coreFilePath := filepath.Join(rootPath, "core.js") + if _, err := os.Stat(coreFilePath); err == nil { + var userInfo []string + if rc.LoginAccount != nil { + userInfo = []string{fmt.Sprintf("%d", rc.LoginAccount.UserId), rc.LoginAccount.UserName} + } + rc.ResData = executeScript(false, rc.Response, rc.Request.Request, coreFilePath, userInfo).String() + return + } +} diff --git a/apps/script/put.go b/apps/script/put.go new file mode 100644 index 0000000..605d28c --- /dev/null +++ b/apps/script/put.go @@ -0,0 +1,103 @@ +package script + +import ( + "bufio" + "fmt" + "io" + "math" + "net/http" + "os" + "path/filepath" +) + +func uploadFile(wr http.ResponseWriter, r *http.Request, path string) (string, string, int64) { + file, fh, err := r.FormFile("file") + if err != nil { + wr.WriteHeader(http.StatusBadRequest) + // l.Echo().WithError(err).Warn("Failed to get file from request") + fmt.Println("无法从请求中获取文件", err) + return "", "", 0 + + } + defer file.Close() + + fileName, fileType, fileSize := fh.Filename, fh.Header.Get("Content-Type"), fh.Size + + cachePath := path + ".cache" + + err = os.MkdirAll(filepath.Dir(path), os.ModePerm) + if err != nil { + wr.WriteHeader(http.StatusInternalServerError) + // l.Echo().WithError(err).Warn("Failed to create directory") + fmt.Println("创建目录失败", err) + return "", "", 0 + } + + f, err := os.OpenFile(cachePath, os.O_WRONLY|os.O_CREATE, 0666) + if err != nil { + wr.WriteHeader(http.StatusInternalServerError) + // l.Echo().WithError(err).Warn("Failed to create file") + fmt.Println("无法创建文件", err) + return "", "", 0 + } + defer f.Close() + + var bufSize int64 + switch { + case fileSize < 10*1024*1024: + bufSize = 100 * 1024 + case fileSize < 100*1024*1024: + bufSize = 1024 * 1024 + case fileSize < 500*1024*1024: + bufSize = 50 * 1024 * 1024 + case fileSize >= 500*1024*1024: + bufSize = 100 * 1024 * 1024 + } + + _, err = file.Seek(0, io.SeekStart) + if err != nil { + wr.WriteHeader(http.StatusInternalServerError) + fmt.Println("无法操作文件游标", err) + return "", "", 0 + } + + wr.Header().Set("Content-Type", "text/event-stream") + + buf := make([]byte, bufSize) + reader := bufio.NewReader(file) + writer := bufio.NewWriter(f) + + totalChunks := int(math.Ceil(float64(fileSize) / float64(bufSize))) + for i := 0; i < totalChunks; i++ { + n, err := reader.Read(buf) + if err != nil && err != io.EOF { + wr.WriteHeader(http.StatusInternalServerError) + fmt.Println("读取文件失败", err) + return "", "", 0 + } + + _, err = writer.Write(buf[:n]) + if err != nil { + wr.WriteHeader(http.StatusInternalServerError) + fmt.Println("写出文件失败", err) + return "", "", 0 + } + + progress := float64(i+1) / float64(totalChunks) * 100 + wr.Write([]byte(fmt.Sprintf("event: progress\ndata: %.2f\n\n", progress))) + wr.(http.Flusher).Flush() // 实时返回数据 + } + + writer.Flush() + + os.Remove(path) + + err = os.Rename(cachePath, path) + if err != nil { + wr.WriteHeader(http.StatusInternalServerError) + fmt.Println("重命名文件失败", err) + return "", "", 0 + } + + return fileName, fileType, fileSize +} diff --git a/apps/script/request.go b/apps/script/request.go new file mode 100644 index 0000000..e27e622 --- /dev/null +++ b/apps/script/request.go @@ -0,0 +1,189 @@ +package script + +import ( + "bytes" + "fmt" + "io" + "net/http" + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" + "strings" + "sync" +) + +type RequestInfo struct { + sync.Mutex + // Method specifies the HTTP method (GET, POST, PUT, etc.). + // For client requests, an empty string means GET. + // + // Go's HTTP client does not support sending a request with + // the CONNECT method. See the documentation on Transport for + // details. + Method string `json:"method"` + + // URL specifies either the URI being requested (for server + // requests) or the URL to access (for client requests). + // + // For server requests, the URL is parsed from the URI + // supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be + // empty. (See RFC 7230, Section 5.3) + // + // For client requests, the URL's Host specifies the server to + // connect to, while the Request's Host field optionally + // specifies the Host header value to send in the HTTP + // request. + URL string `json:"url"` + + // The path portion of the request URL. + Path string `json:"path"` + + // The protocol version for incoming server requests. + // + // For client requests, these fields are ignored. The HTTP + // client code always uses either HTTP/1.1 or HTTP/2. + // See the docs on Transport for details. + Proto string `json:"proto"` // "HTTP/1.0" + ProtoMajor int `json:"proto_major"` // 1 + ProtoMinor int `json:"proro_minor"` // 0 + + // Header contains the request header fields either received + // by the server or to be sent by the client. + // + // If a server received a request with header lines, + // + // Host: example.com + // accept-encoding: gzip, deflate + // Accept-Language: en-us + // fOO: Bar + // foo: two + // + // then + // + // Header = map[string][]string{ + // "Accept-Encoding": {"gzip, deflate"}, + // "Accept-Language": {"en-us"}, + // "Foo": {"Bar", "two"}, + // } + // + // For incoming requests, the Host header is promoted to the + // Request.Host field and removed from the Header map. + // + // HTTP defines that header names are case-insensitive. The + // request parser implements this by using CanonicalHeaderKey, + // making the first character and any characters following a + // hyphen uppercase and the rest lowercase. + // + // For client requests, certain headers such as Content-Length + // and Connection are automatically written when needed and + // values in Header may be ignored. See the documentation + // for the Request.Write method. + Header map[string][]string `json:"headers"` + + // Body is the request's body. + // + // For client requests, a nil body means the request has no + // body, such as a GET request. + + Body engine.Value `json:"body"` + + // ContentLength records the length of the associated content. + // The value -1 indicates that the length is unknown. + // Values >= 0 indicate that the given number of bytes may + // be read from Body. + // + // For client requests, a value of 0 with a non-nil Body is + // also treated as unknown. + ContentLength int64 `json:"content_length"` + + // For server requests, Host specifies the host on which the + // URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this + // is either the value of the "Host" header or the host name + // given in the URL itself. For HTTP/2, it is the value of the + // ":authority" pseudo-header field. + // It may be of the form "host:port". For international domain + // names, Host may be in Punycode or Unicode form. Use + // golang.org/x/net/idna to convert it to either format if + // needed. + // To prevent DNS rebinding attacks, server Handlers should + // validate that the Host header has a value for which the + // Handler considers itself authoritative. The included + // ServeMux supports patterns registered to particular host + // names and thus protects its registered Handlers. + // + // For client requests, Host optionally overrides the Host + // header to send. If empty, the Request.Write method uses + // the value of URL.Host. Host may contain an international + // domain name. + Host string `json:"host"` + + // Form contains the parsed form data, including both the URL + // field's query parameters and the PATCH, POST, or PUT form data. + // This field is only available after ParseForm is called. + // The HTTP client ignores Form and uses Body instead. + Form map[string][]string `json:"form"` + Files map[string][]string `json:"files"` + + Query map[string][]string `json:"query"` + + // RemoteAddr allows HTTP servers and other software to record + // the network address that sent the request, usually for + // logging. This field is not filled in by ReadRequest and + // has no defined format. The HTTP server in this package + // sets RemoteAddr to an "IP:port" address before invoking a + // handler. + // This field is ignored by the HTTP client. + RemoteAddr string `json:"remote_addr"` +} + +func extractRequestInfo(vm *engine.Runtime, r *http.Request) *RequestInfo { + // 提取请求信息的逻辑 + requestInfo := &RequestInfo{ + Method: r.Method, + URL: r.URL.String(), + Path: r.URL.Path, + Header: r.Header, + Proto: r.Proto, + ProtoMajor: r.ProtoMajor, + ProtoMinor: r.ProtoMinor, + ContentLength: r.ContentLength, + Form: make(map[string][]string), + Query: r.URL.Query(), + Host: r.Host, + RemoteAddr: r.RemoteAddr, + } + + contentType := r.Header.Get("Content-Type") + if strings.Contains(contentType, "application/x-www-form-urlencoded") && requestInfo.Body != nil { + data := requestInfo.Body.Export().([]byte) + requestInfo.Form, _ = parseFormUrlEncoded(data) + } + + // 将请求体内容拷贝到bytes.Buffer中 + bodyBuffer := new(bytes.Buffer) + if _, err := io.Copy(bodyBuffer, r.Body); err != nil { + fmt.Println("复制请求体失败,错误:", err) + return requestInfo + } + + // 将bytes.Buffer的内容赋值给RequestInfo结构体的Body字段 + data := bodyBuffer.Bytes() + requestInfo.Body = buffer.Format(vm, data) + + r.Body = io.NopCloser(bytes.NewBuffer(data)) + + return requestInfo +} + +func parseFormUrlEncoded(body []byte) (map[string][]string, error) { + formData := make(map[string][]string) + kvPairs := strings.Split(string(body), "&") + for _, kv := range kvPairs { + kv := strings.Split(kv, "=") + if len(kv) == 2 { + key, value := kv[0], kv[1] + formData[key] = append(formData[key], value) + } + } + return formData, nil +} diff --git a/apps/script/runtime.go b/apps/script/runtime.go new file mode 100644 index 0000000..e71e54c --- /dev/null +++ b/apps/script/runtime.go @@ -0,0 +1,196 @@ +package script + +import ( + "fmt" + "os" + "path" + "path/filepath" + "regexp" + "strings" + "time" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/console" + "pandax/pkg/xscript/crypto" + "pandax/pkg/xscript/database/cache" + "pandax/pkg/xscript/database/mongo" + "pandax/pkg/xscript/database/redis" + "pandax/pkg/xscript/database/sql" + "pandax/pkg/xscript/encoding" + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/extension" + "pandax/pkg/xscript/fs" + "pandax/pkg/xscript/jwt" + "pandax/pkg/xscript/net" + jos "pandax/pkg/xscript/os" + "pandax/pkg/xscript/querystring" + "pandax/pkg/xscript/request" + "pandax/pkg/xscript/require" + "pandax/pkg/xscript/zip" + + "pandax/pkg/http_client" + + jsUrl "pandax/pkg/xscript/url" +) + +func newRuntime() *engine.Runtime { + vm := engine.New() + dir, _ := os.Getwd() + vm.Set("runtimePath", dir) + + vm.SetFieldNameMapper(engine.TagFieldNameMapper("json", true)) + registry := new(require.Registry) // this can be shared by multiple runtimes + + // 定义外部库 + querystring.Enable(vm) + extension.Enable(vm) + registry.Enable(vm) + console.Enable(vm) + encoding.Enable(vm) + request.Enable(vm) + buffer.Enable(vm) + crypto.Enable(vm) + jsUrl.Enable(vm) + cache.Enable(vm) + redis.Enable(vm) + mongo.Enable(vm) + jwt.Enable(vm) + sql.Enable(vm) + net.Enable(vm) + jos.Enable(vm) + zip.Enable(vm) + fs.Enable(vm) + return vm +} + +func loadScript(filepath string) (string, error) { + scriptData, err := os.ReadFile(filepath) + if err != nil { + return "", err + } + return string(scriptData), nil +} + +func getScriptFromURL(url, jsPath string) error { + // 判断文件是否存在,如果存在取修改时间,若修改时间小于1小时则返回nil + if _, err := os.Stat(jsPath); err == nil { + fileInfo, err := os.Stat(jsPath) + if err == nil { + if time.Since(fileInfo.ModTime()) < time.Hour { + return nil + } + } + } + + _, err := http_client.DefaultClient.SetUrl(url).Do() + if err != nil { + return err + } + + http_client.DefaultClient.SaveToFile(jsPath) + + return nil +} + +func removeComments(scriptData string) string { + // 删除单行注释 + commentRegExp := regexp.MustCompile(`(^| )\/\/.*`) + scriptData = commentRegExp.ReplaceAllString(scriptData, "") + + // 删除多行注释 + multilineCommentRegExp := regexp.MustCompile(`\/\*(.|\n)*?\*\/\n`) + scriptData = multilineCommentRegExp.ReplaceAllString(scriptData, "") + + // 删除HTML注释 + commentRegex := regexp.MustCompile(``) + scriptData = commentRegex.ReplaceAllString(scriptData, "") + + return scriptData +} + +func loadAndCheckScript(jsPath, secret string) (string, error) { + // 加载脚本内容 + scriptData, err := loadScript(jsPath) + if err != nil { + return "", err + } + + jsPath = filepath.Dir(jsPath) + + // 删除注释 + scriptData = removeComments(scriptData) + + requireRegExp := regexp.MustCompile(`require\(["']([^"']+)["']\)`) + requireMatches := requireRegExp.FindAllStringSubmatch(scriptData, -1) + + jsPath += "/moudles" + + var filename string + filenameRegExp := regexp.MustCompile(`\?.*`) + for _, match := range requireMatches { + if !strings.HasPrefix(match[1], "//") { + continue + } + + // 如果jsPath不存在则创建 + if _, err := os.Stat(jsPath); os.IsNotExist(err) { + os.MkdirAll(jsPath, os.ModePerm) + } + + url := strings.Replace(match[1], "//", "", 1) + parts := strings.Split(url, "/") + filename := path.Base(parts[len(parts)-1]) + filename = filenameRegExp.ReplaceAllString(filename, "") + filename = filepath.Join(jsPath, filename) + + err = getScriptFromURL(url, filename) + if err != nil { + fmt.Printf("无法从url获取脚本: %s\n", url) + fmt.Printf("尝试使用本地缓存脚本文件: %s\n", filename) + } + + // 合并requireScript到scriptData变量中 + scriptData = strings.ReplaceAll(scriptData, match[1], filename) + } + + useRegExp := regexp.MustCompile(`(?m)(^|^\s+)'use\s+(.*?.js)'`) + useMatches := useRegExp.FindAllStringSubmatch(scriptData, -1) + + for _, match := range useMatches { + filename = match[2] + if strings.HasPrefix(filename, "//") { + // 如果jsPath不存在则创建 + if _, err := os.Stat(jsPath); os.IsNotExist(err) { + os.MkdirAll(jsPath, os.ModePerm) + } + + url := strings.Replace(filename, "//", "", 1) + parts := strings.Split(url, "/") + filename := path.Base(parts[len(parts)-1]) + filename = filenameRegExp.ReplaceAllString(filename, "") + filename = filepath.Join(jsPath, filename) + + err = getScriptFromURL(url, filename) + if err != nil { + fmt.Printf("无法从url获取脚本: %s\n", url) + fmt.Printf("尝试使用本地缓存脚本文件: %s\n", filename) + } + } + // 读取文件内容 + scriptContent, err := loadAndCheckScript(filename, secret) + if err != nil { + fmt.Printf("脚本文件读取失败: %s\n", filename) + return "", err + } + + // 合并requireScript到scriptData变量中 + scriptData = strings.ReplaceAll(scriptData, match[0], fmt.Sprintf("\n%s\n", string(scriptContent))) + } + + // 删除注释 + scriptData = removeComments(scriptData) + + // fmt.Println(fmt.Sprintf("合并后的脚本: %s\n", scriptData)) + + return scriptData, nil +} diff --git a/apps/script/script.go b/apps/script/script.go new file mode 100644 index 0000000..0fc7f1d --- /dev/null +++ b/apps/script/script.go @@ -0,0 +1,104 @@ +package script + +import ( + "crypto/rc4" + "fmt" + "net/http" + "path/filepath" + "regexp" + "strings" + "time" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/database/cache" + "pandax/pkg/xscript/engine" +) + +var scriptCache = cache.NewKVStore(1024) + +func executeScriptWithSecret(isCall bool, wr http.ResponseWriter, r *http.Request, path, secret string, args []string) engine.Value { + var err error + var functionScript, globalScript string + functionJSPath := filepath.Join(path, "function.js") + globalJSPath := filepath.Join(rootPath, "global.js") + + if strings.Contains(path, ".js") { + functionJSPath = path + } + + if secret == "" { + secret = "/__/vlan/" + } + + cipher, err := rc4.NewCipher([]byte(secret)) + if err != nil { + return nil + } + var output []byte + + if script, ok := scriptCache.Get(functionJSPath); ok { + fmt.Println("函数缓存命中:", functionJSPath) + output = script.([]byte) + plain := make([]byte, len(output)) + cipher.XORKeyStream(plain, output) + functionScript = string(output) + } else if functionScript, err = loadAndCheckScript(functionJSPath, secret); err == nil { + if globalScript, err = loadAndCheckScript(globalJSPath, secret); err == nil { + // 因为 global.js 会被其他脚本引用,因此不允许定义 main 函数或名为 main 的变量 + mainCheckRegExp := regexp.MustCompile(`^(function|var|let|const)\s+main\b`) + if mainCheckRegExp.MatchString(globalScript) { + fmt.Println("文件 global.js 中不能包含名为 main 的函数或变量定义", err) + return engine.Null() + } + functionScript = fmt.Sprintf("%s\n%s", globalScript, functionScript) + } + output = []byte(functionScript) + plain := make([]byte, len(output)) + cipher.XORKeyStream(plain, output) + scriptCache.Set(functionJSPath, output, time.Hour*24) + } else { + if wr != nil && !isCall { + wr.WriteHeader(http.StatusInternalServerError) + } + fmt.Println("调用 core.js 失败", err) + return engine.Null() + } + + vm := newRuntime() + vm.Set("args", args) + vm.Set("rootPath", rootPath) + vm.Set("scriptPath", path) + + responeCall(vm, wr, r, path) + + if wr != nil && !isCall { + wr.Header().Set("X-Request-Receive-Date", time.Now().Format(time.RFC1123)) + } + + function := r.Header.Get("X-Method") + if (function == "" || path == filepath.Join(rootPath, "core.js")) && (strings.Contains(functionScript, " main ") || strings.Contains(functionScript, " main(")) { + function = "main" + } + + functionScript = fmt.Sprintf("%s\n%s()", functionScript, function) + + result, err := vm.RunScript(path, functionScript) + if err != nil { + if wr != nil && !isCall { + wr.WriteHeader(http.StatusInternalServerError) + } + // s.l.Echo().WithError(err).Warn("Failed to execute script %s", path) + fmt.Println("函数代码语法错误", err) + return engine.Null() + } + + if !isCall { + checkChace(wr, []byte(result.String())) + fmt.Fprint(wr, result) + } + return buffer.Format(vm, result.String()) +} + +func executeScript(isCall bool, wr http.ResponseWriter, r *http.Request, path string, args []string) engine.Value { + return executeScriptWithSecret(isCall, wr, r, path, "", args) +} diff --git a/apps/system/api/api.go b/apps/system/api/api.go new file mode 100644 index 0000000..8694175 --- /dev/null +++ b/apps/system/api/api.go @@ -0,0 +1,65 @@ +package api + +import ( + entity "pandax/apps/system/entity" + services "pandax/apps/system/services" + "pandax/kit/casbin" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" + "pandax/pkg/global" +) + +type SystemApiApi struct { + ApiApp services.SysApiModel +} + +func (s *SystemApiApi) CreateApi(rc *restfulx.ReqCtx) { + var api entity.SysApi + restfulx.BindJsonAndValid(rc, &api) + s.ApiApp.Insert(api) +} + +func (s *SystemApiApi) DeleteApi(rc *restfulx.ReqCtx) { + ids := rc.Request.PathParameter("id") + s.ApiApp.Delete(utils.IdsStrToIdsIntGroup(ids)) +} + +func (s *SystemApiApi) GetApiList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + path := rc.Request.QueryParameter("path") + description := rc.Request.QueryParameter("description") + method := rc.Request.QueryParameter("method") + apiGroup := rc.Request.QueryParameter("apiGroup") + api := entity.SysApi{Path: path, Description: description, Method: method, ApiGroup: apiGroup} + list, total := s.ApiApp.FindListPage(pageNum, pageSize, api) + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +func (s *SystemApiApi) GetApiById(rc *restfulx.ReqCtx) { + id := restfulx.QueryInt(rc, "id", 0) + rc.ResData = s.ApiApp.FindOne(int64(id)) + +} + +func (s *SystemApiApi) UpdateApi(rc *restfulx.ReqCtx) { + var api entity.SysApi + restfulx.BindJsonAndValid(rc, &api) + s.ApiApp.Update(api) +} + +func (s *SystemApiApi) GetAllApis(rc *restfulx.ReqCtx) { + rc.ResData = s.ApiApp.FindList(entity.SysApi{}) +} + +func (s *SystemApiApi) GetPolicyPathByRoleId(rc *restfulx.ReqCtx) { + roleKey := rc.Request.QueryParameter("roleKey") + ca := casbin.CasbinService{ModelPath: global.Conf.Casbin.ModelPath} + rc.ResData = ca.GetPolicyPathByRoleId(roleKey) +} diff --git a/apps/system/api/config.go b/apps/system/api/config.go new file mode 100644 index 0000000..0f244cb --- /dev/null +++ b/apps/system/api/config.go @@ -0,0 +1,60 @@ +package api + +import ( + entity "pandax/apps/system/entity" + services "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" +) + +type ConfigApi struct { + ConfigApp services.SysConfigModel +} + +func (p *ConfigApi) GetConfigList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + configName := rc.Request.QueryParameter("configName") + configKey := rc.Request.QueryParameter("configKey") + configType := rc.Request.QueryParameter("configType") + config := entity.SysConfig{ConfigName: configName, ConfigKey: configKey, ConfigType: configType} + list, total := p.ConfigApp.FindListPage(pageNum, pageSize, config) + + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +func (p *ConfigApi) GetConfigListByKey(rc *restfulx.ReqCtx) { + configKey := rc.Request.QueryParameter("configKey") + biz.IsTrue(configKey != "", "请传入配置Key") + rc.ResData = p.ConfigApp.FindList(entity.SysConfig{ConfigKey: configKey}) +} + +func (p *ConfigApi) GetConfig(rc *restfulx.ReqCtx) { + id := restfulx.PathParamInt(rc, "configId") + p.ConfigApp.FindOne(int64(id)) +} + +func (p *ConfigApi) InsertConfig(rc *restfulx.ReqCtx) { + var config entity.SysConfig + restfulx.BindJsonAndValid(rc, &config) + + p.ConfigApp.Insert(config) +} + +func (p *ConfigApi) UpdateConfig(rc *restfulx.ReqCtx) { + var post entity.SysConfig + restfulx.BindJsonAndValid(rc, &post) + p.ConfigApp.Update(post) +} + +func (p *ConfigApi) DeleteConfig(rc *restfulx.ReqCtx) { + configId := rc.Request.PathParameter("configId") + p.ConfigApp.Delete(utils.IdsStrToIdsIntGroup(configId)) +} diff --git a/apps/system/api/dict.go b/apps/system/api/dict.go new file mode 100644 index 0000000..d1b5084 --- /dev/null +++ b/apps/system/api/dict.go @@ -0,0 +1,121 @@ +package api + +import ( + "fmt" + entity "pandax/apps/system/entity" + services "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" + "pandax/pkg/global" +) + +type DictApi struct { + DictType services.SysDictTypeModel + DictData services.SysDictDataModel +} + +func (p *DictApi) GetDictTypeList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + status := restfulx.QueryParam(rc, "status") + dictName := restfulx.QueryParam(rc, "dictName") + dictType := restfulx.QueryParam(rc, "dictType") + + list, total := p.DictType.FindListPage(pageNum, pageSize, entity.SysDictType{Status: status, DictName: dictName, DictType: dictType}) + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +func (p *DictApi) GetDictType(rc *restfulx.ReqCtx) { + dictId := restfulx.PathParamInt(rc, "dictId") + p.DictType.FindOne(int64(dictId)) +} + +func (p *DictApi) InsertDictType(rc *restfulx.ReqCtx) { + var dict entity.SysDictType + restfulx.BindJsonAndValid(rc, &dict) + + dict.CreateBy = rc.LoginAccount.UserName + p.DictType.Insert(dict) +} + +func (p *DictApi) UpdateDictType(rc *restfulx.ReqCtx) { + var dict entity.SysDictType + restfulx.BindJsonAndValid(rc, &dict) + + dict.CreateBy = rc.LoginAccount.UserName + p.DictType.Update(dict) +} + +func (p *DictApi) DeleteDictType(rc *restfulx.ReqCtx) { + dictId := restfulx.PathParam(rc, "dictId") + dictIds := utils.IdsStrToIdsIntGroup(dictId) + + deList := make([]int64, 0) + for _, id := range dictIds { + one := p.DictType.FindOne(id) + list := p.DictData.FindList(entity.SysDictData{DictType: one.DictType}) + if len(*list) == 0 { + deList = append(deList, id) + } else { + global.Log.Info(fmt.Sprintf("dictId: %d 存在字典数据绑定无法删除", id)) + } + } + p.DictType.Delete(deList) +} + +func (p *DictApi) ExportDictType(rc *restfulx.ReqCtx) { + filename := restfulx.QueryParam(rc, "filename") + status := restfulx.QueryParam(rc, "status") + dictName := restfulx.QueryParam(rc, "dictName") + dictType := restfulx.QueryParam(rc, "dictType") + + list := p.DictType.FindList(entity.SysDictType{Status: status, DictName: dictName, DictType: dictType}) + fileName := utils.GetFileName(global.Conf.Server.ExcelDir, filename) + utils.InterfaceToExcel(*list, fileName) + rc.Download(fileName) +} + +func (p *DictApi) GetDictDataList(rc *restfulx.ReqCtx) { + dictLabel := restfulx.QueryParam(rc, "dictLabel") + dictType := restfulx.QueryParam(rc, "dictType") + status := restfulx.QueryParam(rc, "status") + rc.ResData = p.DictData.FindList(entity.SysDictData{Status: status, DictType: dictType, DictLabel: dictLabel}) +} + +func (p *DictApi) GetDictDataListByDictType(rc *restfulx.ReqCtx) { + dictType := restfulx.QueryParam(rc, "dictType") + biz.IsTrue(dictType != "", "请传入字典类型") + rc.ResData = p.DictData.FindList(entity.SysDictData{DictType: dictType}) +} + +func (p *DictApi) GetDictData(rc *restfulx.ReqCtx) { + dictCode := restfulx.PathParamInt(rc, "dictCode") + p.DictData.FindOne(int64(dictCode)) +} + +func (p *DictApi) InsertDictData(rc *restfulx.ReqCtx) { + var data entity.SysDictData + restfulx.BindJsonAndValid(rc, &data) + data.CreateBy = rc.LoginAccount.UserName + p.DictData.Insert(data) +} + +func (p *DictApi) UpdateDictData(rc *restfulx.ReqCtx) { + var data entity.SysDictData + restfulx.BindJsonAndValid(rc, &data) + + data.CreateBy = rc.LoginAccount.UserName + p.DictData.Update(data) +} + +func (p *DictApi) DeleteDictData(rc *restfulx.ReqCtx) { + dictCode := restfulx.PathParam(rc, "dictCode") + p.DictData.Delete(utils.IdsStrToIdsIntGroup(dictCode)) +} diff --git a/apps/system/api/form/role.go b/apps/system/api/form/role.go new file mode 100644 index 0000000..7cb26bc --- /dev/null +++ b/apps/system/api/form/role.go @@ -0,0 +1,22 @@ +package form + +// 分配角色资源表单信息 +type RoleResourceForm struct { + Id int + ResourceIds string +} + +// 保存角色信息表单 +type RoleForm struct { + Id int + Status int `json:"status"` // 1:可用;-1:不可用 + Name string `binding:"required"` + Code string `binding:"required"` + Remark string `json:"remark"` +} + +// 账号分配角色表单 +type AccountRoleForm struct { + Id int `binding:"required"` + RoleIds string +} diff --git a/apps/system/api/form/user.go b/apps/system/api/form/user.go new file mode 100644 index 0000000..d8b41d4 --- /dev/null +++ b/apps/system/api/form/user.go @@ -0,0 +1,34 @@ +package form + +// User register structure +type Register struct { + Username string `json:"userName"` + Password string `json:"passWord"` + NickName string `json:"nickName" gorm:"default:'QMPlusUser'"` + HeaderImg string `json:"headerImg" gorm:"default:'http://www.henrongyi.top/avatar/lufu.jpg'"` + AuthorityId string `json:"authorityId" gorm:"default:888"` +} + +// User login structure +type Login struct { + Username string `json:"username" ` // 用户名 + Password string `json:"password"` // 密码 + Captcha string `json:"captcha"` // 验证码 + CaptchaId string `json:"captchaId"` // 验证码ID +} + +// Modify password structure +type ChangePasswordStruct struct { + Username string `json:"username"` // 用户名 + Password string `json:"password"` // 密码 + NewPassword string `json:"newPassword"` // 新密码 +} + +type UserSearch struct { + Username string `json:"username"` // 用户UUID + NickName string `json:"nickName"` // 角色ID + Status int64 `json:"status"` // 角色ID + Phone string `json:"phone"` // 角色ID + PostId int64 `json:"postId"` // 角色ID + OrganizationId int64 `json:"organizationId"` // 角色ID +} diff --git a/apps/system/api/menu.go b/apps/system/api/menu.go new file mode 100644 index 0000000..20bb20c --- /dev/null +++ b/apps/system/api/menu.go @@ -0,0 +1,98 @@ +package api + +import ( + "pandax/apps/system/api/vo" + entity "pandax/apps/system/entity" + services "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/restfulx" + "pandax/kit/utils" +) + +type MenuApi struct { + MenuApp services.SysMenuModel + OrganizationApp services.SysOrganizationModel + + RoleMenuApp services.SysRoleMenuModel + RoleApp services.SysRoleModel +} + +func (m *MenuApi) GetMenuTreeSelect(rc *restfulx.ReqCtx) { + lable := m.MenuApp.SelectMenuLable(entity.SysMenu{}) + rc.ResData = lable +} + +func (m *MenuApi) GetMenuRole(rc *restfulx.ReqCtx) { + roleKey := restfulx.QueryParam(rc, "roleKey") + biz.IsTrue(roleKey != "", "请传入角色Key") + rc.ResData = Build(*m.MenuApp.SelectMenuRole(roleKey)) +} + +func (m *MenuApi) GetMenuTreeRoleSelect(rc *restfulx.ReqCtx) { + roleId := restfulx.PathParamInt(rc, "roleId") + + result := m.MenuApp.SelectMenuLable(entity.SysMenu{}) + menuIds := make([]int64, 0) + if roleId != 0 { + menuIds = m.RoleApp.GetRoleMeunId(entity.SysRole{RoleId: int64(roleId)}) + } + rc.ResData = vo.MenuTreeVo{ + Menus: *result, + CheckedKeys: menuIds, + } +} + +func (m *MenuApi) GetMenuPaths(rc *restfulx.ReqCtx) { + roleKey := restfulx.QueryParam(rc, "roleKey") + biz.IsTrue(roleKey != "", "请传入角色Key") + rc.ResData = m.RoleMenuApp.GetMenuPaths(entity.SysRoleMenu{RoleName: roleKey}) +} + +func (m *MenuApi) GetMenuList(rc *restfulx.ReqCtx) { + menuName := restfulx.QueryParam(rc, "menuName") + status := restfulx.QueryParam(rc, "status") + + menu := entity.SysMenu{MenuName: menuName, Status: status} + if menu.MenuName == "" { + rc.ResData = m.MenuApp.SelectMenu(menu) + } else { + rc.ResData = m.MenuApp.FindList(menu) + } +} + +func (m *MenuApi) GetMenu(rc *restfulx.ReqCtx) { + menuId := restfulx.PathParamInt(rc, "menuId") + + rc.ResData = m.MenuApp.FindOne(int64(menuId)) +} + +func (m *MenuApi) InsertMenu(rc *restfulx.ReqCtx) { + var menu entity.SysMenu + restfulx.BindJsonAndValid(rc, &menu) + menu.CreateBy = rc.LoginAccount.UserName + m.MenuApp.Insert(menu) + permis := m.RoleMenuApp.GetPermis(rc.LoginAccount.RoleId) + menus := m.MenuApp.SelectMenuRole(rc.LoginAccount.RoleKey) + rc.ResData = vo.MenuPermisVo{ + Menus: Build(*menus), + Permissions: permis, + } +} + +func (m *MenuApi) UpdateMenu(rc *restfulx.ReqCtx) { + var menu entity.SysMenu + restfulx.BindJsonAndValid(rc, &menu) + menu.UpdateBy = rc.LoginAccount.UserName + m.MenuApp.Update(menu) + permis := m.RoleMenuApp.GetPermis(rc.LoginAccount.RoleId) + menus := m.MenuApp.SelectMenuRole(rc.LoginAccount.RoleKey) + rc.ResData = vo.MenuPermisVo{ + Menus: Build(*menus), + Permissions: permis, + } +} + +func (m *MenuApi) DeleteMenu(rc *restfulx.ReqCtx) { + menuId := restfulx.PathParam(rc, "menuId") + m.MenuApp.Delete(utils.IdsStrToIdsIntGroup(menuId)) +} diff --git a/apps/system/api/notice.go b/apps/system/api/notice.go new file mode 100644 index 0000000..045a966 --- /dev/null +++ b/apps/system/api/notice.go @@ -0,0 +1,61 @@ +package api + +import ( + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" + "strings" +) + +type NoticeApi struct { + OrganizationApp services.SysOrganizationModel + NoticeApp services.SysNoticeModel +} + +// GetNoticeList 通知列表数据 +func (p *NoticeApi) GetNoticeList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + noticeType := restfulx.QueryParam(rc, "noticeType") + title := restfulx.QueryParam(rc, "title") + + // 获取组织的子组织id + one := p.OrganizationApp.FindOne(rc.LoginAccount.OrganizationId) + split := strings.Split(strings.Trim(one.OrganizationPath, "/"), "/") + // 获取所有父组织id + ids := utils.OrganizationPCIds(split, rc.LoginAccount.OrganizationId, true) + notice := entity.SysNotice{NoticeType: noticeType, Title: title, OrganizationIds: ids} + list, total := p.NoticeApp.FindListPage(pageNum, pageSize, notice) + + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +// InsertNotice 添加通知 +func (p *NoticeApi) InsertNotice(rc *restfulx.ReqCtx) { + var notice entity.SysNotice + restfulx.BindJsonAndValid(rc, ¬ice) + notice.UserName = rc.LoginAccount.UserName + p.NoticeApp.Insert(notice) +} + +// UpdateNotice 修改通知 +func (p *NoticeApi) UpdateNotice(rc *restfulx.ReqCtx) { + var notice entity.SysNotice + restfulx.BindJsonAndValid(rc, ¬ice) + + p.NoticeApp.Update(notice) +} + +// DeleteNotice 删除通知 +func (p *NoticeApi) DeleteNotice(rc *restfulx.ReqCtx) { + noticeId := restfulx.PathParam(rc, "noticeId") + noticeIds := utils.IdsStrToIdsIntGroup(noticeId) + p.NoticeApp.Delete(noticeIds) +} diff --git a/apps/system/api/organization.go b/apps/system/api/organization.go new file mode 100644 index 0000000..d7199ed --- /dev/null +++ b/apps/system/api/organization.go @@ -0,0 +1,105 @@ +package api + +import ( + "errors" + "fmt" + "pandax/apps/system/api/vo" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/restfulx" + "pandax/kit/utils" + "pandax/pkg/global" +) + +type OrganizationApi struct { + OrganizationApp services.SysOrganizationModel + UserApp services.SysUserModel + RoleApp services.SysRoleModel +} + +func (m *OrganizationApi) GetOrganizationTreeRoleSelect(rc *restfulx.ReqCtx) { + roleId := restfulx.PathParamInt(rc, "roleId") + var organization entity.SysOrganization + result := m.OrganizationApp.SelectOrganizationLable(organization) + + organizationIds := make([]int64, 0) + if roleId != 0 { + organizationIds = m.RoleApp.GetRoleOrganizationId(entity.SysRole{RoleId: int64(roleId)}) + } + rc.ResData = vo.OrganizationTreeVo{ + Organizations: result, + CheckedKeys: organizationIds, + } +} + +func (a *OrganizationApi) GetOrganizationList(rc *restfulx.ReqCtx) { + //pageNum := restfulx.QueryInt(rc.GinCtx, "pageNum", 1) + //pageSize := restfulx.QueryInt(rc.GinCtx, "pageSize", 10) + organizationName := restfulx.QueryParam(rc, "organizationName") + status := restfulx.QueryParam(rc, "status") + organizationId := restfulx.QueryInt(rc, "organizationId", 0) + organization := entity.SysOrganization{OrganizationName: organizationName, Status: status, OrganizationId: int64(organizationId)} + + if organization.OrganizationName == "" { + rc.ResData = a.OrganizationApp.SelectOrganization(organization) + } else { + rc.ResData = a.OrganizationApp.FindList(organization) + } +} + +func (a *OrganizationApi) GetOrdinaryOrganizationList(rc *restfulx.ReqCtx) { + var organization entity.SysOrganization + + rc.ResData = a.OrganizationApp.FindList(organization) +} + +func (a *OrganizationApi) GetOrganizationTree(rc *restfulx.ReqCtx) { + organizationName := restfulx.QueryParam(rc, "organizationName") + status := restfulx.QueryParam(rc, "status") + organizationId := restfulx.QueryInt(rc, "organizationId", 0) + organization := entity.SysOrganization{OrganizationName: organizationName, Status: status, OrganizationId: int64(organizationId)} + + rc.ResData = a.OrganizationApp.SelectOrganization(organization) +} + +func (a *OrganizationApi) GetOrganization(rc *restfulx.ReqCtx) { + organizationId := restfulx.PathParamInt(rc, "organizationId") + rc.ResData = a.OrganizationApp.FindOne(int64(organizationId)) +} + +func (a *OrganizationApi) InsertOrganization(rc *restfulx.ReqCtx) { + var organization entity.SysOrganization + restfulx.BindJsonAndValid(rc, &organization) + organization.CreateBy = rc.LoginAccount.UserName + a.OrganizationApp.Insert(organization) +} + +func (a *OrganizationApi) UpdateOrganization(rc *restfulx.ReqCtx) { + var organization entity.SysOrganization + restfulx.BindJsonAndValid(rc, &organization) + + organization.UpdateBy = rc.LoginAccount.UserName + a.OrganizationApp.Update(organization) +} + +func (a *OrganizationApi) DeleteOrganization(rc *restfulx.ReqCtx) { + organizationId := restfulx.PathParam(rc, "organizationId") + organizationIds := utils.IdsStrToIdsIntGroup(organizationId) + + deList := make([]int64, 0) + for _, id := range organizationIds { + user := entity.SysUser{} + user.OrganizationId = id + list := a.UserApp.FindList(user) + if len(*list) == 0 { + deList = append(deList, id) + } else { + global.Log.Info(fmt.Sprintf("dictId: %d 存在用户绑定无法删除", id)) + } + } + if len(deList) == 0 { + biz.ErrIsNil(errors.New("所有组织都已绑定用户无法删除"), "所有组织都已绑定用户,无法删除") + } + a.OrganizationApp.Delete(deList) +} diff --git a/apps/system/api/post.go b/apps/system/api/post.go new file mode 100644 index 0000000..9afd2d8 --- /dev/null +++ b/apps/system/api/post.go @@ -0,0 +1,84 @@ +package api + +import ( + "errors" + "fmt" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" + "pandax/pkg/global" +) + +type PostApi struct { + PostApp services.SysPostModel + UserApp services.SysUserModel + RoleApp services.SysRoleModel +} + +// GetPostList 职位列表数据 +func (p *PostApi) GetPostList(rc *restfulx.ReqCtx) { + + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + status := restfulx.QueryParam(rc, "status") + postName := restfulx.QueryParam(rc, "postName") + postCode := restfulx.QueryParam(rc, "postCode") + post := entity.SysPost{Status: status, PostName: postName, PostCode: postCode} + + list, total := p.PostApp.FindListPage(pageNum, pageSize, post) + + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +// GetPost 获取职位 +func (p *PostApi) GetPost(rc *restfulx.ReqCtx) { + postId := restfulx.PathParamInt(rc, "postId") + p.PostApp.FindOne(int64(postId)) +} + +// InsertPost 添加职位 +func (p *PostApi) InsertPost(rc *restfulx.ReqCtx) { + var post entity.SysPost + restfulx.BindJsonAndValid(rc, &post) + post.CreateBy = rc.LoginAccount.UserName + p.PostApp.Insert(post) +} + +// UpdatePost 修改职位 +func (p *PostApi) UpdatePost(rc *restfulx.ReqCtx) { + var post entity.SysPost + restfulx.BindJsonAndValid(rc, &post) + + post.CreateBy = rc.LoginAccount.UserName + p.PostApp.Update(post) +} + +// DeletePost 删除职位 +func (p *PostApi) DeletePost(rc *restfulx.ReqCtx) { + postId := restfulx.PathParam(rc, "postId") + postIds := utils.IdsStrToIdsIntGroup(postId) + + deList := make([]int64, 0) + for _, id := range postIds { + user := entity.SysUser{} + user.PostId = id + list := p.UserApp.FindList(user) + if len(*list) == 0 { + deList = append(deList, id) + } else { + global.Log.Info(fmt.Sprintf("dictId: %d 存在岗位绑定用户无法删除", id)) + } + } + if len(deList) == 0 { + biz.ErrIsNil(errors.New("所有岗位都已绑定用户,无法删除"), "所有岗位都已绑定用户,无法删除") + } + p.PostApp.Delete(deList) +} diff --git a/apps/system/api/role.go b/apps/system/api/role.go new file mode 100644 index 0000000..3547c6b --- /dev/null +++ b/apps/system/api/role.go @@ -0,0 +1,168 @@ +package api + +import ( + "errors" + "fmt" + entity "pandax/apps/system/entity" + services "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/casbin" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" + "pandax/pkg/global" +) + +type RoleApi struct { + RoleApp services.SysRoleModel + UserApp services.SysUserModel + RoleMenuApp services.SysRoleMenuModel + OrganizationApp services.SysOrganizationModel + RoleOrganizationApp services.SysRoleOrganizationModel +} + +// GetRoleList角色列表数据 +func (r *RoleApi) GetRoleList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + status := restfulx.QueryParam(rc, "status") + roleName := restfulx.QueryParam(rc, "roleName") + roleKey := restfulx.QueryParam(rc, "roleKey") + role := entity.SysRole{Status: status, RoleName: roleName, RoleKey: roleKey} + list, total := r.RoleApp.FindListPage(pageNum, pageSize, role) + + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +// GetRole 获取Role数据 +func (r *RoleApi) GetRole(rc *restfulx.ReqCtx) { + roleId := restfulx.PathParamInt(rc, "roleId") + role := r.RoleApp.FindOne(int64(roleId)) + role.MenuIds = r.RoleApp.GetRoleMeunId(entity.SysRole{RoleId: int64(roleId)}) + + rc.ResData = role +} + +// InsertRole 创建角色 +func (r *RoleApi) InsertRole(rc *restfulx.ReqCtx) { + var role entity.SysRole + restfulx.BindJsonAndValid(rc, &role) + role.CreateBy = rc.LoginAccount.UserName + if role.DataScope == "" { + role.DataScope = "0" + } + // 添加角色对应的菜单 + insert := r.RoleApp.Insert(role) + role.RoleId = insert.RoleId + r.RoleMenuApp.Insert(insert.RoleId, role.MenuIds) + //添加权限 + ca := casbin.CasbinService{ModelPath: global.Conf.Casbin.ModelPath} + ca.UpdateCasbin(role.RoleKey, role.ApiIds) +} + +// UpdateRole 修改用户角色 +func (r *RoleApi) UpdateRole(rc *restfulx.ReqCtx) { + var role entity.SysRole + restfulx.BindJsonAndValid(rc, &role) + role.UpdateBy = rc.LoginAccount.UserName + // 修改角色 + r.RoleApp.Update(role) + // 删除角色的菜单绑定 + r.RoleMenuApp.DeleteRoleMenu(role.RoleId) + // 添加角色菜单绑定 + r.RoleMenuApp.Insert(role.RoleId, role.MenuIds) + //修改api权限 + ca := casbin.CasbinService{ModelPath: global.Conf.Casbin.ModelPath} + ca.UpdateCasbin(role.RoleKey, role.ApiIds) +} + +// UpdateRoleStatus 修改用户角色状态 +func (r *RoleApi) UpdateRoleStatus(rc *restfulx.ReqCtx) { + var role entity.SysRole + restfulx.BindJsonAndValid(rc, &role) + role.UpdateBy = rc.LoginAccount.UserName + // 修改角色 + r.RoleApp.Update(role) +} + +// UpdateRoleDataScope 修改用户角色组织 +func (r *RoleApi) UpdateRoleDataScope(rc *restfulx.ReqCtx) { + var role entity.SysRole + restfulx.BindJsonAndValid(rc, &role) + role.UpdateBy = rc.LoginAccount.UserName + // 修改角色 + update := r.RoleApp.Update(role) + go func() { + if role.DataScope != entity.SELFDATASCOPE { + organizationIds := make([]int64, 0) + if role.DataScope == entity.ALLDATASCOPE { + for _, organization := range *r.OrganizationApp.FindList(entity.SysOrganization{}) { + organizationIds = append(organizationIds, organization.OrganizationId) + } + } + if role.DataScope == entity.DIYDATASCOPE { + organizationIds = role.OrganizationIds + } + if role.DataScope == entity.ORGDATASCOPE { + organizationIds = append(organizationIds, rc.LoginAccount.OrganizationId) + } + if role.DataScope == entity.ORGALLDATASCOPE { + //organizationIds = append(organizationIds, rc.LoginAccount.OrganizationId) + organizationIds = r.OrganizationApp.SelectOrganizationIds(entity.SysOrganization{OrganizationId: rc.LoginAccount.OrganizationId}) + } + // 删除角色的组织绑定 + r.RoleOrganizationApp.Delete(entity.SysRoleOrganization{RoleId: update.RoleId}) + // 添加角色组织绑定 + r.RoleOrganizationApp.Insert(role.RoleId, organizationIds) + } + }() + +} + +// DeleteRole 删除用户角色 +func (r *RoleApi) DeleteRole(rc *restfulx.ReqCtx) { + roleId := restfulx.PathParam(rc, "roleId") + roleIds := utils.IdsStrToIdsIntGroup(roleId) + + user := entity.SysUser{} + delList := make([]int64, 0) + // 判断角色下面是否绑定用户 + for _, rid := range roleIds { + user.RoleId = rid + role := r.RoleApp.FindOne(rid) + list := r.UserApp.FindList(user) + if len(*list) == 0 { + delList = append(delList, rid) + //删除角色绑定api + ca := casbin.CasbinService{ModelPath: global.Conf.Casbin.ModelPath} + ca.ClearCasbin(0, role.RoleKey) + } else { + global.Log.Info(fmt.Sprintf("role:%d 存在用户无法删除", rid)) + } + } + if len(delList) == 0 { + biz.ErrIsNil(errors.New("所有角色都已绑定用户无法删除"), "所有角色都已绑定用户,无法删除") + } + r.RoleApp.Delete(delList) + r.RoleMenuApp.DeleteRoleMenus(delList) +} + +// ExportRole 导出角色 +func (p *RoleApi) ExportRole(rc *restfulx.ReqCtx) { + filename := restfulx.QueryParam(rc, "filename") + status := restfulx.QueryParam(rc, "status") + roleName := restfulx.QueryParam(rc, "roleName") + roleKey := restfulx.QueryParam(rc, "roleKey") + role := entity.SysRole{Status: status, RoleName: roleName, RoleKey: roleKey} + + list := p.RoleApp.FindList(role) + + fileName := utils.GetFileName(global.Conf.Server.ExcelDir, filename) + utils.InterfaceToExcel(*list, fileName) + rc.Download(fileName) +} diff --git a/apps/system/api/system.go b/apps/system/api/system.go new file mode 100644 index 0000000..d81c7c7 --- /dev/null +++ b/apps/system/api/system.go @@ -0,0 +1,83 @@ +package api + +import ( + "fmt" + "pandax/kit/biz" + "pandax/kit/restfulx" + "pandax/kit/ws" + "pandax/pkg/middleware" + "runtime" + + "github.com/emicklei/go-restful/v3" + "github.com/gorilla/websocket" + "github.com/kakuilan/kgo" +) + +type System struct{} + +const ( + B = 1 + KB = 1024 * B + MB = 1024 * KB + GB = 1024 * MB +) + +func (s *System) ServerInfo(request *restful.Request, response *restful.Response) { + osDic := make(map[string]any, 0) + osDic["goOs"] = runtime.GOOS + osDic["arch"] = runtime.GOARCH + osDic["mem"] = runtime.MemProfileRate + osDic["compiler"] = runtime.Compiler + osDic["version"] = runtime.Version() + osDic["numGoroutine"] = runtime.NumGoroutine() + + info := kgo.KOS.GetSystemInfo() + diskDic := make(map[string]any, 0) + diskDic["total"] = info.DiskTotal / GB + diskDic["free"] = info.DiskFree / GB + diskDic["used"] = info.DiskUsed / GB + diskDic["progress"] = fmt.Sprintf("%.2f", (float64(info.DiskUsed)/float64(info.DiskTotal))*100) + + memDic := make(map[string]any, 0) + memDic["total"] = info.MemTotal / GB + memDic["used"] = info.MemUsed / GB + memDic["free"] = info.MemFree / GB + memDic["progress"] = fmt.Sprintf("%.2f", (float64(info.MemUsed)/float64(info.MemTotal))*100) + + cpuDic := make(map[string]any, 0) + cpuDic["num"] = info.CpuNum + cpuDic["used"] = fmt.Sprintf("%.2f", info.CpuUser*100) + cpuDic["free"] = fmt.Sprintf("%.2f", info.CpuFree*100) + + response.WriteEntity(map[string]any{ + "code": 200, + "os": osDic, + "mem": memDic, + "cpu": cpuDic, + "disk": diskDic, + }) +} + +// ConnectWs 连接websocket +func (s *System) ConnectWs(request *restful.Request, response *restful.Response) { + wsConn, err := ws.Upgrader.Upgrade(response, request.Request, nil) + defer func() { + if err := recover(); &err != nil { + wsConn.WriteMessage(websocket.TextMessage, []byte(fmt.Sprintf("websocket 失败: %v", err))) + wsConn.Close() + } + }() + + if err != nil { + panic(any(biz.NewBizErr("升级websocket失败"))) + } + // 权限校验 + rc := restfulx.NewReqCtx(request, response) + if err = middleware.PermissionHandler(rc); err != nil { + panic(any(biz.NewBizErr("没有权限"))) + } + + // 登录账号信息 + la := rc.LoginAccount + ws.Put(uint64(la.UserId), wsConn) +} diff --git a/apps/system/api/tenant.go b/apps/system/api/tenant.go new file mode 100644 index 0000000..606887e --- /dev/null +++ b/apps/system/api/tenant.go @@ -0,0 +1,74 @@ +package api + +/** + * @Description + * @Author 熊猫 + * @Date 2022/7/14 17:55 + **/ +import ( + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + "pandax/kit/utils" +) + +type SysTenantsApi struct { + SysTenantsApp services.SysTenantsModel +} + +func (p *SysTenantsApi) GetSysTenantsList(rc *restfulx.ReqCtx) { + data := entity.SysTenants{} + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + + list, total := p.SysTenantsApp.FindListPage(pageNum, pageSize, data) + + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +func (p *SysTenantsApi) GetSysTenantsAll(rc *restfulx.ReqCtx) { + list := make([]entity.SysTenants, 0) + if rc.LoginAccount.RoleKey == "admin" { + data := entity.SysTenants{} + list = *p.SysTenantsApp.FindList(data) + } else { + list = append(list, *p.SysTenantsApp.FindOne(rc.LoginAccount.TenantId)) + } + rc.ResData = list +} + +func (p *SysTenantsApi) GetSysTenants(rc *restfulx.ReqCtx) { + tenantId := restfulx.PathParamInt(rc, "tenantId") + p.SysTenantsApp.FindOne(int64(tenantId)) +} + +func (p *SysTenantsApi) InsertSysTenants(rc *restfulx.ReqCtx) { + var data entity.SysTenants + restfulx.BindQuery(rc, &data) + + p.SysTenantsApp.Insert(data) +} + +func (p *SysTenantsApi) UpdateSysTenants(rc *restfulx.ReqCtx) { + var data entity.SysTenants + restfulx.BindQuery(rc, &data) + + p.SysTenantsApp.Update(data) +} + +func (p *SysTenantsApi) DeleteSysTenants(rc *restfulx.ReqCtx) { + tenantId := rc.Request.PathParameter("tenantId") + tenantIds := utils.IdsStrToIdsIntGroup(tenantId) + p.SysTenantsApp.Delete(tenantIds) +} + +// IsTenantAdmin 是否为主租户 +func IsTenantAdmin(tenantId int64) bool { + return tenantId == 1 +} diff --git a/apps/system/api/user.go b/apps/system/api/user.go new file mode 100644 index 0000000..7eae29b --- /dev/null +++ b/apps/system/api/user.go @@ -0,0 +1,430 @@ +package api + +import ( + "fmt" + "pandax/apps/system/api/form" + "pandax/apps/system/api/vo" + "pandax/apps/system/entity" + "pandax/kit/model" + "pandax/kit/token" + + "github.com/dgrijalva/jwt-go" + "github.com/emicklei/go-restful/v3" + "github.com/kakuilan/kgo" + "github.com/mssola/user_agent" + + logEntity "pandax/apps/log/entity" + logServices "pandax/apps/log/services" + + "pandax/apps/system/services" + "pandax/kit/biz" + "pandax/kit/captcha" + filek "pandax/kit/file" + "pandax/kit/restfulx" + "pandax/kit/utils" + "pandax/pkg/global" + "strings" + "time" +) + +type UserApi struct { + UserApp services.SysUserModel + MenuApp services.SysMenuModel + PostApp services.SysPostModel + RoleApp services.SysRoleModel + RoleMenuApp services.SysRoleMenuModel + OrganizationApp services.SysOrganizationModel + LogLogin logServices.LogLoginModel +} + +// GenerateCaptcha 获取验证码 +func (u *UserApi) GenerateCaptcha(request *restful.Request, response *restful.Response) { + id, image, _ := captcha.Generate() + response.WriteEntity(vo.CaptchaVo{Base64Captcha: image, CaptchaId: id}) +} + +// RefreshToken 刷新token +func (u *UserApi) RefreshToken(rc *restfulx.ReqCtx) { + tokenStr := rc.Request.Request.Header.Get("X-TOKEN") + // 如果token为空,从请求参数中获取 + if tokenStr == "" { + tokenStr = rc.Request.Request.URL.Query().Get("token") + } + j := token.NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + token, err := j.RefreshToken(tokenStr) + if err != nil { + fmt.Printf("刷新Token失败: %s\n", err.Error()) + return + } + rc.ResData = vo.TokenVo{ + Token: token, + Expire: time.Now().Unix() + global.Conf.Jwt.ExpireTime, + } +} + +// Login 用户登录 +func (u *UserApi) Login(rc *restfulx.ReqCtx) { + var l form.Login + restfulx.BindJsonAndValid(rc, &l) + if captcha.Verify(l.CaptchaId, l.Captcha) { + fmt.Println("验证码认证失败") + return + } + + login := u.UserApp.Login(entity.Login{Username: l.Username, Password: l.Password}) + role := u.RoleApp.FindOne(login.RoleId) + j := token.NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + token, err := j.CreateToken(token.Claims{ + UserId: login.UserId, + UserName: login.Username, + RoleId: login.RoleId, + RoleKey: role.RoleKey, + OrganizationId: login.OrganizationId, + PostId: login.PostId, + StandardClaims: jwt.StandardClaims{ + NotBefore: time.Now().Unix() - 1000, // 签名生效时间 + ExpiresAt: time.Now().Unix() + global.Conf.Jwt.ExpireTime, // 过期时间 7天 配置文件 + Issuer: global.Conf.Jwt.Key, // 签名的发行者 + }, + }) + + if err != nil { + fmt.Printf("生成Token失败: %s\n", err.Error()) + return + } + + rc.ResData = vo.TokenVo{ + Token: token, + Expire: time.Now().Unix() + global.Conf.Jwt.ExpireTime, + } + go func() { + var loginLog logEntity.LogLogin + ua := user_agent.New(rc.Request.Request.UserAgent()) + loginLog.Ipaddr = rc.Request.Request.RemoteAddr + loginLog.LoginLocation = utils.GetRealAddressByIP(strings.Split(rc.Request.Request.RemoteAddr, ":")[0]) + loginLog.LoginTime = time.Now() + loginLog.Status = "0" + loginLog.Remark = rc.Request.Request.UserAgent() + browserName, browserVersion := ua.Browser() + loginLog.Browser = browserName + " " + browserVersion + loginLog.Os = ua.OS() + loginLog.Platform = ua.Platform() + loginLog.Username = login.Username + loginLog.Msg = "登录成功" + loginLog.CreateBy = login.Username + u.LogLogin.Insert(loginLog) + }() +} + +// GetToken 用户登录 +func (u *UserApi) GetToken(rc *restfulx.ReqCtx) { + var l form.Login + restfulx.BindJsonAndValid(rc, &l) + + login := u.UserApp.Login(entity.Login{Username: l.Username, Password: l.Password}) + role := u.RoleApp.FindOne(login.RoleId) + j := token.NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + token, err := j.CreateToken(token.Claims{ + UserId: login.UserId, + UserName: login.Username, + RoleId: login.RoleId, + RoleKey: role.RoleKey, + OrganizationId: login.OrganizationId, + PostId: login.PostId, + StandardClaims: jwt.StandardClaims{ + NotBefore: time.Now().Unix() - 1000, // 签名生效时间 + ExpiresAt: time.Now().Unix() + global.Conf.Jwt.ExpireTime, // 过期时间 7天 配置文件 + Issuer: global.Conf.Jwt.Key, // 签名的发行者 + }, + }) + + if err != nil { + fmt.Printf("生成Token失败: %s\n", err.Error()) + return + } + + rc.ResData = vo.TokenVo{ + Token: token, + Expire: time.Now().Unix() + global.Conf.Jwt.ExpireTime, + } + go func() { + var loginLog logEntity.LogLogin + ua := user_agent.New(rc.Request.Request.UserAgent()) + loginLog.Ipaddr = rc.Request.Request.RemoteAddr + loginLog.LoginLocation = utils.GetRealAddressByIP(strings.Split(rc.Request.Request.RemoteAddr, ":")[0]) + loginLog.LoginTime = time.Now() + loginLog.Status = "0" + loginLog.Remark = rc.Request.Request.UserAgent() + browserName, browserVersion := ua.Browser() + loginLog.Browser = browserName + " " + browserVersion + loginLog.Os = ua.OS() + loginLog.Platform = ua.Platform() + loginLog.Username = login.Username + loginLog.Msg = "登录成功" + loginLog.CreateBy = login.Username + u.LogLogin.Insert(loginLog) + }() +} + +// Auth 用户权限信息 +func (u *UserApi) Auth(rc *restfulx.ReqCtx) { + userName := restfulx.QueryParam(rc, "username") + if userName == "" { + fmt.Println("用户名不能为空") + return + } + var user entity.SysUser + user.Username = userName + userData := u.UserApp.FindOne(user) + role := u.RoleApp.FindOne(userData.RoleId) + //前端权限 + permis := u.RoleMenuApp.GetPermis(role.RoleId) + menus := u.MenuApp.SelectMenuRole(role.RoleKey) + + rc.ResData = vo.AuthVo{ + User: *userData, + Role: *role, + Permissions: permis, + Menus: Build(*menus), + } +} + +// LogOut 退出登录 +func (u *UserApi) LogOut(rc *restfulx.ReqCtx) { + var loginLog logEntity.LogLogin + ua := user_agent.New(rc.Request.Request.UserAgent()) + loginLog.Ipaddr = rc.Request.Request.RemoteAddr + loginLog.LoginTime = time.Now() + loginLog.Status = "0" + loginLog.Remark = rc.Request.Request.UserAgent() + browserName, browserVersion := ua.Browser() + loginLog.Browser = browserName + " " + browserVersion + loginLog.Os = ua.OS() + loginLog.Platform = ua.Platform() + loginLog.Username = rc.LoginAccount.UserName + loginLog.Msg = "退出成功" + u.LogLogin.Insert(loginLog) +} + +// GetSysUserList 列表数据 +func (u *UserApi) GetSysUserList(rc *restfulx.ReqCtx) { + pageNum := restfulx.QueryInt(rc, "pageNum", 1) + pageSize := restfulx.QueryInt(rc, "pageSize", 10) + status := restfulx.QueryParam(rc, "status") + username := restfulx.QueryParam(rc, "username") + phone := restfulx.QueryParam(rc, "phone") + + organizationId := restfulx.QueryInt(rc, "organizationId", 0) + var user entity.SysUser + user.Status = status + user.Username = username + user.Phone = phone + user.OrganizationId = int64(organizationId) + + list, total := u.UserApp.FindListPage(pageNum, pageSize, user) + + rc.ResData = model.ResultPage{ + Total: total, + PageNum: int64(pageNum), + PageSize: int64(pageSize), + Data: list, + } +} + +// GetSysUserProfile 获取当前登录用户 +func (u *UserApi) GetSysUserProfile(rc *restfulx.ReqCtx) { + + sysUser := entity.SysUser{} + sysUser.UserId = rc.LoginAccount.UserId + user := u.UserApp.FindOne(sysUser) + + //获取角色列表 + roleList := u.RoleApp.FindList(entity.SysRole{RoleId: rc.LoginAccount.RoleId}) + //岗位列表 + postList := u.PostApp.FindList(entity.SysPost{PostId: rc.LoginAccount.PostId}) + //获取组织列表 + organizationList := u.OrganizationApp.FindList(entity.SysOrganization{OrganizationId: rc.LoginAccount.OrganizationId}) + + postIds := make([]int64, 0) + postIds = append(postIds, rc.LoginAccount.PostId) + + roleIds := make([]int64, 0) + roleIds = append(roleIds, rc.LoginAccount.RoleId) + + rc.ResData = vo.UserProfileVo{ + Data: user, + PostIds: postIds, + RoleIds: roleIds, + Roles: *roleList, + Posts: *postList, + Organization: *organizationList, + } +} + +// InsetSysUserAvatar 修改头像 +func (u *UserApi) InsetSysUserAvatar(rc *restfulx.ReqCtx) { + form := rc.Request.Request.MultipartForm + + files := form.File["upload[]"] + guid, _ := kgo.KStr.UuidV4() + filPath := "static/uploadfile/" + guid + ".jpg" + for _, file := range files { + global.Log.Info(file.Filename) + // 上传文件至指定目录 + biz.ErrIsNil(filek.SaveUploadedFile(file, filPath), "保存头像失败") + } + sysuser := entity.SysUser{} + sysuser.UserId = rc.LoginAccount.UserId + sysuser.Avatar = "/" + filPath + sysuser.UpdateBy = rc.LoginAccount.UserName + + u.UserApp.Update(sysuser) +} + +// SysUserUpdatePwd 修改密码 +func (u *UserApi) SysUserUpdatePwd(rc *restfulx.ReqCtx) { + var pws entity.SysUserPwd + restfulx.BindJsonAndValid(rc, &pws) + + user := entity.SysUser{} + user.UserId = rc.LoginAccount.UserId + u.UserApp.SetPwd(user, pws) +} + +// GetSysUser 获取用户 +func (u *UserApi) GetSysUser(rc *restfulx.ReqCtx) { + userId := restfulx.PathParamInt(rc, "userId") + + user := entity.SysUser{} + user.UserId = int64(userId) + result := u.UserApp.FindOne(user) + + var role entity.SysRole + var post entity.SysPost + var organization entity.SysOrganization + + rc.ResData = vo.UserVo{ + Data: result, + PostIds: result.PostIds, + RoleIds: result.RoleIds, + Roles: *u.RoleApp.FindList(role), + Posts: *u.PostApp.FindList(post), + Organizations: u.OrganizationApp.SelectOrganization(organization), + } +} + +// GetSysUserInit 获取添加用户角色和职位 +func (u *UserApi) GetSysUserInit(rc *restfulx.ReqCtx) { + + var role entity.SysRole + roles := u.RoleApp.FindList(role) + var post entity.SysPost + posts := u.PostApp.FindList(post) + rc.ResData = vo.UserRolePost{ + Roles: *roles, + Posts: *posts, + } +} + +// GetUserRolePost 获取添加用户角色和职位 +func (u *UserApi) GetUserRolePost(rc *restfulx.ReqCtx) { + var user entity.SysUser + user.UserId = rc.LoginAccount.UserId + + resData := u.UserApp.FindOne(user) + + roles := make([]entity.SysRole, 0) + posts := make([]entity.SysPost, 0) + for _, roleId := range strings.Split(resData.RoleIds, ",") { + ro := u.RoleApp.FindOne(kgo.KConv.Str2Int64(roleId)) + roles = append(roles, *ro) + } + for _, postId := range strings.Split(resData.PostIds, ",") { + po := u.PostApp.FindOne(kgo.KConv.Str2Int64(postId)) + posts = append(posts, *po) + } + rc.ResData = vo.UserRolePost{ + Roles: roles, + Posts: posts, + } +} + +// InsertSysUser 创建用户 +func (u *UserApi) InsertSysUser(rc *restfulx.ReqCtx) { + var sysUser entity.SysUser + restfulx.BindJsonAndValid(rc, &sysUser) + sysUser.CreateBy = rc.LoginAccount.UserName + u.UserApp.Insert(sysUser) +} + +// UpdateSysUser 修改用户数据 +func (u *UserApi) UpdateSysUser(rc *restfulx.ReqCtx) { + var sysUser entity.SysUser + restfulx.BindJsonAndValid(rc, &sysUser) + sysUser.CreateBy = rc.LoginAccount.UserName + u.UserApp.Update(sysUser) +} + +// UpdateSysUserStu 修改用户状态 +func (u *UserApi) UpdateSysUserStu(rc *restfulx.ReqCtx) { + var sysUser entity.SysUser + restfulx.BindJsonAndValid(rc, &sysUser) + sysUser.CreateBy = rc.LoginAccount.UserName + u.UserApp.Update(sysUser) +} + +// DeleteSysUser 删除用户数据 +func (u *UserApi) DeleteSysUser(rc *restfulx.ReqCtx) { + userIds := restfulx.PathParam(rc, "userId") + u.UserApp.Delete(utils.IdsStrToIdsIntGroup(userIds)) +} + +// ExportUser 导出用户 +func (u *UserApi) ExportUser(rc *restfulx.ReqCtx) { + filename := restfulx.QueryParam(rc, "filename") + status := restfulx.QueryParam(rc, "status") + username := restfulx.QueryParam(rc, "username") + phone := restfulx.QueryParam(rc, "phone") + + var user entity.SysUser + user.Status = status + user.Username = username + user.Phone = phone + + list := u.UserApp.FindList(user) + fileName := utils.GetFileName(global.Conf.Server.ExcelDir, filename) + utils.InterfaceToExcel(*list, fileName) + rc.Download(fileName) +} + +// Build 构建前端路由 +func Build(menus []entity.SysMenu) []vo.RouterVo { + equals := func(a string, b string) bool { + return a == b + } + rvs := make([]vo.RouterVo, 0) + for _, ms := range menus { + var rv vo.RouterVo + rv.Name = ms.Path + rv.Path = ms.Path + rv.Component = ms.Component + auth := make([]string, 0) + if ms.Permission != "" { + auth = strings.Split(ms.Permission, ",") + } + rv.Meta = vo.MetaVo{ + Title: ms.MenuName, + IsLink: ms.IsLink, + IsHide: equals("1", ms.IsHide), + IsKeepAlive: equals("0", ms.IsKeepAlive), + IsAffix: equals("0", ms.IsAffix), + IsIframe: equals("0", ms.IsIframe), + Auth: auth, + Icon: ms.Icon, + } + rv.Children = Build(ms.Children) + rvs = append(rvs, rv) + } + + return rvs +} diff --git a/apps/system/api/vo/metaVo.go b/apps/system/api/vo/metaVo.go new file mode 100644 index 0000000..ce869eb --- /dev/null +++ b/apps/system/api/vo/metaVo.go @@ -0,0 +1,25 @@ +package vo + +/**s + * 路由meta对象参数说明 + * meta: { + * title: 菜单栏及 tagsView 栏、菜单搜索名称 + * isLink: 是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空` + * isHide: 是否隐藏此路由 + * isKeepAlive: 是否缓存组件状态 + * isAffix: 是否固定在 tagsView 栏上 + * isFrame: 是否内嵌窗口,,开启条件,`1、isFrame:true 2、链接地址不为空` + * auth: 当前路由权限标识(多个请用逗号隔开),最后转成数组格式,用于与当前用户权限进行对比,控制路由显示、隐藏 + * icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx` + * } + */ +type MetaVo struct { + Title string `json:"title"` + IsLink string `json:"isLink"` + IsHide bool `json:"isHide"` + IsKeepAlive bool `json:"isKeepAlive"` + IsAffix bool `json:"isAffix"` + IsIframe bool `json:"isIframe"` + Auth []string `json:"auth"` + Icon string `json:"icon"` +} diff --git a/apps/system/api/vo/routerVo.go b/apps/system/api/vo/routerVo.go new file mode 100644 index 0000000..562e877 --- /dev/null +++ b/apps/system/api/vo/routerVo.go @@ -0,0 +1,31 @@ +package vo + +/** + * 路由对象参数说明 + * { + * component: 组件地址 + * redirect: 重定向地址,当设置 noRedirect 的时候该路由在面包屑导航中不可被点击 + * path: 路由地址 + * name: 路由名字 + * // 路由meta对象参数说明 + * meta: { + * title: 菜单栏及 tagsView 栏、菜单搜索名称(国际化) + * isLink: 是否超链接菜单,开启外链条件,`1、isLink:true 2、链接地址不为空` + * isHide: 是否隐藏此路由 + * isKeepAlive: 是否缓存组件状态 + * isAffix: 是否固定在 tagsView 栏上 + * isFrame: 是否内嵌窗口,,开启条件,`1、isIframe:true 2、链接地址不为空` + * auth: 当前路由权限标识(多个请用逗号隔开),最后转成数组格式,用于与当前用户权限进行对比,控制路由显示、隐藏 + * icon: 菜单、tagsView 图标,阿里:加 `iconfont xxx`,fontawesome:加 `fa xxx` + * } + * } + * + */ +type RouterVo struct { + Name string `json:"name"` + Path string `json:"path"` + Redirect string `json:"redirect"` + Component string `json:"component"` + Meta MetaVo `json:"meta"` + Children []RouterVo `json:"children"` +} diff --git a/apps/system/api/vo/systemVo.go b/apps/system/api/vo/systemVo.go new file mode 100644 index 0000000..f33d1ca --- /dev/null +++ b/apps/system/api/vo/systemVo.go @@ -0,0 +1,64 @@ +package vo + +import "pandax/apps/system/entity" + +/** + * @Description + * @Author 熊猫 + * @Date 2022/8/4 15:25 + **/ + +type OrganizationTreeVo struct { + Organizations []entity.OrganizationLable `json:"organizations"` + CheckedKeys []int64 `json:"checkedKeys"` +} + +type MenuTreeVo struct { + Menus []entity.MenuLable `json:"menus"` + CheckedKeys []int64 `json:"checkedKeys"` +} + +type MenuPermisVo struct { + Menus []RouterVo `json:"menus"` + Permissions []string `json:"permissions"` +} + +type CaptchaVo struct { + Base64Captcha string `json:"base64Captcha"` + CaptchaId string `json:"captchaId"` +} + +type TokenVo struct { + Token string `json:"token"` + Expire int64 `json:"expire"` +} + +type AuthVo struct { + User entity.SysUserView `json:"user"` + Role entity.SysRole `json:"role"` + Permissions []string `json:"permissions"` + Menus []RouterVo `json:"menus"` +} + +type UserProfileVo struct { + Data any `json:"data"` + PostIds []int64 `json:"postIds"` + RoleIds []int64 `json:"roleIds"` + Roles []entity.SysRole `json:"roles"` + Posts []entity.SysPost `json:"posts"` + Organization []entity.SysOrganization `json:"organization"` +} + +type UserVo struct { + Data any `json:"data"` + PostIds string `json:"postIds"` + RoleIds string `json:"roleIds"` + Roles []entity.SysRole `json:"roles"` + Posts []entity.SysPost `json:"posts"` + Organizations []entity.SysOrganization `json:"organizations"` +} + +type UserRolePost struct { + Roles []entity.SysRole `json:"roles"` + Posts []entity.SysPost `json:"posts"` +} diff --git a/apps/system/entity/api.go b/apps/system/entity/api.go new file mode 100644 index 0000000..07021a5 --- /dev/null +++ b/apps/system/entity/api.go @@ -0,0 +1,11 @@ +package entity + +import "pandax/kit/model" + +type SysApi struct { + model.BaseAutoModel + Path string `json:"path" gorm:"comment:api路径" binding:"required"` // api路径 + Description string `json:"description" gorm:"comment:api中文描述"` // api中文描述 + ApiGroup string `json:"apiGroup" gorm:"comment:api组"` // api组 + Method string `json:"method" gorm:"comment:方法"` // 方法:创建POST(默认)|查看GET|更新PUT|删除DELETE +} diff --git a/apps/system/entity/config.go b/apps/system/entity/config.go new file mode 100644 index 0000000..05cc664 --- /dev/null +++ b/apps/system/entity/config.go @@ -0,0 +1,14 @@ +package entity + +import "pandax/kit/model" + +type SysConfig struct { + ConfigId int64 `json:"configId" gorm:"primaryKey;AUTO_INCREMENT;comment:主键编码"` + ConfigName string `json:"configName" gorm:"type:varchar(128);comment:ConfigName"` + ConfigKey string `json:"configKey" gorm:"type:varchar(128);comment:ConfigKey"` + ConfigValue string `json:"configValue" gorm:"type:varchar(255);comment:ConfigValue"` + ConfigType string `json:"configType" gorm:"type:varchar(64);comment:是否系统内置0,1"` + IsFrontend string `json:"isFrontend" gorm:"type:varchar(1);comment:是否前台"` + Remark string `json:"remark" gorm:"type:varchar(128);comment:Remark"` + model.BaseModel +} diff --git a/apps/system/entity/dict.go b/apps/system/entity/dict.go new file mode 100644 index 0000000..03bc592 --- /dev/null +++ b/apps/system/entity/dict.go @@ -0,0 +1,30 @@ +package entity + +import "pandax/kit/model" + +type SysDictData struct { + DictCode int64 `json:"dictCode" gorm:"primary_key;AUTO_INCREMENT"` + DictSort int `json:"dictSort" gorm:"type:int;comment:排序"` + DictLabel string `json:"dictLabel" gorm:"type:varchar(64);comment:标签"` + DictValue string `json:"dictValue" gorm:"type:varchar(64);comment:值"` + DictType string `json:"dictType" gorm:"type:varchar(64);comment:字典类型"` + Status string `json:"status" gorm:"type:varchar(1);comment:状态(0正常 1停用)"` + CssClass string `json:"cssClass" gorm:"type:varchar(128);comment:CssClass"` + ListClass string `json:"listClass" gorm:"type:varchar(128);comment:ListClass"` + IsDefault string `json:"isDefault" gorm:"type:varchar(8);comment:IsDefault"` + CreateBy string `json:"createBy"` + UpdateBy string `json:"updateBy"` + Remark string `json:"remark" gorm:"type:varchar(256);comment:备注"` + model.BaseModel +} + +type SysDictType struct { + DictId int64 `json:"dictId" gorm:"primary_key;AUTO_INCREMENT"` + DictName string `json:"dictName" gorm:"type:varchar(64);comment:名称"` + DictType string `json:"dictType" gorm:"type:varchar(64);comment:类型"` + Status string `json:"status" gorm:"type:varchar(1);comment:状态"` + CreateBy string `json:"createBy"` + UpdateBy string `json:"updateBy"` + Remark string `json:"remark" gorm:"type:varchar(256);comment:备注"` + model.BaseModel +} diff --git a/apps/system/entity/menu.go b/apps/system/entity/menu.go new file mode 100644 index 0000000..91f7863 --- /dev/null +++ b/apps/system/entity/menu.go @@ -0,0 +1,38 @@ +package entity + +import "pandax/kit/model" + +type SysMenu struct { + MenuId int64 `json:"menuId" gorm:"primary_key;AUTO_INCREMENT"` + MenuName string `json:"menuName" gorm:"type:varchar(128);"` + Title string `json:"title" gorm:"type:varchar(64);"` + ParentId int64 `json:"parentId" gorm:"type:int;"` + Sort int64 `json:"sort" gorm:"type:int;"` + Icon string `json:"icon" gorm:"type:varchar(128);"` + Path string `json:"path" gorm:"type:varchar(128);"` + Component string `json:"component" gorm:"type:varchar(255);"` // 组件路径 + IsIframe string `json:"isIframe" gorm:"type:varchar(1);"` //是否为内嵌 + IsLink string `json:"isLink" gorm:"type:varchar(255);"` //是否超链接菜单 + MenuType string `json:"menuType" gorm:"type:varchar(1);"` //菜单类型(M目录 C菜单 F按钮) + IsHide string `json:"isHide" gorm:"type:varchar(1);"` //显示状态(0显示 1隐藏) + IsKeepAlive string `json:"isKeepAlive" gorm:"type:varchar(1);"` //是否缓存组件状态(0是 1否) + IsAffix string `json:"isAffix" gorm:"type:varchar(1);"` //是否固定在 tagsView 栏上(0是 1否) + Permission string `json:"permission" gorm:"type:varchar(32);"` //权限标识 + Status string `json:"status" gorm:"type:varchar(1);` // 菜单状态(0正常 1停用) + CreateBy string `json:"createBy" gorm:"type:varchar(128);"` + UpdateBy string `json:"updateBy" gorm:"type:varchar(128);"` + Remark string `json:"remark" gorm:"type:varchar(256);` // 备注 + Children []SysMenu `json:"children" gorm:"-"` + model.BaseModel +} + +type MenuLable struct { + MenuId int64 `json:"menuId" gorm:"-"` + MenuName string `json:"menuName" gorm:"-"` + Children []MenuLable `json:"children" gorm:"-"` +} + +type MenuRole struct { + SysMenu + IsSelect bool `json:"is_select" gorm:"-"` +} diff --git a/apps/system/entity/notice.go b/apps/system/entity/notice.go new file mode 100644 index 0000000..2b7b987 --- /dev/null +++ b/apps/system/entity/notice.go @@ -0,0 +1,15 @@ +package entity + +import "pandax/kit/model" + +type SysNotice struct { + NoticeId int64 `json:"noticeId" gorm:"primary_key;AUTO_INCREMENT"` + Title string `json:"title" gorm:"type:varchar(128);comment:标题"` + Content string `json:"content" gorm:"type:text;comment:标题"` + NoticeType string `json:"noticeType" gorm:"type:varchar(1);comment:通知类型"` + OrganizationId int64 `json:"organizationId" gorm:"type:int;comment:组织Id,组织及子组织"` + UserName string `json:"userName" gorm:"type:varchar(64);comment:发布人"` + + OrganizationIds []int64 `json:"organizationIds" gorm:"-"` + model.BaseModel +} diff --git a/apps/system/entity/organization.go b/apps/system/entity/organization.go new file mode 100644 index 0000000..860dbef --- /dev/null +++ b/apps/system/entity/organization.go @@ -0,0 +1,26 @@ +package entity + +import "pandax/kit/model" + +// 组织组织 +type SysOrganization struct { + OrganizationId int64 `json:"organizationId" gorm:"primary_key;AUTO_INCREMENT"` //组织编码 + ParentId int64 `json:"parentId" gorm:"type:int;comment:上级组织"` + OrganizationPath string `json:"organizationPath" gorm:"type:varchar(255);comment:组织路径"` + OrganizationName string `json:"organizationName" gorm:"type:varchar(128);comment:组织名称"` + Sort int64 `json:"sort" gorm:"type:int;comment:排序"` + Leader string `json:"leader" gorm:"type:varchar(64);comment:负责人"` // userId + Phone string `json:"phone" gorm:"type:varchar(11);comment:手机"` + Email string `json:"email" gorm:"type:varchar(64);comment:邮箱"` + Status string `json:"status" gorm:"type:varchar(1);comment:状态"` + CreateBy string `json:"createBy" gorm:"type:varchar(64);comment:创建人"` + UpdateBy string `json:"updateBy" gorm:"type:varchar(64);comment:修改人"` + Children []SysOrganization `json:"children" gorm:"-"` + model.BaseModel +} + +type OrganizationLable struct { + OrganizationId int64 `gorm:"-" json:"organizationId"` + OrganizationName string `gorm:"-" json:"organizationName"` + Children []OrganizationLable `gorm:"-" json:"children"` +} diff --git a/apps/system/entity/post.go b/apps/system/entity/post.go new file mode 100644 index 0000000..b57e3a4 --- /dev/null +++ b/apps/system/entity/post.go @@ -0,0 +1,15 @@ +package entity + +import "pandax/kit/model" + +type SysPost struct { + PostId int64 `gorm:"primary_key;AUTO_INCREMENT" json:"postId"` + PostName string `gorm:"type:varchar(128);comment:岗位名称" json:"postName"` + PostCode string `gorm:"type:varchar(128);comment:岗位代码" json:"postCode"` + Sort int64 `gorm:"type:int;comment:岗位排序" json:"sort"` + Status string `gorm:"type:varchar(1);comment:状态" json:"status"` + Remark string `gorm:"type:varchar(255);comment:描述" json:"remark"` + CreateBy string `gorm:"type:varchar(128);" json:"createBy"` + UpdateBy string `gorm:"type:varchar(128);" json:"updateBy"` + model.BaseModel +} diff --git a/apps/system/entity/role.go b/apps/system/entity/role.go new file mode 100644 index 0000000..38121c0 --- /dev/null +++ b/apps/system/entity/role.go @@ -0,0 +1,41 @@ +package entity + +import ( + "pandax/kit/casbin" + "pandax/kit/model" +) + +const ( + SELFDATASCOPE = "0" + ALLDATASCOPE = "1" + DIYDATASCOPE = "2" + ORGDATASCOPE = "3" + ORGALLDATASCOPE = "4" +) + +type SysRole struct { + model.BaseModel + RoleId int64 `json:"roleId" gorm:"primary_key;AUTO_INCREMENT"` + RoleName string `json:"roleName" gorm:"type:varchar(128);comment:角色名称"` + Status string `json:"status" gorm:"type:varchar(1);comment:状态"` + RoleKey string `json:"roleKey" gorm:"type:varchar(128);comment:角色代码"` + RoleSort int64 `json:"roleSort" gorm:"type:int;comment:角色排序"` + DataScope string `json:"dataScope" gorm:"type:varchar(1);comment:数据范围(0: 本人数据 1:全部数据权限 2:自定数据权限 3:本组织数据权限 4:本组织及以下数据权限)"` + CreateBy string `json:"createBy" gorm:"type:varchar(128);comment:创建人"` + UpdateBy string `json:"updateBy" gorm:"type:varchar(128);comment:修改人"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + ApiIds []casbin.CasbinRule `json:"apiIds" gorm:"-"` + MenuIds []int64 `json:"menuIds" gorm:"-"` + OrganizationIds []int64 `json:"organizationIds" gorm:"-"` +} +type SysRoleAuth struct { + Org string `json:"org" gorm:"column:org"` + DataScope string `json:"dataScope"` +} +type MenuIdList struct { + MenuId int64 `json:"menuId"` +} + +type OrganizationIdList struct { + OrganizationId int64 `json:"organizationId"` +} diff --git a/apps/system/entity/role_menu.go b/apps/system/entity/role_menu.go new file mode 100644 index 0000000..1e8c89a --- /dev/null +++ b/apps/system/entity/role_menu.go @@ -0,0 +1,12 @@ +package entity + +type SysRoleMenu struct { + RoleId int64 `gorm:"type:int"` + MenuId int64 `gorm:"type:int"` + RoleName string `gorm:"type:varchar(128)"` + Id int64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" form:"id"` +} + +type MenuPath struct { + Path string `json:"path"` +} diff --git a/apps/system/entity/role_organization.go b/apps/system/entity/role_organization.go new file mode 100644 index 0000000..3c1eafc --- /dev/null +++ b/apps/system/entity/role_organization.go @@ -0,0 +1,7 @@ +package entity + +type SysRoleOrganization struct { + RoleId int64 `gorm:"type:int"` + OrganizationId int64 `gorm:"type:int"` + Id int64 `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" form:"id"` +} diff --git a/apps/system/entity/tenant.go b/apps/system/entity/tenant.go new file mode 100644 index 0000000..582af65 --- /dev/null +++ b/apps/system/entity/tenant.go @@ -0,0 +1,23 @@ +package entity + +import ( + "pandax/kit/model" + "time" +) + +/** + * @Description + * @Author 熊猫 + * @Date 2022/7/14 16:14 + **/ + +type SysTenants struct { + model.BaseAutoModel + TenantName string `json:"tenantName" gorm:"type:varchar(255);comment:租户名"` + Remark string `json:"remark" gorm:"type:varchar(255);comment:备注"` + ExpireTime time.Time `json:"expireTime" gorm:"comment:过期时间"` +} + +func (SysTenants) TableName() string { + return "sys_tenants" +} diff --git a/apps/system/entity/user.go b/apps/system/entity/user.go new file mode 100644 index 0000000..3ac6753 --- /dev/null +++ b/apps/system/entity/user.go @@ -0,0 +1,63 @@ +package entity + +import "pandax/kit/model" + +type LoginM struct { + Username string `gorm:"type:varchar(64)" json:"username"` + Password string `gorm:"type:varchar(128)" json:"password"` +} + +type SysUserId struct { + UserId int64 `gorm:"primary_key;AUTO_INCREMENT" json:"userId"` // 编码 +} + +type SysUserB struct { + NickName string `gorm:"type:varchar(128)" json:"nickName"` // 昵称 + Phone string `gorm:"type:varchar(11)" json:"phone"` // 手机号 + RoleId int64 `gorm:"type:int" json:"roleId"` // 角色编码 + Salt string `gorm:"type:varchar(255)" json:"salt"` //盐 + Avatar string `gorm:"type:varchar(255)" json:"avatar"` //头像 + Sex string `gorm:"type:varchar(255)" json:"sex"` //性别 + Email string `gorm:"type:varchar(128)" json:"email"` //邮箱 + OrganizationId int64 `gorm:"type:int" json:"organizationId"` //组织编码 + PostId int64 `gorm:"type:int" json:"postId"` //职位编码 + RoleIds string `gorm:"type:varchar(255)" json:"roleIds"` //多角色 + PostIds string `gorm:"type:varchar(255)" json:"postIds"` // 多岗位 + CreateBy string `gorm:"type:varchar(128)" json:"createBy"` // + UpdateBy string `gorm:"type:varchar(128)" json:"updateBy"` // + Remark string `gorm:"type:varchar(255)" json:"remark"` //备注 + Status string `gorm:"type:varchar(1);" json:"status"` + model.BaseModel +} + +type SysUser struct { + SysUserId + SysUserB + LoginM +} + +type SysUserPwd struct { + OldPassword string `json:"oldPassword" form:"oldPassword"` + NewPassword string `json:"newPassword" form:"newPassword"` +} + +type SysUserPage struct { + SysUserId + SysUserB + LoginM + OrganizationName string `gorm:"-" json:"organizationName"` +} + +type Login struct { + Username string `form:"username" json:"username" binding:"required"` + Password string `form:"password" json:"password" binding:"required"` + Code string `form:"code" json:"code" binding:"required"` + UUID string `form:"UUID" json:"uuid" binding:"required"` +} + +type SysUserView struct { + SysUserId + SysUserB + LoginM + RoleName string `gorm:"column:role_name" json:"role_name"` +} diff --git a/apps/system/router/api.go b/apps/system/router/api.go new file mode 100644 index 0000000..6188ed0 --- /dev/null +++ b/apps/system/router/api.go @@ -0,0 +1,87 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/casbin" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitApiRouter(container *restful.Container) { + s := &api.SystemApiApi{ + ApiApp: services.SysApiModelDao, + } + + ws := new(restful.WebService) + ws.Path("/system/api").Produces(restful.MIME_JSON) + tags := []string{"api"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取api分页列表").Handle(s.GetApiList) + }). + Doc("获取api分页列表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("path", "路径").DataType("string")). + Param(ws.QueryParameter("description", "描述").DataType("string")). + Param(ws.QueryParameter("method", "方法").DataType("string")). + Param(ws.QueryParameter("apiGroup", "API组").DataType("string")). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/all").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取所有api").Handle(s.GetAllApis) + }). + Doc("获取所有api"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.SysApi{}). + Returns(200, "OK", []entity.SysApi{})) + + ws.Route(ws.GET("/getPolicyPathByRoleId").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色拥有的api权限").Handle(s.GetPolicyPathByRoleId) + }). + Doc("获取角色拥有的api权限"). + Param(ws.QueryParameter("roleKey", "校色key").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]casbin.CasbinRule{}). + Returns(200, "OK", []casbin.CasbinRule{})) + + ws.Route(ws.GET("/{id}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取api信息").Handle(s.GetApiById) + }). + Doc("获取api信息"). + Param(ws.PathParameter("id", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysApi{}). // on the response + Returns(200, "OK", entity.SysApi{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加api信息").Handle(s.CreateApi) + }). + Doc("添加api信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysApi{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改api信息").Handle(s.UpdateApi) + }). + Doc("修改api信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysApi{})) // from the request + + ws.Route(ws.DELETE("/{id}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除api信息").Handle(s.DeleteApi) + }). + Doc("删除api信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("id", "id").DataType("int"))) + + container.Add(ws) +} diff --git a/apps/system/router/config.go b/apps/system/router/config.go new file mode 100644 index 0000000..3673efc --- /dev/null +++ b/apps/system/router/config.go @@ -0,0 +1,79 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitConfigRouter(container *restful.Container) { + s := &api.ConfigApi{ + ConfigApp: services.SysSysConfigModelDao, + } + + ws := new(restful.WebService) + ws.Path("/system/config").Produces(restful.MIME_JSON) + tags := []string{"config"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取配置分页列表").Handle(s.GetConfigList) + }). + Doc("获取配置分页列表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("configName", "configName").DataType("string")). + Param(ws.QueryParameter("configKey", "configKey").DataType("string")). + Param(ws.QueryParameter("configType", "configType").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/configKey").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取配置列表通过ConfigKey").Handle(s.GetConfigListByKey) + }). + Doc("获取配置列表通过ConfigKey"). + Param(ws.QueryParameter("configKey", "configKey").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.SysConfig{}). + Returns(200, "OK", []entity.SysConfig{})) + + ws.Route(ws.GET("/{configId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取配置信息").Handle(s.GetConfig) + }). + Doc("获取配置信息"). + Param(ws.PathParameter("configId", "configId").DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysConfig{}). + Returns(200, "OK", entity.SysConfig{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加配置信息").Handle(s.InsertConfig) + }). + Doc("添加配置信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysConfig{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改配置信息").Handle(s.UpdateConfig) + }). + Doc("修改配置信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysConfig{})) // from the request + + ws.Route(ws.DELETE("/{configId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除配置信息").Handle(s.DeleteConfig) + }). + Doc("删除配置信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("configId", "多id 1,2,3").DataType("string"))) + + container.Add(ws) + +} diff --git a/apps/system/router/dict.go b/apps/system/router/dict.go new file mode 100644 index 0000000..bba7b2d --- /dev/null +++ b/apps/system/router/dict.go @@ -0,0 +1,124 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitDictRouter(container *restful.Container) { + s := &api.DictApi{ + DictType: services.SysDictTypeModelDao, + DictData: services.SysDictDataModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/dict").Produces(restful.MIME_JSON) + tags := []string{"dict"} + + ws.Route(ws.GET("/type/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取字典类型分页列表").Handle(s.GetDictTypeList) + }). + Doc("获取字典类型分页列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("dictName", "dictName").DataType("string")). + Param(ws.QueryParameter("dictType", "dictType").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/type/{dictId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取字典类型信息").Handle(s.GetDictType) + }). + Doc("获取字典类型信息"). + Param(ws.PathParameter("dictId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", entity.SysDictType{})) + + ws.Route(ws.POST("/type").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加字典类型信息").Handle(s.InsertDictType) + }). + Doc("添加字典类型信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysDictType{})) + + ws.Route(ws.PUT("/type").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改字典类型信息").Handle(s.UpdateDictType) + }). + Doc("修改字典类型信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysDictType{})) + + ws.Route(ws.DELETE("/type/{dictId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除字典类型信息").Handle(s.DeleteDictType) + }). + Doc("删除字典类型信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("dictId", "多id 1,2,3").DataType("string"))) + + ws.Route(ws.GET("/type/export").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("导出字典类型信息").Handle(s.ExportDictType) + }). + Doc("导出字典类型信息"). + Param(ws.QueryParameter("filename", "filename").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("dictName", "dictName").DataType("string")). + Param(ws.QueryParameter("dictType", "dictType").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + ws.Route(ws.GET("/data/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取字典数据分页列表").Handle(s.GetDictDataList) + }). + Doc("获取字典数据分页列表"). + Param(ws.QueryParameter("dictLabel", "dictLabel").DataType("string")). + Param(ws.QueryParameter("dictType", "dictType").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", []entity.SysDictData{})) + + ws.Route(ws.GET("/data/type").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取字典数据列表通过字典类型").Handle(s.GetDictDataListByDictType) + }). + Doc("获取字典数据列表通过字典类型"). + Param(ws.QueryParameter("dictType", "dictType").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", []entity.SysDictData{})) + + ws.Route(ws.GET("/data/{dictCode}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取字典数据信息").Handle(s.GetDictData) + }). + Doc("获取字典数据信息"). + Param(ws.PathParameter("dictCode", "dictCode").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", entity.SysDictData{})) + + ws.Route(ws.POST("/data").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加字典数据信息").Handle(s.InsertDictData) + }). + Doc("添加字典数据信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysDictData{})) + + ws.Route(ws.PUT("/data").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改字典数据信息").Handle(s.UpdateDictData) + }). + Doc("修改字典数据信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysDictData{})) + + ws.Route(ws.DELETE("data/{dictCode}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除字典数据信息").Handle(s.DeleteDictData) + }). + Doc("删除字典数据信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("dictCode", "多id 1,2,3").DataType("string"))) + + container.Add(ws) + +} diff --git a/apps/system/router/menu.go b/apps/system/router/menu.go new file mode 100644 index 0000000..e527ea1 --- /dev/null +++ b/apps/system/router/menu.go @@ -0,0 +1,103 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/api/vo" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitMenuRouter(container *restful.Container) { + s := &api.MenuApi{ + MenuApp: services.SysMenuModelDao, + RoleApp: services.SysRoleModelDao, + RoleMenuApp: services.SysRoleMenuModelDao, + OrganizationApp: services.SysOrganizationModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/menu").Produces(restful.MIME_JSON) + tags := []string{"menu"} + + ws.Route(ws.GET("/menuTreeSelect").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取菜单树").WithNeedToken(false).WithNeedCasbin(false).Handle(s.GetMenuTreeSelect) + }). + Doc("获取菜单树"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.MenuLable{}). + Returns(200, "OK", []entity.MenuLable{})) + + ws.Route(ws.GET("/menuRole").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色菜单").Handle(s.GetMenuRole) + }). + Doc("获取角色菜单"). + Param(ws.QueryParameter("roleKey", "roleKey").Required(true).DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]vo.RouterVo{}). + Returns(200, "OK", []vo.RouterVo{})) + + ws.Route(ws.GET("/roleMenuTreeSelect/{roleId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色菜单树").Handle(s.GetMenuTreeRoleSelect) + }). + Doc("获取角色菜单树"). + Param(ws.PathParameter("roleId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(vo.MenuTreeVo{}). + Returns(200, "OK", vo.MenuTreeVo{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/menuPaths").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色菜单路径列表").Handle(s.GetMenuPaths) + }). + Doc("获取角色菜单"). + Param(ws.QueryParameter("roleKey", "roleKey").Required(true).DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.MenuPath{}). + Returns(200, "OK", []entity.MenuPath{})) + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取菜单列表").Handle(s.GetMenuList) + }). + Doc("获取菜单列表"). + Param(ws.QueryParameter("menuName", "menuName").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.SysMenu{}). + Returns(200, "OK", []entity.SysMenu{})) + + ws.Route(ws.GET("/{menuId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取菜单信息").Handle(s.GetMenu) + }). + Doc("获取菜单信息"). + Param(ws.PathParameter("menuId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysMenu{}). // on the response + Returns(200, "OK", entity.SysMenu{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加菜单信息").Handle(s.InsertMenu) + }). + Doc("添加菜单信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysMenu{})) // from the request + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改菜单信息").Handle(s.UpdateMenu) + }). + Doc("修改菜单信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysMenu{})) // from the request + + ws.Route(ws.DELETE("/{menuId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除菜单信息").Handle(s.DeleteMenu) + }). + Doc("删除SysTenant信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("menuId", "多id 1,2,3").DataType("string"))) + + container.Add(ws) +} diff --git a/apps/system/router/notice.go b/apps/system/router/notice.go new file mode 100644 index 0000000..69d14f8 --- /dev/null +++ b/apps/system/router/notice.go @@ -0,0 +1,58 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitNoticeRouter(container *restful.Container) { + s := &api.NoticeApi{ + OrganizationApp: services.SysOrganizationModelDao, + NoticeApp: services.SysNoticeModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/notice").Produces(restful.MIME_JSON) + tags := []string{"notice"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取通知分页列表").Handle(s.GetNoticeList) + }). + Doc("获取通知分页列表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("noticeType", "noticeType").DataType("string")). + Param(ws.QueryParameter("title", "title").DataType("string")). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加通知信息").Handle(s.InsertNotice) + }). + Doc("添加通知信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysNotice{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改通知信息").Handle(s.UpdateNotice) + }). + Doc("修改通知信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysNotice{})) + + ws.Route(ws.DELETE("/{noticeId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除通知信息").Handle(s.DeleteNotice) + }). + Doc("删除通知信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("noticeId", "多id 1,2,3").DataType("string"))) + + container.Add(ws) + +} diff --git a/apps/system/router/organization.go b/apps/system/router/organization.go new file mode 100644 index 0000000..efa7a17 --- /dev/null +++ b/apps/system/router/organization.go @@ -0,0 +1,91 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/api/vo" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitOrganizationRouter(container *restful.Container) { + s := &api.OrganizationApi{ + OrganizationApp: services.SysOrganizationModelDao, + RoleApp: services.SysRoleModelDao, + UserApp: services.SysUserModelDao, + } + + ws := new(restful.WebService) + ws.Path("/system/organization").Produces(restful.MIME_JSON) + tags := []string{"organization"} + + ws.Route(ws.GET("/roleOrganizationTreeSelect/{roleId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色组织树").Handle(s.GetOrganizationTreeRoleSelect) + }). + Doc("获取角色组织树"). + Param(ws.PathParameter("roleId", "角色Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(vo.OrganizationTreeVo{}). + Returns(200, "OK", vo.OrganizationTreeVo{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/organizationTree").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取所有组织树").Handle(s.GetOrganizationTree) + }). + Doc("获取所有组织树"). + Param(ws.QueryParameter("organizationName", "organizationName").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("organizationId", "organizationId").DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.SysOrganization{}). + Returns(200, "OK", []entity.SysOrganization{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取组织列表").Handle(s.GetOrganizationList) + }). + Doc("获取组织列表"). + Param(ws.QueryParameter("organizationName", "organizationName").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("organizationId", "organizationId").DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.SysOrganization{}). + Returns(200, "OK", []entity.SysOrganization{})) + + ws.Route(ws.GET("/{organizationId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取组织信息").Handle(s.GetOrganization) + }). + Doc("获取组织信息"). + Param(ws.PathParameter("organizationId", "组织Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysOrganization{}). // on the response + Returns(200, "OK", entity.SysOrganization{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加组织信息").Handle(s.InsertOrganization) + }). + Doc("添加组织信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysOrganization{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改组织信息").Handle(s.UpdateOrganization) + }). + Doc("修改组织信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysOrganization{})) + + ws.Route(ws.DELETE("/{organizationId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除组织信息").Handle(s.DeleteOrganization) + }). + Doc("删除组织信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("organizationId", "多id 1,2,3").DataType("int"))) + + container.Add(ws) + +} diff --git a/apps/system/router/post.go b/apps/system/router/post.go new file mode 100644 index 0000000..f4b590a --- /dev/null +++ b/apps/system/router/post.go @@ -0,0 +1,69 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitPostRouter(container *restful.Container) { + s := &api.PostApi{ + PostApp: services.SysPostModelDao, + UserApp: services.SysUserModelDao, + RoleApp: services.SysRoleModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/post").Produces(restful.MIME_JSON) + tags := []string{"post"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取岗位分页列表").Handle(s.GetPostList) + }). + Doc("获取岗位分页列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("postName", "postName").DataType("string")). + Param(ws.QueryParameter("postCode", "postCode").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/{postId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取岗位信息").Handle(s.GetPost) + }). + Doc("获取岗位信息"). + Param(ws.PathParameter("postId", "Id").DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysPost{}). + Returns(200, "OK", entity.SysPost{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加岗位信息").Handle(s.InsertPost) + }). + Doc("添加岗位信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysPost{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改岗位信息").Handle(s.UpdatePost) + }). + Doc("修改岗位信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysPost{})) + + ws.Route(ws.DELETE("/{postId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除岗位信息").Handle(s.DeletePost) + }). + Doc("删除岗位信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("postId", "多id 1,2,3").DataType("string"))) + + container.Add(ws) +} diff --git a/apps/system/router/role.go b/apps/system/router/role.go new file mode 100644 index 0000000..601d71b --- /dev/null +++ b/apps/system/router/role.go @@ -0,0 +1,95 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitRoleRouter(container *restful.Container) { + s := &api.RoleApi{ + RoleApp: services.SysRoleModelDao, + RoleMenuApp: services.SysRoleMenuModelDao, + OrganizationApp: services.SysOrganizationModelDao, + RoleOrganizationApp: services.SysRoleOrganizationModelDao, + UserApp: services.SysUserModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/role").Produces(restful.MIME_JSON) + tags := []string{"role"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色分页列表").Handle(s.GetRoleList) + }). + Doc("获取角色分页列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("roleName", "roleName").DataType("string")). + Param(ws.QueryParameter("roleKey", "roleKey").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/{roleId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取角色信息").Handle(s.GetRole) + }). + Doc("获取角色信息"). + Param(ws.PathParameter("roleId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysRole{}). + Returns(200, "OK", entity.SysRole{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加角色信息").Handle(s.InsertRole) + }). + Doc("添加角色信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysRole{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改角色信息").Handle(s.UpdateRole) + }). + Doc("修改角色信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysRole{})) + + ws.Route(ws.PUT("/changeStatus").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改角色状态").Handle(s.UpdateRoleStatus) + }). + Doc("修改角色状态"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysRole{})) + + ws.Route(ws.PUT("/dataScope").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改角色组织权限").Handle(s.UpdateRoleDataScope) + }). + Doc("修改角色组织权限"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysRole{})) + + ws.Route(ws.DELETE("/{roleId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除角色信息").Handle(s.DeleteRole) + }). + Doc("删除角色信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("roleId", "多id 1,2,3").DataType("string"))) + + ws.Route(ws.GET("/export").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("导出角色信息").Handle(s.ExportRole) + }). + Doc("导出角色信息"). + Param(ws.QueryParameter("filename", "filename").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("roleName", "roleName").DataType("string")). + Param(ws.QueryParameter("roleKey", "roleKey").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + container.Add(ws) +} diff --git a/apps/system/router/system.go b/apps/system/router/system.go new file mode 100644 index 0000000..40be1c2 --- /dev/null +++ b/apps/system/router/system.go @@ -0,0 +1,15 @@ +package router + +import ( + "github.com/emicklei/go-restful/v3" + "pandax/apps/system/api" +) + +func InitSystemRouter(container *restful.Container) { + s := &api.System{} + ws := new(restful.WebService) + ws.Path("/system").Produces(restful.MIME_JSON) + ws.Route(ws.GET("/").To(s.ConnectWs)) + ws.Route(ws.GET("/server").To(s.ServerInfo)) + container.Add(ws) +} diff --git a/apps/system/router/tenant.go b/apps/system/router/tenant.go new file mode 100644 index 0000000..dea9fe2 --- /dev/null +++ b/apps/system/router/tenant.go @@ -0,0 +1,78 @@ +package router + +/** + * @Description + * @Author 熊猫 + * @Date 2022/7/14 17:52 + **/ + +import ( + "pandax/apps/system/api" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "pandax/kit/model" + "pandax/kit/restfulx" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" +) + +func InitSysTenantRouter(container *restful.Container) { + s := &api.SysTenantsApi{ + SysTenantsApp: services.SysTenantModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/tenant").Produces(restful.MIME_JSON) + tags := []string{"tenant"} + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取SysTenant分页列表").Handle(s.GetSysTenantsList) + }). + Doc("获取SysTenant分页列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/lists").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取SysTenant列表").Handle(s.GetSysTenantsAll) + }). + Doc("获取SysTenant列表"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes([]entity.SysTenants{}). + Returns(200, "OK", []entity.SysTenants{})) + + ws.Route(ws.GET("/{tenantId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取SysTenant信息").Handle(s.GetSysTenants) + }). + Doc("获取SysTenant信息"). + Param(ws.PathParameter("tenantId", "租户Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(entity.SysTenants{}). + Returns(200, "OK", entity.SysTenants{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加SysTenant信息").Handle(s.InsertSysTenants) + }). + Doc("添加SysTenant信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysTenants{})) + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改SysTenant信息").Handle(s.UpdateSysTenants) + }). + Doc("修改SysTenant信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysTenants{})) + + ws.Route(ws.DELETE("/{tenantId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除SysTenant信息").Handle(s.DeleteSysTenants) + }). + Doc("删除SysTenant信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("tenantId", "多id 1,2,3").DataType("string"))) + + container.Add(ws) +} diff --git a/apps/system/router/user.go b/apps/system/router/user.go new file mode 100644 index 0000000..3d0ab74 --- /dev/null +++ b/apps/system/router/user.go @@ -0,0 +1,169 @@ +package router + +import ( + "pandax/apps/system/api" + "pandax/apps/system/api/form" + "pandax/apps/system/api/vo" + "pandax/apps/system/entity" + "pandax/apps/system/services" + + "pandax/kit/model" + + restfulspec "github.com/emicklei/go-restful-openapi/v2" + "github.com/emicklei/go-restful/v3" + + logServices "pandax/apps/log/services" + + "pandax/kit/restfulx" +) + +func InitUserRouter(container *restful.Container) { + s := &api.UserApi{ + RoleApp: services.SysRoleModelDao, + MenuApp: services.SysMenuModelDao, + RoleMenuApp: services.SysRoleMenuModelDao, + UserApp: services.SysUserModelDao, + LogLogin: logServices.LogLoginModelDao, + OrganizationApp: services.SysOrganizationModelDao, + PostApp: services.SysPostModelDao, + } + ws := new(restful.WebService) + ws.Path("/system/user").Produces(restful.MIME_JSON) + tags := []string{"user"} + + ws.Route(ws.GET("/getCaptcha").To(s.GenerateCaptcha).Doc("获取验证码")) + + ws.Route(ws.POST("/login").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedToken(false).WithNeedCasbin(false).WithLog("登录").Handle(s.Login) + }). + Doc("登录"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(form.Login{}). + Writes(vo.TokenVo{}). + Returns(200, "OK", vo.TokenVo{})) + + ws.Route(ws.POST("/access").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedToken(false).WithNeedCasbin(false).WithLog("获取Token").Handle(s.GetToken) + }). + Doc("获取Token"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(form.Login{}). + Writes(vo.TokenVo{}). + Returns(200, "OK", vo.TokenVo{})) + + ws.Route(ws.POST("/logout").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedToken(false).WithNeedCasbin(false).WithLog("退出登录").Handle(s.LogOut) + }). + Doc("退出登录"). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + ws.Route(ws.GET("/auth").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithNeedCasbin(false).WithLog("认证信息").Handle(s.Auth) + }). + Doc("认证信息"). + Param(ws.QueryParameter("username", "username").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(vo.AuthVo{}). + Returns(200, "OK", vo.AuthVo{})) + + ws.Route(ws.GET("/list").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("得到用户分页列表").Handle(s.GetSysUserList) + }). + Doc("得到用户分页列表"). + Param(ws.QueryParameter("pageNum", "页数").Required(true).DataType("int")). + Param(ws.QueryParameter("pageSize", "每页条数").Required(true).DataType("int")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("username", "username").DataType("string")). + Param(ws.QueryParameter("phone", "phone").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(model.ResultPage{}). + Returns(200, "OK", model.ResultPage{})) + + ws.Route(ws.GET("/me").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取个人信息").Handle(s.GetSysUserProfile) + }). + Doc("获取个人信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(vo.UserVo{}). + Returns(200, "OK", vo.UserVo{})) + + ws.Route(ws.GET("/getById/{userId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取用户信息").Handle(s.GetSysUser) + }). + Doc("获取用户信息"). + Param(ws.PathParameter("userId", "Id").DataType("int").DefaultValue("1")). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(vo.UserVo{}). + Returns(200, "OK", vo.UserVo{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/getInit").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取初始化角色岗位信息(添加用户初始化)").Handle(s.GetSysUserInit) + }). + Doc("获取初始化角色岗位信息(添加用户初始化)"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Writes(vo.UserRolePost{}). // on the response + Returns(200, "OK", vo.UserRolePost{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.GET("/getRoPo").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("获取用户角色岗位信息(添加用户初始化)").Handle(s.GetUserRolePost) + }). + Doc("获取用户角色岗位信息(添加用户初始化)"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Returns(200, "OK", vo.UserRolePost{}). + Returns(404, "Not Found", nil)) + + ws.Route(ws.POST("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("添加用户信息").Handle(s.InsertSysUser) + }). + Doc("添加用户信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysUser{})) // from the request + + ws.Route(ws.PUT("").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改用户信息").Handle(s.UpdateSysUser) + }). + Doc("修改用户信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysUser{})) + + ws.Route(ws.PUT("/changeStatus").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改用户状态").Handle(s.UpdateSysUserStu) + }). + Doc("修改用户状态"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Reads(entity.SysUser{})) + + ws.Route(ws.DELETE("/{userId}").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("删除用户信息").Handle(s.DeleteSysUser) + }). + Doc("删除用户信息"). + Metadata(restfulspec.KeyOpenAPITags, tags). + Param(ws.PathParameter("userId", "多id 1,2,3").DataType("string"))) + + ws.Route(ws.POST("/avatar").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改用户头像").Handle(s.InsetSysUserAvatar) + }). + Doc("修改用户头像"). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + ws.Route(ws.PUT("pwd").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("修改用户密码").Handle(s.SysUserUpdatePwd) + }). + Doc("修改用户密码"). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + ws.Route(ws.GET("/export").To(func(request *restful.Request, response *restful.Response) { + restfulx.NewReqCtx(request, response).WithLog("导出用户信息").Handle(s.ExportUser) + }). + Doc("导出用户信息"). + Param(ws.QueryParameter("filename", "filename").DataType("string")). + Param(ws.QueryParameter("status", "status").DataType("string")). + Param(ws.QueryParameter("username", "username").DataType("string")). + Param(ws.QueryParameter("phone", "phone").DataType("string")). + Metadata(restfulspec.KeyOpenAPITags, tags)) + + container.Add(ws) + +} diff --git a/apps/system/services/api.go b/apps/system/services/api.go new file mode 100644 index 0000000..b17e97d --- /dev/null +++ b/apps/system/services/api.go @@ -0,0 +1,121 @@ +package services + +import ( + "errors" + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/kit/casbin" + "pandax/pkg/global" + + "gorm.io/gorm" +) + +type ( + SysApiModel interface { + Insert(data entity.SysApi) *entity.SysApi + FindOne(id int64) *entity.SysApi + FindListPage(page, pageSize int, data entity.SysApi) (*[]entity.SysApi, int64) + FindList(data entity.SysApi) *[]entity.SysApi + Update(data entity.SysApi) *entity.SysApi + Delete(ids []int64) + } + + sysApiModelImpl struct { + table string + } +) + +var SysApiModelDao SysApiModel = &sysApiModelImpl{ + table: `sys_apis`, +} + +func (m *sysApiModelImpl) Insert(api entity.SysApi) *entity.SysApi { + err := global.Db.Table(m.table).Where("path = ? AND method = ?", api.Path, api.Method).First(&entity.SysApi{}).Error + biz.IsTrue(errors.Is(err, gorm.ErrRecordNotFound), "存在相同api") + err = global.Db.Table(m.table).Create(&api).Error + biz.ErrIsNil(err, "新增Api失败") + return &api +} + +func (m *sysApiModelImpl) FindOne(id int64) (resData *entity.SysApi) { + resData = new(entity.SysApi) + err := global.Db.Table(m.table).Where("id = ?", id).First(&resData).Error + biz.ErrIsNil(err, "查询Api失败") + return +} + +func (m *sysApiModelImpl) FindListPage(page, pageSize int, data entity.SysApi) (*[]entity.SysApi, int64) { + list := make([]entity.SysApi, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + + db := global.Db.Table(m.table) + + if data.Path != "" { + db = db.Where("path LIKE ?", "%"+data.Path+"%") + } + + if data.Description != "" { + db = db.Where("description LIKE ?", "%"+data.Description+"%") + } + + if data.Method != "" { + db = db.Where("method = ?", data.Method) + } + + if data.ApiGroup != "" { + db = db.Where("api_group = ?", data.ApiGroup) + } + + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Order("api_group").Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询配置分页列表信息失败") + return &list, total +} + +func (m *sysApiModelImpl) FindList(data entity.SysApi) *[]entity.SysApi { + list := make([]entity.SysApi, 0) + db := global.Db.Table(m.table) + + if data.Path != "" { + db = db.Where("path LIKE ?", "%"+data.Path+"%") + } + + if data.Description != "" { + db = db.Where("description LIKE ?", "%"+data.Description+"%") + } + + if data.Method != "" { + db = db.Where("method = ?", data.Method) + } + + if data.ApiGroup != "" { + db = db.Where("api_group = ?", data.ApiGroup) + } + db.Where("delete_time IS NULL") + err := db.Order("api_group").Find(&list).Error + biz.ErrIsNil(err, "查询Api列表信息失败") + return &list +} + +func (m *sysApiModelImpl) Update(api entity.SysApi) *entity.SysApi { + var oldA entity.SysApi + err := global.Db.Table(m.table).Where("id = ?", api.Id).First(&oldA).Error + biz.ErrIsNil(err, "【修改api】查询api失败") + if oldA.Path != api.Path || oldA.Method != api.Method { + err := global.Db.Table(m.table).Where("path = ? AND method = ?", api.Path, api.Method).First(&entity.SysApi{}).Error + biz.IsTrue(errors.Is(err, gorm.ErrRecordNotFound), "存在相同api路径") + } + // 异常直接抛错误 + ca := casbin.CasbinService{ModelPath: global.Conf.Casbin.ModelPath} + ca.UpdateCasbinApi(oldA.Path, api.Path, oldA.Method, api.Method) + err = global.Db.Table(m.table).Model(&api).Updates(&api).Error + biz.ErrIsNil(err, "修改api信息失败") + return &api +} + +func (m *sysApiModelImpl) Delete(ids []int64) { + err := global.Db.Table(m.table).Delete(&entity.SysApi{}, "id in (?)", ids).Error + biz.ErrIsNil(err, "删除配置信息失败") +} diff --git a/apps/system/services/config.go b/apps/system/services/config.go new file mode 100644 index 0000000..2a92035 --- /dev/null +++ b/apps/system/services/config.go @@ -0,0 +1,94 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysConfigModel interface { + Insert(data entity.SysConfig) *entity.SysConfig + FindOne(dictCode int64) *entity.SysConfig + FindListPage(page, pageSize int, data entity.SysConfig) (*[]entity.SysConfig, int64) + FindList(data entity.SysConfig) *[]entity.SysConfig + Update(data entity.SysConfig) *entity.SysConfig + Delete(dictCode []int64) + } + + sysSysConfigModelImpl struct { + table string + } +) + +var SysSysConfigModelDao SysConfigModel = &sysSysConfigModelImpl{ + table: `sys_configs`, +} + +func (m *sysSysConfigModelImpl) Insert(data entity.SysConfig) *entity.SysConfig { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "新增配置失败") + return &data +} + +func (m *sysSysConfigModelImpl) FindOne(configId int64) *entity.SysConfig { + resData := new(entity.SysConfig) + err := global.Db.Table(m.table).Where("config_id = ?", configId).First(resData).Error + biz.ErrIsNil(err, "查询配置信息失败") + return resData +} + +func (m *sysSysConfigModelImpl) FindListPage(page, pageSize int, data entity.SysConfig) (*[]entity.SysConfig, int64) { + list := make([]entity.SysConfig, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.ConfigName != "" { + db = db.Where("config_name like ?", "%"+data.ConfigName+"%") + } + if data.ConfigKey != "" { + db = db.Where("config_key like ?", "%"+data.ConfigKey+"%") + } + if data.ConfigType != "" { + db = db.Where("config_type = ?", data.ConfigType) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询配置分页列表信息失败") + return &list, total +} + +func (m *sysSysConfigModelImpl) FindList(data entity.SysConfig) *[]entity.SysConfig { + list := make([]entity.SysConfig, 0) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.ConfigName != "" { + db = db.Where("config_name like ?", "%"+data.ConfigName+"%") + } + if data.ConfigKey != "" { + db = db.Where("config_key like ?", "%"+data.ConfigKey+"%") + } + if data.ConfigType != "" { + db = db.Where("config_type = ?", data.ConfigType) + } + db.Where("delete_time IS NULL") + err := db.Order("create_time").Find(&list).Error + biz.ErrIsNil(err, "查询配置列表信息失败") + return &list +} + +func (m *sysSysConfigModelImpl) Update(data entity.SysConfig) *entity.SysConfig { + err := global.Db.Table(m.table).Model(&data).Updates(&data).Error + biz.ErrIsNil(err, "修改配置信息失败") + return &data +} + +func (m *sysSysConfigModelImpl) Delete(configIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.SysConfig{}, "config_id in (?)", configIds).Error + biz.ErrIsNil(err, "删除配置信息失败") + return +} diff --git a/apps/system/services/dict_data.go b/apps/system/services/dict_data.go new file mode 100644 index 0000000..d32d17c --- /dev/null +++ b/apps/system/services/dict_data.go @@ -0,0 +1,94 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysDictDataModel interface { + Insert(data entity.SysDictData) *entity.SysDictData + FindOne(dictCode int64) *entity.SysDictData + FindListPage(page, pageSize int, data entity.SysDictData) (*[]entity.SysDictData, int64) + FindList(data entity.SysDictData) *[]entity.SysDictData + Update(data entity.SysDictData) *entity.SysDictData + Delete(dictCode []int64) + } + + sysDictDataModelImpl struct { + table string + } +) + +var SysDictDataModelDao SysDictDataModel = &sysDictDataModelImpl{ + table: `sys_dict_data`, +} + +func (m *sysDictDataModelImpl) Insert(data entity.SysDictData) *entity.SysDictData { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "新增字典数据失败") + return &data +} + +func (m *sysDictDataModelImpl) FindOne(codeId int64) *entity.SysDictData { + resData := new(entity.SysDictData) + err := global.Db.Table(m.table).Where("dict_code = ?", codeId).First(resData).Error + biz.ErrIsNil(err, "查询字典数据信息失败") + return resData +} + +func (m *sysDictDataModelImpl) FindListPage(page, pageSize int, data entity.SysDictData) (*[]entity.SysDictData, int64) { + list := make([]entity.SysDictData, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.DictLabel != "" { + db = db.Where("dict_label = ?", data.DictLabel) + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + if data.DictType != "" { + db = db.Where("dict_type = ?", data.DictType) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Order("dict_sort").Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询字典数据分页列表信息失败") + return &list, total +} + +func (m *sysDictDataModelImpl) FindList(data entity.SysDictData) *[]entity.SysDictData { + list := make([]entity.SysDictData, 0) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.DictLabel != "" { + db = db.Where("dict_label like ?", "%"+data.DictLabel+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + if data.DictType != "" { + db = db.Where("dict_type = ?", data.DictType) + } + db.Where("delete_time IS NULL") + err := db.Order("dict_sort").Find(&list).Error + biz.ErrIsNil(err, "查询字典数据列表信息失败") + return &list +} + +func (m *sysDictDataModelImpl) Update(data entity.SysDictData) *entity.SysDictData { + err := global.Db.Table(m.table).Where("dict_code = ?", data.DictCode).Updates(&data).Error + biz.ErrIsNil(err, "修改字典数据信息失败") + return &data +} + +func (m *sysDictDataModelImpl) Delete(codeIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.SysOrganization{}, "dict_code in (?)", codeIds).Error + biz.ErrIsNil(err, "删除字典数据信息失败") + return +} diff --git a/apps/system/services/dict_type.go b/apps/system/services/dict_type.go new file mode 100644 index 0000000..12eee1d --- /dev/null +++ b/apps/system/services/dict_type.go @@ -0,0 +1,94 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysDictTypeModel interface { + Insert(data entity.SysDictType) *entity.SysDictType + FindOne(organizationId int64) *entity.SysDictType + FindListPage(page, pageSize int, data entity.SysDictType) (*[]entity.SysDictType, int64) + FindList(data entity.SysDictType) *[]entity.SysDictType + Update(data entity.SysDictType) *entity.SysDictType + Delete(organizationId []int64) + } + + sysDictTypeModelImpl struct { + table string + } +) + +var SysDictTypeModelDao SysDictTypeModel = &sysDictTypeModelImpl{ + table: `sys_dict_types`, +} + +func (m *sysDictTypeModelImpl) Insert(data entity.SysDictType) *entity.SysDictType { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "新增字典类型失败") + return &data +} + +func (m *sysDictTypeModelImpl) FindOne(dictId int64) *entity.SysDictType { + resData := new(entity.SysDictType) + err := global.Db.Table(m.table).Where("dict_id = ?", dictId).First(resData).Error + biz.ErrIsNil(err, "查询字典类型信息失败") + return resData +} + +func (m *sysDictTypeModelImpl) FindListPage(page, pageSize int, data entity.SysDictType) (*[]entity.SysDictType, int64) { + list := make([]entity.SysDictType, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.DictName != "" { + db = db.Where("dict_name like ?", "%"+data.DictName+"%") + } + if data.DictType != "" { + db = db.Where("dict_type like ?", "%"+data.DictType+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询字典类型分页列表信息失败") + return &list, total +} + +func (m *sysDictTypeModelImpl) FindList(data entity.SysDictType) *[]entity.SysDictType { + list := make([]entity.SysDictType, 0) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.DictName != "" { + db = db.Where("dict_name like ?", "%"+data.DictName+"%") + } + if data.DictType != "" { + db = db.Where("dict_type like ?", "%"+data.DictType+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + db.Where("delete_time IS NULL") + err := db.Order("create_time").Find(&list).Error + biz.ErrIsNil(err, "查询字典类型列表信息失败") + return &list +} + +func (m *sysDictTypeModelImpl) Update(data entity.SysDictType) *entity.SysDictType { + err := global.Db.Table(m.table).Where("dict_id = ?", data.DictId).Updates(&data).Error + biz.ErrIsNil(err, "修改字典类型信息失败") + return &data +} + +func (m *sysDictTypeModelImpl) Delete(dictIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.SysDictType{}, "dict_id in (?)", dictIds).Error + biz.ErrIsNil(err, "删除字典类型信息失败") + return +} diff --git a/apps/system/services/menu.go b/apps/system/services/menu.go new file mode 100644 index 0000000..9ed2bd3 --- /dev/null +++ b/apps/system/services/menu.go @@ -0,0 +1,235 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysMenuModel interface { + Insert(data entity.SysMenu) *entity.SysMenu + FindOne(menuId int64) *entity.SysMenu + FindListPage(page, pageSize int, data entity.SysMenu) (*[]entity.SysMenu, int64) + FindList(data entity.SysMenu) *[]entity.SysMenu + Update(data entity.SysMenu) *entity.SysMenu + Delete(menuId []int64) + SelectMenu(data entity.SysMenu) *[]entity.SysMenu + SelectMenuLable(data entity.SysMenu) *[]entity.MenuLable + SelectMenuRole(roleName string) *[]entity.SysMenu + GetMenuRole(data entity.MenuRole) *[]entity.MenuRole + } + + sysMenuModelImpl struct { + table string + } +) + +var SysMenuModelDao SysMenuModel = &sysMenuModelImpl{ + table: `sys_menus`, +} + +func (m *sysMenuModelImpl) Insert(data entity.SysMenu) *entity.SysMenu { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "添加菜单失败") + return &data +} + +func (m *sysMenuModelImpl) FindOne(menuId int64) *entity.SysMenu { + resData := new(entity.SysMenu) + err := global.Db.Table(m.table).Where("menu_id = ?", menuId).First(resData).Error + biz.ErrIsNil(err, "查询菜单失败") + return resData +} + +func (m *sysMenuModelImpl) FindListPage(page, pageSize int, data entity.SysMenu) (*[]entity.SysMenu, int64) { + list := make([]entity.SysMenu, 0) + var total int64 = 0 + + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询分页菜单失败") + return &list, total +} + +func (m *sysMenuModelImpl) FindList(data entity.SysMenu) *[]entity.SysMenu { + list := make([]entity.SysMenu, 0) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.MenuName != "" { + db = db.Where("menu_name like ?", "%"+data.MenuName+"%") + } + if data.Path != "" { + db = db.Where("path = ?", data.Path) + } + if data.MenuType != "" { + db = db.Where("menu_type = ?", data.MenuType) + } + if data.Title != "" { + db = db.Where("title like ?", "%"+data.Title+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + db.Where("delete_time IS NULL") + err := db.Order("sort").Find(&list).Error + biz.ErrIsNil(err, "查询菜单列表失败") + return &list +} + +func (m *sysMenuModelImpl) Update(data entity.SysMenu) *entity.SysMenu { + err := global.Db.Table(m.table).Select("*").Updates(data).Error + biz.ErrIsNil(err, "修改菜单失败") + return &data +} + +func (m *sysMenuModelImpl) Delete(menuIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.SysMenu{}, "menu_id in (?)", menuIds).Error + biz.ErrIsNil(err, "修改菜单失败") + return +} + +func (m *sysMenuModelImpl) SelectMenu(data entity.SysMenu) *[]entity.SysMenu { + menuList := m.FindList(data) + + redData := make([]entity.SysMenu, 0) + ml := *menuList + for i := 0; i < len(ml); i++ { + if ml[i].ParentId != 0 { + continue + } + menusInfo := DiguiMenu(menuList, ml[i]) + redData = append(redData, menusInfo) + } + return &redData +} + +func (m *sysMenuModelImpl) SelectMenuLable(data entity.SysMenu) *[]entity.MenuLable { + menuList := m.FindList(data) + + redData := make([]entity.MenuLable, 0) + ml := *menuList + for i := 0; i < len(ml); i++ { + if ml[i].ParentId != 0 { + continue + } + e := entity.MenuLable{} + e.MenuId = ml[i].MenuId + e.MenuName = ml[i].MenuName + menusInfo := DiguiMenuLable(menuList, e) + + redData = append(redData, menusInfo) + } + return &redData +} + +func (m *sysMenuModelImpl) GetMenuByRoleKey(roleKey string) *[]entity.SysMenu { + menus := make([]entity.SysMenu, 0) + db := global.Db.Table(m.table).Select("sys_menus.*").Joins("left join sys_role_menus on sys_role_menus.menu_id=sys_menus.menu_id") + db = db.Where("sys_role_menus.role_name=? and menu_type in ('M','C')", roleKey) + db.Where("sys_menus.delete_time IS NULL") + err := db.Order("sort").Find(&menus).Error + biz.ErrIsNil(err, "通过角色名查询菜单失败") + return &menus +} + +func (m *sysMenuModelImpl) SelectMenuRole(roleKey string) *[]entity.SysMenu { + redData := make([]entity.SysMenu, 0) + + menulist := m.GetMenuByRoleKey(roleKey) + menuList := *menulist + redData = make([]entity.SysMenu, 0) + for i := 0; i < len(menuList); i++ { + if menuList[i].ParentId != 0 { + continue + } + menusInfo := DiguiMenu(&menuList, menuList[i]) + + redData = append(redData, menusInfo) + } + return &redData +} +func (m *sysMenuModelImpl) GetMenuRole(data entity.MenuRole) *[]entity.MenuRole { + menus := make([]entity.MenuRole, 0) + + db := global.Db.Table(m.table) + if data.MenuName != "" { + db = db.Where("menu_name = ?", data.MenuName) + } + db.Where("delete_time IS NULL") + biz.ErrIsNil(db.Order("sort").Find(&menus).Error, "查询角色菜单失败") + return &menus +} + +func DiguiMenu(menulist *[]entity.SysMenu, menu entity.SysMenu) entity.SysMenu { + list := *menulist + + min := make([]entity.SysMenu, 0) + for j := 0; j < len(list); j++ { + + if menu.MenuId != list[j].ParentId { + continue + } + mi := entity.SysMenu{} + mi.MenuId = list[j].MenuId + mi.MenuName = list[j].MenuName + mi.Title = list[j].Title + mi.Icon = list[j].Icon + mi.Path = list[j].Path + mi.MenuType = list[j].MenuType + mi.IsKeepAlive = list[j].IsKeepAlive + mi.Permission = list[j].Permission + mi.ParentId = list[j].ParentId + mi.IsAffix = list[j].IsAffix + mi.IsIframe = list[j].IsIframe + mi.IsLink = list[j].IsLink + mi.Component = list[j].Component + mi.Sort = list[j].Sort + mi.Status = list[j].Status + mi.IsHide = list[j].IsHide + mi.CreatedAt = list[j].CreatedAt + mi.UpdatedAt = list[j].UpdatedAt + mi.Children = []entity.SysMenu{} + + if mi.MenuType != "F" { + ms := DiguiMenu(menulist, mi) + min = append(min, ms) + + } else { + min = append(min, mi) + } + + } + menu.Children = min + return menu +} + +func DiguiMenuLable(menulist *[]entity.SysMenu, menu entity.MenuLable) entity.MenuLable { + list := *menulist + + min := make([]entity.MenuLable, 0) + for j := 0; j < len(list); j++ { + + if menu.MenuId != list[j].ParentId { + continue + } + mi := entity.MenuLable{} + mi.MenuId = list[j].MenuId + mi.MenuName = list[j].MenuName + mi.Children = []entity.MenuLable{} + if list[j].MenuType != "F" { + ms := DiguiMenuLable(menulist, mi) + min = append(min, ms) + } else { + min = append(min, mi) + } + + } + menu.Children = min + return menu +} diff --git a/apps/system/services/notice.go b/apps/system/services/notice.go new file mode 100644 index 0000000..9b86993 --- /dev/null +++ b/apps/system/services/notice.go @@ -0,0 +1,69 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysNoticeModel interface { + Insert(data entity.SysNotice) *entity.SysNotice + FindOne(postId int64) *entity.SysNotice + FindListPage(page, pageSize int, data entity.SysNotice) (*[]entity.SysNotice, int64) + Update(data entity.SysNotice) *entity.SysNotice + Delete(postId []int64) + } + + sysNoticeModelImpl struct { + table string + } +) + +var SysNoticeModelDao SysNoticeModel = &sysNoticeModelImpl{ + table: `sys_notices`, +} + +func (m *sysNoticeModelImpl) Insert(data entity.SysNotice) *entity.SysNotice { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "添加通知失败") + return &data +} + +func (m *sysNoticeModelImpl) FindOne(postId int64) *entity.SysNotice { + resData := new(entity.SysNotice) + err := global.Db.Table(m.table).Where("post_id = ?", postId).First(resData).Error + biz.ErrIsNil(err, "查询通知失败") + return resData +} + +func (m *sysNoticeModelImpl) FindListPage(page, pageSize int, data entity.SysNotice) (*[]entity.SysNotice, int64) { + list := make([]entity.SysNotice, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.Title != "" { + db = db.Where("title like ?", "%"+data.Title+"%") + } + if data.NoticeType != "" { + db = db.Where("notice_type = ?", data.NoticeType) + } + if len(data.OrganizationIds) > 0 { + db = db.Where("organization_id in (?)", data.OrganizationIds) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Order("create_time").Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询通知分页列表失败") + return &list, total +} + +func (m *sysNoticeModelImpl) Update(data entity.SysNotice) *entity.SysNotice { + biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改通知失败") + return &data +} + +func (m *sysNoticeModelImpl) Delete(postIds []int64) { + biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.SysNotice{}, "notice_id in (?)", postIds).Error, "删除通知失败") +} diff --git a/apps/system/services/organization.go b/apps/system/services/organization.go new file mode 100644 index 0000000..56875f1 --- /dev/null +++ b/apps/system/services/organization.go @@ -0,0 +1,238 @@ +package services + +import ( + "errors" + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" + + "github.com/kakuilan/kgo" +) + +type ( + SysOrganizationModel interface { + Insert(data entity.SysOrganization) *entity.SysOrganization + FindOne(organizationId int64) *entity.SysOrganization + FindListPage(page, pageSize int, data entity.SysOrganization) (*[]entity.SysOrganization, int64) + FindList(data entity.SysOrganization) *[]entity.SysOrganization + Update(data entity.SysOrganization) *entity.SysOrganization + Delete(organizationId []int64) + SelectOrganization(data entity.SysOrganization) []entity.SysOrganization + SelectOrganizationLable(data entity.SysOrganization) []entity.OrganizationLable + SelectOrganizationIds(data entity.SysOrganization) []int64 + } + + sysOrganizationModelImpl struct { + table string + } +) + +var SysOrganizationModelDao SysOrganizationModel = &sysOrganizationModelImpl{ + table: `sys_organizations`, +} + +func (m *sysOrganizationModelImpl) Insert(data entity.SysOrganization) *entity.SysOrganization { + biz.ErrIsNil(global.Db.Table(m.table).Create(&data).Error, "新增组织信息失败") + organizationPath := "/" + kgo.KConv.Int2Str(data.OrganizationId) + if int(data.ParentId) != 0 { + organizationP := m.FindOne(data.ParentId) + organizationPath = organizationP.OrganizationPath + organizationPath + } else { + organizationPath = "/0" + organizationPath + } + data.OrganizationPath = organizationPath + biz.ErrIsNil(global.Db.Table(m.table).Model(&data).Updates(&data).Error, "修改组织信息失败") + return &data +} + +func (m *sysOrganizationModelImpl) FindOne(organizationId int64) *entity.SysOrganization { + resData := new(entity.SysOrganization) + err := global.Db.Table(m.table).Where("organization_id = ?", organizationId).First(resData).Error + biz.ErrIsNil(err, "查询组织信息失败") + return resData +} + +func (m *sysOrganizationModelImpl) FindListPage(page, pageSize int, data entity.SysOrganization) (*[]entity.SysOrganization, int64) { + list := make([]entity.SysOrganization, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.OrganizationId != 0 { + db = db.Where("organization_id = ?", data.OrganizationId) + } + if data.OrganizationName != "" { + db = db.Where("organization_name like ?", "%"+data.OrganizationName+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + if data.OrganizationPath != "" { + db = db.Where("organizationPath like %?%", data.OrganizationPath) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询组织分页列表信息失败") + return &list, total +} + +func (m *sysOrganizationModelImpl) FindList(data entity.SysOrganization) *[]entity.SysOrganization { + list := make([]entity.SysOrganization, 0) + + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.OrganizationId != 0 { + db = db.Where("organization_id = ?", data.OrganizationId) + } + if data.OrganizationName != "" { + db = db.Where("organization_name like ?", "%"+data.OrganizationName+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + db.Where("delete_time IS NULL") + err := db.Order("sort").Find(&list).Error + biz.ErrIsNil(err, "查询组织列表信息失败") + return &list +} + +func (m *sysOrganizationModelImpl) Update(data entity.SysOrganization) *entity.SysOrganization { + one := m.FindOne(data.OrganizationId) + + organizationPath := "/" + kgo.KConv.Int2Str(data.OrganizationId) + if int(data.ParentId) != 0 { + organizationP := m.FindOne(data.ParentId) + organizationPath = organizationP.OrganizationPath + organizationPath + } else { + organizationPath = "/0" + organizationPath + } + data.OrganizationPath = organizationPath + + if data.OrganizationPath != "" && data.OrganizationPath != one.OrganizationPath { + biz.ErrIsNil(errors.New("上级组织不允许修改!"), "上级组织不允许修改") + } + biz.ErrIsNil(global.Db.Table(m.table).Model(&data).Updates(&data).Error, "修改组织信息失败") + return &data +} + +func (m *sysOrganizationModelImpl) Delete(organizationIds []int64) { + err := global.Db.Table(m.table).Delete(&entity.SysOrganization{}, "organization_id in (?)", organizationIds).Error + biz.ErrIsNil(err, "删除组织信息失败") + return +} + +func (m *sysOrganizationModelImpl) SelectOrganization(data entity.SysOrganization) []entity.SysOrganization { + list := m.FindList(data) + + sd := make([]entity.SysOrganization, 0) + li := *list + for i := 0; i < len(li); i++ { + if li[i].ParentId != 0 { + continue + } + info := Digui(list, li[i]) + + sd = append(sd, info) + } + return sd +} + +func (m *sysOrganizationModelImpl) SelectOrganizationLable(data entity.SysOrganization) []entity.OrganizationLable { + organizationlist := m.FindList(data) + + dl := make([]entity.OrganizationLable, 0) + organizationl := *organizationlist + for i := 0; i < len(organizationl); i++ { + if organizationl[i].ParentId != 0 { + continue + } + e := entity.OrganizationLable{} + e.OrganizationId = organizationl[i].OrganizationId + e.OrganizationName = organizationl[i].OrganizationName + organizationsInfo := DiguiOrganizationLable(organizationlist, e) + + dl = append(dl, organizationsInfo) + } + return dl +} + +func (m *sysOrganizationModelImpl) SelectOrganizationIds(data entity.SysOrganization) []int64 { + organizationlist := m.FindList(data) + dl := make([]int64, 0) + organizationl := *organizationlist + for i := 0; i < len(organizationl); i++ { + if organizationl[i].ParentId != 0 { + continue + } + dl = append(dl, organizationl[i].OrganizationId) + e := entity.OrganizationLable{} + e.OrganizationId = organizationl[i].OrganizationId + e.OrganizationName = organizationl[i].OrganizationName + id := DiguiOrganizationId(organizationlist, e) + dl = append(dl, id...) + } + return dl +} + +func Digui(organizationlist *[]entity.SysOrganization, menu entity.SysOrganization) entity.SysOrganization { + list := *organizationlist + + min := make([]entity.SysOrganization, 0) + for j := 0; j < len(list); j++ { + + if menu.OrganizationId != list[j].ParentId { + continue + } + mi := entity.SysOrganization{} + mi.OrganizationId = list[j].OrganizationId + mi.ParentId = list[j].ParentId + mi.OrganizationPath = list[j].OrganizationPath + mi.OrganizationName = list[j].OrganizationName + mi.Sort = list[j].Sort + mi.Leader = list[j].Leader + mi.Phone = list[j].Phone + mi.Email = list[j].Email + mi.Status = list[j].Status + mi.CreatedAt = list[j].CreatedAt + mi.UpdatedAt = list[j].UpdatedAt + mi.Children = []entity.SysOrganization{} + ms := Digui(organizationlist, mi) + min = append(min, ms) + } + menu.Children = min + return menu +} +func DiguiOrganizationLable(organizationlist *[]entity.SysOrganization, organization entity.OrganizationLable) entity.OrganizationLable { + list := *organizationlist + + min := make([]entity.OrganizationLable, 0) + for j := 0; j < len(list); j++ { + + if organization.OrganizationId != list[j].ParentId { + continue + } + mi := entity.OrganizationLable{list[j].OrganizationId, list[j].OrganizationName, []entity.OrganizationLable{}} + ms := DiguiOrganizationLable(organizationlist, mi) + min = append(min, ms) + + } + organization.Children = min + return organization +} + +func DiguiOrganizationId(organizationlist *[]entity.SysOrganization, organization entity.OrganizationLable) []int64 { + list := *organizationlist + min := make([]int64, 0) + for j := 0; j < len(list); j++ { + if organization.OrganizationId != list[j].ParentId { + continue + } + min = append(min, list[j].OrganizationId) + mi := entity.OrganizationLable{list[j].OrganizationId, list[j].OrganizationName, []entity.OrganizationLable{}} + id := DiguiOrganizationId(organizationlist, mi) + min = append(min, id...) + } + return min +} diff --git a/apps/system/services/post.go b/apps/system/services/post.go new file mode 100644 index 0000000..786a8c9 --- /dev/null +++ b/apps/system/services/post.go @@ -0,0 +1,94 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysPostModel interface { + Insert(data entity.SysPost) *entity.SysPost + FindOne(postId int64) *entity.SysPost + FindListPage(page, pageSize int, data entity.SysPost) (*[]entity.SysPost, int64) + FindList(data entity.SysPost) *[]entity.SysPost + Update(data entity.SysPost) *entity.SysPost + Delete(postId []int64) + } + + sysPostModelImpl struct { + table string + } +) + +var SysPostModelDao SysPostModel = &sysPostModelImpl{ + table: `sys_posts`, +} + +func (m *sysPostModelImpl) Insert(data entity.SysPost) *entity.SysPost { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "添加岗位失败") + return &data +} + +func (m *sysPostModelImpl) FindOne(postId int64) *entity.SysPost { + resData := new(entity.SysPost) + err := global.Db.Table(m.table).Where("post_id = ?", postId).First(resData).Error + biz.ErrIsNil(err, "查询岗位失败") + return resData +} + +func (m *sysPostModelImpl) FindListPage(page, pageSize int, data entity.SysPost) (*[]entity.SysPost, int64) { + list := make([]entity.SysPost, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.PostId != 0 { + db = db.Where("post_id = ?", data.PostId) + } + if data.PostName != "" { + db = db.Where("post_name like ?", "%"+data.PostName+"%") + } + if data.PostCode != "" { + db = db.Where("post_code like ?", "%"+data.PostCode+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Order("sort").Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询岗位分页列表失败") + return &list, total +} + +func (m *sysPostModelImpl) FindList(data entity.SysPost) *[]entity.SysPost { + list := make([]entity.SysPost, 0) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.PostId != 0 { + db = db.Where("post_id = ?", data.PostId) + } + if data.PostName != "" { + db = db.Where("post_name = ?", data.PostName) + } + if data.PostCode != "" { + db = db.Where("post_code = ?", data.PostCode) + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + db.Where("delete_time IS NULL") + biz.ErrIsNil(db.Order("sort").Find(&list).Error, "查询岗位列表失败") + return &list +} + +func (m *sysPostModelImpl) Update(data entity.SysPost) *entity.SysPost { + biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改岗位失败") + return &data +} + +func (m *sysPostModelImpl) Delete(postIds []int64) { + biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.SysPost{}, "post_id in (?)", postIds).Error, "删除岗位失败") +} diff --git a/apps/system/services/role.go b/apps/system/services/role.go new file mode 100644 index 0000000..274b0c5 --- /dev/null +++ b/apps/system/services/role.go @@ -0,0 +1,143 @@ +package services + +import ( + "errors" + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysRoleModel interface { + Insert(data entity.SysRole) *entity.SysRole + FindOne(roleId int64) *entity.SysRole + FindListPage(page, pageSize int, data entity.SysRole) (list *[]entity.SysRole, total int64) + FindList(data entity.SysRole) (list *[]entity.SysRole) + Update(data entity.SysRole) *entity.SysRole + Delete(roleId []int64) + GetRoleMeunId(data entity.SysRole) []int64 + GetRoleOrganizationId(data entity.SysRole) []int64 + FindOrganizationsByRoleId(roleId int64) (entity.SysRoleAuth, error) + } + + sysRoleModel struct { + table string + } +) + +var SysRoleModelDao SysRoleModel = &sysRoleModel{ + table: `sys_roles`, +} + +func (m *sysRoleModel) Insert(data entity.SysRole) *entity.SysRole { + var i int64 = 0 + global.Db.Table(m.table).Where("(role_name = ? or role_key = ?) and delete_time IS NULL", data.RoleName, data.RoleKey).Count(&i) + biz.IsTrue(i == 0, "角色名称或者角色标识已经存在!") + + data.UpdateBy = "" + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "添加角色失败") + + return &data +} + +func (m *sysRoleModel) FindOne(roleId int64) *entity.SysRole { + resData := new(entity.SysRole) + biz.ErrIsNil(global.Db.Table(m.table).Where("role_id = ?", roleId).First(resData).Error, "查询角色失败") + return resData +} + +func (m *sysRoleModel) FindListPage(page, pageSize int, data entity.SysRole) (*[]entity.SysRole, int64) { + + list := make([]entity.SysRole, 0) + var total int64 = 0 + + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.RoleId != 0 { + db = db.Where("role_id = ?", data.RoleId) + } + if data.RoleName != "" { + db = db.Where("role_name like ?", "%"+data.RoleName+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + if data.RoleKey != "" { + db = db.Where("role_key like ?", "%"+data.RoleKey+"%") + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Order("role_sort").Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询角色分页列表失败") + return &list, total +} + +func (m *sysRoleModel) FindList(data entity.SysRole) *[]entity.SysRole { + list := make([]entity.SysRole, 0) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.RoleName != "" { + db = db.Where("role_name like ?", "%"+data.RoleName+"%") + } + if data.Status != "" { + db = db.Where("status = ?", data.Status) + } + if data.RoleKey != "" { + db = db.Where("role_key like ?", "%"+data.RoleKey+"%") + } + db.Where("delete_time IS NULL") + biz.ErrIsNil(db.Order("role_sort").Find(&list).Error, "查询角色列表失败") + return &list +} + +func (m *sysRoleModel) Update(data entity.SysRole) *entity.SysRole { + update := new(entity.SysRole) + biz.ErrIsNil(global.Db.Table(m.table).First(update, data.RoleId).Error, "查询角色失败") + if data.RoleKey != "" && data.RoleKey != update.RoleKey { + biz.ErrIsNil(errors.New("角色标识不允许修改!"), "角色标识不允许修改!") + } + biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改角色失败") + return &data +} + +func (m *sysRoleModel) Delete(roleIds []int64) { + biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.SysRole{}, "role_id in (?)", roleIds).Error, "删除角色失败") + return +} + +// 获取角色对应的菜单ids +func (m *sysRoleModel) GetRoleMeunId(data entity.SysRole) []int64 { + menuIds := make([]int64, 0) + menuList := make([]entity.MenuIdList, 0) + err := global.Db.Table("sys_menus").Select("sys_menus.menu_id").Joins("LEFT JOIN sys_role_menus on sys_role_menus.menu_id=sys_menus.menu_id").Where("sys_role_menus.role_id = ? ", data.RoleId).Where("sys_menus.menu_id not in (select sys_menus.parent_id from sys_menus LEFT JOIN sys_role_menus on sys_menus.menu_id=sys_role_menus.menu_id where sys_role_menus.role_id =? )", data.RoleId).Find(&menuList).Error + + biz.ErrIsNil(err, "查询角色菜单列表失败") + for i := 0; i < len(menuList); i++ { + menuIds = append(menuIds, menuList[i].MenuId) + } + return menuIds +} + +func (m *sysRoleModel) GetRoleOrganizationId(data entity.SysRole) []int64 { + organizationIds := make([]int64, 0) + organizationList := make([]entity.OrganizationIdList, 0) + err := global.Db.Table("sys_role_organizations").Select("sys_role_organizations.organization_id").Joins("LEFT JOIN sys_organizations on sys_organizations.organization_id=sys_role_organizations.organization_id").Where("role_id = ? ", data.RoleId).Where(" sys_role_organizations.organization_id not in(select sys_organizations.parent_id from sys_role_organizations LEFT JOIN sys_organizations on sys_organizations.organization_id=sys_role_organizations.organization_id where role_id =? )", data.RoleId).Find(&organizationList).Error + biz.ErrIsNil(err, "查询角色组织列表失败") + + for i := 0; i < len(organizationList); i++ { + organizationIds = append(organizationIds, organizationList[i].OrganizationId) + } + return organizationIds +} + +func (m *sysRoleModel) FindOrganizationsByRoleId(roleId int64) (entity.SysRoleAuth, error) { + var roleData entity.SysRoleAuth + GROUP_CONCAT := "GROUP_CONCAT(sys_role_organizations.organization_id) as org" + if global.Conf.Server.DbType == "postgresql" { + GROUP_CONCAT = "string_agg(CAST(sys_role_organizations.organization_id AS VARCHAR), ',') as org" + } + err := global.Db.Raw("SELECT sys_roles.data_scope, "+GROUP_CONCAT+" FROM sys_roles LEFT JOIN sys_role_organizations ON sys_roles.role_id = sys_role_organizations.role_id WHERE sys_roles.role_id = ? GROUP BY sys_roles.role_id", roleId).Scan(&roleData).Error + return roleData, err +} diff --git a/apps/system/services/role_menu.go b/apps/system/services/role_menu.go new file mode 100644 index 0000000..0e88aa1 --- /dev/null +++ b/apps/system/services/role_menu.go @@ -0,0 +1,124 @@ +package services + +import ( + "fmt" + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysRoleMenuModel interface { + Insert(roleId int64, menuId []int64) bool + FindList(data entity.SysRoleMenu) *[]entity.SysRoleMenu + Update(data entity.SysRoleMenu) *entity.SysRoleMenu + Delete(RoleId int64, MenuID int64) + GetPermis(roleId int64) []string + GetMenuPaths(rm entity.SysRoleMenu) []entity.MenuPath + DeleteRoleMenu(RoleId int64) + DeleteRoleMenus(roleIds []int64) + } + + sysRoleMenuImpl struct { + table string + } +) + +var SysRoleMenuModelDao SysRoleMenuModel = &sysRoleMenuImpl{ + table: `sys_role_menus`, +} + +func (m *sysRoleMenuImpl) Insert(roleId int64, menuId []int64) bool { + + var role entity.SysRole + biz.ErrIsNil(global.Db.Table("sys_roles").Where("role_id = ?", roleId).First(&role).Error, "查询角色失败") + + var menu []entity.SysMenu + biz.ErrIsNil(global.Db.Table("sys_menus").Where("menu_id in (?)", menuId).Find(&menu).Error, "查询菜单失败") + + //拼接 sql 串 + sql := "INSERT INTO sys_role_menus (role_id,menu_id,role_name) VALUES " + + for i := 0; i < len(menu); i++ { + if len(menu)-1 == i { + //最后一条数据 以分号结尾 + sql += fmt.Sprintf("(%d,%d,'%s');", role.RoleId, menu[i].MenuId, role.RoleKey) + } else { + sql += fmt.Sprintf("(%d,%d,'%s'),", role.RoleId, menu[i].MenuId, role.RoleKey) + } + } + biz.ErrIsNil(global.Db.Exec(sql).Error, "新增角色菜单失败") + + return true +} + +func (m *sysRoleMenuImpl) FindList(data entity.SysRoleMenu) *[]entity.SysRoleMenu { + list := make([]entity.SysRoleMenu, 0) + db := global.Db.Table(m.table) + // 此处填写 where参数判断 + if data.RoleId != 0 { + db = db.Where("role_id = ?", data.RoleId) + } + biz.ErrIsNil(db.Find(&list).Error, "查询角色菜单失败") + return &list +} + +// 查询权限标识 +func (m *sysRoleMenuImpl) GetPermis(roleId int64) []string { + var r []entity.SysMenu + db := global.Db.Select("sys_menus.permission").Table("sys_menus").Joins("left join sys_role_menus on sys_menus.menu_id = sys_role_menus.menu_id") + + db = db.Where("role_id = ?", roleId) + + db = db.Where("sys_menus.menu_type in ('F','C')") + + biz.ErrIsNil(db.Find(&r).Error, "查询查询权限标识列表失败") + + var list []string + for i := 0; i < len(r); i++ { + list = append(list, r[i].Permission) + } + return list +} + +func (m *sysRoleMenuImpl) GetMenuPaths(rm entity.SysRoleMenu) []entity.MenuPath { + var r []entity.MenuPath + db := global.Db.Select("sys_menus.path").Table(m.table) + db = db.Joins("left join sys_roles on sys_roles.role_id=sys_role_menus.role_id") + db = db.Joins("left join sys_menus on sys_menus.id=sys_role_menus.menu_id") + db = db.Where("sys_roles.role_key = ? and sys_menus.type=1", rm.RoleName) + + biz.ErrIsNil(db.Find(&r).Error, "查询菜单路径失败") + return r +} + +func (m *sysRoleMenuImpl) Update(data entity.SysRoleMenu) *entity.SysRoleMenu { + biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改菜单失败") + return &data +} + +func (m *sysRoleMenuImpl) DeleteRoleMenu(roleId int64) { + var rm entity.SysRoleMenu + if err := global.Db.Table(m.table).Where("role_id = ?", roleId).Delete(&rm).Error; err != nil { + biz.ErrIsNil(err, "删除角色菜单失败") + } + return +} + +func (m *sysRoleMenuImpl) DeleteRoleMenus(roleIds []int64) { + var rm entity.SysRoleMenu + biz.ErrIsNil(global.Db.Table(m.table).Where("role_id in (?)", roleIds).Delete(&rm).Error, "批量删除角色菜单失败") +} + +func (m *sysRoleMenuImpl) Delete(RoleId int64, MenuID int64) { + var rm entity.SysRoleMenu + rm.RoleId = RoleId + db := global.Db.Table(m.table).Where("role_id = ?", RoleId) + if MenuID != 0 { + db = db.Where("menu_id = ?", MenuID) + } + + biz.ErrIsNil(db.Delete(&rm).Error, "删除角色菜单失败") + return + +} diff --git a/apps/system/services/role_organization.go b/apps/system/services/role_organization.go new file mode 100644 index 0000000..6e58cc6 --- /dev/null +++ b/apps/system/services/role_organization.go @@ -0,0 +1,50 @@ +package services + +import ( + "fmt" + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysRoleOrganizationModel interface { + Insert(roleId int64, organizationIds []int64) bool + FindOrganizationsByRoleId(roleId int64) ([]int64, error) + Delete(rm entity.SysRoleOrganization) + } + + sysRoleOrganizationImpl struct { + table string + } +) + +var SysRoleOrganizationModelDao SysRoleOrganizationModel = &sysRoleOrganizationImpl{ + table: `sys_role_organizations`, +} + +func (m *sysRoleOrganizationImpl) Insert(roleId int64, organizationIds []int64) bool { + sql := "INSERT INTO sys_role_organizations (role_id, organization_id) VALUES " + + for i := 0; i < len(organizationIds); i++ { + if len(organizationIds)-1 == i { + //最后一条数据 以分号结尾 + sql += fmt.Sprintf("(%d,%d);", roleId, organizationIds[i]) + } else { + sql += fmt.Sprintf("(%d,%d),", roleId, organizationIds[i]) + } + } + global.Db.Exec(sql) + return true +} + +func (m *sysRoleOrganizationImpl) FindOrganizationsByRoleId(roleId int64) ([]int64, error) { + var result []int64 + err := global.Db.Table(m.table).Where("role_id = ?", roleId).Pluck("organization_id", &result).Error + return result, err +} + +func (m *sysRoleOrganizationImpl) Delete(rm entity.SysRoleOrganization) { + biz.ErrIsNil(global.Db.Table(m.table).Where("role_id = ?", rm.RoleId).Delete(&rm).Error, "删除角色失败") + return +} diff --git a/apps/system/services/tenant.go b/apps/system/services/tenant.go new file mode 100644 index 0000000..2adf005 --- /dev/null +++ b/apps/system/services/tenant.go @@ -0,0 +1,85 @@ +package services + +/** + * @Description + * @Author 熊猫 + * @Date 2022/7/14 17:49 + **/ +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" +) + +type ( + SysTenantsModel interface { + Insert(data entity.SysTenants) *entity.SysTenants + FindOne(tenantId int64) *entity.SysTenants + FindListPage(page, pageSize int, data entity.SysTenants) (*[]entity.SysTenants, int64) + FindList(data entity.SysTenants) *[]entity.SysTenants + Update(data entity.SysTenants) *entity.SysTenants + Delete(tenantIds []int64) + } + + SysTenantModelImpl struct { + table string + } +) + +var SysTenantModelDao SysTenantsModel = &SysTenantModelImpl{ + table: `sys_tenants`, +} + +func (m *SysTenantModelImpl) Insert(data entity.SysTenants) *entity.SysTenants { + err := global.Db.Table(m.table).Create(&data).Error + biz.ErrIsNil(err, "添加SysTenant失败") + return &data +} + +func (m *SysTenantModelImpl) FindOne(tenantId int64) *entity.SysTenants { + resData := new(entity.SysTenants) + err := global.Db.Table(m.table).Where("id = ?", tenantId).First(resData).Error + biz.ErrIsNil(err, "查询SysTenant失败") + return resData +} + +func (m *SysTenantModelImpl) FindListPage(page, pageSize int, data entity.SysTenants) (*[]entity.SysTenants, int64) { + list := make([]entity.SysTenants, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + db := global.Db.Table(m.table) + if data.TenantName != "" { + db = db.Where("tenant_name like ?", "%"+data.TenantName+"%") + } + if data.Id != 0 { + db = db.Where("id = ?", data.Id) + } + db.Where("delete_time IS NULL") + err := db.Count(&total).Error + err = db.Order("create_time").Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询SysTenant分页列表失败") + return &list, total +} + +func (m *SysTenantModelImpl) FindList(data entity.SysTenants) *[]entity.SysTenants { + list := make([]entity.SysTenants, 0) + db := global.Db.Table(m.table) + if data.TenantName != "" { + db = db.Where("tenant_name like ?", "%"+data.TenantName+"%") + } + if data.Id != 0 { + db = db.Where("id = ?", data.Id) + } + db.Where("delete_time IS NULL") + biz.ErrIsNil(db.Order("create_time").Find(&list).Error, "查询SysTenant列表失败") + return &list +} + +func (m *SysTenantModelImpl) Update(data entity.SysTenants) *entity.SysTenants { + biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改SysTenant失败") + return &data +} + +func (m *SysTenantModelImpl) Delete(tenantIds []int64) { + biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.SysTenants{}, "id in (?)", tenantIds).Error, "删除SysTenant失败") +} diff --git a/apps/system/services/user.go b/apps/system/services/user.go new file mode 100644 index 0000000..7077a8b --- /dev/null +++ b/apps/system/services/user.go @@ -0,0 +1,184 @@ +package services + +import ( + "pandax/apps/system/entity" + "pandax/kit/biz" + "pandax/pkg/global" + + "github.com/kakuilan/kgo" + "golang.org/x/crypto/bcrypt" +) + +type ( + SysUserModel interface { + Login(u entity.Login) *entity.SysUser + Insert(data entity.SysUser) *entity.SysUser + FindOne(data entity.SysUser) (resData *entity.SysUserView) + FindListPage(page, pageSize int, data entity.SysUser) (list *[]entity.SysUserPage, total int64) + FindList(data entity.SysUser) (list *[]entity.SysUserView) + Update(data entity.SysUser) *entity.SysUser + Delete(userId []int64) + SetPwd(data entity.SysUser, pwd entity.SysUserPwd) bool + } + + sysUserModelImpl struct { + table string + } +) + +var SysUserModelDao SysUserModel = &sysUserModelImpl{ + table: `sys_users`, +} + +func (m *sysUserModelImpl) Login(u entity.Login) *entity.SysUser { + user := new(entity.SysUser) + + err := global.Db.Table(m.table).Where("username = ? ", u.Username).Find(user) + biz.ErrIsNil(err.Error, "查询用户信息失败") + + // 验证密码 + b := kgo.KEncr.PasswordVerify([]byte(u.Password), []byte(user.Password)) + biz.IsTrue(b, "密码错误") + + return user +} + +func (m *sysUserModelImpl) Insert(data entity.SysUser) *entity.SysUser { + bytes, _ := kgo.KEncr.PasswordHash([]byte(data.Password), bcrypt.DefaultCost) + data.Password = string(bytes) + + // check 用户名 + var count int64 + global.Db.Table(m.table).Where("username = ? and delete_time IS NULL", data.Username).Count(&count) + biz.IsTrue(count == 0, "账户已存在!") + + biz.ErrIsNil(global.Db.Table(m.table).Create(&data).Error, "添加用户失败") + return &data +} + +func (m *sysUserModelImpl) FindOne(data entity.SysUser) *entity.SysUserView { + resData := new(entity.SysUserView) + + db := global.Db.Table(m.table).Select([]string{"sys_users.*", "sys_roles.role_name"}) + db = db.Joins("left join sys_roles on sys_users.role_id=sys_roles.role_id") + if data.UserId != 0 { + db = db.Where("user_id = ?", data.UserId) + } + if data.Username != "" { + db = db.Where("username = ?", data.Username) + } + if data.Password != "" { + db = db.Where("password = ?", data.Password) + } + if data.RoleId != 0 { + db = db.Where("role_id = ?", data.RoleId) + } + if data.OrganizationId != 0 { + db = db.Where("organization_id = ?", data.OrganizationId) + } + if data.PostId != 0 { + db = db.Where("post_id = ?", data.PostId) + } + biz.ErrIsNil(db.First(resData).Error, "查询用户失败") + + return resData +} + +func (m *sysUserModelImpl) FindListPage(page, pageSize int, data entity.SysUser) (*[]entity.SysUserPage, int64) { + list := make([]entity.SysUserPage, 0) + var total int64 = 0 + offset := pageSize * (page - 1) + db := global.Db.Table(m.table).Select("sys_users.*,sys_organizations.organization_name") + db = db.Joins("left join sys_organizations on sys_organizations.organization_id = sys_users.organization_id") + // 此处填写 where参数判断 + if data.Username != "" { + db = db.Where("sys_users.username = ?", data.Username) + } + if data.NickName != "" { + db = db.Where("sys_users.nick_name like ?", "%"+data.NickName+"%") + } + + if data.Status != "" { + db = db.Where("sys_users.status = ?", data.Status) + } + + if data.Phone != "" { + db = db.Where("sys_users.phone like ?", "%"+data.Phone+"%") + } + if data.OrganizationId != 0 { + db = db.Where("sys_users.organization_id = ?", data.OrganizationId) + } + db.Where("sys_users.delete_time IS NULL") + err := db.Count(&total).Error + err = db.Limit(pageSize).Offset(offset).Find(&list).Error + biz.ErrIsNil(err, "查询用户分页列表失败") + return &list, total +} + +func (m *sysUserModelImpl) FindList(data entity.SysUser) *[]entity.SysUserView { + list := make([]entity.SysUserView, 0) + // 此处填写 where参数判断 + db := global.Db.Table(m.table).Select([]string{"sys_users.*", "sys_roles.role_name"}) + db = db.Joins("left join sys_roles on sys_users.role_id=sys_roles.role_id") + if data.UserId != 0 { + db = db.Where("user_id = ?", data.UserId) + } + if data.Username != "" { + db = db.Where("username = ?", data.Username) + } + + if data.Password != "" { + db = db.Where("password = ?", data.Password) + } + + if data.RoleId != 0 { + db = db.Where("sys_users.role_id = ?", data.RoleId) + } + + if data.OrganizationId != 0 { + db = db.Where("sys_users.organization_id = ?", data.OrganizationId) + } + + if data.PostId != 0 { + db = db.Where("sys_users.post_id = ?", data.PostId) + } + if data.Status != "" { + db = db.Where("sys_users.status = ?", data.Status) + } + db.Where("sys_users.delete_time IS NULL") + + biz.ErrIsNilAppendErr(db.Find(&list).Error, "查询用户列表失败") + + return &list +} + +func (m *sysUserModelImpl) Update(data entity.SysUser) *entity.SysUser { + if data.Password != "" { + bytes, _ := kgo.KEncr.PasswordHash([]byte(data.Password), bcrypt.DefaultCost) + data.Password = string(bytes) + } + update := new(entity.SysUser) + biz.ErrIsNil(global.Db.Table(m.table).First(update, data.UserId).Error, "查询用户失败") + + if data.RoleId == 0 { + data.RoleId = update.RoleId + } + + biz.ErrIsNil(global.Db.Table(m.table).Updates(&data).Error, "修改用户失败") + return &data +} + +func (m *sysUserModelImpl) Delete(userIds []int64) { + biz.ErrIsNil(global.Db.Table(m.table).Delete(&entity.SysUser{}, "user_id in (?)", userIds).Error, "删除用户失败") +} + +func (m *sysUserModelImpl) SetPwd(data entity.SysUser, pwd entity.SysUserPwd) bool { + user := m.FindOne(data) + bl := kgo.KEncr.PasswordVerify([]byte(pwd.OldPassword), []byte(user.Password)) + biz.IsTrue(bl, "旧密码输入错误") + + data.Password = pwd.NewPassword + m.Update(data) + + return true +} diff --git a/config.yml b/config.yml new file mode 100644 index 0000000..4f565e6 --- /dev/null +++ b/config.yml @@ -0,0 +1,53 @@ +app: + name: xMagic + version: 1.0.0 + +server: + port: 7788 + cors: true + # 接口限流 + rate: + enable: true + rate-num: 100 + db-type: mysql + excel-dir: ./resource/excel/ + tls: + enable: true + key-file: /www/server/panel/vhost/ssl/r.l-l.cn/privkey.pem + cert-file: /www/server/panel/vhost/ssl/r.l-l.cn/fullchain.pem +jwt: + key: xMagic + # 过期时间单位秒 7天 + expire-time: 604800 + +mysql: + host: sh-cynosdbmysql-grp-l1s8n3g4.sql.tencentcdb.com:28799 + username: root + password: "LMxeon5x" + db-name: tita + config: charset=utf8&loc=Local&parseTime=true + +postgresql: + username: postgres + password: 123456 + host: 127.0.0.1 + port: 5432 + db-name: pandax_iot + max-idle-conns: 10 + max-open-conns: 10 + +casbin: + model-path: "./resource/rbac_model.conf" + +gen: + # 代码生成读取的数据库名称 + dbname: x_magic + # 代码生成是使用前端代码存放位置,需要指定到src文件夹,相对路径 + frontpath: ../PandaUi/src + +log: + # 日志等级, trace, debug, info, warn, error, fatal + level: info +# file: +# path: ./ +# name: panda_log.log diff --git a/core.js b/core.js new file mode 100644 index 0000000..eb78903 --- /dev/null +++ b/core.js @@ -0,0 +1,109 @@ +// core.js 的 main 函数是一切脚本函数执行的预处理入口 +function main() { + // 拒绝任何请求访问和操作以下文件名的路径 + if ( + payload.get().url.includes('function.js') || + payload.get().url.includes('core.js') || + payload.get().url.includes('global.js') + ) { + //当请求的路径包含以上文件名时,返回 404 + response.contentType.html() + response.status.notFound() + return `

404!

` + } + + // 声明路径 + const filePath = rootPath + payload.get().path + + switch (payload.get().method) { + case 'OPTIONS': + // 这样对于所有的 OPTIONS 请求都会返回 200 (OK) 状态码 + response.status.ok() + return + // 当接收的请求为 GET 时 + case 'GET': + // 声明缓存 + response.headers.set( + 'Cache-Control', + 'no-transform,max-age=31536000,immutable' + ) + + // 拒绝任何接口通过GET访问和操作以下文件名的路径 + if ( + payload.get().url.includes('/api') && + payload.get().url.includes('function.js') + ) { + //当请求的路径包含以上文件名时,返回 404 + response.contentType.html() + response.status.notFound() + return `

404!

` + } + + // 判断路径是否为目录 + if (fs.isDir(filePath)) { + // 设置 Content-Type + response.headers.set('Content-Type', 'text/html; charset=utf-8;') + // 如果是目录,则尝试返回目录下的 index.htm + var indexFile = filePath + '/index.htm' + if (fs.exists(filePath)) { + // 如果 index.htm 存在,则返回 index.htm + response.file(filePath) + return `` + } + // 如果是index.htm不存在,则尝试返回目录下的 index.html + indexFile = indexFile + 'l' + if (fs.exists(filePath)) { + // 如果 index.html 存在,则返回 index.html + response.file(filePath) + return `` + } + // 则将请求移交给对应path下的 function.js 进行处理。 + runtime.call(filePath) // call会将 return 的内容写到 response.body 里面。 + return `` // 如果不需要在call的结果后面追加内容,这里请设置为空 + } + // 如果不是目录,则尝试返回文件 + if (fs.exists(filePath)) { + if (payload.get().query.download) { + // 设置 Content-Disposition + response.headers.set('Content-Disposition', 'attachment;') + } + if ( + filePath.includes('.html') || + filePath.includes('.htm') || + filePath[filePath.length - 1] == '/' + ) { + response.headers.set('Content-Type', 'text/html; charset=utf-8;') + } + // response.headers.set("Content-Type", "text/html; charset=utf-8;") + // 设置 Content-Type,虽然会根据URL的文件后缀自动设置,但是为了安全起见,你也可以手动设置一下。 + // response.headers.set("Content-Type", "application/octet-stream; charset=utf-8") + + // 如果文件存在,则返回文件 + response.file(filePath) + return `` // 如果不需要在call的结果后面追加内容,这里请设置为空 + } + // 则将请求移交给对应path下的 function.js 进行处理。 + runtime.call(filePath) // call会将 return 的内容写到 response.body 里面。 + return `` // 如果不需要在call的结果后面追加内容,这里请设置为空 + + // 如果客户端发起 POST + case 'POST': + // 则将请求移交给对应path下的 function.js 进行处理。 + runtime.call(filePath) // call会将 return 的内容写到 response.body 里面。 + return `` // 如果不需要在call的结果后面追加内容,这里请设置为空 + + // 如果客户端发起 PUT + case 'PUT': + // 将请求交给 response 库的的 upload 函数处理 + // console.log(payload.get().body); + // return encoding.from(payload.get().body).toString(); + + response.upload(filePath) + return `` + } + + // 当路由处理逻辑不存在 + response.contentType.html() + response.status.methodNotAllowed() + return `

405!

` +} \ No newline at end of file diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..d2875da --- /dev/null +++ b/go.mod @@ -0,0 +1,108 @@ +module pandax + +go 1.23.0 + +replace git.weixin.qq.com/__/vlan => ../vlan + +require ( + git.weixin.qq.com/__/vlan v0.0.0-00010101000000-000000000000 + github.com/casbin/casbin/v2 v2.37.4 + github.com/casbin/gorm-adapter/v3 v3.4.6 + github.com/dgrijalva/jwt-go v3.2.0+incompatible + github.com/didip/tollbooth v4.0.2+incompatible + github.com/dlclark/regexp2 v1.10.0 + github.com/emicklei/go-restful-openapi/v2 v2.9.0 + github.com/emicklei/go-restful/v3 v3.9.0 + github.com/emmansun/gmsm v0.24.3 + github.com/go-playground/validator/v10 v10.8.0 + github.com/go-redis/redis v6.15.9+incompatible + github.com/go-sql-driver/mysql v1.7.1 + github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7 + github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 + github.com/google/uuid v1.3.0 + github.com/gorilla/schema v1.2.0 + github.com/gorilla/websocket v1.5.1 + github.com/kakuilan/kgo v0.1.8 + github.com/lib/pq v1.10.9 + github.com/mojocn/base64Captcha v1.3.6 + github.com/mssola/user_agent v0.5.3 + github.com/sirupsen/logrus v1.9.3 + github.com/spf13/cobra v1.5.0 + github.com/tidwall/gjson v1.17.0 + github.com/xuri/excelize/v2 v2.4.1 + go.mongodb.org/mongo-driver v1.13.1 + golang.org/x/crypto v0.41.0 + golang.org/x/net v0.43.0 + golang.org/x/text v0.28.0 + gopkg.in/yaml.v3 v3.0.1 + gorm.io/driver/mysql v1.2.0 + gorm.io/driver/postgres v1.2.3 + gorm.io/gorm v1.22.3 + gvisor.dev/gvisor v0.0.0-20240119232905-7b151e25d076 +) + +require ( + github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible // indirect + github.com/Masterminds/semver/v3 v3.4.0 // indirect + github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect + github.com/brianvoe/gofakeit/v6 v6.0.2 // indirect + github.com/denisenkom/go-mssqldb v0.12.3 // indirect + github.com/fsnotify/fsnotify v1.5.4 // indirect + github.com/go-ole/go-ole v1.2.5 // indirect + github.com/go-openapi/jsonpointer v0.19.5 // indirect + github.com/go-openapi/jsonreference v0.20.0 // indirect + github.com/go-openapi/spec v0.20.6 // indirect + github.com/go-openapi/swag v0.19.15 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 // indirect + github.com/golang-sql/sqlexp v0.1.0 // indirect + github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 // indirect + github.com/golang/mock v1.6.0 // indirect + github.com/golang/snappy v0.0.4 // indirect + github.com/gonuts/binary v0.2.0 // indirect + github.com/google/go-cmp v0.7.0 // indirect + github.com/inconshreveable/mousetrap v1.0.0 // indirect + github.com/jackc/chunkreader/v2 v2.0.1 // indirect + github.com/jackc/pgconn v1.10.1 // indirect + github.com/jackc/pgio v1.0.0 // indirect + github.com/jackc/pgpassfile v1.0.0 // indirect + github.com/jackc/pgproto3/v2 v2.2.0 // indirect + github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b // indirect + github.com/jackc/pgtype v1.9.0 // indirect + github.com/jackc/pgx/v4 v4.14.0 // indirect + github.com/jinzhu/inflection v1.0.0 // indirect + github.com/jinzhu/now v1.1.2 // indirect + github.com/josharian/intern v1.0.0 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.17.4 // indirect + github.com/kr/pretty v0.3.0 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mailru/easyjson v0.7.7 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 // indirect + github.com/montanaflynn/stats v0.7.1 // indirect + github.com/onsi/ginkgo v1.16.5 // indirect + github.com/onsi/gomega v1.38.2 // indirect + github.com/patrickmn/go-cache v2.1.0+incompatible // indirect + github.com/richardlehane/mscfb v1.0.3 // indirect + github.com/richardlehane/msoleps v1.0.1 // indirect + github.com/rogpeppe/go-internal v1.8.0 // indirect + github.com/spf13/pflag v1.0.5 // indirect + github.com/tidwall/match v1.1.1 // indirect + github.com/tidwall/pretty v1.2.1 // indirect + github.com/xdg-go/pbkdf2 v1.0.0 // indirect + github.com/xdg-go/scram v1.1.2 // indirect + github.com/xdg-go/stringprep v1.0.4 // indirect + github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 // indirect + github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a // indirect + golang.org/x/image v0.13.0 // indirect + golang.org/x/sync v0.16.0 // indirect + golang.org/x/sys v0.35.0 // indirect + golang.org/x/time v0.5.0 // indirect + gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gorm.io/driver/sqlserver v1.2.1 // indirect + gorm.io/plugin/dbresolver v1.1.0 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..35c4ed3 --- /dev/null +++ b/go.sum @@ -0,0 +1,504 @@ +github.com/Azure/azure-sdk-for-go/sdk/azcore v0.19.0/go.mod h1:h6H6c8enJmmocHUbLiiGY6sx7f9i+X3m1CHdd5c6Rdw= +github.com/Azure/azure-sdk-for-go/sdk/azidentity v0.11.0/go.mod h1:HcM1YX14R7CJcghJGOYCgdezslRSVzqwLf/q+4Y2r/0= +github.com/Azure/azure-sdk-for-go/sdk/internal v0.7.0/go.mod h1:yqy467j36fJxcRV2TzfVZ1pCb5vxm4BtZPUdYWe/Xo8= +github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible h1:1G1pk05UrOh0NlF1oeaaix1x8XzrfjIDK47TY0Zehcw= +github.com/Knetic/govaluate v3.0.1-0.20171022003610-9aa49832a739+incompatible/go.mod h1:r7JcOSlj0wfOMncg0iLm8Leh48TZaKVeNIfJntJ2wa0= +github.com/Masterminds/semver/v3 v3.1.1/go.mod h1:VPu/7SZ7ePZ3QOrcuXROw5FAcLl4a0cBrbBpGY/8hQs= +github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= +github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/PuerkitoBio/purell v1.1.1/go.mod h1:c11w/QuzBsJSee3cPx9rAFu61PvFxuPbtSwDGJws/X0= +github.com/PuerkitoBio/urlesc v0.0.0-20170810143723-de5bf2ad4578/go.mod h1:uGdkoq3SwY9Y+13GIhn11/XLaGBb4BfwItxLd5jeuXE= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= +github.com/brianvoe/gofakeit/v6 v6.0.2 h1:MDvplMAKJMcKZDwQvsIbhT7BV/8UF/3EEy2n14ynUyA= +github.com/brianvoe/gofakeit/v6 v6.0.2/go.mod h1:palrJUk4Fyw38zIFB/uBZqsgzW5VsNllhHKKwAebzew= +github.com/casbin/casbin/v2 v2.37.4 h1:RWSKPjaZ8JlOBlcW1bI/FTII8OPxvQ9jVy9JwyNL6DQ= +github.com/casbin/casbin/v2 v2.37.4/go.mod h1:vByNa/Fchek0KZUgG5wEsl7iFsiviAYKRtgrQfcJqHg= +github.com/casbin/gorm-adapter/v3 v3.4.6 h1:JuLN3/CBTPPlvNyQqY3uXt4Zqnt+hs2sM353aCtLTP4= +github.com/casbin/gorm-adapter/v3 v3.4.6/go.mod h1:6mIYgpByH/uSkfCv4G/vr/12cVZc3rXBQ9KrqS9oTUU= +github.com/cockroachdb/apd v1.1.0 h1:3LFP3629v+1aKXU5Q37mxmRxX/pIu1nijXydLShEq5I= +github.com/cockroachdb/apd v1.1.0/go.mod h1:8Sl8LxpKi29FqWXR16WEFZRNSz3SoPzUzeMeY4+DwBQ= +github.com/coreos/go-systemd v0.0.0-20190321100706-95778dfbb74e/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/coreos/go-systemd v0.0.0-20190719114852-fd7a80b32e1f/go.mod h1:F5haX7vjVVG0kc13fIWeqUViNPyEJxv/OmvnBo0Yme4= +github.com/cpuguy83/go-md2man/v2 v2.0.2/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= +github.com/creack/pty v1.1.7/go.mod h1:lj5s0c3V2DBrqTV7llrYr5NG6My20zk30Fl46Y7DoTY= +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/denisenkom/go-mssqldb v0.11.0/go.mod h1:xbL0rPBG9cCiLr28tMa8zpbdarY27NDyej4t/EjAShU= +github.com/denisenkom/go-mssqldb v0.12.3 h1:pBSGx9Tq67pBOTLmxNuirNTeB8Vjmf886Kx+8Y+8shw= +github.com/denisenkom/go-mssqldb v0.12.3/go.mod h1:k0mtMFOnU+AihqFxPMiF05rtiDrorD1Vrm1KEz5hxDo= +github.com/dgrijalva/jwt-go v3.2.0+incompatible h1:7qlOGliEKZXTDg6OTjfoBKDXWrumCAMpl/TFQ4/5kLM= +github.com/dgrijalva/jwt-go v3.2.0+incompatible/go.mod h1:E3ru+11k8xSBh+hMPgOLZmtrrCbhqsmaPHjLKYnJCaQ= +github.com/didip/tollbooth v4.0.2+incompatible h1:fVSa33JzSz0hoh2NxpwZtksAzAgd7zjmGO20HCZtF4M= +github.com/didip/tollbooth v4.0.2+incompatible/go.mod h1:A9b0665CE6l1KmzpDws2++elm/CsuWBMa5Jv4WY0PEY= +github.com/dlclark/regexp2 v1.10.0 h1:+/GIL799phkJqYW+3YbOd8LCcbHzT0Pbo8zl70MHsq0= +github.com/dlclark/regexp2 v1.10.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= +github.com/dnaeon/go-vcr v1.2.0/go.mod h1:R4UdLID7HZT3taECzJs4YgbbH6PIGXB6W/sc5OLb6RQ= +github.com/emicklei/go-restful-openapi/v2 v2.9.0 h1:djsWqjhI0EVYfkLCCX6jZxUkLmYUq2q9tt09ZbixfyE= +github.com/emicklei/go-restful-openapi/v2 v2.9.0/go.mod h1:VKNgZyYviM1hnyrjD9RDzP2RuE94xTXxV+u6MGN4v4k= +github.com/emicklei/go-restful/v3 v3.7.3/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.9.0 h1:XwGDlfxEnQZzuopoqxwSEllNcCOM9DhhFyhFIIGKwxE= +github.com/emicklei/go-restful/v3 v3.9.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emmansun/gmsm v0.24.3 h1:BBSGqmMcKD2xegxc3BAZE97Gjyh250UYMr0q2pW4X0c= +github.com/emmansun/gmsm v0.24.3/go.mod h1:tKoGqGHkNwJM8wI1BGURqzRx3dsQF7rr2hp8rhrPOb4= +github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo= +github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ= +github.com/fsnotify/fsnotify v1.5.4 h1:jRbGcIw6P2Meqdwuo0H1p6JVLbL5DHKAKlYndzMwVZI= +github.com/fsnotify/fsnotify v1.5.4/go.mod h1:OVB6XrOHzAwXMpEM7uPOzcehqUV2UqJxmVXmkdnm1bU= +github.com/go-kit/log v0.1.0/go.mod h1:zbhenjAZHb184qTLMA9ZjW7ThYL0H2mk7Q6pNt4vbaY= +github.com/go-logfmt/logfmt v0.5.0/go.mod h1:wCYkCAKZfumFQihp8CzCvQ3paCTfi41vtzG1KdI/P7A= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= +github.com/go-openapi/jsonpointer v0.19.3/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonpointer v0.19.5 h1:gZr+CIYByUqjcgeLXnQu2gHYQC9o73G2XUeOFYEICuY= +github.com/go-openapi/jsonpointer v0.19.5/go.mod h1:Pl9vOtqEWErmShwVjC8pYs9cog34VGT37dQOVbmoatg= +github.com/go-openapi/jsonreference v0.19.6/go.mod h1:diGHMEHg2IqXZGKxqyvWdfWU/aim5Dprw5bqpKkTvns= +github.com/go-openapi/jsonreference v0.20.0 h1:MYlu0sBgChmCfJxxUKZ8g1cPWFOB37YSZqewK7OKeyA= +github.com/go-openapi/jsonreference v0.20.0/go.mod h1:Ag74Ico3lPc+zR+qjn4XBUmXymS4zJbYVCZmcgkasdo= +github.com/go-openapi/spec v0.20.4/go.mod h1:faYFR1CvsJZ0mNsmsphTMSoRrNV3TEDoAM7FOEWeq8I= +github.com/go-openapi/spec v0.20.6 h1:ich1RQ3WDbfoeTqTAb+5EIxNmpKVJZWBNah9RAT0jIQ= +github.com/go-openapi/spec v0.20.6/go.mod h1:2OpW+JddWPrpXSCIX8eOx7lZ5iyuWj3RYR6VaaBKcWA= +github.com/go-openapi/swag v0.19.5/go.mod h1:POnQmlKehdgb5mhVOsnJFsivZCEZ/vjK9gh66Z9tfKk= +github.com/go-openapi/swag v0.19.15 h1:D2NRCBzS9/pEY3gP9Nl8aDqGUcPFrwG2p+CNFrLyrCM= +github.com/go-openapi/swag v0.19.15/go.mod h1:QYRuS/SOXUCsnplDa677K7+DxSOj6IPNl/eQntq43wQ= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.13.0/go.mod h1:taPMhCMXrRLJO55olJkUXHZBHCxTMfnGwq/HNwmWNS8= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.17.0/go.mod h1:UkSxE5sNxxRwHyU+Scu5vgOQjsIJAF8j9muTVoKLVtA= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.8.0 h1:1kAa0fCrnpv+QYdkdcRzrRM7AyYs5o8+jZdJCz9xj6k= +github.com/go-playground/validator/v10 v10.8.0/go.mod h1:9JhgTzTaE31GZDpH/HSvHiRJrJ3iKAgqqH0Bl/Ocjdk= +github.com/go-redis/redis v6.15.9+incompatible h1:K0pv1D7EQUjfyoMql+r/jZqCLizCGKFlFgcHWWmHQjg= +github.com/go-redis/redis v6.15.9+incompatible/go.mod h1:NAIEuMOZ/fxfXJIrKDQDz8wamY7mA7PouImQ2Jvg6kA= +github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.6.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= +github.com/go-sql-driver/mysql v1.7.1 h1:lUIinVbN1DY0xBg0eMOzmmtGoHwWBbvnWubQUrtU8EI= +github.com/go-sql-driver/mysql v1.7.1/go.mod h1:OXbVy3sEdcQ2Doequ6Z5BW6fXNQTmx+9S1MCJN5yJMI= +github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7 h1:ow5vK9Q/DSKkxbEIJHBST6g+buBDwdaDIyk1dGGwpQo= +github.com/go-sqlite/sqlite3 v0.0.0-20180313105335-53dd8e640ee7/go.mod h1:JxSQ+SvsjFb+p8Y+bn+GhTkiMfKVGBD0fq43ms2xw04= +github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= +github.com/go-task/slim-sprig v0.0.0-20210107165309-348f09dbbbc0/go.mod h1:fyg7847qk6SyHyPtNmDHnmrv/HOrqktSC+C9fM+CJOE= +github.com/gofrs/uuid v4.0.0+incompatible h1:1SD/1F5pU8p29ybwgQSwpQk+mwdRrXCYuPhW6m+TnJw= +github.com/gofrs/uuid v4.0.0+incompatible/go.mod h1:b2aQJv3Z4Fp6yNu3cdSllBxTCLRxnplIgP/c0N/04lM= +github.com/golang-sql/civil v0.0.0-20190719163853-cb61b32ac6fe/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9 h1:au07oEsX2xN0ktxqI+Sida1w446QrXBRJ0nee3SNZlA= +github.com/golang-sql/civil v0.0.0-20220223132316-b832511892a9/go.mod h1:8vg3r2VgvsThLBIFL93Qb5yWzgyZWhEmBwUJWevAkK0= +github.com/golang-sql/sqlexp v0.1.0 h1:ZCD6MBpcuOVfGVqsEmY5/4FtYiKz6tSyUv9LPEDei6A= +github.com/golang-sql/sqlexp v0.1.0/go.mod h1:J4ad9Vo8ZCWQ2GMrC4UCQy1JpCbwU9m3EOqtpKwwwHI= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0 h1:DACJavvAHhabrF08vX0COfcOBJRhZ8lUbR+ZWIs0Y5g= +github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGwJL78qG/PmXZO1EjYhfJinVAhrmmHX6Z8B9k= +github.com/golang/mock v1.4.4/go.mod h1:l3mdAwkq5BuhzHwde/uurv3sEJeZMXNpwsxVWU71h+4= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= +github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U= +github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8= +github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA= +github.com/golang/protobuf v1.4.0-rc.2/go.mod h1:LlEzMj4AhA7rCAGe4KMBDvJI+AwstrUpVNzEA03Pprs= +github.com/golang/protobuf v1.4.0-rc.4.0.20200313231945-b860323f09d0/go.mod h1:WU3c8KckQ9AFe+yFwt9sWVRKCVIyN9cPHBJSNnbL67w= +github.com/golang/protobuf v1.4.0/go.mod h1:jodUvKwWbYaEsadDk5Fwe5c77LiNKVO9IDvqG2KuDX0= +github.com/golang/protobuf v1.4.2/go.mod h1:oDoupMAO8OvCJWAcko0GGGIgR6R6ocIYbsSw735rRwI= +github.com/golang/snappy v0.0.1/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/golang/snappy v0.0.4 h1:yAGX7huGHXlcLOEtBnF4w7FQwA26wojNCwOYAEhLjQM= +github.com/golang/snappy v0.0.4/go.mod h1:/XxbfmMg8lxefKM7IXC3fBNl/7bRcc72aCRzEWrmP2Q= +github.com/gonuts/binary v0.2.0 h1:caITwMWAoQWlL0RNvv2lTU/AHqAJlVuu6nZmNgfbKW4= +github.com/gonuts/binary v0.2.0/go.mod h1:kM+CtBrCGDSKdv8WXTuCUsw+loiy8f/QEI8YCCC0M/E= +github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= +github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= +github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6 h1:BHT72Gu3keYf3ZEu2J0b1vyeLSOYI8bm5wbJM/8yDe8= +github.com/google/pprof v0.0.0-20250403155104-27863c87afa6/go.mod h1:boTsfXsheKC2y+lKOCMpSfarhxDeIzfZG1jqGcPl3cA= +github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= +github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I= +github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/gorilla/schema v1.2.0 h1:YufUaxZYCKGFuAq3c96BOhjgd5nmXiOY9NGzF247Tsc= +github.com/gorilla/schema v1.2.0/go.mod h1:kgLaKoK1FELgZqMAVxx/5cbj0kT+57qxUrAlIO2eleU= +github.com/gorilla/websocket v1.5.1 h1:gmztn0JnHVt9JZquRuzLw3g4wouNVzKL15iLr/zn/QY= +github.com/gorilla/websocket v1.5.1/go.mod h1:x3kM2JMyaluk02fnUJpQuwD2dCS5NDG2ZHL0uE0tcaY= +github.com/hpcloud/tail v1.0.0/go.mod h1:ab1qPbhIpdTxEkNHXyeSf5vhxWSCs/tWer42PpOxQnU= +github.com/inconshreveable/mousetrap v1.0.0 h1:Z8tu5sraLXCXIcARxBp/8cbvlwVa7Z1NHg9XEKhtSvM= +github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8= +github.com/jackc/chunkreader v1.0.0/go.mod h1:RT6O25fNZIuasFJRyZ4R/Y2BbhasbmZXF9QQ7T3kePo= +github.com/jackc/chunkreader/v2 v2.0.0/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/chunkreader/v2 v2.0.1 h1:i+RDz65UE+mmpjTfyz0MoVTnzeYxroil2G82ki7MGG8= +github.com/jackc/chunkreader/v2 v2.0.1/go.mod h1:odVSm741yZoC3dpHEUXIqA9tQRhFrgOHwnPIn9lDKlk= +github.com/jackc/pgconn v0.0.0-20190420214824-7e0022ef6ba3/go.mod h1:jkELnwuX+w9qN5YIfX0fl88Ehu4XC3keFuOJJk9pcnA= +github.com/jackc/pgconn v0.0.0-20190824142844-760dd75542eb/go.mod h1:lLjNuW/+OfW9/pnVKPazfWOgNfH2aPem8YQ7ilXGvJE= +github.com/jackc/pgconn v0.0.0-20190831204454-2fabfa3c18b7/go.mod h1:ZJKsE/KZfsUgOEh9hBm+xYTstcNHg7UPMVJqRfQxq4s= +github.com/jackc/pgconn v1.8.0/go.mod h1:1C2Pb36bGIP9QHGBYCjnyhqu7Rv3sGshaQUvmfGIB/o= +github.com/jackc/pgconn v1.9.0/go.mod h1:YctiPyvzfU11JFxoXokUOOKQXQmDMoJL9vJzHH8/2JY= +github.com/jackc/pgconn v1.9.1-0.20210724152538-d89c8390a530/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.0/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgconn v1.10.1 h1:DzdIHIjG1AxGwoEEqS+mGsURyjt4enSmqzACXvVzOT8= +github.com/jackc/pgconn v1.10.1/go.mod h1:4z2w8XhRbP1hYxkpTuBjTS3ne3J48K83+u0zoyvg2pI= +github.com/jackc/pgio v1.0.0 h1:g12B9UwVnzGhueNavwioyEEpAmqMe1E/BN9ES+8ovkE= +github.com/jackc/pgio v1.0.0/go.mod h1:oP+2QK2wFfUWgr+gxjoBH9KGBb31Eio69xUb0w5bYf8= +github.com/jackc/pgmock v0.0.0-20190831213851-13a1b77aafa2/go.mod h1:fGZlG77KXmcq05nJLRkk0+p82V8B8Dw8KN2/V9c/OAE= +github.com/jackc/pgmock v0.0.0-20201204152224-4fe30f7445fd/go.mod h1:hrBW0Enj2AZTNpt/7Y5rr2xe/9Mn757Wtb2xeBzPv2c= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65 h1:DadwsjnMwFjfWc9y5Wi/+Zz7xoE5ALHsRQlOctkOiHc= +github.com/jackc/pgmock v0.0.0-20210724152146-4ad1a8207f65/go.mod h1:5R2h2EEX+qri8jOWMbJCtaPWkrrNc7OHwsp2TCqp7ak= +github.com/jackc/pgpassfile v1.0.0 h1:/6Hmqy13Ss2zCq62VdNG8tM1wchn8zjSGOBJ6icpsIM= +github.com/jackc/pgpassfile v1.0.0/go.mod h1:CEx0iS5ambNFdcRtxPj5JhEz+xB6uRky5eyVu/W2HEg= +github.com/jackc/pgproto3 v1.1.0/go.mod h1:eR5FA3leWg7p9aeAqi37XOTgTIbkABlvcPB3E5rlc78= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190420180111-c116219b62db/go.mod h1:bhq50y+xrl9n5mRYyCBFKkpRVTLYJVWeCc+mEAI3yXA= +github.com/jackc/pgproto3/v2 v2.0.0-alpha1.0.20190609003834-432c2951c711/go.mod h1:uH0AWtUmuShn0bcesswc4aBTWGvw0cAxIJp+6OB//Wg= +github.com/jackc/pgproto3/v2 v2.0.0-rc3/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.0-rc3.0.20190831210041-4c03ce451f29/go.mod h1:ryONWYqW6dqSg1Lw6vXNMXoBJhpzvWKnT95C46ckYeM= +github.com/jackc/pgproto3/v2 v2.0.6/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.1.1/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgproto3/v2 v2.2.0 h1:r7JypeP2D3onoQTCxWdTpCtJ4D+qpKr0TxvoyMhZ5ns= +github.com/jackc/pgproto3/v2 v2.2.0/go.mod h1:WfJCnwN3HIg9Ish/j3sgWXnAfK8A9Y0bwXYU5xKaEdA= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b h1:C8S2+VttkHFdOOCXJe+YGfa4vHYwlt4Zx+IVXQ97jYg= +github.com/jackc/pgservicefile v0.0.0-20200714003250-2b9c44734f2b/go.mod h1:vsD4gTJCa9TptPL8sPkXrLZ+hDuNrZCnj29CQpr4X1E= +github.com/jackc/pgtype v0.0.0-20190421001408-4ed0de4755e0/go.mod h1:hdSHsc1V01CGwFsrv11mJRHWJ6aifDLfdV3aVjFF0zg= +github.com/jackc/pgtype v0.0.0-20190824184912-ab885b375b90/go.mod h1:KcahbBH1nCMSo2DXpzsoWOAfFkdEtEJpPbVLq8eE+mc= +github.com/jackc/pgtype v0.0.0-20190828014616-a8802b16cc59/go.mod h1:MWlu30kVJrUS8lot6TQqcg7mtthZ9T0EoIBFiJcmcyw= +github.com/jackc/pgtype v1.8.1-0.20210724151600-32e20a603178/go.mod h1:C516IlIV9NKqfsMCXTdChteoXmwgUceqaLfjg2e3NlM= +github.com/jackc/pgtype v1.8.1/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgtype v1.9.0 h1:/SH1RxEtltvJgsDqp3TbiTFApD3mey3iygpuEGeuBXk= +github.com/jackc/pgtype v1.9.0/go.mod h1:LUMuVrfsFfdKGLw+AFFVv6KtHOFMwRgDDzBt76IqCA4= +github.com/jackc/pgx/v4 v4.0.0-20190420224344-cc3461e65d96/go.mod h1:mdxmSJJuR08CZQyj1PVQBHy9XOp5p8/SHH6a0psbY9Y= +github.com/jackc/pgx/v4 v4.0.0-20190421002000-1b8f0016e912/go.mod h1:no/Y67Jkk/9WuGR0JG/JseM9irFbnEPbuWV2EELPNuM= +github.com/jackc/pgx/v4 v4.0.0-pre1.0.20190824185557-6972a5742186/go.mod h1:X+GQnOEnf1dqHGpw7JmHqHc1NxDoalibchSk9/RWuDc= +github.com/jackc/pgx/v4 v4.12.1-0.20210724153913-640aa07df17c/go.mod h1:1QD0+tgSXP7iUjYm9C1NxKhny7lq6ee99u/z+IHFcgs= +github.com/jackc/pgx/v4 v4.13.0/go.mod h1:9P4X524sErlaxj0XSGZk7s+LD0eOyu1ZDUrrpznYDF0= +github.com/jackc/pgx/v4 v4.14.0 h1:TgdrmgnM7VY72EuSQzBbBd4JA1RLqJolrw9nQVZABVc= +github.com/jackc/pgx/v4 v4.14.0/go.mod h1:jT3ibf/A0ZVCp89rtCIN0zCJxcE74ypROmHEZYsG/j8= +github.com/jackc/puddle v0.0.0-20190413234325-e4ced69a3a2b/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v0.0.0-20190608224051-11cab39313c9/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.1.3/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jackc/puddle v1.2.0/go.mod h1:m4B5Dj62Y0fbyuIc15OsIqK0+JU8nkqQjsgx7dvjSWk= +github.com/jinzhu/inflection v1.0.0 h1:K317FqzuhWc8YvSVlFMCCUb36O/S9MCKRDI7QkRKD/E= +github.com/jinzhu/inflection v1.0.0/go.mod h1:h+uFLlag+Qp1Va5pdKtLDYj+kHp5pxUVkryuEj+Srlc= +github.com/jinzhu/now v1.1.1/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/jinzhu/now v1.1.2 h1:eVKgfIdy9b6zbWBMgFpfDPoAMifwSZagU9HmEU6zgiI= +github.com/jinzhu/now v1.1.2/go.mod h1:d3SSVoowX0Lcu0IBviAWJpolVfI5UJVZZ7cO71lE/z8= +github.com/josharian/intern v1.0.0 h1:vlS4z54oSdjm0bgjRigI+G1HpF+tI+9rE5LLzOg8HmY= +github.com/josharian/intern v1.0.0/go.mod h1:5DoeVV0s6jJacbCEi61lwdGj/aVlrQvzHFFd8Hwg//Y= +github.com/json-iterator/go v1.1.10/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kakuilan/kgo v0.1.8 h1:b9UfGYNbUpWjPheOEgu/MsWUVDNWbcSit6BbNsBAPl0= +github.com/kakuilan/kgo v0.1.8/go.mod h1:S9driqss6OluzqiOfUx7xN8nw0H6bFu5v7c19P09RRc= +github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+oQHNcck= +github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= +github.com/klauspost/compress v1.17.4 h1:Ej5ixsIri7BrIjBkRZLTo6ghwrEtHFk7ijlczPW4fZ4= +github.com/klauspost/compress v1.17.4/go.mod h1:/dCuZOvVtNoHsyb+cuJD3itjs3NbnF6KH9zAO4BDxPM= +github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/konsorten/go-windows-terminal-sequences v1.0.2/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/pty v1.1.8/go.mod h1:O1sed60cT9XZ5uDucP5qwvh+TE3NnUj51EiZO/lmSfw= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/lib/pq v1.0.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.1.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.2.0/go.mod h1:5WUZQaWbwv1U+lTReE5YruASi9Al49XbQIvNi/34Woo= +github.com/lib/pq v1.10.2/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/lib/pq v1.10.9 h1:YXG7RB+JIjhP29X+OtkiDnYaXQwpS4JEWq7dtCCRUEw= +github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= +github.com/mailru/easyjson v0.0.0-20190614124828-94de47d64c63/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.0.0-20190626092158-b2ccc519800e/go.mod h1:C1wdFJiN94OJF2b5HbByQZoLdCWB1Yqtg26g4irojpc= +github.com/mailru/easyjson v0.7.6/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= +github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= +github.com/mattn/go-colorable v0.1.1/go.mod h1:FuOcm+DKB9mbwrcAfNl7/TZVBZ6rcnceauSikq3lYCQ= +github.com/mattn/go-colorable v0.1.6/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc= +github.com/mattn/go-isatty v0.0.5/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.7/go.mod h1:Iq45c/XA43vh69/j3iqttzPXn0bhXyGjM0Hdxcsrc5s= +github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/modocache/gover v0.0.0-20171022184752-b58185e213c5/go.mod h1:caMODM3PzxT8aQXRPkAt8xlV/e7d7w8GM5g0fa5F0D8= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826 h1:RWengNIwukTxcDr9M+97sNutRR1RKhG96O6jWumTTnw= +github.com/mohae/deepcopy v0.0.0-20170929034955-c48cc78d4826/go.mod h1:TaXosZuwdSHYgviHp1DAtfrULt5eUgsSMsZf+YrPgl8= +github.com/mojocn/base64Captcha v1.3.6 h1:gZEKu1nsKpttuIAQgWHO+4Mhhls8cAKyiV2Ew03H+Tw= +github.com/mojocn/base64Captcha v1.3.6/go.mod h1:i5CtHvm+oMbj1UzEPXaA8IH/xHFZ3DGY3Wh3dBpZ28E= +github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe/go.mod h1:wL8QJuTMNUDYhXwkmfOly8iTdp5TEcJFWZD2D7SIkUc= +github.com/montanaflynn/stats v0.7.1 h1:etflOAAHORrCC44V+aR6Ftzort912ZU+YLiSTuV8eaE= +github.com/montanaflynn/stats v0.7.1/go.mod h1:etXPPgVO6n31NxCd9KQUMvCM+ve0ruNzt6R8Bnaayow= +github.com/mssola/user_agent v0.5.3 h1:lBRPML9mdFuIZgI2cmlQ+atbpJdLdeVl2IDodjBR578= +github.com/mssola/user_agent v0.5.3/go.mod h1:TTPno8LPY3wAIEKRpAtkdMT0f8SE24pLRGPahjCH4uw= +github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= +github.com/nxadm/tail v1.4.4/go.mod h1:kenIhsEOeOJmVchQTgglprH7qJGnHDVpk1VPCcaMI8A= +github.com/nxadm/tail v1.4.8 h1:nPr65rt6Y5JFSKQO7qToXr7pePgD6Gwiw05lkbyAQTE= +github.com/nxadm/tail v1.4.8/go.mod h1:+ncqLTQzXmGhMZNUePPaPqPvBxHAIsmXswZKocGu+AU= +github.com/onsi/ginkgo v1.6.0/go.mod h1:lLunBs/Ym6LB5Z9jYTR76FiuTmxDTDusOGeTQH+WWjE= +github.com/onsi/ginkgo v1.12.1/go.mod h1:zj2OWP4+oCPe1qIXoGWkgMRwljMUYCdkwsT2108oapk= +github.com/onsi/ginkgo v1.16.5 h1:8xi0RTUf59SOSfEtZMvwTvXYMzG4gV23XVHOZiXNtnE= +github.com/onsi/ginkgo v1.16.5/go.mod h1:+E8gABHa3K6zRBolWtd+ROzc/U5bkGt0FwiG042wbpU= +github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7JYyY= +github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo= +github.com/onsi/gomega v1.38.2 h1:eZCjf2xjZAqe+LeWvKb5weQ+NcPwX84kqJ0cZNxok2A= +github.com/onsi/gomega v1.38.2/go.mod h1:W2MJcYxRGV63b418Ai34Ud0hEdTVXq9NW9+Sx6uXf3k= +github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaRUnok+kx1WdO15EQc= +github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= +github.com/pkg/browser v0.0.0-20180916011732-0a3d74bf9ce4/go.mod h1:4OwLy04Bl9Ef3GJJCoec+30X3LQs/0/m4HFRt/2LUSA= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/richardlehane/mscfb v1.0.3 h1:rD8TBkYWkObWO0oLDFCbwMeZ4KoalxQy+QgniCj3nKI= +github.com/richardlehane/mscfb v1.0.3/go.mod h1:YzVpcZg9czvAuhk9T+a3avCpcFPMUWm7gK3DypaEsUk= +github.com/richardlehane/msoleps v1.0.1 h1:RfrALnSNXzmXLbGct/P2b4xkFz4e8Gmj/0Vj9M9xC1o= +github.com/richardlehane/msoleps v1.0.1/go.mod h1:BWev5JBpU9Ko2WAgmZEuiz4/u3ZYTKbjLycmwiWUfWg= +github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= +github.com/rs/zerolog v1.13.0/go.mod h1:YbFCdg8HfsridGWAh22vktObvhZbQsZXe4/zB0OKkWU= +github.com/rs/zerolog v1.15.0/go.mod h1:xYTKnLHcpfU2225ny5qZjxnj9NvkumZYjJHlAThCjNc= +github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= +github.com/satori/go.uuid v1.2.0/go.mod h1:dA0hQrYB0VpLJoorglMZABFdXlWrHn1NEOzdhQKdks0= +github.com/shopspring/decimal v0.0.0-20180709203117-cd690d0c9e24/go.mod h1:M+9NzErvs504Cn4c5DxATwIqPbtswREoFCre64PpcG4= +github.com/shopspring/decimal v1.2.0 h1:abSATXmQEYyShuxI4/vyW3tV1MrKAJzCZ/0zLUXYbsQ= +github.com/shopspring/decimal v1.2.0/go.mod h1:DKyhrW/HYNuLGql+MJL6WCR6knT2jwCFRcu2hWCYk4o= +github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q= +github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE= +github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= +github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= +github.com/spf13/cobra v1.5.0 h1:X+jTBEBqF0bHN+9cSMgmfuvv2VHJ9ezmFNf9Y/XstYU= +github.com/spf13/cobra v1.5.0/go.mod h1:dWXEIy2H428czQCjInthrTRUg7yKbok+2Qi/yBIJoUM= +github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA= +github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.2.0/go.mod h1:qt09Ya8vawLte6SNmTgCsAVtYtaKzEcn8ATUoHMkEqE= +github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk= +github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= +github.com/tidwall/gjson v1.17.0 h1:/Jocvlh98kcTfpN2+JzGQWQcqrPQwDrVEMApx/M5ZwM= +github.com/tidwall/gjson v1.17.0/go.mod h1:/wbyibRr2FHMks5tjHJ5F8dMZh3AcwJEMf5vlfC0lxk= +github.com/tidwall/match v1.1.1 h1:+Ho715JplO36QYgwN9PGYNhgZvoUSc9X2c80KVTi+GA= +github.com/tidwall/match v1.1.1/go.mod h1:eRSPERbgtNPcGhD8UCthc6PmLEQXEWd3PRB5JTxsfmM= +github.com/tidwall/pretty v1.2.0/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/tidwall/pretty v1.2.1 h1:qjsOFOWWQl+N3RsoF5/ssm1pHmJJwhjlSbZ51I6wMl4= +github.com/tidwall/pretty v1.2.1/go.mod h1:ITEVvHYasfjBbM0u2Pg8T2nJnzm8xPwvNhhsoaGGjNU= +github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c= +github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= +github.com/xdg-go/scram v1.1.2 h1:FHX5I5B4i4hKRVRBCFRxq1iQRej7WO3hhBuJf+UUySY= +github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= +github.com/xdg-go/stringprep v1.0.4 h1:XLI/Ng3O1Atzq0oBs3TWm+5ZVgkq2aqdlvP9JtoZ6c8= +github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= +github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3 h1:EpI0bqf/eX9SdZDwlMmahKM+CDBgNbsXMhsN28XrM8o= +github.com/xuri/efp v0.0.0-20210322160811-ab561f5b45e3/go.mod h1:ybY/Jr0T0GTCnYjKqmdwxyxn2BQf2RcQIIvex5QldPI= +github.com/xuri/excelize/v2 v2.4.1 h1:veeeFLAJwsNEBPBlDepzPIYS1eLyBVcXNZUW79exZ1E= +github.com/xuri/excelize/v2 v2.4.1/go.mod h1:rSu0C3papjzxQA3sdK8cU544TebhrPUoTOaGPIh0Q1A= +github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a h1:fZHgsYlfvtyqToslyjUt3VOPF4J7aK/3MPcK7xp3PDk= +github.com/youmark/pkcs8 v0.0.0-20201027041543-1326539a0a0a/go.mod h1:ul22v+Nro/R083muKhosV54bj5niojjWZvU8xrevuH4= +github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= +github.com/yuin/goldmark v1.4.13/go.mod h1:6yULJ656Px+3vBD8DxQVa3kxgyrAnzto9xy5taEt/CY= +github.com/zenazn/goji v0.9.0/go.mod h1:7S9M489iMyHBNxwZnk9/EHS098H4/F6TATF2mIxtB1Q= +go.mongodb.org/mongo-driver v1.13.1 h1:YIc7HTYsKndGK4RFzJ3covLz1byri52x0IoMB0Pt/vk= +go.mongodb.org/mongo-driver v1.13.1/go.mod h1:wcDf1JBCXy2mOW0bWHwO/IOYqdca1MPCwDtFu/Z9+eo= +go.uber.org/atomic v1.3.2/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.4.0/go.mod h1:gD2HeocX3+yG+ygLZcrzQJaqmWj9AIm7n08wl/qW/PE= +go.uber.org/atomic v1.5.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= +go.uber.org/multierr v1.1.0/go.mod h1:wR5kodmAFQ0UK8QlbwjlSNy0Z68gJhDJUG5sjR94q/0= +go.uber.org/multierr v1.3.0/go.mod h1:VgVr7evmIr6uPjLBxg28wmKNXyqE9akIJ5XnfpiKl+4= +go.uber.org/multierr v1.5.0/go.mod h1:FeouvMocqHpRaaGuG9EjoKcStLC43Zu/fmqdUMPcKYU= +go.uber.org/tools v0.0.0-20190618225709-2cfd321de3ee/go.mod h1:vJERXedbb3MVM5f9Ejo0C68/HhF8uaILCdgjnY+goOA= +go.uber.org/zap v1.9.1/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.10.0/go.mod h1:vwi/ZaCAaUcBkycHslxD9B2zi4UTXhF60s6SWpuDF0Q= +go.uber.org/zap v1.13.0/go.mod h1:zwrFLgMcdUuIBviXEYEH1YKNaOBnKXsx2IPda5bBwHM= +go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= +go.yaml.in/yaml/v3 v3.0.4/go.mod h1:DhzuOOF2ATzADvBadXxruRBLzYTpT36CKvDb3+aBEFg= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190325154230-a5d413f7728c/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20190411191339-88737f569e3a/go.mod h1:WFFai1msRO1wXaEeE5yQxYXgSfI8pQAWXbQop6sCtWE= +golang.org/x/crypto v0.0.0-20190510104115-cbcb75029529/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20190820162420-60c769a6c586/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= +golang.org/x/crypto v0.0.0-20200302210943-78000ba7a073/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201016220609-9e8e0b390897/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= +golang.org/x/crypto v0.0.0-20201203163018-be400aefbc4c/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20210616213533-5ff15b29337e/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210711020723-a769d52b0f97/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20210921155107-089bfa567519/go.mod h1:GvvjBRRGRdwPK5ydBHafDWAxML/pGHZbMvKqRZ5+Abc= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.41.0 h1:WKYxWedPGCTVVl5+WHSSrOBT0O8lx32+zxmHxijgXp4= +golang.org/x/crypto v0.41.0/go.mod h1:pO5AFd7FA68rFak7rOAGVuygIISepHftHnr8dr6+sUc= +golang.org/x/image v0.0.0-20210220032944-ac19c3e999fb/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0= +golang.org/x/image v0.13.0 h1:3cge/F/QTkNLauhf2QoE9zp+7sr+ZcL4HnoZmdwg9sg= +golang.org/x/image v0.13.0/go.mod h1:6mmbMOeV28HuMTgA6OSRkdXKYw/t5W9Uwn2Yv1r3Yxk= +golang.org/x/lint v0.0.0-20190930215403-16217165b5de/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= +golang.org/x/mod v0.0.0-20190513183733-4bf6d317e70e/go.mod h1:mXi4GBBbnImb6dmsKGUJ2LatrhH/nqhxcFungHvyanc= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/mod v0.3.0/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= +golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= +golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= +golang.org/x/net v0.0.0-20180906233101-161cd47e91fd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= +golang.org/x/net v0.0.0-20190311183353-d8887717615a/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20190813141303-74dc4d7220e7/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/net v0.0.0-20200520004742-59133d7f0dd7/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= +golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210226172049-e18ecbb05110/go.mod h1:m0MpNAwzfU5UDzcl9v0D8zg8gWTRqZa9RBIspLL5mdg= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= +golang.org/x/net v0.0.0-20210421230115-4e50805a0758/go.mod h1:72T/g9IO56b78aLF+1Kcs5dz7/ng1VjMUvfKvpfy+jM= +golang.org/x/net v0.0.0-20210610132358-84b48f89b13b/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20210726213435-c6fcb2dbf985/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220722155237-a158d28d115b/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= +golang.org/x/net v0.43.0 h1:lat02VYK2j4aLzMzecihNvTlJNQUq316m2Mr9rnM6YE= +golang.org/x/net v0.43.0/go.mod h1:vhO1fvI4dGsIjh73sWfUVjj3N7CA9WkKJNQm2svM6Jg= +golang.org/x/sync v0.0.0-20180314180146-1d60e4601c6f/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20201020160332-67f06af15bc9/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20210220032951-036812b2e83c/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.1.0/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sync v0.16.0 h1:ycBJEhp9p4vXvUZNszeOq0kGTPghopOL8q0fq3vstxw= +golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= +golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20180909124046-d0be0721c37e/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190222072716-a9d3bda3a223/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190403152447-81d4e9dc473e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190813064441-fde4db37ae7a/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190904154756-749cb33beabd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191005200804-aed5e4c7ecf9/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191120155948-bd437916bb0e/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200323222414-85ca7c5b95cd/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210112080510-489259a85091/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210420072515-93ed5bcd2bfe/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210525143221-35b2ab0089ea/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220412211240-33da011f77ad/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220722155257-8c9f86f7a55f/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.35.0 h1:vz1N37gP5bs89s7He8XuIYXpyY0+QlsKmzipCbUtyxI= +golang.org/x/sys v0.35.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= +golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= +golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.4/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/text v0.3.8/go.mod h1:E6s5w1FMmriuDzIBO73fBruAKo1PCIq6d2Q6DHfQ8WQ= +golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= +golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE= +golang.org/x/text v0.28.0 h1:rhazDwis8INMIwQ4tpjLDzUhx6RlXqZNPEM0huQojng= +golang.org/x/text v0.28.0/go.mod h1:U8nCwOR8jO/marOQ0QbDiOngZVEBB7MAiitBuMjXiNU= +golang.org/x/time v0.5.0 h1:o7cqy6amK/52YcAKIPlM3a+Fpj35zvRj2TP+e1xFSfk= +golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= +golang.org/x/tools v0.0.0-20190425150028-36563e24a262/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190425163242-31fd60d6bfdc/go.mod h1:RgjU9mgBXZiqYHBnxXauZ1Gv1EHHAz9KjViQ78xBX0Q= +golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= +golang.org/x/tools v0.0.0-20190823170909-c4a336ef6a2f/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029041327-9cc4af7d6b2c/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191029190741-b9c20aec41a5/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= +golang.org/x/tools v0.0.0-20200103221440-774c71fcf114/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/tools v0.0.0-20201224043029-2b0845dc783e/go.mod h1:emZCQorbCU4vsT4fOWvOPXz4eW1wZW4PmDk9uLelYpA= +golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= +golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= +golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= +golang.org/x/xerrors v0.0.0-20190410155217-1f06c39b4373/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190513163551-3ee3066db522/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= +google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= +google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= +google.golang.org/protobuf v1.20.1-0.20200309200217-e05f789c0967/go.mod h1:A+miEFZTKqfCUM6K7xSMQL9OKL/b6hQv+e19PK+JZNE= +google.golang.org/protobuf v1.21.0/go.mod h1:47Nbq4nVaFHyn7ilMalzfO3qCViNmqZ2kzikPIcrTAo= +google.golang.org/protobuf v1.23.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/fsnotify.v1 v1.4.7/go.mod h1:Tz8NjZHkW78fSQdbUxIjBTcgA1z1m8ZHf0WmKUhAMys= +gopkg.in/inconshreveable/log15.v2 v2.0.0-20180818164646-67afb5ed74ec/go.mod h1:aPpfJ7XW+gOuirDoZ8gHhLh3kZ1B08FtV2bbmy7Jv3s= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7 h1:uRGJdciOHaEIrze2W8Q3AKkepLTh2hOroT7a+7czfdQ= +gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7/go.mod h1:dt/ZhP58zS4L8KSrWDmTeBkI65Dw0HsyUHuEVlX15mw= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.4/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.3.0/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20200615113413-eeeca48fe776/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gorm.io/driver/mysql v1.0.3/go.mod h1:twGxftLBlFgNVNakL7F+P/x9oYqoymG3YYT8cAfI9oI= +gorm.io/driver/mysql v1.1.2/go.mod h1:4P/X9vSc3WTrhTLZ259cpFd6xKNYiSSdSZngkSBGIMM= +gorm.io/driver/mysql v1.2.0 h1:l8+9VwjjyzEkw0PNPBOr2JHhLOGVk7XEnl5hk42bcvs= +gorm.io/driver/mysql v1.2.0/go.mod h1:4RQmTg4okPghdt+kbe6e1bTXIQp7Ny1NnBn/3Z6ghjk= +gorm.io/driver/postgres v1.2.2/go.mod h1:Ik3tK+a3FMp8ORZl29v4b3M0RsgXsaeMXh9s9eVMXco= +gorm.io/driver/postgres v1.2.3 h1:f4t0TmNMy9gh3TU2PX+EppoA6YsgFnyq8Ojtddb42To= +gorm.io/driver/postgres v1.2.3/go.mod h1:pJV6RgYQPG47aM1f0QeOzFH9HxQc8JcmAgjRCgS0wjs= +gorm.io/driver/sqlserver v1.2.1 h1:KhGOjvPX7JZ5hPyQICTJfMuTz88zgJ2lk9bWiHVNHd8= +gorm.io/driver/sqlserver v1.2.1/go.mod h1:nixq0OB3iLXZDiPv6JSOjWuPgpyaRpOIIevYtA4Ulb4= +gorm.io/gorm v1.20.4/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.20.11/go.mod h1:0HFTzE/SqkGTzK6TlDPPQbAYCluiVvhzoA1+aVyzenw= +gorm.io/gorm v1.21.12/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.2/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/gorm v1.22.3 h1:/JS6z+GStEQvJNW3t1FTwJwG/gZ+A7crFdRqtvG5ehA= +gorm.io/gorm v1.22.3/go.mod h1:F+OptMscr0P2F2qU97WT1WimdH9GaQPoDW7AYd5i2Y0= +gorm.io/plugin/dbresolver v1.1.0 h1:cegr4DeprR6SkLIQlKhJLYxH8muFbJ4SmnojXvoeb00= +gorm.io/plugin/dbresolver v1.1.0/go.mod h1:tpImigFAEejCALOttyhWqsy4vfa2Uh/vAUVnL5IRF7Y= +gvisor.dev/gvisor v0.0.0-20240119232905-7b151e25d076 h1:LbaTr9qML03qYVNb18i2L5QYAf5Go7BoFILZQ3KXETs= +gvisor.dev/gvisor v0.0.0-20240119232905-7b151e25d076/go.mod h1:10sU+Uh5KKNv1+2x2A0Gvzt8FjD3ASIhorV3YsauXhk= +honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= diff --git a/index.htm b/index.htm new file mode 100644 index 0000000..4efc5fb --- /dev/null +++ b/index.htm @@ -0,0 +1 @@ +

kia ora!

\ No newline at end of file diff --git a/kit/biz/assert.go b/kit/biz/assert.go new file mode 100644 index 0000000..85f33a7 --- /dev/null +++ b/kit/biz/assert.go @@ -0,0 +1,84 @@ +package biz + +import ( + "errors" + "fmt" + "pandax/kit/utils" + "reflect" +) + +func ErrIsNil(err error, msg string, params ...any) error { + if err != nil { + if err.Error() == "record not found" { + return nil + } + return fmt.Errorf(msg, params...) + } + return nil +} + +func ErrIsNilAppendErr(err error, msg string) error { + if err != nil { + return fmt.Errorf(msg, err.Error()) + } + return nil +} + +func IsNil(err error) error { + switch t := err.(type) { + case *BizError: + return errors.New(t.Error()) + case error: + return fmt.Errorf("非业务异常: %s", err.Error()) + } + return nil +} + +func IsTrue(exp bool, msg string, params ...any) error { + if !exp { + return fmt.Errorf(msg, params...) + } + return nil +} + +func IsTrueBy(exp bool, err BizError) error { + if !exp { + return errors.New(err.Error()) + } + return nil +} + +func NotEmpty(str string, msg string, params ...any) error { + if str == "" { + return fmt.Errorf(msg, params...) + } + return nil +} + +func NotNil(data any, msg string) error { + if reflect.ValueOf(data).IsNil() { + return errors.New(msg) + } + return nil +} + +func NotBlank(data any, msg string) error { + if utils.IsBlank(reflect.ValueOf(data)) { + return errors.New(msg) + } + return nil +} + +func IsEquals(data1 any, data2 any, msg string) error { + if !reflect.DeepEqual(data1, data2) { + return errors.New(msg) + } + return nil +} + +func Nil(data any, msg string) error { + if !reflect.ValueOf(data).IsNil() { + return errors.New(msg) + } + return nil +} diff --git a/kit/biz/bizerror.go b/kit/biz/bizerror.go new file mode 100644 index 0000000..d846c7b --- /dev/null +++ b/kit/biz/bizerror.go @@ -0,0 +1,35 @@ +package biz + +// 业务错误 +type BizError struct { + code int16 + err string +} + +var ( + Success *BizError = NewBizErrCode(200, "success") + BizErr *BizError = NewBizErrCode(400, "biz error") + ServerError *BizError = NewBizErrCode(500, "服务器异常,请联系管理员") + PermissionErr *BizError = NewBizErrCode(4001, "没有权限操作,可能是TOKEN过期了,请先登录") + CasbinErr *BizError = NewBizErrCode(403, "没有API接口访问权限,请联系管理员") +) + +// 错误消息 +func (e *BizError) Error() string { + return e.err +} + +// 错误码 +func (e *BizError) Code() int16 { + return e.code +} + +// 创建业务逻辑错误结构体,默认为业务逻辑错误 +func NewBizErr(msg string) *BizError { + return &BizError{code: BizErr.code, err: msg} +} + +// 创建业务逻辑错误结构体,可设置指定错误code +func NewBizErrCode(code int16, msg string) *BizError { + return &BizError{code: code, err: msg} +} diff --git a/kit/captcha/captcha.go b/kit/captcha/captcha.go new file mode 100644 index 0000000..c275ff7 --- /dev/null +++ b/kit/captcha/captcha.go @@ -0,0 +1,25 @@ +package captcha + +import ( + "github.com/mojocn/base64Captcha" +) + +var store = base64Captcha.DefaultMemStore +var driver base64Captcha.Driver = base64Captcha.NewDriverDigit(80, 240, 4, 0.7, 80) + +// 生成验证码 +func Generate() (string, string, string) { + c := base64Captcha.NewCaptcha(driver, store) + // 获取 + id, b64s, answer, _ := c.Generate() + return id, b64s, answer +} + +// 验证验证码 +func Verify(id string, val string) bool { + if id == "" || val == "" { + return false + } + // 同时清理掉这个图片 + return store.Verify(id, val, true) +} diff --git a/kit/casbin/casbin.go b/kit/casbin/casbin.go new file mode 100644 index 0000000..0f5c7b3 --- /dev/null +++ b/kit/casbin/casbin.go @@ -0,0 +1,81 @@ +package casbin + +import ( + "pandax/kit/biz" + "pandax/kit/starter" + "sync" + + "github.com/casbin/casbin/v2" + gormadapter "github.com/casbin/gorm-adapter/v3" +) + +type CasbinService struct { + ModelPath string +} + +func (c *CasbinService) UpdateCasbin(roleKey string, casbinInfos []CasbinRule) error { + c.ClearCasbin(0, roleKey) + rules := [][]string{} + for _, v := range casbinInfos { + rules = append(rules, []string{roleKey, v.Path, v.Method}) + } + e := c.GetCasbinEnforcer() + success, err := e.AddPolicies(rules) + if err != nil { + return err + } + if !success { + return biz.NewBizErr("存在相同api,添加失败,请联系管理员") + } + return nil +} + +func (c *CasbinService) UpdateCasbinApi(oldPath string, newPath string, oldMethod string, newMethod string) error { + err := starter.Db.Table("casbin_rule").Model(&CasbinRule{}).Where("v1 = ? AND v2 = ?", oldPath, oldMethod).Updates(map[string]interface{}{ + "v1": newPath, + "v2": newMethod, + }).Error + if err != nil { + return biz.NewBizErr("修改api失败") + } + return nil +} + +func (c *CasbinService) GetPolicyPathByRoleId(roleKey string) []CasbinRule { + e := c.GetCasbinEnforcer() + list := e.GetFilteredPolicy(0, roleKey) + pathMaps := make([]CasbinRule, len(list)) + for i, v := range list { + pathMaps[i] = CasbinRule{ + Path: v[1], + Method: v[2], + } + } + return pathMaps +} + +func (c *CasbinService) ClearCasbin(v int, p ...string) bool { + e := c.GetCasbinEnforcer() + success, _ := e.RemoveFilteredPolicy(v, p...) + return success +} + +var ( + syncedEnforcer *casbin.SyncedEnforcer + once sync.Once +) + +func (c *CasbinService) GetCasbinEnforcer() *casbin.SyncedEnforcer { + once.Do(func() { + a, err := gormadapter.NewAdapterByDB(starter.Db) + if err != nil { + panic(biz.NewBizErr("新建权限适配器失败")) + } + syncedEnforcer, err = casbin.NewSyncedEnforcer(c.ModelPath, a) + if err != nil { + panic(biz.NewBizErr("新建权限适配器失败")) + } + }) + _ = syncedEnforcer.LoadPolicy() + return syncedEnforcer +} diff --git a/kit/casbin/casbin_model.go b/kit/casbin/casbin_model.go new file mode 100644 index 0000000..6bdf48f --- /dev/null +++ b/kit/casbin/casbin_model.go @@ -0,0 +1,16 @@ +package casbin + +type CasbinRule struct { + Ptype string `json:"ptype" gorm:"column:ptype"` + RoleKey string `json:"roleKey" gorm:"column:v0"` + Path string `json:"path" gorm:"column:v1"` + Method string `json:"method" gorm:"column:v2"` + V3 string `json:"v3" gorm:"column:v3"` + V4 string `json:"v4" gorm:"column:v4"` + V5 string `json:"v5" gorm:"column:v5"` + Id int `json:"id" gorm:"primary_key;AUTO_INCREMENT;column:id"` +} + +func (CasbinRule) TableName() string { + return "casbin_rule" +} diff --git a/kit/file/file.go b/kit/file/file.go new file mode 100644 index 0000000..92c0d9f --- /dev/null +++ b/kit/file/file.go @@ -0,0 +1,132 @@ +package utilFile + +import ( + "io" + "mime/multipart" + "net/http" + "os" + "pandax/kit/biz" + "strconv" + "sync" +) + +const ( + MaxConcurrency = 16 // 最大并发数 +) + +type DownloadTask struct { + URL string + FilePath string +} + +func DownloadFileWithConcurrency(url, filepath string) error { + resp, err := http.Head(url) + if err != nil { + return err + } + defer resp.Body.Close() + + fileSize, err := strconv.Atoi(resp.Header.Get("Content-Length")) + if err != nil { + return err + } + + file, err := os.OpenFile(filepath, os.O_RDWR|os.O_CREATE, 0666) + if err != nil { + return err + } + defer file.Close() + + // 检查本地文件大小 + localFileSize, err := file.Seek(0, io.SeekEnd) + if err != nil { + return err + } + + // 计算剩余未下载的文件大小 + remainingSize := fileSize - int(localFileSize) + + // 计算每个片段的大小 + chunkSize := remainingSize / MaxConcurrency + + // 创建等待组,用于等待所有goroutine完成 + var wg sync.WaitGroup + wg.Add(MaxConcurrency) + + // 创建并发下载任务 + for i := 0; i < MaxConcurrency; i++ { + start := localFileSize + int64(i*chunkSize) + end := start + int64(chunkSize) - 1 + + // 最后一个片段的结束位置可能超过文件大小,需要修正 + if i == MaxConcurrency-1 { + end = int64(fileSize) - 1 + } + + go func(index int, start, end int64) { + defer wg.Done() + + err := downloadChunk(url, filepath, start, end) + if err != nil { + biz.NewBizErr("文件下载失败") + // 处理下载错误 + } + }(i, start, end) + } + + // 等待所有goroutine完成 + wg.Wait() + + return nil +} + +func downloadChunk(url, filepath string, start, end int64) error { + req, err := http.NewRequest("GET", url, nil) + if err != nil { + return err + } + + // 设置Range头部 + req.Header.Set("Range", "bytes="+strconv.FormatInt(start, 10)+"-"+strconv.FormatInt(end, 10)) + + resp, err := http.DefaultClient.Do(req) + if err != nil { + return err + } + defer resp.Body.Close() + + file, err := os.OpenFile(filepath, os.O_RDWR, 0666) + if err != nil { + return err + } + defer file.Close() + + _, err = file.Seek(start, io.SeekStart) + if err != nil { + return err + } + + _, err = io.CopyN(file, resp.Body, end-start+1) + if err != nil { + return err + } + + return nil +} + +func SaveUploadedFile(file *multipart.FileHeader, dst string) error { + src, err := file.Open() + if err != nil { + return err + } + defer src.Close() + + out, err := os.Create(dst) + if err != nil { + return err + } + defer out.Close() + + _, err = io.Copy(out, src) + return err +} diff --git a/kit/logger/logger.go b/kit/logger/logger.go new file mode 100644 index 0000000..dbee893 --- /dev/null +++ b/kit/logger/logger.go @@ -0,0 +1,64 @@ +package logger + +import ( + "fmt" + "os" + "strings" + "time" + + "github.com/sirupsen/logrus" +) + +var Log *logrus.Logger + +func InitLog(fileName, level string) *logrus.Logger { + Log = logrus.New() + Log.SetFormatter(new(LogFormatter)) + Log.SetReportCaller(true) + + // 根据配置文件设置日志级别 + if level != "" { + l, err := logrus.ParseLevel(level) + if err != nil { + panic(any(fmt.Sprintf("日志级别不存在: %s", level))) + } + Log.SetLevel(l) + } else { + Log.SetLevel(logrus.DebugLevel) + } + if fileName != "" { + file, err := os.OpenFile(fileName, os.O_CREATE|os.O_APPEND|os.O_WRONLY, os.ModeAppend|0666) + if err != nil { + panic(any(fmt.Sprintf("创建日志文件失败: %s", err.Error()))) + } + Log.Out = file + } + + return Log +} + +type LogFormatter struct{} + +func (l *LogFormatter) Format(entry *logrus.Entry) ([]byte, error) { + timestamp := time.Now().Local().Format("2006-01-02 15:04:05.000") + level := entry.Level + logMsg := fmt.Sprintf("%s [%s]", timestamp, strings.ToUpper(level.String())) + // 如果存在调用信息,且为error级别以上记录文件及行号 + if caller := entry.Caller; caller != nil { + var fp string + // 全路径切割,只获取项目相关路径, + // 即/Users/hml/Desktop/project/go/pandax/test.go只获取/test.go + ps := strings.Split(caller.File, "pandax/") + if len(ps) >= 2 { + fp = ps[1] + } else { + fp = ps[0] + } + logMsg = logMsg + fmt.Sprintf(" [%s:%d]", fp, caller.Line) + } + for k, v := range entry.Data { + logMsg = logMsg + fmt.Sprintf(" [%s=%v]", k, v) + } + logMsg = logMsg + fmt.Sprintf(" : %s\n", entry.Message) + return []byte(logMsg), nil +} diff --git a/kit/model/base_model.go b/kit/model/base_model.go new file mode 100644 index 0000000..faaa5c0 --- /dev/null +++ b/kit/model/base_model.go @@ -0,0 +1,33 @@ +package model + +import ( + "gorm.io/gorm" + "time" +) + +// BaseAutoModel 使用代码生成需要此,不能自由命名id +type BaseAutoModel struct { + Id int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" form:"id"` + CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"createTime"` + UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"updateTime"` + DeletedAt gorm.DeletedAt `gorm:"column:delete_time" sql:"index" json:"-"` +} + +// BaseAutoModelD 表想要硬删除使用他 +type BaseAutoModelD struct { + Id int `gorm:"primary_key;AUTO_INCREMENT;column:id" json:"id" form:"id"` + CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"createTime"` + UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"updateTime"` +} + +type BaseModel struct { + CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"createTime"` + UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"updateTime"` + DeletedAt gorm.DeletedAt `gorm:"column:delete_time" sql:"index" json:"-"` +} + +// BaseModelD 硬删除 +type BaseModelD struct { + CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"create_time"` + UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"update_time"` +} diff --git a/kit/model/jsonb.go b/kit/model/jsonb.go new file mode 100644 index 0000000..aa0f5e1 --- /dev/null +++ b/kit/model/jsonb.go @@ -0,0 +1,36 @@ +package model + +import ( + "database/sql/driver" + "encoding/json" + "errors" +) + +type JSONB map[string]interface{} + +func (a JSONB) Value() (driver.Value, error) { + return json.Marshal(a) +} + +func (a *JSONB) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return errors.New("type assertion to []byte failed") + } + return json.Unmarshal(b, &a) +} + +// JSONBS Interface for JSONB Field of yourTableName Table +type JSONBS []interface{} + +func (a JSONBS) Value() (driver.Value, error) { + return json.Marshal(a) +} + +func (a *JSONBS) Scan(value interface{}) error { + b, ok := value.([]byte) + if !ok { + return errors.New("type assertion to []byte failed") + } + return json.Unmarshal(b, &a) +} diff --git a/kit/model/login_account.go b/kit/model/login_account.go new file mode 100644 index 0000000..0154bf3 --- /dev/null +++ b/kit/model/login_account.go @@ -0,0 +1,39 @@ +package model + +import "time" + +type AppContext struct { +} + +type LoginAccount struct { + UserId int64 + TenantId int64 + OrganizationId int64 + RoleId int64 + DeptId int64 + PostId int64 + Username string + RoleKey string +} + +type OauthAccount struct { + ID uint `json:"id" gorm:"autoIncrement;primaryKey"` + Name string `json:"name" gorm:"size:100;not null;unique"` + Password string `json:"-" gorm:"size:256;"` + Email string `json:"email" gorm:"size:256;"` + Avatar string `json:"avatar" gorm:"size:256;"` + AuthInfos []AuthInfo `json:"authInfos" gorm:"foreignKey:UserId;references:ID"` +} + +type AuthInfo struct { + ID uint `json:"id" gorm:"autoIncrement;primaryKey"` + UserId uint `json:"userId" gorm:"size:256"` + Url string `json:"url" gorm:"size:256"` + AuthType string `json:"authType" gorm:"size:256"` + AuthId string `json:"authId" gorm:"size:256"` + AccessToken string `json:"-" gorm:"size:256"` + RefreshToken string `json:"-" gorm:"size:256"` + Expiry time.Time `json:"-"` + + BaseModel +} diff --git a/kit/model/model.go b/kit/model/model.go new file mode 100644 index 0000000..c37f664 --- /dev/null +++ b/kit/model/model.go @@ -0,0 +1,199 @@ +package model + +import ( + "fmt" + "pandax/kit/biz" + "pandax/kit/starter" + "strconv" + + "strings" + "time" + + "gorm.io/gorm" +) + +type Model struct { + BaseAutoModel + CreatorId int64 `json:"creatorId"` + Creator string `json:"creator"` //创建者 + ModifierId int64 `json:"modifierId"` + Modifier string `json:"modifier"` //修改者 +} + +// 设置基础信息. 如创建时间,修改时间,创建者,修改者信息 +func (m *Model) SetBaseInfo(account *LoginAccount) { + nowTime := time.Now() + isCreate := m.Id == 0 + if isCreate { + m.CreatedAt = nowTime + } + m.UpdatedAt = nowTime + + if account == nil { + return + } + id := account.UserId + name := account.Username + if isCreate { + m.CreatorId = id + m.Creator = name + } + m.Modifier = name + m.ModifierId = id +} + +// 事务 +func Tx(funcs ...func(db *gorm.DB) error) (err error) { + tx := starter.Db.Begin() + defer func() { + var err any + err = recover() + if err != nil { + tx.Rollback() + err = fmt.Errorf("%v", err) + } + }() + for _, f := range funcs { + err = f(tx) + if err != nil { + tx.Rollback() + return + } + } + err = tx.Commit().Error + return +} + +// 根据id获取实体对象。model需为指针类型(需要将查询出来的值赋值给model) +// +// 若error不为nil则为不存在该记录 +func GetById(model any, id uint64, cols ...string) error { + return starter.Db.Select(cols).Where("id = ?", id).First(model).Error +} + +// 根据id列表查询 +func GetByIdIn(model any, list any, ids []uint64, orderBy ...string) { + var orderByStr string + if orderBy == nil { + orderByStr = "id desc" + } else { + orderByStr = strings.Join(orderBy, ",") + } + starter.Db.Model(model).Where("id in (?)", ids).Order(orderByStr).Find(list) +} + +// 根据id列表查询 model可以是对象,也可以是map[string]interface{} +func CountBy(model any) int64 { + var count int64 + starter.Db.Model(model).Where(model).Count(&count) + return count +} + +// 根据id更新model,更新字段为model中不为空的值,即int类型不为0,ptr类型不为nil这类字段值 +func UpdateById(model any) error { + return starter.Db.Model(model).Updates(model).Error +} +func UpdateByWhere(model any, where any) error { + return starter.Db.Model(model).Where(where).Updates(model).Error +} + +// 根据id删除model +func DeleteById(model any, id uint64) error { + return starter.Db.Delete(model, "id = ?", id).Error +} + +// 根据条件删除 +func DeleteByCondition(model any) error { + return starter.Db.Where(model).Delete(model).Error +} + +// 插入model +func Insert(model any) error { + return starter.Db.Create(model).Error +} + +// 获取满足model中不为空的字段值条件的所有数据. +// +// list为数组类型 如 var users *[]User,可指定为非model结构体,即只包含需要返回的字段结构体 +func ListBy(model any, list any, cols ...string) { + starter.Db.Model(model).Select(cols).Where(model).Find(list) +} + +// 获取满足model中不为空的字段值条件的所有数据. +// +// list为数组类型 如 var users *[]User,可指定为非model结构体 +func ListByOrder(model any, list any, order ...string) { + var orderByStr string + if order == nil { + orderByStr = "id desc" + } else { + orderByStr = strings.Join(order, ",") + } + starter.Db.Model(model).Where(model).Order(orderByStr).Find(list) +} + +// 获取满足model中不为空的字段值条件的单个对象。model需为指针类型(需要将查询出来的值赋值给model) +// +// 若 error不为nil,则为不存在该记录 +func GetBy(model any, cols ...string) error { + return starter.Db.Select(cols).Where(model).First(model).Error +} + +// 获取满足conditionModel中不为空的字段值条件的单个对象。model需为指针类型(需要将查询出来的值赋值给model) +// toModel 需要查询的字段 +// 若 error不为nil,则为不存在该记录 +func GetByConditionTo(conditionModel any, toModel any) error { + return starter.Db.Model(conditionModel).Where(conditionModel).First(toModel).Error +} + +// 获取分页结果 +func GetPage(pageParam *PageParam, conditionModel any, toModels any, orderBy ...string) *ResultPage { + var count int64 + err := starter.Db.Model(conditionModel).Where(conditionModel).Count(&count).Error + biz.ErrIsNilAppendErr(err, " 查询错误:%s") + if count == 0 { + return &ResultPage{Total: 0, Data: []string{}} + } + + page := pageParam.PageNum + pageSize := pageParam.PageSize + var orderByStr string + if orderBy == nil { + orderByStr = "id desc" + } else { + orderByStr = strings.Join(orderBy, ",") + } + err = starter.Db.Model(conditionModel).Where(conditionModel).Order(orderByStr).Limit(pageSize).Offset((page - 1) * pageSize).Find(toModels).Error + biz.ErrIsNil(err, "查询失败") + return &ResultPage{Total: count, Data: toModels} +} + +// 根据sql获取分页对象 +func GetPageBySql(sql string, param *PageParam, toModel any, args ...any) *ResultPage { + db := starter.Db + selectIndex := strings.Index(sql, "SELECT ") + 7 + fromIndex := strings.Index(sql, " FROM") + selectCol := sql[selectIndex:fromIndex] + countSql := strings.Replace(sql, selectCol, "COUNT(*) AS total ", 1) + // 查询count + var count int + db.Raw(countSql, args...).Scan(&count) + if count == 0 { + return &ResultPage{Total: 0, Data: []string{}} + } + // 分页查询 + limitSql := sql + " LIMIT " + strconv.Itoa(param.PageNum-1) + ", " + strconv.Itoa(param.PageSize) + err := db.Raw(limitSql).Scan(toModel).Error + biz.ErrIsNil(err, "查询失败") + return &ResultPage{Total: int64(count), Data: toModel} +} + +func GetListBySql(sql string, params ...any) []map[string]any { + var maps []map[string]any + starter.Db.Raw(sql, params).Scan(&maps) + return maps +} + +func GetListBySql2Model(sql string, toEntity any, params ...any) error { + return starter.Db.Raw(sql, params).Find(toEntity).Error +} diff --git a/kit/model/page.go b/kit/model/page.go new file mode 100644 index 0000000..5242ebd --- /dev/null +++ b/kit/model/page.go @@ -0,0 +1,15 @@ +package model + +// 分页参数 +type PageParam struct { + PageNum int `json:"pageNum"` + PageSize int `json:"pageSize"` + Params string `json:"params"` +} + +type ResultPage struct { + Total int64 `json:"total"` + PageNum int64 `json:"pageNum"` + PageSize int64 `json:"pageSize"` + Data any `json:"data"` +} diff --git a/kit/model/result.go b/kit/model/result.go new file mode 100644 index 0000000..1efb5b1 --- /dev/null +++ b/kit/model/result.go @@ -0,0 +1,62 @@ +package model + +import ( + "encoding/json" + "fmt" + "pandax/kit/biz" +) + +const ( + SuccessCode = 200 + SuccessMsg = "success" +) + +// 统一返回结果结构体 +type Result struct { + Code int16 `json:"code"` + Msg string `json:"msg"` + Data any `json:"data"` +} + +// 将Result转为json字符串 +func (r *Result) ToJson() string { + jsonData, err := json.Marshal(r) + if err != nil { + fmt.Println("data转json错误") + } + return string(jsonData) +} + +// 判断该Result是否为成功状态 +func (r *Result) IsSuccess() bool { + return r.Code == SuccessCode +} + +// 返回成功状态的Result +// data 成功附带的数据消息 +func Success(data any) *Result { + return &Result{Code: SuccessCode, Msg: SuccessMsg, Data: data} +} + +// 返回成功状态的Result +// data 成功不附带数据 +func SuccessNoData() *Result { + return &Result{Code: SuccessCode, Msg: SuccessMsg} +} + +func Error(bizerr *biz.BizError) *Result { + return &Result{Code: bizerr.Code(), Msg: bizerr.Error()} +} + +// 返回服务器错误Result +func ServerError() *Result { + return Error(biz.ServerError) +} + +func TokenError() *Result { + return Error(biz.PermissionErr) +} + +func ErrorBy(code int16, msg string) *Result { + return &Result{Code: code, Msg: msg} +} diff --git a/kit/restfulx/log_handler.go b/kit/restfulx/log_handler.go new file mode 100644 index 0000000..fc39caf --- /dev/null +++ b/kit/restfulx/log_handler.go @@ -0,0 +1,15 @@ +package restfulx + +type LogInfo struct { + LogResp bool // 是否记录返回结果 + Description string // 请求描述 +} + +func NewLogInfo(description string) *LogInfo { + return &LogInfo{Description: description, LogResp: false} +} + +func (i *LogInfo) WithLogResp(logResp bool) *LogInfo { + i.LogResp = logResp + return i +} diff --git a/kit/restfulx/permission_handler.go b/kit/restfulx/permission_handler.go new file mode 100644 index 0000000..65db4bf --- /dev/null +++ b/kit/restfulx/permission_handler.go @@ -0,0 +1,16 @@ +package restfulx + +type Permission struct { + NeedToken bool // 是否需要token + NeedCasbin bool // 是否进行权限 api路径权限验证 +} + +func (p *Permission) WithNeedToken(needToken bool) *Permission { + p.NeedToken = needToken + return p +} + +func (p *Permission) WithNeedCasBin(needCasBin bool) *Permission { + p.NeedCasbin = needCasBin + return p +} diff --git a/kit/restfulx/req_ctx.go b/kit/restfulx/req_ctx.go new file mode 100644 index 0000000..8592668 --- /dev/null +++ b/kit/restfulx/req_ctx.go @@ -0,0 +1,135 @@ +package restfulx + +import ( + "pandax/kit/biz" + "pandax/kit/token" + "time" + + "github.com/emicklei/go-restful/v3" + "github.com/go-playground/validator/v10" +) + +// 处理函数 +type HandlerFunc func(*ReqCtx) + +type ReqCtx struct { + Request *restful.Request + Response *restful.Response + // NeedToken bool // 是否需要token + RequiredPermission *Permission // 需要的权限信息,默认为nil,需要校验token + LoginAccount *token.Claims // 登录账号信息,只有校验token后才会有值 + + LogInfo *LogInfo // 日志相关信息 + ReqParam any // 请求参数,主要用于记录日志 + ResData any // 响应结果 + Err any // 请求错误 + Validate *validator.Validate + Timed int64 // 执行时间 + NoRes bool // 无需返回结果,即文件下载等 +} + +func (rc *ReqCtx) Handle(handler HandlerFunc) { + request := rc.Response + defer func() { + var err any + err = recover() + if err != nil { + rc.Err = err + ErrorRes(request, err) + } + // 应用所有请求后置处理器 + ApplyHandlerInterceptor(afterHandlers, rc) + }() + biz.IsTrue(rc.Request != nil, "Request == nil") + + // 默认为不记录请求参数,可在handler回调函数中覆盖赋值 + rc.ReqParam = 0 + // 默认响应结果为nil,可在handler中赋值 + rc.ResData = nil + + // 调用请求前所有处理器 + err := ApplyHandlerInterceptor(beforeHandlers, rc) + if err != nil { + panic(err) + } + + begin := time.Now() + handler(rc) + rc.Timed = time.Now().Sub(begin).Milliseconds() + if !rc.NoRes { + SuccessRes(rc.Response, rc.ResData) + } +} + +func (rc *ReqCtx) Download(filename string) { + rc.NoRes = true + Download(rc, filename) +} + +func NewReqCtx(request *restful.Request, response *restful.Response) *ReqCtx { + return &ReqCtx{ + Request: request, + Response: response, + LogInfo: NewLogInfo("默认日志信息"), + Validate: validator.New(), + RequiredPermission: &Permission{NeedToken: true, NeedCasbin: true}, + } +} + +// 调用该方法设置请求描述,则默认记录日志,并不记录响应结果 +func (r *ReqCtx) WithLog(model string) *ReqCtx { + r.LogInfo.Description = model + return r +} + +func (r *ReqCtx) NoAppend() *ReqCtx { + r.NoRes = true + return r +} + +// 设置请求上下文需要的权限信息 +func (r *ReqCtx) WithRequiredPermission(permission *Permission) *ReqCtx { + r.RequiredPermission = permission + return r +} + +// 是否需要token +func (r *ReqCtx) WithNeedToken(needToken bool) *ReqCtx { + r.RequiredPermission.NeedToken = needToken + return r +} + +// 是否需要Casbin +func (r *ReqCtx) WithNeedCasbin(needCasbin bool) *ReqCtx { + r.RequiredPermission.NeedCasbin = needCasbin + return r +} + +// 处理器拦截器函数 +type HandlerInterceptorFunc func(*ReqCtx) error +type HandlerInterceptors []HandlerInterceptorFunc + +var ( + beforeHandlers HandlerInterceptors + afterHandlers HandlerInterceptors +) + +// 使用前置处理器函数 +func UseBeforeHandlerInterceptor(b HandlerInterceptorFunc) { + beforeHandlers = append(beforeHandlers, b) +} + +// 使用后置处理器函数 +func UseAfterHandlerInterceptor(b HandlerInterceptorFunc) { + afterHandlers = append(afterHandlers, b) +} + +// 应用指定处理器拦截器,如果有一个错误则直接返回错误 +func ApplyHandlerInterceptor(his HandlerInterceptors, rc *ReqCtx) any { + for _, handler := range his { + if err := handler(rc); err != nil { + return err + } + } + return nil +} diff --git a/kit/restfulx/restfulx.go b/kit/restfulx/restfulx.go new file mode 100644 index 0000000..91303c7 --- /dev/null +++ b/kit/restfulx/restfulx.go @@ -0,0 +1,116 @@ +package restfulx + +import ( + "encoding/json" + "net/http" + "pandax/kit/biz" + "pandax/kit/logger" + "pandax/kit/model" + "strconv" + + "github.com/emicklei/go-restful/v3" + "github.com/gorilla/schema" +) + +// 绑定并校验请求结构体参数 结构体添加 例如: validate:"required" validate:"required,gt=10" +func BindJsonAndValid(rc *ReqCtx, data any) { + if err := rc.Request.ReadEntity(data); err != nil { + panic(any(biz.NewBizErr(err.Error()))) + } + if err := rc.Validate.Struct(data); err != nil { + panic(any(biz.NewBizErr("传参格式错误:" + err.Error()))) + } +} + +// BindQuery 绑定查询字符串到 +func BindQuery(rc *ReqCtx, data any) { + if err := rc.Request.ReadEntity(data); err != nil { + panic(any(biz.NewBizErr(err.Error()))) + } +} + +// PathParamsToAny 获取虽有路径中的参数 +func PathParamsToAny(rc *ReqCtx, in any) { + vars := make(map[string]any) + for k, v := range rc.Request.PathParameters() { + vars[k] = v + } + marshal, _ := json.Marshal(vars) + err := json.Unmarshal(marshal, in) + biz.ErrIsNil(err, "error get path value encoding unmarshal") + return +} + +// QueryParamsToAny 获取所有Query的参数 +func QueryParamsToAny(rc *ReqCtx, in any) { + err := rc.Request.Request.ParseForm() + biz.ErrIsNil(err, "error get ParseForm value encoding unmarshal") + decoder := schema.NewDecoder() + err = decoder.Decode(in, rc.Request.Request.Form) + biz.ErrIsNil(err, "error get path value encoding unmarshal") + return +} + +// GetPageQueryParam 获取分页参数 +func GetPageQueryParam(rc *ReqCtx) *model.PageParam { + return &model.PageParam{PageNum: QueryInt(rc, "pageNum", 1), PageSize: QueryInt(rc, "pageSize", 10)} +} + +// QueryInt 获取查询参数中指定参数值,并转为int +func QueryInt(rc *ReqCtx, qm string, defaultInt int) int { + qv := rc.Request.QueryParameter(qm) + if qv == "" { + return defaultInt + } + qvi, err := strconv.Atoi(qv) + biz.ErrIsNil(err, "query param not int") + return qvi +} + +// QueryParam QueryParam +func QueryParam(rc *ReqCtx, key string) string { + return rc.Request.QueryParameter(key) +} + +// PathParamInt 获取路径参数 +func PathParamInt(rc *ReqCtx, pm string) int { + value, _ := strconv.Atoi(rc.Request.PathParameter(pm)) + return value +} +func PathParam(rc *ReqCtx, pm string) string { + return rc.Request.PathParameter(pm) +} + +// 文件下载 +func Download(rc *ReqCtx, filename string) { + rc.Response.Header().Add("success", "true") + rc.Response.Header().Set("Content-Length", "-1") + rc.Response.Header().Set("Content-Disposition", "attachment; filename="+filename) + http.ServeFile( + rc.Response.ResponseWriter, + rc.Request.Request, filename) +} + +// 返回统一成功结果 +func SuccessRes(response *restful.Response, data any) { + response.WriteEntity(model.Success(data)) +} + +// 返回失败结果集 +func ErrorRes(response *restful.Response, err any) { + switch t := err.(type) { + case *biz.BizError: + response.WriteEntity(model.Error(t)) + break + case error: + response.WriteEntity(model.ServerError()) + logger.Log.Error(t) + break + case string: + response.WriteEntity(model.ServerError()) + logger.Log.Error(t) + break + default: + logger.Log.Error(t) + } +} diff --git a/kit/starter/gorm.go b/kit/starter/gorm.go new file mode 100644 index 0000000..145db62 --- /dev/null +++ b/kit/starter/gorm.go @@ -0,0 +1,80 @@ +package starter + +import ( + "database/sql" + "pandax/kit/logger" + "time" + + "gorm.io/driver/mysql" + "gorm.io/driver/postgres" + "gorm.io/gorm" + + gormlog "gorm.io/gorm/logger" + + _ "github.com/lib/pq" +) + +var Db *gorm.DB + +type DbGorm struct { + Type string + Dsn string + MaxIdleConns int + MaxOpenConns int +} + +func (dg *DbGorm) GormInit() *gorm.DB { + switch dg.Type { + case "mysql": + Db = dg.GormMysql() + case "postgresql": + Db = dg.GormPostgresql() + } + return Db +} +func (dg *DbGorm) GormMysql() *gorm.DB { + + logger.Log.Infof("连接mysql [%s]", dg.Dsn) + mysqlConfig := mysql.Config{ + DSN: dg.Dsn, // DSN data source name + DefaultStringSize: 191, // string 类型字段的默认长度 + DisableDatetimePrecision: true, // 禁用 datetime 精度,MySQL 5.6 之前的数据库不支持 + DontSupportRenameIndex: true, // 重命名索引时采用删除并新建的方式,MySQL 5.7 之前的数据库和 MariaDB 不支持重命名索引 + DontSupportRenameColumn: true, // 用 `change` 重命名列,MySQL 8 之前的数据库和 MariaDB 不支持重命名列 + SkipInitializeWithVersion: false, // 根据版本自动配置 + } + ormConfig := &gorm.Config{Logger: gormlog.Default.LogMode(gormlog.Silent)} + db, err := gorm.Open(mysql.New(mysqlConfig), ormConfig) + if err != nil { + logger.Log.Panicf("连接mysql失败! [%s]", err.Error()) + return nil + } + sqlDB, _ := db.DB() + sqlDB.SetMaxIdleConns(dg.MaxIdleConns) + sqlDB.SetMaxOpenConns(dg.MaxOpenConns) + return db +} + +func (dg *DbGorm) GormPostgresql() *gorm.DB { + + db, err := sql.Open("postgres", dg.Dsn) + if err != nil { + logger.Log.Panicf("连接postgresql失败! [%s]", err.Error()) + } + ormConfig := &gorm.Config{} + gormDb, err := gorm.Open(postgres.New(postgres.Config{ + Conn: db, + }), ormConfig) + if err != nil { + logger.Log.Panicf("连接postgresql失败! [%s]", err.Error()) + } + sqlDB, err := gormDb.DB() + // SetMaxIdleConns 设置空闲连接池中连接的最大数量 + sqlDB.SetMaxIdleConns(dg.MaxIdleConns) + // SetMaxOpenConns 设置打开数据库连接的最大数量。 + sqlDB.SetMaxOpenConns(dg.MaxOpenConns) + // SetConnMaxLifetime 设置了连接可复用的最大时间。 + sqlDB.SetConnMaxLifetime(time.Hour) + + return gormDb +} diff --git a/kit/token/token.go b/kit/token/token.go new file mode 100644 index 0000000..7407631 --- /dev/null +++ b/kit/token/token.go @@ -0,0 +1,135 @@ +package token + +import ( + "errors" + "fmt" + "github.com/dgrijalva/jwt-go" + "strings" + "time" +) + +type Claims struct { + UserId int64 + TenantId int64 + OrganizationId int64 //组织Id + UserName string + RoleId int64 + RoleKey string + DeptId int64 + PostId int64 + jwt.StandardClaims +} + +type JWT struct { + SignedKeyID string + SignedKey []byte + SignedMethod jwt.SigningMethod +} + +var ( + TokenExpired = errors.New("token is expired") + TokenNotValidYet = errors.New("token not active yet") + TokenMalformed = errors.New("that's not even a token") + TokenInvalid = errors.New("couldn't handle this token") +) + +func NewJWT(kid string, key []byte, method jwt.SigningMethod) *JWT { + return &JWT{ + SignedKeyID: kid, + SignedKey: key, + SignedMethod: method, + } +} + +// CreateToken 创建一个token +func (j *JWT) CreateToken(claims Claims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, &claims) + var key interface{} + if j.isEs() { + v, err := jwt.ParseECPrivateKeyFromPEM(j.SignedKey) + if err != nil { + return "", err + } + key = v + } else if j.isRsOrPS() { + v, err := jwt.ParseRSAPrivateKeyFromPEM(j.SignedKey) + if err != nil { + return "", err + } + key = v + } else if j.isHs() { + key = j.SignedKey + } else { + return "", errors.New("unsupported sign method") + } + return token.SignedString(key) +} + +// ParseToken 解析 token +func (j *JWT) ParseToken(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (i interface{}, e error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("parse error") + } + return j.SignedKey, nil + }) + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + return nil, TokenMalformed + } else if ve.Errors&jwt.ValidationErrorExpired != 0 { + // Token is expired + return nil, TokenExpired + } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { + return nil, TokenNotValidYet + } else { + return nil, TokenInvalid + } + } + } + if token != nil { + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + return nil, TokenInvalid + + } else { + return nil, TokenInvalid + + } + +} + +// 更新token +func (j *JWT) RefreshToken(tokenString string) (string, error) { + jwt.TimeFunc = func() time.Time { + return time.Unix(0, 0) + } + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return j.SignedKey, nil + }) + if err != nil { + return "", err + } + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + jwt.TimeFunc = time.Now + claims.StandardClaims.ExpiresAt = time.Now().Unix() + 60*60*24*7 + return j.CreateToken(*claims) + } + return "", TokenInvalid +} + +func (a *JWT) isEs() bool { + return strings.HasPrefix(a.SignedMethod.Alg(), "ES") +} + +func (a *JWT) isRsOrPS() bool { + isRs := strings.HasPrefix(a.SignedMethod.Alg(), "RS") + isPs := strings.HasPrefix(a.SignedMethod.Alg(), "PS") + return isRs || isPs +} + +func (a *JWT) isHs() bool { + return strings.HasPrefix(a.SignedMethod.Alg(), "HS") +} diff --git a/kit/utils/excel.go b/kit/utils/excel.go new file mode 100644 index 0000000..239d6f8 --- /dev/null +++ b/kit/utils/excel.go @@ -0,0 +1,46 @@ +package utils + +import ( + "fmt" + "github.com/xuri/excelize/v2" + "reflect" +) + +func ExportExcel(head []string, datas [][]any, filePath string) error { + excel := excelize.NewFile() + excel.SetSheetRow("Sheet1", "A1", &head) + for i, data := range datas { + axis := fmt.Sprintf("A%d", i+2) + excel.SetSheetRow("Sheet1", axis, &data) + } + excel.SaveAs(filePath) + return nil +} + +func GetFileName(path, filename string) string { + return path + filename +} + +func InterfaceToExcel(data any, fileName string) { + heads := make([]string, 0) + datas := make([][]any, 0) + v := reflect.ValueOf(data) + max := int64(v.Len()) + for i := 0; i < int(max); i++ { + u := v.Index(i).Interface() + var key = reflect.TypeOf(u) + var val = reflect.ValueOf(u) + num := key.NumField() + if i == 0 { + for i := 0; i < num; i++ { + heads = append(heads, key.Field(i).Name) + } + } + field := make([]any, 0) + for i := 0; i < num; i++ { + field = append(field, val.Field(i).Interface()) + } + datas = append(datas, field) + } + ExportExcel(heads, datas, fileName) +} diff --git a/kit/utils/excel_test.go b/kit/utils/excel_test.go new file mode 100644 index 0000000..dd643f0 --- /dev/null +++ b/kit/utils/excel_test.go @@ -0,0 +1,19 @@ +package utils + +import ( + "testing" +) + +type User struct { + Name string `json:"name"` + Age int64 `json:"age"` +} + +func TestExportExcel(t *testing.T) { + us := make([]User, 0) + us = append(us, User{Name: "张三", Age: 12}) + us = append(us, User{Name: "李四", Age: 23}) + name := GetFileName("./", "字典") + t.Log(name) + InterfaceToExcel(us, name) +} diff --git a/kit/utils/float_to_f.go.go b/kit/utils/float_to_f.go.go new file mode 100644 index 0000000..3a8b911 --- /dev/null +++ b/kit/utils/float_to_f.go.go @@ -0,0 +1,26 @@ +package utils + +import ( + "fmt" + "strconv" +) + +func ParseFloat2F(value float64) float64 { + newValue, err := strconv.ParseFloat(fmt.Sprintf("%.2f", value), 64) + if err != nil { + fmt.Println("保留2位小数, 转换float出错") + return value + } + return newValue + +} + +func ParseStringToInt64(value string) int64 { + + newValue, err := strconv.ParseInt(value, 10, 64) + if err != nil { + fmt.Println("string转换int64出错") + return 0 + } + return newValue +} diff --git a/kit/utils/ip.go b/kit/utils/ip.go new file mode 100644 index 0000000..5cab3e7 --- /dev/null +++ b/kit/utils/ip.go @@ -0,0 +1,99 @@ +package utils + +import ( + "fmt" + "io" + "net" + "pandax/pkg/http_client" + "strings" + + "github.com/tidwall/gjson" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" +) + +// GetRealAddressByIP 获取真实地址 +func GetRealAddressByIP(ip string) string { + if strings.HasPrefix(ip, "127.") || + strings.HasPrefix(ip, "10.") || + strings.HasPrefix(ip, "192.168") || + // 172.16~172.31 + strings.HasPrefix(ip, "172.16") || + strings.HasPrefix(ip, "172.17") || + strings.HasPrefix(ip, "172.18") || + strings.HasPrefix(ip, "172.19") || + strings.HasPrefix(ip, "172.20") || + strings.HasPrefix(ip, "172.21") || + strings.HasPrefix(ip, "172.22") || + strings.HasPrefix(ip, "172.23") || + strings.HasPrefix(ip, "172.24") || + strings.HasPrefix(ip, "172.25") || + strings.HasPrefix(ip, "172.26") || + strings.HasPrefix(ip, "172.27") || + strings.HasPrefix(ip, "172.28") || + strings.HasPrefix(ip, "172.29") || + strings.HasPrefix(ip, "172.30") || + strings.HasPrefix(ip, "172.31") || + ip == "localhost" { + return "内部地址" + } + ipApi := fmt.Sprintf("https://api.map.baidu.com/location/ip?ak=1CgrT8hhsdHdVYoUQeFyr6oA&ip=%s&coor=bd09ll", ip) + cli, _ := http_client.NewRequest().SetUrl(ipApi).Do() + + lng := gjson.Get(cli.GetBodyString(), "content.point.x").Str + lat := gjson.Get(cli.GetBodyString(), "content.point.y").Str + + // fmt.Printf("%s,%s,%s,%s\n", cli.GetBodyString(), ip, lng, lat) + + geoApi := fmt.Sprintf("http://apis.map.qq.com/jsapi?qt=rgeoc&lnglat=%s,%s", lng, lat) + + cli, err := http_client.NewRequest().SetUrl(geoApi).Do() + if err != nil { + return "未知地址" + } + + var result = make(map[string]string) + if gjson.Get(cli.GetBodyString(), "status").Int() == 0 { + result["country"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.n").Str) + result["province"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.p").Str) + result["city"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.c").Str) + result["district"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.d").Str) + result["addr"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.1.address_name").Str) + result["all_addr"] = fmt.Sprintf("%s%s%s", result["province"], result["city"], result["addr"]) + } + + return fmt.Sprintf("%s%s%s", result["province"], result["city"], result["addr"]) +} + +// 获取局域网ip地址 +func GetLocaHonst() string { + netInterfaces, err := net.Interfaces() + if err != nil { + fmt.Println("net.Interfaces failed, err:", err.Error()) + } + + for i := 0; i < len(netInterfaces); i++ { + if (netInterfaces[i].Flags & net.FlagUp) != 0 { + addrs, _ := netInterfaces[i].Addrs() + + for _, address := range addrs { + if ipnet, ok := address.(*net.IPNet); ok && !ipnet.IP.IsLoopback() { + if ipnet.IP.To4() != nil { + return ipnet.IP.String() + } + } + } + } + + } + return "" +} + +func gb18030ToUtf8(s string) string { + reader := transform.NewReader(strings.NewReader(s), simplifiedchinese.GB18030.NewDecoder()) + d, e := io.ReadAll(reader) + if e != nil { + return "" + } + return string(d) +} diff --git a/kit/utils/json_utils.go b/kit/utils/json_utils.go new file mode 100644 index 0000000..10822f5 --- /dev/null +++ b/kit/utils/json_utils.go @@ -0,0 +1,14 @@ +package utils + +import ( + "encoding/json" +) + +func Json2Map(jsonStr string) map[string]any { + var res map[string]any + if jsonStr == "" { + return res + } + _ = json.Unmarshal([]byte(jsonStr), &res) + return res +} diff --git a/kit/utils/regexp.go b/kit/utils/regexp.go new file mode 100644 index 0000000..78b0d7b --- /dev/null +++ b/kit/utils/regexp.go @@ -0,0 +1,42 @@ +package utils + +import ( + "regexp" + "sync" +) + +var ( + regexMu = sync.RWMutex{} + // Cache for regex object. + // Note that: + // 1. It uses sync.RWMutex ensuring the concurrent safety. + // 2. There's no expiring logic for this map. + regexMap = make(map[string]*regexp.Regexp) +) + +// getRegexp returns *regexp.Regexp object with given `pattern`. +// It uses cache to enhance the performance for compiling regular expression pattern, +// which means, it will return the same *regexp.Regexp object with the same regular +// expression pattern. +// +// It is concurrent-safe for multiple goroutines. +func GetRegexp(pattern string) (regex *regexp.Regexp, err error) { + // Retrieve the regular expression object using reading lock. + regexMu.RLock() + regex = regexMap[pattern] + regexMu.RUnlock() + if regex != nil { + return + } + // If it does not exist in the cache, + // it compiles the pattern and creates one. + regex, err = regexp.Compile(pattern) + if err != nil { + return + } + // Cache the result object using writing lock. + regexMu.Lock() + regexMap[pattern] = regex + regexMu.Unlock() + return +} diff --git a/kit/utils/str_utils.go b/kit/utils/str_utils.go new file mode 100644 index 0000000..2f6def5 --- /dev/null +++ b/kit/utils/str_utils.go @@ -0,0 +1,156 @@ +package utils + +import ( + "bytes" + "github.com/kakuilan/kgo" + "strings" + "text/template" +) + +func UnicodeIndex(str, substr string) int { + // 子串在字符串的字节位置 + result := strings.Index(str, substr) + if result >= 0 { + // 获得子串之前的字符串并转换成[]byte + prefix := []byte(str)[0:result] + // 将子串之前的字符串转换成[]rune + rs := []rune(string(prefix)) + // 获得子串之前的字符串的长度,便是子串在字符串的字符位置 + result = len(rs) + } + + return result +} + +func ReplaceString(pattern, replace, src string) (string, error) { + if r, err := GetRegexp(pattern); err == nil { + return r.ReplaceAllString(src, replace), nil + } else { + return "", err + } +} + +func Contains(haystack, needle string, startOffset ...int) int { + length := len(haystack) + offset := 0 + if len(startOffset) > 0 { + offset = startOffset[0] + } + if length == 0 || offset > length || -offset > length { + return -1 + } + + if offset < 0 { + offset += length + } + pos := strings.Index(strings.ToLower(haystack[offset:]), strings.ToLower(needle)) + if pos == -1 { + return -1 + } + return pos + offset +} + +// 字符串模板解析 +func TemplateResolve(temp string, data any) string { + t, _ := template.New("string-temp").Parse(temp) + var tmplBytes bytes.Buffer + + err := t.Execute(&tmplBytes, data) + if err != nil { + panic(err) + } + return tmplBytes.String() +} + +func ReverStrTemplate(temp, str string, res map[string]any) { + index := UnicodeIndex(temp, "{") + ei := UnicodeIndex(temp, "}") + 1 + next := kgo.KStr.Trim(temp[ei:], " ") + nextContain := UnicodeIndex(next, "{") + nextIndexValue := next + if nextContain != -1 { + nextIndexValue = kgo.KStr.Substr(next, 0, nextContain) + } + key := temp[index+1 : ei-1] + // 如果后面没有内容了,则取字符串的长度即可 + var valueLastIndex int + if nextIndexValue == "" { + valueLastIndex = kgo.KStr.MbStrlen(str) + } else { + valueLastIndex = UnicodeIndex(str, nextIndexValue) + } + value := kgo.KStr.Trim(kgo.KStr.Substr(str, index, valueLastIndex), " ") + res[key] = value + // 如果后面的还有需要解析的,则递归调用解析 + if nextContain != -1 { + ReverStrTemplate(next, kgo.KStr.Trim(kgo.KStr.Substr(str, UnicodeIndex(str, value)+kgo.KStr.MbStrlen(value), kgo.KStr.MbStrlen(str))), res) + } +} + +func B2S(bs []uint8) string { + ba := []byte{} + for _, b := range bs { + ba = append(ba, byte(b)) + } + return string(ba) +} + +func IdsStrToIdsIntGroup(keys string) []int64 { + IDS := make([]int64, 0) + ids := strings.Split(keys, ",") + for i := 0; i < len(ids); i++ { + ID := kgo.KConv.Str2Int(ids[i]) + IDS = append(IDS, int64(ID)) + } + return IDS +} + +// 获取部门 +// isP 是父ID 否则子ID +func DeptPCIds(deptIds []string, id int64, isP bool) []int64 { + pRes := make([]int64, 0) + cRes := make([]int64, 0) + is := true + for _, deptId := range deptIds { + did := kgo.KConv.Str2Int64(deptId) + if is { + pRes = append(pRes, did) + } + if kgo.KConv.Str2Int64(deptId) == id { + is = false + } + if !is { + cRes = append(cRes, did) + } + } + if isP { + return pRes + } else { + return cRes + } +} + +// 获取组织 +// isP 是父ID 否则子ID +func OrganizationPCIds(orgIds []string, id int64, isP bool) []int64 { + pRes := make([]int64, 0) + cRes := make([]int64, 0) + is := true + for _, orgId := range orgIds { + did := kgo.KConv.Str2Int64(orgId) + if is { + pRes = append(pRes, did) + } + if kgo.KConv.Str2Int64(orgId) == id { + is = false + } + if !is { + cRes = append(cRes, did) + } + } + if isP { + return pRes + } else { + return cRes + } +} diff --git a/kit/utils/str_utils_test.go b/kit/utils/str_utils_test.go new file mode 100644 index 0000000..e66db53 --- /dev/null +++ b/kit/utils/str_utils_test.go @@ -0,0 +1,24 @@ +package utils + +import ( + "log" + "strings" + "testing" +) + +func TestIdsStrToIdsIntGroup(t *testing.T) { + group := IdsStrToIdsIntGroup("aaa") + t.Log(len(group)) +} + +// func TestGetRealAddressByIP(t *testing.T) { +// ip := GetRealAddressByIP("10.42.0.1") +// t.Log(ip) +// } + +func TestDeptPCIds(t *testing.T) { + split := strings.Split(strings.Trim("/0/2", "/"), "/") + log.Println("split", split) + ids := DeptPCIds(split, 2, true) + t.Log(ids) +} diff --git a/kit/utils/struct_utils.go b/kit/utils/struct_utils.go new file mode 100644 index 0000000..dc51d60 --- /dev/null +++ b/kit/utils/struct_utils.go @@ -0,0 +1,654 @@ +package utils + +import ( + "database/sql" + "encoding/json" + "errors" + "fmt" + "reflect" + "strconv" + "strings" +) + +// Copy copy things,引用至copier +func Copy(toValue any, fromValue any) (err error) { + var ( + isSlice bool + amount = 1 + from = Indirect(reflect.ValueOf(fromValue)) + to = Indirect(reflect.ValueOf(toValue)) + ) + + if !to.CanAddr() { + return errors.New("copy to value is unaddressable") + } + + // Return is from value is invalid + if !from.IsValid() { + return + } + + fromType := IndirectType(from.Type()) + toType := IndirectType(to.Type()) + + // Just set it if possible to assign + // And need to do copy anyway if the type is struct + if fromType.Kind() != reflect.Struct && from.Type().AssignableTo(to.Type()) { + to.Set(from) + return + } + + if fromType.Kind() != reflect.Struct || toType.Kind() != reflect.Struct { + return + } + + if to.Kind() == reflect.Slice { + isSlice = true + if from.Kind() == reflect.Slice { + amount = from.Len() + } + } + + for i := 0; i < amount; i++ { + var dest, source reflect.Value + + if isSlice { + // source + if from.Kind() == reflect.Slice { + source = Indirect(from.Index(i)) + } else { + source = Indirect(from) + } + // dest + dest = Indirect(reflect.New(toType).Elem()) + } else { + source = Indirect(from) + dest = Indirect(to) + } + + // check source + if source.IsValid() { + fromTypeFields := deepFields(fromType) + //fmt.Printf("%#v", fromTypeFields) + // Copy from field to field or method + for _, field := range fromTypeFields { + name := field.Name + + if fromField := source.FieldByName(name); fromField.IsValid() { + // has field + if toField := dest.FieldByName(name); toField.IsValid() { + if toField.CanSet() { + if !set(toField, fromField) { + if err := Copy(toField.Addr().Interface(), fromField.Interface()); err != nil { + return err + } + } + } + } else { + // try to set to method + var toMethod reflect.Value + if dest.CanAddr() { + toMethod = dest.Addr().MethodByName(name) + } else { + toMethod = dest.MethodByName(name) + } + + if toMethod.IsValid() && toMethod.Type().NumIn() == 1 && fromField.Type().AssignableTo(toMethod.Type().In(0)) { + toMethod.Call([]reflect.Value{fromField}) + } + } + } + } + + // Copy from method to field + for _, field := range deepFields(toType) { + name := field.Name + + var fromMethod reflect.Value + if source.CanAddr() { + fromMethod = source.Addr().MethodByName(name) + } else { + fromMethod = source.MethodByName(name) + } + + if fromMethod.IsValid() && fromMethod.Type().NumIn() == 0 && fromMethod.Type().NumOut() == 1 { + if toField := dest.FieldByName(name); toField.IsValid() && toField.CanSet() { + values := fromMethod.Call([]reflect.Value{}) + if len(values) >= 1 { + set(toField, values[0]) + } + } + } + } + } + if isSlice { + if dest.Addr().Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest.Addr())) + } else if dest.Type().AssignableTo(to.Type().Elem()) { + to.Set(reflect.Append(to, dest)) + } + } + } + return +} + +// 对结构体的每个字段以及字段值执行doWith回调函数, 包括匿名属性的字段 +func DoWithFields(str any, doWith func(fType reflect.StructField, fValue reflect.Value) error) error { + t := IndirectType(reflect.TypeOf(str)) + if t.Kind() != reflect.Struct { + return errors.New("非结构体") + } + + fieldNum := t.NumField() + v := Indirect(reflect.ValueOf(str)) + for i := 0; i < fieldNum; i++ { + ft := t.Field(i) + fv := v.Field(i) + // 如果是匿名属性,则递归调用该方法 + if ft.Anonymous { + DoWithFields(fv.Interface(), doWith) + continue + } + err := doWith(ft, fv) + if err != nil { + return err + } + } + return nil +} + +func deepFields(reflectType reflect.Type) []reflect.StructField { + var fields []reflect.StructField + + if reflectType = IndirectType(reflectType); reflectType.Kind() == reflect.Struct { + for i := 0; i < reflectType.NumField(); i++ { + v := reflectType.Field(i) + if v.Anonymous { + fields = append(fields, deepFields(v.Type)...) + } else { + fields = append(fields, v) + } + } + } + + return fields +} + +func Indirect(reflectValue reflect.Value) reflect.Value { + for reflectValue.Kind() == reflect.Ptr { + reflectValue = reflectValue.Elem() + } + return reflectValue +} + +func IndirectType(reflectType reflect.Type) reflect.Type { + for reflectType.Kind() == reflect.Ptr || reflectType.Kind() == reflect.Slice { + reflectType = reflectType.Elem() + } + return reflectType +} + +func set(to, from reflect.Value) bool { + if from.IsValid() { + if to.Kind() == reflect.Ptr { + //set `to` to nil if from is nil + if from.Kind() == reflect.Ptr && from.IsNil() { + to.Set(reflect.Zero(to.Type())) + return true + } else if to.IsNil() { + to.Set(reflect.New(to.Type().Elem())) + } + to = to.Elem() + } + + if from.Type().ConvertibleTo(to.Type()) { + to.Set(from.Convert(to.Type())) + } else if scanner, ok := to.Addr().Interface().(sql.Scanner); ok { + err := scanner.Scan(from.Interface()) + if err != nil { + return false + } + } else if from.Kind() == reflect.Ptr { + return set(to, from.Elem()) + } else { + return false + } + } + return true +} + +func Map2Struct(m map[string]any, s any) error { + toValue := Indirect(reflect.ValueOf(s)) + if !toValue.CanAddr() { + return errors.New("to value is unaddressable") + } + + innerStructMaps := getInnerStructMaps(m) + if len(innerStructMaps) != 0 { + for k, v := range innerStructMaps { + var fieldV reflect.Value + if strings.Contains(k, ".") { + fieldV = getFiledValueByPath(k, toValue) + } else { + fieldV = toValue.FieldByName(k) + } + + if !fieldV.CanSet() || !fieldV.CanAddr() { + continue + } + fieldT := fieldV.Type().Elem() + if fieldT.Kind() != reflect.Struct { + return errors.New(k + "不是结构体") + } + // 如果值为nil,则默认创建一个并赋值 + if fieldV.IsNil() { + fieldV.Set(reflect.New(fieldT)) + } + err := Map2Struct(v, fieldV.Addr().Interface()) + if err != nil { + return err + } + } + } + var err error + for k, v := range m { + if v == nil { + continue + } + k = strings.Title(k) + // 如果key含有下划线,则将其转为驼峰 + if strings.Contains(k, "_") { + k = Case2Camel(k) + } + + fieldV := toValue.FieldByName(k) + if !fieldV.CanSet() { + continue + } + + err = decode(k, v, fieldV) + if err != nil { + return err + } + } + + return nil +} + +func Maps2Structs(maps []map[string]any, structs any) error { + structsV := reflect.Indirect(reflect.ValueOf(structs)) + valType := structsV.Type() + valElemType := valType.Elem() + sliceType := reflect.SliceOf(valElemType) + + length := len(maps) + + valSlice := structsV + if valSlice.IsNil() { + // Make a new slice to hold our result, same size as the original data. + valSlice = reflect.MakeSlice(sliceType, length, length) + } + + for i := 0; i < length; i++ { + err := Map2Struct(maps[i], valSlice.Index(i).Addr().Interface()) + if err != nil { + return err + } + } + structsV.Set(valSlice) + return nil +} + +func getFiledValueByPath(path string, value reflect.Value) reflect.Value { + split := strings.Split(path, ".") + for _, v := range split { + if value.Type().Kind() == reflect.Ptr { + // 如果值为nil,则创建并赋值 + if value.IsNil() { + value.Set(reflect.New(IndirectType(value.Type()))) + } + value = value.Elem() + } + value = value.FieldByName(v) + } + return value +} + +func getInnerStructMaps(m map[string]any) map[string]map[string]any { + key2map := make(map[string]map[string]any) + for k, v := range m { + if !strings.Contains(k, ".") { + continue + } + lastIndex := strings.LastIndex(k, ".") + prefix := k[0:lastIndex] + m2 := key2map[prefix] + if m2 == nil { + key2map[prefix] = map[string]any{k[lastIndex+1:]: v} + } else { + m2[k[lastIndex+1:]] = v + } + delete(m, k) + } + return key2map +} + +// decode等方法摘抄自mapstructure库 + +func decode(name string, input any, outVal reflect.Value) error { + var inputVal reflect.Value + if input != nil { + inputVal = reflect.ValueOf(input) + + // We need to check here if input is a typed nil. Typed nils won't + // match the "input == nil" below so we check that here. + if inputVal.Kind() == reflect.Ptr && inputVal.IsNil() { + input = nil + } + } + + if !inputVal.IsValid() { + // If the input value is invalid, then we just set the value + // to be the zero value. + outVal.Set(reflect.Zero(outVal.Type())) + return nil + } + + var err error + outputKind := getKind(outVal) + switch outputKind { + case reflect.Int: + err = decodeInt(name, input, outVal) + case reflect.Uint: + err = decodeUint(name, input, outVal) + case reflect.Float32: + err = decodeFloat(name, input, outVal) + case reflect.String: + err = decodeString(name, input, outVal) + case reflect.Ptr: + _, err = decodePtr(name, input, outVal) + default: + // If we reached this point then we weren't able to decode it + return fmt.Errorf("%s: unsupported type: %s", name, outputKind) + } + return err +} + +func decodeInt(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetInt(dataVal.Int()) + case dataKind == reflect.Uint: + val.SetInt(int64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetInt(int64(dataVal.Float())) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetInt(1) + } else { + val.SetInt(0) + } + case dataKind == reflect.String: + i, err := strconv.ParseInt(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetInt(i) + } else { + return fmt.Errorf("cannot parse '%s' as int: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetInt(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodeUint(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + i := dataVal.Int() + if i < 0 { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + case dataKind == reflect.Uint: + val.SetUint(dataVal.Uint()) + case dataKind == reflect.Float32: + f := dataVal.Float() + if f < 0 { + return fmt.Errorf("cannot parse '%s', %f overflows uint", + name, f) + } + val.SetUint(uint64(f)) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetUint(1) + } else { + val.SetUint(0) + } + case dataKind == reflect.String: + i, err := strconv.ParseUint(dataVal.String(), 0, val.Type().Bits()) + if err == nil { + val.SetUint(i) + } else { + return fmt.Errorf("cannot parse '%s' as uint: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Int64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + if i < 0 { + return fmt.Errorf("cannot parse '%s', %d overflows uint", + name, i) + } + val.SetUint(uint64(i)) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodeFloat(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + dataType := dataVal.Type() + + switch { + case dataKind == reflect.Int: + val.SetFloat(float64(dataVal.Int())) + case dataKind == reflect.Uint: + val.SetFloat(float64(dataVal.Uint())) + case dataKind == reflect.Float32: + val.SetFloat(dataVal.Float()) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetFloat(1) + } else { + val.SetFloat(0) + } + case dataKind == reflect.String: + f, err := strconv.ParseFloat(dataVal.String(), val.Type().Bits()) + if err == nil { + val.SetFloat(f) + } else { + return fmt.Errorf("cannot parse '%s' as float: %s", name, err) + } + case dataType.PkgPath() == "encoding/json" && dataType.Name() == "Number": + jn := data.(json.Number) + i, err := jn.Float64() + if err != nil { + return fmt.Errorf( + "error decoding json.Number into %s: %s", name, err) + } + val.SetFloat(i) + default: + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodeString(name string, data any, val reflect.Value) error { + dataVal := reflect.Indirect(reflect.ValueOf(data)) + dataKind := getKind(dataVal) + + converted := true + switch { + case dataKind == reflect.String: + val.SetString(dataVal.String()) + case dataKind == reflect.Bool: + if dataVal.Bool() { + val.SetString("1") + } else { + val.SetString("0") + } + case dataKind == reflect.Int: + val.SetString(strconv.FormatInt(dataVal.Int(), 10)) + case dataKind == reflect.Uint: + val.SetString(strconv.FormatUint(dataVal.Uint(), 10)) + case dataKind == reflect.Float32: + val.SetString(strconv.FormatFloat(dataVal.Float(), 'f', -1, 64)) + case dataKind == reflect.Slice, + dataKind == reflect.Array: + dataType := dataVal.Type() + elemKind := dataType.Elem().Kind() + switch elemKind { + case reflect.Uint8: + var uints []uint8 + if dataKind == reflect.Array { + uints = make([]uint8, dataVal.Len(), dataVal.Len()) + for i := range uints { + uints[i] = dataVal.Index(i).Interface().(uint8) + } + } else { + uints = dataVal.Interface().([]uint8) + } + val.SetString(string(uints)) + default: + converted = false + } + default: + converted = false + } + + if !converted { + return fmt.Errorf( + "'%s' expected type '%s', got unconvertible type '%s'", + name, val.Type(), dataVal.Type()) + } + + return nil +} + +func decodePtr(name string, data any, val reflect.Value) (bool, error) { + // If the input data is nil, then we want to just set the output + // pointer to be nil as well. + isNil := data == nil + if !isNil { + switch v := reflect.Indirect(reflect.ValueOf(data)); v.Kind() { + case reflect.Chan, + reflect.Func, + reflect.Interface, + reflect.Map, + reflect.Ptr, + reflect.Slice: + isNil = v.IsNil() + } + } + if isNil { + if !val.IsNil() && val.CanSet() { + nilValue := reflect.New(val.Type()).Elem() + val.Set(nilValue) + } + + return true, nil + } + + // Create an element of the concrete (non pointer) type and decode + // into that. Then set the value of the pointer to this type. + valType := val.Type() + valElemType := valType.Elem() + if val.CanSet() { + realVal := val + if realVal.IsNil() { + realVal = reflect.New(valElemType) + } + + if err := decode(name, data, reflect.Indirect(realVal)); err != nil { + return false, err + } + + val.Set(realVal) + } else { + if err := decode(name, data, reflect.Indirect(val)); err != nil { + return false, err + } + } + return false, nil +} + +func getKind(val reflect.Value) reflect.Kind { + kind := val.Kind() + + switch { + case kind >= reflect.Int && kind <= reflect.Int64: + return reflect.Int + case kind >= reflect.Uint && kind <= reflect.Uint64: + return reflect.Uint + case kind >= reflect.Float32 && kind <= reflect.Float64: + return reflect.Float32 + default: + return kind + } +} + +// 下划线写法转为驼峰写法 +func Case2Camel(name string) string { + name = strings.Replace(name, "_", " ", -1) + name = strings.Title(name) + return strings.Replace(name, " ", "", -1) +} + +func IsBlank(value reflect.Value) bool { + switch value.Kind() { + case reflect.String: + return value.Len() == 0 + case reflect.Bool: + return !value.Bool() + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + return value.Int() == 0 + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, reflect.Uintptr: + return value.Uint() == 0 + case reflect.Float32, reflect.Float64: + return value.Float() == 0 + case reflect.Interface, reflect.Ptr: + return value.IsNil() + } + return reflect.DeepEqual(value.Interface(), reflect.Zero(value.Type()).Interface()) +} diff --git a/kit/utils/struct_utils_test.go b/kit/utils/struct_utils_test.go new file mode 100644 index 0000000..21e84df --- /dev/null +++ b/kit/utils/struct_utils_test.go @@ -0,0 +1,194 @@ +package utils + +import ( + "fmt" + "reflect" + "strings" + "testing" + "time" +) + +type Src struct { + Id *int64 `json:"id"` + Username string `json:"username"` + CreateTime time.Time `json:"time"` + UpdateTime time.Time + Inner *SrcInner +} + +type SrcInner struct { + Name string + Desc string + Id int64 + Dest *Dest +} + +type Dest struct { + Username string + Id int64 + CreateTime time.Time + Inner *DestInner +} + +type DestInner struct { + Desc string +} + +func TestDeepFields(t *testing.T) { + ////src := Src{Username: "test", Id: 1000, CreateTime: time.Now()} + //si := SrcInner{Desc: "desc"} + //src.Inner = &si + ////src.Id = 1222 + //dest := new(Dest) + //err := structutils.Copy(dest, src) + //if err != nil { + // fmt.Println(err.Error()) + //} else { + // fmt.Println(dest) + //} + +} + +func TestGetFieldNames(t *testing.T) { + //names := structutils.GetFieldNames(new(Src)) + //fmt.Println(names) +} + +func TestMaps2Structs(t *testing.T) { + mapInstance := make(map[string]any) + mapInstance["Username"] = "liang637210" + mapInstance["Id"] = 28 + mapInstance["CreateTime"] = time.Now() + mapInstance["Creator"] = "createor" + mapInstance["Inner.Id"] = 10 + mapInstance["Inner.Name"] = "hahah" + mapInstance["Inner.Desc"] = "inner desc" + mapInstance["Inner.Dest.Username"] = "inner dest uername" + mapInstance["Inner.Dest.Inner.Desc"] = "inner dest inner desc" + + mapInstance2 := make(map[string]any) + mapInstance2["Username"] = "liang6372102" + mapInstance2["Id"] = 282 + mapInstance2["CreateTime"] = time.Now() + mapInstance2["Creator"] = "createor2" + mapInstance2["Inner.Id"] = 102 + mapInstance2["Inner.Name"] = "hahah2" + mapInstance2["Inner.Desc"] = "inner desc2" + mapInstance2["Inner.Dest.Username"] = "inner dest uername2" + mapInstance2["Inner.Dest.Inner.Desc"] = "inner dest inner desc2" + + maps := make([]map[string]any, 2) + maps[0] = mapInstance + maps[1] = mapInstance2 + res := new([]Src) + err := Maps2Structs(maps, res) + if err != nil { + fmt.Println(err) + } +} + +func TestMap2Struct(t *testing.T) { + mapInstance := make(map[string]any) + mapInstance["Username"] = "liang637210" + mapInstance["Id"] = 12 + mapInstance["CreateTime"] = time.Now() + mapInstance["Creator"] = "createor" + mapInstance["Inner.Id"] = nil + mapInstance["Inner.Name"] = "hahah" + mapInstance["Inner.Desc"] = "inner desc" + mapInstance["Inner.Dest.Username"] = "inner dest uername" + mapInstance["Inner.Dest.Inner.Desc"] = "inner dest inner desc" + + //innerMap := make(map[string]interface{}) + //innerMap["Name"] = "Innername" + + //a := new(Src) + ////a.Inner = new(SrcInner) + // + //stime := time.Now().UnixNano() + //for i := 0; i < 1000000; i++ { + // err := structutils.Map2Struct(mapInstance, a) + // if err != nil { + // fmt.Println(err) + // } + //} + //etime := time.Now().UnixNano() + //fmt.Println(etime - stime) + //if err != nil { + // fmt.Println(err) + //} else { + // fmt.Println(a) + //} + + s := new(Src) + //name, b := structutils.IndirectType(reflect.TypeOf(s)).FieldByName("Inner") + //if structutils.IndirectType(name.Type).Kind() != reflect.Struct { + // fmt.Println(name.Name + "不是结构体") + //} else { + // //innerType := name.Type + // innerValue := structutils.Indirect(reflect.ValueOf(s)).FieldByName("Inner") + // //if innerValue.IsValid() && innerValue.IsNil() { + // // innerValue.Set(reflect.New(innerValue.Type().Elem())) + // //} + // if !innerValue.IsValid() { + // fmt.Println("is valid") + // } else { + // //innerValue.Set(reflect.New(innerValue.Type())) + // fmt.Println(innerValue.CanSet()) + // fmt.Println(innerValue.CanAddr()) + // //mapstructure.Decode(innerMap, innerValue.Addr().Interface()) + // } + // + //} + // + //fmt.Println(name, b) + //将 map 转换为指定的结构体 + // if err := decode(mapInstance, &s); err != nil { + // fmt.Println(err) + // } + fmt.Printf("map2struct后得到的 struct 内容为:%v", s) +} + +func getPrefixKeyMap(m map[string]any) map[string]map[string]any { + key2map := make(map[string]map[string]any) + for k, v := range m { + if !strings.Contains(k, ".") { + continue + } + lastIndex := strings.LastIndex(k, ".") + prefix := k[0:lastIndex] + m2 := key2map[prefix] + if m2 == nil { + key2map[prefix] = map[string]any{k[lastIndex+1:]: v} + } else { + m2[k[lastIndex+1:]] = v + } + delete(m, k) + } + return key2map +} + +func TestReflect(t *testing.T) { + type dog struct { + LegCount int + } + // 获取dog实例的反射值对象 + valueOfDog := reflect.ValueOf(&dog{}).Elem() + + // 获取legCount字段的值 + vLegCount := valueOfDog.FieldByName("LegCount") + + fmt.Println(vLegCount.CanSet()) + fmt.Println(vLegCount.CanAddr()) + // 尝试设置legCount的值(这里会发生崩溃) + vLegCount.SetInt(4) +} + +func TestTemplateResolve(t *testing.T) { + d := make(map[string]string) + d["Name"] = "黄先生" + d["Age"] = "23jlfdsjf" + resolve := TemplateResolve("{{.Name}} is name, and {{.Age}} is age", d) + fmt.Println(resolve) + +} diff --git a/kit/utils/template.go b/kit/utils/template.go new file mode 100644 index 0000000..285527d --- /dev/null +++ b/kit/utils/template.go @@ -0,0 +1,28 @@ +package utils + +import ( + "bytes" + "text/template" +) + +func parse(t *template.Template, vars any) string { + var tmplBytes bytes.Buffer + + err := t.Execute(&tmplBytes, vars) + if err != nil { + panic(err) + } + return tmplBytes.String() +} + +// 模板字符串解析 +// str 模板字符串 +// vars 参数变量 +func TemplateParse(str string, vars any) string { + tmpl, err := template.New("tmpl").Parse(str) + + if err != nil { + panic(err) + } + return parse(tmpl, vars) +} diff --git a/kit/utils/tree_utils.go b/kit/utils/tree_utils.go new file mode 100644 index 0000000..f14ae36 --- /dev/null +++ b/kit/utils/tree_utils.go @@ -0,0 +1,74 @@ +package utils + +// ConvertToINodeArray 其他的结构体想要生成菜单树,直接实现这个接口 +type INode interface { + // GetId获取id + GetId() int + // GetPid 获取父id + GetPid() int + // IsRoot 判断当前节点是否是顶层根节点 + IsRoot() bool + + SetChildren(childern any) +} + +type INodes []INode + +func (nodes INodes) Len() int { + return len(nodes) +} +func (nodes INodes) Swap(i, j int) { + nodes[i], nodes[j] = nodes[j], nodes[i] +} +func (nodes INodes) Less(i, j int) bool { + return nodes[i].GetId() < nodes[j].GetId() +} + +// GenerateTree 自定义的结构体实现 INode 接口后调用此方法生成树结构 +// nodes 需要生成树的节点 +// selectedNode 生成树后选中的节点 +// menuTrees 生成成功后的树结构对象 +func GenerateTree(nodes []INode) (trees []INode) { + trees = []INode{} + // 定义顶层根和子节点 + var roots, childs []INode + for _, v := range nodes { + if v.IsRoot() { + // 判断顶层根节点 + roots = append(roots, v) + } + childs = append(childs, v) + } + + for _, v := range roots { + // 递归 + setChildren(v, childs) + trees = append(trees, v) + } + return +} + +// recursiveTree 递归生成树结构 +// tree 递归的树对象 +// nodes 递归的节点 +// selectedNodes 选中的节点 +func setChildren(parent INode, nodes []INode) { + children := []INode{} + for _, v := range nodes { + if v.IsRoot() { + // 如果当前节点是顶层根节点就跳过 + continue + } + if parent.GetId() == v.GetPid() { + children = append(children, v) + } + } + if len(children) == 0 { + return + } + + parent.SetChildren(children) + for _, c := range children { + setChildren(c, nodes) + } +} diff --git a/kit/utils/yml.go b/kit/utils/yml.go new file mode 100644 index 0000000..60e75ea --- /dev/null +++ b/kit/utils/yml.go @@ -0,0 +1,27 @@ +package utils + +import ( + "errors" + "io/ioutil" + + "gopkg.in/yaml.v3" +) + +// 从指定路径加载yaml文件 +func LoadYml(path string, out any) error { + yamlFileBytes, readErr := ioutil.ReadFile(path) + if readErr != nil { + return readErr + } + // yaml解析 + err := yaml.Unmarshal(yamlFileBytes, out) + if err != nil { + return errors.New("无法解析 [" + path + "] -- " + err.Error()) + } + return nil +} + +func LoadYmlByString(yamlStr string, out any) error { + // yaml解析 + return yaml.Unmarshal([]byte(yamlStr), out) +} diff --git a/kit/ws/msg.go b/kit/ws/msg.go new file mode 100644 index 0000000..3e3a20a --- /dev/null +++ b/kit/ws/msg.go @@ -0,0 +1,27 @@ +package ws + +const SuccessMsgType = 1 +const ErrorMsgType = 0 +const InfoMsgType = 2 + +// websocket消息 +type Msg struct { + Type int `json:"type"` // 消息类型 + Title string `json:"title"` // 消息标题 + Msg string `json:"msg"` // 消息内容 +} + +// 普通消息 +func NewMsg(title, msg string) *Msg { + return &Msg{Type: InfoMsgType, Title: title, Msg: msg} +} + +// 成功消息 +func SuccessMsg(title, msg string) *Msg { + return &Msg{Type: SuccessMsgType, Title: title, Msg: msg} +} + +// 错误消息 +func ErrMsg(title, msg string) *Msg { + return &Msg{Type: ErrorMsgType, Title: title, Msg: msg} +} diff --git a/kit/ws/ws.go b/kit/ws/ws.go new file mode 100644 index 0000000..25665b9 --- /dev/null +++ b/kit/ws/ws.go @@ -0,0 +1,97 @@ +package ws + +import ( + "encoding/json" + "net/http" + "sync" + "time" + + "pandax/kit/logger" + + "github.com/gorilla/websocket" +) + +var Upgrader = websocket.Upgrader{ + ReadBufferSize: 1024, + WriteBufferSize: 1024 * 1024 * 10, + CheckOrigin: func(r *http.Request) bool { + return true + }, +} + +type Connection struct { + conn *websocket.Conn + lock sync.Mutex +} + +var connections = make(map[uint64]*Connection) +var connLock sync.Mutex + +func init() { + go checkConn() +} + +// 放置ws连接 +func Put(userId uint64, conn *websocket.Conn) { + connLock.Lock() + defer connLock.Unlock() + + Delete(userId) + connections[userId] = &Connection{ + conn: conn, + } +} + +func checkConn() { + heartbeat := time.Duration(20) * time.Second + tick := time.NewTicker(heartbeat) + for range tick.C { + connLock.Lock() + for uid, conn := range connections { + conn.lock.Lock() + err := conn.conn.WriteControl(websocket.PingMessage, []byte("ping"), time.Now().Add(heartbeat/2)) + conn.lock.Unlock() + if err != nil { + logger.Log.Info("删除ping失败的websocket连接:uid = ", uid) + Delete(uid) + } + } + connLock.Unlock() + } +} + +// 删除ws连接 +func Delete(userid uint64) { + connLock.Lock() + defer connLock.Unlock() + + conn := connections[userid] + if conn != nil { + conn.lock.Lock() + conn.conn.Close() + conn.lock.Unlock() + delete(connections, userid) + } +} + +// 对指定用户发送消息 +func SendMsg(userId uint64, msg *Msg) { + connLock.Lock() + defer connLock.Unlock() + + conn := connections[userId] + if conn != nil { + conn.lock.Lock() + defer conn.lock.Unlock() + + bytes, err := json.Marshal(msg) + if err != nil { + logger.Log.Error("发送消息失败:", err) + return + } + err = conn.conn.WriteMessage(websocket.TextMessage, bytes) + if err != nil { + logger.Log.Error("发送消息失败:", err) + } + } +} diff --git a/main.go b/main.go new file mode 100644 index 0000000..3b0c528 --- /dev/null +++ b/main.go @@ -0,0 +1,84 @@ +package main + +import ( + "context" + "log" + "os" + "os/signal" + "pandax/kit/logger" + "pandax/kit/restfulx" + "pandax/kit/starter" + "pandax/pkg/config" + "pandax/pkg/global" + "pandax/pkg/initialize" + "pandax/pkg/middleware" + "syscall" + + "github.com/spf13/cobra" +) + +var ( + configFile string +) + +var rootCmd = &cobra.Command{ + Use: "轻量级的xScript运行时.", + Short: `仓湖云函数数字应用引擎`, + PreRun: func(cmd *cobra.Command, args []string) { + if configFile != "" { + global.Conf = config.InitConfig(configFile) + global.Log = logger.InitLog(global.Conf.Log.File.GetFilename(), global.Conf.Log.Level) + dbGorm := starter.DbGorm{Type: global.Conf.Server.DbType} + if global.Conf.Server.DbType == "mysql" { + dbGorm.Dsn = global.Conf.Mysql.Dsn() + dbGorm.MaxIdleConns = global.Conf.Mysql.MaxIdleConns + dbGorm.MaxOpenConns = global.Conf.Mysql.MaxOpenConns + } else { + dbGorm.Dsn = global.Conf.Postgresql.PgDsn() + dbGorm.MaxIdleConns = global.Conf.Postgresql.MaxIdleConns + dbGorm.MaxOpenConns = global.Conf.Postgresql.MaxOpenConns + } + global.Db = dbGorm.GormInit() + global.Log.Infof("%s连接成功", global.Conf.Server.DbType) + } else { + global.Log.Error("请配置config") + os.Exit(1) + } + }, + Run: func(cmd *cobra.Command, args []string) { + // 前置 函数 + restfulx.UseBeforeHandlerInterceptor(middleware.PermissionHandler) + // 后置 函数 + restfulx.UseAfterHandlerInterceptor(middleware.LogHandler) + + app := initialize.InitRouter() + global.Log.Info("路由初始化完成") + app.Start(context.TODO()) + stop := make(chan os.Signal, 1) + signal.Notify(stop, syscall.SIGTERM, os.Interrupt) + <-stop + if err := app.Stop(context.TODO()); err != nil { + log.Fatalf("应用结束失败: %s", err) + os.Exit(-3) + } + }, +} + +func init() { + rootCmd.Flags().StringVar(&configFile, "config", getEnvStr("SYS_CONFIG", "./config.yml"), "system config file path.") +} + +func getEnvStr(env string, defaultValue string) string { + v := os.Getenv(env) + if v == "" { + return defaultValue + } + return v +} + +func main() { + if err := rootCmd.Execute(); err != nil { + rootCmd.PrintErrf("请使用 root 权限执行指令: %s", err) + os.Exit(1) + } +} diff --git a/pandax_iot.sql b/pandax_iot.sql new file mode 100644 index 0000000..e2cbe5b --- /dev/null +++ b/pandax_iot.sql @@ -0,0 +1,1788 @@ +/* + Navicat Premium Data Transfer + + Source Server : localhost_mysql-8.0 + Source Server Type : MySQL + Source Server Version : 80023 + Source Host : localhost:3306 + Source Schema : pandax_iot + + Target Server Type : MySQL + Target Server Version : 80023 + File Encoding : 65001 + + Date: 24/10/2023 16:19:56 +*/ + +SET NAMES utf8mb4; +SET FOREIGN_KEY_CHECKS = 0; + +CREATE DATABASE IF NOT EXISTS pandax_iot; + +-- ---------------------------- +-- Table structure for casbin_rule +-- ---------------------------- +DROP TABLE IF EXISTS `casbin_rule`; +CREATE TABLE `casbin_rule` ( + `ptype` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `v0` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `v1` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `v2` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `v3` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `v4` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `v5` varchar(100) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `id` int(0) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) USING BTREE, + UNIQUE INDEX `idx_casbin_rule`(`ptype`, `v0`, `v1`, `v2`, `v3`, `v4`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 10604 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of casbin_rule +-- ---------------------------- +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api/list', 'GET', '', '', '', 8679); +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api/all', 'GET', '', '', '', 8680); +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api/getPolicyPathByRoleId', 'GET', '', '', '', 8681); +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api/:id', 'GET', '', '', '', 8682); +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api', 'POST', '', '', '', 8683); +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api', 'PUT', '', '', '', 8684); +INSERT INTO `casbin_rule` VALUES ('p', 'test', '/system/api/:id', 'DELETE', '', '', '', 8685); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/api/list', 'GET', '', '', '', 10162); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/api/all', 'GET', '', '', '', 10163); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/api/getPolicyPathByRoleId', 'GET', '', '', '', 10164); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/api/:id', 'GET', '', '', '', 10165); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/config/list', 'GET', '', '', '', 10166); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/config/configKey', 'GET', '', '', '', 10167); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/config/:configId', 'GET', '', '', '', 10168); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/group/list', 'GET', '', '', '', 10169); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/group/list/all', 'GET', '', '', '', 10170); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/group/list/tree', 'GET', '', '', '', 10171); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/group/:id', 'GET', '', '', '', 10172); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/list', 'GET', '', '', '', 10173); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/:id', 'GET', '', '', '', 10174); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/group/list/tree/label', 'GET', '', '', '', 10175); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/list/all', 'GET', '', '', '', 10176); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/:id/status', 'GET', '', '', '', 10177); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/alarm/list', 'GET', '', '', '', 10178); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/cmd/list', 'GET', '', '', '', 10179); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/:id/property/history', 'GET', '', '', '', 10180); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/panel', 'GET', '', '', '', 10181); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/alarm/panel', 'GET', '', '', '', 10182); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/dict/type/list', 'GET', '', '', '', 10183); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/dict/type/:dictId', 'GET', '', '', '', 10184); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/dict/data/list', 'GET', '', '', '', 10185); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/dict/data/type', 'GET', '', '', '', 10186); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/dict/data/:dictCode', 'GET', '', '', '', 10187); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/develop/code/table/db/list', 'GET', '', '', '', 10188); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/develop/code/table/list', 'GET', '', '', '', 10189); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/develop/code/table/info/:tableId', 'GET', '', '', '', 10190); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/develop/code/table/info/tableName', 'GET', '', '', '', 10191); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/develop/code/table/tableTree', 'GET', '', '', '', 10192); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/develop/code/gen/preview/:tableId', 'GET', '', '', '', 10193); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/job/list', 'GET', '', '', '', 10194); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/job/:jobId', 'GET', '', '', '', 10195); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/log/logLogin/list', 'GET', '', '', '', 10196); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/log/logOper/list', 'GET', '', '', '', 10197); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/menu/menuTreeSelect', 'GET', '', '', '', 10198); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/menu/menuRole', 'GET', '', '', '', 10199); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/menu/roleMenuTreeSelect/:roleId', 'GET', '', '', '', 10200); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/menu/menuPaths', 'GET', '', '', '', 10201); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/menu/list', 'GET', '', '', '', 10202); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/menu/:menuId', 'GET', '', '', '', 10203); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/notice/list', 'GET', '', '', '', 10204); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/organization/list', 'GET', '', '', '', 10205); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/organization/:organizationId', 'GET', '', '', '', 10206); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/organization/roleOrganizationTreeSelect/:roleId', 'GET', '', '', '', 10207); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/organization/organizationTree', 'GET', '', '', '', 10208); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/ota/list', 'GET', '', '', '', 10209); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/ota/:id', 'GET', '', '', '', 10210); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/post/list', 'GET', '', '', '', 10211); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/post/:postId', 'GET', '', '', '', 10212); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/category/list', 'GET', '', '', '', 10213); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/category/list/all', 'GET', '', '', '', 10214); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/category/list/tree', 'GET', '', '', '', 10215); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/category/:id', 'GET', '', '', '', 10216); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/:id', 'GET', '', '', '', 10217); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/list', 'GET', '', '', '', 10218); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/category/list/tree/label', 'GET', '', '', '', 10219); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/list/all', 'GET', '', '', '', 10220); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/product/:id/tsl', 'GET', '', '', '', 10221); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/role/list', 'GET', '', '', '', 10222); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/role/:roleId', 'GET', '', '', '', 10223); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/rule/chain/list', 'GET', '', '', '', 10224); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/rule/chain/:ruleId', 'GET', '', '', '', 10225); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/rule/chain/list/label', 'GET', '', '', '', 10226); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/rule/chain/log/list', 'GET', '', '', '', 10227); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/visual/screen/:screenId', 'GET', '', '', '', 10228); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/visual/screen/list', 'GET', '', '', '', 10229); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/visual/screen/group/list', 'GET', '', '', '', 10230); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/visual/screen/group/list/tree', 'GET', '', '', '', 10231); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/visual/screen/group/list/all', 'GET', '', '', '', 10232); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/visual/screen/group/:id', 'GET', '', '', '', 10233); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/template/list', 'GET', '', '', '', 10234); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/template/:id', 'GET', '', '', '', 10235); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/device/template/list/all', 'GET', '', '', '', 10236); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/user/list', 'GET', '', '', '', 10237); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/user/getById/:userId', 'GET', '', '', '', 10238); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/user/getInit', 'GET', '', '', '', 10239); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/system/user/getRoPo', 'GET', '', '', '', 10240); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/video/ys/device/list', 'GET', '', '', '', 10241); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/video/ys/:deviceSerial/channel', 'GET', '', '', '', 10242); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/video/ys/:deviceSerial/channel/live', 'GET', '', '', '', 10243); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/video/ys/:deviceSerial/ptz/start', 'GET', '', '', '', 10244); +INSERT INTO `casbin_rule` VALUES ('p', 'manage', '/video/ys/:deviceSerial/ptz/stop', 'GET', '', '', '', 10245); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api/list', 'GET', '', '', '', 10425); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api/all', 'GET', '', '', '', 10426); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api/getPolicyPathByRoleId', 'GET', '', '', '', 10427); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api/:id', 'GET', '', '', '', 10428); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api', 'POST', '', '', '', 10429); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api', 'PUT', '', '', '', 10430); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/api/:id', 'DELETE', '', '', '', 10431); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/config/list', 'GET', '', '', '', 10432); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/config/configKey', 'GET', '', '', '', 10433); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/config/:configId', 'GET', '', '', '', 10434); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/config', 'POST', '', '', '', 10435); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/config', 'PUT', '', '', '', 10436); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/config/:configId', 'DELETE', '', '', '', 10437); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group/list', 'GET', '', '', '', 10438); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group/list/all', 'GET', '', '', '', 10439); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group/list/tree', 'GET', '', '', '', 10440); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group/:id', 'GET', '', '', '', 10441); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group', 'POST', '', '', '', 10442); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group', 'PUT', '', '', '', 10443); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group/:id', 'DELETE', '', '', '', 10444); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/list', 'GET', '', '', '', 10445); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/:id', 'GET', '', '', '', 10446); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device', 'POST', '', '', '', 10447); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/:id', 'DELETE', '', '', '', 10448); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device', 'PUT', '', '', '', 10449); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/group/list/tree/label', 'GET', '', '', '', 10450); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/list/all', 'GET', '', '', '', 10451); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/:id/status', 'GET', '', '', '', 10452); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/alarm/list', 'GET', '', '', '', 10453); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/alarm', 'PUT', '', '', '', 10454); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/alarm/:id', 'DELETE', '', '', '', 10455); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/cmd/list', 'GET', '', '', '', 10456); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/cmd', 'POST', '', '', '', 10457); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/cmd/:id', 'DELETE', '', '', '', 10458); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/:id/attribute/down', 'GET', '', '', '', 10459); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/:id/property/history', 'GET', '', '', '', 10460); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/:id/allot/org', 'GET', '', '', '', 10461); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/panel', 'GET', '', '', '', 10462); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/alarm/panel', 'GET', '', '', '', 10463); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/type/list', 'GET', '', '', '', 10464); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/type/:dictId', 'GET', '', '', '', 10465); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/type', 'POST', '', '', '', 10466); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/type', 'PUT', '', '', '', 10467); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/type/:dictId', 'DELETE', '', '', '', 10468); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/type/export', 'GET', '', '', '', 10469); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/data/list', 'GET', '', '', '', 10470); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/data/type', 'GET', '', '', '', 10471); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/data/:dictCode', 'GET', '', '', '', 10472); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/data', 'POST', '', '', '', 10473); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/data', 'PUT', '', '', '', 10474); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/dict/data/:dictCode', 'DELETE', '', '', '', 10475); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table/db/list', 'GET', '', '', '', 10476); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table/list', 'GET', '', '', '', 10477); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table/info/:tableId', 'GET', '', '', '', 10478); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table/info/tableName', 'GET', '', '', '', 10479); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table/tableTree', 'GET', '', '', '', 10480); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table', 'POST', '', '', '', 10481); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table', 'PUT', '', '', '', 10482); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/table/:tableId', 'DELETE', '', '', '', 10483); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/gen/preview/:tableId', 'GET', '', '', '', 10484); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/gen/code/:tableId', 'GET', '', '', '', 10485); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/develop/code/gen/configure/:tableId', 'GET', '', '', '', 10486); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/list', 'GET', '', '', '', 10487); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job', 'POST', '', '', '', 10488); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job', 'PUT', '', '', '', 10489); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/:jobId', 'GET', '', '', '', 10490); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/:jobId', 'DELETE', '', '', '', 10491); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/stop/:jobId', 'GET', '', '', '', 10492); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/start/:jobId', 'GET', '', '', '', 10493); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/log/list', 'GET', '', '', '', 10494); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/log/all', 'DELETE', '', '', '', 10495); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/log/:logId', 'DELETE', '', '', '', 10496); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/job/changeStatus', 'PUT', '', '', '', 10497); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/log/logLogin/list', 'GET', '', '', '', 10498); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/log/logLogin/:infoId', 'DELETE', '', '', '', 10499); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/log/logLogin/all', 'DELETE', '', '', '', 10500); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/log/logOper/list', 'GET', '', '', '', 10501); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/log/logOper/:operId', 'DELETE', '', '', '', 10502); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/log/logOper/all', 'DELETE', '', '', '', 10503); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/menuTreeSelect', 'GET', '', '', '', 10504); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/menuRole', 'GET', '', '', '', 10505); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/roleMenuTreeSelect/:roleId', 'GET', '', '', '', 10506); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/menuPaths', 'GET', '', '', '', 10507); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/list', 'GET', '', '', '', 10508); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/:menuId', 'GET', '', '', '', 10509); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu', 'POST', '', '', '', 10510); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu', 'PUT', '', '', '', 10511); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/menu/:menuId', 'DELETE', '', '', '', 10512); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/notice/list', 'GET', '', '', '', 10513); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/notice', 'POST', '', '', '', 10514); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/notice', 'PUT', '', '', '', 10515); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/notice/:noticeId', 'DELETE', '', '', '', 10516); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization/list', 'GET', '', '', '', 10517); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization/:organizationId', 'GET', '', '', '', 10518); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization/roleOrganizationTreeSelect/:roleId', 'GET', '', '', '', 10519); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization/organizationTree', 'GET', '', '', '', 10520); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization', 'POST', '', '', '', 10521); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization', 'PUT', '', '', '', 10522); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/organization/:organizationId', 'DELETE', '', '', '', 10523); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/ota/list', 'GET', '', '', '', 10524); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/ota', 'POST', '', '', '', 10525); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/ota', 'PUT', '', '', '', 10526); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/ota/:id', 'DELETE', '', '', '', 10527); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/ota/:id', 'GET', '', '', '', 10528); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/post/list', 'GET', '', '', '', 10529); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/post/:postId', 'GET', '', '', '', 10530); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/post', 'POST', '', '', '', 10531); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/post', 'PUT', '', '', '', 10532); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/post/:postId', 'DELETE', '', '', '', 10533); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category/list', 'GET', '', '', '', 10534); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category/list/all', 'GET', '', '', '', 10535); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category/list/tree', 'GET', '', '', '', 10536); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category/:id', 'GET', '', '', '', 10537); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category', 'POST', '', '', '', 10538); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category', 'PUT', '', '', '', 10539); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category/:id', 'DELETE', '', '', '', 10540); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/:id', 'DELETE', '', '', '', 10541); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/:id', 'GET', '', '', '', 10542); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product', 'PUT', '', '', '', 10543); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/list', 'GET', '', '', '', 10544); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product', 'POST', '', '', '', 10545); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/category/list/tree/label', 'GET', '', '', '', 10546); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/list/all', 'GET', '', '', '', 10547); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/product/:id/tsl', 'GET', '', '', '', 10548); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role/list', 'GET', '', '', '', 10549); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role/:roleId', 'GET', '', '', '', 10550); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role', 'POST', '', '', '', 10551); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role', 'PUT', '', '', '', 10552); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role/:roleId', 'DELETE', '', '', '', 10553); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role/changeStatus', 'PUT', '', '', '', 10554); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role/dataScope', 'PUT', '', '', '', 10555); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/role/export', 'GET', '', '', '', 10556); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/changeRoot', 'PUT', '', '', '', 10557); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/list', 'GET', '', '', '', 10558); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/:ruleId', 'DELETE', '', '', '', 10559); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain', 'PUT', '', '', '', 10560); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain', 'POST', '', '', '', 10561); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/:ruleId', 'GET', '', '', '', 10562); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/list/label', 'GET', '', '', '', 10563); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/clone/:ruleId', 'POST', '', '', '', 10564); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/log/list', 'GET', '', '', '', 10565); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/rule/chain/log/delete', 'GET', '', '', '', 10566); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen', 'PUT', '', '', '', 10567); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/:screenId', 'GET', '', '', '', 10568); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/list', 'GET', '', '', '', 10569); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/:screenId', 'DELETE', '', '', '', 10570); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen', 'POST', '', '', '', 10571); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/changeStatus', 'PUT', '', '', '', 10572); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group/list', 'GET', '', '', '', 10573); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group/list/tree', 'GET', '', '', '', 10574); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group/list/all', 'GET', '', '', '', 10575); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group/:id', 'GET', '', '', '', 10576); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group', 'POST', '', '', '', 10577); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group', 'PUT', '', '', '', 10578); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/visual/screen/group/:id', 'DELETE', '', '', '', 10579); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/template/list', 'GET', '', '', '', 10580); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/template', 'PUT', '', '', '', 10581); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/template/:id', 'GET', '', '', '', 10582); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/template/:id', 'DELETE', '', '', '', 10583); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/template', 'POST', '', '', '', 10584); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/device/template/list/all', 'GET', '', '', '', 10585); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/upload/up/oss', 'POST', '', '', '', 10586); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/upload/up', 'POST', '', '', '', 10587); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/list', 'GET', '', '', '', 10588); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/changeStatus', 'PUT', '', '', '', 10589); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/:userId', 'DELETE', '', '', '', 10590); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/avatar', 'POST', '', '', '', 10591); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/pwd', 'PUT', '', '', '', 10592); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/getById/:userId', 'GET', '', '', '', 10593); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/getInit', 'GET', '', '', '', 10594); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/getRoPo', 'GET', '', '', '', 10595); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user', 'POST', '', '', '', 10596); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user', 'PUT', '', '', '', 10597); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/system/user/export', 'GET', '', '', '', 10598); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/video/ys/device/list', 'GET', '', '', '', 10599); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/video/ys/:deviceSerial/channel', 'GET', '', '', '', 10600); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/video/ys/:deviceSerial/channel/live', 'GET', '', '', '', 10601); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/video/ys/:deviceSerial/ptz/start', 'GET', '', '', '', 10602); +INSERT INTO `casbin_rule` VALUES ('p', 'admin', '/video/ys/:deviceSerial/ptz/stop', 'GET', '', '', '', 10603); + +-- ---------------------------- +-- Table structure for demo_new_trend_of_diagnosis +-- ---------------------------- +DROP TABLE IF EXISTS `demo_new_trend_of_diagnosis`; +CREATE TABLE `demo_new_trend_of_diagnosis` ( + `date` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '日期', + `new_diagnosis` bigint(0) NULL DEFAULT NULL COMMENT '新增确诊', + `current_diagnosis` bigint(0) NULL DEFAULT NULL COMMENT '现有确诊' +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of demo_new_trend_of_diagnosis +-- ---------------------------- +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-10', 33, 505); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-11', 28, 506); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-12', 32, 512); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-13', 35, 523); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-14', 49, 542); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-15', 206, 727); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-16', 236, 935); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-17', 358, 1262); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-18', 258, 1497); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-19', 286, 1759); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-20', 317, 2097); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-21', 325, 2365); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-22', 743, 3098); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-23', 480, 3561); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-24', 612, 4143); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-25', 554, 4675); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-26', 655, 5036); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-27', 677, 5948); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-28', 570, 6480); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-29', 503, 6951); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-30', 381, 7303); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-5-31', 378, 7652); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-1', 362, 7983); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-2', 571, 8535); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-3', 610, 9110); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-4', 497, 9674); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-5', 541, 10049); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-6', 368, 10372); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-7', 233, 10552); +INSERT INTO `demo_new_trend_of_diagnosis` VALUES ('2021-6-8', 232, 10740); + +-- ---------------------------- +-- Table structure for dev_gen_table_columns +-- ---------------------------- +DROP TABLE IF EXISTS `dev_gen_table_columns`; +CREATE TABLE `dev_gen_table_columns` ( + `column_id` bigint(0) NOT NULL AUTO_INCREMENT, + `table_id` bigint(0) NULL DEFAULT NULL, + `table_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `column_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `column_comment` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `column_type` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `column_key` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `go_type` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `go_field` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `json_field` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `html_field` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_pk` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_increment` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_required` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_insert` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_edit` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_list` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `is_query` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `query_type` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `html_type` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `dict_type` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `sort` bigint(0) NULL DEFAULT NULL, + `link_table_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `link_table_class` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `link_table_package` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `link_label_id` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `link_label_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + PRIMARY KEY (`column_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 138 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of dev_gen_table_columns +-- ---------------------------- + +-- ---------------------------- +-- Table structure for dev_gen_tables +-- ---------------------------- +DROP TABLE IF EXISTS `dev_gen_tables`; +CREATE TABLE `dev_gen_tables` ( + `table_id` bigint(0) NOT NULL AUTO_INCREMENT, + `table_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `table_comment` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `class_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `tpl_category` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `package_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `module_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `business_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `function_name` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `function_author` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `options` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `remark` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pk_column` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pk_go_field` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pk_go_type` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `pk_json_field` varchar(191) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + PRIMARY KEY (`table_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 13 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of dev_gen_tables +-- ---------------------------- + +-- ---------------------------- +-- Table structure for device_alarms +-- ---------------------------- +DROP TABLE IF EXISTS `device_alarms`; +CREATE TABLE `device_alarms` ( + `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '告警名称', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `device_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `product_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `type` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '告警类型', + `level` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '告警级别', + `state` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '告警状态', + `details` varchar(1024) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '详情', + `time` datetime(0) NULL DEFAULT NULL COMMENT '告警时间', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of device_alarms +-- ---------------------------- + + +-- ---------------------------- +-- Table structure for device_cmd_log +-- ---------------------------- +DROP TABLE IF EXISTS `device_cmd_log`; +CREATE TABLE `device_cmd_log` ( + `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `device_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `cmd_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `cmd_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `state` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `response_content` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL, + `request_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `response_time` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `mode` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of device_cmd_log +-- ---------------------------- + + +-- ---------------------------- +-- Table structure for device_groups +-- ---------------------------- +DROP TABLE IF EXISTS `device_groups`; +CREATE TABLE `device_groups` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备分组名称', + `pid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '上级设备分组类型', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备分组路径', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备分组说明', + `sort` int(0) NULL DEFAULT NULL COMMENT '排序', + `ext` json NULL COMMENT '扩展', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of device_groups +-- ---------------------------- +INSERT INTO `device_groups` VALUES ('5h2eEVcqLw', 'panda', 2, '2023-06-30 08:52:37', '2023-06-30 08:52:37', '一层', 'eiAx7ZgWKg', '/0/eiAx7ZgWKg/5h2eEVcqLw', '', 1, 'null', '0'); +INSERT INTO `device_groups` VALUES ('9uOQ1Ku0PQ', 'panda', 2, '2023-10-14 17:43:25', '2023-10-17 10:10:07', '默认分组', '0', '/0/9uOQ1Ku0PQ', '未定义分组的设备都在这里面', 1, 'null', '0'); +INSERT INTO `device_groups` VALUES ('eiAx7ZgWKg', 'panda', 2, '2023-06-30 08:52:16', '2023-06-30 08:53:47', '1号楼', '0', '/0/eiAx7ZgWKg', '1号楼,位于园区东南角,安保人:张三,电话:11111', 1, 'null', '0'); + +-- ---------------------------- +-- Table structure for devices +-- ---------------------------- +DROP TABLE IF EXISTS `devices`; +CREATE TABLE `devices` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备名称', + `token` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备token', + `alias` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备别名', + `pid` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品Id', + `gid` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '分组Id', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品说明', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + `ota_version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '固件版本', + `ext` json NULL COMMENT '拓展', + `parent_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '父Id,子设备时,父设备为网关', + `device_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备类型', + `link_status` varchar(10) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '连接状态', + `last_time` datetime(0) NULL DEFAULT NULL COMMENT '最后一次在线时间', + PRIMARY KEY (`id`) USING BTREE, + INDEX `fk_devices_product`(`pid`) USING BTREE, + INDEX `fk_devices_device_group`(`gid`) USING BTREE, + CONSTRAINT `fk_devices_device_group` FOREIGN KEY (`gid`) REFERENCES `device_groups` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT, + CONSTRAINT `fk_devices_product` FOREIGN KEY (`pid`) REFERENCES `products` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of devices +-- ---------------------------- +INSERT INTO `devices` VALUES ('2HbCMj8WOQ', 'panda', 2, '2023-10-12 19:22:25', '2023-10-12 19:22:25', 'sparksiiot', 'ZGRlMTE2NmEtYWY5MS0zZDRmLTlhYTktZWE1Njg5Yjk0MTlm', '星原网关', 'uqNNwYJ5rw', '5h2eEVcqLw', '', '0', '', 'null', '', 'gateway', 'online', '2023-10-14 21:32:48'); +INSERT INTO `devices` VALUES ('68zSC94dFQ', 'panda', 2, '2023-07-31 14:23:13', '2023-07-31 14:23:13', 'ctr453', '', '智能控制器453', 'p_bf52caf91f7cdd2abb52eaaf', 'eiAx7ZgWKg', '', '0', '', 'null', 'rC82hwE6iw', 'gatewayS', 'offline', '2023-09-28 10:08:07'); +INSERT INTO `devices` VALUES ('9GOIPOI6GQ', 'panda', 2, '2023-07-26 22:23:16', '2023-09-07 11:35:22', 'ws432', 'YWRlMTA0MmYtMzc2MS0zZTljLThjNjAtMzNhMzg4ZjdkOGQ3', '温湿度器', 'p_3ba460634520cf4590dc90e5', 'eiAx7ZgWKg', '设备说明1', '0', '', '{\"location\": {\"lat\": 37.037581, \"lng\": 118.18431, \"address\": \"山东省淄博市张店区傅家镇淄博市植物园志愿者阅览室\", \"position\": [118.027698, 36.791573]}}', '', 'direct', 'online', '2023-10-14 12:27:55'); +INSERT INTO `devices` VALUES ('k2opRSpr-g', 'panda', 7, '2023-10-14 22:21:02', '2023-10-17 10:09:05', 'testsub', '', '测试子设备', 'I_HlHDdh_Q', '9uOQ1Ku0PQ', '', '0', '', 'null', 'rC82hwE6iw', 'gatewayS', 'online', '2023-10-17 16:10:48'); +INSERT INTO `devices` VALUES ('l7HF7UZCEA', 'panda', 2, '2023-09-28 09:22:41', '2023-09-28 09:22:41', 'zhilian01', 'MTZlZDM3OGItODdiOS0zZDIwLWJmZjQtMWY3ODM3YzRhN2Ji', '直连设备', 'p_3ba460634520cf4590dc90e5', '5h2eEVcqLw', '', '0', '', 'null', '', 'direct', 'inactive', '2023-09-28 09:22:41'); +INSERT INTO `devices` VALUES ('lCtIzLLdIQ', 'panda', 2, '2023-09-27 11:47:47', '2023-09-27 11:47:47', 'TestTcp', 'OTYwNTE3ODUtYTFhMy0zOTIwLWIwZmItYzc3OWVkZWZjOTUw', 'TCP透传', 'mSOWuiA97g', '5h2eEVcqLw', '', '0', '', 'null', '', 'direct', 'offline', '2023-10-08 13:52:06'); +INSERT INTO `devices` VALUES ('qmWqYlY6-w', 'panda', 2, '2023-09-27 15:08:56', '2023-09-27 15:08:56', 'httpde1', 'MDVlY2MyNzYtMzczMS0zN2Y2LTk1MWMtMDMwM2ZjNmQyNjlm', 'HTTP设备测试', 'ek2WUADl6g', '5h2eEVcqLw', '', '0', '', 'null', '', 'direct', 'offline', '2023-10-07 15:03:32'); +INSERT INTO `devices` VALUES ('rC82hwE6iw', 'panda', 7, '2023-09-23 14:22:18', '2023-09-16 10:03:12', 'gateway4353', 'ZTg0ZDNkZDItOWQ1Mi0zYjM2LTg1NWQtYTI0NmE0NDcyOTM2', '智能网关4353', 'p_cdbb1eccd902018d51fe062e', 'eiAx7ZgWKg', '', '0', '', '{\"location\": {\"label\": \"\", \"address\": \"天津市西青区中北镇现快速处理中心\", \"content\": \"\", \"position\": [117.100495, 39.135469]}}', '', 'gateway', 'offline', '2023-10-17 16:35:39'); +INSERT INTO `devices` VALUES ('YbWKD905pQ', 'panda', 2, '2023-10-12 19:23:29', '2023-10-12 19:23:29', 'Panasonic', '', '松下PLC', 'M32969chcw', '5h2eEVcqLw', '', '0', '', 'null', '2HbCMj8WOQ', 'gatewayS', 'online', '2023-10-12 19:53:59'); + +-- ---------------------------- +-- Table structure for job_logs +-- ---------------------------- +DROP TABLE IF EXISTS `job_logs`; +CREATE TABLE `job_logs` ( + `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '任务名称', + `entry_id` int(0) NULL DEFAULT NULL COMMENT '任务id', + `target_invoke` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '调用方法', + `log_info` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '日志信息', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of job_logs +-- ---------------------------- + +-- ---------------------------- +-- Table structure for jobs +-- ---------------------------- +DROP TABLE IF EXISTS `jobs`; +CREATE TABLE `jobs` ( + `id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `job_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称', + `target_invoke` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '调用目标', + `target_args` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '目标传参', + `job_content` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '目标传参 要执行的内容', + `cron_expression` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'cron表达式', + `misfire_policy` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '执行策略', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + `entry_id` int(0) NULL DEFAULT NULL COMMENT 'job启动时返回的id', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of jobs +-- ---------------------------- +INSERT INTO `jobs` VALUES ('wvz4D6CXSw', 'panda', 2, '2023-08-08 17:29:30', '2023-08-08 17:42:58', 'adsa', 'cronDevice', 'd_1928b99619910dae5a001fa7', '{\"设备下发\":\"asdas\"}', ' 0/10 * * * * ?', '1', '0', 0); + +-- ---------------------------- +-- Table structure for log_logins +-- ---------------------------- +DROP TABLE IF EXISTS `log_logins`; +CREATE TABLE `log_logins` ( + `info_id` bigint(0) NOT NULL AUTO_INCREMENT, + `username` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '用户名', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态', + `ipaddr` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ip地址', + `login_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '归属地', + `browser` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '浏览器', + `os` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '系统', + `platform` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '固件', + `login_time` timestamp(0) NULL DEFAULT NULL COMMENT '登录时间', + `create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人', + `update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '更新者', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `msg` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`info_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 3622 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of log_logins +-- ---------------------------- + +-- ---------------------------- +-- Table structure for log_opers +-- ---------------------------- +DROP TABLE IF EXISTS `log_opers`; +CREATE TABLE `log_opers` ( + `oper_id` bigint(0) NOT NULL AUTO_INCREMENT, + `title` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作的模块', + `business_type` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '0其它 1新增 2修改 3删除', + `method` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求方法', + `oper_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作人员', + `oper_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作url', + `oper_ip` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作IP', + `oper_location` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '操作地点', + `oper_param` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '请求参数', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '0=正常,1=异常', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`oper_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1759 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of log_opers +-- ---------------------------- + +-- ---------------------------- +-- Table structure for product_categories +-- ---------------------------- +DROP TABLE IF EXISTS `product_categories`; +CREATE TABLE `product_categories` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品类型名称', + `pid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '上级产品类型', + `path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品类型路径', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品类型说明', + `sort` int(0) NULL DEFAULT NULL COMMENT '排序', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of product_categories +-- ---------------------------- +INSERT INTO `product_categories` VALUES ('-_FMhNN1QA', 'panda', 2, '2023-10-10 14:55:42', '2023-10-10 14:55:43', '温度传感器', '0', '/0/-_FMhNN1QA', '', 3, '0'); +INSERT INTO `product_categories` VALUES ('4BqMUN37_g', 'panda', 2, '2023-10-10 14:56:41', '2023-10-10 14:56:42', 'xx温度传感器', '-_FMhNN1QA', '/0/-_FMhNN1QA/4BqMUN37_g', '', 1, '0'); +INSERT INTO `product_categories` VALUES ('8f_oaHIo9A', 'panda', 2, '2023-10-12 19:17:09', '2023-10-12 19:17:09', 'SBOX-G系列网关', '0', '/0/8f_oaHIo9A', '', 4, '0'); +INSERT INTO `product_categories` VALUES ('KVys13MMsA', 'panda', 2, '2023-10-14 17:36:30', '2023-10-17 10:09:57', '平台默认产品', '0', '/0/KVys13MMsA', '未定义产品的设备,默认继承的产品', 1, '0'); +INSERT INTO `product_categories` VALUES ('oKQcjqY8ZQ', 'panda', 2, '2023-10-12 19:17:30', '2023-10-12 19:17:30', 'SBOX-G系列网关', '8f_oaHIo9A', '/0/8f_oaHIo9A/oKQcjqY8ZQ', '', 1, '0'); +INSERT INTO `product_categories` VALUES ('pc_8e12a1ec7ba3bffc1337e163', 'panda', 2, '2023-08-09 11:04:37', '2023-08-09 11:04:37', '海康摄像头', 'pc_d31572a0ceaa070f18cb669a', '/0/pc_d31572a0ceaa070f18cb669a/pc_8e12a1ec7ba3bffc1337e163', '', 1, '0'); +INSERT INTO `product_categories` VALUES ('pc_d31572a0ceaa070f18cb669a', 'panda', 2, '2023-08-09 11:04:00', '2023-08-09 11:04:00', '视频产品', '0', '/0/pc_d31572a0ceaa070f18cb669a', '', 1, '0'); +INSERT INTO `product_categories` VALUES ('pc61058315302171445335c3d5', 'panda', 2, '2023-06-29 17:50:30', '2023-06-29 17:50:31', ' 测试', '0', '/0/pc61058315302171445335c3d5', '', 1, '0'); +INSERT INTO `product_categories` VALUES ('pcd2e673d2cd92e860cff5d958', 'panda', 2, '2023-06-29 17:52:18', '2023-06-29 17:52:18', '啊实打实', 'pc61058315302171445335c3d5', '/0/pc61058315302171445335c3d5/pcd2e673d2cd92e860cff5d958', '', 2, '0'); + +-- ---------------------------- +-- Table structure for product_ota +-- ---------------------------- +DROP TABLE IF EXISTS `product_ota`; +CREATE TABLE `product_ota` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `pid` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品Id', + `is_latest` tinyint(0) NULL DEFAULT NULL COMMENT '最新版本', + `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '固件名称', + `version` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '固件版本', + `url` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '下载地址', + `check` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT 'md5校验值', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '说明', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of product_ota +-- ---------------------------- +INSERT INTO `product_ota` VALUES ('FlwNLfdNWg', '2023-10-05 12:09:26', '2023-10-05 12:09:26', 'p_3ba460634520cf4590dc90e5', 0, '测试固件', 'v1.1', '0683c172cdf300720c55ae418a8e83fc_20231005120900.zip', '4bb850e9e7ceb3e9f12fc35b8073eca3', ''); + +-- ---------------------------- +-- Table structure for product_templates +-- ---------------------------- +DROP TABLE IF EXISTS `product_templates`; +CREATE TABLE `product_templates` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `pid` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品Id', + `classify` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '模型归类', + `name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称', + `key` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '标识', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '属性说明', + `type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '数据类型', + `define` json NULL COMMENT '数据约束', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of product_templates +-- ---------------------------- +INSERT INTO `product_templates` VALUES ('5QH8bNo7iA', '2023-10-12 19:21:20', '2023-10-17 15:48:44', 'M32969chcw', 'telemetry', 'X00', 'X00', '', 'bool', '{\"boolDefine\": [{\"key\": \"0\", \"value\": \"正确\"}, {\"key\": \"1\", \"value\": \"失败\"}]}'); +INSERT INTO `product_templates` VALUES ('8AcZGXTL5A', '2023-10-12 19:21:30', '2023-10-12 19:21:30', 'M32969chcw', 'telemetry', 'Y00', 'Y00', '', 'bool', '{\"boolDefine\": [{\"key\": \"0\", \"value\": \"\"}, {\"key\": \"1\", \"value\": \"\"}]}'); +INSERT INTO `product_templates` VALUES ('iEPLrCC1gA', '2023-09-27 11:35:25', '2023-09-27 11:35:25', 'mSOWuiA97g', 'telemetry', '温度', 'temperature', '', 'float64', '{\"max\": 100, \"min\": 0, \"unit\": \"摄氏度\"}'); +INSERT INTO `product_templates` VALUES ('jvvLVElnLg', '2023-09-28 09:26:46', '2023-09-28 09:44:50', 'p_bf52caf91f7cdd2abb52eaaf', 'telemetry', '温度1', 'temperature', '', 'float64', '{}'); +INSERT INTO `product_templates` VALUES ('n_yL_KvBOg', '2023-10-17 10:46:14', '2023-10-17 10:46:14', 'I_HlHDdh_Q', 'telemetry', 'test', 'test', '', 'string', 'null'); +INSERT INTO `product_templates` VALUES ('R83jjHlUog', '2023-10-12 19:21:45', '2023-10-12 19:21:45', 'M32969chcw', 'telemetry', 'D0', 'D0', '', 'int64', '{}'); +INSERT INTO `product_templates` VALUES ('SrVmTxfd5A', '2023-10-17 15:50:22', '2023-10-17 16:11:39', 'I_HlHDdh_Q', 'telemetry', 'bolt', 'bolt', '', 'bool', '{\"boolDefine\": [{\"key\": \"0\", \"value\": \"失败\"}, {\"key\": \"1\", \"value\": \"正确\"}]}'); +INSERT INTO `product_templates` VALUES ('tm_14732f0fa234453e328bbd30', '2023-08-01 09:09:51', '2023-08-01 10:26:05', 'p_3ba460634520cf4590dc90e5', 'telemetry', '开关', 'open', '', 'bool', '{\"boolDefine\": [{\"key\": \"0\", \"value\": \"开\"}, {\"key\": \"1\", \"value\": \"关1\"}]}'); +INSERT INTO `product_templates` VALUES ('tm_377e0b1cc9812ab11464e2b4', '2023-08-01 09:22:54', '2023-08-01 09:23:09', 'p_3ba460634520cf4590dc90e5', 'telemetry', '测试参数', 'test', '', 'enum', '{\"enumDefine\": [{\"key\": \"0\", \"value\": \"开\"}, {\"key\": \"1\", \"value\": \"关\"}]}'); +INSERT INTO `product_templates` VALUES ('tm_43fa702e0c3aa6bb91d79e95', '2023-07-26 22:21:59', '2023-07-26 22:21:59', 'p_3ba460634520cf4590dc90e5', 'attributes', '编号', 'num', '', 'string', '{\"rw\": \"rw\", \"default_value\": \"23332442\"}'); +INSERT INTO `product_templates` VALUES ('tm_538231f64592eb53b6d46d12', '2023-09-08 13:57:19', '2023-09-08 13:57:19', 'p_cdbb1eccd902018d51fe062e', 'attributes', '版本号', 'version', '', 'string', '{\"rw\": \"r\", \"default_value\": \"v1.0\"}'); +INSERT INTO `product_templates` VALUES ('tm_925cec0662102b40fe33b7bb', '2023-07-26 22:20:45', '2023-07-26 22:20:45', 'p_3ba460634520cf4590dc90e5', 'telemetry', '湿度', 'humidity', '', 'float64', '{\"max\": \"100\", \"min\": \"1\", \"step\": 0.01, \"unit\": \"G\"}'); +INSERT INTO `product_templates` VALUES ('tm_ac52beea237bb9009f1029af', '2023-07-26 22:20:08', '2023-07-26 22:20:08', 'p_3ba460634520cf4590dc90e5', 'telemetry', '温度', 'temperature', '', 'float64', '{\"max\": \"100\", \"min\": \"1\", \"step\": 0.01, \"unit\": \"度\"}'); +INSERT INTO `product_templates` VALUES ('tm_e815087669adc6f9fcf6bcf4', '2023-08-01 14:14:47', '2023-08-01 14:14:47', 'p_3ba460634520cf4590dc90e5', 'commands', '重启', 'restart', '设备重启指令', '', '{\"input\": [{\"key\": \"aa\", \"name\": \"重启参数\", \"type\": \"int64\", \"define\": {\"max\": 100, \"min\": 1, \"step\": 1, \"unit\": \"KW\"}}], \"output\": []}'); +INSERT INTO `product_templates` VALUES ('tm-4991928839c4dec5c08109f5', '2023-07-21 10:38:02', '2023-07-21 10:38:02', 'p03d9a6fb450e8443456f41b0', 'telemetry', '电流', 'i', '', 'float64', '{\"max\": \"100\", \"min\": \"1\", \"step\": \"1\", \"unit\": \"A\"}'); +INSERT INTO `product_templates` VALUES ('tm-9e922dad5c325348c123103d', '2023-07-21 10:37:05', '2023-07-21 10:37:05', 'p03d9a6fb450e8443456f41b0', 'attributes', '序列号', 'ns', '', 'string', '{\"rw\": \"r\", \"default_value\": \"NS42342\"}'); +INSERT INTO `product_templates` VALUES ('tm-a2998852fd8c1507cfc8d0e1', '2023-07-21 10:37:39', '2023-07-21 10:57:49', 'p03d9a6fb450e8443456f41b0', 'telemetry', '电压', 'u', '', 'float64', '{\"max\": 100, \"min\": 1, \"step\": 1, \"unit\": \"V\"}'); +INSERT INTO `product_templates` VALUES ('UksLt1hVdQ', '2023-10-05 11:43:31', '2023-10-05 11:43:31', 'p_3ba460634520cf4590dc90e5', 'commands', '固件升级', 'ota', '', '', '{\"input\": [{\"key\": \"version\", \"name\": \"版本\", \"type\": \"string\", \"define\": {}, \"description\": \"要升级的版本\"}, {\"key\": \"url\", \"name\": \"固件路径\", \"type\": \"string\", \"define\": {}, \"description\": \"固件路径\"}, {\"key\": \"module\", \"name\": \"固件模块\", \"type\": \"string\", \"define\": {}}, {\"key\": \"check\", \"name\": \"校验和\", \"type\": \"string\", \"define\": {}, \"description\": \"md5校验,校验和\"}], \"output\": []}'); +INSERT INTO `product_templates` VALUES ('VFuqZIlNnQ', '2023-09-27 11:36:15', '2023-09-27 11:36:15', 'mSOWuiA97g', 'telemetry', '湿度', 'humidity', '', 'float64', '{\"max\": 100, \"min\": 0, \"unit\": \"RH\"}'); +INSERT INTO `product_templates` VALUES ('wR1s2TfugA', '2023-09-27 15:07:54', '2023-09-27 15:07:54', 'ek2WUADl6g', 'telemetry', '温度', 'temperature', '', 'float64', '{\"max\": 100, \"min\": 0, \"unit\": \"摄氏度\"}'); +INSERT INTO `product_templates` VALUES ('YoDVrJFyAg', '2023-09-27 16:43:14', '2023-09-27 16:43:14', 'mSOWuiA97g', 'commands', '关闭指示灯', 'closeD', '', '', '{\"input\": [{\"key\": \"close\", \"name\": \"关闭\", \"type\": \"string\", \"define\": {}}], \"output\": []}'); + +-- ---------------------------- +-- Table structure for products +-- ---------------------------- +DROP TABLE IF EXISTS `products`; +CREATE TABLE `products` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品名称', + `photo_url` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '图片地址', + `description` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品说明', + `product_category_id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '产品类型Id', + `protocol_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '协议名称', + `device_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '设备类型', + `rule_chain_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '规则链Id', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '状态', + PRIMARY KEY (`id`) USING BTREE, + INDEX `fk_products_product_category`(`product_category_id`) USING BTREE, + CONSTRAINT `fk_products_product_category` FOREIGN KEY (`product_category_id`) REFERENCES `product_categories` (`id`) ON DELETE RESTRICT ON UPDATE RESTRICT +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of products +-- ---------------------------- +INSERT INTO `products` VALUES ('ek2WUADl6g', 'panda', 2, '2023-09-27 15:07:02', '2023-09-27 15:07:02', 'HTTP设备产品', '', '', 'pcd2e673d2cd92e860cff5d958', 'HTTP', 'direct', 'rulee765e9ef022812a8b89dfb4c', '0'); +INSERT INTO `products` VALUES ('Fb3DlUja_Q', 'panda', 2, '2023-09-27 11:27:24', '2023-09-27 11:27:24', 'MQTT透传解析', '', '', 'pcd2e673d2cd92e860cff5d958', 'MQTT', 'direct', '', '0'); +INSERT INTO `products` VALUES ('I_HlHDdh_Q', 'panda', 2, '2023-10-14 17:37:14', '2023-10-17 11:36:32', '默认子网关', '', '', 'KVys13MMsA', 'MQTT', 'gatewayS', 'rulee765e9ef022812a8b89dfb4c', '0'); +INSERT INTO `products` VALUES ('kqEUXwsU9w', 'panda', 2, '2023-08-19 09:26:50', '2023-10-05 10:41:22', '测试产品1', '9b37cd4ca37090649adcee8bf17cfdcc_20230414141350.png', '', 'pcd2e673d2cd92e860cff5d958', 'MQTT', 'direct', 'rulee765e9ef022812a8b89dfb4c', '0'); +INSERT INTO `products` VALUES ('M32969chcw', 'panda', 2, '2023-10-12 19:20:20', '2023-10-12 19:20:20', '松下PLC', '', '', 'oKQcjqY8ZQ', 'MQTT', 'gatewayS', 'rulee765e9ef022812a8b89dfb4c', '0'); +INSERT INTO `products` VALUES ('mSOWuiA97g', 'panda', 2, '2023-09-27 11:25:11', '2023-09-27 11:26:20', 'TCP透传测试产品', '', '', 'pcd2e673d2cd92e860cff5d958', 'TCP', 'direct', 'mq1YRZbUgQ', '0'); +INSERT INTO `products` VALUES ('p_3ba460634520cf4590dc90e5', 'panda', 2, '2023-07-26 22:17:27', '2023-08-03 10:13:45', '测试产品', '', '', 'pcd2e673d2cd92e860cff5d958', 'MQTT', 'direct', 'rule_a37571bb6c45378b57803793', '0'); +INSERT INTO `products` VALUES ('p_bf52caf91f7cdd2abb52eaaf', 'panda', 2, '2023-07-31 14:16:29', '2023-07-31 14:16:29', '智能控制器', '', '', 'pcd2e673d2cd92e860cff5d958', 'MQTT', 'gatewayS', 'rulee765e9ef022812a8b89dfb4c', '0'); +INSERT INTO `products` VALUES ('p_cdbb1eccd902018d51fe062e', 'panda', 2, '2023-07-31 14:15:35', '2023-07-31 14:15:35', '网关设备', '', '网关设备', 'pcd2e673d2cd92e860cff5d958', 'MQTT', 'gateway', 'rulee765e9ef022812a8b89dfb4c', '0'); +INSERT INTO `products` VALUES ('uqNNwYJ5rw', 'panda', 2, '2023-10-12 19:19:17', '2023-10-12 19:19:17', '星原网关', '1df420e901be965018e95bac136ec17f_20231012191851.jpg', '', 'oKQcjqY8ZQ', 'MQTT', 'gateway', 'rulee765e9ef022812a8b89dfb4c', '0'); + +-- ---------------------------- +-- Table structure for rule_chain +-- ---------------------------- +DROP TABLE IF EXISTS `rule_chain`; +CREATE TABLE `rule_chain` ( + `id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL, + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `root` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '是否根节点1 根链 0 普通链', + `rule_name` varchar(50) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '名称', + `rule_base64` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'Base64缩略图', + `rule_remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '说明', + `rule_data_json` longtext CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL COMMENT 'Json数据', + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of rule_chain +-- ---------------------------- +INSERT INTO `rule_chain` VALUES ('mq1YRZbUgQ', 'panda', 2, '2023-07-21 16:17:51', '2023-09-27 11:40:38', '0', 'tcp透传原始数据解析', 'data:image/png;base64,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BAR5RxLyewIECBAgQIBACQXaWkqbTqN93/veF/369SuhiC4TIECAAAECBLpfQJDX/WOgBQQIECBAgACBhhFoayltmi11/vnnZ0tpXQQIECBAgAABAt0nIMjrPns1EyBAgAABAgQaSqCtpbTDhw/PZuGNGTOmodqqMQQIECBAgACBMgoI8so46vpMgAABAgQIEGgl0NZS2smTJ2cz8fr06cOLAAECBAgQIECgAQQEeQ0wCJpAgAABAgQIEOgugbaW0vbs2TObhTdlypTuapZ6CRAgQIAAAQIE2hAQ5HktCBAgQIAAAQIlFdiyZUvMnz//oFNpR4wYkYV4o0ePLqmKbhMgQIAAAQIEGldAkNe4Y6NlBAgQIECAAIGaCaxfvz4L8Xbu3Fmp48wzz8xCvN69e9esXgUTIECAAAECBAgcu4Ag79jtPEmAAAECBAgQKKTAq6++GnfffXfs27cva39LS0tccMEFcdZZZxWyPxpNgAABAgQIECiLgCCvLCOtnwQIECBAgACBiFi5cmXce++9FYt+/frFZZddFieffDIfAgQIECBAgACBBhcQ5DX4AGkeAQIECBAgQKCrBJYtWxYPPvhgpbgTTjghLr/88hg+fHhXVaEcAgQIECBAgACBGgoI8mqIq2gCBAgQIECAQKMILF26NB599NFKc0aNGpWFeAMHDmyUJmoHAQIECBAgQIDAEQQEeV4RAgQIECBAgECTCyxYsCAWL15c6eUpp5wSV155ZfTq1avJe657BAgQIECAAIHmEhDkNdd46g0BAgQIECBA4CCBdDLtiy++WPnZ5MmT49JLL6VEgAABAgQIECBQQAFBXgEHTZMJECBAgAABAh0R+M///M9Yv3595dZzzjknzj///I486h4CBAgQIECAAIEGFBDkNeCgaBIBAgQIECBAoLMC3/rWt2Lnzp2VYs4777yYPn16Z4v1PAECBAgQIECAQDcKCPK6EV/VBAgQIECAAIFaCHz5y18+qNiLL744zjzzzFpUpUwCBAgQIECAAIE6Cgjy6oitKgIECBAgQIBArQW+9rWvxTvvvFOp5uqrr45TTz211tUqnwABAgQIECBAoA4Cgrw6IKuCAAECBAgQIFAPgX/7t3+LN998s1LVjTfeGGPGjKlH1eogQIAAAQIECBCog4Agrw7IqiBAgAABAgQI1Frg1ltvje3bt1eque6662LcuHG1rlb5BAgQIECAAAECdRQQ5NURW1UECBAgQIAAgVoI3HbbbbFx48ZK0VdccUWcdtpptahKmQQIECBAgAABAt0oIMjrRnxVEyBAgAABAgQ6K3DHHXfEmjVrKsU42KKzop4nQIAAAQIECDSugCCvccdGywgQIECAAAEChxW455574uWXX67cc95558X06dOpESBAgAABAgQINKmAIK9JB1a3CBAgQIAAgeYWeOCBB2L58uWVTs6YMSNmz57d3J3WOwIECBAgQIBAyQUEeSV/AXSfAAECBAgQKJ7Ao48+GkuXLq00/Oyzz44LL7yweB3RYgIECBAgQIAAgaMSEOQdFZebCRAgQIAAAQLdK7Bw4cJYtGhRpRGTJk2Kyy67rHsbpXYCBAgQIECAAIG6CAjy6sKsEgIECBAgQIBA5wVeeOGFePjhhysFjR8/Pq655prOF6wEAgQIECBAgACBQggI8goxTBpJgAABAgQIlF1g9erVcdddd1UYxo4dG3PmzCk7i/4TIECAAAECBEolIMgr1XDrLAECBAgQIFBEgW3btsW3v/3tStP79u0bN998cxG7os0ECBAgQIAAAQKdEBDkdQLPowQIECBAgACBWgvs2bMnbrnllti7d2+lqs9+9rPR0tJS66qVT4AAAQIECBAg0GACgrwGGxDNIUCAAAECBAhUC9x6662xffv2yo8+9KEPxciRIyERIECAAAECBAiUUECQV8JB12UCBAgQIECgGAJz586NtWvXVhp74YUXxtlnn12MxmslAQIECBAgQIBAlwsI8rqcVIEECBAgQIAAgc4LPPDAA7F8+fJKQWeccUZccsklnS9YCQQIECBAgAABAoUVEOQVdug0nAABAgQIEGhWgQULFsTixYsr3TvllFPi2muvbdbu6hcBAgQIECBAgEAHBQR5HYRyGwECBAgQIECgHgIvvPBCPPzww5Wqhg8fHjfddFM9qlYHAQIECBAgQIBAgwsI8hp8gDSPAAECBAgQKI/Aq6++GnfddVfs378/6/Txxx8fH/zgB2PgwIHlQdBTAgQIECBAgACBdgUEeV4OAgQIECBAgEADCGzdujXuvvvu2LlzZ9aalpaWbDntySef3ACt0wQCBAgQIECAAIFGEBDkNcIoaAMBAgQIECBQaoE0A++OO+446ITaSy+9NCZPnlxqF50nQIAAAQIECBA4WECQ540gQIAAAQIECHSzwKOPPhpLly6ttOK8886L6dOnd3OrVE+AAAECBAgQINBoAoK8RhsR7SFAgAABAgRKJfCjH/0oHnrooUqf0yy8NBvPRYAAAQIECBAgQKC1gCDPO0GAAAECBAgQ6CaBDRs2xJ133hnvvPNO1oKRI0fGDTfcEMcdd1w3tUi1BAgQIECAAAG0YsEZAAAgAElEQVQCjSwgyGvk0dE2AgQIECBAoGkFUniXQrwU5qWrb9++cf3118fw4cObts86RoAAAQIECBAg0DkBQV7n/DxNgAABAgQIEDgmgbScNi2rza/LL788Tj/99GMqy0MECBAgQIAAAQLlEBDklWOc9ZIAAQIECBBoIIF0sEU64CK/ZsyYEbNnz26gFmoKAQIECBAgQIBAIwoI8hpxVLSJAAECBAgQaFqBtWvXxh133BH79+/P+jhhwoS46qqrmra/OkaAAAECBAgQINB1AoK8rrNUEgECBAgQIEDgsAK7du2KuXPnxtatW7P7hg4dmu2Ld/zxx5MjQIAAAQIECBAgcEQBQd4RidxAgAABAgQIEOgagfvvvz9WrFiRFdajR4/shNqxY8d2TeFKIUCAAAECBAgQaHoBQV7TD7EOEiBAgAABAo0gsHjx4liwYEGlKRdeeGGcffbZjdA0bSBAgAABAgQIECiIgCCvIAOlmQQIECBAgEBxBVatWhXz5s2rdGDKlClxwQUXFLdDWk6AAAECBAgQINAtAoK8bmFXKQECBAgQIFAWgddffz073CJ9TtdJJ52U7YuXlta6CBAgQIAAAQIECByNgCDvaLTcS4AAAQIECBA4SoF77703Vq5cmT01YMCALMQbMmTIUZbidgIECBAgQIAAAQIRgjxvAQECBAgQIECgRgJPP/10PPnkk5XSr7766jj11FNrVJtiCRAgQIAAAQIEml1AkNfsI6x/BAgQIECAQLcIrF27NubOnVupe8aMGTF79uxuaYtKCRAgQIAAAQIEmkNAkNcc46gXBAgQIECAQAMJvP3221mIt3nz5qxVY8eOjTlz5jRQCzWFAAECBAgQIECgiAKCvCKOmjYTIECAAAECDS3w8MMPxwsvvJC18bjjjstCvBEjRjR0mzWOAAECBAgQIECg8QUEeY0/RlpIgAABAgQIFEggBXgpyMuvCy+8MM4+++wC9UBTCRAgQIAAAQIEGlVAkNeoI6NdBAgQIECAQOEENm3alC2pfeedd7K2T5o0KS677LLC9UODCRAgQIAAAQIEGlNAkNeY46JVBAgQIECAQMEE9u/fn4V469aty1o+ZMiQbElt//79C9YTzSVAgAABAgQIEGhUAUFeo46MdhEgQIAAAQKFEnjiiSfimWeeqbT5uuuui3HjxhWqDxpLgAABAgQIECDQ2AKCvMYeH60jQIAAAQIECiDw0ksvxX333Vdp6bnnnhvpw0WAAAECBAgQIECgKwUEeV2pqSwCBAgQIECgdAI7duyIO+64I3bu3Jn1ffz48XHNNdeUzkGHCRAgQIAAAQIEai8gyKu9sRoIECBAgACBJha455574uWXX856OGDAgLjhhhvihBNOaOIe6xoBAgQIECBAgEB3CQjyuktevQQIECBAgEDhBZ599tl4/PHHK/244oor4rTTTit8v3SAAAECBAgQIECgMQUEeY05LlpFgAABAgQINLjApk2b4vbbb489e/ZkLZ06dWq8//3vb/BWax4BAgQIECBAgECRBQR5RR49bSdAgAABAgS6TeCuu+6K1atXZ/WPHDkybrzxxujZs2e3tUfFBAgQIECAAAECzS8gyGv+MdZDAgQIECBAoIsFnn766XjyyScrpaYQb8yYMV1ci+IIECBAgAABAgQIHCwgyPNGECBAgAABAgSOQmDDhg3xgx/8IPbt25c9NXv27JgxY8ZRlOBWAgQIECBAgAABAscmIMg7NjdPESBAgAABAiUVmDt3bqxduzbr/SmnnBLXXnttSSV0mwABAgQIECBAoN4Cgrx6i6uPAAECBAgQKKzAokWLYuHChVn7+/btG3PmzIkTTzyxsP3RcAIECBAgQIAAgWIJCPKKNV5aS4AAAQIECHSTQJqFl2bj5ddFF10UZ511Vje1RrUECBAgQIAAAQJlFBDklXHU9ZkAAQIECBA4KoG9e/fG7bffHhs3bsyeO/300+Pyyy8/qjLcTIAAAQIECBAgQKCzAoK8zgp6ngABAgQIEGh6gUcffTSWLl2a9XPQoEGRTqkdMGBA0/dbBwkQIECAAAECBBpLQJDXWOOhNQQIECBAgECDCaxYsSLuv//+SquuuOKKOO200xqslZpDgAABAgQIECBQBgFBXhlGWR8JECBAgACBYxLYuXNntqQ2fU7X2WefHRdeeOExleUhAgQIECBAgAAB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IECAAAECBAgQqIWAIK8WqsokQIAAAQIEjlpg27Ztceedd8Ybb7yRPTthwoS46qqrjrocDxAgQIAAAQIECBBoVgFBXrOOrH4RIECAAIECCezfvz8L8dasWZO1etCgQXHDDTdkn10ECBAgQIAAAQIECBwQEOR5EwgQIECAAIFuF3j00Udj6dKllXZcc801MX78+G5vlwYQIECAAAECBAgQaCQBQV4jjYa2ECBAgACBEgo899xz8cgjj1R6Pnv27JgxY0YJJXSZAAECBAgQIECAwOEFBHneEAIECBAgQKDbBNauXRt33HFHpKW16TrttNPiiiuu6Lb2qJgAAQIECBAgQIBAIwsI8hp5dLSNAAECBAg0scCbb76Z7Yu3devWrJdDhw6NOXPmRL9+/Zq417pGgAABAgQIECBA4NgFBHnHbudJAgQIECBAoBMC9957b6xcuTIroUePHtnhFmPHju1EiR4lQIAAAQIECBAg0NwCgrzmHl+9I0CAAAECDSmwYMGCWLx4caVtF1xwQUyZMqUh26pRBAgQIECAAAECBBpFQJDXKCOhHQQIECBAoCQCL774YsyfP7/S2zPOOCMuueSSkvReNwkQIECAAAECBAgcu4Ag79jtPEmAAAECBAgcpcCmTZuyffF2796dPTly5Mi4/vrro3fv3kdZktsJECBAgAABAgQIlE9AkFe+MddjAgQIECDQLQJ79uzJTqjdsGFDVn8K71KIl8I8FwECBAgQIECAAAECRxYQ5B3ZyB0ECBAgQIBAFwg8+OCDsWzZskpJaTltWlbrIkCAAAECBAgQIECgYwKCvI45uYsAAQIECBDohMAzzzwTTzzxRKWEdLBFOuDCRYAAAQIECBAgQIBAxwUEeR23cicBAgQIECBwDAIrV66Me++9t/Lk2LFj44YbbogePXocQ2keIUCAAAECBAgQIFBeAUFeecdezwkQIECAQM0F0uEW3//+92Pfvn1ZXYMGDYprr702hgwZUvO6VUCAAAECBAgQIECg2QQEec02ovpDgAABAgQaRGDXrl0xd+7c2Lp1a9aiNAPvxhtvjNGjRzdICzWDAAECBAgQIECAQLEEBHnFGi+tJUCAAAEChRGYN29erFq1qtLeq6++Ok499dTCtF9DCRAgQIAAAQIECDSagCCv0UZEewgQIECAQBMIpIMt0gEX+XXxxRfHmWee2QQ90wUCBAgQIECAAAEC3ScgyOs+ezUTIECAAIGmFHjhhRfi4YcfrvRt9uzZMWPGjKbsq04RIECAAAECBAgQqKeAIK+e2uoiQIAAAQJNLrBmzZq44447Kr0866yz4qKLLmryXuseAQIECBAgQIAAgfoICPLq46wWAgQIECDQ9AI7duyIf//3f6/0c9SoUfHBD36w6futgwQIECBAgAABAgTqJSDIq5e0eggQIECAQBML7Nu3L77yla9UetinT5/45Cc/GT179mziXusaAQIECBAgQIAAgfoKCPLq6602AgQIECDQlALf/OY344033qj07ROf+EQcf/zxTdlXnSJAgAABAgQIECDQXQKCvO6SVy8BAgQIEGgSgblz58batWsrvfnIRz4Sw4YNa5Le6QYBAgQIECBAgACBxhEQ5DXOWGgJAQIECBAonMC8efNi1apVlXZffvnlcfrppxeuHxpMgAABAgQIECBAoAgCgrwijJI2EiBAgACBBhR44IEHYvny5ZWWzZw5M2bNmtWALdUkAgQIECBAgAABAs0hIMhrjnHUCwIECBAgUFeBRx55JJ577rlKnVOmTIkLLrigrm1QGQECBAgQIECAAIGyCQjyyjbi+kuAAAECBDopsGDBgli8eHGllEmTJsVll13WyVI9ToAAAQIECBAgQIDAkQQEeUcS8nsCBAgQIECgIvD000/Hk08+Wfn+1FNPjauvvpoQAQIECBAgQIAAAQJ1EBDk1QFZFQQIECBAoBkEXnjhhXj44YcrXTnppJPihhtuaIau6QMBAgQIECBAgACBQggI8goxTBpJgAABAgS6V2DFihVx//33VxoxYsSI+PCHP9y9jVI7AQIECBAgQIAAgZIJCPJKNuC6S4AAAQIEjlZg9erVcdddd1UeO+GEE+LjH//40RbjfgIECBAgQIAAAQIEOikgyOskoMcJECBAgEAzC6xduzbmzp1b6eLxxx8fn/jEJ5q5y/pGgAABAgQIECBAoGEFBHkNOzQaRoAAAQIEulfgRz/6UTz00EOVRvTu3Ts+85nPdG+j1E6AAAECBAgQIECgxAKCvBIPvq4TIECAAIH2BBYsWBCLFy8+6Nef+9zngBEgQIAAAQIECBAg0I0CgrxuxFc1AQIECBBoRIH58+fHiy++WGla37594+abb27EpmoTAQIECBAgQIAAgVIJCPJKNdw6S4AA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fz5++MMfVlp80003xfDhwwvUA00lQIAAAQIECBAgQOBIAqUK8hJGmpm3eOFTceLgd2JA3zcieuw9klGHf58CvLS5ffQ8sD37lq07s88nDh3Y4TKa4saa7k7fK3a+NTC2bOsTM2acayZeU7wwOkGAQFcJPPDAA7F8+fKsuFGjRsX111+f7ZvnIkCAAAECBAgQIECgOQRKF+SlYbO5feO+vEfakL6om9E3rriWESDQTAJvvfVW3H777bF9+/asWzNnzoxZs2Y1Uxf1hQABAgQIECBAgECpBUoZ5JV6xHWeAAECBJpaYNWqVTFv3rysj3379o20xHbgwJLNDG/qEdY5AgQIECBAgACBMgsI8so8+vpOgAABAk0pcN9998VLL72U9W3atGnxvve9ryn7qVMECBAgQIAAAQIEyiYgyCvbiOsvAQIECDS9wGuvvRY/+MEPsn6mPfLSrLwTTjih6futgwQIECBAgAABAgSaXUCQ1+wjrH8ECBAgUEqBhx9+OF544YWs72effXZceOGFpXTQaQIECBAgQIAAAQLNJCDIa6bR1BcCBAgQIPBfAlu3bo3bbrstO+ApHRT04Q9/OIYNG8aHAAECBAgQIECAAIECCwjyCjx4mk6AAAECBA4n8OSTT8bTTz+d3WJWnneFAAECBAgQIECAQPEFBHnFH0M9IECAAAECbQq8+eab8Z3vfCfefvvt6N+/f3z84x+P3r170yJAgAABAgQIECBAoKACgryCDpxmEyBAgACBjgg8+OCDsWzZsuzWyy67LCZNmtSRx9xDgAABAgQIECBAgEADCgjyGnBQNIkAAQIECHSVwKpVq2LevHlZcaecckpce+21XVW0cggQIECAAAECBAgQqLOAIK/O4KojQIAAAQL1FNi3b1/ceuut8frrr2fVfuxjH4shQ4bUswnqIkCAAAECBAgQIECgiwQEeV0EqRgCBAgQINCoAo899lgsWbIka965556bfbgIECBAgAABAgQIECiegCCveGOmxQQIECBA4KgE1q5dG3Pnzs2eOfHEE+OjH/3oUT3vZgIECBAgQIAAAQIEGkNAkNcY46AVBAgQIECgpgLf/e53Y/PmzVkd119/fZx88sk1rU/hBAgQIECAAAECBAh0vYAgr+tNlUiAAAECBBpOYOHChbFo0aKsXTNmzIjZs2c3XBs1iAABAgQIECBAgACBwwsI8rwhBAgQIECgBAKvvvpq3HnnnVlPR44cGR/60IdK0GtdJECAAAECBAgQINBcAoK85hpPvSFAgAABAm0K7NmzJ2655ZbYu3dv9vtPfOITcfzxx9MiQIAAAQIECBAgQKBAAoK8Ag2WphIgQIAAgc4I3H777bFu3bqsiEsvvTQmT57cmeI8S4AAAQIECBAgQIBAnQUEeXUGVx0BAgQIEOgugQUL/v/27j1Iq/LOE/iP7uaicgdBQEFBY8QQAZGsl6gI0XjLRTFbcZPoJDtuZnamamZnNzW7m9md2czsbs3MbrZqs7NTqXUSM04ySXRiEtR4v0Q0KkIQgUgEFVouyq1BkXtvPYe8zdtNA03Tb/M+7/t5q7q67T7nOb/n83vwj28955wXY9GiRcXlzznnnJg1a9aJKsV1CRAgQIAAAQIECBDohoAgrxtoTiFAgAABAjkKNDc3xwMPPFCUPnDgwLj18BVl3AAAIABJREFU1ltznIaaCRAgQIAAAQIECNStgCCvbltv4gQIECBQbwL79++PO++8M1pbW4up33LLLTFs2LB6YzBfAgQIECBAgAABAtkKCPKybZ3CCRAgQIDAsQv89Kc/jXXr1hUnXnzxxTFlypRjH8QZBAgQIECAAAECBAicEAFB3glhd1ECBAgQIHBiBBYsWBALFy4sLp5edpFeeuFDgAABAgQIECBAgEAeAoK8PPqkSgIECBAg0CMCq1atikcffbQYa9SoUfGpT32qR8Y1CAECBAgQIECAAAEClRcQ5FXe2BUIECBAgEDVCGzZsiV++MMfFvX069cvbr/99qqpTSEECBAgQIAAAQIECBxZQJBnhRAgQIAAgToT+OY3v9k248997nNx8skn15mA6RIgQIAAAQIECBDIU0CQl2ffVE2AAAECBLotcM8998TmzZuL82+44YYYO3Zst8dyIgECBAgQIECAAAECvScgyOs9a1ciQIAAAQJVIZCekZeelZc+l156aZx//vlVUZciCBAgQIAAAQIECBA4soAgzwohQIAAAQJ1JlD+5toU4qUwz4cAAQIECBAgQIAAgeoXEORVf49USIAAAQIEelRg5cqV8dhjjxVjnn766XHdddf16PgGI0CAAAECBAgQIECgMgKCvMq4GpUAAQIECFStwKZNm+Lee+8t6hs4cGDceuutVVurwggQIECAAAECBAgQOCggyLMaCBAgQIBAnQns27cv7rzzzrZZf+lLX4rGxsY6UzBdAgQIECBAgAABAvkJCPLy65mKCRAgQIDAcQt873vfi+3btxfjzJ07N4YPH37cYxqAAAECBAgQIECAAIHKCgjyKutrdAIECBAgUJUCDz74YKxZs6aobc6cOTFx4sSqrFNRBAgQIECAAAECBAgcFBDkWQ0ECBAgQKAOBZ577rlYsmRJMfMZM2bE9OnT61DBlAkQIECAAAECBAjkJSDIy6tfqiVAgAABAj0isGzZsnjmmWeKsc4555yYNWtWj4xrEAIECBAgQIAAAQIEKicgyKucrZEJECBAgEDVCqxduzbmzZtX1Dd69Oj45Cc/WbW1KowAAQIECBAgQIAAgQMCgjwrgQABAgQIECBAgAABAgQIECBAgEAGAoK8DJqkRAIECBAgQIAAAQIECBAgQIAAAQKCPGuAAAECBAgQIECAAAECBAgQIECAQAYCgrwMmqREAgQIECBAgAABAgQIECBAgAABAoI8a4AAAQIECNSgwDeej7h32cGJDWiK+NpVETPG1eBkTYkAAQIECBAgQIBAnQgI8uqk0aZJgAABAvUlkIK81S0Rf3n1gXmn//7l+oivfzxiUP/6sjBbAgQIECBAgAABArUiIMirlU6aBwECBAgQKBPoGOQteCvi689F/NU1EWMHoSJAgAABAgQIECBAIEcBQV6OXVMzAQIECBA4ikDHIO8rDx84obRDLwV7f/J4xM69B35/0bgDf7t/RcSPlh/cuddxJ1/67/T5vY9oAQECBAgQIECAAAECvS0gyOttcdcjQIAAAQK9INDxGXn/9tKI6z9w4MIpxPuLpyP+4+UHnpm3fVfEH/4sYuppETdNjvhPj0f8l6siBvWL+A+PRmx6P+LfXBxx7sgD/33bVM/a64UWugQBAgQIECBAgACBQwQEeRYFAQIECBCoQYHyHXnp58dfj/jG9Qduq+24Wy9Nv3wn3teeirjizIjRp0TMWxEx8uQDQP/s9Ii7fhnxX+d4zl4NLhlTIkCAAAECBAgQyEBAkJdBk5RIgAABAgSOVaA8rCvfcZduiT1akPf0mxFPvRExfkjEWcMOBHopwPvohIi333Nb7bH2wvEECBAgQIAAAQIEekpAkNdTksYhQIAAAQJVJNDZyy5Kt9OmMg93a20K+kq33o44uf0tts3bIv7lhQdv0a2i6SqFAAECBAgQIECAQF0ICPLqos0mSYAAAQL1JtDZrrv0wovN7x94kUXadffX8w+q3Dz54E670g6+4ScdfDlGx9tz683TfAkQIECAAAECBAhUg4Agrxq6oAYCBAgQIECAAAECBAgQIECAAAECRxEQ5FkiBAgQIECAAAECBAgQIECAAAECBDIQEORl0CQlEiBAgAABAgQIECBAgAABAgQIEBDkWQMECBAgQIAAAQIECBAgQIAAAQIEMhAQ5GXQJCUSIECAAIFKCezbty/uvPPOtuF/+7d/O/r06VOpyxmXAAECBAgQIECAAIHjEBDkHQeeUwkQIECAQC0I3H333bFjx45iKp/97Gdj0KBBtTAtcyBAgAABAgQIECBQcwKCvJprqQkRIECAAIFjE/jxj38cGzZsKE664YYbYuzYscc2gKMJECBAgAABAgQIEOgVAUFerzC7CAECBAgQqF6BJ554In79618XBV522WUxefLk6i1WZQQIECBAgAABAgTqWECQV8fNN3UCBAgQIJAEFi5cGAsWLCgwpkyZEhdffDEYAgQIECBAgAABAgSqUECQV4VNURIBAgQIEOhNgVWrVsWjjz5aXPKMM86Ia6+9tjcv71oECBAgQIAAAQIECHRRQJDXRSiHESBAgACBWhXYvHlz3HPPPcX00osu0gsvfAgQIECAAAECBAgQqD4BQV719URFBAgQIECgVwX27dsXd955Z9s1v/SlL0VjY2Ov1uBiBAgQIECAAAECBAgcXUCQd3QjRxAgQIAAgZoX+O53vxvvvvtuMc+bb745RowYUfNzNkECBAgQIECAAAECuQkI8nLrmHoJECBAgEAFBB544IFobm4uRp49e3ZMmjSpAlcxJAECBAgQIECAAAECxyMgyDsePecSIECAAIEaEZg/f34sXbq0mM306dNjxowZNTIz0yBAgAABAgQIECBQOwKCvNrppZkQIECAAIFuC6QQL4V56TNx4sSYM2dOt8dyIgECBAgQIECAAAEClREQ5FXG1agECBAgQCArgbVr18a8efOKmocPHx5z587Nqn7FEiBAgAABAgQIEKgHAUFePXTZHAkQIECAwFEEduzYEXfffXfbUXfccQczAgQIECBAgAABAgSqTECQV2UNUQ4BAgQIEDhRAt/+9rdj9+7dxeVvueWWGDZs2IkqxXUJECBAgAABAgQI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eemzcXNL8X3k8CE9d40MRupB0UNm2xp9YtPOxljfsiemTptuJ14G60GJBCopIMirpK6xCRAgQIAAAQIECBAgUAUC6Zl5q1evjpaWlti3b18VVKSEkkCfPkd+hmFDQ0MMGTIkxo8f75l4lg0BAiHIswgIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIs9KLqlAAAA0ESURBVAYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAgIMizBggQIECAAAECBAgQIECAAAECBAhkICDIy6BJSiRAgAABAgQIECBAgAABAgQIECAgyLMGCBAgQIAAAQIECBAgQIAAAQIECGQgIMjLoElKJECAAAECBAgQIECAAAECBAgQICDIswYIECBAgAABAgQIECBAgAABAgQIZCAgyMugSUokQIAAAQIECBAgQIAAAQIECBAg0BbkoSBAgAABAgQIECBAgAABAgQIECBAoLoF+lR3eaojQIAAAQIECBAgQIAAAQIECBAgQCAJCPKsAwIECBAgQIAAAQIECBAgQIAAAQIZCAjyMmiSEgkQIECAAAECBAgQIECAAAECBAgI8qwBAgQIECBAgAABAgQIECBAgAABAhkI/H8jezO8JIXAZwAAAABJRU5ErkJggg==', 'tcp透传原始数据解析', '{\"globalColor\":\"#D49BEF\",\"dataCode\":{\"nodes\":[{\"id\":\"input\",\"type\":\"InputNode\",\"x\":116,\"y\":337,\"properties\":{\"debugMode\":false},\"zIndex\":1013,\"text\":{\"x\":126,\"y\":337,\"value\":\"输入\"}},{\"id\":\"b6365013-18f2-4361-85ed-d9db4b8144b5\",\"type\":\"SaveAttributesNode\",\"x\":757,\"y\":125,\"properties\":{\"debugMode\":false,\"name\":\"保存参数\"},\"zIndex\":1012,\"text\":{\"x\":767,\"y\":125,\"value\":\"保存参数\"}},{\"id\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"type\":\"SaveTimeSeriesNode\",\"x\":894,\"y\":318,\"properties\":{\"debugMode\":false,\"name\":\"\"},\"zIndex\":1014,\"text\":{\"x\":904,\"y\":318,\"value\":\"保存遥测\"}},{\"id\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"type\":\"MessageTypeSwitchNode\",\"x\":386,\"y\":332,\"properties\":{\"debugMode\":false,\"name\":\"消息类型切换\"},\"zIndex\":1002,\"text\":{\"x\":396,\"y\":332,\"value\":\"消息类型切换\"}},{\"id\":\"4a1b3ce3-64f6-4dd5-b703-122fac28eb99\",\"type\":\"ScriptKeyNode\",\"x\":651,\"y\":465,\"properties\":{\"debugMode\":false,\"name\":\"TCP原始数据解析\",\"script\":\"var tempVal = msg.rowdata;\\n/*物模型温度标识符*/\\nmsg.temperature = (parseInt(\'0x\'+tempVal.substr(10, 4))*0.1).toFixed(2);\\n/*物模型湿度标识符*/\\nmsg.humidity = (parseInt(\'0x\'+tempVal.substr(6, 4))*0.1).toFixed(2);\\nreturn {msg: msg, metadata: metadata, msgType: msgType};\"},\"zIndex\":1005,\"text\":{\"x\":661,\"y\":465,\"value\":\"TCP原始数据解析\"}},{\"id\":\"ae890015-52b9-472b-bb70-3de0c11465e8\",\"type\":\"SaveTimeSeriesNode\",\"x\":949,\"y\":540,\"properties\":{\"debugMode\":false,\"name\":\"保存遥测\"},\"zIndex\":1009,\"text\":{\"x\":959,\"y\":540,\"value\":\"保存遥测\"}}],\"edges\":[{\"id\":\"ba8d0084-9b85-437e-be3b-d83c644e8e09\",\"type\":\"bezier-link\",\"sourceNodeId\":\"input\",\"targetNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"startPoint\":{\"x\":176,\"y\":337},\"endPoint\":{\"x\":316,\"y\":332},\"properties\":{\"lineType\":[\"True\"]},\"text\":{\"x\":246,\"y\":334.5,\"value\":\"True\"},\"zIndex\":1003,\"pointsList\":[{\"x\":176,\"y\":337},{\"x\":276,\"y\":337},{\"x\":216,\"y\":332},{\"x\":316,\"y\":332}]},{\"id\":\"13cf5637-f995-4b56-9c41-530040418cdd\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"startPoint\":{\"x\":456,\"y\":332},\"endPoint\":{\"x\":834,\"y\":318},\"properties\":{\"lineType\":[\"Telemetry\"]},\"text\":{\"x\":645,\"y\":325,\"value\":\"Telemetry\"},\"zIndex\":1005,\"pointsList\":[{\"x\":456,\"y\":332},{\"x\":556,\"y\":332},{\"x\":734,\"y\":318},{\"x\":834,\"y\":318}]},{\"id\":\"0117eae6-8d1b-4eeb-96d6-6c42cddba1b4\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"b6365013-18f2-4361-85ed-d9db4b8144b5\",\"startPoint\":{\"x\":456,\"y\":332},\"endPoint\":{\"x\":697,\"y\":125},\"properties\":{\"lineType\":[\"Attributes\"]},\"text\":{\"x\":576.5,\"y\":228.5,\"value\":\"Attributes\"},\"zIndex\":1006,\"pointsList\":[{\"x\":456,\"y\":332},{\"x\":556,\"y\":332},{\"x\":597,\"y\":125},{\"x\":697,\"y\":125}]},{\"id\":\"8dc6c4df-de54-47d0-930a-c0196d216712\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"4a1b3ce3-64f6-4dd5-b703-122fac28eb99\",\"startPoint\":{\"x\":456,\"y\":332},\"endPoint\":{\"x\":571,\"y\":465},\"properties\":{\"lineType\":[\"Row\"]},\"text\":{\"x\":513.5,\"y\":398.5,\"value\":\"Row\"},\"zIndex\":1007,\"pointsList\":[{\"x\":456,\"y\":332},{\"x\":556,\"y\":332},{\"x\":471,\"y\":465},{\"x\":571,\"y\":465}]},{\"id\":\"e0db6f4a-ae6c-4367-a412-0d191e4b375b\",\"type\":\"bezier-link\",\"sourceNodeId\":\"4a1b3ce3-64f6-4dd5-b703-122fac28eb99\",\"targetNodeId\":\"ae890015-52b9-472b-bb70-3de0c11465e8\",\"startPoint\":{\"x\":731,\"y\":465},\"endPoint\":{\"x\":889,\"y\":540},\"properties\":{\"lineType\":[\"Success\"]},\"text\":{\"x\":810,\"y\":502.75,\"value\":\"Success\"},\"zIndex\":1010,\"pointsList\":[{\"x\":731,\"y\":465},{\"x\":831,\"y\":466},{\"x\":789,\"y\":540},{\"x\":889,\"y\":540}]}]},\"openRule\":false,\"setting\":{\"describe\":\"\",\"grid\":{\"size\":20,\"open\":false,\"type\":\"mesh\",\"config\":{\"color\":\"#cccccc\",\"thickness\":1}},\"backgroundColor\":\"#ffffff\"}}'); +INSERT INTO `rule_chain` VALUES ('rule_a37571bb6c45378b57803793', 'panda', 2, '2023-07-21 16:17:51', '2023-10-14 10:03:49', '0', '高温告警规则', 'data:image/png;base64,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lESAAAECBMopsHnz5pg6dWr2yB49esTVV19dzsd7FgECBAgQIECAAAECrRQQ5LUSym0ECBAgQKBaBbZt2xY33HBD1r0uXbrEF77whWrtqn4RIECAAAECBAgQyLWAIC/Xw6d4AgQIECDw4QXq6+vj3/7t3xobuu666z58o1ogQIAAAQIECBAgQKDdBQR57U6qQQIECBAgkD+BG2+8Md57772s8LS1Nm2xdREgQIAAAQIECBAgUFkCgrzKGg/VECBAgACBDhH4xS9+EevWrcuefckll8SAAQM6pA4PJUCAAAECBAgQIECgZQFBntlBgAABAgQIxD333BPLli3LJM4///w47LDDqBAgQIAAAQIECBAgUGECgrwKGxDlECBAgACBjhB47LHH4rXXXssefdZZZ8XIkSM7ogzPJECAAAECBAgQIEBgDwKCPNODAAECBAgQiGeeeSbmzZuXSZxyyikxZswYKgQIECBAgAABAgQIVJiAIK/CBkQ5BAgQIECgIwTmzJkTM2bMyB59/PHHx4QJEzqiDM8kQIAAAQIECBAgQGAPAoI804MAAQIECBCIBQsWxOOPP55JDBs2LCZPnkyFAAECBAgQIECAAIEKExDkVdiAKIcAAQIECHSEwNKlS+O+++7LHj148OC48MILO6IMzyRAgAABAgQIECBAYA8CgjzTgwABAgQIEIg1a9bEHXfckUn06dMnrrzySioECBAgQIAAAQIECFSYgCCvwgZEOQQIECBAoCMENm/eHFOnTs0e3bVr1/j85z/fEWV4JgECBAgQIECAAAECexAQ5JkeBAgQIECAQCZw/fXXN0pcddVV0bNnTzIECBAgQIAAAQIECFSQgCCvggZDKQQIECBAoCMF0tbatMU2Xeeff34cdthhHVmOZxMgQIAAAQIECBAg0ERAkGdKECBAgAABAplAOrU2nV6brpNPPjnGjh1LhgABAgQIECBAgACBChIQ5FXQYCiFAAECBAh0pMALL7wQzz77bFbCsGHDYvLkyR1ZjmcTIECAAAECBAgQINBEQJBnShAgQIAAAQKZwFtvvRX33ntv9vO+ffvGFVdcQYYAAQIECBAgQIAAgQoSEORV0GAohQABAgQIdKTA1q1b46abbmos4Qtf+EJ06dKlI0vybAIECBAgQIAAAQIEigQEeaYDAQIECBAg0Cjw05/+NN59993s11OmTIlBgwbRIUCAAAECBAgQIECgQgQEeRUyEMogQIAAAQKVIPDAAw/E4sWLs1ImTJgQxx9/fCWUpQYCBAgQIECAAAECBCJCkGcaECBAgAABAo0Czz33XMyePTv79THHHBNnnnkmHQIECBAgQIAAAQIEKkRAkFchA6EMAgQIECBQCQILFy6MRx99NCulf//+cdlll1VCWWogQIAAAQIECBAgQMCKPHOAAAECBAgQKBbYvHlzTJ06tfFLV111VfTs2RMSAQIECBAgQIAAAQIVIGBFXgUMghIIECBAgEAlCfzqV7+KFStWZCVNnDgxRowYUUnlqYUAAQIECBAgQIBAzQoI8mp26HWcAAECBAg0LzBnzpyYMWNG9s0U4qUwz0WAAAECBAgQIECAQMcLCPI6fgxUQIAAAQIEKkpg9erVceedd2Y19ejRI66++uqKqk8xBAgQIECAAAECBGpVQJBXqyOv3wQIECBAYA8Ct912W6xfvz67Y8qUKTFo0CBeBAgQIECAAAECBAh0sIAgr4MHwOMJECBAgEAlCjzzzDMxb968rLSTTjop++EiQIAAAQIECBAgQKBjBQR5Hevv6QQIECBAoCIFFi9eHA888EBW25AhQ+KCCy6oyDoVRYAAAQIECBAgQKCWBAR5tTTa+kqAAAECBFopsH379vjZz34WW7ZsyT5x6aWXxoEHHtjKT7uNAAECBAgQIECAAIFSCAjySqGqTQIECBAgUAUCTz75ZLzyyitZT0444YQ4/fTTq6BXukCAAAECBAgQIEAgvwKCvPyOncoJECBAgEBJBZYvXx5333139oyuXbvGJZdcEn379i3pMzVOgAABAgQIECBAgEDLAoI8s4MAAQIECBBoUeCRRx6JRYsWZd8fO3ZsnHzyybQIECBAgAABAgQIEOggAUFeB8F7LAECBAgQyIPAkiVL4v77789K7dmzZ/auvB49euShdDUSIECAAAECBAgQqDoBQV7VDakOESBAgACB9hVIQV4K9NJ16qmnxujRo9v3AVojQIAAAQIECBAgQKBVAoK8VjG5iQABAgQI1K5A2lqbttim64ADDsjeldelS5faBdFzAgQIECBAgAABAh0kIMjrIHiPJUCAAAECeRJIh16kwy/SNWHChDj++OPzVL5aCRAgQIAAAQIECFSFgCCvKoZRJwgQIECAQGkFXnnllXjyySezh/Tv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CBAgQIAAAQIEBHnmAAECBAgQIECAAAECBAgQIECAAIEcCAjycjBISiRAgAABAgQIECBAgAABAgQIECAgyDMHCBAgQIAAAQIECBAgQIAAAQIECORAQJCXg0FSIgECBAgQIECAAAECBAgQIECAAAFBnjlAgAABAgQIECBAgAABAgQIECBAIAcCgrwcDJISCRAgQIAAAQIECBAgQIAAAQIECAjyzAECBAgQIECAAAECBAgQIECAAAECORAQ5OVgkJRIgAABAgQIECBAgAABAgQIECBAQJBnDhAgQIAAAQIECBAgQIAAAQIECBDIgYAgLweDpEQCBAgQIECAAAECBAgQIECAAAECgjxzgAABAgQIECBAgAABAgQIECBAgEAOBAR5ORgkJRIgQIAAAQIECBAgQIAAAQIECBAQ5JkDBAgQIECAAAECBAgQIECAAAECBHIgIMjLwSApkQABAgQIECBAgAABAgQIECBAgIAgzxwgQIAAAQIECBAgQIAAAQIECBAgkAMBQV4OBkmJBAgQIECAAAECBAgQIECAAAECBAR55gABAgQIECBAgAABAgQIECBAgACBHAgI8nIwSEokQIAAAQIECBAgQIAAAQIECBAgIMgzBwgQIECAAAECBAgQIECAAAECBAjkQECQl4NBUiIBAgQIECBAgAABAgQIECBAgAABQZ45QIAAAQIECBAgQIAAAQIECBAgQCAHAoK8HAySEgkQIECAAAECBAgQIECAAAECBAgI8swBAgQIECBAgAABAgQIECBAgAABAjkQEOTlYJCUSIAAAQIECBAgQIAAAQIECBAgQECQZw4QIECAAAECBAgQIECAAAECBAgQyIGAIC8Hg6REAgQIECBAgAABAgQIECBAgAABAoI8c4AAAQIECBAgQIAAAQIECBAgQIBADgQEeTkYJCUSIECAAAECBAgQIECAAAECBAgQEOSZAwQIECBAgAABAgQIECBAgAABAgRyICDIy8EgKZEAAQIECBAgQIAAAQIECBAgQICAIM8cIECAAAECBAgQIECAAAECBAgQIJADAUFeDgZJiQQIECBAgAABAgQIECBAgAABAgQEeeYAAQIECBAgQIAAAQIECBAgQIAAgRwICPJyMEhKJECAAAECBAgQIECAAAECBAgQICDIMwcIECBAgAABAgQIECBAgAABAgQI5EBAkJeDQVIiAQIECBAgQIAAAQIECBAgQIAAAUGeOUCAAAECBAgQIECAAAECBAgQIEAgBwKCvBwMkhIJECBAgAABAgQIECBAgAABAgQICPLMAQIECBAgQIAAAQIECBAgQIAAAQI5EBDk5WCQlEiAAAECBAgQIECAAAECBAgQIEBAkGcOECBAgAABAgQIECBAgAABAgQIEMiBgCAvB4OkRAIECBAgQIAAAQIECBAgQIAAAQKCPHOAAAECBAgQIECAAAECBAgQIECAQA4EBHk5GCQlEiBAgAABAgQIECBAgAABAgQIEBDkmQMECBAgQIAAAQIECBAgQIAAAQIEciAgyMvBICmRAAECBAgQIECAAAECBAgQIECAgCDPHCBAgAABAgQIECBAgAABAgQIECCQAwFBXg4GSYkECBAgQIAAAQIECBAgQIAAAQIEBHnmAAECBAgQIECAAAECBAgQIECAAIEcCAjycjBISiRAgAABAgQIECBAgAABAgQIECAgyDMHCBAgQIAAAQIECBAgQIAAAQIECORAQJCXg0FSIgECBAgQIECAAAECBAgQIECAAAFBnjlAgAABAgQIECBAgAABAgQIECBAIAcCgrwcDJISCRAgQIAAAQIECBAgQIAAAQIECAjyzAECBAgQIECAAAECBAgQIECAAAECORAQ5OVgkJRIgAABAgQIECBAgAABAgQIECBAQJBnDhAgQIAAAQIECBAgQIAAAQIECBDIgYAgLweDpEQCBAgQIECAAAECBAgQIECAAAECgjxzgAABAgQIECBAgAABAgQIECBAgEAOBAR5ORgkJRIgQIAAAQIECBAgQIAAAQIECBAQ5JkDBAgQIECAAAECBAgQIECAAAECBHIgIMjLwSApkQABAgQIECBAgAABAgQIECBAgIAgzxwgQIAAAQIECBAgQIAAAQIECBAgkAMBQV4OBkmJBAgQIECAAAECBAgQIECAAAECBAR55gABAgQIECBAgAABAgQIECBAgACBHAgI8nIwSEokQIAAAQIECBAgQIAAAQIECBAgIMgzBwgQIECAAAECBAgQIECAAAECBAjkQECQl4NBUiIBAgQIECBAgAABAgQIECBAgAABQZ45QIAAAQIECBAgQIAAAQIECBAgQCAHAoK8HAySEgkQIECAAAECBAgQIECAAAECBAgI8swBAgQIECBAgAABAgQIECBAgAABAjkQEOTlYJCUSIAAAQIECBAgQIAAAQIECBAgQECQZw4QIECAAAECBAgQIECAAAECBAgQyIGAIC8Hg6REAgQIECBAgAABAgQIECBAgAABAoI8c4AAAQIECBAgQIAAAQIECBAgQIBADgQEeTkYJCUSIECAAAECBAgQIECAAAECBAgQEOSZAwQIECBAgAABAgQIECBAgAABAgRyICDIy8EgKZEAAQIECBAgQIAAAQIECBAgQICAIM8cIECAAAECBAgQIECAAAECBAgQIJADAUFeDgZJiQQIECBAgAABAgQIECBAgAABAgQEeeYAAQIECBAgQIAAAQIECBAgQIAAgRwICPJyMEhKJECAAAECBAgQIECAAAECBAgQICDIMwcIECBAgAABAgQIECBAgAABAgQI5EBAkJeDQVIiAQIECBAgQIAAAQIECBAgQIAAAUGeOUCAAAECBAgQIECAAAECBAgQIEAgBwKCvBwMkhIJECBAgAABAgQIECBAgAABAgQICPLMAQIECBAgQIAAAQIECBAgQIAAAQI5EBDk5WCQlEiAAAECBAgQIECAAAECBAgQIEBAkGcOECBAgAABAgQIECBAgAABAgQIEMiBgCAvB4OkRAIECBAgQIAAAQIECBAgQIAAAQKCPHOAAAECBAgQIECAAAECBAgQIECAQA4EBHk5GCQlEiBAgAABAgQIECBAgAABAgQIEBDkmQMECBAgQIAAAQIECBAgQIAAAQIEciAgyMvBICmRAAECBAgQIECAAAECBAgQIECAgCDPHCBAgAABAgQIECBAgAABAgQIECCQAwFBXg4GSYkECBAgQIAAAQIECBAgQIAAAQIEBHnmAAECBAgQIECAAAECBAgQIECAAIEcCAjycjBISiRAgAABAgQIECBAgAABAgQIECAgyDMHCBAgQIAAAQIECBAgQIAAAQIECORAQJCXg0FSIgECBAgQIECAAAECBAgQIECAAAFBnjlAgAABAgQIECBAgAABAgQIECBAIAcCgrwcDJISCRAgQIAAAQIECBAgQIAAAQIECAjyzAECBAgQIECAAAECBAgQIECAAAECORAQ5OVgkJRIgAABAgQIECBAgAABAgQIECBAQJBnDhAgQIAAAQIECBAgQIAAAQIECBDIgYAgLweDpEQCBAgQIECAAAECBAgQIECAAAECgjxzgAABAgQIECBAgAABAgQIECBAgEAOBAR5ORgkJRIgQIAAAQIECBAgQIAAAQIECBAQ5JkDBAgQIECAAAECBAgQIECAAAECBHIgIMjLwSApkQABAgQIECBAgAABAgQIECBAgIAgzxwgQIAAAQIECBAgQIAAAQIECBAgkAMBQV4OBkmJBAgQIECAAAECBAgQIECAAAECBAR55gABAgQIECBAgAABAgQIECBAgACBHAgI8nIwSEokQIAAAQIECBAgQIAAAQIECBAgIMgzBwgQIECAAAECBAgQIECAAAECBAjkQECQl4NBUiIBAgQIECBAgAABAgQIECBAgAABQZ45QIAAAQIECBAgQIAAAQIECBAgQCAHAoK8HAySEgkQIECAAAECBAgQIECAAAECBAgI8swBAgQIECBAgAABAgQIECBAgAABAjkQEOTlYJCUSIAAAQIECBAgQIAAAQIECBAgQECQZw4QIECAAAECBAgQIECAAAECBAgQyIGAIC8Hg6REAgQIECBAgAABAgQIECBAgAABAoI8c4AAAQIECBAgQIAAAQIECBAgQIBADgQEeTkYJCUSIECAAAECBAgQIECAAAECBAgQEOSZAwQIECBAgAABAgQIECBAgAABAgRyICDIy8EgKZEAAQIECBAgQIAAAQIECBAgQICAIM8cIECAAAECBAgQIECAAAECBAgQIJADAUFeDgZJiQQIECBAgAABAgQIECBAgAABAgQEeeYAAQIECBAgQIAAAQIECBAgQIAAgRwICPJyMEhKJECAAAECBAgQIECAAAECBAgQICDIMwcIECBAgAABAgQIECBAgAABAgQI5EBAkJeDQVIiAQIECBAgQIAAAQIECBAgQIAAAUGeOUCAAAECBAgQIECAAAECBAgQIEAgBwKCvBwMkhIJECBAgAABAgQIECBAgAABAgQICPLMAQIECBAgQIAAAQIECBAgQIAAAQI5EBDk5WCQlEiAAAECBAgQIECAAAECBAgQIEBAkGcOECBAgAABAgQIECBAgAABAgQIEMiBgCAvB4OkRAIECBAgQIAAAQIECBAgQIAAAQKCPHOAAAECBAgQIECAAAECBAgQIECAQA4EBHk5GCQlEiBAgAABAgQIECBAgAABAgQIEBDkmQMECBAgQIAAAQIECBAgQIAAAQIEciAgyMvBICmRAAECBAgQIECAAAECBAgQIECAgCDPHCBAgAABAgQIECBAgAABAgQIECCQAwFBXg4GSYkECBAgQIAAAQIECBAgQIAAAQIEBHnmAAECBAgQIECAAAECBAgQIECAAIEcCAjycjBISiRAgAABAgQIECBAgAABAgQIECAgyDMHCBAgQIAAAQIECBAgQIAAAQIECORAQJCXg0FSIgECBAgQIECAAAECBAgQIECAAAFBnjlAgAABAgQIECBAgAABAgQIECBAIAcCgrwcDJISCRAgQIAAAQIECBAgQIAAAQIECAjyzAECBAgQIECAAAECBAgQIECAAAECORAQ5OVgkJRIgAABAgQIECBAgAABAgQIECBAQJBnDhAgQIAAAQIECBAgQIAAAQIECBDIgYAgLweDpEQCBAgQIECAAAECBAgQIECAAAECgjxzgAABAgQIECBAgAABAgQIECBAgEAOBAR5ORgkJRIgQIAAAQIECBAgQIAAAQIECBAQ5JkDBAgQIECAAAECBAgQIECAAAECBHIgIMjLwSApkQABAgQIECBAgAABAgQIECBAgIAgzxwgQIAAAQIECBAgQIAAAQIECBAgkAMBQV4OBkmJBAgQIECAAAECBAgQIECAAAECBAR55gABAgQIECBAgAABAgQIECBAgACBHAgI8nIwSEokQIAAAQIECBAgQIAAAQIECBAgIMgzBwgQIECAAAECBAgQIECAAAECBAjkQECQl4NBUiIBAgQIECBAgAABAgQIECBAgAABQZ45QIAAAQIECBAgQIAAAQIECBAgQCAHAoK8HAySEgkQIECAAAECBAgQIECAAAECBAgI8swBAgQIECBAgAABAgQIECBAgAABAjkQEOTlYJCUSIAAAQIECBAgQIAAAQIECBAgQECQZw4QIECAAAECBAgQIECAAAECBAgQyIGAIC8Hg6REAgQIECBAgAABAgQIECBAgAABAoI8c4AAAQIECBAgQIAAAQIECBAgQIBADgQEeTkYJCUSIECAAAECBAgQIECAAAECBAgQEOSZAwQIECBAgAABAgQIECBAgAABAgRyICDIy8EgKZEAAQIECBAgQIAAAQIECBAgQICAIM8cIECAAAECBAgQIECAAAECBAgQIJADAUFeDgZJiQQIECBAgAABAgQIECBAgAABAgQEeeYAAQIECBAgQIAAAQIECBAgQIAAgRwICPJyMEhKJECAAAECBAgQIECAAAECBAgQICDIMwcIECBAgAABAgQIECBAgAABAgQI5EBAkJeDQVIiAQIECBAgQIAAAQIECBAgQIAAAUGeOUCAAAECBAgQIECAAAECBAgQIEAgBwKCvBwMkhIJECBAgAABAgQIECBAgAABAgQICPLMAQIECBAgQIAAAQIECBAgQIAAAQI5EBDk5WCQlEiAAAECBAgQIECAAAECBAgQIEBAkGcOECBAgAABAgQIECBAgAABAgQIEMiBgCAvB4OkRAIECBAgQIAAAQIECBAgQIAAAQKCPHOAAAECBAgQIECAAAECBAgQIECAQA4EBHk5GCQlEiBAgAABAgQIECBAgAABAgQIEBDkmQMECBAgQIAAAQL/fzt2cAIAEAJBDPtv+vBzNTiQDpboawgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAU4L5oQAABvNJREFUAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAgIeX6AAAECBAgQIECAAAECBAgQIECAQEBAyAscyUQCBAgQIECAAAECBAgQIECAAAECQp4fIECAAAECBAgQIECAAAECBAgQIBAQEPICRzKRAAECBAgQIECAAAECBAgQIECAgJDnBwgQIECAAAECBAgQIECAAAECBAgEBIS8wJFMJECAAAECBAgQIECAAAECBAgQICDk+QECBAgQIECAAAECBAgQIECAAAECAQEhL3AkEwkQIECAAAECBAgQIECAAAECBAj8kIeCAAECBAgQIECAAAECBAgQIECAAIHbAnN7nnUECBAgQIAAAQIECBAgQIAAAQIECKyAkOcPCBAgQIAAAQIECBAgQIAAAQIECAQEhLzAkUwkQIAAAQIECBAgQIAAAQIECBAgIOT5AQIECBAgQIAAAQIECBAgQIAAAQIBgQfQb0x/yG5r4AAAAABJRU5ErkJggg==', '根链1', '{\"globalColor\":\"#D49BEF\",\"dataCode\":{\"nodes\":[{\"id\":\"input\",\"type\":\"InputNode\",\"x\":116,\"y\":337,\"properties\":{\"debugMode\":false},\"zIndex\":1013,\"text\":{\"x\":126,\"y\":337,\"value\":\"输入\"}},{\"id\":\"b6365013-18f2-4361-85ed-d9db4b8144b5\",\"type\":\"SaveAttributesNode\",\"x\":624,\"y\":249,\"properties\":{\"debugMode\":false,\"name\":\"\"},\"zIndex\":1012,\"text\":{\"x\":634,\"y\":249,\"value\":\"保存参数\"}},{\"id\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"type\":\"SaveTimeSeriesNode\",\"x\":624,\"y\":409,\"properties\":{\"debugMode\":false,\"name\":\"\"},\"zIndex\":1014,\"text\":{\"x\":634,\"y\":409,\"value\":\"保存遥测\"}},{\"id\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"type\":\"MessageTypeSwitchNode\",\"x\":406,\"y\":338,\"properties\":{\"debugMode\":false},\"zIndex\":1002,\"text\":{\"x\":416,\"y\":338,\"value\":\"消息类型切换\"}},{\"id\":\"594f9c98-daaf-4348-a4c7-208feb413ff8\",\"type\":\"ScriptFilterNode\",\"x\":813,\"y\":308,\"properties\":{\"debugMode\":false,\"name\":\"验证温度大于20\",\"script\":\"return msg.temperature > 20;\"},\"zIndex\":1015,\"text\":{\"x\":823,\"y\":308,\"value\":\"验证温度大于20\"}},{\"id\":\"62882142-b992-490b-ad01-bd5f965c8570\",\"type\":\"CreateAlarmNode\",\"x\":1049,\"y\":212,\"properties\":{\"debugMode\":false,\"name\":\"创建设备告警信息\",\"alarmType\":\"高温报警\",\"alarmSeverity\":\"MAJOR\"},\"zIndex\":1002,\"text\":{\"x\":1059,\"y\":212,\"value\":\"创建设备告警信息\"}},{\"id\":\"b14a20cc-0369-4f91-8157-c98a25c19a04\",\"type\":\"ClearAlarmNode\",\"x\":1041,\"y\":431,\"properties\":{\"debugMode\":false,\"name\":\"清除告警\",\"alarmType\":\"高温报警\"},\"zIndex\":1006,\"text\":{\"x\":1051,\"y\":431,\"value\":\"清除告警\"}},{\"id\":\"33355f02-8eaf-4a9d-b7f3-d69a28d8f202\",\"type\":\"RpcRequestToDeviceNode\",\"x\":624,\"y\":513,\"properties\":{\"debugMode\":false},\"zIndex\":1002,\"text\":{\"x\":634,\"y\":513,\"value\":\"服务RPC下发\"}}],\"edges\":[{\"id\":\"ba8d0084-9b85-437e-be3b-d83c644e8e09\",\"type\":\"bezier-link\",\"sourceNodeId\":\"input\",\"targetNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"startPoint\":{\"x\":176,\"y\":337},\"endPoint\":{\"x\":336,\"y\":338},\"properties\":{\"lineType\":[\"True\"]},\"text\":{\"x\":256,\"y\":337.5,\"value\":\"True\"},\"zIndex\":1003,\"pointsList\":[{\"x\":176,\"y\":337},{\"x\":276,\"y\":337},{\"x\":236,\"y\":338},{\"x\":336,\"y\":338}]},{\"id\":\"13cf5637-f995-4b56-9c41-530040418cdd\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"startPoint\":{\"x\":476,\"y\":338},\"endPoint\":{\"x\":564,\"y\":409},\"properties\":{\"lineType\":[\"Telemetry\"]},\"text\":{\"x\":520,\"y\":373.5,\"value\":\"Telemetry\"},\"zIndex\":1005,\"pointsList\":[{\"x\":476,\"y\":338},{\"x\":576,\"y\":338},{\"x\":464,\"y\":409},{\"x\":564,\"y\":409}]},{\"id\":\"0117eae6-8d1b-4eeb-96d6-6c42cddba1b4\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"b6365013-18f2-4361-85ed-d9db4b8144b5\",\"startPoint\":{\"x\":476,\"y\":338},\"endPoint\":{\"x\":564,\"y\":249},\"properties\":{\"lineType\":[\"Attributes\"]},\"text\":{\"x\":520,\"y\":293.5,\"value\":\"Attributes\"},\"zIndex\":1006,\"pointsList\":[{\"x\":476,\"y\":338},{\"x\":576,\"y\":338},{\"x\":464,\"y\":249},{\"x\":564,\"y\":249}]},{\"id\":\"5c2bc46c-10c8-4a96-a1c2-3f50f19777e4\",\"type\":\"bezier-link\",\"sourceNodeId\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"targetNodeId\":\"594f9c98-daaf-4348-a4c7-208feb413ff8\",\"startPoint\":{\"x\":684,\"y\":409},\"endPoint\":{\"x\":733,\"y\":308},\"properties\":{\"lineType\":[\"Success\"]},\"text\":{\"x\":708.5,\"y\":358.5,\"value\":\"Success\"},\"zIndex\":1016,\"pointsList\":[{\"x\":684,\"y\":409},{\"x\":784,\"y\":409},{\"x\":633,\"y\":308},{\"x\":733,\"y\":308}]},{\"id\":\"9a80d1c4-b0f6-4a57-89a4-af4fa9d459c4\",\"type\":\"bezier-link\",\"sourceNodeId\":\"594f9c98-daaf-4348-a4c7-208feb413ff8\",\"targetNodeId\":\"62882142-b992-490b-ad01-bd5f965c8570\",\"startPoint\":{\"x\":893,\"y\":308},\"endPoint\":{\"x\":969,\"y\":212},\"properties\":{\"lineType\":[\"True\"]},\"text\":{\"x\":931,\"y\":260,\"value\":\"True\"},\"zIndex\":1003,\"pointsList\":[{\"x\":893,\"y\":308},{\"x\":993,\"y\":308},{\"x\":869,\"y\":212},{\"x\":969,\"y\":212}]},{\"id\":\"2f659b99-c508-4e3e-a98c-fe87b9a79da1\",\"type\":\"bezier-link\",\"sourceNodeId\":\"594f9c98-daaf-4348-a4c7-208feb413ff8\",\"targetNodeId\":\"b14a20cc-0369-4f91-8157-c98a25c19a04\",\"startPoint\":{\"x\":893,\"y\":308},\"endPoint\":{\"x\":981,\"y\":431},\"properties\":{\"lineType\":[\"False\"]},\"text\":{\"x\":937,\"y\":369.5,\"value\":\"False\"},\"zIndex\":1007,\"pointsList\":[{\"x\":893,\"y\":308},{\"x\":993,\"y\":308},{\"x\":881,\"y\":431},{\"x\":981,\"y\":431}]},{\"id\":\"ee538862-d80b-43f1-9520-4def7491dac7\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"33355f02-8eaf-4a9d-b7f3-d69a28d8f202\",\"startPoint\":{\"x\":466,\"y\":338},\"endPoint\":{\"x\":564,\"y\":513},\"properties\":{\"lineType\":[\"RpcRequestToDevice\"]},\"text\":{\"x\":515,\"y\":425.5,\"value\":\"RpcRequestToDevice\"},\"zIndex\":1003,\"pointsList\":[{\"x\":466,\"y\":338},{\"x\":566,\"y\":338},{\"x\":464,\"y\":513},{\"x\":564,\"y\":513}]}]},\"openRule\":false,\"setting\":{\"describe\":\"\",\"grid\":{\"size\":20,\"open\":false,\"type\":\"mesh\",\"config\":{\"color\":\"#cccccc\",\"thickness\":1}},\"backgroundColor\":\"#ffffff\"}}'); +INSERT INTO `rule_chain` VALUES ('rulee765e9ef022812a8b89dfb4c', 'panda', 2, '2023-07-21 16:17:51', '2023-10-13 20:31:26', '1', 'Root Rule Chain', 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABPIAAAMvCAYAAABY+f5KAAAAAXNSR0IArs4c6QAAIABJREFUeF7s3QmY1fV9L/7PDMgmiCDIooKCoCLIImhUoqLWJWqsJm3TNprcJP80W5e0SZ7b3jTNdtP7JGlN26Rpc5ukiWnS3jYaqyZq4h5NFAQUhIDiAojsMCCbgPN/vr/JGc8MAwww58z5nd/r9zznmYVzvsvr+4XWd75Lw8L/u6k5PAQIECBAgAABAgQIECBAgAABAgQI1LRAQwryznzfsTXdSI0jQIAAAQIECBAgQIAAAQIECBAgUGSBZ/5lcwjyijwD9J0AAQIECBAgQIAAAQIECBAgQCAXAoK8XAyTRhIgQIAAAQIECBAgQIAAAQIECBRdQJBX9Bmg/wQIECBAgAABAgQIECBAgAABArkQEOTlYpg0kgABAgQIECBAgAABAgQIECBAoOgCgryizwD9J0CAAAECBAgQIECAAAECBAgQyIWAIC8Xw6SRBAgQIECAAAECBAgQIECAAAECRRcQ5BV9Bug/AQIECBAgQIAAAQIECBAgQIBALgQEebkYJo0kQIAAAQIECBAgQIAAAQIECBAouoAgr+gzQP8JECBAgAABAgQIECBAgAABAgRyISDIy8UwaSQBAgQIECBAgAABAgQIECBAgEDRBQR5RZ8B+k+AAAECBAgQIECAAAECBAgQIJALAUFeLoZJIwkQIECAAAECBAgQIECAAAECBIouIMgr+gzQfwIECBAgQIAAAQIECBAgQIAAgVwICPJyMUwaSYAAAQIECBAgQIAAAQIECBAgUHQBQV7RZ4D+EyBAgAABAgQIECBAgAABAgQI5EJAkJeLYdJIAgQIECBAgAABAgQIECBAgACBogsI8oo+A/SfAAECBAgQIECAAAECBAgQIEAgFwKCvFwMk0YSIECAAAECBAgQIECAAAECBAgUXUCQV/QZoP8ECBAgQIAAAQIECBAgQIAAAQK5EBDk5WKYNJIAAQIECBAgQIAAAQIECBAgQKDoAoK8os8A/SdAgAABAgQIECBAgAABAgQIEMiFgCAvF8OkkQQIECBAgAABAgQIECBAgAABAkUXEOQVfQboPwECBAgQIECAAAECBAgQIECAQC4EBHm5GCaNJECAAAECBAgQIECAAAECBAgQKLqAIK/oM0D/CRAgQIAAAQIECBAgQIAAAQIEciEgyMvFMGkkAQIECBAgQIAAAQIECBAgQIBA0QUEeUWfAfpPgAABAgQIECBAgAABAgQIECCQCwFBXi6GSSMJECBAgAABAgQIECBAgAABAgSKLiDIK/oM0H8CBAgQIECAAAECBAgQIECAAIFcCAjycjFMGkmAAAECBAgQIECAAAECBAgQIFB0AUFe0WeA/hMgQIAAAQIECBAgQIAAAQIECORCQJCXi2HSSAIECBAgQIAAAQIECBAgQIAAgaILCPKKPgP0nwABAgQIECBAgAABAgQIECBAIBcCgrxcDJNGEiBAgAABAgQIECBAgAABAgQIFF1AkFf0GaD/BAgQIECAAAECBAgQIECAAAECuRAQ5OVimDSSAAECBAgQIECAAAECBAgQIECg6AKCvKLPAP0nQIAAAQIECBAgQIAAAQIECBDIhYAgLxfDpJEECBAgQIAAAQIECBAgQIAAAQJFFxDkFX0G6D8BAgQIECBAgAABAgQIECBAgEAuBAR5uRgmjSRAgAABAgQIECBAgAABAgQIECi6gCCv6DNA/wkQIECAAAECBAgQIECAAAECBHIhIMjLxTBpJAECBAgQIECAAAECBAgQIECAQNEFBHlFnwH6T4AAAQIECBAgQIAAAQIECBAgkAsBQV4uhkkjCRAgQIAAAQIECBAgQIAAAQIEii4gyCv6DNB/AgQIECBAgAABAgQIECBAgACBXAgI8nIxTBpJgAABAgQIECBAgAABAgQIECBQdAFBXtFngP4TIECAAAECBAgQIECAAAECBAjkQkCQl4th0kgCBAgQIECAAAECBAgQIECAAIGiCwjyij4D9J8AAQIECBAgQIAAAQIECBAgQCAXAoK8XAyTRhIgQIAAAQIECBAgQIAAAQIECBRdQJBX9Bmg/wQIECBAgAABAgQIECBAgAABArkQEOTlYpg0kgABAgQIECBAgAABAgQIECBAoOgCgryizwD9J0CAAAECBAgQIECAAAECBAgQyIWAIC8Xw6SRBAgQIECAAAECBAgQIECAAAECRRcQ5BV9Bug/AQIECBAgQIAAAQIECBAgQIBALgQEebkYJo0kQIAAAQIECBAgQIAAAQIECBAouoAgr+gzQP8JECBAgAABAgQIECBAgAABAgRyISDIy8UwaSQBAgQIECBAgAABAgQIECBAgEDRBQR5RZ8B+k+AAAECBAgQIECAAAECBAgQIJALAUFeLoZJIwkQIECAAAECBAgQIECAAAECBIouIMgr+gzQfwIECBAgQIAAAQIECBAgQIAAgVwICPJyMUwaSYAAAQIECBAgQIAAAQIECBAgUHQBQV7RZ4D+EyBAgAABAgQIECBAgAABAgQI5EJAkJeLYdJIAgQIECBAgAABAgQIECBAgACBogsI8oo+A/SfAAECBAgQIECAAAECBAgQIEAgFwKCvFwMk0YSIECAAAECBAgQIECAAAECBAgUXUCQV/QZoP8ECBAgQIAAAQIECBAgQIAAAQK5EBDk5WKYNJIAAQIECBAgQIAAAQIECBAgQKDoAoK8os8A/SdAgAABAgQIECBAgAABAgQIEMiFgCAvF8OkkQQIECBAgAABAgQIECBAgAABAkUXEOQVfQboPwECBAgQIECAAAECBAgQIECAQC4EBHm5GCaNJECAAAECBAgQIECAAAECBAgQKLqAIK/oM0D/CRAgQIAAAQIECBAgQIAAAQIEciEgyMvFMGkkAQIECBAgQIAAAQIECBAgQIBA0QUEeUWfAfpPgAABAgQIECBAgAABAgQIECCQCwFBXi6GSSMJECBAgAABAgQIECBAgAABAgSKLiDIK/oM0H8CBAgQIECAAAECBAgQIECAAIFcCAjycjFMGkmAAAECBAgQIECAAAECBAgQIFB0AUFe0WeA/hMgQIAAAQIECBAgQIAAAQIECORCQJCXi2HSSAIECBAgQIAAAQIECBAgQIAAgaILCPKKPgP0nwABAgQIECBAgAABAgQIECBAIBcCgrxcDJNGEiBAgAABAgQIECBAgAABAgQIFF1AkFf0GaD/BAgQIECAAAECBAgQIECAAAECuRAQ5OVimDSSAAECBAgQIECAAAECBAgQIECg6AKCvKLPAP0nQIAAAQIECBAgQIAAAQIECBDIhYAgLxfDpJEECBAgQIAAAQIECBAgQIAAAQJFFxDkFX0G6D8BAgQIECBAgAABAgQIECBAgEAuBAR5uRgmjSRAgAABAgQIECBAgAABAgQIECi6gCCv6DNA/wkQIECAAAECBAgQIECAAAECBHIhIMjLxTBpJAECBAgQIECAAAECBAgQIECAQNEFBHlFnwH6T4AAAQIECBAgQIAAAQIECBAgkAsBQV4uhkkjCRAgQIAAAQIECBAgQIAAAQIEii4gyCv6DNB/AgQIECBAgAABAgQIECBAgACBXAgI8nIxTBpJgAABAgQIECBAgAABAgQIECBQdAFBXtFngP4TIECAAAECBAgQIECAAAECBAjkQkCQl4th0kgCBAgQIECAAAECBAgQIECAAIGiCwjyij4D9J8AAQIECBAgQIAAAQIECBAgQCAXAoK8XAyTRhIgQIAAAQIECBAgQIAAAQIECBRdQJBX9Bmg/wQIECBAgAABAgQIECBAgAABArkQEOTlYpg0kgABAgQIECBAgAABAgQIECBAoOgCgryizwD9J0CAAAECBAgQIECAAAECBAgQyIWAIC8Xw6SRBAgQIECAAAECBAgQIECAAAECRRcQ5BV9Bug/AQIECBAgQIAAAQIECBAgQIBALgQEebkYJo0kQIAAAQIECBAgQIAAAQIECBAouoAgr+gzQP8JECBAgAABAgQIECBAgAABAgRyISDIy8UwaSQBAgQIECBAgAABAgQIECBAgEDRBQR5RZ8B+k+AAAECBAgQIECAAAECBAgQIJALAUFeLoZJIwkQIECAAAECBAgQIECAAAECBIouIMgr+gzQfwIECBAgQIAAAQIECBAgQIAAgVwICPJyMUwaSYAAAQIECBAgQIAAAQIECBAgUHQBQV7RZ4D+EyBAgAABAgQIECBAgAABAgQI5EJAkJeLYdJIAgQIECBAgAABAgQIECBAgACBogsI8oo+A/SfAAECBAgQIECAAAECBAgQIEAgFwKCvFwMk0YSIECAAAECBAgQIECAAAECBAgUXUCQV/QZoP8ECBAgQIAAAQIECBAgQIAAAQK5EBDk5WKYNJIAAQIECBAgQIAAAQIECBAgQKDoAoK8os8A/SdAgAABAgQIECBAgAABAgQIEMiFgCAvF8OkkQQIECBAgAABAgQIECBAgAABAkUXEOQVfQboPwECBAgQIECAAAECBAgQIECAQC4EBHm5GCaNJECAAAECBAgQIECAAAECBAgQKLqAIK/oM0D/CRAgQIAAAQIECBAgQIAAAQIEciEgyMvFMGkkAQIECBAgQIAAAQIECBAgQIBA0QUEeUWfAfpPgAABAgQIECBAgAABAgQIECCQCwFBXi6GSSMJECBAgAABAgQIECBAgAABAgSKLiDIK/oM0H8CBAgQIECAAAECBAgQIECAAIFcCAjycjFMGkmAAAECBAgQIECAAAECBAgQIFB0AUFe0WeA/hMgQIAAAQIECBAgQIAAAQIECORCQJCXi2HSSAIECBAgQIAAAQIECBAgQIAAgaILCPKKPgP0nwABAgQIECBAgAABAgQIECBAIBcCgrxcDJNGEiBAgAABAgQIECBAgAABAgQIFF1AkFf0GaD/BAgQIECAAAECBAgQIECAAAECuRAQ5OVimDSSAAECBAgQIECAAAECBAgQIECg6AKCvKLPAP0nQIAAAQIECBAgQIAAAQIECBDIhYAgLxfDpJEECBAgQIAAAQIECBAgQIAAAQJFFxDkFX0G6D8BAgQIECBAgAABAgQIECBAgEAuBAR5uRgmjSRAgAABAgQIECBAgAABAgQIECi6gCCv6DNA/wkQIECAAAECBAgQIECAAAECBHIhIMjLxTBpJAECBAgQIECAAAECBAgQIECAQNEFBHlFnwH6T4AAAQIECBAgQIAAAQIECBAgkAsBQV4uhkkjCRAgQIAAAQIECBAgQIAAAQIEii4gyCv6DNB/AgQIECBAgAABAgQIECBAgACBXAgI8nIxTBpJgAABAgQIECBAgAABAgQIECBQdAFBXtFngP4TIECAAAECBAgQIECAAAECBAjkQkCQl4th0kgCBAgQIECAAAECBAgQIECAAIGiCwjyij4D9J8AAQIECBAgQIAAAQIECBAgQCAXAoK8XAyTRhIgQIAAAQIECBAgQIAAAQIECBRdQJBX9Bmg/wQIECBAgAABAgQIECBAgAABArkQEOTlYpg0kgABAgQIECBAgAABAgQIECBAoOgCgryizwD9J0CAAAECBAgQIECAAAECBAgQyIWAIC8Xw6SRBAgQIECAAAECBAgQIECAAAECRRcQ5BV9Bug/AQIECBAgQIAAAQIECBAgQIBALgQEebkYJo0kQIAAAQIECBAgQIAAAQIECBAouoAgr+gzQP8JECBAgAABAgQIECBAgAABAgRyISDIy8UwaSQBAgQIECBAgAABAgQIECBAgEDRBQR5RZ8B+k+AAAECBAgQIECAAAECBAgQIJALAUFeLoZJIwkQIECAAAECBAgQIECAAAECBIouIMgr+gzQfwIECBAgQIAAAQIECBAgQIAAgVwICPJyMUwaSYAAAQIECBAgQIAAAQIECBAgUHQBQV7RZ4D+EyBAgAABAgQIECBAgAABAgQI5EJAkJeLYdJIAgQIECBAgAABAgQIECBAgACBogsI8oo+A/SfAAECBAgQIECAAAECBAgQIEAgFwKCvFwMk0YSIECAAAECBAgQIECAAAECBAgUXUCQV/QZoP8ECBAgQIAAAQIECBAgQIAAAQK5EBDk5WKYNJIAAQIECBAgQIAAAQIECBAgQKDoAoK8os8A/SdAgAABAgQIECBAgAABAgQIEMiFgCAvF8OkkQQIECBAgAABAgQIECBAgAABAkUXEOQVfQboPwECBAgQIECAAAECBAgQIECAQC4EBHm5GCaNJECAAAECBAgQIECAAAECBAgQKLqAIK/oM0D/CRAgQIAAAQIECBAgQIAAAQIEciEgyMvFMGkkAQIECBAgQIAAAQIECBAgQIBA0QUEeUWfAfpPgAABAgQIECBAgAABAgQIECCQCwFBXi6GSSMJECBAgAABAgQIECBAgAABAgSKLiDIK/oM0H8CBAgQIECAAAECBAgQIECAAIFcCAjycjFMGkmAAAECBAgQIECAAAECBAgQIFB0AUFe0WeA/hMgQIAAAQIECBAgQIAAAQIECORCQJCXi2HSSAIECBAgQIAAAQIECBAgQIAAgaILCPKKPgP0nwABAgQIECBAgAABAgQIECBAIBcCgrxcDJNGEiBAgAABAgQIECBAgAABAgQIFF1AkFf0GaD/BAgQIECAAAECBAgQIECAAAECuRAQ5OVimDSSAAECBAgQIECAAAECBAgQIECg6AKCvKLPAP0nQIAAAQIECBAgQIAAAQIECBDIhYAgLxfDpJEECBAgQIAAAQIECBAgQIAAAQJFFxDkFX0G6D8BAgQIECBAgAABAgQIECBAgEAuBAR5uRgmjSRAgAABAgQIECBAgAABAgQIECi6gCCv6DNA/wkQIECAAAECBAgQIECAAAECBHIhIMjLxTBpJAECBAgQIECAAAECBAgQIECAQNEFBHlFnwH6T4AAAQIECBAgQIAAAQIECBAgkAsBQV4uhkkjCRAgQIAAAQIECBAgQIAAAQIEii4gyCv6DNB/AgQIECBAgAABAgQIECBAgACBXAgI8nIxTBpJgAABAgQIECBAgAABAgQIECBQdAFBXtFngP4TIECAAAECBAgQIECAAAECBAjkQkCQl4th0kgCBAgQIECAAAECBAgQIECAAIGiCwjyij4D9J8AAQIECBAgQIAAAQIECBAgQCAXAoK8XAyTRhIg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rLPOije96U2HUoT3EiBAgAABAgQIEKgZAUFezQyFhhAgQIAAAQJdJdDc3By33nprm5tpzz///Jg4cWJXVaEcAgQIECBAgAABAlUXEORVnVyFBAgQIECAQCUFdu7cGd/73vfi9ddfb63myiuvjFGjRlWyWmUTIECAAAECBAgQqLiAIK/ixCogQIAAAQIEqiWwZs2auP3229tU9/a3vz0GDx5crSaohwABAgQIECBAgEDFBAR5FaNVMAECBAgQIFBNgeXLl8fdd9/dWmXfvn3jHe94Rxx11FHVbIa6CBAgQIAAAQIECFRMQJBXMVoFEyBAgAABAtUSSBdapIstSs+wYcPiuuuuq1b16iFAgAABAgQIECBQFQFBXlWYVUKAAAECBAhUSuCZZ56JRx99tLX4IUOGxA033FCp6pRLgAABAgQIECBAoNsEBHndRq9iAgQIECBA4EgF5s+fH0888URrMcccc0y2ndZDgAABAgQIECBAoB4FBHn1OKr6RIAAAQIECiAwb968mD17dmtP05l4N954YwF6rosECBAgQIAAAQJFFRDkFXXk9ZsAAQIECORYYOHChfHYY4+19qBHjx7x3ve+N8c90nQCBAgQIECAAAECBxcQ5B3cyDsIECBAgACBGhL41a9+FQ8//HCbFr3//e+voRZqCgECBAgQIECAAIHKCAjyKuOqVAIECBAgQKACAsuWLYv77ruvTcnvec97omfPnhWoTZEECBAgQIAAAQIEaktAkFdb46E1BAgQIECAwH4Empqa4rnnnmvzp5MnTxbimTEECBAgQIAAAQKFERDkFWaodZQAAQIECBAgQIAAAQIECBAgQCDPAoK8PI+ethMgQIAAAQIECBAgQIAAAQIECBRGQJBXmKHWUQIECBAgUFsCX308YnlTxBcvjyj/vrZaqTUECBAgQIAAAQIEakdAkFc7Y6ElBAgQIECgpgXmvBzxl/dHfOTciKvHH3lTBXlHbqgEAgQIECBAgACBYgkI8oo13npLgAABAgQOWyAFb4+tiOh3VMTNV0YM6N1S1NZdER+9O+L6M94I+O5aGnHb4rbvO1DFR7Iir6P6D7uTPkiAAAECBAgQIECghgUEeTU8OJpGgAABAgRqRSCFZX/xs4grx0V8/+mIj54XMf0EQV6tjI92ECBAgAABAgQIFENAkFeMcdZLAgQIECBwRALlK+w+91BLUelsu1VbIz5yV8SmHS2/G9Q34twTIu5+7o3q3jYh4k0nRtz8i4hxx0U89GLExy6IeGHTvmfkpU/Nfrnls+lzaRtvRyvuSiv4/uS8fev/6tURq7a0bAPeuaelrFRf2g5cKmvZxrZ1HBGODxMgQIAAAQIECBCokoAgr0rQqiFAgAABAnkW+MS9EaMGtgRrKdT75tyIFJiNHNC5rbWl8/VSmJbKSE/7M/J+uOiNwK0UEL53WsSFo/fdulv+2fZBX/rsp+6P+OwlLe1LP3/8npZVhL9c+UZ4mOfx0HYCBAgQIECAAIFiCgjyijnuek2AAAECBDotUB6qpSCu/c+dOSMvBXlpRd6XrmgJ1zoK8ko32JYalsK69LxryqEFeSlo/PKj+3YvrcobdnTLSr0Tjun8+X2dhvJGAgQIECBAgAABAhUWEORVGFjxBAgQIEAg7wIpUEur5do/Ywe3hGHpOdhlF4cT5JVWAR5OkHewizZKYd+ME1q2CHsIECBAgAABAgQI5EFAkJeHUdJGAgQIECDQTQKl1XaD+7YNvFIw978fjvhfF0acNqRrgrwUrn3ukpZLNMrLTz+nUC895efynTq45eeOttamc/vSttzSuXhfnx3xwRkRP3k2YubollWBh3qzbjcNgWoJECBAgAABAgQItAoI8kwGAgQIECBAYL8C7QO18jeWh2ulVXvpsot0dl56SpdglF92caCttUvWR+zYE1G6iKJ0QUUqq3TGXrq8ItVx1rCI7bvfCBfb119+2UWfnm8EhOWXc5T/3hQgQIAAAQIECBAgkAcBQV4eRkkbCRAgQIAAAQIECBAgQIAAAQIECi8gyCv8FABAgAABAgQIECBAgAABAgQIECCQBwFBXh5GSRsJECBAgAABAgQIECBAgAABAgQKLyDIK/wUAECAAAECBAgQIECAAAECBAgQIJAHAUFeHkZJGwkQIECAQB0I7NixIxYtWtSmJxMnTozevXvXQe90gQABAgQIECBAgEDlBQR5lTdWAwECBAgQKLzAli1b4t///d/bOFxzzTUxcuTIwtsAIECAAAECBAgQINBZAUFeZ6W8jwABAgQIEDgsgbQS75Zbbmnz2SuvvDJGjRp1WOX5EAECBAgQIECAAIGiCgjyijry+k2AAAECBKogsGfPnvjWt77VpqZLL700xo4dW4XaVUGAAAECBAgQIECgvgQEefU1nnpDgAABAgRqSuAb3/hGm/ZceOGFcfrpp9dUGzWGAAECBAgQIECAQF4EBHl5GSntJECAAAECORP45je/GXv37m1t9fnnnx/pcgsPAQIECBAgQIAAAQKHJyDIOzw3nyJAgAABAgQOIJDOxEtn45WeGTNmxNSpU5kRIECAAAECBAgQIHAEAoK8I8DzUQIECBAgQGBfgXQ7bbqltvScc845MWXKFFQECBAgQIAAAQIECByhgCDvCAF9nAABAgQIEHhD4NZbb43169e3/uKCCy6IM888ExEBAgQIECBAgAABAl0gIMjrAkRFECBAgAABAhG33357rFmzppXikksuiVNPPRUNAQIECBAgQIAAAQJdJCDI6yJIxRAgQIAAgaIK7Nq1K37wgx/Ea6+91kpw5ZVXxqhRo4pKot8ECBAgQIAAAQIEKiIgyKsIq0IJECBAgEAxBF5++eW466672nT2rW99awwfPrwYAHpJgAABAgQIECBAoIoCgrwqYquKAAECBAjUk8D8+fPjiSeeaO1SQ0NDvOMd74gBAwbUUzf1hQABAgQIECBAgEDNCAjyamYoNIQAAQIECORH4JFHHonFixe3Nrh///5ZiNfY2JifTmgpAQIECBAgQIAAgZwJCPJyNmCaS4AAAQIEulvg3nvvjRdffLG1GWkbbdpO6yFAgAABAgQIECBAoLICgrzK+iqdAAECBAjUjcDmzZvjoYceanMz7ZgxY+Kyyy6rmz7qCAECBAgQIECAAIFaFhDk1fLoaBsBAgQIEKgRgVWrVsXDDz8cW7ZsaW3RWWedFW9605tqpIWaQYAAAQIECBAgQKD+BQR59T/GekiAAAECBI5I4Nlnn410Jt6ePXtay0kBXgryPAQIECBAgAABAgQIVE9AkFc9azURIECAAIFcCezatStmz54dixYtam137969481vfnOkLbUeAgQIECBAgAABAgSqKyDIq6632ggQIECAQC4Eli9fnoV4GzZsaG3vcccdFxdccEGkyy08BAgQIECAAAECBAhUX0CQV31zNRIgQIAAgZoWmDNnTsydO7dNG88888yYPn16pBV5HgIECBAgQIAAAQIEukdAkNc97molQIAAAQI1J7B27dpIId7KlStb2zZgwICYMWNGnHrqqTXXXg0iQIAAAQIECBAgUDQBQV7RRlx/CRAgQIBABwILFy7M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E08//fS48MILa7W52kWAAAECBAgQIECgEAKCvEIMs04SIECAAIFDF1i8eHE88sgjrR+cNWtWjBs37tAL8gkCBAgQIECAAAECBLpEQJDXJYwKIUCAAAEC9SnwwAMPxLPPPpt17phjjsluse3fv399dlavCBAgQIAAAQIECNS4gCCvxgdI8wgQIECAQHcKNDU1ZVtsX3311awZp512Wlx00UXd2SR1EyBAgAABAgQIECisgCCvsEOv4wQIECBAoHMCS5YsiYceeqj1zW6x7ZybdxEgQIAAAQIECBDoagFBXleLKo8AAQIECNShwIMPPhhLly7NejZkyJC47rrrokePHnXYU10iQIAAAQIECBAgULsCgrzaHRstI0CAAAECNSOwefPmuP3222PXrl1Zm2bMmBFTp06tmfZpCAECBAgQIECAAIEiCAjyijDK+kiAAAECBLpAYN68eTF79uyspN69e8db3/rWGDRoUBeUrAgCBAgQIECAAAECBDojIMjrjJL3ECBAgAABAtHc3Jytylu7dm2mMX78+Lj44ovJECBAgAABAgQIECBQJQFBXpWgVUOAAAECBOpB4Pnnn4+f/exnrV254oorYvTo0fXQNX0gQIAAAQIECBAgUPMCgryaHyINJECAAAECtSVw3333xbJly7JGjRw5Mq655praaqDWECBAgAABAgQIEKhTAUFenQ6sbhEgQIAAgUoJbNiwIdtiu2fPnqyKmTNnxoQJEypVnXIJECBAgAABAgQIEPi1gCDPVCBAgAABAgQOWeDJJ5+M9ErPMcccE7/5m78Zffr0OeRyfIAAAQIECBAgQIAAgc4LCPI6b+WdBAgQIECAwK8Fdu/eHf/93/8daXVeeqZMmRLnnHMOHwIECBAgQIAAAQIEKiggyKsgrqIJECBAgEA9Czz77LPxwAMPZF3s0aNHXHfddTFkyJB67rK+ESBAgAABAgQIEOhWAUFet/KrnAABAgQI5Fvgpz/9abzwwgtZJ8aNGxezZs3Kd4e0ngABAgQIECBAgEANCwjyanhwNI0AAQIECNS6wNq1a7OLL5qbm7OmXnHFFTF69Ohab7b2ESBAgAABAgQIEMilgCAvl8Om0QQIECBAoHYEHn/88XjqqaeyBo0YMSKuvfba2mmclhAgQIAAAQIECBCoIwFBXh0Npq4QIECAAIHuENi5c2f86Ec/ii1b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KPIaLFDrECBAgACBRhHYt29f/PznP49du3aVK1144YXlLy8CBAgQIECAAAECuQoo8nJN3t4ECBAgQCABgdWrV8eSJUvKSVtbW8sHXxRX53kRIECAAAECBAgQyFFAkZdj6nYmQIAAAQIJCdx5552xcePGcuKzzz47rrzyyoSmNyoBAgQIECBAgACB3hNQ5PWepSMRIECAAAECfSCwYcOGuOeee7qOfNVVV8VZZ53VB2dySAIECBAgQIAAAQL1LaDIq+98TEeAAAECBAhExL333hvr1q0rLU477bTyFtumpiY2BAgQIECAAAECBLISUORlFbdlCRAgQIBAmgI7duyIX/ziF9He3l4ucPHFF8cb3vCGNJcxNQECBAgQIECAAIETFFDknSCcjxEgQIAAAQL9K/DII4/EsmXLypMOHjy4vCpv5MiR/TuEsxEgQIAAAQIECBAYQAFF3gDiOzUBAgQIECDQc4GOjo64/fbbY+vWreWHpk2bFldccUXPD+CdBAgQIECAAAECBBIXUOQlHqDxCRAgQIBATgLr16+PhQsXdq08b968OPfcc3MisCsBAgQIECBAgEDGAoq8jMO3OgECBAgQSFHgvvvui7Vr15ajDxs2LK677roYMWJEiquYmQABAgQIECBAgMBxCSjyjovLmwkQIECAAIGBFnjxxRfjzjvvjF27dpWjTJ06NebPnz/QYzk/AQIECBAgQIAAgT4XUOT1ObETECBAgAABAr0t8MQTT8SiRYu6Djt37tyYPn16b5/G8QgQIECAAAECBAjUlYAir67iMAwBAgQIECDQU4HFixfHmjVryrcPHTq0vMV21KhRPf249xEgQIAAAQIECBBITkCRl1xkBiZAgAABAgQKgT179sQdd9wRO3fuLEHOPvvsuPLKK+EQIECAAAECBAgQaFgBRV7DRmsxAgQIECDQ+AK1T7G99NJL47zzzmv8xW1IgAABAgQIECCQpYAiL8vYLU2AAAECBBpHYMmSJbF69epyoSFDhsT1118fo0ePbpwFbUKAAAECBAgQIEDgjwKKPD8KBAgQIECAQNIC+/btK59iu3379nKPKVOmxNVXX530ToYnQIAAAQIECBAg0J2AIs/PBQECBAgQIJC8wFNPPRW/+tWvuva45JJLYvbs2cnvZQECBAgQIECAAAEC1QKKPD8PBAgQIECAQEMIPPjgg7Fq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1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIBtvnC3AAAay0lEQVQAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gKKvKzjtzwBAgQIECBAgAABAgQIECBAgEAqAoq8VJIyJwECBAgQIECAAAECBAgQIECAQNYCirys47c8AQIECBAgQIAAAQIECBAgQIBAKgKKvFSSMicBAgQIECBAgAABAgQIECBAgEDWAoq8rOO3PAECBAgQIECAAAECBAgQIECAQCoCirxUkjInAQIECBAgQIAAAQIECBAgQIBA1gJdRV7WCpYnQIAAAQIECBAgQIAAAQIECBAgkIBAUwIzGpEAAQIECBAgQIAAAQIECBAgQIBA9gKKvOx/BAAQIECAAAECBAgQIECAAAECBAikIKDISyElMxIgQIAAAQIECBAgQIAAAQIECGQvoMjL/kcAAAECBAgQIECAAAECBAgQIECAQAoC/x8UBxQmPaZC2wAAAABJRU5ErkJggg==', '根链1', '{\"globalColor\":\"#D49BEF\",\"dataCode\":{\"nodes\":[{\"id\":\"input\",\"type\":\"InputNode\",\"x\":116,\"y\":337,\"properties\":{\"debugMode\":false},\"zIndex\":1013,\"text\":{\"x\":126,\"y\":337,\"value\":\"输入\"}},{\"id\":\"b6365013-18f2-4361-85ed-d9db4b8144b5\",\"type\":\"SaveAttributesNode\",\"x\":702,\"y\":238,\"properties\":{\"debugMode\":false,\"name\":\"\"},\"zIndex\":1012,\"text\":{\"x\":712,\"y\":238,\"value\":\"保存参数\"}},{\"id\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"type\":\"SaveTimeSeriesNode\",\"x\":699,\"y\":354,\"properties\":{\"debugMode\":false,\"name\":\"\"},\"zIndex\":1014,\"text\":{\"x\":709,\"y\":354,\"value\":\"保存遥测\"}},{\"id\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"type\":\"MessageTypeSwitchNode\",\"x\":386,\"y\":332,\"properties\":{\"debugMode\":false},\"zIndex\":1002,\"text\":{\"x\":396,\"y\":332,\"value\":\"消息类型切换\"}}],\"edges\":[{\"id\":\"ba8d0084-9b85-437e-be3b-d83c644e8e09\",\"type\":\"bezier-link\",\"sourceNodeId\":\"input\",\"targetNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"startPoint\":{\"x\":176,\"y\":337},\"endPoint\":{\"x\":316,\"y\":332},\"properties\":{\"lineType\":[\"True\"]},\"text\":{\"x\":246,\"y\":334.5,\"value\":\"True\"},\"zIndex\":1003,\"pointsList\":[{\"x\":176,\"y\":337},{\"x\":276,\"y\":337},{\"x\":216,\"y\":332},{\"x\":316,\"y\":332}]},{\"id\":\"13cf5637-f995-4b56-9c41-530040418cdd\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"0c2710b5-9714-4563-944c-8b1a78536814\",\"startPoint\":{\"x\":456,\"y\":332},\"endPoint\":{\"x\":639,\"y\":354},\"properties\":{\"lineType\":[\"Telemetry\"]},\"text\":{\"x\":547.5,\"y\":343,\"value\":\"Telemetry\"},\"zIndex\":1005,\"pointsList\":[{\"x\":456,\"y\":332},{\"x\":556,\"y\":332},{\"x\":539,\"y\":354},{\"x\":639,\"y\":354}]},{\"id\":\"0117eae6-8d1b-4eeb-96d6-6c42cddba1b4\",\"type\":\"bezier-link\",\"sourceNodeId\":\"74b514b9-9591-4ab2-b6f9-c6f2f005047f\",\"targetNodeId\":\"b6365013-18f2-4361-85ed-d9db4b8144b5\",\"startPoint\":{\"x\":456,\"y\":332},\"endPoint\":{\"x\":642,\"y\":238},\"properties\":{\"lineType\":[\"Attributes\"]},\"text\":{\"x\":549,\"y\":285,\"value\":\"Attributes\"},\"zIndex\":1006,\"pointsList\":[{\"x\":456,\"y\":332},{\"x\":556,\"y\":332},{\"x\":542,\"y\":238},{\"x\":642,\"y\":238}]}]},\"openRule\":false,\"setting\":{\"describe\":\"\",\"grid\":{\"size\":20,\"open\":false,\"type\":\"mesh\",\"config\":{\"color\":\"#cccccc\",\"thickness\":1}},\"backgroundColor\":\"#ffffff\"}}'); + +-- ---------------------------- +-- Table structure for rule_chain_msg_log +-- ---------------------------- +DROP TABLE IF EXISTS `rule_chain_msg_log`; +CREATE TABLE `rule_chain_msg_log` ( + `message_id` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `org_id` int(0) NULL DEFAULT NULL COMMENT '机构ID', + `owner` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL COMMENT '创建者,所有者', + `msg_type` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `device_id` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `device_name` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `ts` datetime(0) NULL DEFAULT NULL, + `content` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NULL DEFAULT NULL, + `create_at` datetime(0) NULL DEFAULT NULL +) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_0900_ai_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of rule_chain_msg_log +-- ---------------------------- +INSERT INTO `rule_chain_msg_log` VALUES ('3453d', 2, 'panda', 'Telemetry', 'd_1928b99619910dae5a001fa7', 'ws432', '2023-07-31 14:23:13', 'Incoming message', '2023-09-06 15:28:45'); + +-- ---------------------------- +-- Table structure for sys_apis +-- ---------------------------- +DROP TABLE IF EXISTS `sys_apis`; +CREATE TABLE `sys_apis` ( + `id` bigint(0) NOT NULL AUTO_INCREMENT, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + `path` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'api路径', + `description` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'api中文描述', + `api_group` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'api组', + `method` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '方法', + PRIMARY KEY (`id`) USING BTREE, + UNIQUE KEY `sys_apis_udk` (`path`,`method`) +) ENGINE = InnoDB AUTO_INCREMENT = 206 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_apis +-- ---------------------------- +INSERT INTO `sys_apis` VALUES (1, '2021-12-09 09:21:04', '2021-12-09 09:21:04', NULL, '/system/user/list', '查询用户列表(分页)', 'user', 'GET'); +INSERT INTO `sys_apis` VALUES (2, '2021-12-09 09:29:36', '2021-12-09 09:29:36', NULL, '/system/user/changeStatus', '修改用户状态', 'user', 'PUT'); +INSERT INTO `sys_apis` VALUES (3, '2021-12-09 09:34:37', '2021-12-09 09:34:37', NULL, '/system/user/:userId', '删除用户信息', 'user', 'DELETE'); +INSERT INTO `sys_apis` VALUES (4, '2021-12-09 09:36:43', '2023-09-14 14:05:54', NULL, '/system/organization/list', '获取组织列表', 'organization', 'GET'); +INSERT INTO `sys_apis` VALUES (5, '2021-12-09 09:37:31', '2023-09-14 14:06:51', NULL, '/system/organization/:organizationId', '获取组织信息', 'organization', 'GET'); +INSERT INTO `sys_apis` VALUES (6, '2021-12-09 18:20:32', '2021-12-09 18:20:32', NULL, '/system/user/avatar', '上传头像', 'user', 'POST'); +INSERT INTO `sys_apis` VALUES (7, '2021-12-09 18:21:10', '2021-12-09 18:21:10', NULL, '/system/user/pwd', '修改密码', 'user', 'PUT'); +INSERT INTO `sys_apis` VALUES (8, '2021-12-09 18:21:54', '2021-12-09 18:21:54', NULL, '/system/user/getById/:userId', '通过id获取用户信息', 'user', 'GET'); +INSERT INTO `sys_apis` VALUES (9, '2021-12-09 18:58:50', '2021-12-09 18:58:50', NULL, '/system/user/getInit', '获取初始化角色岗位信息(添加用户初始化)', 'user', 'GET'); +INSERT INTO `sys_apis` VALUES (10, '2021-12-09 18:59:43', '2021-12-09 18:59:43', NULL, '/system/user/getRoPo', '获取用户角色岗位信息', 'user', 'GET'); +INSERT INTO `sys_apis` VALUES (11, '2021-12-09 19:00:24', '2021-12-09 19:00:24', NULL, '/system/user', '添加用户信息', 'user', 'POST'); +INSERT INTO `sys_apis` VALUES (12, '2021-12-09 19:00:52', '2021-12-09 19:00:52', NULL, '/system/user', '修改用户信息', 'user', 'PUT'); +INSERT INTO `sys_apis` VALUES (13, '2021-12-09 19:02:30', '2021-12-09 19:02:30', NULL, '/system/user/export', '导出用户信息', 'user', 'GET'); +INSERT INTO `sys_apis` VALUES (14, '2021-12-09 19:04:04', '2023-09-14 14:06:35', NULL, '/system/organization/roleOrganizationTreeSelect/:roleId', '获取角色部门树', 'organization', 'GET'); +INSERT INTO `sys_apis` VALUES (15, '2021-12-09 19:04:48', '2023-09-14 14:07:06', NULL, '/system/organization/organizationTree', '获取所有组织树', 'organization', 'GET'); +INSERT INTO `sys_apis` VALUES (16, '2021-12-09 19:07:37', '2023-09-14 14:07:18', NULL, '/system/organization', '添加组织信息', 'organization', 'POST'); +INSERT INTO `sys_apis` VALUES (17, '2021-12-09 19:08:14', '2023-09-14 14:07:28', NULL, '/system/organization', '修改组织信息', 'organization', 'PUT'); +INSERT INTO `sys_apis` VALUES (18, '2021-12-09 19:08:40', '2023-09-14 14:07:41', NULL, '/system/organization/:organizationId', '删除组织信息', 'organization', 'DELETE'); +INSERT INTO `sys_apis` VALUES (19, '2021-12-09 19:09:41', '2021-12-09 19:09:41', NULL, '/system/config/list', '获取配置分页列表', 'config', 'GET'); +INSERT INTO `sys_apis` VALUES (20, '2021-12-09 19:10:11', '2021-12-09 19:10:11', NULL, '/system/config/configKey', '获取配置列表通过ConfigKey', 'config', 'GET'); +INSERT INTO `sys_apis` VALUES (21, '2021-12-09 19:10:45', '2021-12-09 19:10:45', NULL, '/system/config/:configId', '获取配置信息', 'config', 'GET'); +INSERT INTO `sys_apis` VALUES (22, '2021-12-09 19:11:22', '2021-12-09 19:11:22', NULL, '/system/config', '添加配置信息', 'config', 'POST'); +INSERT INTO `sys_apis` VALUES (23, '2021-12-09 19:11:41', '2021-12-09 19:11:41', NULL, '/system/config', '修改配置信息', 'config', 'PUT'); +INSERT INTO `sys_apis` VALUES (24, '2021-12-09 19:12:28', '2021-12-09 19:12:28', NULL, '/system/config/:configId', '删除配置信息', 'config', 'DELETE'); +INSERT INTO `sys_apis` VALUES (25, '2021-12-09 19:13:08', '2021-12-09 19:13:08', NULL, '/system/dict/type/list', '获取字典类型分页列表', 'dict', 'GET'); +INSERT INTO `sys_apis` VALUES (26, '2021-12-09 19:13:55', '2021-12-09 19:13:55', NULL, '/system/dict/type/:dictId', '获取字典类型信息', 'dict', 'GET'); +INSERT INTO `sys_apis` VALUES (27, '2021-12-09 19:14:28', '2021-12-09 19:14:28', NULL, '/system/dict/type', '添加字典类型信息', 'dict', 'POST'); +INSERT INTO `sys_apis` VALUES (28, '2021-12-09 19:14:55', '2021-12-09 19:14:55', NULL, '/system/dict/type', '修改字典类型信息', 'dict', 'PUT'); +INSERT INTO `sys_apis` VALUES (29, '2021-12-09 19:15:17', '2021-12-09 19:15:17', NULL, '/system/dict/type/:dictId', '删除字典类型信息', 'dict', 'DELETE'); +INSERT INTO `sys_apis` VALUES (30, '2021-12-09 19:15:50', '2021-12-09 19:15:50', NULL, '/system/dict/type/export', '导出字典类型信息', 'dict', 'GET'); +INSERT INTO `sys_apis` VALUES (31, '2021-12-09 19:16:26', '2021-12-09 19:16:26', NULL, '/system/dict/data/list', '获取字典数据分页列表', 'dict', 'GET'); +INSERT INTO `sys_apis` VALUES (32, '2021-12-09 19:17:01', '2021-12-09 19:17:01', NULL, '/system/dict/data/type', '获取字典数据列表通过字典类型', 'dict', 'GET'); +INSERT INTO `sys_apis` VALUES (33, '2021-12-09 19:17:39', '2021-12-09 19:17:39', NULL, '/system/dict/data/:dictCode', '获取字典数据信息', 'dict', 'GET'); +INSERT INTO `sys_apis` VALUES (34, '2021-12-09 19:18:20', '2021-12-09 19:18:20', NULL, '/system/dict/data', '添加字典数据信息', 'dict', 'POST'); +INSERT INTO `sys_apis` VALUES (35, '2021-12-09 19:18:44', '2021-12-09 19:18:44', NULL, '/system/dict/data', '修改字典数据信息', 'dict', 'PUT'); +INSERT INTO `sys_apis` VALUES (36, '2021-12-09 19:19:16', '2021-12-09 19:19:16', NULL, '/system/dict/data/:dictCode', '删除字典数据信息', 'dict', 'DELETE'); +INSERT INTO `sys_apis` VALUES (37, '2021-12-09 19:21:18', '2021-12-09 19:21:18', NULL, '/system/menu/menuTreeSelect', '获取菜单树', 'menu', 'GET'); +INSERT INTO `sys_apis` VALUES (38, '2021-12-09 19:21:47', '2021-12-09 19:21:47', NULL, '/system/menu/menuRole', '获取角色菜单', 'menu', 'GET'); +INSERT INTO `sys_apis` VALUES (39, '2021-12-09 19:22:42', '2021-12-09 19:22:42', NULL, '/system/menu/roleMenuTreeSelect/:roleId', '获取角色菜单树', 'menu', 'GET'); +INSERT INTO `sys_apis` VALUES (40, '2021-12-09 19:23:17', '2021-12-09 19:23:17', NULL, '/system/menu/menuPaths', '获取角色菜单路径列表', 'menu', 'GET'); +INSERT INTO `sys_apis` VALUES (41, '2021-12-09 19:23:40', '2021-12-09 19:23:40', NULL, '/system/menu/list', '获取菜单列表', 'menu', 'GET'); +INSERT INTO `sys_apis` VALUES (42, '2021-12-09 19:24:09', '2021-12-09 19:24:09', NULL, '/system/menu/:menuId', '获取菜单信息', 'menu', 'GET'); +INSERT INTO `sys_apis` VALUES (43, '2021-12-09 19:24:29', '2021-12-09 19:24:29', NULL, '/system/menu', '添加菜单信息', 'menu', 'POST'); +INSERT INTO `sys_apis` VALUES (44, '2021-12-09 19:24:48', '2021-12-09 19:24:48', NULL, '/system/menu', '修改菜单信息', 'menu', 'PUT'); +INSERT INTO `sys_apis` VALUES (45, '2021-12-09 19:25:10', '2021-12-09 19:25:10', NULL, '/system/menu/:menuId', '删除菜单信息', 'menu', 'DELETE'); +INSERT INTO `sys_apis` VALUES (46, '2021-12-09 19:25:44', '2021-12-09 19:27:06', NULL, '/system/post/list', '获取岗位分页列表', 'post', 'GET'); +INSERT INTO `sys_apis` VALUES (47, '2021-12-09 19:26:55', '2021-12-09 19:26:55', NULL, '/system/post/:postId', '获取岗位信息', 'post', 'GET'); +INSERT INTO `sys_apis` VALUES (48, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/post', '添加岗位信息', 'post', 'POST'); +INSERT INTO `sys_apis` VALUES (49, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/post', '修改岗位信息', 'post', 'PUT'); +INSERT INTO `sys_apis` VALUES (50, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/post/:postId', '删除岗位信息', 'post', 'DELETE'); +INSERT INTO `sys_apis` VALUES (51, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role/list', '获取角色分页列表', 'role', 'GET'); +INSERT INTO `sys_apis` VALUES (52, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role/:roleId', '获取角色信息', 'role', 'GET'); +INSERT INTO `sys_apis` VALUES (53, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role', '添加角色信息', 'role', 'POST'); +INSERT INTO `sys_apis` VALUES (54, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role', '修改角色信息', 'role', 'PUT'); +INSERT INTO `sys_apis` VALUES (55, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role/:roleId', '删除角色信息', 'role', 'DELETE'); +INSERT INTO `sys_apis` VALUES (56, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role/changeStatus', '修改角色状态', 'role', 'PUT'); +INSERT INTO `sys_apis` VALUES (57, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role/dataScope', '修改角色部门权限', 'role', 'PUT'); +INSERT INTO `sys_apis` VALUES (58, '2021-12-09 19:25:44', '2021-12-09 19:25:44', NULL, '/system/role/export', '导出角色信息', 'role', 'GET'); +INSERT INTO `sys_apis` VALUES (59, '2021-12-09 19:50:57', '2022-01-19 08:58:20', NULL, '/system/api/list', '获取api分页列表1', 'api', 'GET'); +INSERT INTO `sys_apis` VALUES (60, '2021-12-09 19:51:26', '2021-12-09 19:51:26', NULL, '/system/api/all', '获取所有api', 'api', 'GET'); +INSERT INTO `sys_apis` VALUES (61, '2021-12-09 19:51:54', '2021-12-09 19:51:54', NULL, '/system/api/getPolicyPathByRoleId', '获取角色拥有的api权限', 'api', 'GET'); +INSERT INTO `sys_apis` VALUES (62, '2021-12-09 19:52:14', '2021-12-09 19:52:14', NULL, '/system/api/:id', '获取api信息', 'api', 'GET'); +INSERT INTO `sys_apis` VALUES (63, '2021-12-09 19:52:35', '2021-12-09 19:52:35', NULL, '/system/api', '添加api信息', 'api', 'POST'); +INSERT INTO `sys_apis` VALUES (64, '2021-12-09 19:52:50', '2021-12-09 19:52:50', NULL, '/system/api', '修改api信息', 'api', 'PUT'); +INSERT INTO `sys_apis` VALUES (65, '2021-12-09 19:53:07', '2021-12-09 19:53:07', NULL, '/system/api/:id', '删除api信息', 'api', 'DELETE'); +INSERT INTO `sys_apis` VALUES (66, '2021-12-17 10:51:05', '2021-12-17 10:54:22', NULL, '/log/logLogin/list', '获取登录日志', 'log', 'GET'); +INSERT INTO `sys_apis` VALUES (67, '2021-12-17 10:51:43', '2021-12-17 10:54:28', NULL, '/log/logLogin/:infoId', '删除日志', 'log', 'DELETE'); +INSERT INTO `sys_apis` VALUES (68, '2021-12-17 10:53:09', '2021-12-17 10:54:34', NULL, '/log/logLogin/all', '清空所有', 'log', 'DELETE'); +INSERT INTO `sys_apis` VALUES (69, '2021-12-17 10:54:07', '2021-12-17 10:54:07', NULL, '/log/logOper/list', '操作日志列表', 'log', 'GET'); +INSERT INTO `sys_apis` VALUES (70, '2021-12-17 10:53:09', '2021-12-17 10:53:09', NULL, '/log/logOper/:operId', '删除', 'log', 'DELETE'); +INSERT INTO `sys_apis` VALUES (71, '2021-12-17 10:53:09', '2021-12-17 10:53:09', NULL, '/log/logOper/all', '清空', 'log', 'DELETE'); +INSERT INTO `sys_apis` VALUES (72, '2021-12-24 15:41:23', '2021-12-24 15:41:23', NULL, '/job/list', '任务列表', 'job', 'GET'); +INSERT INTO `sys_apis` VALUES (73, '2021-12-24 15:41:54', '2021-12-24 15:41:54', NULL, '/job', '添加', 'job', 'POST'); +INSERT INTO `sys_apis` VALUES (74, '2021-12-24 15:42:11', '2021-12-24 15:42:11', NULL, '/job', '修改任务', 'job', 'PUT'); +INSERT INTO `sys_apis` VALUES (75, '2021-12-24 15:42:37', '2021-12-24 16:32:21', NULL, '/job/:jobId', '获取任务', 'job', 'GET'); +INSERT INTO `sys_apis` VALUES (76, '2021-12-24 15:43:09', '2021-12-24 16:32:05', NULL, '/job/:jobId', '删除job', 'job', 'DELETE'); +INSERT INTO `sys_apis` VALUES (77, '2021-12-24 15:43:35', '2021-12-24 16:31:11', NULL, '/job/stop/:jobId', '停止任务', 'job', 'GET'); +INSERT INTO `sys_apis` VALUES (78, '2021-12-24 15:44:09', '2021-12-24 16:30:38', NULL, '/job/start/:jobId', '开始任务', 'job', 'GET'); +INSERT INTO `sys_apis` VALUES (79, '2021-12-24 15:45:03', '2023-08-08 14:15:59', NULL, '/job/log/list', '任务日志列表', 'job', 'GET'); +INSERT INTO `sys_apis` VALUES (80, '2021-12-24 15:45:33', '2023-08-08 14:16:07', NULL, '/job/log/all', '清空任务日志', 'job', 'DELETE'); +INSERT INTO `sys_apis` VALUES (81, '2021-12-24 15:46:08', '2023-08-08 14:16:15', NULL, '/job/log/:logId', '删除任务日志', 'job', 'DELETE'); +INSERT INTO `sys_apis` VALUES (82, '2021-12-24 15:45:33', '2021-12-24 15:45:33', NULL, '/system/notice/list', '获取通知分页列表', 'notice', 'GET'); +INSERT INTO `sys_apis` VALUES (83, '2021-12-24 15:45:33', '2021-12-24 15:45:33', NULL, '/system/notice', '添加通知信息', 'notice', 'POST'); +INSERT INTO `sys_apis` VALUES (84, '2021-12-24 15:45:33', '2021-12-24 15:45:33', NULL, '/system/notice', '修改通知信息', 'notice', 'PUT'); +INSERT INTO `sys_apis` VALUES (85, '2021-12-24 15:45:33', '2021-12-24 16:33:48', NULL, '/system/notice/:noticeId', '删除通知信息', 'notice', 'DELETE'); +INSERT INTO `sys_apis` VALUES (86, '2021-12-24 22:40:19', '2021-12-24 22:40:19', NULL, '/job/changeStatus', '修改状态', 'job', 'PUT'); +INSERT INTO `sys_apis` VALUES (88, '2022-01-02 13:53:06', '2022-07-18 10:57:58', NULL, '/develop/code/table/db/list', '数据库表列表', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (89, '2022-01-02 13:53:44', '2022-01-02 13:53:44', NULL, '/develop/code/table/list', '表列表', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (90, '2022-01-02 13:54:10', '2022-01-02 13:54:10', NULL, '/develop/code/table/info/:tableId', '表信息', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (91, '2022-01-02 13:54:42', '2022-07-18 10:58:35', NULL, '/develop/code/table/info/tableName', '表名获取表信息', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (92, '2022-01-02 13:55:13', '2022-01-02 13:55:13', NULL, '/develop/code/table/tableTree', '表树', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (93, '2022-01-02 13:56:37', '2022-01-02 13:56:37', NULL, '/develop/code/table', '导入表', 'gen', 'POST'); +INSERT INTO `sys_apis` VALUES (94, '2022-01-02 13:57:36', '2022-01-02 13:57:36', NULL, '/develop/code/table', '修改代码生成信息', 'gen', 'PUT'); +INSERT INTO `sys_apis` VALUES (95, '2022-01-02 13:58:25', '2022-01-02 13:58:25', NULL, '/develop/code/table/:tableId', '删除表数据', 'gen', 'DELETE'); +INSERT INTO `sys_apis` VALUES (96, '2022-01-02 13:59:07', '2022-01-02 13:59:07', NULL, '/develop/code/gen/preview/:tableId', '预览代码', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (97, '2022-01-02 13:59:43', '2022-01-02 13:59:43', NULL, '/develop/code/gen/code/:tableId', '生成代码', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (98, '2022-01-02 14:00:10', '2022-07-17 01:19:42', NULL, '/develop/code/gen/configure/:tableId', '生成api菜单', 'gen', 'GET'); +INSERT INTO `sys_apis` VALUES (124, '2023-06-29 16:59:08', '2023-06-29 17:00:17', NULL, '/device/product/category/list', '获取产品分类列表', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (125, '2023-06-29 17:00:08', '2023-06-29 17:00:08', NULL, '/device/product/category/list/all', '获取所有列表', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (126, '2023-06-29 17:00:56', '2023-06-29 17:00:56', NULL, '/device/product/category/list/tree', '获取树', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (127, '2023-06-29 17:01:44', '2023-06-29 17:01:44', NULL, '/device/product/category/:id', '查询单个', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (128, '2023-06-29 17:02:16', '2023-06-29 17:02:16', NULL, '/device/product/category', '添加分类', 'product', 'POST'); +INSERT INTO `sys_apis` VALUES (129, '2023-06-29 17:02:42', '2023-06-29 17:02:42', NULL, '/device/product/category', '修改分类', 'product', 'PUT'); +INSERT INTO `sys_apis` VALUES (130, '2023-06-29 17:03:07', '2023-06-29 17:03:07', NULL, '/device/product/category/:id', '删除分类', 'product', 'DELETE'); +INSERT INTO `sys_apis` VALUES (131, '2023-06-29 16:59:08', '2023-06-29 17:00:17', NULL, '/device/group/list', '获取设备分组列表', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (132, '2023-06-29 17:00:08', '2023-06-29 17:00:08', NULL, '/device/group/list/all', '获取所有列表', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (133, '2023-06-29 17:00:56', '2023-06-29 17:00:56', NULL, '/device/group/list/tree', '获取树', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (134, '2023-06-29 17:01:44', '2023-06-29 17:01:44', NULL, '/device/group/:id', '查询单个', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (135, '2023-06-29 17:02:16', '2023-06-29 17:02:16', NULL, '/device/group', '添加分组', 'device', 'POST'); +INSERT INTO `sys_apis` VALUES (136, '2023-06-29 17:02:42', '2023-06-29 17:02:42', NULL, '/device/group', '修改分组', 'device', 'PUT'); +INSERT INTO `sys_apis` VALUES (137, '2023-06-29 17:03:07', '2023-06-29 17:03:07', NULL, '/device/group/:id', '删除分组', 'device', 'DELETE'); +INSERT INTO `sys_apis` VALUES (138, '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL, '/device/product/:id', '删除产品信息', 'product', 'DELETE'); +INSERT INTO `sys_apis` VALUES (139, '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL, '/device/product/:id', '获取产品信息', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (140, '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL, '/device/product', '修改产品信息', 'product', 'PUT'); +INSERT INTO `sys_apis` VALUES (141, '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL, '/device/product/list', '查询产品列表(分页)', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (142, '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL, '/device/product', '添加产品信息', 'product', 'POST'); +INSERT INTO `sys_apis` VALUES (143, '2023-06-30 14:20:03', '2023-06-30 15:26:46', NULL, '/device/list', '查询设备列表(分页)', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (144, '2023-06-30 14:20:03', '2023-06-30 15:26:52', NULL, '/device/:id', '获取设备信息', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (145, '2023-06-30 14:20:03', '2023-06-30 15:26:57', NULL, '/device', '添加设备信息', 'device', 'POST'); +INSERT INTO `sys_apis` VALUES (146, '2023-06-30 14:20:03', '2023-06-30 15:27:04', NULL, '/device/:id', '删除设备信息', 'device', 'DELETE'); +INSERT INTO `sys_apis` VALUES (147, '2023-06-30 14:20:03', '2023-06-30 15:27:09', NULL, '/device', '修改设备信息', 'device', 'PUT'); +INSERT INTO `sys_apis` VALUES (148, '2023-06-30 15:11:25', '2023-08-02 16:06:13', NULL, '/device/group/list/tree/label', '获取设备组label', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (149, '2023-06-30 15:14:08', '2023-09-22 16:58:04', NULL, '/device/product/category/list/tree/label', '获取设置分类树', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (150, '2023-07-01 10:44:25', '2023-07-01 10:44:25', NULL, '/upload/up/oss', '上传文件到oss', 'upload', 'POST'); +INSERT INTO `sys_apis` VALUES (151, '2023-07-06 15:31:15', '2023-07-06 15:31:15', NULL, '/device/ota/list', '查询产品固件列表(分页)', 'ota', 'GET'); +INSERT INTO `sys_apis` VALUES (152, '2023-07-06 15:31:15', '2023-07-06 15:31:15', NULL, '/device/ota', '添加产品固件信息', 'ota', 'POST'); +INSERT INTO `sys_apis` VALUES (153, '2023-07-06 15:31:15', '2023-07-06 15:31:15', NULL, '/device/ota', '修改产品固件信息', 'ota', 'PUT'); +INSERT INTO `sys_apis` VALUES (154, '2023-07-06 15:31:15', '2023-07-06 15:31:15', NULL, '/device/ota/:id', '删除产品固件信息', 'ota', 'DELETE'); +INSERT INTO `sys_apis` VALUES (155, '2023-07-06 15:31:15', '2023-07-06 15:31:15', NULL, '/device/ota/:id', '获取产品固件信息', 'ota', 'GET'); +INSERT INTO `sys_apis` VALUES (156, '2023-07-06 15:32:10', '2023-07-06 15:32:10', NULL, '/device/template/list', '查询产品模型列表(分页)', 'template', 'GET'); +INSERT INTO `sys_apis` VALUES (157, '2023-07-06 15:32:10', '2023-07-06 15:32:10', NULL, '/device/template', '修改产品模型信息', 'template', 'PUT'); +INSERT INTO `sys_apis` VALUES (158, '2023-07-06 15:32:10', '2023-07-06 15:32:10', NULL, '/device/template/:id', '获取产品模型信息', 'template', 'GET'); +INSERT INTO `sys_apis` VALUES (159, '2023-07-06 15:32:10', '2023-07-06 15:32:10', NULL, '/device/template/:id', '删除产品模型信息', 'template', 'DELETE'); +INSERT INTO `sys_apis` VALUES (160, '2023-07-06 15:32:10', '2023-07-06 15:32:10', NULL, '/device/template', '添加产品模型信息', 'template', 'POST'); +INSERT INTO `sys_apis` VALUES (161, '2023-07-07 16:35:45', '2023-07-07 16:35:45', NULL, '/device/product/list/all', '获取所有列表', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (162, '2023-04-13 09:03:47', '2023-04-13 09:03:47', NULL, '/visual/screen', '修改bi大屏信息', 'screen', 'PUT'); +INSERT INTO `sys_apis` VALUES (163, '2023-04-13 09:03:47', '2023-04-13 09:03:47', NULL, '/visual/screen/:screenId', '获取bi大屏信息', 'screen', 'GET'); +INSERT INTO `sys_apis` VALUES (164, '2023-04-13 09:03:47', '2023-04-13 09:03:47', NULL, '/visual/screen/list', '查询bi大屏列表(分页)', 'screen', 'GET'); +INSERT INTO `sys_apis` VALUES (165, '2023-04-13 09:03:47', '2023-04-13 09:03:47', NULL, '/visual/screen/:screenId', '删除bi大屏信息', 'screen', 'DELETE'); +INSERT INTO `sys_apis` VALUES (166, '2023-04-13 09:03:47', '2023-04-13 09:03:47', NULL, '/visual/screen', '添加bi大屏信息', 'screen', 'POST'); +INSERT INTO `sys_apis` VALUES (167, '2023-04-13 10:15:27', '2023-04-13 10:15:27', NULL, '/visual/screen/group/list', '大屏分组列表', 'screen_group', 'GET'); +INSERT INTO `sys_apis` VALUES (168, '2023-04-13 10:16:15', '2023-04-13 10:16:15', NULL, '/visual/screen/group/list/tree', '大屏分组列表树', 'screen_group', 'GET'); +INSERT INTO `sys_apis` VALUES (169, '2023-04-13 10:16:38', '2023-04-13 10:16:38', NULL, '/visual/screen/group/list/all', '获取所有分组', 'screen_group', 'GET'); +INSERT INTO `sys_apis` VALUES (170, '2023-04-13 10:17:34', '2023-04-13 10:17:34', NULL, '/visual/screen/group/:id', '获取分组', 'screen_group', 'GET'); +INSERT INTO `sys_apis` VALUES (171, '2023-04-13 10:18:10', '2023-04-13 10:18:10', NULL, '/visual/screen/group', '添加分组', 'screen_group', 'POST'); +INSERT INTO `sys_apis` VALUES (172, '2023-04-13 10:18:35', '2023-04-13 10:18:35', NULL, '/visual/screen/group', '修改分组', 'screen_group', 'PUT'); +INSERT INTO `sys_apis` VALUES (173, '2023-04-13 10:19:09', '2023-04-13 10:19:09', NULL, '/visual/screen/group/:id', '删除分组', 'screen_group', 'DELETE'); +INSERT INTO `sys_apis` VALUES (174, '2023-04-13 15:49:39', '2023-04-13 15:49:39', NULL, '/visual/screen/changeStatus', '改变状态', 'screen', 'PUT'); +INSERT INTO `sys_apis` VALUES (175, '2023-04-13 15:50:18', '2023-07-21 17:44:48', NULL, '/rule/chain/changeRoot', '改变规则链', 'rulechain', 'PUT'); +INSERT INTO `sys_apis` VALUES (176, '2023-04-11 02:05:25', '2023-04-11 02:05:25', NULL, '/rule/chain/list', '查询规则链列表(分页)', 'rulechain', 'GET'); +INSERT INTO `sys_apis` VALUES (177, '2023-04-11 02:05:25', '2023-04-11 02:05:25', NULL, '/rule/chain/:ruleId', '删除规则链信息', 'rulechain', 'DELETE'); +INSERT INTO `sys_apis` VALUES (178, '2023-04-11 02:05:25', '2023-04-11 02:05:25', NULL, '/rule/chain', '修改规则链信息', 'rulechain', 'PUT'); +INSERT INTO `sys_apis` VALUES (179, '2023-04-11 02:05:25', '2023-04-11 02:05:25', NULL, '/rule/chain', '添加规则链信息', 'rulechain', 'POST'); +INSERT INTO `sys_apis` VALUES (180, '2023-04-11 02:05:25', '2023-04-11 02:05:25', NULL, '/rule/chain/:ruleId', '获取规则链信息', 'rulechain', 'GET'); +INSERT INTO `sys_apis` VALUES (181, '2023-07-24 11:51:10', '2023-07-24 11:51:10', NULL, '/rule/chain/list/label', '获取规则链label列表', 'rulechain', 'GET'); +INSERT INTO `sys_apis` VALUES (182, '2023-07-31 14:14:06', '2023-07-31 14:14:06', NULL, '/device/list/all', '获取所有设备', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (183, '2023-08-02 16:05:24', '2023-08-02 16:05:24', NULL, '/device/:id/status', '获取设备状态', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (184, '2023-08-03 09:50:41', '2023-08-03 09:50:41', NULL, '/rule/chain/clone/:ruleId', '克隆规则链', 'rulechain', 'POST'); +INSERT INTO `sys_apis` VALUES (185, '2023-08-03 14:16:55', '2023-08-03 14:16:55', NULL, '/device/alarm/list', '告警分页列表', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (186, '2023-08-03 14:17:23', '2023-08-03 14:17:23', NULL, '/device/alarm', '修改告警', 'device', 'PUT'); +INSERT INTO `sys_apis` VALUES (187, '2023-08-03 14:18:14', '2023-08-03 14:18:14', NULL, '/device/alarm/:id', '删除告警信息', 'device', 'DELETE'); +INSERT INTO `sys_apis` VALUES (188, '2023-08-04 10:59:57', '2023-08-04 10:59:57', NULL, '/device/cmd/list', '设备命令日志列表', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (189, '2023-08-04 11:00:18', '2023-08-04 11:00:18', NULL, '/device/cmd', '下发命令', 'device', 'POST'); +INSERT INTO `sys_apis` VALUES (190, '2023-08-04 11:00:46', '2023-08-04 11:00:46', NULL, '/device/cmd/:id', '删除命令记录', 'device', 'DELETE'); +INSERT INTO `sys_apis` VALUES (191, '2023-08-04 14:16:06', '2023-08-04 14:16:06', NULL, '/device/template/list/all', '查询所有tsl', 'template', 'GET'); +INSERT INTO `sys_apis` VALUES (192, '2023-08-04 16:39:06', '2023-08-04 16:39:06', NULL, '/device/:id/attribute/down', '下发设备属性', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (193, '2023-08-19 09:12:31', '2023-08-19 09:12:31', NULL, '/upload/up', '上传文件到本地', 'upload', 'POST'); +INSERT INTO `sys_apis` VALUES (194, '2023-09-05 08:42:13', '2023-09-05 08:42:13', NULL, '/video/ys/device/list', '获取萤石设备列表', 'video', 'GET'); +INSERT INTO `sys_apis` VALUES (195, '2023-09-05 08:43:11', '2023-09-05 08:43:11', NULL, '/video/ys/:deviceSerial/channel', '获取指定设备通道', 'video', 'GET'); +INSERT INTO `sys_apis` VALUES (196, '2023-09-05 08:45:31', '2023-09-05 08:45:31', NULL, '/video/ys/:deviceSerial/channel/live', '设备通道直播地址', 'video', 'GET'); +INSERT INTO `sys_apis` VALUES (197, '2023-09-05 08:46:14', '2023-09-05 08:46:14', NULL, '/video/ys/:deviceSerial/ptz/start', '摄像头操作', 'video', 'GET'); +INSERT INTO `sys_apis` VALUES (198, '2023-09-05 08:46:47', '2023-09-05 08:46:47', NULL, '/video/ys/:deviceSerial/ptz/stop', '摄像头操作停止', 'video', 'GET'); +INSERT INTO `sys_apis` VALUES (199, '2023-09-06 15:55:44', '2023-09-06 15:55:44', NULL, '/rule/chain/log/list', '规则链审计日志', 'rulechain', 'GET'); +INSERT INTO `sys_apis` VALUES (200, '2023-09-06 15:56:35', '2023-09-06 15:56:35', NULL, '/rule/chain/log/delete', '条件删除规则链审计', 'rulechain', 'GET'); +INSERT INTO `sys_apis` VALUES (201, '2023-09-08 17:20:35', '2023-09-08 17:20:35', NULL, '/device/:id/property/history', '获取设备属性的遥测历史', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (202, '2023-09-15 17:29:07', '2023-09-15 17:29:07', NULL, '/device/:id/allot/org', '设备分配组织', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (203, '2023-09-22 16:56:56', '2023-09-22 16:56:56', NULL, '/device/product/:id/tsl', '获取产品tsl', 'product', 'GET'); +INSERT INTO `sys_apis` VALUES (204, '2023-09-23 14:25:58', '2023-09-23 14:25:58', NULL, '/device/panel', '获取设备统计面板', 'device', 'GET'); +INSERT INTO `sys_apis` VALUES (205, '2023-09-25 10:13:59', '2023-09-25 10:13:59', NULL, '/device/alarm/panel', '获取面板告警分组', 'device', 'GET'); + +-- ---------------------------- +-- Table structure for sys_configs +-- ---------------------------- +DROP TABLE IF EXISTS `sys_configs`; +CREATE TABLE `sys_configs` ( + `config_id` bigint(0) NOT NULL AUTO_INCREMENT COMMENT '主键编码', + `config_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ConfigName', + `config_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ConfigKey', + `config_value` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ConfigValue', + `config_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否系统内置0,1', + `is_frontend` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '是否前台', + `remark` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'Remark', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`config_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 1 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_configs +-- ---------------------------- +INSERT INTO `sys_configs` VALUES (1, '账号初始密码', 'sys.user.initPassword', '123456', '0', '0', '初始密码', '2021-12-04 13:50:13', '2021-12-04 13:54:52', NULL); + +-- ---------------------------- +-- Table structure for sys_dict_data +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_data`; +CREATE TABLE `sys_dict_data` ( + `dict_code` bigint(0) NOT NULL AUTO_INCREMENT, + `dict_sort` int(0) NULL DEFAULT NULL COMMENT '排序', + `dict_label` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '标签', + `dict_value` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '值', + `dict_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '字典类型', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态(0正常 1停用)', + `css_class` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'CssClass', + `list_class` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'ListClass', + `is_default` varchar(8) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT 'IsDefault', + `create_by` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `update_by` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`dict_code`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 41 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_data +-- ---------------------------- +INSERT INTO `sys_dict_data` VALUES (1, 0, '男', '0', 'sys_user_sex', '0', '', '', '', 'admin', '', '男', '2021-11-30 14:58:18', '2021-11-30 14:58:18', NULL); +INSERT INTO `sys_dict_data` VALUES (2, 1, '女', '1', 'sys_user_sex', '0', '', '', '', 'admin', '', '女生', '2021-11-30 15:09:11', '2021-11-30 15:10:28', NULL); +INSERT INTO `sys_dict_data` VALUES (3, 2, '未知', '2', 'sys_user_sex', '0', '', '', '', 'admin', '', '未知', '2021-11-30 15:09:11', '2021-11-30 15:10:28', NULL); +INSERT INTO `sys_dict_data` VALUES (4, 0, '正常', '0', 'sys_normal_disable', '0', '', '', '', 'admin', '', '', '2021-12-01 15:58:50', '2021-12-01 15:58:50', NULL); +INSERT INTO `sys_dict_data` VALUES (5, 1, '停用', '1', 'sys_normal_disable', '0', '', '', '', 'admin', '', '', '2021-12-01 15:59:08', '2021-12-01 15:59:08', NULL); +INSERT INTO `sys_dict_data` VALUES (6, 0, '目录', 'M', 'sys_menu_type', '0', '', '', '', 'admin', '', '', '2021-12-02 09:49:12', '2021-12-02 09:49:12', NULL); +INSERT INTO `sys_dict_data` VALUES (7, 1, '菜单', 'C', 'sys_menu_type', '0', '', '', '', 'admin', '', '', '2021-12-02 09:49:35', '2021-12-02 09:49:52', NULL); +INSERT INTO `sys_dict_data` VALUES (8, 2, '按钮', 'F', 'sys_menu_type', '0', '', '', '', 'admin', '', '', '2021-12-02 09:49:35', '2021-12-02 09:49:35', NULL); +INSERT INTO `sys_dict_data` VALUES (9, 0, '显示', '0', 'sys_show_hide', '0', '', '', '', 'admin', '', '', '2021-12-02 09:56:40', '2021-12-02 09:56:40', NULL); +INSERT INTO `sys_dict_data` VALUES (10, 0, '隐藏', '1', 'sys_show_hide', '0', '', '', '', 'admin', '', '', '2021-12-02 09:56:52', '2021-12-02 09:56:52', NULL); +INSERT INTO `sys_dict_data` VALUES (11, 0, '是', '0', 'sys_num_yes_no', '0', '', '', '', 'admin', '', '', '2021-12-02 10:16:16', '2021-12-02 10:16:16', NULL); +INSERT INTO `sys_dict_data` VALUES (12, 1, '否', '1', 'sys_num_yes_no', '0', '', '', '', 'admin', '', '', '2021-12-02 10:16:26', '2021-12-02 10:16:26', NULL); +INSERT INTO `sys_dict_data` VALUES (13, 0, '是', '0', 'sys_yes_no', '0', '', '', '', 'admin', '', '', '2021-12-04 13:48:15', '2021-12-04 13:48:15', NULL); +INSERT INTO `sys_dict_data` VALUES (14, 0, '否', '1', 'sys_yes_no', '0', '', '', '', 'admin', '', '', '2021-12-04 13:48:21', '2021-12-04 13:48:21', NULL); +INSERT INTO `sys_dict_data` VALUES (15, 0, '创建(POST)', 'POST', 'sys_method_api', '0', '', '', '', 'admin', '', '', '2021-12-08 17:22:05', '2021-12-09 09:29:52', NULL); +INSERT INTO `sys_dict_data` VALUES (16, 1, '查询(GET)', 'GET', 'sys_method_api', '0', '', '', '', 'admin', '', '', '2021-12-08 17:22:24', '2021-12-09 09:29:59', NULL); +INSERT INTO `sys_dict_data` VALUES (17, 2, '修改(PUT)', 'PUT', 'sys_method_api', '0', '', '', '', 'admin', '', '', '2021-12-08 17:22:40', '2021-12-09 09:30:06', NULL); +INSERT INTO `sys_dict_data` VALUES (18, 3, '删除(DELETE)', 'DELETE', 'sys_method_api', '0', '', '', '', 'admin', '', '', '2021-12-08 17:22:54', '2021-12-09 09:30:13', NULL); +INSERT INTO `sys_dict_data` VALUES (19, 0, '成功', '0', 'sys_common_status', '0', '', '', '', 'admin', '', '', '2021-12-17 11:01:52', '2021-12-17 11:01:52', NULL); +INSERT INTO `sys_dict_data` VALUES (20, 0, '失败', '1', 'sys_common_status', '0', '', '', '', 'admin', '', '', '2021-12-17 11:02:08', '2021-12-17 11:02:08', NULL); +INSERT INTO `sys_dict_data` VALUES (21, 0, '其他', '0', 'sys_oper_type', '0', '', '', '', 'admin', '', '', '2021-12-17 11:30:07', '2021-12-17 11:30:07', NULL); +INSERT INTO `sys_dict_data` VALUES (22, 0, '新增', '1', 'sys_oper_type', '0', '', '', '', 'admin', '', '', '2021-12-17 11:30:21', '2021-12-17 11:30:21', NULL); +INSERT INTO `sys_dict_data` VALUES (23, 0, '修改', '2', 'sys_oper_type', '0', '', '', '', 'admin', '', '', '2021-12-17 11:30:32', '2021-12-17 11:30:32', NULL); +INSERT INTO `sys_dict_data` VALUES (24, 0, '删除', '3', 'sys_oper_type', '0', '', '', '', 'admin', '', '', '2021-12-17 11:30:40', '2021-12-17 11:30:40', NULL); +INSERT INTO `sys_dict_data` VALUES (25, 0, '默认', 'DEFAULT', 'sys_job_group', '0', '', '', '', 'panda', '', '', '2021-12-24 15:15:31', '2021-12-24 15:15:31', NULL); +INSERT INTO `sys_dict_data` VALUES (26, 1, '系统', 'SYSTEM', 'sys_job_group', '0', '', '', '', 'panda', '', '', '2021-12-24 15:15:50', '2021-12-24 15:15:50', NULL); +INSERT INTO `sys_dict_data` VALUES (27, 0, '发布通知', '1', 'sys_notice_type', '0', '', '', '', 'panda', '', '', '2021-12-26 15:24:07', '2021-12-26 15:24:07', NULL); +INSERT INTO `sys_dict_data` VALUES (28, 0, '任免通知', '2', 'sys_notice_type', '0', '', '', '', 'panda', '', '', '2021-12-26 15:24:18', '2021-12-26 15:24:18', NULL); +INSERT INTO `sys_dict_data` VALUES (29, 0, '事物通知', '3', 'sys_notice_type', '0', '', '', '', 'panda', '', '', '2021-12-26 15:24:46', '2021-12-26 15:24:46', NULL); +INSERT INTO `sys_dict_data` VALUES (30, 0, '审批通知', '4', 'sys_notice_type', '0', '', '', '', 'panda', '', '', '2021-12-26 15:25:08', '2021-12-26 15:25:08', NULL); +INSERT INTO `sys_dict_data` VALUES (31, 0, '阿里云', '0', 'res_oss_category', '0', '', '', '', 'panda', '', '', '2022-01-13 15:44:01', '2022-01-13 15:44:01', NULL); +INSERT INTO `sys_dict_data` VALUES (32, 1, '七牛云', '1', 'res_oss_category', '0', '', '', '', 'panda', '', '', '2022-01-13 15:44:18', '2022-01-13 15:44:18', NULL); +INSERT INTO `sys_dict_data` VALUES (33, 2, '腾讯云', '2', 'res_oss_category', '0', '', '', '', 'panda', '', '', '2022-01-13 15:44:39', '2022-01-13 15:44:39', NULL); +INSERT INTO `sys_dict_data` VALUES (34, 0, '阿里云', '0', 'res_sms_category', '0', '', '', '', 'panda', '', '', '2022-01-13 15:47:30', '2022-01-13 15:47:30', NULL); +INSERT INTO `sys_dict_data` VALUES (35, 1, '腾讯云', '1', 'res_sms_category', '0', '', '', '', 'panda', '', '', '2022-01-13 15:47:39', '2022-01-13 15:47:39', NULL); +INSERT INTO `sys_dict_data` VALUES (36, 0, '163邮箱', '0', 'res_mail_category', '0', '', '', '', 'panda', '', '', '2022-01-14 15:43:42', '2022-01-14 15:43:42', NULL); +INSERT INTO `sys_dict_data` VALUES (37, 0, 'qq邮箱', '1', 'res_mail_category', '0', '', '', '', 'panda', '', '', '2022-01-14 15:44:08', '2022-01-14 15:44:08', NULL); +INSERT INTO `sys_dict_data` VALUES (38, 0, '企业邮箱', '2', 'res_mail_category', '0', '', '', '', 'panda', '', '', '2022-01-14 15:44:20', '2022-01-14 15:44:20', NULL); +INSERT INTO `sys_dict_data` VALUES (39, 1, '未发布', '0', 'sys_release_type', '0', '', '', '', 'panda', '', '', '2023-07-21 16:11:27', '2023-07-21 16:11:27', NULL); +INSERT INTO `sys_dict_data` VALUES (40, 2, '已发布', '1', 'sys_release_type', '0', '', '', '', 'panda', '', '', '2023-07-21 16:11:44', '2023-07-21 16:11:44', NULL); + +-- ---------------------------- +-- Table structure for sys_dict_types +-- ---------------------------- +DROP TABLE IF EXISTS `sys_dict_types`; +CREATE TABLE `sys_dict_types` ( + `dict_id` bigint(0) NOT NULL AUTO_INCREMENT, + `dict_name` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '名称', + `dict_type` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '类型', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态', + `create_by` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `update_by` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `remark` varchar(256) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '备注', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`dict_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 33 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_dict_types +-- ---------------------------- +INSERT INTO `sys_dict_types` VALUES (1, '用户性别', 'sys_user_sex', '0', 'admin', '', '性别列表', '2021-11-30 14:02:52', '2021-11-30 14:07:55', '2021-11-30 14:11:54'); +INSERT INTO `sys_dict_types` VALUES (2, '用户性别', 'sys_user_sex', '0', 'admin', '', '用户性别列表', '2021-11-30 14:12:33', '2021-11-30 14:12:33', '2021-11-30 14:14:19'); +INSERT INTO `sys_dict_types` VALUES (3, '的心', 'sfd', '0', 'admin', '', 'fs', '2021-11-30 14:13:22', '2021-11-30 14:13:22', '2021-11-30 14:14:19'); +INSERT INTO `sys_dict_types` VALUES (4, '用户性别', 'sys_user_sex', '0', 'admin', '', '性别字典', '2021-11-30 14:15:25', '2021-11-30 14:15:25', NULL); +INSERT INTO `sys_dict_types` VALUES (5, 'df', 'da', '0', 'admin', '', 'sd', '2021-11-30 15:54:33', '2021-11-30 15:54:33', '2021-11-30 15:54:40'); +INSERT INTO `sys_dict_types` VALUES (6, '系统开关', 'sys_normal_disable', '0', 'admin', '', '开关列表', '2021-12-01 15:57:58', '2021-12-01 15:57:58', NULL); +INSERT INTO `sys_dict_types` VALUES (7, '菜单类型', 'sys_menu_type', '0', 'admin', '', '菜单类型列表', '2021-12-02 09:48:48', '2021-12-02 09:56:12', NULL); +INSERT INTO `sys_dict_types` VALUES (8, '菜单状态', 'sys_show_hide', '0', 'admin', '', '菜单状态列表', '2021-12-02 09:55:59', '2021-12-02 09:55:59', NULL); +INSERT INTO `sys_dict_types` VALUES (9, '数字是否', 'sys_num_yes_no', '0', 'admin', '', '数字是否列表', '2021-12-02 10:13:29', '2021-12-02 10:13:40', '2021-12-02 10:15:07'); +INSERT INTO `sys_dict_types` VALUES (10, '数字是否', 'sys_num_yes_no', '0', 'admin', '', '数字是否', '2021-12-02 10:13:29', '2021-12-02 10:13:29', NULL); +INSERT INTO `sys_dict_types` VALUES (11, '状态是否', 'sys_yes_no', '0', 'admin', '', '状态是否', '2021-12-04 13:47:57', '2021-12-04 13:47:57', NULL); +INSERT INTO `sys_dict_types` VALUES (12, '网络请求方法', 'sys_method_api', '0', 'admin', '', '网络请求方法列表', '2021-12-08 17:21:27', '2021-12-08 17:21:27', NULL); +INSERT INTO `sys_dict_types` VALUES (13, '成功失败', 'sys_common_status', '0', 'admin', '', '是否成功失败', '2021-12-17 10:10:03', '2021-12-17 10:10:03', NULL); +INSERT INTO `sys_dict_types` VALUES (27, '操作分类', 'sys_oper_type', '0', 'admin', '', '操作分类列表', '2021-12-17 11:29:50', '2021-12-17 11:29:50', NULL); +INSERT INTO `sys_dict_types` VALUES (28, '任务组', 'sys_job_group', '0', 'panda', '', '系统任务,开机自启', '2021-12-24 15:14:56', '2021-12-24 15:14:56', NULL); +INSERT INTO `sys_dict_types` VALUES (29, '通知类型', 'sys_notice_type', '0', 'panda', '', '通知类型列表', '2021-12-26 15:23:26', '2021-12-26 15:23:26', NULL); +INSERT INTO `sys_dict_types` VALUES (30, 'oss分类', 'res_oss_category', '0', 'panda', '', 'oss分类列表', '2022-01-13 15:43:29', '2022-01-13 15:43:29', NULL); +INSERT INTO `sys_dict_types` VALUES (31, 'sms分类', 'res_sms_category', '0', 'panda', '', 'sms分类列表', '2021-12-26 15:23:26', '2022-01-13 15:47:13', NULL); +INSERT INTO `sys_dict_types` VALUES (32, 'mail分类', 'res_mail_category', '0', 'panda', '', 'mail分类列表', '2022-01-14 15:43:17', '2022-01-14 15:43:17', NULL); +INSERT INTO `sys_dict_types` VALUES (33, '发布状态', 'sys_release_type', '0', 'panda', '', '发布状态', '2023-07-21 16:10:38', '2023-07-21 16:10:38', NULL); + +-- ---------------------------- +-- Table structure for sys_menus +-- ---------------------------- +DROP TABLE IF EXISTS `sys_menus`; +CREATE TABLE `sys_menus` ( + `menu_id` bigint(0) NOT NULL AUTO_INCREMENT, + `menu_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `title` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `parent_id` int(0) NULL DEFAULT NULL, + `sort` int(0) NULL DEFAULT NULL, + `icon` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `path` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `component` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `is_iframe` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `is_link` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `menu_type` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `is_hide` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `is_keep_alive` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `is_affix` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `permission` varchar(32) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `status` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `remark` varchar(191) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`menu_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 179 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_menus +-- ---------------------------- +INSERT INTO `sys_menus` VALUES (1, '系统设置', '', 0, 0, 'elementSetting', '/system', 'Layout', '1', '', 'M', '0', '0', '1', '', '0', 'admin', 'panda', '', '2021-12-02 11:04:08', '2021-12-28 13:32:21', NULL); +INSERT INTO `sys_menus` VALUES (3, '用户管理', '', 1, 1, 'elementUser', '/system/user', '/system/user/index', '1', '', 'C', '0', '1', '1', 'system:user:list', '0', 'admin', 'panda', '', '2021-12-02 14:07:56', '2021-12-28 13:32:44', NULL); +INSERT INTO `sys_menus` VALUES (4, '添加用户', '', 3, 1, '', '', '', '', '', 'F', '0', '', '', 'system:user:add', '0', 'admin', '', '', '2021-12-03 13:36:33', '2021-12-03 13:36:33', NULL); +INSERT INTO `sys_menus` VALUES (5, '编辑用户', '', 3, 1, '', '', '', '', '', 'F', '0', '', '', 'system:user:edit', '0', 'admin', '', '', '2021-12-03 13:48:13', '2021-12-03 13:48:13', NULL); +INSERT INTO `sys_menus` VALUES (6, '角色管理', '', 1, 2, 'elementUserFilled', '/system/role', '/system/role/index', '1', '', 'C', '0', '1', '1', 'system:role:list', '0', '', 'panda', '', '2021-12-03 13:51:55', '2022-07-16 10:23:21', NULL); +INSERT INTO `sys_menus` VALUES (7, '菜单管理', '', 1, 2, 'iconfont icon-juxingkaobei', '/system/menu', '/system/menu/index', '1', '', 'C', '0', '1', '1', 'system:menu:list', '0', 'admin', 'panda', '', '2021-12-03 13:54:44', '2021-12-28 13:33:19', NULL); +INSERT INTO `sys_menus` VALUES (8, '组织管理', '', 1, 3, 'iconfont icon-jiliandongxuanzeqi', '/system/organization', '/system/organization/index', '1', '', 'C', '0', '1', '1', 'system:organization:list', '0', '', 'panda', '', '2021-12-03 13:58:36', '2023-09-14 14:05:07', NULL); +INSERT INTO `sys_menus` VALUES (9, '岗位管理', '', 1, 4, 'iconfont icon-neiqianshujuchucun', '/system/post', '/system/post/index', '1', '', 'C', '0', '1', '1', 'system:post:list', '0', 'admin', 'panda', '', '2021-12-03 13:54:44', '2021-12-28 13:40:31', NULL); +INSERT INTO `sys_menus` VALUES (10, '字典管理', '', 1, 5, 'elementCellphone', '/system/dict', '/system/dict/index', '1', '', 'C', '0', '1', '1', 'system:dict:list', '0', 'admin', 'panda', '', '2021-12-03 13:54:44', '2021-12-28 13:40:50', NULL); +INSERT INTO `sys_menus` VALUES (11, '参数管理', '', 1, 6, 'elementDocumentCopy', '/system/config', '/system/config/index', '1', '', 'C', '0', '1', '1', 'system:config:list', '0', 'admin', 'panda', '', '2021-12-03 13:54:44', '2021-12-28 13:41:05', NULL); +INSERT INTO `sys_menus` VALUES (12, '个人中心', '', 0, 10, 'elementAvatar', '/personal', '/personal/index', '1', '', 'M', '1', '0', '0', '', '0', 'admin', 'panda', '', '2021-12-03 14:12:43', '2023-06-27 10:09:26', NULL); +INSERT INTO `sys_menus` VALUES (13, '添加配置', '', 11, 1, '', '', '', '', '', 'F', '', '', '', 'system:config:add', '0', 'admin', '', '', '2021-12-06 17:19:19', '2021-12-06 17:19:19', NULL); +INSERT INTO `sys_menus` VALUES (14, '修改配置', '', 11, 1, '', '', '', '', '', 'F', '', '', '', 'system:config:edit', '0', 'admin', '', '', '2021-12-06 17:20:30', '2021-12-06 17:20:30', NULL); +INSERT INTO `sys_menus` VALUES (15, '删除配置', '', 11, 1, '', '', '', '', '', 'F', '', '', '', 'system:config:delete', '0', 'admin', '', '', '2021-12-06 17:23:52', '2021-12-06 17:23:52', NULL); +INSERT INTO `sys_menus` VALUES (16, '导出配置', '', 11, 1, '', '', '', '', '', 'F', '', '', '', 'system:config:export', '0', 'admin', '', '', '2021-12-06 17:24:41', '2021-12-06 17:24:41', NULL); +INSERT INTO `sys_menus` VALUES (17, '新增角色', '', 6, 1, '', '', '', '', '', 'F', '', '', '', 'system:role:add', '0', 'admin', '', '', '2021-12-06 17:43:35', '2021-12-06 17:43:35', NULL); +INSERT INTO `sys_menus` VALUES (18, '删除角色', '', 6, 1, '', '', '', '', '', 'F', '', '', '', 'system:role:delete', '0', 'admin', '', '', '2021-12-06 17:44:10', '2021-12-06 17:44:10', NULL); +INSERT INTO `sys_menus` VALUES (19, '修改角色', '', 6, 1, '', '', '', '', '', 'F', '', '', '', 'system:role:edit', '0', 'admin', '', '', '2021-12-06 17:44:48', '2021-12-06 17:44:48', NULL); +INSERT INTO `sys_menus` VALUES (20, '导出角色', '', 6, 1, '', '', '', '', '', 'F', '', '', '', 'system:role:export', '0', 'admin', '', '', '2021-12-06 17:45:25', '2021-12-06 17:45:25', NULL); +INSERT INTO `sys_menus` VALUES (21, '添加菜单', '', 7, 1, '', '', '', '', '', 'F', '', '', '', 'system:menu:add', '0', 'admin', '', '', '2021-12-06 17:46:01', '2021-12-06 17:46:01', NULL); +INSERT INTO `sys_menus` VALUES (22, '修改菜单', '', 7, 1, '', '', '', '', '', 'F', '', '', '', 'system:menu:edit', '0', 'admin', '', '', '2021-12-06 17:46:24', '2021-12-06 17:46:24', NULL); +INSERT INTO `sys_menus` VALUES (23, '删除菜单', '', 7, 1, '', '', '', '', '', 'F', '', '', '', 'system:menu:delete', '0', 'admin', '', '', '2021-12-06 17:46:47', '2021-12-06 17:46:47', NULL); +INSERT INTO `sys_menus` VALUES (24, '添加部门', '', 8, 1, '', '', '', '', '', 'F', '', '', '', 'system:organization:add', '0', '', 'panda', '', '2021-12-07 09:33:58', '2023-09-14 14:05:20', NULL); +INSERT INTO `sys_menus` VALUES (25, '编辑部门', '', 8, 1, '', '', '', '', '', 'F', '', '', '', 'system:organization:edit', '0', '', 'panda', '', '2021-12-07 09:34:39', '2023-09-14 14:05:26', NULL); +INSERT INTO `sys_menus` VALUES (26, '删除部门', '', 8, 1, '', '', '', '', '', 'F', '', '', '', 'system:organization:delete', '0', '', 'panda', '', '2021-12-07 09:35:09', '2023-09-14 14:05:32', NULL); +INSERT INTO `sys_menus` VALUES (28, '添加岗位', '', 9, 1, '', '', '', '', '', 'F', '', '', '', 'system:post:add', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (29, '编辑岗位', '', 9, 1, '', '', '', '', '', 'F', '', '', '', 'system:post:edit', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (30, '删除岗位', '', 9, 1, '', '', '', '', '', 'F', '', '', '', 'system:post:delete', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (31, '导出岗位', '', 9, 1, '', '', '', '', '', 'F', '', '', '', 'system:post:export', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (32, '添加字典类型', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictT:add', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (33, '编辑字典类型', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictT:edit', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (34, '删除字典类型', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictT:delete', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (35, '导出字典类型', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictT:export', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (36, '新增字典数据', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictD:add', '0', 'admin', '', '', '2021-12-07 09:35:09', '2021-12-07 09:35:09', NULL); +INSERT INTO `sys_menus` VALUES (37, '修改字典数据', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictD:edit', '0', 'admin', '', '', '2021-12-07 09:48:04', '2021-12-07 09:48:04', NULL); +INSERT INTO `sys_menus` VALUES (38, '删除字典数据', '', 10, 1, '', '', '', '', '', 'F', '', '', '', 'system:dictD:delete', '0', 'admin', '', '', '2021-12-07 09:48:42', '2021-12-07 09:48:42', NULL); +INSERT INTO `sys_menus` VALUES (39, 'API管理', '', 1, 2, 'iconfont icon-siweidaotu', '/system/api', '/system/api/index', '1', '', 'C', '0', '1', '1', 'system:api:list', '0', '', 'panda', '', '2021-12-09 09:09:13', '2022-07-16 10:23:42', NULL); +INSERT INTO `sys_menus` VALUES (40, '添加api', '', 39, 1, '', '/system/api', '', '', '', 'F', '', '', '', 'system:api:add', '0', 'admin', '', '', '2021-12-09 09:09:54', '2021-12-09 09:09:54', NULL); +INSERT INTO `sys_menus` VALUES (41, '编辑api', '', 39, 1, '', '/system/api', '', '', '', 'F', '', '', '', 'system:api:edit', '0', 'admin', '', '', '2021-12-09 09:10:38', '2021-12-09 09:10:38', NULL); +INSERT INTO `sys_menus` VALUES (42, '删除api', '', 39, 1, '', '/system/api', '', '', '', 'F', '', '', '', 'system:api:delete', '0', 'admin', '', '', '2021-12-09 09:11:11', '2021-12-09 09:11:11', NULL); +INSERT INTO `sys_menus` VALUES (43, '日志系统', '', 0, 11, 'iconfont icon-biaodan', '/log', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'admin', 'panda', '', '2021-12-02 11:04:08', '2023-06-30 08:57:08', NULL); +INSERT INTO `sys_menus` VALUES (44, '告警监控', '', 0, 9, 'iconfont icon-gongju', '/tool', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'admin', 'panda', '', '2021-12-16 16:35:15', '2023-10-18 10:17:52', NULL); +INSERT INTO `sys_menus` VALUES (45, '操作日志', '', 43, 1, 'iconfont icon-bolangnengshiyanchang', '/log/operation', '/log/operation/index', '1', '', 'C', '0', '1', '1', 'log:operation:list', '0', 'admin', 'panda', '', '2021-12-16 16:42:03', '2021-12-28 13:39:44', NULL); +INSERT INTO `sys_menus` VALUES (46, '登录日志', '', 43, 2, 'iconfont icon--chaifenlie', '/log/login', '/log/login/index', '1', '', 'C', '0', '1', '1', 'log:login:list', '0', 'admin', 'panda', '', '2021-12-16 16:43:28', '2021-12-28 13:39:58', NULL); +INSERT INTO `sys_menus` VALUES (47, '服务监控', '', 44, 1, 'elementCpu', '/tool/monitor/', '/tool/monitor/index', '1', '', 'C', '0', '1', '1', 'tool:monitor:list', '0', 'admin', 'panda', '', '2021-12-03 14:12:43', '2021-12-28 13:41:25', NULL); +INSERT INTO `sys_menus` VALUES (49, '开发工具', '', 0, 10, 'iconfont icon-zhongduancanshu', '/develop', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'admin', 'panda', '', '2021-12-16 16:53:11', '2023-06-29 16:29:23', NULL); +INSERT INTO `sys_menus` VALUES (50, '表单构建', '', 49, 1, 'iconfont icon-zidingyibuju', '/develop/form', '/develop/form/index', '1', '', 'C', '0', '1', '1', 'develop:form:list', '0', 'admin', 'panda', '', '2021-12-16 16:55:01', '2022-07-12 18:56:18', NULL); +INSERT INTO `sys_menus` VALUES (51, '代码生成', '', 49, 2, 'iconfont icon-zhongduancanshu', '/develop/code', '/develop/code/index', '1', '', 'C', '0', '1', '1', 'develop:code:list', '0', 'admin', '', '', '2021-12-16 16:56:48', '2021-12-16 16:56:48', NULL); +INSERT INTO `sys_menus` VALUES (52, '系统接口', '', 49, 3, 'iconfont icon-wenducanshu-05', '/develop/apis', '/layout/routerView/iframes', '0', 'https://82200r6gti.apifox.cn', 'C', '0', '1', '1', 'develop:apis:list', '0', '', 'panda', '', '2021-12-16 16:58:07', '2023-09-04 11:02:29', NULL); +INSERT INTO `sys_menus` VALUES (54, '对象存储', '', 53, 1, 'iconfont icon-chazhaobiaodanliebiao', '/resource/file', '/resource/file/index', '1', '', 'C', '0', '1', '1', 'resource:file:list', '0', 'admin', 'panda', '', '2021-12-16 17:06:04', '2022-01-13 17:30:09', NULL); +INSERT INTO `sys_menus` VALUES (55, '公告通知', '', 44, 3, 'elementTicket', '/tool/notice', '/tool/notice/index', '1', '', 'C', '0', '1', '1', 'tool:notice:list', '0', 'admin', 'panda', '', '2021-12-16 22:09:11', '2021-12-28 13:42:39', NULL); +INSERT INTO `sys_menus` VALUES (59, '删除', '', 45, 1, '', '', '', '', '', 'F', '', '', '', 'log:operation:delete', '0', 'panda', '', '', '2022-01-14 13:28:25', '2022-01-14 13:28:25', NULL); +INSERT INTO `sys_menus` VALUES (60, '清空', '', 45, 1, '', '', '', '', '', 'F', '', '', '', 'log:operation:clean', '0', 'panda', '', '', '2022-01-14 13:29:24', '2022-01-14 13:29:24', NULL); +INSERT INTO `sys_menus` VALUES (63, '删除', '', 46, 1, '', '', '', '', '', 'F', '', '', '', 'log:login:delete', '0', 'panda', '', '', '2022-01-14 13:30:46', '2022-01-14 13:30:46', NULL); +INSERT INTO `sys_menus` VALUES (64, '清空', '', 46, 1, '', '', '', '', '', 'F', '', '', '', 'log:login:clean', '0', 'panda', '', '', '2022-01-14 13:31:06', '2022-01-14 13:31:06', NULL); +INSERT INTO `sys_menus` VALUES (69, '添加', '', 55, 1, '', '', '', '', '', 'F', '', '', '', 'tool:notice:add', '0', 'panda', '', '', '2022-01-14 13:35:23', '2022-01-14 13:35:23', NULL); +INSERT INTO `sys_menus` VALUES (70, '编辑', '', 55, 1, '', '', '', '', '', 'F', '', '', '', 'tool:notice:edit', '0', 'panda', '', '', '2022-01-14 13:36:04', '2022-01-14 13:36:04', NULL); +INSERT INTO `sys_menus` VALUES (71, '删除', '', 55, 1, '', '', '', '', '', 'F', '', '', '', 'tool:notice:delete', '0', 'panda', '', '', '2022-01-14 13:36:26', '2022-01-14 13:36:26', NULL); +INSERT INTO `sys_menus` VALUES (72, '查看', '', 55, 1, '', '', '', '', '', 'F', '', '', '', 'tool:notice:view', '0', 'panda', '', '', '2022-01-14 13:36:51', '2022-01-14 13:36:51', NULL); +INSERT INTO `sys_menus` VALUES (73, '导入', '', 51, 1, '', '', '', '', '', 'F', '', '', '', 'develop:code:add', '0', 'panda', '', '', '2022-01-14 13:38:35', '2022-01-14 13:38:35', NULL); +INSERT INTO `sys_menus` VALUES (74, '编辑', '', 51, 1, '', '', '', '', '', 'F', '', '', '', 'develop:code:edit', '0', 'panda', '', '', '2022-01-14 13:41:25', '2022-01-14 13:41:25', NULL); +INSERT INTO `sys_menus` VALUES (75, '删除', '', 51, 1, '', '', '', '', '', 'F', '', '', '', 'develop:code:delete', '0', 'panda', '', '', '2022-01-14 13:41:42', '2022-01-14 13:41:42', NULL); +INSERT INTO `sys_menus` VALUES (76, '预览', '', 51, 1, '', '', '', '', '', 'F', '', '', '', 'develop:code:view', '0', 'panda', '', '', '2022-01-14 13:42:01', '2022-01-14 13:42:01', NULL); +INSERT INTO `sys_menus` VALUES (77, '生成代码', '', 51, 1, '', '', '', '', '', 'F', '', '', '', 'develop:code:code', '0', 'panda', '', '', '2022-01-14 13:42:48', '2022-01-14 13:42:48', NULL); +INSERT INTO `sys_menus` VALUES (95, '设备管理', '', 0, 1, 'iconfont icon-dongtai', '/device', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'panda', 'panda', '', '2023-06-29 16:21:31', '2023-09-02 15:03:55', NULL); +INSERT INTO `sys_menus` VALUES (96, '规则链库', '', 0, 2, 'iconfont icon-shuxingtu', '/rule', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'panda', 'panda', '', '2023-06-29 16:33:23', '2023-09-02 15:04:06', NULL); +INSERT INTO `sys_menus` VALUES (97, '组态大屏', '', 0, 3, 'iconfont icon-diannaobangong', '/visual', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'panda', 'panda', '', '2023-06-29 16:34:50', '2023-07-21 14:41:55', NULL); +INSERT INTO `sys_menus` VALUES (98, '产品分类', '', 95, 1, 'iconfont icon-jiliandongxuanzeqi', '/device/product_category', '/device/product_category/index', '1', '', 'C', '0', '1', '1', 'product:category:list', '0', '', 'panda', '', '2023-06-29 16:44:56', '2023-06-29 16:49:55', NULL); +INSERT INTO `sys_menus` VALUES (100, '设备分组', '', 95, 3, 'iconfont icon-zidingyibuju', '/device/device_group', '/device/device_group/index', '1', '', 'C', '0', '1', '1', 'device:group:list', '0', '', 'panda', '', '2023-06-29 16:48:05', '2023-06-29 16:50:49', NULL); +INSERT INTO `sys_menus` VALUES (102, '添加', '', 98, 1, '', '', '', '', '', 'F', '', '', '', 'product:category:add', '0', 'panda', '', '', '2023-06-29 16:51:38', '2023-06-29 16:51:38', NULL); +INSERT INTO `sys_menus` VALUES (103, '修改', '', 98, 2, '', '', '', '', '', 'F', '', '', '', 'product:category:edit', '0', 'panda', '', '', '2023-06-29 16:52:00', '2023-06-29 16:52:00', NULL); +INSERT INTO `sys_menus` VALUES (104, '删除', '', 98, 3, '', '', '', '', '', 'F', '', '', '', 'product:category:delete', '0', 'panda', '', '', '2023-06-29 16:52:36', '2023-06-29 16:52:36', NULL); +INSERT INTO `sys_menus` VALUES (105, '新增', '', 100, 1, '', '', '', '', '', 'F', '', '', '', 'device:group:add', '0', 'panda', '', '', '2023-06-29 16:53:16', '2023-06-29 16:53:16', NULL); +INSERT INTO `sys_menus` VALUES (106, '修改', '', 100, 2, '', '', '', '', '', 'F', '', '', '', 'device:group:edit', '0', 'panda', '', '', '2023-06-29 16:53:37', '2023-06-29 16:53:37', NULL); +INSERT INTO `sys_menus` VALUES (107, '删除', '', 100, 3, '', '', '', '', '', 'F', '', '', '', 'device:group:delete', '0', 'panda', '', '', '2023-06-29 16:53:56', '2023-06-29 16:53:56', NULL); +INSERT INTO `sys_menus` VALUES (114, '产品管理', '', 95, 2, 'elementCpu', '/device/product', '/device/product/index', '1', '', 'C', '0', '1', '1', 'device:product:list', '0', '', 'panda', '', '2023-06-30 14:13:39', '2023-07-21 16:03:31', NULL); +INSERT INTO `sys_menus` VALUES (115, '新增产品', '', 114, 1, '', '', '', '', '', 'F', '', '', '', 'device:product:add', '0', 'admin', '', '', '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL); +INSERT INTO `sys_menus` VALUES (116, '修改产品', '', 114, 2, '', '', '', '', '', 'F', '', '', '', 'device:product:edit', '0', 'admin', '', '', '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL); +INSERT INTO `sys_menus` VALUES (117, '删除产品', '', 114, 3, '', '', '', '', '', 'F', '', '', '', 'device:product:delete', '0', 'admin', '', '', '2023-06-30 14:13:39', '2023-06-30 14:13:39', NULL); +INSERT INTO `sys_menus` VALUES (118, '设备管理', '', 95, 4, 'elementSetting', '/device/device', '/device/device/index', '1', '', 'C', '0', '1', '1', 'device:device:list', '0', '', 'panda', '', '2023-06-30 14:20:03', '2023-07-21 16:03:41', NULL); +INSERT INTO `sys_menus` VALUES (119, '修改设备', '', 118, 2, '', '', '', '', '', 'F', '', '', '', 'device:device:edit', '0', 'admin', '', '', '2023-06-30 14:20:03', '2023-06-30 14:20:03', NULL); +INSERT INTO `sys_menus` VALUES (120, '新增设备', '', 118, 1, '', '', '', '', '', 'F', '', '', '', 'device:device:add', '0', 'admin', '', '', '2023-06-30 14:20:03', '2023-06-30 14:20:03', NULL); +INSERT INTO `sys_menus` VALUES (121, '删除设备', '', 118, 3, '', '', '', '', '', 'F', '', '', '', 'device:device:delete', '0', 'admin', '', '', '2023-06-30 14:20:03', '2023-06-30 14:20:03', NULL); +INSERT INTO `sys_menus` VALUES (122, '查看', '', 114, 4, '', '', '', '', '', 'F', '', '', '', 'device:product:view', '0', 'panda', '', '', '2023-07-05 17:14:20', '2023-07-05 17:14:20', NULL); +INSERT INTO `sys_menus` VALUES (131, '查看设备', '', 118, 4, '', '', '', '', '', 'F', '', '', '', 'device:device:view', '0', 'panda', '', '', '2023-07-10 08:50:48', '2023-07-10 08:50:48', NULL); +INSERT INTO `sys_menus` VALUES (132, '规则设计', '', 96, 1, 'iconfont icon-shuxingtu', '/rule/chain', '/rule/chain/index', '1', '', 'C', '0', '1', '1', 'rule:chain:list', '0', '', 'panda', '', '2023-07-21 14:38:54', '2023-09-02 14:33:03', NULL); +INSERT INTO `sys_menus` VALUES (133, '克隆', '', 132, 1, '', '', '', '', '', 'F', '', '', '', 'rule:chain:clone', '0', '', 'panda', '', '2023-07-21 14:39:27', '2023-07-21 14:57:05', NULL); +INSERT INTO `sys_menus` VALUES (134, '设计', '', 132, 2, '', '', '', '', '', 'F', '', '', '', 'rule:chain:design', '0', '', 'panda', '', '2023-07-21 14:39:53', '2023-07-21 14:57:13', NULL); +INSERT INTO `sys_menus` VALUES (135, '预览', '', 132, 3, '', '', '', '', '', 'F', '', '', '', 'rule:chain:view', '0', '', 'panda', '', '2023-07-21 14:40:08', '2023-07-21 14:57:20', NULL); +INSERT INTO `sys_menus` VALUES (136, '修改', '', 132, 4, '', '', '', '', '', 'F', '', '', '', 'rule:chain:edit', '0', '', 'panda', '', '2023-07-21 14:40:31', '2023-07-21 14:57:26', NULL); +INSERT INTO `sys_menus` VALUES (137, '删除', '', 132, 5, '', '', '', '', '', 'F', '', '', '', 'rule:chain:delete', '0', '', 'panda', '', '2023-07-21 14:40:47', '2023-07-21 14:57:33', NULL); +INSERT INTO `sys_menus` VALUES (138, '添加', '', 132, 6, '', '', '', '', '', 'F', '', '', '', 'rule:chain:add', '0', '', 'panda', '', '2023-07-21 14:41:04', '2023-07-21 14:57:39', NULL); +INSERT INTO `sys_menus` VALUES (139, '大屏分组', '', 97, 1, 'iconfont icon-wenducanshu-05', '/visual/screen_group', '/visual/screen_group/index', '1', '', 'C', '0', '1', '1', 'screen:group:list', '0', 'panda', '', '', '2023-07-21 14:46:41', '2023-07-21 14:46:41', NULL); +INSERT INTO `sys_menus` VALUES (140, '组态大屏', '', 97, 2, 'iconfont icon-diannaobangong', '/visual/screen', '/visual/screen/index', '1', '', 'C', '0', '1', '1', 'visual:screen:list', '0', 'panda', '', '', '2023-07-21 14:47:46', '2023-07-21 14:47:46', NULL); +INSERT INTO `sys_menus` VALUES (141, '添加', '', 139, 1, '', '', '', '', '', 'F', '', '', '', 'screen:group:add', '0', 'panda', '', '', '2023-07-21 14:50:40', '2023-07-21 14:50:40', NULL); +INSERT INTO `sys_menus` VALUES (142, '编辑', '', 139, 2, '', '', '', '', '', 'F', '', '', '', 'screen:group:edit', '0', 'panda', '', '', '2023-07-21 14:50:56', '2023-07-21 14:50:56', NULL); +INSERT INTO `sys_menus` VALUES (143, '删除', '', 139, 3, '', '', '', '', '', 'F', '', '', '', ' screen:group:delete', '0', 'panda', '', '', '2023-07-21 14:51:22', '2023-07-21 14:51:22', NULL); +INSERT INTO `sys_menus` VALUES (144, '新增组态', '', 140, 1, '', '', '', '', '', 'F', '', '', '', 'visual:screen:add', '0', 'panda', '', '', '2023-07-21 14:53:26', '2023-07-21 14:53:26', NULL); +INSERT INTO `sys_menus` VALUES (145, '修改大屏', '', 140, 2, '', '', '', '', '', 'F', '', '', '', 'visual:screen:edit', '0', 'panda', '', '', '2023-07-21 14:53:50', '2023-07-21 14:53:50', NULL); +INSERT INTO `sys_menus` VALUES (146, '删除大屏', '', 140, 3, '', '', '', '', '', 'F', '', '', '', 'visual:screen:delete', '0', 'panda', '', '', '2023-07-21 14:54:14', '2023-07-21 14:54:14', NULL); +INSERT INTO `sys_menus` VALUES (147, '克隆', '', 140, 4, '', '', '', '', '', 'F', '', '', '', 'visual:screen:clone', '0', 'panda', '', '', '2023-07-21 14:54:30', '2023-07-21 14:54:30', NULL); +INSERT INTO `sys_menus` VALUES (148, '设计', '', 140, 5, '', '', '', '', '', 'F', '', '', '', 'visual:screen:design', '0', 'panda', '', '', '2023-07-21 14:54:57', '2023-07-21 14:54:57', NULL); +INSERT INTO `sys_menus` VALUES (149, '预览', '', 140, 6, '', '', '', '', '', 'F', '', '', '', 'visual:screen:view', '0', 'panda', '', '', '2023-07-21 14:55:27', '2023-07-21 14:55:27', NULL); +INSERT INTO `sys_menus` VALUES (150, '报表管理', '', 0, 4, 'iconfont icon-putong', '/report', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'panda', 'panda', '', '2023-07-24 10:12:26', '2023-07-24 10:13:54', '2023-08-18 14:01:16'); +INSERT INTO `sys_menus` VALUES (151, '报表设计', '', 150, 1, 'iconfont icon-dayin', '/report', '/layout/routerView/iframes', '0', 'http://101.35.247.125:9001/edit', 'C', '0', '1', '1', 'report:list', '0', 'panda', '', '', '2023-07-24 10:13:47', '2023-07-24 10:13:47', '2023-08-18 14:01:12'); +INSERT INTO `sys_menus` VALUES (152, '任务中心', '', 0, 5, 'iconfont icon-dayin', '/job', 'Layout', '1', '', 'M', '0', '1', '1', '', '0', 'panda', 'panda', '', '2023-08-08 14:08:11', '2023-10-05 14:03:38', NULL); +INSERT INTO `sys_menus` VALUES (153, '任务中心', '', 152, 1, 'elementAlarmClock', '/job/job', '/job/job/index', '1', '', 'C', '0', '1', '1', 'job:list', '0', '', 'panda', '', '2023-08-08 14:10:37', '2023-08-08 14:12:49', NULL); +INSERT INTO `sys_menus` VALUES (154, '任务日志', '', 152, 2, 'elementDocument', '/job/log', '/job/log/index', '1', '', 'C', '0', '1', '1', 'job:log:list', '0', 'panda', '', '', '2023-08-08 14:12:37', '2023-08-08 14:12:37', NULL); +INSERT INTO `sys_menus` VALUES (155, '新增', '', 153, 1, '', '', '', '', '', 'F', '', '', '', 'job:add', '0', 'panda', '', '', '2023-08-08 14:20:17', '2023-08-08 14:20:17', NULL); +INSERT INTO `sys_menus` VALUES (156, '编辑', '', 153, 2, '', '', '', '', '', 'F', '', '', '', 'job:edit', '0', 'panda', '', '', '2023-08-08 14:20:44', '2023-08-08 14:20:44', NULL); +INSERT INTO `sys_menus` VALUES (157, '删除', '', 153, 3, '', '', '', '', '', 'F', '', '', '', 'job:delete', '0', 'panda', '', '', '2023-08-08 14:21:03', '2023-08-08 14:21:03', NULL); +INSERT INTO `sys_menus` VALUES (158, '运行启动', '', 153, 4, '', '', '', '', '', 'F', '', '', '', 'job:run', '0', 'panda', '', '', '2023-08-08 14:21:25', '2023-08-08 14:21:25', NULL); +INSERT INTO `sys_menus` VALUES (159, '删除', '', 154, 1, '', '', '', '', '', 'F', '', '', '', 'job:log:delete', '0', 'panda', '', '', '2023-08-08 14:22:05', '2023-08-08 14:22:05', NULL); +INSERT INTO `sys_menus` VALUES (160, '清空', '', 154, 2, '', '', '', '', '', 'F', '', '', '', 'job:log:clean', '0', 'panda', '', '', '2023-08-08 14:22:33', '2023-08-08 14:22:33', NULL); +INSERT INTO `sys_menus` VALUES (161, '视频监控', '', 0, 4, 'iconfont icon-step', '/video', 'Layout', '1', '', 'M', '1', '1', '1', '', '1', 'panda', 'panda', '', '2023-09-02 13:52:17', '2023-10-24 14:05:25', NULL); +INSERT INTO `sys_menus` VALUES (164, '视频广场', '', 161, 3, 'elementGrid', '/video/splitview', '/video/splitview/index', '1', '', 'C', '0', '1', '1', 'video:splitview:list', '0', 'panda', '', '', '2023-09-02 14:00:41', '2023-09-02 14:00:41', NULL); +INSERT INTO `sys_menus` VALUES (165, '规则审计', '', 96, 2, 'iconfont icon--chaifenhang', '/rule/log', '/rule/log/index', '1', '', 'C', '0', '1', '1', 'rule:log:list', '0', 'panda', '', '', '2023-09-02 14:05:46', '2023-09-02 14:05:46', NULL); +INSERT INTO `sys_menus` VALUES (168, '设备地图', '', 95, 5, 'iconfont icon-ditu', 'device:map', '/device/map/index', '1', '', 'C', '0', '1', '1', 'device:map:list', '0', 'panda', '', '', '2023-09-02 14:14:00', '2023-09-02 14:14:00', NULL); +INSERT INTO `sys_menus` VALUES (169, '边缘管理', '', 0, 7, 'iconfont icon-wendu', '/edge', 'Layout', '1', '', 'M', '1', '1', '1', '', '1', 'panda', 'panda', '', '2023-09-02 14:27:39', '2023-10-24 14:05:34', NULL); +INSERT INTO `sys_menus` VALUES (170, '网关管理', '', 169, 1, 'iconfont icon-gouxuan-weixuanzhong-xianxingfangkuang', '/edge/gateway', '/edge/gateway/index', '1', '', 'C', '0', '1', '1', 'edge:gateway:list', '0', '', 'panda', '', '2023-09-02 14:44:13', '2023-09-19 10:20:34', NULL); +INSERT INTO `sys_menus` VALUES (171, '采集器', '', 169, 2, 'iconfont icon-wendu', '/edge/collector', '/edge/collector/index', '1', '', 'C', '0', '1', '1', 'edge:collector:list', '0', '', 'panda', '', '2023-09-02 14:45:31', '2023-09-19 10:20:57', NULL); +INSERT INTO `sys_menus` VALUES (172, '应用管理', '', 0, 8, 'iconfont icon-shoujidiannao', '/apply', 'Layout', '1', '', 'M', '1', '1', '1', '', '1', 'panda', 'panda', '', '2023-09-02 14:50:48', '2023-09-04 10:55:19', NULL); +INSERT INTO `sys_menus` VALUES (173, '应用商店', '', 172, 1, 'iconfont icon-shoujidiannao', '/apply/common', '/apply/common/index', '1', '', 'C', '0', '1', '1', 'apply:common:list', '0', 'panda', '', '', '2023-09-02 14:51:56', '2023-09-02 14:51:56', NULL); +INSERT INTO `sys_menus` VALUES (174, '我的应用', '', 172, 2, 'iconfont icon-LoggedinPC', '/apply/meapp', '/apply/meapp/index', '1', '', 'C', '0', '1', '1', 'apply:meapp:list', '0', 'panda', '', '', '2023-09-02 14:52:45', '2023-09-02 14:52:45', NULL); +INSERT INTO `sys_menus` VALUES (175, '萤石设备', '', 161, 2, 'iconfont icon-gerenzhongxin', '/video/ezviz', '/video/ezviz/index', '1', '', 'C', '0', '1', '1', 'video:ezviz:list', '0', 'panda', '', '', '2023-09-05 10:05:27', '2023-09-05 10:05:27', NULL); +INSERT INTO `sys_menus` VALUES (176, '国标设备', '', 161, 1, 'iconfont icon-wendu', '/video/gb28181', '/video/gb28181/index', '1', '', 'C', '0', '1', '1', 'video:gb28181:list', '0', 'panda', '', '', '2023-09-05 10:07:07', '2023-09-05 10:07:07', NULL); +INSERT INTO `sys_menus` VALUES (177, '分配设备', '', 118, 5, '', '', '', '', '', 'F', '', '', '', 'device:device:allot', '0', 'panda', '', '', '2023-09-15 17:32:08', '2023-09-15 17:32:08', NULL); +INSERT INTO `sys_menus` VALUES (178, '告警中心', '', 44, 1, 'iconfont icon-radio-off-full', '/tool/alarm', '/tool/alarm/index', '1', '', 'C', '0', '1', '1', 'tool:alarm:list', '0', 'panda', '', '', '2023-10-18 10:20:51', '2023-10-18 10:20:51', NULL); + +-- ---------------------------- +-- Table structure for sys_notices +-- ---------------------------- +DROP TABLE IF EXISTS `sys_notices`; +CREATE TABLE `sys_notices` ( + `notice_id` bigint(0) NOT NULL AUTO_INCREMENT, + `title` varchar(128) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '标题', + `content` text CHARACTER SET utf8 COLLATE utf8_general_ci NULL COMMENT '标题', + `notice_type` varchar(1) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL COMMENT '通知类型', + `organization_id` int(0) NULL DEFAULT NULL COMMENT '部门Id,部门及子部门', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + `user_name` varchar(64) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`notice_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 4 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Compact; + +-- ---------------------------- +-- Records of sys_notices +-- ---------------------------- +INSERT INTO `sys_notices` VALUES (1, '关于学习交流的通知', '

发布入群通知 467890197, 交流学习

', '1', 0, '2021-12-26 15:29:25', '2021-12-26 16:19:48', NULL, 'panda'); + +-- ---------------------------- +-- Table structure for sys_organizations +-- ---------------------------- +DROP TABLE IF EXISTS `sys_organizations`; +CREATE TABLE `sys_organizations` ( + `organization_id` bigint(0) NOT NULL AUTO_INCREMENT, + `parent_id` int(0) NULL DEFAULT NULL COMMENT '上级组织', + `organization_path` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织路径', + `organization_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '组织名称', + `sort` int(0) NULL DEFAULT NULL COMMENT '排序', + `leader` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '负责人', + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '手机', + `email` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '邮箱', + `status` char(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态', + `create_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '创建人', + `update_by` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '修改人', + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`organization_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_organizations +-- ---------------------------- +INSERT INTO `sys_organizations` VALUES (2, 0, '/0/2', '熊猫科技', 0, 'xm', '18353366836', '342@qq.com', '0', 'admin', 'admin', '2021-12-01 17:31:53', '2021-12-02 08:56:19', NULL); +INSERT INTO `sys_organizations` VALUES (3, 2, '/0/2/3', '研发部', 1, 'panda', '18353366543', 'ewr@qq.com', '0', 'admin', 'admin', '2021-12-01 17:37:43', '2021-12-02 08:55:56', NULL); +INSERT INTO `sys_organizations` VALUES (7, 2, '/0/2/7', '营销部', 2, 'panda', '18353333333', '342@qq.com', '0', 'panda', 'panda', '2021-12-24 10:46:24', '2021-12-24 10:47:15', NULL); + +-- ---------------------------- +-- Table structure for sys_posts +-- ---------------------------- +DROP TABLE IF EXISTS `sys_posts`; +CREATE TABLE `sys_posts` ( + `post_id` bigint(0) NOT NULL AUTO_INCREMENT, + `post_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '岗位名称', + `post_code` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '岗位代码', + `sort` int(0) NULL DEFAULT NULL COMMENT '岗位排序', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态', + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '描述', + `create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`post_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_posts +-- ---------------------------- +INSERT INTO `sys_posts` VALUES (1, '首席执行官', 'CEO', 1, '0', '首席执行官', 'panda', '', '2021-12-02 09:21:44', '2022-07-16 17:36:32', NULL); +INSERT INTO `sys_posts` VALUES (4, '首席技术执行官', 'CTO', 2, '0', '', 'panda', '', '2021-12-02 09:21:44', '2022-07-16 17:37:42', NULL); + +-- ---------------------------- +-- Table structure for sys_role_menus +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_menus`; +CREATE TABLE `sys_role_menus` ( + `id` bigint(0) NOT NULL AUTO_INCREMENT, + `role_id` int(0) NULL DEFAULT NULL, + `menu_id` int(0) NULL DEFAULT NULL, + `role_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 8253 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_menus +-- ---------------------------- +INSERT INTO `sys_role_menus` VALUES (6590, 5, 1, 'test'); +INSERT INTO `sys_role_menus` VALUES (6591, 5, 3, 'test'); +INSERT INTO `sys_role_menus` VALUES (6592, 5, 4, 'test'); +INSERT INTO `sys_role_menus` VALUES (6593, 5, 5, 'test'); +INSERT INTO `sys_role_menus` VALUES (6594, 5, 6, 'test'); +INSERT INTO `sys_role_menus` VALUES (6595, 5, 7, 'test'); +INSERT INTO `sys_role_menus` VALUES (6596, 5, 8, 'test'); +INSERT INTO `sys_role_menus` VALUES (6597, 5, 9, 'test'); +INSERT INTO `sys_role_menus` VALUES (6598, 5, 10, 'test'); +INSERT INTO `sys_role_menus` VALUES (6599, 5, 11, 'test'); +INSERT INTO `sys_role_menus` VALUES (6600, 5, 13, 'test'); +INSERT INTO `sys_role_menus` VALUES (6601, 5, 14, 'test'); +INSERT INTO `sys_role_menus` VALUES (6602, 5, 15, 'test'); +INSERT INTO `sys_role_menus` VALUES (6603, 5, 16, 'test'); +INSERT INTO `sys_role_menus` VALUES (6604, 5, 17, 'test'); +INSERT INTO `sys_role_menus` VALUES (6605, 5, 18, 'test'); +INSERT INTO `sys_role_menus` VALUES (6606, 5, 19, 'test'); +INSERT INTO `sys_role_menus` VALUES (6607, 5, 20, 'test'); +INSERT INTO `sys_role_menus` VALUES (6608, 5, 21, 'test'); +INSERT INTO `sys_role_menus` VALUES (6609, 5, 22, 'test'); +INSERT INTO `sys_role_menus` VALUES (6610, 5, 23, 'test'); +INSERT INTO `sys_role_menus` VALUES (6611, 5, 24, 'test'); +INSERT INTO `sys_role_menus` VALUES (6612, 5, 25, 'test'); +INSERT INTO `sys_role_menus` VALUES (6613, 5, 26, 'test'); +INSERT INTO `sys_role_menus` VALUES (6614, 5, 28, 'test'); +INSERT INTO `sys_role_menus` VALUES (6615, 5, 29, 'test'); +INSERT INTO `sys_role_menus` VALUES (6616, 5, 30, 'test'); +INSERT INTO `sys_role_menus` VALUES (6617, 5, 31, 'test'); +INSERT INTO `sys_role_menus` VALUES (6618, 5, 32, 'test'); +INSERT INTO `sys_role_menus` VALUES (6619, 5, 33, 'test'); +INSERT INTO `sys_role_menus` VALUES (6620, 5, 34, 'test'); +INSERT INTO `sys_role_menus` VALUES (6621, 5, 35, 'test'); +INSERT INTO `sys_role_menus` VALUES (6622, 5, 36, 'test'); +INSERT INTO `sys_role_menus` VALUES (6623, 5, 37, 'test'); +INSERT INTO `sys_role_menus` VALUES (6624, 5, 38, 'test'); +INSERT INTO `sys_role_menus` VALUES (6625, 5, 39, 'test'); +INSERT INTO `sys_role_menus` VALUES (6626, 5, 40, 'test'); +INSERT INTO `sys_role_menus` VALUES (6627, 5, 41, 'test'); +INSERT INTO `sys_role_menus` VALUES (6628, 5, 42, 'test'); +INSERT INTO `sys_role_menus` VALUES (7878, 2, 1, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7879, 2, 3, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7880, 2, 4, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7881, 2, 5, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7882, 2, 6, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7883, 2, 7, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7884, 2, 8, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7885, 2, 9, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7886, 2, 10, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7887, 2, 11, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7888, 2, 12, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7889, 2, 13, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7890, 2, 14, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7891, 2, 15, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7892, 2, 16, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7893, 2, 17, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7894, 2, 18, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7895, 2, 19, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7896, 2, 20, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7897, 2, 21, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7898, 2, 22, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7899, 2, 23, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7900, 2, 24, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7901, 2, 25, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7902, 2, 26, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7903, 2, 28, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7904, 2, 29, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7905, 2, 30, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7906, 2, 31, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7907, 2, 32, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7908, 2, 33, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7909, 2, 34, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7910, 2, 35, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7911, 2, 36, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7912, 2, 37, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7913, 2, 38, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7914, 2, 39, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7915, 2, 40, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7916, 2, 41, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7917, 2, 42, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7918, 2, 43, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7919, 2, 44, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7920, 2, 45, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7921, 2, 46, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7922, 2, 47, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7923, 2, 49, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7924, 2, 50, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7925, 2, 51, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7926, 2, 52, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7927, 2, 55, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7928, 2, 59, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7929, 2, 60, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7930, 2, 63, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7931, 2, 64, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7932, 2, 69, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7933, 2, 70, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7934, 2, 71, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7935, 2, 72, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7936, 2, 73, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7937, 2, 74, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7938, 2, 75, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7939, 2, 76, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7940, 2, 77, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7941, 2, 95, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7942, 2, 96, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7943, 2, 97, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7944, 2, 98, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7945, 2, 100, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7946, 2, 102, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7947, 2, 103, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7948, 2, 104, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7949, 2, 105, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7950, 2, 106, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7951, 2, 107, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7952, 2, 114, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7953, 2, 115, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7954, 2, 116, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7955, 2, 117, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7956, 2, 118, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7957, 2, 119, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7958, 2, 120, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7959, 2, 121, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7960, 2, 122, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7961, 2, 131, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7962, 2, 132, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7963, 2, 133, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7964, 2, 134, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7965, 2, 135, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7966, 2, 136, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7967, 2, 137, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7968, 2, 138, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7969, 2, 139, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7970, 2, 140, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7971, 2, 141, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7972, 2, 142, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7973, 2, 143, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7974, 2, 144, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7975, 2, 145, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7976, 2, 146, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7977, 2, 147, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7978, 2, 148, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7979, 2, 149, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7980, 2, 152, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7981, 2, 153, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7982, 2, 154, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7983, 2, 155, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7984, 2, 156, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7985, 2, 157, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7986, 2, 158, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7987, 2, 159, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7988, 2, 160, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7989, 2, 161, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7990, 2, 164, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7991, 2, 165, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7992, 2, 166, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7993, 2, 167, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7994, 2, 168, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7995, 2, 169, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7996, 2, 170, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7997, 2, 171, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7998, 2, 175, 'manage'); +INSERT INTO `sys_role_menus` VALUES (7999, 2, 176, 'manage'); +INSERT INTO `sys_role_menus` VALUES (8000, 2, 177, 'manage'); +INSERT INTO `sys_role_menus` VALUES (8128, 1, 1, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8129, 1, 3, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8130, 1, 4, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8131, 1, 5, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8132, 1, 6, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8133, 1, 7, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8134, 1, 8, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8135, 1, 9, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8136, 1, 10, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8137, 1, 11, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8138, 1, 12, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8139, 1, 13, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8140, 1, 14, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8141, 1, 15, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8142, 1, 16, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8143, 1, 17, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8144, 1, 18, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8145, 1, 19, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8146, 1, 20, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8147, 1, 21, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8148, 1, 22, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8149, 1, 23, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8150, 1, 24, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8151, 1, 25, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8152, 1, 26, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8153, 1, 28, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8154, 1, 29, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8155, 1, 30, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8156, 1, 31, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8157, 1, 32, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8158, 1, 33, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8159, 1, 34, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8160, 1, 35, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8161, 1, 36, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8162, 1, 37, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8163, 1, 38, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8164, 1, 39, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8165, 1, 40, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8166, 1, 41, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8167, 1, 42, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8168, 1, 43, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8169, 1, 44, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8170, 1, 45, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8171, 1, 46, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8172, 1, 47, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8173, 1, 49, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8174, 1, 50, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8175, 1, 51, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8176, 1, 52, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8177, 1, 55, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8178, 1, 59, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8179, 1, 60, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8180, 1, 63, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8181, 1, 64, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8182, 1, 69, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8183, 1, 70, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8184, 1, 71, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8185, 1, 72, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8186, 1, 73, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8187, 1, 74, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8188, 1, 75, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8189, 1, 76, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8190, 1, 77, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8191, 1, 95, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8192, 1, 96, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8193, 1, 97, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8194, 1, 98, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8195, 1, 100, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8196, 1, 102, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8197, 1, 103, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8198, 1, 104, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8199, 1, 105, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8200, 1, 106, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8201, 1, 107, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8202, 1, 114, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8203, 1, 115, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8204, 1, 116, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8205, 1, 117, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8206, 1, 118, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8207, 1, 119, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8208, 1, 120, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8209, 1, 121, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8210, 1, 122, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8211, 1, 131, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8212, 1, 132, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8213, 1, 133, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8214, 1, 134, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8215, 1, 135, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8216, 1, 136, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8217, 1, 137, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8218, 1, 138, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8219, 1, 139, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8220, 1, 140, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8221, 1, 141, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8222, 1, 142, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8223, 1, 143, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8224, 1, 144, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8225, 1, 145, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8226, 1, 146, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8227, 1, 147, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8228, 1, 148, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8229, 1, 149, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8230, 1, 152, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8231, 1, 153, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8232, 1, 154, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8233, 1, 155, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8234, 1, 156, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8235, 1, 157, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8236, 1, 158, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8237, 1, 159, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8238, 1, 160, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8239, 1, 161, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8240, 1, 164, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8241, 1, 165, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8242, 1, 168, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8243, 1, 169, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8244, 1, 170, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8245, 1, 171, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8246, 1, 172, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8247, 1, 173, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8248, 1, 174, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8249, 1, 175, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8250, 1, 176, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8251, 1, 177, 'admin'); +INSERT INTO `sys_role_menus` VALUES (8252, 1, 178, 'admin'); + +-- ---------------------------- +-- Table structure for sys_role_organizations +-- ---------------------------- +DROP TABLE IF EXISTS `sys_role_organizations`; +CREATE TABLE `sys_role_organizations` ( + `role_id` int(0) NULL DEFAULT NULL, + `organization_id` int(0) NULL DEFAULT NULL, + `id` bigint(0) NOT NULL AUTO_INCREMENT, + PRIMARY KEY (`id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 15 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_role_organizations +-- ---------------------------- +INSERT INTO `sys_role_organizations` VALUES (1, 2, 9); +INSERT INTO `sys_role_organizations` VALUES (1, 3, 10); +INSERT INTO `sys_role_organizations` VALUES (1, 7, 11); +INSERT INTO `sys_role_organizations` VALUES (2, 2, 12); +INSERT INTO `sys_role_organizations` VALUES (2, 3, 13); +INSERT INTO `sys_role_organizations` VALUES (2, 7, 14); + +-- ---------------------------- +-- Table structure for sys_roles +-- ---------------------------- +DROP TABLE IF EXISTS `sys_roles`; +CREATE TABLE `sys_roles` ( + `role_id` bigint(0) NOT NULL AUTO_INCREMENT, + `role_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色名称', + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '状态', + `role_key` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '角色代码', + `data_scope` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本部门数据权限 4:本部门及以下数据权限)', + `role_sort` int(0) NULL DEFAULT NULL COMMENT '角色排序', + `create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + PRIMARY KEY (`role_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 6 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_roles +-- ---------------------------- +INSERT INTO `sys_roles` VALUES (1, '超管理员', '0', 'admin', '4', 1, 'admin', 'panda', '超级管理', '2021-12-02 16:03:26', '2023-10-18 10:23:08', NULL); +INSERT INTO `sys_roles` VALUES (2, '管理员', '0', 'manage', '1', 2, 'panda', 'panda', '', '2021-12-19 16:06:20', '2023-10-07 11:37:58', NULL); +INSERT INTO `sys_roles` VALUES (5, '测试', '0', 'test', '0', 3, 'panda', '', '', '2023-09-14 11:34:44', '2023-09-14 11:34:44', NULL); + +-- ---------------------------- +-- Table structure for sys_users +-- ---------------------------- +DROP TABLE IF EXISTS `sys_users`; +CREATE TABLE `sys_users` ( + `user_id` bigint(0) NOT NULL AUTO_INCREMENT, + `nick_name` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `phone` varchar(11) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `role_id` int(0) NULL DEFAULT NULL, + `salt` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `avatar` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `sex` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `email` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `organization_id` int(0) NULL DEFAULT NULL, + `post_id` int(0) NULL DEFAULT NULL, + `create_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `update_by` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `remark` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `status` varchar(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `create_time` datetime(0) NULL DEFAULT NULL, + `update_time` datetime(0) NULL DEFAULT NULL, + `delete_time` datetime(0) NULL DEFAULT NULL, + `username` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `password` varchar(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL, + `role_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '多角色', + `post_ids` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '多岗位', + PRIMARY KEY (`user_id`) USING BTREE +) ENGINE = InnoDB AUTO_INCREMENT = 7 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic; + +-- ---------------------------- +-- Records of sys_users +-- ---------------------------- +INSERT INTO `sys_users` VALUES (1, 'pandax', '13818888888', 1, NULL, '', '0', '1@qq.com', 2, 4, 'panda', '1', NULL, '0', '2021-12-03 09:46:55', '2022-02-09 13:28:49', NULL, 'panda', '$2a$10$cKFFTCzGOvaIHHJY2K45Zuwt8TD6oPzYi4s5MzYIBAWCLL6ZhouP2', '1', '1,4'); +INSERT INTO `sys_users` VALUES (3, '测试用户', '18435234356', 2, '', '', '0', '342@163.com', 3, 1, 'test', '', '', '0', '2021-12-06 15:16:53', '2022-05-10 19:19:25', NULL, 'test', '$2a$10$4cHTracxWJLdhMmazvbm1urKyD3v5N2AYxAFtNYh6juU39kgae73e', '2', '1,4'); +INSERT INTO `sys_users` VALUES (4, 'panda', '18353366912', 2, '', '', '0', '2417920382@qq.com', 2, 4, 'panda', '', '', '0', '2021-12-19 15:58:09', '2021-12-19 16:06:54', NULL, 'admin', '$2a$10$cKFFTCzGOvaIHHJY2K45Zuwt8TD6oPzYi4s5MzYIBAWCLL6ZhouP2', '2', '4,1'); +INSERT INTO `sys_users` VALUES (5, 'tenant', '', 1, '', '', '0', '', 3, 1, 'panda', '1', '', '0', '2021-12-03 09:46:55', '2022-02-09 13:28:49', NULL, 'tenant', '$2a$10$ycRsRdsrNQInLB2Ib0maOetsWZ0kFctmF6ytAErWTjOx5cWdeJMcK', '1', '1,4'); + +SET FOREIGN_KEY_CHECKS = 1; diff --git a/pkg/config/app.go b/pkg/config/app.go new file mode 100644 index 0000000..bef8b38 --- /dev/null +++ b/pkg/config/app.go @@ -0,0 +1,12 @@ +package config + +import "fmt" + +type App struct { + Name string `yaml:"name"` + Version string `yaml:"version"` +} + +func (a *App) GetAppInfo() string { + return fmt.Sprintf("[%s:%s]", a.Name, a.Version) +} diff --git a/pkg/config/casbin.go b/pkg/config/casbin.go new file mode 100644 index 0000000..4b5b196 --- /dev/null +++ b/pkg/config/casbin.go @@ -0,0 +1,5 @@ +package config + +type Casbin struct { + ModelPath string `mapstructure:"model-path" json:"model-path" yaml:"model-path"` +} diff --git a/pkg/config/config.go b/pkg/config/config.go new file mode 100644 index 0000000..37d38eb --- /dev/null +++ b/pkg/config/config.go @@ -0,0 +1,62 @@ +package config + +import ( + "flag" + "fmt" + "pandax/kit/biz" + "pandax/kit/utils" + "path/filepath" +) + +func InitConfig(configFilePath string) *Config { + // 获取启动参数中,配置文件的绝对路径 + path, _ := filepath.Abs(configFilePath) + startConfigParam = &CmdConfigParam{ConfigFilePath: path} + // 读取配置文件信息 + yc := &Config{} + if err := utils.LoadYml(startConfigParam.ConfigFilePath, yc); err != nil { + panic(any(fmt.Sprintf("读取配置文件[%s]失败: %s", startConfigParam.ConfigFilePath, err.Error()))) + } + // 校验配置文件内容信息 + yc.Valid() + + return yc + +} + +// 启动配置参数 +type CmdConfigParam struct { + ConfigFilePath string // -e 配置文件路径 +} + +// 启动可执行文件时的参数 +var startConfigParam *CmdConfigParam + +// yaml配置文件映射对象 +type Config struct { + App *App `yaml:"app"` + Server *Server `yaml:"server"` + Jwt *Jwt `yaml:"jwt"` + Redis *Redis `yaml:"redis"` + Mysql *Mysql `yaml:"mysql"` + Postgresql *Postgresql `yaml:"postgresql"` + Casbin *Casbin `yaml:"casbin"` + Gen *Gen `yaml:"gen"` + Log *Log `yaml:"log"` +} + +// 配置文件内容校验 +func (c *Config) Valid() { + biz.IsTrue(c.Jwt != nil, "配置文件的[jwt]信息不能为空") + c.Jwt.Valid() +} + +// 获取执行可执行文件时,指定的启动参数 +func getStartConfig() *CmdConfigParam { + configFilePath := flag.String("e", "./config.yml", "配置文件路径,默认为可执行文件目录") + flag.Parse() + // 获取配置文件绝对路径 + path, _ := filepath.Abs(*configFilePath) + sc := &CmdConfigParam{ConfigFilePath: path} + return sc +} diff --git a/pkg/config/db.go b/pkg/config/db.go new file mode 100644 index 0000000..c2a2a26 --- /dev/null +++ b/pkg/config/db.go @@ -0,0 +1,33 @@ +package config + +import "fmt" + +type Mysql struct { + Host string `mapstructure:"host" json:"host" yaml:"host"` + Config string `mapstructure:"config" json:"config" yaml:"config"` + Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"` + Username string `mapstructure:"username" json:"username" yaml:"username"` + Password string `mapstructure:"password" json:"password" yaml:"password"` + MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` + MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` + LogMode bool `mapstructure:"log-mode" json:"logMode" yaml:"log-mode"` + LogZap string `mapstructure:"log-zap" json:"logZap" yaml:"log-zap"` +} + +func (m *Mysql) Dsn() string { + return m.Username + ":" + m.Password + "@tcp(" + m.Host + ")/" + m.Dbname + "?" + m.Config +} + +type Postgresql struct { + Host string `mapstructure:"host" json:"host" yaml:"host"` + Port int `mapstructure:"port" json:"port" yaml:"port"` + Dbname string `mapstructure:"db-name" json:"dbname" yaml:"db-name"` + Username string `mapstructure:"username" json:"username" yaml:"username"` + Password string `mapstructure:"password" json:"password" yaml:"password"` + MaxIdleConns int `mapstructure:"max-idle-conns" json:"maxIdleConns" yaml:"max-idle-conns"` + MaxOpenConns int `mapstructure:"max-open-conns" json:"maxOpenConns" yaml:"max-open-conns"` +} + +func (m *Postgresql) PgDsn() string { + return fmt.Sprintf("host=%s port=%d user=%s password=%s dbname=%s sslmode=disable", m.Host, m.Port, m.Username, m.Password, m.Dbname) +} diff --git a/pkg/config/gen.go b/pkg/config/gen.go new file mode 100644 index 0000000..fc6468c --- /dev/null +++ b/pkg/config/gen.go @@ -0,0 +1,11 @@ +package config + +/** + * @Description + * @Author Panda + * @Date 2021/12/31 15:13 + **/ +type Gen struct { + Dbname string `mapstructure:"dbname" json:"dbname" yaml:"dbname"` + Frontpath string `mapstructure:"frontpath" json:"frontpath" yaml:"frontpath"` +} diff --git a/pkg/config/jwt.go b/pkg/config/jwt.go new file mode 100644 index 0000000..f47c5d6 --- /dev/null +++ b/pkg/config/jwt.go @@ -0,0 +1,15 @@ +package config + +import ( + "pandax/kit/biz" +) + +type Jwt struct { + Key string `yaml:"key"` + ExpireTime int64 `yaml:"expire-time"` // 过期时间,单位分钟 +} + +func (j *Jwt) Valid() { + biz.IsTrue(j.Key != "", "config.yml之 [jwt.key] 不能为空") + biz.IsTrue(j.ExpireTime != 0, "config.yml之 [jwt.expire-time] 不能为空") +} diff --git a/pkg/config/log.go b/pkg/config/log.go new file mode 100644 index 0000000..e2de254 --- /dev/null +++ b/pkg/config/log.go @@ -0,0 +1,33 @@ +package config + +import "path" + +type Log struct { + Level string `yaml:"level"` + File *LogFile `yaml:"file"` +} + +type LogFile struct { + Name string `yaml:"name"` + Path string `yaml:"path"` +} + +// 获取完整路径文件名 +func (l *LogFile) GetFilename() string { + var filepath, filename string + if l == nil { + return "" + } + if fp := l.Path; fp == "" { + filepath = "./" + } else { + filepath = fp + } + if fn := l.Name; fn == "" { + filename = "default.log" + } else { + filename = fn + } + + return path.Join(filepath, filename) +} diff --git a/pkg/config/mqtt.go b/pkg/config/mqtt.go new file mode 100644 index 0000000..95b6703 --- /dev/null +++ b/pkg/config/mqtt.go @@ -0,0 +1,9 @@ +package config + +type Mqtt struct { + Broker string `mapstructure:"broker" json:"broker" yaml:"broker"` + HttpBroker string `mapstructure:"httpBroker" json:"httpBroker" yaml:"httpBroker"` + Qos int `mapstructure:"qos" json:"qos" yaml:"qos"` + Username string `mapstructure:"username" json:"username" yaml:"username"` + Password string `mapstructure:"password" json:"password" yaml:"password"` +} diff --git a/pkg/config/oss.go b/pkg/config/oss.go new file mode 100644 index 0000000..80e3615 --- /dev/null +++ b/pkg/config/oss.go @@ -0,0 +1,9 @@ +package config + +type Oss struct { + Endpoint string `yaml:"endpoint"` + AccessKey string `yaml:"accessKey"` + SecretKey string `yaml:"secretKey"` + BucketName string `yaml:"bucketName"` + UseSSL bool `yaml:"useSSL"` +} diff --git a/pkg/config/queue.go b/pkg/config/queue.go new file mode 100644 index 0000000..6103c66 --- /dev/null +++ b/pkg/config/queue.go @@ -0,0 +1,7 @@ +package config + +type Queue struct { + QueuePool int64 `yaml:"queue-pool"` //消息队列池 + TaskNum int64 `yaml:"task-num"` //任务队列数 + ChNum int64 `yaml:"ch-num"` //并发数 +} diff --git a/pkg/config/redis.go b/pkg/config/redis.go new file mode 100644 index 0000000..10316a4 --- /dev/null +++ b/pkg/config/redis.go @@ -0,0 +1,8 @@ +package config + +type Redis struct { + Host string `yaml:"host"` + Port int `yaml:"port"` + Password string `yaml:"password"` + Db int `yaml:"db"` +} diff --git a/pkg/config/server.go b/pkg/config/server.go new file mode 100644 index 0000000..0bdc20c --- /dev/null +++ b/pkg/config/server.go @@ -0,0 +1,27 @@ +package config + +import "fmt" + +type Server struct { + Port int `yaml:"port"` + Cors bool `yaml:"cors"` + Rate *Rate `yaml:"rate"` + DbType string `yaml:"db-type"` + ExcelDir string `yaml:"excel-dir"` + Tls *Tls `yaml:"tls"` +} + +func (s *Server) GetPort() string { + return fmt.Sprintf(":%d", s.Port) +} + +type Tls struct { + Enable bool `yaml:"enable"` // 是否启用tls + KeyFile string `yaml:"key-file"` // 私钥文件路径 + CertFile string `yaml:"cert-file"` // 证书文件路径 +} + +type Rate struct { + Enable bool `yaml:"enable"` // 是否限流 + RateNum float64 `yaml:"rate-num"` // 限流数量 +} diff --git a/pkg/config/taos.go b/pkg/config/taos.go new file mode 100644 index 0000000..4e1b63e --- /dev/null +++ b/pkg/config/taos.go @@ -0,0 +1,9 @@ +package config + +type Taos struct { + Host string `yaml:"host"` // 服务器地址:端口 + Username string `yaml:"username"` // 数据库用户名 + Password string `yaml:"password"` // 数据库密码 + Database string `yaml:"database"` + Config string `yaml:"config"` +} diff --git a/pkg/config/ys.go b/pkg/config/ys.go new file mode 100644 index 0000000..0b3c62e --- /dev/null +++ b/pkg/config/ys.go @@ -0,0 +1,6 @@ +package config + +type Ys struct { + AppKey string `yaml:"appKey"` + Secret string `yaml:"secret"` +} diff --git a/pkg/global/global.go b/pkg/global/global.go new file mode 100644 index 0000000..36b80c5 --- /dev/null +++ b/pkg/global/global.go @@ -0,0 +1,14 @@ +package global + +import ( + "pandax/pkg/config" + + "github.com/sirupsen/logrus" + "gorm.io/gorm" +) + +var ( + Log *logrus.Logger // 日志 + Db *gorm.DB // gorm + Conf *config.Config +) diff --git a/pkg/global/model/auth_model.go b/pkg/global/model/auth_model.go new file mode 100644 index 0000000..7572b26 --- /dev/null +++ b/pkg/global/model/auth_model.go @@ -0,0 +1,42 @@ +package model + +import ( + "encoding/base64" + "errors" + "math/rand" + "pandax/apps/system/entity" + "pandax/apps/system/services" + "strings" + "time" + + "gorm.io/gorm" +) + +func OrgAuthSet(tx *gorm.DB, roleId int64, owner string) error { + if roleId == 0 { + return errors.New("角色Id不能为空") + } + role, err := services.SysRoleModelDao.FindOrganizationsByRoleId(roleId) + if err != nil { + return err + } + if role.DataScope != entity.SELFDATASCOPE { + if len(role.Org) <= 0 { + return errors.New("该角色下未分配组织权限") + } + tx.Where("org_id in (?)", strings.Split(role.Org, ",")) + } else { + tx.Where("owner = ?", owner) + } + + return nil +} +func GenerateID() string { + rand.Seed(time.Now().UnixNano()) + id := make([]byte, 7) // 由于base64编码会增加字符数,这里使用7个字节生成10位ID + _, err := rand.Read(id) + if err != nil { + panic(err) // 错误处理,根据实际情况进行处理 + } + return base64.URLEncoding.EncodeToString(id)[:10] +} diff --git a/pkg/global/model/global_model.go b/pkg/global/model/global_model.go new file mode 100644 index 0000000..a0cde06 --- /dev/null +++ b/pkg/global/model/global_model.go @@ -0,0 +1,21 @@ +package model + +import ( + "time" +) + +type BaseModel struct { + Id string `json:"id" gorm:"primary_key;"` + CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"create_time"` + UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"update_time"` +} + +type BaseAuthModel struct { + Id string `json:"id" gorm:"primary_key;"` + Owner string `json:"owner" gorm:"type:varchar(64);comment:创建者,所有者"` + OrgId int64 `json:"orgId" gorm:"type:int;comment:机构ID"` + CreatedAt time.Time `gorm:"column:create_time" json:"createTime" form:"create_time"` + UpdatedAt time.Time `gorm:"column:update_time" json:"updateTime" form:"update_time"` + + RoleId int64 `gorm:"-"` // 角色数据权限 +} diff --git a/pkg/http_client/client.go b/pkg/http_client/client.go new file mode 100644 index 0000000..0a0ab8a --- /dev/null +++ b/pkg/http_client/client.go @@ -0,0 +1,416 @@ +package http_client + +import ( + "bufio" + "crypto/tls" + "fmt" + "io" + "net/http" + "net/url" + "os" + "strings" + "time" +) + +// The Client type represents a client with a request, cookie, and result. +// @property Request - The `Request` property is a pointer to a `Request` struct. This struct likely +// contains information about the client's HTTP request, such as the method (GET, POST, etc.), URL, +// headers, and body. +// @property Cookie - The `Cookie` property is a pointer to an `http.Cookie` object. This object +// represents an HTTP cookie that is sent by the client to the server with each request. It contains +// information such as the name, value, expiration time, and other attributes of the cookie. The +// `Cookie` property +// @property {Result} Result - The `Result` property is a variable that stores the result of a client's +// request. It could be any data type that represents the response or outcome of the request, such as a +// string, struct, or custom data type. +type Client struct { + Request *Request + Cookie []*http.Cookie + Response Respone +} + +// The Request type represents a HTTP request with various properties such as URL, method, data, +// headers, timeout, and proxy settings. +// @property {string} Url - The `Url` property is a string that represents the URL of the request. It +// specifies the location of the resource that the request is being made to. +// @property {string} Method - The HTTP method to be used for the request (e.g., GET, POST, PUT, +// DELETE, etc.). +// @property Data - The `Data` property is of type `io.Reader` and is used to provide the data to be +// sent in the request body. It can be any type that implements the `io.Reader` interface, such as a +// `bytes.Buffer`, `strings.Reader`, or a file reader. +// @property {string} ContentType - The ContentType property is used to specify the type of data being +// sent in the request body. It is typically used in conjunction with the Data property to set the +// content type header of the request. Common content types include "application/json", +// "application/xml", "application/x-www-form-urlencoded", etc. +// @property {string} Authorization - The "Authorization" property is used to specify the +// authentication credentials for the request. It is typically used to include a token or +// username/password combination to authenticate the request with the server. +// @property {string} UserAgent - The UserAgent property is used to specify the user agent string that +// will be sent in the HTTP request header. The user agent string identifies the client software and +// version that is making the request. It is often used by servers to determine how to handle the +// request or to provide customized content based on the client +// @property Header - The `Header` property is a map that contains additional headers to be included in +// the HTTP request. Each key-value pair in the map represents a header field and its corresponding +// value. These headers can be used to provide additional information or instructions to the server +// handling the request. +// @property Timeout - The Timeout property is of type time.Duration and represents the maximum amount +// of time allowed for the request to complete. It specifies the duration after which the request will +// be canceled if it hasn't completed. +// @property ProxyUrl - The `ProxyUrl` property is of type `url.URL` and represents the URL of the +// proxy server to be used for the request. The type `url.URL` is a struct that contains various +// properties such as `Scheme`, `Host`, `Path`, etc. In this case, the ` +type Request struct { + Url string `json:"url"` + Method string `json:"method"` + Data io.Reader `json:"-"` + ContentType string `json:"content_type"` + Authorization string `json:"authorization"` + UserAgent string `json:"user_agent"` + Headers map[string]string `json:"headers"` + // The proxy type is determined by the URL scheme. "http", + // "https", and "socks5" are supported. If the scheme is empty, + // + // If Proxy is nil or nil *URL, no proxy is used. + ProxyUrl url.URL `json:"proxy"` + + client http.Client + MaxIdleConns int `json:"max_idle_conns"` + MaxIdleConnsPerHost int `json:"max_idle_conns_per_host"` + IdleConnTimeout time.Duration `json:"idle_conn_timeout"` + + // The `StreamCallBack` field is a function that takes a pointer to an `io.ReadCloser` and returns an + // error. This function is used to handle streaming responses from the server. The `io.ReadCloser` is + // a type that represents a readable and closable stream of data. By providing a callback function, + // the client can specify how to handle the streamed response data. + StreamCallBack func(line *[]byte) error +} + +var DefaultClient = NewRequest() + +// The function creates a new HTTP client request with default values. +func NewRequest() *Client { + return &Client{Request: &Request{Method: "GET", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 HttpClient", MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 15 * time.Second}, Response: Respone{}} +} + +// The `Do()` function is a method of the `Client` struct. It is used to execute the HTTP request +// specified in the `Client` object and return the result. +func (c *Client) Do() (*Client, error) { + if !strings.Contains(c.Request.Url, "://") { + c.Request.Url = "http://" + c.Request.Url + } + request, err := http.NewRequest(c.Request.Method, c.Request.Url, c.Request.Data) + if err != nil { + fmt.Println(err) + return c, err + } + if c.Request.ContentType != "" { + request.Header.Set("Content-Type", c.Request.ContentType) + } + request.Header.Set("Referer", c.Request.Url) + if c.Request.Authorization != "" { + request.Header.Set("Authorization", c.Request.Authorization) + } + if c.Request.UserAgent != "" { + request.Header.Set("User-Agent", c.Request.UserAgent) + } + for _, v := range c.Cookie { + request.AddCookie(v) + } + for k, v := range c.Request.Headers { + request.Header.Set(k, v) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + MaxIdleConns: c.Request.MaxIdleConns, + MaxIdleConnsPerHost: c.Request.MaxIdleConnsPerHost, + IdleConnTimeout: c.Request.IdleConnTimeout, + } + if c.Request.ProxyUrl.String() != "" { + transport.Proxy = http.ProxyURL(&c.Request.ProxyUrl) + } + c.Request.client.Transport = transport + + res, err := c.Request.client.Do(request) + if err != nil { + fmt.Println(err) + return c, err + } + for _, v := range res.Cookies() { + c.AddCookie(v) + } + defer res.Body.Close() + c.Response.StatusCode = res.StatusCode + c.Response.Method = res.Request.Method + c.Response.URL = res.Request.URL.String() + c.Response.Path = res.Request.URL.Path + c.Response.Header = res.Header + c.Response.Proto = res.Proto + c.Response.ProtoMajor = res.ProtoMajor + c.Response.ContentLength = res.ContentLength + c.Response.Host = res.Request.Host + c.Response.RemoteAddr = res.Request.RemoteAddr + if c.Request.StreamCallBack != nil { + reader := bufio.NewReader(res.Body) + for { + line, err := reader.ReadBytes('\n') + if err != nil && err != io.EOF { + fmt.Println(err) + continue + } + if len(line) > 0 { + c.Request.StreamCallBack(&line) + } + if err == io.EOF { + break + } + } + } + c.Response.Body, err = io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return c, err + } + return c, nil +} + +// Get sets the HTTP request method to "GET" and returns the client instance. +// +// No parameters. +// Returns the client instance. +func (c *Client) Get() *Client { + c.Request.Method = "GET" + return c +} + +// The `Post()` function is a method of the `Client` struct. It sets the HTTP request method to "POST" +// and returns the client instance. This method is used to specify that the client should make a POST +// request instead of a GET request. +func (c *Client) Post() *Client { + c.Request.Method = "POST" + return c +} + +// The `Put()` function is a method of the `Client` struct. It sets the HTTP request method to "PUT" +// and returns the client instance. This method is used to specify that the client should make a PUT +// request instead of a GET or POST request. +func (c *Client) Put() *Client { + c.Request.Method = "PUT" + return c +} + +// The `Delete()` function is a method of the `Client` struct. It sets the HTTP request method to +// "DELETE" and returns the client instance. This method is used to specify that the client should make +// a DELETE request instead of a GET or POST request. +func (c *Client) Delete() *Client { + c.Request.Method = "DELETE" + return c +} + +// The `SetUrl` function is a method of the `Client` struct. It is used to set the URL of the HTTP +// request. +func (c *Client) SetUrl(url ...any) *Client { + c.Request.Url = fmt.Sprintf(url[0].(string), url[1:]...) + return c +} + +// The `SetMethod` function is a method of the `Client` struct. It is used to set the HTTP request +// method for the client. It takes a `method` parameter, which is a string representing the desired +// HTTP method (e.g., "GET", "POST", "PUT", "DELETE", etc.). +func (c *Client) SetMethod(method string) *Client { + c.Request.Method = method + return c +} + +// The `SetContentType` function is a method of the `Client` struct. It is used to set the content type +// of the HTTP request. +func (c *Client) SetContentType(contentType string) *Client { + c.Request.ContentType = contentType + return c +} + +// The `SetBody` function is a method of the `Client` struct. It is used to set the body of the HTTP +// request. The `body` parameter is of type `io.Reader`, which means it can accept any type that +// implements the `io.Reader` interface, such as a `bytes.Buffer`, `strings.Reader`, or a file reader. +func (c *Client) SetBody(body io.Reader) *Client { + c.Request.Data = body + return c +} + +func (c *Client) SetStreamCallback(call func(line *[]byte) error) *Client { + if call != nil { + c.Request.StreamCallBack = call + } + return c +} + +// The `SetAuthorization` function is a method of the `Client` struct. It is used to set the +// authorization credentials for the HTTP request. The `credentials` parameter is a string that +// represents the authentication credentials, such as a token or username/password combination. +func (c *Client) SetAuthorization(credentials string) *Client { + if credentials != "" { + c.Request.Authorization = credentials + } + return c +} + +// The above code is defining a method called "AddHeader" for a struct type "Client". This method takes +// two parameters, "key" and "value", both of type string. It adds a header to the client by setting +// the key-value pair in the headers map of the client. The method returns a pointer to the client, +// allowing for method chaining. +func (c *Client) AddHeader(key, value string) *Client { + if c.Request.Headers == nil { + c.Request.Headers = make(map[string]string) + } + c.Request.Headers[key] = value + return c +} + +// The above code is defining a method called "AddCookie" for a struct type "Client" in the Go +// programming language. This method takes a pointer to an http.Cookie object as a parameter and +// returns a pointer to a Client object. The purpose of this method is to add a cookie to the client's +// cookie jar. +func (c *Client) AddCookie(cookie *http.Cookie) *Client { + c.Cookie = append(c.Cookie, cookie) + return c +} + +// The above code is defining a method called "SetProxy" for a struct type "Client" in the Go +// programming language. This method takes a parameter "proxyUrl" of type "url.URL" and returns a +// pointer to a "Client" object. The purpose of this method is to set the proxy URL for the client. +func (c *Client) SetProxy(proxyUrl url.URL) *Client { + c.Request.ProxyUrl = proxyUrl + return c +} + +// The above code is defining a method called `SetUserAgent` for a struct type `Client`. This method +// takes a string parameter `userAgent` and returns a pointer to the `Client` struct. The purpose of +// this method is to set the user agent for the client. +func (c *Client) SetUserAgent(userAgent string) *Client { + c.Request.UserAgent = userAgent + return c +} + +// The above code is defining a method called `SetBasicAuth` for a struct type `Client`. This method +// takes two parameters, `username` and `password`, both of type string. The method does not return +// anything (`*Client` is the receiver type). The purpose of this method is to set the basic +// authentication credentials (username and password) for the client. +func (c *Client) SetBasicAuth(username, password string) *Client { + c.Request.Authorization = "Basic " + username + ":" + password + return c +} + +// The above code is defining a method called "SetDigestAuth" for a struct type "Client". This method +// takes two parameters, "username" and "password", both of type string. The method does not return +// anything. +func (c *Client) SetDigestAuth(username, password string) *Client { + c.Request.Authorization = "Digest " + username + ":" + password + return c +} + +// The above code is defining a method called `SetBearerAuth` for a struct type `Client`. This method +// takes a string parameter `token` and returns a pointer to the `Client` struct. The purpose of this +// method is to set the `Authorization` header of the HTTP requests made by the client to use the +// provided `token` as a bearer token. +func (c *Client) SetBearerAuth(token string) *Client { + c.Request.Authorization = "Bearer " + token + return c +} + +// The `SetTimeout` function is a method of the `Client` struct. It is used to set the timeout duration +// for the HTTP request. The `timeout` parameter is of type `time.Duration`, which represents a +// duration of time. +func (c *Client) SetTimeout(timeout time.Duration) *Client { + c.Request.client.Timeout = timeout + return c +} + +// SetMaxIdleConns sets the maximum number of idle connections in the connection pool for the Client. +// +// maxIdleConns: The maximum number of idle connections. +// Returns: A pointer to the Client object. +func (c *Client) SetMaxIdleConns(maxIdleConns int) *Client { + c.Request.MaxIdleConns = maxIdleConns + return c +} + +// SetMaxIdleConnsPerHost sets the maximum number of idle connections per host for the client. +// +// maxIdleConnsPerHost: The maximum number of idle connections per host (int). +// Returns: The client itself (pointer to Client). +func (c *Client) SetMaxIdleConnsPerHost(maxIdleConnsPerHost int) *Client { + c.Request.MaxIdleConnsPerHost = maxIdleConnsPerHost + return c +} + +// SetIdleConnTimeout sets the idle connection timeout for the Client. +// +// idleConnTimeout: The duration after which idle connections are closed. +// Return: The updated Client object. +func (c *Client) SetIdleConnTimeout(idleConnTimeout time.Duration) *Client { + c.Request.IdleConnTimeout = idleConnTimeout + return c +} + +// The `GetStatusCode()` function is a method of the `Client` struct. It returns the HTTP status code +// of the response received from the server. It retrieves the status code from the `Result` property of +// the `Client` object and returns it as an integer. + +func (c *Client) GetStatusCode() int { + return c.Response.StatusCode +} + +// The above code is defining a method called "GetHeaders" for a struct type "Client". This method +// returns a map of strings to slices of strings. +func (c *Client) GetHeaders() map[string][]string { + return c.Response.Header +} + +// The above code is defining a method called GetHeader on a struct type called Client. This method +// takes a string parameter called key and returns a string. +func (c *Client) GetHeader(key string, index int) string { + return c.Response.Header[key][index] +} + +// The above code is defining a method called "GetCookie" for a struct type "Client". This method takes +// a string parameter called "key" and returns a string. +func (c *Client) GetCookie(key string) string { + for _, v := range c.Cookie { + if v.Name == key { + return v.Value + } + } + return "" +} + +// The `GetCookies()` function is a method of the `Client` struct. It returns a slice of pointers to +// `http.Cookie` objects. It retrieves the cookies from the `Cookie` property of the `Client` object +// and returns them. +func (c *Client) GetCookies() []*http.Cookie { + return c.Cookie +} + +// The `GetBody()` function is a method of the `Client` struct. It returns the response body of the +// HTTP request as a byte array. It retrieves the body from the `Result` property of the `Client` +// object and returns it. +func (c *Client) GetBody() []byte { + return c.Response.Body +} + +// The `GetBodyString()` function is a method of the `Client` struct. It returns the response body of +// the HTTP request as a string. It retrieves the body from the `Result` property of the `Client` +// object and converts it to a string using the `string()` function. +func (c *Client) GetBodyString() string { + return string(c.Response.Body) +} + +// The `SaveToFile` function is a method of the `Client` struct. It is used to save the response body +// of the HTTP request to a file on the local filesystem. +func (c *Client) SaveToFile(filepath string) (err error) { + // Write the body to file + err = os.WriteFile(filepath, c.GetBody(), os.ModePerm) + if err != nil { + return err + } + return nil +} diff --git a/pkg/http_client/client_test.go b/pkg/http_client/client_test.go new file mode 100644 index 0000000..db311c3 --- /dev/null +++ b/pkg/http_client/client_test.go @@ -0,0 +1,11 @@ +package http_client + +import ( + "fmt" + "testing" +) + +func TestEcho(t *testing.T) { + req, err := NewRequest().SetContentType("application/octet-stream").SetUrl("https://gitcode.net/to/vlan/-/raw/master/packages.sha1").Do() + fmt.Println(req.GetBodyString(), err) +} diff --git a/pkg/http_client/respone.go b/pkg/http_client/respone.go new file mode 100644 index 0000000..c11d573 --- /dev/null +++ b/pkg/http_client/respone.go @@ -0,0 +1,122 @@ +package http_client + +type Respone struct { + // Status specifies the HTTP status code + // returned by the server. + // + // For client requests, these fields are ignored. The + // HTTP client code always uses either StatusCode + // or Status. + StatusCode int `json:"status_code"` + // Method specifies the HTTP method (GET, POST, PUT, etc.). + // For client requests, an empty string means GET. + // + // Go's HTTP client does not support sending a request with + // the CONNECT method. See the documentation on Transport for + // details. + Method string `json:"method"` + + // URL specifies either the URI being requested (for server + // requests) or the URL to access (for client requests). + // + // For server requests, the URL is parsed from the URI + // supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be + // empty. (See RFC 7230, Section 5.3) + // + // For client requests, the URL's Host specifies the server to + // connect to, while the Request's Host field optionally + // specifies the Host header value to send in the HTTP + // request. + URL string `json:"url"` + + Path string // path (relative paths may omit leading slash) + + // The protocol version for incoming server requests. + // + // For client requests, these fields are ignored. The HTTP + // client code always uses either HTTP/1.1 or HTTP/2. + // See the docs on Transport for details. + Proto string `json:"proto"` // "HTTP/1.0" + ProtoMajor int `json:"proto_major"` // 1 + ProtoMinor int `json:"proro_minor"` // 0 + + // Header contains the request header fields either received + // by the server or to be sent by the client. + // + // If a server received a request with header lines, + // + // Host: example.com + // accept-encoding: gzip, deflate + // Accept-Language: en-us + // fOO: Bar + // foo: two + // + // then + // + // Header = map[string][]string{ + // "Accept-Encoding": {"gzip, deflate"}, + // "Accept-Language": {"en-us"}, + // "Foo": {"Bar", "two"}, + // } + // + // For incoming requests, the Host header is promoted to the + // Request.Host field and removed from the Header map. + // + // HTTP defines that header names are case-insensitive. The + // request parser implements this by using CanonicalHeaderKey, + // making the first character and any characters following a + // hyphen uppercase and the rest lowercase. + // + // For client requests, certain headers such as Content-Length + // and Connection are automatically written when needed and + // values in Header may be ignored. See the documentation + // for the Request.Write method. + Header map[string][]string `json:"headers"` + + // Body is the request's body. + // + // For client requests, a nil body means the request has no + // body, such as a GET request. + + Body []byte `json:"body"` + + // ContentLength records the length of the associated content. + // The value -1 indicates that the length is unknown. + // Values >= 0 indicate that the given number of bytes may + // be read from Body. + // + // For client requests, a value of 0 with a non-nil Body is + // also treated as unknown. + ContentLength int64 `json:"content_length"` + + // For server requests, Host specifies the host on which the + // URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this + // is either the value of the "Host" header or the host name + // given in the URL itself. For HTTP/2, it is the value of the + // ":authority" pseudo-header field. + // It may be of the form "host:port". For international domain + // names, Host may be in Punycode or Unicode form. Use + // golang.org/x/net/idna to convert it to either format if + // needed. + // To prevent DNS rebinding attacks, server Handlers should + // validate that the Host header has a value for which the + // Handler considers itself authoritative. The included + // ServeMux supports patterns registered to particular host + // names and thus protects its registered Handlers. + // + // For client requests, Host optionally overrides the Host + // header to send. If empty, the Request.Write method uses + // the value of URL.Host. Host may contain an international + // domain name. + Host string `json:"host"` + + // RemoteAddr allows HTTP servers and other software to record + // the network address that sent the request, usually for + // logging. This field is not filled in by ReadRequest and + // has no defined format. The HTTP server in this package + // sets RemoteAddr to an "IP:port" address before invoking a + // handler. + // This field is ignored by the HTTP client. + RemoteAddr string `json:"remote_addr"` +} diff --git a/pkg/initialize/router.go b/pkg/initialize/router.go new file mode 100644 index 0000000..36cd437 --- /dev/null +++ b/pkg/initialize/router.go @@ -0,0 +1,96 @@ +package initialize + +import ( + "pandax/kit/restfulx" + "pandax/pkg/global" + "pandax/pkg/transport" + "strings" + + "github.com/emicklei/go-restful/v3" + + devRouter "pandax/apps/develop/router" + "pandax/apps/script" + sysRouter "pandax/apps/system/router" + "pandax/pkg/middleware" +) + +func InitRouter() *transport.HttpServer { + // server配置 + serverConfig := global.Conf.Server + server := transport.NewHttpServer(serverConfig.GetPort()) + + container := server.Container + // 防止XSS + container.Filter(middleware.EscapeHTML) + // 是否允许跨域 + if serverConfig.Cors { + container.Filter(middleware.Cors(container).Filter) + container.Filter(container.OPTIONSFilter) + } + // 流量限制 + if serverConfig.Rate.Enable { + container.Filter(middleware.Rate) + } + //设置静态页面 + { + webservice := new(restful.WebService) + webservice.Route(webservice.GET("/").To(callFunctionFilter)) + webservice.Route(webservice.PUT("/").To(callFunctionFilter)) + webservice.Route(webservice.POST("/").To(callFunctionFilter)) + webservice.Route(webservice.DELETE("/").To(callFunctionFilter)) + webservice.Route(webservice.GET("/{subpath:*}").To(callFunctionFilter)) + webservice.Route(webservice.PUT("/{subpath:*}").To(callFunctionFilter)) + webservice.Route(webservice.POST("/{subpath:*}").To(callFunctionFilter)) + webservice.Route(webservice.DELETE("/{subpath:*}").To(callFunctionFilter)) + container.Add(webservice) + } + // 设置路由组 + { + sysRouter.InitSystemRouter(container) + sysRouter.InitOrganizationRouter(container) + sysRouter.InitConfigRouter(container) + sysRouter.InitApiRouter(container) + sysRouter.InitDictRouter(container) + sysRouter.InitMenuRouter(container) + sysRouter.InitRoleRouter(container) + sysRouter.InitPostRouter(container) + sysRouter.InitUserRouter(container) + sysRouter.InitNoticeRouter(container) + } + // 代码生成 + { + devRouter.InitGenTableRouter(container) + devRouter.InitGenRouter(container) + } + + return server +} + +// func indexHandler(request *restful.Request, response *restful.Response) { +// // 返回 index.html 文件 +// http.ServeFile(response.ResponseWriter, request.Request, "static/index.html") +// } + +// func // The `staticResourceHandler` function is responsible for serving static resources, such as CSS, +// // JavaScript, and image files. It takes the subpath of the requested resource as a parameter and +// // constructs the complete path to the static resource file. Then, it uses the `http.ServeFile` +// // function to serve the file to the client. +// staticResourceHandler(request *restful.Request, response *restful.Response) { +// // 获取静态资源的子路径 +// subpath := request.PathParameter("subpath") + +// // 构建静态资源的完整路径 +// resourcePath := "static/" + subpath + +// // 读取静态资源文件 +// http.ServeFile(response.ResponseWriter, request.Request, resourcePath) +// } + +func callFunctionFilter(request *restful.Request, response *restful.Response) { + s := &script.ScriptApi{} + if strings.HasPrefix(request.Request.URL.Path, "/open") { + restfulx.NewReqCtx(request, response).WithNeedToken(false).WithNeedCasbin(false).NoAppend().WithLog("执行脚本").Handle(s.Call) + return + } + restfulx.NewReqCtx(request, response).NoAppend().WithLog("执行脚本").Handle(s.Call) +} diff --git a/pkg/middleware/cors.go b/pkg/middleware/cors.go new file mode 100644 index 0000000..24cf4d9 --- /dev/null +++ b/pkg/middleware/cors.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "github.com/emicklei/go-restful/v3" +) + +// 处理跨域请求,支持options访问 +func Cors(wsContainer *restful.Container) restful.CrossOriginResourceSharing { + cors := restful.CrossOriginResourceSharing{ + ExposeHeaders: []string{"Content-Length", "Access-Control-Allow-Origin", "Access-Control-Allow-Headers", "Content-Type"}, + AllowedHeaders: []string{"Content-Type", "AccessToken", "X-CSRF-Token", "Authorization", "Token", "X-Token", "X-User-Id"}, + AllowedMethods: []string{"POST", "GET", "OPTIONS", "DELETE", "PUT"}, + CookiesAllowed: false, + Container: wsContainer} + + return cors +} diff --git a/pkg/middleware/escape_html.go b/pkg/middleware/escape_html.go new file mode 100644 index 0000000..52a0a19 --- /dev/null +++ b/pkg/middleware/escape_html.go @@ -0,0 +1,17 @@ +package middleware + +import ( + "github.com/emicklei/go-restful/v3" + "html" +) + +// 防止XSS攻击 +func EscapeHTML(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + // 获取请求参数中的HTML标签 + for _, p := range req.Request.URL.Query() { + escaped := html.EscapeString(p[0]) + // 将转义后的参数重新设置到请求参数中 + req.Request.URL.Query().Set(p[0], escaped) + } + chain.ProcessFilter(req, resp) +} diff --git a/pkg/middleware/log.go b/pkg/middleware/log.go new file mode 100644 index 0000000..0743f6f --- /dev/null +++ b/pkg/middleware/log.go @@ -0,0 +1,71 @@ +package middleware + +import ( + "encoding/json" + "fmt" + "pandax/kit/biz" + "pandax/kit/logger" + "pandax/kit/restfulx" + "pandax/kit/utils" + "reflect" + "runtime/debug" + + "github.com/sirupsen/logrus" +) + +func LogHandler(rc *restfulx.ReqCtx) error { + li := rc.LogInfo + if li == nil { + return nil + } + + lfs := logrus.Fields{} + if la := rc.LoginAccount; la != nil { + lfs["uid"] = la.UserId + lfs["uname"] = la.UserName + } + + req := rc.Request.Request + lfs[req.Method] = req.URL.Path + + if err := rc.Err; err != nil { + logger.Log.WithFields(lfs).Error(getErrMsg(rc, err)) + return nil + } + logger.Log.WithFields(lfs).Info(getLogMsg(rc)) + return nil +} + +func getLogMsg(rc *restfulx.ReqCtx) string { + msg := rc.LogInfo.Description + fmt.Sprintf(" ->%dms", rc.Timed) + if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) { + rb, _ := json.Marshal(rc.ReqParam) + msg = msg + fmt.Sprintf("\n--> %s", string(rb)) + } + + // 返回结果不为空,则记录返回结果 + if rc.LogInfo.LogResp && !utils.IsBlank(reflect.ValueOf(rc.ResData)) { + respB, _ := json.Marshal(rc.ResData) + msg = msg + fmt.Sprintf("\n<-- %s", string(respB)) + } + return msg +} + +func getErrMsg(rc *restfulx.ReqCtx, err any) string { + msg := rc.LogInfo.Description + if !utils.IsBlank(reflect.ValueOf(rc.ReqParam)) { + rb, _ := json.Marshal(rc.ReqParam) + msg = msg + fmt.Sprintf("\n--> %s", string(rb)) + } + + var errMsg string + switch t := err.(type) { + case *biz.BizError: + errMsg = fmt.Sprintf("\n<-e errCode: %d, errMsg: %s", t.Code(), t.Error()) + case error: + errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t.Error(), string(debug.Stack())) + case string: + errMsg = fmt.Sprintf("\n<-e errMsg: %s\n%s", t, string(debug.Stack())) + } + return (msg + errMsg) +} diff --git a/pkg/middleware/oper.go b/pkg/middleware/oper.go new file mode 100644 index 0000000..8f20af8 --- /dev/null +++ b/pkg/middleware/oper.go @@ -0,0 +1,44 @@ +package middleware + +import ( + "net/http" + "pandax/apps/log/entity" + "pandax/apps/log/services" + "pandax/kit/restfulx" + "pandax/kit/utils" + "strings" +) + +func OperationHandler(rc *restfulx.ReqCtx) error { + c := rc.Request + // 请求操作不做记录 + if c.Request.Method == http.MethodGet || rc.LoginAccount == nil { + return nil + } + if rc.RequiredPermission == nil || !rc.RequiredPermission.NeedToken { + return nil + } + go func() { + oper := entity.LogOper{ + Title: rc.LogInfo.Description, + BusinessType: "0", + Method: c.Request.Method, + OperName: rc.LoginAccount.UserName, + OperUrl: c.Request.URL.Path, + OperIp: c.Request.RemoteAddr, + OperLocation: utils.GetRealAddressByIP(strings.Split(c.Request.RemoteAddr, ":")[0]), + OperParam: "", + Status: "0", + } + if c.Request.Method == "POST" { + oper.BusinessType = "1" + } else if c.Request.Method == "PUT" { + oper.BusinessType = "2" + } else if c.Request.Method == "DELETE" { + oper.BusinessType = "3" + } + services.LogOperModelDao.Insert(oper) + }() + + return nil +} diff --git a/pkg/middleware/permission.go b/pkg/middleware/permission.go new file mode 100644 index 0000000..fd784d5 --- /dev/null +++ b/pkg/middleware/permission.go @@ -0,0 +1,47 @@ +package middleware + +import ( + "pandax/kit/biz" + "pandax/kit/casbin" + "pandax/kit/restfulx" + "pandax/kit/token" + "pandax/pkg/global" + + "github.com/dgrijalva/jwt-go" +) + +func PermissionHandler(rc *restfulx.ReqCtx) error { + permission := rc.RequiredPermission + // 如果需要的权限信息不为空,并且不需要token,则不返回错误,继续后续逻辑 + if permission != nil && !permission.NeedToken { + return nil + } + tokenStr := rc.Request.Request.Header.Get("X-TOKEN") + // header不存在则从查询参数token中获取 + if tokenStr == "" { + tokenStr = rc.Request.QueryParameter("token") + } + if tokenStr == "" { + return biz.PermissionErr + } + j := token.NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + loginAccount, err := j.ParseToken(tokenStr) + if err != nil || loginAccount == nil { + return biz.PermissionErr + } + rc.LoginAccount = loginAccount + + if !permission.NeedCasbin { + return nil + } + + ca := casbin.CasbinService{ModelPath: global.Conf.Casbin.ModelPath} + e := ca.GetCasbinEnforcer() + // 判断策略中是否存在 + success, err := e.Enforce(loginAccount.RoleKey, rc.Request.Request.URL.Path, rc.Request.Request.Method) + if !success || err != nil { + return biz.CasbinErr + } + + return nil +} diff --git a/pkg/middleware/rate.go b/pkg/middleware/rate.go new file mode 100644 index 0000000..075827f --- /dev/null +++ b/pkg/middleware/rate.go @@ -0,0 +1,25 @@ +package middleware + +import ( + "github.com/didip/tollbooth" + "github.com/emicklei/go-restful/v3" + "pandax/pkg/global" +) + +/** + * @Description 添加qq群467890197 交流学习 + * @Author 熊猫 + * @Date 2022/1/19 8:28 + **/ + +//Rate 限流中间件 +func Rate(req *restful.Request, resp *restful.Response, chain *restful.FilterChain) { + lmt := tollbooth.NewLimiter(global.Conf.Server.Rate.RateNum, nil) + lmt.SetMessage("已经超出接口请求限制,稍后再试.") + httpError := tollbooth.LimitByRequest(lmt, resp, req.Request) + if httpError != nil { + resp.WriteErrorString(httpError.StatusCode, httpError.Message) + return + } + chain.ProcessFilter(req, resp) +} diff --git a/pkg/tool/base.go b/pkg/tool/base.go new file mode 100644 index 0000000..2288db6 --- /dev/null +++ b/pkg/tool/base.go @@ -0,0 +1,75 @@ +package tool + +import ( + "reflect" + "regexp" + "strings" +) + +func ToCamelCase(s string) string { + re := regexp.MustCompile(`[_\W]+`) + words := re.Split(s, -1) + for i, word := range words { + if i != 0 { + words[i] = strings.Title(word) + } + } + return strings.Join(words, "") +} + +func RegexpKey(str string) []string { + re := regexp.MustCompile(`${([^}]+)}`) + matches := re.FindAllStringSubmatch(str, -1) + results := make([]string, len(matches)) + for i, match := range matches { + if len(match) >= 2 { + results[i] = match[1] + } + } + return results +} + +func RegexpGetSql(str string) string { + re := regexp.MustCompile(`${([^}]+)}`) + return re.ReplaceAllString(str, "?") +} + +func GetStructKeys(obj interface{}) []string { + typ := reflect.TypeOf(obj) + keys := make([]string, typ.NumField()) + for i := 0; i < typ.NumField(); i++ { + keys[i] = typ.Field(i).Name + } + return keys +} + +func GetMapKeys(obj map[string]interface{}) []string { + keys := make([]string, len(obj)) + i := 0 + for key := range obj { + keys[i] = key + i++ + } + return keys +} + +func CheckInterfaceIsArray(data interface{}) (bool, []map[string]interface{}) { + if data == nil { + return false, nil + } + valueType := reflect.TypeOf(data) + if valueType.Kind() == reflect.Slice || valueType.Kind() == reflect.Array { + var maps []map[string]interface{} + for _, item := range data.([]interface{}) { + if m, ok := item.(map[string]interface{}); ok { + maps = append(maps, m) + } + } + return true, maps + } + return false, nil +} + +func GetInterfaceType(v interface{}) string { + return reflect.TypeOf(v).String() +} diff --git a/pkg/tool/base_test.go b/pkg/tool/base_test.go new file mode 100644 index 0000000..7240b84 --- /dev/null +++ b/pkg/tool/base_test.go @@ -0,0 +1,21 @@ +package tool + +import ( + "pandax/pkg/global/model" + "testing" +) + +func TestToCamelCase(t *testing.T) { + camelCase := ToCamelCase("hello_world") + t.Log(camelCase) +} + +func TestGenerateID(t *testing.T) { + id := model.GenerateID() + t.Log(id) +} + +func TestGetInterfaceType(t *testing.T) { + id := GetInterfaceType(`{"aa": 23}`) + t.Log(id) +} diff --git a/pkg/tool/conv.go b/pkg/tool/conv.go new file mode 100644 index 0000000..ca42b93 --- /dev/null +++ b/pkg/tool/conv.go @@ -0,0 +1,153 @@ +package tool + +import ( + "encoding/json" + "pandax/pkg/global" + "reflect" + "strings" + "time" +) + +// SnakeString snake string, XxYy to xx_yy , XxYY to xx_y_y +func SnakeString(s string) string { + data := make([]byte, 0, len(s)*2) + j := false + num := len(s) + for i := 0; i < num; i++ { + d := s[i] + if i > 0 && d >= 'A' && d <= 'Z' && j { + data = append(data, '_') + } + if d != '_' { + j = true + } + data = append(data, d) + } + return strings.ToLower(string(data)) +} + +// CamelString camel string, xx_yy to XxYy +func CamelString(s string) string { + data := make([]byte, 0, len(s)) + flag, num := true, len(s)-1 + for i := 0; i <= num; i++ { + d := s[i] + if d == '_' { + flag = true + continue + } else if flag { + if d >= 'a' && d <= 'z' { + d = d - 32 + } + flag = false + } + data = append(data, d) + } + return string(data) +} + +func convertString(s string, firstLower bool) string { + data := make([]byte, 0, len(s)) + flag, num := true, len(s)-1 + for i := 0; i <= num; i++ { + d := s[i] + if d == '_' { + flag = true + continue + } else if flag { + if d >= 'a' && d <= 'z' { + d = d - 32 + } + flag = false + } + data = append(data, d) + } + if firstLower && len(data) > 0 && data[0] >= 'A' && data[0] <= 'Z' { + data[0] = data[0] + 32 + } + return string(data) +} + +// FirstLowCamelString first low camel string, xx_yy to xxYy +func FirstLowCamelString(s string) string { + return convertString(s, true) +} + +// StructToMap convert struct to map +func StructToMap(s interface{}) map[string]interface{} { + result := make(map[string]interface{}) + + value := reflect.ValueOf(s) + typ := reflect.TypeOf(s) + + for i := 0; i < value.NumField(); i++ { + field := typ.Field(i) + fieldValue := value.Field(i).Interface() + result[field.Name] = fieldValue + } + + return result +} + +// MapToStruct convert map to struct +func MapToStruct(m map[string]interface{}, s interface{}) error { + data, err := json.Marshal(m) + if err != nil { + return err + } + + err = json.Unmarshal(data, s) + if err != nil { + return err + } + + return nil +} + +// InterfaceToStruct convert interface to struct +func InterfaceToStruct(m interface{}, s interface{}) error { + data, err := json.Marshal(m) + if err != nil { + return err + } + + err = json.Unmarshal(data, s) + if err != nil { + return err + } + + return nil +} + +// StringToStruct convert string to struct +func StringToStruct(m string, s interface{}) error { + err := json.Unmarshal([]byte(m), s) + if err != nil { + return err + } + return nil +} + +// TimeToFormat convert time to formatted string +func TimeToFormat(val interface{}) string { + switch v := val.(type) { + case int64: + // 如果是时间戳类型,将其转换为时间对象 + t := time.Unix(v, 0) + // 格式化时间字符串 + formattedTime := t.Format("2006-01-02 15:04:05") + return formattedTime + case string: + // 如果是字符串类型,将其解析为时间对象 + t, err := time.Parse("2006-01-02 15:04:05", v) + if err != nil { + global.Log.Error("时间格式非标准格式") + return "" + } + // 格式化时间字符串 + formattedTime := t.Format("2006-01-02 15:04:05") + return formattedTime + default: + return "" + } +} diff --git a/pkg/tool/excel.go b/pkg/tool/excel.go new file mode 100644 index 0000000..7a23602 --- /dev/null +++ b/pkg/tool/excel.go @@ -0,0 +1,91 @@ +package tool + +import ( + "fmt" + + "github.com/xuri/excelize/v2" +) + +// 读取数据表 +func ReadExcel(filename string) ([]string, []map[string]interface{}) { + ret := make([]map[string]interface{}, 0) + f, err := excelize.OpenFile(filename) + if err != nil { + fmt.Println("读取excel文件出错", err.Error()) + return nil, ret + } + sheets := f.GetSheetMap() + sheet1 := sheets[1] + rows, _ := f.GetRows(sheet1) + cols := make([]string, 0) + isHead := true + for _, row := range rows { + if isHead { //取得第一行的所有数据---execel表头 + if len(row) == 0 { + continue + } + for _, colCell := range row { + cols = append(cols, colCell) + } + isHead = false + } else { + theRow := map[string]interface{}{} + for j, colCell := range row { + k := cols[j] + theRow[k] = colCell + } + ret = append(ret, theRow) + } + } + return cols, ret +} + +/* +func ReadExcelByFilter(filename string, data entity.DataSetDataReq) ([]string, []map[string]interface{}) { + dataDs := make([]string, 0) + for _, ds := range data.DataDs { + dataDs = append(dataDs, ds.Value) + } + + ret := make([]map[string]interface{}, 0) + f, err := excelize.OpenFile(filename) + if err != nil { + fmt.Println("读取excel文件出错", err.Error()) + return nil, ret + } + sheets := f.GetSheetMap() + sheet1 := sheets[1] + rows, err := f.GetRows(sheet1) + cols := make([]string, 0) + colsIndex := make([]int, 0) + isHead := true + count := 0 + for _, row := range rows { + if data.ShowNumType == "2" { + if count == int(data.ShowNum) { + break + } + } + if isHead { //取得第一行的所有数据---execel表头 + if len(row) == 0 { + continue + } + for i, colCell := range row { + cols = append(cols, colCell) + colsIndex = append(colsIndex, i) + } + isHead = false + } else { + theRow := map[string]interface{}{} + for j, colCell := range row { + k := cols[j] + theRow[k] = colCell + + } + ret = append(ret, theRow) + } + count++ + } + return cols, ret +} +*/ diff --git a/pkg/transport/http_server.go b/pkg/transport/http_server.go new file mode 100644 index 0000000..5adb5bb --- /dev/null +++ b/pkg/transport/http_server.go @@ -0,0 +1,68 @@ +package transport + +import ( + "context" + "net/http" + "pandax/kit/logger" + "pandax/pkg/global" + + "github.com/emicklei/go-restful/v3" +) + +type HttpServer struct { + Addr string + srv *http.Server + + Container *restful.Container +} + +func NewHttpServer(addr string) *HttpServer { + c := restful.NewContainer() + c.EnableContentEncoding(true) + restful.TraceLogger(&httpLog{}) + restful.SetLogger(&httpLog{}) + return &HttpServer{ + Addr: addr, + Container: c, + srv: &http.Server{ + Addr: addr, + Handler: c, + }, + } +} + +func (s *HttpServer) Type() Type { + return TypeHTTP +} + +func (s *HttpServer) Start(ctx context.Context) error { + global.Log.Infof("HTTP Server listen: %s", s.Addr) + go func() { + if global.Conf.Server.Tls.Enable { + global.Log.Infof("HTTPS Server listen: %s", s.Addr) + if err := s.srv.ListenAndServeTLS(global.Conf.Server.Tls.CertFile, global.Conf.Server.Tls.KeyFile); err != nil { + global.Log.Errorf("error http serve: %s", err) + } + } else { + global.Log.Infof("HTTP Server listen: %s", s.Addr) + if err := s.srv.ListenAndServe(); err != nil { + global.Log.Errorf("error http serve: %s", err) + } + } + }() + return nil +} + +func (s *HttpServer) Stop(ctx context.Context) error { + return s.srv.Shutdown(ctx) +} + +type httpLog struct{} + +func (t *httpLog) Print(v ...any) { + logger.Log.Debug(v...) +} + +func (t *httpLog) Printf(format string, v ...any) { + logger.Log.Debugf(format, v...) +} diff --git a/pkg/transport/transport.go b/pkg/transport/transport.go new file mode 100644 index 0000000..7693fed --- /dev/null +++ b/pkg/transport/transport.go @@ -0,0 +1,19 @@ +package transport + +import ( + "context" +) + +type Type string + +const ( + TypeHTTP Type = "HTTP" + TypeGRPC Type = "GRPC" +) + +// Server is transport server. +type Server interface { + Type() Type + Start(context.Context) error + Stop(context.Context) error +} diff --git a/pkg/xscript/buffer/base58.go b/pkg/xscript/buffer/base58.go new file mode 100644 index 0000000..0841df0 --- /dev/null +++ b/pkg/xscript/buffer/base58.go @@ -0,0 +1,144 @@ +package buffer + +import ( + "errors" + "strings" +) + +var ErrDecodingB58 = errors.New("base58 decoding error") + +const b58chars = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz" + +var b58DecodeMap map[byte]int + +func init() { + length := len(b58chars) + b58DecodeMap = make(map[byte]int) + for i := 0; i < length; i++ { + b58DecodeMap[b58chars[i]] = i + } +} + +// B58Encode takes a byte array and returns a string of encoded data +func B58Encode(inData []byte) string { + var outData strings.Builder + + length := len(inData) + chunkCount := uint32(length / 4) + var dataIndex uint32 + + for i := uint32(0); i < chunkCount; i++ { + var decnum, remainder uint32 + decnum = uint32(inData[dataIndex])<<24 | uint32(inData[dataIndex+1])<<16 | + uint32(inData[dataIndex+2])<<8 | uint32(inData[dataIndex+3]) + if decnum == 0 { + outData.WriteByte(b58chars[0]) + } else { + for decnum > 0 { + remainder = decnum % 58 + outData.WriteByte(b58chars[remainder]) + decnum /= 58 + } + } + dataIndex += 4 + } + + extraBytes := length % 4 + if extraBytes != 0 { + lastChunk := uint32(0) + for i := length - extraBytes; i < length; i++ { + lastChunk <<= 8 + lastChunk |= uint32(inData[i]) + } + + // Pad extra bytes with zeroes + for i := (4 - extraBytes); i > 0; i-- { + lastChunk <<= 8 + } + if lastChunk == 0 { + outData.WriteByte(b58chars[0]) + } else { + for lastChunk > 0 { + remainder := lastChunk % 58 + outData.WriteByte(b58chars[remainder]) + lastChunk /= 58 + } + } + } + return outData.String() +} + +// B58Decode takes in a string of encoded data and returns a byte array of decoded data and an error +// code. The data is considered valid only if the error code is nil. Whitespace is ignored during +// decoding. +func B58Decode(inData string) ([]byte, error) { + length := uint32(len(inData)) + outData := make([]byte, length) + var accumulator, outCount uint32 + var inIndex uint32 + chunkCount := length / 4 + for chunk := uint32(0); chunk < chunkCount; chunk++ { + accumulator = 0 + for i := 0; i < 4; i++ { + switch inData[inIndex] { + case 32, 10, 11, 13: + // Ignore whitespace + i-- + inIndex++ + continue + } + value, ok := b58DecodeMap[inData[inIndex]] + if !ok { + return outData, ErrDecodingB58 + } + accumulator = (accumulator * 58) + uint32(value) + inIndex++ + } + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outData[outCount+2] = byte((accumulator >> 8) & 255) + outData[outCount+3] = byte(accumulator & 255) + outCount += 4 + } + + remainder := length % 4 + if remainder > 0 { + accumulator = 0 + for i := uint32(0); i < 4; i++ { + var value int + if i < remainder { + switch inData[inIndex] { + case 32, 10, 11, 13: + // Ignore whitespace + i-- + inIndex++ + continue + } + var ok bool + value, ok = b58DecodeMap[inData[inIndex]] + if !ok { + return outData, ErrDecodingB58 + } + } else { + value = 0 + } + accumulator = (accumulator * 58) + uint32(value) + inIndex++ + } + switch remainder { + case 3: + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outData[outCount+2] = byte((accumulator >> 8) & 255) + outCount += 3 + case 2: + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outCount += 2 + case 1: + outData[outCount] = byte(accumulator >> 24) + outCount++ + } + } + return outData[:outCount], nil +} diff --git a/pkg/xscript/buffer/base85.go b/pkg/xscript/buffer/base85.go new file mode 100644 index 0000000..c27a326 --- /dev/null +++ b/pkg/xscript/buffer/base85.go @@ -0,0 +1,151 @@ +package buffer + +// This package implements the RFC 1924 Base 85 algorithm + +import ( + "errors" + "fmt" + "strings" +) + +var ErrDecodingB85 = errors.New("base85 decoding error") + +const b85chars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZ" + + "abcdefghijklmnopqrstuvwxyz!#$%&()*+-;<=>?@^_`{|}~" + +var decodeMap map[byte]int + +func init() { + length := len(b85chars) + decodeMap = make(map[byte]int) + for i := 0; i < length; i++ { + decodeMap[b85chars[i]] = i + } +} + +// B85Encode takes a byte array and returns a string of encoded data +func B85Encode(inData []byte) string { + var outData strings.Builder + + length := len(inData) + chunkCount := uint32(length / 4) + var dataIndex uint32 + + for i := uint32(0); i < chunkCount; i++ { + var decnum, remainder uint32 + decnum = uint32(inData[dataIndex])<<24 | uint32(inData[dataIndex+1])<<16 | + uint32(inData[dataIndex+2])<<8 | uint32(inData[dataIndex+3]) + outData.WriteByte(b85chars[decnum/52200625]) + remainder = decnum % 52200625 + outData.WriteByte(b85chars[remainder/614125]) + remainder %= 614125 + outData.WriteByte(b85chars[remainder/7225]) + remainder %= 7225 + outData.WriteByte(b85chars[remainder/85]) + outData.WriteByte(b85chars[remainder%85]) + dataIndex += 4 + } + + extraBytes := length % 4 + if extraBytes != 0 { + lastChunk := uint32(0) + for i := length - extraBytes; i < length; i++ { + lastChunk <<= 8 + lastChunk |= uint32(inData[i]) + } + + // Pad extra bytes with zeroes + for i := (4 - extraBytes); i > 0; i-- { + lastChunk <<= 8 + } + outData.WriteByte(b85chars[lastChunk/52200625]) + remainder := lastChunk % 52200625 + outData.WriteByte(b85chars[remainder/614125]) + if extraBytes > 1 { + remainder %= 614125 + outData.WriteByte(b85chars[remainder/7225]) + if extraBytes > 2 { + remainder %= 7225 + outData.WriteByte(b85chars[remainder/85]) + } + } + } + return outData.String() +} + +// B85Decode takes in a string of encoded data and returns a byte array of decoded data and an error +// code. The data is considered valid only if the error code is nil. Whitespace is ignored during +// decoding. +func B85Decode(inData string) ([]byte, error) { + + length := uint32(len(inData)) + outData := make([]byte, length) + var accumulator, outCount uint32 + var inIndex uint32 + chunkCount := length / 5 + for chunk := uint32(0); chunk < chunkCount; chunk++ { + accumulator = 0 + for i := 0; i < 5; i++ { + switch inData[inIndex] { + case 32, 10, 11, 13: + // Ignore whitespace + i-- + inIndex++ + continue + } + value, ok := decodeMap[inData[inIndex]] + if !ok { + return outData, fmt.Errorf("bad value %v in data", inData[inIndex]) + } + accumulator = (accumulator * 85) + uint32(value) + inIndex++ + } + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outData[outCount+2] = byte((accumulator >> 8) & 255) + outData[outCount+3] = byte(accumulator & 255) + outCount += 4 + } + + remainder := length % 5 + if remainder > 0 { + accumulator = 0 + for i := uint32(0); i < 5; i++ { + var value int + if i < remainder { + switch inData[inIndex] { + case 32, 10, 11, 13: + // Ignore whitespace + i-- + inIndex++ + continue + } + var ok bool + value, ok = decodeMap[inData[inIndex]] + if !ok { + return outData, fmt.Errorf("bad value %v in data", inData[inIndex]) + } + } else { + value = 126 + } + accumulator = (accumulator * 85) + uint32(value) + inIndex++ + } + switch remainder { + case 4: + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outData[outCount+2] = byte((accumulator >> 8) & 255) + outCount += 3 + case 3: + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outCount += 2 + case 2: + outData[outCount] = byte(accumulator >> 24) + outData[outCount+1] = byte((accumulator >> 16) & 255) + outCount++ + } + } + return outData[:outCount], nil +} diff --git a/pkg/xscript/buffer/bits.go b/pkg/xscript/buffer/bits.go new file mode 100644 index 0000000..2833f50 --- /dev/null +++ b/pkg/xscript/buffer/bits.go @@ -0,0 +1,151 @@ +package buffer + +import ( + "bytes" + "encoding/base64" + "encoding/binary" + "encoding/hex" + "encoding/json" + "fmt" + + "pandax/pkg/xscript/engine" +) + +type T struct { + Bytes []byte + Runtime *engine.Runtime +} + +func (t *T) ToString() string { + return string(t.Bytes) +} + +func (t *T) ToObject() engine.Value { + var data map[string]interface{} + err := json.Unmarshal(t.Bytes, &data) + if err != nil { + fmt.Println("JSON syntax error:", err) + return nil + } + return t.Runtime.ToValue(data) +} + +func (t *T) ToHex() engine.Value { + return Format(t.Runtime, hex.EncodeToString(t.Bytes)) +} + +func (t *T) FromHex() engine.Value { + decoded, err := hex.DecodeString(string(t.Bytes)) + if err != nil { + return t.Runtime.ToValue(err.Error()) + } + return Format(t.Runtime, decoded) +} + +func (t *T) getBlock(call *engine.FunctionCall) engine.Value { + start, end := int(call.Argument(0).ToInteger()), call.Argument(1).ToInteger() + if len(call.Arguments) < 2 { + return Format(t.Runtime, t.Bytes[start:]) + } + return Format(t.Runtime, t.Bytes[start:end]) +} + +func (t *T) FromBase58() engine.Value { + decoded, err := B58Decode(string(t.Bytes)) + + if err != nil { + return t.Runtime.ToValue(err.Error()) + } + return Format(t.Runtime, decoded) +} + +func (t *T) ToBase58() engine.Value { + return Format(t.Runtime, B58Encode(t.Bytes)) +} + +func (t *T) FromBase64() engine.Value { + decoded, err := base64.StdEncoding.DecodeString(string(t.Bytes)) + if err != nil { + return t.Runtime.ToValue(err.Error()) + } + return Format(t.Runtime, decoded) +} + +func (t *T) ToBase64() engine.Value { + return Format(t.Runtime, base64.StdEncoding.EncodeToString(t.Bytes)) +} + +func (t *T) FromBase85() engine.Value { + decoded, err := B85Decode(string(t.Bytes)) + + if err != nil { + return t.Runtime.ToValue(err.Error()) + } + return Format(t.Runtime, decoded) +} + +func (t *T) ToBase85() engine.Value { + return Format(t.Runtime, B85Encode(t.Bytes)) +} + +func Format(vm *engine.Runtime, data interface{}) engine.Value { + t := &T{Runtime: vm} + switch d := data.(type) { + case []byte: + t.Bytes = d + case string: + t.Bytes = []byte(d) + case int: + t.Bytes = make([]byte, 4) + binary.BigEndian.PutUint32(t.Bytes, uint32(d)) + case int64: + t.Bytes = make([]byte, 8) + binary.BigEndian.PutUint64(t.Bytes, uint64(d)) + case map[string]interface{}: + buf := &bytes.Buffer{} + encoder := json.NewEncoder(buf) + encoder.Encode(d) + t.Bytes = buf.Bytes() + } + subObj := t.Runtime.NewObject() + subObj.Set("toBytes", t.Bytes) + subObj.Set("Bytes", t.Bytes) + subObj.Set("toObject", t.ToObject) + subObj.Set("Object", t.ToObject) + subObj.Set("toString", t.ToString) + subObj.Set("String", t.ToString) + subObj.Set("getBlock", t.getBlock) + subObj.Set("toHEX", map[string]interface{}{ + "encoding": t.ToHex, + "decoding": t.FromHex, + }) + subObj.Set("HEX", map[string]interface{}{ + "encoding": t.ToHex, + "decoding": t.FromHex, + }) + subObj.Set("Base58", map[string]interface{}{ + "encoding": t.ToBase58, + "decoding": t.FromBase58, + }) + subObj.Set("toBase58", map[string]interface{}{ + "encoding": t.ToBase58, + "decoding": t.FromBase58, + }) + subObj.Set("Base64", map[string]interface{}{ + "encoding": t.ToBase64, + "decoding": t.FromBase64, + }) + subObj.Set("toBase64", map[string]interface{}{ + "encoding": t.ToBase64, + "decoding": t.FromBase64, + }) + subObj.Set("Base85", map[string]interface{}{ + "encoding": t.ToBase85, + "decoding": t.FromBase85, + }) + subObj.Set("toBase85", map[string]interface{}{ + "encoding": t.ToBase85, + "decoding": t.FromBase85, + }) + return subObj +} diff --git a/pkg/xscript/buffer/buffer.go b/pkg/xscript/buffer/buffer.go new file mode 100644 index 0000000..0f8d17c --- /dev/null +++ b/pkg/xscript/buffer/buffer.go @@ -0,0 +1,446 @@ +package buffer + +import ( + "bytes" + "encoding/base64" + "encoding/hex" + "reflect" + "strconv" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" + "pandax/pkg/xscript/require" + + "golang.org/x/text/encoding/unicode" +) + +const ModuleName = "buffer" + +type Buffer struct { + r *engine.Runtime + + bufferCtorObj *engine.Object + + uint8ArrayCtorObj *engine.Object + uint8ArrayCtor engine.Constructor +} + +var ( + symApi = engine.NewSymbol("api") +) + +var ( + reflectTypeArrayBuffer = reflect.TypeOf(engine.ArrayBuffer{}) + reflectTypeString = reflect.TypeOf("") + reflectTypeInt = reflect.TypeOf(int64(0)) + reflectTypeFloat = reflect.TypeOf(0.0) + reflectTypeBytes = reflect.TypeOf(([]byte)(nil)) +) + +func Enable(runtime *engine.Runtime) { + runtime.Set("Buffer", require.Require(runtime, ModuleName).ToObject(runtime).Get("Buffer")) +} + +func Bytes(r *engine.Runtime, v engine.Value) []byte { + var b []byte + err := r.ExportTo(v, &b) + if err != nil { + return []byte(v.String()) + } + return b +} + +func mod(r *engine.Runtime) *engine.Object { + res := r.Get("Buffer") + if res == nil { + res = require.Require(r, ModuleName).ToObject(r).Get("Buffer") + } + m, ok := res.(*engine.Object) + if !ok { + panic(r.NewTypeError("Could not extract Buffer")) + } + return m +} + +func api(mod *engine.Object) *Buffer { + if s := mod.GetSymbol(symApi); s != nil { + b, _ := s.Export().(*Buffer) + return b + } + + return nil +} + +func GetApi(r *engine.Runtime) *Buffer { + return api(mod(r)) +} + +func DecodeBytes(r *engine.Runtime, arg, enc engine.Value) []byte { + switch arg.ExportType() { + case reflectTypeArrayBuffer: + return arg.Export().(engine.ArrayBuffer).Bytes() + case reflectTypeString: + var codec StringCodec + if !engine.IsUndefined(enc) { + codec = stringCodecs[enc.String()] + } + if codec == nil { + codec = utf8Codec + } + return codec.DecodeAppend(arg.String(), nil) + default: + if o, ok := arg.(*engine.Object); ok { + if o.ExportType() == reflectTypeBytes { + return o.Export().([]byte) + } + } + } + panic(errors.NewTypeError(r, errors.ErrCodeInvalidArgType, "The \"data\" argument must be of type string or an instance of Buffer, TypedArray, or DataView.")) +} + +func WrapBytes(r *engine.Runtime, data []byte) *engine.Object { + m := mod(r) + if api := api(m); api != nil { + return api.WrapBytes(data) + } + if from, ok := engine.AssertFunction(m.Get("from")); ok { + ab := r.NewArrayBuffer(data) + v, err := from(m, r.ToValue(ab)) + if err != nil { + panic(err) + } + return v.ToObject(r) + } + panic(r.NewTypeError("Buffer.from is not a function")) +} + +// EncodeBytes returns the given byte slice encoded as string with the given encoding. If encoding +// is not specified or not supported, returns a Buffer that wraps the data. +func EncodeBytes(r *engine.Runtime, data []byte, enc engine.Value) engine.Value { + var codec StringCodec + if !engine.IsUndefined(enc) { + codec = StringCodecByName(enc.String()) + } + if codec != nil { + return r.ToValue(codec.Encode(data)) + } + return WrapBytes(r, data) +} + +func (b *Buffer) WrapBytes(data []byte) *engine.Object { + return b.fromBytes(data) +} + +func (b *Buffer) ctor(call engine.ConstructorCall) (res *engine.Object) { + arg := call.Argument(0) + switch arg.ExportType() { + case reflectTypeInt, reflectTypeFloat: + panic(b.r.NewTypeError("Calling the Buffer constructor with numeric argument is not implemented yet")) + // TODO implement + } + return b._from(call.Arguments...) +} + +type StringCodec interface { + DecodeAppend(string, []byte) []byte + Encode([]byte) string +} + +type hexCodec struct{} + +func (hexCodec) DecodeAppend(s string, b []byte) []byte { + l := hex.DecodedLen(len(s)) + dst, res := expandSlice(b, l) + n, err := hex.Decode(dst, []byte(s)) + if err != nil { + res = res[:len(b)+n] + } + return res +} + +func (hexCodec) Encode(b []byte) string { + return hex.EncodeToString(b) +} + +type _utf8Codec struct{} + +func (_utf8Codec) DecodeAppend(s string, b []byte) []byte { + r, _ := unicode.UTF8.NewEncoder().String(s) + dst, res := expandSlice(b, len(r)) + copy(dst, r) + return res +} + +func (_utf8Codec) Encode(b []byte) string { + r, _ := unicode.UTF8.NewDecoder().Bytes(b) + return string(r) +} + +type base64Codec struct{} + +type base64UrlCodec struct { + base64Codec +} + +func (base64Codec) DecodeAppend(s string, b []byte) []byte { + res, _ := Base64DecodeAppend(b, s) + return res +} + +func (base64Codec) Encode(b []byte) string { + return base64.RawStdEncoding.EncodeToString(b) +} + +func (base64UrlCodec) Encode(b []byte) string { + return base64.URLEncoding.EncodeToString(b) +} + +var utf8Codec StringCodec = _utf8Codec{} + +var stringCodecs = map[string]StringCodec{ + "hex": hexCodec{}, + "utf8": utf8Codec, + "utf-8": utf8Codec, + "base64": base64Codec{}, + "base64Url": base64UrlCodec{}, +} + +func expandSlice(b []byte, l int) (dst, res []byte) { + if cap(b)-len(b) < l { + b1 := make([]byte, len(b)+l) + copy(b1, b) + dst = b1[len(b):] + res = b1 + } else { + dst = b[len(b) : len(b)+l] + res = b[:len(b)+l] + } + return +} + +func Base64DecodeAppend(dst []byte, src string) ([]byte, error) { + l := base64.RawStdEncoding.DecodedLen(len(src)) + d, res := expandSlice(dst, l) + srcBytes := []byte(src) + n, err := base64.RawStdEncoding.Decode(d, srcBytes) + if errPos, ok := err.(base64.CorruptInputError); ok { + if ch := src[errPos]; ch == '-' || ch == '_' { + start := int(errPos / 4 * 3) + n, err = base64.URLEncoding.Decode(d[start:], srcBytes[errPos&^3:]) + n += start + } + } + res = res[:len(dst)+n] + return res, err +} + +func (b *Buffer) fromString(str, enc string) *engine.Object { + codec := stringCodecs[enc] + if codec == nil { + codec = utf8Codec + } + return b.fromBytes(codec.DecodeAppend(str, nil)) +} + +func (b *Buffer) fromBytes(data []byte) *engine.Object { + o, err := b.uint8ArrayCtor(b.bufferCtorObj, b.r.ToValue(b.r.NewArrayBuffer(data))) + if err != nil { + panic(err) + } + return o +} + +func (b *Buffer) _from(args ...engine.Value) *engine.Object { + if len(args) == 0 { + panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received undefined")) + } + arg := args[0] + switch arg.ExportType() { + case reflectTypeArrayBuffer: + v, err := b.uint8ArrayCtor(b.bufferCtorObj, args...) + if err != nil { + panic(err) + } + return v + case reflectTypeString: + var enc string + if len(args) > 1 { + enc = args[1].String() + } + return b.fromString(arg.String(), enc) + default: + if o, ok := arg.(*engine.Object); ok { + if o.ExportType() == reflectTypeBytes { + bb, _ := o.Export().([]byte) + a := make([]byte, len(bb)) + copy(a, bb) + return b.fromBytes(a) + } else { + if f, ok := engine.AssertFunction(o.Get("valueOf")); ok { + valueOf, err := f(o) + if err != nil { + panic(err) + } + if valueOf != o { + args[0] = valueOf + return b._from(args...) + } + } + + if s := o.GetSymbol(engine.SymToPrimitive); s != nil { + if f, ok := engine.AssertFunction(s); ok { + str, err := f(o, b.r.ToValue("string")) + if err != nil { + panic(err) + } + args[0] = str + return b._from(args...) + } + } + } + // array-like + if v := o.Get("length"); v != nil { + length := int(v.ToInteger()) + a := make([]byte, length) + for i := 0; i < length; i++ { + item := o.Get(strconv.Itoa(i)) + if item != nil { + a[i] = byte(item.ToInteger()) + } + } + return b.fromBytes(a) + } + } + } + panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The first argument must be of type string or an instance of Buffer, ArrayBuffer, or Array or an Array-like Object. Received %s", arg)) +} + +func (b *Buffer) from(call engine.FunctionCall) engine.Value { + return b._from(call.Arguments...) +} + +func isNumber(v engine.Value) bool { + switch v.ExportType() { + case reflectTypeInt, reflectTypeFloat: + return true + } + return false +} + +func isString(v engine.Value) bool { + return v.ExportType() == reflectTypeString +} + +func StringCodecByName(name string) StringCodec { + return stringCodecs[name] +} + +func (b *Buffer) getStringCodec(enc engine.Value) (codec StringCodec) { + if !engine.IsUndefined(enc) { + codec = stringCodecs[enc.String()] + if codec == nil { + panic(errors.NewTypeError(b.r, "ERR_UNKNOWN_ENCODING", "Unknown encoding: %s", enc)) + } + } else { + codec = utf8Codec + } + return +} + +func (b *Buffer) fill(buf []byte, fill string, enc engine.Value) []byte { + codec := b.getStringCodec(enc) + b1 := codec.DecodeAppend(fill, buf[:0]) + if len(b1) > len(buf) { + return b1[:len(buf)] + } + for i := len(b1); i < len(buf); { + i += copy(buf[i:], buf[:i]) + } + return buf +} + +func (b *Buffer) alloc(call engine.FunctionCall) engine.Value { + arg0 := call.Argument(0) + size := -1 + if isNumber(arg0) { + size = int(arg0.ToInteger()) + } + if size < 0 { + panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"size\" argument must be of type number.")) + } + fill := call.Argument(1) + buf := make([]byte, size) + if !engine.IsUndefined(fill) { + if isString(fill) { + var enc engine.Value + if a := call.Argument(2); isString(a) { + enc = a + } else { + enc = engine.Undefined() + } + buf = b.fill(buf, fill.String(), enc) + } else { + fill = fill.ToNumber() + if !engine.IsNaN(fill) && !engine.IsInfinity(fill) { + fillByte := byte(fill.ToInteger()) + if fillByte != 0 { + for i := range buf { + buf[i] = fillByte + } + } + } + } + } + return b.fromBytes(buf) +} + +func (b *Buffer) proto_toString(call engine.FunctionCall) engine.Value { + bb := Bytes(b.r, call.This) + codec := b.getStringCodec(call.Argument(0)) + return b.r.ToValue(codec.Encode(bb)) +} + +func (b *Buffer) proto_equals(call engine.FunctionCall) engine.Value { + bb := Bytes(b.r, call.This) + other := call.Argument(0) + if b.r.InstanceOf(other, b.uint8ArrayCtorObj) { + otherBytes := Bytes(b.r, other) + return b.r.ToValue(bytes.Equal(bb, otherBytes)) + } + panic(errors.NewTypeError(b.r, errors.ErrCodeInvalidArgType, "The \"otherBuffer\" argument must be an instance of Buffer or Uint8Array.")) +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + b := &Buffer{r: runtime} + uint8Array := runtime.Get("Uint8Array") + if c, ok := engine.AssertConstructor(uint8Array); ok { + b.uint8ArrayCtor = c + } else { + panic(runtime.NewTypeError("Uint8Array is not a constructor")) + } + uint8ArrayObj := uint8Array.ToObject(runtime) + + ctor := runtime.ToValue(b.ctor).ToObject(runtime) + ctor.SetPrototype(uint8ArrayObj) + ctor.DefineDataPropertySymbol(symApi, runtime.ToValue(b), engine.FLAG_FALSE, engine.FLAG_FALSE, engine.FLAG_FALSE) + b.bufferCtorObj = ctor + b.uint8ArrayCtorObj = uint8ArrayObj + + proto := runtime.NewObject() + proto.SetPrototype(uint8ArrayObj.Get("prototype").ToObject(runtime)) + proto.DefineDataProperty("constructor", ctor, engine.FLAG_TRUE, engine.FLAG_TRUE, engine.FLAG_FALSE) + proto.Set("equals", b.proto_equals) + proto.Set("toString", b.proto_toString) + + ctor.Set("prototype", proto) + ctor.Set("poolSize", 8192) + ctor.Set("from", b.from) + ctor.Set("alloc", b.alloc) + + exports := module.Get("exports").(*engine.Object) + exports.Set("Buffer", ctor) +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/buffer/buffer_test.go b/pkg/xscript/buffer/buffer_test.go new file mode 100644 index 0000000..3263b61 --- /dev/null +++ b/pkg/xscript/buffer/buffer_test.go @@ -0,0 +1,222 @@ +package buffer + +import ( + "testing" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +func TestBufferFrom(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + + _, err := vm.RunString(` + const Buffer = require("node:buffer").Buffer; + + function checkBuffer(buf) { + if (!(buf instanceof Buffer)) { + throw new Error("instanceof Buffer"); + } + + if (!(buf instanceof Uint8Array)) { + throw new Error("instanceof Uint8Array"); + } + } + + checkBuffer(Buffer.from(new ArrayBuffer(16))); + checkBuffer(Buffer.from(new Uint16Array(8))); + + { + const b = Buffer.from("\xff\xfe\xfd"); + const h = b.toString("hex") + if (h !== "c3bfc3bec3bd") { + throw new Error(h); + } + } + + { + const b = Buffer.from("0102fffdXXX", "hex"); + checkBuffer(b); + if (b.toString("hex") !== "0102fffd") { + throw new Error(b.toString("hex")); + } + } + + { + const b = Buffer.from('1ag123', 'hex'); + if (b.length !== 1 || b[0] !== 0x1a) { + throw new Error(b); + } + } + + { + const b = Buffer.from('1a7', 'hex'); + if (b.length !== 1 || b[0] !== 0x1a) { + throw new Error(b); + } + } + + { + const b = Buffer.from("\uD801", "utf-8"); + if (b.length !== 3 || b[0] !== 0xef || b[1] !== 0xbf || b[2] !== 0xbd) { + throw new Error(b); + } + } + `) + + if err != nil { + t.Fatal(err) + } +} + +func TestFromBase64(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + + _, err := vm.RunString(` + const Buffer = require("node:buffer").Buffer; + + { + let b = Buffer.from("AAA_", "base64"); + if (b.length !== 3 || b[0] !== 0 || b[1] !== 0 || b[2] !== 0x3f) { + throw new Error(b.toString("hex")); + } + + let r = b.toString("base64"); + if (r !== "AAA/") { + throw new Error("to base64: " + r); + } + for (let i = 0; i < 20; i++) { + let s = "A".repeat(i) + "_" + "A".repeat(20-i); + let s1 = "A".repeat(i) + "/" + "A".repeat(20-i); + let b = Buffer.from(s, "base64"); + let b1 = Buffer.from(s1, "base64"); + if (!b.equals(b1)) { + throw new Error(s); + } + } + } + + { + let b = Buffer.from("SQ==???", "base64"); + if (b.length !== 1 || b[0] != 0x49) { + throw new Error(b.toString("hex")); + } + } + + `) + + if err != nil { + t.Fatal(err) + } +} + +func TestWrapBytes(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + b := []byte{1, 2, 3} + buffer := GetApi(vm) + vm.Set("b", buffer.WrapBytes(b)) + Enable(vm) + _, err := vm.RunString(` + if (typeof Buffer !== "function") { + throw new Error("Buffer is not a function: " + typeof Buffer); + } + if (!(b instanceof Buffer)) { + throw new Error("instanceof Buffer"); + } + if (b.toString("hex") !== "010203") { + throw new Error(b); + } + `) + + if err != nil { + t.Fatal(err) + } +} + +func TestBuffer_alloc(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + + _, err := vm.RunString(` + const Buffer = require("node:buffer").Buffer; + + { + const b = Buffer.alloc(2, "abc"); + if (b.toString() !== "ab") { + throw new Error(b); + } + } + + { + const b = Buffer.alloc(16, "abc"); + if (b.toString() !== "abcabcabcabcabca") { + throw new Error(b); + } + } + + { + const fill = { + valueOf() { + return 0xac; + } + } + const b = Buffer.alloc(8, fill); + if (b.toString("hex") !== "acacacacacacacac") { + throw new Error(b); + } + } + + { + const fill = { + valueOf() { + return Infinity; + } + } + const b = Buffer.alloc(2, fill); + if (b.toString("hex") !== "0000") { + throw new Error(b); + } + } + + { + const fill = { + valueOf() { + return "ac"; + } + } + const b = Buffer.alloc(2, fill); + if (b.toString("hex") !== "0000") { + throw new Error(b); + } + } + + { + const b = Buffer.alloc(2, -257.4); + if (b.toString("hex") !== "ffff") { + throw new Error(b); + } + } + + { + const b = Buffer.alloc(2, Infinity); + if (b.toString("hex") !== "0000") { + throw new Error("Infinity: " + b.toString("hex")); + } + } + + { + const b = Buffer.alloc(2, null); + if (b.toString("hex") !== "0000") { + throw new Error("Infinity: " + b.toString("hex")); + } + } + + `) + + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/xscript/buffer/conv.go b/pkg/xscript/buffer/conv.go new file mode 100644 index 0000000..9cb553c --- /dev/null +++ b/pkg/xscript/buffer/conv.go @@ -0,0 +1,35 @@ +package buffer + +import ( + "encoding/binary" + "encoding/json" + + "pandax/pkg/xscript/engine" +) + +func Conv(vm *engine.Runtime, value engine.Value) []byte { + var data []byte + switch v := value.Export().(type) { + case []byte: + data = v + case string: + data = []byte(v) + case int64: + data = make([]byte, 8) + binary.BigEndian.PutUint64(data, uint64(v)) + case map[string]interface{}: + // convert map[string]interface{} to json string + var err error + data, err = json.Marshal(v) + if err != nil { + // handle error + // 可以根据实际情况处理错误,例如记录日志或返回默认值 + return nil + } + default: + // handle unsupported type + // 可以根据实际情况处理不支持的类型,例如返回默认值或报错 + return nil + } + return data +} diff --git a/pkg/xscript/call.go b/pkg/xscript/call.go new file mode 100644 index 0000000..94578ef --- /dev/null +++ b/pkg/xscript/call.go @@ -0,0 +1,165 @@ +package xscript + +import ( + "context" + "fmt" + "strings" + "sync" + + "pandax/pkg/xscript/engine" + + "github.com/google/uuid" +) + +// 在对 s.ThreadPool 进行操作时使用互斥锁保证并发安全 +var mu sync.Mutex + +var tag = "/__/vlan/" + +func (e *Engine) responeCall(vm *engine.Runtime, path string) { + vm.Set("runtime", map[string]any{ + "version": "xScript Enging for VLAN 1.15.1.0", + + "call": func(call engine.FunctionCall) engine.Value { + var args []string + for _, arg := range call.Arguments[1:] { + args = append(args, arg.String()) + } + return e.ExecuteScriptWithSecret(call.Argument(0).String(), tag, args) + }, + + "exec": func(call engine.FunctionCall) engine.Value { + var args []string + for _, arg := range call.Arguments[1:] { + args = append(args, arg.String()) + } + return e.ExecuteScriptWithSecret(call.Argument(0).String(), tag, args) + }, + + "thread": map[string]interface{}{ + "start": func(call engine.FunctionCall) engine.Value { + scriptPath := call.Argument(0).String() + var args []string + for _, arg := range call.Arguments[1:] { + args = append(args, arg.String()) + } + tid := uuid.New().String() + mu.Lock() + defer mu.Unlock() + ctx, cancel := context.WithCancel(context.Background()) + e.ThreadPool.Store(tid, cancel) + go func() { + defer e.ThreadPool.Delete(tid) + for { + select { + case <-ctx.Done(): + fmt.Println("Goroutine received cancellation signal -->", tid) + return + default: + e.ExecuteScriptWithSecret(scriptPath, tag, args) + return + } + } + }() + return vm.ToValue(tid) + }, + "stop": func(call engine.FunctionCall) engine.Value { + tid := call.Argument(0).String() + mu.Lock() + defer mu.Unlock() + if cancel, ok := e.ThreadPool.Load(tid); ok { + e.ThreadPool.Delete(tid) + cancel.(context.CancelFunc)() + } + return vm.ToValue(true) + }, + "list": func() engine.Value { + var list []string + e.ThreadPool.Range(func(key, value interface{}) bool { + list = append(list, key.(string)) + return true + }) + return vm.ToValue(list) + }, + "match": func(call engine.FunctionCall) engine.Value { + var wg sync.WaitGroup + reference := call.Argument(0).String() + total := len(call.Arguments) + if total < 2 { + fmt.Println("runtime.thread.match() requires at least 2 script path") + return vm.ToValue(false) + } + + wg.Add(1) + var finished bool + for i := 1; i < total; i++ { + go func(script string) { + r := e.ExecuteScriptWithSecret(path, tag, nil) + if strings.Contains(r.String(), reference) { + if !finished { + finished = true + wg.Done() + } + } + }(call.Argument(i).String()) + } + wg.Wait() + + return vm.ToValue(finished) + }, + "getOne": func(call engine.FunctionCall) engine.Value { + var wg sync.WaitGroup + total := len(call.Arguments) + if total < 2 { + fmt.Println("runtime.thread.getOne() requires at least 2 script path") + return vm.ToValue(false) + } + + wg.Add(1) + var finished bool + var result engine.Value + for i := 1; i < total; i++ { + go func(script string) { + r := e.ExecuteScriptWithSecret(path, tag, nil) + if r.String() != "" { + if !finished { + result = r + wg.Done() + } + } + }(call.Argument(i).String()) + } + wg.Wait() + + return result + }, + "getAll": func(call engine.FunctionCall) engine.Value { + total := len(call.Arguments) + if total < 2 { + fmt.Println("runtime.thread.getOne() requires at least 2 script path") + return vm.ToValue(false) + } + resultChan := make(chan engine.Value, total) + for i := range call.Arguments { + go func(path string) { + result := e.ExecuteScriptWithSecret(path, tag, nil) + if result.String() != "" { + resultChan <- result + } + }(call.Argument(i).String()) + } + + var results []engine.Value + for i := 0; i < total; i++ { + select { + case res := <-resultChan: + results = append(results, res) + default: + continue + } + } + return vm.ToValue(results) + }, + }, + }) +} diff --git a/pkg/xscript/console/module.go b/pkg/xscript/console/module.go new file mode 100644 index 0000000..53d907e --- /dev/null +++ b/pkg/xscript/console/module.go @@ -0,0 +1,64 @@ +package console + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" + "pandax/pkg/xscript/util" + + "github.com/sirupsen/logrus" +) + +const ModuleName = "console" + +// Console represents a JS Console implemented as a logrus.FieldLogger. +type Console struct { + runtime *engine.Runtime + util *engine.Object +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + requireWithPrinter()(runtime, module) +} + +func RequireWithPrinter() require.ModuleLoader { + return requireWithPrinter() +} + +var log = logrus.New() + +func requireWithPrinter() require.ModuleLoader { + return func(runtime *engine.Runtime, module *engine.Object) { + c := &Console{ + runtime: runtime, + } + + // logrus.SetFormatter((&nested.Formatter{TimestampFormat: "2006/01/02 15:04:05"})) + + c.util = require.Require(runtime, util.ModuleName).(*engine.Object) + + o := module.Get("exports").(*engine.Object) + o.Set("log", c.log) + o.Set("info", c.info) + o.Set("warn", c.warn) + o.Set("error", c.error) + o.Set("debug", c.debug) + o.Set("fatal", c.fatal) + o.Set("trace", c.trace) + + o.Set("level", c.level) + o.Set("setLevel", c.setLevel) + o.Set("setNodeID", c.setNodeID) + + o.Set("format", c.valueString) + o.Set("toFile", c.toFile) + + } +} + +func Enable(runtime *engine.Runtime) { + runtime.Set("console", require.Require(runtime, ModuleName)) +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/console/module_test.go b/pkg/xscript/console/module_test.go new file mode 100644 index 0000000..0a4bf75 --- /dev/null +++ b/pkg/xscript/console/module_test.go @@ -0,0 +1,81 @@ +package console + +import ( + "testing" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +func TestConsole(t *testing.T) { + vm := engine.New() + + new(require.Registry).Enable(vm) + Enable(vm) + + if c := vm.Get("console"); c == nil { + t.Fatal("console not found") + } + + if _, err := vm.RunString("console.log('This is a log')"); err != nil { + t.Fatal("console.log() error", err) + } + + if _, err := vm.RunString("console.error('This is a error')"); err != nil { + t.Fatal("console.error() error", err) + } + + if _, err := vm.RunString("console.warn('This is a warn')"); err != nil { + t.Fatal("console.warn() error", err) + } + + if _, err := vm.RunString("console.info('This is a info')"); err != nil { + t.Fatal("console.info() error", err) + } + + if _, err := vm.RunString("console.debug('This is a debug')"); err != nil { + t.Fatal("console.debug() error", err) + } + + if _, err := vm.RunString("console.fatal('This is a fatal')"); err != nil { + t.Fatal("console.fatal() error", err) + } + + if _, err := vm.RunString("console.trace('This is a trace')"); err != nil { + t.Fatal("console.trace() error", err) + } +} + +func TestConsoleWithPrinter(t *testing.T) { + var stdoutStr, stderrStr string + + vm := engine.New() + + registry := new(require.Registry) + registry.Enable(vm) + registry.RegisterNativeModule(ModuleName, RequireWithPrinter()) + Enable(vm) + + if c := vm.Get("console"); c == nil { + t.Fatal("console not found") + } + + _, err := vm.RunString(` + console.log('a') + console.error('b') + console.warn('c') + console.debug('d') + console.info('e') + `) + if err != nil { + t.Fatal(err) + } + + if want := "ade"; stdoutStr != want { + t.Fatalf("Unexpected stdout output: got %q, want %q", stdoutStr, want) + } + + if want := "bc"; stderrStr != want { + t.Fatalf("Unexpected stderr output: got %q, want %q", stderrStr, want) + } +} diff --git a/pkg/xscript/console/std_printer.go b/pkg/xscript/console/std_printer.go new file mode 100644 index 0000000..183dbb7 --- /dev/null +++ b/pkg/xscript/console/std_printer.go @@ -0,0 +1,99 @@ +package console + +import ( + "encoding/json" + "fmt" + "os" + "strings" + + "pandax/pkg/xscript/engine" + + "github.com/sirupsen/logrus" +) + +func (c Console) print(level logrus.Level, args ...engine.Value) { + var strs strings.Builder + for i := 0; i < len(args); i++ { + if i > 0 { + strs.WriteString(" ") + } + strs.WriteString(c.valueString(args[i])) + } + msg := strs.String() + + switch level { //nolint:exhaustive + case logrus.DebugLevel: + log.Debug(msg) + case logrus.InfoLevel: + log.Info(msg) + case logrus.WarnLevel: + log.Warn(msg) + case logrus.ErrorLevel: + log.Error(msg) + } +} + +func (c Console) log(args ...any) { + fmt.Println(args...) +} + +func (c Console) debug(args ...engine.Value) { + c.print(logrus.DebugLevel, args...) +} + +func (c Console) info(args ...engine.Value) { + c.print(logrus.InfoLevel, args...) +} + +func (c Console) warn(args ...engine.Value) { + c.print(logrus.WarnLevel, args...) +} + +func (c Console) error(args ...engine.Value) { + c.print(logrus.ErrorLevel, args...) +} + +func (c Console) fatal(args ...engine.Value) { + c.print(logrus.FatalLevel, args...) +} + +func (c Console) trace(args ...engine.Value) { + c.print(logrus.TraceLevel, args...) +} + +func (c Console) valueString(v engine.Value) string { + mv, ok := v.(json.Marshaler) + if !ok { + return v.String() + } + + b, err := json.Marshal(mv) + if err != nil { + return v.String() + } + return string(b) +} + +func (c Console) setLevel(level string) { + if lvl, err := logrus.ParseLevel(level); err == nil { + log.SetLevel(lvl) + } +} + +func (c Console) level() string { + return log.GetLevel().String() +} + +func (c Console) toFile(filepath string) error { + f, err := os.OpenFile(filepath, os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0o644) + if err != nil { + return err + } + log.SetOutput(f) + + return nil +} + +func (c Console) setNodeID(nodeID int) { + log = logrus.New() +} diff --git a/pkg/xscript/crypto/3des.go b/pkg/xscript/crypto/3des.go new file mode 100644 index 0000000..2d4c10e --- /dev/null +++ b/pkg/xscript/crypto/3des.go @@ -0,0 +1,139 @@ +package crypto + +import ( + "bytes" + "crypto/cipher" + "crypto/des" + "fmt" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" +) + +func (e *Extension) encrypt3DESFunc(call engine.FunctionCall) engine.Value { + mode := call.Argument(0).String() + key := convertToBytes(call.Argument(1).Export()) + plaintext := convertToBytes(call.Argument(2).Export()) + iv := convertToBytes(call.Argument(3).Export()) + + ciphertext, err := encrypt3DES(mode, key, iv, plaintext) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + + return buffer.Format(e.runtime, ciphertext) +} + +func (e *Extension) decrypt3DESFunc(call engine.FunctionCall) engine.Value { + mode := call.Argument(0).String() + key := convertToBytes(call.Argument(1).Export()) + ciphertext := convertToBytes(call.Argument(2).Export()) + iv := convertToBytes(call.Argument(3).Export()) + + plaintext, err := decrypt3DES(mode, key, iv, ciphertext) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + + return buffer.Format(e.runtime, plaintext) +} + +func convertToBytes(data interface{}) []byte { + switch v := data.(type) { + case []byte: + return v + case string: + return []byte(v) + default: + return nil + } +} + +func encrypt3DES(mode string, key, iv, plaintext []byte) ([]byte, error) { + block, err := des.NewTripleDESCipher(key) + if err != nil { + return nil, fmt.Errorf("error creating 3DES cipher: %v", err) + } + + var encrypter cipher.BlockMode + switch mode { + case "CBC": + encrypter = cipher.NewCBCEncrypter(block, iv) + case "ECB": + encrypter = NewECBEncrypter(block) + default: + return nil, fmt.Errorf("invalid mode: %s", mode) + } + + plaintext = PKCS5Padding(plaintext, block.BlockSize()) + + ciphertext := make([]byte, len(plaintext)) + encrypter.CryptBlocks(ciphertext, plaintext) + + return ciphertext, nil +} + +func decrypt3DES(mode string, key, iv, ciphertext []byte) ([]byte, error) { + block, err := des.NewTripleDESCipher(key) + if err != nil { + return nil, fmt.Errorf("error creating 3DES cipher: %v", err) + } + + var decrypter cipher.BlockMode + switch mode { + case "CBC": + decrypter = cipher.NewCBCDecrypter(block, iv) + case "ECB": + decrypter = NewECBDecrypter(block) + default: + return nil, fmt.Errorf("invalid mode: %s", mode) + } + + decrypted := make([]byte, len(ciphertext)) + decrypter.CryptBlocks(decrypted, ciphertext) + decrypted = PKCS5Unpadding(decrypted) + + return decrypted, nil +} + +type ecb struct { + b cipher.Block + blockSize int +} + +func (e *ecb) BlockSize() int { + return e.blockSize +} + +func (e *ecb) CryptBlocks(dst, src []byte) { + if len(src)%e.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + e.b.Encrypt(dst, src[:e.blockSize]) + src = src[e.blockSize:] + dst = dst[e.blockSize:] + } +} + +func NewECB(b cipher.Block) cipher.BlockMode { + return &ecb{ + b: b, + blockSize: b.BlockSize(), + } +} + +func PKCS5Padding(data []byte, blockSize int) []byte { + padding := blockSize - len(data)%blockSize + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(data, padText...) +} + +func PKCS5Unpadding(data []byte) []byte { + length := len(data) + unpadding := int(data[length-1]) + return data[:(length - unpadding)] +} diff --git a/pkg/xscript/crypto/aes.go b/pkg/xscript/crypto/aes.go new file mode 100644 index 0000000..f873aa7 --- /dev/null +++ b/pkg/xscript/crypto/aes.go @@ -0,0 +1,351 @@ +package crypto + +import ( + "bytes" + "crypto/aes" + "crypto/cipher" + "crypto/rand" + "errors" + "fmt" + "io" + + "pandax/pkg/xscript/engine" +) + +func AESEncrypt(mode string, plaintext []byte, key []byte, iv []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + switch mode { + case "CBC", "": + return AESCBCEncrypt(block, plaintext, iv) + case "ECB": + return AESECBEncrypt(block, plaintext) + case "CTR": + return AESCTREncrypt(block, plaintext, iv) + case "CFB": + return AESCFBEncrypt(block, plaintext, iv) + case "OFB": + return AESOFBEncrypt(block, plaintext, iv) + case "GCM": + return AESGCMEncrypt(key, plaintext, iv) + default: + return nil, errors.New("unsupported mode") + } +} + +func AESCBCEncrypt(block cipher.Block, plaintext, iv []byte) ([]byte, error) { + blockSize := block.BlockSize() + + // 生成随机的初始向量 (IV) + // iv = make([]byte, blockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + + // 使用 CBC 模式加密数据 + plaintext = pkcs7Padding(plaintext, blockSize) + ciphertext := make([]byte, len(plaintext)) + mode := cipher.NewCBCEncrypter(block, iv) + mode.CryptBlocks(ciphertext, plaintext) + + return ciphertext, nil +} + +func AESECBEncrypt(block cipher.Block, plaintext []byte) ([]byte, error) { + plaintext = pkcs7Padding(plaintext, aes.BlockSize) + // 使用 ECB 模式加密数据 + ciphertext := make([]byte, len(plaintext)) + mode := NewECBEncrypter(block) + mode.CryptBlocks(ciphertext, plaintext) + + return ciphertext, nil +} + +func AESCTREncrypt(block cipher.Block, plaintext, nonce []byte) ([]byte, error) { + // blockSize := block.BlockSize() + + // 生成随机的初始计数器 (nonce) + // nonce := make([]byte, blockSize) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + // 使用 CTR 模式加密数据 + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCTR(block, nonce) + stream.XORKeyStream(ciphertext, plaintext) + + return ciphertext, nil +} + +func AESCFBEncrypt(block cipher.Block, plaintext, iv []byte) ([]byte, error) { + // blockSize := block.BlockSize() + + // 生成随机的初始向量 (IV) + // iv := make([]byte, blockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + + // 使用 CFB 模式加密数据 + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewCFBEncrypter(block, iv) + stream.XORKeyStream(ciphertext, plaintext) + + return ciphertext, nil +} + +func AESGCMEncrypt(key []byte, plaintext, nonce []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // 创建GCM模式的加密器 + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // 生成随机的nonce + // nonce := make([]byte, aesgcm.NonceSize()) + if _, err := io.ReadFull(rand.Reader, nonce); err != nil { + return nil, err + } + + // 使用GCM模式加密数据 + ciphertext := aesgcm.Seal(nil, nonce, plaintext, nil) + + // 将nonce和密文拼接起来 + ciphertext = append(nonce, ciphertext...) + + return ciphertext, nil +} + +func AESOFBEncrypt(block cipher.Block, plaintext, iv []byte) ([]byte, error) { + // blockSize := block.BlockSize() + + // 生成随机的初始向量 (IV) + // iv := make([]byte, blockSize) + if _, err := io.ReadFull(rand.Reader, iv); err != nil { + return nil, err + } + + // 使用 OFB 模式加密数据 + ciphertext := make([]byte, len(plaintext)) + stream := cipher.NewOFB(block, iv) + stream.XORKeyStream(ciphertext, plaintext) + + return ciphertext, nil +} + +func AESDecrypt(mode string, key []byte, ciphertext, iv []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + switch mode { + case "CBC", "": + return AESCBCDecrypt(block, ciphertext, iv) + case "ECB": + return AESECBDecrypt(block, ciphertext) + case "CTR": + return AESCTRDecrypt(block, ciphertext, iv) + case "CFB": + return AESCFBDecrypt(block, ciphertext, iv) + case "OFB": + return AESOFBDecrypt(block, ciphertext, iv) + case "GCM": + return AESGCMDecrypt(key, ciphertext, iv) + default: + return nil, errors.New("unsupported mode") + } +} + +func AESCBCDecrypt(block cipher.Block, ciphertext, iv []byte) ([]byte, error) { + blockSize := block.BlockSize() + + // 验证密文长度是否合法 + if len(ciphertext)%blockSize != 0 { + return nil, fmt.Errorf("invalid ciphertext length") + } + + // 生成初始向量 (IV) + // iv := ciphertext[:blockSize] + ciphertext = ciphertext[blockSize:] + + // 使用 CBC 模式解密数据 + plaintext := make([]byte, len(ciphertext)) + mode := cipher.NewCBCDecrypter(block, iv) + mode.CryptBlocks(plaintext, ciphertext) + + // 移除填充字节 + plaintext = pkcs7Unpadding(plaintext) + + return plaintext, nil +} + +func AESECBDecrypt(block cipher.Block, ciphertext []byte) ([]byte, error) { + // 使用 ECB 模式解密数据 + plaintext := make([]byte, len(ciphertext)) + mode := NewECBDecrypter(block) + mode.CryptBlocks(plaintext, ciphertext) + plaintext = pkcs7Unpadding(plaintext) + + return plaintext, nil +} + +// NewECBEncrypter 创建AES ECB模式加密器 +func NewECBEncrypter(block cipher.Block) cipher.BlockMode { + blockSize := block.BlockSize() + return &modeECB{ + b: block, + blockSize: blockSize, + } +} + +// NewECBDecrypter 创建AES ECB模式解密器 +func NewECBDecrypter(block cipher.Block) cipher.BlockMode { + return &modeECB{b: block} +} + +type modeECB struct { + b cipher.Block + blockSize int +} + +func (m *modeECB) BlockSize() int { + return m.blockSize +} + +func (m *modeECB) CryptBlocks(dst, src []byte) { + if len(src)%m.blockSize != 0 { + panic("crypto/cipher: input not full blocks") + } + if len(dst) < len(src) { + panic("crypto/cipher: output smaller than input") + } + for len(src) > 0 { + m.b.Encrypt(dst, src[:m.blockSize]) + src = src[m.blockSize:] + dst = dst[m.blockSize:] + } +} + +func AESCTRDecrypt(block cipher.Block, ciphertext, nonce []byte) ([]byte, error) { + blockSize := block.BlockSize() + + // 从密文中获取初始计数器 (nonce) + // nonce := ciphertext[:blockSize] + ciphertext = ciphertext[blockSize:] + + // 使用 CTR 模式解密数据 + plaintext := make([]byte, len(ciphertext)) + stream := cipher.NewCTR(block, nonce) + stream.XORKeyStream(plaintext, ciphertext) + + return plaintext, nil +} + +func AESCFBDecrypt(block cipher.Block, ciphertext, iv []byte) ([]byte, error) { + blockSize := block.BlockSize() + + // 从密文中获取初始向量 (IV) + // iv := ciphertext[:blockSize] + ciphertext = ciphertext[blockSize:] + + // 使用 CFB 模式解密数据 + plaintext := make([]byte, len(ciphertext)) + stream := cipher.NewCFBDecrypter(block, iv) + stream.XORKeyStream(plaintext, ciphertext) + + return plaintext, nil +} + +func AESGCMDecrypt(key []byte, ciphertext, nonce []byte) ([]byte, error) { + block, err := aes.NewCipher(key) + if err != nil { + return nil, err + } + + // 创建GCM模式的解密器 + aesgcm, err := cipher.NewGCM(block) + if err != nil { + return nil, err + } + + // 提取nonce和密文 + // nonceSize := aesgcm.NonceSize() + // nonce, ciphertext := ciphertext[:nonceSize], ciphertext[nonceSize:] + + // 使用GCM模式解密数据 + plaintext, err := aesgcm.Open(nil, nonce, ciphertext, nil) + if err != nil { + return nil, err + } + + return plaintext, nil +} + +func AESOFBDecrypt(block cipher.Block, ciphertext, iv []byte) ([]byte, error) { + blockSize := block.BlockSize() + + // 验证密文长度是否合法 + if len(ciphertext)%blockSize != 0 { + return nil, fmt.Errorf("invalid ciphertext length") + } + + // 生成初始向量 (IV) + // iv := ciphertext[:blockSize] + ciphertext = ciphertext[blockSize:] + + // 使用 OFB 模式解密数据 + plaintext := make([]byte, len(ciphertext)) + stream := cipher.NewOFB(block, iv) + stream.XORKeyStream(plaintext, ciphertext) + + return plaintext, nil +} + +func (e *Extension) encryptAESFunc(call engine.FunctionCall) engine.Value { + mode := call.Argument(0).String() + key := call.Argument(1).String() + plaintext := call.Argument(2).String() + iv := call.Argument(3).String() + + ciphertext, err := AESEncrypt(mode, []byte(plaintext), []byte(key), []byte(iv)) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + + return e.runtime.ToValue(ciphertext) +} + +func (e *Extension) decryptAESFunc(call engine.FunctionCall) engine.Value { + mode := call.Argument(0).String() + key := call.Argument(1).String() + ciphertext := call.Argument(2).String() + iv := call.Argument(3).String() + + plaintext, err := AESDecrypt(mode, []byte(ciphertext), []byte(key), []byte(iv)) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + + return e.runtime.ToValue(string(plaintext)) +} + +func pkcs7Padding(plaintext []byte, blockSize int) []byte { + padding := blockSize - (len(plaintext) % blockSize) + padText := bytes.Repeat([]byte{byte(padding)}, padding) + return append(plaintext, padText...) +} + +func pkcs7Unpadding(plaintext []byte) []byte { + padding := int(plaintext[len(plaintext)-1]) + return plaintext[:len(plaintext)-padding] +} diff --git a/pkg/xscript/crypto/crypto_test.go b/pkg/xscript/crypto/crypto_test.go new file mode 100644 index 0000000..744b1a8 --- /dev/null +++ b/pkg/xscript/crypto/crypto_test.go @@ -0,0 +1,61 @@ +package crypto + +import ( + "encoding/base64" + "fmt" + "testing" +) + +func TestEncryptSM4(t *testing.T) { + plain := []byte("Hello, World!") + key := []byte("0123456789abcdef") // 16字节的密钥 + + cipherData, err := encryptSM4(plain, key) + if err != nil { + t.Errorf("加密失败: %v", err) + } + + println(string(cipherData)) + // TODO: 添加断言语句,验证加密结果是否符合预期 +} + +func Test3DES(t *testing.T) { + // key := []byte("0123456789abcdef01234567") // 24字节密钥 + key := []byte("OOO123456789012345678901") // 24字节密钥 + // iv := []byte("12345678") // 8字节初始向量 + iv := []byte("01234567") // 8字节初始向量 + + plaintext := []byte(`{ + "phone":"18022980036", + "channelSign":"7d7a5" + }`) // 待加密的明文 + + // 加密明文 + ciphertext, err := encrypt3DES("CBC", key, iv, plaintext) + if err != nil { + fmt.Println("Error encrypting data:", err) + return + } + + fmt.Printf("加密后的密文: %x\n", ciphertext) + fmt.Printf("加密后的密文: %s\n", base64.StdEncoding.EncodeToString(ciphertext)) + + data, _ := base64.StdEncoding.DecodeString("BSEtHjtCgZSK2m75/j8Lnar7aG9Wwwjp2+jwU+PcpwW/e4v7Dx6JBEKXxnt0uhBus2aLpFfPox8=") + + // 解密密文 + decrypted, err := decrypt3DES("CBC", key, data, iv) + if err != nil { + fmt.Println("Error decrypting data:", err) + return + } + fmt.Printf("解密后的明文1: %s\n", decrypted) + + // 解密密文 + decrypted, err = decrypt3DES("CBC", key, iv, ciphertext) + if err != nil { + fmt.Println("Error decrypting data:", err) + return + } + + fmt.Printf("解密后的明文2: %s\n", decrypted) +} diff --git a/pkg/xscript/crypto/ecdsa.go b/pkg/xscript/crypto/ecdsa.go new file mode 100644 index 0000000..6e17ceb --- /dev/null +++ b/pkg/xscript/crypto/ecdsa.go @@ -0,0 +1,112 @@ +package crypto + +import ( + "crypto/ecdsa" + "crypto/elliptic" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "math/big" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" +) + +func (e *Extension) generateECDSAKeyPair() (engine.Value, engine.Value) { + // 生成ECDSA密钥对 + privateKey, err := ecdsa.GenerateKey(elliptic.P256(), rand.Reader) + if err != nil { + return e.runtime.ToValue(nil), e.runtime.ToValue(err) + } + + privateKeyPem, err := x509.MarshalECPrivateKey(privateKey) + if err != nil { + return e.runtime.ToValue(nil), e.runtime.ToValue(err) + } + // 将私钥编码为PEM格式 + privateKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "ECDSA PRIVATE KEY", + Bytes: privateKeyPem, + }) + + // 获取公钥 + publicKey := privateKey.PublicKey + + // 将公钥编码为PEM格式 + publicKeyPEM, err := x509.MarshalPKIXPublicKey(&publicKey) + if err != nil { + return e.runtime.ToValue(nil), e.runtime.ToValue(err) + } + + publicKeyPEMBlock := pem.Block{ + Type: "ECDSA PUBLIC KEY", + Bytes: publicKeyPEM, + } + publicKeyPEMBytes := pem.EncodeToMemory(&publicKeyPEMBlock) + + return e.runtime.ToValue(map[string]string{"privateKey": string(privateKeyPEM), "publicKey": string(publicKeyPEMBytes)}), e.runtime.ToValue(nil) +} + +func (e *Extension) signECDSA(privateKeyPEM string, data []byte) engine.Value { + // 解码私钥PEM + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析私钥 + privateKey, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 对数据进行哈希处理 + hashed := sha256.Sum256(data) + + // 对数据进行签名 + r, s, err := ecdsa.Sign(rand.Reader, privateKey, hashed[:]) + if err != nil { + return e.runtime.ToValue(err) + } + + // 将签名编码为DER格式 + signature, err := asn1.Marshal(struct{ R, S *big.Int }{r, s}) + if err != nil { + return e.runtime.ToValue(err) + } + + return buffer.Format(e.runtime, signature) +} + +func (e *Extension) verifyECDSA(publicKeyPEM string, data []byte, signature []byte) engine.Value { + // 解码公钥PEM + block, _ := pem.Decode([]byte(publicKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析公钥 + publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 对数据进行哈希处理 + hashed := sha256.Sum256(data) + + // 将签名解码为DER格式 + var r, s big.Int + _, err = asn1.Unmarshal(signature, &r) + if err != nil { + return e.runtime.ToValue(err) + } + _, err = asn1.Unmarshal(signature[len(signature)-len(s.Bytes()):], &s) + if err != nil { + return e.runtime.ToValue(err) + } + // 验证签名 + valid := ecdsa.Verify(publicKey.(*ecdsa.PublicKey), hashed[:], &r, &s) + return e.runtime.ToValue(valid) +} diff --git a/pkg/xscript/crypto/export.go b/pkg/xscript/crypto/export.go new file mode 100644 index 0000000..2763b9f --- /dev/null +++ b/pkg/xscript/crypto/export.go @@ -0,0 +1,86 @@ +package crypto + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "crypto" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("sm4", map[string]any{ + "encrypt": e.encryptSM4Func, + "decrypt": e.decryptSM4Func, + }) + + obj.Set("aes", map[string]any{ + "encrypt": e.encryptAESFunc, + "decrypt": e.decryptAESFunc, + }) + + obj.Set("tripleDES", map[string]any{ + "encrypt": e.encrypt3DESFunc, + "decrypt": e.decrypt3DESFunc, + }) + + obj.Set("rc4", e.RC4Func) + + obj.Set("rsa", map[string]any{ + "generateKeyPair": e.generateRsaKeyPair, + "encrypt": e.encryptRsa, + "decrypt": e.decryptRSA, + "sign": e.signRSA, + "verify": e.verifyRSA, + }) + + obj.Set("sm2", map[string]any{ + "generateKeyPair": e.generateSM2KeyPair, + "sign": e.signSM2, + "verify": e.verifySM2, + }) + + obj.Set("ecdsa", map[string]any{ + "generateKeyPair": e.generateECDSAKeyPair, + "sign": e.signECDSA, + "verify": e.verifyECDSA, + }) + + obj.Set("hash", map[string]any{ + "sm3": e.sm3Hash, + "md5": e.md5Hash, + "sha1": e.sha1Hash, + "sha224": e.sha224Hash, + "sha256": e.sha256Hash, + "sha384": e.sha384Hash, + "sha512": e.sha512Hash, + "hmacMd5": e.hmacMd5Hash, + "hmacSha1": e.hmacSha1Hash, + "hmacSha224": e.hmacSha224Hash, + "hmacSha256": e.hmacSha256Hash, + "hmacSha384": e.hmacSha384Hash, + "hmacSha512": e.hmacSha512Hash, + "keccak256": e.keccak256Hash, + "blake2b": e.blake2bHash, + "blake2s": e.blake2sHash, + "ripemd160": e.ripemd160Hash, + }) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/crypto/hash.go b/pkg/xscript/crypto/hash.go new file mode 100644 index 0000000..5528fe0 --- /dev/null +++ b/pkg/xscript/crypto/hash.go @@ -0,0 +1,114 @@ +package crypto + +import ( + "crypto/hmac" + "crypto/md5" + "crypto/sha1" + "crypto/sha256" + "hash" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" + + "github.com/emmansun/gmsm/sm3" + "golang.org/x/crypto/blake2b" + "golang.org/x/crypto/blake2s" + "golang.org/x/crypto/ripemd160" + "golang.org/x/crypto/sha3" +) + +type HashFunc func() hash.Hash + +func (e *Extension) md5Hash(value engine.Value) engine.Value { + b := value.Export().([]byte) + hash := md5.Sum(b) + + return buffer.Format(e.runtime, hash[:]) +} + +func (e *Extension) hash(call engine.FunctionCall, hashFunc HashFunc) engine.Value { + b := call.Argument(0).Export().([]byte) + hash := hashFunc() + hash.Write(b) + + return buffer.Format(e.runtime, hash.Sum(nil)) +} + +func (e *Extension) sha1Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sha1.New) +} + +func (e *Extension) sha224Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sha3.New224) +} + +func (e *Extension) sha256Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sha3.New256) +} + +func (e *Extension) sha384Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sha3.New384) +} + +func (e *Extension) sha512Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sha3.New512) +} + +func (e *Extension) keccak256Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sha3.NewLegacyKeccak256) +} + +func (e *Extension) sm3Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, sm3.New) +} + +func (e *Extension) blake2bHash(value engine.Value) engine.Value { + b := value.Export().([]byte) + hash := blake2b.Sum512(b) + + return buffer.Format(e.runtime, hash[:]) +} + +func (e *Extension) blake2sHash(value engine.Value) engine.Value { + b := value.Export().([]byte) + hash := blake2s.Sum256(b) + + return buffer.Format(e.runtime, hash[:]) +} + +func (e *Extension) ripemd160Hash(call engine.FunctionCall) engine.Value { + return e.hash(call, ripemd160.New) +} + +func (e *Extension) hmacHash(call engine.FunctionCall, hashFunc func() hash.Hash) engine.Value { + str := call.Argument(0).String() + key := call.Argument(1).String() + hash := hmac.New(hashFunc, []byte(key)) + hash.Write([]byte(str)) + + return buffer.Format(e.runtime, hash.Sum(nil)) +} + +func (e *Extension) hmacSha1Hash(call engine.FunctionCall) engine.Value { + return e.hmacHash(call, sha1.New) +} + +func (e *Extension) hmacSha224Hash(call engine.FunctionCall) engine.Value { + return e.hmacHash(call, sha3.New224) +} + +func (e *Extension) hmacSha256Hash(call engine.FunctionCall) engine.Value { + return e.hmacHash(call, sha256.New) +} + +func (e *Extension) hmacSha384Hash(call engine.FunctionCall) engine.Value { + return e.hmacHash(call, sha3.New384) +} + +func (e *Extension) hmacSha512Hash(call engine.FunctionCall) engine.Value { + return e.hmacHash(call, sha3.New512) +} + +func (e *Extension) hmacMd5Hash(call engine.FunctionCall) engine.Value { + return e.hmacHash(call, md5.New) +} diff --git a/pkg/xscript/crypto/rc4.go b/pkg/xscript/crypto/rc4.go new file mode 100644 index 0000000..6562bda --- /dev/null +++ b/pkg/xscript/crypto/rc4.go @@ -0,0 +1,17 @@ +package crypto + +import ( + "crypto/rc4" + + "pandax/pkg/xscript/engine" +) + +func (e *Extension) RC4Func(key string, content []byte) engine.Value { + cipher, err := rc4.NewCipher([]byte(key)) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + result := make([]byte, len(content)) + cipher.XORKeyStream(result, content) + return e.runtime.ToValue(result) +} diff --git a/pkg/xscript/crypto/rsa.go b/pkg/xscript/crypto/rsa.go new file mode 100644 index 0000000..0b6ca75 --- /dev/null +++ b/pkg/xscript/crypto/rsa.go @@ -0,0 +1,141 @@ +package crypto + +import ( + "crypto" + "crypto/rand" + "crypto/rsa" + "crypto/sha256" + "crypto/x509" + "encoding/pem" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" +) + +func (e *Extension) generateRsaKeyPair(length int) engine.Value { + if length == 0 { + length = 2048 + } + // 生成RSA密钥对 + privateKey, err := rsa.GenerateKey(rand.Reader, length) + if err != nil { + return e.runtime.ToValue(err) + } + + // 将私钥编码为PEM格式 + privateKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "RSA PRIVATE KEY", + Bytes: x509.MarshalPKCS1PrivateKey(privateKey), + }) + + // 获取公钥 + publicKey := privateKey.PublicKey + + // 将公钥编码为PEM格式 + publicKeyPEM, err := x509.MarshalPKIXPublicKey(&publicKey) + if err != nil { + return e.runtime.ToValue(err) + } + + publicKeyPEMBlock := pem.Block{ + Type: "RSA PUBLIC KEY", + Bytes: publicKeyPEM, + } + publicKeyPEMBytes := pem.EncodeToMemory(&publicKeyPEMBlock) + + return e.runtime.ToValue(map[string]string{"privateKey": string(privateKeyPEM), "publicKey": string(publicKeyPEMBytes)}) +} + +func (e *Extension) encryptRsa(publicKeyPEM string, plain []byte) engine.Value { + // 解码公钥PEM + block, _ := pem.Decode([]byte(publicKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析公钥 + publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 类型断言为RSA公钥 + rsaPublicKey := publicKey.(*rsa.PublicKey) + + // 加密数据 + ciphertext, err := rsa.EncryptPKCS1v15(rand.Reader, rsaPublicKey, []byte(plain)) + if err != nil { + return e.runtime.ToValue(err) + } + + return e.runtime.ToValue(ciphertext) +} + +func (e *Extension) decryptRSA(privateKeyPEM string, cipherData engine.Value) engine.Value { + // 解码私钥PEM + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析私钥 + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 解密数据 + ciphertext, err := rsa.DecryptPKCS1v15(rand.Reader, privateKey, cipherData.Export().([]byte)) + if err != nil { + return e.runtime.ToValue(err) + } + + return e.runtime.ToValue(ciphertext) +} + +func (e *Extension) signRSA(privateKeyPEM string, data []byte) engine.Value { + // 解码私钥PEM + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析私钥 + privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 对数据进行哈希处理 + hashed := sha256.Sum256(data) + + // 对数据进行签名 + signature, err := rsa.SignPKCS1v15(rand.Reader, privateKey, crypto.SHA256, hashed[:]) + if err != nil { + return e.runtime.ToValue(err) + } + + return buffer.Format(e.runtime, signature) +} + +func (e *Extension) verifyRSA(publicKeyPEM string, data []byte, signature []byte) engine.Value { + // 解码公钥PEM + block, _ := pem.Decode([]byte(publicKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + // 解析公钥 + publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + // 对数据进行哈希处理 + hashed := sha256.Sum256(data) + // 验证签名 + err = rsa.VerifyPKCS1v15(publicKey.(*rsa.PublicKey), crypto.SHA256, hashed[:], signature) + if err != nil { + + return e.runtime.ToValue(err) + } + return e.runtime.ToValue(true) +} diff --git a/pkg/xscript/crypto/sm2.go b/pkg/xscript/crypto/sm2.go new file mode 100644 index 0000000..a19e4ec --- /dev/null +++ b/pkg/xscript/crypto/sm2.go @@ -0,0 +1,102 @@ +package crypto + +import ( + "crypto/ecdsa" + "crypto/rand" + "crypto/sha256" + "crypto/x509" + "encoding/asn1" + "encoding/pem" + "math/big" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" + + sm2_edch "github.com/emmansun/gmsm/ecdh" +) + +func (e *Extension) generateSM2KeyPair() engine.Value { + // 生成ECDSA密钥对 + privateKey, err := sm2_edch.P256().GenerateKey(rand.Reader) + if err != nil { + return nil + } + + // 将私钥编码为PEM格式 + privateKeyPEM := pem.EncodeToMemory(&pem.Block{ + Type: "ECDSA PRIVATE KEY", + Bytes: privateKey.Bytes(), + }) + + // 获取公钥 + publicKey := privateKey.PublicKey() + + publicKeyPEMBlock := pem.Block{ + Type: "ECDSA PUBLIC KEY", + Bytes: publicKey.Bytes(), + } + publicKeyPEMBytes := pem.EncodeToMemory(&publicKeyPEMBlock) + + return e.runtime.ToValue(map[string]string{"privateKey": string(privateKeyPEM), "publicKey": string(publicKeyPEMBytes)}) +} + +func (e *Extension) signSM2(privateKeyPEM string, data []byte) engine.Value { + // 解码私钥PEM + block, _ := pem.Decode([]byte(privateKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析私钥 + privateKey, err := x509.ParseECPrivateKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 对数据进行哈希处理 + hashed := sha256.Sum256(data) + + // 对数据进行签名 + r, s, err := ecdsa.Sign(rand.Reader, privateKey, hashed[:]) + if err != nil { + return e.runtime.ToValue(err) + } + + // 将签名编码为DER格式 + signature, err := asn1.Marshal(struct{ R, S *big.Int }{r, s}) + if err != nil { + return e.runtime.ToValue(err) + } + + return buffer.Format(e.runtime, signature) +} + +func (e *Extension) verifySM2(publicKeyPEM string, data []byte, signature []byte) engine.Value { + // 解码公钥PEM + block, _ := pem.Decode([]byte(publicKeyPEM)) + if block == nil { + return e.runtime.ToValue("error: PEM decode error") + } + + // 解析公钥 + publicKey, err := x509.ParsePKIXPublicKey(block.Bytes) + if err != nil { + return e.runtime.ToValue(err) + } + + // 将签名解码为DER格式 + var sm2Signature struct { + R, S *big.Int + } + _, err = asn1.Unmarshal(signature, &sm2Signature) + if err != nil { + return e.runtime.ToValue(err) + } + // 对数据进行哈希处理 + hashed := sha256.Sum256(data) + // 验证签名 + if !ecdsa.Verify(publicKey.(*ecdsa.PublicKey), hashed[:], sm2Signature.R, sm2Signature.S) { + return e.runtime.ToValue("error: SM2 signature verification failed") + } + return e.runtime.ToValue(true) +} diff --git a/pkg/xscript/crypto/sm4.go b/pkg/xscript/crypto/sm4.go new file mode 100644 index 0000000..9bf33ae --- /dev/null +++ b/pkg/xscript/crypto/sm4.go @@ -0,0 +1,64 @@ +package crypto + +import ( + "bytes" + + "pandax/pkg/xscript/engine" + + "github.com/emmansun/gmsm/sm4" +) + +func encryptSM4(plain []byte, key []byte) ([]byte, error) { + cipher, err := sm4.NewCipher(key) + if err != nil { + return nil, err + } + + // 填充数据 + blockSize := cipher.BlockSize() + padLen := blockSize - (len(plain) % blockSize) + paddedPlain := append(plain, bytes.Repeat([]byte{byte(padLen)}, padLen)...) + + cipherData := make([]byte, len(paddedPlain)) + cipher.Encrypt(cipherData, paddedPlain) + + return cipherData, nil +} + +func decryptSM4(cipherData []byte, key []byte) ([]byte, error) { + cipher, err := sm4.NewCipher(key) + if err != nil { + return nil, err + } + + plain := make([]byte, len(cipherData)) + cipher.Decrypt(plain, cipherData) + + return plain, nil +} + +// 在 Extension 结构体中添加相应的方法,供 goja 调用 + +func (e *Extension) encryptSM4Func(call engine.FunctionCall) engine.Value { + plain := call.Argument(0).Export().([]byte) + key := call.Argument(1).String() + + cipherData, err := encryptSM4(plain, []byte(key)) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + + return e.runtime.ToValue(cipherData) +} + +func (e *Extension) decryptSM4Func(call engine.FunctionCall) engine.Value { + cipherData := call.Argument(0).Export().([]byte) + key := call.Argument(1).String() + + plain, err := decryptSM4(cipherData, []byte(key)) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + + return e.runtime.ToValue(plain) +} diff --git a/pkg/xscript/database/cache/export.go b/pkg/xscript/database/cache/export.go new file mode 100644 index 0000000..ddfbcba --- /dev/null +++ b/pkg/xscript/database/cache/export.go @@ -0,0 +1,57 @@ +package cache + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" + + "gvisor.dev/gvisor/pkg/sync" +) + +const ModuleName = "cache" + +type Extension struct { + runtime *engine.Runtime + mu *sync.Mutex +} + +var database *sync.Map + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + mu: &sync.Mutex{}, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("new", e.new) + obj.Set("drop", e.drop) + obj.Set("list", e.list) + obj.Set("set", e.set) + obj.Set("get", e.get) + obj.Set("del", e.del) + obj.Set("clear", e.clear) + obj.Set("keys", e.keys) + obj.Set("stack", e.stack) + obj.Set("values", e.values) + obj.Set("size", e.size) + obj.Set("has", e.has) + obj.Set("hasOrSet", e.hasOrSet) + obj.Set("peek", e.peek) + obj.Set("peekOrSet", e.peekOrSet) + obj.Set("resize", e.resize) + obj.Set("export", e.export) + obj.Set("import", e.load) + +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + mu: &sync.Mutex{}, + } +} + +func init() { + database = &sync.Map{} + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/database/cache/function.go b/pkg/xscript/database/cache/function.go new file mode 100644 index 0000000..db52692 --- /dev/null +++ b/pkg/xscript/database/cache/function.go @@ -0,0 +1,463 @@ +package cache + +import ( + "encoding/json" + "os" + "strconv" + "sync" + "time" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" +) + +// The `new` function is a method of the `Extension` struct. It is used to create a new cache with a +// given name and size. +func (e *Extension) new(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + size := call.Argument(1).ToInteger() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + e.mu.Lock() + defer e.mu.Unlock() + + exists := false + if _, ok := database.Load(name); ok { + exists = true + } + if exists { + return e.runtime.ToValue(name) + } + database.Store(name, NewKVStore(int(size))) + return e.runtime.ToValue(name) +} + +// The `set` function is a method of the `Extension` struct. It is used to set a key-value pair in a +// specific cache. +func (e *Extension) set(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + value := call.Argument(2).String() + ttl := call.Argument(3).ToInteger() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + if value == "" { + return e.newInvalidValueError("value is required", value) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + lru.(*KVStore).Set(key, value, time.Duration(ttl)*time.Second) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +// The `get` function is a method of the `Extension` struct. It is used to retrieve the value +// associated with a given key from a specific cache. +func (e *Extension) get(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + if value, ok := lru.(*KVStore).Get(key); ok { + return e.runtime.ToValue(buffer.Format(e.runtime, value)) + } + } + return e.runtime.ToValue(engine.Null()) +} + +// The `del` function is a method of the `Extension` struct. It is used to delete a key-value pair from +// a specific cache. +func (e *Extension) del(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + lru.(*KVStore).Remove(key) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +// The `keys` function is a method of the `Extension` struct. It is used to retrieve all the keys +// stored in a specific cache. +func (e *Extension) keys(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + return e.runtime.ToValue(lru.(*KVStore).Keys()) + } + return e.runtime.ToValue(engine.Null()) +} + +// The above code is defining a method called "stack" for a struct called "Extension" in Go. This +// method takes a function call as an argument. +func (e *Extension) stack(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + return e.runtime.ToValue(lru.(*KVStore).List()) + } + return e.runtime.ToValue(engine.Null()) +} + +// The `values` function is a method of the `Extension` struct. It is used to retrieve all the values +// stored in a specific cache. +func (e *Extension) values(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + return e.runtime.ToValue(lru.(*KVStore).Values()) + } + return e.runtime.ToValue(engine.Null()) +} + +// The `size` function is a method of the `Extension` struct. It is used to retrieve the number of +// key-value pairs in a specific cache. +func (e *Extension) size(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + return e.runtime.ToValue(lru.(*KVStore).Len()) + } + return e.runtime.ToValue(engine.Null()) +} + +// The `clear` function is a method of the `Extension` struct. It is used to clear all the data in a +// specific cache. +func (e *Extension) clear(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + lru.(*KVStore).Purge() + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +// The `has` function is a method of the `Extension` struct. It is used to check if a key exists in the +// cache. +func (e *Extension) has(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + return e.runtime.ToValue(lru.(*KVStore).Contains(key)) + } + return e.runtime.ToValue(false) +} + +// The `hasOrSet` function is a method of the `Extension` struct. It is used to check if a key exists +// in the cache and if not, set a new value for the key. +func (e *Extension) hasOrSet(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + value := call.Argument(2).String() + ttl := call.Argument(3).ToInteger() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + if value == "" { + return e.newInvalidValueError("value is required", value) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + if _, ok := lru.(*KVStore).Get(key); !ok { + lru.(*KVStore).Set(key, value, time.Duration(ttl)*time.Second) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) + } + return e.runtime.ToValue(false) +} + +// The `resize` function is a method of the `Extension` struct. It is used to resize the cache with a +// new size. It takes two arguments: `name` (the name of the cache) and `size` (the new size of the +// cache). +func (e *Extension) resize(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + size := call.Argument(1).ToInteger() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if size == 0 { + return e.newInvalidValueError("size is required", strconv.Itoa(int(size))) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + lru.(*KVStore).Resize(int(size)) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +// The `peek` function is used to retrieve the value associated with a given key from the cache without +// updating its access time. It takes two arguments: `name` (the name of the cache) and `key` (the key +// to retrieve the value for). +func (e *Extension) peek(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + if value, ok := lru.(*KVStore).Peek(key); ok { + return e.runtime.ToValue(value) + } + } + return e.runtime.ToValue(engine.Null()) +} + +// The `peekOrSet` function is used to either retrieve the value associated with a given key from the +// cache or set a new value for the key if it doesn't exist in the cache. +func (e *Extension) peekOrSet(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + value := call.Argument(2).String() + ttl := call.Argument(3).ToInteger() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + if value == "" { + return e.newInvalidValueError("value is required", value) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + if _, ok := lru.(*KVStore).Get(key); !ok { + lru.(*KVStore).Set(key, value, time.Duration(ttl)*time.Second) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) + } + return e.runtime.ToValue(false) +} + +// The `list` function is a method of the `Extension` struct. It is used to retrieve a list of all the +// cache names stored in the database. It iterates over the `database` map using the `Range` function, +// and for each key-value pair, it appends the key (cache name) to the `dbs` slice. Finally, it returns +// the `dbs` slice as a value. +func (e *Extension) list(call engine.FunctionCall) engine.Value { + var dbs []string + database.Range(func(key, value any) bool { + dbs = append(dbs, key.(string)) + return true + }) + return e.runtime.ToValue(dbs) +} + +// The `drop` function is a method of the `Extension` struct. It is used to remove a specific cache +// from the database. It takes a `name` parameter, which is the name of the cache to be dropped. +func (e *Extension) drop(name engine.Value) engine.Value { + if name == engine.Undefined() { + return e.newInvalidValueError("name is required", name.String()) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.LoadAndDelete(name.String()); ok { + lru.(*KVStore).Purge() + } + return e.runtime.ToValue(true) +} + +// 从指定路径的JSON文件中增量导入数据到LRU缓存中 +// The `load` function is a method of the `Extension` struct. It is used to incrementally import data +// from a JSON file into an LRU cache. +func (e *Extension) load(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + filePath := call.Argument(1).String() + + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if filePath == "" { + return e.newInvalidValueError("filePath is required", filePath) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + jsonData, err := os.ReadFile(filePath) + if err != nil { + return e.runtime.ToValue(false) + } + + var data map[string]string + err = json.Unmarshal(jsonData, &data) + if err != nil { + return e.runtime.ToValue(false) + } + + // 使用WaitGroup来等待所有goroutine完成 + var wg sync.WaitGroup + wg.Add(len(data)) + + // 使用通道来传递导入结果 + resultCh := make(chan bool) + + // 启动多个goroutine并发导入数据 + for key, value := range data { + go func(k, v string) { + defer wg.Done() + lru.(*KVStore).Set(k, v, 0) + }(key, value) + } + + // 在另一个goroutine中等待所有导入完成,并向通道发送结果 + go func() { + wg.Wait() + resultCh <- true + }() + + // 从通道接收导入结果 + select { + case <-resultCh: + return e.runtime.ToValue(true) + case <-time.After(time.Second * 10): // 设置一个超时时间,避免无限等待 + return e.runtime.ToValue(false) + } + } + + return e.runtime.ToValue(false) +} + +// 导出LRU缓存中的数据到指定路径的JSON文件 +// The `export` function is a method of the `Extension` struct. It is used to export the data stored in +// a specific cache to a JSON file at the specified file path. +func (e *Extension) export(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + filePath := call.Argument(1).String() + + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if filePath == "" { + return e.newInvalidValueError("filePath is required", filePath) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if lru, ok := database.Load(name); ok { + // 获取LRU缓存的所有键值对 + keys := lru.(*KVStore).Keys() + + // 使用WaitGroup来等待所有goroutine完成 + var wg sync.WaitGroup + wg.Add(len(keys)) + + // 启动多个goroutine并发导出数据 + for _, key := range keys { + go func(k string) { + defer wg.Done() + value, _ := lru.(*KVStore).Get(k) + exportData := map[string]string{k: value.(string)} + jsonData, _ := json.Marshal(exportData) + os.WriteFile(filePath, jsonData, 0644) + }(key) + } + + // 等待所有goroutine完成 + wg.Wait() + + return e.runtime.ToValue(true) + } + + return e.runtime.ToValue(false) +} + +func (e *Extension) newInvalidValueError(msg, input string) *engine.Object { + o := errors.NewTypeError(e.runtime, "ERR_INVALID_VALUE", msg, input) + o.Set("input", e.runtime.ToValue(input)) + return o +} diff --git a/pkg/xscript/database/cache/kv.go b/pkg/xscript/database/cache/kv.go new file mode 100644 index 0000000..04b7ef7 --- /dev/null +++ b/pkg/xscript/database/cache/kv.go @@ -0,0 +1,337 @@ +package cache + +import ( + "container/heap" + "encoding/json" + "fmt" + "pandax/pkg/global" + "sync" + "time" +) + +type KeyValue struct { + Value interface{} `json:"-"` + ValidAt time.Time `json:"validAt"` + HitNum int `json:"hitNum"` + AccessAt time.Time `json:"accessAt"` + CreateAt time.Time `json:"createAt"` +} + +type Item struct { + key string + validAt time.Time +} + +type PriorityQueue []*Item + +func (pq PriorityQueue) Len() int { return len(pq) } + +func (pq PriorityQueue) Less(i, j int) bool { + return pq[i].validAt.Before(pq[j].validAt) +} + +func (pq PriorityQueue) Swap(i, j int) { + pq[i], pq[j] = pq[j], pq[i] +} + +func (pq *PriorityQueue) Push(x interface{}) { + item := x.(*Item) + *pq = append(*pq, item) +} + +func (pq *PriorityQueue) Pop() interface{} { + old := *pq + n := len(old) + item := old[n-1] + *pq = old[0 : n-1] + return item +} + +type KVStore struct { + data sync.Map + size int + expiration PriorityQueue + mu sync.Mutex + sqlTable string +} + +func NewKVStore(size int) *KVStore { + kv := &KVStore{ + data: sync.Map{}, + size: size, + expiration: make(PriorityQueue, 0), + } + heap.Init(&kv.expiration) + go kv.expirationCleanup() + return kv +} + +func NewKVStoreWithSQL(size int, sqlTable string) *KVStore { + kv := &KVStore{ + data: sync.Map{}, + size: size, + expiration: make(PriorityQueue, 0), + sqlTable: sqlTable, + } + heap.Init(&kv.expiration) + go kv.expirationCleanup() + return kv +} + +func (kv *KVStore) expirationCleanup() { + ticker := time.NewTicker(time.Second) + defer ticker.Stop() + for { + <-ticker.C + kv.mu.Lock() + now := time.Now() + var expiredKeys []string + for kv.expiration.Len() > 0 { + if kv.expiration[0].validAt.Before(now) { + expiredKeys = append(expiredKeys, kv.expiration[0].key) + heap.Pop(&kv.expiration) + } else { + break + } + } + for _, key := range expiredKeys { + kv.data.Delete(key) + kv.deleteFromDB(key) + } + kv.mu.Unlock() + } +} + +func (kv *KVStore) Set(key string, value interface{}, ttl time.Duration) { + expiration := time.Now().Add(ttl) + kv.mu.Lock() + defer kv.mu.Unlock() + if kv.size > 0 && kv.expiration.Len() >= kv.size { + oldest := heap.Pop(&kv.expiration).(*Item) + kv.data.Delete(oldest.key) + kv.deleteFromDB(oldest.key) + heap.Push(&kv.expiration, &Item{key: key, validAt: expiration}) + return + } + kv.data.Store(key, KeyValue{ + Value: value, + ValidAt: expiration, + CreateAt: time.Now(), + }) + heap.Push(&kv.expiration, &Item{key: key, validAt: expiration}) + kv.saveToDB(key, value, expiration) +} + +func (kv *KVStore) saveToDB(key string, value interface{}, expiration time.Time) { + if kv.sqlTable == `` { + return + } + jsonValue, err := json.Marshal(value) + if err != nil { + fmt.Printf("Failed to marshal value for key %s: %v\n", key, err) + return + } + + err = global.Db.Raw("INSERT INTO ? (key, value, expiration) VALUES (?, ?, ?) ON DUPLICATE KEY UPDATE value = ?, expiration = ?", kv.sqlTable, key, jsonValue, expiration, jsonValue, expiration).Error + if err != nil { + fmt.Printf("Failed to save value for key %s to database: %v\n", key, err) + return + } +} + +func (kv *KVStore) deleteFromDB(key string) { + if kv.sqlTable == `` { + return + } + err := global.Db.Raw("DELETE FROM ? WHERE key = ?", kv.sqlTable, key).Error + if err != nil { + fmt.Printf("Failed to save value for key %s to database: %v\n", key, err) + return + } +} + +func (kv *KVStore) LoadFromDB() { + if kv.sqlTable == `` { + return + } + rows, err := global.Db.Raw("SELECT key, value, expiration FROM ?;", kv.sqlTable).Rows() + if err != nil { + fmt.Printf("Failed to load values from database: %v\n", err) + return + } + defer rows.Close() + for rows.Next() { + var key string + var value []byte + var expiration time.Time + err := rows.Scan(&key, &value, &expiration) + if err != nil { + fmt.Printf("Failed to scan row from database: %v\n", err) + continue + } + kv.data.Store(key, KeyValue{ + Value: value, + ValidAt: expiration, + CreateAt: time.Now(), + }) + heap.Push(&kv.expiration, &Item{key: key, validAt: expiration}) + } +} + +func (kv *KVStore) Resize(size int) { + kv.mu.Lock() + defer kv.mu.Unlock() + kv.size = size +} + +func (kv *KVStore) Remove(key string) { + kv.mu.Lock() + defer kv.mu.Unlock() + kv.data.Delete(key) + kv.deleteFromDB(key) +} + +func (kv *KVStore) Keys() []string { + keys := make([]string, 0) + kv.data.Range(func(key, value interface{}) bool { + keys = append(keys, key.(string)) + return true + }) + return keys +} + +func (kv *KVStore) Size() int { + return kv.size +} + +func (kv *KVStore) Len() int { + return kv.expiration.Len() +} + +func (kv *KVStore) Purge() { + kv.mu.Lock() + defer kv.mu.Unlock() + kv.data.Range(func(key, value interface{}) bool { + kv.data.Delete(key) + kv.deleteFromDB(key.(string)) + return true + }) +} +func (kv *KVStore) Range(f func(key string, value interface{}) bool) { + kv.data.Range(func(key, value interface{}) bool { + return f(key.(string), value.(KeyValue).Value) + }) +} + +func (kv *KVStore) Values() []interface{} { + values := make([]interface{}, 0) + kv.data.Range(func(key, value interface{}) bool { + values = append(values, value.(KeyValue).Value) + return true + }) + return values +} + +func (kv *KVStore) Contains(key string) bool { + _, ok := kv.data.Load(key) + return ok +} + +func (kv *KVStore) ContainsOrSet(key string, value any, ttl time.Duration) bool { + _, ok := kv.data.LoadOrStore(key, KeyValue{ + Value: value, + ValidAt: time.Now().Add(ttl), + CreateAt: time.Now(), + }) + return ok +} + +func (kv *KVStore) Peek(key string) (interface{}, bool) { + value, ok := kv.data.Load(key) + if !ok { + return nil, false + } + kvPair, ok := value.(KeyValue) + if !ok { + return nil, false + } + go kv.hit(key, kvPair) + return kvPair.Value, true +} + +// 如果key存在,则返回key对应的value,如果key不存在,则设置key对应的value,并返回value +func (kv *KVStore) PeekOrSet(key string, value any, ttl time.Duration) (interface{}, bool) { + v, _ := kv.data.LoadOrStore(key, KeyValue{ + Value: value, + ValidAt: time.Now().Add(ttl), + CreateAt: time.Now(), + }) + + kvPair, ok := v.(KeyValue) + if !ok { + return nil, false + } + go kv.hit(key, kvPair) + return kvPair.Value, ok +} + +func (kv *KVStore) Get(key string) (interface{}, bool) { + value, ok := kv.data.Load(key) + if !ok { + return nil, false + } + kvPair, ok := value.(KeyValue) + if !ok { + return nil, false + } + go kv.hit(key, kvPair) + return kvPair.Value, true +} + +func (kv *KVStore) GetDetail(key string) (kvPair KeyValue) { + value, ok := kv.data.Load(key) + if !ok { + return + } + kvPair, ok = value.(KeyValue) + if !ok { + return + } + go kv.hit(key, kvPair) + return kvPair +} + +func (kv *KVStore) List() map[string]map[string]any { + list := make(map[string]map[string]any) + kv.data.Range(func(key, value interface{}) bool { + kvPair, ok := value.(KeyValue) + if ok { + list[key.(string)] = map[string]any{ + "hitNum": kvPair.HitNum, + "validAt": kvPair.ValidAt.Format("2006-01-02 15:04:05"), + "createAt": kvPair.CreateAt.Format("2006-01-02 15:04:05"), + "accessAt": kvPair.AccessAt.Format("2006-01-02 15:04:05"), + } + } + return true + }) + return list +} + +func (kv *KVStore) GetKeysWithExpiration() map[string]time.Time { + keys := make(map[string]time.Time) + kv.data.Range(func(key, value interface{}) bool { + kvPair, ok := value.(KeyValue) + if ok { + keys[key.(string)] = kvPair.ValidAt + } + return true + }) + return keys +} + +func (kv *KVStore) hit(key string, item KeyValue) { + item.HitNum++ + item.AccessAt = time.Now() + kv.data.Store(key, item) +} diff --git a/pkg/xscript/database/cache/kv_test.go b/pkg/xscript/database/cache/kv_test.go new file mode 100644 index 0000000..f2bdf6a --- /dev/null +++ b/pkg/xscript/database/cache/kv_test.go @@ -0,0 +1,169 @@ +package cache + +import ( + "testing" + "time" +) + +func TestNewKVStore(t *testing.T) { + kv := NewKVStore(10) + if kv == nil { + t.Error("Failed to create KVStore") + } + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + v, ok := kv.Get(key) + if !ok || v != value { + t.Error("Failed to set key-value pair") + } + + time.Sleep(time.Second * 20) + v, ok = kv.Get(key) + if !ok || v != value { + t.Error("Failed to get value for key in KVStore") + } +} + +func TestSet(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + v, ok := kv.Get(key) + if !ok || v != value { + t.Error("Failed to set key-value pair") + } +} + +func TestResize(t *testing.T) { + kv := NewKVStore(10) + newSize := 20 + kv.Resize(newSize) + if kv.Size() != newSize { + t.Error("Failed to resize KVStore") + } +} + +func TestRemove(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + kv.Remove(key) + if kv.Contains(key) { + t.Error("Failed to remove key from KVStore") + } +} + +func TestKeys(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + keys := kv.Keys() + if len(keys) != 1 || keys[0] != key { + t.Error("Failed to retrieve keys from KVStore") + } +} + +func TestGetKeysWithExpiration(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + keysWithExpiration := kv.GetKeysWithExpiration() + if keysWithExpiration[key].Before(time.Now()) { + t.Error("Failed to read all keys with expiration") + } + +} + +func TestLen(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + if kv.Len() != 1 { + t.Error("Failed to retrieve length of KVStore") + } +} + +func TestPurge(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + kv.Purge() + if kv.Len() != 0 { + t.Error("Failed to purge KVStore") + } +} + +func TestRange(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + kv.Range(func(k string, v interface{}) bool { + if k != key || v != value { + t.Error("Failed to range over KVStore") + } + return true + }) +} + +func TestValues(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + values := kv.Values() + if len(values) != 1 || values[0] != value { + t.Error("Failed to retrieve values from KVStore") + } +} + +func TestContains(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + if !kv.Contains(key) { + t.Error("Failed to check if key exists in KVStore") + } +} + +func TestPeek(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + v, ok := kv.Peek(key) + if !ok || v != value { + t.Error("Failed to peek key in KVStore") + } +} + +func TestGet(t *testing.T) { + kv := NewKVStore(10) + key := "testKey" + value := "testValue" + ttl := time.Second * 10 + kv.Set(key, value, ttl) + v, ok := kv.Get(key) + if !ok || v != value { + t.Error("Failed to get value for key in KVStore") + } +} diff --git a/pkg/xscript/database/mongo/export.go b/pkg/xscript/database/mongo/export.go new file mode 100644 index 0000000..c424aa1 --- /dev/null +++ b/pkg/xscript/database/mongo/export.go @@ -0,0 +1,48 @@ +package mongo + +import ( + "sync" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "mongo" + +var database *sync.Map + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("new", e.new) + obj.Set("close", e.close) + obj.Set("list", e.list) + obj.Set("database", e.database) + obj.Set("collection", e.collection) + obj.Set("find", e.find) + obj.Set("findOne", e.findOne) + obj.Set("insertOne", e.insertOne) + obj.Set("insertMany", e.insertMany) + obj.Set("updateOne", e.updateOne) + obj.Set("updateMany", e.updateMany) + obj.Set("deleteOne", e.deleteOne) + obj.Set("deleteMany", e.deleteMany) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + database = &sync.Map{} + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/database/mongo/function.go b/pkg/xscript/database/mongo/function.go new file mode 100644 index 0000000..1fbbb5a --- /dev/null +++ b/pkg/xscript/database/mongo/function.go @@ -0,0 +1,302 @@ +package mongo + +import ( + "context" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" + + "go.mongodb.org/mongo-driver/mongo" + "go.mongodb.org/mongo-driver/mongo/options" +) + +func (e *Extension) new(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + uri := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if _, ok := database.Load(name); ok { + return e.runtime.ToValue(name) + } + client, err := mongo.Connect(context.Background(), options.Client().ApplyURI(uri)) + if err != nil { + if err != nil { + return nil + } + database.Store(name, client) + return e.runtime.ToValue(name) + } + return e.newInvalidValueError("connect failed", name) +} +func (e *Extension) close(dbname engine.Value) engine.Value { + name := dbname.String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + client.(*mongo.Client).Disconnect(context.Background()) + database.Delete(name) + return e.runtime.ToValue(true) +} +func (e *Extension) database(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + return e.runtime.ToValue(client.(*mongo.Client).Database(dbName)) +} +func (e *Extension) collection(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + return e.runtime.ToValue(client.(*mongo.Client).Database(dbName).Collection(collectionName)) +} +func (e *Extension) findOne(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + filter := call.Argument(3).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result := client.(*mongo.Client).Database(dbName).Collection(collectionName).FindOne(context.Background(), filter) + if result.Err() != nil { + return e.newInvalidValueError(result.Err().Error(), name) + } + var value interface{} + if err := result.Decode(&value); err != nil { + return e.newInvalidValueError(err.Error(), name) + } + + return e.runtime.ToValue(value) +} +func (e *Extension) find(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + filter := call.Argument(3).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + cursor, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).Find(context.Background(), filter) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + defer cursor.Close(context.Background()) + var results []interface{} + for cursor.Next(context.Background()) { + var result interface{} + if err := cursor.Decode(&result); err != nil { + return e.newInvalidValueError(err.Error(), name) + } + results = append(results, result) + } + if err := cursor.Err(); err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(results) +} +func (e *Extension) insertOne(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + + document := call.Argument(3).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if dbName == "" { + return e.newInvalidValueError("dbName is required", dbName) + } + if collectionName == "" { + return e.newInvalidValueError("collectionName is required", collectionName) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).InsertOne(context.Background(), document) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(result.InsertedID) +} +func (e *Extension) insertMany(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + + document := call.Argument(3).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if dbName == "" { + return e.newInvalidValueError("dbName is required", dbName) + } + if collectionName == "" { + return e.newInvalidValueError("collectionName is required", collectionName) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).InsertOne(context.Background(), document.([]any)) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(result.InsertedID) +} +func (e *Extension) updateOne(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + filter := call.Argument(3).Export() + update := call.Argument(4).Export() + + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).UpdateOne(context.Background(), filter, update) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(result.ModifiedCount) +} +func (e *Extension) updateMany(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + filter := call.Argument(3).Export() + update := call.Argument(4).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).UpdateMany(context.Background(), filter, update) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(result.ModifiedCount) +} +func (e *Extension) deleteOne(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + filter := call.Argument(3).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).DeleteOne(context.Background(), filter) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(result) +} +func (e *Extension) deleteMany(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + dbName := call.Argument(1).String() + collectionName := call.Argument(2).String() + filter := call.Argument(3).Export() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + client, ok := database.Load(name) + if !ok { + return e.newInvalidValueError("database not found", name) + } + if client.(*mongo.Client).Ping(context.Background(), nil) != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + result, err := client.(*mongo.Client).Database(dbName).Collection(collectionName).DeleteMany(context.Background(), filter) + if err != nil { + return e.newInvalidValueError(err.Error(), name) + } + return e.runtime.ToValue(result) +} + +func (e *Extension) list(call engine.FunctionCall) engine.Value { + var dbs []string + database.Range(func(key, value any) bool { + dbs = append(dbs, key.(string)) + return true + }) + return e.runtime.ToValue(dbs) +} + +func (e *Extension) newInvalidValueError(msg, input string) *engine.Object { + o := errors.NewTypeError(e.runtime, "ERR_INVALID_VALUE", msg, input) + o.Set("input", e.runtime.ToValue(input)) + return o +} diff --git a/pkg/xscript/database/redis/export.go b/pkg/xscript/database/redis/export.go new file mode 100644 index 0000000..64cb1c9 --- /dev/null +++ b/pkg/xscript/database/redis/export.go @@ -0,0 +1,45 @@ +package redis + +import ( + "sync" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "redis" + +var database *sync.Map + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("new", e.new) + obj.Set("drop", e.close) + obj.Set("list", e.list) + obj.Set("set", e.set) + obj.Set("get", e.get) + obj.Set("del", e.del) + obj.Set("flush", e.flush) + obj.Set("keys", e.keys) + obj.Set("export", e.export) + obj.Set("import", e.load) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + database = &sync.Map{} + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/database/redis/function.go b/pkg/xscript/database/redis/function.go new file mode 100644 index 0000000..445b730 --- /dev/null +++ b/pkg/xscript/database/redis/function.go @@ -0,0 +1,264 @@ +package redis + +import ( + "encoding/json" + "os" + "sync" + "time" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" +) + +func (e *Extension) new(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + uri := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + + if _, ok := database.Load(name); ok { + return e.runtime.ToValue(name) + } + + rds := &Redis{} + if err := rds.Connect(uri); err != nil { + e.newInvalidValueError("connect error", err.Error()) + } + database.Store(name, rds) + + return e.runtime.ToValue(name) +} + +func (e *Extension) set(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + value := call.Argument(2).String() + ttl := call.Argument(3).ToInteger() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + if value == "" { + return e.newInvalidValueError("value is required", value) + } + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + rds.(*Redis).Set(key, value, time.Duration(ttl)*time.Second) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +func (e *Extension) get(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + if value, err := rds.(*Redis).Get(key); err == nil { + return e.runtime.ToValue(value) + } + } + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) del(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + key := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if key == "" { + return e.newInvalidValueError("key is required", key) + } + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + rds.(*Redis).Del(key) + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +func (e *Extension) keys(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + pattern := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if pattern == "" { + pattern = "*" + } + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + if keys, err := rds.(*Redis).Keys(pattern); err == nil { + return e.runtime.ToValue(keys) + } + } + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) flush(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + rds.(*Redis).Flush() + return e.runtime.ToValue(true) + } + return e.runtime.ToValue(false) +} + +func (e *Extension) list(call engine.FunctionCall) engine.Value { + var dbs []string + database.Range(func(key, value any) bool { + dbs = append(dbs, key.(string)) + return true + }) + return e.runtime.ToValue(dbs) +} + +func (e *Extension) close(name engine.Value) engine.Value { + if name == engine.Undefined() { + return e.newInvalidValueError("name is required", name.String()) + } + + database.LoadAndDelete(name.String()) + return e.runtime.ToValue(true) +} + +// 从指定路径的JSON文件中增量导入数据到LRU缓存中 +func (e *Extension) load(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + filePath := call.Argument(1).String() + ttl := call.Argument(2).ToInteger() + + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if filePath == "" { + return e.newInvalidValueError("filePath is required", filePath) + } + + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + jsonData, err := os.ReadFile(filePath) + if err != nil { + return e.runtime.ToValue(false) + } + + var data map[string]string + err = json.Unmarshal(jsonData, &data) + if err != nil { + return e.runtime.ToValue(false) + } + + // 使用WaitGroup来等待所有goroutine完成 + var wg sync.WaitGroup + wg.Add(len(data)) + + // 使用通道来传递导入结果 + resultCh := make(chan bool) + + // 启动多个goroutine并发导入数据 + for key, value := range data { + go func(k, v string) { + defer wg.Done() + rds.(*Redis).Set(k, v, time.Duration(ttl)*time.Second) + }(key, value) + } + + // 在另一个goroutine中等待所有导入完成,并向通道发送结果 + go func() { + wg.Wait() + resultCh <- true + }() + + // 从通道接收导入结果 + select { + case <-resultCh: + return e.runtime.ToValue(true) + case <-time.After(time.Second * 10): // 设置一个超时时间,避免无限等待 + return e.runtime.ToValue(false) + } + } + + return e.runtime.ToValue(false) +} + +// 导出LRU缓存中的数据到指定路径的JSON文件 +func (e *Extension) export(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + filePath := call.Argument(1).String() + + if name == "" { + return e.newInvalidValueError("name is required", name) + } + if filePath == "" { + return e.newInvalidValueError("filePath is required", filePath) + } + + if rds, ok := database.Load(name); ok { + if _, err := rds.(*Redis).Session.Ping().Result(); err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + // 获取LRU缓存的所有键值对 + keys, _ := rds.(*Redis).Keys("*") + + // 使用WaitGroup来等待所有goroutine完成 + var wg sync.WaitGroup + wg.Add(len(keys)) + + // 启动多个goroutine并发导出数据 + for _, key := range keys { + go func(k string) { + defer wg.Done() + value, _ := rds.(*Redis).Get(k) + exportData := map[string]string{k: value} + jsonData, _ := json.Marshal(exportData) + os.WriteFile(filePath, jsonData, 0644) + }(key) + } + + // 等待所有goroutine完成 + wg.Wait() + + return e.runtime.ToValue(true) + } + + return e.runtime.ToValue(false) +} + +func (e *Extension) newInvalidValueError(msg, input string) *engine.Object { + o := errors.NewTypeError(e.runtime, "ERR_INVALID_VALUE", msg, input) + o.Set("input", e.runtime.ToValue(input)) + return o +} diff --git a/pkg/xscript/database/redis/redis.go b/pkg/xscript/database/redis/redis.go new file mode 100644 index 0000000..e31c8ae --- /dev/null +++ b/pkg/xscript/database/redis/redis.go @@ -0,0 +1,61 @@ +package redis + +import ( + "context" + "time" + + "github.com/go-redis/redis" +) + +type Redis struct { + Session *redis.Client + ctx context.Context +} + +// 初始化连接 +func (r *Redis) Connect(uri string) (err error) { + info, err := redis.ParseURL(uri) + if err != nil { + return err + } + + r.Session = redis.NewClient(info) + + r.ctx = context.Background() + + _, err = r.Session.Ping().Result() + if err != nil { + return err + } + return nil +} + +// 设置项 +func (r *Redis) Set(key string, value any, expire time.Duration) (err error) { + err = r.Session.Set(key, value, expire).Err() + return +} + +// 删除项 +func (r *Redis) Del(key string) (err error) { + err = r.Session.Del(key).Err() + return +} + +// 获取项 +func (r *Redis) Keys(pattern string) (val []string, err error) { + val, err = r.Session.Keys(pattern).Result() + return +} + +// 获取项 +func (r *Redis) Get(key string) (val string, err error) { + val, err = r.Session.Get(key).Result() + return +} + +// 清空表 +func (r *Redis) Flush() (err error) { + err = r.Session.FlushDB().Err() + return +} diff --git a/pkg/xscript/database/sql/export.go b/pkg/xscript/database/sql/export.go new file mode 100644 index 0000000..285e449 --- /dev/null +++ b/pkg/xscript/database/sql/export.go @@ -0,0 +1,70 @@ +package sql + +import ( + "database/sql" + "sync" + + "pandax/pkg/global" + "pandax/pkg/xscript/database/cache" + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "sql" + +var database *sync.Map + +type Extension struct { + runtime *engine.Runtime + mu *sync.Mutex +} + +type Session struct { + xsql *sql.DB + cache *cache.KVStore +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + mu: &sync.Mutex{}, + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("new", e.new) + obj.Set("query", e.query) + obj.Set("queryWithCache", e.queryWithCache) + obj.Set("removeCache", e.removeCache) + obj.Set("exec", e.exec) + obj.Set("transaction", e.transaction) + obj.Set("list", e.list) + obj.Set("close", e.close) + + // 将系统连接的db加入默认数据库 + if global.Conf.Server.DbType == "mysql" { + e.new(engine.FunctionCall{Arguments: []engine.Value{ + e.runtime.ToValue("system_sql"), + e.runtime.ToValue(global.Conf.Server.DbType), + e.runtime.ToValue(global.Conf.Mysql.Dsn()), + }}) + } else { + e.new(engine.FunctionCall{Arguments: []engine.Value{ + e.runtime.ToValue("system_sql"), + e.runtime.ToValue(global.Conf.Server.DbType), + e.runtime.ToValue(global.Conf.Postgresql.PgDsn()), + }}) + } + +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + mu: &sync.Mutex{}, + runtime: runtime, + } +} + +func init() { + database = &sync.Map{} + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/database/sql/function.go b/pkg/xscript/database/sql/function.go new file mode 100644 index 0000000..81a790b --- /dev/null +++ b/pkg/xscript/database/sql/function.go @@ -0,0 +1,373 @@ +package sql + +import ( + "crypto/md5" + "database/sql" + "encoding/hex" + "fmt" + "strings" + "time" + + "pandax/pkg/xscript/database/cache" + "pandax/pkg/xscript/engine" + + _ "github.com/go-sql-driver/mysql" + _ "github.com/go-sqlite/sqlite3" + _ "github.com/lib/pq" +) + +func (e *Extension) new(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + t := call.Argument(1).String() + dsn := call.Argument(2).String() + size := call.Argument(3).ToInteger() + if name == "" { + return e.newInvalidValueError("new", "name is required", name) + } + + if t == "" { + return e.newInvalidValueError(name, "type is required", t) + } + + switch { + case t == "oracle": + t = "godror" + case t == "sqlite" && dsn == "": + dsn = ":memory:" + } + + if dsn == "" { + return e.newInvalidValueError(name, "uri is required", dsn) + } + + e.mu.Lock() + defer e.mu.Unlock() + + if _, ok := database.Load(name); ok { + return e.runtime.ToValue(name) + } + + db, err := sql.Open(t, dsn) // support mysql、postgres、sqlite、mssql、clickhouse + if err != nil { + return e.newInvalidValueError(name, "connect error", err.Error()) + } + + db.SetConnMaxLifetime(5 * time.Minute) + + kv := cache.NewKVStore(int(size)) + + session := &Session{ + xsql: db, + cache: kv, + } + + database.Store(name, session) + + return e.runtime.ToValue(name) +} + +func (e *Extension) close(name engine.Value) engine.Value { + if name == engine.Undefined() { + return e.newInvalidValueError("close", "name is required", name.String()) + } + + e.mu.Lock() + defer e.mu.Unlock() + + db, ok := database.LoadAndDelete(name.String()) + if !ok { + return e.runtime.ToValue(false) + } + + err := db.(*Session).xsql.Close() + if err != nil { + return e.newInvalidValueError("close", err.Error(), name.String()) + } + + go db.(*Session).cache.Purge() + + return e.runtime.ToValue(true) +} + +func (e *Extension) query(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + stmt := call.Argument(1).String() + + if name == "" { + return e.newInvalidValueError(stmt, "name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + db, ok := database.Load(name) + if !ok { + return e.runtime.ToValue(false) + } + + err := db.(*Session).xsql.Ping() + if err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + + var args []interface{} // 使用interface{}类型切片存储参数 + if total := len(call.Arguments); total > 2 { + for i := 2; i < total; i++ { + args = append(args, call.Argument(i).String()) + } + } + + // 执行查询语句时使用参数化查询,避免SQL注入风险 + rows, err := db.(*Session).xsql.Query(stmt, args...) + if err != nil { + return e.newInvalidValueError(stmt, "query error", err.Error()) + } + defer rows.Close() + + columns, _ := rows.Columns() + count := len(columns) + + result := make([]map[string]interface{}, 0) // 使用interface{}类型存储查询结果 + values := make([]interface{}, count) + valuePtrs := make([]interface{}, count) + for rows.Next() { + for i := 0; i < count; i++ { + valuePtrs[i] = &values[i] + } + rows.Scan(valuePtrs...) + + entry := make(map[string]interface{}) + for i, col := range columns { + var v interface{} + val := values[i] + + switch val := val.(type) { + case []byte: + v = string(val) + default: + v = val + } + + entry[col] = v + } + + result = append(result, entry) + } + + return e.runtime.ToValue(result) +} + +func (e *Extension) exec(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + stmt := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError(stmt, "name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + db, ok := database.Load(name) + if !ok { + return e.runtime.ToValue(false) + } + + err := db.(*Session).xsql.Ping() + if err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + + var args []interface{} // 使用interface{}类型切片存储参数 + if total := len(call.Arguments); total > 2 { + for i := 2; i < total; i++ { + args = append(args, call.Argument(i).String()) + } + } + + // 执行SQL语句时使用参数化查询,避免SQL注入风险 + result, err := db.(*Session).xsql.Exec(stmt, args...) + if err != nil { + return e.newInvalidValueError(stmt, "exec error", err.Error()) + } + + if strings.HasPrefix(strings.ToLower(stmt), "insert") { + id, err := result.LastInsertId() + if err != nil { + return e.newInvalidValueError(stmt, "insert error", err.Error()) + } + + return e.runtime.ToValue(id) + } + + return e.runtime.ToValue(true) +} + +func (e *Extension) queryWithCache(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + ttl := call.Argument(1).ToInteger() + stmt := call.Argument(2).String() + + if name == "" { + return e.newInvalidValueError(stmt, "name is required", name) + } + + e.mu.Lock() + defer e.mu.Unlock() + + db, ok := database.Load(name) + if !ok { + return e.runtime.ToValue(false) + } + + var args []any + if total := len(call.Arguments); total > 3 { + for i := 3; i < total; i++ { + args = append(args, call.Argument(i).String()) + } + } + + var mark string + hash := md5.New() + hash.Write([]byte(fmt.Sprintf("%s:%s", stmt, args))) + mark = hex.EncodeToString(hash.Sum(nil)) + + // cache + if value, ok := db.(*Session).cache.Get(mark); ok { + if val, ok := value.([]map[string]any); ok { + return e.runtime.ToValue(val) + } + } + + // query + rows, err := db.(*Session).xsql.Query(stmt, args...) + if err != nil { + return e.newInvalidValueError(stmt, "query error", err.Error()) + } + defer rows.Close() + + columns, _ := rows.Columns() + count := len(columns) + + result := make([]map[string]any, 0) + values := make([]any, count) + valuePtrs := make([]any, count) + for rows.Next() { + for i := 0; i < count; i++ { + valuePtrs[i] = &values[i] + } + rows.Scan(valuePtrs...) + + entry := make(map[string]any) + for i, col := range columns { + var v any + val := values[i] + + switch val := val.(type) { + case []byte: + v = string(val) + default: + v = val + } + + entry[col] = v + } + + result = append(result, entry) + } + + // set cache + db.(*Session).cache.Set(mark, result, time.Duration(ttl)*time.Second) + + return e.runtime.ToValue(result) +} + +func (e *Extension) removeCache(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + stmt := call.Argument(1).String() + + if name == "" { + return e.newInvalidValueError(stmt, "name is required", name) + } + + db, ok := database.Load(name) + if !ok { + return e.runtime.ToValue(false) + } + + err := db.(*Session).xsql.Ping() + if err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + + var args []any + if total := len(call.Arguments); total > 2 { + for i := 2; i < total; i++ { + args = append(args, call.Argument(i).String()) + } + } + + mark := fmt.Sprintf("%s:%s", stmt, args) + hash := md5.New() + hash.Write([]byte(mark)) + mark = hex.EncodeToString(hash.Sum(nil)) + + go db.(*Session).cache.Remove(mark) + + return e.runtime.ToValue(true) +} + +func (e *Extension) transaction(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + stmts := call.Argument(1).String() + if name == "" { + return e.newInvalidValueError(stmts, "name is required", name) + } + + db, ok := database.Load(name) + if !ok { + return e.runtime.ToValue(false) + } + + err := db.(*Session).xsql.Ping() + if err != nil { + e.close(e.runtime.ToValue(name)) + return e.runtime.ToValue(false) + } + + tx, err := db.(*Session).xsql.Begin() + if err != nil { + return e.newInvalidValueError(stmts, "begin error", err.Error()) + } + defer tx.Rollback() + + for _, stmt := range strings.Split(stmts, ";") { + _, err = tx.Exec(stmt) + if err != nil { + tx.Rollback() + return e.newInvalidValueError(stmt, "exec error", err.Error()) + } + } + + return e.runtime.ToValue(true) +} + +func (e *Extension) list(call engine.FunctionCall) engine.Value { + var dbs []string + + database.Range(func(key, value any) bool { + dbs = append(dbs, key.(string)) + return true + }) + + return e.runtime.ToValue(dbs) +} + +func (e *Extension) newInvalidValueError(stmt, msg, input string) engine.Value { + // o := errors.NewTypeError(e.runtime, "ERR_INVALID_VALUE", msg, input) + // o.Set("input", e.runtime.ToValue(input)) + fmt.Printf("\n* %s - %s - %s\n\n", stmt, msg, input) + return e.runtime.ToValue(false) +} diff --git a/pkg/xscript/encoding/convert.go b/pkg/xscript/encoding/convert.go new file mode 100644 index 0000000..5fde84d --- /dev/null +++ b/pkg/xscript/encoding/convert.go @@ -0,0 +1,10 @@ +package encoding + +import ( + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" +) + +func (e *Extension) from(value engine.Value) engine.Value { + return buffer.Format(e.runtime, buffer.Conv(e.runtime, value)) +} diff --git a/pkg/xscript/encoding/encoding.go b/pkg/xscript/encoding/encoding.go new file mode 100644 index 0000000..6f32f5b --- /dev/null +++ b/pkg/xscript/encoding/encoding.go @@ -0,0 +1,124 @@ +package encoding + +import ( + "fmt" + "strings" + + "pandax/pkg/xscript/engine" + + "golang.org/x/text/encoding/charmap" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/encoding/traditionalchinese" + "golang.org/x/text/encoding/unicode" +) + +func (e *Extension) encodeChipher(call engine.FunctionCall) engine.Value { + chipherType := call.Argument(0).String() + var chipher = map[string]any{ + "type": chipherType, + "encode": func(call engine.FunctionCall) engine.Value { + return e.encoderFunc(chipherType, call.Argument(0).String()) + }, + } + + return e.runtime.ToValue(chipher) +} + +func (e *Extension) decodeChipher(call engine.FunctionCall) engine.Value { + chipherType := call.Argument(0).String() + var chipher = map[string]any{ + "type": chipherType, + "encode": func(call engine.FunctionCall) engine.Value { + return e.decoderFunc(chipherType, call.Argument(0).String()) + }, + } + + return e.runtime.ToValue(chipher) +} + +func (e *Extension) encoderFunc(chipher, str string) engine.Value { + switch strings.ToLower(chipher) { + case "utf-8": + encoded, err := unicode.UTF8.NewEncoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "utf-16", "utf-16le": + if encoded, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewEncoder().String(str); err == nil { + return e.runtime.ToValue(encoded) + } + case "utf-16be": + encoded, err := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewEncoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "windows-1252": + encoded, err := charmap.Windows1252.NewEncoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "gbk": + encoded, err := simplifiedchinese.GBK.NewEncoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "gb2312": + encoded, err := simplifiedchinese.HZGB2312.NewEncoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "iso-8859-1": + if encoded, err := charmap.ISO8859_1.NewEncoder().String(str); err == nil { + return e.runtime.ToValue(encoded) + } + case "big5": + if encoded, err := traditionalchinese.Big5.NewEncoder().String(str); err == nil { + return e.runtime.ToValue(encoded) + } + } + + fmt.Println("encoding.TextEncoder not support the chipher:", chipher) + return nil +} + +func (e *Extension) decoderFunc(chipher, str string) engine.Value { + switch strings.ToLower(chipher) { + case "utf-8": + return e.runtime.ToValue(str) + case "utf-16", "utf-16be": + encoded, err := unicode.UTF16(unicode.BigEndian, unicode.IgnoreBOM).NewDecoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "utf-16le": + if encoded, err := unicode.UTF16(unicode.LittleEndian, unicode.IgnoreBOM).NewDecoder().String(str); err == nil { + return e.runtime.ToValue(encoded) + } + case "windows-1252": + encoded, err := charmap.Windows1252.NewDecoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "gbk": + encoded, err := simplifiedchinese.GBK.NewDecoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "gb2312": + encoded, err := simplifiedchinese.HZGB2312.NewDecoder().String(str) + if err == nil { + return e.runtime.ToValue(encoded) + } + case "iso-8859-1": + if encoded, err := charmap.ISO8859_1.NewDecoder().String(str); err == nil { + return e.runtime.ToValue(encoded) + } + case "big5": + if encoded, err := traditionalchinese.Big5.NewDecoder().String(str); err == nil { + return e.runtime.ToValue(encoded) + } + } + + fmt.Println("encoding.TextDecoder not support the chipher:", chipher) + return nil +} diff --git a/pkg/xscript/encoding/export.go b/pkg/xscript/encoding/export.go new file mode 100644 index 0000000..66f7866 --- /dev/null +++ b/pkg/xscript/encoding/export.go @@ -0,0 +1,34 @@ +package encoding + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "encoding" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + obj.Set("from", e.from) + + runtime.Set("TextEncoder", e.encodeChipher) + runtime.Set("TextDecoder", e.decodeChipher) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + // require.RegisterCoreModule("", Require) + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/engine/array.go b/pkg/xscript/engine/array.go new file mode 100644 index 0000000..9e365b9 --- /dev/null +++ b/pkg/xscript/engine/array.go @@ -0,0 +1,565 @@ +package engine + +import ( + "fmt" + "math" + "math/bits" + "reflect" + "strconv" + + "pandax/pkg/xscript/engine/unistring" +) + +type arrayIterObject struct { + baseObject + obj *Object + nextIdx int64 + kind iterationKind +} + +func (ai *arrayIterObject) next() Value { + if ai.obj == nil { + return ai.val.runtime.createIterResultObject(_undefined, true) + } + if ta, ok := ai.obj.self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + } + l := toLength(ai.obj.self.getStr("length", nil)) + index := ai.nextIdx + if index >= l { + ai.obj = nil + return ai.val.runtime.createIterResultObject(_undefined, true) + } + ai.nextIdx++ + idxVal := valueInt(index) + if ai.kind == iterationKindKey { + return ai.val.runtime.createIterResultObject(idxVal, false) + } + elementValue := nilSafe(ai.obj.self.getIdx(idxVal, nil)) + var result Value + if ai.kind == iterationKindValue { + result = elementValue + } else { + result = ai.val.runtime.newArrayValues([]Value{idxVal, elementValue}) + } + return ai.val.runtime.createIterResultObject(result, false) +} + +func (r *Runtime) createArrayIterator(iterObj *Object, kind iterationKind) Value { + o := &Object{runtime: r} + + ai := &arrayIterObject{ + obj: iterObj, + kind: kind, + } + ai.class = classObject + ai.val = o + ai.extensible = true + o.self = ai + ai.prototype = r.getArrayIteratorPrototype() + ai.init() + + return o +} + +type arrayObject struct { + baseObject + values []Value + length uint32 + objCount int + propValueCount int + lengthProp valueProperty +} + +func (a *arrayObject) init() { + a.baseObject.init() + a.lengthProp.writable = true + + a._put("length", &a.lengthProp) +} + +func (a *arrayObject) _setLengthInt(l uint32, throw bool) bool { + ret := true + if l <= a.length { + if a.propValueCount > 0 { + // Slow path + for i := len(a.values) - 1; i >= int(l); i-- { + if prop, ok := a.values[i].(*valueProperty); ok { + if !prop.configurable { + l = uint32(i) + 1 + ret = false + break + } + a.propValueCount-- + } + } + } + } + if l <= uint32(len(a.values)) { + if l >= 16 && l < uint32(cap(a.values))>>2 { + ar := make([]Value, l) + copy(ar, a.values) + a.values = ar + } else { + ar := a.values[l:len(a.values)] + for i := range ar { + ar[i] = nil + } + a.values = a.values[:l] + } + } + a.length = l + if !ret { + a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length") + } + return ret +} + +func (a *arrayObject) setLengthInt(l uint32, throw bool) bool { + if l == a.length { + return true + } + if !a.lengthProp.writable { + a.val.runtime.typeErrorResult(throw, "length is not writable") + return false + } + return a._setLengthInt(l, throw) +} + +func (a *arrayObject) setLength(v uint32, throw bool) bool { + if !a.lengthProp.writable { + a.val.runtime.typeErrorResult(throw, "length is not writable") + return false + } + return a._setLengthInt(v, throw) +} + +func (a *arrayObject) getIdx(idx valueInt, receiver Value) Value { + prop := a.getOwnPropIdx(idx) + if prop == nil { + if a.prototype != nil { + if receiver == nil { + return a.prototype.self.getIdx(idx, a.val) + } + return a.prototype.self.getIdx(idx, receiver) + } + } + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(a.val) + } + return prop.get(receiver) + } + return prop +} + +func (a *arrayObject) getOwnPropStr(name unistring.String) Value { + if len(a.values) > 0 { + if i := strToArrayIdx(name); i != math.MaxUint32 { + if i < uint32(len(a.values)) { + return a.values[i] + } + } + } + if name == "length" { + return a.getLengthProp() + } + return a.baseObject.getOwnPropStr(name) +} + +func (a *arrayObject) getOwnPropIdx(idx valueInt) Value { + if i := toIdx(idx); i != math.MaxUint32 { + if i < uint32(len(a.values)) { + return a.values[i] + } + return nil + } + + return a.baseObject.getOwnPropStr(idx.string()) +} + +func (a *arrayObject) sortLen() int { + return len(a.values) +} + +func (a *arrayObject) sortGet(i int) Value { + v := a.values[i] + if p, ok := v.(*valueProperty); ok { + v = p.get(a.val) + } + return v +} + +func (a *arrayObject) swap(i int, j int) { + a.values[i], a.values[j] = a.values[j], a.values[i] +} + +func (a *arrayObject) getStr(name unistring.String, receiver Value) Value { + return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) +} + +func (a *arrayObject) getLengthProp() *valueProperty { + a.lengthProp.value = intToValue(int64(a.length)) + return &a.lengthProp +} + +func (a *arrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { + if i := toIdx(idx); i != math.MaxUint32 { + return a._setOwnIdx(i, val, throw) + } else { + return a.baseObject.setOwnStr(idx.string(), val, throw) + } +} + +func (a *arrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool { + var prop Value + if idx < uint32(len(a.values)) { + prop = a.values[idx] + } + + if prop == nil { + if proto := a.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok { + return res + } + } + // new property + if !a.extensible { + a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx) + return false + } else { + if idx >= a.length { + if !a.setLengthInt(idx+1, throw) { + return false + } + } + if idx >= uint32(len(a.values)) { + if !a.expand(idx) { + a.val.self.(*sparseArrayObject).add(idx, val) + return true + } + } + a.objCount++ + } + } else { + if prop, ok := prop.(*valueProperty); ok { + if !prop.isWritable() { + a.val.runtime.typeErrorResult(throw) + return false + } + prop.set(a.val, val) + return true + } + } + a.values[idx] = val + return true +} + +func (a *arrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._setOwnIdx(idx, val, throw) + } else { + if name == "length" { + return a.setLength(a.val.runtime.toLengthUint32(val), throw) + } else { + return a.baseObject.setOwnStr(name, val, throw) + } + } +} + +func (a *arrayObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { + return a._setForeignIdx(idx, a.getOwnPropIdx(idx), val, receiver, throw) +} + +func (a *arrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw) +} + +type arrayPropIter struct { + a *arrayObject + limit int + idx int +} + +func (i *arrayPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.a.values) && i.idx < i.limit { + name := asciiString(strconv.Itoa(i.idx)) + prop := i.a.values[i.idx] + i.idx++ + if prop != nil { + return propIterItem{name: name, value: prop}, i.next + } + } + + return i.a.baseObject.iterateStringKeys()() +} + +func (a *arrayObject) iterateStringKeys() iterNextFunc { + return (&arrayPropIter{ + a: a, + limit: len(a.values), + }).next +} + +func (a *arrayObject) stringKeys(all bool, accum []Value) []Value { + for i, prop := range a.values { + name := strconv.Itoa(i) + if prop != nil { + if !all { + if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { + continue + } + } + accum = append(accum, asciiString(name)) + } + } + return a.baseObject.stringKeys(all, accum) +} + +func (a *arrayObject) hasOwnPropertyStr(name unistring.String) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return idx < uint32(len(a.values)) && a.values[idx] != nil + } else { + return a.baseObject.hasOwnPropertyStr(name) + } +} + +func (a *arrayObject) hasOwnPropertyIdx(idx valueInt) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + return idx < uint32(len(a.values)) && a.values[idx] != nil + } + return a.baseObject.hasOwnPropertyStr(idx.string()) +} + +func (a *arrayObject) hasPropertyIdx(idx valueInt) bool { + if a.hasOwnPropertyIdx(idx) { + return true + } + + if a.prototype != nil { + return a.prototype.self.hasPropertyIdx(idx) + } + + return false +} + +func (a *arrayObject) expand(idx uint32) bool { + targetLen := idx + 1 + if targetLen > uint32(len(a.values)) { + if targetLen < uint32(cap(a.values)) { + a.values = a.values[:targetLen] + } else { + if idx > 4096 && (a.objCount == 0 || idx/uint32(a.objCount) > 10) { + //log.Println("Switching standard->sparse") + sa := &sparseArrayObject{ + baseObject: a.baseObject, + length: a.length, + propValueCount: a.propValueCount, + } + sa.setValues(a.values, a.objCount+1) + sa.val.self = sa + sa.lengthProp.writable = a.lengthProp.writable + sa._put("length", &sa.lengthProp) + return false + } else { + if bits.UintSize == 32 { + if targetLen >= math.MaxInt32 { + panic(a.val.runtime.NewTypeError("Array index overflows int")) + } + } + tl := int(targetLen) + newValues := make([]Value, tl, growCap(tl, len(a.values), cap(a.values))) + copy(newValues, a.values) + a.values = newValues + } + } + } + return true +} + +func (r *Runtime) defineArrayLength(prop *valueProperty, descr PropertyDescriptor, setter func(uint32, bool) bool, throw bool) bool { + var newLen uint32 + ret := true + if descr.Value != nil { + newLen = r.toLengthUint32(descr.Value) + } + + if descr.Configurable == FLAG_TRUE || descr.Enumerable == FLAG_TRUE || descr.Getter != nil || descr.Setter != nil { + ret = false + goto Reject + } + + if descr.Value != nil { + oldLen := uint32(prop.value.ToInteger()) + if oldLen != newLen { + ret = setter(newLen, false) + } + } else { + ret = true + } + + if descr.Writable != FLAG_NOT_SET { + w := descr.Writable.Bool() + if prop.writable { + prop.writable = w + } else { + if w { + ret = false + goto Reject + } + } + } + +Reject: + if !ret { + r.typeErrorResult(throw, "Cannot redefine property: length") + } + + return ret +} + +func (a *arrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool { + var existing Value + if idx < uint32(len(a.values)) { + existing = a.values[idx] + } + prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw) + if ok { + if idx >= a.length { + if !a.setLengthInt(idx+1, throw) { + return false + } + } + if a.expand(idx) { + a.values[idx] = prop + a.objCount++ + if _, ok := prop.(*valueProperty); ok { + a.propValueCount++ + } + } else { + a.val.self.(*sparseArrayObject).add(idx, prop) + } + } + return ok +} + +func (a *arrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._defineIdxProperty(idx, descr, throw) + } + if name == "length" { + return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw) + } + return a.baseObject.defineOwnPropertyStr(name, descr, throw) +} + +func (a *arrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + return a._defineIdxProperty(idx, descr, throw) + } + return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw) +} + +func (a *arrayObject) _deleteIdxProp(idx uint32, throw bool) bool { + if idx < uint32(len(a.values)) { + if v := a.values[idx]; v != nil { + if p, ok := v.(*valueProperty); ok { + if !p.configurable { + a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.toString()) + return false + } + a.propValueCount-- + } + a.values[idx] = nil + a.objCount-- + } + } + return true +} + +func (a *arrayObject) deleteStr(name unistring.String, throw bool) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._deleteIdxProp(idx, throw) + } + return a.baseObject.deleteStr(name, throw) +} + +func (a *arrayObject) deleteIdx(idx valueInt, throw bool) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + return a._deleteIdxProp(idx, throw) + } + return a.baseObject.deleteStr(idx.string(), throw) +} + +func (a *arrayObject) export(ctx *objectExportCtx) any { + if v, exists := ctx.get(a.val); exists { + return v + } + arr := make([]any, a.length) + ctx.put(a.val, arr) + if a.propValueCount == 0 && a.length == uint32(len(a.values)) && uint32(a.objCount) == a.length { + for i, v := range a.values { + if v != nil { + arr[i] = exportValue(v, ctx) + } + } + } else { + for i := uint32(0); i < a.length; i++ { + v := a.getIdx(valueInt(i), nil) + if v != nil { + arr[i] = exportValue(v, ctx) + } + } + } + return arr +} + +func (a *arrayObject) exportType() reflect.Type { + return reflectTypeArray +} + +func (a *arrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + r := a.val.runtime + if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil { + l := toIntStrict(int64(a.length)) + if typ.Kind() == reflect.Array { + if dst.Len() != l { + return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len()) + } + } else { + dst.Set(reflect.MakeSlice(typ, l, l)) + } + ctx.putTyped(a.val, typ, dst.Interface()) + for i := 0; i < l; i++ { + if i >= len(a.values) { + break + } + val := a.values[i] + if p, ok := val.(*valueProperty); ok { + val = p.get(a.val) + } + err := r.toReflectValue(val, dst.Index(i), ctx) + if err != nil { + return fmt.Errorf("could not convert array element %v to %v at %d: %w", val, typ, i, err) + } + } + return nil + } + return a.baseObject.exportToArrayOrSlice(dst, typ, ctx) +} + +func (a *arrayObject) setValuesFromSparse(items []sparseArrayItem, newMaxIdx int) { + a.values = make([]Value, newMaxIdx+1) + for _, item := range items { + a.values[item.idx] = item.value + } + a.objCount = len(items) +} + +func toIdx(v valueInt) uint32 { + if v >= 0 && v < math.MaxUint32 { + return uint32(v) + } + return math.MaxUint32 +} diff --git a/pkg/xscript/engine/array_sparse.go b/pkg/xscript/engine/array_sparse.go new file mode 100644 index 0000000..8489611 --- /dev/null +++ b/pkg/xscript/engine/array_sparse.go @@ -0,0 +1,500 @@ +package engine + +import ( + "fmt" + "math" + "math/bits" + "reflect" + "sort" + "strconv" + + "pandax/pkg/xscript/engine/unistring" +) + +type sparseArrayItem struct { + idx uint32 + value Value +} + +type sparseArrayObject struct { + baseObject + items []sparseArrayItem + length uint32 + propValueCount int + lengthProp valueProperty +} + +func (a *sparseArrayObject) findIdx(idx uint32) int { + return sort.Search(len(a.items), func(i int) bool { + return a.items[i].idx >= idx + }) +} + +func (a *sparseArrayObject) _setLengthInt(l uint32, throw bool) bool { + ret := true + if l <= a.length { + if a.propValueCount > 0 { + // Slow path + for i := len(a.items) - 1; i >= 0; i-- { + item := a.items[i] + if item.idx <= l { + break + } + if prop, ok := item.value.(*valueProperty); ok { + if !prop.configurable { + l = item.idx + 1 + ret = false + break + } + a.propValueCount-- + } + } + } + } + + idx := a.findIdx(l) + + aa := a.items[idx:] + for i := range aa { + aa[i].value = nil + } + a.items = a.items[:idx] + a.length = l + if !ret { + a.val.runtime.typeErrorResult(throw, "Cannot redefine property: length") + } + return ret +} + +func (a *sparseArrayObject) setLengthInt(l uint32, throw bool) bool { + if l == a.length { + return true + } + if !a.lengthProp.writable { + a.val.runtime.typeErrorResult(throw, "length is not writable") + return false + } + return a._setLengthInt(l, throw) +} + +func (a *sparseArrayObject) setLength(v uint32, throw bool) bool { + if !a.lengthProp.writable { + a.val.runtime.typeErrorResult(throw, "length is not writable") + return false + } + return a._setLengthInt(v, throw) +} + +func (a *sparseArrayObject) _getIdx(idx uint32) Value { + i := a.findIdx(idx) + if i < len(a.items) && a.items[i].idx == idx { + return a.items[i].value + } + + return nil +} + +func (a *sparseArrayObject) getStr(name unistring.String, receiver Value) Value { + return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) +} + +func (a *sparseArrayObject) getIdx(idx valueInt, receiver Value) Value { + prop := a.getOwnPropIdx(idx) + if prop == nil { + if a.prototype != nil { + if receiver == nil { + return a.prototype.self.getIdx(idx, a.val) + } + return a.prototype.self.getIdx(idx, receiver) + } + } + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(a.val) + } + return prop.get(receiver) + } + return prop +} + +func (a *sparseArrayObject) getLengthProp() *valueProperty { + a.lengthProp.value = intToValue(int64(a.length)) + return &a.lengthProp +} + +func (a *sparseArrayObject) getOwnPropStr(name unistring.String) Value { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._getIdx(idx) + } + if name == "length" { + return a.getLengthProp() + } + return a.baseObject.getOwnPropStr(name) +} + +func (a *sparseArrayObject) getOwnPropIdx(idx valueInt) Value { + if idx := toIdx(idx); idx != math.MaxUint32 { + return a._getIdx(idx) + } + return a.baseObject.getOwnPropStr(idx.string()) +} + +func (a *sparseArrayObject) add(idx uint32, val Value) { + i := a.findIdx(idx) + a.items = append(a.items, sparseArrayItem{}) + copy(a.items[i+1:], a.items[i:]) + a.items[i] = sparseArrayItem{ + idx: idx, + value: val, + } +} + +func (a *sparseArrayObject) _setOwnIdx(idx uint32, val Value, throw bool) bool { + var prop Value + i := a.findIdx(idx) + if i < len(a.items) && a.items[i].idx == idx { + prop = a.items[i].value + } + + if prop == nil { + if proto := a.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, ok := proto.self.setForeignIdx(valueInt(idx), val, a.val, throw); ok { + return res + } + } + + // new property + if !a.extensible { + a.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx) + return false + } + + if idx >= a.length { + if !a.setLengthInt(idx+1, throw) { + return false + } + } + + if a.expand(idx) { + a.items = append(a.items, sparseArrayItem{}) + copy(a.items[i+1:], a.items[i:]) + a.items[i] = sparseArrayItem{ + idx: idx, + value: val, + } + } else { + ar := a.val.self.(*arrayObject) + ar.values[idx] = val + ar.objCount++ + return true + } + } else { + if prop, ok := prop.(*valueProperty); ok { + if !prop.isWritable() { + a.val.runtime.typeErrorResult(throw) + return false + } + prop.set(a.val, val) + } else { + a.items[i].value = val + } + } + return true +} + +func (a *sparseArrayObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._setOwnIdx(idx, val, throw) + } else { + if name == "length" { + return a.setLength(a.val.runtime.toLengthUint32(val), throw) + } else { + return a.baseObject.setOwnStr(name, val, throw) + } + } +} + +func (a *sparseArrayObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + return a._setOwnIdx(idx, val, throw) + } + + return a.baseObject.setOwnStr(idx.string(), val, throw) +} + +func (a *sparseArrayObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw) +} + +func (a *sparseArrayObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) { + return a._setForeignIdx(name, a.getOwnPropIdx(name), val, receiver, throw) +} + +type sparseArrayPropIter struct { + a *sparseArrayObject + idx int +} + +func (i *sparseArrayPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.a.items) { + name := asciiString(strconv.Itoa(int(i.a.items[i.idx].idx))) + prop := i.a.items[i.idx].value + i.idx++ + if prop != nil { + return propIterItem{name: name, value: prop}, i.next + } + } + + return i.a.baseObject.iterateStringKeys()() +} + +func (a *sparseArrayObject) iterateStringKeys() iterNextFunc { + return (&sparseArrayPropIter{ + a: a, + }).next +} + +func (a *sparseArrayObject) stringKeys(all bool, accum []Value) []Value { + if all { + for _, item := range a.items { + accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10))) + } + } else { + for _, item := range a.items { + if prop, ok := item.value.(*valueProperty); ok && !prop.enumerable { + continue + } + accum = append(accum, asciiString(strconv.FormatUint(uint64(item.idx), 10))) + } + } + + return a.baseObject.stringKeys(all, accum) +} + +func (a *sparseArrayObject) setValues(values []Value, objCount int) { + a.items = make([]sparseArrayItem, 0, objCount) + for i, val := range values { + if val != nil { + a.items = append(a.items, sparseArrayItem{ + idx: uint32(i), + value: val, + }) + } + } +} + +func (a *sparseArrayObject) hasOwnPropertyStr(name unistring.String) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + i := a.findIdx(idx) + return i < len(a.items) && a.items[i].idx == idx + } else { + return a.baseObject.hasOwnPropertyStr(name) + } +} + +func (a *sparseArrayObject) hasOwnPropertyIdx(idx valueInt) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + i := a.findIdx(idx) + return i < len(a.items) && a.items[i].idx == idx + } + + return a.baseObject.hasOwnPropertyStr(idx.string()) +} + +func (a *sparseArrayObject) hasPropertyIdx(idx valueInt) bool { + if a.hasOwnPropertyIdx(idx) { + return true + } + + if a.prototype != nil { + return a.prototype.self.hasPropertyIdx(idx) + } + + return false +} + +func (a *sparseArrayObject) expand(idx uint32) bool { + if l := len(a.items); l >= 1024 { + if ii := a.items[l-1].idx; ii > idx { + idx = ii + } + if (bits.UintSize == 64 || idx < math.MaxInt32) && int(idx)>>3 < l { + //log.Println("Switching sparse->standard") + ar := &arrayObject{ + baseObject: a.baseObject, + length: a.length, + propValueCount: a.propValueCount, + } + ar.setValuesFromSparse(a.items, int(idx)) + ar.val.self = ar + ar.lengthProp.writable = a.lengthProp.writable + a._put("length", &ar.lengthProp) + return false + } + } + return true +} + +func (a *sparseArrayObject) _defineIdxProperty(idx uint32, desc PropertyDescriptor, throw bool) bool { + var existing Value + i := a.findIdx(idx) + if i < len(a.items) && a.items[i].idx == idx { + existing = a.items[i].value + } + prop, ok := a.baseObject._defineOwnProperty(unistring.String(strconv.FormatUint(uint64(idx), 10)), existing, desc, throw) + if ok { + if idx >= a.length { + if !a.setLengthInt(idx+1, throw) { + return false + } + } + if i >= len(a.items) || a.items[i].idx != idx { + if a.expand(idx) { + a.items = append(a.items, sparseArrayItem{}) + copy(a.items[i+1:], a.items[i:]) + a.items[i] = sparseArrayItem{ + idx: idx, + value: prop, + } + if idx >= a.length { + a.length = idx + 1 + } + } else { + a.val.self.(*arrayObject).values[idx] = prop + } + } else { + a.items[i].value = prop + } + if _, ok := prop.(*valueProperty); ok { + a.propValueCount++ + } + } + return ok +} + +func (a *sparseArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._defineIdxProperty(idx, descr, throw) + } + if name == "length" { + return a.val.runtime.defineArrayLength(a.getLengthProp(), descr, a.setLength, throw) + } + return a.baseObject.defineOwnPropertyStr(name, descr, throw) +} + +func (a *sparseArrayObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + return a._defineIdxProperty(idx, descr, throw) + } + return a.baseObject.defineOwnPropertyStr(idx.string(), descr, throw) +} + +func (a *sparseArrayObject) _deleteIdxProp(idx uint32, throw bool) bool { + i := a.findIdx(idx) + if i < len(a.items) && a.items[i].idx == idx { + if p, ok := a.items[i].value.(*valueProperty); ok { + if !p.configurable { + a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.toString()) + return false + } + a.propValueCount-- + } + copy(a.items[i:], a.items[i+1:]) + a.items[len(a.items)-1].value = nil + a.items = a.items[:len(a.items)-1] + } + return true +} + +func (a *sparseArrayObject) deleteStr(name unistring.String, throw bool) bool { + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + return a._deleteIdxProp(idx, throw) + } + return a.baseObject.deleteStr(name, throw) +} + +func (a *sparseArrayObject) deleteIdx(idx valueInt, throw bool) bool { + if idx := toIdx(idx); idx != math.MaxUint32 { + return a._deleteIdxProp(idx, throw) + } + return a.baseObject.deleteStr(idx.string(), throw) +} + +func (a *sparseArrayObject) sortLen() int { + if len(a.items) > 0 { + return toIntStrict(int64(a.items[len(a.items)-1].idx) + 1) + } + + return 0 +} + +func (a *sparseArrayObject) export(ctx *objectExportCtx) any { + if v, exists := ctx.get(a.val); exists { + return v + } + arr := make([]any, a.length) + ctx.put(a.val, arr) + var prevIdx uint32 + for _, item := range a.items { + idx := item.idx + for i := prevIdx; i < idx; i++ { + if a.prototype != nil { + if v := a.prototype.self.getIdx(valueInt(i), nil); v != nil { + arr[i] = exportValue(v, ctx) + } + } + } + v := item.value + if v != nil { + if prop, ok := v.(*valueProperty); ok { + v = prop.get(a.val) + } + arr[idx] = exportValue(v, ctx) + } + prevIdx = idx + 1 + } + for i := prevIdx; i < a.length; i++ { + if a.prototype != nil { + if v := a.prototype.self.getIdx(valueInt(i), nil); v != nil { + arr[i] = exportValue(v, ctx) + } + } + } + return arr +} + +func (a *sparseArrayObject) exportType() reflect.Type { + return reflectTypeArray +} + +func (a *sparseArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + r := a.val.runtime + if iter := a.getSym(SymIterator, nil); iter == r.getArrayValues() || iter == nil { + l := toIntStrict(int64(a.length)) + if typ.Kind() == reflect.Array { + if dst.Len() != l { + return fmt.Errorf("cannot convert an Array into an array, lengths mismatch (have %d, need %d)", l, dst.Len()) + } + } else { + dst.Set(reflect.MakeSlice(typ, l, l)) + } + ctx.putTyped(a.val, typ, dst.Interface()) + for _, item := range a.items { + val := item.value + if p, ok := val.(*valueProperty); ok { + val = p.get(a.val) + } + idx := toIntStrict(int64(item.idx)) + if idx >= l { + break + } + err := r.toReflectValue(val, dst.Index(idx), ctx) + if err != nil { + return fmt.Errorf("could not convert array element %v to %v at %d: %w", item.value, typ, idx, err) + } + } + return nil + } + return a.baseObject.exportToArrayOrSlice(dst, typ, ctx) +} diff --git a/pkg/xscript/engine/array_sparse_test.go b/pkg/xscript/engine/array_sparse_test.go new file mode 100644 index 0000000..e4f7777 --- /dev/null +++ b/pkg/xscript/engine/array_sparse_test.go @@ -0,0 +1,264 @@ +package engine + +import ( + "testing" +) + +func TestSparseArraySetLengthWithPropItems(t *testing.T) { + const SCRIPT = ` + var a = [1,2,3,4]; + a[100000] = 5; + var thrown = false; + + Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false}); + try { + Object.defineProperty(a, "length", {value: 0, writable: false}); + } catch (e) { + thrown = e instanceof TypeError; + } + thrown && a.length === 3; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestSparseArraySwitch(t *testing.T) { + vm := New() + _, err := vm.RunString(` + var a = []; + a[20470] = 5; // switch to sparse`) + if err != nil { + t.Fatal(err) + } + a := vm.Get("a").(*Object) + if _, ok := a.self.(*sparseArrayObject); !ok { + t.Fatal("1: array is not sparse") + } + _, err = vm.RunString(` + var cutoffIdx = Math.round(20470 - 20470/8); + for (var i = a.length - 1; i >= cutoffIdx; i--) { + a[i] = i; + } + + // At this point it will have switched to a normal array + if (a.length != 20471) { + throw new Error("Invalid length: " + a.length); + } + + for (var i = 0; i < cutoffIdx; i++) { + if (a[i] !== undefined) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + } + + for (var i = cutoffIdx; i < a.length; i++) { + if (a[i] !== i) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + }`) + if err != nil { + t.Fatal(err) + } + if _, ok := a.self.(*arrayObject); !ok { + t.Fatal("2: array is not normal") + } + _, err = vm.RunString(` + // Now try to expand. Should stay a normal array + a[20471] = 20471; + if (a.length != 20472) { + throw new Error("Invalid length: " + a.length); + } + + for (var i = 0; i < cutoffIdx; i++) { + if (a[i] !== undefined) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + } + + for (var i = cutoffIdx; i < a.length; i++) { + if (a[i] !== i) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + }`) + if err != nil { + t.Fatal(err) + } + if _, ok := a.self.(*arrayObject); !ok { + t.Fatal("3: array is not normal") + } + _, err = vm.RunString(` + // Delete enough elements for it to become sparse again. + var cutoffIdx1 = Math.round(20472 - 20472/10); + for (var i = cutoffIdx; i < cutoffIdx1; i++) { + delete a[i]; + } + + // This should switch it back to sparse. + a[25590] = 25590; + if (a.length != 25591) { + throw new Error("Invalid length: " + a.length); + } + + for (var i = 0; i < cutoffIdx1; i++) { + if (a[i] !== undefined) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + } + + for (var i = cutoffIdx1; i < 20472; i++) { + if (a[i] !== i) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + } + + for (var i = 20472; i < 25590; i++) { + if (a[i] !== undefined) { + throw new Error("Invalid value at " + i + ": " + a[i]); + } + } + + if (a[25590] !== 25590) { + throw new Error("Invalid value at 25590: " + a[25590]); + } + `) + if err != nil { + t.Fatal(err) + } + if _, ok := a.self.(*sparseArrayObject); !ok { + t.Fatal("4: array is not sparse") + } +} + +func TestSparseArrayOwnKeys(t *testing.T) { + const SCRIPT = ` + var a1 = []; + a1[500000] = 1; + var seen = false; + var count = 0; + var keys = Object.keys(a1); + keys.length === 1 && keys[0] === "500000"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestSparseArrayEnumerate(t *testing.T) { + const SCRIPT = ` + var a1 = []; + a1[500000] = 1; + var seen = false; + var count = 0; + for (var i in a1) { + if (i === "500000") { + if (seen) { + throw new Error("seen twice"); + } + seen = true; + } + count++; + } + seen && count === 1; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArraySparseMaxLength(t *testing.T) { + const SCRIPT = ` + var a = []; + a[4294967294]=1; + a.length === 4294967295 && a[4294967294] === 1; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArraySparseExportProps(t *testing.T) { + vm := New() + proto := vm.NewArray() + for _, idx := range []string{"0", "500", "9999", "10001", "20471"} { + err := proto.Set(idx, true) + if err != nil { + t.Fatal(err) + } + } + + arr := vm.NewArray() + err := arr.SetPrototype(proto) + if err != nil { + t.Fatal(err) + } + err = arr.DefineDataProperty("20470", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE) + if err != nil { + t.Fatal(err) + } + err = arr.DefineDataProperty("10000", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE) + if err != nil { + t.Fatal(err) + } + err = arr.Set("length", 20472) + if err != nil { + t.Fatal(err) + } + actual := arr.Export() + if actualArr, ok := actual.([]any); ok { + if len(actualArr) == 20472 { + expectedIdx := map[int]struct{}{ + 0: {}, + 500: {}, + 9999: {}, + 10000: {}, + 10001: {}, + 20470: {}, + 20471: {}, + } + for i, v := range actualArr { + if _, exists := expectedIdx[i]; exists { + if v != true { + t.Fatalf("Expected true at %d, got %v", i, v) + } + } else { + if v != nil { + t.Fatalf("Expected nil at %d, got %v", i, v) + } + } + } + } else { + t.Fatalf("Expected len 20471, actual: %d", len(actualArr)) + } + } else { + t.Fatalf("Invalid export type: %T", actual) + } +} + +func TestSparseArrayExportToSlice(t *testing.T) { + vm := New() + arr := vm.NewArray() + err := arr.Set("20470", 120470) + if err != nil { + t.Fatal(err) + } + err = arr.DefineDataProperty("20471", vm.ToValue(220471), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE) + if err != nil { + t.Fatal(err) + } + var exp []int + err = vm.ExportTo(arr, &exp) + if err != nil { + t.Fatal(err) + } + if len(exp) != 20472 { + t.Fatalf("len: %d", len(exp)) + } + if e := exp[20470]; e != 120470 { + t.Fatalf("20470: %d", e) + } + if e := exp[20471]; e != 220471 { + t.Fatalf("20471: %d", e) + } + for i := 0; i < 20470; i++ { + if exp[i] != 0 { + t.Fatalf("at %d: %d", i, exp[i]) + } + } +} diff --git a/pkg/xscript/engine/array_test.go b/pkg/xscript/engine/array_test.go new file mode 100644 index 0000000..8ae357f --- /dev/null +++ b/pkg/xscript/engine/array_test.go @@ -0,0 +1,133 @@ +package engine + +import ( + "reflect" + "testing" +) + +func TestArray1(t *testing.T) { + r := &Runtime{} + a := r.newArray(nil) + a.setOwnIdx(valueInt(0), asciiString("test"), true) + if l := a.getStr("length", nil).ToInteger(); l != 1 { + t.Fatalf("Unexpected length: %d", l) + } +} + +func TestArrayExportProps(t *testing.T) { + vm := New() + arr := vm.NewArray() + err := arr.DefineDataProperty("0", vm.ToValue(true), FLAG_TRUE, FLAG_FALSE, FLAG_TRUE) + if err != nil { + t.Fatal(err) + } + actual := arr.Export() + expected := []any{true} + if !reflect.DeepEqual(actual, expected) { + t.Fatalf("Expected: %#v, actual: %#v", expected, actual) + } +} + +func TestArrayCanonicalIndex(t *testing.T) { + const SCRIPT = ` + var a = []; + a["00"] = 1; + a["01"] = 2; + if (a[0] !== undefined) { + throw new Error("a[0]"); + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func BenchmarkArrayGetStr(b *testing.B) { + b.StopTimer() + r := New() + v := &Object{runtime: r} + + a := &arrayObject{ + baseObject: baseObject{ + val: v, + extensible: true, + }, + } + v.self = a + + a.init() + + v.setOwn(valueInt(0), asciiString("test"), false) + b.StartTimer() + + for i := 0; i < b.N; i++ { + a.getStr("0", nil) + } + +} + +func BenchmarkArrayGet(b *testing.B) { + b.StopTimer() + r := New() + v := &Object{runtime: r} + + a := &arrayObject{ + baseObject: baseObject{ + val: v, + extensible: true, + }, + } + v.self = a + + a.init() + + var idx Value = valueInt(0) + + v.setOwn(idx, asciiString("test"), false) + + b.StartTimer() + + for i := 0; i < b.N; i++ { + v.get(idx, nil) + } + +} + +func BenchmarkArrayPut(b *testing.B) { + b.StopTimer() + r := New() + + v := &Object{runtime: r} + + a := &arrayObject{ + baseObject: baseObject{ + val: v, + extensible: true, + }, + } + + v.self = a + + a.init() + + var idx Value = valueInt(0) + var val Value = asciiString("test") + + b.StartTimer() + + for i := 0; i < b.N; i++ { + v.setOwn(idx, val, false) + } + +} + +func BenchmarkArraySetEmpty(b *testing.B) { + r := New() + _ = r.Get("Array").(*Object).Get("prototype").String() // materialise Array.prototype + a := r.NewArray(0, 0) + values := a.self.(*arrayObject).values + b.ResetTimer() + for i := 0; i < b.N; i++ { + values[0] = nil + a.self.setOwnIdx(0, valueTrue, true) + } +} diff --git a/pkg/xscript/engine/ast/README.markdown b/pkg/xscript/engine/ast/README.markdown new file mode 100644 index 0000000..aba088e --- /dev/null +++ b/pkg/xscript/engine/ast/README.markdown @@ -0,0 +1,1068 @@ +# ast +-- + import "github.com/dop251/goja/ast" + +Package ast declares types representing a JavaScript AST. + + +### Warning + +The parser and AST interfaces are still works-in-progress (particularly where +node types are concerned) and may change in the future. + +## Usage + +#### type ArrayLiteral + +```go +type ArrayLiteral struct { + LeftBracket file.Idx + RightBracket file.Idx + Value []Expression +} +``` + + +#### func (*ArrayLiteral) Idx0 + +```go +func (self *ArrayLiteral) Idx0() file.Idx +``` + +#### func (*ArrayLiteral) Idx1 + +```go +func (self *ArrayLiteral) Idx1() file.Idx +``` + +#### type AssignExpression + +```go +type AssignExpression struct { + Operator token.Token + Left Expression + Right Expression +} +``` + + +#### func (*AssignExpression) Idx0 + +```go +func (self *AssignExpression) Idx0() file.Idx +``` + +#### func (*AssignExpression) Idx1 + +```go +func (self *AssignExpression) Idx1() file.Idx +``` + +#### type BadExpression + +```go +type BadExpression struct { + From file.Idx + To file.Idx +} +``` + + +#### func (*BadExpression) Idx0 + +```go +func (self *BadExpression) Idx0() file.Idx +``` + +#### func (*BadExpression) Idx1 + +```go +func (self *BadExpression) Idx1() file.Idx +``` + +#### type BadStatement + +```go +type BadStatement struct { + From file.Idx + To file.Idx +} +``` + + +#### func (*BadStatement) Idx0 + +```go +func (self *BadStatement) Idx0() file.Idx +``` + +#### func (*BadStatement) Idx1 + +```go +func (self *BadStatement) Idx1() file.Idx +``` + +#### type BinaryExpression + +```go +type BinaryExpression struct { + Operator token.Token + Left Expression + Right Expression + Comparison bool +} +``` + + +#### func (*BinaryExpression) Idx0 + +```go +func (self *BinaryExpression) Idx0() file.Idx +``` + +#### func (*BinaryExpression) Idx1 + +```go +func (self *BinaryExpression) Idx1() file.Idx +``` + +#### type BlockStatement + +```go +type BlockStatement struct { + LeftBrace file.Idx + List []Statement + RightBrace file.Idx +} +``` + + +#### func (*BlockStatement) Idx0 + +```go +func (self *BlockStatement) Idx0() file.Idx +``` + +#### func (*BlockStatement) Idx1 + +```go +func (self *BlockStatement) Idx1() file.Idx +``` + +#### type BooleanLiteral + +```go +type BooleanLiteral struct { + Idx file.Idx + Literal string + Value bool +} +``` + + +#### func (*BooleanLiteral) Idx0 + +```go +func (self *BooleanLiteral) Idx0() file.Idx +``` + +#### func (*BooleanLiteral) Idx1 + +```go +func (self *BooleanLiteral) Idx1() file.Idx +``` + +#### type BracketExpression + +```go +type BracketExpression struct { + Left Expression + Member Expression + LeftBracket file.Idx + RightBracket file.Idx +} +``` + + +#### func (*BracketExpression) Idx0 + +```go +func (self *BracketExpression) Idx0() file.Idx +``` + +#### func (*BracketExpression) Idx1 + +```go +func (self *BracketExpression) Idx1() file.Idx +``` + +#### type BranchStatement + +```go +type BranchStatement struct { + Idx file.Idx + Token token.Token + Label *Identifier +} +``` + + +#### func (*BranchStatement) Idx0 + +```go +func (self *BranchStatement) Idx0() file.Idx +``` + +#### func (*BranchStatement) Idx1 + +```go +func (self *BranchStatement) Idx1() file.Idx +``` + +#### type CallExpression + +```go +type CallExpression struct { + Callee Expression + LeftParenthesis file.Idx + ArgumentList []Expression + RightParenthesis file.Idx +} +``` + + +#### func (*CallExpression) Idx0 + +```go +func (self *CallExpression) Idx0() file.Idx +``` + +#### func (*CallExpression) Idx1 + +```go +func (self *CallExpression) Idx1() file.Idx +``` + +#### type CaseStatement + +```go +type CaseStatement struct { + Case file.Idx + Test Expression + Consequent []Statement +} +``` + + +#### func (*CaseStatement) Idx0 + +```go +func (self *CaseStatement) Idx0() file.Idx +``` + +#### func (*CaseStatement) Idx1 + +```go +func (self *CaseStatement) Idx1() file.Idx +``` + +#### type CatchStatement + +```go +type CatchStatement struct { + Catch file.Idx + Parameter *Identifier + Body Statement +} +``` + + +#### func (*CatchStatement) Idx0 + +```go +func (self *CatchStatement) Idx0() file.Idx +``` + +#### func (*CatchStatement) Idx1 + +```go +func (self *CatchStatement) Idx1() file.Idx +``` + +#### type ConditionalExpression + +```go +type ConditionalExpression struct { + Test Expression + Consequent Expression + Alternate Expression +} +``` + + +#### func (*ConditionalExpression) Idx0 + +```go +func (self *ConditionalExpression) Idx0() file.Idx +``` + +#### func (*ConditionalExpression) Idx1 + +```go +func (self *ConditionalExpression) Idx1() file.Idx +``` + +#### type DebuggerStatement + +```go +type DebuggerStatement struct { + Debugger file.Idx +} +``` + + +#### func (*DebuggerStatement) Idx0 + +```go +func (self *DebuggerStatement) Idx0() file.Idx +``` + +#### func (*DebuggerStatement) Idx1 + +```go +func (self *DebuggerStatement) Idx1() file.Idx +``` + +#### type Declaration + +```go +type Declaration interface { + // contains filtered or unexported methods +} +``` + +All declaration nodes implement the Declaration interface. + +#### type DoWhileStatement + +```go +type DoWhileStatement struct { + Do file.Idx + Test Expression + Body Statement +} +``` + + +#### func (*DoWhileStatement) Idx0 + +```go +func (self *DoWhileStatement) Idx0() file.Idx +``` + +#### func (*DoWhileStatement) Idx1 + +```go +func (self *DoWhileStatement) Idx1() file.Idx +``` + +#### type DotExpression + +```go +type DotExpression struct { + Left Expression + Identifier Identifier +} +``` + + +#### func (*DotExpression) Idx0 + +```go +func (self *DotExpression) Idx0() file.Idx +``` + +#### func (*DotExpression) Idx1 + +```go +func (self *DotExpression) Idx1() file.Idx +``` + +#### type EmptyStatement + +```go +type EmptyStatement struct { + Semicolon file.Idx +} +``` + + +#### func (*EmptyStatement) Idx0 + +```go +func (self *EmptyStatement) Idx0() file.Idx +``` + +#### func (*EmptyStatement) Idx1 + +```go +func (self *EmptyStatement) Idx1() file.Idx +``` + +#### type Expression + +```go +type Expression interface { + Node + // contains filtered or unexported methods +} +``` + +All expression nodes implement the Expression interface. + +#### type ExpressionStatement + +```go +type ExpressionStatement struct { + Expression Expression +} +``` + + +#### func (*ExpressionStatement) Idx0 + +```go +func (self *ExpressionStatement) Idx0() file.Idx +``` + +#### func (*ExpressionStatement) Idx1 + +```go +func (self *ExpressionStatement) Idx1() file.Idx +``` + +#### type ForInStatement + +```go +type ForInStatement struct { + For file.Idx + Into Expression + Source Expression + Body Statement +} +``` + + +#### func (*ForInStatement) Idx0 + +```go +func (self *ForInStatement) Idx0() file.Idx +``` + +#### func (*ForInStatement) Idx1 + +```go +func (self *ForInStatement) Idx1() file.Idx +``` + +#### type ForStatement + +```go +type ForStatement struct { + For file.Idx + Initializer Expression + Update Expression + Test Expression + Body Statement +} +``` + + +#### func (*ForStatement) Idx0 + +```go +func (self *ForStatement) Idx0() file.Idx +``` + +#### func (*ForStatement) Idx1 + +```go +func (self *ForStatement) Idx1() file.Idx +``` + +#### type FunctionDeclaration + +```go +type FunctionDeclaration struct { + Function *FunctionLiteral +} +``` + + +#### type FunctionLiteral + +```go +type FunctionLiteral struct { + Function file.Idx + Name *Identifier + ParameterList *ParameterList + Body Statement + Source string + + DeclarationList []Declaration +} +``` + + +#### func (*FunctionLiteral) Idx0 + +```go +func (self *FunctionLiteral) Idx0() file.Idx +``` + +#### func (*FunctionLiteral) Idx1 + +```go +func (self *FunctionLiteral) Idx1() file.Idx +``` + +#### type Identifier + +```go +type Identifier struct { + Name string + Idx file.Idx +} +``` + + +#### func (*Identifier) Idx0 + +```go +func (self *Identifier) Idx0() file.Idx +``` + +#### func (*Identifier) Idx1 + +```go +func (self *Identifier) Idx1() file.Idx +``` + +#### type IfStatement + +```go +type IfStatement struct { + If file.Idx + Test Expression + Consequent Statement + Alternate Statement +} +``` + + +#### func (*IfStatement) Idx0 + +```go +func (self *IfStatement) Idx0() file.Idx +``` + +#### func (*IfStatement) Idx1 + +```go +func (self *IfStatement) Idx1() file.Idx +``` + +#### type LabelledStatement + +```go +type LabelledStatement struct { + Label *Identifier + Colon file.Idx + Statement Statement +} +``` + + +#### func (*LabelledStatement) Idx0 + +```go +func (self *LabelledStatement) Idx0() file.Idx +``` + +#### func (*LabelledStatement) Idx1 + +```go +func (self *LabelledStatement) Idx1() file.Idx +``` + +#### type NewExpression + +```go +type NewExpression struct { + New file.Idx + Callee Expression + LeftParenthesis file.Idx + ArgumentList []Expression + RightParenthesis file.Idx +} +``` + + +#### func (*NewExpression) Idx0 + +```go +func (self *NewExpression) Idx0() file.Idx +``` + +#### func (*NewExpression) Idx1 + +```go +func (self *NewExpression) Idx1() file.Idx +``` + +#### type Node + +```go +type Node interface { + Idx0() file.Idx // The index of the first character belonging to the node + Idx1() file.Idx // The index of the first character immediately after the node +} +``` + +All nodes implement the Node interface. + +#### type NullLiteral + +```go +type NullLiteral struct { + Idx file.Idx + Literal string +} +``` + + +#### func (*NullLiteral) Idx0 + +```go +func (self *NullLiteral) Idx0() file.Idx +``` + +#### func (*NullLiteral) Idx1 + +```go +func (self *NullLiteral) Idx1() file.Idx +``` + +#### type NumberLiteral + +```go +type NumberLiteral struct { + Idx file.Idx + Literal string + Value interface{} +} +``` + + +#### func (*NumberLiteral) Idx0 + +```go +func (self *NumberLiteral) Idx0() file.Idx +``` + +#### func (*NumberLiteral) Idx1 + +```go +func (self *NumberLiteral) Idx1() file.Idx +``` + +#### type ObjectLiteral + +```go +type ObjectLiteral struct { + LeftBrace file.Idx + RightBrace file.Idx + Value []Property +} +``` + + +#### func (*ObjectLiteral) Idx0 + +```go +func (self *ObjectLiteral) Idx0() file.Idx +``` + +#### func (*ObjectLiteral) Idx1 + +```go +func (self *ObjectLiteral) Idx1() file.Idx +``` + +#### type ParameterList + +```go +type ParameterList struct { + Opening file.Idx + List []*Identifier + Closing file.Idx +} +``` + + +#### type Program + +```go +type Program struct { + Body []Statement + + DeclarationList []Declaration + + File *file.File +} +``` + + +#### func (*Program) Idx0 + +```go +func (self *Program) Idx0() file.Idx +``` + +#### func (*Program) Idx1 + +```go +func (self *Program) Idx1() file.Idx +``` + +#### type Property + +```go +type Property struct { + Key string + Kind string + Value Expression +} +``` + + +#### type RegExpLiteral + +```go +type RegExpLiteral struct { + Idx file.Idx + Literal string + Pattern string + Flags string + Value string +} +``` + + +#### func (*RegExpLiteral) Idx0 + +```go +func (self *RegExpLiteral) Idx0() file.Idx +``` + +#### func (*RegExpLiteral) Idx1 + +```go +func (self *RegExpLiteral) Idx1() file.Idx +``` + +#### type ReturnStatement + +```go +type ReturnStatement struct { + Return file.Idx + Argument Expression +} +``` + + +#### func (*ReturnStatement) Idx0 + +```go +func (self *ReturnStatement) Idx0() file.Idx +``` + +#### func (*ReturnStatement) Idx1 + +```go +func (self *ReturnStatement) Idx1() file.Idx +``` + +#### type SequenceExpression + +```go +type SequenceExpression struct { + Sequence []Expression +} +``` + + +#### func (*SequenceExpression) Idx0 + +```go +func (self *SequenceExpression) Idx0() file.Idx +``` + +#### func (*SequenceExpression) Idx1 + +```go +func (self *SequenceExpression) Idx1() file.Idx +``` + +#### type Statement + +```go +type Statement interface { + Node + // contains filtered or unexported methods +} +``` + +All statement nodes implement the Statement interface. + +#### type StringLiteral + +```go +type StringLiteral struct { + Idx file.Idx + Literal string + Value string +} +``` + + +#### func (*StringLiteral) Idx0 + +```go +func (self *StringLiteral) Idx0() file.Idx +``` + +#### func (*StringLiteral) Idx1 + +```go +func (self *StringLiteral) Idx1() file.Idx +``` + +#### type SwitchStatement + +```go +type SwitchStatement struct { + Switch file.Idx + Discriminant Expression + Default int + Body []*CaseStatement +} +``` + + +#### func (*SwitchStatement) Idx0 + +```go +func (self *SwitchStatement) Idx0() file.Idx +``` + +#### func (*SwitchStatement) Idx1 + +```go +func (self *SwitchStatement) Idx1() file.Idx +``` + +#### type ThisExpression + +```go +type ThisExpression struct { + Idx file.Idx +} +``` + + +#### func (*ThisExpression) Idx0 + +```go +func (self *ThisExpression) Idx0() file.Idx +``` + +#### func (*ThisExpression) Idx1 + +```go +func (self *ThisExpression) Idx1() file.Idx +``` + +#### type ThrowStatement + +```go +type ThrowStatement struct { + Throw file.Idx + Argument Expression +} +``` + + +#### func (*ThrowStatement) Idx0 + +```go +func (self *ThrowStatement) Idx0() file.Idx +``` + +#### func (*ThrowStatement) Idx1 + +```go +func (self *ThrowStatement) Idx1() file.Idx +``` + +#### type TryStatement + +```go +type TryStatement struct { + Try file.Idx + Body Statement + Catch *CatchStatement + Finally Statement +} +``` + + +#### func (*TryStatement) Idx0 + +```go +func (self *TryStatement) Idx0() file.Idx +``` + +#### func (*TryStatement) Idx1 + +```go +func (self *TryStatement) Idx1() file.Idx +``` + +#### type UnaryExpression + +```go +type UnaryExpression struct { + Operator token.Token + Idx file.Idx // If a prefix operation + Operand Expression + Postfix bool +} +``` + + +#### func (*UnaryExpression) Idx0 + +```go +func (self *UnaryExpression) Idx0() file.Idx +``` + +#### func (*UnaryExpression) Idx1 + +```go +func (self *UnaryExpression) Idx1() file.Idx +``` + +#### type VariableDeclaration + +```go +type VariableDeclaration struct { + Var file.Idx + List []*VariableExpression +} +``` + + +#### type VariableExpression + +```go +type VariableExpression struct { + Name string + Idx file.Idx + Initializer Expression +} +``` + + +#### func (*VariableExpression) Idx0 + +```go +func (self *VariableExpression) Idx0() file.Idx +``` + +#### func (*VariableExpression) Idx1 + +```go +func (self *VariableExpression) Idx1() file.Idx +``` + +#### type VariableStatement + +```go +type VariableStatement struct { + Var file.Idx + List []Expression +} +``` + + +#### func (*VariableStatement) Idx0 + +```go +func (self *VariableStatement) Idx0() file.Idx +``` + +#### func (*VariableStatement) Idx1 + +```go +func (self *VariableStatement) Idx1() file.Idx +``` + +#### type WhileStatement + +```go +type WhileStatement struct { + While file.Idx + Test Expression + Body Statement +} +``` + + +#### func (*WhileStatement) Idx0 + +```go +func (self *WhileStatement) Idx0() file.Idx +``` + +#### func (*WhileStatement) Idx1 + +```go +func (self *WhileStatement) Idx1() file.Idx +``` + +#### type WithStatement + +```go +type WithStatement struct { + With file.Idx + Object Expression + Body Statement +} +``` + + +#### func (*WithStatement) Idx0 + +```go +func (self *WithStatement) Idx0() file.Idx +``` + +#### func (*WithStatement) Idx1 + +```go +func (self *WithStatement) Idx1() file.Idx +``` + +-- +**godocdown** http://github.com/robertkrimen/godocdown diff --git a/pkg/xscript/engine/ast/node.go b/pkg/xscript/engine/ast/node.go new file mode 100644 index 0000000..612020d --- /dev/null +++ b/pkg/xscript/engine/ast/node.go @@ -0,0 +1,876 @@ +/* +Package ast declares types representing a JavaScript AST. + +# Warning + +The parser and AST interfaces are still works-in-progress (particularly where +node types are concerned) and may change in the future. +*/ +package ast + +import ( + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +type PropertyKind string + +const ( + PropertyKindValue PropertyKind = "value" + PropertyKindGet PropertyKind = "get" + PropertyKindSet PropertyKind = "set" + PropertyKindMethod PropertyKind = "method" +) + +// All nodes implement the Node interface. +type Node interface { + Idx0() file.Idx // The index of the first character belonging to the node + Idx1() file.Idx // The index of the first character immediately after the node +} + +// ========== // +// Expression // +// ========== // + +type ( + // All expression nodes implement the Expression interface. + Expression interface { + Node + _expressionNode() + } + + BindingTarget interface { + Expression + _bindingTarget() + } + + Binding struct { + Target BindingTarget + Initializer Expression + } + + Pattern interface { + BindingTarget + _pattern() + } + + YieldExpression struct { + Yield file.Idx + Argument Expression + Delegate bool + } + + AwaitExpression struct { + Await file.Idx + Argument Expression + } + + ArrayLiteral struct { + LeftBracket file.Idx + RightBracket file.Idx + Value []Expression + } + + ArrayPattern struct { + LeftBracket file.Idx + RightBracket file.Idx + Elements []Expression + Rest Expression + } + + AssignExpression struct { + Operator token.Token + Left Expression + Right Expression + } + + BadExpression struct { + From file.Idx + To file.Idx + } + + BinaryExpression struct { + Operator token.Token + Left Expression + Right Expression + Comparison bool + } + + BooleanLiteral struct { + Idx file.Idx + Literal string + Value bool + } + + BracketExpression struct { + Left Expression + Member Expression + LeftBracket file.Idx + RightBracket file.Idx + } + + CallExpression struct { + Callee Expression + LeftParenthesis file.Idx + ArgumentList []Expression + RightParenthesis file.Idx + } + + ConditionalExpression struct { + Test Expression + Consequent Expression + Alternate Expression + } + + DotExpression struct { + Left Expression + Identifier Identifier + } + + PrivateDotExpression struct { + Left Expression + Identifier PrivateIdentifier + } + + OptionalChain struct { + Expression + } + + Optional struct { + Expression + } + + FunctionLiteral struct { + Function file.Idx + Name *Identifier + ParameterList *ParameterList + Body *BlockStatement + Source string + + DeclarationList []*VariableDeclaration + + Async, Generator bool + } + + ClassLiteral struct { + Class file.Idx + RightBrace file.Idx + Name *Identifier + SuperClass Expression + Body []ClassElement + Source string + } + + ConciseBody interface { + Node + _conciseBody() + } + + ExpressionBody struct { + Expression Expression + } + + ArrowFunctionLiteral struct { + Start file.Idx + ParameterList *ParameterList + Body ConciseBody + Source string + DeclarationList []*VariableDeclaration + Async bool + } + + Identifier struct { + Name unistring.String + Idx file.Idx + } + + PrivateIdentifier struct { + Identifier + } + + NewExpression struct { + New file.Idx + Callee Expression + LeftParenthesis file.Idx + ArgumentList []Expression + RightParenthesis file.Idx + } + + NullLiteral struct { + Idx file.Idx + Literal string + } + + NumberLiteral struct { + Idx file.Idx + Literal string + Value any + } + + ObjectLiteral struct { + LeftBrace file.Idx + RightBrace file.Idx + Value []Property + } + + ObjectPattern struct { + LeftBrace file.Idx + RightBrace file.Idx + Properties []Property + Rest Expression + } + + ParameterList struct { + Opening file.Idx + List []*Binding + Rest Expression + Closing file.Idx + } + + Property interface { + Expression + _property() + } + + PropertyShort struct { + Name Identifier + Initializer Expression + } + + PropertyKeyed struct { + Key Expression + Kind PropertyKind + Value Expression + Computed bool + } + + SpreadElement struct { + Expression + } + + RegExpLiteral struct { + Idx file.Idx + Literal string + Pattern string + Flags string + } + + SequenceExpression struct { + Sequence []Expression + } + + StringLiteral struct { + Idx file.Idx + Literal string + Value unistring.String + } + + TemplateElement struct { + Idx file.Idx + Literal string + Parsed unistring.String + Valid bool + } + + TemplateLiteral struct { + OpenQuote file.Idx + CloseQuote file.Idx + Tag Expression + Elements []*TemplateElement + Expressions []Expression + } + + ThisExpression struct { + Idx file.Idx + } + + SuperExpression struct { + Idx file.Idx + } + + UnaryExpression struct { + Operator token.Token + Idx file.Idx // If a prefix operation + Operand Expression + Postfix bool + } + + MetaProperty struct { + Meta, Property *Identifier + Idx file.Idx + } +) + +// _expressionNode + +func (*ArrayLiteral) _expressionNode() {} +func (*AssignExpression) _expressionNode() {} +func (*YieldExpression) _expressionNode() {} +func (*AwaitExpression) _expressionNode() {} +func (*BadExpression) _expressionNode() {} +func (*BinaryExpression) _expressionNode() {} +func (*BooleanLiteral) _expressionNode() {} +func (*BracketExpression) _expressionNode() {} +func (*CallExpression) _expressionNode() {} +func (*ConditionalExpression) _expressionNode() {} +func (*DotExpression) _expressionNode() {} +func (*PrivateDotExpression) _expressionNode() {} +func (*FunctionLiteral) _expressionNode() {} +func (*ClassLiteral) _expressionNode() {} +func (*ArrowFunctionLiteral) _expressionNode() {} +func (*Identifier) _expressionNode() {} +func (*NewExpression) _expressionNode() {} +func (*NullLiteral) _expressionNode() {} +func (*NumberLiteral) _expressionNode() {} +func (*ObjectLiteral) _expressionNode() {} +func (*RegExpLiteral) _expressionNode() {} +func (*SequenceExpression) _expressionNode() {} +func (*StringLiteral) _expressionNode() {} +func (*TemplateLiteral) _expressionNode() {} +func (*ThisExpression) _expressionNode() {} +func (*SuperExpression) _expressionNode() {} +func (*UnaryExpression) _expressionNode() {} +func (*MetaProperty) _expressionNode() {} +func (*ObjectPattern) _expressionNode() {} +func (*ArrayPattern) _expressionNode() {} +func (*Binding) _expressionNode() {} + +func (*PropertyShort) _expressionNode() {} +func (*PropertyKeyed) _expressionNode() {} + +// ========= // +// Statement // +// ========= // + +type ( + // All statement nodes implement the Statement interface. + Statement interface { + Node + _statementNode() + } + + BadStatement struct { + From file.Idx + To file.Idx + } + + BlockStatement struct { + LeftBrace file.Idx + List []Statement + RightBrace file.Idx + } + + BranchStatement struct { + Idx file.Idx + Token token.Token + Label *Identifier + } + + CaseStatement struct { + Case file.Idx + Test Expression + Consequent []Statement + } + + CatchStatement struct { + Catch file.Idx + Parameter BindingTarget + Body *BlockStatement + } + + DebuggerStatement struct { + Debugger file.Idx + } + + DoWhileStatement struct { + Do file.Idx + Test Expression + Body Statement + RightParenthesis file.Idx + } + + EmptyStatement struct { + Semicolon file.Idx + } + + ExpressionStatement struct { + Expression Expression + } + + ForInStatement struct { + For file.Idx + Into ForInto + Source Expression + Body Statement + } + + ForOfStatement struct { + For file.Idx + Into ForInto + Source Expression + Body Statement + } + + ForStatement struct { + For file.Idx + Initializer ForLoopInitializer + Update Expression + Test Expression + Body Statement + } + + IfStatement struct { + If file.Idx + Test Expression + Consequent Statement + Alternate Statement + } + + LabelledStatement struct { + Label *Identifier + Colon file.Idx + Statement Statement + } + + ReturnStatement struct { + Return file.Idx + Argument Expression + } + + SwitchStatement struct { + Switch file.Idx + Discriminant Expression + Default int + Body []*CaseStatement + RightBrace file.Idx + } + + ThrowStatement struct { + Throw file.Idx + Argument Expression + } + + TryStatement struct { + Try file.Idx + Body *BlockStatement + Catch *CatchStatement + Finally *BlockStatement + } + + VariableStatement struct { + Var file.Idx + List []*Binding + } + + LexicalDeclaration struct { + Idx file.Idx + Token token.Token + List []*Binding + } + + WhileStatement struct { + While file.Idx + Test Expression + Body Statement + } + + WithStatement struct { + With file.Idx + Object Expression + Body Statement + } + + FunctionDeclaration struct { + Function *FunctionLiteral + } + + ClassDeclaration struct { + Class *ClassLiteral + } +) + +// _statementNode + +func (*BadStatement) _statementNode() {} +func (*BlockStatement) _statementNode() {} +func (*BranchStatement) _statementNode() {} +func (*CaseStatement) _statementNode() {} +func (*CatchStatement) _statementNode() {} +func (*DebuggerStatement) _statementNode() {} +func (*DoWhileStatement) _statementNode() {} +func (*EmptyStatement) _statementNode() {} +func (*ExpressionStatement) _statementNode() {} +func (*ForInStatement) _statementNode() {} +func (*ForOfStatement) _statementNode() {} +func (*ForStatement) _statementNode() {} +func (*IfStatement) _statementNode() {} +func (*LabelledStatement) _statementNode() {} +func (*ReturnStatement) _statementNode() {} +func (*SwitchStatement) _statementNode() {} +func (*ThrowStatement) _statementNode() {} +func (*TryStatement) _statementNode() {} +func (*VariableStatement) _statementNode() {} +func (*WhileStatement) _statementNode() {} +func (*WithStatement) _statementNode() {} +func (*LexicalDeclaration) _statementNode() {} +func (*FunctionDeclaration) _statementNode() {} +func (*ClassDeclaration) _statementNode() {} + +// =========== // +// Declaration // +// =========== // + +type ( + VariableDeclaration struct { + Var file.Idx + List []*Binding + } + + ClassElement interface { + Node + _classElement() + } + + FieldDefinition struct { + Idx file.Idx + Key Expression + Initializer Expression + Computed bool + Static bool + } + + MethodDefinition struct { + Idx file.Idx + Key Expression + Kind PropertyKind // "method", "get" or "set" + Body *FunctionLiteral + Computed bool + Static bool + } + + ClassStaticBlock struct { + Static file.Idx + Block *BlockStatement + Source string + DeclarationList []*VariableDeclaration + } +) + +type ( + ForLoopInitializer interface { + Node + _forLoopInitializer() + } + + ForLoopInitializerExpression struct { + Expression Expression + } + + ForLoopInitializerVarDeclList struct { + Var file.Idx + List []*Binding + } + + ForLoopInitializerLexicalDecl struct { + LexicalDeclaration LexicalDeclaration + } + + ForInto interface { + Node + _forInto() + } + + ForIntoVar struct { + Binding *Binding + } + + ForDeclaration struct { + Idx file.Idx + IsConst bool + Target BindingTarget + } + + ForIntoExpression struct { + Expression Expression + } +) + +func (*ForLoopInitializerExpression) _forLoopInitializer() {} +func (*ForLoopInitializerVarDeclList) _forLoopInitializer() {} +func (*ForLoopInitializerLexicalDecl) _forLoopInitializer() {} + +func (*ForIntoVar) _forInto() {} +func (*ForDeclaration) _forInto() {} +func (*ForIntoExpression) _forInto() {} + +func (*ArrayPattern) _pattern() {} +func (*ArrayPattern) _bindingTarget() {} + +func (*ObjectPattern) _pattern() {} +func (*ObjectPattern) _bindingTarget() {} + +func (*BadExpression) _bindingTarget() {} + +func (*PropertyShort) _property() {} +func (*PropertyKeyed) _property() {} +func (*SpreadElement) _property() {} + +func (*Identifier) _bindingTarget() {} + +func (*BlockStatement) _conciseBody() {} +func (*ExpressionBody) _conciseBody() {} + +func (*FieldDefinition) _classElement() {} +func (*MethodDefinition) _classElement() {} +func (*ClassStaticBlock) _classElement() {} + +// ==== // +// Node // +// ==== // + +type Program struct { + Body []Statement + + DeclarationList []*VariableDeclaration + + File *file.File +} + +// ==== // +// Idx0 // +// ==== // + +func (self *ArrayLiteral) Idx0() file.Idx { return self.LeftBracket } +func (self *ArrayPattern) Idx0() file.Idx { return self.LeftBracket } +func (self *YieldExpression) Idx0() file.Idx { return self.Yield } +func (self *AwaitExpression) Idx0() file.Idx { return self.Await } +func (self *ObjectPattern) Idx0() file.Idx { return self.LeftBrace } +func (self *ParameterList) Idx0() file.Idx { return self.Opening } +func (self *AssignExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *BadExpression) Idx0() file.Idx { return self.From } +func (self *BinaryExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *BooleanLiteral) Idx0() file.Idx { return self.Idx } +func (self *BracketExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *CallExpression) Idx0() file.Idx { return self.Callee.Idx0() } +func (self *ConditionalExpression) Idx0() file.Idx { return self.Test.Idx0() } +func (self *DotExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *PrivateDotExpression) Idx0() file.Idx { return self.Left.Idx0() } +func (self *FunctionLiteral) Idx0() file.Idx { return self.Function } +func (self *ClassLiteral) Idx0() file.Idx { return self.Class } +func (self *ArrowFunctionLiteral) Idx0() file.Idx { return self.Start } +func (self *Identifier) Idx0() file.Idx { return self.Idx } +func (self *NewExpression) Idx0() file.Idx { return self.New } +func (self *NullLiteral) Idx0() file.Idx { return self.Idx } +func (self *NumberLiteral) Idx0() file.Idx { return self.Idx } +func (self *ObjectLiteral) Idx0() file.Idx { return self.LeftBrace } +func (self *RegExpLiteral) Idx0() file.Idx { return self.Idx } +func (self *SequenceExpression) Idx0() file.Idx { return self.Sequence[0].Idx0() } +func (self *StringLiteral) Idx0() file.Idx { return self.Idx } +func (self *TemplateElement) Idx0() file.Idx { return self.Idx } +func (self *TemplateLiteral) Idx0() file.Idx { return self.OpenQuote } +func (self *ThisExpression) Idx0() file.Idx { return self.Idx } +func (self *SuperExpression) Idx0() file.Idx { return self.Idx } +func (self *UnaryExpression) Idx0() file.Idx { + if self.Postfix { + return self.Operand.Idx0() + } + return self.Idx +} +func (self *MetaProperty) Idx0() file.Idx { return self.Idx } + +func (self *BadStatement) Idx0() file.Idx { return self.From } +func (self *BlockStatement) Idx0() file.Idx { return self.LeftBrace } +func (self *BranchStatement) Idx0() file.Idx { return self.Idx } +func (self *CaseStatement) Idx0() file.Idx { return self.Case } +func (self *CatchStatement) Idx0() file.Idx { return self.Catch } +func (self *DebuggerStatement) Idx0() file.Idx { return self.Debugger } +func (self *DoWhileStatement) Idx0() file.Idx { return self.Do } +func (self *EmptyStatement) Idx0() file.Idx { return self.Semicolon } +func (self *ExpressionStatement) Idx0() file.Idx { return self.Expression.Idx0() } +func (self *ForInStatement) Idx0() file.Idx { return self.For } +func (self *ForOfStatement) Idx0() file.Idx { return self.For } +func (self *ForStatement) Idx0() file.Idx { return self.For } +func (self *IfStatement) Idx0() file.Idx { return self.If } +func (self *LabelledStatement) Idx0() file.Idx { return self.Label.Idx0() } +func (self *Program) Idx0() file.Idx { return self.Body[0].Idx0() } +func (self *ReturnStatement) Idx0() file.Idx { return self.Return } +func (self *SwitchStatement) Idx0() file.Idx { return self.Switch } +func (self *ThrowStatement) Idx0() file.Idx { return self.Throw } +func (self *TryStatement) Idx0() file.Idx { return self.Try } +func (self *VariableStatement) Idx0() file.Idx { return self.Var } +func (self *WhileStatement) Idx0() file.Idx { return self.While } +func (self *WithStatement) Idx0() file.Idx { return self.With } +func (self *LexicalDeclaration) Idx0() file.Idx { return self.Idx } +func (self *FunctionDeclaration) Idx0() file.Idx { return self.Function.Idx0() } +func (self *ClassDeclaration) Idx0() file.Idx { return self.Class.Idx0() } +func (self *Binding) Idx0() file.Idx { return self.Target.Idx0() } + +func (self *ForLoopInitializerExpression) Idx0() file.Idx { return self.Expression.Idx0() } +func (self *ForLoopInitializerVarDeclList) Idx0() file.Idx { return self.List[0].Idx0() } +func (self *ForLoopInitializerLexicalDecl) Idx0() file.Idx { return self.LexicalDeclaration.Idx0() } +func (self *PropertyShort) Idx0() file.Idx { return self.Name.Idx } +func (self *PropertyKeyed) Idx0() file.Idx { return self.Key.Idx0() } +func (self *ExpressionBody) Idx0() file.Idx { return self.Expression.Idx0() } + +func (self *VariableDeclaration) Idx0() file.Idx { return self.Var } +func (self *FieldDefinition) Idx0() file.Idx { return self.Idx } +func (self *MethodDefinition) Idx0() file.Idx { return self.Idx } +func (self *ClassStaticBlock) Idx0() file.Idx { return self.Static } + +func (self *ForDeclaration) Idx0() file.Idx { return self.Idx } +func (self *ForIntoVar) Idx0() file.Idx { return self.Binding.Idx0() } +func (self *ForIntoExpression) Idx0() file.Idx { return self.Expression.Idx0() } + +// ==== // +// Idx1 // +// ==== // + +func (self *ArrayLiteral) Idx1() file.Idx { return self.RightBracket + 1 } +func (self *ArrayPattern) Idx1() file.Idx { return self.RightBracket + 1 } +func (self *AssignExpression) Idx1() file.Idx { return self.Right.Idx1() } +func (self *AwaitExpression) Idx1() file.Idx { return self.Argument.Idx1() } +func (self *BadExpression) Idx1() file.Idx { return self.To } +func (self *BinaryExpression) Idx1() file.Idx { return self.Right.Idx1() } +func (self *BooleanLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *BracketExpression) Idx1() file.Idx { return self.RightBracket + 1 } +func (self *CallExpression) Idx1() file.Idx { return self.RightParenthesis + 1 } +func (self *ConditionalExpression) Idx1() file.Idx { return self.Alternate.Idx1() } +func (self *DotExpression) Idx1() file.Idx { return self.Identifier.Idx1() } +func (self *PrivateDotExpression) Idx1() file.Idx { return self.Identifier.Idx1() } +func (self *FunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() } +func (self *ClassLiteral) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ArrowFunctionLiteral) Idx1() file.Idx { return self.Body.Idx1() } +func (self *Identifier) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Name)) } +func (self *NewExpression) Idx1() file.Idx { + if self.ArgumentList != nil { + return self.RightParenthesis + 1 + } else { + return self.Callee.Idx1() + } +} +func (self *NullLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + 4) } // "null" +func (self *NumberLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *ObjectLiteral) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ObjectPattern) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ParameterList) Idx1() file.Idx { return self.Closing + 1 } +func (self *RegExpLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *SequenceExpression) Idx1() file.Idx { return self.Sequence[len(self.Sequence)-1].Idx1() } +func (self *StringLiteral) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *TemplateElement) Idx1() file.Idx { return file.Idx(int(self.Idx) + len(self.Literal)) } +func (self *TemplateLiteral) Idx1() file.Idx { return self.CloseQuote + 1 } +func (self *ThisExpression) Idx1() file.Idx { return self.Idx + 4 } +func (self *SuperExpression) Idx1() file.Idx { return self.Idx + 5 } +func (self *UnaryExpression) Idx1() file.Idx { + if self.Postfix { + return self.Operand.Idx1() + 2 // ++ -- + } + return self.Operand.Idx1() +} +func (self *MetaProperty) Idx1() file.Idx { + return self.Property.Idx1() +} + +func (self *BadStatement) Idx1() file.Idx { return self.To } +func (self *BlockStatement) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *BranchStatement) Idx1() file.Idx { + if self.Label == nil { + return file.Idx(int(self.Idx) + len(self.Token.String())) + } + return self.Label.Idx1() +} +func (self *CaseStatement) Idx1() file.Idx { return self.Consequent[len(self.Consequent)-1].Idx1() } +func (self *CatchStatement) Idx1() file.Idx { return self.Body.Idx1() } +func (self *DebuggerStatement) Idx1() file.Idx { return self.Debugger + 8 } +func (self *DoWhileStatement) Idx1() file.Idx { return self.RightParenthesis + 1 } +func (self *EmptyStatement) Idx1() file.Idx { return self.Semicolon + 1 } +func (self *ExpressionStatement) Idx1() file.Idx { return self.Expression.Idx1() } +func (self *ForInStatement) Idx1() file.Idx { return self.Body.Idx1() } +func (self *ForOfStatement) Idx1() file.Idx { return self.Body.Idx1() } +func (self *ForStatement) Idx1() file.Idx { return self.Body.Idx1() } +func (self *IfStatement) Idx1() file.Idx { + if self.Alternate != nil { + return self.Alternate.Idx1() + } + return self.Consequent.Idx1() +} +func (self *LabelledStatement) Idx1() file.Idx { return self.Statement.Idx1() } +func (self *Program) Idx1() file.Idx { return self.Body[len(self.Body)-1].Idx1() } +func (self *ReturnStatement) Idx1() file.Idx { + if self.Argument != nil { + return self.Argument.Idx1() + } + return self.Return + 6 +} +func (self *SwitchStatement) Idx1() file.Idx { return self.RightBrace + 1 } +func (self *ThrowStatement) Idx1() file.Idx { return self.Argument.Idx1() } +func (self *TryStatement) Idx1() file.Idx { + if self.Finally != nil { + return self.Finally.Idx1() + } + if self.Catch != nil { + return self.Catch.Idx1() + } + return self.Body.Idx1() +} +func (self *VariableStatement) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() } +func (self *WhileStatement) Idx1() file.Idx { return self.Body.Idx1() } +func (self *WithStatement) Idx1() file.Idx { return self.Body.Idx1() } +func (self *LexicalDeclaration) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() } +func (self *FunctionDeclaration) Idx1() file.Idx { return self.Function.Idx1() } +func (self *ClassDeclaration) Idx1() file.Idx { return self.Class.Idx1() } +func (self *Binding) Idx1() file.Idx { + if self.Initializer != nil { + return self.Initializer.Idx1() + } + return self.Target.Idx1() +} + +func (self *ForLoopInitializerExpression) Idx1() file.Idx { return self.Expression.Idx1() } +func (self *ForLoopInitializerVarDeclList) Idx1() file.Idx { return self.List[len(self.List)-1].Idx1() } +func (self *ForLoopInitializerLexicalDecl) Idx1() file.Idx { return self.LexicalDeclaration.Idx1() } + +func (self *PropertyShort) Idx1() file.Idx { + if self.Initializer != nil { + return self.Initializer.Idx1() + } + return self.Name.Idx1() +} + +func (self *PropertyKeyed) Idx1() file.Idx { return self.Value.Idx1() } + +func (self *ExpressionBody) Idx1() file.Idx { return self.Expression.Idx1() } + +func (self *VariableDeclaration) Idx1() file.Idx { + if len(self.List) > 0 { + return self.List[len(self.List)-1].Idx1() + } + + return self.Var + 3 +} + +func (self *FieldDefinition) Idx1() file.Idx { + if self.Initializer != nil { + return self.Initializer.Idx1() + } + return self.Key.Idx1() +} + +func (self *MethodDefinition) Idx1() file.Idx { + return self.Body.Idx1() +} + +func (self *ClassStaticBlock) Idx1() file.Idx { + return self.Block.Idx1() +} + +func (self *YieldExpression) Idx1() file.Idx { + if self.Argument != nil { + return self.Argument.Idx1() + } + return self.Yield + 5 +} + +func (self *ForDeclaration) Idx1() file.Idx { return self.Target.Idx1() } +func (self *ForIntoVar) Idx1() file.Idx { return self.Binding.Idx1() } +func (self *ForIntoExpression) Idx1() file.Idx { return self.Expression.Idx1() } diff --git a/pkg/xscript/engine/builtin_array.go b/pkg/xscript/engine/builtin_array.go new file mode 100644 index 0000000..ccdcdee --- /dev/null +++ b/pkg/xscript/engine/builtin_array.go @@ -0,0 +1,1693 @@ +package engine + +import ( + "math" + "sort" + "sync" +) + +func (r *Runtime) newArray(prototype *Object) (a *arrayObject) { + v := &Object{runtime: r} + + a = &arrayObject{} + a.class = classArray + a.val = v + a.extensible = true + v.self = a + a.prototype = prototype + a.init() + return +} + +func (r *Runtime) newArrayObject() *arrayObject { + return r.newArray(r.getArrayPrototype()) +} + +func setArrayValues(a *arrayObject, values []Value) *arrayObject { + a.values = values + a.length = uint32(len(values)) + a.objCount = len(values) + return a +} + +func setArrayLength(a *arrayObject, l int64) *arrayObject { + a.setOwnStr("length", intToValue(l), true) + return a +} + +func arraySpeciesCreate(obj *Object, size int64) *Object { + if isArray(obj) { + v := obj.self.getStr("constructor", nil) + if constructObj, ok := v.(*Object); ok { + v = constructObj.self.getSym(SymSpecies, nil) + if v == _null { + v = nil + } + } + + if v != nil && v != _undefined { + constructObj, _ := v.(*Object) + if constructObj != nil { + if constructor := constructObj.self.assertConstructor(); constructor != nil { + return constructor([]Value{intToValue(size)}, constructObj) + } + } + panic(obj.runtime.NewTypeError("Species is not a constructor")) + } + } + return obj.runtime.newArrayLength(size) +} + +func max(a, b int64) int64 { + if a > b { + return a + } + return b +} + +func min(a, b int64) int64 { + if a < b { + return a + } + return b +} + +func relToIdx(rel, l int64) int64 { + if rel >= 0 { + return min(rel, l) + } + return max(l+rel, 0) +} + +func (r *Runtime) newArrayValues(values []Value) *Object { + return setArrayValues(r.newArrayObject(), values).val +} + +func (r *Runtime) newArrayLength(l int64) *Object { + return setArrayLength(r.newArrayObject(), l).val +} + +func (r *Runtime) builtin_newArray(args []Value, proto *Object) *Object { + l := len(args) + if l == 1 { + if al, ok := args[0].(valueInt); ok { + return setArrayLength(r.newArray(proto), int64(al)).val + } else if f, ok := args[0].(valueFloat); ok { + al := int64(f) + if float64(al) == float64(f) { + return r.newArrayLength(al) + } else { + panic(r.newError(r.getRangeError(), "Invalid array length")) + } + } + return setArrayValues(r.newArray(proto), []Value{args[0]}).val + } else { + argsCopy := make([]Value, l) + copy(argsCopy, args) + return setArrayValues(r.newArray(proto), argsCopy).val + } +} + +func (r *Runtime) generic_push(obj *Object, call FunctionCall) Value { + l := toLength(obj.self.getStr("length", nil)) + nl := l + int64(len(call.Arguments)) + if nl >= maxInt { + r.typeErrorResult(true, "Invalid array length") + panic("unreachable") + } + for i, arg := range call.Arguments { + obj.self.setOwnIdx(valueInt(l+int64(i)), arg, true) + } + n := valueInt(nl) + obj.self.setOwnStr("length", n, true) + return n +} + +func (r *Runtime) arrayproto_push(call FunctionCall) Value { + obj := call.This.ToObject(r) + return r.generic_push(obj, call) +} + +func (r *Runtime) arrayproto_pop_generic(obj *Object) Value { + l := toLength(obj.self.getStr("length", nil)) + if l == 0 { + obj.self.setOwnStr("length", intToValue(0), true) + return _undefined + } + idx := valueInt(l - 1) + val := obj.self.getIdx(idx, nil) + obj.self.deleteIdx(idx, true) + obj.self.setOwnStr("length", idx, true) + return val +} + +func (r *Runtime) arrayproto_pop(call FunctionCall) Value { + obj := call.This.ToObject(r) + if a, ok := obj.self.(*arrayObject); ok { + l := a.length + var val Value + if l > 0 { + l-- + if l < uint32(len(a.values)) { + val = a.values[l] + } + if val == nil { + // optimisation bail-out + return r.arrayproto_pop_generic(obj) + } + if _, ok := val.(*valueProperty); ok { + // optimisation bail-out + return r.arrayproto_pop_generic(obj) + } + //a._setLengthInt(l, false) + a.values[l] = nil + a.values = a.values[:l] + } else { + val = _undefined + } + if a.lengthProp.writable { + a.length = l + } else { + a.setLength(0, true) // will throw + } + return val + } else { + return r.arrayproto_pop_generic(obj) + } +} + +func (r *Runtime) arrayproto_join(call FunctionCall) Value { + o := call.This.ToObject(r) + l := int(toLength(o.self.getStr("length", nil))) + var sep String + if s := call.Argument(0); s != _undefined { + sep = s.toString() + } else { + sep = asciiString(",") + } + if l == 0 { + return stringEmpty + } + + var buf StringBuilder + + element0 := o.self.getIdx(valueInt(0), nil) + if element0 != nil && element0 != _undefined && element0 != _null { + buf.WriteString(element0.toString()) + } + + for i := 1; i < l; i++ { + buf.WriteString(sep) + element := o.self.getIdx(valueInt(int64(i)), nil) + if element != nil && element != _undefined && element != _null { + buf.WriteString(element.toString()) + } + } + + return buf.String() +} + +func (r *Runtime) arrayproto_with(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + + callbackFn := r.toCallable(call.Argument(0)) + thisArg := call.Argument(1) + + a := arraySpeciesCreate(o, length) + + for k := int64(0); k < length; k++ { + idx := valueInt(k) + item := o.self.getIdx(idx, nil) + + newItem := callbackFn(FunctionCall{ + This: thisArg, + Arguments: []Value{item, idx}, + }) + + createDataPropertyOrThrow(a, idx, newItem) + } + + return a +} + +func (r *Runtime) arrayproto_toString(call FunctionCall) Value { + array := call.This.ToObject(r) + var toString func() Value + switch a := array.self.(type) { + case *objectGoSliceReflect: + toString = a.toString + case *objectGoArrayReflect: + toString = a.toString + } + if toString != nil { + return toString() + } + f := array.self.getStr("join", nil) + if fObj, ok := f.(*Object); ok { + if fcall, ok := fObj.self.assertCallable(); ok { + return fcall(FunctionCall{ + This: array, + }) + } + } + return r.objectproto_toString(FunctionCall{ + This: array, + }) +} + +func (r *Runtime) writeItemLocaleString(item Value, buf *StringBuilder) { + if item != nil && item != _undefined && item != _null { + if f, ok := r.getVStr(item, "toLocaleString").(*Object); ok { + if c, ok := f.self.assertCallable(); ok { + strVal := c(FunctionCall{ + This: item, + }) + buf.WriteString(strVal.toString()) + return + } + } + r.typeErrorResult(true, "Property 'toLocaleString' of object %s is not a function", item) + } +} + +func (r *Runtime) arrayproto_toLocaleString(call FunctionCall) Value { + array := call.This.ToObject(r) + var buf StringBuilder + if a := r.checkStdArrayObj(array); a != nil { + for i, item := range a.values { + if i > 0 { + buf.WriteRune(',') + } + r.writeItemLocaleString(item, &buf) + } + } else { + length := toLength(array.self.getStr("length", nil)) + for i := int64(0); i < length; i++ { + if i > 0 { + buf.WriteRune(',') + } + item := array.self.getIdx(valueInt(i), nil) + r.writeItemLocaleString(item, &buf) + } + } + + return buf.String() +} + +func isConcatSpreadable(obj *Object) bool { + spreadable := obj.self.getSym(SymIsConcatSpreadable, nil) + if spreadable != nil && spreadable != _undefined { + return spreadable.ToBoolean() + } + return isArray(obj) +} + +func (r *Runtime) arrayproto_concat_append(a *Object, item Value) { + aLength := toLength(a.self.getStr("length", nil)) + if obj, ok := item.(*Object); ok && isConcatSpreadable(obj) { + length := toLength(obj.self.getStr("length", nil)) + if aLength+length >= maxInt { + panic(r.NewTypeError("Invalid array length")) + } + for i := int64(0); i < length; i++ { + v := obj.self.getIdx(valueInt(i), nil) + if v != nil { + createDataPropertyOrThrow(a, intToValue(aLength), v) + } + aLength++ + } + } else { + createDataPropertyOrThrow(a, intToValue(aLength), item) + aLength++ + } + a.self.setOwnStr("length", intToValue(aLength), true) +} + +func (r *Runtime) arrayproto_concat(call FunctionCall) Value { + obj := call.This.ToObject(r) + a := arraySpeciesCreate(obj, 0) + r.arrayproto_concat_append(a, call.This.ToObject(r)) + for _, item := range call.Arguments { + r.arrayproto_concat_append(a, item) + } + return a +} + +func (r *Runtime) arrayproto_slice(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + start := relToIdx(call.Argument(0).ToInteger(), length) + var end int64 + if endArg := call.Argument(1); endArg != _undefined { + end = endArg.ToInteger() + } else { + end = length + } + end = relToIdx(end, length) + + count := end - start + if count < 0 { + count = 0 + } + + a := arraySpeciesCreate(o, count) + if src := r.checkStdArrayObj(o); src != nil { + if dst := r.checkStdArrayObjWithProto(a); dst != nil { + values := make([]Value, count) + copy(values, src.values[start:]) + setArrayValues(dst, values) + return a + } + } + + n := int64(0) + for start < end { + p := o.self.getIdx(valueInt(start), nil) + if p != nil { + createDataPropertyOrThrow(a, valueInt(n), p) + } + start++ + n++ + } + return a +} + +func (r *Runtime) arrayproto_sort(call FunctionCall) Value { + o := call.This.ToObject(r) + + var compareFn func(FunctionCall) Value + arg := call.Argument(0) + if arg != _undefined { + if arg, ok := call.Argument(0).(*Object); ok { + compareFn, _ = arg.self.assertCallable() + } + if compareFn == nil { + panic(r.NewTypeError("The comparison function must be either a function or undefined")) + } + } + + var s sortable + if r.checkStdArrayObj(o) != nil { + s = o.self + } else if _, ok := o.self.(reflectValueWrapper); ok { + s = o.self + } + + if s != nil { + ctx := arraySortCtx{ + obj: s, + compare: compareFn, + } + + sort.Stable(&ctx) + } else { + length := toLength(o.self.getStr("length", nil)) + a := make([]Value, 0, length) + for i := int64(0); i < length; i++ { + idx := valueInt(i) + if o.self.hasPropertyIdx(idx) { + a = append(a, nilSafe(o.self.getIdx(idx, nil))) + } + } + ar := r.newArrayValues(a) + ctx := arraySortCtx{ + obj: ar.self, + compare: compareFn, + } + + sort.Stable(&ctx) + for i := 0; i < len(a); i++ { + o.self.setOwnIdx(valueInt(i), a[i], true) + } + for i := int64(len(a)); i < length; i++ { + o.self.deleteIdx(valueInt(i), true) + } + } + return o +} + +func (r *Runtime) arrayproto_toSorted(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + values := make([]Value, length) + for i := int64(0); i < length; i++ { + values[i] = o.self.getIdx(valueInt(i), nil) + } + sort.Slice(values, func(i, j int) bool { + return r.toPrimitive(values[i], hintString).toString().CompareTo(r.toPrimitive(values[j], hintString).toString()) < 0 + }) + return r.newArrayValues(values) +} + +func (r *Runtime) arrayproto_splice(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + actualStart := relToIdx(call.Argument(0).ToInteger(), length) + var actualDeleteCount int64 + switch len(call.Arguments) { + case 0: + case 1: + actualDeleteCount = length - actualStart + default: + actualDeleteCount = min(max(call.Argument(1).ToInteger(), 0), length-actualStart) + } + itemCount := max(int64(len(call.Arguments)-2), 0) + newLength := length - actualDeleteCount + itemCount + if newLength >= maxInt { + panic(r.NewTypeError("Invalid array length")) + } + a := arraySpeciesCreate(o, actualDeleteCount) + if src := r.checkStdArrayObj(o); src != nil { + if dst := r.checkStdArrayObjWithProto(a); dst != nil { + values := make([]Value, actualDeleteCount) + copy(values, src.values[actualStart:]) + setArrayValues(dst, values) + } else { + for k := int64(0); k < actualDeleteCount; k++ { + createDataPropertyOrThrow(a, intToValue(k), src.values[k+actualStart]) + } + a.self.setOwnStr("length", intToValue(actualDeleteCount), true) + } + var values []Value + if itemCount < actualDeleteCount { + values = src.values + copy(values[actualStart+itemCount:], values[actualStart+actualDeleteCount:]) + tail := values[newLength:] + for k := range tail { + tail[k] = nil + } + values = values[:newLength] + } else if itemCount > actualDeleteCount { + if int64(cap(src.values)) >= newLength { + values = src.values[:newLength] + copy(values[actualStart+itemCount:], values[actualStart+actualDeleteCount:length]) + } else { + values = make([]Value, newLength) + copy(values, src.values[:actualStart]) + copy(values[actualStart+itemCount:], src.values[actualStart+actualDeleteCount:]) + } + } else { + values = src.values + } + if itemCount > 0 { + copy(values[actualStart:], call.Arguments[2:]) + } + src.values = values + src.objCount = len(values) + } else { + for k := int64(0); k < actualDeleteCount; k++ { + from := valueInt(k + actualStart) + if o.self.hasPropertyIdx(from) { + createDataPropertyOrThrow(a, valueInt(k), nilSafe(o.self.getIdx(from, nil))) + } + } + + if itemCount < actualDeleteCount { + for k := actualStart; k < length-actualDeleteCount; k++ { + from := valueInt(k + actualDeleteCount) + to := valueInt(k + itemCount) + if o.self.hasPropertyIdx(from) { + o.self.setOwnIdx(to, nilSafe(o.self.getIdx(from, nil)), true) + } else { + o.self.deleteIdx(to, true) + } + } + + for k := length; k > length-actualDeleteCount+itemCount; k-- { + o.self.deleteIdx(valueInt(k-1), true) + } + } else if itemCount > actualDeleteCount { + for k := length - actualDeleteCount; k > actualStart; k-- { + from := valueInt(k + actualDeleteCount - 1) + to := valueInt(k + itemCount - 1) + if o.self.hasPropertyIdx(from) { + o.self.setOwnIdx(to, nilSafe(o.self.getIdx(from, nil)), true) + } else { + o.self.deleteIdx(to, true) + } + } + } + + if itemCount > 0 { + for i, item := range call.Arguments[2:] { + o.self.setOwnIdx(valueInt(actualStart+int64(i)), item, true) + } + } + } + + o.self.setOwnStr("length", intToValue(newLength), true) + + return a +} + +func (r *Runtime) arrayproto_toSpliced(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + start := relToIdx(call.Argument(0).ToInteger(), length) + deleteCount := relToIdx(call.Argument(1).ToInteger(), length-start) + values := make([]Value, deleteCount) + for i := int64(0); i < deleteCount; i++ { + values[i] = o.self.getIdx(valueInt(start+i), nil) + } + return r.newArrayValues(values) +} + +func (r *Runtime) arrayproto_unshift(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + argCount := int64(len(call.Arguments)) + newLen := intToValue(length + argCount) + if argCount > 0 { + newSize := length + argCount + if newSize >= maxInt { + panic(r.NewTypeError("Invalid array length")) + } + if arr := r.checkStdArrayObjWithProto(o); arr != nil && newSize < math.MaxUint32 { + if int64(cap(arr.values)) >= newSize { + arr.values = arr.values[:newSize] + copy(arr.values[argCount:], arr.values[:length]) + } else { + values := make([]Value, newSize) + copy(values[argCount:], arr.values) + arr.values = values + } + copy(arr.values, call.Arguments) + arr.objCount = int(arr.length) + } else { + for k := length - 1; k >= 0; k-- { + from := valueInt(k) + to := valueInt(k + argCount) + if o.self.hasPropertyIdx(from) { + o.self.setOwnIdx(to, nilSafe(o.self.getIdx(from, nil)), true) + } else { + o.self.deleteIdx(to, true) + } + } + + for k, arg := range call.Arguments { + o.self.setOwnIdx(valueInt(int64(k)), arg, true) + } + } + } + + o.self.setOwnStr("length", newLen, true) + return newLen +} + +func (r *Runtime) arrayproto_at(call FunctionCall) Value { + o := call.This.ToObject(r) + idx := call.Argument(0).ToInteger() + length := toLength(o.self.getStr("length", nil)) + if idx < 0 { + idx = length + idx + } + if idx >= length || idx < 0 { + return _undefined + } + i := valueInt(idx) + if o.self.hasPropertyIdx(i) { + return o.self.getIdx(i, nil) + } + return _undefined +} + +func (r *Runtime) arrayproto_indexOf(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + if length == 0 { + return intToValue(-1) + } + + n := call.Argument(1).ToInteger() + if n >= length { + return intToValue(-1) + } + + if n < 0 { + n = max(length+n, 0) + } + + searchElement := call.Argument(0) + + if arr := r.checkStdArrayObj(o); arr != nil { + for i, val := range arr.values[n:] { + if searchElement.StrictEquals(val) { + return intToValue(n + int64(i)) + } + } + return intToValue(-1) + } + + for ; n < length; n++ { + idx := valueInt(n) + if o.self.hasPropertyIdx(idx) { + if val := o.self.getIdx(idx, nil); val != nil { + if searchElement.StrictEquals(val) { + return idx + } + } + } + } + + return intToValue(-1) +} + +func (r *Runtime) arrayproto_includes(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + if length == 0 { + return valueFalse + } + + n := call.Argument(1).ToInteger() + if n >= length { + return valueFalse + } + + if n < 0 { + n = max(length+n, 0) + } + + searchElement := call.Argument(0) + if searchElement == _negativeZero { + searchElement = _positiveZero + } + + if arr := r.checkStdArrayObj(o); arr != nil { + for _, val := range arr.values[n:] { + if searchElement.SameAs(val) { + return valueTrue + } + } + return valueFalse + } + + for ; n < length; n++ { + idx := valueInt(n) + val := nilSafe(o.self.getIdx(idx, nil)) + if searchElement.SameAs(val) { + return valueTrue + } + } + + return valueFalse +} + +func (r *Runtime) arrayproto_lastIndexOf(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + if length == 0 { + return intToValue(-1) + } + + var fromIndex int64 + + if len(call.Arguments) < 2 { + fromIndex = length - 1 + } else { + fromIndex = call.Argument(1).ToInteger() + if fromIndex >= 0 { + fromIndex = min(fromIndex, length-1) + } else { + fromIndex += length + } + } + + searchElement := call.Argument(0) + + if arr := r.checkStdArrayObj(o); arr != nil { + vals := arr.values + for k := fromIndex; k >= 0; k-- { + if v := vals[k]; v != nil && searchElement.StrictEquals(v) { + return intToValue(k) + } + } + return intToValue(-1) + } + + for k := fromIndex; k >= 0; k-- { + idx := valueInt(k) + if o.self.hasPropertyIdx(idx) { + if val := o.self.getIdx(idx, nil); val != nil { + if searchElement.StrictEquals(val) { + return idx + } + } + } + } + + return intToValue(-1) +} + +func (r *Runtime) arrayproto_every(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + if !callbackFn(fc).ToBoolean() { + return valueFalse + } + } + } + return valueTrue +} + +func (r *Runtime) arrayproto_some(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + if callbackFn(fc).ToBoolean() { + return valueTrue + } + } + } + return valueFalse +} + +func (r *Runtime) arrayproto_forEach(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + callbackFn(fc) + } + } + return _undefined +} + +func (r *Runtime) arrayproto_map(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + a := arraySpeciesCreate(o, length) + if _, stdSrc := o.self.(*arrayObject); stdSrc { + if arr, ok := a.self.(*arrayObject); ok { + values := make([]Value, length) + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + values[k] = callbackFn(fc) + } + } + setArrayValues(arr, values) + return a + } + } + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + createDataPropertyOrThrow(a, idx, callbackFn(fc)) + } + } + return a +} + +func (r *Runtime) arrayproto_filter(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := call.Argument(0).ToObject(r) + if callbackFn, ok := callbackFn.self.assertCallable(); ok { + a := arraySpeciesCreate(o, 0) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + if _, stdSrc := o.self.(*arrayObject); stdSrc { + if arr := r.checkStdArrayObj(a); arr != nil { + var values []Value + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + if callbackFn(fc).ToBoolean() { + values = append(values, val) + } + } + } + setArrayValues(arr, values) + return a + } + } + + to := int64(0) + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + fc.Arguments[1] = idx + if callbackFn(fc).ToBoolean() { + createDataPropertyOrThrow(a, intToValue(to), val) + to++ + } + } + } + return a + } else { + r.typeErrorResult(true, "%s is not a function", call.Argument(0)) + } + panic("unreachable") +} + +func (r *Runtime) arrayproto_reduce(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := call.Argument(0).ToObject(r) + if callbackFn, ok := callbackFn.self.assertCallable(); ok { + fc := FunctionCall{ + This: _undefined, + Arguments: []Value{nil, nil, nil, o}, + } + + var k int64 + + if len(call.Arguments) >= 2 { + fc.Arguments[0] = call.Argument(1) + } else { + for ; k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + break + } + } + if fc.Arguments[0] == nil { + r.typeErrorResult(true, "No initial value") + panic("unreachable") + } + k++ + } + + for ; k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[1] = val + fc.Arguments[2] = idx + fc.Arguments[0] = callbackFn(fc) + } + } + return fc.Arguments[0] + } else { + r.typeErrorResult(true, "%s is not a function", call.Argument(0)) + } + panic("unreachable") +} + +func (r *Runtime) arrayproto_reduceRight(call FunctionCall) Value { + o := call.This.ToObject(r) + length := toLength(o.self.getStr("length", nil)) + callbackFn := call.Argument(0).ToObject(r) + if callbackFn, ok := callbackFn.self.assertCallable(); ok { + fc := FunctionCall{ + This: _undefined, + Arguments: []Value{nil, nil, nil, o}, + } + + k := length - 1 + + if len(call.Arguments) >= 2 { + fc.Arguments[0] = call.Argument(1) + } else { + for ; k >= 0; k-- { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[0] = val + break + } + } + if fc.Arguments[0] == nil { + r.typeErrorResult(true, "No initial value") + panic("unreachable") + } + k-- + } + + for ; k >= 0; k-- { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + fc.Arguments[1] = val + fc.Arguments[2] = idx + fc.Arguments[0] = callbackFn(fc) + } + } + return fc.Arguments[0] + } else { + r.typeErrorResult(true, "%s is not a function", call.Argument(0)) + } + panic("unreachable") +} + +func arrayproto_reverse_generic_step(o *Object, lower, upper int64) { + lowerP := valueInt(lower) + upperP := valueInt(upper) + var lowerValue, upperValue Value + lowerExists := o.self.hasPropertyIdx(lowerP) + if lowerExists { + lowerValue = nilSafe(o.self.getIdx(lowerP, nil)) + } + upperExists := o.self.hasPropertyIdx(upperP) + if upperExists { + upperValue = nilSafe(o.self.getIdx(upperP, nil)) + } + if lowerExists && upperExists { + o.self.setOwnIdx(lowerP, upperValue, true) + o.self.setOwnIdx(upperP, lowerValue, true) + } else if !lowerExists && upperExists { + o.self.setOwnIdx(lowerP, upperValue, true) + o.self.deleteIdx(upperP, true) + } else if lowerExists && !upperExists { + o.self.deleteIdx(lowerP, true) + o.self.setOwnIdx(upperP, lowerValue, true) + } +} + +func (r *Runtime) arrayproto_reverse_generic(o *Object, start int64) { + l := toLength(o.self.getStr("length", nil)) + middle := l / 2 + for lower := start; lower != middle; lower++ { + arrayproto_reverse_generic_step(o, lower, l-lower-1) + } +} + +func (r *Runtime) arrayproto_reverse(call FunctionCall) Value { + o := call.This.ToObject(r) + if a := r.checkStdArrayObj(o); a != nil { + l := len(a.values) + middle := l / 2 + for lower := 0; lower != middle; lower++ { + upper := l - lower - 1 + a.values[lower], a.values[upper] = a.values[upper], a.values[lower] + } + //TODO: go arrays + } else { + r.arrayproto_reverse_generic(o, 0) + } + return o +} + +func (r *Runtime) arrayproto_shift(call FunctionCall) Value { + o := call.This.ToObject(r) + if a := r.checkStdArrayObjWithProto(o); a != nil { + if len(a.values) == 0 { + if !a.lengthProp.writable { + a.setLength(0, true) // will throw + } + return _undefined + } + first := a.values[0] + copy(a.values, a.values[1:]) + a.values[len(a.values)-1] = nil + a.values = a.values[:len(a.values)-1] + a.length-- + return first + } + length := toLength(o.self.getStr("length", nil)) + if length == 0 { + o.self.setOwnStr("length", intToValue(0), true) + return _undefined + } + first := o.self.getIdx(valueInt(0), nil) + for i := int64(1); i < length; i++ { + idxFrom := valueInt(i) + idxTo := valueInt(i - 1) + if o.self.hasPropertyIdx(idxFrom) { + o.self.setOwnIdx(idxTo, nilSafe(o.self.getIdx(idxFrom, nil)), true) + } else { + o.self.deleteIdx(idxTo, true) + } + } + + lv := valueInt(length - 1) + o.self.deleteIdx(lv, true) + o.self.setOwnStr("length", lv, true) + + return first +} + +func (r *Runtime) arrayproto_values(call FunctionCall) Value { + return r.createArrayIterator(call.This.ToObject(r), iterationKindValue) +} + +func (r *Runtime) arrayproto_keys(call FunctionCall) Value { + return r.createArrayIterator(call.This.ToObject(r), iterationKindKey) +} + +func (r *Runtime) arrayproto_copyWithin(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + var relEnd, dir int64 + to := relToIdx(call.Argument(0).ToInteger(), l) + from := relToIdx(call.Argument(1).ToInteger(), l) + if end := call.Argument(2); end != _undefined { + relEnd = end.ToInteger() + } else { + relEnd = l + } + final := relToIdx(relEnd, l) + count := min(final-from, l-to) + if arr := r.checkStdArrayObj(o); arr != nil { + if count > 0 { + copy(arr.values[to:to+count], arr.values[from:from+count]) + } + return o + } + if from < to && to < from+count { + dir = -1 + from = from + count - 1 + to = to + count - 1 + } else { + dir = 1 + } + for count > 0 { + if o.self.hasPropertyIdx(valueInt(from)) { + o.self.setOwnIdx(valueInt(to), nilSafe(o.self.getIdx(valueInt(from), nil)), true) + } else { + o.self.deleteIdx(valueInt(to), true) + } + from += dir + to += dir + count-- + } + + return o +} + +func (r *Runtime) arrayproto_entries(call FunctionCall) Value { + return r.createArrayIterator(call.This.ToObject(r), iterationKindKeyValue) +} + +func (r *Runtime) arrayproto_fill(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + k := relToIdx(call.Argument(1).ToInteger(), l) + var relEnd int64 + if endArg := call.Argument(2); endArg != _undefined { + relEnd = endArg.ToInteger() + } else { + relEnd = l + } + final := relToIdx(relEnd, l) + value := call.Argument(0) + if arr := r.checkStdArrayObj(o); arr != nil { + for ; k < final; k++ { + arr.values[k] = value + } + } else { + for ; k < final; k++ { + o.self.setOwnIdx(valueInt(k), value, true) + } + } + return o +} + +func (r *Runtime) arrayproto_find(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(0); k < l; k++ { + idx := valueInt(k) + kValue := o.self.getIdx(idx, nil) + fc.Arguments[0], fc.Arguments[1] = kValue, idx + if predicate(fc).ToBoolean() { + return kValue + } + } + + return _undefined +} + +func (r *Runtime) arrayproto_findIndex(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(0); k < l; k++ { + idx := valueInt(k) + kValue := o.self.getIdx(idx, nil) + fc.Arguments[0], fc.Arguments[1] = kValue, idx + if predicate(fc).ToBoolean() { + return idx + } + } + + return intToValue(-1) +} + +func (r *Runtime) arrayproto_findLast(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(l - 1); k >= 0; k-- { + idx := valueInt(k) + kValue := o.self.getIdx(idx, nil) + fc.Arguments[0], fc.Arguments[1] = kValue, idx + if predicate(fc).ToBoolean() { + return kValue + } + } + + return _undefined +} + +func (r *Runtime) arrayproto_findLastIndex(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, o}, + } + for k := int64(l - 1); k >= 0; k-- { + idx := valueInt(k) + kValue := o.self.getIdx(idx, nil) + fc.Arguments[0], fc.Arguments[1] = kValue, idx + if predicate(fc).ToBoolean() { + return idx + } + } + + return intToValue(-1) +} + +func (r *Runtime) arrayproto_toReversed(call FunctionCall) Value { + o := call.This.ToObject(r) + if a := r.checkStdArrayObjWithProto(o); a != nil { + if len(a.values) == 0 { + return r.newArrayValues(nil) + } + values := make([]Value, len(a.values)) + copy(values, a.values) + for i, j := 0, len(values)-1; i < j; i, j = i+1, j-1 { + values[i], values[j] = values[j], values[i] + } + return r.newArrayValues(values) + } + length := toLength(o.self.getStr("length", nil)) + a := r.newArrayValues(nil) + for k := int64(0); k < length; k++ { + idx := valueInt(k) + if val := o.self.getIdx(idx, nil); val != nil { + createDataPropertyOrThrow(a, intToValue(length-k-1), val) + } + } + return a +} + +func (r *Runtime) arrayproto_flat(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + depthNum := int64(1) + if len(call.Arguments) > 0 { + depthNum = call.Argument(0).ToInteger() + } + a := arraySpeciesCreate(o, 0) + r.flattenIntoArray(a, o, l, 0, depthNum, nil, nil) + return a +} + +func (r *Runtime) flattenIntoArray(target, source *Object, sourceLen, start, depth int64, mapperFunction func(FunctionCall) Value, thisArg Value) int64 { + targetIndex, sourceIndex := start, int64(0) + for sourceIndex < sourceLen { + p := intToValue(sourceIndex) + if source.hasProperty(p.toString()) { + element := nilSafe(source.get(p, source)) + if mapperFunction != nil { + element = mapperFunction(FunctionCall{ + This: thisArg, + Arguments: []Value{element, p, source}, + }) + } + var elementArray *Object + if depth > 0 { + if elementObj, ok := element.(*Object); ok && isArray(elementObj) { + elementArray = elementObj + } + } + if elementArray != nil { + elementLen := toLength(elementArray.self.getStr("length", nil)) + targetIndex = r.flattenIntoArray(target, elementArray, elementLen, targetIndex, depth-1, nil, nil) + } else { + if targetIndex >= maxInt-1 { + panic(r.NewTypeError("Invalid array length")) + } + createDataPropertyOrThrow(target, intToValue(targetIndex), element) + targetIndex++ + } + } + sourceIndex++ + } + return targetIndex +} + +func (r *Runtime) arrayproto_flatMap(call FunctionCall) Value { + o := call.This.ToObject(r) + l := toLength(o.self.getStr("length", nil)) + callbackFn := r.toCallable(call.Argument(0)) + thisArg := Undefined() + if len(call.Arguments) > 1 { + thisArg = call.Argument(1) + } + a := arraySpeciesCreate(o, 0) + r.flattenIntoArray(a, o, l, 0, 1, callbackFn, thisArg) + return a +} + +func (r *Runtime) checkStdArrayObj(obj *Object) *arrayObject { + if arr, ok := obj.self.(*arrayObject); ok && + arr.propValueCount == 0 && + arr.length == uint32(len(arr.values)) && + uint32(arr.objCount) == arr.length { + + return arr + } + + return nil +} + +func (r *Runtime) checkStdArrayObjWithProto(obj *Object) *arrayObject { + if arr := r.checkStdArrayObj(obj); arr != nil { + if p1, ok := arr.prototype.self.(*arrayObject); ok && p1.propValueCount == 0 { + if p2, ok := p1.prototype.self.(*baseObject); ok && p2.prototype == nil { + p2.ensurePropOrder() + if p2.idxPropCount == 0 { + return arr + } + } + } + } + return nil +} + +func (r *Runtime) checkStdArray(v Value) *arrayObject { + if obj, ok := v.(*Object); ok { + return r.checkStdArrayObj(obj) + } + + return nil +} + +func (r *Runtime) checkStdArrayIter(v Value) *arrayObject { + if arr := r.checkStdArray(v); arr != nil && + arr.getSym(SymIterator, nil) == r.getArrayValues() { + + return arr + } + + return nil +} + +func (r *Runtime) array_from(call FunctionCall) Value { + var mapFn func(FunctionCall) Value + if mapFnArg := call.Argument(1); mapFnArg != _undefined { + if mapFnObj, ok := mapFnArg.(*Object); ok { + if fn, ok := mapFnObj.self.assertCallable(); ok { + mapFn = fn + } + } + if mapFn == nil { + panic(r.NewTypeError("%s is not a function", mapFnArg)) + } + } + t := call.Argument(2) + items := call.Argument(0) + if mapFn == nil && call.This == r.global.Array { // mapFn may mutate the array + if arr := r.checkStdArrayIter(items); arr != nil { + items := make([]Value, len(arr.values)) + copy(items, arr.values) + return r.newArrayValues(items) + } + } + + var ctor func(args []Value, newTarget *Object) *Object + if call.This != r.global.Array { + if o, ok := call.This.(*Object); ok { + if c := o.self.assertConstructor(); c != nil { + ctor = c + } + } + } + var arr *Object + if usingIterator := toMethod(r.getV(items, SymIterator)); usingIterator != nil { + if ctor != nil { + arr = ctor([]Value{}, nil) + } else { + arr = r.newArrayValues(nil) + } + iter := r.getIterator(items, usingIterator) + if mapFn == nil { + if a := r.checkStdArrayObjWithProto(arr); a != nil { + var values []Value + iter.iterate(func(val Value) { + values = append(values, val) + }) + setArrayValues(a, values) + return arr + } + } + k := int64(0) + iter.iterate(func(val Value) { + if mapFn != nil { + val = mapFn(FunctionCall{This: t, Arguments: []Value{val, intToValue(k)}}) + } + createDataPropertyOrThrow(arr, intToValue(k), val) + k++ + }) + arr.self.setOwnStr("length", intToValue(k), true) + } else { + arrayLike := items.ToObject(r) + l := toLength(arrayLike.self.getStr("length", nil)) + if ctor != nil { + arr = ctor([]Value{intToValue(l)}, nil) + } else { + arr = r.newArrayValues(nil) + } + if mapFn == nil { + if a := r.checkStdArrayObjWithProto(arr); a != nil { + values := make([]Value, l) + for k := int64(0); k < l; k++ { + values[k] = nilSafe(arrayLike.self.getIdx(valueInt(k), nil)) + } + setArrayValues(a, values) + return arr + } + } + for k := int64(0); k < l; k++ { + idx := valueInt(k) + item := arrayLike.self.getIdx(idx, nil) + if mapFn != nil { + item = mapFn(FunctionCall{This: t, Arguments: []Value{item, idx}}) + } else { + item = nilSafe(item) + } + createDataPropertyOrThrow(arr, idx, item) + } + arr.self.setOwnStr("length", intToValue(l), true) + } + + return arr +} + +func (r *Runtime) array_isArray(call FunctionCall) Value { + if o, ok := call.Argument(0).(*Object); ok { + if isArray(o) { + return valueTrue + } + } + return valueFalse +} + +func (r *Runtime) array_of(call FunctionCall) Value { + var ctor func(args []Value, newTarget *Object) *Object + if call.This != r.global.Array { + if o, ok := call.This.(*Object); ok { + if c := o.self.assertConstructor(); c != nil { + ctor = c + } + } + } + if ctor == nil { + values := make([]Value, len(call.Arguments)) + copy(values, call.Arguments) + return r.newArrayValues(values) + } + l := intToValue(int64(len(call.Arguments))) + arr := ctor([]Value{l}, nil) + for i, val := range call.Arguments { + createDataPropertyOrThrow(arr, intToValue(int64(i)), val) + } + arr.self.setOwnStr("length", l, true) + return arr +} + +func (r *Runtime) arrayIterProto_next(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if iter, ok := thisObj.self.(*arrayIterObject); ok { + return iter.next() + } + panic(r.NewTypeError("Method Array Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) +} + +func createArrayProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("length", func(r *Runtime) Value { return valueProp(_positiveZero, true, false, false) }) + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) }) + + t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.arrayproto_at, "at", 1) }) + t.putStr("concat", func(r *Runtime) Value { return r.methodProp(r.arrayproto_concat, "concat", 1) }) + t.putStr("copyWithin", func(r *Runtime) Value { return r.methodProp(r.arrayproto_copyWithin, "copyWithin", 2) }) + t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.arrayproto_entries, "entries", 0) }) + t.putStr("every", func(r *Runtime) Value { return r.methodProp(r.arrayproto_every, "every", 1) }) + t.putStr("fill", func(r *Runtime) Value { return r.methodProp(r.arrayproto_fill, "fill", 1) }) + t.putStr("filter", func(r *Runtime) Value { return r.methodProp(r.arrayproto_filter, "filter", 1) }) + t.putStr("find", func(r *Runtime) Value { return r.methodProp(r.arrayproto_find, "find", 1) }) + t.putStr("findIndex", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findIndex, "findIndex", 1) }) + t.putStr("findLast", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findLast, "findLast", 1) }) + t.putStr("findLastIndex", func(r *Runtime) Value { return r.methodProp(r.arrayproto_findLastIndex, "findLastIndex", 1) }) + t.putStr("flat", func(r *Runtime) Value { return r.methodProp(r.arrayproto_flat, "flat", 0) }) + t.putStr("flatMap", func(r *Runtime) Value { return r.methodProp(r.arrayproto_flatMap, "flatMap", 1) }) + t.putStr("forEach", func(r *Runtime) Value { return r.methodProp(r.arrayproto_forEach, "forEach", 1) }) + t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.arrayproto_includes, "includes", 1) }) + t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.arrayproto_indexOf, "indexOf", 1) }) + t.putStr("join", func(r *Runtime) Value { return r.methodProp(r.arrayproto_join, "join", 1) }) + t.putStr("with", func(r *Runtime) Value { return r.methodProp(r.arrayproto_with, "with", 1) }) + t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.arrayproto_keys, "keys", 0) }) + t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.arrayproto_lastIndexOf, "lastIndexOf", 1) }) + t.putStr("map", func(r *Runtime) Value { return r.methodProp(r.arrayproto_map, "map", 1) }) + t.putStr("pop", func(r *Runtime) Value { return r.methodProp(r.arrayproto_pop, "pop", 0) }) + t.putStr("push", func(r *Runtime) Value { return r.methodProp(r.arrayproto_push, "push", 1) }) + t.putStr("reduce", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reduce, "reduce", 1) }) + t.putStr("reduceRight", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reduceRight, "reduceRight", 1) }) + t.putStr("reverse", func(r *Runtime) Value { return r.methodProp(r.arrayproto_reverse, "reverse", 0) }) + t.putStr("toReversed", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toReversed, "toReversed", 0) }) + t.putStr("shift", func(r *Runtime) Value { return r.methodProp(r.arrayproto_shift, "shift", 0) }) + t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.arrayproto_slice, "slice", 2) }) + t.putStr("some", func(r *Runtime) Value { return r.methodProp(r.arrayproto_some, "some", 1) }) + t.putStr("sort", func(r *Runtime) Value { return r.methodProp(r.arrayproto_sort, "sort", 1) }) + t.putStr("toSorted", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toSorted, "toSorted", 1) }) + t.putStr("splice", func(r *Runtime) Value { return r.methodProp(r.arrayproto_splice, "splice", 2) }) + t.putStr("toSpliced", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toSpliced, "toSpliced", 2) }) + t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.arrayproto_toLocaleString, "toLocaleString", 0) }) + t.putStr("toString", func(r *Runtime) Value { return valueProp(r.getArrayToString(), true, false, true) }) + t.putStr("unshift", func(r *Runtime) Value { return r.methodProp(r.arrayproto_unshift, "unshift", 1) }) + t.putStr("values", func(r *Runtime) Value { return valueProp(r.getArrayValues(), true, false, true) }) + + t.putSym(SymIterator, func(r *Runtime) Value { return valueProp(r.getArrayValues(), true, false, true) }) + t.putSym(SymUnscopables, func(r *Runtime) Value { + bl := r.newBaseObject(nil, classObject) + bl.setOwnStr("copyWithin", valueTrue, true) + bl.setOwnStr("entries", valueTrue, true) + bl.setOwnStr("fill", valueTrue, true) + bl.setOwnStr("find", valueTrue, true) + bl.setOwnStr("findIndex", valueTrue, true) + bl.setOwnStr("findLast", valueTrue, true) + bl.setOwnStr("findLastIndex", valueTrue, true) + bl.setOwnStr("flat", valueTrue, true) + bl.setOwnStr("flatMap", valueTrue, true) + bl.setOwnStr("includes", valueTrue, true) + bl.setOwnStr("keys", valueTrue, true) + bl.setOwnStr("values", valueTrue, true) + bl.setOwnStr("groupBy", valueTrue, true) + bl.setOwnStr("groupByToMap", valueTrue, true) + + return valueProp(bl.val, false, false, true) + }) + + return t +} + +var arrayProtoTemplate *objectTemplate +var arrayProtoTemplateOnce sync.Once + +func getArrayProtoTemplate() *objectTemplate { + arrayProtoTemplateOnce.Do(func() { + arrayProtoTemplate = createArrayProtoTemplate() + }) + return arrayProtoTemplate +} + +func (r *Runtime) getArrayPrototype() *Object { + ret := r.global.ArrayPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.ArrayPrototype = ret + r.newTemplatedArrayObject(getArrayProtoTemplate(), ret) + } + return ret +} + +func (r *Runtime) getArray() *Object { + ret := r.global.Array + if ret == nil { + ret = &Object{runtime: r} + ret.self = r.createArray(ret) + r.global.Array = ret + } + return ret +} + +func (r *Runtime) createArray(val *Object) objectImpl { + o := r.newNativeFuncConstructObj(val, r.builtin_newArray, "Array", r.getArrayPrototype(), 1) + o._putProp("from", r.newNativeFunc(r.array_from, "from", 1), true, false, true) + o._putProp("isArray", r.newNativeFunc(r.array_isArray, "isArray", 1), true, false, true) + o._putProp("of", r.newNativeFunc(r.array_of, "of", 0), true, false, true) + r.putSpeciesReturnThis(o) + + return o +} + +func (r *Runtime) createArrayIterProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) + + o._putProp("next", r.newNativeFunc(r.arrayIterProto_next, "next", 0), true, false, true) + o._putSym(SymToStringTag, valueProp(asciiString(classArrayIterator), false, false, true)) + + return o +} + +func (r *Runtime) getArrayValues() *Object { + ret := r.global.arrayValues + if ret == nil { + ret = r.newNativeFunc(r.arrayproto_values, "values", 0) + r.global.arrayValues = ret + } + return ret +} + +func (r *Runtime) getArrayToString() *Object { + ret := r.global.arrayToString + if ret == nil { + ret = r.newNativeFunc(r.arrayproto_toString, "toString", 0) + r.global.arrayToString = ret + } + return ret +} + +func (r *Runtime) getArrayIteratorPrototype() *Object { + var o *Object + if o = r.global.ArrayIteratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.ArrayIteratorPrototype = o + o.self = r.createArrayIterProto(o) + } + return o + +} + +type sortable interface { + sortLen() int + sortGet(int) Value + swap(int, int) +} + +type arraySortCtx struct { + obj sortable + compare func(FunctionCall) Value +} + +func (a *arraySortCtx) sortCompare(x, y Value) int { + if x == nil && y == nil { + return 0 + } + + if x == nil { + return 1 + } + + if y == nil { + return -1 + } + + if x == _undefined && y == _undefined { + return 0 + } + + if x == _undefined { + return 1 + } + + if y == _undefined { + return -1 + } + + if a.compare != nil { + f := a.compare(FunctionCall{ + This: _undefined, + Arguments: []Value{x, y}, + }).ToFloat() + if f > 0 { + return 1 + } + if f < 0 { + return -1 + } + if math.Signbit(f) { + return -1 + } + return 0 + } + return x.toString().CompareTo(y.toString()) +} + +// sort.Interface + +func (a *arraySortCtx) Len() int { + return a.obj.sortLen() +} + +func (a *arraySortCtx) Less(j, k int) bool { + return a.sortCompare(a.obj.sortGet(j), a.obj.sortGet(k)) < 0 +} + +func (a *arraySortCtx) Swap(j, k int) { + a.obj.swap(j, k) +} diff --git a/pkg/xscript/engine/builtin_arrray_test.go b/pkg/xscript/engine/builtin_arrray_test.go new file mode 100644 index 0000000..af0718e --- /dev/null +++ b/pkg/xscript/engine/builtin_arrray_test.go @@ -0,0 +1,341 @@ +package engine + +import "testing" + +func TestArrayProtoProp(t *testing.T) { + const SCRIPT = ` + Object.defineProperty(Array.prototype, '0', {value: 42, configurable: true, writable: false}) + var a = [] + a[0] = 1 + a[0] + ` + + testScript(SCRIPT, valueInt(42), t) +} + +func TestArrayDelete(t *testing.T) { + const SCRIPT = ` + var a = [1, 2]; + var deleted = delete a[0]; + var undef = a[0] === undefined; + var len = a.length; + + deleted && undef && len === 2; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayDeleteNonexisting(t *testing.T) { + const SCRIPT = ` + Array.prototype[0] = 42; + var a = []; + delete a[0] && a[0] === 42; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArraySetLength(t *testing.T) { + const SCRIPT = ` + var a = [1, 2]; + var assert0 = a.length == 2; + a.length = "1"; + a.length = 1.0; + a.length = 1; + var assert1 = a.length == 1; + a.length = 2; + var assert2 = a.length == 2; + assert0 && assert1 && assert2 && a[1] === undefined; + + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayReverseNonOptimisable(t *testing.T) { + const SCRIPT = ` + var a = []; + Object.defineProperty(a, "0", {get: function() {return 42}, set: function(v) {Object.defineProperty(a, "0", {value: v + 1, writable: true, configurable: true})}, configurable: true}) + a[1] = 43; + a.reverse(); + + a.length === 2 && a[0] === 44 && a[1] === 42; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayPushNonOptimisable(t *testing.T) { + const SCRIPT = ` + Object.defineProperty(Object.prototype, "0", {value: 42}); + var a = []; + var thrown = false; + try { + a.push(1); + } catch (e) { + thrown = e instanceof TypeError; + } + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArraySetLengthWithPropItems(t *testing.T) { + const SCRIPT = ` + var a = [1,2,3,4]; + var thrown = false; + + Object.defineProperty(a, "2", {value: 42, configurable: false, writable: false}); + try { + Object.defineProperty(a, "length", {value: 0, writable: false}); + } catch (e) { + thrown = e instanceof TypeError; + } + thrown && a.length === 3; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayFrom(t *testing.T) { + const SCRIPT = ` + function checkDestHoles(dest, prefix) { + assert(dest !== source, prefix + ": dest !== source"); + assert.sameValue(dest.length, 3, prefix + ": dest.length"); + assert.sameValue(dest[0], 1, prefix + ": [0]"); + assert.sameValue(dest[1], undefined, prefix + ": [1]"); + assert(dest.hasOwnProperty("1"), prefix + ': hasOwnProperty("1")'); + assert.sameValue(dest[2], 3, prefix + ": [2]"); + } + + function checkDest(dest, prefix) { + assert(dest !== source, prefix + ": dest !== source"); + assert.sameValue(dest.length, 3, prefix + ": dest.length"); + assert.sameValue(dest[0], 1, prefix + ": [0]"); + assert.sameValue(dest[1], 2, prefix + ": [1]"); + assert.sameValue(dest[2], 3, prefix + ": [2]"); + } + + var source = [1,2,3]; + var srcHoles = [1,,3]; + + checkDest(Array.from(source), "std source/std dest"); + checkDestHoles(Array.from(srcHoles), "std source (holes)/std dest"); + + function Iter() { + this.idx = 0; + } + Iter.prototype.next = function() { + if (this.idx < source.length) { + return {value: source[this.idx++]}; + } else { + return {done: true}; + } + } + + var src = {}; + src[Symbol.iterator] = function() { + return new Iter(); + } + checkDest(Array.from(src), "iter src/std dest"); + + src = {0: 1, 2: 3, length: 3}; + checkDestHoles(Array.from(src), "arrayLike src/std dest"); + + function A() {} + A.from = Array.from; + + checkDest(A.from(source), "std src/cust dest"); + checkDestHoles(A.from(srcHoles), "std src (holes)/cust dest"); + checkDestHoles(A.from(src), "arrayLike src/cust dest"); + + function T2() { + Object.defineProperty(this, 0, { + configurable: false, + writable: true, + enumerable: true + }); + } + + assert.throws(TypeError, function() { + Array.from.call(T2, source); + }); + + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestArrayOf(t *testing.T) { + const SCRIPT = ` + function T1() { + Object.preventExtensions(this); + } + + assert.throws(TypeError, function() { + Array.of.call(T1, 'Bob'); + }); + + function T2() { + Object.defineProperty(this, 0, { + configurable: false, + writable: true, + enumerable: true + }); + } + + assert.throws(TypeError, function() { + Array.of.call(T2, 'Bob'); + }) + + result = Array.of.call(undefined); + assert( + result instanceof Array, + 'this is not a constructor' + ); + + result = Array.of.call(Math.cos); + assert( + result instanceof Array, + 'this is a builtin function with no [[Construct]] slot' + ); + + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestUnscopables(t *testing.T) { + const SCRIPT = ` + var keys = []; + var _length; + with (Array.prototype) { + _length = length; + keys.push('something'); + } + _length === 0 && keys.length === 1 && keys[0] === "something"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestArraySort(t *testing.T) { + const SCRIPT = ` + assert.throws(TypeError, function() { + [1,2].sort(null); + }, "null compare function"); + assert.throws(TypeError, function() { + [1,2].sort({}); + }, "non-callable compare function"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestArraySortNonStdArray(t *testing.T) { + const SCRIPT = ` + const array = [undefined, 'c', /*hole*/, 'b', undefined, /*hole*/, 'a', 'd']; + + Object.defineProperty(array, '2', { + get() { + array.pop(); + array.pop(); + return this.foo; + }, + set(v) { + this.foo = v; + } + }); + + array.sort(); + + assert.sameValue(array[0], 'b'); + assert.sameValue(array[1], 'c'); + assert.sameValue(array[3], undefined); + assert.sameValue(array[4], undefined); + assert.sameValue('5' in array, false); + assert.sameValue(array.hasOwnProperty('5'), false); + assert.sameValue(array.length, 6); + assert.sameValue(array.foo, undefined); + + assert.sameValue(array[2], undefined); + assert.sameValue(array.length, 4); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestArrayConcat(t *testing.T) { + const SCRIPT = ` + var concat = Array.prototype.concat; + var array = [1, 2]; + var sparseArray = [1, , 2]; + var nonSpreadableArray = [1, 2]; + nonSpreadableArray[Symbol.isConcatSpreadable] = false; + var arrayLike = { 0: 1, 1: 2, length: 2 }; + var spreadableArrayLike = { 0: 1, 1: 2, length: 2 }; + spreadableArrayLike[Symbol.isConcatSpreadable] = true; + assert(looksNative(concat)); + assert(deepEqual(array.concat(), [1, 2]), '#1'); + assert(deepEqual(sparseArray.concat(), [1, , 2]), '#2'); + assert(deepEqual(nonSpreadableArray.concat(), [[1, 2]]), '#3'); + assert(deepEqual(concat.call(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#4'); + assert(deepEqual(concat.call(spreadableArrayLike), [1, 2]), '#5'); + assert(deepEqual([].concat(array), [1, 2]), '#6'); + assert(deepEqual([].concat(sparseArray), [1, , 2]), '#7'); + assert(deepEqual([].concat(nonSpreadableArray), [[1, 2]]), '#8'); + assert(deepEqual([].concat(arrayLike), [{ 0: 1, 1: 2, length: 2 }]), '#9'); + assert(deepEqual([].concat(spreadableArrayLike), [1, 2]), '#10'); + assert(deepEqual(array.concat(sparseArray, nonSpreadableArray, arrayLike, spreadableArrayLike), [ + 1, 2, 1, , 2, [1, 2], { 0: 1, 1: 2, length: 2 }, 1, 2, + ]), '#11'); + array = []; + array.constructor = {}; + array.constructor[Symbol.species] = function () { + return { foo: 1 }; + } + assert.sameValue(array.concat().foo, 1, '@@species'); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestArrayFlat(t *testing.T) { + const SCRIPT = ` + var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]]; + assert(deepEqual(array.flat(), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#1'); + assert(deepEqual(array.flat(1), [1,2,3,[4,5,6],[[[7,8,9]]]]), '#2'); + assert(deepEqual(array.flat(3), [1,2,3,4,5,6,[7,8,9]]), '#3'); + assert(deepEqual(array.flat(4), [1,2,3,4,5,6,7,8,9]), '#4'); + assert(deepEqual(array.flat(10), [1,2,3,4,5,6,7,8,9]), '#5'); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestArrayFlatMap(t *testing.T) { + const SCRIPT = ` + var double = function(x) { + if (isNaN(x)) { + return x + } + return x * 2 + } + var array = [1, [2,3,[4,5,6]], [[[[7,8,9]]]]]; + assert(deepEqual(array.flatMap(double), [2,2,3,[4,5,6],[[[7,8,9]]]]), '#1'); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestArrayProto(t *testing.T) { + const SCRIPT = ` + const a = Array.prototype; + a.push(1, 2, 3, 4, 5); + assert.sameValue(a.length, 5); + assert.sameValue(a[0], 1); + a.length = 3; + assert.sameValue(a.length, 3); + assert(compareArray(a, [1, 2, 3])); + a.shift(); + assert.sameValue(a.length, 2); + assert(compareArray(a, [2, 3])); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} diff --git a/pkg/xscript/engine/builtin_boolean.go b/pkg/xscript/engine/builtin_boolean.go new file mode 100644 index 0000000..bc55ac0 --- /dev/null +++ b/pkg/xscript/engine/builtin_boolean.go @@ -0,0 +1,75 @@ +package engine + +func (r *Runtime) booleanproto_toString(call FunctionCall) Value { + var b bool + switch o := call.This.(type) { + case valueBool: + b = bool(o) + goto success + case *Object: + if p, ok := o.self.(*primitiveValueObject); ok { + if b1, ok := p.pValue.(valueBool); ok { + b = bool(b1) + goto success + } + } + if o, ok := o.self.(*objectGoReflect); ok { + if o.class == classBoolean && o.toString != nil { + return o.toString() + } + } + } + r.typeErrorResult(true, "Method Boolean.prototype.toString is called on incompatible receiver") + +success: + if b { + return stringTrue + } + return stringFalse +} + +func (r *Runtime) booleanproto_valueOf(call FunctionCall) Value { + switch o := call.This.(type) { + case valueBool: + return o + case *Object: + if p, ok := o.self.(*primitiveValueObject); ok { + if b, ok := p.pValue.(valueBool); ok { + return b + } + } + if o, ok := o.self.(*objectGoReflect); ok { + if o.class == classBoolean && o.valueOf != nil { + return o.valueOf() + } + } + } + + r.typeErrorResult(true, "Method Boolean.prototype.valueOf is called on incompatible receiver") + return nil +} + +func (r *Runtime) getBooleanPrototype() *Object { + ret := r.global.BooleanPrototype + if ret == nil { + ret = r.newPrimitiveObject(valueFalse, r.global.ObjectPrototype, classBoolean) + r.global.BooleanPrototype = ret + o := ret.self + o._putProp("toString", r.newNativeFunc(r.booleanproto_toString, "toString", 0), true, false, true) + o._putProp("valueOf", r.newNativeFunc(r.booleanproto_valueOf, "valueOf", 0), true, false, true) + o._putProp("constructor", r.getBoolean(), true, false, true) + } + return ret +} + +func (r *Runtime) getBoolean() *Object { + ret := r.global.Boolean + if ret == nil { + ret = &Object{runtime: r} + r.global.Boolean = ret + proto := r.getBooleanPrototype() + r.newNativeFuncAndConstruct(ret, r.builtin_Boolean, + r.wrapNativeConstruct(r.builtin_newBoolean, ret, proto), proto, "Boolean", intToValue(1)) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_date.go b/pkg/xscript/engine/builtin_date.go new file mode 100644 index 0000000..533aa8e --- /dev/null +++ b/pkg/xscript/engine/builtin_date.go @@ -0,0 +1,1058 @@ +package engine + +import ( + "fmt" + "math" + "sync" + "time" +) + +func (r *Runtime) makeDate(args []Value, utc bool) (t time.Time, valid bool) { + switch { + case len(args) >= 2: + t = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.Local) + t, valid = _dateSetYear(t, FunctionCall{Arguments: args}, 0, utc) + case len(args) == 0: + t = r.now() + valid = true + default: // one argument + if o, ok := args[0].(*Object); ok { + if d, ok := o.self.(*dateObject); ok { + t = d.time() + valid = true + } + } + if !valid { + pv := toPrimitive(args[0]) + if val, ok := pv.(String); ok { + return dateParse(val.String()) + } + pv = pv.ToNumber() + var n int64 + if i, ok := pv.(valueInt); ok { + n = int64(i) + } else if f, ok := pv.(valueFloat); ok { + f := float64(f) + if math.IsNaN(f) || math.IsInf(f, 0) { + return + } + if math.Abs(f) > maxTime { + return + } + n = int64(f) + } else { + n = pv.ToInteger() + } + t = timeFromMsec(n) + valid = true + } + } + if valid { + msec := t.Unix()*1000 + int64(t.Nanosecond()/1e6) + if msec < 0 { + msec = -msec + } + if msec > maxTime { + valid = false + } + } + return +} + +func (r *Runtime) newDateTime(args []Value, proto *Object) *Object { + t, isSet := r.makeDate(args, false) + return r.newDateObject(t, isSet, proto) +} + +func (r *Runtime) builtin_newDate(args []Value, proto *Object) *Object { + return r.newDateTime(args, proto) +} + +func (r *Runtime) builtin_date(FunctionCall) Value { + return asciiString(dateFormat(r.now())) +} + +func (r *Runtime) date_parse(call FunctionCall) Value { + t, set := dateParse(call.Argument(0).toString().String()) + if set { + return intToValue(timeToMsec(t)) + } + return _NaN +} + +func (r *Runtime) date_UTC(call FunctionCall) Value { + var args []Value + if len(call.Arguments) < 2 { + args = []Value{call.Argument(0), _positiveZero} + } else { + args = call.Arguments + } + t, valid := r.makeDate(args, true) + if !valid { + return _NaN + } + return intToValue(timeToMsec(t)) +} + +func (r *Runtime) date_now(FunctionCall) Value { + return intToValue(timeToMsec(r.now())) +} + +func (r *Runtime) dateproto_toString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.time().Format(dateTimeLayout)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toUTCString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.timeUTC().Format(utcDateTimeLayout)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toUTCString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toISOString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + utc := d.timeUTC() + year := utc.Year() + if year >= -9999 && year <= 9999 { + return asciiString(utc.Format(isoDateTimeLayout)) + } + // extended year + return asciiString(fmt.Sprintf("%+06d-", year) + utc.Format(isoDateTimeLayout[5:])) + } else { + panic(r.newError(r.getRangeError(), "Invalid time value")) + } + } + panic(r.NewTypeError("Method Date.prototype.toISOString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toJSON(call FunctionCall) Value { + obj := call.This.ToObject(r) + tv := obj.toPrimitiveNumber() + if f, ok := tv.(valueFloat); ok { + f := float64(f) + if math.IsNaN(f) || math.IsInf(f, 0) { + return _null + } + } + + if toISO, ok := obj.self.getStr("toISOString", nil).(*Object); ok { + if toISO, ok := toISO.self.assertCallable(); ok { + return toISO(FunctionCall{ + This: obj, + }) + } + } + + panic(r.NewTypeError("toISOString is not a function")) +} + +func (r *Runtime) dateproto_toPrimitive(call FunctionCall) Value { + o := r.toObject(call.This) + arg := call.Argument(0) + + if asciiString("string").StrictEquals(arg) || asciiString("default").StrictEquals(arg) { + return o.ordinaryToPrimitiveString() + } + if asciiString("number").StrictEquals(arg) { + return o.ordinaryToPrimitiveNumber() + } + panic(r.NewTypeError("Invalid hint: %s", arg)) +} + +func (r *Runtime) dateproto_toDateString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.time().Format(dateLayout)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toDateString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toTimeString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.time().Format(timeLayout)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toTimeString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toLocaleString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.time().Format(datetimeLayout_en_GB)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toLocaleString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toLocaleDateString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.time().Format(dateLayout_en_GB)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toLocaleDateString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_toLocaleTimeString(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return asciiString(d.time().Format(timeLayout_en_GB)) + } else { + return stringInvalidDate + } + } + panic(r.NewTypeError("Method Date.prototype.toLocaleTimeString is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_valueOf(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(d.msec) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.valueOf is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getTime(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(d.msec) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getTime is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getFullYear(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Year())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getFullYear is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCFullYear(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Year())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCFullYear is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getMonth(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Month()) - 1) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getMonth is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCMonth(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Month()) - 1) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCMonth is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getHours(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Hour())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getHours is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCHours(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Hour())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCHours is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getDate(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Day())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getDate is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCDate(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Day())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCDate is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getDay(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Weekday())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getDay is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCDay(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Weekday())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCDay is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getMinutes(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Minute())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getMinutes is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCMinutes(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Minute())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCMinutes is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getSeconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Second())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getSeconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCSeconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Second())) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCSeconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getMilliseconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.time().Nanosecond() / 1e6)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getMilliseconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getUTCMilliseconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + return intToValue(int64(d.timeUTC().Nanosecond() / 1e6)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getUTCMilliseconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_getTimezoneOffset(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + if d.isSet() { + _, offset := d.time().Zone() + return floatToValue(float64(-offset) / 60) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.getTimezoneOffset is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setTime(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + n := call.Argument(0).ToNumber() + if IsNaN(n) { + d.unset() + return _NaN + } + return d.setTimeMs(n.ToInteger()) + } + panic(r.NewTypeError("Method Date.prototype.setTime is called on incompatible receiver")) +} + +// _norm returns nhi, nlo such that +// +// hi * base + lo == nhi * base + nlo +// 0 <= nlo < base +func _norm(hi, lo, base int64) (nhi, nlo int64, ok bool) { + if lo < 0 { + if hi == math.MinInt64 && lo <= -base { + // underflow + ok = false + return + } + n := (-lo-1)/base + 1 + hi -= n + lo += n * base + } + if lo >= base { + if hi == math.MaxInt64 { + // overflow + ok = false + return + } + n := lo / base + hi += n + lo -= n * base + } + return hi, lo, true +} + +func mkTime(year, m, day, hour, min, sec, nsec int64, loc *time.Location) (t time.Time, ok bool) { + year, m, ok = _norm(year, m, 12) + if !ok { + return + } + + // Normalise nsec, sec, min, hour, overflowing into day. + sec, nsec, ok = _norm(sec, nsec, 1e9) + if !ok { + return + } + min, sec, ok = _norm(min, sec, 60) + if !ok { + return + } + hour, min, ok = _norm(hour, min, 60) + if !ok { + return + } + day, hour, ok = _norm(day, hour, 24) + if !ok { + return + } + if year > math.MaxInt32 || year < math.MinInt32 || + day > math.MaxInt32 || day < math.MinInt32 || + m >= math.MaxInt32 || m < math.MinInt32-1 { + return time.Time{}, false + } + month := time.Month(m) + 1 + return time.Date(int(year), month, int(day), int(hour), int(min), int(sec), int(nsec), loc), true +} + +func _intArg(call FunctionCall, argNum int) (int64, bool) { + n := call.Argument(argNum).ToNumber() + if IsNaN(n) { + return 0, false + } + return n.ToInteger(), true +} + +func _dateSetYear(t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var year int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + year, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + if year >= 0 && year <= 99 { + year += 1900 + } + } else { + year = int64(t.Year()) + } + + return _dateSetMonth(year, t, call, argNum+1, utc) +} + +func _dateSetFullYear(t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var year int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + year, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + year = int64(t.Year()) + } + return _dateSetMonth(year, t, call, argNum+1, utc) +} + +func _dateSetMonth(year int64, t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var mon int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + mon, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + mon = int64(t.Month()) - 1 + } + + return _dateSetDay(year, mon, t, call, argNum+1, utc) +} + +func _dateSetDay(year, mon int64, t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var day int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + day, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + day = int64(t.Day()) + } + + return _dateSetHours(year, mon, day, t, call, argNum+1, utc) +} + +func _dateSetHours(year, mon, day int64, t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var hours int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + hours, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + hours = int64(t.Hour()) + } + return _dateSetMinutes(year, mon, day, hours, t, call, argNum+1, utc) +} + +func _dateSetMinutes(year, mon, day, hours int64, t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var min int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + min, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + min = int64(t.Minute()) + } + return _dateSetSeconds(year, mon, day, hours, min, t, call, argNum+1, utc) +} + +func _dateSetSeconds(year, mon, day, hours, min int64, t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var sec int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + sec, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + sec = int64(t.Second()) + } + return _dateSetMilliseconds(year, mon, day, hours, min, sec, t, call, argNum+1, utc) +} + +func _dateSetMilliseconds(year, mon, day, hours, min, sec int64, t time.Time, call FunctionCall, argNum int, utc bool) (time.Time, bool) { + var msec int64 + if argNum == 0 || argNum > 0 && argNum < len(call.Arguments) { + var ok bool + msec, ok = _intArg(call, argNum) + if !ok { + return time.Time{}, false + } + } else { + msec = int64(t.Nanosecond() / 1e6) + } + var ok bool + sec, msec, ok = _norm(sec, msec, 1e3) + if !ok { + return time.Time{}, false + } + + var loc *time.Location + if utc { + loc = time.UTC + } else { + loc = time.Local + } + r, ok := mkTime(year, mon, day, hours, min, sec, msec*1e6, loc) + if !ok { + return time.Time{}, false + } + if utc { + return r.In(time.Local), true + } + return r, true +} + +func (r *Runtime) dateproto_setMilliseconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + n := call.Argument(0).ToNumber() + if IsNaN(n) { + d.unset() + return _NaN + } + msec := n.ToInteger() + sec := d.msec / 1e3 + var ok bool + sec, msec, ok = _norm(sec, msec, 1e3) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(sec*1e3 + msec) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setMilliseconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCMilliseconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + n := call.Argument(0).ToNumber() + if IsNaN(n) { + d.unset() + return _NaN + } + msec := n.ToInteger() + sec := d.msec / 1e3 + var ok bool + sec, msec, ok = _norm(sec, msec, 1e3) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(sec*1e3 + msec) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setUTCMilliseconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setSeconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.time(), call, -5, false) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setSeconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCSeconds(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.timeUTC(), call, -5, true) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setUTCSeconds is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setMinutes(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.time(), call, -4, false) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setMinutes is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCMinutes(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.timeUTC(), call, -4, true) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setUTCMinutes is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setHours(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.time(), call, -3, false) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setHours is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCHours(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.timeUTC(), call, -3, true) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setUTCHours is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setDate(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.time(), limitCallArgs(call, 1), -2, false) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setDate is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCDate(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.timeUTC(), limitCallArgs(call, 1), -2, true) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setUTCDate is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setMonth(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.time(), limitCallArgs(call, 2), -1, false) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setMonth is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCMonth(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + t, ok := _dateSetFullYear(d.timeUTC(), limitCallArgs(call, 2), -1, true) + if !ok { + d.unset() + return _NaN + } + if d.isSet() { + return d.setTimeMs(timeToMsec(t)) + } else { + return _NaN + } + } + panic(r.NewTypeError("Method Date.prototype.setUTCMonth is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setFullYear(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + var t time.Time + if d.isSet() { + t = d.time() + } else { + t = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.Local) + } + t, ok := _dateSetFullYear(t, limitCallArgs(call, 3), 0, false) + if !ok { + d.unset() + return _NaN + } + return d.setTimeMs(timeToMsec(t)) + } + panic(r.NewTypeError("Method Date.prototype.setFullYear is called on incompatible receiver")) +} + +func (r *Runtime) dateproto_setUTCFullYear(call FunctionCall) Value { + obj := r.toObject(call.This) + if d, ok := obj.self.(*dateObject); ok { + var t time.Time + if d.isSet() { + t = d.timeUTC() + } else { + t = time.Date(1970, time.January, 1, 0, 0, 0, 0, time.UTC) + } + t, ok := _dateSetFullYear(t, limitCallArgs(call, 3), 0, true) + if !ok { + d.unset() + return _NaN + } + return d.setTimeMs(timeToMsec(t)) + } + panic(r.NewTypeError("Method Date.prototype.setUTCFullYear is called on incompatible receiver")) +} + +var dateTemplate *objectTemplate +var dateTemplateOnce sync.Once + +func getDateTemplate() *objectTemplate { + dateTemplateOnce.Do(func() { + dateTemplate = createDateTemplate() + }) + return dateTemplate +} + +func createDateTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.getFunctionPrototype() + } + + t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Date"), false, false, true) }) + t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(7), false, false, true) }) + + t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getDatePrototype(), false, false, false) }) + + t.putStr("parse", func(r *Runtime) Value { return r.methodProp(r.date_parse, "parse", 1) }) + t.putStr("UTC", func(r *Runtime) Value { return r.methodProp(r.date_UTC, "UTC", 7) }) + t.putStr("now", func(r *Runtime) Value { return r.methodProp(r.date_now, "now", 0) }) + + return t +} + +func (r *Runtime) getDate() *Object { + ret := r.global.Date + if ret == nil { + ret = &Object{runtime: r} + r.global.Date = ret + r.newTemplatedFuncObject(getDateTemplate(), ret, r.builtin_date, + r.wrapNativeConstruct(r.builtin_newDate, ret, r.getDatePrototype())) + } + return ret +} + +func createDateProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) }) + + t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toString, "toString", 0) }) + t.putStr("toDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toDateString, "toDateString", 0) }) + t.putStr("toTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toTimeString, "toTimeString", 0) }) + t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleString, "toLocaleString", 0) }) + t.putStr("toLocaleDateString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleDateString, "toLocaleDateString", 0) }) + t.putStr("toLocaleTimeString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toLocaleTimeString, "toLocaleTimeString", 0) }) + t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.dateproto_valueOf, "valueOf", 0) }) + t.putStr("getTime", func(r *Runtime) Value { return r.methodProp(r.dateproto_getTime, "getTime", 0) }) + t.putStr("getFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_getFullYear, "getFullYear", 0) }) + t.putStr("getUTCFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCFullYear, "getUTCFullYear", 0) }) + t.putStr("getMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_getMonth, "getMonth", 0) }) + t.putStr("getUTCMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCMonth, "getUTCMonth", 0) }) + t.putStr("getDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_getDate, "getDate", 0) }) + t.putStr("getUTCDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCDate, "getUTCDate", 0) }) + t.putStr("getDay", func(r *Runtime) Value { return r.methodProp(r.dateproto_getDay, "getDay", 0) }) + t.putStr("getUTCDay", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCDay, "getUTCDay", 0) }) + t.putStr("getHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_getHours, "getHours", 0) }) + t.putStr("getUTCHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCHours, "getUTCHours", 0) }) + t.putStr("getMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_getMinutes, "getMinutes", 0) }) + t.putStr("getUTCMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCMinutes, "getUTCMinutes", 0) }) + t.putStr("getSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getSeconds, "getSeconds", 0) }) + t.putStr("getUTCSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCSeconds, "getUTCSeconds", 0) }) + t.putStr("getMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getMilliseconds, "getMilliseconds", 0) }) + t.putStr("getUTCMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_getUTCMilliseconds, "getUTCMilliseconds", 0) }) + t.putStr("getTimezoneOffset", func(r *Runtime) Value { return r.methodProp(r.dateproto_getTimezoneOffset, "getTimezoneOffset", 0) }) + t.putStr("setTime", func(r *Runtime) Value { return r.methodProp(r.dateproto_setTime, "setTime", 1) }) + t.putStr("setMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setMilliseconds, "setMilliseconds", 1) }) + t.putStr("setUTCMilliseconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCMilliseconds, "setUTCMilliseconds", 1) }) + t.putStr("setSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setSeconds, "setSeconds", 2) }) + t.putStr("setUTCSeconds", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCSeconds, "setUTCSeconds", 2) }) + t.putStr("setMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_setMinutes, "setMinutes", 3) }) + t.putStr("setUTCMinutes", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCMinutes, "setUTCMinutes", 3) }) + t.putStr("setHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_setHours, "setHours", 4) }) + t.putStr("setUTCHours", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCHours, "setUTCHours", 4) }) + t.putStr("setDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_setDate, "setDate", 1) }) + t.putStr("setUTCDate", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCDate, "setUTCDate", 1) }) + t.putStr("setMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_setMonth, "setMonth", 2) }) + t.putStr("setUTCMonth", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCMonth, "setUTCMonth", 2) }) + t.putStr("setFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_setFullYear, "setFullYear", 3) }) + t.putStr("setUTCFullYear", func(r *Runtime) Value { return r.methodProp(r.dateproto_setUTCFullYear, "setUTCFullYear", 3) }) + t.putStr("toUTCString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toUTCString, "toUTCString", 0) }) + t.putStr("toISOString", func(r *Runtime) Value { return r.methodProp(r.dateproto_toISOString, "toISOString", 0) }) + t.putStr("toJSON", func(r *Runtime) Value { return r.methodProp(r.dateproto_toJSON, "toJSON", 1) }) + + t.putSym(SymToPrimitive, func(r *Runtime) Value { + return valueProp(r.newNativeFunc(r.dateproto_toPrimitive, "[Symbol.toPrimitive]", 1), false, false, true) + }) + + return t +} + +var dateProtoTemplate *objectTemplate +var dateProtoTemplateOnce sync.Once + +func getDateProtoTemplate() *objectTemplate { + dateProtoTemplateOnce.Do(func() { + dateProtoTemplate = createDateProtoTemplate() + }) + return dateProtoTemplate +} + +func (r *Runtime) getDatePrototype() *Object { + ret := r.global.DatePrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.DatePrototype = ret + r.newTemplatedObject(getDateProtoTemplate(), ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_error.go b/pkg/xscript/engine/builtin_error.go new file mode 100644 index 0000000..4db53c7 --- /dev/null +++ b/pkg/xscript/engine/builtin_error.go @@ -0,0 +1,289 @@ +package engine + +import "pandax/pkg/xscript/engine/unistring" + +const propNameStack = "stack" + +type errorObject struct { + baseObject + stack []StackFrame + stackPropAdded bool +} + +func (e *errorObject) formatStack() String { + var b StringBuilder + val := writeErrorString(&b, e.val) + if val != nil { + b.WriteString(val) + } + b.WriteRune('\n') + + for _, frame := range e.stack { + b.writeASCII("\tat ") + frame.WriteToValueBuilder(&b) + b.WriteRune('\n') + } + return b.String() +} + +func (e *errorObject) addStackProp() Value { + if !e.stackPropAdded { + res := e._putProp(propNameStack, e.formatStack(), true, false, true) + if len(e.propNames) > 1 { + // reorder property names to ensure 'stack' is the first one + copy(e.propNames[1:], e.propNames) + e.propNames[0] = propNameStack + } + e.stackPropAdded = true + return res + } + return nil +} + +func (e *errorObject) getStr(p unistring.String, receiver Value) Value { + return e.getStrWithOwnProp(e.getOwnPropStr(p), p, receiver) +} + +func (e *errorObject) getOwnPropStr(name unistring.String) Value { + res := e.baseObject.getOwnPropStr(name) + if res == nil && name == propNameStack { + return e.addStackProp() + } + + return res +} + +func (e *errorObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + if name == propNameStack { + e.addStackProp() + } + return e.baseObject.setOwnStr(name, val, throw) +} + +func (e *errorObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return e._setForeignStr(name, e.getOwnPropStr(name), val, receiver, throw) +} + +func (e *errorObject) deleteStr(name unistring.String, throw bool) bool { + if name == propNameStack { + e.addStackProp() + } + return e.baseObject.deleteStr(name, throw) +} + +func (e *errorObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + if name == propNameStack { + e.addStackProp() + } + return e.baseObject.defineOwnPropertyStr(name, desc, throw) +} + +func (e *errorObject) hasOwnPropertyStr(name unistring.String) bool { + if e.baseObject.hasOwnPropertyStr(name) { + return true + } + + return name == propNameStack && !e.stackPropAdded +} + +func (e *errorObject) stringKeys(all bool, accum []Value) []Value { + if all && !e.stackPropAdded { + accum = append(accum, asciiString(propNameStack)) + } + return e.baseObject.stringKeys(all, accum) +} + +func (e *errorObject) iterateStringKeys() iterNextFunc { + e.addStackProp() + return e.baseObject.iterateStringKeys() +} + +func (e *errorObject) init() { + e.baseObject.init() + vm := e.val.runtime.vm + e.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0) +} + +func (r *Runtime) newErrorObject(proto *Object, class string) *errorObject { + obj := &Object{runtime: r} + o := &errorObject{ + baseObject: baseObject{ + class: class, + val: obj, + extensible: true, + prototype: proto, + }, + } + obj.self = o + o.init() + return o +} + +func (r *Runtime) builtin_Error(args []Value, proto *Object) *Object { + obj := r.newErrorObject(proto, classError) + if len(args) > 0 && args[0] != _undefined { + obj._putProp("message", args[0], true, false, true) + } + return obj.val +} + +func (r *Runtime) builtin_AggregateError(args []Value, proto *Object) *Object { + obj := r.newErrorObject(proto, classError) + if len(args) > 1 && args[1] != nil && args[1] != _undefined { + obj._putProp("message", args[1].toString(), true, false, true) + } + var errors []Value + if len(args) > 0 { + errors = r.iterableToList(args[0], nil) + } + obj._putProp("errors", r.newArrayValues(errors), true, false, true) + + return obj.val +} + +func writeErrorString(sb *StringBuilder, obj *Object) String { + var nameStr, msgStr String + name := obj.self.getStr("name", nil) + if name == nil || name == _undefined { + nameStr = asciiString("Error") + } else { + nameStr = name.toString() + } + msg := obj.self.getStr("message", nil) + if msg == nil || msg == _undefined { + msgStr = stringEmpty + } else { + msgStr = msg.toString() + } + if nameStr.Length() == 0 { + return msgStr + } + if msgStr.Length() == 0 { + return nameStr + } + sb.WriteString(nameStr) + sb.WriteString(asciiString(": ")) + sb.WriteString(msgStr) + return nil +} + +func (r *Runtime) error_toString(call FunctionCall) Value { + var sb StringBuilder + val := writeErrorString(&sb, r.toObject(call.This)) + if val != nil { + return val + } + return sb.String() +} + +func (r *Runtime) createErrorPrototype(name String, ctor *Object) *Object { + o := r.newBaseObject(r.getErrorPrototype(), classObject) + o._putProp("message", stringEmpty, true, false, true) + o._putProp("name", name, true, false, true) + o._putProp("constructor", ctor, true, false, true) + return o.val +} + +func (r *Runtime) getErrorPrototype() *Object { + ret := r.global.ErrorPrototype + if ret == nil { + ret = r.NewObject() + r.global.ErrorPrototype = ret + o := ret.self + o._putProp("message", stringEmpty, true, false, true) + o._putProp("name", stringError, true, false, true) + o._putProp("toString", r.newNativeFunc(r.error_toString, "toString", 0), true, false, true) + o._putProp("constructor", r.getError(), true, false, true) + } + return ret +} + +func (r *Runtime) getError() *Object { + ret := r.global.Error + if ret == nil { + ret = &Object{runtime: r} + r.global.Error = ret + r.newNativeFuncConstruct(ret, r.builtin_Error, "Error", r.getErrorPrototype(), 1) + } + return ret +} + +func (r *Runtime) getAggregateError() *Object { + ret := r.global.AggregateError + if ret == nil { + ret = &Object{runtime: r} + r.global.AggregateError = ret + r.newNativeFuncConstructProto(ret, r.builtin_AggregateError, "AggregateError", r.createErrorPrototype(stringAggregateError, ret), r.getError(), 2) + } + return ret +} + +func (r *Runtime) getTypeError() *Object { + ret := r.global.TypeError + if ret == nil { + ret = &Object{runtime: r} + r.global.TypeError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "TypeError", r.createErrorPrototype(stringTypeError, ret), r.getError(), 1) + } + return ret +} + +func (r *Runtime) getReferenceError() *Object { + ret := r.global.ReferenceError + if ret == nil { + ret = &Object{runtime: r} + r.global.ReferenceError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "ReferenceError", r.createErrorPrototype(stringReferenceError, ret), r.getError(), 1) + } + return ret +} + +func (r *Runtime) getSyntaxError() *Object { + ret := r.global.SyntaxError + if ret == nil { + ret = &Object{runtime: r} + r.global.SyntaxError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "SyntaxError", r.createErrorPrototype(stringSyntaxError, ret), r.getError(), 1) + } + return ret +} + +func (r *Runtime) getRangeError() *Object { + ret := r.global.RangeError + if ret == nil { + ret = &Object{runtime: r} + r.global.RangeError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "RangeError", r.createErrorPrototype(stringRangeError, ret), r.getError(), 1) + } + return ret +} + +func (r *Runtime) getEvalError() *Object { + ret := r.global.EvalError + if ret == nil { + ret = &Object{runtime: r} + r.global.EvalError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "EvalError", r.createErrorPrototype(stringEvalError, ret), r.getError(), 1) + } + return ret +} + +func (r *Runtime) getURIError() *Object { + ret := r.global.URIError + if ret == nil { + ret = &Object{runtime: r} + r.global.URIError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "URIError", r.createErrorPrototype(stringURIError, ret), r.getError(), 1) + } + return ret +} + +func (r *Runtime) getGoError() *Object { + ret := r.global.GoError + if ret == nil { + ret = &Object{runtime: r} + r.global.GoError = ret + r.newNativeFuncConstructProto(ret, r.builtin_Error, "GoError", r.createErrorPrototype(stringGoError, ret), r.getError(), 1) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_event.go b/pkg/xscript/engine/builtin_event.go new file mode 100644 index 0000000..582915b --- /dev/null +++ b/pkg/xscript/engine/builtin_event.go @@ -0,0 +1,220 @@ +package engine + +import ( + "sync" + "time" + + "github.com/google/uuid" +) + +type Task struct { + done chan bool + function Value +} + +var ( + taskMap sync.Map + taskMapLock sync.RWMutex +) + +const ( + setImmediateFuncName = "setImmediate" + setTimeoutFuncName = "setTimeout" + setIntervalFuncName = "setInterval" + clearIntervalFuncName = "clearInterval" + getAllIntervalFuncName = "getAllInterval" + clearAllIntervalFuncName = "clearAllInterval" +) + +func (r *Runtime) builtin_setImmediate(call FunctionCall) Value { + taskMapLock.Lock() + defer taskMapLock.Unlock() + + taskID := uuid.New().String() + function := call.Argument(0) + + // if _, ok := taskMap.Load(taskID); ok { + // return r.NewTypeError("task is already exist") + // } + + task := &Task{ + done: make(chan bool), + function: function, + } + taskMap.Store(taskID, task) + + go func(done chan bool) { + for { + select { + case <-time.After(0): + if fn, ok := AssertFunction(task.function); ok { + if len(call.Arguments) > 1 { + fn(nil, call.Arguments[1:]...) + } else { + fn(nil) + } + } + case <-done: + return + } + } + }(task.done) + + return r.ToValue(taskID) +} + +func (r *Runtime) builtin_setTimeout(call FunctionCall) Value { + taskMapLock.Lock() + defer taskMapLock.Unlock() + + taskID := uuid.New().String() + function := call.Argument(0) + delay := call.Argument(1).ToInteger() + + // if _, ok := taskMap.Load(taskID); ok { + // return r.NewTypeError("task is already exist") + // } + + task := &Task{ + done: make(chan bool), + function: function, + } + + taskMap.Store(taskID, task) + + go func(done chan bool) { + select { + case <-time.After(time.Duration(delay) * time.Millisecond): + if fn, ok := AssertFunction(task.function); ok { + if len(call.Arguments) > 1 { + fn(nil, call.Arguments[1:]...) + } else { + fn(nil) + } + } + case <-done: + return + } + }(task.done) + + return r.ToValue(taskID) +} + +func (r *Runtime) builtin_setInterval(call FunctionCall) Value { + taskID := uuid.New().String() + function := call.Argument(0) + interval := call.Argument(1).ToInteger() + + // if _, ok := taskMap.Load(taskID); ok { + // return r.NewTypeError("task is already exist") + // } + if taskID == "" { + return Undefined() + } + + task := &Task{ + done: make(chan bool), + function: function, + } + taskMap.Store(taskID, task) + + go func(done chan bool) { + ticker := time.NewTicker(time.Duration(interval) * time.Millisecond) + for { + select { + case <-ticker.C: + if fn, ok := AssertFunction(task.function); ok { + if len(call.Arguments) > 2 { + fn(nil, call.Arguments[2:]...) + } else { + fn(nil) + } + } + case <-done: + ticker.Stop() + return + } + } + }(task.done) + + return r.ToValue(taskID) +} + +func (r *Runtime) setImmediate() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(r.builtin_setImmediate, setImmediateFuncName, 0) + r.global.Eval = ret + } + return ret +} + +func (r *Runtime) setTimeout() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(r.builtin_setTimeout, setTimeoutFuncName, 0) + r.global.Eval = ret + } + return ret +} + +func (r *Runtime) setInterval() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(r.builtin_setInterval, setIntervalFuncName, 0) + r.global.Eval = ret + } + return ret +} + +func (r *Runtime) clearInterval() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(clearInterval, clearIntervalFuncName, 0) + r.global.Eval = ret + } + return ret +} + +func (r *Runtime) getAllInterval() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(r.builtin_getAllInterval, getAllIntervalFuncName, 1) + r.global.Eval = ret + } + return ret +} + +func (r *Runtime) builtin_getAllInterval(call FunctionCall) Value { + var taskIDs []string + taskMap.Range(func(key, value interface{}) bool { + taskIDs = append(taskIDs, key.(string)) + return true + }) + return r.ToValue(taskIDs) +} + +func (r *Runtime) clearAllInterval() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(clearAllInterval, clearAllIntervalFuncName, 0) + r.global.Eval = ret + } + return ret +} + +func clearInterval(call FunctionCall) Value { + taskID := call.Argument(0).String() + taskMap.Delete(taskID) + return Undefined() +} + +func clearAllInterval(call FunctionCall) Value { + taskMap.Range(func(key, value interface{}) bool { + task := value.(*Task) + task.done <- true + taskMap.Delete(key) + return true + }) + return Undefined() +} diff --git a/pkg/xscript/engine/builtin_function.go b/pkg/xscript/engine/builtin_function.go new file mode 100644 index 0000000..f4eab27 --- /dev/null +++ b/pkg/xscript/engine/builtin_function.go @@ -0,0 +1,416 @@ +package engine + +import ( + "math" + "sync" +) + +func (r *Runtime) functionCtor(args []Value, proto *Object, async, generator bool) *Object { + var sb StringBuilder + if async { + if generator { + sb.WriteString(asciiString("(async function* anonymous(")) + } else { + sb.WriteString(asciiString("(async function anonymous(")) + } + } else { + if generator { + sb.WriteString(asciiString("(function* anonymous(")) + } else { + sb.WriteString(asciiString("(function anonymous(")) + } + } + if len(args) > 1 { + ar := args[:len(args)-1] + for i, arg := range ar { + sb.WriteString(arg.toString()) + if i < len(ar)-1 { + sb.WriteRune(',') + } + } + } + sb.WriteString(asciiString("\n) {\n")) + if len(args) > 0 { + sb.WriteString(args[len(args)-1].toString()) + } + sb.WriteString(asciiString("\n})")) + + ret := r.toObject(r.eval(sb.String(), false, false)) + ret.self.setProto(proto, true) + return ret +} + +func (r *Runtime) builtin_Function(args []Value, proto *Object) *Object { + return r.functionCtor(args, proto, false, false) +} + +func (r *Runtime) builtin_asyncFunction(args []Value, proto *Object) *Object { + return r.functionCtor(args, proto, true, false) +} + +func (r *Runtime) builtin_generatorFunction(args []Value, proto *Object) *Object { + return r.functionCtor(args, proto, false, true) +} + +func (r *Runtime) functionproto_toString(call FunctionCall) Value { + obj := r.toObject(call.This) + switch f := obj.self.(type) { + case funcObjectImpl: + return f.source() + case *proxyObject: + if _, ok := f.target.self.(funcObjectImpl); ok { + return asciiString("function () { [native code] }") + } + } + panic(r.NewTypeError("Function.prototype.toString requires that 'this' be a Function")) +} + +func (r *Runtime) functionproto_hasInstance(call FunctionCall) Value { + if o, ok := call.This.(*Object); ok { + if _, ok = o.self.assertCallable(); ok { + return r.toBoolean(o.self.hasInstance(call.Argument(0))) + } + } + + return valueFalse +} + +func (r *Runtime) createListFromArrayLike(a Value) []Value { + o := r.toObject(a) + if arr := r.checkStdArrayObj(o); arr != nil { + return arr.values + } + l := toLength(o.self.getStr("length", nil)) + res := make([]Value, 0, l) + for k := int64(0); k < l; k++ { + res = append(res, nilSafe(o.self.getIdx(valueInt(k), nil))) + } + return res +} + +func (r *Runtime) functionproto_apply(call FunctionCall) Value { + var args []Value + if len(call.Arguments) >= 2 { + args = r.createListFromArrayLike(call.Arguments[1]) + } + + f := r.toCallable(call.This) + return f(FunctionCall{ + This: call.Argument(0), + Arguments: args, + }) +} + +func (r *Runtime) functionproto_call(call FunctionCall) Value { + var args []Value + if len(call.Arguments) > 0 { + args = call.Arguments[1:] + } + + f := r.toCallable(call.This) + return f(FunctionCall{ + This: call.Argument(0), + Arguments: args, + }) +} + +func (r *Runtime) boundCallable(target func(FunctionCall) Value, boundArgs []Value) func(FunctionCall) Value { + var this Value + var args []Value + if len(boundArgs) > 0 { + this = boundArgs[0] + args = make([]Value, len(boundArgs)-1) + copy(args, boundArgs[1:]) + } else { + this = _undefined + } + return func(call FunctionCall) Value { + a := append(args, call.Arguments...) + return target(FunctionCall{ + This: this, + Arguments: a, + }) + } +} + +func (r *Runtime) boundConstruct(f *Object, target func([]Value, *Object) *Object, boundArgs []Value) func([]Value, *Object) *Object { + if target == nil { + return nil + } + var args []Value + if len(boundArgs) > 1 { + args = make([]Value, len(boundArgs)-1) + copy(args, boundArgs[1:]) + } + return func(fargs []Value, newTarget *Object) *Object { + a := append(args, fargs...) + if newTarget == f { + newTarget = nil + } + return target(a, newTarget) + } +} + +func (r *Runtime) functionproto_bind(call FunctionCall) Value { + obj := r.toObject(call.This) + + fcall := r.toCallable(call.This) + construct := obj.self.assertConstructor() + + var l = _positiveZero + if obj.self.hasOwnPropertyStr("length") { + var li int64 + switch lenProp := nilSafe(obj.self.getStr("length", nil)).(type) { + case valueInt: + li = lenProp.ToInteger() + case valueFloat: + switch lenProp { + case _positiveInf: + l = lenProp + goto lenNotInt + case _negativeInf: + goto lenNotInt + case _negativeZero: + // no-op, li == 0 + default: + if !math.IsNaN(float64(lenProp)) { + li = int64(math.Abs(float64(lenProp))) + } // else li = 0 + } + } + if len(call.Arguments) > 1 { + li -= int64(len(call.Arguments)) - 1 + } + if li < 0 { + li = 0 + } + l = intToValue(li) + } +lenNotInt: + name := obj.self.getStr("name", nil) + nameStr := stringBound_ + if s, ok := name.(String); ok { + nameStr = nameStr.Concat(s) + } + + v := &Object{runtime: r} + ff := r.newNativeFuncAndConstruct(v, r.boundCallable(fcall, call.Arguments), r.boundConstruct(v, construct, call.Arguments), nil, nameStr.string(), l) + bf := &boundFuncObject{ + nativeFuncObject: *ff, + wrapped: obj, + } + bf.prototype = obj.self.proto() + v.self = bf + + return v +} + +func (r *Runtime) getThrower() *Object { + ret := r.global.thrower + if ret == nil { + ret = r.newNativeFunc(r.builtin_thrower, "", 0) + r.global.thrower = ret + r.object_freeze(FunctionCall{Arguments: []Value{ret}}) + } + return ret +} + +func (r *Runtime) newThrowerProperty(configurable bool) Value { + thrower := r.getThrower() + return &valueProperty{ + getterFunc: thrower, + setterFunc: thrower, + accessor: true, + configurable: configurable, + } +} + +func createFunctionProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) }) + + t.putStr("length", func(r *Runtime) Value { return valueProp(_positiveZero, false, false, true) }) + t.putStr("name", func(r *Runtime) Value { return valueProp(stringEmpty, false, false, true) }) + + t.putStr("apply", func(r *Runtime) Value { return r.methodProp(r.functionproto_apply, "apply", 2) }) + t.putStr("bind", func(r *Runtime) Value { return r.methodProp(r.functionproto_bind, "bind", 1) }) + t.putStr("call", func(r *Runtime) Value { return r.methodProp(r.functionproto_call, "call", 1) }) + t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.functionproto_toString, "toString", 0) }) + + t.putStr("caller", func(r *Runtime) Value { return r.newThrowerProperty(true) }) + t.putStr("arguments", func(r *Runtime) Value { return r.newThrowerProperty(true) }) + + t.putSym(SymHasInstance, func(r *Runtime) Value { + return valueProp(r.newNativeFunc(r.functionproto_hasInstance, "[Symbol.hasInstance]", 1), false, false, false) + }) + + return t +} + +var functionProtoTemplate *objectTemplate +var functionProtoTemplateOnce sync.Once + +func getFunctionProtoTemplate() *objectTemplate { + functionProtoTemplateOnce.Do(func() { + functionProtoTemplate = createFunctionProtoTemplate() + }) + return functionProtoTemplate +} + +func (r *Runtime) getFunctionPrototype() *Object { + ret := r.global.FunctionPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.FunctionPrototype = ret + r.newTemplatedFuncObject(getFunctionProtoTemplate(), ret, func(FunctionCall) Value { + return _undefined + }, nil) + } + return ret +} + +func (r *Runtime) createFunction(v *Object) objectImpl { + return r.newNativeFuncConstructObj(v, r.builtin_Function, "Function", r.getFunctionPrototype(), 1) +} + +func (r *Runtime) createAsyncFunctionProto(val *Object) objectImpl { + o := &baseObject{ + class: classObject, + val: val, + extensible: true, + prototype: r.getFunctionPrototype(), + } + o.init() + + o._putProp("constructor", r.getAsyncFunction(), true, false, true) + + o._putSym(SymToStringTag, valueProp(asciiString(classAsyncFunction), false, false, true)) + + return o +} + +func (r *Runtime) getAsyncFunctionPrototype() *Object { + var o *Object + if o = r.global.AsyncFunctionPrototype; o == nil { + o = &Object{runtime: r} + r.global.AsyncFunctionPrototype = o + o.self = r.createAsyncFunctionProto(o) + } + return o +} + +func (r *Runtime) createAsyncFunction(val *Object) objectImpl { + o := r.newNativeFuncConstructObj(val, r.builtin_asyncFunction, "AsyncFunction", r.getAsyncFunctionPrototype(), 1) + + return o +} + +func (r *Runtime) getAsyncFunction() *Object { + var o *Object + if o = r.global.AsyncFunction; o == nil { + o = &Object{runtime: r} + r.global.AsyncFunction = o + o.self = r.createAsyncFunction(o) + } + return o +} + +func (r *Runtime) builtin_genproto_next(call FunctionCall) Value { + if o, ok := call.This.(*Object); ok { + if gen, ok := o.self.(*generatorObject); ok { + return gen.next(call.Argument(0)) + } + } + panic(r.NewTypeError("Method [Generator].prototype.next called on incompatible receiver")) +} + +func (r *Runtime) builtin_genproto_return(call FunctionCall) Value { + if o, ok := call.This.(*Object); ok { + if gen, ok := o.self.(*generatorObject); ok { + return gen._return(call.Argument(0)) + } + } + panic(r.NewTypeError("Method [Generator].prototype.return called on incompatible receiver")) +} + +func (r *Runtime) builtin_genproto_throw(call FunctionCall) Value { + if o, ok := call.This.(*Object); ok { + if gen, ok := o.self.(*generatorObject); ok { + return gen.throw(call.Argument(0)) + } + } + panic(r.NewTypeError("Method [Generator].prototype.throw called on incompatible receiver")) +} + +func (r *Runtime) createGeneratorFunctionProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getFunctionPrototype(), classObject) + + o._putProp("constructor", r.getGeneratorFunction(), false, false, true) + o._putProp("prototype", r.getGeneratorPrototype(), false, false, true) + o._putSym(SymToStringTag, valueProp(asciiString(classGeneratorFunction), false, false, true)) + + return o +} + +func (r *Runtime) getGeneratorFunctionPrototype() *Object { + var o *Object + if o = r.global.GeneratorFunctionPrototype; o == nil { + o = &Object{runtime: r} + r.global.GeneratorFunctionPrototype = o + o.self = r.createGeneratorFunctionProto(o) + } + return o +} + +func (r *Runtime) createGeneratorFunction(val *Object) objectImpl { + o := r.newNativeFuncConstructObj(val, r.builtin_generatorFunction, "GeneratorFunction", r.getGeneratorFunctionPrototype(), 1) + return o +} + +func (r *Runtime) getGeneratorFunction() *Object { + var o *Object + if o = r.global.GeneratorFunction; o == nil { + o = &Object{runtime: r} + r.global.GeneratorFunction = o + o.self = r.createGeneratorFunction(o) + } + return o +} + +func (r *Runtime) createGeneratorProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) + + o._putProp("constructor", r.getGeneratorFunctionPrototype(), false, false, true) + o._putProp("next", r.newNativeFunc(r.builtin_genproto_next, "next", 1), true, false, true) + o._putProp("return", r.newNativeFunc(r.builtin_genproto_return, "return", 1), true, false, true) + o._putProp("throw", r.newNativeFunc(r.builtin_genproto_throw, "throw", 1), true, false, true) + + o._putSym(SymToStringTag, valueProp(asciiString(classGenerator), false, false, true)) + + return o +} + +func (r *Runtime) getGeneratorPrototype() *Object { + var o *Object + if o = r.global.GeneratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.GeneratorPrototype = o + o.self = r.createGeneratorProto(o) + } + return o +} + +func (r *Runtime) getFunction() *Object { + ret := r.global.Function + if ret == nil { + ret = &Object{runtime: r} + r.global.Function = ret + ret.self = r.createFunction(ret) + } + + return ret +} diff --git a/pkg/xscript/engine/builtin_function_test.go b/pkg/xscript/engine/builtin_function_test.go new file mode 100644 index 0000000..60fa5cd --- /dev/null +++ b/pkg/xscript/engine/builtin_function_test.go @@ -0,0 +1,14 @@ +package engine + +import ( + "testing" +) + +func TestHashbangInFunctionConstructor(t *testing.T) { + const SCRIPT = ` + assert.throws(SyntaxError, function() { + new Function("#!") + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} diff --git a/pkg/xscript/engine/builtin_global.go b/pkg/xscript/engine/builtin_global.go new file mode 100644 index 0000000..0c9b3e8 --- /dev/null +++ b/pkg/xscript/engine/builtin_global.go @@ -0,0 +1,588 @@ +package engine + +import ( + "errors" + "io" + "math" + "regexp" + "strconv" + "strings" + "sync" + "unicode/utf8" + + "pandax/pkg/xscript/engine/unistring" +) + +const hexUpper = "0123456789ABCDEF" + +var ( + parseFloatRegexp = regexp.MustCompile(`^([+-]?(?:Infinity|[0-9]*\.?[0-9]*(?:[eE][+-]?[0-9]+)?))`) +) + +func (r *Runtime) builtin_isNaN(call FunctionCall) Value { + if math.IsNaN(call.Argument(0).ToFloat()) { + return valueTrue + } else { + return valueFalse + } +} + +func (r *Runtime) builtin_parseInt(call FunctionCall) Value { + str := call.Argument(0).toString().toTrimmedUTF8() + radix := int(toInt32(call.Argument(1))) + v, _ := parseInt(str, radix) + return v +} + +func (r *Runtime) builtin_parseFloat(call FunctionCall) Value { + m := parseFloatRegexp.FindStringSubmatch(call.Argument(0).toString().toTrimmedUTF8()) + if len(m) == 2 { + if s := m[1]; s != "" && s != "+" && s != "-" { + switch s { + case "+", "-": + case "Infinity", "+Infinity": + return _positiveInf + case "-Infinity": + return _negativeInf + default: + f, err := strconv.ParseFloat(s, 64) + if err == nil || isRangeErr(err) { + return floatToValue(f) + } + } + } + } + return _NaN +} + +func (r *Runtime) builtin_isFinite(call FunctionCall) Value { + f := call.Argument(0).ToFloat() + if math.IsNaN(f) || math.IsInf(f, 0) { + return valueFalse + } + return valueTrue +} + +func (r *Runtime) _encode(uriString String, unescaped *[256]bool) String { + reader := uriString.Reader() + utf8Buf := make([]byte, utf8.UTFMax) + needed := false + l := 0 + for { + rn, _, err := reader.ReadRune() + if err != nil { + if err != io.EOF { + panic(r.newError(r.getURIError(), "Malformed URI")) + } + break + } + + if rn >= utf8.RuneSelf { + needed = true + l += utf8.EncodeRune(utf8Buf, rn) * 3 + } else if !unescaped[rn] { + needed = true + l += 3 + } else { + l++ + } + } + + if !needed { + return uriString + } + + buf := make([]byte, l) + i := 0 + reader = uriString.Reader() + for { + rn, _, err := reader.ReadRune() + if err == io.EOF { + break + } + + if rn >= utf8.RuneSelf { + n := utf8.EncodeRune(utf8Buf, rn) + for _, b := range utf8Buf[:n] { + buf[i] = '%' + buf[i+1] = hexUpper[b>>4] + buf[i+2] = hexUpper[b&15] + i += 3 + } + } else if !unescaped[rn] { + buf[i] = '%' + buf[i+1] = hexUpper[rn>>4] + buf[i+2] = hexUpper[rn&15] + i += 3 + } else { + buf[i] = byte(rn) + i++ + } + } + return asciiString(buf) +} + +func (r *Runtime) _decode(sv String, reservedSet *[256]bool) String { + s := sv.String() + hexCount := 0 + for i := 0; i < len(s); { + switch s[i] { + case '%': + if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { + panic(r.newError(r.getURIError(), "Malformed URI")) + } + c := unhex(s[i+1])<<4 | unhex(s[i+2]) + if !reservedSet[c] { + hexCount++ + } + i += 3 + default: + i++ + } + } + + if hexCount == 0 { + return sv + } + + t := make([]byte, len(s)-hexCount*2) + j := 0 + isUnicode := false + for i := 0; i < len(s); { + ch := s[i] + switch ch { + case '%': + c := unhex(s[i+1])<<4 | unhex(s[i+2]) + if reservedSet[c] { + t[j] = s[i] + t[j+1] = s[i+1] + t[j+2] = s[i+2] + j += 3 + } else { + t[j] = c + if c >= utf8.RuneSelf { + isUnicode = true + } + j++ + } + i += 3 + default: + if ch >= utf8.RuneSelf { + isUnicode = true + } + t[j] = ch + j++ + i++ + } + } + + if !isUnicode { + return asciiString(t) + } + + us := make([]rune, 0, len(s)) + for len(t) > 0 { + rn, size := utf8.DecodeRune(t) + if rn == utf8.RuneError { + if size != 3 || t[0] != 0xef || t[1] != 0xbf || t[2] != 0xbd { + panic(r.newError(r.getURIError(), "Malformed URI")) + } + } + us = append(us, rn) + t = t[size:] + } + return unicodeStringFromRunes(us) +} + +func ishex(c byte) bool { + switch { + case '0' <= c && c <= '9': + return true + case 'a' <= c && c <= 'f': + return true + case 'A' <= c && c <= 'F': + return true + } + return false +} + +func unhex(c byte) byte { + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + } + return 0 +} + +func (r *Runtime) builtin_decodeURI(call FunctionCall) Value { + uriString := call.Argument(0).toString() + return r._decode(uriString, &uriReservedHash) +} + +func (r *Runtime) builtin_decodeURIComponent(call FunctionCall) Value { + uriString := call.Argument(0).toString() + return r._decode(uriString, &emptyEscapeSet) +} + +func (r *Runtime) builtin_encodeURI(call FunctionCall) Value { + uriString := call.Argument(0).toString() + return r._encode(uriString, &uriReservedUnescapedHash) +} + +func (r *Runtime) builtin_encodeURIComponent(call FunctionCall) Value { + uriString := call.Argument(0).toString() + return r._encode(uriString, &uriUnescaped) +} + +func (r *Runtime) builtin_escape(call FunctionCall) Value { + s := call.Argument(0).toString() + var sb strings.Builder + l := s.Length() + for i := 0; i < l; i++ { + r := s.CharAt(i) + if r >= 'A' && r <= 'Z' || r >= 'a' && r <= 'z' || r >= '0' && r <= '9' || + r == '@' || r == '*' || r == '_' || r == '+' || r == '-' || r == '.' || r == '/' { + sb.WriteByte(byte(r)) + } else if r <= 0xff { + sb.WriteByte('%') + sb.WriteByte(hexUpper[r>>4]) + sb.WriteByte(hexUpper[r&0xf]) + } else { + sb.WriteString("%u") + sb.WriteByte(hexUpper[r>>12]) + sb.WriteByte(hexUpper[(r>>8)&0xf]) + sb.WriteByte(hexUpper[(r>>4)&0xf]) + sb.WriteByte(hexUpper[r&0xf]) + } + } + return asciiString(sb.String()) +} + +func (r *Runtime) builtin_unescape(call FunctionCall) Value { + s := call.Argument(0).toString() + l := s.Length() + var asciiBuf []byte + var unicodeBuf []uint16 + _, u := devirtualizeString(s) + unicode := u != nil + if unicode { + unicodeBuf = make([]uint16, 1, l+1) + unicodeBuf[0] = unistring.BOM + } else { + asciiBuf = make([]byte, 0, l) + } + for i := 0; i < l; { + r := s.CharAt(i) + if r == '%' { + if i <= l-6 && s.CharAt(i+1) == 'u' { + c0 := s.CharAt(i + 2) + c1 := s.CharAt(i + 3) + c2 := s.CharAt(i + 4) + c3 := s.CharAt(i + 5) + if c0 <= 0xff && ishex(byte(c0)) && + c1 <= 0xff && ishex(byte(c1)) && + c2 <= 0xff && ishex(byte(c2)) && + c3 <= 0xff && ishex(byte(c3)) { + r = uint16(unhex(byte(c0)))<<12 | + uint16(unhex(byte(c1)))<<8 | + uint16(unhex(byte(c2)))<<4 | + uint16(unhex(byte(c3))) + i += 5 + goto out + } + } + if i <= l-3 { + c0 := s.CharAt(i + 1) + c1 := s.CharAt(i + 2) + if c0 <= 0xff && ishex(byte(c0)) && + c1 <= 0xff && ishex(byte(c1)) { + r = uint16(unhex(byte(c0))<<4 | unhex(byte(c1))) + i += 2 + } + } + } + out: + if r >= utf8.RuneSelf && !unicode { + unicodeBuf = make([]uint16, 1, l+1) + unicodeBuf[0] = unistring.BOM + for _, b := range asciiBuf { + unicodeBuf = append(unicodeBuf, uint16(b)) + } + asciiBuf = nil + unicode = true + } + if unicode { + unicodeBuf = append(unicodeBuf, r) + } else { + asciiBuf = append(asciiBuf, byte(r)) + } + i++ + } + if unicode { + return unicodeString(unicodeBuf) + } + + return asciiString(asciiBuf) +} + +func createGlobalObjectTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("Object", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) }) + t.putStr("Function", func(r *Runtime) Value { return valueProp(r.getFunction(), true, false, true) }) + t.putStr("Array", func(r *Runtime) Value { return valueProp(r.getArray(), true, false, true) }) + t.putStr("String", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) }) + t.putStr("Number", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) }) + t.putStr("RegExp", func(r *Runtime) Value { return valueProp(r.getRegExp(), true, false, true) }) + t.putStr("Date", func(r *Runtime) Value { return valueProp(r.getDate(), true, false, true) }) + t.putStr("Boolean", func(r *Runtime) Value { return valueProp(r.getBoolean(), true, false, true) }) + t.putStr("Proxy", func(r *Runtime) Value { return valueProp(r.getProxy(), true, false, true) }) + t.putStr("Reflect", func(r *Runtime) Value { return valueProp(r.getReflect(), true, false, true) }) + t.putStr("Error", func(r *Runtime) Value { return valueProp(r.getError(), true, false, true) }) + t.putStr("AggregateError", func(r *Runtime) Value { return valueProp(r.getAggregateError(), true, false, true) }) + t.putStr("TypeError", func(r *Runtime) Value { return valueProp(r.getTypeError(), true, false, true) }) + t.putStr("ReferenceError", func(r *Runtime) Value { return valueProp(r.getReferenceError(), true, false, true) }) + t.putStr("SyntaxError", func(r *Runtime) Value { return valueProp(r.getSyntaxError(), true, false, true) }) + t.putStr("RangeError", func(r *Runtime) Value { return valueProp(r.getRangeError(), true, false, true) }) + t.putStr("EvalError", func(r *Runtime) Value { return valueProp(r.getEvalError(), true, false, true) }) + t.putStr("URIError", func(r *Runtime) Value { return valueProp(r.getURIError(), true, false, true) }) + t.putStr("GoError", func(r *Runtime) Value { return valueProp(r.getGoError(), true, false, true) }) + + t.putStr("eval", func(r *Runtime) Value { return valueProp(r.getEval(), true, false, true) }) + + t.putStr("setImmediate", func(r *Runtime) Value { return valueProp(r.setImmediate(), true, false, true) }) + t.putStr("clearImmediate", func(r *Runtime) Value { return valueProp(r.clearInterval(), true, false, true) }) + t.putStr("getAllImmediate", func(r *Runtime) Value { return valueProp(r.getAllInterval(), true, false, true) }) + t.putStr("clearAllImmediate", func(r *Runtime) Value { return valueProp(r.clearAllInterval(), false, false, true) }) + + t.putStr("setTimeout", func(r *Runtime) Value { return valueProp(r.setTimeout(), true, false, true) }) + t.putStr("setInterval", func(r *Runtime) Value { return valueProp(r.setInterval(), true, false, true) }) + t.putStr("clearInterval", func(r *Runtime) Value { return valueProp(r.clearInterval(), true, false, true) }) + t.putStr("getAllInterval", func(r *Runtime) Value { return valueProp(r.getAllInterval(), true, false, true) }) + t.putStr("clearAllInterval", func(r *Runtime) Value { return valueProp(r.clearAllInterval(), false, false, true) }) + + t.putStr("fetch", func(r *Runtime) Value { return valueProp(r.fetch(), true, false, true) }) + + t.putStr("Math", func(r *Runtime) Value { return valueProp(r.getMath(), true, false, true) }) + t.putStr("JSON", func(r *Runtime) Value { return valueProp(r.getJSON(), true, false, true) }) + addTypedArrays(t) + t.putStr("Symbol", func(r *Runtime) Value { return valueProp(r.getSymbol(), true, false, true) }) + t.putStr("WeakSet", func(r *Runtime) Value { return valueProp(r.getWeakSet(), true, false, true) }) + t.putStr("WeakMap", func(r *Runtime) Value { return valueProp(r.getWeakMap(), true, false, true) }) + t.putStr("Map", func(r *Runtime) Value { return valueProp(r.getMap(), true, false, true) }) + t.putStr("Set", func(r *Runtime) Value { return valueProp(r.getSet(), true, false, true) }) + t.putStr("Promise", func(r *Runtime) Value { return valueProp(r.getPromise(), true, false, true) }) + + t.putStr("globalThis", func(r *Runtime) Value { return valueProp(r.globalObject, true, false, true) }) + t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) }) + t.putStr("undefined", func(r *Runtime) Value { return valueProp(_undefined, false, false, false) }) + t.putStr("Infinity", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) }) + + t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.builtin_isNaN, "isNaN", 1) }) + t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) }) + t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) }) + t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.builtin_isFinite, "isFinite", 1) }) + t.putStr("decodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURI, "decodeURI", 1) }) + t.putStr("decodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_decodeURIComponent, "decodeURIComponent", 1) }) + t.putStr("encodeURI", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURI, "encodeURI", 1) }) + t.putStr("encodeURIComponent", func(r *Runtime) Value { return r.methodProp(r.builtin_encodeURIComponent, "encodeURIComponent", 1) }) + t.putStr("escape", func(r *Runtime) Value { return r.methodProp(r.builtin_escape, "escape", 1) }) + t.putStr("unescape", func(r *Runtime) Value { return r.methodProp(r.builtin_unescape, "unescape", 1) }) + + // TODO: Annex B + + t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classGlobal), false, false, true) }) + + return t +} + +var globalObjectTemplate *objectTemplate +var globalObjectTemplateOnce sync.Once + +func getGlobalObjectTemplate() *objectTemplate { + globalObjectTemplateOnce.Do(func() { + globalObjectTemplate = createGlobalObjectTemplate() + }) + return globalObjectTemplate +} + +func (r *Runtime) getEval() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(r.builtin_eval, "eval", 1) + r.global.Eval = ret + } + return ret +} + +func digitVal(d byte) int { + var v byte + switch { + case '0' <= d && d <= '9': + v = d - '0' + case 'a' <= d && d <= 'z': + v = d - 'a' + 10 + case 'A' <= d && d <= 'Z': + v = d - 'A' + 10 + default: + return 36 + } + return int(v) +} + +// ECMAScript compatible version of strconv.ParseInt +func parseInt(s string, base int) (Value, error) { + var n int64 + var err error + var cutoff, maxVal int64 + var sign bool + i := 0 + + if len(s) < 1 { + err = strconv.ErrSyntax + goto Error + } + + switch s[0] { + case '-': + sign = true + s = s[1:] + case '+': + s = s[1:] + } + + if len(s) < 1 { + err = strconv.ErrSyntax + goto Error + } + + // Look for hex prefix. + if s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X') { + if base == 0 || base == 16 { + base = 16 + s = s[2:] + } + } + + switch { + case len(s) < 1: + err = strconv.ErrSyntax + goto Error + + case 2 <= base && base <= 36: + // valid base; nothing to do + + case base == 0: + // Look for hex prefix. + switch { + case s[0] == '0' && len(s) > 1 && (s[1] == 'x' || s[1] == 'X'): + if len(s) < 3 { + err = strconv.ErrSyntax + goto Error + } + base = 16 + s = s[2:] + default: + base = 10 + } + + default: + err = errors.New("invalid base " + strconv.Itoa(base)) + goto Error + } + + // Cutoff is the smallest number such that cutoff*base > maxInt64. + // Use compile-time constants for common cases. + switch base { + case 10: + cutoff = math.MaxInt64/10 + 1 + case 16: + cutoff = math.MaxInt64/16 + 1 + default: + cutoff = math.MaxInt64/int64(base) + 1 + } + + maxVal = math.MaxInt64 + for ; i < len(s); i++ { + if n >= cutoff { + // n*base overflows + return parseLargeInt(float64(n), s[i:], base, sign) + } + v := digitVal(s[i]) + if v >= base { + break + } + n *= int64(base) + + n1 := n + int64(v) + if n1 < n || n1 > maxVal { + // n+v overflows + return parseLargeInt(float64(n)+float64(v), s[i+1:], base, sign) + } + n = n1 + } + + if i == 0 { + err = strconv.ErrSyntax + goto Error + } + + if sign { + n = -n + } + return intToValue(n), nil + +Error: + return _NaN, err +} + +func parseLargeInt(n float64, s string, base int, sign bool) (Value, error) { + i := 0 + b := float64(base) + for ; i < len(s); i++ { + v := digitVal(s[i]) + if v >= base { + break + } + n = n*b + float64(v) + } + if sign { + n = -n + } + // We know it can't be represented as int, so use valueFloat instead of floatToValue + return valueFloat(n), nil +} + +var ( + uriUnescaped [256]bool + uriReserved [256]bool + uriReservedHash [256]bool + uriReservedUnescapedHash [256]bool + emptyEscapeSet [256]bool +) + +func init() { + for _, c := range "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789-_.!~*'()" { + uriUnescaped[c] = true + } + + for _, c := range ";/?:@&=+$," { + uriReserved[c] = true + } + + for i := 0; i < 256; i++ { + if uriUnescaped[i] || uriReserved[i] { + uriReservedUnescapedHash[i] = true + } + uriReservedHash[i] = uriReserved[i] + } + uriReservedUnescapedHash['#'] = true + uriReservedHash['#'] = true +} diff --git a/pkg/xscript/engine/builtin_global_test.go b/pkg/xscript/engine/builtin_global_test.go new file mode 100644 index 0000000..c610b95 --- /dev/null +++ b/pkg/xscript/engine/builtin_global_test.go @@ -0,0 +1,21 @@ +package engine + +import ( + "testing" +) + +func TestEncodeURI(t *testing.T) { + const SCRIPT = ` + encodeURI('тест') + ` + + testScript(SCRIPT, asciiString("%D1%82%D0%B5%D1%81%D1%82"), t) +} + +func TestDecodeURI(t *testing.T) { + const SCRIPT = ` + decodeURI("http://ru.wikipedia.org/wiki/%d0%ae%D0%bd%D0%B8%D0%BA%D0%BE%D0%B4") + ` + + testScript(SCRIPT, newStringValue("http://ru.wikipedia.org/wiki/Юникод"), t) +} diff --git a/pkg/xscript/engine/builtin_http.go b/pkg/xscript/engine/builtin_http.go new file mode 100644 index 0000000..7a9de52 --- /dev/null +++ b/pkg/xscript/engine/builtin_http.go @@ -0,0 +1,36 @@ +package engine + +import ( + "io" + "net/http" +) + +func (r *Runtime) builtin_fetch(call FunctionCall) Value { + url := call.Argument(0).String() + + // 发送GET请求 + resp, err := http.Get(url) + if err != nil { + // 处理错误 + return Null() + } + defer resp.Body.Close() + + // 读取响应内容 + body, err := io.ReadAll(resp.Body) + if err != nil { + // 处理错误 + return Null() + } + + return r.ToValue(body) +} + +func (r *Runtime) fetch() *Object { + ret := r.global.Eval + if ret == nil { + ret = r.newNativeFunc(r.builtin_fetch, "fetch", 1) + r.global.Eval = ret + } + return ret +} diff --git a/pkg/xscript/engine/builtin_json.go b/pkg/xscript/engine/builtin_json.go new file mode 100644 index 0000000..e7e0c99 --- /dev/null +++ b/pkg/xscript/engine/builtin_json.go @@ -0,0 +1,536 @@ +package engine + +import ( + "bytes" + "encoding/json" + "errors" + "fmt" + "io" + "math" + "strconv" + "strings" + "unicode/utf16" + "unicode/utf8" + + "pandax/pkg/xscript/engine/unistring" +) + +const hex = "0123456789abcdef" + +func (r *Runtime) builtinJSON_parse(call FunctionCall) Value { + d := json.NewDecoder(strings.NewReader(call.Argument(0).toString().String())) + + value, err := r.builtinJSON_decodeValue(d) + if errors.Is(err, io.EOF) { + panic(r.newError(r.getSyntaxError(), "Unexpected end of JSON input (%v)", err.Error())) + } + if err != nil { + panic(r.newError(r.getSyntaxError(), err.Error())) + } + + if tok, err := d.Token(); err != io.EOF { + panic(r.newError(r.getSyntaxError(), "Unexpected token at the end: %v", tok)) + } + + var reviver func(FunctionCall) Value + + if arg1 := call.Argument(1); arg1 != _undefined { + reviver, _ = arg1.ToObject(r).self.assertCallable() + } + + if reviver != nil { + root := r.NewObject() + createDataPropertyOrThrow(root, stringEmpty, value) + return r.builtinJSON_reviveWalk(reviver, root, stringEmpty) + } + + return value +} + +func (r *Runtime) builtinJSON_decodeToken(d *json.Decoder, tok json.Token) (Value, error) { + switch tok := tok.(type) { + case json.Delim: + switch tok { + case '{': + return r.builtinJSON_decodeObject(d) + case '[': + return r.builtinJSON_decodeArray(d) + } + case nil: + return _null, nil + case string: + return newStringValue(tok), nil + case float64: + return floatToValue(tok), nil + case bool: + if tok { + return valueTrue, nil + } + return valueFalse, nil + } + return nil, fmt.Errorf("Unexpected token (%T): %v", tok, tok) +} + +func (r *Runtime) builtinJSON_decodeValue(d *json.Decoder) (Value, error) { + tok, err := d.Token() + if err != nil { + return nil, err + } + return r.builtinJSON_decodeToken(d, tok) +} + +func (r *Runtime) builtinJSON_decodeObject(d *json.Decoder) (*Object, error) { + object := r.NewObject() + for { + key, end, err := r.builtinJSON_decodeObjectKey(d) + if err != nil { + return nil, err + } + if end { + break + } + value, err := r.builtinJSON_decodeValue(d) + if err != nil { + return nil, err + } + + object.self._putProp(unistring.NewFromString(key), value, true, true, true) + } + return object, nil +} + +func (r *Runtime) builtinJSON_decodeObjectKey(d *json.Decoder) (string, bool, error) { + tok, err := d.Token() + if err != nil { + return "", false, err + } + switch tok := tok.(type) { + case json.Delim: + if tok == '}' { + return "", true, nil + } + case string: + return tok, false, nil + } + + return "", false, fmt.Errorf("Unexpected token (%T): %v", tok, tok) +} + +func (r *Runtime) builtinJSON_decodeArray(d *json.Decoder) (*Object, error) { + var arrayValue []Value + for { + tok, err := d.Token() + if err != nil { + return nil, err + } + if delim, ok := tok.(json.Delim); ok { + if delim == ']' { + break + } + } + value, err := r.builtinJSON_decodeToken(d, tok) + if err != nil { + return nil, err + } + arrayValue = append(arrayValue, value) + } + return r.newArrayValues(arrayValue), nil +} + +func (r *Runtime) builtinJSON_reviveWalk(reviver func(FunctionCall) Value, holder *Object, name Value) Value { + value := nilSafe(holder.get(name, nil)) + + if object, ok := value.(*Object); ok { + if isArray(object) { + length := toLength(object.self.getStr("length", nil)) + for index := int64(0); index < length; index++ { + name := asciiString(strconv.FormatInt(index, 10)) + value := r.builtinJSON_reviveWalk(reviver, object, name) + if value == _undefined { + object.delete(name, false) + } else { + createDataProperty(object, name, value) + } + } + } else { + for _, name := range object.self.stringKeys(false, nil) { + value := r.builtinJSON_reviveWalk(reviver, object, name) + if value == _undefined { + object.self.deleteStr(name.string(), false) + } else { + createDataProperty(object, name, value) + } + } + } + } + return reviver(FunctionCall{ + This: holder, + Arguments: []Value{name, value}, + }) +} + +type _builtinJSON_stringifyContext struct { + r *Runtime + stack []*Object + propertyList []Value + replacerFunction func(FunctionCall) Value + gap, indent string + buf bytes.Buffer + allAscii bool +} + +func (r *Runtime) builtinJSON_stringify(call FunctionCall) Value { + ctx := _builtinJSON_stringifyContext{ + r: r, + allAscii: true, + } + + replacer, _ := call.Argument(1).(*Object) + if replacer != nil { + if isArray(replacer) { + length := toLength(replacer.self.getStr("length", nil)) + seen := map[string]bool{} + propertyList := make([]Value, length) + length = 0 + for index := range propertyList { + var name string + value := replacer.self.getIdx(valueInt(int64(index)), nil) + switch v := value.(type) { + case valueFloat, valueInt, String: + name = value.String() + case *Object: + switch v.self.className() { + case classNumber, classString: + name = value.String() + default: + continue + } + default: + continue + } + if seen[name] { + continue + } + seen[name] = true + propertyList[length] = newStringValue(name) + length += 1 + } + ctx.propertyList = propertyList[0:length] + } else if c, ok := replacer.self.assertCallable(); ok { + ctx.replacerFunction = c + } + } + if spaceValue := call.Argument(2); spaceValue != _undefined { + if o, ok := spaceValue.(*Object); ok { + switch oImpl := o.self.(type) { + case *primitiveValueObject: + switch oImpl.pValue.(type) { + case valueInt, valueFloat: + spaceValue = o.ToNumber() + } + case *stringObject: + spaceValue = o.ToString() + } + } + isNum := false + var num int64 + if i, ok := spaceValue.(valueInt); ok { + num = int64(i) + isNum = true + } else if f, ok := spaceValue.(valueFloat); ok { + num = int64(f) + isNum = true + } + if isNum { + if num > 0 { + if num > 10 { + num = 10 + } + ctx.gap = strings.Repeat(" ", int(num)) + } + } else { + if s, ok := spaceValue.(String); ok { + str := s.String() + if len(str) > 10 { + ctx.gap = str[:10] + } else { + ctx.gap = str + } + } + } + } + + if ctx.do(call.Argument(0)) { + if ctx.allAscii { + return asciiString(ctx.buf.String()) + } else { + return &importedString{ + s: ctx.buf.String(), + } + } + } + return _undefined +} + +func (ctx *_builtinJSON_stringifyContext) do(v Value) bool { + holder := ctx.r.NewObject() + createDataPropertyOrThrow(holder, stringEmpty, v) + return ctx.str(stringEmpty, holder) +} + +func (ctx *_builtinJSON_stringifyContext) str(key Value, holder *Object) bool { + value := nilSafe(holder.get(key, nil)) + + if object, ok := value.(*Object); ok { + if toJSON, ok := object.self.getStr("toJSON", nil).(*Object); ok { + if c, ok := toJSON.self.assertCallable(); ok { + value = c(FunctionCall{ + This: value, + Arguments: []Value{key}, + }) + } + } + } + + if ctx.replacerFunction != nil { + value = ctx.replacerFunction(FunctionCall{ + This: holder, + Arguments: []Value{key, value}, + }) + } + + if o, ok := value.(*Object); ok { + switch o1 := o.self.(type) { + case *primitiveValueObject: + switch pValue := o1.pValue.(type) { + case valueInt, valueFloat: + value = o.ToNumber() + default: + value = pValue + } + case *stringObject: + value = o.toString() + case *objectGoReflect: + if o1.toJson != nil { + value = ctx.r.ToValue(o1.toJson()) + } else if v, ok := o1.origValue.Interface().(json.Marshaler); ok { + b, err := v.MarshalJSON() + if err != nil { + panic(ctx.r.NewGoError(err)) + } + ctx.buf.Write(b) + ctx.allAscii = false + return true + } else { + switch o1.className() { + case classNumber: + value = o1.val.ordinaryToPrimitiveNumber() + case classString: + value = o1.val.ordinaryToPrimitiveString() + case classBoolean: + if o.ToInteger() != 0 { + value = valueTrue + } else { + value = valueFalse + } + } + } + } + } + + switch value1 := value.(type) { + case valueBool: + if value1 { + ctx.buf.WriteString("true") + } else { + ctx.buf.WriteString("false") + } + case String: + ctx.quote(value1) + case valueInt: + ctx.buf.WriteString(value.String()) + case valueFloat: + if !math.IsNaN(float64(value1)) && !math.IsInf(float64(value1), 0) { + ctx.buf.WriteString(value.String()) + } else { + ctx.buf.WriteString("null") + } + case valueNull: + ctx.buf.WriteString("null") + case *Object: + for _, object := range ctx.stack { + if value1.SameAs(object) { + ctx.r.typeErrorResult(true, "Converting circular structure to JSON") + } + } + ctx.stack = append(ctx.stack, value1) + defer func() { ctx.stack = ctx.stack[:len(ctx.stack)-1] }() + if _, ok := value1.self.assertCallable(); !ok { + if isArray(value1) { + ctx.ja(value1) + } else { + ctx.jo(value1) + } + } else { + return false + } + default: + return false + } + return true +} + +func (ctx *_builtinJSON_stringifyContext) ja(array *Object) { + var stepback string + if ctx.gap != "" { + stepback = ctx.indent + ctx.indent += ctx.gap + } + length := toLength(array.self.getStr("length", nil)) + if length == 0 { + ctx.buf.WriteString("[]") + return + } + + ctx.buf.WriteByte('[') + var separator string + if ctx.gap != "" { + ctx.buf.WriteByte('\n') + ctx.buf.WriteString(ctx.indent) + separator = ",\n" + ctx.indent + } else { + separator = "," + } + + for i := int64(0); i < length; i++ { + if !ctx.str(asciiString(strconv.FormatInt(i, 10)), array) { + ctx.buf.WriteString("null") + } + if i < length-1 { + ctx.buf.WriteString(separator) + } + } + if ctx.gap != "" { + ctx.buf.WriteByte('\n') + ctx.buf.WriteString(stepback) + ctx.indent = stepback + } + ctx.buf.WriteByte(']') +} + +func (ctx *_builtinJSON_stringifyContext) jo(object *Object) { + var stepback string + if ctx.gap != "" { + stepback = ctx.indent + ctx.indent += ctx.gap + } + + ctx.buf.WriteByte('{') + mark := ctx.buf.Len() + var separator string + if ctx.gap != "" { + ctx.buf.WriteByte('\n') + ctx.buf.WriteString(ctx.indent) + separator = ",\n" + ctx.indent + } else { + separator = "," + } + + var props []Value + if ctx.propertyList == nil { + props = object.self.stringKeys(false, nil) + } else { + props = ctx.propertyList + } + + empty := true + for _, name := range props { + off := ctx.buf.Len() + if !empty { + ctx.buf.WriteString(separator) + } + ctx.quote(name.toString()) + if ctx.gap != "" { + ctx.buf.WriteString(": ") + } else { + ctx.buf.WriteByte(':') + } + if ctx.str(name, object) { + if empty { + empty = false + } + } else { + ctx.buf.Truncate(off) + } + } + + if empty { + ctx.buf.Truncate(mark) + } else { + if ctx.gap != "" { + ctx.buf.WriteByte('\n') + ctx.buf.WriteString(stepback) + ctx.indent = stepback + } + } + ctx.buf.WriteByte('}') +} + +func (ctx *_builtinJSON_stringifyContext) quote(str String) { + ctx.buf.WriteByte('"') + reader := &lenientUtf16Decoder{utf16Reader: str.utf16Reader()} + for { + r, _, err := reader.ReadRune() + if err != nil { + break + } + switch r { + case '"', '\\': + ctx.buf.WriteByte('\\') + ctx.buf.WriteByte(byte(r)) + case 0x08: + ctx.buf.WriteString(`\b`) + case 0x09: + ctx.buf.WriteString(`\t`) + case 0x0A: + ctx.buf.WriteString(`\n`) + case 0x0C: + ctx.buf.WriteString(`\f`) + case 0x0D: + ctx.buf.WriteString(`\r`) + default: + if r < 0x20 { + ctx.buf.WriteString(`\u00`) + ctx.buf.WriteByte(hex[r>>4]) + ctx.buf.WriteByte(hex[r&0xF]) + } else { + if utf16.IsSurrogate(r) { + ctx.buf.WriteString(`\u`) + ctx.buf.WriteByte(hex[r>>12]) + ctx.buf.WriteByte(hex[(r>>8)&0xF]) + ctx.buf.WriteByte(hex[(r>>4)&0xF]) + ctx.buf.WriteByte(hex[r&0xF]) + } else { + ctx.buf.WriteRune(r) + if ctx.allAscii && r >= utf8.RuneSelf { + ctx.allAscii = false + } + } + } + } + } + ctx.buf.WriteByte('"') +} + +func (r *Runtime) getJSON() *Object { + ret := r.global.JSON + if ret == nil { + JSON := r.newBaseObject(r.global.ObjectPrototype, classObject) + ret = JSON.val + r.global.JSON = ret + JSON._putProp("parse", r.newNativeFunc(r.builtinJSON_parse, "parse", 2), true, false, true) + JSON._putProp("stringify", r.newNativeFunc(r.builtinJSON_stringify, "stringify", 3), true, false, true) + JSON._putSym(SymToStringTag, valueProp(asciiString(classJSON), false, false, true)) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_json_test.go b/pkg/xscript/engine/builtin_json_test.go new file mode 100644 index 0000000..0f8478c --- /dev/null +++ b/pkg/xscript/engine/builtin_json_test.go @@ -0,0 +1,140 @@ +package engine + +import ( + "encoding/json" + "errors" + "strings" + "testing" + "time" +) + +func TestJSONMarshalObject(t *testing.T) { + vm := New() + o := vm.NewObject() + o.Set("test", 42) + o.Set("testfunc", vm.Get("Error")) + b, err := json.Marshal(o) + if err != nil { + t.Fatal(err) + } + if string(b) != `{"test":42}` { + t.Fatalf("Unexpected value: %s", b) + } +} + +func TestJSONMarshalGoDate(t *testing.T) { + vm := New() + o := vm.NewObject() + o.Set("test", time.Unix(86400, 0).UTC()) + b, err := json.Marshal(o) + if err != nil { + t.Fatal(err) + } + if string(b) != `{"test":"1970-01-02T00:00:00Z"}` { + t.Fatalf("Unexpected value: %s", b) + } +} + +func TestJSONMarshalObjectCircular(t *testing.T) { + vm := New() + o := vm.NewObject() + o.Set("o", o) + _, err := json.Marshal(o) + if err == nil { + t.Fatal("Expected error") + } + if !strings.HasSuffix(err.Error(), "Converting circular structure to JSON") { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestJSONStringifyCircularWrappedGo(t *testing.T) { + type CircularType struct { + Self *CircularType + } + vm := New() + v := CircularType{} + v.Self = &v + vm.Set("v", &v) + _, err := vm.RunString("JSON.stringify(v)") + if err == nil { + t.Fatal("Expected error") + } + if !strings.HasPrefix(err.Error(), "TypeError: Converting circular structure to JSON") { + t.Fatalf("Unexpected error: %v", err) + } +} + +func TestJSONParseReviver(t *testing.T) { + // example from + // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/parse + const SCRIPT = ` + JSON.parse('{"p": 5}', function(key, value) { + return typeof value === 'number' + ? value * 2 // return value * 2 for numbers + : value // return everything else unchanged + })["p"] + ` + + testScript(SCRIPT, intToValue(10), t) +} + +func TestQuoteMalformedSurrogatePair(t *testing.T) { + testScript(`JSON.stringify("\uD800")`, asciiString(`"\ud800"`), t) +} + +func TestEOFWrapping(t *testing.T) { + vm := New() + + _, err := vm.RunString("JSON.parse('{')") + if err == nil { + t.Fatal("Expected error") + } + + if !strings.Contains(err.Error(), "Unexpected end of JSON input") { + t.Fatalf("Error doesn't contain human-friendly wrapper: %v", err) + } +} + +type testMarshalJSONErrorStruct struct { + e error +} + +func (s *testMarshalJSONErrorStruct) MarshalJSON() ([]byte, error) { + return nil, s.e +} + +func TestMarshalJSONError(t *testing.T) { + vm := New() + v := testMarshalJSONErrorStruct{e: errors.New("test error")} + vm.Set("v", &v) + _, err := vm.RunString("JSON.stringify(v)") + if !errors.Is(err, v.e) { + t.Fatalf("Unexpected error: %v", err) + } +} + +func BenchmarkJSONStringify(b *testing.B) { + b.StopTimer() + vm := New() + var createObj func(level int) *Object + createObj = func(level int) *Object { + o := vm.NewObject() + o.Set("field1", "test") + o.Set("field2", 42) + if level > 0 { + level-- + o.Set("obj1", createObj(level)) + o.Set("obj2", createObj(level)) + } + return o + } + + o := createObj(3) + json := vm.Get("JSON").(*Object) + stringify, _ := AssertFunction(json.Get("stringify")) + b.StartTimer() + for i := 0; i < b.N; i++ { + stringify(nil, o) + } +} diff --git a/pkg/xscript/engine/builtin_map.go b/pkg/xscript/engine/builtin_map.go new file mode 100644 index 0000000..1d17999 --- /dev/null +++ b/pkg/xscript/engine/builtin_map.go @@ -0,0 +1,342 @@ +package engine + +import ( + "reflect" +) + +var mapExportType = reflect.TypeOf([][2]any{}) + +type mapObject struct { + baseObject + m *orderedMap +} + +type mapIterObject struct { + baseObject + iter *orderedMapIter + kind iterationKind +} + +func (o *mapIterObject) next() Value { + if o.iter == nil { + return o.val.runtime.createIterResultObject(_undefined, true) + } + + entry := o.iter.next() + if entry == nil { + o.iter = nil + return o.val.runtime.createIterResultObject(_undefined, true) + } + + var result Value + switch o.kind { + case iterationKindKey: + result = entry.key + case iterationKindValue: + result = entry.value + default: + result = o.val.runtime.newArrayValues([]Value{entry.key, entry.value}) + } + + return o.val.runtime.createIterResultObject(result, false) +} + +func (mo *mapObject) init() { + mo.baseObject.init() + mo.m = newOrderedMap(mo.val.runtime.getHash()) +} + +func (mo *mapObject) exportType() reflect.Type { + return mapExportType +} + +func (mo *mapObject) export(ctx *objectExportCtx) any { + m := make([][2]any, mo.m.size) + ctx.put(mo.val, m) + + iter := mo.m.newIter() + for i := 0; i < len(m); i++ { + entry := iter.next() + if entry == nil { + break + } + m[i][0] = exportValue(entry.key, ctx) + m[i][1] = exportValue(entry.value, ctx) + } + + return m +} + +func (mo *mapObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + dst.Set(reflect.MakeMap(typ)) + ctx.putTyped(mo.val, typ, dst.Interface()) + keyTyp := typ.Key() + elemTyp := typ.Elem() + iter := mo.m.newIter() + r := mo.val.runtime + for { + entry := iter.next() + if entry == nil { + break + } + keyVal := reflect.New(keyTyp).Elem() + err := r.toReflectValue(entry.key, keyVal, ctx) + if err != nil { + return err + } + elemVal := reflect.New(elemTyp).Elem() + err = r.toReflectValue(entry.value, elemVal, ctx) + if err != nil { + return err + } + dst.SetMapIndex(keyVal, elemVal) + } + return nil +} + +func (r *Runtime) mapProto_clear(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method Map.prototype.clear called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + mo.m.clear() + + return _undefined +} + +func (r *Runtime) mapProto_delete(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method Map.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + return r.toBoolean(mo.m.remove(call.Argument(0))) +} + +func (r *Runtime) mapProto_get(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method Map.prototype.get called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + return nilSafe(mo.m.get(call.Argument(0))) +} + +func (r *Runtime) mapProto_has(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method Map.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + if mo.m.has(call.Argument(0)) { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) mapProto_set(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method Map.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + mo.m.set(call.Argument(0), call.Argument(1)) + return call.This +} + +func (r *Runtime) mapProto_entries(call FunctionCall) Value { + return r.createMapIterator(call.This, iterationKindKeyValue) +} + +func (r *Runtime) mapProto_forEach(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method Map.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable() + if !ok { + panic(r.NewTypeError("object is not a function %s")) + } + t := call.Argument(1) + iter := mo.m.newIter() + for { + entry := iter.next() + if entry == nil { + break + } + callbackFn(FunctionCall{This: t, Arguments: []Value{entry.value, entry.key, thisObj}}) + } + + return _undefined +} + +func (r *Runtime) mapProto_keys(call FunctionCall) Value { + return r.createMapIterator(call.This, iterationKindKey) +} + +func (r *Runtime) mapProto_values(call FunctionCall) Value { + return r.createMapIterator(call.This, iterationKindValue) +} + +func (r *Runtime) mapProto_getSize(call FunctionCall) Value { + thisObj := r.toObject(call.This) + mo, ok := thisObj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Method get Map.prototype.size called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + return intToValue(int64(mo.m.size)) +} + +func (r *Runtime) builtin_newMap(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("Map")) + } + proto := r.getPrototypeFromCtor(newTarget, r.global.Map, r.global.MapPrototype) + o := &Object{runtime: r} + + mo := &mapObject{} + mo.class = classObject + mo.val = o + mo.extensible = true + o.self = mo + mo.prototype = proto + mo.init() + if len(args) > 0 { + if arg := args[0]; arg != nil && arg != _undefined && arg != _null { + adder := mo.getStr("set", nil) + adderFn := toMethod(adder) + if adderFn == nil { + panic(r.NewTypeError("Map.set in missing")) + } + iter := r.getIterator(arg, nil) + i0 := valueInt(0) + i1 := valueInt(1) + if adder == r.global.mapAdder { + iter.iterate(func(item Value) { + itemObj := r.toObject(item) + k := nilSafe(itemObj.self.getIdx(i0, nil)) + v := nilSafe(itemObj.self.getIdx(i1, nil)) + mo.m.set(k, v) + }) + } else { + iter.iterate(func(item Value) { + itemObj := r.toObject(item) + k := itemObj.self.getIdx(i0, nil) + v := itemObj.self.getIdx(i1, nil) + adderFn(FunctionCall{This: o, Arguments: []Value{k, v}}) + }) + } + } + } + return o +} + +func (r *Runtime) createMapIterator(mapValue Value, kind iterationKind) Value { + obj := r.toObject(mapValue) + mapObj, ok := obj.self.(*mapObject) + if !ok { + panic(r.NewTypeError("Object is not a Map")) + } + + o := &Object{runtime: r} + + mi := &mapIterObject{ + iter: mapObj.m.newIter(), + kind: kind, + } + mi.class = classObject + mi.val = o + mi.extensible = true + o.self = mi + mi.prototype = r.getMapIteratorPrototype() + mi.init() + + return o +} + +func (r *Runtime) mapIterProto_next(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if iter, ok := thisObj.self.(*mapIterObject); ok { + return iter.next() + } + panic(r.NewTypeError("Method Map Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) +} + +func (r *Runtime) createMapProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + + o._putProp("constructor", r.getMap(), true, false, true) + o._putProp("clear", r.newNativeFunc(r.mapProto_clear, "clear", 0), true, false, true) + r.global.mapAdder = r.newNativeFunc(r.mapProto_set, "set", 2) + o._putProp("set", r.global.mapAdder, true, false, true) + o._putProp("delete", r.newNativeFunc(r.mapProto_delete, "delete", 1), true, false, true) + o._putProp("forEach", r.newNativeFunc(r.mapProto_forEach, "forEach", 1), true, false, true) + o._putProp("has", r.newNativeFunc(r.mapProto_has, "has", 1), true, false, true) + o._putProp("get", r.newNativeFunc(r.mapProto_get, "get", 1), true, false, true) + o.setOwnStr("size", &valueProperty{ + getterFunc: r.newNativeFunc(r.mapProto_getSize, "get size", 0), + accessor: true, + writable: true, + configurable: true, + }, true) + o._putProp("keys", r.newNativeFunc(r.mapProto_keys, "keys", 0), true, false, true) + o._putProp("values", r.newNativeFunc(r.mapProto_values, "values", 0), true, false, true) + + entriesFunc := r.newNativeFunc(r.mapProto_entries, "entries", 0) + o._putProp("entries", entriesFunc, true, false, true) + o._putSym(SymIterator, valueProp(entriesFunc, true, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classMap), false, false, true)) + + return o +} + +func (r *Runtime) createMap(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newMap, r.getMapPrototype(), "Map", 0) + r.putSpeciesReturnThis(o) + + return o +} + +func (r *Runtime) createMapIterProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) + + o._putProp("next", r.newNativeFunc(r.mapIterProto_next, "next", 0), true, false, true) + o._putSym(SymToStringTag, valueProp(asciiString(classMapIterator), false, false, true)) + + return o +} + +func (r *Runtime) getMapIteratorPrototype() *Object { + var o *Object + if o = r.global.MapIteratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.MapIteratorPrototype = o + o.self = r.createMapIterProto(o) + } + return o +} + +func (r *Runtime) getMapPrototype() *Object { + ret := r.global.MapPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.MapPrototype = ret + ret.self = r.createMapProto(ret) + } + return ret +} + +func (r *Runtime) getMap() *Object { + ret := r.global.Map + if ret == nil { + ret = &Object{runtime: r} + r.global.Map = ret + ret.self = r.createMap(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_map_test.go b/pkg/xscript/engine/builtin_map_test.go new file mode 100644 index 0000000..5c77136 --- /dev/null +++ b/pkg/xscript/engine/builtin_map_test.go @@ -0,0 +1,244 @@ +package engine + +import ( + "fmt" + "hash/maphash" + "testing" +) + +func TestMapEvilIterator(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var o = {}; + + function Iter(value) { + this.value = value; + this.idx = 0; + } + + Iter.prototype.next = function() { + var idx = this.idx; + if (idx === 0) { + this.idx++; + return this.value; + } + return {done: true}; + } + + o[Symbol.iterator] = function() { + return new Iter({}); + } + + assert.throws(TypeError, function() { + new Map(o); + }); + + o[Symbol.iterator] = function() { + return new Iter({value: []}); + } + + function t(prefix) { + var m = new Map(o); + assert.sameValue(1, m.size, prefix+": m.size"); + assert.sameValue(true, m.has(undefined), prefix+": m.has(undefined)"); + assert.sameValue(undefined, m.get(undefined), prefix+": m.get(undefined)"); + } + + t("standard adder"); + + var count = 0; + var origSet = Map.prototype.set; + + Map.prototype.set = function() { + count++; + origSet.apply(this, arguments); + } + + t("custom adder"); + assert.sameValue(1, count, "count"); + + undefined; + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestMapExportToNilMap(t *testing.T) { + vm := New() + var m map[int]any + res, err := vm.RunString("new Map([[1, true]])") + if err != nil { + t.Fatal(err) + } + err = vm.ExportTo(res, &m) + if err != nil { + t.Fatal(err) + } + if len(m) != 1 { + t.Fatal(m) + } + if _, exists := m[1]; !exists { + t.Fatal(m) + } +} + +func TestMapExportToNonNilMap(t *testing.T) { + vm := New() + m := map[int]any{ + 2: true, + } + res, err := vm.RunString("new Map([[1, true]])") + if err != nil { + t.Fatal(err) + } + err = vm.ExportTo(res, &m) + if err != nil { + t.Fatal(err) + } + if len(m) != 1 { + t.Fatal(m) + } + if _, exists := m[1]; !exists { + t.Fatal(m) + } +} + +func TestMapGetAdderGetIteratorOrder(t *testing.T) { + const SCRIPT = ` + let getterCalled = 0; + + class M extends Map { + get set() { + getterCalled++; + return null; + } + } + + let getIteratorCalled = 0; + + let iterable = {}; + iterable[Symbol.iterator] = () => { + getIteratorCalled++ + return { + next: 1 + }; + } + + let thrown = false; + + try { + new M(iterable); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + + thrown && getterCalled === 1 && getIteratorCalled === 0; + ` + testScript(SCRIPT, valueTrue, t) +} + +func ExampleObject_Export_map() { + vm := New() + m, err := vm.RunString(` + new Map([[1, true], [2, false]]); + `) + if err != nil { + panic(err) + } + exp := m.Export() + fmt.Printf("%T, %v\n", exp, exp) + // Output: [][2]interface {}, [[1 true] [2 false]] +} + +func ExampleRuntime_ExportTo_mapToMap() { + vm := New() + m, err := vm.RunString(` + new Map([[1, true], [2, false]]); + `) + if err != nil { + panic(err) + } + exp := make(map[int]bool) + err = vm.ExportTo(m, &exp) + if err != nil { + panic(err) + } + fmt.Println(exp) + // Output: map[1:true 2:false] +} + +func ExampleRuntime_ExportTo_mapToSlice() { + vm := New() + m, err := vm.RunString(` + new Map([[1, true], [2, false]]); + `) + if err != nil { + panic(err) + } + exp := make([][]any, 0) + err = vm.ExportTo(m, &exp) + if err != nil { + panic(err) + } + fmt.Println(exp) + // Output: [[1 true] [2 false]] +} + +func ExampleRuntime_ExportTo_mapToTypedSlice() { + vm := New() + m, err := vm.RunString(` + new Map([[1, true], [2, false]]); + `) + if err != nil { + panic(err) + } + exp := make([][2]any, 0) + err = vm.ExportTo(m, &exp) + if err != nil { + panic(err) + } + fmt.Println(exp) + // Output: [[1 true] [2 false]] +} + +func BenchmarkMapDelete(b *testing.B) { + var key1 Value = asciiString("a") + var key2 Value = asciiString("b") + one := intToValue(1) + two := intToValue(2) + for i := 0; i < b.N; i++ { + m := newOrderedMap(&maphash.Hash{}) + m.set(key1, one) + m.set(key2, two) + if !m.remove(key1) { + b.Fatal("remove() returned false") + } + } +} + +func BenchmarkMapDeleteJS(b *testing.B) { + prg, err := Compile("test.js", ` + var m = new Map([['a',1], ['b', 2]]); + + var result = m.delete('a'); + + if (!result || m.size !== 1) { + throw new Error("Fail!"); + } + `, + false) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + vm := New() + _, err := vm.RunProgram(prg) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/xscript/engine/builtin_math.go b/pkg/xscript/engine/builtin_math.go new file mode 100644 index 0000000..bfa3a36 --- /dev/null +++ b/pkg/xscript/engine/builtin_math.go @@ -0,0 +1,358 @@ +package engine + +import ( + "math" + "math/bits" + "sync" +) + +func (r *Runtime) math_abs(call FunctionCall) Value { + return floatToValue(math.Abs(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_acos(call FunctionCall) Value { + return floatToValue(math.Acos(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_acosh(call FunctionCall) Value { + return floatToValue(math.Acosh(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_asin(call FunctionCall) Value { + return floatToValue(math.Asin(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_asinh(call FunctionCall) Value { + return floatToValue(math.Asinh(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_atan(call FunctionCall) Value { + return floatToValue(math.Atan(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_atanh(call FunctionCall) Value { + return floatToValue(math.Atanh(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_atan2(call FunctionCall) Value { + y := call.Argument(0).ToFloat() + x := call.Argument(1).ToFloat() + + return floatToValue(math.Atan2(y, x)) +} + +func (r *Runtime) math_cbrt(call FunctionCall) Value { + return floatToValue(math.Cbrt(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_ceil(call FunctionCall) Value { + return floatToValue(math.Ceil(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_clz32(call FunctionCall) Value { + return intToValue(int64(bits.LeadingZeros32(toUint32(call.Argument(0))))) +} + +func (r *Runtime) math_cos(call FunctionCall) Value { + return floatToValue(math.Cos(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_cosh(call FunctionCall) Value { + return floatToValue(math.Cosh(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_exp(call FunctionCall) Value { + return floatToValue(math.Exp(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_expm1(call FunctionCall) Value { + return floatToValue(math.Expm1(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_floor(call FunctionCall) Value { + return floatToValue(math.Floor(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_fround(call FunctionCall) Value { + return floatToValue(float64(float32(call.Argument(0).ToFloat()))) +} + +func (r *Runtime) math_hypot(call FunctionCall) Value { + var max float64 + var hasNaN bool + absValues := make([]float64, 0, len(call.Arguments)) + for _, v := range call.Arguments { + arg := nilSafe(v).ToFloat() + if math.IsNaN(arg) { + hasNaN = true + } else { + abs := math.Abs(arg) + if abs > max { + max = abs + } + absValues = append(absValues, abs) + } + } + if math.IsInf(max, 1) { + return _positiveInf + } + if hasNaN { + return _NaN + } + if max == 0 { + return _positiveZero + } + + // Kahan summation to avoid rounding errors. + // Normalize the numbers to the largest one to avoid overflow. + var sum, compensation float64 + for _, n := range absValues { + n /= max + summand := n*n - compensation + preliminary := sum + summand + compensation = (preliminary - sum) - summand + sum = preliminary + } + return floatToValue(math.Sqrt(sum) * max) +} + +func (r *Runtime) math_imul(call FunctionCall) Value { + x := toUint32(call.Argument(0)) + y := toUint32(call.Argument(1)) + return intToValue(int64(int32(x * y))) +} + +func (r *Runtime) math_log(call FunctionCall) Value { + return floatToValue(math.Log(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_log1p(call FunctionCall) Value { + return floatToValue(math.Log1p(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_log10(call FunctionCall) Value { + return floatToValue(math.Log10(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_log2(call FunctionCall) Value { + return floatToValue(math.Log2(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_max(call FunctionCall) Value { + result := math.Inf(-1) + args := call.Arguments + for i, arg := range args { + n := nilSafe(arg).ToFloat() + if math.IsNaN(n) { + args = args[i+1:] + goto NaNLoop + } + result = math.Max(result, n) + } + + return floatToValue(result) + +NaNLoop: + // All arguments still need to be coerced to number according to the specs. + for _, arg := range args { + nilSafe(arg).ToFloat() + } + return _NaN +} + +func (r *Runtime) math_min(call FunctionCall) Value { + result := math.Inf(1) + args := call.Arguments + for i, arg := range args { + n := nilSafe(arg).ToFloat() + if math.IsNaN(n) { + args = args[i+1:] + goto NaNLoop + } + result = math.Min(result, n) + } + + return floatToValue(result) + +NaNLoop: + // All arguments still need to be coerced to number according to the specs. + for _, arg := range args { + nilSafe(arg).ToFloat() + } + return _NaN +} + +func pow(x, y Value) Value { + if x, ok := x.(valueInt); ok { + if y, ok := y.(valueInt); ok && y >= 0 { + if y == 0 { + return intToValue(1) + } + if x == 0 { + return intToValue(0) + } + ip := ipow(int64(x), int64(y)) + if ip != 0 { + return intToValue(ip) + } + } + } + xf := x.ToFloat() + yf := y.ToFloat() + if math.Abs(xf) == 1 && math.IsInf(yf, 0) { + return _NaN + } + if xf == 1 && math.IsNaN(yf) { + return _NaN + } + return floatToValue(math.Pow(xf, yf)) +} + +func (r *Runtime) math_pow(call FunctionCall) Value { + return pow(call.Argument(0), call.Argument(1)) +} + +func (r *Runtime) math_random(call FunctionCall) Value { + return floatToValue(r.rand()) +} + +func (r *Runtime) math_round(call FunctionCall) Value { + f := call.Argument(0).ToFloat() + if math.IsNaN(f) { + return _NaN + } + + if f == 0 && math.Signbit(f) { + return _negativeZero + } + + t := math.Trunc(f) + + if f >= 0 { + if f-t >= 0.5 { + return floatToValue(t + 1) + } + } else { + if t-f > 0.5 { + return floatToValue(t - 1) + } + } + + return floatToValue(t) +} + +func (r *Runtime) math_sign(call FunctionCall) Value { + arg := call.Argument(0) + num := arg.ToFloat() + if math.IsNaN(num) || num == 0 { // this will match -0 too + return arg + } + if num > 0 { + return intToValue(1) + } + return intToValue(-1) +} + +func (r *Runtime) math_sin(call FunctionCall) Value { + return floatToValue(math.Sin(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_sinh(call FunctionCall) Value { + return floatToValue(math.Sinh(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_sqrt(call FunctionCall) Value { + return floatToValue(math.Sqrt(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_tan(call FunctionCall) Value { + return floatToValue(math.Tan(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_tanh(call FunctionCall) Value { + return floatToValue(math.Tanh(call.Argument(0).ToFloat())) +} + +func (r *Runtime) math_trunc(call FunctionCall) Value { + arg := call.Argument(0) + if i, ok := arg.(valueInt); ok { + return i + } + return floatToValue(math.Trunc(arg.ToFloat())) +} + +func createMathTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("E", func(r *Runtime) Value { return valueProp(valueFloat(math.E), false, false, false) }) + t.putStr("LN10", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln10), false, false, false) }) + t.putStr("LN2", func(r *Runtime) Value { return valueProp(valueFloat(math.Ln2), false, false, false) }) + t.putStr("LOG10E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log10E), false, false, false) }) + t.putStr("LOG2E", func(r *Runtime) Value { return valueProp(valueFloat(math.Log2E), false, false, false) }) + t.putStr("PI", func(r *Runtime) Value { return valueProp(valueFloat(math.Pi), false, false, false) }) + t.putStr("SQRT1_2", func(r *Runtime) Value { return valueProp(valueFloat(sqrt1_2), false, false, false) }) + t.putStr("SQRT2", func(r *Runtime) Value { return valueProp(valueFloat(math.Sqrt2), false, false, false) }) + + t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString(classMath), false, false, true) }) + + t.putStr("abs", func(r *Runtime) Value { return r.methodProp(r.math_abs, "abs", 1) }) + t.putStr("acos", func(r *Runtime) Value { return r.methodProp(r.math_acos, "acos", 1) }) + t.putStr("acosh", func(r *Runtime) Value { return r.methodProp(r.math_acosh, "acosh", 1) }) + t.putStr("asin", func(r *Runtime) Value { return r.methodProp(r.math_asin, "asin", 1) }) + t.putStr("asinh", func(r *Runtime) Value { return r.methodProp(r.math_asinh, "asinh", 1) }) + t.putStr("atan", func(r *Runtime) Value { return r.methodProp(r.math_atan, "atan", 1) }) + t.putStr("atanh", func(r *Runtime) Value { return r.methodProp(r.math_atanh, "atanh", 1) }) + t.putStr("atan2", func(r *Runtime) Value { return r.methodProp(r.math_atan2, "atan2", 2) }) + t.putStr("cbrt", func(r *Runtime) Value { return r.methodProp(r.math_cbrt, "cbrt", 1) }) + t.putStr("ceil", func(r *Runtime) Value { return r.methodProp(r.math_ceil, "ceil", 1) }) + t.putStr("clz32", func(r *Runtime) Value { return r.methodProp(r.math_clz32, "clz32", 1) }) + t.putStr("cos", func(r *Runtime) Value { return r.methodProp(r.math_cos, "cos", 1) }) + t.putStr("cosh", func(r *Runtime) Value { return r.methodProp(r.math_cosh, "cosh", 1) }) + t.putStr("exp", func(r *Runtime) Value { return r.methodProp(r.math_exp, "exp", 1) }) + t.putStr("expm1", func(r *Runtime) Value { return r.methodProp(r.math_expm1, "expm1", 1) }) + t.putStr("floor", func(r *Runtime) Value { return r.methodProp(r.math_floor, "floor", 1) }) + t.putStr("fround", func(r *Runtime) Value { return r.methodProp(r.math_fround, "fround", 1) }) + t.putStr("hypot", func(r *Runtime) Value { return r.methodProp(r.math_hypot, "hypot", 2) }) + t.putStr("imul", func(r *Runtime) Value { return r.methodProp(r.math_imul, "imul", 2) }) + t.putStr("log", func(r *Runtime) Value { return r.methodProp(r.math_log, "log", 1) }) + t.putStr("log1p", func(r *Runtime) Value { return r.methodProp(r.math_log1p, "log1p", 1) }) + t.putStr("log10", func(r *Runtime) Value { return r.methodProp(r.math_log10, "log10", 1) }) + t.putStr("log2", func(r *Runtime) Value { return r.methodProp(r.math_log2, "log2", 1) }) + t.putStr("max", func(r *Runtime) Value { return r.methodProp(r.math_max, "max", 2) }) + t.putStr("min", func(r *Runtime) Value { return r.methodProp(r.math_min, "min", 2) }) + t.putStr("pow", func(r *Runtime) Value { return r.methodProp(r.math_pow, "pow", 2) }) + t.putStr("random", func(r *Runtime) Value { return r.methodProp(r.math_random, "random", 0) }) + t.putStr("round", func(r *Runtime) Value { return r.methodProp(r.math_round, "round", 1) }) + t.putStr("sign", func(r *Runtime) Value { return r.methodProp(r.math_sign, "sign", 1) }) + t.putStr("sin", func(r *Runtime) Value { return r.methodProp(r.math_sin, "sin", 1) }) + t.putStr("sinh", func(r *Runtime) Value { return r.methodProp(r.math_sinh, "sinh", 1) }) + t.putStr("sqrt", func(r *Runtime) Value { return r.methodProp(r.math_sqrt, "sqrt", 1) }) + t.putStr("tan", func(r *Runtime) Value { return r.methodProp(r.math_tan, "tan", 1) }) + t.putStr("tanh", func(r *Runtime) Value { return r.methodProp(r.math_tanh, "tanh", 1) }) + t.putStr("trunc", func(r *Runtime) Value { return r.methodProp(r.math_trunc, "trunc", 1) }) + + return t +} + +var mathTemplate *objectTemplate +var mathTemplateOnce sync.Once + +func getMathTemplate() *objectTemplate { + mathTemplateOnce.Do(func() { + mathTemplate = createMathTemplate() + }) + return mathTemplate +} + +func (r *Runtime) getMath() *Object { + ret := r.global.Math + if ret == nil { + ret = &Object{runtime: r} + r.global.Math = ret + r.newTemplatedObject(getMathTemplate(), ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_number.go b/pkg/xscript/engine/builtin_number.go new file mode 100644 index 0000000..4a068c8 --- /dev/null +++ b/pkg/xscript/engine/builtin_number.go @@ -0,0 +1,303 @@ +package engine + +import ( + "math" + "sync" + + "pandax/pkg/xscript/engine/ftoa" +) + +func (r *Runtime) toNumber(v Value) Value { + switch t := v.(type) { + case valueFloat, valueInt: + return v + case *Object: + switch t := t.self.(type) { + case *primitiveValueObject: + return r.toNumber(t.pValue) + case *objectGoReflect: + if t.class == classNumber && t.valueOf != nil { + return t.valueOf() + } + } + if t == r.global.NumberPrototype { + return _positiveZero + } + } + panic(r.NewTypeError("Value is not a number: %s", v)) +} + +func (r *Runtime) numberproto_valueOf(call FunctionCall) Value { + return r.toNumber(call.This) +} + +func (r *Runtime) numberproto_toString(call FunctionCall) Value { + var numVal Value + switch t := call.This.(type) { + case valueFloat, valueInt: + numVal = t + case *Object: + switch t := t.self.(type) { + case *primitiveValueObject: + numVal = r.toNumber(t.pValue) + case *objectGoReflect: + if t.class == classNumber { + if t.toString != nil { + return t.toString() + } + if t.valueOf != nil { + numVal = t.valueOf() + } + } + } + if t == r.global.NumberPrototype { + return asciiString("0") + } + } + if numVal == nil { + panic(r.NewTypeError("Value is not a number")) + } + var radix int + if arg := call.Argument(0); arg != _undefined { + radix = int(arg.ToInteger()) + } else { + radix = 10 + } + + if radix < 2 || radix > 36 { + panic(r.newError(r.getRangeError(), "toString() radix argument must be between 2 and 36")) + } + + num := numVal.ToFloat() + + if math.IsNaN(num) { + return stringNaN + } + + if math.IsInf(num, 1) { + return stringInfinity + } + + if math.IsInf(num, -1) { + return stringNegInfinity + } + + if radix == 10 { + return asciiString(fToStr(num, ftoa.ModeStandard, 0)) + } + + return asciiString(ftoa.FToBaseStr(num, radix)) +} + +func (r *Runtime) numberproto_toFixed(call FunctionCall) Value { + num := r.toNumber(call.This).ToFloat() + prec := call.Argument(0).ToInteger() + + if prec < 0 || prec > 100 { + panic(r.newError(r.getRangeError(), "toFixed() precision must be between 0 and 100")) + } + if math.IsNaN(num) { + return stringNaN + } + return asciiString(fToStr(num, ftoa.ModeFixed, int(prec))) +} + +func (r *Runtime) numberproto_toExponential(call FunctionCall) Value { + num := r.toNumber(call.This).ToFloat() + precVal := call.Argument(0) + var prec int64 + if precVal == _undefined { + return asciiString(fToStr(num, ftoa.ModeStandardExponential, 0)) + } else { + prec = precVal.ToInteger() + } + + if math.IsNaN(num) { + return stringNaN + } + if math.IsInf(num, 1) { + return stringInfinity + } + if math.IsInf(num, -1) { + return stringNegInfinity + } + + if prec < 0 || prec > 100 { + panic(r.newError(r.getRangeError(), "toExponential() precision must be between 0 and 100")) + } + + return asciiString(fToStr(num, ftoa.ModeExponential, int(prec+1))) +} + +func (r *Runtime) numberproto_toPrecision(call FunctionCall) Value { + numVal := r.toNumber(call.This) + precVal := call.Argument(0) + if precVal == _undefined { + return numVal.toString() + } + num := numVal.ToFloat() + prec := precVal.ToInteger() + + if math.IsNaN(num) { + return stringNaN + } + if math.IsInf(num, 1) { + return stringInfinity + } + if math.IsInf(num, -1) { + return stringNegInfinity + } + if prec < 1 || prec > 100 { + panic(r.newError(r.getRangeError(), "toPrecision() precision must be between 1 and 100")) + } + + return asciiString(fToStr(num, ftoa.ModePrecision, int(prec))) +} + +func (r *Runtime) number_isFinite(call FunctionCall) Value { + switch arg := call.Argument(0).(type) { + case valueInt: + return valueTrue + case valueFloat: + f := float64(arg) + return r.toBoolean(!math.IsInf(f, 0) && !math.IsNaN(f)) + default: + return valueFalse + } +} + +func (r *Runtime) number_isInteger(call FunctionCall) Value { + switch arg := call.Argument(0).(type) { + case valueInt: + return valueTrue + case valueFloat: + f := float64(arg) + return r.toBoolean(!math.IsNaN(f) && !math.IsInf(f, 0) && math.Floor(f) == f) + default: + return valueFalse + } +} + +func (r *Runtime) number_isNaN(call FunctionCall) Value { + if f, ok := call.Argument(0).(valueFloat); ok && math.IsNaN(float64(f)) { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) number_isSafeInteger(call FunctionCall) Value { + arg := call.Argument(0) + if i, ok := arg.(valueInt); ok && i >= -(maxInt-1) && i <= maxInt-1 { + return valueTrue + } + if arg == _negativeZero { + return valueTrue + } + return valueFalse +} + +func createNumberProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getNumber(), true, false, true) }) + + t.putStr("toExponential", func(r *Runtime) Value { return r.methodProp(r.numberproto_toExponential, "toExponential", 1) }) + t.putStr("toFixed", func(r *Runtime) Value { return r.methodProp(r.numberproto_toFixed, "toFixed", 1) }) + t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toLocaleString", 0) }) + t.putStr("toPrecision", func(r *Runtime) Value { return r.methodProp(r.numberproto_toPrecision, "toPrecision", 1) }) + t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.numberproto_toString, "toString", 1) }) + t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.numberproto_valueOf, "valueOf", 0) }) + + return t +} + +var numberProtoTemplate *objectTemplate +var numberProtoTemplateOnce sync.Once + +func getNumberProtoTemplate() *objectTemplate { + numberProtoTemplateOnce.Do(func() { + numberProtoTemplate = createNumberProtoTemplate() + }) + return numberProtoTemplate +} + +func (r *Runtime) getNumberPrototype() *Object { + ret := r.global.NumberPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.NumberPrototype = ret + o := r.newTemplatedObject(getNumberProtoTemplate(), ret) + o.class = classNumber + } + return ret +} + +func (r *Runtime) getParseFloat() *Object { + ret := r.global.parseFloat + if ret == nil { + ret = r.newNativeFunc(r.builtin_parseFloat, "parseFloat", 1) + r.global.parseFloat = ret + } + return ret +} + +func (r *Runtime) getParseInt() *Object { + ret := r.global.parseInt + if ret == nil { + ret = r.newNativeFunc(r.builtin_parseInt, "parseInt", 2) + r.global.parseInt = ret + } + return ret +} + +func createNumberTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.getFunctionPrototype() + } + t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) }) + t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Number"), false, false, true) }) + + t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.getNumberPrototype(), false, false, false) }) + + t.putStr("EPSILON", func(r *Runtime) Value { return valueProp(_epsilon, false, false, false) }) + t.putStr("isFinite", func(r *Runtime) Value { return r.methodProp(r.number_isFinite, "isFinite", 1) }) + t.putStr("isInteger", func(r *Runtime) Value { return r.methodProp(r.number_isInteger, "isInteger", 1) }) + t.putStr("isNaN", func(r *Runtime) Value { return r.methodProp(r.number_isNaN, "isNaN", 1) }) + t.putStr("isSafeInteger", func(r *Runtime) Value { return r.methodProp(r.number_isSafeInteger, "isSafeInteger", 1) }) + t.putStr("MAX_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(maxInt-1), false, false, false) }) + t.putStr("MIN_SAFE_INTEGER", func(r *Runtime) Value { return valueProp(valueInt(-(maxInt - 1)), false, false, false) }) + t.putStr("MIN_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.SmallestNonzeroFloat64), false, false, false) }) + t.putStr("MAX_VALUE", func(r *Runtime) Value { return valueProp(valueFloat(math.MaxFloat64), false, false, false) }) + t.putStr("NaN", func(r *Runtime) Value { return valueProp(_NaN, false, false, false) }) + t.putStr("NEGATIVE_INFINITY", func(r *Runtime) Value { return valueProp(_negativeInf, false, false, false) }) + t.putStr("parseFloat", func(r *Runtime) Value { return valueProp(r.getParseFloat(), true, false, true) }) + t.putStr("parseInt", func(r *Runtime) Value { return valueProp(r.getParseInt(), true, false, true) }) + t.putStr("POSITIVE_INFINITY", func(r *Runtime) Value { return valueProp(_positiveInf, false, false, false) }) + + return t +} + +var numberTemplate *objectTemplate +var numberTemplateOnce sync.Once + +func getNumberTemplate() *objectTemplate { + numberTemplateOnce.Do(func() { + numberTemplate = createNumberTemplate() + }) + return numberTemplate +} + +func (r *Runtime) getNumber() *Object { + ret := r.global.Number + if ret == nil { + ret = &Object{runtime: r} + r.global.Number = ret + r.newTemplatedFuncObject(getNumberTemplate(), ret, r.builtin_Number, + r.wrapNativeConstruct(r.builtin_newNumber, ret, r.getNumberPrototype())) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_object.go b/pkg/xscript/engine/builtin_object.go new file mode 100644 index 0000000..c2b96b1 --- /dev/null +++ b/pkg/xscript/engine/builtin_object.go @@ -0,0 +1,711 @@ +package engine + +import ( + "fmt" + "sync" +) + +func (r *Runtime) builtin_Object(args []Value, newTarget *Object) *Object { + if newTarget != nil && newTarget != r.getObject() { + proto := r.getPrototypeFromCtor(newTarget, nil, r.global.ObjectPrototype) + return r.newBaseObject(proto, classObject).val + } + if len(args) > 0 { + arg := args[0] + if arg != _undefined && arg != _null { + return arg.ToObject(r) + } + } + return r.NewObject() +} + +func (r *Runtime) object_getPrototypeOf(call FunctionCall) Value { + o := call.Argument(0).ToObject(r) + p := o.self.proto() + if p == nil { + return _null + } + return p +} + +func (r *Runtime) valuePropToDescriptorObject(desc Value) Value { + if desc == nil { + return _undefined + } + var writable, configurable, enumerable, accessor bool + var get, set *Object + var value Value + if v, ok := desc.(*valueProperty); ok { + writable = v.writable + configurable = v.configurable + enumerable = v.enumerable + accessor = v.accessor + value = v.value + get = v.getterFunc + set = v.setterFunc + } else { + writable = true + configurable = true + enumerable = true + value = desc + } + + ret := r.NewObject() + obj := ret.self + if !accessor { + obj.setOwnStr("value", value, false) + obj.setOwnStr("writable", r.toBoolean(writable), false) + } else { + if get != nil { + obj.setOwnStr("get", get, false) + } else { + obj.setOwnStr("get", _undefined, false) + } + if set != nil { + obj.setOwnStr("set", set, false) + } else { + obj.setOwnStr("set", _undefined, false) + } + } + obj.setOwnStr("enumerable", r.toBoolean(enumerable), false) + obj.setOwnStr("configurable", r.toBoolean(configurable), false) + + return ret +} + +func (r *Runtime) object_getOwnPropertyDescriptor(call FunctionCall) Value { + o := call.Argument(0).ToObject(r) + propName := toPropertyKey(call.Argument(1)) + return r.valuePropToDescriptorObject(o.getOwnProp(propName)) +} + +func (r *Runtime) object_getOwnPropertyDescriptors(call FunctionCall) Value { + o := call.Argument(0).ToObject(r) + result := r.newBaseObject(r.global.ObjectPrototype, classObject).val + for item, next := o.self.iterateKeys()(); next != nil; item, next = next() { + var prop Value + if item.value == nil { + prop = o.getOwnProp(item.name) + if prop == nil { + continue + } + } else { + prop = item.value + } + descriptor := r.valuePropToDescriptorObject(prop) + if descriptor != _undefined { + createDataPropertyOrThrow(result, item.name, descriptor) + } + } + return result +} + +func (r *Runtime) object_getOwnPropertyNames(call FunctionCall) Value { + obj := call.Argument(0).ToObject(r) + + return r.newArrayValues(obj.self.stringKeys(true, nil)) +} + +func (r *Runtime) object_getOwnPropertySymbols(call FunctionCall) Value { + obj := call.Argument(0).ToObject(r) + return r.newArrayValues(obj.self.symbols(true, nil)) +} + +func (r *Runtime) toValueProp(v Value) *valueProperty { + if v == nil || v == _undefined { + return nil + } + obj := r.toObject(v) + getter := obj.self.getStr("get", nil) + setter := obj.self.getStr("set", nil) + writable := obj.self.getStr("writable", nil) + value := obj.self.getStr("value", nil) + if (getter != nil || setter != nil) && (value != nil || writable != nil) { + r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute") + } + + ret := &valueProperty{} + if writable != nil && writable.ToBoolean() { + ret.writable = true + } + if e := obj.self.getStr("enumerable", nil); e != nil && e.ToBoolean() { + ret.enumerable = true + } + if c := obj.self.getStr("configurable", nil); c != nil && c.ToBoolean() { + ret.configurable = true + } + ret.value = value + + if getter != nil && getter != _undefined { + o := r.toObject(getter) + if _, ok := o.self.assertCallable(); !ok { + r.typeErrorResult(true, "getter must be a function") + } + ret.getterFunc = o + } + + if setter != nil && setter != _undefined { + o := r.toObject(setter) + if _, ok := o.self.assertCallable(); !ok { + r.typeErrorResult(true, "setter must be a function") + } + ret.setterFunc = o + } + + if ret.getterFunc != nil || ret.setterFunc != nil { + ret.accessor = true + } + + return ret +} + +func (r *Runtime) toPropertyDescriptor(v Value) (ret PropertyDescriptor) { + if o, ok := v.(*Object); ok { + descr := o.self + + // Save the original descriptor for reference + ret.jsDescriptor = o + + ret.Value = descr.getStr("value", nil) + + if p := descr.getStr("writable", nil); p != nil { + ret.Writable = ToFlag(p.ToBoolean()) + } + if p := descr.getStr("enumerable", nil); p != nil { + ret.Enumerable = ToFlag(p.ToBoolean()) + } + if p := descr.getStr("configurable", nil); p != nil { + ret.Configurable = ToFlag(p.ToBoolean()) + } + + ret.Getter = descr.getStr("get", nil) + ret.Setter = descr.getStr("set", nil) + + if ret.Getter != nil && ret.Getter != _undefined { + if _, ok := r.toObject(ret.Getter).self.assertCallable(); !ok { + r.typeErrorResult(true, "getter must be a function") + } + } + + if ret.Setter != nil && ret.Setter != _undefined { + if _, ok := r.toObject(ret.Setter).self.assertCallable(); !ok { + r.typeErrorResult(true, "setter must be a function") + } + } + + if (ret.Getter != nil || ret.Setter != nil) && (ret.Value != nil || ret.Writable != FLAG_NOT_SET) { + r.typeErrorResult(true, "Invalid property descriptor. Cannot both specify accessors and a value or writable attribute") + } + } else { + r.typeErrorResult(true, "Property description must be an object: %s", v.String()) + } + + return +} + +func (r *Runtime) _defineProperties(o *Object, p Value) { + type propItem struct { + name Value + prop PropertyDescriptor + } + props := p.ToObject(r) + var list []propItem + for item, next := iterateEnumerableProperties(props)(); next != nil; item, next = next() { + list = append(list, propItem{ + name: item.name, + prop: r.toPropertyDescriptor(item.value), + }) + } + for _, prop := range list { + o.defineOwnProperty(prop.name, prop.prop, true) + } +} + +func (r *Runtime) object_create(call FunctionCall) Value { + var proto *Object + if arg := call.Argument(0); arg != _null { + if o, ok := arg.(*Object); ok { + proto = o + } else { + r.typeErrorResult(true, "Object prototype may only be an Object or null: %s", arg.String()) + } + } + o := r.newBaseObject(proto, classObject).val + + if props := call.Argument(1); props != _undefined { + r._defineProperties(o, props) + } + + return o +} + +func (r *Runtime) object_defineProperty(call FunctionCall) (ret Value) { + if obj, ok := call.Argument(0).(*Object); ok { + descr := r.toPropertyDescriptor(call.Argument(2)) + obj.defineOwnProperty(toPropertyKey(call.Argument(1)), descr, true) + ret = call.Argument(0) + } else { + r.typeErrorResult(true, "Object.defineProperty called on non-object") + } + return +} + +func (r *Runtime) object_defineProperties(call FunctionCall) Value { + obj := r.toObject(call.Argument(0)) + r._defineProperties(obj, call.Argument(1)) + return obj +} + +func (r *Runtime) object_seal(call FunctionCall) Value { + // ES6 + arg := call.Argument(0) + if obj, ok := arg.(*Object); ok { + obj.self.preventExtensions(true) + descr := PropertyDescriptor{ + Configurable: FLAG_FALSE, + } + + for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() { + if prop, ok := item.value.(*valueProperty); ok { + prop.configurable = false + } else { + obj.defineOwnProperty(item.name, descr, true) + } + } + + return obj + } + return arg +} + +func (r *Runtime) object_freeze(call FunctionCall) Value { + arg := call.Argument(0) + if obj, ok := arg.(*Object); ok { + obj.self.preventExtensions(true) + + for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() { + if prop, ok := item.value.(*valueProperty); ok { + prop.configurable = false + if !prop.accessor { + prop.writable = false + } + } else { + prop := obj.getOwnProp(item.name) + descr := PropertyDescriptor{ + Configurable: FLAG_FALSE, + } + if prop, ok := prop.(*valueProperty); ok && prop.accessor { + // no-op + } else { + descr.Writable = FLAG_FALSE + } + obj.defineOwnProperty(item.name, descr, true) + } + } + return obj + } else { + // ES6 behavior + return arg + } +} + +func (r *Runtime) object_preventExtensions(call FunctionCall) (ret Value) { + arg := call.Argument(0) + if obj, ok := arg.(*Object); ok { + obj.self.preventExtensions(true) + } + return arg +} + +func (r *Runtime) object_isSealed(call FunctionCall) Value { + if obj, ok := call.Argument(0).(*Object); ok { + if obj.self.isExtensible() { + return valueFalse + } + for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() { + var prop Value + if item.value == nil { + prop = obj.getOwnProp(item.name) + if prop == nil { + continue + } + } else { + prop = item.value + } + if prop, ok := prop.(*valueProperty); ok { + if prop.configurable { + return valueFalse + } + } else { + return valueFalse + } + } + } + return valueTrue +} + +func (r *Runtime) object_isFrozen(call FunctionCall) Value { + if obj, ok := call.Argument(0).(*Object); ok { + if obj.self.isExtensible() { + return valueFalse + } + for item, next := obj.self.iterateKeys()(); next != nil; item, next = next() { + var prop Value + if item.value == nil { + prop = obj.getOwnProp(item.name) + if prop == nil { + continue + } + } else { + prop = item.value + } + if prop, ok := prop.(*valueProperty); ok { + if prop.configurable || prop.value != nil && prop.writable { + return valueFalse + } + } else { + return valueFalse + } + } + } + return valueTrue +} + +func (r *Runtime) object_isExtensible(call FunctionCall) Value { + if obj, ok := call.Argument(0).(*Object); ok { + if obj.self.isExtensible() { + return valueTrue + } + return valueFalse + } else { + // ES6 + //r.typeErrorResult(true, "Object.isExtensible called on non-object") + return valueFalse + } +} + +func (r *Runtime) object_keys(call FunctionCall) Value { + obj := call.Argument(0).ToObject(r) + + return r.newArrayValues(obj.self.stringKeys(false, nil)) +} + +func (r *Runtime) object_entries(call FunctionCall) Value { + obj := call.Argument(0).ToObject(r) + + var values []Value + + for item, next := iterateEnumerableStringProperties(obj)(); next != nil; item, next = next() { + values = append(values, r.newArrayValues([]Value{item.name, item.value})) + } + + return r.newArrayValues(values) +} + +func (r *Runtime) object_values(call FunctionCall) Value { + obj := call.Argument(0).ToObject(r) + + var values []Value + + for item, next := iterateEnumerableStringProperties(obj)(); next != nil; item, next = next() { + values = append(values, item.value) + } + + return r.newArrayValues(values) +} + +func (r *Runtime) objectproto_hasOwnProperty(call FunctionCall) Value { + p := toPropertyKey(call.Argument(0)) + o := call.This.ToObject(r) + if o.hasOwnProperty(p) { + return valueTrue + } else { + return valueFalse + } +} + +func (r *Runtime) objectproto_isPrototypeOf(call FunctionCall) Value { + if v, ok := call.Argument(0).(*Object); ok { + o := call.This.ToObject(r) + for { + v = v.self.proto() + if v == nil { + break + } + if v == o { + return valueTrue + } + } + } + return valueFalse +} + +func (r *Runtime) objectproto_propertyIsEnumerable(call FunctionCall) Value { + p := toPropertyKey(call.Argument(0)) + o := call.This.ToObject(r) + pv := o.getOwnProp(p) + if pv == nil { + return valueFalse + } + if prop, ok := pv.(*valueProperty); ok { + if !prop.enumerable { + return valueFalse + } + } + return valueTrue +} + +func (r *Runtime) objectproto_toString(call FunctionCall) Value { + switch o := call.This.(type) { + case valueNull: + return stringObjectNull + case valueUndefined: + return stringObjectUndefined + default: + obj := o.ToObject(r) + if o, ok := obj.self.(*objectGoReflect); ok { + if toString := o.toString; toString != nil { + return toString() + } + } + var clsName string + if isArray(obj) { + clsName = classArray + } else { + clsName = obj.self.className() + } + if tag := obj.self.getSym(SymToStringTag, nil); tag != nil { + if str, ok := tag.(String); ok { + clsName = str.String() + } + } + return newStringValue(fmt.Sprintf("[object %s]", clsName)) + } +} + +func (r *Runtime) objectproto_toLocaleString(call FunctionCall) Value { + toString := toMethod(r.getVStr(call.This, "toString")) + return toString(FunctionCall{This: call.This}) +} + +func (r *Runtime) objectproto_getProto(call FunctionCall) Value { + proto := call.This.ToObject(r).self.proto() + if proto != nil { + return proto + } + return _null +} + +func (r *Runtime) setObjectProto(o, arg Value) { + r.checkObjectCoercible(o) + var proto *Object + if arg != _null { + if obj, ok := arg.(*Object); ok { + proto = obj + } else { + return + } + } + if o, ok := o.(*Object); ok { + o.self.setProto(proto, true) + } +} + +func (r *Runtime) objectproto_setProto(call FunctionCall) Value { + r.setObjectProto(call.This, call.Argument(0)) + return _undefined +} + +func (r *Runtime) objectproto_valueOf(call FunctionCall) Value { + return call.This.ToObject(r) +} + +func (r *Runtime) object_assign(call FunctionCall) Value { + to := call.Argument(0).ToObject(r) + if len(call.Arguments) > 1 { + for _, arg := range call.Arguments[1:] { + if arg != _undefined && arg != _null { + source := arg.ToObject(r) + for item, next := iterateEnumerableProperties(source)(); next != nil; item, next = next() { + to.setOwn(item.name, item.value, true) + } + } + } + } + + return to +} + +func (r *Runtime) object_is(call FunctionCall) Value { + return r.toBoolean(call.Argument(0).SameAs(call.Argument(1))) +} + +func (r *Runtime) toProto(proto Value) *Object { + if proto != _null { + if obj, ok := proto.(*Object); ok { + return obj + } else { + panic(r.NewTypeError("Object prototype may only be an Object or null: %s", proto)) + } + } + return nil +} + +func (r *Runtime) object_setPrototypeOf(call FunctionCall) Value { + o := call.Argument(0) + r.checkObjectCoercible(o) + proto := r.toProto(call.Argument(1)) + if o, ok := o.(*Object); ok { + o.self.setProto(proto, true) + } + + return o +} + +func (r *Runtime) object_fromEntries(call FunctionCall) Value { + o := call.Argument(0) + r.checkObjectCoercible(o) + + result := r.newBaseObject(r.global.ObjectPrototype, classObject).val + + iter := r.getIterator(o, nil) + iter.iterate(func(nextValue Value) { + i0 := valueInt(0) + i1 := valueInt(1) + + itemObj := r.toObject(nextValue) + k := itemObj.self.getIdx(i0, nil) + v := itemObj.self.getIdx(i1, nil) + key := toPropertyKey(k) + + createDataPropertyOrThrow(result, key, v) + }) + + return result +} + +func (r *Runtime) object_hasOwn(call FunctionCall) Value { + o := call.Argument(0) + obj := o.ToObject(r) + p := toPropertyKey(call.Argument(1)) + + if obj.hasOwnProperty(p) { + return valueTrue + } else { + return valueFalse + } +} + +func createObjectTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.getFunctionPrototype() + } + + t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(1), false, false, true) }) + t.putStr("name", func(r *Runtime) Value { return valueProp(asciiString("Object"), false, false, true) }) + + t.putStr("prototype", func(r *Runtime) Value { return valueProp(r.global.ObjectPrototype, false, false, false) }) + + t.putStr("assign", func(r *Runtime) Value { return r.methodProp(r.object_assign, "assign", 2) }) + t.putStr("defineProperty", func(r *Runtime) Value { return r.methodProp(r.object_defineProperty, "defineProperty", 3) }) + t.putStr("defineProperties", func(r *Runtime) Value { return r.methodProp(r.object_defineProperties, "defineProperties", 2) }) + t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.object_entries, "entries", 1) }) + t.putStr("getOwnPropertyDescriptor", func(r *Runtime) Value { + return r.methodProp(r.object_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2) + }) + t.putStr("getOwnPropertyDescriptors", func(r *Runtime) Value { + return r.methodProp(r.object_getOwnPropertyDescriptors, "getOwnPropertyDescriptors", 1) + }) + t.putStr("getPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_getPrototypeOf, "getPrototypeOf", 1) }) + t.putStr("is", func(r *Runtime) Value { return r.methodProp(r.object_is, "is", 2) }) + t.putStr("getOwnPropertyNames", func(r *Runtime) Value { return r.methodProp(r.object_getOwnPropertyNames, "getOwnPropertyNames", 1) }) + t.putStr("getOwnPropertySymbols", func(r *Runtime) Value { + return r.methodProp(r.object_getOwnPropertySymbols, "getOwnPropertySymbols", 1) + }) + t.putStr("create", func(r *Runtime) Value { return r.methodProp(r.object_create, "create", 2) }) + t.putStr("seal", func(r *Runtime) Value { return r.methodProp(r.object_seal, "seal", 1) }) + t.putStr("freeze", func(r *Runtime) Value { return r.methodProp(r.object_freeze, "freeze", 1) }) + t.putStr("preventExtensions", func(r *Runtime) Value { return r.methodProp(r.object_preventExtensions, "preventExtensions", 1) }) + t.putStr("isSealed", func(r *Runtime) Value { return r.methodProp(r.object_isSealed, "isSealed", 1) }) + t.putStr("isFrozen", func(r *Runtime) Value { return r.methodProp(r.object_isFrozen, "isFrozen", 1) }) + t.putStr("isExtensible", func(r *Runtime) Value { return r.methodProp(r.object_isExtensible, "isExtensible", 1) }) + t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.object_keys, "keys", 1) }) + t.putStr("setPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.object_setPrototypeOf, "setPrototypeOf", 2) }) + t.putStr("values", func(r *Runtime) Value { return r.methodProp(r.object_values, "values", 1) }) + t.putStr("fromEntries", func(r *Runtime) Value { return r.methodProp(r.object_fromEntries, "fromEntries", 1) }) + t.putStr("hasOwn", func(r *Runtime) Value { return r.methodProp(r.object_hasOwn, "hasOwn", 2) }) + + return t +} + +var _objectTemplate *objectTemplate +var objectTemplateOnce sync.Once + +func getObjectTemplate() *objectTemplate { + objectTemplateOnce.Do(func() { + _objectTemplate = createObjectTemplate() + }) + return _objectTemplate +} + +func (r *Runtime) getObject() *Object { + ret := r.global.Object + if ret == nil { + ret = &Object{runtime: r} + r.global.Object = ret + r.newTemplatedFuncObject(getObjectTemplate(), ret, func(call FunctionCall) Value { + return r.builtin_Object(call.Arguments, nil) + }, r.builtin_Object) + } + return ret +} + +/* +func (r *Runtime) getObjectPrototype() *Object { + ret := r.global.ObjectPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.ObjectPrototype = ret + r.newTemplatedObject(getObjectProtoTemplate(), ret) + } + return ret +} +*/ + +var objectProtoTemplate *objectTemplate +var objectProtoTemplateOnce sync.Once + +func getObjectProtoTemplate() *objectTemplate { + objectProtoTemplateOnce.Do(func() { + objectProtoTemplate = createObjectProtoTemplate() + }) + return objectProtoTemplate +} + +func createObjectProtoTemplate() *objectTemplate { + t := newObjectTemplate() + + // null prototype + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getObject(), true, false, true) }) + + t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toString, "toString", 0) }) + t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.objectproto_toLocaleString, "toLocaleString", 0) }) + t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_valueOf, "valueOf", 0) }) + t.putStr("hasOwnProperty", func(r *Runtime) Value { return r.methodProp(r.objectproto_hasOwnProperty, "hasOwnProperty", 1) }) + t.putStr("isPrototypeOf", func(r *Runtime) Value { return r.methodProp(r.objectproto_isPrototypeOf, "isPrototypeOf", 1) }) + t.putStr("propertyIsEnumerable", func(r *Runtime) Value { + return r.methodProp(r.objectproto_propertyIsEnumerable, "propertyIsEnumerable", 1) + }) + t.putStr(__proto__, func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + getterFunc: r.newNativeFunc(r.objectproto_getProto, "get __proto__", 0), + setterFunc: r.newNativeFunc(r.objectproto_setProto, "set __proto__", 1), + configurable: true, + } + }) + + return t +} diff --git a/pkg/xscript/engine/builtin_promise.go b/pkg/xscript/engine/builtin_promise.go new file mode 100644 index 0000000..fdd8e8e --- /dev/null +++ b/pkg/xscript/engine/builtin_promise.go @@ -0,0 +1,647 @@ +package engine + +import ( + "reflect" + + "pandax/pkg/xscript/engine/unistring" +) + +type PromiseState int +type PromiseRejectionOperation int + +type promiseReactionType int + +const ( + PromiseStatePending PromiseState = iota + PromiseStateFulfilled + PromiseStateRejected +) + +const ( + PromiseRejectionReject PromiseRejectionOperation = iota + PromiseRejectionHandle +) + +const ( + promiseReactionFulfill promiseReactionType = iota + promiseReactionReject +) + +type PromiseRejectionTracker func(p *Promise, operation PromiseRejectionOperation) + +type jobCallback struct { + callback func(FunctionCall) Value +} + +type promiseCapability struct { + promise *Object + resolveObj, rejectObj *Object +} + +type promiseReaction struct { + capability *promiseCapability + typ promiseReactionType + handler *jobCallback + asyncRunner *asyncRunner + asyncCtx any +} + +var typePromise = reflect.TypeOf((*Promise)(nil)) + +// Promise is a Go wrapper around ECMAScript Promise. Calling Runtime.ToValue() on it +// returns the underlying Object. Calling Export() on a Promise Object returns a Promise. +// +// Use Runtime.NewPromise() to create one. Calling Runtime.ToValue() on a zero object or nil returns null Value. +// +// WARNING: Instances of Promise are not goroutine-safe. See Runtime.NewPromise() for more details. +type Promise struct { + baseObject + state PromiseState + result Value + fulfillReactions []*promiseReaction + rejectReactions []*promiseReaction + handled bool +} + +func (p *Promise) State() PromiseState { + return p.state +} + +func (p *Promise) Result() Value { + return p.result +} + +func (p *Promise) toValue(r *Runtime) Value { + if p == nil || p.val == nil { + return _null + } + promise := p.val + if promise.runtime != r { + panic(r.NewTypeError("Illegal runtime transition of a Promise")) + } + return promise +} + +func (p *Promise) createResolvingFunctions() (resolve, reject *Object) { + r := p.val.runtime + alreadyResolved := false + return p.val.runtime.newNativeFunc(func(call FunctionCall) Value { + if alreadyResolved { + return _undefined + } + alreadyResolved = true + resolution := call.Argument(0) + if resolution.SameAs(p.val) { + return p.reject(r.NewTypeError("Promise self-resolution")) + } + if obj, ok := resolution.(*Object); ok { + var thenAction Value + ex := r.vm.try(func() { + thenAction = obj.self.getStr("then", nil) + }) + if ex != nil { + return p.reject(ex.val) + } + if call, ok := assertCallable(thenAction); ok { + job := r.newPromiseResolveThenableJob(p, resolution, &jobCallback{callback: call}) + r.enqueuePromiseJob(job) + return _undefined + } + } + return p.fulfill(resolution) + }, "", 1), + p.val.runtime.newNativeFunc(func(call FunctionCall) Value { + if alreadyResolved { + return _undefined + } + alreadyResolved = true + reason := call.Argument(0) + return p.reject(reason) + }, "", 1) +} + +func (p *Promise) reject(reason Value) Value { + reactions := p.rejectReactions + p.result = reason + p.fulfillReactions, p.rejectReactions = nil, nil + p.state = PromiseStateRejected + r := p.val.runtime + if !p.handled { + r.trackPromiseRejection(p, PromiseRejectionReject) + } + r.triggerPromiseReactions(reactions, reason) + return _undefined +} + +func (p *Promise) fulfill(value Value) Value { + reactions := p.fulfillReactions + p.result = value + p.fulfillReactions, p.rejectReactions = nil, nil + p.state = PromiseStateFulfilled + p.val.runtime.triggerPromiseReactions(reactions, value) + return _undefined +} + +func (p *Promise) exportType() reflect.Type { + return typePromise +} + +func (p *Promise) export(*objectExportCtx) any { + return p +} + +func (p *Promise) addReactions(fulfillReaction *promiseReaction, rejectReaction *promiseReaction) { + r := p.val.runtime + if tracker := r.asyncContextTracker; tracker != nil { + ctx := tracker.Grab() + fulfillReaction.asyncCtx = ctx + rejectReaction.asyncCtx = ctx + } + switch p.state { + case PromiseStatePending: + p.fulfillReactions = append(p.fulfillReactions, fulfillReaction) + p.rejectReactions = append(p.rejectReactions, rejectReaction) + case PromiseStateFulfilled: + r.enqueuePromiseJob(r.newPromiseReactionJob(fulfillReaction, p.result)) + default: + reason := p.result + if !p.handled { + r.trackPromiseRejection(p, PromiseRejectionHandle) + } + r.enqueuePromiseJob(r.newPromiseReactionJob(rejectReaction, reason)) + } + p.handled = true +} + +func (r *Runtime) newPromiseResolveThenableJob(p *Promise, thenable Value, then *jobCallback) func() { + return func() { + resolve, reject := p.createResolvingFunctions() + ex := r.vm.try(func() { + r.callJobCallback(then, thenable, resolve, reject) + }) + if ex != nil { + if fn, ok := reject.self.assertCallable(); ok { + fn(FunctionCall{Arguments: []Value{ex.val}}) + } + } + } +} + +func (r *Runtime) enqueuePromiseJob(job func()) { + r.jobQueue = append(r.jobQueue, job) +} + +func (r *Runtime) triggerPromiseReactions(reactions []*promiseReaction, argument Value) { + for _, reaction := range reactions { + r.enqueuePromiseJob(r.newPromiseReactionJob(reaction, argument)) + } +} + +func (r *Runtime) newPromiseReactionJob(reaction *promiseReaction, argument Value) func() { + return func() { + var handlerResult Value + fulfill := false + if reaction.handler == nil { + handlerResult = argument + if reaction.typ == promiseReactionFulfill { + fulfill = true + } + } else { + if tracker := r.asyncContextTracker; tracker != nil { + tracker.Resumed(reaction.asyncCtx) + } + ex := r.vm.try(func() { + handlerResult = r.callJobCallback(reaction.handler, _undefined, argument) + fulfill = true + }) + if ex != nil { + handlerResult = ex.val + } + if tracker := r.asyncContextTracker; tracker != nil { + tracker.Exited() + } + } + if reaction.capability != nil { + if fulfill { + reaction.capability.resolve(handlerResult) + } else { + reaction.capability.reject(handlerResult) + } + } + } +} + +func (r *Runtime) newPromise(proto *Object) *Promise { + o := &Object{runtime: r} + + po := &Promise{} + po.class = classObject + po.val = o + po.extensible = true + o.self = po + po.prototype = proto + po.init() + return po +} + +func (r *Runtime) builtin_newPromise(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("Promise")) + } + var arg0 Value + if len(args) > 0 { + arg0 = args[0] + } + executor := r.toCallable(arg0) + + proto := r.getPrototypeFromCtor(newTarget, r.global.Promise, r.getPromisePrototype()) + po := r.newPromise(proto) + + resolve, reject := po.createResolvingFunctions() + ex := r.vm.try(func() { + executor(FunctionCall{Arguments: []Value{resolve, reject}}) + }) + if ex != nil { + if fn, ok := reject.self.assertCallable(); ok { + fn(FunctionCall{Arguments: []Value{ex.val}}) + } + } + return po.val +} + +func (r *Runtime) promiseProto_then(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if p, ok := thisObj.self.(*Promise); ok { + c := r.speciesConstructorObj(thisObj, r.getPromise()) + resultCapability := r.newPromiseCapability(c) + return r.performPromiseThen(p, call.Argument(0), call.Argument(1), resultCapability) + } + panic(r.NewTypeError("Method Promise.prototype.then called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) +} + +func (r *Runtime) newPromiseCapability(c *Object) *promiseCapability { + pcap := new(promiseCapability) + if c == r.getPromise() { + p := r.newPromise(r.getPromisePrototype()) + pcap.resolveObj, pcap.rejectObj = p.createResolvingFunctions() + pcap.promise = p.val + } else { + var resolve, reject Value + executor := r.newNativeFunc(func(call FunctionCall) Value { + if resolve != nil { + panic(r.NewTypeError("resolve is already set")) + } + if reject != nil { + panic(r.NewTypeError("reject is already set")) + } + if arg := call.Argument(0); arg != _undefined { + resolve = arg + } + if arg := call.Argument(1); arg != _undefined { + reject = arg + } + return nil + }, "", 2) + pcap.promise = r.toConstructor(c)([]Value{executor}, c) + pcap.resolveObj = r.toObject(resolve) + r.toCallable(pcap.resolveObj) // make sure it's callable + pcap.rejectObj = r.toObject(reject) + r.toCallable(pcap.rejectObj) + } + return pcap +} + +func (r *Runtime) performPromiseThen(p *Promise, onFulfilled, onRejected Value, resultCapability *promiseCapability) Value { + var onFulfilledJobCallback, onRejectedJobCallback *jobCallback + if f, ok := assertCallable(onFulfilled); ok { + onFulfilledJobCallback = &jobCallback{callback: f} + } + if f, ok := assertCallable(onRejected); ok { + onRejectedJobCallback = &jobCallback{callback: f} + } + fulfillReaction := &promiseReaction{ + capability: resultCapability, + typ: promiseReactionFulfill, + handler: onFulfilledJobCallback, + } + rejectReaction := &promiseReaction{ + capability: resultCapability, + typ: promiseReactionReject, + handler: onRejectedJobCallback, + } + p.addReactions(fulfillReaction, rejectReaction) + if resultCapability == nil { + return _undefined + } + return resultCapability.promise +} + +func (r *Runtime) promiseProto_catch(call FunctionCall) Value { + return r.invoke(call.This, "then", _undefined, call.Argument(0)) +} + +func (r *Runtime) promiseResolve(c *Object, x Value) *Object { + if obj, ok := x.(*Object); ok { + xConstructor := nilSafe(obj.self.getStr("constructor", nil)) + if xConstructor.SameAs(c) { + return obj + } + } + pcap := r.newPromiseCapability(c) + pcap.resolve(x) + return pcap.promise +} + +func (r *Runtime) promiseProto_finally(call FunctionCall) Value { + promise := r.toObject(call.This) + c := r.speciesConstructorObj(promise, r.getPromise()) + onFinally := call.Argument(0) + var thenFinally, catchFinally Value + if onFinallyFn, ok := assertCallable(onFinally); !ok { + thenFinally, catchFinally = onFinally, onFinally + } else { + thenFinally = r.newNativeFunc(func(call FunctionCall) Value { + value := call.Argument(0) + result := onFinallyFn(FunctionCall{}) + promise := r.promiseResolve(c, result) + valueThunk := r.newNativeFunc(func(call FunctionCall) Value { + return value + }, "", 0) + return r.invoke(promise, "then", valueThunk) + }, "", 1) + + catchFinally = r.newNativeFunc(func(call FunctionCall) Value { + reason := call.Argument(0) + result := onFinallyFn(FunctionCall{}) + promise := r.promiseResolve(c, result) + thrower := r.newNativeFunc(func(call FunctionCall) Value { + panic(reason) + }, "", 0) + return r.invoke(promise, "then", thrower) + }, "", 1) + } + return r.invoke(promise, "then", thenFinally, catchFinally) +} + +func (pcap *promiseCapability) resolve(result Value) { + pcap.promise.runtime.toCallable(pcap.resolveObj)(FunctionCall{Arguments: []Value{result}}) +} + +func (pcap *promiseCapability) reject(reason Value) { + pcap.promise.runtime.toCallable(pcap.rejectObj)(FunctionCall{Arguments: []Value{reason}}) +} + +func (pcap *promiseCapability) try(f func()) bool { + ex := pcap.promise.runtime.vm.try(f) + if ex != nil { + pcap.reject(ex.val) + return false + } + return true +} + +func (r *Runtime) promise_all(call FunctionCall) Value { + c := r.toObject(call.This) + pcap := r.newPromiseCapability(c) + + pcap.try(func() { + promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) + iter := r.getIterator(call.Argument(0), nil) + var values []Value + remainingElementsCount := 1 + iter.iterate(func(nextValue Value) { + index := len(values) + values = append(values, _undefined) + nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) + alreadyCalled := false + onFulfilled := r.newNativeFunc(func(call FunctionCall) Value { + if alreadyCalled { + return _undefined + } + alreadyCalled = true + values[index] = call.Argument(0) + remainingElementsCount-- + if remainingElementsCount == 0 { + pcap.resolve(r.newArrayValues(values)) + } + return _undefined + }, "", 1) + remainingElementsCount++ + r.invoke(nextPromise, "then", onFulfilled, pcap.rejectObj) + }) + remainingElementsCount-- + if remainingElementsCount == 0 { + pcap.resolve(r.newArrayValues(values)) + } + }) + return pcap.promise +} + +func (r *Runtime) promise_allSettled(call FunctionCall) Value { + c := r.toObject(call.This) + pcap := r.newPromiseCapability(c) + + pcap.try(func() { + promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) + iter := r.getIterator(call.Argument(0), nil) + var values []Value + remainingElementsCount := 1 + iter.iterate(func(nextValue Value) { + index := len(values) + values = append(values, _undefined) + nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) + alreadyCalled := false + reaction := func(status Value, valueKey unistring.String) *Object { + return r.newNativeFunc(func(call FunctionCall) Value { + if alreadyCalled { + return _undefined + } + alreadyCalled = true + obj := r.NewObject() + obj.self._putProp("status", status, true, true, true) + obj.self._putProp(valueKey, call.Argument(0), true, true, true) + values[index] = obj + remainingElementsCount-- + if remainingElementsCount == 0 { + pcap.resolve(r.newArrayValues(values)) + } + return _undefined + }, "", 1) + } + onFulfilled := reaction(asciiString("fulfilled"), "value") + onRejected := reaction(asciiString("rejected"), "reason") + remainingElementsCount++ + r.invoke(nextPromise, "then", onFulfilled, onRejected) + }) + remainingElementsCount-- + if remainingElementsCount == 0 { + pcap.resolve(r.newArrayValues(values)) + } + }) + return pcap.promise +} + +func (r *Runtime) promise_any(call FunctionCall) Value { + c := r.toObject(call.This) + pcap := r.newPromiseCapability(c) + + pcap.try(func() { + promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) + iter := r.getIterator(call.Argument(0), nil) + var errors []Value + remainingElementsCount := 1 + iter.iterate(func(nextValue Value) { + index := len(errors) + errors = append(errors, _undefined) + nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) + alreadyCalled := false + onRejected := r.newNativeFunc(func(call FunctionCall) Value { + if alreadyCalled { + return _undefined + } + alreadyCalled = true + errors[index] = call.Argument(0) + remainingElementsCount-- + if remainingElementsCount == 0 { + _error := r.builtin_new(r.getAggregateError(), nil) + _error.self._putProp("errors", r.newArrayValues(errors), true, false, true) + pcap.reject(_error) + } + return _undefined + }, "", 1) + + remainingElementsCount++ + r.invoke(nextPromise, "then", pcap.resolveObj, onRejected) + }) + remainingElementsCount-- + if remainingElementsCount == 0 { + _error := r.builtin_new(r.getAggregateError(), nil) + _error.self._putProp("errors", r.newArrayValues(errors), true, false, true) + pcap.reject(_error) + } + }) + return pcap.promise +} + +func (r *Runtime) promise_race(call FunctionCall) Value { + c := r.toObject(call.This) + pcap := r.newPromiseCapability(c) + + pcap.try(func() { + promiseResolve := r.toCallable(c.self.getStr("resolve", nil)) + iter := r.getIterator(call.Argument(0), nil) + iter.iterate(func(nextValue Value) { + nextPromise := promiseResolve(FunctionCall{This: c, Arguments: []Value{nextValue}}) + r.invoke(nextPromise, "then", pcap.resolveObj, pcap.rejectObj) + }) + }) + return pcap.promise +} + +func (r *Runtime) promise_reject(call FunctionCall) Value { + pcap := r.newPromiseCapability(r.toObject(call.This)) + pcap.reject(call.Argument(0)) + return pcap.promise +} + +func (r *Runtime) promise_resolve(call FunctionCall) Value { + return r.promiseResolve(r.toObject(call.This), call.Argument(0)) +} + +func (r *Runtime) createPromiseProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + o._putProp("constructor", r.getPromise(), true, false, true) + + o._putProp("catch", r.newNativeFunc(r.promiseProto_catch, "catch", 1), true, false, true) + o._putProp("finally", r.newNativeFunc(r.promiseProto_finally, "finally", 1), true, false, true) + o._putProp("then", r.newNativeFunc(r.promiseProto_then, "then", 2), true, false, true) + + o._putSym(SymToStringTag, valueProp(asciiString(classPromise), false, false, true)) + + return o +} + +func (r *Runtime) createPromise(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newPromise, r.getPromisePrototype(), "Promise", 1) + + o._putProp("all", r.newNativeFunc(r.promise_all, "all", 1), true, false, true) + o._putProp("allSettled", r.newNativeFunc(r.promise_allSettled, "allSettled", 1), true, false, true) + o._putProp("any", r.newNativeFunc(r.promise_any, "any", 1), true, false, true) + o._putProp("race", r.newNativeFunc(r.promise_race, "race", 1), true, false, true) + o._putProp("reject", r.newNativeFunc(r.promise_reject, "reject", 1), true, false, true) + o._putProp("resolve", r.newNativeFunc(r.promise_resolve, "resolve", 1), true, false, true) + + r.putSpeciesReturnThis(o) + + return o +} + +func (r *Runtime) getPromisePrototype() *Object { + ret := r.global.PromisePrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.PromisePrototype = ret + ret.self = r.createPromiseProto(ret) + } + return ret +} + +func (r *Runtime) getPromise() *Object { + ret := r.global.Promise + if ret == nil { + ret = &Object{runtime: r} + r.global.Promise = ret + ret.self = r.createPromise(ret) + } + return ret +} + +func (r *Runtime) wrapPromiseReaction(fObj *Object) func(any) { + f, _ := AssertFunction(fObj) + return func(x any) { + _, _ = f(nil, r.ToValue(x)) + } +} + +// NewPromise creates and returns a Promise and resolving functions for it. +// +// WARNING: The returned values are not goroutine-safe and must not be called in parallel with VM running. +// In order to make use of this method you need an event loop such as the one in goja_nodejs (https://pandax/pkg/xscript/xjs_nodejs) +// where it can be used like this: +// +// loop := NewEventLoop() +// loop.Start() +// defer loop.Stop() +// loop.RunOnLoop(func(vm *xjs.Runtime) { +// p, resolve, _ := vm.NewPromise() +// vm.Set("p", p) +// go func() { +// time.Sleep(500 * time.Millisecond) // or perform any other blocking operation +// loop.RunOnLoop(func(*xjs.Runtime) { // resolve() must be called on the loop, cannot call it here +// resolve(result) +// }) +// }() +// } +func (r *Runtime) NewPromise() (promise *Promise, resolve func(result any), reject func(reason any)) { + p := r.newPromise(r.getPromisePrototype()) + resolveF, rejectF := p.createResolvingFunctions() + return p, r.wrapPromiseReaction(resolveF), r.wrapPromiseReaction(rejectF) +} + +// SetPromiseRejectionTracker registers a function that will be called in two scenarios: when a promise is rejected +// without any handlers (with operation argument set to PromiseRejectionReject), and when a handler is added to a +// rejected promise for the first time (with operation argument set to PromiseRejectionHandle). +// +// Setting a tracker replaces any existing one. Setting it to nil disables the functionality. +// +// See https://tc39.es/ecma262/#sec-host-promise-rejection-tracker for more details. +func (r *Runtime) SetPromiseRejectionTracker(tracker PromiseRejectionTracker) { + r.promiseRejectionTracker = tracker +} + +// SetAsyncContextTracker registers a handler that allows to track async execution contexts. See AsyncContextTracker +// documentation for more details. Setting it to nil disables the functionality. +// This method (as Runtime in general) is not goroutine-safe. +func (r *Runtime) SetAsyncContextTracker(tracker AsyncContextTracker) { + r.asyncContextTracker = tracker +} diff --git a/pkg/xscript/engine/builtin_proxy.go b/pkg/xscript/engine/builtin_proxy.go new file mode 100644 index 0000000..29db998 --- /dev/null +++ b/pkg/xscript/engine/builtin_proxy.go @@ -0,0 +1,396 @@ +package engine + +import ( + "pandax/pkg/xscript/engine/unistring" +) + +type nativeProxyHandler struct { + handler *ProxyTrapConfig +} + +func (h *nativeProxyHandler) getPrototypeOf(target *Object) (Value, bool) { + if trap := h.handler.GetPrototypeOf; trap != nil { + return trap(target), true + } + return nil, false +} + +func (h *nativeProxyHandler) setPrototypeOf(target *Object, proto *Object) (bool, bool) { + if trap := h.handler.SetPrototypeOf; trap != nil { + return trap(target, proto), true + } + return false, false +} + +func (h *nativeProxyHandler) isExtensible(target *Object) (bool, bool) { + if trap := h.handler.IsExtensible; trap != nil { + return trap(target), true + } + return false, false +} + +func (h *nativeProxyHandler) preventExtensions(target *Object) (bool, bool) { + if trap := h.handler.PreventExtensions; trap != nil { + return trap(target), true + } + return false, false +} + +func (h *nativeProxyHandler) getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) { + if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil { + if idx, ok := strToInt(prop); ok { + desc := trap(target, idx) + return desc.toValue(target.runtime), true + } + } + if trap := h.handler.GetOwnPropertyDescriptor; trap != nil { + desc := trap(target, prop.String()) + return desc.toValue(target.runtime), true + } + return nil, false +} + +func (h *nativeProxyHandler) getOwnPropertyDescriptorIdx(target *Object, prop valueInt) (Value, bool) { + if trap := h.handler.GetOwnPropertyDescriptorIdx; trap != nil { + desc := trap(target, toIntStrict(int64(prop))) + return desc.toValue(target.runtime), true + } + if trap := h.handler.GetOwnPropertyDescriptor; trap != nil { + desc := trap(target, prop.String()) + return desc.toValue(target.runtime), true + } + return nil, false +} + +func (h *nativeProxyHandler) getOwnPropertyDescriptorSym(target *Object, prop *Symbol) (Value, bool) { + if trap := h.handler.GetOwnPropertyDescriptorSym; trap != nil { + desc := trap(target, prop) + return desc.toValue(target.runtime), true + } + return nil, false +} + +func (h *nativeProxyHandler) definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) { + if trap := h.handler.DefinePropertyIdx; trap != nil { + if idx, ok := strToInt(prop); ok { + return trap(target, idx, desc), true + } + } + if trap := h.handler.DefineProperty; trap != nil { + return trap(target, prop.String(), desc), true + } + return false, false +} + +func (h *nativeProxyHandler) definePropertyIdx(target *Object, prop valueInt, desc PropertyDescriptor) (bool, bool) { + if trap := h.handler.DefinePropertyIdx; trap != nil { + return trap(target, toIntStrict(int64(prop)), desc), true + } + if trap := h.handler.DefineProperty; trap != nil { + return trap(target, prop.String(), desc), true + } + return false, false +} + +func (h *nativeProxyHandler) definePropertySym(target *Object, prop *Symbol, desc PropertyDescriptor) (bool, bool) { + if trap := h.handler.DefinePropertySym; trap != nil { + return trap(target, prop, desc), true + } + return false, false +} + +func (h *nativeProxyHandler) hasStr(target *Object, prop unistring.String) (bool, bool) { + if trap := h.handler.HasIdx; trap != nil { + if idx, ok := strToInt(prop); ok { + return trap(target, idx), true + } + } + if trap := h.handler.Has; trap != nil { + return trap(target, prop.String()), true + } + return false, false +} + +func (h *nativeProxyHandler) hasIdx(target *Object, prop valueInt) (bool, bool) { + if trap := h.handler.HasIdx; trap != nil { + return trap(target, toIntStrict(int64(prop))), true + } + if trap := h.handler.Has; trap != nil { + return trap(target, prop.String()), true + } + return false, false +} + +func (h *nativeProxyHandler) hasSym(target *Object, prop *Symbol) (bool, bool) { + if trap := h.handler.HasSym; trap != nil { + return trap(target, prop), true + } + return false, false +} + +func (h *nativeProxyHandler) getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) { + if trap := h.handler.GetIdx; trap != nil { + if idx, ok := strToInt(prop); ok { + return trap(target, idx, receiver), true + } + } + if trap := h.handler.Get; trap != nil { + return trap(target, prop.String(), receiver), true + } + return nil, false +} + +func (h *nativeProxyHandler) getIdx(target *Object, prop valueInt, receiver Value) (Value, bool) { + if trap := h.handler.GetIdx; trap != nil { + return trap(target, toIntStrict(int64(prop)), receiver), true + } + if trap := h.handler.Get; trap != nil { + return trap(target, prop.String(), receiver), true + } + return nil, false +} + +func (h *nativeProxyHandler) getSym(target *Object, prop *Symbol, receiver Value) (Value, bool) { + if trap := h.handler.GetSym; trap != nil { + return trap(target, prop, receiver), true + } + return nil, false +} + +func (h *nativeProxyHandler) setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) { + if trap := h.handler.SetIdx; trap != nil { + if idx, ok := strToInt(prop); ok { + return trap(target, idx, value, receiver), true + } + } + if trap := h.handler.Set; trap != nil { + return trap(target, prop.String(), value, receiver), true + } + return false, false +} + +func (h *nativeProxyHandler) setIdx(target *Object, prop valueInt, value Value, receiver Value) (bool, bool) { + if trap := h.handler.SetIdx; trap != nil { + return trap(target, toIntStrict(int64(prop)), value, receiver), true + } + if trap := h.handler.Set; trap != nil { + return trap(target, prop.String(), value, receiver), true + } + return false, false +} + +func (h *nativeProxyHandler) setSym(target *Object, prop *Symbol, value Value, receiver Value) (bool, bool) { + if trap := h.handler.SetSym; trap != nil { + return trap(target, prop, value, receiver), true + } + return false, false +} + +func (h *nativeProxyHandler) deleteStr(target *Object, prop unistring.String) (bool, bool) { + if trap := h.handler.DeletePropertyIdx; trap != nil { + if idx, ok := strToInt(prop); ok { + return trap(target, idx), true + } + } + if trap := h.handler.DeleteProperty; trap != nil { + return trap(target, prop.String()), true + } + return false, false +} + +func (h *nativeProxyHandler) deleteIdx(target *Object, prop valueInt) (bool, bool) { + if trap := h.handler.DeletePropertyIdx; trap != nil { + return trap(target, toIntStrict(int64(prop))), true + } + if trap := h.handler.DeleteProperty; trap != nil { + return trap(target, prop.String()), true + } + return false, false +} + +func (h *nativeProxyHandler) deleteSym(target *Object, prop *Symbol) (bool, bool) { + if trap := h.handler.DeletePropertySym; trap != nil { + return trap(target, prop), true + } + return false, false +} + +func (h *nativeProxyHandler) ownKeys(target *Object) (*Object, bool) { + if trap := h.handler.OwnKeys; trap != nil { + return trap(target), true + } + return nil, false +} + +func (h *nativeProxyHandler) apply(target *Object, this Value, args []Value) (Value, bool) { + if trap := h.handler.Apply; trap != nil { + return trap(target, this, args), true + } + return nil, false +} + +func (h *nativeProxyHandler) construct(target *Object, args []Value, newTarget *Object) (Value, bool) { + if trap := h.handler.Construct; trap != nil { + return trap(target, args, newTarget), true + } + return nil, false +} + +func (h *nativeProxyHandler) toObject(runtime *Runtime) *Object { + return runtime.ToValue(h.handler).ToObject(runtime) +} + +func (r *Runtime) newNativeProxyHandler(nativeHandler *ProxyTrapConfig) proxyHandler { + return &nativeProxyHandler{handler: nativeHandler} +} + +// ProxyTrapConfig provides a simplified Go-friendly API for implementing Proxy traps. +// If an *Idx trap is defined it gets called for integer property keys, including negative ones. Note that +// this only includes string property keys that represent a canonical integer +// (i.e. "0", "123", but not "00", "01", " 1" or "-0"). +// For efficiency strings representing integers exceeding 2^53 are not checked to see if they are canonical, +// i.e. the *Idx traps will receive "9007199254740993" as well as "9007199254740994", even though the former is not +// a canonical representation in ECMAScript (Number("9007199254740993") === 9007199254740992). +// See https://262.ecma-international.org/#sec-canonicalnumericindexstring +// If an *Idx trap is not set, the corresponding string one is used. +type ProxyTrapConfig struct { + // A trap for Object.getPrototypeOf, Reflect.getPrototypeOf, __proto__, Object.prototype.isPrototypeOf, instanceof + GetPrototypeOf func(target *Object) (prototype *Object) + + // A trap for Object.setPrototypeOf, Reflect.setPrototypeOf + SetPrototypeOf func(target *Object, prototype *Object) (success bool) + + // A trap for Object.isExtensible, Reflect.isExtensible + IsExtensible func(target *Object) (success bool) + + // A trap for Object.preventExtensions, Reflect.preventExtensions + PreventExtensions func(target *Object) (success bool) + + // A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (string properties) + GetOwnPropertyDescriptor func(target *Object, prop string) (propertyDescriptor PropertyDescriptor) + + // A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (integer properties) + GetOwnPropertyDescriptorIdx func(target *Object, prop int) (propertyDescriptor PropertyDescriptor) + + // A trap for Object.getOwnPropertyDescriptor, Reflect.getOwnPropertyDescriptor (Symbol properties) + GetOwnPropertyDescriptorSym func(target *Object, prop *Symbol) (propertyDescriptor PropertyDescriptor) + + // A trap for Object.defineProperty, Reflect.defineProperty (string properties) + DefineProperty func(target *Object, key string, propertyDescriptor PropertyDescriptor) (success bool) + + // A trap for Object.defineProperty, Reflect.defineProperty (integer properties) + DefinePropertyIdx func(target *Object, key int, propertyDescriptor PropertyDescriptor) (success bool) + + // A trap for Object.defineProperty, Reflect.defineProperty (Symbol properties) + DefinePropertySym func(target *Object, key *Symbol, propertyDescriptor PropertyDescriptor) (success bool) + + // A trap for the in operator, with operator, Reflect.has (string properties) + Has func(target *Object, property string) (available bool) + + // A trap for the in operator, with operator, Reflect.has (integer properties) + HasIdx func(target *Object, property int) (available bool) + + // A trap for the in operator, with operator, Reflect.has (Symbol properties) + HasSym func(target *Object, property *Symbol) (available bool) + + // A trap for getting property values, Reflect.get (string properties) + Get func(target *Object, property string, receiver Value) (value Value) + + // A trap for getting property values, Reflect.get (integer properties) + GetIdx func(target *Object, property int, receiver Value) (value Value) + + // A trap for getting property values, Reflect.get (Symbol properties) + GetSym func(target *Object, property *Symbol, receiver Value) (value Value) + + // A trap for setting property values, Reflect.set (string properties) + Set func(target *Object, property string, value Value, receiver Value) (success bool) + + // A trap for setting property values, Reflect.set (integer properties) + SetIdx func(target *Object, property int, value Value, receiver Value) (success bool) + + // A trap for setting property values, Reflect.set (Symbol properties) + SetSym func(target *Object, property *Symbol, value Value, receiver Value) (success bool) + + // A trap for the delete operator, Reflect.deleteProperty (string properties) + DeleteProperty func(target *Object, property string) (success bool) + + // A trap for the delete operator, Reflect.deleteProperty (integer properties) + DeletePropertyIdx func(target *Object, property int) (success bool) + + // A trap for the delete operator, Reflect.deleteProperty (Symbol properties) + DeletePropertySym func(target *Object, property *Symbol) (success bool) + + // A trap for Object.getOwnPropertyNames, Object.getOwnPropertySymbols, Object.keys, Reflect.ownKeys + OwnKeys func(target *Object) (object *Object) + + // A trap for a function call, Function.prototype.apply, Function.prototype.call, Reflect.apply + Apply func(target *Object, this Value, argumentsList []Value) (value Value) + + // A trap for the new operator, Reflect.construct + Construct func(target *Object, argumentsList []Value, newTarget *Object) (value *Object) +} + +func (r *Runtime) newProxy(args []Value, proto *Object) *Object { + if len(args) >= 2 { + if target, ok := args[0].(*Object); ok { + if proxyHandler, ok := args[1].(*Object); ok { + return r.newProxyObject(target, proxyHandler, proto).val + } + } + } + panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler")) +} + +func (r *Runtime) builtin_newProxy(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("Proxy")) + } + return r.newProxy(args, r.getPrototypeFromCtor(newTarget, r.getProxy(), r.global.ObjectPrototype)) +} + +func (r *Runtime) NewProxy(target *Object, nativeHandler *ProxyTrapConfig) Proxy { + if p, ok := target.self.(*proxyObject); ok { + if p.handler == nil { + panic(r.NewTypeError("Cannot create proxy with a revoked proxy as target")) + } + } + handler := r.newNativeProxyHandler(nativeHandler) + proxy := r._newProxyObject(target, handler, nil) + return Proxy{proxy: proxy} +} + +func (r *Runtime) builtin_proxy_revocable(call FunctionCall) Value { + if len(call.Arguments) >= 2 { + if target, ok := call.Argument(0).(*Object); ok { + if proxyHandler, ok := call.Argument(1).(*Object); ok { + proxy := r.newProxyObject(target, proxyHandler, nil) + revoke := r.newNativeFunc(func(FunctionCall) Value { + proxy.revoke() + return _undefined + }, "", 0) + ret := r.NewObject() + ret.self._putProp("proxy", proxy.val, true, true, true) + ret.self._putProp("revoke", revoke, true, true, true) + return ret + } + } + } + panic(r.NewTypeError("Cannot create proxy with a non-object as target or handler")) +} + +func (r *Runtime) createProxy(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newProxy, nil, "Proxy", 2) + + o._putProp("revocable", r.newNativeFunc(r.builtin_proxy_revocable, "revocable", 2), true, false, true) + return o +} + +func (r *Runtime) getProxy() *Object { + ret := r.global.Proxy + if ret == nil { + ret = &Object{runtime: r} + r.global.Proxy = ret + r.createProxy(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_proxy_test.go b/pkg/xscript/engine/builtin_proxy_test.go new file mode 100644 index 0000000..8f08186 --- /dev/null +++ b/pkg/xscript/engine/builtin_proxy_test.go @@ -0,0 +1,1275 @@ +package engine + +import ( + "strconv" + "testing" +) + +func TestProxy_Object_target_getPrototypeOf(t *testing.T) { + const SCRIPT = ` + var proto = {}; + var obj = Object.create(proto); + var proxy = new Proxy(obj, {}); + var p = Object.getPrototypeOf(proxy); + assert.sameValue(proto, p); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_Object_proxy_getPrototypeOf(t *testing.T) { + const SCRIPT = ` + var proto = {}; + var proto2 = {}; + var obj = Object.create(proto); + var proxy = new Proxy(obj, { + getPrototypeOf: function(target) { + return proto2; + } + }); + var p = Object.getPrototypeOf(proxy); + assert.sameValue(proto2, p); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_Object_native_proxy_getPrototypeOf(t *testing.T) { + const SCRIPT = ` + var p = Object.getPrototypeOf(proxy); + assert.sameValue(proto, p); + ` + + runtime := New() + + prototype := runtime.NewObject() + runtime.Set("proto", prototype) + + target := runtime.NewObject() + proxy := runtime.NewProxy(target, &ProxyTrapConfig{ + GetPrototypeOf: func(target *Object) *Object { + return prototype + }, + }) + runtime.Set("proxy", proxy) + + runtime.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_Object_target_setPrototypeOf(t *testing.T) { + const SCRIPT = ` + var proto = {}; + var obj = {}; + Object.setPrototypeOf(obj, proto); + var proxy = new Proxy(obj, {}); + var p = Object.getPrototypeOf(proxy); + assert.sameValue(proto, p); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_Object_proxy_setPrototypeOf(t *testing.T) { + const SCRIPT = ` + var proto = {}; + var proto2 = {}; + var obj = {}; + Object.setPrototypeOf(obj, proto); + var proxy = new Proxy(obj, { + setPrototypeOf: function(target, prototype) { + return Object.setPrototypeOf(target, proto2); + } + }); + Object.setPrototypeOf(proxy, null); + var p = Object.getPrototypeOf(proxy); + assert.sameValue(proto2, p); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_Object_target_isExtensible(t *testing.T) { + const SCRIPT = ` + var obj = {}; + Object.seal(obj); + var proxy = new Proxy(obj, {}); + Object.isExtensible(proxy); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestProxy_proxy_isExtensible(t *testing.T) { + const SCRIPT = ` + var obj = {}; + Object.seal(obj); + var proxy = new Proxy(obj, { + isExtensible: function(target) { + return false; + } + }); + Object.isExtensible(proxy); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestProxy_native_proxy_isExtensible(t *testing.T) { + const SCRIPT = ` + (function() { + Object.preventExtensions(target); + return Object.isExtensible(proxy); + })(); + ` + + runtime := New() + + target := runtime.NewObject() + runtime.Set("target", target) + + proxy := runtime.NewProxy(target, &ProxyTrapConfig{ + IsExtensible: func(target *Object) (success bool) { + return false + }, + }) + runtime.Set("proxy", proxy) + + val, err := runtime.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if val.ToBoolean() { + t.Fatal() + } +} + +func TestProxy_Object_target_preventExtensions(t *testing.T) { + const SCRIPT = ` + var obj = { + canEvolve: true + }; + var proxy = new Proxy(obj, {}); + Object.preventExtensions(proxy); + proxy.canEvolve + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestProxy_proxy_preventExtensions(t *testing.T) { + const SCRIPT = ` + var obj = { + canEvolve: true + }; + var proxy = new Proxy(obj, { + preventExtensions: function(target) { + target.canEvolve = false; + Object.preventExtensions(obj); + return true; + } + }); + Object.preventExtensions(proxy); + proxy.canEvolve; + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestProxy_native_proxy_preventExtensions(t *testing.T) { + const SCRIPT = ` + (function() { + Object.preventExtensions(proxy); + return proxy.canEvolve; + })(); + ` + + runtime := New() + + target := runtime.NewObject() + target.Set("canEvolve", true) + runtime.Set("target", target) + + proxy := runtime.NewProxy(target, &ProxyTrapConfig{ + PreventExtensions: func(target *Object) (success bool) { + target.Set("canEvolve", false) + _, err := runtime.RunString("Object.preventExtensions(target)") + if err != nil { + panic(err) + } + return true + }, + }) + runtime.Set("proxy", proxy) + + val, err := runtime.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if val.ToBoolean() { + t.Fatal() + } +} + +func TestProxy_Object_target_getOwnPropertyDescriptor(t *testing.T) { + const SCRIPT = ` + var desc = { + configurable: false, + enumerable: false, + value: 42, + writable: false + }; + + var obj = {}; + Object.defineProperty(obj, "foo", desc); + + var proxy = new Proxy(obj, {}); + + var desc2 = Object.getOwnPropertyDescriptor(proxy, "foo"); + desc2.value + ` + + testScript(SCRIPT, valueInt(42), t) +} + +func TestProxy_proxy_getOwnPropertyDescriptor(t *testing.T) { + const SCRIPT = ` + var desc = { + configurable: false, + enumerable: false, + value: 42, + writable: false + }; + var proxy_desc = { + configurable: false, + enumerable: false, + value: 24, + writable: false + }; + + var obj = {}; + Object.defineProperty(obj, "foo", desc); + + var proxy = new Proxy(obj, { + getOwnPropertyDescriptor: function(target, property) { + return proxy_desc; + } + }); + + assert.throws(TypeError, function() { + Object.getOwnPropertyDescriptor(proxy, "foo"); + }); + undefined; + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_native_proxy_getOwnPropertyDescriptor(t *testing.T) { + const SCRIPT = ` + (function() { + var desc = { + configurable: true, + enumerable: false, + value: 42, + writable: false + }; + var proxy_desc = { + configurable: true, + enumerable: false, + value: 24, + writable: false + }; + + var obj = {}; + Object.defineProperty(obj, "foo", desc); + + return function(constructor) { + var proxy = constructor(obj, proxy_desc); + + var desc2 = Object.getOwnPropertyDescriptor(proxy, "foo"); + return desc2.value + } + })(); + ` + + runtime := New() + + constructor := func(call FunctionCall) Value { + target := call.Argument(0).(*Object) + proxyDesc := call.Argument(1).(*Object) + + return runtime.NewProxy(target, &ProxyTrapConfig{ + GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { + return runtime.toPropertyDescriptor(proxyDesc) + }, + }).proxy.val + } + + val, err := runtime.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if c, ok := val.(*Object).self.assertCallable(); ok { + val := c(FunctionCall{ + This: val, + Arguments: []Value{runtime.ToValue(constructor)}, + }) + if i := val.ToInteger(); i != 24 { + t.Fatalf("val: %d", i) + } + } else { + t.Fatal("not a function") + } +} + +func TestProxy_native_proxy_getOwnPropertyDescriptorIdx(t *testing.T) { + vm := New() + a := vm.NewArray() + proxy1 := vm.NewProxy(a, &ProxyTrapConfig{ + GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { + panic(vm.NewTypeError("GetOwnPropertyDescriptor was called for %q", prop)) + }, + GetOwnPropertyDescriptorIdx: func(target *Object, prop int) PropertyDescriptor { + if prop >= -1 && prop <= 1 { + return PropertyDescriptor{ + Value: vm.ToValue(prop), + Configurable: FLAG_TRUE, + } + } + return PropertyDescriptor{} + }, + }) + + proxy2 := vm.NewProxy(a, &ProxyTrapConfig{ + GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { + switch prop { + case "-1", "0", "1": + return PropertyDescriptor{ + Value: vm.ToValue(prop), + Configurable: FLAG_TRUE, + } + } + return PropertyDescriptor{} + }, + }) + + proxy3 := vm.NewProxy(a, &ProxyTrapConfig{ + GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { + return PropertyDescriptor{ + Value: vm.ToValue(prop), + Configurable: FLAG_TRUE, + } + }, + GetOwnPropertyDescriptorIdx: func(target *Object, prop int) PropertyDescriptor { + panic(vm.NewTypeError("GetOwnPropertyDescriptorIdx was called for %d", prop)) + }, + }) + + vm.Set("proxy1", proxy1) + vm.Set("proxy2", proxy2) + vm.Set("proxy3", proxy3) + vm.testScriptWithTestLibX(` + var desc; + for (var i = -1; i <= 1; i++) { + desc = Object.getOwnPropertyDescriptor(proxy1, i); + assert(deepEqual(desc, {value: i, writable: false, enumerable: false, configurable: true}), "1. int "+i); + + desc = Object.getOwnPropertyDescriptor(proxy1, ""+i); + assert(deepEqual(desc, {value: i, writable: false, enumerable: false, configurable: true}), "1. str "+i); + + desc = Object.getOwnPropertyDescriptor(proxy2, i); + assert(deepEqual(desc, {value: ""+i, writable: false, enumerable: false, configurable: true}), "2. int "+i); + + desc = Object.getOwnPropertyDescriptor(proxy2, ""+i); + assert(deepEqual(desc, {value: ""+i, writable: false, enumerable: false, configurable: true}), "2. str "+i); + } + + for (const prop of ["00", " 0", "-0", "01"]) { + desc = Object.getOwnPropertyDescriptor(proxy3, prop); + assert(deepEqual(desc, {value: prop, writable: false, enumerable: false, configurable: true}), "3. "+prop); + } + `, _undefined, t) +} + +func TestProxy_native_proxy_getOwnPropertyDescriptorSym(t *testing.T) { + vm := New() + o := vm.NewObject() + sym := NewSymbol("42") + vm.Set("sym", sym) + proxy := vm.NewProxy(o, &ProxyTrapConfig{ + GetOwnPropertyDescriptorSym: func(target *Object, s *Symbol) PropertyDescriptor { + if target != o { + panic(vm.NewTypeError("Invalid target")) + } + if s == sym { + return PropertyDescriptor{ + Value: vm.ToValue("passed"), + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + } + } + return PropertyDescriptor{} + }, + }) + + vm.Set("proxy", proxy) + vm.testScriptWithTestLibX(` + var desc = Object.getOwnPropertyDescriptor(proxy, sym); + assert(deepEqual(desc, {value: "passed", writable: true, enumerable: false, configurable: true})); + assert.sameValue(Object.getOwnPropertyDescriptor(proxy, Symbol.iterator), undefined); + `, _undefined, t) +} + +func TestProxy_native_proxy_getOwnPropertyDescriptor_non_existing(t *testing.T) { + vm := New() + proxy := vm.NewProxy(vm.NewObject(), &ProxyTrapConfig{ + GetOwnPropertyDescriptor: func(target *Object, prop string) (propertyDescriptor PropertyDescriptor) { + return // empty PropertyDescriptor + }, + }) + vm.Set("proxy", proxy) + res, err := vm.RunString(`Object.getOwnPropertyDescriptor(proxy, "foo") === undefined`) + if err != nil { + t.Fatal(err) + } + if res != valueTrue { + t.Fatal(res) + } +} + +func TestProxy_Object_target_defineProperty(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, {}); + Object.defineProperty(proxy, "foo", { + value: "test123" + }); + proxy.foo; + ` + + testScript(SCRIPT, asciiString("test123"), t) +} + +func TestProxy_proxy_defineProperty(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, { + defineProperty: function(target, prop, descriptor) { + target.foo = "321tset"; + return true; + } + }); + Object.defineProperty(proxy, "foo", { + value: "test123" + }); + proxy.foo; + ` + + testScript(SCRIPT, asciiString("321tset"), t) +} + +func TestProxy_native_proxy_defineProperty(t *testing.T) { + const SCRIPT = ` + Object.defineProperty(proxy, "foo", { + value: "teststr" + }); + Object.defineProperty(proxy, "0", { + value: "testidx" + }); + Object.defineProperty(proxy, Symbol.toStringTag, { + value: "testsym" + }); + assert.sameValue(proxy.foo, "teststr-passed-str"); + assert.sameValue(proxy[0], "testidx-passed-idx"); + assert.sameValue(proxy[Symbol.toStringTag], "testsym-passed-sym"); + ` + + runtime := New() + + target := runtime.NewObject() + + proxy := runtime.NewProxy(target, &ProxyTrapConfig{ + DefineProperty: func(target *Object, key string, propertyDescriptor PropertyDescriptor) (success bool) { + target.Set(key, propertyDescriptor.Value.String()+"-passed-str") + return true + }, + DefinePropertyIdx: func(target *Object, key int, propertyDescriptor PropertyDescriptor) (success bool) { + target.Set(strconv.Itoa(key), propertyDescriptor.Value.String()+"-passed-idx") + return true + }, + DefinePropertySym: func(target *Object, key *Symbol, propertyDescriptor PropertyDescriptor) (success bool) { + target.SetSymbol(key, propertyDescriptor.Value.String()+"-passed-sym") + return true + }, + }) + runtime.Set("proxy", proxy) + + runtime.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_target_has_in(t *testing.T) { + const SCRIPT = ` + var obj = { + secret: true + }; + var proxy = new Proxy(obj, {}); + + "secret" in proxy + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestProxy_proxy_has_in(t *testing.T) { + const SCRIPT = ` + var obj = { + secret: true + }; + var proxy = new Proxy(obj, { + has: function(target, key) { + return key !== "secret"; + } + }); + + "secret" in proxy + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestProxy_target_has_with(t *testing.T) { + const SCRIPT = ` + var obj = { + secret: true + }; + var proxy = new Proxy(obj, {}); + + with(proxy) { + (secret); + } + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestProxy_proxy_has_with(t *testing.T) { + const SCRIPT = ` + var obj = { + secret: true + }; + var proxy = new Proxy(obj, { + has: function(target, key) { + return key !== "secret"; + } + }); + + var thrown = false; + try { + with(proxy) { + (secret); + } + } catch (e) { + if (e instanceof ReferenceError) { + thrown = true; + } else { + throw e; + } + } + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestProxy_target_get(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, {}); + Object.defineProperty(proxy, "foo", { + value: "test123" + }); + proxy.foo; + ` + + testScript(SCRIPT, asciiString("test123"), t) +} + +func TestProxy_proxy_get(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, { + get: function(target, prop, receiver) { + return "321tset" + } + }); + Object.defineProperty(proxy, "foo", { + value: "test123", + configurable: true, + }); + proxy.foo; + ` + + testScript(SCRIPT, asciiString("321tset"), t) +} + +func TestProxy_proxy_get_json_stringify(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var propValue = "321tset"; + var _handler, _target, _prop, _receiver; + var proxy = new Proxy(obj, { + ownKeys: function() { + return ["foo"]; + }, + getOwnPropertyDescriptor: function(target, prop) { + if (prop === "foo") { + return { + value: propValue, + enumerable: true, + configurable: true + } + } + }, + get: function(target, prop, receiver) { + if (prop === "foo") { + _prop = prop; + _receiver = receiver; + return propValue; + } + return obj[prop]; + } + }); + var res = JSON.stringify(proxy); + assert.sameValue(res, '{"foo":"321tset"}'); + assert.sameValue(_prop, "foo"); + assert.sameValue(_receiver, proxy); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxy_native_proxy_get(t *testing.T) { + vm := New() + propValueStr := vm.ToValue("321tset") + propValueIdx := vm.ToValue("idx") + propValueSym := vm.ToValue("sym") + sym := NewSymbol("test") + obj := vm.NewObject() + proxy := vm.NewProxy(obj, &ProxyTrapConfig{ + OwnKeys: func(*Object) *Object { + return vm.NewArray("0", "foo") + }, + GetOwnPropertyDescriptor: func(target *Object, prop string) (propertyDescriptor PropertyDescriptor) { + if prop == "foo" { + return PropertyDescriptor{ + Value: propValueStr, + Enumerable: FLAG_TRUE, + Configurable: FLAG_TRUE, + } + } + if prop == "0" { + panic(vm.NewTypeError("GetOwnPropertyDescriptor(0) was called")) + } + return + }, + GetOwnPropertyDescriptorIdx: func(target *Object, prop int) (propertyDescriptor PropertyDescriptor) { + if prop == 0 { + return PropertyDescriptor{ + Value: propValueIdx, + Enumerable: FLAG_TRUE, + Configurable: FLAG_TRUE, + } + } + return + }, + Get: func(target *Object, property string, receiver Value) (value Value) { + if property == "foo" { + return propValueStr + } + if property == "0" { + panic(vm.NewTypeError("Get(0) was called")) + } + return obj.Get(property) + }, + GetIdx: func(target *Object, property int, receiver Value) (value Value) { + if property == 0 { + return propValueIdx + } + return obj.Get(strconv.Itoa(property)) + }, + GetSym: func(target *Object, property *Symbol, receiver Value) (value Value) { + if property == sym { + return propValueSym + } + return obj.GetSymbol(property) + }, + }) + vm.Set("proxy", proxy) + res, err := vm.RunString(`JSON.stringify(proxy)`) + if err != nil { + t.Fatal(err) + } + if !res.SameAs(asciiString(`{"0":"idx","foo":"321tset"}`)) { + t.Fatalf("res: %v", res) + } + res, err = vm.RunString(`proxy[Symbol.toPrimitive]`) + if err != nil { + t.Fatal(err) + } + if !IsUndefined(res) { + t.Fatalf("res: %v", res) + } + + res, err = vm.RunString(`proxy.hasOwnProperty(Symbol.toPrimitive)`) + if err != nil { + t.Fatal(err) + } + if !res.SameAs(valueFalse) { + t.Fatalf("res: %v", res) + } + + if val := vm.ToValue(proxy).(*Object).GetSymbol(sym); val == nil || !val.SameAs(propValueSym) { + t.Fatalf("Get(symbol): %v", val) + } + + res, err = vm.RunString(`proxy.toString()`) + if err != nil { + t.Fatal(err) + } + if !res.SameAs(asciiString(`[object Object]`)) { + t.Fatalf("res: %v", res) + } +} + +func TestProxy_native_proxy_set(t *testing.T) { + vm := New() + propValueStr := vm.ToValue("321tset") + propValueIdx := vm.ToValue("idx") + propValueSym := vm.ToValue("sym") + sym := NewSymbol("test") + obj := vm.NewObject() + proxy := vm.NewProxy(obj, &ProxyTrapConfig{ + Set: func(target *Object, property string, value Value, receiver Value) (success bool) { + if property == "str" { + obj.Set(property, propValueStr) + return true + } + panic(vm.NewTypeError("Setter for unexpected property: %q", property)) + }, + SetIdx: func(target *Object, property int, value Value, receiver Value) (success bool) { + if property == 0 { + obj.Set(strconv.Itoa(property), propValueIdx) + return true + } + panic(vm.NewTypeError("Setter for unexpected idx property: %d", property)) + }, + SetSym: func(target *Object, property *Symbol, value Value, receiver Value) (success bool) { + if property == sym { + obj.SetSymbol(property, propValueSym) + return true + } + panic(vm.NewTypeError("Setter for unexpected sym property: %q", property.String())) + }, + }) + proxyObj := vm.ToValue(proxy).ToObject(vm) + err := proxyObj.Set("str", "") + if err != nil { + t.Fatal(err) + } + err = proxyObj.Set("0", "") + if err != nil { + t.Fatal(err) + } + err = proxyObj.SetSymbol(sym, "") + if err != nil { + t.Fatal(err) + } + if v := obj.Get("str"); !propValueStr.SameAs(v) { + t.Fatal(v) + } + if v := obj.Get("0"); !propValueIdx.SameAs(v) { + t.Fatal(v) + } + if v := obj.GetSymbol(sym); !propValueSym.SameAs(v) { + t.Fatal(v) + } +} + +func TestProxy_target_set_prop(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, {}); + proxy.foo = "test123"; + proxy.foo; + ` + + testScript(SCRIPT, asciiString("test123"), t) +} + +func TestProxy_proxy_set_prop(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, { + set: function(target, prop, receiver) { + target.foo = "321tset"; + return true; + } + }); + proxy.foo = "test123"; + proxy.foo; + ` + + testScript(SCRIPT, asciiString("321tset"), t) +} +func TestProxy_target_set_associative(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, {}); + proxy["foo"] = "test123"; + proxy.foo; + ` + + testScript(SCRIPT, asciiString("test123"), t) +} + +func TestProxy_proxy_set_associative(t *testing.T) { + const SCRIPT = ` + var obj = {}; + var proxy = new Proxy(obj, { + set: function(target, property, value, receiver) { + target["foo"] = "321tset"; + return true; + } + }); + proxy["foo"] = "test123"; + proxy.foo; + ` + + testScript(SCRIPT, asciiString("321tset"), t) +} + +func TestProxy_target_delete(t *testing.T) { + const SCRIPT = ` + var obj = { + foo: "test" + }; + var proxy = new Proxy(obj, {}); + delete proxy.foo; + + proxy.foo; + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestProxy_proxy_delete(t *testing.T) { + const SCRIPT = ` + var obj = { + foo: "test" + }; + var proxy = new Proxy(obj, { + deleteProperty: function(target, prop) { + return true; + } + }); + delete proxy.foo; + + proxy.foo; + ` + + testScript(SCRIPT, asciiString("test"), t) +} + +func TestProxy_native_delete(t *testing.T) { + vm := New() + sym := NewSymbol("test") + obj := vm.NewObject() + var strCalled, idxCalled, symCalled, strNegCalled, idxNegCalled, symNegCalled bool + proxy := vm.NewProxy(obj, &ProxyTrapConfig{ + DeleteProperty: func(target *Object, property string) (success bool) { + if property == "str" { + strCalled = true + return true + } + if property == "strNeg" { + strNegCalled = true + return false + } + panic(vm.NewTypeError("DeleteProperty for unexpected property: %q", property)) + }, + DeletePropertyIdx: func(target *Object, property int) (success bool) { + if property == 0 { + idxCalled = true + return true + } + if property == 1 { + idxNegCalled = true + return false + } + panic(vm.NewTypeError("DeletePropertyIdx for unexpected idx property: %d", property)) + }, + DeletePropertySym: func(target *Object, property *Symbol) (success bool) { + if property == sym { + symCalled = true + return true + } + if property == SymIterator { + symNegCalled = true + return false + } + panic(vm.NewTypeError("DeletePropertySym for unexpected sym property: %q", property.String())) + }, + }) + proxyObj := vm.ToValue(proxy).ToObject(vm) + err := proxyObj.Delete("str") + if err != nil { + t.Fatal(err) + } + err = proxyObj.Delete("0") + if err != nil { + t.Fatal(err) + } + err = proxyObj.DeleteSymbol(sym) + if err != nil { + t.Fatal(err) + } + if !strCalled { + t.Fatal("str") + } + if !idxCalled { + t.Fatal("idx") + } + if !symCalled { + t.Fatal("sym") + } + vm.Set("proxy", proxy) + _, err = vm.RunString(` + if (delete proxy.strNeg) { + throw new Error("strNeg"); + } + if (delete proxy[1]) { + throw new Error("idxNeg"); + } + if (delete proxy[Symbol.iterator]) { + throw new Error("symNeg"); + } + `) + if err != nil { + t.Fatal(err) + } + if !strNegCalled { + t.Fatal("strNeg") + } + if !idxNegCalled { + t.Fatal("idxNeg") + } + if !symNegCalled { + t.Fatal("symNeg") + } +} + +func TestProxy_target_keys(t *testing.T) { + const SCRIPT = ` + var obj = { + foo: "test" + }; + var proxy = new Proxy(obj, {}); + + var keys = Object.keys(proxy); + if (keys.length != 1) { + throw new Error("assertion error"); + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestProxy_proxy_keys(t *testing.T) { + const SCRIPT = ` + var obj = { + foo: "test" + }; + var proxy = new Proxy(obj, { + ownKeys: function(target) { + return ["foo", "bar"]; + } + }); + + var keys = Object.keys(proxy); + if (keys.length !== 1) { + throw new Error("length is "+keys.length); + } + if (keys[0] !== "foo") { + throw new Error("keys[0] is "+keys[0]); + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestProxy_target_call(t *testing.T) { + const SCRIPT = ` + var obj = function() { + return "test" + } + + var proxy = new Proxy(obj, {}); + + proxy(); + ` + + testScript(SCRIPT, asciiString("test"), t) +} + +func TestProxy_proxy_call(t *testing.T) { + const SCRIPT = ` + var obj = function() { + return "test" + } + + var proxy = new Proxy(obj, { + apply: function(target, thisArg, args) { + return "tset" + } + }); + + proxy(); + ` + + testScript(SCRIPT, asciiString("tset"), t) +} + +func TestProxy_target_func_apply(t *testing.T) { + const SCRIPT = ` + var obj = function() { + return "test" + } + + var proxy = new Proxy(obj, {}); + + proxy.apply(); + ` + + testScript(SCRIPT, asciiString("test"), t) +} + +func TestProxy_proxy_func_apply(t *testing.T) { + const SCRIPT = ` + var obj = function() { + return "test" + } + + var proxy = new Proxy(obj, { + apply: function(target, thisArg, args) { + return "tset" + } + }); + + proxy.apply(); + ` + + testScript(SCRIPT, asciiString("tset"), t) +} + +func TestProxy_target_func_call(t *testing.T) { + const SCRIPT = ` + var obj = function() { + return "test" + } + + var proxy = new Proxy(obj, {}); + + proxy.call(); + ` + + testScript(SCRIPT, asciiString("test"), t) +} + +func TestProxy_proxy_func_call(t *testing.T) { + const SCRIPT = ` + var obj = function() { + return "test" + } + + var proxy = new Proxy(obj, { + apply: function(target, thisArg, args) { + return "tset" + } + }); + + proxy.call(); + ` + + testScript(SCRIPT, asciiString("tset"), t) +} + +func TestProxy_target_new(t *testing.T) { + const SCRIPT = ` + var obj = function(word) { + this.foo = function() { + return word; + } + } + + var proxy = new Proxy(obj, {}); + + var instance = new proxy("test"); + instance.foo(); + ` + + testScript(SCRIPT, asciiString("test"), t) +} + +func TestProxy_proxy_new(t *testing.T) { + const SCRIPT = ` + var obj = function(word) { + this.foo = function() { + return word; + } + } + + var proxy = new Proxy(obj, { + construct: function(target, args, newTarget) { + var word = args[0]; + return { + foo: function() { + return "caught-" + word + } + } + } + }); + + var instance = new proxy("test"); + instance.foo(); + ` + + testScript(SCRIPT, asciiString("caught-test"), t) +} + +func TestProxy_Object_native_proxy_ownKeys(t *testing.T) { + headers := map[string][]string{ + "k0": {}, + } + vm := New() + proxy := vm.NewProxy(vm.NewObject(), &ProxyTrapConfig{ + OwnKeys: func(target *Object) (object *Object) { + keys := make([]any, 0, len(headers)) + for k := range headers { + keys = append(keys, k) + } + return vm.ToValue(keys).ToObject(vm) + }, + GetOwnPropertyDescriptor: func(target *Object, prop string) PropertyDescriptor { + v, exists := headers[prop] + if exists { + return PropertyDescriptor{ + Value: vm.ToValue(v), + Enumerable: FLAG_TRUE, + Configurable: FLAG_TRUE, + } + } + return PropertyDescriptor{} + }, + }) + vm.Set("headers", proxy) + v, err := vm.RunString(` + var keys = Object.keys(headers); + keys.length === 1 && keys[0] === "k0"; + `) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatal("not true", v) + } +} + +func TestProxy_proxy_forIn(t *testing.T) { + const SCRIPT = ` + var proto = { + a: 2, + protoProp: 1 + } + Object.defineProperty(proto, "protoNonEnum", { + value: 2, + writable: true, + configurable: true + }); + var target = Object.create(proto); + var proxy = new Proxy(target, { + ownKeys: function() { + return ["a", "b"]; + }, + getOwnPropertyDescriptor: function(target, p) { + switch (p) { + case "a": + case "b": + return { + value: 42, + enumerable: true, + configurable: true + } + } + }, + }); + + var forInResult = []; + for (var key in proxy) { + if (forInResult.indexOf(key) !== -1) { + throw new Error("Duplicate property "+key); + } + forInResult.push(key); + } + forInResult.length === 3 && forInResult[0] === "a" && forInResult[1] === "b" && forInResult[2] === "protoProp"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestProxyExport(t *testing.T) { + vm := New() + v, err := vm.RunString(` + new Proxy({}, {}); + `) + if err != nil { + t.Fatal(err) + } + v1 := v.Export() + if _, ok := v1.(Proxy); !ok { + t.Fatalf("Export returned unexpected type: %T", v1) + } +} + +func TestProxy_proxy_createTargetNotCallable(t *testing.T) { + // from https://github.com/tc39/test262/blob/main/test/built-ins/Proxy/create-target-is-not-callable.js + const SCRIPT = ` + var p = new Proxy({}, {}); + + assert.throws(TypeError, function() { + p(); + }); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestProxyEnumerableSymbols(t *testing.T) { + const SCRIPT = ` + var getOwnKeys = []; + var ownKeysResult = [Symbol(), "foo", "0"]; + var proxy = new Proxy({}, { + getOwnPropertyDescriptor: function(_target, key) { + getOwnKeys.push(key); + }, + ownKeys: function() { + return ownKeysResult; + }, + }); + + let {...$} = proxy; + compareArray(getOwnKeys, ownKeysResult); + ` + + testScriptWithTestLib(SCRIPT, valueTrue, t) +} diff --git a/pkg/xscript/engine/builtin_reflect.go b/pkg/xscript/engine/builtin_reflect.go new file mode 100644 index 0000000..218400c --- /dev/null +++ b/pkg/xscript/engine/builtin_reflect.go @@ -0,0 +1,140 @@ +package engine + +func (r *Runtime) builtin_reflect_apply(call FunctionCall) Value { + return r.toCallable(call.Argument(0))(FunctionCall{ + This: call.Argument(1), + Arguments: r.createListFromArrayLike(call.Argument(2))}) +} + +func (r *Runtime) toConstructor(v Value) func(args []Value, newTarget *Object) *Object { + if ctor := r.toObject(v).self.assertConstructor(); ctor != nil { + return ctor + } + panic(r.NewTypeError("Value is not a constructor")) +} + +func (r *Runtime) builtin_reflect_construct(call FunctionCall) Value { + target := call.Argument(0) + ctor := r.toConstructor(target) + var newTarget Value + if len(call.Arguments) > 2 { + newTarget = call.Argument(2) + r.toConstructor(newTarget) + } else { + newTarget = target + } + return ctor(r.createListFromArrayLike(call.Argument(1)), r.toObject(newTarget)) +} + +func (r *Runtime) builtin_reflect_defineProperty(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + key := toPropertyKey(call.Argument(1)) + desc := r.toPropertyDescriptor(call.Argument(2)) + + return r.toBoolean(target.defineOwnProperty(key, desc, false)) +} + +func (r *Runtime) builtin_reflect_deleteProperty(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + key := toPropertyKey(call.Argument(1)) + + return r.toBoolean(target.delete(key, false)) +} + +func (r *Runtime) builtin_reflect_get(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + key := toPropertyKey(call.Argument(1)) + var receiver Value + if len(call.Arguments) > 2 { + receiver = call.Arguments[2] + } + return target.get(key, receiver) +} + +func (r *Runtime) builtin_reflect_getOwnPropertyDescriptor(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + key := toPropertyKey(call.Argument(1)) + return r.valuePropToDescriptorObject(target.getOwnProp(key)) +} + +func (r *Runtime) builtin_reflect_getPrototypeOf(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + if proto := target.self.proto(); proto != nil { + return proto + } + + return _null +} + +func (r *Runtime) builtin_reflect_has(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + key := toPropertyKey(call.Argument(1)) + return r.toBoolean(target.hasProperty(key)) +} + +func (r *Runtime) builtin_reflect_isExtensible(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + return r.toBoolean(target.self.isExtensible()) +} + +func (r *Runtime) builtin_reflect_ownKeys(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + return r.newArrayValues(target.self.keys(true, nil)) +} + +func (r *Runtime) builtin_reflect_preventExtensions(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + return r.toBoolean(target.self.preventExtensions(false)) +} + +func (r *Runtime) builtin_reflect_set(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + var receiver Value + if len(call.Arguments) >= 4 { + receiver = call.Argument(3) + } else { + receiver = target + } + return r.toBoolean(target.set(call.Argument(1), call.Argument(2), receiver, false)) +} + +func (r *Runtime) builtin_reflect_setPrototypeOf(call FunctionCall) Value { + target := r.toObject(call.Argument(0)) + var proto *Object + if arg := call.Argument(1); arg != _null { + proto = r.toObject(arg) + } + return r.toBoolean(target.self.setProto(proto, false)) +} + +func (r *Runtime) createReflect(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + + o._putProp("apply", r.newNativeFunc(r.builtin_reflect_apply, "apply", 3), true, false, true) + o._putProp("construct", r.newNativeFunc(r.builtin_reflect_construct, "construct", 2), true, false, true) + o._putProp("defineProperty", r.newNativeFunc(r.builtin_reflect_defineProperty, "defineProperty", 3), true, false, true) + o._putProp("deleteProperty", r.newNativeFunc(r.builtin_reflect_deleteProperty, "deleteProperty", 2), true, false, true) + o._putProp("get", r.newNativeFunc(r.builtin_reflect_get, "get", 2), true, false, true) + o._putProp("getOwnPropertyDescriptor", r.newNativeFunc(r.builtin_reflect_getOwnPropertyDescriptor, "getOwnPropertyDescriptor", 2), true, false, true) + o._putProp("getPrototypeOf", r.newNativeFunc(r.builtin_reflect_getPrototypeOf, "getPrototypeOf", 1), true, false, true) + o._putProp("has", r.newNativeFunc(r.builtin_reflect_has, "has", 2), true, false, true) + o._putProp("isExtensible", r.newNativeFunc(r.builtin_reflect_isExtensible, "isExtensible", 1), true, false, true) + o._putProp("ownKeys", r.newNativeFunc(r.builtin_reflect_ownKeys, "ownKeys", 1), true, false, true) + o._putProp("preventExtensions", r.newNativeFunc(r.builtin_reflect_preventExtensions, "preventExtensions", 1), true, false, true) + o._putProp("set", r.newNativeFunc(r.builtin_reflect_set, "set", 3), true, false, true) + o._putProp("setPrototypeOf", r.newNativeFunc(r.builtin_reflect_setPrototypeOf, "setPrototypeOf", 2), true, false, true) + + o._putSym(SymToStringTag, valueProp(asciiString("Reflect"), false, false, true)) + + return o +} + +func (r *Runtime) getReflect() *Object { + ret := r.global.Reflect + if ret == nil { + ret = &Object{runtime: r} + r.global.Reflect = ret + ret.self = r.createReflect(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_regexp.go b/pkg/xscript/engine/builtin_regexp.go new file mode 100644 index 0000000..3220f25 --- /dev/null +++ b/pkg/xscript/engine/builtin_regexp.go @@ -0,0 +1,1305 @@ +package engine + +import ( + "fmt" + "regexp" + "strings" + "unicode/utf16" + "unicode/utf8" + + "pandax/pkg/xscript/engine/parser" +) + +func (r *Runtime) newRegexpObject(proto *Object) *regexpObject { + v := &Object{runtime: r} + + o := ®expObject{} + o.class = classRegExp + o.val = v + o.extensible = true + v.self = o + o.prototype = proto + o.init() + return o +} + +func (r *Runtime) newRegExpp(pattern *regexpPattern, patternStr String, proto *Object) *regexpObject { + o := r.newRegexpObject(proto) + + o.pattern = pattern + o.source = patternStr + + return o +} + +func decodeHex(s string) (int, bool) { + var hex int + for i := 0; i < len(s); i++ { + var n byte + chr := s[i] + switch { + case '0' <= chr && chr <= '9': + n = chr - '0' + case 'a' <= chr && chr <= 'f': + n = chr - 'a' + 10 + case 'A' <= chr && chr <= 'F': + n = chr - 'A' + 10 + default: + return 0, false + } + hex = hex*16 + int(n) + } + return hex, true +} + +func writeHex4(b *strings.Builder, i int) { + b.WriteByte(hex[i>>12]) + b.WriteByte(hex[(i>>8)&0xF]) + b.WriteByte(hex[(i>>4)&0xF]) + b.WriteByte(hex[i&0xF]) +} + +// Convert any valid surrogate pairs in the form of \uXXXX\uXXXX to unicode characters +func convertRegexpToUnicode(patternStr string) string { + var sb strings.Builder + pos := 0 + for i := 0; i < len(patternStr)-11; { + r, size := utf8.DecodeRuneInString(patternStr[i:]) + if r == '\\' { + i++ + if patternStr[i] == 'u' && patternStr[i+5] == '\\' && patternStr[i+6] == 'u' { + if first, ok := decodeHex(patternStr[i+1 : i+5]); ok { + if isUTF16FirstSurrogate(uint16(first)) { + if second, ok := decodeHex(patternStr[i+7 : i+11]); ok { + if isUTF16SecondSurrogate(uint16(second)) { + r = utf16.DecodeRune(rune(first), rune(second)) + sb.WriteString(patternStr[pos : i-1]) + sb.WriteRune(r) + i += 11 + pos = i + continue + } + } + } + } + } + i++ + } else { + i += size + } + } + if pos > 0 { + sb.WriteString(patternStr[pos:]) + return sb.String() + } + return patternStr +} + +// Convert any extended unicode characters to UTF-16 in the form of \uXXXX\uXXXX +func convertRegexpToUtf16(patternStr string) string { + var sb strings.Builder + pos := 0 + var prevRune rune + for i := 0; i < len(patternStr); { + r, size := utf8.DecodeRuneInString(patternStr[i:]) + if r > 0xFFFF { + sb.WriteString(patternStr[pos:i]) + if prevRune == '\\' { + sb.WriteRune('\\') + } + first, second := utf16.EncodeRune(r) + sb.WriteString(`\u`) + writeHex4(&sb, int(first)) + sb.WriteString(`\u`) + writeHex4(&sb, int(second)) + pos = i + size + } + i += size + prevRune = r + } + if pos > 0 { + sb.WriteString(patternStr[pos:]) + return sb.String() + } + return patternStr +} + +// convert any broken UTF-16 surrogate pairs to \uXXXX +func escapeInvalidUtf16(s String) string { + if imported, ok := s.(*importedString); ok { + return imported.s + } + if ascii, ok := s.(asciiString); ok { + return ascii.String() + } + var sb strings.Builder + rd := &lenientUtf16Decoder{utf16Reader: s.utf16Reader()} + pos := 0 + utf8Size := 0 + var utf8Buf [utf8.UTFMax]byte + for { + c, size, err := rd.ReadRune() + if err != nil { + break + } + if utf16.IsSurrogate(c) { + if sb.Len() == 0 { + sb.Grow(utf8Size + 7) + hrd := s.Reader() + var c rune + for p := 0; p < pos; { + var size int + var err error + c, size, err = hrd.ReadRune() + if err != nil { + // will not happen + panic(fmt.Errorf("error while reading string head %q, pos: %d: %w", s.String(), pos, err)) + } + sb.WriteRune(c) + p += size + } + if c == '\\' { + sb.WriteRune(c) + } + } + sb.WriteString(`\u`) + writeHex4(&sb, int(c)) + } else { + if sb.Len() > 0 { + sb.WriteRune(c) + } else { + utf8Size += utf8.EncodeRune(utf8Buf[:], c) + pos += size + } + } + } + if sb.Len() > 0 { + return sb.String() + } + return s.String() +} + +func compileRegexpFromValueString(patternStr String, flags string) (*regexpPattern, error) { + return compileRegexp(escapeInvalidUtf16(patternStr), flags) +} + +func compileRegexp(patternStr, flags string) (p *regexpPattern, err error) { + var global, ignoreCase, multiline, sticky, unicode bool + var wrapper *regexpWrapper + var wrapper2 *regexp2Wrapper + + if flags != "" { + invalidFlags := func() { + err = fmt.Errorf("Invalid flags supplied to RegExp constructor '%s'", flags) + } + for _, chr := range flags { + switch chr { + case 'g': + if global { + invalidFlags() + return + } + global = true + case 'm': + if multiline { + invalidFlags() + return + } + multiline = true + case 'i': + if ignoreCase { + invalidFlags() + return + } + ignoreCase = true + case 'y': + if sticky { + invalidFlags() + return + } + sticky = true + case 'u': + if unicode { + invalidFlags() + } + unicode = true + default: + invalidFlags() + return + } + } + } + + if unicode { + patternStr = convertRegexpToUnicode(patternStr) + } else { + patternStr = convertRegexpToUtf16(patternStr) + } + + re2Str, err1 := parser.TransformRegExp(patternStr) + if err1 == nil { + re2flags := "" + if multiline { + re2flags += "m" + } + if ignoreCase { + re2flags += "i" + } + if len(re2flags) > 0 { + re2Str = fmt.Sprintf("(?%s:%s)", re2flags, re2Str) + } + + pattern, err1 := regexp.Compile(re2Str) + if err1 != nil { + err = fmt.Errorf("Invalid regular expression (re2): %s (%v)", re2Str, err1) + return + } + wrapper = (*regexpWrapper)(pattern) + } else { + if _, incompat := err1.(parser.RegexpErrorIncompatible); !incompat { + err = err1 + return + } + wrapper2, err = compileRegexp2(patternStr, multiline, ignoreCase) + if err != nil { + err = fmt.Errorf("Invalid regular expression (regexp2): %s (%v)", patternStr, err) + return + } + } + + p = ®expPattern{ + src: patternStr, + regexpWrapper: wrapper, + regexp2Wrapper: wrapper2, + global: global, + ignoreCase: ignoreCase, + multiline: multiline, + sticky: sticky, + unicode: unicode, + } + return +} + +func (r *Runtime) _newRegExp(patternStr String, flags string, proto *Object) *regexpObject { + pattern, err := compileRegexpFromValueString(patternStr, flags) + if err != nil { + panic(r.newSyntaxError(err.Error(), -1)) + } + return r.newRegExpp(pattern, patternStr, proto) +} + +func (r *Runtime) builtin_newRegExp(args []Value, proto *Object) *Object { + var patternVal, flagsVal Value + if len(args) > 0 { + patternVal = args[0] + } + if len(args) > 1 { + flagsVal = args[1] + } + return r.newRegExp(patternVal, flagsVal, proto).val +} + +func (r *Runtime) newRegExp(patternVal, flagsVal Value, proto *Object) *regexpObject { + var pattern String + var flags string + if isRegexp(patternVal) { // this may have side effects so need to call it anyway + if obj, ok := patternVal.(*Object); ok { + if rx, ok := obj.self.(*regexpObject); ok { + if flagsVal == nil || flagsVal == _undefined { + return rx.clone() + } else { + return r._newRegExp(rx.source, flagsVal.toString().String(), proto) + } + } else { + pattern = nilSafe(obj.self.getStr("source", nil)).toString() + if flagsVal == nil || flagsVal == _undefined { + flags = nilSafe(obj.self.getStr("flags", nil)).toString().String() + } else { + flags = flagsVal.toString().String() + } + goto exit + } + } + } + + if patternVal != nil && patternVal != _undefined { + pattern = patternVal.toString() + } + if flagsVal != nil && flagsVal != _undefined { + flags = flagsVal.toString().String() + } + + if pattern == nil { + pattern = stringEmpty + } +exit: + return r._newRegExp(pattern, flags, proto) +} + +func (r *Runtime) builtin_RegExp(call FunctionCall) Value { + pattern := call.Argument(0) + patternIsRegExp := isRegexp(pattern) + flags := call.Argument(1) + if patternIsRegExp && flags == _undefined { + if obj, ok := call.Argument(0).(*Object); ok { + patternConstructor := obj.self.getStr("constructor", nil) + if patternConstructor == r.global.RegExp { + return pattern + } + } + } + return r.newRegExp(pattern, flags, r.getRegExpPrototype()).val +} + +func (r *Runtime) regexpproto_compile(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + var ( + pattern *regexpPattern + source String + flags string + err error + ) + patternVal := call.Argument(0) + flagsVal := call.Argument(1) + if o, ok := patternVal.(*Object); ok { + if p, ok := o.self.(*regexpObject); ok { + if flagsVal != _undefined { + panic(r.NewTypeError("Cannot supply flags when constructing one RegExp from another")) + } + this.pattern = p.pattern + this.source = p.source + goto exit + } + } + if patternVal != _undefined { + source = patternVal.toString() + } else { + source = stringEmpty + } + if flagsVal != _undefined { + flags = flagsVal.toString().String() + } + pattern, err = compileRegexpFromValueString(source, flags) + if err != nil { + panic(r.newSyntaxError(err.Error(), -1)) + } + this.pattern = pattern + this.source = source + exit: + this.setOwnStr("lastIndex", intToValue(0), true) + return call.This + } + + panic(r.NewTypeError("Method RegExp.prototype.compile called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) regexpproto_exec(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + return this.exec(call.Argument(0).toString()) + } else { + r.typeErrorResult(true, "Method RegExp.prototype.exec called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This})) + return nil + } +} + +func (r *Runtime) regexpproto_test(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + if this.test(call.Argument(0).toString()) { + return valueTrue + } else { + return valueFalse + } + } else { + panic(r.NewTypeError("Method RegExp.prototype.test called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) + } +} + +func (r *Runtime) regexpproto_toString(call FunctionCall) Value { + obj := r.toObject(call.This) + if this := r.checkStdRegexp(obj); this != nil { + var sb StringBuilder + sb.WriteRune('/') + if !this.writeEscapedSource(&sb) { + sb.WriteString(this.source) + } + sb.WriteRune('/') + if this.pattern.global { + sb.WriteRune('g') + } + if this.pattern.ignoreCase { + sb.WriteRune('i') + } + if this.pattern.multiline { + sb.WriteRune('m') + } + if this.pattern.unicode { + sb.WriteRune('u') + } + if this.pattern.sticky { + sb.WriteRune('y') + } + return sb.String() + } + pattern := nilSafe(obj.self.getStr("source", nil)).toString() + flags := nilSafe(obj.self.getStr("flags", nil)).toString() + var sb StringBuilder + sb.WriteRune('/') + sb.WriteString(pattern) + sb.WriteRune('/') + sb.WriteString(flags) + return sb.String() +} + +func (r *regexpObject) writeEscapedSource(sb *StringBuilder) bool { + if r.source.Length() == 0 { + sb.WriteString(asciiString("(?:)")) + return true + } + pos := 0 + lastPos := 0 + rd := &lenientUtf16Decoder{utf16Reader: r.source.utf16Reader()} +L: + for { + c, size, err := rd.ReadRune() + if err != nil { + break + } + switch c { + case '\\': + pos++ + _, size, err = rd.ReadRune() + if err != nil { + break L + } + case '/', '\u000a', '\u000d', '\u2028', '\u2029': + sb.WriteSubstring(r.source, lastPos, pos) + sb.WriteRune('\\') + switch c { + case '\u000a': + sb.WriteRune('n') + case '\u000d': + sb.WriteRune('r') + default: + sb.WriteRune('u') + sb.WriteRune(rune(hex[c>>12])) + sb.WriteRune(rune(hex[(c>>8)&0xF])) + sb.WriteRune(rune(hex[(c>>4)&0xF])) + sb.WriteRune(rune(hex[c&0xF])) + } + lastPos = pos + size + } + pos += size + } + if lastPos > 0 { + sb.WriteSubstring(r.source, lastPos, r.source.Length()) + return true + } + return false +} + +func (r *Runtime) regexpproto_getSource(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + var sb StringBuilder + if this.writeEscapedSource(&sb) { + return sb.String() + } + return this.source + } else if call.This == r.global.RegExpPrototype { + return asciiString("(?:)") + } else { + panic(r.NewTypeError("Method RegExp.prototype.source getter called on incompatible receiver")) + } +} + +func (r *Runtime) regexpproto_getGlobal(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + if this.pattern.global { + return valueTrue + } else { + return valueFalse + } + } else if call.This == r.global.RegExpPrototype { + return _undefined + } else { + panic(r.NewTypeError("Method RegExp.prototype.global getter called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) + } +} + +func (r *Runtime) regexpproto_getMultiline(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + if this.pattern.multiline { + return valueTrue + } else { + return valueFalse + } + } else if call.This == r.global.RegExpPrototype { + return _undefined + } else { + panic(r.NewTypeError("Method RegExp.prototype.multiline getter called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) + } +} + +func (r *Runtime) regexpproto_getIgnoreCase(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + if this.pattern.ignoreCase { + return valueTrue + } else { + return valueFalse + } + } else if call.This == r.global.RegExpPrototype { + return _undefined + } else { + panic(r.NewTypeError("Method RegExp.prototype.ignoreCase getter called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) + } +} + +func (r *Runtime) regexpproto_getUnicode(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + if this.pattern.unicode { + return valueTrue + } else { + return valueFalse + } + } else if call.This == r.global.RegExpPrototype { + return _undefined + } else { + panic(r.NewTypeError("Method RegExp.prototype.unicode getter called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) + } +} + +func (r *Runtime) regexpproto_getSticky(call FunctionCall) Value { + if this, ok := r.toObject(call.This).self.(*regexpObject); ok { + if this.pattern.sticky { + return valueTrue + } else { + return valueFalse + } + } else if call.This == r.global.RegExpPrototype { + return _undefined + } else { + panic(r.NewTypeError("Method RegExp.prototype.sticky getter called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) + } +} + +func (r *Runtime) regexpproto_getFlags(call FunctionCall) Value { + var global, ignoreCase, multiline, sticky, unicode bool + + thisObj := r.toObject(call.This) + size := 0 + if v := thisObj.self.getStr("global", nil); v != nil { + global = v.ToBoolean() + if global { + size++ + } + } + if v := thisObj.self.getStr("ignoreCase", nil); v != nil { + ignoreCase = v.ToBoolean() + if ignoreCase { + size++ + } + } + if v := thisObj.self.getStr("multiline", nil); v != nil { + multiline = v.ToBoolean() + if multiline { + size++ + } + } + if v := thisObj.self.getStr("sticky", nil); v != nil { + sticky = v.ToBoolean() + if sticky { + size++ + } + } + if v := thisObj.self.getStr("unicode", nil); v != nil { + unicode = v.ToBoolean() + if unicode { + size++ + } + } + + var sb strings.Builder + sb.Grow(size) + if global { + sb.WriteByte('g') + } + if ignoreCase { + sb.WriteByte('i') + } + if multiline { + sb.WriteByte('m') + } + if unicode { + sb.WriteByte('u') + } + if sticky { + sb.WriteByte('y') + } + + return asciiString(sb.String()) +} + +func (r *Runtime) regExpExec(execFn func(FunctionCall) Value, rxObj *Object, arg Value) Value { + res := execFn(FunctionCall{ + This: rxObj, + Arguments: []Value{arg}, + }) + + if res != _null { + if _, ok := res.(*Object); !ok { + panic(r.NewTypeError("RegExp exec method returned something other than an Object or null")) + } + } + + return res +} + +func (r *Runtime) getGlobalRegexpMatches(rxObj *Object, s String) []Value { + fullUnicode := nilSafe(rxObj.self.getStr("unicode", nil)).ToBoolean() + rxObj.self.setOwnStr("lastIndex", intToValue(0), true) + execFn, ok := r.toObject(rxObj.self.getStr("exec", nil)).self.assertCallable() + if !ok { + panic(r.NewTypeError("exec is not a function")) + } + var a []Value + for { + res := r.regExpExec(execFn, rxObj, s) + if res == _null { + break + } + a = append(a, res) + matchStr := nilSafe(r.toObject(res).self.getIdx(valueInt(0), nil)).toString() + if matchStr.Length() == 0 { + thisIndex := toLength(rxObj.self.getStr("lastIndex", nil)) + rxObj.self.setOwnStr("lastIndex", valueInt(advanceStringIndex64(s, thisIndex, fullUnicode)), true) + } + } + + return a +} + +func (r *Runtime) regexpproto_stdMatcherGeneric(rxObj *Object, s String) Value { + rx := rxObj.self + global := rx.getStr("global", nil) + if global != nil && global.ToBoolean() { + a := r.getGlobalRegexpMatches(rxObj, s) + if len(a) == 0 { + return _null + } + ar := make([]Value, 0, len(a)) + for _, result := range a { + obj := r.toObject(result) + matchStr := nilSafe(obj.self.getIdx(valueInt(0), nil)).ToString() + ar = append(ar, matchStr) + } + return r.newArrayValues(ar) + } + + execFn, ok := r.toObject(rx.getStr("exec", nil)).self.assertCallable() + if !ok { + panic(r.NewTypeError("exec is not a function")) + } + + return r.regExpExec(execFn, rxObj, s) +} + +func (r *Runtime) checkStdRegexp(rxObj *Object) *regexpObject { + if deoptimiseRegexp { + return nil + } + + rx, ok := rxObj.self.(*regexpObject) + if !ok { + return nil + } + + if !rx.standard || rx.prototype == nil || rx.prototype.self != r.global.stdRegexpProto { + return nil + } + + return rx +} + +func (r *Runtime) regexpproto_stdMatcher(call FunctionCall) Value { + thisObj := r.toObject(call.This) + s := call.Argument(0).toString() + rx := r.checkStdRegexp(thisObj) + if rx == nil { + return r.regexpproto_stdMatcherGeneric(thisObj, s) + } + if rx.pattern.global { + res := rx.pattern.findAllSubmatchIndex(s, 0, -1, rx.pattern.sticky) + if len(res) == 0 { + rx.setOwnStr("lastIndex", intToValue(0), true) + return _null + } + a := make([]Value, 0, len(res)) + for _, result := range res { + a = append(a, s.Substring(result[0], result[1])) + } + rx.setOwnStr("lastIndex", intToValue(int64(res[len(res)-1][1])), true) + return r.newArrayValues(a) + } else { + return rx.exec(s) + } +} + +func (r *Runtime) regexpproto_stdSearchGeneric(rxObj *Object, arg String) Value { + rx := rxObj.self + previousLastIndex := nilSafe(rx.getStr("lastIndex", nil)) + zero := intToValue(0) + if !previousLastIndex.SameAs(zero) { + rx.setOwnStr("lastIndex", zero, true) + } + execFn, ok := r.toObject(rx.getStr("exec", nil)).self.assertCallable() + if !ok { + panic(r.NewTypeError("exec is not a function")) + } + + result := r.regExpExec(execFn, rxObj, arg) + currentLastIndex := nilSafe(rx.getStr("lastIndex", nil)) + if !currentLastIndex.SameAs(previousLastIndex) { + rx.setOwnStr("lastIndex", previousLastIndex, true) + } + + if result == _null { + return intToValue(-1) + } + + return r.toObject(result).self.getStr("index", nil) +} + +func (r *Runtime) regexpproto_stdMatcherAll(call FunctionCall) Value { + thisObj := r.toObject(call.This) + s := call.Argument(0).toString() + flags := nilSafe(thisObj.self.getStr("flags", nil)).toString() + c := r.speciesConstructorObj(call.This.(*Object), r.getRegExp()) + matcher := r.toConstructor(c)([]Value{call.This, flags}, nil) + matcher.self.setOwnStr("lastIndex", valueInt(toLength(thisObj.self.getStr("lastIndex", nil))), true) + flagsStr := flags.String() + global := strings.Contains(flagsStr, "g") + fullUnicode := strings.Contains(flagsStr, "u") + return r.createRegExpStringIterator(matcher, s, global, fullUnicode) +} + +func (r *Runtime) createRegExpStringIterator(matcher *Object, s String, global, fullUnicode bool) Value { + o := &Object{runtime: r} + + ri := ®ExpStringIterObject{ + matcher: matcher, + s: s, + global: global, + fullUnicode: fullUnicode, + } + ri.class = classObject + ri.val = o + ri.extensible = true + o.self = ri + ri.prototype = r.getRegExpStringIteratorPrototype() + ri.init() + + return o +} + +type regExpStringIterObject struct { + baseObject + matcher *Object + s String + global, fullUnicode, done bool +} + +// RegExpExec as defined in 21.2.5.2.1 +func regExpExec(r *Object, s String) Value { + exec := r.self.getStr("exec", nil) + if execObject, ok := exec.(*Object); ok { + if execFn, ok := execObject.self.assertCallable(); ok { + return r.runtime.regExpExec(execFn, r, s) + } + } + if rx, ok := r.self.(*regexpObject); ok { + return rx.exec(s) + } + panic(r.runtime.NewTypeError("no RegExpMatcher internal slot")) +} + +func (ri *regExpStringIterObject) next() (v Value) { + if ri.done { + return ri.val.runtime.createIterResultObject(_undefined, true) + } + + match := regExpExec(ri.matcher, ri.s) + if IsNull(match) { + ri.done = true + return ri.val.runtime.createIterResultObject(_undefined, true) + } + if !ri.global { + ri.done = true + return ri.val.runtime.createIterResultObject(match, false) + } + + matchStr := nilSafe(ri.val.runtime.toObject(match).self.getIdx(valueInt(0), nil)).toString() + if matchStr.Length() == 0 { + thisIndex := toLength(ri.matcher.self.getStr("lastIndex", nil)) + ri.matcher.self.setOwnStr("lastIndex", valueInt(advanceStringIndex64(ri.s, thisIndex, ri.fullUnicode)), true) + } + return ri.val.runtime.createIterResultObject(match, false) +} + +func (r *Runtime) regexpproto_stdSearch(call FunctionCall) Value { + thisObj := r.toObject(call.This) + s := call.Argument(0).toString() + rx := r.checkStdRegexp(thisObj) + if rx == nil { + return r.regexpproto_stdSearchGeneric(thisObj, s) + } + + previousLastIndex := rx.getStr("lastIndex", nil) + rx.setOwnStr("lastIndex", intToValue(0), true) + + match, result := rx.execRegexp(s) + rx.setOwnStr("lastIndex", previousLastIndex, true) + + if !match { + return intToValue(-1) + } + return intToValue(int64(result[0])) +} + +func (r *Runtime) regexpproto_stdSplitterGeneric(splitter *Object, s String, limit Value, unicodeMatching bool) Value { + var a []Value + var lim int64 + if limit == nil || limit == _undefined { + lim = maxInt - 1 + } else { + lim = toLength(limit) + } + if lim == 0 { + return r.newArrayValues(a) + } + size := s.Length() + p := 0 + execFn := toMethod(splitter.ToObject(r).self.getStr("exec", nil)) // must be non-nil + + if size == 0 { + if r.regExpExec(execFn, splitter, s) == _null { + a = append(a, s) + } + return r.newArrayValues(a) + } + + q := p + for q < size { + splitter.self.setOwnStr("lastIndex", intToValue(int64(q)), true) + z := r.regExpExec(execFn, splitter, s) + if z == _null { + q = advanceStringIndex(s, q, unicodeMatching) + } else { + z := r.toObject(z) + e := toLength(splitter.self.getStr("lastIndex", nil)) + if e == int64(p) { + q = advanceStringIndex(s, q, unicodeMatching) + } else { + a = append(a, s.Substring(p, q)) + if int64(len(a)) == lim { + return r.newArrayValues(a) + } + if e > int64(size) { + p = size + } else { + p = int(e) + } + numberOfCaptures := max(toLength(z.self.getStr("length", nil))-1, 0) + for i := int64(1); i <= numberOfCaptures; i++ { + a = append(a, nilSafe(z.self.getIdx(valueInt(i), nil))) + if int64(len(a)) == lim { + return r.newArrayValues(a) + } + } + q = p + } + } + } + a = append(a, s.Substring(p, size)) + return r.newArrayValues(a) +} + +func advanceStringIndex(s String, pos int, unicode bool) int { + next := pos + 1 + if !unicode { + return next + } + l := s.Length() + if next >= l { + return next + } + if !isUTF16FirstSurrogate(s.CharAt(pos)) { + return next + } + if !isUTF16SecondSurrogate(s.CharAt(next)) { + return next + } + return next + 1 +} + +func advanceStringIndex64(s String, pos int64, unicode bool) int64 { + next := pos + 1 + if !unicode { + return next + } + l := int64(s.Length()) + if next >= l { + return next + } + if !isUTF16FirstSurrogate(s.CharAt(int(pos))) { + return next + } + if !isUTF16SecondSurrogate(s.CharAt(int(next))) { + return next + } + return next + 1 +} + +func (r *Runtime) regexpproto_stdSplitter(call FunctionCall) Value { + rxObj := r.toObject(call.This) + s := call.Argument(0).toString() + limitValue := call.Argument(1) + var splitter *Object + search := r.checkStdRegexp(rxObj) + c := r.speciesConstructorObj(rxObj, r.getRegExp()) + if search == nil || c != r.global.RegExp { + flags := nilSafe(rxObj.self.getStr("flags", nil)).toString() + flagsStr := flags.String() + + // Add 'y' flag if missing + if !strings.Contains(flagsStr, "y") { + flags = flags.Concat(asciiString("y")) + } + splitter = r.toConstructor(c)([]Value{rxObj, flags}, nil) + search = r.checkStdRegexp(splitter) + if search == nil { + return r.regexpproto_stdSplitterGeneric(splitter, s, limitValue, strings.Contains(flagsStr, "u")) + } + } + + pattern := search.pattern // toUint32() may recompile the pattern, but we still need to use the original + limit := -1 + if limitValue != _undefined { + limit = int(toUint32(limitValue)) + } + + if limit == 0 { + return r.newArrayValues(nil) + } + + targetLength := s.Length() + var valueArray []Value + lastIndex := 0 + found := 0 + + result := pattern.findAllSubmatchIndex(s, 0, -1, false) + if targetLength == 0 { + if result == nil { + valueArray = append(valueArray, s) + } + goto RETURN + } + + for _, match := range result { + if match[0] == match[1] { + // FIXME Ugh, this is a hack + if match[0] == 0 || match[0] == targetLength { + continue + } + } + + if lastIndex != match[0] { + valueArray = append(valueArray, s.Substring(lastIndex, match[0])) + found++ + } else if lastIndex == match[0] { + if lastIndex != -1 { + valueArray = append(valueArray, stringEmpty) + found++ + } + } + + lastIndex = match[1] + if found == limit { + goto RETURN + } + + captureCount := len(match) / 2 + for index := 1; index < captureCount; index++ { + offset := index * 2 + var value Value + if match[offset] != -1 { + value = s.Substring(match[offset], match[offset+1]) + } else { + value = _undefined + } + valueArray = append(valueArray, value) + found++ + if found == limit { + goto RETURN + } + } + } + + if found != limit { + if lastIndex != targetLength { + valueArray = append(valueArray, s.Substring(lastIndex, targetLength)) + } else { + valueArray = append(valueArray, stringEmpty) + } + } + +RETURN: + return r.newArrayValues(valueArray) +} + +func (r *Runtime) regexpproto_stdReplacerGeneric(rxObj *Object, s, replaceStr String, rcall func(FunctionCall) Value) Value { + var results []Value + if nilSafe(rxObj.self.getStr("global", nil)).ToBoolean() { + results = r.getGlobalRegexpMatches(rxObj, s) + } else { + execFn := toMethod(rxObj.self.getStr("exec", nil)) // must be non-nil + result := r.regExpExec(execFn, rxObj, s) + if result != _null { + results = append(results, result) + } + } + lengthS := s.Length() + nextSourcePosition := 0 + var resultBuf StringBuilder + for _, result := range results { + obj := r.toObject(result) + nCaptures := max(toLength(obj.self.getStr("length", nil))-1, 0) + matched := nilSafe(obj.self.getIdx(valueInt(0), nil)).toString() + matchLength := matched.Length() + position := toIntStrict(max(min(nilSafe(obj.self.getStr("index", nil)).ToInteger(), int64(lengthS)), 0)) + var captures []Value + if rcall != nil { + captures = make([]Value, 0, nCaptures+3) + } else { + captures = make([]Value, 0, nCaptures+1) + } + captures = append(captures, matched) + for n := int64(1); n <= nCaptures; n++ { + capN := nilSafe(obj.self.getIdx(valueInt(n), nil)) + if capN != _undefined { + capN = capN.ToString() + } + captures = append(captures, capN) + } + var replacement String + if rcall != nil { + captures = append(captures, intToValue(int64(position)), s) + replacement = rcall(FunctionCall{ + This: _undefined, + Arguments: captures, + }).toString() + if position >= nextSourcePosition { + resultBuf.WriteString(s.Substring(nextSourcePosition, position)) + resultBuf.WriteString(replacement) + nextSourcePosition = position + matchLength + } + } else { + if position >= nextSourcePosition { + resultBuf.WriteString(s.Substring(nextSourcePosition, position)) + writeSubstitution(s, position, len(captures), func(idx int) String { + capture := captures[idx] + if capture != _undefined { + return capture.toString() + } + return stringEmpty + }, replaceStr, &resultBuf) + nextSourcePosition = position + matchLength + } + } + } + if nextSourcePosition < lengthS { + resultBuf.WriteString(s.Substring(nextSourcePosition, lengthS)) + } + return resultBuf.String() +} + +func writeSubstitution(s String, position int, numCaptures int, getCapture func(int) String, replaceStr String, buf *StringBuilder) { + l := s.Length() + rl := replaceStr.Length() + matched := getCapture(0) + tailPos := position + matched.Length() + + for i := 0; i < rl; i++ { + c := replaceStr.CharAt(i) + if c == '$' && i < rl-1 { + ch := replaceStr.CharAt(i + 1) + switch ch { + case '$': + buf.WriteRune('$') + case '`': + buf.WriteString(s.Substring(0, position)) + case '\'': + if tailPos < l { + buf.WriteString(s.Substring(tailPos, l)) + } + case '&': + buf.WriteString(matched) + default: + matchNumber := 0 + j := i + 1 + for j < rl { + ch := replaceStr.CharAt(j) + if ch >= '0' && ch <= '9' { + m := matchNumber*10 + int(ch-'0') + if m >= numCaptures { + break + } + matchNumber = m + j++ + } else { + break + } + } + if matchNumber > 0 { + buf.WriteString(getCapture(matchNumber)) + i = j - 1 + continue + } else { + buf.WriteRune('$') + buf.WriteRune(rune(ch)) + } + } + i++ + } else { + buf.WriteRune(rune(c)) + } + } +} + +func (r *Runtime) regexpproto_stdReplacer(call FunctionCall) Value { + rxObj := r.toObject(call.This) + s := call.Argument(0).toString() + replaceStr, rcall := getReplaceValue(call.Argument(1)) + + rx := r.checkStdRegexp(rxObj) + if rx == nil { + return r.regexpproto_stdReplacerGeneric(rxObj, s, replaceStr, rcall) + } + + var index int64 + find := 1 + if rx.pattern.global { + find = -1 + rx.setOwnStr("lastIndex", intToValue(0), true) + } else { + index = rx.getLastIndex() + } + found := rx.pattern.findAllSubmatchIndex(s, toIntStrict(index), find, rx.pattern.sticky) + if len(found) > 0 { + if !rx.updateLastIndex(index, found[0], found[len(found)-1]) { + found = nil + } + } else { + rx.updateLastIndex(index, nil, nil) + } + + return stringReplace(s, found, replaceStr, rcall) +} + +func (r *Runtime) regExpStringIteratorProto_next(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if iter, ok := thisObj.self.(*regExpStringIterObject); ok { + return iter.next() + } + panic(r.NewTypeError("Method RegExp String Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) +} + +func (r *Runtime) createRegExpStringIteratorPrototype(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) + + o._putProp("next", r.newNativeFunc(r.regExpStringIteratorProto_next, "next", 0), true, false, true) + o._putSym(SymToStringTag, valueProp(asciiString(classRegExpStringIterator), false, false, true)) + + return o +} + +func (r *Runtime) getRegExpStringIteratorPrototype() *Object { + var o *Object + if o = r.global.RegExpStringIteratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.RegExpStringIteratorPrototype = o + o.self = r.createRegExpStringIteratorPrototype(o) + } + return o +} + +func (r *Runtime) getRegExp() *Object { + ret := r.global.RegExp + if ret == nil { + ret = &Object{runtime: r} + r.global.RegExp = ret + proto := r.getRegExpPrototype() + r.newNativeFuncAndConstruct(ret, r.builtin_RegExp, + r.wrapNativeConstruct(r.builtin_newRegExp, ret, proto), proto, "RegExp", intToValue(2)) + rx := ret.self + r.putSpeciesReturnThis(rx) + } + return ret +} + +func (r *Runtime) getRegExpPrototype() *Object { + ret := r.global.RegExpPrototype + if ret == nil { + o := r.newGuardedObject(r.global.ObjectPrototype, classObject) + ret = o.val + r.global.RegExpPrototype = ret + r.global.stdRegexpProto = o + + o._putProp("constructor", r.getRegExp(), true, false, true) + o._putProp("compile", r.newNativeFunc(r.regexpproto_compile, "compile", 2), true, false, true) + o._putProp("exec", r.newNativeFunc(r.regexpproto_exec, "exec", 1), true, false, true) + o._putProp("test", r.newNativeFunc(r.regexpproto_test, "test", 1), true, false, true) + o._putProp("toString", r.newNativeFunc(r.regexpproto_toString, "toString", 0), true, false, true) + o.setOwnStr("source", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getSource, "get source", 0), + accessor: true, + }, false) + o.setOwnStr("global", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getGlobal, "get global", 0), + accessor: true, + }, false) + o.setOwnStr("multiline", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getMultiline, "get multiline", 0), + accessor: true, + }, false) + o.setOwnStr("ignoreCase", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getIgnoreCase, "get ignoreCase", 0), + accessor: true, + }, false) + o.setOwnStr("unicode", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getUnicode, "get unicode", 0), + accessor: true, + }, false) + o.setOwnStr("sticky", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getSticky, "get sticky", 0), + accessor: true, + }, false) + o.setOwnStr("flags", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(r.regexpproto_getFlags, "get flags", 0), + accessor: true, + }, false) + + o._putSym(SymMatch, valueProp(r.newNativeFunc(r.regexpproto_stdMatcher, "[Symbol.match]", 1), true, false, true)) + o._putSym(SymMatchAll, valueProp(r.newNativeFunc(r.regexpproto_stdMatcherAll, "[Symbol.matchAll]", 1), true, false, true)) + o._putSym(SymSearch, valueProp(r.newNativeFunc(r.regexpproto_stdSearch, "[Symbol.search]", 1), true, false, true)) + o._putSym(SymSplit, valueProp(r.newNativeFunc(r.regexpproto_stdSplitter, "[Symbol.split]", 2), true, false, true)) + o._putSym(SymReplace, valueProp(r.newNativeFunc(r.regexpproto_stdReplacer, "[Symbol.replace]", 2), true, false, true)) + o.guard("exec", "global", "multiline", "ignoreCase", "unicode", "sticky") + } + return ret +} diff --git a/pkg/xscript/engine/builtin_set.go b/pkg/xscript/engine/builtin_set.go new file mode 100644 index 0000000..5fb7bfe --- /dev/null +++ b/pkg/xscript/engine/builtin_set.go @@ -0,0 +1,346 @@ +package engine + +import ( + "fmt" + "reflect" +) + +var setExportType = reflectTypeArray + +type setObject struct { + baseObject + m *orderedMap +} + +type setIterObject struct { + baseObject + iter *orderedMapIter + kind iterationKind +} + +func (o *setIterObject) next() Value { + if o.iter == nil { + return o.val.runtime.createIterResultObject(_undefined, true) + } + + entry := o.iter.next() + if entry == nil { + o.iter = nil + return o.val.runtime.createIterResultObject(_undefined, true) + } + + var result Value + switch o.kind { + case iterationKindValue: + result = entry.key + default: + result = o.val.runtime.newArrayValues([]Value{entry.key, entry.key}) + } + + return o.val.runtime.createIterResultObject(result, false) +} + +func (so *setObject) init() { + so.baseObject.init() + so.m = newOrderedMap(so.val.runtime.getHash()) +} + +func (so *setObject) exportType() reflect.Type { + return setExportType +} + +func (so *setObject) export(ctx *objectExportCtx) any { + a := make([]any, so.m.size) + ctx.put(so.val, a) + iter := so.m.newIter() + for i := 0; i < len(a); i++ { + entry := iter.next() + if entry == nil { + break + } + a[i] = exportValue(entry.key, ctx) + } + return a +} + +func (so *setObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + l := so.m.size + if typ.Kind() == reflect.Array { + if dst.Len() != l { + return fmt.Errorf("cannot convert a Set into an array, lengths mismatch: have %d, need %d)", l, dst.Len()) + } + } else { + dst.Set(reflect.MakeSlice(typ, l, l)) + } + ctx.putTyped(so.val, typ, dst.Interface()) + iter := so.m.newIter() + r := so.val.runtime + for i := 0; i < l; i++ { + entry := iter.next() + if entry == nil { + break + } + err := r.toReflectValue(entry.key, dst.Index(i), ctx) + if err != nil { + return err + } + } + return nil +} + +func (so *setObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + dst.Set(reflect.MakeMap(typ)) + keyTyp := typ.Key() + elemTyp := typ.Elem() + iter := so.m.newIter() + r := so.val.runtime + for { + entry := iter.next() + if entry == nil { + break + } + keyVal := reflect.New(keyTyp).Elem() + err := r.toReflectValue(entry.key, keyVal, ctx) + if err != nil { + return err + } + dst.SetMapIndex(keyVal, reflect.Zero(elemTyp)) + } + return nil +} + +func (r *Runtime) setProto_add(call FunctionCall) Value { + thisObj := r.toObject(call.This) + so, ok := thisObj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Method Set.prototype.add called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + so.m.set(call.Argument(0), nil) + return call.This +} + +func (r *Runtime) setProto_clear(call FunctionCall) Value { + thisObj := r.toObject(call.This) + so, ok := thisObj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Method Set.prototype.clear called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + so.m.clear() + return _undefined +} + +func (r *Runtime) setProto_delete(call FunctionCall) Value { + thisObj := r.toObject(call.This) + so, ok := thisObj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Method Set.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + return r.toBoolean(so.m.remove(call.Argument(0))) +} + +func (r *Runtime) setProto_entries(call FunctionCall) Value { + return r.createSetIterator(call.This, iterationKindKeyValue) +} + +func (r *Runtime) setProto_forEach(call FunctionCall) Value { + thisObj := r.toObject(call.This) + so, ok := thisObj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Method Set.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + callbackFn, ok := r.toObject(call.Argument(0)).self.assertCallable() + if !ok { + panic(r.NewTypeError("object is not a function %s")) + } + t := call.Argument(1) + iter := so.m.newIter() + for { + entry := iter.next() + if entry == nil { + break + } + callbackFn(FunctionCall{This: t, Arguments: []Value{entry.key, entry.key, thisObj}}) + } + + return _undefined +} + +func (r *Runtime) setProto_has(call FunctionCall) Value { + thisObj := r.toObject(call.This) + so, ok := thisObj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Method Set.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + return r.toBoolean(so.m.has(call.Argument(0))) +} + +func (r *Runtime) setProto_getSize(call FunctionCall) Value { + thisObj := r.toObject(call.This) + so, ok := thisObj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Method get Set.prototype.size called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + + return intToValue(int64(so.m.size)) +} + +func (r *Runtime) setProto_values(call FunctionCall) Value { + return r.createSetIterator(call.This, iterationKindValue) +} + +func (r *Runtime) builtin_newSet(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("Set")) + } + proto := r.getPrototypeFromCtor(newTarget, r.global.Set, r.global.SetPrototype) + o := &Object{runtime: r} + + so := &setObject{} + so.class = classObject + so.val = o + so.extensible = true + o.self = so + so.prototype = proto + so.init() + if len(args) > 0 { + if arg := args[0]; arg != nil && arg != _undefined && arg != _null { + adder := so.getStr("add", nil) + stdArr := r.checkStdArrayIter(arg) + if adder == r.global.setAdder { + if stdArr != nil { + for _, v := range stdArr.values { + so.m.set(v, nil) + } + } else { + r.getIterator(arg, nil).iterate(func(item Value) { + so.m.set(item, nil) + }) + } + } else { + adderFn := toMethod(adder) + if adderFn == nil { + panic(r.NewTypeError("Set.add in missing")) + } + if stdArr != nil { + for _, item := range stdArr.values { + adderFn(FunctionCall{This: o, Arguments: []Value{item}}) + } + } else { + r.getIterator(arg, nil).iterate(func(item Value) { + adderFn(FunctionCall{This: o, Arguments: []Value{item}}) + }) + } + } + } + } + return o +} + +func (r *Runtime) createSetIterator(setValue Value, kind iterationKind) Value { + obj := r.toObject(setValue) + setObj, ok := obj.self.(*setObject) + if !ok { + panic(r.NewTypeError("Object is not a Set")) + } + + o := &Object{runtime: r} + + si := &setIterObject{ + iter: setObj.m.newIter(), + kind: kind, + } + si.class = classObject + si.val = o + si.extensible = true + o.self = si + si.prototype = r.getSetIteratorPrototype() + si.init() + + return o +} + +func (r *Runtime) setIterProto_next(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if iter, ok := thisObj.self.(*setIterObject); ok { + return iter.next() + } + panic(r.NewTypeError("Method Set Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) +} + +func (r *Runtime) createSetProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + + o._putProp("constructor", r.getSet(), true, false, true) + r.global.setAdder = r.newNativeFunc(r.setProto_add, "add", 1) + o._putProp("add", r.global.setAdder, true, false, true) + + o._putProp("clear", r.newNativeFunc(r.setProto_clear, "clear", 0), true, false, true) + o._putProp("delete", r.newNativeFunc(r.setProto_delete, "delete", 1), true, false, true) + o._putProp("forEach", r.newNativeFunc(r.setProto_forEach, "forEach", 1), true, false, true) + o._putProp("has", r.newNativeFunc(r.setProto_has, "has", 1), true, false, true) + o.setOwnStr("size", &valueProperty{ + getterFunc: r.newNativeFunc(r.setProto_getSize, "get size", 0), + accessor: true, + writable: true, + configurable: true, + }, true) + + valuesFunc := r.newNativeFunc(r.setProto_values, "values", 0) + o._putProp("values", valuesFunc, true, false, true) + o._putProp("keys", valuesFunc, true, false, true) + o._putProp("entries", r.newNativeFunc(r.setProto_entries, "entries", 0), true, false, true) + o._putSym(SymIterator, valueProp(valuesFunc, true, false, true)) + o._putSym(SymToStringTag, valueProp(asciiString(classSet), false, false, true)) + + return o +} + +func (r *Runtime) createSet(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newSet, r.getSetPrototype(), "Set", 0) + r.putSpeciesReturnThis(o) + + return o +} + +func (r *Runtime) createSetIterProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) + + o._putProp("next", r.newNativeFunc(r.setIterProto_next, "next", 0), true, false, true) + o._putSym(SymToStringTag, valueProp(asciiString(classSetIterator), false, false, true)) + + return o +} + +func (r *Runtime) getSetIteratorPrototype() *Object { + var o *Object + if o = r.global.SetIteratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.SetIteratorPrototype = o + o.self = r.createSetIterProto(o) + } + return o +} + +func (r *Runtime) getSetPrototype() *Object { + ret := r.global.SetPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.SetPrototype = ret + ret.self = r.createSetProto(ret) + } + return ret +} + +func (r *Runtime) getSet() *Object { + ret := r.global.Set + if ret == nil { + ret = &Object{runtime: r} + r.global.Set = ret + ret.self = r.createSet(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_set_test.go b/pkg/xscript/engine/builtin_set_test.go new file mode 100644 index 0000000..bb909b0 --- /dev/null +++ b/pkg/xscript/engine/builtin_set_test.go @@ -0,0 +1,180 @@ +package engine + +import ( + "fmt" + "strings" + "testing" +) + +func TestSetEvilIterator(t *testing.T) { + const SCRIPT = ` + var o = {}; + o[Symbol.iterator] = function() { + return { + next: function() { + if (!this.flag) { + this.flag = true; + return {}; + } + return {done: true}; + } + } + } + new Set(o); + undefined; + ` + testScript(SCRIPT, _undefined, t) +} + +func ExampleRuntime_ExportTo_setToMap() { + vm := New() + s, err := vm.RunString(` + new Set([1, 2, 3]) + `) + if err != nil { + panic(err) + } + m := make(map[int]struct{}) + err = vm.ExportTo(s, &m) + if err != nil { + panic(err) + } + fmt.Println(m) + // Output: map[1:{} 2:{} 3:{}] +} + +func ExampleRuntime_ExportTo_setToSlice() { + vm := New() + s, err := vm.RunString(` + new Set([1, 2, 3]) + `) + if err != nil { + panic(err) + } + var a []int + err = vm.ExportTo(s, &a) + if err != nil { + panic(err) + } + fmt.Println(a) + // Output: [1 2 3] +} + +func TestSetExportToSliceCircular(t *testing.T) { + vm := New() + s, err := vm.RunString(` + let s = new Set(); + s.add(s); + s; + `) + if err != nil { + t.Fatal(err) + } + var a []Value + err = vm.ExportTo(s, &a) + if err != nil { + t.Fatal(err) + } + if len(a) != 1 { + t.Fatalf("len: %d", len(a)) + } + if a[0] != s { + t.Fatalf("a: %v", a) + } +} + +func TestSetExportToArrayMismatchedLengths(t *testing.T) { + vm := New() + s, err := vm.RunString(` + new Set([1, 2]) + `) + if err != nil { + panic(err) + } + var s1 [3]int + err = vm.ExportTo(s, &s1) + if err == nil { + t.Fatal("expected error") + } + if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestSetExportToNilMap(t *testing.T) { + vm := New() + var m map[int]any + res, err := vm.RunString("new Set([1])") + if err != nil { + t.Fatal(err) + } + err = vm.ExportTo(res, &m) + if err != nil { + t.Fatal(err) + } + if len(m) != 1 { + t.Fatal(m) + } + if _, exists := m[1]; !exists { + t.Fatal(m) + } +} + +func TestSetExportToNonNilMap(t *testing.T) { + vm := New() + m := map[int]any{ + 2: true, + } + res, err := vm.RunString("new Set([1])") + if err != nil { + t.Fatal(err) + } + err = vm.ExportTo(res, &m) + if err != nil { + t.Fatal(err) + } + if len(m) != 1 { + t.Fatal(m) + } + if _, exists := m[1]; !exists { + t.Fatal(m) + } +} + +func TestSetGetAdderGetIteratorOrder(t *testing.T) { + const SCRIPT = ` + let getterCalled = 0; + + class S extends Set { + get add() { + getterCalled++; + return null; + } + } + + let getIteratorCalled = 0; + + let iterable = {}; + iterable[Symbol.iterator] = () => { + getIteratorCalled++ + return { + next: 1 + }; + } + + let thrown = false; + + try { + new S(iterable); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + + thrown && getterCalled === 1 && getIteratorCalled === 0; + ` + testScript(SCRIPT, valueTrue, t) +} diff --git a/pkg/xscript/engine/builtin_string.go b/pkg/xscript/engine/builtin_string.go new file mode 100644 index 0000000..2fe5618 --- /dev/null +++ b/pkg/xscript/engine/builtin_string.go @@ -0,0 +1,1118 @@ +package engine + +import ( + "math" + "strings" + "sync" + "unicode/utf16" + "unicode/utf8" + + "pandax/pkg/xscript/engine/unistring" + + "pandax/pkg/xscript/engine/parser" + + "golang.org/x/text/collate" + "golang.org/x/text/language" + "golang.org/x/text/unicode/norm" +) + +func (r *Runtime) collator() *collate.Collator { + collator := r._collator + if collator == nil { + collator = collate.New(language.Und) + r._collator = collator + } + return collator +} + +func toString(arg Value) String { + if s, ok := arg.(String); ok { + return s + } + if s, ok := arg.(*Symbol); ok { + return s.descriptiveString() + } + return arg.toString() +} + +func (r *Runtime) builtin_String(call FunctionCall) Value { + if len(call.Arguments) > 0 { + return toString(call.Arguments[0]) + } else { + return stringEmpty + } +} + +func (r *Runtime) _newString(s String, proto *Object) *Object { + v := &Object{runtime: r} + + o := &stringObject{} + o.class = classString + o.val = v + o.extensible = true + v.self = o + o.prototype = proto + if s != nil { + o.value = s + } + o.init() + return v +} + +func (r *Runtime) builtin_newString(args []Value, proto *Object) *Object { + var s String + if len(args) > 0 { + s = args[0].toString() + } else { + s = stringEmpty + } + return r._newString(s, proto) +} + +func (r *Runtime) stringproto_toStringValueOf(this Value, funcName string) Value { + if str, ok := this.(String); ok { + return str + } + if obj, ok := this.(*Object); ok { + if strObj, ok := obj.self.(*stringObject); ok { + return strObj.value + } + if reflectObj, ok := obj.self.(*objectGoReflect); ok && reflectObj.class == classString { + if toString := reflectObj.toString; toString != nil { + return toString() + } + if valueOf := reflectObj.valueOf; valueOf != nil { + return valueOf() + } + } + if obj == r.global.StringPrototype { + return stringEmpty + } + } + r.typeErrorResult(true, "String.prototype.%s is called on incompatible receiver", funcName) + return nil +} + +func (r *Runtime) stringproto_toString(call FunctionCall) Value { + return r.stringproto_toStringValueOf(call.This, "toString") +} + +func (r *Runtime) stringproto_valueOf(call FunctionCall) Value { + return r.stringproto_toStringValueOf(call.This, "valueOf") +} + +func (r *Runtime) stringproto_iterator(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + return r.createStringIterator(call.This.toString()) +} + +func (r *Runtime) string_fromcharcode(call FunctionCall) Value { + b := make([]byte, len(call.Arguments)) + for i, arg := range call.Arguments { + chr := toUint16(arg) + if chr >= utf8.RuneSelf { + bb := make([]uint16, len(call.Arguments)+1) + bb[0] = unistring.BOM + bb1 := bb[1:] + for j := 0; j < i; j++ { + bb1[j] = uint16(b[j]) + } + bb1[i] = chr + i++ + for j, arg := range call.Arguments[i:] { + bb1[i+j] = toUint16(arg) + } + return unicodeString(bb) + } + b[i] = byte(chr) + } + + return asciiString(b) +} + +func (r *Runtime) string_fromcodepoint(call FunctionCall) Value { + var sb StringBuilder + for _, arg := range call.Arguments { + num := arg.ToNumber() + var c rune + if numInt, ok := num.(valueInt); ok { + if numInt < 0 || numInt > utf8.MaxRune { + panic(r.newError(r.getRangeError(), "Invalid code point %d", numInt)) + } + c = rune(numInt) + } else { + panic(r.newError(r.getRangeError(), "Invalid code point %s", num)) + } + sb.WriteRune(c) + } + return sb.String() +} + +func (r *Runtime) string_raw(call FunctionCall) Value { + cooked := call.Argument(0).ToObject(r) + raw := nilSafe(cooked.self.getStr("raw", nil)).ToObject(r) + literalSegments := toLength(raw.self.getStr("length", nil)) + if literalSegments <= 0 { + return stringEmpty + } + var stringElements StringBuilder + nextIndex := int64(0) + numberOfSubstitutions := int64(len(call.Arguments) - 1) + for { + nextSeg := nilSafe(raw.self.getIdx(valueInt(nextIndex), nil)).toString() + stringElements.WriteString(nextSeg) + if nextIndex+1 == literalSegments { + return stringElements.String() + } + if nextIndex < numberOfSubstitutions { + stringElements.WriteString(nilSafe(call.Arguments[nextIndex+1]).toString()) + } + nextIndex++ + } +} + +func (r *Runtime) stringproto_at(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + pos := call.Argument(0).ToInteger() + length := int64(s.Length()) + if pos < 0 { + pos = length + pos + } + if pos >= length || pos < 0 { + return _undefined + } + return s.Substring(int(pos), int(pos+1)) +} + +func (r *Runtime) stringproto_charAt(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + pos := call.Argument(0).ToInteger() + if pos < 0 || pos >= int64(s.Length()) { + return stringEmpty + } + return s.Substring(int(pos), int(pos+1)) +} + +func (r *Runtime) stringproto_charCodeAt(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + pos := call.Argument(0).ToInteger() + if pos < 0 || pos >= int64(s.Length()) { + return _NaN + } + return intToValue(int64(s.CharAt(toIntStrict(pos)) & 0xFFFF)) +} + +func (r *Runtime) stringproto_codePointAt(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + p := call.Argument(0).ToInteger() + size := s.Length() + if p < 0 || p >= int64(size) { + return _undefined + } + pos := toIntStrict(p) + first := s.CharAt(pos) + if isUTF16FirstSurrogate(first) { + pos++ + if pos < size { + second := s.CharAt(pos) + if isUTF16SecondSurrogate(second) { + return intToValue(int64(utf16.DecodeRune(rune(first), rune(second)))) + } + } + } + return intToValue(int64(first & 0xFFFF)) +} + +func (r *Runtime) stringproto_concat(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + strs := make([]String, len(call.Arguments)+1) + a, u := devirtualizeString(call.This.toString()) + allAscii := true + totalLen := 0 + if u == nil { + strs[0] = a + totalLen = len(a) + } else { + strs[0] = u + totalLen = u.Length() + allAscii = false + } + for i, arg := range call.Arguments { + a, u := devirtualizeString(arg.toString()) + if u != nil { + allAscii = false + totalLen += u.Length() + strs[i+1] = u + } else { + totalLen += a.Length() + strs[i+1] = a + } + } + + if allAscii { + var buf strings.Builder + buf.Grow(totalLen) + for _, s := range strs { + buf.WriteString(s.String()) + } + return asciiString(buf.String()) + } else { + buf := make([]uint16, totalLen+1) + buf[0] = unistring.BOM + pos := 1 + for _, s := range strs { + switch s := s.(type) { + case asciiString: + for i := 0; i < len(s); i++ { + buf[pos] = uint16(s[i]) + pos++ + } + case unicodeString: + copy(buf[pos:], s[1:]) + pos += s.Length() + } + } + return unicodeString(buf) + } +} + +func (r *Runtime) stringproto_endsWith(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + searchString := call.Argument(0) + if isRegexp(searchString) { + panic(r.NewTypeError("First argument to String.prototype.endsWith must not be a regular expression")) + } + searchStr := searchString.toString() + l := int64(s.Length()) + var pos int64 + if posArg := call.Argument(1); posArg != _undefined { + pos = posArg.ToInteger() + } else { + pos = l + } + end := toIntStrict(min(max(pos, 0), l)) + searchLength := searchStr.Length() + start := end - searchLength + if start < 0 { + return valueFalse + } + for i := 0; i < searchLength; i++ { + if s.CharAt(start+i) != searchStr.CharAt(i) { + return valueFalse + } + } + return valueTrue +} + +func (r *Runtime) stringproto_includes(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + searchString := call.Argument(0) + if isRegexp(searchString) { + panic(r.NewTypeError("First argument to String.prototype.includes must not be a regular expression")) + } + searchStr := searchString.toString() + var pos int64 + if posArg := call.Argument(1); posArg != _undefined { + pos = posArg.ToInteger() + } else { + pos = 0 + } + start := toIntStrict(min(max(pos, 0), int64(s.Length()))) + if s.index(searchStr, start) != -1 { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) stringproto_indexOf(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + value := call.This.toString() + target := call.Argument(0).toString() + pos := call.Argument(1).ToInteger() + + if pos < 0 { + pos = 0 + } else { + l := int64(value.Length()) + if pos > l { + pos = l + } + } + + return intToValue(int64(value.index(target, toIntStrict(pos)))) +} + +func (r *Runtime) stringproto_lastIndexOf(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + value := call.This.toString() + target := call.Argument(0).toString() + numPos := call.Argument(1).ToNumber() + + var pos int64 + if f, ok := numPos.(valueFloat); ok && math.IsNaN(float64(f)) { + pos = int64(value.Length()) + } else { + pos = numPos.ToInteger() + if pos < 0 { + pos = 0 + } else { + l := int64(value.Length()) + if pos > l { + pos = l + } + } + } + + return intToValue(int64(value.lastIndex(target, toIntStrict(pos)))) +} + +func (r *Runtime) stringproto_localeCompare(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + this := norm.NFD.String(call.This.toString().String()) + that := norm.NFD.String(call.Argument(0).toString().String()) + return intToValue(int64(r.collator().CompareString(this, that))) +} + +func (r *Runtime) stringproto_match(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + regexp := call.Argument(0) + if regexp != _undefined && regexp != _null { + if matcher := toMethod(r.getV(regexp, SymMatch)); matcher != nil { + return matcher(FunctionCall{ + This: regexp, + Arguments: []Value{call.This}, + }) + } + } + + var rx *regexpObject + if regexp, ok := regexp.(*Object); ok { + rx, _ = regexp.self.(*regexpObject) + } + + if rx == nil { + rx = r.newRegExp(regexp, nil, r.getRegExpPrototype()) + } + + if matcher, ok := r.toObject(rx.getSym(SymMatch, nil)).self.assertCallable(); ok { + return matcher(FunctionCall{ + This: rx.val, + Arguments: []Value{call.This.toString()}, + }) + } + + panic(r.NewTypeError("RegExp matcher is not a function")) +} + +func (r *Runtime) stringproto_matchAll(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + regexp := call.Argument(0) + if regexp != _undefined && regexp != _null { + if isRegexp(regexp) { + if o, ok := regexp.(*Object); ok { + flags := nilSafe(o.self.getStr("flags", nil)) + r.checkObjectCoercible(flags) + if !strings.Contains(flags.toString().String(), "g") { + panic(r.NewTypeError("RegExp doesn't have global flag set")) + } + } + } + if matcher := toMethod(r.getV(regexp, SymMatchAll)); matcher != nil { + return matcher(FunctionCall{ + This: regexp, + Arguments: []Value{call.This}, + }) + } + } + + rx := r.newRegExp(regexp, asciiString("g"), r.getRegExpPrototype()) + + if matcher, ok := r.toObject(rx.getSym(SymMatchAll, nil)).self.assertCallable(); ok { + return matcher(FunctionCall{ + This: rx.val, + Arguments: []Value{call.This.toString()}, + }) + } + + panic(r.NewTypeError("RegExp matcher is not a function")) +} + +func (r *Runtime) stringproto_normalize(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + var form string + if formArg := call.Argument(0); formArg != _undefined { + form = formArg.toString().toString().String() + } else { + form = "NFC" + } + var f norm.Form + switch form { + case "NFC": + f = norm.NFC + case "NFD": + f = norm.NFD + case "NFKC": + f = norm.NFKC + case "NFKD": + f = norm.NFKD + default: + panic(r.newError(r.getRangeError(), "The normalization form should be one of NFC, NFD, NFKC, NFKD")) + } + + switch s := s.(type) { + case asciiString: + return s + case unicodeString: + ss := s.String() + return newStringValue(f.String(ss)) + case *importedString: + if s.scanned && s.u == nil { + return asciiString(s.s) + } + return newStringValue(f.String(s.s)) + default: + panic(unknownStringTypeErr(s)) + } +} + +func (r *Runtime) _stringPad(call FunctionCall, start bool) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + maxLength := toLength(call.Argument(0)) + stringLength := int64(s.Length()) + if maxLength <= stringLength { + return s + } + strAscii, strUnicode := devirtualizeString(s) + var filler String + var fillerAscii asciiString + var fillerUnicode unicodeString + if fillString := call.Argument(1); fillString != _undefined { + filler = fillString.toString() + if filler.Length() == 0 { + return s + } + fillerAscii, fillerUnicode = devirtualizeString(filler) + } else { + fillerAscii = " " + filler = fillerAscii + } + remaining := toIntStrict(maxLength - stringLength) + if fillerUnicode == nil && strUnicode == nil { + fl := fillerAscii.Length() + var sb strings.Builder + sb.Grow(toIntStrict(maxLength)) + if !start { + sb.WriteString(string(strAscii)) + } + for remaining >= fl { + sb.WriteString(string(fillerAscii)) + remaining -= fl + } + if remaining > 0 { + sb.WriteString(string(fillerAscii[:remaining])) + } + if start { + sb.WriteString(string(strAscii)) + } + return asciiString(sb.String()) + } + var sb unicodeStringBuilder + sb.ensureStarted(toIntStrict(maxLength)) + if !start { + sb.writeString(s) + } + fl := filler.Length() + for remaining >= fl { + sb.writeString(filler) + remaining -= fl + } + if remaining > 0 { + sb.writeString(filler.Substring(0, remaining)) + } + if start { + sb.writeString(s) + } + + return sb.String() +} + +func (r *Runtime) stringproto_padEnd(call FunctionCall) Value { + return r._stringPad(call, false) +} + +func (r *Runtime) stringproto_padStart(call FunctionCall) Value { + return r._stringPad(call, true) +} + +func (r *Runtime) stringproto_repeat(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + n := call.Argument(0).ToNumber() + if n == _positiveInf { + panic(r.newError(r.getRangeError(), "Invalid count value")) + } + numInt := n.ToInteger() + if numInt < 0 { + panic(r.newError(r.getRangeError(), "Invalid count value")) + } + if numInt == 0 || s.Length() == 0 { + return stringEmpty + } + num := toIntStrict(numInt) + a, u := devirtualizeString(s) + if u == nil { + var sb strings.Builder + sb.Grow(len(a) * num) + for i := 0; i < num; i++ { + sb.WriteString(string(a)) + } + return asciiString(sb.String()) + } + + var sb unicodeStringBuilder + sb.Grow(u.Length() * num) + for i := 0; i < num; i++ { + sb.writeUnicodeString(u) + } + return sb.String() +} + +func getReplaceValue(replaceValue Value) (str String, rcall func(FunctionCall) Value) { + if replaceValue, ok := replaceValue.(*Object); ok { + if c, ok := replaceValue.self.assertCallable(); ok { + rcall = c + return + } + } + str = replaceValue.toString() + return +} + +func stringReplace(s String, found [][]int, newstring String, rcall func(FunctionCall) Value) Value { + if len(found) == 0 { + return s + } + + a, u := devirtualizeString(s) + + var buf StringBuilder + + lastIndex := 0 + lengthS := s.Length() + if rcall != nil { + for _, item := range found { + if item[0] != lastIndex { + buf.WriteSubstring(s, lastIndex, item[0]) + } + matchCount := len(item) / 2 + argumentList := make([]Value, matchCount+2) + for index := 0; index < matchCount; index++ { + offset := 2 * index + if item[offset] != -1 { + if u == nil { + argumentList[index] = a[item[offset]:item[offset+1]] + } else { + argumentList[index] = u.Substring(item[offset], item[offset+1]) + } + } else { + argumentList[index] = _undefined + } + } + argumentList[matchCount] = valueInt(item[0]) + argumentList[matchCount+1] = s + replacement := rcall(FunctionCall{ + This: _undefined, + Arguments: argumentList, + }).toString() + buf.WriteString(replacement) + lastIndex = item[1] + } + } else { + for _, item := range found { + if item[0] != lastIndex { + buf.WriteString(s.Substring(lastIndex, item[0])) + } + matchCount := len(item) / 2 + writeSubstitution(s, item[0], matchCount, func(idx int) String { + if item[idx*2] != -1 { + if u == nil { + return a[item[idx*2]:item[idx*2+1]] + } + return u.Substring(item[idx*2], item[idx*2+1]) + } + return stringEmpty + }, newstring, &buf) + lastIndex = item[1] + } + } + + if lastIndex != lengthS { + buf.WriteString(s.Substring(lastIndex, lengthS)) + } + + return buf.String() +} + +func (r *Runtime) stringproto_replace(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + searchValue := call.Argument(0) + replaceValue := call.Argument(1) + if searchValue != _undefined && searchValue != _null { + if replacer := toMethod(r.getV(searchValue, SymReplace)); replacer != nil { + return replacer(FunctionCall{ + This: searchValue, + Arguments: []Value{call.This, replaceValue}, + }) + } + } + + s := call.This.toString() + var found [][]int + searchStr := searchValue.toString() + pos := s.index(searchStr, 0) + if pos != -1 { + found = append(found, []int{pos, pos + searchStr.Length()}) + } + + str, rcall := getReplaceValue(replaceValue) + return stringReplace(s, found, str, rcall) +} + +func (r *Runtime) stringproto_replaceAll(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + searchValue := call.Argument(0) + replaceValue := call.Argument(1) + if searchValue != _undefined && searchValue != _null { + if isRegexp(searchValue) { + if o, ok := searchValue.(*Object); ok { + flags := nilSafe(o.self.getStr("flags", nil)) + r.checkObjectCoercible(flags) + if !strings.Contains(flags.toString().String(), "g") { + panic(r.NewTypeError("String.prototype.replaceAll called with a non-global RegExp argument")) + } + } + } + if replacer := toMethod(r.getV(searchValue, SymReplace)); replacer != nil { + return replacer(FunctionCall{ + This: searchValue, + Arguments: []Value{call.This, replaceValue}, + }) + } + } + + s := call.This.toString() + var found [][]int + searchStr := searchValue.toString() + searchLength := searchStr.Length() + advanceBy := toIntStrict(max(1, int64(searchLength))) + + pos := s.index(searchStr, 0) + for pos != -1 { + found = append(found, []int{pos, pos + searchLength}) + pos = s.index(searchStr, pos+advanceBy) + } + + str, rcall := getReplaceValue(replaceValue) + return stringReplace(s, found, str, rcall) +} + +func (r *Runtime) stringproto_search(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + regexp := call.Argument(0) + if regexp != _undefined && regexp != _null { + if searcher := toMethod(r.getV(regexp, SymSearch)); searcher != nil { + return searcher(FunctionCall{ + This: regexp, + Arguments: []Value{call.This}, + }) + } + } + + var rx *regexpObject + if regexp, ok := regexp.(*Object); ok { + rx, _ = regexp.self.(*regexpObject) + } + + if rx == nil { + rx = r.newRegExp(regexp, nil, r.getRegExpPrototype()) + } + + if searcher, ok := r.toObject(rx.getSym(SymSearch, nil)).self.assertCallable(); ok { + return searcher(FunctionCall{ + This: rx.val, + Arguments: []Value{call.This.toString()}, + }) + } + + panic(r.NewTypeError("RegExp searcher is not a function")) +} + +func (r *Runtime) stringproto_slice(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + l := int64(s.Length()) + start := call.Argument(0).ToInteger() + var end int64 + if arg1 := call.Argument(1); arg1 != _undefined { + end = arg1.ToInteger() + } else { + end = l + } + + if start < 0 { + start += l + if start < 0 { + start = 0 + } + } else { + if start > l { + start = l + } + } + + if end < 0 { + end += l + if end < 0 { + end = 0 + } + } else { + if end > l { + end = l + } + } + + if end > start { + return s.Substring(int(start), int(end)) + } + return stringEmpty +} + +func (r *Runtime) stringproto_split(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + separatorValue := call.Argument(0) + limitValue := call.Argument(1) + if separatorValue != _undefined && separatorValue != _null { + if splitter := toMethod(r.getV(separatorValue, SymSplit)); splitter != nil { + return splitter(FunctionCall{ + This: separatorValue, + Arguments: []Value{call.This, limitValue}, + }) + } + } + s := call.This.toString() + + limit := -1 + if limitValue != _undefined { + limit = int(toUint32(limitValue)) + } + + separatorValue = separatorValue.ToString() + + if limit == 0 { + return r.newArrayValues(nil) + } + + if separatorValue == _undefined { + return r.newArrayValues([]Value{s}) + } + + separator := separatorValue.String() + + excess := false + str := s.String() + if limit > len(str) { + limit = len(str) + } + splitLimit := limit + if limit > 0 { + splitLimit = limit + 1 + excess = true + } + + // TODO handle invalid UTF-16 + split := strings.SplitN(str, separator, splitLimit) + + if excess && len(split) > limit { + split = split[:limit] + } + + valueArray := make([]Value, len(split)) + for index, value := range split { + valueArray[index] = newStringValue(value) + } + + return r.newArrayValues(valueArray) +} + +func (r *Runtime) stringproto_startsWith(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + searchString := call.Argument(0) + if isRegexp(searchString) { + panic(r.NewTypeError("First argument to String.prototype.startsWith must not be a regular expression")) + } + searchStr := searchString.toString() + l := int64(s.Length()) + var pos int64 + if posArg := call.Argument(1); posArg != _undefined { + pos = posArg.ToInteger() + } + start := toIntStrict(min(max(pos, 0), l)) + searchLength := searchStr.Length() + if int64(searchLength+start) > l { + return valueFalse + } + for i := 0; i < searchLength; i++ { + if s.CharAt(start+i) != searchStr.CharAt(i) { + return valueFalse + } + } + return valueTrue +} + +func (r *Runtime) stringproto_substring(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + l := int64(s.Length()) + intStart := call.Argument(0).ToInteger() + var intEnd int64 + if end := call.Argument(1); end != _undefined { + intEnd = end.ToInteger() + } else { + intEnd = l + } + if intStart < 0 { + intStart = 0 + } else if intStart > l { + intStart = l + } + + if intEnd < 0 { + intEnd = 0 + } else if intEnd > l { + intEnd = l + } + + if intStart > intEnd { + intStart, intEnd = intEnd, intStart + } + + return s.Substring(int(intStart), int(intEnd)) +} + +func (r *Runtime) stringproto_toLowerCase(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + return s.toLower() +} + +func (r *Runtime) stringproto_toUpperCase(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + return s.toUpper() +} + +func (r *Runtime) stringproto_trim(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + // TODO handle invalid UTF-16 + return newStringValue(strings.Trim(s.String(), parser.WhitespaceChars)) +} + +func (r *Runtime) stringproto_trimEnd(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + // TODO handle invalid UTF-16 + return newStringValue(strings.TrimRight(s.String(), parser.WhitespaceChars)) +} + +func (r *Runtime) stringproto_trimStart(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + + // TODO handle invalid UTF-16 + return newStringValue(strings.TrimLeft(s.String(), parser.WhitespaceChars)) +} + +func (r *Runtime) stringproto_substr(call FunctionCall) Value { + r.checkObjectCoercible(call.This) + s := call.This.toString() + start := call.Argument(0).ToInteger() + var length int64 + sl := int64(s.Length()) + if arg := call.Argument(1); arg != _undefined { + length = arg.ToInteger() + } else { + length = sl + } + + if start < 0 { + start = max(sl+start, 0) + } + + length = min(max(length, 0), sl-start) + if length <= 0 { + return stringEmpty + } + + return s.Substring(int(start), int(start+length)) +} + +func (r *Runtime) stringIterProto_next(call FunctionCall) Value { + thisObj := r.toObject(call.This) + if iter, ok := thisObj.self.(*stringIterObject); ok { + return iter.next() + } + panic(r.NewTypeError("Method String Iterator.prototype.next called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) +} + +func (r *Runtime) createStringIterProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.getIteratorPrototype(), classObject) + + o._putProp("next", r.newNativeFunc(r.stringIterProto_next, "next", 0), true, false, true) + o._putSym(SymToStringTag, valueProp(asciiString(classStringIterator), false, false, true)) + + return o +} + +func (r *Runtime) getStringIteratorPrototype() *Object { + var o *Object + if o = r.global.StringIteratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.StringIteratorPrototype = o + o.self = r.createStringIterProto(o) + } + return o +} + +func createStringProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("length", func(r *Runtime) Value { return valueProp(intToValue(0), false, false, false) }) + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getString(), true, false, true) }) + + t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.stringproto_at, "at", 1) }) + t.putStr("charAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_charAt, "charAt", 1) }) + t.putStr("charCodeAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_charCodeAt, "charCodeAt", 1) }) + t.putStr("codePointAt", func(r *Runtime) Value { return r.methodProp(r.stringproto_codePointAt, "codePointAt", 1) }) + t.putStr("concat", func(r *Runtime) Value { return r.methodProp(r.stringproto_concat, "concat", 1) }) + t.putStr("endsWith", func(r *Runtime) Value { return r.methodProp(r.stringproto_endsWith, "endsWith", 1) }) + t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.stringproto_includes, "includes", 1) }) + t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_indexOf, "indexOf", 1) }) + t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_lastIndexOf, "lastIndexOf", 1) }) + t.putStr("localeCompare", func(r *Runtime) Value { return r.methodProp(r.stringproto_localeCompare, "localeCompare", 1) }) + t.putStr("match", func(r *Runtime) Value { return r.methodProp(r.stringproto_match, "match", 1) }) + t.putStr("matchAll", func(r *Runtime) Value { return r.methodProp(r.stringproto_matchAll, "matchAll", 1) }) + t.putStr("normalize", func(r *Runtime) Value { return r.methodProp(r.stringproto_normalize, "normalize", 0) }) + t.putStr("padEnd", func(r *Runtime) Value { return r.methodProp(r.stringproto_padEnd, "padEnd", 1) }) + t.putStr("padStart", func(r *Runtime) Value { return r.methodProp(r.stringproto_padStart, "padStart", 1) }) + t.putStr("repeat", func(r *Runtime) Value { return r.methodProp(r.stringproto_repeat, "repeat", 1) }) + t.putStr("replace", func(r *Runtime) Value { return r.methodProp(r.stringproto_replace, "replace", 2) }) + t.putStr("replaceAll", func(r *Runtime) Value { return r.methodProp(r.stringproto_replaceAll, "replaceAll", 2) }) + t.putStr("search", func(r *Runtime) Value { return r.methodProp(r.stringproto_search, "search", 1) }) + t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.stringproto_slice, "slice", 2) }) + t.putStr("split", func(r *Runtime) Value { return r.methodProp(r.stringproto_split, "split", 2) }) + t.putStr("startsWith", func(r *Runtime) Value { return r.methodProp(r.stringproto_startsWith, "startsWith", 1) }) + t.putStr("substring", func(r *Runtime) Value { return r.methodProp(r.stringproto_substring, "substring", 2) }) + t.putStr("toLocaleLowerCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toLowerCase, "toLocaleLowerCase", 0) }) + t.putStr("toLocaleUpperCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toUpperCase, "toLocaleUpperCase", 0) }) + t.putStr("toLowerCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toLowerCase, "toLowerCase", 0) }) + t.putStr("toString", func(r *Runtime) Value { return r.methodProp(r.stringproto_toString, "toString", 0) }) + t.putStr("toUpperCase", func(r *Runtime) Value { return r.methodProp(r.stringproto_toUpperCase, "toUpperCase", 0) }) + t.putStr("trim", func(r *Runtime) Value { return r.methodProp(r.stringproto_trim, "trim", 0) }) + t.putStr("trimEnd", func(r *Runtime) Value { return valueProp(r.getStringproto_trimEnd(), true, false, true) }) + t.putStr("trimStart", func(r *Runtime) Value { return valueProp(r.getStringproto_trimStart(), true, false, true) }) + t.putStr("trimRight", func(r *Runtime) Value { return valueProp(r.getStringproto_trimEnd(), true, false, true) }) + t.putStr("trimLeft", func(r *Runtime) Value { return valueProp(r.getStringproto_trimStart(), true, false, true) }) + t.putStr("valueOf", func(r *Runtime) Value { return r.methodProp(r.stringproto_valueOf, "valueOf", 0) }) + + // Annex B + t.putStr("substr", func(r *Runtime) Value { return r.methodProp(r.stringproto_substr, "substr", 2) }) + + t.putSym(SymIterator, func(r *Runtime) Value { + return valueProp(r.newNativeFunc(r.stringproto_iterator, "[Symbol.iterator]", 0), true, false, true) + }) + + return t +} + +func (r *Runtime) getStringproto_trimEnd() *Object { + ret := r.global.stringproto_trimEnd + if ret == nil { + ret = r.newNativeFunc(r.stringproto_trimEnd, "trimEnd", 0) + r.global.stringproto_trimEnd = ret + } + return ret +} + +func (r *Runtime) getStringproto_trimStart() *Object { + ret := r.global.stringproto_trimStart + if ret == nil { + ret = r.newNativeFunc(r.stringproto_trimStart, "trimStart", 0) + r.global.stringproto_trimStart = ret + } + return ret +} + +func (r *Runtime) getStringSingleton() *stringObject { + ret := r.stringSingleton + if ret == nil { + ret = r.builtin_new(r.getString(), nil).self.(*stringObject) + r.stringSingleton = ret + } + return ret +} + +func (r *Runtime) getString() *Object { + ret := r.global.String + if ret == nil { + ret = &Object{runtime: r} + r.global.String = ret + proto := r.getStringPrototype() + o := r.newNativeFuncAndConstruct(ret, r.builtin_String, r.wrapNativeConstruct(r.builtin_newString, ret, proto), proto, "String", intToValue(1)) + ret.self = o + o._putProp("fromCharCode", r.newNativeFunc(r.string_fromcharcode, "fromCharCode", 1), true, false, true) + o._putProp("fromCodePoint", r.newNativeFunc(r.string_fromcodepoint, "fromCodePoint", 1), true, false, true) + o._putProp("raw", r.newNativeFunc(r.string_raw, "raw", 1), true, false, true) + } + return ret +} + +var stringProtoTemplate *objectTemplate +var stringProtoTemplateOnce sync.Once + +func getStringProtoTemplate() *objectTemplate { + stringProtoTemplateOnce.Do(func() { + stringProtoTemplate = createStringProtoTemplate() + }) + return stringProtoTemplate +} + +func (r *Runtime) getStringPrototype() *Object { + ret := r.global.StringPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.StringPrototype = ret + o := r.newTemplatedObject(getStringProtoTemplate(), ret) + o.class = classString + } + return ret +} diff --git a/pkg/xscript/engine/builtin_string_test.go b/pkg/xscript/engine/builtin_string_test.go new file mode 100644 index 0000000..78e4407 --- /dev/null +++ b/pkg/xscript/engine/builtin_string_test.go @@ -0,0 +1,278 @@ +package engine + +import "testing" + +func TestSubstr(t *testing.T) { + const SCRIPT = ` +assert.sameValue('abc'.substr(0, false), '', 'start: 0, length: false'); +assert.sameValue('abc'.substr(1, false), '', 'start: 1, length: false'); +assert.sameValue('abc'.substr(2, false), '', 'start: 2, length: false'); +assert.sameValue('abc'.substr(3, false), '', 'start: 3, length: false'); + +assert.sameValue('abc'.substr(0, NaN), '', 'start: 0, length: NaN'); +assert.sameValue('abc'.substr(1, NaN), '', 'start: 1, length: NaN'); +assert.sameValue('abc'.substr(2, NaN), '', 'start: 2, length: NaN'); +assert.sameValue('abc'.substr(3, NaN), '', 'start: 3, length: NaN'); + +assert.sameValue('abc'.substr(0, ''), '', 'start: 0, length: ""'); +assert.sameValue('abc'.substr(1, ''), '', 'start: 1, length: ""'); +assert.sameValue('abc'.substr(2, ''), '', 'start: 2, length: ""'); +assert.sameValue('abc'.substr(3, ''), '', 'start: 3, length: ""'); + +assert.sameValue('abc'.substr(0, null), '', 'start: 0, length: null'); +assert.sameValue('abc'.substr(1, null), '', 'start: 1, length: null'); +assert.sameValue('abc'.substr(2, null), '', 'start: 2, length: null'); +assert.sameValue('abc'.substr(3, null), '', 'start: 3, length: null'); + +assert.sameValue('abc'.substr(0, -1), '', '0, -1'); +assert.sameValue('abc'.substr(0, -2), '', '0, -2'); +assert.sameValue('abc'.substr(0, -3), '', '0, -3'); +assert.sameValue('abc'.substr(0, -4), '', '0, -4'); + +assert.sameValue('abc'.substr(1, -1), '', '1, -1'); +assert.sameValue('abc'.substr(1, -2), '', '1, -2'); +assert.sameValue('abc'.substr(1, -3), '', '1, -3'); +assert.sameValue('abc'.substr(1, -4), '', '1, -4'); + +assert.sameValue('abc'.substr(2, -1), '', '2, -1'); +assert.sameValue('abc'.substr(2, -2), '', '2, -2'); +assert.sameValue('abc'.substr(2, -3), '', '2, -3'); +assert.sameValue('abc'.substr(2, -4), '', '2, -4'); + +assert.sameValue('abc'.substr(3, -1), '', '3, -1'); +assert.sameValue('abc'.substr(3, -2), '', '3, -2'); +assert.sameValue('abc'.substr(3, -3), '', '3, -3'); +assert.sameValue('abc'.substr(3, -4), '', '3, -4'); + +assert.sameValue('abc'.substr(0, 1), 'a', '0, 1'); +assert.sameValue('abc'.substr(0, 2), 'ab', '0, 1'); +assert.sameValue('abc'.substr(0, 3), 'abc', '0, 1'); +assert.sameValue('abc'.substr(0, 4), 'abc', '0, 1'); + +assert.sameValue('abc'.substr(1, 1), 'b', '1, 1'); +assert.sameValue('abc'.substr(1, 2), 'bc', '1, 1'); +assert.sameValue('abc'.substr(1, 3), 'bc', '1, 1'); +assert.sameValue('abc'.substr(1, 4), 'bc', '1, 1'); + +assert.sameValue('abc'.substr(2, 1), 'c', '2, 1'); +assert.sameValue('abc'.substr(2, 2), 'c', '2, 1'); +assert.sameValue('abc'.substr(2, 3), 'c', '2, 1'); +assert.sameValue('abc'.substr(2, 4), 'c', '2, 1'); + +assert.sameValue('abc'.substr(3, 1), '', '3, 1'); +assert.sameValue('abc'.substr(3, 2), '', '3, 1'); +assert.sameValue('abc'.substr(3, 3), '', '3, 1'); +assert.sameValue('abc'.substr(3, 4), '', '3, 1'); + +assert.sameValue('abc'.substr(0), 'abc', 'start: 0, length: unspecified'); +assert.sameValue('abc'.substr(1), 'bc', 'start: 1, length: unspecified'); +assert.sameValue('abc'.substr(2), 'c', 'start: 2, length: unspecified'); +assert.sameValue('abc'.substr(3), '', 'start: 3, length: unspecified'); + +assert.sameValue( + 'abc'.substr(0, undefined), 'abc', 'start: 0, length: undefined' +); +assert.sameValue( + 'abc'.substr(1, undefined), 'bc', 'start: 1, length: undefined' +); +assert.sameValue( + 'abc'.substr(2, undefined), 'c', 'start: 2, length: undefined' +); +assert.sameValue( + 'abc'.substr(3, undefined), '', 'start: 3, length: undefined' +); + +assert.sameValue('A—', String.fromCharCode(65, 0x2014)); + + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestStringMatchSym(t *testing.T) { + const SCRIPT = ` +function Prefix(p) { + this.p = p; +} + +Prefix.prototype[Symbol.match] = function(s) { + return s.substring(0, this.p.length) === this.p; +} + +var prefix1 = new Prefix("abc"); +var prefix2 = new Prefix("def"); + +"abc123".match(prefix1) === true && "abc123".match(prefix2) === false && +"def123".match(prefix1) === false && "def123".match(prefix2) === true; +` + testScript(SCRIPT, valueTrue, t) +} + +func TestStringMatchAllSym(t *testing.T) { + const SCRIPT = ` +function Prefix(p) { + this.p = p; +} + +Prefix.prototype[Symbol.matchAll] = function(s) { + return s.substring(0, this.p.length) === this.p; +} + +var prefix1 = new Prefix("abc"); +var prefix2 = new Prefix("def"); + +"abc123".matchAll(prefix1) === true && "abc123".matchAll(prefix2) === false && +"def123".matchAll(prefix1) === false && "def123".matchAll(prefix2) === true; +` + testScript(SCRIPT, valueTrue, t) +} + +func TestGenericSplitter(t *testing.T) { + const SCRIPT = ` +function MyRegexp(pattern, flags) { + if (pattern instanceof MyRegexp) { + pattern = pattern.wrapped; + } + this.wrapped = new RegExp(pattern, flags); +} + +MyRegexp.prototype.exec = function() { + return this.wrapped.exec.apply(this.wrapped, arguments); +} + +Object.defineProperty(MyRegexp.prototype, "lastIndex", { + get: function() { + return this.wrapped.lastIndex; + }, + set: function(v) { + this.wrapped.lastIndex = v; + } +}); + +Object.defineProperty(MyRegexp.prototype, "flags", { + get: function() { + return this.wrapped.flags; + } +}); + +MyRegexp[Symbol.species] = MyRegexp; +MyRegexp.prototype[Symbol.split] = RegExp.prototype[Symbol.split]; + +var r = new MyRegexp(/ /); +var res = "a b c".split(r); +res.length === 3 && res[0] === "a" && res[1] === "b" && res[2] === "c"; +` + testScript(SCRIPT, valueTrue, t) +} + +func TestStringIterSurrPair(t *testing.T) { + const SCRIPT = ` +var lo = '\uD834'; +var hi = '\uDF06'; +var pair = lo + hi; +var string = 'a' + pair + 'b' + lo + pair + hi + lo; +var iterator = string[Symbol.iterator](); +var result; + +result = iterator.next(); +if (result.value !== 'a') { + throw new Error("at 0: " + result.value); +} +result = iterator.next(); +if (result.value !== pair) { + throw new Error("at 1: " + result.value); +} + +` + testScript(SCRIPT, _undefined, t) +} + +func TestValueStringBuilder(t *testing.T) { + t.Run("substringASCII", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + str := newStringValue("a\U00010000b") + sb.WriteSubstring(str, 0, 1) + res := sb.String() + if res != asciiString("a") { + t.Fatal(res) + } + }) + + t.Run("substringASCIIPure", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + str := newStringValue("ab") + sb.WriteSubstring(str, 0, 1) + res := sb.String() + if res != asciiString("a") { + t.Fatal(res) + } + }) + + t.Run("substringUnicode", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + str := newStringValue("a\U00010000b") + sb.WriteSubstring(str, 1, 3) + res := sb.String() + if !res.SameAs(unicodeStringFromRunes([]rune{0x10000})) { + t.Fatal(res) + } + }) + + t.Run("substringASCIIUnicode", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + str := newStringValue("a\U00010000b") + sb.WriteSubstring(str, 0, 2) + res := sb.String() + if !res.SameAs(unicodeStringFromRunes([]rune{'a', 0xD800})) { + t.Fatal(res) + } + }) + + t.Run("substringUnicodeASCII", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + str := newStringValue("a\U00010000b") + sb.WriteSubstring(str, 2, 4) + res := sb.String() + if !res.SameAs(unicodeStringFromRunes([]rune{0xDC00, 'b'})) { + t.Fatal(res) + } + }) + + t.Run("concatSubstringUnicodeASCII", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + sb.WriteString(newStringValue("юникод")) + sb.WriteSubstring(asciiString(" ascii"), 0, 6) + if res := sb.String(); !res.SameAs(newStringValue("юникод ascii")) { + t.Fatal(res) + } + }) + + t.Run("concat_ASCII_importedASCII", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + sb.WriteString(asciiString("ascii")) + sb.WriteString(&importedString{s: " imported_ascii1234567890"}) + s := sb.String() + if res, ok := s.(asciiString); !ok || res != "ascii imported_ascii1234567890" { + t.Fatal(s) + } + }) + + t.Run("concat_ASCII_importedUnicode", func(t *testing.T) { + t.Parallel() + var sb StringBuilder + sb.WriteString(asciiString("ascii")) + sb.WriteString(&importedString{s: " imported_юникод"}) + s := sb.String() + if res, ok := s.(unicodeString); !ok || !res.SameAs(newStringValue("ascii imported_юникод")) { + t.Fatal(s) + } + }) + +} diff --git a/pkg/xscript/engine/builtin_symbol.go b/pkg/xscript/engine/builtin_symbol.go new file mode 100644 index 0000000..af09e39 --- /dev/null +++ b/pkg/xscript/engine/builtin_symbol.go @@ -0,0 +1,177 @@ +package engine + +import "pandax/pkg/xscript/engine/unistring" + +var ( + SymHasInstance = newSymbol(asciiString("Symbol.hasInstance")) + SymIsConcatSpreadable = newSymbol(asciiString("Symbol.isConcatSpreadable")) + SymIterator = newSymbol(asciiString("Symbol.iterator")) + SymMatch = newSymbol(asciiString("Symbol.match")) + SymMatchAll = newSymbol(asciiString("Symbol.matchAll")) + SymReplace = newSymbol(asciiString("Symbol.replace")) + SymSearch = newSymbol(asciiString("Symbol.search")) + SymSpecies = newSymbol(asciiString("Symbol.species")) + SymSplit = newSymbol(asciiString("Symbol.split")) + SymToPrimitive = newSymbol(asciiString("Symbol.toPrimitive")) + SymToStringTag = newSymbol(asciiString("Symbol.toStringTag")) + SymUnscopables = newSymbol(asciiString("Symbol.unscopables")) +) + +func (r *Runtime) builtin_symbol(call FunctionCall) Value { + var desc String + if arg := call.Argument(0); !IsUndefined(arg) { + desc = arg.toString() + } + return newSymbol(desc) +} + +func (r *Runtime) symbolproto_tostring(call FunctionCall) Value { + sym, ok := call.This.(*Symbol) + if !ok { + if obj, ok := call.This.(*Object); ok { + if v, ok := obj.self.(*primitiveValueObject); ok { + if sym1, ok := v.pValue.(*Symbol); ok { + sym = sym1 + } + } + } + } + if sym == nil { + panic(r.NewTypeError("Method Symbol.prototype.toString is called on incompatible receiver")) + } + return sym.descriptiveString() +} + +func (r *Runtime) symbolproto_valueOf(call FunctionCall) Value { + _, ok := call.This.(*Symbol) + if ok { + return call.This + } + + if obj, ok := call.This.(*Object); ok { + if v, ok := obj.self.(*primitiveValueObject); ok { + if sym, ok := v.pValue.(*Symbol); ok { + return sym + } + } + } + + panic(r.NewTypeError("Symbol.prototype.valueOf requires that 'this' be a Symbol")) +} + +func (r *Runtime) symbol_for(call FunctionCall) Value { + key := call.Argument(0).toString() + keyStr := key.string() + if v := r.symbolRegistry[keyStr]; v != nil { + return v + } + if r.symbolRegistry == nil { + r.symbolRegistry = make(map[unistring.String]*Symbol) + } + v := newSymbol(key) + r.symbolRegistry[keyStr] = v + return v +} + +func (r *Runtime) symbol_keyfor(call FunctionCall) Value { + arg := call.Argument(0) + sym, ok := arg.(*Symbol) + if !ok { + panic(r.NewTypeError("%s is not a symbol", arg.String())) + } + for key, s := range r.symbolRegistry { + if s == sym { + return stringValueFromRaw(key) + } + } + return _undefined +} + +func (r *Runtime) thisSymbolValue(v Value) *Symbol { + if sym, ok := v.(*Symbol); ok { + return sym + } + if obj, ok := v.(*Object); ok { + if pVal, ok := obj.self.(*primitiveValueObject); ok { + if sym, ok := pVal.pValue.(*Symbol); ok { + return sym + } + } + } + panic(r.NewTypeError("Value is not a Symbol")) +} + +func (r *Runtime) createSymbolProto(val *Object) objectImpl { + o := &baseObject{ + class: classObject, + val: val, + extensible: true, + prototype: r.global.ObjectPrototype, + } + o.init() + + o._putProp("constructor", r.getSymbol(), true, false, true) + o.setOwnStr("description", &valueProperty{ + configurable: true, + getterFunc: r.newNativeFunc(func(call FunctionCall) Value { + return r.thisSymbolValue(call.This).desc + }, "get description", 0), + accessor: true, + }, false) + o._putProp("toString", r.newNativeFunc(r.symbolproto_tostring, "toString", 0), true, false, true) + o._putProp("valueOf", r.newNativeFunc(r.symbolproto_valueOf, "valueOf", 0), true, false, true) + o._putSym(SymToPrimitive, valueProp(r.newNativeFunc(r.symbolproto_valueOf, "[Symbol.toPrimitive]", 1), false, false, true)) + o._putSym(SymToStringTag, valueProp(newStringValue("Symbol"), false, false, true)) + + return o +} + +func (r *Runtime) createSymbol(val *Object) objectImpl { + o := r.newNativeFuncAndConstruct(val, r.builtin_symbol, func(args []Value, newTarget *Object) *Object { + panic(r.NewTypeError("Symbol is not a constructor")) + }, r.getSymbolPrototype(), "Symbol", _positiveZero) + + o._putProp("for", r.newNativeFunc(r.symbol_for, "for", 1), true, false, true) + o._putProp("keyFor", r.newNativeFunc(r.symbol_keyfor, "keyFor", 1), true, false, true) + + for _, s := range []*Symbol{ + SymHasInstance, + SymIsConcatSpreadable, + SymIterator, + SymMatch, + SymMatchAll, + SymReplace, + SymSearch, + SymSpecies, + SymSplit, + SymToPrimitive, + SymToStringTag, + SymUnscopables, + } { + n := s.desc.(asciiString) + n = n[len("Symbol."):] + o._putProp(unistring.String(n), s, false, false, false) + } + + return o +} + +func (r *Runtime) getSymbolPrototype() *Object { + ret := r.global.SymbolPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.SymbolPrototype = ret + ret.self = r.createSymbolProto(ret) + } + return ret +} + +func (r *Runtime) getSymbol() *Object { + ret := r.global.Symbol + if ret == nil { + ret = &Object{runtime: r} + r.global.Symbol = ret + ret.self = r.createSymbol(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_typedarrays.go b/pkg/xscript/engine/builtin_typedarrays.go new file mode 100644 index 0000000..a113877 --- /dev/null +++ b/pkg/xscript/engine/builtin_typedarrays.go @@ -0,0 +1,1780 @@ +package engine + +import ( + "fmt" + "math" + "sort" + "sync" + "unsafe" + + "pandax/pkg/xscript/engine/unistring" +) + +type typedArraySortCtx struct { + ta *typedArrayObject + compare func(FunctionCall) Value + needValidate bool +} + +func (ctx *typedArraySortCtx) Len() int { + return ctx.ta.length +} + +func (ctx *typedArraySortCtx) Less(i, j int) bool { + if ctx.needValidate { + ctx.ta.viewedArrayBuf.ensureNotDetached(true) + ctx.needValidate = false + } + offset := ctx.ta.offset + if ctx.compare != nil { + x := ctx.ta.typedArray.get(offset + i) + y := ctx.ta.typedArray.get(offset + j) + res := ctx.compare(FunctionCall{ + This: _undefined, + Arguments: []Value{x, y}, + }).ToNumber() + ctx.needValidate = true + if i, ok := res.(valueInt); ok { + return i < 0 + } + f := res.ToFloat() + if f < 0 { + return true + } + if f > 0 { + return false + } + if math.Signbit(f) { + return true + } + return false + } + + return ctx.ta.typedArray.less(offset+i, offset+j) +} + +func (ctx *typedArraySortCtx) Swap(i, j int) { + if ctx.needValidate { + ctx.ta.viewedArrayBuf.ensureNotDetached(true) + ctx.needValidate = false + } + offset := ctx.ta.offset + ctx.ta.typedArray.swap(offset+i, offset+j) +} + +func allocByteSlice(size int) (b []byte) { + defer func() { + if x := recover(); x != nil { + panic(rangeError(fmt.Sprintf("Buffer size is too large: %d", size))) + } + }() + if size < 0 { + panic(rangeError(fmt.Sprintf("Invalid buffer size: %d", size))) + } + b = make([]byte, size) + return +} + +func (r *Runtime) builtin_newArrayBuffer(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("ArrayBuffer")) + } + b := r._newArrayBuffer(r.getPrototypeFromCtor(newTarget, r.getArrayBuffer(), r.getArrayBufferPrototype()), nil) + if len(args) > 0 { + b.data = allocByteSlice(r.toIndex(args[0])) + } + return b.val +} + +func (r *Runtime) arrayBufferProto_getByteLength(call FunctionCall) Value { + o := r.toObject(call.This) + if b, ok := o.self.(*arrayBufferObject); ok { + if b.ensureNotDetached(false) { + return intToValue(int64(len(b.data))) + } + return intToValue(0) + } + panic(r.NewTypeError("Object is not ArrayBuffer: %s", o)) +} + +func (r *Runtime) arrayBufferProto_slice(call FunctionCall) Value { + o := r.toObject(call.This) + if b, ok := o.self.(*arrayBufferObject); ok { + l := int64(len(b.data)) + start := relToIdx(call.Argument(0).ToInteger(), l) + var stop int64 + if arg := call.Argument(1); arg != _undefined { + stop = arg.ToInteger() + } else { + stop = l + } + stop = relToIdx(stop, l) + newLen := max(stop-start, 0) + ret := r.speciesConstructor(o, r.getArrayBuffer())([]Value{intToValue(newLen)}, nil) + if ab, ok := ret.self.(*arrayBufferObject); ok { + if newLen > 0 { + b.ensureNotDetached(true) + if ret == o { + panic(r.NewTypeError("Species constructor returned the same ArrayBuffer")) + } + if int64(len(ab.data)) < newLen { + panic(r.NewTypeError("Species constructor returned an ArrayBuffer that is too small: %d", len(ab.data))) + } + ab.ensureNotDetached(true) + copy(ab.data, b.data[start:stop]) + } + return ret + } + panic(r.NewTypeError("Species constructor did not return an ArrayBuffer: %s", ret.String())) + } + panic(r.NewTypeError("Object is not ArrayBuffer: %s", o)) +} + +func (r *Runtime) arrayBuffer_isView(call FunctionCall) Value { + if o, ok := call.Argument(0).(*Object); ok { + if _, ok := o.self.(*dataViewObject); ok { + return valueTrue + } + if _, ok := o.self.(*typedArrayObject); ok { + return valueTrue + } + } + return valueFalse +} + +func (r *Runtime) newDataView(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("DataView")) + } + proto := r.getPrototypeFromCtor(newTarget, r.getDataView(), r.getDataViewPrototype()) + var bufArg Value + if len(args) > 0 { + bufArg = args[0] + } + var buffer *arrayBufferObject + if o, ok := bufArg.(*Object); ok { + if b, ok := o.self.(*arrayBufferObject); ok { + buffer = b + } + } + if buffer == nil { + panic(r.NewTypeError("First argument to DataView constructor must be an ArrayBuffer")) + } + var byteOffset, byteLen int + if len(args) > 1 { + offsetArg := nilSafe(args[1]) + byteOffset = r.toIndex(offsetArg) + buffer.ensureNotDetached(true) + if byteOffset > len(buffer.data) { + panic(r.newError(r.getRangeError(), "Start offset %s is outside the bounds of the buffer", offsetArg.String())) + } + } + if len(args) > 2 && args[2] != nil && args[2] != _undefined { + byteLen = r.toIndex(args[2]) + if byteOffset+byteLen > len(buffer.data) { + panic(r.newError(r.getRangeError(), "Invalid DataView length %d", byteLen)) + } + } else { + byteLen = len(buffer.data) - byteOffset + } + o := &Object{runtime: r} + b := &dataViewObject{ + baseObject: baseObject{ + class: classObject, + val: o, + prototype: proto, + extensible: true, + }, + viewedArrayBuf: buffer, + byteOffset: byteOffset, + byteLen: byteLen, + } + o.self = b + b.init() + return o +} + +func (r *Runtime) dataViewProto_getBuffer(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return dv.viewedArrayBuf.val + } + panic(r.NewTypeError("Method get DataView.prototype.buffer called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getByteLen(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + dv.viewedArrayBuf.ensureNotDetached(true) + return intToValue(int64(dv.byteLen)) + } + panic(r.NewTypeError("Method get DataView.prototype.byteLength called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getByteOffset(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + dv.viewedArrayBuf.ensureNotDetached(true) + return intToValue(int64(dv.byteOffset)) + } + panic(r.NewTypeError("Method get DataView.prototype.byteOffset called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getFloat32(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return floatToValue(float64(dv.viewedArrayBuf.getFloat32(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 4)))) + } + panic(r.NewTypeError("Method DataView.prototype.getFloat32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getFloat64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return floatToValue(dv.viewedArrayBuf.getFloat64(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 8))) + } + panic(r.NewTypeError("Method DataView.prototype.getFloat64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getInt8(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idx, _ := dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 1) + return intToValue(int64(dv.viewedArrayBuf.getInt8(idx))) + } + panic(r.NewTypeError("Method DataView.prototype.getInt8 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getInt16(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return intToValue(int64(dv.viewedArrayBuf.getInt16(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 2)))) + } + panic(r.NewTypeError("Method DataView.prototype.getInt16 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getInt32(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return intToValue(int64(dv.viewedArrayBuf.getInt32(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 4)))) + } + panic(r.NewTypeError("Method DataView.prototype.getInt32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getUint8(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idx, _ := dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 1) + return intToValue(int64(dv.viewedArrayBuf.getUint8(idx))) + } + panic(r.NewTypeError("Method DataView.prototype.getUint8 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getUint16(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return intToValue(int64(dv.viewedArrayBuf.getUint16(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 2)))) + } + panic(r.NewTypeError("Method DataView.prototype.getUint16 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_getUint32(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + return intToValue(int64(dv.viewedArrayBuf.getUint32(dv.getIdxAndByteOrder(r.toIndex(call.Argument(0)), call.Argument(1), 4)))) + } + panic(r.NewTypeError("Method DataView.prototype.getUint32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setFloat32(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toFloat32(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 4) + dv.viewedArrayBuf.setFloat32(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setFloat32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setFloat64(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := call.Argument(1).ToFloat() + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 8) + dv.viewedArrayBuf.setFloat64(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setFloat64 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setInt8(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toInt8(call.Argument(1)) + idx, _ := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 1) + dv.viewedArrayBuf.setInt8(idx, val) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setInt8 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setInt16(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toInt16(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 2) + dv.viewedArrayBuf.setInt16(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setInt16 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setInt32(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toInt32(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 4) + dv.viewedArrayBuf.setInt32(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setInt32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setUint8(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toUint8(call.Argument(1)) + idx, _ := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 1) + dv.viewedArrayBuf.setUint8(idx, val) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setUint8 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setUint16(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toUint16(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 2) + dv.viewedArrayBuf.setUint16(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setUint16 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) dataViewProto_setUint32(call FunctionCall) Value { + if dv, ok := r.toObject(call.This).self.(*dataViewObject); ok { + idxVal := r.toIndex(call.Argument(0)) + val := toUint32(call.Argument(1)) + idx, bo := dv.getIdxAndByteOrder(idxVal, call.Argument(2), 4) + dv.viewedArrayBuf.setUint32(idx, val, bo) + return _undefined + } + panic(r.NewTypeError("Method DataView.prototype.setUint32 called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_getBuffer(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + return ta.viewedArrayBuf.val + } + panic(r.NewTypeError("Method get TypedArray.prototype.buffer called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_getByteLen(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + if ta.viewedArrayBuf.data == nil { + return _positiveZero + } + return intToValue(int64(ta.length) * int64(ta.elemSize)) + } + panic(r.NewTypeError("Method get TypedArray.prototype.byteLength called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_getLength(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + if ta.viewedArrayBuf.data == nil { + return _positiveZero + } + return intToValue(int64(ta.length)) + } + panic(r.NewTypeError("Method get TypedArray.prototype.length called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_getByteOffset(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + if ta.viewedArrayBuf.data == nil { + return _positiveZero + } + return intToValue(int64(ta.offset) * int64(ta.elemSize)) + } + panic(r.NewTypeError("Method get TypedArray.prototype.byteOffset called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_copyWithin(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + l := int64(ta.length) + var relEnd int64 + to := toIntStrict(relToIdx(call.Argument(0).ToInteger(), l)) + from := toIntStrict(relToIdx(call.Argument(1).ToInteger(), l)) + if end := call.Argument(2); end != _undefined { + relEnd = end.ToInteger() + } else { + relEnd = l + } + final := toIntStrict(relToIdx(relEnd, l)) + data := ta.viewedArrayBuf.data + offset := ta.offset + elemSize := ta.elemSize + if final > from { + ta.viewedArrayBuf.ensureNotDetached(true) + copy(data[(offset+to)*elemSize:], data[(offset+from)*elemSize:(offset+final)*elemSize]) + } + return call.This + } + panic(r.NewTypeError("Method TypedArray.prototype.copyWithin called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_entries(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + return r.createArrayIterator(ta.val, iterationKindKeyValue) + } + panic(r.NewTypeError("Method TypedArray.prototype.entries called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_every(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := 0; k < ta.length; k++ { + if ta.isValidIntegerIndex(k) { + fc.Arguments[0] = ta.typedArray.get(ta.offset + k) + } else { + fc.Arguments[0] = _undefined + } + fc.Arguments[1] = intToValue(int64(k)) + if !callbackFn(fc).ToBoolean() { + return valueFalse + } + } + return valueTrue + + } + panic(r.NewTypeError("Method TypedArray.prototype.every called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_fill(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + l := int64(ta.length) + k := toIntStrict(relToIdx(call.Argument(1).ToInteger(), l)) + var relEnd int64 + if endArg := call.Argument(2); endArg != _undefined { + relEnd = endArg.ToInteger() + } else { + relEnd = l + } + final := toIntStrict(relToIdx(relEnd, l)) + value := ta.typedArray.toRaw(call.Argument(0)) + ta.viewedArrayBuf.ensureNotDetached(true) + for ; k < final; k++ { + ta.typedArray.setRaw(ta.offset+k, value) + } + return call.This + } + panic(r.NewTypeError("Method TypedArray.prototype.fill called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_filter(call FunctionCall) Value { + o := r.toObject(call.This) + if ta, ok := o.self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + buf := make([]byte, 0, ta.length*ta.elemSize) + captured := 0 + rawVal := make([]byte, ta.elemSize) + for k := 0; k < ta.length; k++ { + if ta.isValidIntegerIndex(k) { + fc.Arguments[0] = ta.typedArray.get(ta.offset + k) + i := (ta.offset + k) * ta.elemSize + copy(rawVal, ta.viewedArrayBuf.data[i:]) + } else { + fc.Arguments[0] = _undefined + for i := range rawVal { + rawVal[i] = 0 + } + } + fc.Arguments[1] = intToValue(int64(k)) + if callbackFn(fc).ToBoolean() { + buf = append(buf, rawVal...) + captured++ + } + } + c := r.speciesConstructorObj(o, ta.defaultCtor) + ab := r._newArrayBuffer(r.getArrayBufferPrototype(), nil) + ab.data = buf + kept := r.toConstructor(ta.defaultCtor)([]Value{ab.val}, ta.defaultCtor) + if c == ta.defaultCtor { + return kept + } else { + ret := r.typedArrayCreate(c, intToValue(int64(captured))) + keptTa := kept.self.(*typedArrayObject) + for i := 0; i < captured; i++ { + ret.typedArray.set(i, keptTa.typedArray.get(keptTa.offset+i)) + } + return ret.val + } + } + panic(r.NewTypeError("Method TypedArray.prototype.filter called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_find(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := 0; k < ta.length; k++ { + var val Value + if ta.isValidIntegerIndex(k) { + val = ta.typedArray.get(ta.offset + k) + } + fc.Arguments[0] = val + fc.Arguments[1] = intToValue(int64(k)) + if predicate(fc).ToBoolean() { + return val + } + } + return _undefined + } + panic(r.NewTypeError("Method TypedArray.prototype.find called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_findIndex(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := 0; k < ta.length; k++ { + if ta.isValidIntegerIndex(k) { + fc.Arguments[0] = ta.typedArray.get(ta.offset + k) + } else { + fc.Arguments[0] = _undefined + } + fc.Arguments[1] = intToValue(int64(k)) + if predicate(fc).ToBoolean() { + return fc.Arguments[1] + } + } + return intToValue(-1) + } + panic(r.NewTypeError("Method TypedArray.prototype.findIndex called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_findLast(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := ta.length - 1; k >= 0; k-- { + var val Value + if ta.isValidIntegerIndex(k) { + val = ta.typedArray.get(ta.offset + k) + } + fc.Arguments[0] = val + fc.Arguments[1] = intToValue(int64(k)) + if predicate(fc).ToBoolean() { + return val + } + } + return _undefined + } + panic(r.NewTypeError("Method TypedArray.prototype.findLast called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_findLastIndex(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + predicate := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := ta.length - 1; k >= 0; k-- { + if ta.isValidIntegerIndex(k) { + fc.Arguments[0] = ta.typedArray.get(ta.offset + k) + } else { + fc.Arguments[0] = _undefined + } + fc.Arguments[1] = intToValue(int64(k)) + if predicate(fc).ToBoolean() { + return fc.Arguments[1] + } + } + return intToValue(-1) + } + panic(r.NewTypeError("Method TypedArray.prototype.findLastIndex called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_forEach(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := 0; k < ta.length; k++ { + var val Value + if ta.isValidIntegerIndex(k) { + val = ta.typedArray.get(ta.offset + k) + } + fc.Arguments[0] = val + fc.Arguments[1] = intToValue(int64(k)) + callbackFn(fc) + } + return _undefined + } + panic(r.NewTypeError("Method TypedArray.prototype.forEach called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_includes(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + length := int64(ta.length) + if length == 0 { + return valueFalse + } + + n := call.Argument(1).ToInteger() + if n >= length { + return valueFalse + } + + if n < 0 { + n = max(length+n, 0) + } + + searchElement := call.Argument(0) + if searchElement == _negativeZero { + searchElement = _positiveZero + } + startIdx := toIntStrict(n) + if !ta.viewedArrayBuf.ensureNotDetached(false) { + if searchElement == _undefined && startIdx < ta.length { + return valueTrue + } + return valueFalse + } + if ta.typedArray.typeMatch(searchElement) { + se := ta.typedArray.toRaw(searchElement) + for k := startIdx; k < ta.length; k++ { + if ta.typedArray.getRaw(ta.offset+k) == se { + return valueTrue + } + } + } + return valueFalse + } + panic(r.NewTypeError("Method TypedArray.prototype.includes called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_at(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + idx := call.Argument(0).ToInteger() + length := int64(ta.length) + if idx < 0 { + idx = length + idx + } + if idx >= length || idx < 0 { + return _undefined + } + if ta.viewedArrayBuf.ensureNotDetached(false) { + return ta.typedArray.get(ta.offset + int(idx)) + } + return _undefined + } + panic(r.NewTypeError("Method TypedArray.prototype.at called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_indexOf(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + length := int64(ta.length) + if length == 0 { + return intToValue(-1) + } + + n := call.Argument(1).ToInteger() + if n >= length { + return intToValue(-1) + } + + if n < 0 { + n = max(length+n, 0) + } + + if ta.viewedArrayBuf.ensureNotDetached(false) { + searchElement := call.Argument(0) + if searchElement == _negativeZero { + searchElement = _positiveZero + } + if !IsNaN(searchElement) && ta.typedArray.typeMatch(searchElement) { + se := ta.typedArray.toRaw(searchElement) + for k := toIntStrict(n); k < ta.length; k++ { + if ta.typedArray.getRaw(ta.offset+k) == se { + return intToValue(int64(k)) + } + } + } + } + return intToValue(-1) + } + panic(r.NewTypeError("Method TypedArray.prototype.indexOf called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_join(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + s := call.Argument(0) + var sep String + if s != _undefined { + sep = s.toString() + } else { + sep = asciiString(",") + } + l := ta.length + if l == 0 { + return stringEmpty + } + + var buf StringBuilder + + var element0 Value + if ta.isValidIntegerIndex(0) { + element0 = ta.typedArray.get(ta.offset + 0) + } + if element0 != nil && element0 != _undefined && element0 != _null { + buf.WriteString(element0.toString()) + } + + for i := 1; i < l; i++ { + buf.WriteString(sep) + if ta.isValidIntegerIndex(i) { + element := ta.typedArray.get(ta.offset + i) + if element != nil && element != _undefined && element != _null { + buf.WriteString(element.toString()) + } + } + } + + return buf.String() + } + panic(r.NewTypeError("Method TypedArray.prototype.join called on incompatible receiver")) +} + +func (r *Runtime) typedArrayProto_keys(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + return r.createArrayIterator(ta.val, iterationKindKey) + } + panic(r.NewTypeError("Method TypedArray.prototype.keys called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_lastIndexOf(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + length := int64(ta.length) + if length == 0 { + return intToValue(-1) + } + + var fromIndex int64 + + if len(call.Arguments) < 2 { + fromIndex = length - 1 + } else { + fromIndex = call.Argument(1).ToInteger() + if fromIndex >= 0 { + fromIndex = min(fromIndex, length-1) + } else { + fromIndex += length + if fromIndex < 0 { + fromIndex = -1 // prevent underflow in toIntStrict() on 32-bit platforms + } + } + } + + if ta.viewedArrayBuf.ensureNotDetached(false) { + searchElement := call.Argument(0) + if searchElement == _negativeZero { + searchElement = _positiveZero + } + if !IsNaN(searchElement) && ta.typedArray.typeMatch(searchElement) { + se := ta.typedArray.toRaw(searchElement) + for k := toIntStrict(fromIndex); k >= 0; k-- { + if ta.typedArray.getRaw(ta.offset+k) == se { + return intToValue(int64(k)) + } + } + } + } + + return intToValue(-1) + } + panic(r.NewTypeError("Method TypedArray.prototype.lastIndexOf called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_map(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + dst := r.typedArraySpeciesCreate(ta, []Value{intToValue(int64(ta.length))}) + for i := 0; i < ta.length; i++ { + if ta.isValidIntegerIndex(i) { + fc.Arguments[0] = ta.typedArray.get(ta.offset + i) + } else { + fc.Arguments[0] = _undefined + } + fc.Arguments[1] = intToValue(int64(i)) + dst.typedArray.set(i, callbackFn(fc)) + } + return dst.val + } + panic(r.NewTypeError("Method TypedArray.prototype.map called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_reduce(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: _undefined, + Arguments: []Value{nil, nil, nil, call.This}, + } + k := 0 + if len(call.Arguments) >= 2 { + fc.Arguments[0] = call.Argument(1) + } else { + if ta.length > 0 { + fc.Arguments[0] = ta.typedArray.get(ta.offset + 0) + k = 1 + } + } + if fc.Arguments[0] == nil { + panic(r.NewTypeError("Reduce of empty array with no initial value")) + } + for ; k < ta.length; k++ { + if ta.isValidIntegerIndex(k) { + fc.Arguments[1] = ta.typedArray.get(ta.offset + k) + } else { + fc.Arguments[1] = _undefined + } + idx := valueInt(k) + fc.Arguments[2] = idx + fc.Arguments[0] = callbackFn(fc) + } + return fc.Arguments[0] + } + panic(r.NewTypeError("Method TypedArray.prototype.reduce called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_reduceRight(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: _undefined, + Arguments: []Value{nil, nil, nil, call.This}, + } + k := ta.length - 1 + if len(call.Arguments) >= 2 { + fc.Arguments[0] = call.Argument(1) + } else { + if k >= 0 { + fc.Arguments[0] = ta.typedArray.get(ta.offset + k) + k-- + } + } + if fc.Arguments[0] == nil { + panic(r.NewTypeError("Reduce of empty array with no initial value")) + } + for ; k >= 0; k-- { + if ta.isValidIntegerIndex(k) { + fc.Arguments[1] = ta.typedArray.get(ta.offset + k) + } else { + fc.Arguments[1] = _undefined + } + idx := valueInt(k) + fc.Arguments[2] = idx + fc.Arguments[0] = callbackFn(fc) + } + return fc.Arguments[0] + } + panic(r.NewTypeError("Method TypedArray.prototype.reduceRight called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_reverse(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + l := ta.length + middle := l / 2 + for lower := 0; lower != middle; lower++ { + upper := l - lower - 1 + ta.typedArray.swap(ta.offset+lower, ta.offset+upper) + } + + return call.This + } + panic(r.NewTypeError("Method TypedArray.prototype.reverse called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_set(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + srcObj := call.Argument(0).ToObject(r) + targetOffset := toIntStrict(call.Argument(1).ToInteger()) + if targetOffset < 0 { + panic(r.newError(r.getRangeError(), "offset should be >= 0")) + } + ta.viewedArrayBuf.ensureNotDetached(true) + targetLen := ta.length + if src, ok := srcObj.self.(*typedArrayObject); ok { + src.viewedArrayBuf.ensureNotDetached(true) + srcLen := src.length + if x := srcLen + targetOffset; x < 0 || x > targetLen { + panic(r.newError(r.getRangeError(), "Source is too large")) + } + if src.defaultCtor == ta.defaultCtor { + copy(ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize:], + src.viewedArrayBuf.data[src.offset*src.elemSize:(src.offset+srcLen)*src.elemSize]) + } else { + curSrc := uintptr(unsafe.Pointer(&src.viewedArrayBuf.data[src.offset*src.elemSize])) + endSrc := curSrc + uintptr(srcLen*src.elemSize) + curDst := uintptr(unsafe.Pointer(&ta.viewedArrayBuf.data[(ta.offset+targetOffset)*ta.elemSize])) + dstOffset := ta.offset + targetOffset + srcOffset := src.offset + if ta.elemSize == src.elemSize { + if curDst <= curSrc || curDst >= endSrc { + for i := 0; i < srcLen; i++ { + ta.typedArray.set(dstOffset+i, src.typedArray.get(srcOffset+i)) + } + } else { + for i := srcLen - 1; i >= 0; i-- { + ta.typedArray.set(dstOffset+i, src.typedArray.get(srcOffset+i)) + } + } + } else { + x := int(curDst-curSrc) / (src.elemSize - ta.elemSize) + if x < 0 { + x = 0 + } else if x > srcLen { + x = srcLen + } + if ta.elemSize < src.elemSize { + for i := x; i < srcLen; i++ { + ta.typedArray.set(dstOffset+i, src.typedArray.get(srcOffset+i)) + } + for i := x - 1; i >= 0; i-- { + ta.typedArray.set(dstOffset+i, src.typedArray.get(srcOffset+i)) + } + } else { + for i := 0; i < x; i++ { + ta.typedArray.set(dstOffset+i, src.typedArray.get(srcOffset+i)) + } + for i := srcLen - 1; i >= x; i-- { + ta.typedArray.set(dstOffset+i, src.typedArray.get(srcOffset+i)) + } + } + } + } + } else { + targetLen := ta.length + srcLen := toIntStrict(toLength(srcObj.self.getStr("length", nil))) + if x := srcLen + targetOffset; x < 0 || x > targetLen { + panic(r.newError(r.getRangeError(), "Source is too large")) + } + for i := 0; i < srcLen; i++ { + val := nilSafe(srcObj.self.getIdx(valueInt(i), nil)) + ta.viewedArrayBuf.ensureNotDetached(true) + if ta.isValidIntegerIndex(i) { + ta.typedArray.set(targetOffset+i, val) + } + } + } + return _undefined + } + panic(r.NewTypeError("Method TypedArray.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_slice(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + length := int64(ta.length) + start := toIntStrict(relToIdx(call.Argument(0).ToInteger(), length)) + var e int64 + if endArg := call.Argument(1); endArg != _undefined { + e = endArg.ToInteger() + } else { + e = length + } + end := toIntStrict(relToIdx(e, length)) + + count := end - start + if count < 0 { + count = 0 + } + dst := r.typedArraySpeciesCreate(ta, []Value{intToValue(int64(count))}) + if dst.defaultCtor == ta.defaultCtor { + if count > 0 { + ta.viewedArrayBuf.ensureNotDetached(true) + offset := ta.offset + elemSize := ta.elemSize + copy(dst.viewedArrayBuf.data, ta.viewedArrayBuf.data[(offset+start)*elemSize:(offset+start+count)*elemSize]) + } + } else { + for i := 0; i < count; i++ { + ta.viewedArrayBuf.ensureNotDetached(true) + dst.typedArray.set(i, ta.typedArray.get(ta.offset+start+i)) + } + } + return dst.val + } + panic(r.NewTypeError("Method TypedArray.prototype.slice called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_some(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + callbackFn := r.toCallable(call.Argument(0)) + fc := FunctionCall{ + This: call.Argument(1), + Arguments: []Value{nil, nil, call.This}, + } + for k := 0; k < ta.length; k++ { + if ta.isValidIntegerIndex(k) { + fc.Arguments[0] = ta.typedArray.get(ta.offset + k) + } else { + fc.Arguments[0] = _undefined + } + fc.Arguments[1] = intToValue(int64(k)) + if callbackFn(fc).ToBoolean() { + return valueTrue + } + } + return valueFalse + } + panic(r.NewTypeError("Method TypedArray.prototype.some called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_sort(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + var compareFn func(FunctionCall) Value + + if arg := call.Argument(0); arg != _undefined { + compareFn = r.toCallable(arg) + } + + ctx := typedArraySortCtx{ + ta: ta, + compare: compareFn, + } + + sort.Stable(&ctx) + return call.This + } + panic(r.NewTypeError("Method TypedArray.prototype.sort called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_subarray(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + l := int64(ta.length) + beginIdx := relToIdx(call.Argument(0).ToInteger(), l) + var relEnd int64 + if endArg := call.Argument(1); endArg != _undefined { + relEnd = endArg.ToInteger() + } else { + relEnd = l + } + endIdx := relToIdx(relEnd, l) + newLen := max(endIdx-beginIdx, 0) + return r.typedArraySpeciesCreate(ta, []Value{ta.viewedArrayBuf.val, + intToValue((int64(ta.offset) + beginIdx) * int64(ta.elemSize)), + intToValue(newLen), + }).val + } + panic(r.NewTypeError("Method TypedArray.prototype.subarray called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_toLocaleString(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + length := ta.length + var buf StringBuilder + for i := 0; i < length; i++ { + ta.viewedArrayBuf.ensureNotDetached(true) + if i > 0 { + buf.WriteRune(',') + } + item := ta.typedArray.get(ta.offset + i) + r.writeItemLocaleString(item, &buf) + } + return buf.String() + } + panic(r.NewTypeError("Method TypedArray.prototype.toLocaleString called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_values(call FunctionCall) Value { + if ta, ok := r.toObject(call.This).self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + return r.createArrayIterator(ta.val, iterationKindValue) + } + panic(r.NewTypeError("Method TypedArray.prototype.values called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: call.This}))) +} + +func (r *Runtime) typedArrayProto_toStringTag(call FunctionCall) Value { + if obj, ok := call.This.(*Object); ok { + if ta, ok := obj.self.(*typedArrayObject); ok { + return nilSafe(ta.defaultCtor.self.getStr("name", nil)) + } + } + + return _undefined +} + +func (r *Runtime) newTypedArray([]Value, *Object) *Object { + panic(r.NewTypeError("Abstract class TypedArray not directly constructable")) +} + +func (r *Runtime) typedArray_from(call FunctionCall) Value { + c := r.toObject(call.This) + var mapFc func(call FunctionCall) Value + thisValue := call.Argument(2) + if mapFn := call.Argument(1); mapFn != _undefined { + mapFc = r.toCallable(mapFn) + } + source := r.toObject(call.Argument(0)) + usingIter := toMethod(source.self.getSym(SymIterator, nil)) + if usingIter != nil { + values := r.iterableToList(source, usingIter) + ta := r.typedArrayCreate(c, intToValue(int64(len(values)))) + if mapFc == nil { + for idx, val := range values { + ta.typedArray.set(idx, val) + } + } else { + fc := FunctionCall{ + This: thisValue, + Arguments: []Value{nil, nil}, + } + for idx, val := range values { + fc.Arguments[0], fc.Arguments[1] = val, intToValue(int64(idx)) + val = mapFc(fc) + ta.typedArray.set(idx, val) + } + } + return ta.val + } + length := toIntStrict(toLength(source.self.getStr("length", nil))) + ta := r.typedArrayCreate(c, intToValue(int64(length))) + if mapFc == nil { + for i := 0; i < length; i++ { + ta.typedArray.set(i, nilSafe(source.self.getIdx(valueInt(i), nil))) + } + } else { + fc := FunctionCall{ + This: thisValue, + Arguments: []Value{nil, nil}, + } + for i := 0; i < length; i++ { + idx := valueInt(i) + fc.Arguments[0], fc.Arguments[1] = source.self.getIdx(idx, nil), idx + ta.typedArray.set(i, mapFc(fc)) + } + } + return ta.val +} + +func (r *Runtime) typedArray_of(call FunctionCall) Value { + ta := r.typedArrayCreate(r.toObject(call.This), intToValue(int64(len(call.Arguments)))) + for i, val := range call.Arguments { + ta.typedArray.set(i, val) + } + return ta.val +} + +func (r *Runtime) allocateTypedArray(newTarget *Object, length int, taCtor typedArrayObjectCtor, proto *Object) *typedArrayObject { + buf := r._newArrayBuffer(r.getArrayBufferPrototype(), nil) + ta := taCtor(buf, 0, length, r.getPrototypeFromCtor(newTarget, nil, proto)) + if length > 0 { + buf.data = allocByteSlice(length * ta.elemSize) + } + return ta +} + +func (r *Runtime) typedArraySpeciesCreate(ta *typedArrayObject, args []Value) *typedArrayObject { + return r.typedArrayCreate(r.speciesConstructorObj(ta.val, ta.defaultCtor), args...) +} + +func (r *Runtime) typedArrayCreate(ctor *Object, args ...Value) *typedArrayObject { + o := r.toConstructor(ctor)(args, ctor) + if ta, ok := o.self.(*typedArrayObject); ok { + ta.viewedArrayBuf.ensureNotDetached(true) + if len(args) == 1 { + if l, ok := args[0].(valueInt); ok { + if ta.length < int(l) { + panic(r.NewTypeError("Derived TypedArray constructor created an array which was too small")) + } + } + } + return ta + } + panic(r.NewTypeError("Invalid TypedArray: %s", o)) +} + +func (r *Runtime) typedArrayFrom(ctor, items *Object, mapFn, thisValue Value, taCtor typedArrayObjectCtor, proto *Object) *Object { + var mapFc func(call FunctionCall) Value + if mapFn != nil { + mapFc = r.toCallable(mapFn) + if thisValue == nil { + thisValue = _undefined + } + } + usingIter := toMethod(items.self.getSym(SymIterator, nil)) + if usingIter != nil { + values := r.iterableToList(items, usingIter) + ta := r.allocateTypedArray(ctor, len(values), taCtor, proto) + if mapFc == nil { + for idx, val := range values { + ta.typedArray.set(idx, val) + } + } else { + fc := FunctionCall{ + This: thisValue, + Arguments: []Value{nil, nil}, + } + for idx, val := range values { + fc.Arguments[0], fc.Arguments[1] = val, intToValue(int64(idx)) + val = mapFc(fc) + ta.typedArray.set(idx, val) + } + } + return ta.val + } + length := toIntStrict(toLength(items.self.getStr("length", nil))) + ta := r.allocateTypedArray(ctor, length, taCtor, proto) + if mapFc == nil { + for i := 0; i < length; i++ { + ta.typedArray.set(i, nilSafe(items.self.getIdx(valueInt(i), nil))) + } + } else { + fc := FunctionCall{ + This: thisValue, + Arguments: []Value{nil, nil}, + } + for i := 0; i < length; i++ { + idx := valueInt(i) + fc.Arguments[0], fc.Arguments[1] = items.self.getIdx(idx, nil), idx + ta.typedArray.set(i, mapFc(fc)) + } + } + return ta.val +} + +func (r *Runtime) _newTypedArrayFromArrayBuffer(ab *arrayBufferObject, args []Value, newTarget *Object, taCtor typedArrayObjectCtor, proto *Object) *Object { + ta := taCtor(ab, 0, 0, r.getPrototypeFromCtor(newTarget, nil, proto)) + var byteOffset int + if len(args) > 1 && args[1] != nil && args[1] != _undefined { + byteOffset = r.toIndex(args[1]) + if byteOffset%ta.elemSize != 0 { + panic(r.newError(r.getRangeError(), "Start offset of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize)) + } + } + var length int + if len(args) > 2 && args[2] != nil && args[2] != _undefined { + length = r.toIndex(args[2]) + ab.ensureNotDetached(true) + if byteOffset+length*ta.elemSize > len(ab.data) { + panic(r.newError(r.getRangeError(), "Invalid typed array length: %d", length)) + } + } else { + ab.ensureNotDetached(true) + if len(ab.data)%ta.elemSize != 0 { + panic(r.newError(r.getRangeError(), "Byte length of %s should be a multiple of %d", newTarget.self.getStr("name", nil), ta.elemSize)) + } + length = (len(ab.data) - byteOffset) / ta.elemSize + if length < 0 { + panic(r.newError(r.getRangeError(), "Start offset %d is outside the bounds of the buffer", byteOffset)) + } + } + ta.offset = byteOffset / ta.elemSize + ta.length = length + return ta.val +} + +func (r *Runtime) _newTypedArrayFromTypedArray(src *typedArrayObject, newTarget *Object, taCtor typedArrayObjectCtor, proto *Object) *Object { + dst := r.allocateTypedArray(newTarget, 0, taCtor, proto) + src.viewedArrayBuf.ensureNotDetached(true) + l := src.length + + arrayBuffer := r.getArrayBuffer() + dst.viewedArrayBuf.prototype = r.getPrototypeFromCtor(r.speciesConstructorObj(src.viewedArrayBuf.val, arrayBuffer), arrayBuffer, r.getArrayBufferPrototype()) + dst.viewedArrayBuf.data = allocByteSlice(toIntStrict(int64(l) * int64(dst.elemSize))) + src.viewedArrayBuf.ensureNotDetached(true) + if src.defaultCtor == dst.defaultCtor { + copy(dst.viewedArrayBuf.data, src.viewedArrayBuf.data[src.offset*src.elemSize:]) + dst.length = src.length + return dst.val + } + dst.length = l + for i := 0; i < l; i++ { + dst.typedArray.set(i, src.typedArray.get(src.offset+i)) + } + return dst.val +} + +func (r *Runtime) _newTypedArray(args []Value, newTarget *Object, taCtor typedArrayObjectCtor, proto *Object) *Object { + if newTarget == nil { + panic(r.needNew("TypedArray")) + } + if len(args) > 0 { + if obj, ok := args[0].(*Object); ok { + switch o := obj.self.(type) { + case *arrayBufferObject: + return r._newTypedArrayFromArrayBuffer(o, args, newTarget, taCtor, proto) + case *typedArrayObject: + return r._newTypedArrayFromTypedArray(o, newTarget, taCtor, proto) + default: + return r.typedArrayFrom(newTarget, obj, nil, nil, taCtor, proto) + } + } + } + var l int + if len(args) > 0 { + if arg0 := args[0]; arg0 != nil { + l = r.toIndex(arg0) + } + } + return r.allocateTypedArray(newTarget, l, taCtor, proto).val +} + +func (r *Runtime) newUint8Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newUint8ArrayObject, proto) +} + +func (r *Runtime) newUint8ClampedArray(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newUint8ClampedArrayObject, proto) +} + +func (r *Runtime) newInt8Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newInt8ArrayObject, proto) +} + +func (r *Runtime) newUint16Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newUint16ArrayObject, proto) +} + +func (r *Runtime) newInt16Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newInt16ArrayObject, proto) +} + +func (r *Runtime) newUint32Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newUint32ArrayObject, proto) +} + +func (r *Runtime) newInt32Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newInt32ArrayObject, proto) +} + +func (r *Runtime) newFloat32Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newFloat32ArrayObject, proto) +} + +func (r *Runtime) newFloat64Array(args []Value, newTarget, proto *Object) *Object { + return r._newTypedArray(args, newTarget, r.newFloat64ArrayObject, proto) +} + +func (r *Runtime) createArrayBufferProto(val *Object) objectImpl { + b := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + byteLengthProp := &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.arrayBufferProto_getByteLength, "get byteLength", 0), + } + b._put("byteLength", byteLengthProp) + b._putProp("constructor", r.getArrayBuffer(), true, false, true) + b._putProp("slice", r.newNativeFunc(r.arrayBufferProto_slice, "slice", 2), true, false, true) + b._putSym(SymToStringTag, valueProp(asciiString("ArrayBuffer"), false, false, true)) + return b +} + +func (r *Runtime) createArrayBuffer(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newArrayBuffer, r.getArrayBufferPrototype(), "ArrayBuffer", 1) + o._putProp("isView", r.newNativeFunc(r.arrayBuffer_isView, "isView", 1), true, false, true) + r.putSpeciesReturnThis(o) + + return o +} + +func (r *Runtime) createDataView(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.newDataView, r.getDataViewPrototype(), "DataView", 1) + return o +} + +func (r *Runtime) createTypedArray(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.newTypedArray, r.getTypedArrayPrototype(), "TypedArray", 0) + o._putProp("from", r.newNativeFunc(r.typedArray_from, "from", 1), true, false, true) + o._putProp("of", r.newNativeFunc(r.typedArray_of, "of", 0), true, false, true) + r.putSpeciesReturnThis(o) + + return o +} + +func (r *Runtime) getTypedArray() *Object { + ret := r.global.TypedArray + if ret == nil { + ret = &Object{runtime: r} + r.global.TypedArray = ret + r.createTypedArray(ret) + } + return ret +} + +func (r *Runtime) createTypedArrayCtor(val *Object, ctor func(args []Value, newTarget, proto *Object) *Object, name unistring.String, bytesPerElement int) { + p := r.newBaseObject(r.getTypedArrayPrototype(), classObject) + o := r.newNativeConstructOnly(val, func(args []Value, newTarget *Object) *Object { + return ctor(args, newTarget, p.val) + }, p.val, name, 3) + + p._putProp("constructor", o.val, true, false, true) + + o.prototype = r.getTypedArray() + bpe := intToValue(int64(bytesPerElement)) + o._putProp("BYTES_PER_ELEMENT", bpe, false, false, false) + p._putProp("BYTES_PER_ELEMENT", bpe, false, false, false) +} + +func addTypedArrays(t *objectTemplate) { + t.putStr("ArrayBuffer", func(r *Runtime) Value { return valueProp(r.getArrayBuffer(), true, false, true) }) + t.putStr("DataView", func(r *Runtime) Value { return valueProp(r.getDataView(), true, false, true) }) + t.putStr("Uint8Array", func(r *Runtime) Value { return valueProp(r.getUint8Array(), true, false, true) }) + t.putStr("Uint8ClampedArray", func(r *Runtime) Value { return valueProp(r.getUint8ClampedArray(), true, false, true) }) + t.putStr("Int8Array", func(r *Runtime) Value { return valueProp(r.getInt8Array(), true, false, true) }) + t.putStr("Uint16Array", func(r *Runtime) Value { return valueProp(r.getUint16Array(), true, false, true) }) + t.putStr("Int16Array", func(r *Runtime) Value { return valueProp(r.getInt16Array(), true, false, true) }) + t.putStr("Uint32Array", func(r *Runtime) Value { return valueProp(r.getUint32Array(), true, false, true) }) + t.putStr("Int32Array", func(r *Runtime) Value { return valueProp(r.getInt32Array(), true, false, true) }) + t.putStr("Float32Array", func(r *Runtime) Value { return valueProp(r.getFloat32Array(), true, false, true) }) + t.putStr("Float64Array", func(r *Runtime) Value { return valueProp(r.getFloat64Array(), true, false, true) }) +} + +func createTypedArrayProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("buffer", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.typedArrayProto_getBuffer, "get buffer", 0), + } + }) + + t.putStr("byteLength", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.typedArrayProto_getByteLen, "get byteLength", 0), + } + }) + + t.putStr("byteOffset", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.typedArrayProto_getByteOffset, "get byteOffset", 0), + } + }) + + t.putStr("at", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_at, "at", 1) }) + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getTypedArray(), true, false, true) }) + t.putStr("copyWithin", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_copyWithin, "copyWithin", 2) }) + t.putStr("entries", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_entries, "entries", 0) }) + t.putStr("every", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_every, "every", 1) }) + t.putStr("fill", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_fill, "fill", 1) }) + t.putStr("filter", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_filter, "filter", 1) }) + t.putStr("find", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_find, "find", 1) }) + t.putStr("findIndex", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_findIndex, "findIndex", 1) }) + t.putStr("findLast", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_findLast, "findLast", 1) }) + t.putStr("findLastIndex", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_findLastIndex, "findLastIndex", 1) }) + t.putStr("forEach", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_forEach, "forEach", 1) }) + t.putStr("includes", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_includes, "includes", 1) }) + t.putStr("indexOf", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_indexOf, "indexOf", 1) }) + t.putStr("join", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_join, "join", 1) }) + t.putStr("keys", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_keys, "keys", 0) }) + t.putStr("lastIndexOf", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_lastIndexOf, "lastIndexOf", 1) }) + t.putStr("length", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.typedArrayProto_getLength, "get length", 0), + } + }) + t.putStr("map", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_map, "map", 1) }) + t.putStr("reduce", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_reduce, "reduce", 1) }) + t.putStr("reduceRight", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_reduceRight, "reduceRight", 1) }) + t.putStr("reverse", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_reverse, "reverse", 0) }) + t.putStr("set", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_set, "set", 1) }) + t.putStr("slice", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_slice, "slice", 2) }) + t.putStr("some", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_some, "some", 1) }) + t.putStr("sort", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_sort, "sort", 1) }) + t.putStr("subarray", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_subarray, "subarray", 2) }) + t.putStr("toLocaleString", func(r *Runtime) Value { return r.methodProp(r.typedArrayProto_toLocaleString, "toLocaleString", 0) }) + t.putStr("toString", func(r *Runtime) Value { return valueProp(r.getArrayToString(), true, false, true) }) + t.putStr("values", func(r *Runtime) Value { return valueProp(r.getTypedArrayValues(), true, false, true) }) + + t.putSym(SymIterator, func(r *Runtime) Value { return valueProp(r.getTypedArrayValues(), true, false, true) }) + t.putSym(SymToStringTag, func(r *Runtime) Value { + return &valueProperty{ + getterFunc: r.newNativeFunc(r.typedArrayProto_toStringTag, "get [Symbol.toStringTag]", 0), + accessor: true, + configurable: true, + } + }) + + return t +} + +func (r *Runtime) getTypedArrayValues() *Object { + ret := r.global.typedArrayValues + if ret == nil { + ret = r.newNativeFunc(r.typedArrayProto_values, "values", 0) + r.global.typedArrayValues = ret + } + return ret +} + +var typedArrayProtoTemplate *objectTemplate +var typedArrayProtoTemplateOnce sync.Once + +func getTypedArrayProtoTemplate() *objectTemplate { + typedArrayProtoTemplateOnce.Do(func() { + typedArrayProtoTemplate = createTypedArrayProtoTemplate() + }) + return typedArrayProtoTemplate +} + +func (r *Runtime) getTypedArrayPrototype() *Object { + ret := r.global.TypedArrayPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.TypedArrayPrototype = ret + r.newTemplatedObject(getTypedArrayProtoTemplate(), ret) + } + return ret +} + +func (r *Runtime) getUint8Array() *Object { + ret := r.global.Uint8Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Uint8Array = ret + r.createTypedArrayCtor(ret, r.newUint8Array, "Uint8Array", 1) + } + return ret +} + +func (r *Runtime) getUint8ClampedArray() *Object { + ret := r.global.Uint8ClampedArray + if ret == nil { + ret = &Object{runtime: r} + r.global.Uint8ClampedArray = ret + r.createTypedArrayCtor(ret, r.newUint8ClampedArray, "Uint8ClampedArray", 1) + } + return ret +} + +func (r *Runtime) getInt8Array() *Object { + ret := r.global.Int8Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Int8Array = ret + r.createTypedArrayCtor(ret, r.newInt8Array, "Int8Array", 1) + } + return ret +} + +func (r *Runtime) getUint16Array() *Object { + ret := r.global.Uint16Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Uint16Array = ret + r.createTypedArrayCtor(ret, r.newUint16Array, "Uint16Array", 2) + } + return ret +} + +func (r *Runtime) getInt16Array() *Object { + ret := r.global.Int16Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Int16Array = ret + r.createTypedArrayCtor(ret, r.newInt16Array, "Int16Array", 2) + } + return ret +} + +func (r *Runtime) getUint32Array() *Object { + ret := r.global.Uint32Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Uint32Array = ret + r.createTypedArrayCtor(ret, r.newUint32Array, "Uint32Array", 4) + } + return ret +} + +func (r *Runtime) getInt32Array() *Object { + ret := r.global.Int32Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Int32Array = ret + r.createTypedArrayCtor(ret, r.newInt32Array, "Int32Array", 4) + } + return ret +} + +func (r *Runtime) getFloat32Array() *Object { + ret := r.global.Float32Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Float32Array = ret + r.createTypedArrayCtor(ret, r.newFloat32Array, "Float32Array", 4) + } + return ret +} + +func (r *Runtime) getFloat64Array() *Object { + ret := r.global.Float64Array + if ret == nil { + ret = &Object{runtime: r} + r.global.Float64Array = ret + r.createTypedArrayCtor(ret, r.newFloat64Array, "Float64Array", 8) + } + return ret +} + +func createDataViewProtoTemplate() *objectTemplate { + t := newObjectTemplate() + t.protoFactory = func(r *Runtime) *Object { + return r.global.ObjectPrototype + } + + t.putStr("buffer", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.dataViewProto_getBuffer, "get buffer", 0), + } + }) + t.putStr("byteLength", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.dataViewProto_getByteLen, "get byteLength", 0), + } + }) + t.putStr("byteOffset", func(r *Runtime) Value { + return &valueProperty{ + accessor: true, + configurable: true, + getterFunc: r.newNativeFunc(r.dataViewProto_getByteOffset, "get byteOffset", 0), + } + }) + + t.putStr("constructor", func(r *Runtime) Value { return valueProp(r.getDataView(), true, false, true) }) + + t.putStr("getFloat32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getFloat32, "getFloat32", 1) }) + t.putStr("getFloat64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getFloat64, "getFloat64", 1) }) + t.putStr("getInt8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getInt8, "getInt8", 1) }) + t.putStr("getInt16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getInt16, "getInt16", 1) }) + t.putStr("getInt32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getInt32, "getInt32", 1) }) + t.putStr("getUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint8, "getUint8", 1) }) + t.putStr("getUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint16, "getUint16", 1) }) + t.putStr("getUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_getUint32, "getUint32", 1) }) + t.putStr("setFloat32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat32, "setFloat32", 2) }) + t.putStr("setFloat64", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setFloat64, "setFloat64", 2) }) + t.putStr("setInt8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt8, "setInt8", 2) }) + t.putStr("setInt16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt16, "setInt16", 2) }) + t.putStr("setInt32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setInt32, "setInt32", 2) }) + t.putStr("setUint8", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint8, "setUint8", 2) }) + t.putStr("setUint16", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint16, "setUint16", 2) }) + t.putStr("setUint32", func(r *Runtime) Value { return r.methodProp(r.dataViewProto_setUint32, "setUint32", 2) }) + + t.putSym(SymToStringTag, func(r *Runtime) Value { return valueProp(asciiString("DataView"), false, false, true) }) + + return t +} + +var dataViewProtoTemplate *objectTemplate +var dataViewProtoTemplateOnce sync.Once + +func getDataViewProtoTemplate() *objectTemplate { + dataViewProtoTemplateOnce.Do(func() { + dataViewProtoTemplate = createDataViewProtoTemplate() + }) + return dataViewProtoTemplate +} + +func (r *Runtime) getDataViewPrototype() *Object { + ret := r.global.DataViewPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.DataViewPrototype = ret + r.newTemplatedObject(getDataViewProtoTemplate(), ret) + } + return ret +} + +func (r *Runtime) getDataView() *Object { + ret := r.global.DataView + if ret == nil { + ret = &Object{runtime: r} + r.global.DataView = ret + ret.self = r.createDataView(ret) + } + return ret +} + +func (r *Runtime) getArrayBufferPrototype() *Object { + ret := r.global.ArrayBufferPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.ArrayBufferPrototype = ret + ret.self = r.createArrayBufferProto(ret) + } + return ret +} + +func (r *Runtime) getArrayBuffer() *Object { + ret := r.global.ArrayBuffer + if ret == nil { + ret = &Object{runtime: r} + r.global.ArrayBuffer = ret + ret.self = r.createArrayBuffer(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_typedarrays_test.go b/pkg/xscript/engine/builtin_typedarrays_test.go new file mode 100644 index 0000000..69a9c35 --- /dev/null +++ b/pkg/xscript/engine/builtin_typedarrays_test.go @@ -0,0 +1,326 @@ +package engine + +import ( + "testing" +) + +/* +func TestArrayBufferNew(t *testing.T) { + const SCRIPT = ` + var b = new ArrayBuffer(16); + b.byteLength; + ` + + testScript(SCRIPT, intToValue(16), t) +} +*/ + +func TestArrayBufferSetUint32(t *testing.T) { + vm := New() + b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil) + b.data = make([]byte, 4) + b.setUint32(0, 0xCAFEBABE, bigEndian) + + i := b.getUint32(0, bigEndian) + if i != 0xCAFEBABE { + t.Fatal(i) + } + i = b.getUint32(0, littleEndian) + if i != 0xBEBAFECA { + t.Fatal(i) + } + + b.setUint32(0, 0xBEBAFECA, littleEndian) + i = b.getUint32(0, bigEndian) + if i != 0xCAFEBABE { + t.Fatal(i) + } +} + +func TestArrayBufferSetInt32(t *testing.T) { + vm := New() + b := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil) + b.data = make([]byte, 4) + b.setInt32(0, -42, littleEndian) + if v := b.getInt32(0, littleEndian); v != -42 { + t.Fatal(v) + } + + b.setInt32(0, -42, bigEndian) + if v := b.getInt32(0, bigEndian); v != -42 { + t.Fatal(v) + } +} + +func TestNewUint8Array(t *testing.T) { + const SCRIPT = ` + var a = new Uint8Array(1); + a[0] = 42; + a.byteLength === 1 && a.length === 1 && a[0] === 42; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestNewUint16Array(t *testing.T) { + const SCRIPT = ` + var a = new Uint16Array(1); + a[0] = 42; + a.byteLength === 2 && a.length === 1 && a[0] === 42; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestTypedArraysSpeciesConstructor(t *testing.T) { + const SCRIPT = ` + 'use strict'; + function MyArray() { + var NewTarget = this.__proto__.constructor; + return Reflect.construct(Uint16Array, arguments, NewTarget); + } + MyArray.prototype = Object.create(Uint16Array.prototype, { + constructor: { + value: MyArray, + writable: true, + configurable: true + } + }); + var a = new MyArray(1); + Object.defineProperty(MyArray, Symbol.species, {value: Uint8Array, configurable: true}); + a[0] = 32767; + var b = a.filter(function() { + return true; + }); + if (a[0] !== 32767) { + throw new Error("a[0]=" + a[0]); + } + if (!(b instanceof Uint8Array)) { + throw new Error("b instanceof Uint8Array"); + } + if (b[0] != 255) { + throw new Error("b[0]=" + b[0]); + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArrayFromArrayBuffer(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(2); + var a16 = new Uint16Array(buf); + if (!(a16 instanceof Uint16Array)) { + throw new Error("a16 is not an instance"); + } + if (a16.buffer !== buf) { + throw new Error("a16.buffer !== buf"); + } + if (a16.length !== 1) { + throw new Error("a16.length=" + a16.length); + } + var a8 = new Uint8Array(buf); + a8.fill(0xAA); + if (a16[0] !== 0xAAAA) { + throw new Error("a16[0]=" + a16[0]); + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetOverlapDifSize(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(4); + var src = new Uint8Array(buf, 1, 2); + src[0] = 1; + src[1] = 2; + var dst = new Uint16Array(buf); + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetOverlapDifSize2(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(4); + var src = new Uint8Array(buf, 0, 2); + src[0] = 1; + src[1] = 2; + var dst = new Uint16Array(buf); + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetOverlapDifSize3(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(8); + var src = new Uint8Array(buf, 2, 4); + src[0] = 1; + src[1] = 2; + src[2] = 3; + src[3] = 4; + var dst = new Uint16Array(buf); + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetOverlapDifSize4(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(10); + var dst = new Uint8Array(buf, 2, 5); + var src = new Uint16Array(buf); + src[0] = 1; + src[1] = 2; + src[2] = 3; + src[3] = 4; + src[4] = 5; + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2 || dst[2] !== 3 || dst[3] !== 4 || dst[4] !== 5) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetNoOverlapDifSizeForward(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(10); + var dst = new Uint8Array(buf, 7, 2); + var src = new Uint16Array(buf, 0, 2); + src[0] = 1; + src[1] = 2; + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetNoOverlapDifSizeBackward(t *testing.T) { + const SCRIPT = ` + var buf = new ArrayBuffer(10); + var dst = new Uint8Array(buf, 0, 2); + var src = new Uint16Array(buf, 6, 2); + src[0] = 1; + src[1] = 2; + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetNoOverlapDifSizeDifBuffers(t *testing.T) { + const SCRIPT = ` + var dstBuf = new ArrayBuffer(1024); + var dst = new Uint8Array(dstBuf, 0, 2); + var src = new Uint16Array(2); + src[0] = 1; + src[1] = 2; + dst.set(src); + if (dst[0] !== 1 || dst[1] !== 2 || src[0] !== 1 || src[1] !== 2) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySliceSameType(t *testing.T) { + const SCRIPT = ` + var src = Uint8Array.of(1,2,3,4); + var dst = src.slice(1, 3); + if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySliceDifType(t *testing.T) { + const SCRIPT = ` + var src = Uint8Array.of(1,2,3,4); + Object.defineProperty(Uint8Array, Symbol.species, {value: Uint16Array, configurable: true}); + var dst = src.slice(1, 3); + if (!(dst instanceof Uint16Array)) { + throw new Error("wrong dst type: " + dst); + } + if (dst.length !== 2 || dst[0] !== 2 || dst[1] !== 3) { + throw new Error("dst: " + dst.join(",")); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySortComparatorReturnValueFloats(t *testing.T) { + const SCRIPT = ` + var a = Float64Array.of( + 5.97, + 9.91, + 4.13, + 9.28, + 3.29 + ); + a.sort( function(a, b) { return a - b; } ); + for (var i = 1; i < a.length; i++) { + if (a[i] < a[i-1]) { + throw new Error("Array is not sorted: " + a); + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySortComparatorReturnValueNegZero(t *testing.T) { + const SCRIPT = ` + var a = new Uint8Array([2, 1]); + a.sort( function(a, b) { return a > b ? 0 : -0; } ); + for (var i = 1; i < a.length; i++) { + if (a[i] < a[i-1]) { + throw new Error("Array is not sorted: " + a); + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestInt32ArrayNegativeIndex(t *testing.T) { + const SCRIPT = ` + new Int32Array()[-1] === undefined; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestTypedArrayDeleteUnconfigurable(t *testing.T) { + const SCRIPT = ` + try { + (function() { + 'use strict'; + delete Uint8Array.prototype.BYTES_PER_ELEMENT; + })(); + } catch(e) { + if (!(e instanceof TypeError)) { + throw e; + } + if (!e.message.startsWith("Cannot delete property")) { + throw e; + } + } + ` + + testScript(SCRIPT, _undefined, t) +} diff --git a/pkg/xscript/engine/builtin_weakmap.go b/pkg/xscript/engine/builtin_weakmap.go new file mode 100644 index 0000000..1e9f002 --- /dev/null +++ b/pkg/xscript/engine/builtin_weakmap.go @@ -0,0 +1,176 @@ +package engine + +type weakMap uint64 + +type weakMapObject struct { + baseObject + m weakMap +} + +func (wmo *weakMapObject) init() { + wmo.baseObject.init() + wmo.m = weakMap(wmo.val.runtime.genId()) +} + +func (wm weakMap) set(key *Object, value Value) { + key.getWeakRefs()[wm] = value +} + +func (wm weakMap) get(key *Object) Value { + return key.weakRefs[wm] +} + +func (wm weakMap) remove(key *Object) bool { + if _, exists := key.weakRefs[wm]; exists { + delete(key.weakRefs, wm) + return true + } + return false +} + +func (wm weakMap) has(key *Object) bool { + _, exists := key.weakRefs[wm] + return exists +} + +func (r *Runtime) weakMapProto_delete(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wmo, ok := thisObj.self.(*weakMapObject) + if !ok { + panic(r.NewTypeError("Method WeakMap.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + key, ok := call.Argument(0).(*Object) + if ok && wmo.m.remove(key) { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) weakMapProto_get(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wmo, ok := thisObj.self.(*weakMapObject) + if !ok { + panic(r.NewTypeError("Method WeakMap.prototype.get called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + var res Value + if key, ok := call.Argument(0).(*Object); ok { + res = wmo.m.get(key) + } + if res == nil { + return _undefined + } + return res +} + +func (r *Runtime) weakMapProto_has(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wmo, ok := thisObj.self.(*weakMapObject) + if !ok { + panic(r.NewTypeError("Method WeakMap.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + key, ok := call.Argument(0).(*Object) + if ok && wmo.m.has(key) { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) weakMapProto_set(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wmo, ok := thisObj.self.(*weakMapObject) + if !ok { + panic(r.NewTypeError("Method WeakMap.prototype.set called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + key := r.toObject(call.Argument(0)) + wmo.m.set(key, call.Argument(1)) + return call.This +} + +func (r *Runtime) needNew(name string) *Object { + return r.NewTypeError("Constructor %s requires 'new'", name) +} + +func (r *Runtime) builtin_newWeakMap(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("WeakMap")) + } + proto := r.getPrototypeFromCtor(newTarget, r.global.WeakMap, r.global.WeakMapPrototype) + o := &Object{runtime: r} + + wmo := &weakMapObject{} + wmo.class = classObject + wmo.val = o + wmo.extensible = true + o.self = wmo + wmo.prototype = proto + wmo.init() + if len(args) > 0 { + if arg := args[0]; arg != nil && arg != _undefined && arg != _null { + adder := wmo.getStr("set", nil) + adderFn := toMethod(adder) + if adderFn == nil { + panic(r.NewTypeError("WeakMap.set in missing")) + } + iter := r.getIterator(arg, nil) + i0 := valueInt(0) + i1 := valueInt(1) + if adder == r.global.weakMapAdder { + iter.iterate(func(item Value) { + itemObj := r.toObject(item) + k := itemObj.self.getIdx(i0, nil) + v := nilSafe(itemObj.self.getIdx(i1, nil)) + wmo.m.set(r.toObject(k), v) + }) + } else { + iter.iterate(func(item Value) { + itemObj := r.toObject(item) + k := itemObj.self.getIdx(i0, nil) + v := itemObj.self.getIdx(i1, nil) + adderFn(FunctionCall{This: o, Arguments: []Value{k, v}}) + }) + } + } + } + return o +} + +func (r *Runtime) createWeakMapProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + + o._putProp("constructor", r.getWeakMap(), true, false, true) + r.global.weakMapAdder = r.newNativeFunc(r.weakMapProto_set, "set", 2) + o._putProp("set", r.global.weakMapAdder, true, false, true) + o._putProp("delete", r.newNativeFunc(r.weakMapProto_delete, "delete", 1), true, false, true) + o._putProp("has", r.newNativeFunc(r.weakMapProto_has, "has", 1), true, false, true) + o._putProp("get", r.newNativeFunc(r.weakMapProto_get, "get", 1), true, false, true) + + o._putSym(SymToStringTag, valueProp(asciiString(classWeakMap), false, false, true)) + + return o +} + +func (r *Runtime) createWeakMap(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newWeakMap, r.getWeakMapPrototype(), "WeakMap", 0) + + return o +} + +func (r *Runtime) getWeakMapPrototype() *Object { + ret := r.global.WeakMapPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.WeakMapPrototype = ret + ret.self = r.createWeakMapProto(ret) + } + return ret +} + +func (r *Runtime) getWeakMap() *Object { + ret := r.global.WeakMap + if ret == nil { + ret = &Object{runtime: r} + r.global.WeakMap = ret + ret.self = r.createWeakMap(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_weakmap_test.go b/pkg/xscript/engine/builtin_weakmap_test.go new file mode 100644 index 0000000..4cfd263 --- /dev/null +++ b/pkg/xscript/engine/builtin_weakmap_test.go @@ -0,0 +1,76 @@ +package engine + +import ( + "testing" +) + +func TestWeakMap(t *testing.T) { + vm := New() + _, err := vm.RunString(` + var m = new WeakMap(); + var m1 = new WeakMap(); + var key = {}; + m.set(key, true); + m1.set(key, false); + if (!m.has(key)) { + throw new Error("has"); + } + if (m.get(key) !== true) { + throw new Error("value does not match"); + } + if (!m1.has(key)) { + throw new Error("has (m1)"); + } + if (m1.get(key) !== false) { + throw new Error("m1 value does not match"); + } + m.delete(key); + if (m.has(key)) { + throw new Error("m still has after delete"); + } + if (!m1.has(key)) { + throw new Error("m1 does not have after delete from m"); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestWeakMapGetAdderGetIteratorOrder(t *testing.T) { + const SCRIPT = ` + let getterCalled = 0; + + class M extends WeakMap { + get set() { + getterCalled++; + return null; + } + } + + let getIteratorCalled = 0; + + let iterable = {}; + iterable[Symbol.iterator] = () => { + getIteratorCalled++ + return { + next: 1 + }; + } + + let thrown = false; + + try { + new M(iterable); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + + thrown && getterCalled === 1 && getIteratorCalled === 0; + ` + testScript(SCRIPT, valueTrue, t) +} diff --git a/pkg/xscript/engine/builtin_weakset.go b/pkg/xscript/engine/builtin_weakset.go new file mode 100644 index 0000000..11ff7c6 --- /dev/null +++ b/pkg/xscript/engine/builtin_weakset.go @@ -0,0 +1,135 @@ +package engine + +type weakSetObject struct { + baseObject + s weakMap +} + +func (ws *weakSetObject) init() { + ws.baseObject.init() + ws.s = weakMap(ws.val.runtime.genId()) +} + +func (r *Runtime) weakSetProto_add(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wso, ok := thisObj.self.(*weakSetObject) + if !ok { + panic(r.NewTypeError("Method WeakSet.prototype.add called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + wso.s.set(r.toObject(call.Argument(0)), nil) + return call.This +} + +func (r *Runtime) weakSetProto_delete(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wso, ok := thisObj.self.(*weakSetObject) + if !ok { + panic(r.NewTypeError("Method WeakSet.prototype.delete called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + obj, ok := call.Argument(0).(*Object) + if ok && wso.s.remove(obj) { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) weakSetProto_has(call FunctionCall) Value { + thisObj := r.toObject(call.This) + wso, ok := thisObj.self.(*weakSetObject) + if !ok { + panic(r.NewTypeError("Method WeakSet.prototype.has called on incompatible receiver %s", r.objectproto_toString(FunctionCall{This: thisObj}))) + } + obj, ok := call.Argument(0).(*Object) + if ok && wso.s.has(obj) { + return valueTrue + } + return valueFalse +} + +func (r *Runtime) builtin_newWeakSet(args []Value, newTarget *Object) *Object { + if newTarget == nil { + panic(r.needNew("WeakSet")) + } + proto := r.getPrototypeFromCtor(newTarget, r.global.WeakSet, r.global.WeakSetPrototype) + o := &Object{runtime: r} + + wso := &weakSetObject{} + wso.class = classObject + wso.val = o + wso.extensible = true + o.self = wso + wso.prototype = proto + wso.init() + if len(args) > 0 { + if arg := args[0]; arg != nil && arg != _undefined && arg != _null { + adder := wso.getStr("add", nil) + stdArr := r.checkStdArrayIter(arg) + if adder == r.global.weakSetAdder { + if stdArr != nil { + for _, v := range stdArr.values { + wso.s.set(r.toObject(v), nil) + } + } else { + r.getIterator(arg, nil).iterate(func(item Value) { + wso.s.set(r.toObject(item), nil) + }) + } + } else { + adderFn := toMethod(adder) + if adderFn == nil { + panic(r.NewTypeError("WeakSet.add in missing")) + } + if stdArr != nil { + for _, item := range stdArr.values { + adderFn(FunctionCall{This: o, Arguments: []Value{item}}) + } + } else { + r.getIterator(arg, nil).iterate(func(item Value) { + adderFn(FunctionCall{This: o, Arguments: []Value{item}}) + }) + } + } + } + } + return o +} + +func (r *Runtime) createWeakSetProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + + o._putProp("constructor", r.global.WeakSet, true, false, true) + r.global.weakSetAdder = r.newNativeFunc(r.weakSetProto_add, "add", 1) + o._putProp("add", r.global.weakSetAdder, true, false, true) + o._putProp("delete", r.newNativeFunc(r.weakSetProto_delete, "delete", 1), true, false, true) + o._putProp("has", r.newNativeFunc(r.weakSetProto_has, "has", 1), true, false, true) + + o._putSym(SymToStringTag, valueProp(asciiString(classWeakSet), false, false, true)) + + return o +} + +func (r *Runtime) createWeakSet(val *Object) objectImpl { + o := r.newNativeConstructOnly(val, r.builtin_newWeakSet, r.getWeakSetPrototype(), "WeakSet", 0) + + return o +} + +func (r *Runtime) getWeakSetPrototype() *Object { + ret := r.global.WeakSetPrototype + if ret == nil { + ret = &Object{runtime: r} + r.global.WeakSetPrototype = ret + ret.self = r.createWeakSetProto(ret) + } + return ret +} + +func (r *Runtime) getWeakSet() *Object { + ret := r.global.WeakSet + if ret == nil { + ret = &Object{runtime: r} + r.global.WeakSet = ret + ret.self = r.createWeakSet(ret) + } + return ret +} diff --git a/pkg/xscript/engine/builtin_weakset_test.go b/pkg/xscript/engine/builtin_weakset_test.go new file mode 100644 index 0000000..59fa602 --- /dev/null +++ b/pkg/xscript/engine/builtin_weakset_test.go @@ -0,0 +1,101 @@ +package engine + +import ( + "testing" +) + +func TestWeakSetBasic(t *testing.T) { + const SCRIPT = ` + var s = new WeakSet(); + var o = {}; + s.add(o); + if (!s.has(o)) { + throw new Error("has"); + } + s.delete(o); + if (s.has(o)) { + throw new Error("still has"); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestWeakSetArraySimple(t *testing.T) { + const SCRIPT = ` + var o1 = {}, o2 = {}, o3 = {}; + + var s = new WeakSet([o1, o2, o3]); + s.has(o1) && s.has(o2) && s.has(o3); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestWeakSetArrayGeneric(t *testing.T) { + const SCRIPT = ` + var o1 = {}, o2 = {}, o3 = {}; + var a = new Array(); + var s; + var thrown = false; + a[1] = o2; + + try { + s = new WeakSet(a); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } + } + if (!thrown) { + throw new Error("Case 1 does not throw"); + } + + Object.defineProperty(a.__proto__, "0", {value: o1, writable: true, enumerable: true, configurable: true}); + s = new WeakSet(a); + if (!(s.has(o1) && s.has(o2) && !s.has(o3))) { + throw new Error("Case 2 failed"); + } + + Object.defineProperty(a, "2", {value: o3, configurable: true}); + s = new WeakSet(a); + s.has(o1) && s.has(o2) && s.has(o3); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestWeakSetGetAdderGetIteratorOrder(t *testing.T) { + const SCRIPT = ` + let getterCalled = 0; + + class S extends WeakSet { + get add() { + getterCalled++; + return null; + } + } + + let getIteratorCalled = 0; + + let iterable = {}; + iterable[Symbol.iterator] = () => { + getIteratorCalled++ + return { + next: 1 + }; + } + + let thrown = false; + + try { + new S(iterable); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + + thrown && getterCalled === 1 && getIteratorCalled === 0; + ` + testScript(SCRIPT, valueTrue, t) +} diff --git a/pkg/xscript/engine/cmd/main.go b/pkg/xscript/engine/cmd/main.go new file mode 100644 index 0000000..453843e --- /dev/null +++ b/pkg/xscript/engine/cmd/main.go @@ -0,0 +1,127 @@ +package main + +import ( + crand "crypto/rand" + "encoding/binary" + "flag" + "fmt" + "io" + "log" + "math/rand" + "os" + "runtime/debug" + "runtime/pprof" + "time" + + "pandax/pkg/xscript/console" + "pandax/pkg/xscript/require" + "pandax/pkg/xscript/engine" +) + +var cpuprofile = flag.String("cpuprofile", "", "write cpu profile to file") +var timelimit = flag.Int("timelimit", 0, "max time to run (in seconds)") + +func readSource(filename string) ([]byte, error) { + if filename == "" || filename == "-" { + return io.ReadAll(os.Stdin) + } + return os.ReadFile(filename) +} + +func load(vm *engine.Runtime, call engine.FunctionCall) engine.Value { + p := call.Argument(0).String() + b, err := readSource(p) + if err != nil { + panic(vm.ToValue(fmt.Sprintf("Could not read %s: %v", p, err))) + } + v, err := vm.RunScript(p, string(b)) + if err != nil { + panic(err) + } + return v +} + +func newRandSource() engine.RandSource { + var seed int64 + if err := binary.Read(crand.Reader, binary.LittleEndian, &seed); err != nil { + panic(fmt.Errorf("Could not read random bytes: %v", err)) + } + return rand.New(rand.NewSource(seed)).Float64 +} + +func run() error { + filename := flag.Arg(0) + src, err := readSource(filename) + if err != nil { + return err + } + + if filename == "" || filename == "-" { + filename = "" + } + + vm := engine.New() + vm.SetRandSource(newRandSource()) + + new(require.Registry).Enable(vm) + console.Enable(vm) + + vm.Set("load", func(call engine.FunctionCall) engine.Value { + return load(vm, call) + }) + + vm.Set("readFile", func(name string) (string, error) { + b, err := os.ReadFile(name) + if err != nil { + return "", err + } + return string(b), nil + }) + + if *timelimit > 0 { + time.AfterFunc(time.Duration(*timelimit)*time.Second, func() { + vm.Interrupt("timeout") + }) + } + + //log.Println("Compiling...") + prg, err := engine.Compile(filename, string(src), false) + if err != nil { + return err + } + //log.Println("Running...") + _, err = vm.RunProgram(prg) + //log.Println("Finished.") + return err +} + +func main() { + defer func() { + if x := recover(); x != nil { + debug.Stack() + panic(x) + } + }() + flag.Parse() + if *cpuprofile != "" { + f, err := os.Create(*cpuprofile) + if err != nil { + log.Fatal(err) + } + pprof.StartCPUProfile(f) + defer pprof.StopCPUProfile() + } + + if err := run(); err != nil { + //fmt.Printf("err type: %T\n", err) + switch err := err.(type) { + case *engine.Exception: + fmt.Println(err.String()) + case *engine.InterruptedError: + fmt.Println(err.String()) + default: + fmt.Println(err) + } + os.Exit(64) + } +} diff --git a/pkg/xscript/engine/compiler.go b/pkg/xscript/engine/compiler.go new file mode 100644 index 0000000..038ed0d --- /dev/null +++ b/pkg/xscript/engine/compiler.go @@ -0,0 +1,1466 @@ +package engine + +import ( + "fmt" + "sort" + + "pandax/pkg/xscript/engine/token" + + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/unistring" +) + +type blockType int + +const ( + blockLoop blockType = iota + blockLoopEnum + blockTry + blockLabel + blockSwitch + blockWith + blockScope + blockIterScope + blockOptChain +) + +const ( + maskConst = 1 << 31 + maskVar = 1 << 30 + maskDeletable = 1 << 29 + maskStrict = maskDeletable + + maskTyp = maskConst | maskVar | maskDeletable +) + +type varType byte + +const ( + varTypeVar varType = iota + varTypeLet + varTypeStrictConst + varTypeConst +) + +const thisBindingName = " this" // must not be a valid identifier + +type CompilerError struct { + Message string + File *file.File + Offset int +} + +type CompilerSyntaxError struct { + CompilerError +} + +type CompilerReferenceError struct { + CompilerError +} + +type srcMapItem struct { + pc int + srcPos int +} + +type Program struct { + code []instruction + values []Value + + funcName unistring.String + src *file.File + srcMap []srcMapItem +} + +type compiler struct { + p *Program + scope *scope + block *block + + classScope *classScope + + enumGetExpr compiledEnumGetExpr + + evalVM *vm // VM used to evaluate constant expressions + ctxVM *vm // VM in which an eval() code is compiled + + codeScratchpad []instruction +} + +type binding struct { + scope *scope + name unistring.String + accessPoints map[*scope]*[]int + isConst bool + isStrict bool + isArg bool + isVar bool + inStash bool +} + +func (b *binding) getAccessPointsForScope(s *scope) *[]int { + m := b.accessPoints[s] + if m == nil { + a := make([]int, 0, 1) + m = &a + if b.accessPoints == nil { + b.accessPoints = make(map[*scope]*[]int) + } + b.accessPoints[s] = m + } + return m +} + +func (b *binding) markAccessPointAt(pos int) { + scope := b.scope.c.scope + m := b.getAccessPointsForScope(scope) + *m = append(*m, pos-scope.base) +} + +func (b *binding) markAccessPointAtScope(scope *scope, pos int) { + m := b.getAccessPointsForScope(scope) + *m = append(*m, pos-scope.base) +} + +func (b *binding) markAccessPoint() { + scope := b.scope.c.scope + m := b.getAccessPointsForScope(scope) + *m = append(*m, len(scope.prg.code)-scope.base) +} + +func (b *binding) emitGet() { + b.markAccessPoint() + if b.isVar && !b.isArg { + b.scope.c.emit(loadStack(0)) + } else { + b.scope.c.emit(loadStackLex(0)) + } +} + +func (b *binding) emitGetAt(pos int) { + b.markAccessPointAt(pos) + if b.isVar && !b.isArg { + b.scope.c.p.code[pos] = loadStack(0) + } else { + b.scope.c.p.code[pos] = loadStackLex(0) + } +} + +func (b *binding) emitGetP() { + if b.isVar && !b.isArg { + // no-op + } else { + // make sure TDZ is checked + b.markAccessPoint() + b.scope.c.emit(loadStackLex(0), pop) + } +} + +func (b *binding) emitSet() { + if b.isConst { + if b.isStrict || b.scope.c.scope.strict { + b.scope.c.emit(throwAssignToConst) + } + return + } + b.markAccessPoint() + if b.isVar && !b.isArg { + b.scope.c.emit(storeStack(0)) + } else { + b.scope.c.emit(storeStackLex(0)) + } +} + +func (b *binding) emitSetP() { + if b.isConst { + if b.isStrict || b.scope.c.scope.strict { + b.scope.c.emit(throwAssignToConst) + } + return + } + b.markAccessPoint() + if b.isVar && !b.isArg { + b.scope.c.emit(storeStackP(0)) + } else { + b.scope.c.emit(storeStackLexP(0)) + } +} + +func (b *binding) emitInitP() { + if !b.isVar && b.scope.outer == nil { + b.scope.c.emit(initGlobalP(b.name)) + } else { + b.markAccessPoint() + b.scope.c.emit(initStackP(0)) + } +} + +func (b *binding) emitInit() { + if !b.isVar && b.scope.outer == nil { + b.scope.c.emit(initGlobal(b.name)) + } else { + b.markAccessPoint() + b.scope.c.emit(initStack(0)) + } +} + +func (b *binding) emitInitAt(pos int) { + if !b.isVar && b.scope.outer == nil { + b.scope.c.p.code[pos] = initGlobal(b.name) + } else { + b.markAccessPointAt(pos) + b.scope.c.p.code[pos] = initStack(0) + } +} + +func (b *binding) emitInitAtScope(scope *scope, pos int) { + if !b.isVar && scope.outer == nil { + scope.c.p.code[pos] = initGlobal(b.name) + } else { + b.markAccessPointAtScope(scope, pos) + scope.c.p.code[pos] = initStack(0) + } +} + +func (b *binding) emitInitPAtScope(scope *scope, pos int) { + if !b.isVar && scope.outer == nil { + scope.c.p.code[pos] = initGlobalP(b.name) + } else { + b.markAccessPointAtScope(scope, pos) + scope.c.p.code[pos] = initStackP(0) + } +} + +func (b *binding) emitGetVar(callee bool) { + b.markAccessPoint() + if b.isVar && !b.isArg { + b.scope.c.emit(&loadMixed{name: b.name, callee: callee}) + } else { + b.scope.c.emit(&loadMixedLex{name: b.name, callee: callee}) + } +} + +func (b *binding) emitResolveVar(strict bool) { + b.markAccessPoint() + if b.isVar && !b.isArg { + b.scope.c.emit(&resolveMixed{name: b.name, strict: strict, typ: varTypeVar}) + } else { + var typ varType + if b.isConst { + if b.isStrict { + typ = varTypeStrictConst + } else { + typ = varTypeConst + } + } else { + typ = varTypeLet + } + b.scope.c.emit(&resolveMixed{name: b.name, strict: strict, typ: typ}) + } +} + +func (b *binding) moveToStash() { + if b.isArg && !b.scope.argsInStash { + b.scope.moveArgsToStash() + } else { + b.inStash = true + b.scope.needStash = true + } +} + +func (b *binding) useCount() (count int) { + for _, a := range b.accessPoints { + count += len(*a) + } + return +} + +type scope struct { + c *compiler + prg *Program + outer *scope + nested []*scope + boundNames map[unistring.String]*binding + bindings []*binding + base int + numArgs int + + // function type. If not funcNone, this is a function or a top-level lexical environment + funcType funcType + + // in strict mode + strict bool + // eval top-level scope + eval bool + // at least one inner scope has direct eval() which can lookup names dynamically (by name) + dynLookup bool + // at least one binding has been marked for placement in stash + needStash bool + + // is a variable environment, i.e. the target for dynamically created var bindings + variable bool + // a function scope that has at least one direct eval() and non-strict, so the variables can be added dynamically + dynamic bool + // arguments have been marked for placement in stash (functions only) + argsInStash bool + // need 'arguments' object (functions only) + argsNeeded bool +} + +type block struct { + typ blockType + label unistring.String + cont int + breaks []int + conts []int + outer *block + breaking *block // set when the 'finally' block is an empty break statement sequence + needResult bool +} + +func (c *compiler) leaveScopeBlock(enter *enterBlock) { + c.updateEnterBlock(enter) + leave := &leaveBlock{ + stackSize: enter.stackSize, + popStash: enter.stashSize > 0, + } + c.emit(leave) + for _, pc := range c.block.breaks { + c.p.code[pc] = leave + } + c.block.breaks = nil + c.leaveBlock() +} + +func (c *compiler) leaveBlock() { + lbl := len(c.p.code) + for _, item := range c.block.breaks { + c.p.code[item] = jump(lbl - item) + } + if t := c.block.typ; t == blockLoop || t == blockLoopEnum { + for _, item := range c.block.conts { + c.p.code[item] = jump(c.block.cont - item) + } + } + c.block = c.block.outer +} + +func (e *CompilerSyntaxError) Error() string { + if e.File != nil { + return fmt.Sprintf("SyntaxError: %s at %s", e.Message, e.File.Position(e.Offset)) + } + return fmt.Sprintf("SyntaxError: %s", e.Message) +} + +func (e *CompilerReferenceError) Error() string { + return fmt.Sprintf("ReferenceError: %s", e.Message) +} + +func (c *compiler) newScope() { + strict := false + if c.scope != nil { + strict = c.scope.strict + } + c.scope = &scope{ + c: c, + prg: c.p, + outer: c.scope, + strict: strict, + } +} + +func (c *compiler) newBlockScope() { + c.newScope() + if outer := c.scope.outer; outer != nil { + outer.nested = append(outer.nested, c.scope) + } + c.scope.base = len(c.p.code) +} + +func (c *compiler) popScope() { + c.scope = c.scope.outer +} + +func newCompiler() *compiler { + c := &compiler{ + p: &Program{}, + } + + c.enumGetExpr.init(c, file.Idx(0)) + + return c +} + +func (p *Program) defineLiteralValue(val Value) uint32 { + for idx, v := range p.values { + if v.SameAs(val) { + return uint32(idx) + } + } + idx := uint32(len(p.values)) + p.values = append(p.values, val) + return idx +} + +func (p *Program) dumpCode(logger func(format string, args ...any)) { + p._dumpCode("", logger) +} + +func (p *Program) _dumpCode(indent string, logger func(format string, args ...any)) { + logger("values: %+v", p.values) + dumpInitFields := func(initFields *Program) { + i := indent + ">" + logger("%s ---- init_fields:", i) + initFields._dumpCode(i, logger) + logger("%s ----", i) + } + for pc, ins := range p.code { + logger("%s %d: %T(%v)", indent, pc, ins, ins) + var prg *Program + switch f := ins.(type) { + case newFuncInstruction: + prg = f.getPrg() + case *newDerivedClass: + if f.initFields != nil { + dumpInitFields(f.initFields) + } + prg = f.ctor + case *newClass: + if f.initFields != nil { + dumpInitFields(f.initFields) + } + prg = f.ctor + case *newStaticFieldInit: + if f.initFields != nil { + dumpInitFields(f.initFields) + } + } + if prg != nil { + prg._dumpCode(indent+">", logger) + } + } +} + +func (p *Program) sourceOffset(pc int) int { + i := sort.Search(len(p.srcMap), func(idx int) bool { + return p.srcMap[idx].pc > pc + }) - 1 + if i >= 0 { + return p.srcMap[i].srcPos + } + + return 0 +} + +func (p *Program) addSrcMap(srcPos int) { + if len(p.srcMap) > 0 && p.srcMap[len(p.srcMap)-1].srcPos == srcPos { + return + } + p.srcMap = append(p.srcMap, srcMapItem{pc: len(p.code), srcPos: srcPos}) +} + +func (s *scope) lookupName(name unistring.String) (binding *binding, noDynamics bool) { + noDynamics = true + toStash := false + for curScope := s; ; curScope = curScope.outer { + if curScope.outer != nil { + if b, exists := curScope.boundNames[name]; exists { + if toStash && !b.inStash { + b.moveToStash() + } + binding = b + return + } + } else { + noDynamics = false + return + } + if curScope.dynamic { + noDynamics = false + } + if name == "arguments" && curScope.funcType != funcNone && curScope.funcType != funcArrow { + if curScope.funcType == funcClsInit { + s.c.throwSyntaxError(0, "'arguments' is not allowed in class field initializer or static initialization block") + } + curScope.argsNeeded = true + binding, _ = curScope.bindName(name) + return + } + if curScope.isFunction() { + toStash = true + } + } +} + +func (s *scope) lookupThis() (*binding, bool) { + toStash := false + for curScope := s; curScope != nil; curScope = curScope.outer { + if curScope.outer == nil { + if curScope.eval { + return nil, true + } + } + if b, exists := curScope.boundNames[thisBindingName]; exists { + if toStash && !b.inStash { + b.moveToStash() + } + return b, false + } + if curScope.isFunction() { + toStash = true + } + } + return nil, false +} + +func (s *scope) ensureBoundNamesCreated() { + if s.boundNames == nil { + s.boundNames = make(map[unistring.String]*binding) + } +} + +func (s *scope) addBinding(offset int) *binding { + if len(s.bindings) >= (1<<24)-1 { + s.c.throwSyntaxError(offset, "Too many variables") + } + b := &binding{ + scope: s, + } + s.bindings = append(s.bindings, b) + return b +} + +func (s *scope) bindNameLexical(name unistring.String, unique bool, offset int) (*binding, bool) { + if b := s.boundNames[name]; b != nil { + if unique { + s.c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name) + } + return b, false + } + b := s.addBinding(offset) + b.name = name + s.ensureBoundNamesCreated() + s.boundNames[name] = b + return b, true +} + +func (s *scope) createThisBinding() *binding { + thisBinding, _ := s.bindNameLexical(thisBindingName, false, 0) + thisBinding.isVar = true // don't check on load + return thisBinding +} + +func (s *scope) bindName(name unistring.String) (*binding, bool) { + if !s.isFunction() && !s.variable && s.outer != nil { + return s.outer.bindName(name) + } + b, created := s.bindNameLexical(name, false, 0) + if created { + b.isVar = true + } + return b, created +} + +func (s *scope) bindNameShadow(name unistring.String) (*binding, bool) { + if !s.isFunction() && s.outer != nil { + return s.outer.bindNameShadow(name) + } + + _, exists := s.boundNames[name] + b := &binding{ + scope: s, + name: name, + } + s.bindings = append(s.bindings, b) + s.ensureBoundNamesCreated() + s.boundNames[name] = b + return b, !exists +} + +func (s *scope) nearestFunction() *scope { + for sc := s; sc != nil; sc = sc.outer { + if sc.isFunction() { + return sc + } + } + return nil +} + +func (s *scope) nearestThis() *scope { + for sc := s; sc != nil; sc = sc.outer { + if sc.eval || sc.isFunction() && sc.funcType != funcArrow { + return sc + } + } + return nil +} + +func (s *scope) finaliseVarAlloc(stackOffset int) (stashSize, stackSize int) { + argsInStash := false + if f := s.nearestFunction(); f != nil { + argsInStash = f.argsInStash + } + stackIdx, stashIdx := 0, 0 + allInStash := s.isDynamic() + var derivedCtor bool + if fs := s.nearestThis(); fs != nil && fs.funcType == funcDerivedCtor { + derivedCtor = true + } + for i, b := range s.bindings { + var this bool + if b.name == thisBindingName { + this = true + } + if allInStash || b.inStash { + for scope, aps := range b.accessPoints { + var level uint32 + for sc := scope; sc != nil && sc != s; sc = sc.outer { + if sc.needStash || sc.isDynamic() { + level++ + } + } + if level > 255 { + s.c.throwSyntaxError(0, "Maximum nesting level (256) exceeded") + } + idx := (level << 24) | uint32(stashIdx) + base := scope.base + code := scope.prg.code + if this { + if derivedCtor { + for _, pc := range *aps { + ap := &code[base+pc] + switch (*ap).(type) { + case loadStack: + *ap = loadThisStash(idx) + case initStack: + *ap = initStash(idx) + case resolveThisStack: + *ap = resolveThisStash(idx) + case _ret: + *ap = cret(idx) + default: + s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'") + } + } + } else { + for _, pc := range *aps { + ap := &code[base+pc] + switch (*ap).(type) { + case loadStack: + *ap = loadStash(idx) + case initStack: + *ap = initStash(idx) + default: + s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'") + } + } + } + } else { + for _, pc := range *aps { + ap := &code[base+pc] + switch i := (*ap).(type) { + case loadStack: + *ap = loadStash(idx) + case storeStack: + *ap = storeStash(idx) + case storeStackP: + *ap = storeStashP(idx) + case loadStackLex: + *ap = loadStashLex(idx) + case storeStackLex: + *ap = storeStashLex(idx) + case storeStackLexP: + *ap = storeStashLexP(idx) + case initStackP: + *ap = initStashP(idx) + case initStack: + *ap = initStash(idx) + case *loadMixed: + i.idx = idx + case *loadMixedLex: + i.idx = idx + case *resolveMixed: + i.idx = idx + default: + s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for binding: %T", i) + } + } + } + } + stashIdx++ + } else { + var idx int + if !this { + if i < s.numArgs { + idx = -(i + 1) + } else { + stackIdx++ + idx = stackIdx + stackOffset + } + } + for scope, aps := range b.accessPoints { + var level int + for sc := scope; sc != nil && sc != s; sc = sc.outer { + if sc.needStash || sc.isDynamic() { + level++ + } + } + if level > 255 { + s.c.throwSyntaxError(0, "Maximum nesting level (256) exceeded") + } + code := scope.prg.code + base := scope.base + if this { + if derivedCtor { + for _, pc := range *aps { + ap := &code[base+pc] + switch (*ap).(type) { + case loadStack: + *ap = loadThisStack{} + case initStack: + // no-op + case resolveThisStack: + // no-op + case _ret: + // no-op, already in the right place + default: + s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for 'this'") + } + } + } /*else { + no-op + }*/ + } else if argsInStash { + for _, pc := range *aps { + ap := &code[base+pc] + switch i := (*ap).(type) { + case loadStack: + *ap = loadStack1(idx) + case storeStack: + *ap = storeStack1(idx) + case storeStackP: + *ap = storeStack1P(idx) + case loadStackLex: + *ap = loadStack1Lex(idx) + case storeStackLex: + *ap = storeStack1Lex(idx) + case storeStackLexP: + *ap = storeStack1LexP(idx) + case initStackP: + *ap = initStack1P(idx) + case initStack: + *ap = initStack1(idx) + case *loadMixed: + *ap = &loadMixedStack1{name: i.name, idx: idx, level: uint8(level), callee: i.callee} + case *loadMixedLex: + *ap = &loadMixedStack1Lex{name: i.name, idx: idx, level: uint8(level), callee: i.callee} + case *resolveMixed: + *ap = &resolveMixedStack1{typ: i.typ, name: i.name, idx: idx, level: uint8(level), strict: i.strict} + default: + s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for binding: %T", i) + } + } + } else { + for _, pc := range *aps { + ap := &code[base+pc] + switch i := (*ap).(type) { + case loadStack: + *ap = loadStack(idx) + case storeStack: + *ap = storeStack(idx) + case storeStackP: + *ap = storeStackP(idx) + case loadStackLex: + *ap = loadStackLex(idx) + case storeStackLex: + *ap = storeStackLex(idx) + case storeStackLexP: + *ap = storeStackLexP(idx) + case initStack: + *ap = initStack(idx) + case initStackP: + *ap = initStackP(idx) + case *loadMixed: + *ap = &loadMixedStack{name: i.name, idx: idx, level: uint8(level), callee: i.callee} + case *loadMixedLex: + *ap = &loadMixedStackLex{name: i.name, idx: idx, level: uint8(level), callee: i.callee} + case *resolveMixed: + *ap = &resolveMixedStack{typ: i.typ, name: i.name, idx: idx, level: uint8(level), strict: i.strict} + default: + s.c.assert(false, s.c.p.sourceOffset(pc), "Unsupported instruction for binding: %T", i) + } + } + } + } + } + } + for _, nested := range s.nested { + nested.finaliseVarAlloc(stackIdx + stackOffset) + } + return stashIdx, stackIdx +} + +func (s *scope) moveArgsToStash() { + for _, b := range s.bindings { + if !b.isArg { + break + } + b.inStash = true + } + s.argsInStash = true + s.needStash = true +} + +func (c *compiler) trimCode(delta int) { + src := c.p.code[delta:] + newCode := make([]instruction, len(src)) + copy(newCode, src) + if cap(c.codeScratchpad) < cap(c.p.code) { + c.codeScratchpad = c.p.code[:0] + } + c.p.code = newCode +} + +func (s *scope) trimCode(delta int) { + s.c.trimCode(delta) + if delta != 0 { + srcMap := s.c.p.srcMap + for i := range srcMap { + srcMap[i].pc -= delta + } + s.adjustBase(-delta) + } +} + +func (s *scope) adjustBase(delta int) { + s.base += delta + for _, nested := range s.nested { + nested.adjustBase(delta) + } +} + +func (s *scope) makeNamesMap() map[unistring.String]uint32 { + l := len(s.bindings) + if l == 0 { + return nil + } + names := make(map[unistring.String]uint32, l) + for i, b := range s.bindings { + idx := uint32(i) + if b.isConst { + idx |= maskConst + if b.isStrict { + idx |= maskStrict + } + } + if b.isVar { + idx |= maskVar + } + names[b.name] = idx + } + return names +} + +func (s *scope) isDynamic() bool { + return s.dynLookup || s.dynamic +} + +func (s *scope) isFunction() bool { + return s.funcType != funcNone && !s.eval +} + +func (s *scope) deleteBinding(b *binding) { + idx := 0 + for i, bb := range s.bindings { + if bb == b { + idx = i + goto found + } + } + return +found: + delete(s.boundNames, b.name) + copy(s.bindings[idx:], s.bindings[idx+1:]) + l := len(s.bindings) - 1 + s.bindings[l] = nil + s.bindings = s.bindings[:l] +} + +func (c *compiler) compile(in *ast.Program, strict, inGlobal bool, evalVm *vm) { + c.ctxVM = evalVm + + eval := evalVm != nil + c.p.src = in.File + c.newScope() + scope := c.scope + scope.dynamic = true + scope.eval = eval + if !strict && len(in.Body) > 0 { + strict = c.isStrict(in.Body) != nil + } + scope.strict = strict + ownVarScope := eval && strict + ownLexScope := !inGlobal || eval + if ownVarScope { + c.newBlockScope() + scope = c.scope + scope.variable = true + } + if eval && !inGlobal { + for s := evalVm.stash; s != nil; s = s.outer { + if ft := s.funcType; ft != funcNone && ft != funcArrow { + scope.funcType = ft + break + } + } + } + funcs := c.extractFunctions(in.Body) + c.createFunctionBindings(funcs) + numFuncs := len(scope.bindings) + if inGlobal && !ownVarScope { + if numFuncs == len(funcs) { + c.compileFunctionsGlobalAllUnique(funcs) + } else { + c.compileFunctionsGlobal(funcs) + } + } + c.compileDeclList(in.DeclarationList, false) + numVars := len(scope.bindings) - numFuncs + vars := make([]unistring.String, len(scope.bindings)) + for i, b := range scope.bindings { + vars[i] = b.name + } + if len(vars) > 0 && !ownVarScope && ownLexScope { + if inGlobal { + c.emit(&bindGlobal{ + vars: vars[numFuncs:], + funcs: vars[:numFuncs], + deletable: eval, + }) + } else { + c.emit(&bindVars{names: vars, deletable: eval}) + } + } + var enter *enterBlock + if c.compileLexicalDeclarations(in.Body, ownVarScope || !ownLexScope) { + if ownLexScope { + c.block = &block{ + outer: c.block, + typ: blockScope, + needResult: true, + } + enter = &enterBlock{} + c.emit(enter) + } + } + if len(scope.bindings) > 0 && !ownLexScope { + var lets, consts []unistring.String + for _, b := range c.scope.bindings[numFuncs+numVars:] { + if b.isConst { + consts = append(consts, b.name) + } else { + lets = append(lets, b.name) + } + } + c.emit(&bindGlobal{ + vars: vars[numFuncs:], + funcs: vars[:numFuncs], + lets: lets, + consts: consts, + }) + } + if !inGlobal || ownVarScope { + c.compileFunctions(funcs) + } + c.compileStatements(in.Body, true) + if enter != nil { + c.leaveScopeBlock(enter) + c.popScope() + } + + scope.finaliseVarAlloc(0) +} + +func (c *compiler) compileDeclList(v []*ast.VariableDeclaration, inFunc bool) { + for _, value := range v { + c.createVarBindings(value, inFunc) + } +} + +func (c *compiler) extractLabelled(st ast.Statement) ast.Statement { + if st, ok := st.(*ast.LabelledStatement); ok { + return c.extractLabelled(st.Statement) + } + return st +} + +func (c *compiler) extractFunctions(list []ast.Statement) (funcs []*ast.FunctionDeclaration) { + for _, st := range list { + var decl *ast.FunctionDeclaration + switch st := c.extractLabelled(st).(type) { + case *ast.FunctionDeclaration: + decl = st + case *ast.LabelledStatement: + if st1, ok := st.Statement.(*ast.FunctionDeclaration); ok { + decl = st1 + } else { + continue + } + default: + continue + } + funcs = append(funcs, decl) + } + return +} + +func (c *compiler) createFunctionBindings(funcs []*ast.FunctionDeclaration) { + s := c.scope + if s.outer != nil { + unique := !s.isFunction() && !s.variable && s.strict + if !unique { + hasNonStandard := false + for _, decl := range funcs { + if !decl.Function.Async && !decl.Function.Generator { + s.bindNameLexical(decl.Function.Name.Name, false, int(decl.Function.Name.Idx1())-1) + } else { + hasNonStandard = true + } + } + if hasNonStandard { + for _, decl := range funcs { + if decl.Function.Async || decl.Function.Generator { + s.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) + } + } + } + } else { + for _, decl := range funcs { + s.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) + } + } + } else { + for _, decl := range funcs { + s.bindName(decl.Function.Name.Name) + } + } +} + +func (c *compiler) compileFunctions(list []*ast.FunctionDeclaration) { + for _, decl := range list { + c.compileFunction(decl) + } +} + +func (c *compiler) compileFunctionsGlobalAllUnique(list []*ast.FunctionDeclaration) { + for _, decl := range list { + c.compileFunctionLiteral(decl.Function, false).emitGetter(true) + } +} + +func (c *compiler) compileFunctionsGlobal(list []*ast.FunctionDeclaration) { + m := make(map[unistring.String]int, len(list)) + for i := len(list) - 1; i >= 0; i-- { + name := list[i].Function.Name.Name + if _, exists := m[name]; !exists { + m[name] = i + } + } + idx := 0 + for i, decl := range list { + name := decl.Function.Name.Name + if m[name] == i { + c.compileFunctionLiteral(decl.Function, false).emitGetter(true) + c.scope.bindings[idx] = c.scope.boundNames[name] + idx++ + } else { + leave := c.enterDummyMode() + c.compileFunctionLiteral(decl.Function, false).emitGetter(false) + leave() + } + } +} + +func (c *compiler) createVarIdBinding(name unistring.String, offset int, inFunc bool) { + if c.scope.strict { + c.checkIdentifierLName(name, offset) + c.checkIdentifierName(name, offset) + } + if !inFunc || name != "arguments" { + c.scope.bindName(name) + } +} + +func (c *compiler) createBindings(target ast.Expression, createIdBinding func(name unistring.String, offset int)) { + switch target := target.(type) { + case *ast.Identifier: + createIdBinding(target.Name, int(target.Idx)-1) + case *ast.ObjectPattern: + for _, prop := range target.Properties { + switch prop := prop.(type) { + case *ast.PropertyShort: + createIdBinding(prop.Name.Name, int(prop.Name.Idx)-1) + case *ast.PropertyKeyed: + c.createBindings(prop.Value, createIdBinding) + default: + c.throwSyntaxError(int(target.Idx0()-1), "unsupported property type in ObjectPattern: %T", prop) + } + } + if target.Rest != nil { + c.createBindings(target.Rest, createIdBinding) + } + case *ast.ArrayPattern: + for _, elt := range target.Elements { + if elt != nil { + c.createBindings(elt, createIdBinding) + } + } + if target.Rest != nil { + c.createBindings(target.Rest, createIdBinding) + } + case *ast.AssignExpression: + c.createBindings(target.Left, createIdBinding) + default: + c.throwSyntaxError(int(target.Idx0()-1), "unsupported binding target: %T", target) + } +} + +func (c *compiler) createVarBinding(target ast.Expression, inFunc bool) { + c.createBindings(target, func(name unistring.String, offset int) { + c.createVarIdBinding(name, offset, inFunc) + }) +} + +func (c *compiler) createVarBindings(v *ast.VariableDeclaration, inFunc bool) { + for _, item := range v.List { + c.createVarBinding(item.Target, inFunc) + } +} + +func (c *compiler) createLexicalIdBinding(name unistring.String, isConst bool, offset int) *binding { + if name == "let" { + c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") + } + if c.scope.strict { + c.checkIdentifierLName(name, offset) + c.checkIdentifierName(name, offset) + } + b, _ := c.scope.bindNameLexical(name, true, offset) + if isConst { + b.isConst, b.isStrict = true, true + } + return b +} + +func (c *compiler) createLexicalIdBindingFuncBody(name unistring.String, isConst bool, offset int, calleeBinding *binding) *binding { + if name == "let" { + c.throwSyntaxError(offset, "let is disallowed as a lexically bound name") + } + if c.scope.strict { + c.checkIdentifierLName(name, offset) + c.checkIdentifierName(name, offset) + } + paramScope := c.scope.outer + parentBinding := paramScope.boundNames[name] + if parentBinding != nil { + if parentBinding != calleeBinding && (name != "arguments" || !paramScope.argsNeeded) { + c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name) + } + } + b, _ := c.scope.bindNameLexical(name, true, offset) + if isConst { + b.isConst, b.isStrict = true, true + } + return b +} + +func (c *compiler) createLexicalBinding(target ast.Expression, isConst bool) { + c.createBindings(target, func(name unistring.String, offset int) { + c.createLexicalIdBinding(name, isConst, offset) + }) +} + +func (c *compiler) createLexicalBindings(lex *ast.LexicalDeclaration) { + for _, d := range lex.List { + c.createLexicalBinding(d.Target, lex.Token == token.CONST) + } +} + +func (c *compiler) compileLexicalDeclarations(list []ast.Statement, scopeDeclared bool) bool { + for _, st := range list { + if lex, ok := st.(*ast.LexicalDeclaration); ok { + if !scopeDeclared { + c.newBlockScope() + scopeDeclared = true + } + c.createLexicalBindings(lex) + } else if cls, ok := st.(*ast.ClassDeclaration); ok { + if !scopeDeclared { + c.newBlockScope() + scopeDeclared = true + } + c.createLexicalIdBinding(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1) + } + } + return scopeDeclared +} + +func (c *compiler) compileLexicalDeclarationsFuncBody(list []ast.Statement, calleeBinding *binding) { + for _, st := range list { + if lex, ok := st.(*ast.LexicalDeclaration); ok { + isConst := lex.Token == token.CONST + for _, d := range lex.List { + c.createBindings(d.Target, func(name unistring.String, offset int) { + c.createLexicalIdBindingFuncBody(name, isConst, offset, calleeBinding) + }) + } + } else if cls, ok := st.(*ast.ClassDeclaration); ok { + c.createLexicalIdBindingFuncBody(cls.Class.Name.Name, false, int(cls.Class.Name.Idx)-1, calleeBinding) + } + } +} + +func (c *compiler) compileFunction(v *ast.FunctionDeclaration) { + name := v.Function.Name.Name + b := c.scope.boundNames[name] + if b == nil || b.isVar { + e := &compiledIdentifierExpr{ + name: v.Function.Name.Name, + } + e.init(c, v.Function.Idx0()) + e.emitSetter(c.compileFunctionLiteral(v.Function, false), false) + } else { + c.compileFunctionLiteral(v.Function, false).emitGetter(true) + b.emitInitP() + } +} + +func (c *compiler) compileStandaloneFunctionDecl(v *ast.FunctionDeclaration) { + if v.Function.Async { + c.throwSyntaxError(int(v.Idx0())-1, "Async functions can only be declared at top level or inside a block.") + } + if v.Function.Generator { + c.throwSyntaxError(int(v.Idx0())-1, "Generators can only be declared at top level or inside a block.") + } + if c.scope.strict { + c.throwSyntaxError(int(v.Idx0())-1, "In strict mode code, functions can only be declared at top level or inside a block.") + } + c.throwSyntaxError(int(v.Idx0())-1, "In non-strict mode code, functions can only be declared at top level, inside a block, or as the body of an if statement.") +} + +func (c *compiler) emit(instructions ...instruction) { + c.p.code = append(c.p.code, instructions...) +} + +func (c *compiler) throwSyntaxError(offset int, format string, args ...any) { + panic(&CompilerSyntaxError{ + CompilerError: CompilerError{ + File: c.p.src, + Offset: offset, + Message: fmt.Sprintf(format, args...), + }, + }) +} + +func (c *compiler) isStrict(list []ast.Statement) *ast.StringLiteral { + for _, st := range list { + if st, ok := st.(*ast.ExpressionStatement); ok { + if e, ok := st.Expression.(*ast.StringLiteral); ok { + if e.Literal == `"use strict"` || e.Literal == `'use strict'` { + return e + } + } else { + break + } + } else { + break + } + } + return nil +} + +func (c *compiler) isStrictStatement(s ast.Statement) *ast.StringLiteral { + if s, ok := s.(*ast.BlockStatement); ok { + return c.isStrict(s.List) + } + return nil +} + +func (c *compiler) checkIdentifierName(name unistring.String, offset int) { + switch name { + case "implements", "interface", "let", "package", "private", "protected", "public", "static", "yield": + c.throwSyntaxError(offset, "Unexpected strict mode reserved word") + } +} + +func (c *compiler) checkIdentifierLName(name unistring.String, offset int) { + switch name { + case "eval", "arguments": + c.throwSyntaxError(offset, "Assignment to eval or arguments is not allowed in strict mode") + } +} + +// Enter a 'dummy' compilation mode. Any code produced after this method is called will be discarded after +// leaveFunc is called with no additional side effects. This is useful for compiling code inside a +// constant falsy condition 'if' branch or a loop (i.e 'if (false) { ... } or while (false) { ... }). +// Such code should not be included in the final compilation result as it's never called, but it must +// still produce compilation errors if there are any. +// TODO: make sure variable lookups do not de-optimise parent scopes +func (c *compiler) enterDummyMode() (leaveFunc func()) { + savedBlock, savedProgram := c.block, c.p + if savedBlock != nil { + c.block = &block{ + typ: savedBlock.typ, + label: savedBlock.label, + outer: savedBlock.outer, + breaking: savedBlock.breaking, + } + } + c.p = &Program{ + src: c.p.src, + } + c.newScope() + return func() { + c.block, c.p = savedBlock, savedProgram + c.popScope() + } +} + +func (c *compiler) compileStatementDummy(statement ast.Statement) { + leave := c.enterDummyMode() + c.compileStatement(statement, false) + leave() +} + +func (c *compiler) assert(cond bool, offset int, msg string, args ...any) { + if !cond { + c.throwSyntaxError(offset, "Compiler bug: "+msg, args...) + } +} + +func privateIdString(desc unistring.String) unistring.String { + return asciiString("#").Concat(stringValueFromRaw(desc)).string() +} + +type privateName struct { + idx int + isStatic bool + isMethod bool + hasGetter, hasSetter bool +} + +type resolvedPrivateName struct { + name unistring.String + idx uint32 + level uint8 + isStatic bool + isMethod bool +} + +func (r *resolvedPrivateName) string() unistring.String { + return privateIdString(r.name) +} + +type privateEnvRegistry struct { + fields, methods []unistring.String +} + +type classScope struct { + c *compiler + privateNames map[unistring.String]*privateName + + instanceEnv, staticEnv privateEnvRegistry + + outer *classScope +} + +func (r *privateEnvRegistry) createPrivateMethodId(name unistring.String) int { + r.methods = append(r.methods, name) + return len(r.methods) - 1 +} + +func (r *privateEnvRegistry) createPrivateFieldId(name unistring.String) int { + r.fields = append(r.fields, name) + return len(r.fields) - 1 +} + +func (s *classScope) declarePrivateId(name unistring.String, kind ast.PropertyKind, isStatic bool, offset int) { + pn := s.privateNames[name] + if pn != nil { + if pn.isStatic == isStatic { + switch kind { + case ast.PropertyKindGet: + if pn.hasSetter && !pn.hasGetter { + pn.hasGetter = true + return + } + case ast.PropertyKindSet: + if pn.hasGetter && !pn.hasSetter { + pn.hasSetter = true + return + } + } + } + s.c.throwSyntaxError(offset, "Identifier '#%s' has already been declared", name) + panic("unreachable") + } + var env *privateEnvRegistry + if isStatic { + env = &s.staticEnv + } else { + env = &s.instanceEnv + } + + pn = &privateName{ + isStatic: isStatic, + hasGetter: kind == ast.PropertyKindGet, + hasSetter: kind == ast.PropertyKindSet, + } + if kind != ast.PropertyKindValue { + pn.idx = env.createPrivateMethodId(name) + pn.isMethod = true + } else { + pn.idx = env.createPrivateFieldId(name) + } + + if s.privateNames == nil { + s.privateNames = make(map[unistring.String]*privateName) + } + s.privateNames[name] = pn +} + +func (s *classScope) getDeclaredPrivateId(name unistring.String) *privateName { + if n := s.privateNames[name]; n != nil { + return n + } + s.c.assert(false, 0, "getDeclaredPrivateId() for undeclared id") + panic("unreachable") +} + +func (c *compiler) resolvePrivateName(name unistring.String, offset int) (*resolvedPrivateName, *privateId) { + level := 0 + for s := c.classScope; s != nil; s = s.outer { + if len(s.privateNames) > 0 { + if pn := s.privateNames[name]; pn != nil { + return &resolvedPrivateName{ + name: name, + idx: uint32(pn.idx), + level: uint8(level), + isStatic: pn.isStatic, + isMethod: pn.isMethod, + }, nil + } + level++ + } + } + if c.ctxVM != nil { + for s := c.ctxVM.privEnv; s != nil; s = s.outer { + if id := s.names[name]; id != nil { + return nil, id + } + } + } + c.throwSyntaxError(offset, "Private field '#%s' must be declared in an enclosing class", name) + panic("unreachable") +} diff --git a/pkg/xscript/engine/compiler_expr.go b/pkg/xscript/engine/compiler_expr.go new file mode 100644 index 0000000..a1e8915 --- /dev/null +++ b/pkg/xscript/engine/compiler_expr.go @@ -0,0 +1,3609 @@ +package engine + +import ( + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +type compiledExpr interface { + emitGetter(putOnStack bool) + emitSetter(valueExpr compiledExpr, putOnStack bool) + emitRef() + emitUnary(prepare, body func(), postfix, putOnStack bool) + deleteExpr() compiledExpr + constant() bool + addSrcMap() +} + +type compiledExprOrRef interface { + compiledExpr + emitGetterOrRef() +} + +type compiledCallExpr struct { + baseCompiledExpr + args []compiledExpr + callee compiledExpr + + isVariadic bool +} + +type compiledNewExpr struct { + compiledCallExpr +} + +type compiledObjectLiteral struct { + baseCompiledExpr + expr *ast.ObjectLiteral +} + +type compiledArrayLiteral struct { + baseCompiledExpr + expr *ast.ArrayLiteral +} + +type compiledRegexpLiteral struct { + baseCompiledExpr + expr *ast.RegExpLiteral +} + +type compiledLiteral struct { + baseCompiledExpr + val Value +} + +type compiledTemplateLiteral struct { + baseCompiledExpr + tag compiledExpr + elements []*ast.TemplateElement + expressions []compiledExpr +} + +type compiledAssignExpr struct { + baseCompiledExpr + left, right compiledExpr + operator token.Token +} + +type compiledObjectAssignmentPattern struct { + baseCompiledExpr + expr *ast.ObjectPattern +} + +type compiledArrayAssignmentPattern struct { + baseCompiledExpr + expr *ast.ArrayPattern +} + +type deleteGlobalExpr struct { + baseCompiledExpr + name unistring.String +} + +type deleteVarExpr struct { + baseCompiledExpr + name unistring.String +} + +type deletePropExpr struct { + baseCompiledExpr + left compiledExpr + name unistring.String +} + +type deleteElemExpr struct { + baseCompiledExpr + left, member compiledExpr +} + +type constantExpr struct { + baseCompiledExpr + val Value +} + +type baseCompiledExpr struct { + c *compiler + offset int +} + +type compiledIdentifierExpr struct { + baseCompiledExpr + name unistring.String +} + +type compiledAwaitExpression struct { + baseCompiledExpr + arg compiledExpr +} + +type compiledYieldExpression struct { + baseCompiledExpr + arg compiledExpr + delegate bool +} + +type funcType uint8 + +const ( + funcNone funcType = iota + funcRegular + funcArrow + funcMethod + funcClsInit + funcCtor + funcDerivedCtor +) + +type compiledFunctionLiteral struct { + baseCompiledExpr + name *ast.Identifier + parameterList *ast.ParameterList + body []ast.Statement + source string + declarationList []*ast.VariableDeclaration + lhsName unistring.String + strict *ast.StringLiteral + homeObjOffset uint32 + typ funcType + isExpr bool + + isAsync, isGenerator bool +} + +type compiledBracketExpr struct { + baseCompiledExpr + left, member compiledExpr +} + +type compiledThisExpr struct { + baseCompiledExpr +} + +type compiledSuperExpr struct { + baseCompiledExpr +} + +type compiledNewTarget struct { + baseCompiledExpr +} + +type compiledSequenceExpr struct { + baseCompiledExpr + sequence []compiledExpr +} + +type compiledUnaryExpr struct { + baseCompiledExpr + operand compiledExpr + operator token.Token + postfix bool +} + +type compiledConditionalExpr struct { + baseCompiledExpr + test, consequent, alternate compiledExpr +} + +type compiledLogicalOr struct { + baseCompiledExpr + left, right compiledExpr +} + +type compiledCoalesce struct { + baseCompiledExpr + left, right compiledExpr +} + +type compiledLogicalAnd struct { + baseCompiledExpr + left, right compiledExpr +} + +type compiledBinaryExpr struct { + baseCompiledExpr + left, right compiledExpr + operator token.Token +} + +type compiledEnumGetExpr struct { + baseCompiledExpr +} + +type defaultDeleteExpr struct { + baseCompiledExpr + expr compiledExpr +} + +type compiledSpreadCallArgument struct { + baseCompiledExpr + expr compiledExpr +} + +type compiledOptionalChain struct { + baseCompiledExpr + expr compiledExpr +} + +type compiledOptional struct { + baseCompiledExpr + expr compiledExpr +} + +func (e *defaultDeleteExpr) emitGetter(putOnStack bool) { + e.expr.emitGetter(false) + if putOnStack { + e.c.emit(loadVal(e.c.p.defineLiteralValue(valueTrue))) + } +} + +func (c *compiler) compileExpression(v ast.Expression) compiledExpr { + // log.Printf("compileExpression: %T", v) + switch v := v.(type) { + case nil: + return nil + case *ast.AssignExpression: + return c.compileAssignExpression(v) + case *ast.NumberLiteral: + return c.compileNumberLiteral(v) + case *ast.StringLiteral: + return c.compileStringLiteral(v) + case *ast.TemplateLiteral: + return c.compileTemplateLiteral(v) + case *ast.BooleanLiteral: + return c.compileBooleanLiteral(v) + case *ast.NullLiteral: + r := &compiledLiteral{ + val: _null, + } + r.init(c, v.Idx0()) + return r + case *ast.Identifier: + return c.compileIdentifierExpression(v) + case *ast.CallExpression: + return c.compileCallExpression(v) + case *ast.ObjectLiteral: + return c.compileObjectLiteral(v) + case *ast.ArrayLiteral: + return c.compileArrayLiteral(v) + case *ast.RegExpLiteral: + return c.compileRegexpLiteral(v) + case *ast.BinaryExpression: + return c.compileBinaryExpression(v) + case *ast.UnaryExpression: + return c.compileUnaryExpression(v) + case *ast.ConditionalExpression: + return c.compileConditionalExpression(v) + case *ast.FunctionLiteral: + return c.compileFunctionLiteral(v, true) + case *ast.ArrowFunctionLiteral: + return c.compileArrowFunctionLiteral(v) + case *ast.ClassLiteral: + return c.compileClassLiteral(v, true) + case *ast.DotExpression: + return c.compileDotExpression(v) + case *ast.PrivateDotExpression: + return c.compilePrivateDotExpression(v) + case *ast.BracketExpression: + return c.compileBracketExpression(v) + case *ast.ThisExpression: + r := &compiledThisExpr{} + r.init(c, v.Idx0()) + return r + case *ast.SuperExpression: + c.throwSyntaxError(int(v.Idx0())-1, "'super' keyword unexpected here") + panic("unreachable") + case *ast.SequenceExpression: + return c.compileSequenceExpression(v) + case *ast.NewExpression: + return c.compileNewExpression(v) + case *ast.MetaProperty: + return c.compileMetaProperty(v) + case *ast.ObjectPattern: + return c.compileObjectAssignmentPattern(v) + case *ast.ArrayPattern: + return c.compileArrayAssignmentPattern(v) + case *ast.OptionalChain: + r := &compiledOptionalChain{ + expr: c.compileExpression(v.Expression), + } + r.init(c, v.Idx0()) + return r + case *ast.Optional: + r := &compiledOptional{ + expr: c.compileExpression(v.Expression), + } + r.init(c, v.Idx0()) + return r + case *ast.AwaitExpression: + r := &compiledAwaitExpression{ + arg: c.compileExpression(v.Argument), + } + r.init(c, v.Await) + return r + case *ast.YieldExpression: + r := &compiledYieldExpression{ + arg: c.compileExpression(v.Argument), + delegate: v.Delegate, + } + r.init(c, v.Yield) + return r + default: + c.assert(false, int(v.Idx0())-1, "Unknown expression type: %T", v) + panic("unreachable") + } +} + +func (e *baseCompiledExpr) constant() bool { + return false +} + +func (e *baseCompiledExpr) init(c *compiler, idx file.Idx) { + e.c = c + e.offset = int(idx) - 1 +} + +func (e *baseCompiledExpr) emitSetter(compiledExpr, bool) { + e.c.throwSyntaxError(e.offset, "Not a valid left-value expression") +} + +func (e *baseCompiledExpr) emitRef() { + e.c.assert(false, e.offset, "Cannot emit reference for this type of expression") +} + +func (e *baseCompiledExpr) deleteExpr() compiledExpr { + r := &constantExpr{ + val: valueTrue, + } + r.init(e.c, file.Idx(e.offset+1)) + return r +} + +func (e *baseCompiledExpr) emitUnary(func(), func(), bool, bool) { + e.c.throwSyntaxError(e.offset, "Not a valid left-value expression") +} + +func (e *baseCompiledExpr) addSrcMap() { + if e.offset >= 0 { + e.c.p.addSrcMap(e.offset) + } +} + +func (e *constantExpr) emitGetter(putOnStack bool) { + if putOnStack { + e.addSrcMap() + e.c.emit(loadVal(e.c.p.defineLiteralValue(e.val))) + } +} + +func (e *compiledIdentifierExpr) emitGetter(putOnStack bool) { + e.addSrcMap() + if b, noDynamics := e.c.scope.lookupName(e.name); noDynamics { + e.c.assert(b != nil, e.offset, "No dynamics and not found") + if putOnStack { + b.emitGet() + } else { + b.emitGetP() + } + } else { + if b != nil { + b.emitGetVar(false) + } else { + e.c.emit(loadDynamic(e.name)) + } + if !putOnStack { + e.c.emit(pop) + } + } +} + +func (e *compiledIdentifierExpr) emitGetterOrRef() { + e.addSrcMap() + if b, noDynamics := e.c.scope.lookupName(e.name); noDynamics { + e.c.assert(b != nil, e.offset, "No dynamics and not found") + b.emitGet() + } else { + if b != nil { + b.emitGetVar(false) + } else { + e.c.emit(loadDynamicRef(e.name)) + } + } +} + +func (e *compiledIdentifierExpr) emitGetterAndCallee() { + e.addSrcMap() + if b, noDynamics := e.c.scope.lookupName(e.name); noDynamics { + e.c.assert(b != nil, e.offset, "No dynamics and not found") + e.c.emit(loadUndef) + b.emitGet() + } else { + if b != nil { + b.emitGetVar(true) + } else { + e.c.emit(loadDynamicCallee(e.name)) + } + } +} + +func (e *compiledIdentifierExpr) emitVarSetter1(putOnStack bool, emitRight func(isRef bool)) { + e.addSrcMap() + c := e.c + + if b, noDynamics := c.scope.lookupName(e.name); noDynamics { + if c.scope.strict { + c.checkIdentifierLName(e.name, e.offset) + } + emitRight(false) + if b != nil { + if putOnStack { + b.emitSet() + } else { + b.emitSetP() + } + } else { + if c.scope.strict { + c.emit(setGlobalStrict(e.name)) + } else { + c.emit(setGlobal(e.name)) + } + if !putOnStack { + c.emit(pop) + } + } + } else { + c.emitVarRef(e.name, e.offset, b) + emitRight(true) + if putOnStack { + c.emit(putValue) + } else { + c.emit(putValueP) + } + } +} + +func (e *compiledIdentifierExpr) emitVarSetter(valueExpr compiledExpr, putOnStack bool) { + e.emitVarSetter1(putOnStack, func(bool) { + e.c.emitNamedOrConst(valueExpr, e.name) + }) +} + +func (c *compiler) emitVarRef(name unistring.String, offset int, b *binding) { + if c.scope.strict { + c.checkIdentifierLName(name, offset) + } + + if b != nil { + b.emitResolveVar(c.scope.strict) + } else { + if c.scope.strict { + c.emit(resolveVar1Strict(name)) + } else { + c.emit(resolveVar1(name)) + } + } +} + +func (e *compiledIdentifierExpr) emitRef() { + b, _ := e.c.scope.lookupName(e.name) + e.c.emitVarRef(e.name, e.offset, b) +} + +func (e *compiledIdentifierExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) { + e.emitVarSetter(valueExpr, putOnStack) +} + +func (e *compiledIdentifierExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) { + if putOnStack { + e.emitVarSetter1(true, func(isRef bool) { + e.c.emit(loadUndef) + if isRef { + e.c.emit(getValue) + } else { + e.emitGetter(true) + } + if prepare != nil { + prepare() + } + if !postfix { + body() + } + e.c.emit(rdupN(1)) + if postfix { + body() + } + }) + e.c.emit(pop) + } else { + e.emitVarSetter1(false, func(isRef bool) { + if isRef { + e.c.emit(getValue) + } else { + e.emitGetter(true) + } + body() + }) + } +} + +func (e *compiledIdentifierExpr) deleteExpr() compiledExpr { + if e.c.scope.strict { + e.c.throwSyntaxError(e.offset, "Delete of an unqualified identifier in strict mode") + panic("Unreachable") + } + if b, noDynamics := e.c.scope.lookupName(e.name); noDynamics { + if b == nil { + r := &deleteGlobalExpr{ + name: e.name, + } + r.init(e.c, file.Idx(0)) + return r + } + } else { + if b == nil { + r := &deleteVarExpr{ + name: e.name, + } + r.init(e.c, file.Idx(e.offset+1)) + return r + } + } + r := &compiledLiteral{ + val: valueFalse, + } + r.init(e.c, file.Idx(e.offset+1)) + return r +} + +type compiledSuperDotExpr struct { + baseCompiledExpr + name unistring.String +} + +func (e *compiledSuperDotExpr) emitGetter(putOnStack bool) { + e.c.emitLoadThis() + e.c.emit(loadSuper) + e.addSrcMap() + e.c.emit(getPropRecv(e.name)) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledSuperDotExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) { + e.c.emitLoadThis() + e.c.emit(loadSuper) + valueExpr.emitGetter(true) + e.addSrcMap() + if putOnStack { + if e.c.scope.strict { + e.c.emit(setPropRecvStrict(e.name)) + } else { + e.c.emit(setPropRecv(e.name)) + } + } else { + if e.c.scope.strict { + e.c.emit(setPropRecvStrictP(e.name)) + } else { + e.c.emit(setPropRecvP(e.name)) + } + } +} + +func (e *compiledSuperDotExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) { + if !putOnStack { + e.c.emitLoadThis() + e.c.emit(loadSuper, dupLast(2), getPropRecv(e.name)) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setPropRecvStrictP(e.name)) + } else { + e.c.emit(setPropRecvP(e.name)) + } + } else { + if !postfix { + e.c.emitLoadThis() + e.c.emit(loadSuper, dupLast(2), getPropRecv(e.name)) + if prepare != nil { + prepare() + } + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setPropRecvStrict(e.name)) + } else { + e.c.emit(setPropRecv(e.name)) + } + } else { + e.c.emit(loadUndef) + e.c.emitLoadThis() + e.c.emit(loadSuper, dupLast(2), getPropRecv(e.name)) + if prepare != nil { + prepare() + } + e.c.emit(rdupN(3)) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setPropRecvStrictP(e.name)) + } else { + e.c.emit(setPropRecvP(e.name)) + } + } + } +} + +func (e *compiledSuperDotExpr) emitRef() { + e.c.emitLoadThis() + e.c.emit(loadSuper) + if e.c.scope.strict { + e.c.emit(getPropRefRecvStrict(e.name)) + } else { + e.c.emit(getPropRefRecv(e.name)) + } +} + +func (e *compiledSuperDotExpr) deleteExpr() compiledExpr { + return e.c.superDeleteError(e.offset) +} + +type compiledDotExpr struct { + baseCompiledExpr + left compiledExpr + name unistring.String +} + +type compiledPrivateDotExpr struct { + baseCompiledExpr + left compiledExpr + name unistring.String +} + +func (c *compiler) checkSuperBase(idx file.Idx) { + if s := c.scope.nearestThis(); s != nil { + switch s.funcType { + case funcMethod, funcClsInit, funcCtor, funcDerivedCtor: + return + } + } + c.throwSyntaxError(int(idx)-1, "'super' keyword unexpected here") + panic("unreachable") +} + +func (c *compiler) compileDotExpression(v *ast.DotExpression) compiledExpr { + if sup, ok := v.Left.(*ast.SuperExpression); ok { + c.checkSuperBase(sup.Idx) + r := &compiledSuperDotExpr{ + name: v.Identifier.Name, + } + r.init(c, v.Identifier.Idx) + return r + } + + r := &compiledDotExpr{ + left: c.compileExpression(v.Left), + name: v.Identifier.Name, + } + r.init(c, v.Identifier.Idx) + return r +} + +func (c *compiler) compilePrivateDotExpression(v *ast.PrivateDotExpression) compiledExpr { + r := &compiledPrivateDotExpr{ + left: c.compileExpression(v.Left), + name: v.Identifier.Name, + } + r.init(c, v.Identifier.Idx) + return r +} + +func (e *compiledPrivateDotExpr) _emitGetter(rn *resolvedPrivateName, id *privateId) { + if rn != nil { + e.c.emit((*getPrivatePropRes)(rn)) + } else { + e.c.emit((*getPrivatePropId)(id)) + } +} + +func (e *compiledPrivateDotExpr) _emitSetter(rn *resolvedPrivateName, id *privateId) { + if rn != nil { + e.c.emit((*setPrivatePropRes)(rn)) + } else { + e.c.emit((*setPrivatePropId)(id)) + } +} + +func (e *compiledPrivateDotExpr) _emitSetterP(rn *resolvedPrivateName, id *privateId) { + if rn != nil { + e.c.emit((*setPrivatePropResP)(rn)) + } else { + e.c.emit((*setPrivatePropIdP)(id)) + } +} + +func (e *compiledPrivateDotExpr) emitGetter(putOnStack bool) { + e.left.emitGetter(true) + e.addSrcMap() + rn, id := e.c.resolvePrivateName(e.name, e.offset) + e._emitGetter(rn, id) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledPrivateDotExpr) emitSetter(v compiledExpr, putOnStack bool) { + rn, id := e.c.resolvePrivateName(e.name, e.offset) + e.left.emitGetter(true) + v.emitGetter(true) + e.addSrcMap() + if putOnStack { + e._emitSetter(rn, id) + } else { + e._emitSetterP(rn, id) + } +} + +func (e *compiledPrivateDotExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) { + rn, id := e.c.resolvePrivateName(e.name, e.offset) + if !putOnStack { + e.left.emitGetter(true) + e.c.emit(dup) + e._emitGetter(rn, id) + body() + e.addSrcMap() + e._emitSetterP(rn, id) + } else { + if !postfix { + e.left.emitGetter(true) + e.c.emit(dup) + e._emitGetter(rn, id) + if prepare != nil { + prepare() + } + body() + e.addSrcMap() + e._emitSetter(rn, id) + } else { + e.c.emit(loadUndef) + e.left.emitGetter(true) + e.c.emit(dup) + e._emitGetter(rn, id) + if prepare != nil { + prepare() + } + e.c.emit(rdupN(2)) + body() + e.addSrcMap() + e._emitSetterP(rn, id) + } + } +} + +func (e *compiledPrivateDotExpr) deleteExpr() compiledExpr { + e.c.throwSyntaxError(e.offset, "Private fields can not be deleted") + panic("unreachable") +} + +func (e *compiledPrivateDotExpr) emitRef() { + e.left.emitGetter(true) + rn, id := e.c.resolvePrivateName(e.name, e.offset) + if rn != nil { + e.c.emit((*getPrivateRefRes)(rn)) + } else { + e.c.emit((*getPrivateRefId)(id)) + } +} + +type compiledSuperBracketExpr struct { + baseCompiledExpr + member compiledExpr +} + +func (e *compiledSuperBracketExpr) emitGetter(putOnStack bool) { + e.c.emitLoadThis() + e.member.emitGetter(true) + e.c.emit(loadSuper) + e.addSrcMap() + e.c.emit(getElemRecv) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledSuperBracketExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) { + e.c.emitLoadThis() + e.member.emitGetter(true) + e.c.emit(loadSuper) + valueExpr.emitGetter(true) + e.addSrcMap() + if putOnStack { + if e.c.scope.strict { + e.c.emit(setElemRecvStrict) + } else { + e.c.emit(setElemRecv) + } + } else { + if e.c.scope.strict { + e.c.emit(setElemRecvStrictP) + } else { + e.c.emit(setElemRecvP) + } + } +} + +func (e *compiledSuperBracketExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) { + if !putOnStack { + e.c.emitLoadThis() + e.member.emitGetter(true) + e.c.emit(loadSuper, dupLast(3), getElemRecv) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setElemRecvStrictP) + } else { + e.c.emit(setElemRecvP) + } + } else { + if !postfix { + e.c.emitLoadThis() + e.member.emitGetter(true) + e.c.emit(loadSuper, dupLast(3), getElemRecv) + if prepare != nil { + prepare() + } + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setElemRecvStrict) + } else { + e.c.emit(setElemRecv) + } + } else { + e.c.emit(loadUndef) + e.c.emitLoadThis() + e.member.emitGetter(true) + e.c.emit(loadSuper, dupLast(3), getElemRecv) + if prepare != nil { + prepare() + } + e.c.emit(rdupN(4)) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setElemRecvStrictP) + } else { + e.c.emit(setElemRecvP) + } + } + } +} + +func (e *compiledSuperBracketExpr) emitRef() { + e.c.emitLoadThis() + e.member.emitGetter(true) + e.c.emit(loadSuper) + if e.c.scope.strict { + e.c.emit(getElemRefRecvStrict) + } else { + e.c.emit(getElemRefRecv) + } +} + +func (c *compiler) superDeleteError(offset int) compiledExpr { + return c.compileEmitterExpr(func() { + c.emit(throwConst{referenceError("Unsupported reference to 'super'")}) + }, file.Idx(offset+1)) +} + +func (e *compiledSuperBracketExpr) deleteExpr() compiledExpr { + return e.c.superDeleteError(e.offset) +} + +func (c *compiler) checkConstantString(expr compiledExpr) (unistring.String, bool) { + if expr.constant() { + if val, ex := c.evalConst(expr); ex == nil { + if s, ok := val.(String); ok { + return s.string(), true + } + } + } + return "", false +} + +func (c *compiler) compileBracketExpression(v *ast.BracketExpression) compiledExpr { + if sup, ok := v.Left.(*ast.SuperExpression); ok { + c.checkSuperBase(sup.Idx) + member := c.compileExpression(v.Member) + if name, ok := c.checkConstantString(member); ok { + r := &compiledSuperDotExpr{ + name: name, + } + r.init(c, v.LeftBracket) + return r + } + + r := &compiledSuperBracketExpr{ + member: member, + } + r.init(c, v.LeftBracket) + return r + } + + left := c.compileExpression(v.Left) + member := c.compileExpression(v.Member) + if name, ok := c.checkConstantString(member); ok { + r := &compiledDotExpr{ + left: left, + name: name, + } + r.init(c, v.LeftBracket) + return r + } + + r := &compiledBracketExpr{ + left: left, + member: member, + } + r.init(c, v.LeftBracket) + return r +} + +func (e *compiledDotExpr) emitGetter(putOnStack bool) { + e.left.emitGetter(true) + e.addSrcMap() + e.c.emit(getProp(e.name)) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledDotExpr) emitRef() { + e.left.emitGetter(true) + if e.c.scope.strict { + e.c.emit(getPropRefStrict(e.name)) + } else { + e.c.emit(getPropRef(e.name)) + } +} + +func (e *compiledDotExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) { + e.left.emitGetter(true) + valueExpr.emitGetter(true) + e.addSrcMap() + if e.c.scope.strict { + if putOnStack { + e.c.emit(setPropStrict(e.name)) + } else { + e.c.emit(setPropStrictP(e.name)) + } + } else { + if putOnStack { + e.c.emit(setProp(e.name)) + } else { + e.c.emit(setPropP(e.name)) + } + } +} + +func (e *compiledDotExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) { + if !putOnStack { + e.left.emitGetter(true) + e.c.emit(dup) + e.c.emit(getProp(e.name)) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setPropStrictP(e.name)) + } else { + e.c.emit(setPropP(e.name)) + } + } else { + if !postfix { + e.left.emitGetter(true) + e.c.emit(dup) + e.c.emit(getProp(e.name)) + if prepare != nil { + prepare() + } + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setPropStrict(e.name)) + } else { + e.c.emit(setProp(e.name)) + } + } else { + e.c.emit(loadUndef) + e.left.emitGetter(true) + e.c.emit(dup) + e.c.emit(getProp(e.name)) + if prepare != nil { + prepare() + } + e.c.emit(rdupN(2)) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setPropStrictP(e.name)) + } else { + e.c.emit(setPropP(e.name)) + } + } + } +} + +func (e *compiledDotExpr) deleteExpr() compiledExpr { + r := &deletePropExpr{ + left: e.left, + name: e.name, + } + r.init(e.c, file.Idx(e.offset)+1) + return r +} + +func (e *compiledBracketExpr) emitGetter(putOnStack bool) { + e.left.emitGetter(true) + e.member.emitGetter(true) + e.addSrcMap() + e.c.emit(getElem) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledBracketExpr) emitRef() { + e.left.emitGetter(true) + e.member.emitGetter(true) + if e.c.scope.strict { + e.c.emit(getElemRefStrict) + } else { + e.c.emit(getElemRef) + } +} + +func (e *compiledBracketExpr) emitSetter(valueExpr compiledExpr, putOnStack bool) { + e.left.emitGetter(true) + e.member.emitGetter(true) + valueExpr.emitGetter(true) + e.addSrcMap() + if e.c.scope.strict { + if putOnStack { + e.c.emit(setElemStrict) + } else { + e.c.emit(setElemStrictP) + } + } else { + if putOnStack { + e.c.emit(setElem) + } else { + e.c.emit(setElemP) + } + } +} + +func (e *compiledBracketExpr) emitUnary(prepare, body func(), postfix, putOnStack bool) { + if !putOnStack { + e.left.emitGetter(true) + e.member.emitGetter(true) + e.c.emit(dupLast(2), getElem) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setElemStrict, pop) + } else { + e.c.emit(setElem, pop) + } + } else { + if !postfix { + e.left.emitGetter(true) + e.member.emitGetter(true) + e.c.emit(dupLast(2), getElem) + if prepare != nil { + prepare() + } + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setElemStrict) + } else { + e.c.emit(setElem) + } + } else { + e.c.emit(loadUndef) + e.left.emitGetter(true) + e.member.emitGetter(true) + e.c.emit(dupLast(2), getElem) + if prepare != nil { + prepare() + } + e.c.emit(rdupN(3)) + body() + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(setElemStrict, pop) + } else { + e.c.emit(setElem, pop) + } + } + } +} + +func (e *compiledBracketExpr) deleteExpr() compiledExpr { + r := &deleteElemExpr{ + left: e.left, + member: e.member, + } + r.init(e.c, file.Idx(e.offset)+1) + return r +} + +func (e *deleteElemExpr) emitGetter(putOnStack bool) { + e.left.emitGetter(true) + e.member.emitGetter(true) + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(deleteElemStrict) + } else { + e.c.emit(deleteElem) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *deletePropExpr) emitGetter(putOnStack bool) { + e.left.emitGetter(true) + e.addSrcMap() + if e.c.scope.strict { + e.c.emit(deletePropStrict(e.name)) + } else { + e.c.emit(deleteProp(e.name)) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *deleteVarExpr) emitGetter(putOnStack bool) { + /*if e.c.scope.strict { + e.c.throwSyntaxError(e.offset, "Delete of an unqualified identifier in strict mode") + return + }*/ + e.c.emit(deleteVar(e.name)) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *deleteGlobalExpr) emitGetter(putOnStack bool) { + /*if e.c.scope.strict { + e.c.throwSyntaxError(e.offset, "Delete of an unqualified identifier in strict mode") + return + }*/ + + e.c.emit(deleteGlobal(e.name)) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledAssignExpr) emitGetter(putOnStack bool) { + switch e.operator { + case token.ASSIGN: + e.left.emitSetter(e.right, putOnStack) + case token.PLUS: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(add) + }, false, putOnStack) + case token.MINUS: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(sub) + }, false, putOnStack) + case token.MULTIPLY: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(mul) + }, false, putOnStack) + case token.EXPONENT: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(exp) + }, false, putOnStack) + case token.SLASH: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(div) + }, false, putOnStack) + case token.REMAINDER: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(mod) + }, false, putOnStack) + case token.OR: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(or) + }, false, putOnStack) + case token.AND: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(and) + }, false, putOnStack) + case token.EXCLUSIVE_OR: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(xor) + }, false, putOnStack) + case token.SHIFT_LEFT: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(sal) + }, false, putOnStack) + case token.SHIFT_RIGHT: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(sar) + }, false, putOnStack) + case token.UNSIGNED_SHIFT_RIGHT: + e.left.emitUnary(nil, func() { + e.right.emitGetter(true) + e.c.emit(shr) + }, false, putOnStack) + default: + e.c.assert(false, e.offset, "Unknown assign operator: %s", e.operator.String()) + panic("unreachable") + } +} + +func (e *compiledLiteral) emitGetter(putOnStack bool) { + if putOnStack { + e.c.emit(loadVal(e.c.p.defineLiteralValue(e.val))) + } +} + +func (e *compiledLiteral) constant() bool { + return true +} + +func (e *compiledTemplateLiteral) emitGetter(putOnStack bool) { + if e.tag == nil { + if len(e.elements) == 0 { + e.c.emit(loadVal(e.c.p.defineLiteralValue(stringEmpty))) + } else { + tail := e.elements[len(e.elements)-1].Parsed + if len(e.elements) == 1 { + e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(tail)))) + } else { + stringCount := 0 + if head := e.elements[0].Parsed; head != "" { + e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(head)))) + stringCount++ + } + e.expressions[0].emitGetter(true) + e.c.emit(_toString{}) + stringCount++ + for i := 1; i < len(e.elements)-1; i++ { + if elt := e.elements[i].Parsed; elt != "" { + e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(elt)))) + stringCount++ + } + e.expressions[i].emitGetter(true) + e.c.emit(_toString{}) + stringCount++ + } + if tail != "" { + e.c.emit(loadVal(e.c.p.defineLiteralValue(stringValueFromRaw(tail)))) + stringCount++ + } + e.c.emit(concatStrings(stringCount)) + } + } + } else { + cooked := make([]Value, len(e.elements)) + raw := make([]Value, len(e.elements)) + for i, elt := range e.elements { + raw[i] = &valueProperty{ + enumerable: true, + value: newStringValue(elt.Literal), + } + var cookedVal Value + if elt.Valid { + cookedVal = stringValueFromRaw(elt.Parsed) + } else { + cookedVal = _undefined + } + cooked[i] = &valueProperty{ + enumerable: true, + value: cookedVal, + } + } + e.c.emitCallee(e.tag) + e.c.emit(&getTaggedTmplObject{ + raw: raw, + cooked: cooked, + }) + for _, expr := range e.expressions { + expr.emitGetter(true) + } + e.c.emit(call(len(e.expressions) + 1)) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileParameterBindingIdentifier(name unistring.String, offset int) (*binding, bool) { + if c.scope.strict { + c.checkIdentifierName(name, offset) + c.checkIdentifierLName(name, offset) + } + return c.scope.bindNameShadow(name) +} + +func (c *compiler) compileParameterPatternIdBinding(name unistring.String, offset int) { + if _, unique := c.compileParameterBindingIdentifier(name, offset); !unique { + c.throwSyntaxError(offset, "Duplicate parameter name not allowed in this context") + } +} + +func (c *compiler) compileParameterPatternBinding(item ast.Expression) { + c.createBindings(item, c.compileParameterPatternIdBinding) +} + +func (c *compiler) newCode(length, minCap int) (buf []instruction) { + if c.codeScratchpad != nil { + buf = c.codeScratchpad + c.codeScratchpad = nil + } + if cap(buf) < minCap { + buf = make([]instruction, length, minCap) + } else { + buf = buf[:length] + } + return +} + +func (e *compiledFunctionLiteral) compile() (prg *Program, name unistring.String, length int, strict bool) { + e.c.assert(e.typ != funcNone, e.offset, "compiledFunctionLiteral.typ is not set") + + savedPrg := e.c.p + preambleLen := 8 // enter, boxThis, loadStack(0), initThis, createArgs, set, loadCallee, init + e.c.p = &Program{ + src: e.c.p.src, + code: e.c.newCode(preambleLen, 16), + srcMap: []srcMapItem{{srcPos: e.offset}}, + } + e.c.newScope() + s := e.c.scope + s.funcType = e.typ + + if e.name != nil { + name = e.name.Name + } else { + name = e.lhsName + } + + if name != "" { + e.c.p.funcName = name + } + savedBlock := e.c.block + defer func() { + e.c.block = savedBlock + }() + + e.c.block = &block{ + typ: blockScope, + } + + if !s.strict { + s.strict = e.strict != nil + } + + hasPatterns := false + hasInits := false + firstDupIdx := -1 + + if e.parameterList.Rest != nil { + hasPatterns = true // strictly speaking not, but we need to activate all the checks + } + + // First, make sure that the first bindings correspond to the formal parameters + for _, item := range e.parameterList.List { + switch tgt := item.Target.(type) { + case *ast.Identifier: + offset := int(tgt.Idx) - 1 + b, unique := e.c.compileParameterBindingIdentifier(tgt.Name, offset) + if !unique { + firstDupIdx = offset + } + b.isArg = true + case ast.Pattern: + b := s.addBinding(int(item.Idx0()) - 1) + b.isArg = true + hasPatterns = true + default: + e.c.throwSyntaxError(int(item.Idx0())-1, "Unsupported BindingElement type: %T", item) + return + } + if item.Initializer != nil { + hasInits = true + } + + if firstDupIdx >= 0 && (hasPatterns || hasInits || s.strict || e.typ == funcArrow || e.typ == funcMethod) { + e.c.throwSyntaxError(firstDupIdx, "Duplicate parameter name not allowed in this context") + return + } + + if (hasPatterns || hasInits) && e.strict != nil { + e.c.throwSyntaxError(int(e.strict.Idx)-1, "Illegal 'use strict' directive in function with non-simple parameter list") + return + } + + if !hasInits { + length++ + } + } + + var thisBinding *binding + if e.typ != funcArrow { + thisBinding = s.createThisBinding() + } + + // create pattern bindings + if hasPatterns { + for _, item := range e.parameterList.List { + switch tgt := item.Target.(type) { + case *ast.Identifier: + // we already created those in the previous loop, skipping + default: + e.c.compileParameterPatternBinding(tgt) + } + } + if rest := e.parameterList.Rest; rest != nil { + e.c.compileParameterPatternBinding(rest) + } + } + + paramsCount := len(e.parameterList.List) + + s.numArgs = paramsCount + body := e.body + funcs := e.c.extractFunctions(body) + var calleeBinding *binding + + emitArgsRestMark := -1 + firstForwardRef := -1 + enterFunc2Mark := -1 + + if hasPatterns || hasInits { + if e.isExpr && e.name != nil { + if b, created := s.bindNameLexical(e.name.Name, false, 0); created { + b.isConst = true + calleeBinding = b + } + } + for i, item := range e.parameterList.List { + if pattern, ok := item.Target.(ast.Pattern); ok { + i := i + e.c.compilePatternInitExpr(func() { + if firstForwardRef == -1 { + s.bindings[i].emitGet() + } else { + e.c.emit(loadStackLex(-i - 1)) + } + }, item.Initializer, item.Target.Idx0()).emitGetter(true) + e.c.emitPattern(pattern, func(target, init compiledExpr) { + e.c.emitPatternLexicalAssign(target, init) + }, false) + } else if item.Initializer != nil { + markGet := len(e.c.p.code) + e.c.emit(nil) + mark := len(e.c.p.code) + e.c.emit(nil) + e.c.emitExpr(e.c.compileExpression(item.Initializer), true) + if firstForwardRef == -1 && (s.isDynamic() || s.bindings[i].useCount() > 0) { + firstForwardRef = i + } + if firstForwardRef == -1 { + s.bindings[i].emitGetAt(markGet) + } else { + e.c.p.code[markGet] = loadStackLex(-i - 1) + } + s.bindings[i].emitInitP() + e.c.p.code[mark] = jdefP(len(e.c.p.code) - mark) + } else { + if firstForwardRef == -1 && s.bindings[i].useCount() > 0 { + firstForwardRef = i + } + if firstForwardRef != -1 { + e.c.emit(loadStackLex(-i - 1)) + s.bindings[i].emitInitP() + } + } + } + if rest := e.parameterList.Rest; rest != nil { + e.c.emitAssign(rest, e.c.compileEmitterExpr( + func() { + emitArgsRestMark = len(e.c.p.code) + e.c.emit(createArgsRestStack(paramsCount)) + }, rest.Idx0()), + func(target, init compiledExpr) { + e.c.emitPatternLexicalAssign(target, init) + }) + } + if firstForwardRef != -1 { + for _, b := range s.bindings { + b.inStash = true + } + s.argsInStash = true + s.needStash = true + } + + e.c.newBlockScope() + varScope := e.c.scope + varScope.variable = true + enterFunc2Mark = len(e.c.p.code) + e.c.emit(nil) + e.c.compileDeclList(e.declarationList, false) + e.c.createFunctionBindings(funcs) + e.c.compileLexicalDeclarationsFuncBody(body, calleeBinding) + for _, b := range varScope.bindings { + if b.isVar { + if parentBinding := s.boundNames[b.name]; parentBinding != nil && parentBinding != calleeBinding { + parentBinding.emitGet() + b.emitSetP() + } + } + } + } else { + // To avoid triggering variable conflict when binding from non-strict direct eval(). + // Parameters are supposed to be in a parent scope, hence no conflict. + for _, b := range s.bindings[:paramsCount] { + b.isVar = true + } + e.c.compileDeclList(e.declarationList, true) + e.c.createFunctionBindings(funcs) + e.c.compileLexicalDeclarations(body, true) + if e.isExpr && e.name != nil { + if b, created := s.bindNameLexical(e.name.Name, false, 0); created { + b.isConst = true + calleeBinding = b + } + } + if calleeBinding != nil { + e.c.emit(loadCallee) + calleeBinding.emitInitP() + } + } + + e.c.compileFunctions(funcs) + if e.isGenerator { + e.c.emit(yieldEmpty) + } + e.c.compileStatements(body, false) + + var last ast.Statement + if l := len(body); l > 0 { + last = body[l-1] + } + if _, ok := last.(*ast.ReturnStatement); !ok { + if e.typ == funcDerivedCtor { + e.c.emit(loadUndef) + thisBinding.markAccessPoint() + e.c.emit(ret) + } else { + e.c.emit(loadUndef, ret) + } + } + + delta := 0 + code := e.c.p.code + + if s.isDynamic() && !s.argsInStash { + s.moveArgsToStash() + } + + if s.argsNeeded || s.isDynamic() && e.typ != funcArrow && e.typ != funcClsInit { + if e.typ == funcClsInit { + e.c.throwSyntaxError(e.offset, "'arguments' is not allowed in class field initializer or static initialization block") + } + b, created := s.bindNameLexical("arguments", false, 0) + if created || b.isVar { + if !s.argsInStash { + s.moveArgsToStash() + } + if s.strict { + b.isConst = true + } else { + b.isVar = e.c.scope.isFunction() + } + pos := preambleLen - 2 + delta += 2 + if s.strict || hasPatterns || hasInits { + code[pos] = createArgsUnmapped(paramsCount) + } else { + code[pos] = createArgsMapped(paramsCount) + } + pos++ + b.emitInitPAtScope(s, pos) + } + } + + if calleeBinding != nil { + if !s.isDynamic() && calleeBinding.useCount() == 0 { + s.deleteBinding(calleeBinding) + calleeBinding = nil + } else { + delta++ + calleeBinding.emitInitPAtScope(s, preambleLen-delta) + delta++ + code[preambleLen-delta] = loadCallee + } + } + + if thisBinding != nil { + if !s.isDynamic() && thisBinding.useCount() == 0 { + s.deleteBinding(thisBinding) + thisBinding = nil + } else { + if thisBinding.inStash || s.isDynamic() { + delta++ + thisBinding.emitInitAtScope(s, preambleLen-delta) + } + } + } + + stashSize, stackSize := s.finaliseVarAlloc(0) + + if thisBinding != nil && thisBinding.inStash && (!s.argsInStash || stackSize > 0) { + delta++ + code[preambleLen-delta] = loadStack(0) + } // otherwise, 'this' will be at stack[sp-1], no need to load + + if !s.strict && thisBinding != nil { + delta++ + code[preambleLen-delta] = boxThis + } + delta++ + delta = preambleLen - delta + var enter instruction + if stashSize > 0 || s.argsInStash { + if firstForwardRef == -1 { + enter1 := enterFunc{ + numArgs: uint32(paramsCount), + argsToStash: s.argsInStash, + stashSize: uint32(stashSize), + stackSize: uint32(stackSize), + extensible: s.dynamic, + funcType: e.typ, + } + if s.isDynamic() { + enter1.names = s.makeNamesMap() + } + enter = &enter1 + if enterFunc2Mark != -1 { + ef2 := &enterFuncBody{ + extensible: e.c.scope.dynamic, + funcType: e.typ, + } + e.c.updateEnterBlock(&ef2.enterBlock) + e.c.p.code[enterFunc2Mark] = ef2 + } + } else { + enter1 := enterFunc1{ + stashSize: uint32(stashSize), + numArgs: uint32(paramsCount), + argsToCopy: uint32(firstForwardRef), + extensible: s.dynamic, + funcType: e.typ, + } + if s.isDynamic() { + enter1.names = s.makeNamesMap() + } + enter = &enter1 + if enterFunc2Mark != -1 { + ef2 := &enterFuncBody{ + adjustStack: true, + extensible: e.c.scope.dynamic, + funcType: e.typ, + } + e.c.updateEnterBlock(&ef2.enterBlock) + e.c.p.code[enterFunc2Mark] = ef2 + } + } + if emitArgsRestMark != -1 && s.argsInStash { + e.c.p.code[emitArgsRestMark] = createArgsRestStash + } + } else { + enter = &enterFuncStashless{ + stackSize: uint32(stackSize), + args: uint32(paramsCount), + } + if enterFunc2Mark != -1 { + ef2 := &enterFuncBody{ + extensible: e.c.scope.dynamic, + funcType: e.typ, + } + e.c.updateEnterBlock(&ef2.enterBlock) + e.c.p.code[enterFunc2Mark] = ef2 + } + } + code[delta] = enter + e.c.p.srcMap[0].pc = delta + s.trimCode(delta) + + strict = s.strict + prg = e.c.p + // e.c.p.dumpCode() + if enterFunc2Mark != -1 { + e.c.popScope() + } + e.c.popScope() + e.c.p = savedPrg + + return +} + +func (e *compiledFunctionLiteral) emitGetter(putOnStack bool) { + p, name, length, strict := e.compile() + switch e.typ { + case funcArrow: + if e.isAsync { + e.c.emit(&newAsyncArrowFunc{newArrowFunc: newArrowFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}}}) + } else { + e.c.emit(&newArrowFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}}) + } + case funcMethod, funcClsInit: + if e.isAsync { + e.c.emit(&newAsyncMethod{newMethod: newMethod{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}, homeObjOffset: e.homeObjOffset}}) + } else { + if e.isGenerator { + e.c.emit(&newGeneratorMethod{newMethod: newMethod{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}, homeObjOffset: e.homeObjOffset}}) + } else { + e.c.emit(&newMethod{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}, homeObjOffset: e.homeObjOffset}) + } + } + case funcRegular: + if e.isAsync { + e.c.emit(&newAsyncFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}}) + } else { + if e.isGenerator { + e.c.emit(&newGeneratorFunc{newFunc: newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}}) + } else { + e.c.emit(&newFunc{prg: p, length: length, name: name, source: e.source, strict: strict}) + } + } + default: + e.c.throwSyntaxError(e.offset, "Unsupported func type: %v", e.typ) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileFunctionLiteral(v *ast.FunctionLiteral, isExpr bool) *compiledFunctionLiteral { + strictBody := c.isStrictStatement(v.Body) + if v.Name != nil && (c.scope.strict || strictBody != nil) { + c.checkIdentifierName(v.Name.Name, int(v.Name.Idx)-1) + c.checkIdentifierLName(v.Name.Name, int(v.Name.Idx)-1) + } + if v.Async && v.Generator { + c.throwSyntaxError(int(v.Function)-1, "Async generators are not supported yet") + } + r := &compiledFunctionLiteral{ + name: v.Name, + parameterList: v.ParameterList, + body: v.Body.List, + source: v.Source, + declarationList: v.DeclarationList, + isExpr: isExpr, + typ: funcRegular, + strict: strictBody, + isAsync: v.Async, + isGenerator: v.Generator, + } + r.init(c, v.Idx0()) + return r +} + +type compiledClassLiteral struct { + baseCompiledExpr + name *ast.Identifier + superClass compiledExpr + body []ast.ClassElement + lhsName unistring.String + source string + isExpr bool +} + +func (c *compiler) processKey(expr ast.Expression) (val unistring.String, computed bool) { + keyExpr := c.compileExpression(expr) + if keyExpr.constant() { + v, ex := c.evalConst(keyExpr) + if ex == nil { + return v.string(), false + } + } + keyExpr.emitGetter(true) + computed = true + return +} + +func (e *compiledClassLiteral) processClassKey(expr ast.Expression) (privateName *privateName, key unistring.String, computed bool) { + if p, ok := expr.(*ast.PrivateIdentifier); ok { + privateName = e.c.classScope.getDeclaredPrivateId(p.Name) + key = privateIdString(p.Name) + return + } + key, computed = e.c.processKey(expr) + return +} + +type clsElement struct { + key unistring.String + privateName *privateName + initializer compiledExpr + body *compiledFunctionLiteral + computed bool +} + +func (e *compiledClassLiteral) emitGetter(putOnStack bool) { + e.c.newBlockScope() + s := e.c.scope + s.strict = true + + enter := &enterBlock{} + mark0 := len(e.c.p.code) + e.c.emit(enter) + e.c.block = &block{ + typ: blockScope, + outer: e.c.block, + } + var clsBinding *binding + var clsName unistring.String + if name := e.name; name != nil { + clsName = name.Name + clsBinding = e.c.createLexicalIdBinding(clsName, true, int(name.Idx)-1) + } else { + clsName = e.lhsName + } + + var ctorMethod *ast.MethodDefinition + ctorMethodIdx := -1 + staticsCount := 0 + instanceFieldsCount := 0 + hasStaticPrivateMethods := false + cs := &classScope{ + c: e.c, + outer: e.c.classScope, + } + + for idx, elt := range e.body { + switch elt := elt.(type) { + case *ast.ClassStaticBlock: + if len(elt.Block.List) > 0 { + staticsCount++ + } + case *ast.FieldDefinition: + if id, ok := elt.Key.(*ast.PrivateIdentifier); ok { + cs.declarePrivateId(id.Name, ast.PropertyKindValue, elt.Static, int(elt.Idx)-1) + } + if elt.Static { + staticsCount++ + } else { + instanceFieldsCount++ + } + case *ast.MethodDefinition: + if !elt.Static { + if id, ok := elt.Key.(*ast.StringLiteral); ok { + if !elt.Computed && id.Value == "constructor" { + if ctorMethod != nil { + e.c.throwSyntaxError(int(id.Idx)-1, "A class may only have one constructor") + } + ctorMethod = elt + ctorMethodIdx = idx + continue + } + } + } + if id, ok := elt.Key.(*ast.PrivateIdentifier); ok { + cs.declarePrivateId(id.Name, elt.Kind, elt.Static, int(elt.Idx)-1) + if elt.Static { + hasStaticPrivateMethods = true + } + } + default: + e.c.assert(false, int(elt.Idx0())-1, "Unsupported static element: %T", elt) + } + } + + var staticInit *newStaticFieldInit + if staticsCount > 0 || hasStaticPrivateMethods { + staticInit = &newStaticFieldInit{} + e.c.emit(staticInit) + } + + var derived bool + var newClassIns *newClass + if superClass := e.superClass; superClass != nil { + derived = true + superClass.emitGetter(true) + ndc := &newDerivedClass{ + newClass: newClass{ + name: clsName, + source: e.source, + }, + } + e.addSrcMap() + e.c.emit(ndc) + newClassIns = &ndc.newClass + } else { + newClassIns = &newClass{ + name: clsName, + source: e.source, + } + e.addSrcMap() + e.c.emit(newClassIns) + } + + e.c.classScope = cs + + if ctorMethod != nil { + newClassIns.ctor, newClassIns.length = e.c.compileCtor(ctorMethod.Body, derived) + } + + curIsPrototype := false + + instanceFields := make([]clsElement, 0, instanceFieldsCount) + staticElements := make([]clsElement, 0, staticsCount) + + // stack at this point: + // + // staticFieldInit (if staticsCount > 0 || hasStaticPrivateMethods) + // prototype + // class function + // <- sp + + for idx, elt := range e.body { + if idx == ctorMethodIdx { + continue + } + switch elt := elt.(type) { + case *ast.ClassStaticBlock: + if len(elt.Block.List) > 0 { + f := e.c.compileFunctionLiteral(&ast.FunctionLiteral{ + Function: elt.Idx0(), + ParameterList: &ast.ParameterList{}, + Body: elt.Block, + Source: elt.Source, + DeclarationList: elt.DeclarationList, + }, true) + f.typ = funcClsInit + //f.lhsName = "" + f.homeObjOffset = 1 + staticElements = append(staticElements, clsElement{ + body: f, + }) + } + case *ast.FieldDefinition: + privateName, key, computed := e.processClassKey(elt.Key) + var el clsElement + if elt.Initializer != nil { + el.initializer = e.c.compileExpression(elt.Initializer) + } + el.computed = computed + if computed { + if elt.Static { + if curIsPrototype { + e.c.emit(defineComputedKey(5)) + } else { + e.c.emit(defineComputedKey(4)) + } + } else { + if curIsPrototype { + e.c.emit(defineComputedKey(3)) + } else { + e.c.emit(defineComputedKey(2)) + } + } + } else { + el.privateName = privateName + el.key = key + } + if elt.Static { + staticElements = append(staticElements, el) + } else { + instanceFields = append(instanceFields, el) + } + case *ast.MethodDefinition: + if elt.Static { + if curIsPrototype { + e.c.emit(pop) + curIsPrototype = false + } + } else { + if !curIsPrototype { + e.c.emit(dupN(1)) + curIsPrototype = true + } + } + privateName, key, computed := e.processClassKey(elt.Key) + lit := e.c.compileFunctionLiteral(elt.Body, true) + lit.typ = funcMethod + if computed { + e.c.emit(_toPropertyKey{}) + lit.homeObjOffset = 2 + } else { + lit.homeObjOffset = 1 + lit.lhsName = key + } + lit.emitGetter(true) + if privateName != nil { + var offset int + if elt.Static { + if curIsPrototype { + /* + staticInit + proto + cls + proto + method + <- sp + */ + offset = 5 + } else { + /* + staticInit + proto + cls + method + <- sp + */ + offset = 4 + } + } else { + if curIsPrototype { + offset = 3 + } else { + offset = 2 + } + } + switch elt.Kind { + case ast.PropertyKindGet: + e.c.emit(&definePrivateGetter{ + definePrivateMethod: definePrivateMethod{ + idx: privateName.idx, + targetOffset: offset, + }, + }) + case ast.PropertyKindSet: + e.c.emit(&definePrivateSetter{ + definePrivateMethod: definePrivateMethod{ + idx: privateName.idx, + targetOffset: offset, + }, + }) + default: + e.c.emit(&definePrivateMethod{ + idx: privateName.idx, + targetOffset: offset, + }) + } + } else if computed { + switch elt.Kind { + case ast.PropertyKindGet: + e.c.emit(&defineGetter{}) + case ast.PropertyKindSet: + e.c.emit(&defineSetter{}) + default: + e.c.emit(&defineMethod{}) + } + } else { + switch elt.Kind { + case ast.PropertyKindGet: + e.c.emit(&defineGetterKeyed{key: key}) + case ast.PropertyKindSet: + e.c.emit(&defineSetterKeyed{key: key}) + default: + e.c.emit(&defineMethodKeyed{key: key}) + } + } + } + } + if curIsPrototype { + e.c.emit(pop) + } + + if len(instanceFields) > 0 { + newClassIns.initFields = e.compileFieldsAndStaticBlocks(instanceFields, "") + } + if staticInit != nil { + if len(staticElements) > 0 { + staticInit.initFields = e.compileFieldsAndStaticBlocks(staticElements, "") + } + } + + env := e.c.classScope.instanceEnv + if s.dynLookup { + newClassIns.privateMethods, newClassIns.privateFields = env.methods, env.fields + } + newClassIns.numPrivateMethods = uint32(len(env.methods)) + newClassIns.numPrivateFields = uint32(len(env.fields)) + newClassIns.hasPrivateEnv = len(e.c.classScope.privateNames) > 0 + + if (clsBinding != nil && clsBinding.useCount() > 0) || s.dynLookup { + if clsBinding != nil { + // Because this block may be in the middle of an expression, its initial stack position + // cannot be known, and therefore it may not have any stack variables. + // Note, because clsBinding would be accessed through a function, it should already be in stash, + // this is just to make sure. + clsBinding.moveToStash() + clsBinding.emitInit() + } + } else { + if clsBinding != nil { + s.deleteBinding(clsBinding) + clsBinding = nil + } + e.c.p.code[mark0] = jump(1) + } + + if staticsCount > 0 || hasStaticPrivateMethods { + ise := &initStaticElements{} + e.c.emit(ise) + env := e.c.classScope.staticEnv + staticInit.numPrivateFields = uint32(len(env.fields)) + staticInit.numPrivateMethods = uint32(len(env.methods)) + if s.dynLookup { + // These cannot be set on staticInit, because it is executed before ClassHeritage, and therefore + // the VM's PrivateEnvironment is still not set. + ise.privateFields = env.fields + ise.privateMethods = env.methods + } + } else { + e.c.emit(endVariadic) // re-using as semantics match + } + + if !putOnStack { + e.c.emit(pop) + } + + if clsBinding != nil || s.dynLookup { + e.c.leaveScopeBlock(enter) + e.c.assert(enter.stackSize == 0, e.offset, "enter.StackSize != 0 in compiledClassLiteral") + } else { + e.c.block = e.c.block.outer + } + if len(e.c.classScope.privateNames) > 0 { + e.c.emit(popPrivateEnv{}) + } + e.c.classScope = e.c.classScope.outer + e.c.popScope() +} + +func (e *compiledClassLiteral) compileFieldsAndStaticBlocks(elements []clsElement, funcName unistring.String) *Program { + savedPrg := e.c.p + savedBlock := e.c.block + defer func() { + e.c.p = savedPrg + e.c.block = savedBlock + }() + + e.c.block = &block{ + typ: blockScope, + } + + e.c.p = &Program{ + src: savedPrg.src, + funcName: funcName, + code: e.c.newCode(2, 16), + } + + e.c.newScope() + s := e.c.scope + s.funcType = funcClsInit + thisBinding := s.createThisBinding() + + valIdx := 0 + for _, elt := range elements { + if elt.body != nil { + e.c.emit(dup) // this + elt.body.emitGetter(true) + elt.body.addSrcMap() + e.c.emit(call(0), pop) + } else { + if elt.computed { + e.c.emit(loadComputedKey(valIdx)) + valIdx++ + } + if init := elt.initializer; init != nil { + if !elt.computed { + e.c.emitNamedOrConst(init, elt.key) + } else { + e.c.emitExpr(init, true) + } + } else { + e.c.emit(loadUndef) + } + if elt.privateName != nil { + e.c.emit(&definePrivateProp{ + idx: elt.privateName.idx, + }) + } else if elt.computed { + e.c.emit(defineProp{}) + } else { + e.c.emit(definePropKeyed(elt.key)) + } + } + } + //e.c.emit(halt) + if s.isDynamic() || thisBinding.useCount() > 0 { + if s.isDynamic() || thisBinding.inStash { + thisBinding.emitInitAt(1) + } + } else { + s.deleteBinding(thisBinding) + } + stashSize, stackSize := s.finaliseVarAlloc(0) + e.c.assert(stackSize == 0, e.offset, "stackSize != 0 in initFields") + if stashSize > 0 { + e.c.assert(stashSize == 1, e.offset, "stashSize != 1 in initFields") + enter := &enterFunc{ + stashSize: 1, + funcType: funcClsInit, + } + if s.dynLookup { + enter.names = s.makeNamesMap() + } + e.c.p.code[0] = enter + s.trimCode(0) + } else { + s.trimCode(2) + } + res := e.c.p + e.c.popScope() + return res +} + +func (c *compiler) compileClassLiteral(v *ast.ClassLiteral, isExpr bool) *compiledClassLiteral { + if v.Name != nil { + c.checkIdentifierLName(v.Name.Name, int(v.Name.Idx)-1) + } + r := &compiledClassLiteral{ + name: v.Name, + superClass: c.compileExpression(v.SuperClass), + body: v.Body, + source: v.Source, + isExpr: isExpr, + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileCtor(ctor *ast.FunctionLiteral, derived bool) (p *Program, length int) { + f := c.compileFunctionLiteral(ctor, true) + if derived { + f.typ = funcDerivedCtor + } else { + f.typ = funcCtor + } + p, _, length, _ = f.compile() + return +} + +func (c *compiler) compileArrowFunctionLiteral(v *ast.ArrowFunctionLiteral) *compiledFunctionLiteral { + var strictBody *ast.StringLiteral + var body []ast.Statement + switch b := v.Body.(type) { + case *ast.BlockStatement: + strictBody = c.isStrictStatement(b) + body = b.List + case *ast.ExpressionBody: + body = []ast.Statement{ + &ast.ReturnStatement{ + Argument: b.Expression, + }, + } + default: + c.throwSyntaxError(int(b.Idx0())-1, "Unsupported ConciseBody type: %T", b) + } + r := &compiledFunctionLiteral{ + parameterList: v.ParameterList, + body: body, + source: v.Source, + declarationList: v.DeclarationList, + isExpr: true, + typ: funcArrow, + strict: strictBody, + isAsync: v.Async, + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) emitLoadThis() { + b, eval := c.scope.lookupThis() + if b != nil { + b.emitGet() + } else { + if eval { + c.emit(getThisDynamic{}) + } else { + c.emit(loadGlobalObject) + } + } +} + +func (e *compiledThisExpr) emitGetter(putOnStack bool) { + e.addSrcMap() + e.c.emitLoadThis() + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledSuperExpr) emitGetter(putOnStack bool) { + if putOnStack { + e.c.emit(loadSuper) + } +} + +func (e *compiledNewExpr) emitGetter(putOnStack bool) { + if e.isVariadic { + e.c.emit(startVariadic) + } + e.callee.emitGetter(true) + for _, expr := range e.args { + expr.emitGetter(true) + } + e.addSrcMap() + if e.isVariadic { + e.c.emit(newVariadic, endVariadic) + } else { + e.c.emit(_new(len(e.args))) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileCallArgs(list []ast.Expression) (args []compiledExpr, isVariadic bool) { + args = make([]compiledExpr, len(list)) + for i, argExpr := range list { + if spread, ok := argExpr.(*ast.SpreadElement); ok { + args[i] = c.compileSpreadCallArgument(spread) + isVariadic = true + } else { + args[i] = c.compileExpression(argExpr) + } + } + return +} + +func (c *compiler) compileNewExpression(v *ast.NewExpression) compiledExpr { + args, isVariadic := c.compileCallArgs(v.ArgumentList) + r := &compiledNewExpr{ + compiledCallExpr: compiledCallExpr{ + callee: c.compileExpression(v.Callee), + args: args, + isVariadic: isVariadic, + }, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledNewTarget) emitGetter(putOnStack bool) { + if s := e.c.scope.nearestThis(); s == nil || s.funcType == funcNone { + e.c.throwSyntaxError(e.offset, "new.target expression is not allowed here") + } + if putOnStack { + e.addSrcMap() + e.c.emit(loadNewTarget) + } +} + +func (c *compiler) compileMetaProperty(v *ast.MetaProperty) compiledExpr { + if v.Meta.Name == "new" || v.Property.Name != "target" { + r := &compiledNewTarget{} + r.init(c, v.Idx0()) + return r + } + c.throwSyntaxError(int(v.Idx)-1, "Unsupported meta property: %s.%s", v.Meta.Name, v.Property.Name) + return nil +} + +func (e *compiledSequenceExpr) emitGetter(putOnStack bool) { + if len(e.sequence) > 0 { + for i := 0; i < len(e.sequence)-1; i++ { + e.sequence[i].emitGetter(false) + } + e.sequence[len(e.sequence)-1].emitGetter(putOnStack) + } +} + +func (c *compiler) compileSequenceExpression(v *ast.SequenceExpression) compiledExpr { + s := make([]compiledExpr, len(v.Sequence)) + for i, expr := range v.Sequence { + s[i] = c.compileExpression(expr) + } + r := &compiledSequenceExpr{ + sequence: s, + } + var idx file.Idx + if len(v.Sequence) > 0 { + idx = v.Idx0() + } + r.init(c, idx) + return r +} + +func (c *compiler) emitThrow(v Value) { + if o, ok := v.(*Object); ok { + t := nilSafe(o.self.getStr("name", nil)).toString().String() + switch t { + case "TypeError": + c.emit(loadDynamic(t)) + msg := o.self.getStr("message", nil) + if msg != nil { + c.emit(loadVal(c.p.defineLiteralValue(msg))) + c.emit(_new(1)) + } else { + c.emit(_new(0)) + } + c.emit(throw) + return + } + } + c.assert(false, 0, "unknown exception type thrown while evaluating constant expression: %s", v.String()) + panic("unreachable") +} + +func (c *compiler) emitConst(expr compiledExpr, putOnStack bool) { + v, ex := c.evalConst(expr) + if ex == nil { + if putOnStack { + c.emit(loadVal(c.p.defineLiteralValue(v))) + } + } else { + c.emitThrow(ex.val) + } +} + +func (c *compiler) evalConst(expr compiledExpr) (Value, *Exception) { + if expr, ok := expr.(*compiledLiteral); ok { + return expr.val, nil + } + if c.evalVM == nil { + c.evalVM = New().vm + } + var savedPrg *Program + createdPrg := false + if c.evalVM.prg == nil { + c.evalVM.prg = &Program{ + src: c.p.src, + } + savedPrg = c.p + c.p = c.evalVM.prg + createdPrg = true + } + savedPc := len(c.p.code) + expr.emitGetter(true) + c.evalVM.pc = savedPc + ex := c.evalVM.runTry() + if createdPrg { + c.evalVM.prg = nil + c.evalVM.pc = 0 + c.p = savedPrg + } else { + c.evalVM.prg.code = c.evalVM.prg.code[:savedPc] + c.p.code = c.evalVM.prg.code + } + if ex == nil { + return c.evalVM.pop(), nil + } + return nil, ex +} + +func (e *compiledUnaryExpr) constant() bool { + return e.operand.constant() +} + +func (e *compiledUnaryExpr) emitGetter(putOnStack bool) { + var prepare, body func() + + toNumber := func() { + e.addSrcMap() + e.c.emit(toNumber) + } + + switch e.operator { + case token.NOT: + e.operand.emitGetter(true) + e.c.emit(not) + goto end + case token.BITWISE_NOT: + e.operand.emitGetter(true) + e.c.emit(bnot) + goto end + case token.TYPEOF: + if o, ok := e.operand.(compiledExprOrRef); ok { + o.emitGetterOrRef() + } else { + e.operand.emitGetter(true) + } + e.c.emit(typeof) + goto end + case token.DELETE: + e.operand.deleteExpr().emitGetter(putOnStack) + return + case token.MINUS: + e.c.emitExpr(e.operand, true) + e.c.emit(neg) + goto end + case token.PLUS: + e.c.emitExpr(e.operand, true) + e.c.emit(plus) + goto end + case token.INCREMENT: + prepare = toNumber + body = func() { + e.c.emit(inc) + } + case token.DECREMENT: + prepare = toNumber + body = func() { + e.c.emit(dec) + } + case token.VOID: + e.c.emitExpr(e.operand, false) + if putOnStack { + e.c.emit(loadUndef) + } + return + default: + e.c.assert(false, e.offset, "Unknown unary operator: %s", e.operator.String()) + panic("unreachable") + } + + e.operand.emitUnary(prepare, body, e.postfix, putOnStack) + return + +end: + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileUnaryExpression(v *ast.UnaryExpression) compiledExpr { + r := &compiledUnaryExpr{ + operand: c.compileExpression(v.Operand), + operator: v.Operator, + postfix: v.Postfix, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledConditionalExpr) emitGetter(putOnStack bool) { + e.test.emitGetter(true) + j := len(e.c.p.code) + e.c.emit(nil) + e.consequent.emitGetter(putOnStack) + j1 := len(e.c.p.code) + e.c.emit(nil) + e.c.p.code[j] = jne(len(e.c.p.code) - j) + e.alternate.emitGetter(putOnStack) + e.c.p.code[j1] = jump(len(e.c.p.code) - j1) +} + +func (c *compiler) compileConditionalExpression(v *ast.ConditionalExpression) compiledExpr { + r := &compiledConditionalExpr{ + test: c.compileExpression(v.Test), + consequent: c.compileExpression(v.Consequent), + alternate: c.compileExpression(v.Alternate), + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledLogicalOr) constant() bool { + if e.left.constant() { + if v, ex := e.c.evalConst(e.left); ex == nil { + if v.ToBoolean() { + return true + } + return e.right.constant() + } else { + return true + } + } + + return false +} + +func (e *compiledLogicalOr) emitGetter(putOnStack bool) { + if e.left.constant() { + if v, ex := e.c.evalConst(e.left); ex == nil { + if !v.ToBoolean() { + e.c.emitExpr(e.right, putOnStack) + } else { + if putOnStack { + e.c.emit(loadVal(e.c.p.defineLiteralValue(v))) + } + } + } else { + e.c.emitThrow(ex.val) + } + return + } + e.c.emitExpr(e.left, true) + j := len(e.c.p.code) + e.addSrcMap() + e.c.emit(nil) + e.c.emitExpr(e.right, true) + e.c.p.code[j] = jeq1(len(e.c.p.code) - j) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledCoalesce) constant() bool { + if e.left.constant() { + if v, ex := e.c.evalConst(e.left); ex == nil { + if v != _null && v != _undefined { + return true + } + return e.right.constant() + } else { + return true + } + } + + return false +} + +func (e *compiledCoalesce) emitGetter(putOnStack bool) { + if e.left.constant() { + if v, ex := e.c.evalConst(e.left); ex == nil { + if v == _undefined || v == _null { + e.c.emitExpr(e.right, putOnStack) + } else { + if putOnStack { + e.c.emit(loadVal(e.c.p.defineLiteralValue(v))) + } + } + } else { + e.c.emitThrow(ex.val) + } + return + } + e.c.emitExpr(e.left, true) + j := len(e.c.p.code) + e.addSrcMap() + e.c.emit(nil) + e.c.emitExpr(e.right, true) + e.c.p.code[j] = jcoalesc(len(e.c.p.code) - j) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledLogicalAnd) constant() bool { + if e.left.constant() { + if v, ex := e.c.evalConst(e.left); ex == nil { + if !v.ToBoolean() { + return true + } else { + return e.right.constant() + } + } else { + return true + } + } + + return false +} + +func (e *compiledLogicalAnd) emitGetter(putOnStack bool) { + var j int + if e.left.constant() { + if v, ex := e.c.evalConst(e.left); ex == nil { + if !v.ToBoolean() { + e.c.emit(loadVal(e.c.p.defineLiteralValue(v))) + } else { + e.c.emitExpr(e.right, putOnStack) + } + } else { + e.c.emitThrow(ex.val) + } + return + } + e.left.emitGetter(true) + j = len(e.c.p.code) + e.addSrcMap() + e.c.emit(nil) + e.c.emitExpr(e.right, true) + e.c.p.code[j] = jneq1(len(e.c.p.code) - j) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledBinaryExpr) constant() bool { + return e.left.constant() && e.right.constant() +} + +func (e *compiledBinaryExpr) emitGetter(putOnStack bool) { + e.c.emitExpr(e.left, true) + e.c.emitExpr(e.right, true) + e.addSrcMap() + + switch e.operator { + case token.LESS: + e.c.emit(op_lt) + case token.GREATER: + e.c.emit(op_gt) + case token.LESS_OR_EQUAL: + e.c.emit(op_lte) + case token.GREATER_OR_EQUAL: + e.c.emit(op_gte) + case token.EQUAL: + e.c.emit(op_eq) + case token.NOT_EQUAL: + e.c.emit(op_neq) + case token.STRICT_EQUAL: + e.c.emit(op_strict_eq) + case token.STRICT_NOT_EQUAL: + e.c.emit(op_strict_neq) + case token.PLUS: + e.c.emit(add) + case token.MINUS: + e.c.emit(sub) + case token.MULTIPLY: + e.c.emit(mul) + case token.EXPONENT: + e.c.emit(exp) + case token.SLASH: + e.c.emit(div) + case token.REMAINDER: + e.c.emit(mod) + case token.AND: + e.c.emit(and) + case token.OR: + e.c.emit(or) + case token.EXCLUSIVE_OR: + e.c.emit(xor) + case token.INSTANCEOF: + e.c.emit(op_instanceof) + case token.IN: + e.c.emit(op_in) + case token.SHIFT_LEFT: + e.c.emit(sal) + case token.SHIFT_RIGHT: + e.c.emit(sar) + case token.UNSIGNED_SHIFT_RIGHT: + e.c.emit(shr) + default: + e.c.assert(false, e.offset, "Unknown operator: %s", e.operator.String()) + panic("unreachable") + } + + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileBinaryExpression(v *ast.BinaryExpression) compiledExpr { + + switch v.Operator { + case token.LOGICAL_OR: + return c.compileLogicalOr(v.Left, v.Right, v.Idx0()) + case token.COALESCE: + return c.compileCoalesce(v.Left, v.Right, v.Idx0()) + case token.LOGICAL_AND: + return c.compileLogicalAnd(v.Left, v.Right, v.Idx0()) + } + + if id, ok := v.Left.(*ast.PrivateIdentifier); ok { + return c.compilePrivateIn(id, v.Right, id.Idx) + } + + r := &compiledBinaryExpr{ + left: c.compileExpression(v.Left), + right: c.compileExpression(v.Right), + operator: v.Operator, + } + r.init(c, v.Idx0()) + return r +} + +type compiledPrivateIn struct { + baseCompiledExpr + id unistring.String + right compiledExpr +} + +func (e *compiledPrivateIn) emitGetter(putOnStack bool) { + e.right.emitGetter(true) + rn, id := e.c.resolvePrivateName(e.id, e.offset) + if rn != nil { + e.c.emit((*privateInRes)(rn)) + } else { + e.c.emit((*privateInId)(id)) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compilePrivateIn(id *ast.PrivateIdentifier, right ast.Expression, idx file.Idx) compiledExpr { + r := &compiledPrivateIn{ + id: id.Name, + right: c.compileExpression(right), + } + r.init(c, idx) + return r +} + +func (c *compiler) compileLogicalOr(left, right ast.Expression, idx file.Idx) compiledExpr { + r := &compiledLogicalOr{ + left: c.compileExpression(left), + right: c.compileExpression(right), + } + r.init(c, idx) + return r +} + +func (c *compiler) compileCoalesce(left, right ast.Expression, idx file.Idx) compiledExpr { + r := &compiledCoalesce{ + left: c.compileExpression(left), + right: c.compileExpression(right), + } + r.init(c, idx) + return r +} + +func (c *compiler) compileLogicalAnd(left, right ast.Expression, idx file.Idx) compiledExpr { + r := &compiledLogicalAnd{ + left: c.compileExpression(left), + right: c.compileExpression(right), + } + r.init(c, idx) + return r +} + +func (e *compiledObjectLiteral) emitGetter(putOnStack bool) { + e.addSrcMap() + e.c.emit(newObject) + hasProto := false + for _, prop := range e.expr.Value { + switch prop := prop.(type) { + case *ast.PropertyKeyed: + key, computed := e.c.processKey(prop.Key) + valueExpr := e.c.compileExpression(prop.Value) + var ne namedEmitter + if fn, ok := valueExpr.(*compiledFunctionLiteral); ok { + if fn.name == nil { + ne = fn + } + switch prop.Kind { + case ast.PropertyKindMethod, ast.PropertyKindGet, ast.PropertyKindSet: + fn.typ = funcMethod + if computed { + fn.homeObjOffset = 2 + } else { + fn.homeObjOffset = 1 + } + } + } else if v, ok := valueExpr.(namedEmitter); ok { + ne = v + } + if computed { + e.c.emit(_toPropertyKey{}) + e.c.emitExpr(valueExpr, true) + switch prop.Kind { + case ast.PropertyKindValue: + if ne != nil { + e.c.emit(setElem1Named) + } else { + e.c.emit(setElem1) + } + case ast.PropertyKindMethod: + e.c.emit(&defineMethod{enumerable: true}) + case ast.PropertyKindGet: + e.c.emit(&defineGetter{enumerable: true}) + case ast.PropertyKindSet: + e.c.emit(&defineSetter{enumerable: true}) + default: + e.c.assert(false, e.offset, "unknown property kind: %s", prop.Kind) + panic("unreachable") + } + } else { + isProto := key == __proto__ && !prop.Computed + if isProto { + if hasProto { + e.c.throwSyntaxError(int(prop.Idx0())-1, "Duplicate __proto__ fields are not allowed in object literals") + } else { + hasProto = true + } + } + if ne != nil && !isProto { + ne.emitNamed(key) + } else { + e.c.emitExpr(valueExpr, true) + } + switch prop.Kind { + case ast.PropertyKindValue: + if isProto { + e.c.emit(setProto) + } else { + e.c.emit(putProp(key)) + } + case ast.PropertyKindMethod: + e.c.emit(&defineMethodKeyed{key: key, enumerable: true}) + case ast.PropertyKindGet: + e.c.emit(&defineGetterKeyed{key: key, enumerable: true}) + case ast.PropertyKindSet: + e.c.emit(&defineSetterKeyed{key: key, enumerable: true}) + default: + e.c.assert(false, e.offset, "unknown property kind: %s", prop.Kind) + panic("unreachable") + } + } + case *ast.PropertyShort: + key := prop.Name.Name + if prop.Initializer != nil { + e.c.throwSyntaxError(int(prop.Initializer.Idx0())-1, "Invalid shorthand property initializer") + } + if e.c.scope.strict && key == "let" { + e.c.throwSyntaxError(e.offset, "'let' cannot be used as a shorthand property in strict mode") + } + e.c.compileIdentifierExpression(&prop.Name).emitGetter(true) + e.c.emit(putProp(key)) + case *ast.SpreadElement: + e.c.compileExpression(prop.Expression).emitGetter(true) + e.c.emit(copySpread) + default: + e.c.assert(false, e.offset, "unknown Property type: %T", prop) + panic("unreachable") + } + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileObjectLiteral(v *ast.ObjectLiteral) compiledExpr { + r := &compiledObjectLiteral{ + expr: v, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledArrayLiteral) emitGetter(putOnStack bool) { + e.addSrcMap() + hasSpread := false + mark := len(e.c.p.code) + e.c.emit(nil) + for _, v := range e.expr.Value { + if spread, ok := v.(*ast.SpreadElement); ok { + hasSpread = true + e.c.compileExpression(spread.Expression).emitGetter(true) + e.c.emit(pushArraySpread) + } else { + if v != nil { + e.c.emitExpr(e.c.compileExpression(v), true) + } else { + e.c.emit(loadNil) + } + e.c.emit(pushArrayItem) + } + } + var objCount uint32 + if !hasSpread { + objCount = uint32(len(e.expr.Value)) + } + e.c.p.code[mark] = newArray(objCount) + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileArrayLiteral(v *ast.ArrayLiteral) compiledExpr { + r := &compiledArrayLiteral{ + expr: v, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledRegexpLiteral) emitGetter(putOnStack bool) { + if putOnStack { + pattern, err := compileRegexp(e.expr.Pattern, e.expr.Flags) + if err != nil { + e.c.throwSyntaxError(e.offset, err.Error()) + } + + e.c.emit(&newRegexp{pattern: pattern, src: newStringValue(e.expr.Pattern)}) + } +} + +func (c *compiler) compileRegexpLiteral(v *ast.RegExpLiteral) compiledExpr { + r := &compiledRegexpLiteral{ + expr: v, + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) emitCallee(callee compiledExpr) (calleeName unistring.String) { + switch callee := callee.(type) { + case *compiledDotExpr: + callee.left.emitGetter(true) + c.emit(getPropCallee(callee.name)) + case *compiledPrivateDotExpr: + callee.left.emitGetter(true) + rn, id := c.resolvePrivateName(callee.name, callee.offset) + if rn != nil { + c.emit((*getPrivatePropResCallee)(rn)) + } else { + c.emit((*getPrivatePropIdCallee)(id)) + } + case *compiledSuperDotExpr: + c.emitLoadThis() + c.emit(loadSuper) + c.emit(getPropRecvCallee(callee.name)) + case *compiledBracketExpr: + callee.left.emitGetter(true) + callee.member.emitGetter(true) + c.emit(getElemCallee) + case *compiledSuperBracketExpr: + c.emitLoadThis() + c.emit(loadSuper) + callee.member.emitGetter(true) + c.emit(getElemRecvCallee) + case *compiledIdentifierExpr: + calleeName = callee.name + callee.emitGetterAndCallee() + case *compiledOptionalChain: + c.startOptChain() + c.emitCallee(callee.expr) + c.endOptChain() + case *compiledOptional: + c.emitCallee(callee.expr) + c.block.conts = append(c.block.conts, len(c.p.code)) + c.emit(nil) + case *compiledSuperExpr: + // no-op + default: + c.emit(loadUndef) + callee.emitGetter(true) + } + return +} + +func (e *compiledCallExpr) emitGetter(putOnStack bool) { + if e.isVariadic { + e.c.emit(startVariadic) + } + calleeName := e.c.emitCallee(e.callee) + + for _, expr := range e.args { + expr.emitGetter(true) + } + + e.addSrcMap() + if _, ok := e.callee.(*compiledSuperExpr); ok { + b, eval := e.c.scope.lookupThis() + e.c.assert(eval || b != nil, e.offset, "super call, but no 'this' binding") + if eval { + e.c.emit(resolveThisDynamic{}) + } else { + b.markAccessPoint() + e.c.emit(resolveThisStack{}) + } + if e.isVariadic { + e.c.emit(superCallVariadic) + } else { + e.c.emit(superCall(len(e.args))) + } + } else if calleeName == "eval" { + foundVar := false + for sc := e.c.scope; sc != nil; sc = sc.outer { + if !foundVar && (sc.variable || sc.isFunction()) { + foundVar = true + if !sc.strict { + sc.dynamic = true + } + } + sc.dynLookup = true + } + + if e.c.scope.strict { + if e.isVariadic { + e.c.emit(callEvalVariadicStrict) + } else { + e.c.emit(callEvalStrict(len(e.args))) + } + } else { + if e.isVariadic { + e.c.emit(callEvalVariadic) + } else { + e.c.emit(callEval(len(e.args))) + } + } + } else { + if e.isVariadic { + e.c.emit(callVariadic) + } else { + e.c.emit(call(len(e.args))) + } + } + if e.isVariadic { + e.c.emit(endVariadic) + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledCallExpr) deleteExpr() compiledExpr { + r := &defaultDeleteExpr{ + expr: e, + } + r.init(e.c, file.Idx(e.offset+1)) + return r +} + +func (c *compiler) compileSpreadCallArgument(spread *ast.SpreadElement) compiledExpr { + r := &compiledSpreadCallArgument{ + expr: c.compileExpression(spread.Expression), + } + r.init(c, spread.Idx0()) + return r +} + +func (c *compiler) compileCallee(v ast.Expression) compiledExpr { + if sup, ok := v.(*ast.SuperExpression); ok { + if s := c.scope.nearestThis(); s != nil && s.funcType == funcDerivedCtor { + e := &compiledSuperExpr{} + e.init(c, sup.Idx) + return e + } + c.throwSyntaxError(int(v.Idx0())-1, "'super' keyword unexpected here") + panic("unreachable") + } + return c.compileExpression(v) +} + +func (c *compiler) compileCallExpression(v *ast.CallExpression) compiledExpr { + + args := make([]compiledExpr, len(v.ArgumentList)) + isVariadic := false + for i, argExpr := range v.ArgumentList { + if spread, ok := argExpr.(*ast.SpreadElement); ok { + args[i] = c.compileSpreadCallArgument(spread) + isVariadic = true + } else { + args[i] = c.compileExpression(argExpr) + } + } + + r := &compiledCallExpr{ + args: args, + callee: c.compileCallee(v.Callee), + isVariadic: isVariadic, + } + r.init(c, v.LeftParenthesis) + return r +} + +func (c *compiler) compileIdentifierExpression(v *ast.Identifier) compiledExpr { + if c.scope.strict { + c.checkIdentifierName(v.Name, int(v.Idx)-1) + } + + r := &compiledIdentifierExpr{ + name: v.Name, + } + r.offset = int(v.Idx) - 1 + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileNumberLiteral(v *ast.NumberLiteral) compiledExpr { + if c.scope.strict && len(v.Literal) > 1 && v.Literal[0] == '0' && v.Literal[1] <= '7' && v.Literal[1] >= '0' { + c.throwSyntaxError(int(v.Idx)-1, "Octal literals are not allowed in strict mode") + panic("Unreachable") + } + var val Value + switch num := v.Value.(type) { + case int64: + val = intToValue(num) + case float64: + val = floatToValue(num) + default: + c.assert(false, int(v.Idx)-1, "Unsupported number literal type: %T", v.Value) + panic("unreachable") + } + r := &compiledLiteral{ + val: val, + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileStringLiteral(v *ast.StringLiteral) compiledExpr { + r := &compiledLiteral{ + val: stringValueFromRaw(v.Value), + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileTemplateLiteral(v *ast.TemplateLiteral) compiledExpr { + r := &compiledTemplateLiteral{} + if v.Tag != nil { + r.tag = c.compileExpression(v.Tag) + } + ce := make([]compiledExpr, len(v.Expressions)) + for i, expr := range v.Expressions { + ce[i] = c.compileExpression(expr) + } + r.expressions = ce + r.elements = v.Elements + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileBooleanLiteral(v *ast.BooleanLiteral) compiledExpr { + var val Value + if v.Value { + val = valueTrue + } else { + val = valueFalse + } + + r := &compiledLiteral{ + val: val, + } + r.init(c, v.Idx0()) + return r +} + +func (c *compiler) compileAssignExpression(v *ast.AssignExpression) compiledExpr { + // log.Printf("compileAssignExpression(): %+v", v) + + r := &compiledAssignExpr{ + left: c.compileExpression(v.Left), + right: c.compileExpression(v.Right), + operator: v.Operator, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledEnumGetExpr) emitGetter(putOnStack bool) { + e.c.emit(enumGet) + if !putOnStack { + e.c.emit(pop) + } +} + +func (c *compiler) compileObjectAssignmentPattern(v *ast.ObjectPattern) compiledExpr { + r := &compiledObjectAssignmentPattern{ + expr: v, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledObjectAssignmentPattern) emitGetter(putOnStack bool) { + if putOnStack { + e.c.emit(loadUndef) + } +} + +func (c *compiler) compileArrayAssignmentPattern(v *ast.ArrayPattern) compiledExpr { + r := &compiledArrayAssignmentPattern{ + expr: v, + } + r.init(c, v.Idx0()) + return r +} + +func (e *compiledArrayAssignmentPattern) emitGetter(putOnStack bool) { + if putOnStack { + e.c.emit(loadUndef) + } +} + +func (c *compiler) emitExpr(expr compiledExpr, putOnStack bool) { + if expr.constant() { + c.emitConst(expr, putOnStack) + } else { + expr.emitGetter(putOnStack) + } +} + +type namedEmitter interface { + emitNamed(name unistring.String) +} + +func (c *compiler) emitNamed(expr compiledExpr, name unistring.String) { + if en, ok := expr.(namedEmitter); ok { + en.emitNamed(name) + } else { + expr.emitGetter(true) + } +} + +func (c *compiler) emitNamedOrConst(expr compiledExpr, name unistring.String) { + if expr.constant() { + c.emitConst(expr, true) + } else { + c.emitNamed(expr, name) + } +} + +func (e *compiledFunctionLiteral) emitNamed(name unistring.String) { + e.lhsName = name + e.emitGetter(true) +} + +func (e *compiledClassLiteral) emitNamed(name unistring.String) { + e.lhsName = name + e.emitGetter(true) +} + +func (c *compiler) emitPattern(pattern ast.Pattern, emitter func(target, init compiledExpr), putOnStack bool) { + switch pattern := pattern.(type) { + case *ast.ObjectPattern: + c.emitObjectPattern(pattern, emitter, putOnStack) + case *ast.ArrayPattern: + c.emitArrayPattern(pattern, emitter, putOnStack) + default: + c.assert(false, int(pattern.Idx0())-1, "unsupported Pattern: %T", pattern) + panic("unreachable") + } +} + +func (c *compiler) emitAssign(target ast.Expression, init compiledExpr, emitAssignSimple func(target, init compiledExpr)) { + pattern, isPattern := target.(ast.Pattern) + if isPattern { + init.emitGetter(true) + c.emitPattern(pattern, emitAssignSimple, false) + } else { + emitAssignSimple(c.compileExpression(target), init) + } +} + +func (c *compiler) emitObjectPattern(pattern *ast.ObjectPattern, emitAssign func(target, init compiledExpr), putOnStack bool) { + if pattern.Rest != nil { + c.emit(createDestructSrc) + } else { + c.emit(checkObjectCoercible) + } + for _, prop := range pattern.Properties { + switch prop := prop.(type) { + case *ast.PropertyShort: + c.emit(dup) + emitAssign(c.compileIdentifierExpression(&prop.Name), c.compilePatternInitExpr(func() { + c.emit(getProp(prop.Name.Name)) + }, prop.Initializer, prop.Idx0())) + case *ast.PropertyKeyed: + c.emit(dup) + c.compileExpression(prop.Key).emitGetter(true) + c.emit(_toPropertyKey{}) + var target ast.Expression + var initializer ast.Expression + if e, ok := prop.Value.(*ast.AssignExpression); ok { + target = e.Left + initializer = e.Right + } else { + target = prop.Value + } + c.emitAssign(target, c.compilePatternInitExpr(func() { + c.emit(getKey) + }, initializer, prop.Idx0()), emitAssign) + default: + c.throwSyntaxError(int(prop.Idx0()-1), "Unsupported AssignmentProperty type: %T", prop) + } + } + if pattern.Rest != nil { + emitAssign(c.compileExpression(pattern.Rest), c.compileEmitterExpr(func() { + c.emit(copyRest) + }, pattern.Rest.Idx0())) + c.emit(pop) + } + if !putOnStack { + c.emit(pop) + } +} + +func (c *compiler) emitArrayPattern(pattern *ast.ArrayPattern, emitAssign func(target, init compiledExpr), putOnStack bool) { + c.emit(iterate) + for _, elt := range pattern.Elements { + switch elt := elt.(type) { + case nil: + c.emit(iterGetNextOrUndef{}, pop) + case *ast.AssignExpression: + c.emitAssign(elt.Left, c.compilePatternInitExpr(func() { + c.emit(iterGetNextOrUndef{}) + }, elt.Right, elt.Idx0()), emitAssign) + default: + c.emitAssign(elt, c.compileEmitterExpr(func() { + c.emit(iterGetNextOrUndef{}) + }, elt.Idx0()), emitAssign) + } + } + if pattern.Rest != nil { + c.emitAssign(pattern.Rest, c.compileEmitterExpr(func() { + c.emit(newArrayFromIter) + }, pattern.Rest.Idx0()), emitAssign) + } else { + c.emit(enumPopClose) + } + + if !putOnStack { + c.emit(pop) + } +} + +func (e *compiledObjectAssignmentPattern) emitSetter(valueExpr compiledExpr, putOnStack bool) { + valueExpr.emitGetter(true) + e.c.emitObjectPattern(e.expr, e.c.emitPatternAssign, putOnStack) +} + +func (e *compiledArrayAssignmentPattern) emitSetter(valueExpr compiledExpr, putOnStack bool) { + valueExpr.emitGetter(true) + e.c.emitArrayPattern(e.expr, e.c.emitPatternAssign, putOnStack) +} + +type compiledPatternInitExpr struct { + baseCompiledExpr + emitSrc func() + def compiledExpr +} + +func (e *compiledPatternInitExpr) emitGetter(putOnStack bool) { + if !putOnStack { + return + } + e.emitSrc() + if e.def != nil { + mark := len(e.c.p.code) + e.c.emit(nil) + e.c.emitExpr(e.def, true) + e.c.p.code[mark] = jdef(len(e.c.p.code) - mark) + } +} + +func (e *compiledPatternInitExpr) emitNamed(name unistring.String) { + e.emitSrc() + if e.def != nil { + mark := len(e.c.p.code) + e.c.emit(nil) + e.c.emitNamedOrConst(e.def, name) + e.c.p.code[mark] = jdef(len(e.c.p.code) - mark) + } +} + +func (c *compiler) compilePatternInitExpr(emitSrc func(), def ast.Expression, idx file.Idx) compiledExpr { + r := &compiledPatternInitExpr{ + emitSrc: emitSrc, + def: c.compileExpression(def), + } + r.init(c, idx) + return r +} + +type compiledEmitterExpr struct { + baseCompiledExpr + emitter func() + namedEmitter func(name unistring.String) +} + +func (e *compiledEmitterExpr) emitGetter(putOnStack bool) { + if e.emitter != nil { + e.emitter() + } else { + e.namedEmitter("") + } + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledEmitterExpr) emitNamed(name unistring.String) { + if e.namedEmitter != nil { + e.namedEmitter(name) + } else { + e.emitter() + } +} + +func (c *compiler) compileEmitterExpr(emitter func(), idx file.Idx) *compiledEmitterExpr { + r := &compiledEmitterExpr{ + emitter: emitter, + } + r.init(c, idx) + return r +} + +func (e *compiledSpreadCallArgument) emitGetter(putOnStack bool) { + e.expr.emitGetter(putOnStack) + if putOnStack { + e.c.emit(pushSpread) + } +} + +func (c *compiler) startOptChain() { + c.block = &block{ + typ: blockOptChain, + outer: c.block, + } +} + +func (c *compiler) endOptChain() { + lbl := len(c.p.code) + for _, item := range c.block.breaks { + c.p.code[item] = jopt(lbl - item) + } + for _, item := range c.block.conts { + c.p.code[item] = joptc(lbl - item) + } + c.block = c.block.outer +} + +func (e *compiledOptionalChain) emitGetter(putOnStack bool) { + e.c.startOptChain() + e.expr.emitGetter(true) + e.c.endOptChain() + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledOptional) emitGetter(putOnStack bool) { + e.expr.emitGetter(putOnStack) + if putOnStack { + e.c.block.breaks = append(e.c.block.breaks, len(e.c.p.code)) + e.c.emit(nil) + } +} + +func (e *compiledAwaitExpression) emitGetter(putOnStack bool) { + e.arg.emitGetter(true) + e.c.emit(await) + if !putOnStack { + e.c.emit(pop) + } +} + +func (e *compiledYieldExpression) emitGetter(putOnStack bool) { + if e.arg != nil { + e.arg.emitGetter(true) + } else { + e.c.emit(loadUndef) + } + if putOnStack { + if e.delegate { + e.c.emit(yieldDelegateRes) + } else { + e.c.emit(yieldRes) + } + } else { + if e.delegate { + e.c.emit(yieldDelegate) + } else { + e.c.emit(yield) + } + } +} diff --git a/pkg/xscript/engine/compiler_stmt.go b/pkg/xscript/engine/compiler_stmt.go new file mode 100644 index 0000000..153f88d --- /dev/null +++ b/pkg/xscript/engine/compiler_stmt.go @@ -0,0 +1,1127 @@ +package engine + +import ( + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +func (c *compiler) compileStatement(v ast.Statement, needResult bool) { + + switch v := v.(type) { + case *ast.BlockStatement: + c.compileBlockStatement(v, needResult) + case *ast.ExpressionStatement: + c.compileExpressionStatement(v, needResult) + case *ast.VariableStatement: + c.compileVariableStatement(v) + case *ast.LexicalDeclaration: + c.compileLexicalDeclaration(v) + case *ast.ReturnStatement: + c.compileReturnStatement(v) + case *ast.IfStatement: + c.compileIfStatement(v, needResult) + case *ast.DoWhileStatement: + c.compileDoWhileStatement(v, needResult) + case *ast.ForStatement: + c.compileForStatement(v, needResult) + case *ast.ForInStatement: + c.compileForInStatement(v, needResult) + case *ast.ForOfStatement: + c.compileForOfStatement(v, needResult) + case *ast.WhileStatement: + c.compileWhileStatement(v, needResult) + case *ast.BranchStatement: + c.compileBranchStatement(v) + case *ast.TryStatement: + c.compileTryStatement(v, needResult) + case *ast.ThrowStatement: + c.compileThrowStatement(v) + case *ast.SwitchStatement: + c.compileSwitchStatement(v, needResult) + case *ast.LabelledStatement: + c.compileLabeledStatement(v, needResult) + case *ast.EmptyStatement: + c.compileEmptyStatement(needResult) + case *ast.FunctionDeclaration: + c.compileStandaloneFunctionDecl(v) + // note functions inside blocks are hoisted to the top of the block and are compiled using compileFunctions() + case *ast.ClassDeclaration: + c.compileClassDeclaration(v) + case *ast.WithStatement: + c.compileWithStatement(v, needResult) + case *ast.DebuggerStatement: + default: + c.assert(false, int(v.Idx0())-1, "Unknown statement type: %T", v) + panic("unreachable") + } +} + +func (c *compiler) compileLabeledStatement(v *ast.LabelledStatement, needResult bool) { + label := v.Label.Name + if c.scope.strict { + c.checkIdentifierName(label, int(v.Label.Idx)-1) + } + for b := c.block; b != nil; b = b.outer { + if b.label == label { + c.throwSyntaxError(int(v.Label.Idx-1), "Label '%s' has already been declared", label) + } + } + switch s := v.Statement.(type) { + case *ast.ForInStatement: + c.compileLabeledForInStatement(s, needResult, label) + case *ast.ForOfStatement: + c.compileLabeledForOfStatement(s, needResult, label) + case *ast.ForStatement: + c.compileLabeledForStatement(s, needResult, label) + case *ast.WhileStatement: + c.compileLabeledWhileStatement(s, needResult, label) + case *ast.DoWhileStatement: + c.compileLabeledDoWhileStatement(s, needResult, label) + default: + c.compileGenericLabeledStatement(s, needResult, label) + } +} + +func (c *compiler) updateEnterBlock(enter *enterBlock) { + scope := c.scope + stashSize, stackSize := 0, 0 + if scope.dynLookup { + stashSize = len(scope.bindings) + enter.names = scope.makeNamesMap() + } else { + for _, b := range scope.bindings { + if b.inStash { + stashSize++ + } else { + stackSize++ + } + } + } + enter.stashSize, enter.stackSize = uint32(stashSize), uint32(stackSize) +} + +func (c *compiler) compileTryStatement(v *ast.TryStatement, needResult bool) { + c.block = &block{ + typ: blockTry, + outer: c.block, + } + var lp int + var bodyNeedResult bool + var finallyBreaking *block + if v.Finally != nil { + lp, finallyBreaking = c.scanStatements(v.Finally.List) + } + if finallyBreaking != nil { + c.block.breaking = finallyBreaking + if lp == -1 { + bodyNeedResult = finallyBreaking.needResult + } + } else { + bodyNeedResult = needResult + } + lbl := len(c.p.code) + c.emit(nil) + if needResult { + c.emit(clearResult) + } + c.compileBlockStatement(v.Body, bodyNeedResult) + var catchOffset int + if v.Catch != nil { + lbl2 := len(c.p.code) // jump over the catch block + c.emit(nil) + catchOffset = len(c.p.code) - lbl + if v.Catch.Parameter != nil { + c.block = &block{ + typ: blockScope, + outer: c.block, + } + c.newBlockScope() + list := v.Catch.Body.List + funcs := c.extractFunctions(list) + if _, ok := v.Catch.Parameter.(ast.Pattern); ok { + // add anonymous binding for the catch parameter, note it must be first + c.scope.addBinding(int(v.Catch.Idx0()) - 1) + } + c.createBindings(v.Catch.Parameter, func(name unistring.String, offset int) { + if c.scope.strict { + switch name { + case "arguments", "eval": + c.throwSyntaxError(offset, "Catch variable may not be eval or arguments in strict mode") + } + } + c.scope.bindNameLexical(name, true, offset) + }) + enter := &enterBlock{} + c.emit(enter) + if pattern, ok := v.Catch.Parameter.(ast.Pattern); ok { + c.scope.bindings[0].emitGet() + c.emitPattern(pattern, func(target, init compiledExpr) { + c.emitPatternLexicalAssign(target, init) + }, false) + } + for _, decl := range funcs { + c.scope.bindNameLexical(decl.Function.Name.Name, true, int(decl.Function.Name.Idx1())-1) + } + c.compileLexicalDeclarations(list, true) + c.compileFunctions(funcs) + c.compileStatements(list, bodyNeedResult) + c.leaveScopeBlock(enter) + if c.scope.dynLookup || c.scope.bindings[0].inStash { + c.p.code[lbl+catchOffset] = &enterCatchBlock{ + names: enter.names, + stashSize: enter.stashSize, + stackSize: enter.stackSize, + } + } else { + enter.stackSize-- + } + c.popScope() + } else { + c.emit(pop) + c.compileBlockStatement(v.Catch.Body, bodyNeedResult) + } + c.p.code[lbl2] = jump(len(c.p.code) - lbl2) + } + var finallyOffset int + if v.Finally != nil { + c.emit(enterFinally{}) + finallyOffset = len(c.p.code) - lbl // finallyOffset should not include enterFinally + if bodyNeedResult && finallyBreaking != nil && lp == -1 { + c.emit(clearResult) + } + c.compileBlockStatement(v.Finally, false) + c.emit(leaveFinally{}) + } else { + c.emit(leaveTry{}) + } + c.p.code[lbl] = try{catchOffset: int32(catchOffset), finallyOffset: int32(finallyOffset)} + c.leaveBlock() +} + +func (c *compiler) addSrcMap(node ast.Node) { + c.p.addSrcMap(int(node.Idx0()) - 1) +} + +func (c *compiler) compileThrowStatement(v *ast.ThrowStatement) { + c.compileExpression(v.Argument).emitGetter(true) + c.addSrcMap(v) + c.emit(throw) +} + +func (c *compiler) compileDoWhileStatement(v *ast.DoWhileStatement, needResult bool) { + c.compileLabeledDoWhileStatement(v, needResult, "") +} + +func (c *compiler) compileLabeledDoWhileStatement(v *ast.DoWhileStatement, needResult bool, label unistring.String) { + c.block = &block{ + typ: blockLoop, + outer: c.block, + label: label, + needResult: needResult, + } + + start := len(c.p.code) + c.compileStatement(v.Body, needResult) + c.block.cont = len(c.p.code) + c.emitExpr(c.compileExpression(v.Test), true) + c.emit(jeq(start - len(c.p.code))) + c.leaveBlock() +} + +func (c *compiler) compileForStatement(v *ast.ForStatement, needResult bool) { + c.compileLabeledForStatement(v, needResult, "") +} + +func (c *compiler) compileForHeadLexDecl(decl *ast.LexicalDeclaration, needResult bool) *enterBlock { + c.block = &block{ + typ: blockIterScope, + outer: c.block, + needResult: needResult, + } + + c.newBlockScope() + enterIterBlock := &enterBlock{} + c.emit(enterIterBlock) + c.createLexicalBindings(decl) + c.compileLexicalDeclaration(decl) + return enterIterBlock +} + +func (c *compiler) compileLabeledForStatement(v *ast.ForStatement, needResult bool, label unistring.String) { + loopBlock := &block{ + typ: blockLoop, + outer: c.block, + label: label, + needResult: needResult, + } + c.block = loopBlock + + var enterIterBlock *enterBlock + switch init := v.Initializer.(type) { + case nil: + // no-op + case *ast.ForLoopInitializerLexicalDecl: + enterIterBlock = c.compileForHeadLexDecl(&init.LexicalDeclaration, needResult) + case *ast.ForLoopInitializerVarDeclList: + for _, expr := range init.List { + c.compileVarBinding(expr) + } + case *ast.ForLoopInitializerExpression: + c.compileExpression(init.Expression).emitGetter(false) + default: + c.assert(false, int(v.For)-1, "Unsupported for loop initializer: %T", init) + panic("unreachable") + } + + if needResult { + c.emit(clearResult) // initial result + } + + if enterIterBlock != nil { + c.emit(jump(1)) + } + + start := len(c.p.code) + var j int + testConst := false + if v.Test != nil { + expr := c.compileExpression(v.Test) + if expr.constant() { + r, ex := c.evalConst(expr) + if ex == nil { + if r.ToBoolean() { + testConst = true + } else { + leave := c.enterDummyMode() + c.compileStatement(v.Body, false) + if v.Update != nil { + c.compileExpression(v.Update).emitGetter(false) + } + leave() + goto end + } + } else { + expr.addSrcMap() + c.emitThrow(ex.val) + goto end + } + } else { + expr.emitGetter(true) + j = len(c.p.code) + c.emit(nil) + } + } + if needResult { + c.emit(clearResult) + } + c.compileStatement(v.Body, needResult) + loopBlock.cont = len(c.p.code) + if enterIterBlock != nil { + c.emit(jump(1)) + } + if v.Update != nil { + c.compileExpression(v.Update).emitGetter(false) + } + if enterIterBlock != nil { + if c.scope.needStash || c.scope.isDynamic() { + c.p.code[start-1] = copyStash{} + c.p.code[loopBlock.cont] = copyStash{} + } else { + if l := len(c.p.code); l > loopBlock.cont { + loopBlock.cont++ + } else { + c.p.code = c.p.code[:l-1] + } + } + } + c.emit(jump(start - len(c.p.code))) + if v.Test != nil { + if !testConst { + c.p.code[j] = jne(len(c.p.code) - j) + } + } +end: + if enterIterBlock != nil { + c.leaveScopeBlock(enterIterBlock) + c.popScope() + } + c.leaveBlock() +} + +func (c *compiler) compileForInStatement(v *ast.ForInStatement, needResult bool) { + c.compileLabeledForInStatement(v, needResult, "") +} + +func (c *compiler) compileForInto(into ast.ForInto, needResult bool) (enter *enterBlock) { + switch into := into.(type) { + case *ast.ForIntoExpression: + c.compileExpression(into.Expression).emitSetter(&c.enumGetExpr, false) + case *ast.ForIntoVar: + if c.scope.strict && into.Binding.Initializer != nil { + c.throwSyntaxError(int(into.Binding.Initializer.Idx0())-1, "for-in loop variable declaration may not have an initializer.") + } + switch target := into.Binding.Target.(type) { + case *ast.Identifier: + c.compileIdentifierExpression(target).emitSetter(&c.enumGetExpr, false) + case ast.Pattern: + c.emit(enumGet) + c.emitPattern(target, c.emitPatternVarAssign, false) + default: + c.throwSyntaxError(int(target.Idx0()-1), "unsupported for-in var target: %T", target) + } + case *ast.ForDeclaration: + + c.block = &block{ + typ: blockIterScope, + outer: c.block, + needResult: needResult, + } + + c.newBlockScope() + enter = &enterBlock{} + c.emit(enter) + switch target := into.Target.(type) { + case *ast.Identifier: + b := c.createLexicalIdBinding(target.Name, into.IsConst, int(into.Idx)-1) + c.emit(enumGet) + b.emitInitP() + case ast.Pattern: + c.createLexicalBinding(target, into.IsConst) + c.emit(enumGet) + c.emitPattern(target, func(target, init compiledExpr) { + c.emitPatternLexicalAssign(target, init) + }, false) + default: + c.assert(false, int(into.Idx)-1, "Unsupported ForBinding: %T", into.Target) + } + default: + c.assert(false, int(into.Idx0())-1, "Unsupported for-into: %T", into) + panic("unreachable") + } + + return +} + +func (c *compiler) compileLabeledForInOfStatement(into ast.ForInto, source ast.Expression, body ast.Statement, iter, needResult bool, label unistring.String) { + c.block = &block{ + typ: blockLoopEnum, + outer: c.block, + label: label, + needResult: needResult, + } + enterPos := -1 + if forDecl, ok := into.(*ast.ForDeclaration); ok { + c.block = &block{ + typ: blockScope, + outer: c.block, + needResult: false, + } + c.newBlockScope() + enterPos = len(c.p.code) + c.emit(jump(1)) + c.createLexicalBinding(forDecl.Target, forDecl.IsConst) + } + c.compileExpression(source).emitGetter(true) + if enterPos != -1 { + s := c.scope + used := len(c.block.breaks) > 0 || s.isDynamic() + if !used { + for _, b := range s.bindings { + if b.useCount() > 0 { + used = true + break + } + } + } + if used { + // We need the stack untouched because it contains the source. + // This is not the most optimal way, but it's an edge case, hopefully quite rare. + for _, b := range s.bindings { + b.moveToStash() + } + enter := &enterBlock{} + c.p.code[enterPos] = enter + c.leaveScopeBlock(enter) + } else { + c.block = c.block.outer + } + c.popScope() + } + if iter { + c.emit(iterateP) + } else { + c.emit(enumerate) + } + if needResult { + c.emit(clearResult) + } + start := len(c.p.code) + c.block.cont = start + c.emit(nil) + enterIterBlock := c.compileForInto(into, needResult) + if needResult { + c.emit(clearResult) + } + c.compileStatement(body, needResult) + if enterIterBlock != nil { + c.leaveScopeBlock(enterIterBlock) + c.popScope() + } + c.emit(jump(start - len(c.p.code))) + if iter { + c.p.code[start] = iterNext(len(c.p.code) - start) + } else { + c.p.code[start] = enumNext(len(c.p.code) - start) + } + c.emit(enumPop, jump(2)) + c.leaveBlock() + c.emit(enumPopClose) +} + +func (c *compiler) compileLabeledForInStatement(v *ast.ForInStatement, needResult bool, label unistring.String) { + c.compileLabeledForInOfStatement(v.Into, v.Source, v.Body, false, needResult, label) +} + +func (c *compiler) compileForOfStatement(v *ast.ForOfStatement, needResult bool) { + c.compileLabeledForOfStatement(v, needResult, "") +} + +func (c *compiler) compileLabeledForOfStatement(v *ast.ForOfStatement, needResult bool, label unistring.String) { + c.compileLabeledForInOfStatement(v.Into, v.Source, v.Body, true, needResult, label) +} + +func (c *compiler) compileWhileStatement(v *ast.WhileStatement, needResult bool) { + c.compileLabeledWhileStatement(v, needResult, "") +} + +func (c *compiler) compileLabeledWhileStatement(v *ast.WhileStatement, needResult bool, label unistring.String) { + c.block = &block{ + typ: blockLoop, + outer: c.block, + label: label, + needResult: needResult, + } + + if needResult { + c.emit(clearResult) + } + start := len(c.p.code) + c.block.cont = start + expr := c.compileExpression(v.Test) + testTrue := false + var j int + if expr.constant() { + if t, ex := c.evalConst(expr); ex == nil { + if t.ToBoolean() { + testTrue = true + } else { + c.compileStatementDummy(v.Body) + goto end + } + } else { + c.emitThrow(ex.val) + goto end + } + } else { + expr.emitGetter(true) + j = len(c.p.code) + c.emit(nil) + } + if needResult { + c.emit(clearResult) + } + c.compileStatement(v.Body, needResult) + c.emit(jump(start - len(c.p.code))) + if !testTrue { + c.p.code[j] = jne(len(c.p.code) - j) + } +end: + c.leaveBlock() +} + +func (c *compiler) compileEmptyStatement(needResult bool) { + if needResult { + c.emit(clearResult) + } +} + +func (c *compiler) compileBranchStatement(v *ast.BranchStatement) { + switch v.Token { + case token.BREAK: + c.compileBreak(v.Label, v.Idx) + case token.CONTINUE: + c.compileContinue(v.Label, v.Idx) + default: + c.assert(false, int(v.Idx0())-1, "Unknown branch statement token: %s", v.Token.String()) + panic("unreachable") + } +} + +func (c *compiler) findBranchBlock(st *ast.BranchStatement) *block { + switch st.Token { + case token.BREAK: + return c.findBreakBlock(st.Label, true) + case token.CONTINUE: + return c.findBreakBlock(st.Label, false) + } + return nil +} + +func (c *compiler) findBreakBlock(label *ast.Identifier, isBreak bool) (res *block) { + if label != nil { + var found *block + for b := c.block; b != nil; b = b.outer { + if res == nil { + if bb := b.breaking; bb != nil { + res = bb + if isBreak { + return + } + } + } + if b.label == label.Name { + found = b + break + } + } + if !isBreak && found != nil && found.typ != blockLoop && found.typ != blockLoopEnum { + c.throwSyntaxError(int(label.Idx)-1, "Illegal continue statement: '%s' does not denote an iteration statement", label.Name) + } + if res == nil { + res = found + } + } else { + // find the nearest loop or switch (if break) + L: + for b := c.block; b != nil; b = b.outer { + if bb := b.breaking; bb != nil { + return bb + } + switch b.typ { + case blockLoop, blockLoopEnum: + res = b + break L + case blockSwitch: + if isBreak { + res = b + break L + } + } + } + } + + return +} + +func (c *compiler) emitBlockExitCode(label *ast.Identifier, idx file.Idx, isBreak bool) *block { + block := c.findBreakBlock(label, isBreak) + if block == nil { + c.throwSyntaxError(int(idx)-1, "Could not find block") + panic("unreachable") + } + contForLoop := !isBreak && block.typ == blockLoop +L: + for b := c.block; b != block; b = b.outer { + switch b.typ { + case blockIterScope: + // blockIterScope in 'for' loops is shared across iterations, so + // continue should not pop it. + if contForLoop && b.outer == block { + break L + } + fallthrough + case blockScope: + b.breaks = append(b.breaks, len(c.p.code)) + c.emit(nil) + case blockTry: + c.emit(leaveTry{}) + case blockWith: + c.emit(leaveWith) + case blockLoopEnum: + c.emit(enumPopClose) + } + } + return block +} + +func (c *compiler) compileBreak(label *ast.Identifier, idx file.Idx) { + block := c.emitBlockExitCode(label, idx, true) + block.breaks = append(block.breaks, len(c.p.code)) + c.emit(nil) +} + +func (c *compiler) compileContinue(label *ast.Identifier, idx file.Idx) { + block := c.emitBlockExitCode(label, idx, false) + block.conts = append(block.conts, len(c.p.code)) + c.emit(nil) +} + +func (c *compiler) compileIfBody(s ast.Statement, needResult bool) { + if !c.scope.strict { + if s, ok := s.(*ast.FunctionDeclaration); ok && !s.Function.Async && !s.Function.Generator { + c.compileFunction(s) + if needResult { + c.emit(clearResult) + } + return + } + } + c.compileStatement(s, needResult) +} + +func (c *compiler) compileIfBodyDummy(s ast.Statement) { + leave := c.enterDummyMode() + defer leave() + c.compileIfBody(s, false) +} + +func (c *compiler) compileIfStatement(v *ast.IfStatement, needResult bool) { + test := c.compileExpression(v.Test) + if needResult { + c.emit(clearResult) + } + if test.constant() { + r, ex := c.evalConst(test) + if ex != nil { + test.addSrcMap() + c.emitThrow(ex.val) + return + } + if r.ToBoolean() { + c.compileIfBody(v.Consequent, needResult) + if v.Alternate != nil { + c.compileIfBodyDummy(v.Alternate) + } + } else { + c.compileIfBodyDummy(v.Consequent) + if v.Alternate != nil { + c.compileIfBody(v.Alternate, needResult) + } else { + if needResult { + c.emit(clearResult) + } + } + } + return + } + test.emitGetter(true) + jmp := len(c.p.code) + c.emit(nil) + c.compileIfBody(v.Consequent, needResult) + if v.Alternate != nil { + jmp1 := len(c.p.code) + c.emit(nil) + c.p.code[jmp] = jne(len(c.p.code) - jmp) + c.compileIfBody(v.Alternate, needResult) + c.p.code[jmp1] = jump(len(c.p.code) - jmp1) + } else { + if needResult { + c.emit(jump(2)) + c.p.code[jmp] = jne(len(c.p.code) - jmp) + c.emit(clearResult) + } else { + c.p.code[jmp] = jne(len(c.p.code) - jmp) + } + } +} + +func (c *compiler) compileReturnStatement(v *ast.ReturnStatement) { + if s := c.scope.nearestFunction(); s != nil && s.funcType == funcClsInit { + c.throwSyntaxError(int(v.Return)-1, "Illegal return statement") + } + if v.Argument != nil { + c.emitExpr(c.compileExpression(v.Argument), true) + } else { + c.emit(loadUndef) + } + for b := c.block; b != nil; b = b.outer { + switch b.typ { + case blockTry: + c.emit(saveResult, leaveTry{}, loadResult) + case blockLoopEnum: + c.emit(enumPopClose) + } + } + if s := c.scope.nearestFunction(); s != nil && s.funcType == funcDerivedCtor { + b := s.boundNames[thisBindingName] + c.assert(b != nil, int(v.Return)-1, "Derived constructor, but no 'this' binding") + b.markAccessPoint() + } + c.emit(ret) +} + +func (c *compiler) checkVarConflict(name unistring.String, offset int) { + for sc := c.scope; sc != nil; sc = sc.outer { + if b, exists := sc.boundNames[name]; exists && !b.isVar && !(b.isArg && sc != c.scope) { + c.throwSyntaxError(offset, "Identifier '%s' has already been declared", name) + } + if sc.isFunction() { + break + } + } +} + +func (c *compiler) emitVarAssign(name unistring.String, offset int, init compiledExpr) { + c.checkVarConflict(name, offset) + if init != nil { + b, noDyn := c.scope.lookupName(name) + if noDyn { + c.emitNamedOrConst(init, name) + c.p.addSrcMap(offset) + b.emitInitP() + } else { + c.emitVarRef(name, offset, b) + c.emitNamedOrConst(init, name) + c.p.addSrcMap(offset) + c.emit(initValueP) + } + } +} + +func (c *compiler) compileVarBinding(expr *ast.Binding) { + switch target := expr.Target.(type) { + case *ast.Identifier: + c.emitVarAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer)) + case ast.Pattern: + c.compileExpression(expr.Initializer).emitGetter(true) + c.emitPattern(target, c.emitPatternVarAssign, false) + default: + c.throwSyntaxError(int(target.Idx0()-1), "unsupported variable binding target: %T", target) + } +} + +func (c *compiler) emitLexicalAssign(name unistring.String, offset int, init compiledExpr) { + b := c.scope.boundNames[name] + c.assert(b != nil, offset, "Lexical declaration for an unbound name") + if init != nil { + c.emitNamedOrConst(init, name) + c.p.addSrcMap(offset) + } else { + if b.isConst { + c.throwSyntaxError(offset, "Missing initializer in const declaration") + } + c.emit(loadUndef) + } + b.emitInitP() +} + +func (c *compiler) emitPatternVarAssign(target, init compiledExpr) { + id := target.(*compiledIdentifierExpr) + c.emitVarAssign(id.name, id.offset, init) +} + +func (c *compiler) emitPatternLexicalAssign(target, init compiledExpr) { + id := target.(*compiledIdentifierExpr) + c.emitLexicalAssign(id.name, id.offset, init) +} + +func (c *compiler) emitPatternAssign(target, init compiledExpr) { + if id, ok := target.(*compiledIdentifierExpr); ok { + b, noDyn := c.scope.lookupName(id.name) + if noDyn { + c.emitNamedOrConst(init, id.name) + b.emitSetP() + } else { + c.emitVarRef(id.name, id.offset, b) + c.emitNamedOrConst(init, id.name) + c.emit(putValueP) + } + } else { + target.emitRef() + c.emitExpr(init, true) + c.emit(putValueP) + } +} + +func (c *compiler) compileLexicalBinding(expr *ast.Binding) { + switch target := expr.Target.(type) { + case *ast.Identifier: + c.emitLexicalAssign(target.Name, int(target.Idx)-1, c.compileExpression(expr.Initializer)) + case ast.Pattern: + c.compileExpression(expr.Initializer).emitGetter(true) + c.emitPattern(target, func(target, init compiledExpr) { + c.emitPatternLexicalAssign(target, init) + }, false) + default: + c.throwSyntaxError(int(target.Idx0()-1), "unsupported lexical binding target: %T", target) + } +} + +func (c *compiler) compileVariableStatement(v *ast.VariableStatement) { + for _, expr := range v.List { + c.compileVarBinding(expr) + } +} + +func (c *compiler) compileLexicalDeclaration(v *ast.LexicalDeclaration) { + for _, e := range v.List { + c.compileLexicalBinding(e) + } +} + +func (c *compiler) isEmptyResult(st ast.Statement) bool { + switch st := st.(type) { + case *ast.EmptyStatement, *ast.VariableStatement, *ast.LexicalDeclaration, *ast.FunctionDeclaration, + *ast.ClassDeclaration, *ast.BranchStatement, *ast.DebuggerStatement: + return true + case *ast.LabelledStatement: + return c.isEmptyResult(st.Statement) + case *ast.BlockStatement: + for _, s := range st.List { + if _, ok := s.(*ast.BranchStatement); ok { + return true + } + if !c.isEmptyResult(s) { + return false + } + } + return true + } + return false +} + +func (c *compiler) scanStatements(list []ast.Statement) (lastProducingIdx int, breakingBlock *block) { + lastProducingIdx = -1 + for i, st := range list { + if bs, ok := st.(*ast.BranchStatement); ok { + if blk := c.findBranchBlock(bs); blk != nil { + breakingBlock = blk + } + break + } + if !c.isEmptyResult(st) { + lastProducingIdx = i + } + } + return +} + +func (c *compiler) compileStatementsNeedResult(list []ast.Statement, lastProducingIdx int) { + if lastProducingIdx >= 0 { + for _, st := range list[:lastProducingIdx] { + if _, ok := st.(*ast.FunctionDeclaration); ok { + continue + } + c.compileStatement(st, false) + } + c.compileStatement(list[lastProducingIdx], true) + } + var leave func() + defer func() { + if leave != nil { + leave() + } + }() + for _, st := range list[lastProducingIdx+1:] { + if _, ok := st.(*ast.FunctionDeclaration); ok { + continue + } + c.compileStatement(st, false) + if leave == nil { + if _, ok := st.(*ast.BranchStatement); ok { + leave = c.enterDummyMode() + } + } + } +} + +func (c *compiler) compileStatements(list []ast.Statement, needResult bool) { + lastProducingIdx, blk := c.scanStatements(list) + if blk != nil { + needResult = blk.needResult + } + if needResult { + c.compileStatementsNeedResult(list, lastProducingIdx) + return + } + for _, st := range list { + if _, ok := st.(*ast.FunctionDeclaration); ok { + continue + } + c.compileStatement(st, false) + } +} + +func (c *compiler) compileGenericLabeledStatement(v ast.Statement, needResult bool, label unistring.String) { + c.block = &block{ + typ: blockLabel, + outer: c.block, + label: label, + needResult: needResult, + } + c.compileStatement(v, needResult) + c.leaveBlock() +} + +func (c *compiler) compileBlockStatement(v *ast.BlockStatement, needResult bool) { + var scopeDeclared bool + funcs := c.extractFunctions(v.List) + if len(funcs) > 0 { + c.newBlockScope() + scopeDeclared = true + } + c.createFunctionBindings(funcs) + scopeDeclared = c.compileLexicalDeclarations(v.List, scopeDeclared) + + var enter *enterBlock + if scopeDeclared { + c.block = &block{ + outer: c.block, + typ: blockScope, + needResult: needResult, + } + enter = &enterBlock{} + c.emit(enter) + } + c.compileFunctions(funcs) + c.compileStatements(v.List, needResult) + if scopeDeclared { + c.leaveScopeBlock(enter) + c.popScope() + } +} + +func (c *compiler) compileExpressionStatement(v *ast.ExpressionStatement, needResult bool) { + c.emitExpr(c.compileExpression(v.Expression), needResult) + if needResult { + c.emit(saveResult) + } +} + +func (c *compiler) compileWithStatement(v *ast.WithStatement, needResult bool) { + if c.scope.strict { + c.throwSyntaxError(int(v.With)-1, "Strict mode code may not include a with statement") + return + } + c.compileExpression(v.Object).emitGetter(true) + c.emit(enterWith) + c.block = &block{ + outer: c.block, + typ: blockWith, + needResult: needResult, + } + c.newBlockScope() + c.scope.dynamic = true + c.compileStatement(v.Body, needResult) + c.emit(leaveWith) + c.leaveBlock() + c.popScope() +} + +func (c *compiler) compileSwitchStatement(v *ast.SwitchStatement, needResult bool) { + c.block = &block{ + typ: blockSwitch, + outer: c.block, + needResult: needResult, + } + + c.compileExpression(v.Discriminant).emitGetter(true) + + var funcs []*ast.FunctionDeclaration + for _, s := range v.Body { + f := c.extractFunctions(s.Consequent) + funcs = append(funcs, f...) + } + var scopeDeclared bool + if len(funcs) > 0 { + c.newBlockScope() + scopeDeclared = true + c.createFunctionBindings(funcs) + } + + for _, s := range v.Body { + scopeDeclared = c.compileLexicalDeclarations(s.Consequent, scopeDeclared) + } + + var enter *enterBlock + var db *binding + if scopeDeclared { + c.block = &block{ + typ: blockScope, + outer: c.block, + needResult: needResult, + } + enter = &enterBlock{} + c.emit(enter) + // create anonymous variable for the discriminant + bindings := c.scope.bindings + var bb []*binding + if cap(bindings) == len(bindings) { + bb = make([]*binding, len(bindings)+1) + } else { + bb = bindings[:len(bindings)+1] + } + copy(bb[1:], bindings) + db = &binding{ + scope: c.scope, + isConst: true, + isStrict: true, + } + bb[0] = db + c.scope.bindings = bb + } + + c.compileFunctions(funcs) + + if needResult { + c.emit(clearResult) + } + + jumps := make([]int, len(v.Body)) + + for i, s := range v.Body { + if s.Test != nil { + if db != nil { + db.emitGet() + } else { + c.emit(dup) + } + c.compileExpression(s.Test).emitGetter(true) + c.emit(op_strict_eq) + if db != nil { + c.emit(jne(2)) + } else { + c.emit(jne(3), pop) + } + jumps[i] = len(c.p.code) + c.emit(nil) + } + } + + if db == nil { + c.emit(pop) + } + jumpNoMatch := -1 + if v.Default != -1 { + if v.Default != 0 { + jumps[v.Default] = len(c.p.code) + c.emit(nil) + } + } else { + jumpNoMatch = len(c.p.code) + c.emit(nil) + } + + for i, s := range v.Body { + if s.Test != nil || i != 0 { + c.p.code[jumps[i]] = jump(len(c.p.code) - jumps[i]) + } + c.compileStatements(s.Consequent, needResult) + } + + if jumpNoMatch != -1 { + c.p.code[jumpNoMatch] = jump(len(c.p.code) - jumpNoMatch) + } + if enter != nil { + c.leaveScopeBlock(enter) + enter.stackSize-- + c.popScope() + } + c.leaveBlock() +} + +func (c *compiler) compileClassDeclaration(v *ast.ClassDeclaration) { + c.emitLexicalAssign(v.Class.Name.Name, int(v.Class.Class)-1, c.compileClassLiteral(v.Class, false)) +} diff --git a/pkg/xscript/engine/compiler_test.go b/pkg/xscript/engine/compiler_test.go new file mode 100644 index 0000000..431b9da --- /dev/null +++ b/pkg/xscript/engine/compiler_test.go @@ -0,0 +1,5874 @@ +package engine + +import ( + "os" + "sync" + "testing" +) + +const TESTLIB = ` +function $ERROR(message) { + throw new Error(message); +} + +function Test262Error() { +} + +function assert(mustBeTrue, message) { + if (mustBeTrue === true) { + return; + } + + if (message === undefined) { + message = 'Expected true but got ' + String(mustBeTrue); + } + $ERROR(message); +} + +assert._isSameValue = function (a, b) { + if (a === b) { + // Handle +/-0 vs. -/+0 + return a !== 0 || 1 / a === 1 / b; + } + + // Handle NaN vs. NaN + return a !== a && b !== b; +}; + +assert.sameValue = function (actual, expected, message) { + if (assert._isSameValue(actual, expected)) { + return; + } + + if (message === undefined) { + message = ''; + } else { + message += ' '; + } + + message += 'Expected SameValue(«' + String(actual) + '», «' + String(expected) + '») to be true'; + + $ERROR(message); +}; + +assert.throws = function (expectedErrorConstructor, func, message) { + if (typeof func !== "function") { + $ERROR('assert.throws requires two arguments: the error constructor ' + + 'and a function to run'); + return; + } + if (message === undefined) { + message = ''; + } else { + message += ' '; + } + + try { + func(); + } catch (thrown) { + if (typeof thrown !== 'object' || thrown === null) { + message += 'Thrown value was not an object!'; + $ERROR(message); + } else if (thrown.constructor !== expectedErrorConstructor) { + message += 'Expected a ' + expectedErrorConstructor.name + ' but got a ' + thrown.constructor.name; + $ERROR(message); + } + return; + } + + message += 'Expected a ' + expectedErrorConstructor.name + ' to be thrown but no exception was thrown at all'; + $ERROR(message); +}; + +function compareArray(a, b) { + if (b.length !== a.length) { + return false; + } + + for (var i = 0; i < a.length; i++) { + if (b[i] !== a[i]) { + return false; + } + } + return true; +} +` + +const TESTLIBX = ` + function looksNative(fn) { + return /native code/.test(Function.prototype.toString.call(fn)); + } + + function deepEqual(a, b) { + if (typeof a === "object") { + if (typeof b === "object") { + if (a === b) { + return true; + } + if (Reflect.getPrototypeOf(a) !== Reflect.getPrototypeOf(b)) { + return false; + } + var keysA = Object.keys(a); + var keysB = Object.keys(b); + if (keysA.length !== keysB.length) { + return false; + } + if (!compareArray(keysA.sort(), keysB.sort())) { + return false; + } + for (var i = 0; i < keysA.length; i++) { + var key = keysA[i]; + if (!deepEqual(a[key], b[key])) { + return false; + } + } + return true; + } else { + return false; + } + } + return assert._isSameValue(a, b); + } + + function assertStack(e, expected) { + const lines = e.stack.split('\n'); + assert.sameValue(lines.length, expected.length + 2, "Stack lengths mismatch"); + let lnum = 1; + for (const [file, func, line, col] of expected) { + const expLine = func === "" ? + "\tat " + file + ":" + line + ":" + col + "(" : + "\tat " + func + " (" + file + ":" + line + ":" + col + "("; + assert.sameValue(lines[lnum].substring(0, expLine.length), expLine, "line " + lnum); + lnum++; + } + } +` + +var ( + // The reason it's implemented this way rather than just as _testLib = MustCompile(...) + // is because when you try to debug the compiler and set a breakpoint it gets triggered during the + // initialisation which is annoying. + _testLib, _testLibX *Program + testLibOnce, testLibXOnce sync.Once +) + +func testLib() *Program { + testLibOnce.Do(func() { + _testLib = MustCompile("testlib.js", TESTLIB, false) + }) + return _testLib +} + +func testLibX() *Program { + testLibXOnce.Do(func() { + _testLibX = MustCompile("testlibx.js", TESTLIBX, false) + }) + return _testLibX +} + +func (r *Runtime) testPrg(p *Program, expectedResult Value, t *testing.T) { + p.dumpCode(t.Logf) + v, err := r.RunProgram(p) + if err != nil { + if ex, ok := err.(*Exception); ok { + t.Fatalf("Exception: %v", ex.String()) + } + } + vm := r.vm + t.Logf("stack size: %d", len(vm.stack)) + t.Logf("stashAllocs: %d", vm.stashAllocs) + + if v == nil && expectedResult != nil || !v.SameAs(expectedResult) { + t.Fatalf("Result: %+v, expected: %+v", v, expectedResult) + } + + if vm.sp != 0 { + t.Fatalf("sp: %d", vm.sp) + } + + if l := len(vm.iterStack); l > 0 { + t.Fatalf("iter stack is not empty: %d", l) + } +} + +func (r *Runtime) testScriptWithTestLib(script string, expectedResult Value, t *testing.T) { + _, err := r.RunProgram(testLib()) + if err != nil { + t.Fatal(err) + } + + r.testScript(script, expectedResult, t) +} + +func (r *Runtime) testScriptWithTestLibX(script string, expectedResult Value, t *testing.T) { + _, err := r.RunProgram(testLib()) + if err != nil { + t.Fatal(err) + } + + _, err = r.RunProgram(testLibX()) + if err != nil { + t.Fatal(err) + } + + r.testScript(script, expectedResult, t) +} + +func (r *Runtime) testScript(script string, expectedResult Value, t *testing.T) { + r.testPrg(MustCompile("test.js", script, false), expectedResult, t) +} + +func testScript(script string, expectedResult Value, t *testing.T) { + New().testScript(script, expectedResult, t) +} + +func testScriptWithTestLib(script string, expectedResult Value, t *testing.T) { + New().testScriptWithTestLib(script, expectedResult, t) +} + +func testScriptWithTestLibX(script string, expectedResult Value, t *testing.T) { + New().testScriptWithTestLibX(script, expectedResult, t) +} + +func (r *Runtime) testAsyncFunc(src string, expectedResult Value, t *testing.T) { + v, err := r.RunScript("test.js", "(async function test() {"+src+"\n})()") + if err != nil { + t.Fatal(err) + } + promise := v.Export().(*Promise) + switch s := promise.State(); s { + case PromiseStateFulfilled: + if res := promise.Result(); res == nil && expectedResult != nil || !res.SameAs(expectedResult) { + t.Fatalf("Result: %+v, expected: %+v", res, expectedResult) + } + case PromiseStateRejected: + res := promise.Result() + if resObj, ok := res.(*Object); ok { + if stack := resObj.Get("stack"); stack != nil { + t.Fatal(stack.String()) + } + } + t.Fatal(res.String()) + default: + t.Fatalf("Unexpected promise state: %v", s) + } +} + +func (r *Runtime) testAsyncFuncWithTestLib(src string, expectedResult Value, t *testing.T) { + _, err := r.RunProgram(testLib()) + if err != nil { + t.Fatal(err) + } + + r.testAsyncFunc(src, expectedResult, t) +} + +func (r *Runtime) testAsyncFuncWithTestLibX(src string, expectedResult Value, t *testing.T) { + _, err := r.RunProgram(testLib()) + if err != nil { + t.Fatal(err) + } + + _, err = r.RunProgram(testLibX()) + if err != nil { + t.Fatal(err) + } + + r.testAsyncFunc(src, expectedResult, t) +} + +func testAsyncFunc(src string, expectedResult Value, t *testing.T) { + New().testAsyncFunc(src, expectedResult, t) +} + +func testAsyncFuncWithTestLib(src string, expectedResult Value, t *testing.T) { + New().testAsyncFuncWithTestLib(src, expectedResult, t) +} + +func testAsyncFuncWithTestLibX(src string, expectedResult Value, t *testing.T) { + New().testAsyncFuncWithTestLibX(src, expectedResult, t) +} + +func TestEmptyProgram(t *testing.T) { + const SCRIPT = ` + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestResultEmptyBlock(t *testing.T) { + const SCRIPT = ` + undefined; + {} + ` + testScript(SCRIPT, _undefined, t) +} + +func TestResultVarDecl(t *testing.T) { + const SCRIPT = ` + 7; var x = 1; + ` + testScript(SCRIPT, valueInt(7), t) +} + +func TestResultLexDecl(t *testing.T) { + const SCRIPT = ` + 7; {let x = 1}; + ` + testScript(SCRIPT, valueInt(7), t) +} + +func TestResultLexDeclBreak(t *testing.T) { + const SCRIPT = ` + L:{ 7; {let x = 1; break L;}}; + ` + testScript(SCRIPT, valueInt(7), t) +} + +func TestResultLexDeclNested(t *testing.T) { + const SCRIPT = ` + 7; {let x = (function() { return eval("8; {let y = 9}")})()}; + ` + testScript(SCRIPT, valueInt(7), t) +} + +func TestErrorProto(t *testing.T) { + const SCRIPT = ` + var e = new TypeError(); + e.name; + ` + + testScript(SCRIPT, asciiString("TypeError"), t) +} + +func TestThis1(t *testing.T) { + const SCRIPT = ` + function independent() { + return this.prop; + } + var o = {}; + o.b = {g: independent, prop: 42}; + + o.b.g(); + ` + testScript(SCRIPT, intToValue(42), t) +} + +func TestThis2(t *testing.T) { + const SCRIPT = ` +var o = { + prop: 37, + f: function() { + return this.prop; + } +}; + +o.f(); +` + + testScript(SCRIPT, intToValue(37), t) +} + +func TestThisStrict(t *testing.T) { + const SCRIPT = ` + "use strict"; + + Object.defineProperty(Object.prototype, "x", { get: function () { return this; } }); + + (5).x === 5; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestThisNoStrict(t *testing.T) { + const SCRIPT = ` + Object.defineProperty(Object.prototype, "x", { get: function () { return this; } }); + + (5).x == 5; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestNestedFuncVarResolution(t *testing.T) { + const SCRIPT = ` + (function outer() { + var v = 42; + function inner() { + return v; + } + return inner(); + })(); +` + testScript(SCRIPT, valueInt(42), t) +} + +func TestNestedFuncVarResolution1(t *testing.T) { + const SCRIPT = ` + function outer(argOuter) { + var called = 0; + var inner = function(argInner) { + if (arguments.length !== 1) { + throw new Error(); + } + called++; + if (argOuter !== 1) { + throw new Error("argOuter"); + } + if (argInner !== 2) { + throw new Error("argInner"); + } + }; + inner(2); + } + outer(1); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestCallFewerArgs(t *testing.T) { + const SCRIPT = ` +function A(a, b, c) { + return String(a) + " " + String(b) + " " + String(c); +} + +A(1, 2); +` + testScript(SCRIPT, asciiString("1 2 undefined"), t) +} + +func TestCallFewerArgsClosureNoArgs(t *testing.T) { + const SCRIPT = ` + var x; + function A(a, b, c) { + var y = a; + x = function() { return " " + y }; + return String(a) + " " + String(b) + " " + String(c); + } + + A(1, 2) + x(); +` + testScript(SCRIPT, asciiString("1 2 undefined 1"), t) +} + +func TestCallFewerArgsClosureArgs(t *testing.T) { + const SCRIPT = ` + var x; + function A(a, b, c) { + var y = b; + x = function() { return " " + a + " " + y }; + return String(a) + " " + String(b) + " " + String(c); + } + + A(1, 2) + x(); +` + testScript(SCRIPT, asciiString("1 2 undefined 1 2"), t) +} + +func TestCallMoreArgs(t *testing.T) { + const SCRIPT = ` +function A(a, b) { + var c = 4; + return a - b + c; +} + +A(1, 2, 3); +` + testScript(SCRIPT, intToValue(3), t) +} + +func TestCallMoreArgsDynamic(t *testing.T) { + const SCRIPT = ` +function A(a, b) { + var c = 4; + if (false) { + eval(""); + } + return a - b + c; +} + +A(1, 2, 3); +` + testScript(SCRIPT, intToValue(3), t) +} + +func TestCallLessArgsDynamic(t *testing.T) { + const SCRIPT = ` +function A(a, b, c) { + // Make it stashful + function B() { + return a; + } + return String(a) + " " + String(b) + " " + String(c); +} + +A(1, 2); +` + testScript(SCRIPT, asciiString("1 2 undefined"), t) +} + +func TestCallLessArgsDynamicLocalVar(t *testing.T) { + const SCRIPT = ` + function f(param) { + var a = 42; + if (false) { + eval(""); + } + return a; + } + f(); +` + + testScript(SCRIPT, intToValue(42), t) +} + +/* +func TestFib(t *testing.T) { + testScript(TEST_FIB, valueInt(9227465), t) +} +*/ + +func TestNativeCall(t *testing.T) { + const SCRIPT = ` + var o = Object(1); + Object.defineProperty(o, "test", {value: 42}); + o.test; + ` + testScript(SCRIPT, intToValue(42), t) +} + +func TestJSCall(t *testing.T) { + const SCRIPT = ` + function getter() { + return this.x; + } + var o = Object(1); + o.x = 42; + Object.defineProperty(o, "test", {get: getter}); + o.test; + ` + testScript(SCRIPT, intToValue(42), t) + +} + +func TestLoop1(t *testing.T) { + const SCRIPT = ` + function A() { + var x = 1; + for (var i = 0; i < 1; i++) { + var x = 2; + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(2), t) +} + +func TestLoopBreak(t *testing.T) { + const SCRIPT = ` + function A() { + var x = 1; + for (var i = 0; i < 1; i++) { + break; + var x = 2; + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestForLoopOptionalExpr(t *testing.T) { + const SCRIPT = ` + function A() { + var x = 1; + for (;;) { + break; + var x = 2; + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestBlockBreak(t *testing.T) { + const SCRIPT = ` + var rv = 0; + B1: { + rv = 1; + B2: { + rv = 2; + break B1; + } + rv = 3; + } + rv; + ` + testScript(SCRIPT, intToValue(2), t) + +} + +func TestTry(t *testing.T) { + const SCRIPT = ` + function A() { + var x = 1; + try { + x = 2; + } catch(e) { + x = 3; + } finally { + x = 4; + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(4), t) +} + +func TestTryOptionalCatchBinding(t *testing.T) { + const SCRIPT = ` + try { + throw null; + } catch { + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryCatch(t *testing.T) { + const SCRIPT = ` + function A() { + var x; + try { + throw 4; + } catch(e) { + x = e; + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(4), t) +} + +func TestTryCatchDirectEval(t *testing.T) { + const SCRIPT = ` + function A() { + var x; + try { + throw 4; + } catch(e) { + eval("x = e"); + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(4), t) +} + +func TestTryExceptionInCatch(t *testing.T) { + const SCRIPT = ` + function A() { + var x; + try { + throw 4; + } catch(e) { + throw 5; + } + return x; + } + + var rv; + try { + A(); + } catch (e) { + rv = e; + } + rv; + ` + testScript(SCRIPT, intToValue(5), t) +} + +func TestTryContinueInCatch(t *testing.T) { + const SCRIPT = ` + var c3 = 0, fin3 = 0; + while (c3 < 2) { + try { + throw "ex1"; + } catch(er1) { + c3 += 1; + continue; + } finally { + fin3 = 1; + } + fin3 = 0; + } + + fin3; + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestContinueInWith(t *testing.T) { + const SCRIPT = ` + var x; + var o = {x: 0}; + for (var i = 0; i < 2; i++) { + with(o) { + x = i; + if (i === 0) { + continue; + } + } + break; + } + x; + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryContinueInFinally(t *testing.T) { + const SCRIPT = ` + var c3 = 0, fin3 = 0; + while (c3 < 2) { + try { + throw "ex1"; + } catch(er1) { + c3 += 1; + } finally { + fin3 = 1; + continue; + } + fin3 = 0; + } + + fin3; + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestTryBreakFinallyContinue(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 3; i++) { + try { + break; + } finally { + continue; + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryBreakFinallyContinueWithResult(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 3; i++) { + try { + true; + break; + } finally { + continue; + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryBreakFinallyContinueWithResult1(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 3; i++) { + try { + true; + break; + } finally { + var x = 1; + continue; + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryBreakFinallyContinueWithResultNested(t *testing.T) { + const SCRIPT = ` +LOOP: + for (var i = 0; i < 3; i++) { + try { + if (true) { + false; break; + } + } finally { + if (true) { + true; continue; + } + } + } + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestTryBreakOuterFinallyContinue(t *testing.T) { + const SCRIPT = ` + let iCount = 0, jCount = 0; + OUTER: for (let i = 0; i < 1; i++) { + iCount++; + for (let j = 0; j < 2; j++) { + jCount++; + try { + break OUTER; + } finally { + continue; + } + } + } + ""+iCount+jCount; + ` + testScript(SCRIPT, asciiString("12"), t) +} + +func TestTryIllegalContinueWithFinallyOverride(t *testing.T) { + const SCRIPT = ` + L: { + while (Math.random() > 0.5) { + try { + continue L; + } finally { + break; + } + } + } + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestTryIllegalContinueWithFinallyOverrideNoLabel(t *testing.T) { + const SCRIPT = ` + L: { + try { + continue; + } finally { + break L; + } + } + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestTryIllegalContinueWithFinallyOverrideDummy(t *testing.T) { + const SCRIPT = ` + L: { + while (false) { + try { + continue L; + } finally { + break; + } + } + } + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestTryNoResult(t *testing.T) { + const SCRIPT = ` + true; + L: + try { + break L; + } finally { + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestCatchLexicalEnv(t *testing.T) { + const SCRIPT = ` + function F() { + try { + throw 1; + } catch (e) { + var x = e; + } + return x; + } + + F(); + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestThrowType(t *testing.T) { + const SCRIPT = ` + function Exception(message) { + this.message = message; + } + + + function A() { + try { + throw new Exception("boo!"); + } catch(e) { + return e; + } + } + var thrown = A(); + thrown !== null && typeof thrown === "object" && thrown.constructor === Exception; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestThrowConstructorName(t *testing.T) { + const SCRIPT = ` + function Exception(message) { + this.message = message; + } + + + function A() { + try { + throw new Exception("boo!"); + } catch(e) { + return e; + } + } + A().constructor.name; + ` + + testScript(SCRIPT, asciiString("Exception"), t) +} + +func TestThrowNativeConstructorName(t *testing.T) { + const SCRIPT = ` + + + function A() { + try { + throw new TypeError(); + } catch(e) { + return e; + } + } + A().constructor.name; + ` + + testScript(SCRIPT, asciiString("TypeError"), t) +} + +func TestEmptyTryNoCatch(t *testing.T) { + const SCRIPT = ` + var called = false; + try { + } finally { + called = true; + } + called; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestTryReturnFromCatch(t *testing.T) { + const SCRIPT = ` + function f(o) { + var x = 42; + + function innerf(o) { + try { + throw o; + } catch (e) { + return x; + } + } + + return innerf(o); + } + f({}); + ` + + testScript(SCRIPT, valueInt(42), t) +} + +func TestTryCompletionResult(t *testing.T) { + const SCRIPT = ` + 99; do { -99; try { 39 } catch (e) { -1 } finally { break; -2 }; } while (false); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestIfElse(t *testing.T) { + const SCRIPT = ` + var rv; + if (rv === undefined) { + rv = "passed"; + } else { + rv = "failed"; + } + rv; + ` + + testScript(SCRIPT, asciiString("passed"), t) +} + +func TestIfElseRetVal(t *testing.T) { + const SCRIPT = ` + var x; + if (x === undefined) { + "passed"; + } else { + "failed"; + } + ` + + testScript(SCRIPT, asciiString("passed"), t) +} + +func TestWhileReturnValue(t *testing.T) { + const SCRIPT = ` + var x = 0; + while(true) { + x = 1; + break; + } + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestIfElseLabel(t *testing.T) { + const SCRIPT = ` + var x = 0; + abc: if (true) { + x = 1; + break abc; + } + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestIfMultipleLabels(t *testing.T) { + const SCRIPT = ` + var x = 0; + xyz:abc: if (true) { + break xyz; + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestBreakOutOfTry(t *testing.T) { + const SCRIPT = ` + function A() { + var x = 1; + B: { + try { + x = 2; + } catch(e) { + x = 3; + } finally { + break B; + x = 4; + } + } + return x; + } + + A(); + ` + testScript(SCRIPT, intToValue(2), t) +} + +func TestReturnOutOfTryNested(t *testing.T) { + const SCRIPT = ` + function A() { + function nested() { + try { + return 1; + } catch(e) { + return 2; + } + } + return nested(); + } + + A(); + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestReturnOutOfTryWithFinally(t *testing.T) { + const SCRIPT = ` + function test() { + try { + return 'Hello, world!'; + } finally { + const dummy = 'unexpected'; + } + } + test(); + ` + testScript(SCRIPT, asciiString("Hello, world!"), t) +} + +func TestContinueLoop(t *testing.T) { + const SCRIPT = ` + function A() { + var r = 0; + for (var i = 0; i < 5; i++) { + if (i > 1) { + continue; + } + r++; + } + return r; + } + + A(); + ` + testScript(SCRIPT, intToValue(2), t) +} + +func TestContinueOutOfTry(t *testing.T) { + const SCRIPT = ` + function A() { + var r = 0; + for (var i = 0; i < 5; i++) { + try { + if (i > 1) { + continue; + } + } catch(e) { + return 99; + } + r++; + } + return r; + } + + A(); + ` + testScript(SCRIPT, intToValue(2), t) +} + +func TestThisInCatch(t *testing.T) { + const SCRIPT = ` + function O() { + try { + f(); + } catch (e) { + this.value = e.toString(); + } + } + + function f() { + throw "ex"; + } + + var o = new O(); + o.value; + ` + testScript(SCRIPT, asciiString("ex"), t) +} + +func TestNestedTry(t *testing.T) { + const SCRIPT = ` + var ex; + try { + throw "ex1"; + } catch (er1) { + try { + throw "ex2"; + } catch (er1) { + ex = er1; + } + } + ex; + ` + testScript(SCRIPT, asciiString("ex2"), t) +} + +func TestNestedTryInStashlessFunc(t *testing.T) { + const SCRIPT = ` + function f() { + var ex1, ex2; + try { + throw "ex1"; + } catch (er1) { + try { + throw "ex2"; + } catch (er1) { + ex2 = er1; + } + ex1 = er1; + } + return ex1 == "ex1" && ex2 == "ex2"; + } + f(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalLexicalDecl(t *testing.T) { + const SCRIPT = ` + eval("let x = true; x;"); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalInCatchInStashlessFunc(t *testing.T) { + const SCRIPT = ` + function f() { + var ex; + try { + throw "ex1"; + } catch (er1) { + eval("ex = er1"); + } + return ex; + } + f(); + ` + testScript(SCRIPT, asciiString("ex1"), t) +} + +func TestCatchClosureInStashlessFunc(t *testing.T) { + const SCRIPT = ` + function f() { + var ex; + try { + throw "ex1"; + } catch (er1) { + return function() { + return er1; + } + } + } + f()(); + ` + testScript(SCRIPT, asciiString("ex1"), t) +} + +func TestCatchVarNotUsedInStashlessFunc(t *testing.T) { + const SCRIPT = ` + function f() { + var ex; + try { + throw "ex1"; + } catch (er1) { + ex = "ok"; + } + return ex; + } + f(); + ` + testScript(SCRIPT, asciiString("ok"), t) +} + +func TestNew(t *testing.T) { + const SCRIPT = ` + function O() { + this.x = 42; + } + + new O().x; + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestStringConstructor(t *testing.T) { + const SCRIPT = ` + function F() { + return String(33) + " " + String("cows"); + } + + F(); + ` + testScript(SCRIPT, asciiString("33 cows"), t) +} + +func TestError(t *testing.T) { + const SCRIPT = ` + function F() { + return new Error("test"); + } + + var e = F(); + e.message == "test" && e.name == "Error"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestTypeError(t *testing.T) { + const SCRIPT = ` + function F() { + return new TypeError("test"); + } + + var e = F(); + e.message == "test" && e.name == "TypeError"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestToString(t *testing.T) { + const SCRIPT = ` + var o = {x: 42}; + o.toString = function() { + return String(this.x); + } + + var o1 = {}; + o.toString() + " ### " + o1.toString(); + ` + testScript(SCRIPT, asciiString("42 ### [object Object]"), t) +} + +func TestEvalOrder(t *testing.T) { + const SCRIPT = ` + var o = {f: function() {return 42}, x: 0}; + var trace = ""; + + function F1() { + trace += "First!"; + return o; + } + + function F2() { + trace += "Second!"; + return "f"; + } + + function F3() { + trace += "Third!"; + } + + var rv = F1()[F2()](F3()); + rv += trace; + rv; + ` + + testScript(SCRIPT, asciiString("42First!Second!Third!"), t) +} + +func TestPostfixIncBracket(t *testing.T) { + const SCRIPT = ` + var o = {x: 42}; + var trace = ""; + + function F1() { + trace += "First!"; + return o; + } + + function F2() { + trace += "Second!"; + return "x"; + } + + + var rv = F1()[F2()]++; + rv + trace + o.x; + ` + testScript(SCRIPT, asciiString("42First!Second!43"), t) +} + +func TestPostfixIncDot(t *testing.T) { + const SCRIPT = ` + var o = {x: 42}; + var trace = ""; + + function F1() { + trace += "First!"; + return o; + } + + var rv = F1().x++; + rv + trace + o.x; + ` + testScript(SCRIPT, asciiString("42First!43"), t) +} + +func TestPrefixIncBracket(t *testing.T) { + const SCRIPT = ` + var o = {x: 42}; + var trace = ""; + + function F1() { + trace += "First!"; + return o; + } + + function F2() { + trace += "Second!"; + return "x"; + } + + + var rv = ++F1()[F2()]; + rv + trace + o.x; + ` + testScript(SCRIPT, asciiString("43First!Second!43"), t) +} + +func TestPrefixIncDot(t *testing.T) { + const SCRIPT = ` + var o = {x: 42}; + var trace = ""; + + function F1() { + trace += "First!"; + return o; + } + + var rv = ++F1().x; + rv + trace + o.x; + ` + testScript(SCRIPT, asciiString("43First!43"), t) +} + +func TestPostDecObj(t *testing.T) { + const SCRIPT = ` + var object = {valueOf: function() {return 1}}; + var y = object--; + var ok = false; + if (y === 1) { + ok = true; + } + ok; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestPropAcc1(t *testing.T) { + const SCRIPT = ` + 1..toString() + ` + + testScript(SCRIPT, asciiString("1"), t) +} + +func TestEvalDirect(t *testing.T) { + const SCRIPT = ` + var rv = false; + function foo(){ rv = true; } + + var o = { }; + function f() { + try { + eval("o.bar( foo() );"); + } catch (e) { + } + } + f(); + rv; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalRet(t *testing.T) { + const SCRIPT = ` + eval("for (var i = 0; i < 3; i++) {i}") + ` + + testScript(SCRIPT, valueInt(2), t) +} + +func TestEvalFunctionDecl(t *testing.T) { + const SCRIPT = ` + eval("function F() {}") + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestEvalFunctionExpr(t *testing.T) { + const SCRIPT = ` + eval("(function F() {return 42;})")() + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestEvalDirectScope(t *testing.T) { + const SCRIPT = ` + var __10_4_2_1_3 = "str"; + function testcase() { + var __10_4_2_1_3 = "str1"; + try { + throw "error"; + } catch (e) { + var __10_4_2_1_3 = "str2"; + return eval("__10_4_2_1_3"); + } + } + testcase(); + ` + + testScript(SCRIPT, asciiString("str2"), t) +} + +func TestEvalDirectScope1(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var __10_4_2_1_5 = "str"; + function testcase() { + var __10_4_2_1_5 = "str1"; + var r = eval("\ + var __10_4_2_1_5 = \'str2\'; \ + eval(\"\'str2\' === __10_4_2_1_5\")\ + "); + return r; + } + testcase(); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalDirectCreateBinding(t *testing.T) { + const SCRIPT = ` + function f() { + eval("var x = true"); + return x; + } + var res = f(); + var thrown = false; + try { + x; + } catch(e) { + if (e instanceof ReferenceError) { + thrown = true; + } else { + throw e; + } + } + res && thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalDirectCreateBinding1(t *testing.T) { + const SCRIPT = ` + function f() { + eval("let x = 1; var y = 2; function f1() {return x};"); + assert.throws(ReferenceError, function() { x }); + return ""+y+f1(); + } + f(); + ` + + testScriptWithTestLib(SCRIPT, asciiString("21"), t) +} + +func TestEvalDirectCreateBinding3(t *testing.T) { + const SCRIPT = ` + function f() { + let x; + try { + eval("var y=1, x=2"); + } catch(e) {} + return y; + } + assert.throws(ReferenceError, f); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestEvalGlobalStrict(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var evalStr = + 'for (var x in this) {\n'+ + ' if ( x === \'Math\' ) {\n'+ + ' }\n'+ + '}\n'; + + eval(evalStr); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestEvalEmptyStrict(t *testing.T) { + const SCRIPT = ` + 'use strict'; + eval(""); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestEvalFuncDecl(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var funcA = eval("function __funcA(__arg){return __arg;}; __funcA"); + typeof funcA; + ` + + testScript(SCRIPT, asciiString("function"), t) +} + +func TestGetAfterSet(t *testing.T) { + const SCRIPT = ` + function f() { + var x = 1; + return x; + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestForLoopRet(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 20; i++) { if (i > 2) {break;} else { i }} + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestForLoopRet1(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 20; i++) { if (i > 2) {42;; {L:{break;}}} else { i }} + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestForInLoopRet(t *testing.T) { + const SCRIPT = ` + var o = [1, 2, 3, 4]; + for (var i in o) { if (i > 2) {break;} else { i }} + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestForInLoopRet1(t *testing.T) { + const SCRIPT = ` + var o = {}; + o.x = 1; + o.y = 2; + for (var i in o) { + true; + } + + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestDoWhileLoopRet(t *testing.T) { + const SCRIPT = ` + var i = 0; + do { + if (i > 2) { + break; + } else { + i; + } + } while (i++ < 20); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestDoWhileContinueRet(t *testing.T) { + const SCRIPT = ` + var i = 0; + do { + if (i > 2) { + true; + continue; + } else { + i; + } + } while (i++ < 20); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestWhileLoopRet(t *testing.T) { + const SCRIPT = ` + var i; while (i < 20) { if (i > 2) {break;} else { i++ }} + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestLoopRet1(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 20; i++) { } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestInstanceof(t *testing.T) { + const SCRIPT = ` + var rv; + try { + true(); + } catch (e) { + rv = e instanceof TypeError; + } + rv; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestStrictAssign(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var rv; + var called = false; + function F() { + called = true; + return 1; + } + try { + x = F(); + } catch (e) { + rv = e instanceof ReferenceError; + } + rv + " " + called; + ` + + testScript(SCRIPT, asciiString("true true"), t) +} + +func TestStrictScope(t *testing.T) { + const SCRIPT = ` + var rv; + var called = false; + function F() { + 'use strict'; + x = 1; + } + try { + F(); + } catch (e) { + rv = e instanceof ReferenceError; + } + x = 1; + rv + " " + x; + ` + + testScript(SCRIPT, asciiString("true 1"), t) +} + +func TestStringObj(t *testing.T) { + const SCRIPT = ` + var s = new String("test"); + s[0] + s[2] + s[1]; + ` + + testScript(SCRIPT, asciiString("tse"), t) +} + +func TestStringPrimitive(t *testing.T) { + const SCRIPT = ` + var s = "test"; + s[0] + s[2] + s[1]; + ` + + testScript(SCRIPT, asciiString("tse"), t) +} + +func TestCallGlobalObject(t *testing.T) { + const SCRIPT = ` + var rv; + try { + this(); + } catch (e) { + rv = e instanceof TypeError + } + rv; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestFuncLength(t *testing.T) { + const SCRIPT = ` + function F(x, y) { + + } + F.length + ` + + testScript(SCRIPT, intToValue(2), t) +} + +func TestNativeFuncLength(t *testing.T) { + const SCRIPT = ` + eval.length + Object.defineProperty.length + String.length + ` + + testScript(SCRIPT, intToValue(5), t) +} + +func TestArguments(t *testing.T) { + const SCRIPT = ` + function F() { + return arguments.length + " " + arguments[1]; + } + + F(1,2,3) + ` + + testScript(SCRIPT, asciiString("3 2"), t) +} + +func TestArgumentsPut(t *testing.T) { + const SCRIPT = ` + function F(x, y) { + arguments[0] -= arguments[1]; + return x; + } + + F(5, 2) + ` + + testScript(SCRIPT, intToValue(3), t) +} + +func TestArgumentsPutStrict(t *testing.T) { + const SCRIPT = ` + function F(x, y) { + 'use strict'; + arguments[0] -= arguments[1]; + return x; + } + + F(5, 2) + ` + + testScript(SCRIPT, intToValue(5), t) +} + +func TestArgumentsExtra(t *testing.T) { + const SCRIPT = ` + function F(x, y) { + return arguments[2]; + } + + F(1, 2, 42) + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestArgumentsExist(t *testing.T) { + const SCRIPT = ` + function F(x, arguments) { + return arguments; + } + + F(1, 42) + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestArgumentsDelete(t *testing.T) { + const SCRIPT = ` + function f(x) { + delete arguments[0]; + arguments[0] = 42; + return x; + } + f(1) + ` + + testScript(SCRIPT, intToValue(1), t) +} + +func TestArgumentsInEval(t *testing.T) { + const SCRIPT = ` + function f() { + return eval("arguments"); + } + f(1)[0]; + ` + + testScript(SCRIPT, intToValue(1), t) +} + +func TestArgumentsRedeclareInEval(t *testing.T) { + const SCRIPT = ` + assert.sameValue("arguments" in this, false, "No global 'arguments' binding"); + + function f(p = eval("var arguments = 'param'"), arguments) {} + assert.throws(SyntaxError, f); + + assert.sameValue("arguments" in this, false, "No global 'arguments' binding"); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestArgumentsRedeclareArrow(t *testing.T) { + const SCRIPT = ` + const oldArguments = globalThis.arguments; + let count = 0; + const f = (p = eval("var arguments = 'param'"), q = () => arguments) => { + var arguments = "local"; + assert.sameValue(arguments, "local", "arguments"); + assert.sameValue(q(), "param", "q"); + count++; + } + f(); + assert.sameValue(count, 1); + assert.sameValue(globalThis.arguments, oldArguments, "globalThis.arguments unchanged"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestEvalParamWithDef(t *testing.T) { + const SCRIPT = ` + function f(param = 0) { + eval("var param = 1"); + return param; + } + f(); + ` + + testScript(SCRIPT, valueInt(1), t) +} + +func TestArgumentsRedefinedAsLetDyn(t *testing.T) { + const SCRIPT = ` + function f() { + let arguments; + eval(""); // force dynamic scope + return arguments; + } + + f(1,2); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestWith(t *testing.T) { + const SCRIPT = ` + var b = 1; + var o = {a: 41}; + with(o) { + a += b; + } + o.a; + + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestWithInFunc(t *testing.T) { + const SCRIPT = ` + function F() { + var b = 1; + var c = 0; + var o = {a: 40, c: 1}; + with(o) { + a += b + c; + } + return o.a; + } + + F(); + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestAssignNonExtendable(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + function F() { + this.x = 1; + } + + var o = new F(); + Object.preventExtensions(o); + o.x = 42; + o.x; + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestAssignNonExtendable1(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + function F() { + } + + var o = new F(); + var rv; + + Object.preventExtensions(o); + try { + o.x = 42; + } catch (e) { + rv = e.constructor === TypeError; + } + + rv += " " + o.x; + rv; + ` + + testScript(SCRIPT, asciiString("true undefined"), t) +} + +func TestAssignStrict(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + try { + eval("eval = 42"); + } catch(e) { + var rv = e instanceof SyntaxError + } + rv; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestIllegalArgmentName(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + try { + eval("function F(eval) {}"); + } catch (e) { + var rv = e instanceof SyntaxError + } + rv; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestFunction(t *testing.T) { + const SCRIPT = ` + + var f0 = Function(""); + var f1 = Function("return ' one'"); + var f2 = Function("arg", "return ' ' + arg"); + f0() + f1() + f2("two"); + ` + + testScript(SCRIPT, asciiString("undefined one two"), t) +} + +func TestFunction1(t *testing.T) { + const SCRIPT = ` + + var f = function f1(count) { + if (count == 0) { + return true; + } + return f1(count-1); + } + + f(1); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestFunction2(t *testing.T) { + const SCRIPT = ` + var trace = ""; + function f(count) { + trace += "f("+count+")"; + if (count == 0) { + return; + } + return f(count-1); + } + + function f1() { + trace += "f1"; + } + + var f2 = f; + f = f1; + f2(1); + trace; + + ` + + testScript(SCRIPT, asciiString("f(1)f1"), t) +} + +func TestFunctionToString(t *testing.T) { + const SCRIPT = ` + + Function("arg1", "arg2", "return 42").toString(); + ` + + testScript(SCRIPT, asciiString("function anonymous(arg1,arg2\n) {\nreturn 42\n}"), t) +} + +func TestObjectLiteral(t *testing.T) { + const SCRIPT = ` + var getterCalled = false; + var setterCalled = false; + + var o = {get x() {getterCalled = true}, set x(_) {setterCalled = true}}; + + o.x; + o.x = 42; + + getterCalled && setterCalled; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestConst(t *testing.T) { + const SCRIPT = ` + + var v1 = true && true; + var v2 = 1/(-1 * 0); + var v3 = 1 == 2 || v1; + var v4 = true && false + v1 === true && v2 === -Infinity && v3 === v1 && v4 === false; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestConstWhile(t *testing.T) { + const SCRIPT = ` + var c = 0; + while (2 + 2 === 4) { + if (++c > 9) { + break; + } + } + c === 10; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestConstWhileThrow(t *testing.T) { + const SCRIPT = ` + var thrown = false; + try { + while ('s' in true) { + break; + } + } catch (e) { + thrown = e instanceof TypeError + } + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestDupParams(t *testing.T) { + const SCRIPT = ` + function F(x, y, x) { + return x; + } + + F(1, 2); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestUseUnsuppliedParam(t *testing.T) { + const SCRIPT = ` + function getMessage(message) { + if (message === undefined) { + message = ''; + } + message += " 123 456"; + return message; + } + + getMessage(); + ` + + testScript(SCRIPT, asciiString(" 123 456"), t) +} + +func TestForInLetWithInitializer(t *testing.T) { + const SCRIPT = `for (let x = 3 in {}) { }` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func TestForInLoop(t *testing.T) { + const SCRIPT = ` + function Proto() {} + Proto.prototype.x = 42; + var o = new Proto(); + o.y = 44; + o.x = 45; + var hasX = false; + var hasY = false; + + for (var i in o) { + switch(i) { + case "x": + if (hasX) { + throw new Error("Already has X"); + } + hasX = true; + break; + case "y": + if (hasY) { + throw new Error("Already has Y"); + } + hasY = true; + break; + } + } + + hasX && hasY; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestWhileLoopResult(t *testing.T) { + const SCRIPT = ` + while(false); + + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestEmptySwitch(t *testing.T) { + const SCRIPT = ` + switch(1){} + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestEmptyDoWhile(t *testing.T) { + const SCRIPT = ` + do {} while(false) + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSwitch(t *testing.T) { + const SCRIPT = ` + function F(x) { + var i = 0; + switch (x) { + case 0: + i++; + case 1: + i++; + default: + i++; + case 2: + i++; + break; + case 3: + i++; + } + return i; + } + + F(0) + F(1) + F(2) + F(4); + + ` + + testScript(SCRIPT, intToValue(10), t) +} + +func TestSwitchDefFirst(t *testing.T) { + const SCRIPT = ` + function F(x) { + var i = 0; + switch (x) { + default: + i++; + case 0: + i++; + case 1: + i++; + case 2: + i++; + break; + case 3: + i++; + } + return i; + } + + F(0) + F(1) + F(2) + F(4); + + ` + + testScript(SCRIPT, intToValue(10), t) +} + +func TestSwitchResult(t *testing.T) { + const SCRIPT = ` + var x = 2; + + switch (x) { + case 0: + "zero"; + case 1: + "one"; + case 2: + "two"; + break; + case 3: + "three"; + default: + "default"; + } + ` + + testScript(SCRIPT, asciiString("two"), t) +} + +func TestSwitchResult1(t *testing.T) { + const SCRIPT = ` + var x = 0; + switch (x) { case 0: "two"; case 1: break} + ` + + testScript(SCRIPT, asciiString("two"), t) +} + +func TestSwitchResult2(t *testing.T) { + const SCRIPT = ` + 6; switch ("a") { case "a": 7; case "b": } + ` + + testScript(SCRIPT, valueInt(7), t) +} + +func TestSwitchResultJumpIntoEmptyEval(t *testing.T) { + const SCRIPT = ` + function t(x) { + return eval("switch(x) { case 1: 2; break; case 2: let x = 1; case 3: x+2; break; case 4: default: 9}"); + } + ""+t(2)+t(); + ` + + testScript(SCRIPT, asciiString("39"), t) +} + +func TestSwitchResultJumpIntoEmpty(t *testing.T) { + const SCRIPT = ` + switch(2) { case 1: 2; break; case 2: let x = 1; case 3: x+2; case 4: {let y = 2}; break; default: 9}; + ` + + testScript(SCRIPT, valueInt(3), t) +} + +func TestSwitchLexical(t *testing.T) { + const SCRIPT = ` + switch (true) { case true: let x = 1; } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSwitchBreakOuter(t *testing.T) { + const SCRIPT = ` + LOOP: + for (let i = 0; i < 10; i++) { + switch (i) { + case 0: + continue; + case 1: + let x = 1; + continue; + case 2: + try { + x++; + } catch (e) { + if (e instanceof ReferenceError) { + break LOOP; + } + } + throw new Error("Exception was not thrown"); + } + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestIfBreakResult(t *testing.T) { + const SCRIPT = ` + L: {if (true) {42;} break L;} + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestSwitchNoMatch(t *testing.T) { + const SCRIPT = ` + var result; + var x; + switch (x) { + case 0: + result = "2"; + break; + } + + result; + + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSwitchNoMatchNoDefault(t *testing.T) { + const SCRIPT = ` + switch (1) { + case 0: + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSwitchNoMatchNoDefaultNoResult(t *testing.T) { + const SCRIPT = ` + switch (1) { + case 0: + } + 42; + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestSwitchNoMatchNoDefaultNoResultMatch(t *testing.T) { + const SCRIPT = ` + switch (1) { + case 1: + } + 42; + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestEmptySwitchNoResult(t *testing.T) { + const SCRIPT = ` + switch (1) {} + 42; + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestGetOwnPropertyNames(t *testing.T) { + const SCRIPT = ` + var o = { + prop1: 42, + prop2: "test" + } + + var hasProp1 = false; + var hasProp2 = false; + + var names = Object.getOwnPropertyNames(o); + for (var i in names) { + var p = names[i]; + switch(p) { + case "prop1": + hasProp1 = true; + break; + case "prop2": + hasProp2 = true; + break; + } + } + + hasProp1 && hasProp2; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayLiteral(t *testing.T) { + const SCRIPT = ` + + var f1Called = false; + var f2Called = false; + var f3Called = false; + var errorThrown = false; + + function F1() { + f1Called = true; + } + + function F2() { + f2Called = true; + } + + function F3() { + f3Called = true; + } + + + try { + var a = [F1(), x(F3()), F2()]; + } catch(e) { + if (e instanceof ReferenceError) { + errorThrown = true; + } else { + throw e; + } + } + + f1Called && !f2Called && f3Called && errorThrown && a === undefined; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestJumpOutOfReturn(t *testing.T) { + const SCRIPT = ` + function f() { + var a; + if (a == 0) { + return true; + } + } + + f(); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSwitchJumpOutOfReturn(t *testing.T) { + const SCRIPT = ` + function f(x) { + switch(x) { + case 0: + break; + default: + return x; + } + } + + f(0); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSetToReadOnlyPropertyStrictBracket(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + var o = {}; + var thrown = false; + Object.defineProperty(o, "test", {value: 42, configurable: true}); + try { + o["test"] = 43; + } catch (e) { + thrown = e instanceof TypeError; + } + + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestSetToReadOnlyPropertyStrictDot(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + var o = {}; + var thrown = false; + Object.defineProperty(o, "test", {value: 42, configurable: true}); + try { + o.test = 43; + } catch (e) { + thrown = e instanceof TypeError; + } + + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestDeleteNonConfigurablePropertyStrictBracket(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + var o = {}; + var thrown = false; + Object.defineProperty(o, "test", {value: 42}); + try { + delete o["test"]; + } catch (e) { + thrown = e instanceof TypeError; + } + + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestDeleteNonConfigurablePropertyStrictDot(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + var o = {}; + var thrown = false; + Object.defineProperty(o, "test", {value: 42}); + try { + delete o.test; + } catch (e) { + thrown = e instanceof TypeError; + } + + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestCompound1(t *testing.T) { + const SCRIPT = ` + var x = 0; + var scope = {x: 1}; + var f; + with (scope) { + f = function() { + x *= (delete scope.x, 2); + } + } + f(); + + scope.x === 2 && x === 0; + + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestCompound2(t *testing.T) { + const SCRIPT = ` + +var x; +x = "x"; +x ^= "1"; + + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestDeleteArguments(t *testing.T) { + defer func() { + if _, ok := recover().(*CompilerSyntaxError); !ok { + t.Fatal("Expected syntax error") + } + }() + const SCRIPT = ` + 'use strict'; + + function f() { + delete arguments; + } + + ` + testScript(SCRIPT, _undefined, t) +} + +func TestReturnUndefined(t *testing.T) { + const SCRIPT = ` + function f() { + return x; + } + + var thrown = false; + try { + f(); + } catch (e) { + thrown = e instanceof ReferenceError; + } + + thrown; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestForBreak(t *testing.T) { + const SCRIPT = ` + var supreme, count; + supreme = 5; + var __evaluated = eval("for(count=0;;) {if (count===supreme)break;else count++; }"); + if (__evaluated !== void 0) { + throw new Error('#1: __evaluated === 4. Actual: __evaluated ==='+ __evaluated ); + } + + ` + testScript(SCRIPT, _undefined, t) +} + +func TestLargeNumberLiteral(t *testing.T) { + const SCRIPT = ` + var x = 0x800000000000000000000; + x.toString(); + ` + testScript(SCRIPT, asciiString("9.671406556917033e+24"), t) +} + +func TestIncDelete(t *testing.T) { + const SCRIPT = ` + var o = {x: 1}; + o.x += (delete o.x, 1); + o.x; + ` + testScript(SCRIPT, intToValue(2), t) +} + +func TestCompoundAssignRefError(t *testing.T) { + const SCRIPT = ` + var thrown = false; + try { + a *= 1; + } catch (e) { + if (e instanceof ReferenceError) { + thrown = true; + } else { + throw e; + } + } + thrown; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestObjectLiteral__Proto__(t *testing.T) { + const SCRIPT = ` + var o = { + __proto__: null, + test: 42 + } + + Object.getPrototypeOf(o); + ` + + testScript(SCRIPT, _null, t) +} + +func TestEmptyCodeError(t *testing.T) { + if _, err := New().RunString(`i`); err == nil { + t.Fatal("Expected an error") + } else { + if e := err.Error(); e != "ReferenceError: i is not defined at :1:1(0)" { + t.Fatalf("Unexpected error: '%s'", e) + } + } +} + +func TestForOfArray(t *testing.T) { + const SCRIPT = ` + var array = [0, 'a', true, false, null, /* hole */, undefined, NaN]; + var i = 0; + + for (var value of array) { + assert.sameValue(value, array[i], 'element at index ' + i); + i++; + } + + assert.sameValue(i, 8, 'Visits all elements'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestForOfReturn(t *testing.T) { + const SCRIPT = ` + var callCount = 0; + var iterationCount = 0; + var iterable = {}; + var x = { + set attr(_) { + throw new Test262Error(); + } + }; + + iterable[Symbol.iterator] = function() { + return { + next: function() { + return { done: false, value: 0 }; + }, + return: function() { + callCount += 1; + } + } + }; + + assert.throws(Test262Error, function() { + for (x.attr of iterable) { + iterationCount += 1; + } + }); + + assert.sameValue(iterationCount, 0, 'The loop body is not evaluated'); + assert.sameValue(callCount, 1, 'Iterator is closed'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestForOfReturn1(t *testing.T) { + const SCRIPT = ` + var iterable = {}; + var iterationCount = 0; + + iterable[Symbol.iterator] = function() { + return { + next: function() { + return { done: false, value: null }; + }, + get return() { + throw new Test262Error(); + } + }; + }; + + assert.throws(Test262Error, function() { + for (var x of iterable) { + iterationCount += 1; + break; + } + }); + + assert.sameValue(iterationCount, 1, 'The loop body is evaluated'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestForOfLet(t *testing.T) { + const SCRIPT = ` + var iterCount = 0; + function f() {} + for (var let of [23]) { + f(let); + if (let != 23) { + throw new Error(""); + } + iterCount += 1; + } + + iterCount; +` + testScript(SCRIPT, valueInt(1), t) +} + +func TestForOfLetLet(t *testing.T) { + const SCRIPT = ` + for (let let of [23]) { + } +` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func TestForHeadLet(t *testing.T) { + const SCRIPT = ` + for (let = 0; let < 2; let++); +` + testScript(SCRIPT, _undefined, t) +} + +func TestLhsLet(t *testing.T) { + const SCRIPT = ` + let = 1; + let; + ` + testScript(SCRIPT, valueInt(1), t) +} + +func TestLetPostfixASI(t *testing.T) { + const SCRIPT = ` + let + ++ + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func TestIteratorReturnNormal(t *testing.T) { + const SCRIPT = ` + var iterable = {}; + var iterationCount = 0; + + iterable[Symbol.iterator] = function() { + return { + next: function() { + return { done: ++iterationCount > 2, value: null }; + }, + get return() { + throw new Test262Error(); + } + }; + }; + + for (var x of iterable) { + } + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestIteratorReturnErrorNested(t *testing.T) { + const SCRIPT = ` + var returnCalled = {}; + function iter(id) { + return function() { + var count = 0; + return { + next: function () { + return { + value: null, + done: ++count > 2 + }; + }, + return: function () { + returnCalled[id] = true; + throw new Error(id); + } + }; + } + } + var iterable1 = {}; + iterable1[Symbol.iterator] = iter("1"); + var iterable2 = {}; + iterable2[Symbol.iterator] = iter("2"); + + try { + for (var i of iterable1) { + for (var j of iterable2) { + break; + } + } + throw new Error("no exception was thrown"); + } catch (e) { + if (e.message !== "2") { + throw e; + } + } + if (!returnCalled["1"]) { + throw new Error("no return 1"); + } + if (!returnCalled["2"]) { + throw new Error("no return 2"); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestReturnFromForInLoop(t *testing.T) { + const SCRIPT = ` + (function f() { + for (var i in {a: 1}) { + return true; + } + })(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestReturnFromForOfLoop(t *testing.T) { + const SCRIPT = ` + (function f() { + for (var i of [1]) { + return true; + } + })(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestIfStackLeaks(t *testing.T) { + const SCRIPT = ` + var t = 0; + if (t === 0) { + t; + } + ` + testScript(SCRIPT, _positiveZero, t) +} + +func TestWithCallee(t *testing.T) { + const SCRIPT = ` + function O() { + var that = this; + this.m = function() { + return this === that; + } + } + with(new O()) { + m(); + } + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestWithScope(t *testing.T) { + const SCRIPT = ` + function f(o) { + var x = 42; + + function innerf(o) { + with (o) { + return x; + } + } + + return innerf(o); + } + f({}); + ` + testScript(SCRIPT, valueInt(42), t) +} + +func TestEvalCallee(t *testing.T) { + const SCRIPT = ` + (function () { + 'use strict'; + var v = function() { + return this === undefined; + }; + return eval('v()'); + })(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalBindingDeleteVar(t *testing.T) { + const SCRIPT = ` + (function () { + eval("var x = 1"); + return x === 1 && delete x; + })(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEvalBindingDeleteFunc(t *testing.T) { + const SCRIPT = ` + (function () { + eval("function x(){}"); + return typeof x === "function" && delete x; + })(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestDeleteGlobalLexical(t *testing.T) { + const SCRIPT = ` + let x; + delete x; + ` + testScript(SCRIPT, valueFalse, t) +} + +func TestDeleteGlobalEval(t *testing.T) { + const SCRIPT = ` + eval("var x"); + delete x; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestGlobalVarNames(t *testing.T) { + vm := New() + _, err := vm.RunString("(0,eval)('var x')") + if err != nil { + t.Fatal(err) + } + _, err = vm.RunString("let x") + if err == nil { + t.Fatal("Expected error") + } +} + +func TestTryResultEmpty(t *testing.T) { + const SCRIPT = ` + 1; try { } finally { } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryResultEmptyCatch(t *testing.T) { + const SCRIPT = ` + 1; try { throw null } catch(e) { } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryResultEmptyContinueLoop(t *testing.T) { + const SCRIPT = ` + for (var i = 0; i < 2; i++) { try {throw null;} catch(e) {continue;} 'bad'} + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryEmptyCatchStackLeak(t *testing.T) { + const SCRIPT = ` + (function() { + var f; + // Make sure the outer function is not stashless. + (function() { + f++; + })(); + try { + throw new Error(); + } catch(e) {} + })(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestTryThrowEmptyCatch(t *testing.T) { + const SCRIPT = ` + try { + throw new Error(); + } + catch (e) {} + ` + testScript(SCRIPT, _undefined, t) +} + +func TestFalsyLoopBreak(t *testing.T) { + const SCRIPT = ` + while(false) { + break; + } + for(;false;) { + break; + } + undefined; + ` + MustCompile("", SCRIPT, false) +} + +func TestFalsyLoopBreakWithResult(t *testing.T) { + const SCRIPT = ` + while(false) { + break; + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestDummyCompile(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + for (;false;) { + eval = 1; + } + ` + + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestDummyCompileForUpdate(t *testing.T) { + const SCRIPT = ` + 'use strict'; + + for (;false;eval=1) { + } + ` + + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestObjectLiteralWithNumericKeys(t *testing.T) { + const SCRIPT = ` + var o = {1e3: true}; + var keys = Object.keys(o); + var o1 = {get 1e3() {return true;}}; + var keys1 = Object.keys(o1); + var o2 = {1e21: true}; + var keys2 = Object.keys(o2); + let o3 = {0(){return true}}; + keys.length === 1 && keys[0] === "1000" && + keys1.length === 1 && keys1[0] === "1000" && o1[1e3] === true && + keys2.length === 1 && keys2[0] === "1e+21" && o3[0](); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEscapedObjectPropertyKeys(t *testing.T) { + const SCRIPT = ` + var obj = { + w\u0069th: 42 + }; + var obj = { + with() {42} + }; + ` + + _, err := Compile("", SCRIPT, false) + if err != nil { + t.Fatal(err) + } +} + +func TestEscapedKeywords(t *testing.T) { + const SCRIPT = `r\u0065turn;` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func TestEscapedLet(t *testing.T) { + const SCRIPT = ` +this.let = 0; + +l\u0065t // ASI +a; + +// If the parser treated the previous escaped "let" as a lexical declaration, +// this variable declaration will result an early syntax error. +var a; +` + _, err := Compile("", SCRIPT, false) + if err != nil { + t.Fatal(err) + } +} + +func TestObjectLiteralFuncProps(t *testing.T) { + const SCRIPT = ` + (function() { + 'use strict'; + var o = { + eval: function() {return 1;}, + arguments() {return 2;}, + test: function test1() {} + } + assert.sameValue(o.eval.name, "eval"); + assert.sameValue(o.arguments.name, "arguments"); + assert.sameValue(o.eval(), 1); + assert.sameValue(o.arguments(), 2); + assert.sameValue(o.test.name, "test1"); + })(); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestFuncName(t *testing.T) { + const SCRIPT = ` + var method = 1; + var o = { + method: function() { + return method; + }, + method1: function method() { + return method; + } + } + o.method() === 1 && o.method1() === o.method1; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestFuncNameAssign(t *testing.T) { + const SCRIPT = ` + var f = function() {}; + var f1; + f1 = function() {}; + let f2 = function() {}; + + f.name === "f" && f1.name === "f1" && f2.name === "f2"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestLexicalDeclGlobal(t *testing.T) { + const SCRIPT = ` + if (true) { + let it = "be"; + if (it !== "be") { + throw new Error(it); + } + } + let thrown = false; + try { + it; + } catch(e) { + if (e instanceof ReferenceError) { + thrown = true; + } + } + thrown; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestLexicalDeclFunction(t *testing.T) { + const SCRIPT = ` + function f() { + if (true) { + let it = "be"; + if (it !== "be") { + throw new Error(it); + } + } + let thrown = false; + try { + it; + } catch(e) { + if (e instanceof ReferenceError) { + thrown = true; + } + } + return thrown; + } + f(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestLexicalDynamicScope(t *testing.T) { + const SCRIPT = ` + const global = 1; + function f() { + const func = global + 1; + function inner() { + function assertThrows(fn) { + let thrown = false; + try { + fn(); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + if (!thrown) { + throw new Error("Did not throw"); + } + } + + assertThrows(function() { + func++; + }); + assertThrows(function() { + global++; + }); + + assertThrows(function() { + eval("func++"); + }); + assertThrows(function() { + eval("global++"); + }); + + return eval("func + 1"); + } + return inner(); + } + f(); + ` + testScript(SCRIPT, valueInt(3), t) +} + +func TestLexicalDynamicScope1(t *testing.T) { + const SCRIPT = ` + (function() { + const x = 1 * 4; + return (function() { + eval(""); + return x; + })(); + })(); + ` + testScript(SCRIPT, intToValue(4), t) +} + +func TestLexicalDynamicScope2(t *testing.T) { + const SCRIPT = ` + (function() { + const x = 1 + 3; + var y = 2 * 2; + eval(""); + return x; + })(); + ` + testScript(SCRIPT, intToValue(4), t) +} + +func TestNonStrictLet(t *testing.T) { + const SCRIPT = ` + var let = 1; + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestStrictLet(t *testing.T) { + const SCRIPT = ` + var let = 1; + ` + + _, err := Compile("", SCRIPT, true) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestLetLet(t *testing.T) { + const SCRIPT = ` + let let = 1; + ` + + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestLetASI(t *testing.T) { + const SCRIPT = ` + while (false) let // ASI + x = 1; + ` + + _, err := Compile("", SCRIPT, false) + if err != nil { + t.Fatal(err) + } +} + +func TestLetASI1(t *testing.T) { + const SCRIPT = ` + let + x = 1; + ` + + _, err := Compile("", SCRIPT, true) + if err != nil { + t.Fatal(err) + } +} + +func TestLetNoASI(t *testing.T) { + const SCRIPT = ` + function f() {}let +x = 1; + ` + + _, err := Compile("", SCRIPT, true) + if err != nil { + t.Fatal(err) + } +} + +func TestLetNoASI1(t *testing.T) { + const SCRIPT = ` +let +let = 1; + ` + + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func TestLetArrayWithNewline(t *testing.T) { + const SCRIPT = ` + with ({}) let + [a] = 0; + ` + + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func TestDynamicUninitedVarAccess(t *testing.T) { + const SCRIPT = ` + function f() { + var x; + return eval("x"); + } + f(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestLexicalForLoopNoClosure(t *testing.T) { + const SCRIPT = ` + let sum = 0; + for (let i = 0; i < 3; i++) { + sum += i; + } + sum; + ` + testScript(SCRIPT, valueInt(3), t) +} + +func TestLexicalForLoopClosure(t *testing.T) { + const SCRIPT = ` + var f = []; + for (let i = 0; i < 3; i++) { + f.push(function() { + return i; + }); + } + f.length === 3 && f[0]() === 0 && f[1]() === 1 && f[2]() === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestLexicalForLoopClosureInNext(t *testing.T) { + const SCRIPT = ` + const a = []; + for (let i = 0; i < 5; a.push(function () { return i; }), ++i) { } + let res = ""; + for (let k = 0; k < 5; ++k) { + res += ""+a[k](); + } + res; + ` + testScript(SCRIPT, asciiString("12345"), t) +} + +func TestVarForLoop(t *testing.T) { + const SCRIPT = ` + var f = []; + for (var i = 0, j = 0; i < 3; i++) { + f.push(function() { + return i; + }); + } + f.length === 3 && f[0]() === 3 && f[1]() === 3 && f[2]() === 3; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestLexicalForOfLoop(t *testing.T) { + const SCRIPT = ` + var f = []; + for (let i of [0, 1, 2]) { + f.push(function() { + return i; + }); + } + f.length === 3 && f[0]() === 0 && f[1]() === 1 && f[2]() === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestLexicalForOfLoopContBreak(t *testing.T) { + const SCRIPT = ` + const f = []; + for (let i of [0, 1, 2, 3, 4, 5]) { + if (i % 2) continue; + f.push(function() { + return i; + }); + if (i > 2) break; + } + let res = ""; + f.forEach(function(item) {res += item()}); + f.length === 3 && res === "024"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestVarBlockConflict(t *testing.T) { + const SCRIPT = ` + let x; + { + if (false) { + var x; + } + } + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestVarBlockConflictEval(t *testing.T) { + const SCRIPT = ` + assert.throws(SyntaxError, function() { + let x; + { + if (true) { + eval("var x"); + } + } + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestVarBlockNoConflict(t *testing.T) { + const SCRIPT = ` + function f() { + let x; + function ff() { + { + var x = 3; + } + } + ff(); + } + f(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestVarBlockNoConflictEval(t *testing.T) { + const SCRIPT = ` + function f() { + let x; + function ff() { + { + eval("var x = 3"); + } + } + ff(); + } + f(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestVarDeclCorrectScope(t *testing.T) { + const SCRIPT = ` + function f() { + { + let z; + eval("var x = 3"); + } + return x; + } + f(); + ` + testScript(SCRIPT, valueInt(3), t) +} + +func TestLexicalCatch(t *testing.T) { + const SCRIPT = ` + try { + throw null; + } catch (e) { + let x = 1; + function f() {} + e; + } + ` + testScript(SCRIPT, _null, t) +} + +func TestArgumentsLexicalDecl(t *testing.T) { + const SCRIPT = ` + function f1() { + let arguments; + return arguments; + } + f1(42); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestArgumentsLexicalDeclAssign(t *testing.T) { + const SCRIPT = ` + function f1() { + let arguments = arguments; + return a; + } + assert.throws(ReferenceError, function() { + f1(42); + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestLexicalConstModifyFromEval(t *testing.T) { + const SCRIPT = ` + const x = 1; + function f() { + eval("x = 2"); + } + assert.throws(TypeError, function() { + f(); + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestLexicalStrictNames(t *testing.T) { + const SCRIPT = `let eval = 1;` + + _, err := Compile("", SCRIPT, true) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestAssignAfterStackExpand(t *testing.T) { + // make sure the reference to the variable x does not remain stale after the stack is copied + const SCRIPT = ` + function f() { + let sum = 0; + for (let i = 0; i < arguments.length; i++) { + sum += arguments[i]; + } + return sum; + } + function testAssignment() { + var x = 0; + var scope = {}; + + with (scope) { + x = (scope.x = f(0, 0, 0, 0, 0, 0, 1, 1), 1); + } + + if (scope.x !== 2) { + throw new Error('#1: scope.x === 2. Actual: ' + (scope.x)); + } + if (x !== 1) { + throw new Error('#2: x === 1. Actual: ' + (x)); + } + } + testAssignment(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestArgAccessFromDynamicStash(t *testing.T) { + const SCRIPT = ` + function f(arg) { + function test() { + eval(""); + return a; + } + return arg; + } + f(true); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestLoadMixedLex(t *testing.T) { + const SCRIPT = ` + function f() { + let a = 1; + { + function inner() { + eval("var a = true"); + return a; + } + return inner(); + } + } + f(); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestObjectLiteralSpread(t *testing.T) { + const SCRIPT = ` + let src = {prop1: 1}; + Object.defineProperty(src, "prop2", {value: 2, configurable: true}); + Object.defineProperty(src, "prop3", {value: 3, enumerable: true, configurable: true}); + let target = {prop4: 4, ...src}; + assert(deepEqual(target, {prop1: 1, prop3: 3, prop4: 4})); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestArrayLiteralSpread(t *testing.T) { + const SCRIPT = ` + let a1 = [1, 2]; + let a2 = [3, 4]; + let a = [...a1, 0, ...a2, 1]; + assert(compareArray(a, [1, 2, 0, 3, 4, 1])); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjectAssignmentPattern(t *testing.T) { + const SCRIPT = ` + let a, b, c; + ({a, b, c=3} = {a: 1, b: 2}); + assert.sameValue(a, 1, "a"); + assert.sameValue(b, 2, "b"); + assert.sameValue(c, 3, "c"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjectAssignmentPatternNoDyn(t *testing.T) { + const SCRIPT = ` + (function() { + let a, b, c; + ({a, b, c=3} = {a: 1, b: 2}); + assert.sameValue(a, 1, "a"); + assert.sameValue(b, 2, "b"); + assert.sameValue(c, 3, "c"); + })(); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjectAssignmentPatternNested(t *testing.T) { + const SCRIPT = ` + let a, b, c, d; + ({a, b, c: {d} = 3} = {a: 1, b: 2, c: {d: 4}}); + assert.sameValue(a, 1, "a"); + assert.sameValue(b, 2, "b"); + assert.sameValue(c, undefined, "c"); + assert.sameValue(d, 4, "d"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjectAssignmentPatternEvalOrder(t *testing.T) { + const SCRIPT = ` + let trace = ""; + let target_obj = {}; + + function src() { + trace += "src(),"; + return { + get a() { + trace += "get a,"; + return "a"; + } + } + } + + function prop1() { + trace += "prop1()," + return { + toString: function() { + trace += "prop1-to-string(),"; + return "a"; + } + } + } + + function prop2() { + trace += "prop2(),"; + return { + toString: function() { + trace += "prop2-to-string(),"; + return "b"; + } + } + } + + function target() { + trace += "target()," + return target_obj; + } + + let a, b; + + ({[prop1()]: target().a, [prop2()]: b} = src()); + if (target_obj.a !== "a") { + throw new Error("target_obj.a="+target_obj.a); + } + trace; + ` + testScript(SCRIPT, asciiString("src(),prop1(),prop1-to-string(),target(),get a,prop2(),prop2-to-string(),"), t) +} + +func TestArrayAssignmentPatternEvalOrder(t *testing.T) { + const SCRIPT = ` + let trace = ""; + + let src_arr = { + [Symbol.iterator]: function() { + let done = false; + return { + next: function() { + trace += "next,"; + if (!done) { + done = true; + return {value: 0}; + } + return {done: true}; + }, + return: function() { + trace += "return,"; + } + } + } + } + + function src() { + trace += "src(),"; + return src_arr; + } + + let tgt = { + get a() { + trace += "get a,"; + return "a"; + }, + get b() { + trace += "get b,"; + return "b"; + } + } + + function target() { + trace += "target(),"; + return tgt; + } + + function default_a() { + trace += "default a,"; + return "def_a"; + } + + function default_b() { + trace += "default b,"; + return "def_b"; + } + + ([target().a = default_a(), target().b = default_b()] = src()); + trace; + ` + testScript(SCRIPT, asciiString("src(),target(),next,target(),next,default b,"), t) +} + +func TestObjectAssignPatternRest(t *testing.T) { + const SCRIPT = ` + let a, b, c, d; + ({a, b, c, ...d} = {a: 1, b: 2, d: 4}); + assert.sameValue(a, 1, "a"); + assert.sameValue(b, 2, "b"); + assert.sameValue(c, undefined, "c"); + assert(deepEqual(d, {d: 4}), "d"); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestObjectBindPattern(t *testing.T) { + const SCRIPT = ` + let {a, b, c, ...d} = {a: 1, b: 2, d: 4}; + assert.sameValue(a, 1, "a"); + assert.sameValue(b, 2, "b"); + assert.sameValue(c, undefined, "c"); + assert(deepEqual(d, {d: 4}), "d"); + + var { x: y, } = { x: 23 }; + + assert.sameValue(y, 23); + + assert.throws(ReferenceError, function() { + x; + }); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestObjLiteralShorthandWithInitializer(t *testing.T) { + const SCRIPT = ` + o = {a=1}; + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestObjLiteralShorthandLetStringLit(t *testing.T) { + const SCRIPT = ` + o = {"let"}; + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestObjLiteralComputedKeys(t *testing.T) { + const SCRIPT = ` + let o = { + get [Symbol.toString]() { + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestObjLiteralComputedKeysEvalOrder(t *testing.T) { + const SCRIPT = ` + let trace = []; + function key() { + trace.push("key"); + return { + toString: function() { + trace.push("key-toString"); + return "key"; + } + } + } + function val() { + trace.push("val"); + return "val"; + } + + const _ = { + [key()]: val(), + } + + trace.join(","); + ` + testScript(SCRIPT, asciiString("key,key-toString,val"), t) +} + +func TestArrayAssignPattern(t *testing.T) { + const SCRIPT = ` + let a, b; + ([a, b] = [1, 2]); + a === 1 && b === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayAssignPattern1(t *testing.T) { + const SCRIPT = ` + let a, b; + ([a = 3, b = 2] = [1]); + a === 1 && b === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayAssignPatternLHS(t *testing.T) { + const SCRIPT = ` + let a = {}; + [ a.b, a['c'] = 2 ] = [1]; + a.b === 1 && a.c === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayAssignPatternElision(t *testing.T) { + const SCRIPT = ` + let a, b; + ([a,, b] = [1, 4, 2]); + a === 1 && b === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayAssignPatternRestPattern(t *testing.T) { + const SCRIPT = ` + let a, b, z; + [ z, ...[a, b] ] = [0, 1, 2]; + z === 0 && a === 1 && b === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestArrayBindingPattern(t *testing.T) { + const SCRIPT = ` + let [a, b] = [1, 2]; + a === 1 && b === 2; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestObjectPatternShorthandInit(t *testing.T) { + const SCRIPT = ` + [...{ x = 1 }] = []; + x; + ` + testScript(SCRIPT, valueInt(1), t) +} + +func TestArrayBindingPatternRestPattern(t *testing.T) { + const SCRIPT = ` + const [a, b, ...[c, d]] = [1, 2, 3, 4]; + a === 1 && b === 2 && c === 3 && d === 4; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestForVarPattern(t *testing.T) { + const SCRIPT = ` + var o = {a: 1}; + var trace = ""; + for (var [key, value] of Object.entries(o)) { + trace += key+":"+value; + } + trace; + ` + testScript(SCRIPT, asciiString("a:1"), t) +} + +func TestForLexPattern(t *testing.T) { + const SCRIPT = ` + var o = {a: 1}; + var trace = ""; + for (const [key, value] of Object.entries(o)) { + trace += key+":"+value; + } + trace; + ` + testScript(SCRIPT, asciiString("a:1"), t) +} + +func TestBindingPatternRestTrailingComma(t *testing.T) { + const SCRIPT = ` + const [a, b, ...rest,] = []; + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestAssignPatternRestTrailingComma(t *testing.T) { + const SCRIPT = ` + ([a, b, ...rest,] = []); + ` + _, err := Compile("", SCRIPT, false) + if err == nil { + t.Fatal("Expected an error") + } +} + +func TestFuncParamInitializerSimple(t *testing.T) { + const SCRIPT = ` + function f(a = 1) { + return a; + } + ""+f()+f(2); + ` + testScript(SCRIPT, asciiString("12"), t) +} + +func TestFuncParamObjectPatternSimple(t *testing.T) { + const SCRIPT = ` + function f({a, b} = {a: 1, b: 2}) { + return "" + a + b; + } + ""+f()+" "+f({a: 3, b: 4}); + ` + testScript(SCRIPT, asciiString("12 34"), t) +} + +func TestFuncParamRestStackSimple(t *testing.T) { + const SCRIPT = ` + function f(arg1, ...rest) { + return rest; + } + let ar = f(1, 2, 3); + ar.join(","); + ` + testScript(SCRIPT, asciiString("2,3"), t) +} + +func TestFuncParamRestStashSimple(t *testing.T) { + const SCRIPT = ` + function f(arg1, ...rest) { + eval("true"); + return rest; + } + let ar = f(1, 2, 3); + ar.join(","); + ` + testScript(SCRIPT, asciiString("2,3"), t) +} + +func TestRestArgsNotInStash(t *testing.T) { + const SCRIPT = ` + function f(...rest) { + () => rest; + return rest.length; + } + f(1,2); + ` + testScript(SCRIPT, valueInt(2), t) +} + +func TestRestArgsInStash(t *testing.T) { + const SCRIPT = ` + function f(first, ...rest) { + () => first; + () => rest; + return rest.length; + } + f(1,2); + ` + testScript(SCRIPT, valueInt(1), t) +} + +func TestRestArgsInStashFwdRef(t *testing.T) { + const SCRIPT = ` + function f(first = eval(), ...rest) { + () => first; + () => rest; + return rest.length === 1 && rest[0] === 2; + } + f(1,2); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestFuncParamRestPattern(t *testing.T) { + const SCRIPT = ` + function f(arg1, ...{0: rest1, 1: rest2}) { + return ""+arg1+" "+rest1+" "+rest2; + } + f(1, 2, 3); + ` + testScript(SCRIPT, asciiString("1 2 3"), t) +} + +func TestFuncParamForwardRef(t *testing.T) { + const SCRIPT = ` + function f(a = b + 1, b) { + return ""+a+" "+b; + } + f(1, 2); + ` + testScript(SCRIPT, asciiString("1 2"), t) +} + +func TestFuncParamForwardRefMissing(t *testing.T) { + const SCRIPT = ` + function f(a = b + 1, b) { + return ""+a+" "+b; + } + assert.throws(ReferenceError, function() { + f(); + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestFuncParamInnerRef(t *testing.T) { + const SCRIPT = ` + function f(a = inner) { + var inner = 42; + return a; + } + assert.throws(ReferenceError, function() { + f(); + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestFuncParamInnerRefEval(t *testing.T) { + const SCRIPT = ` + function f(a = eval("inner")) { + var inner = 42; + return a; + } + assert.throws(ReferenceError, function() { + f(); + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestFuncParamCalleeName(t *testing.T) { + const SCRIPT = ` + function f(a = f) { + var f; + return f; + } + typeof f(); + ` + testScript(SCRIPT, asciiString("undefined"), t) +} + +func TestFuncParamVarCopy(t *testing.T) { + const SCRIPT = ` + function f(a = f) { + var a; + return a; + } + typeof f(); + ` + testScript(SCRIPT, asciiString("function"), t) +} + +func TestFuncParamScope(t *testing.T) { + const SCRIPT = ` + var x = 'outside'; + var probe1, probe2; + + function f( + _ = probe1 = function() { return x; }, + __ = (eval('var x = "inside";'), probe2 = function() { return x; }) + ) { + } + f(); + probe1()+" "+probe2(); + ` + testScript(SCRIPT, asciiString("inside inside"), t) +} + +func TestDefParamsStackPtr(t *testing.T) { + const SCRIPT = ` + function A() {}; + A.B = function () {}; + function D(message = '') { + var C = A.B; + C([1,2,3]); + }; + + D(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestNestedVariadicCalls(t *testing.T) { + const SCRIPT = ` + function f() { + return Array.prototype.join.call(arguments, ","); + } + f(...[1], "a", f(...[2])); + ` + testScript(SCRIPT, asciiString("1,a,2"), t) +} + +func TestVariadicNew(t *testing.T) { + const SCRIPT = ` + function C() { + this.res = Array.prototype.join.call(arguments, ","); + } + var c = new C(...[1], "a", new C(...[2]).res); + c.res; + ` + testScript(SCRIPT, asciiString("1,a,2"), t) +} + +func TestVariadicUseStackVars(t *testing.T) { + const SCRIPT = ` + function A(message) { return message; } + function B(...args){ + return A(...args); + } + B("C"); + ` + testScript(SCRIPT, asciiString("C"), t) +} + +func TestCatchParamPattern(t *testing.T) { + const SCRIPT = ` + function f() { + let x = 3; + try { + throw {a: 1, b: 2}; + } catch ({a, b, c = x}) { + let x = 99; + return ""+a+" "+b+" "+c; + } + } + f(); + ` + testScript(SCRIPT, asciiString("1 2 3"), t) +} + +func TestArrowUseStrict(t *testing.T) { + // simple parameter list -- ok + _, err := Compile("", "(a) => {'use strict';}", false) + if err != nil { + t.Fatal(err) + } + // non-simple parameter list -- syntax error + _, err = Compile("", "(a=0) => {'use strict';}", false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestArrowBoxedThis(t *testing.T) { + const SCRIPT = ` + var context; + fn = function() { + return (arg) => { var local; context = this; }; + }; + + fn()(); + context === this; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestParameterOverride(t *testing.T) { + const SCRIPT = ` + function f(arg) { + var arg = arg || "default" + return arg + } + f() + ` + testScript(SCRIPT, asciiString("default"), t) +} + +func TestEvalInIterScope(t *testing.T) { + const SCRIPT = ` + for (let a = 0; a < 1; a++) { + eval("a"); + } + ` + + testScript(SCRIPT, valueInt(0), t) +} + +func TestTemplateLiterals(t *testing.T) { + vm := New() + _, err := vm.RunString("const a = 1, b = 'b';") + if err != nil { + t.Fatal(err) + } + f := func(t *testing.T, template, expected string) { + res, err := vm.RunString(template) + if err != nil { + t.Fatal(err) + } + if actual := res.Export(); actual != expected { + t.Fatalf("Expected: %q, actual: %q", expected, actual) + } + } + t.Run("empty", func(t *testing.T) { + f(t, "``", "") + }) + t.Run("noSub", func(t *testing.T) { + f(t, "`test`", "test") + }) + t.Run("emptyTail", func(t *testing.T) { + f(t, "`a=${a},b=${b}`", "a=1,b=b") + }) + t.Run("emptyHead", func(t *testing.T) { + f(t, "`${a},b=${b}$`", "1,b=b$") + }) + t.Run("headAndTail", func(t *testing.T) { + f(t, "`a=${a},b=${b}$`", "a=1,b=b$") + }) +} + +func TestTaggedTemplate(t *testing.T) { + const SCRIPT = ` + let res; + const o = { + tmpl() { + res = this; + return () => {}; + } + } + ` + + "o.tmpl()`test`;" + ` + res === o; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestDuplicateGlobalFunc(t *testing.T) { + const SCRIPT = ` + function a(){} + function b(){ return "b" } + function c(){ return "c" } + function a(){} + b(); + ` + + testScript(SCRIPT, asciiString("b"), t) +} + +func TestDuplicateFunc(t *testing.T) { + const SCRIPT = ` + function f() { + function a(){} + function b(){ return "b" } + function c(){ return "c" } + function a(){} + return b(); + } + f(); + ` + + testScript(SCRIPT, asciiString("b"), t) +} + +func TestSrcLocations(t *testing.T) { + // Do not reformat, assertions depend on the line and column numbers + const SCRIPT = ` + let i = { + valueOf() { + throw new Error(); + } + }; + try { + i++; + } catch(e) { + assertStack(e, [["test.js", "valueOf", 4, 10], + ["test.js", "", 8, 3] + ]); + } + + Object.defineProperty(globalThis, "x", { + get() { + throw new Error(); + }, + set() { + throw new Error(); + } + }); + + try { + x; + } catch(e) { + assertStack(e, [["test.js", "get", 17, 10], + ["test.js", "", 25, 3] + ]); + } + + try { + x++; + } catch(e) { + assertStack(e, [["test.js", "get", 17, 10], + ["test.js", "", 33, 3] + ]); + } + + try { + x = 2; + } catch(e) { + assertStack(e, [["test.js", "set", 20, 10], + ["test.js", "", 41, 3] + ]); + } + + try { + +i; + } catch(e) { + assertStack(e, [["test.js", "valueOf", 4, 10], + ["test.js", "", 49, 4] + ]); + } + + try { + let n; + n.field = { + "key1": "test", + "key2": {}, + } + } catch(e) { + assertStack(e, [["test.js", "", 58, 5] + ]); + } + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestSrcLocationThrowLiteral(t *testing.T) { + vm := New() + _, err := vm.RunString(` + const z = 1; + throw ""; + `) + if ex, ok := err.(*Exception); ok { + pos := ex.stack[0].Position() + if pos.Line != 3 { + t.Fatal(pos) + } + } else { + t.Fatal(err) + } +} + +func TestSrcLocation(t *testing.T) { + prg := MustCompile("test.js", ` +f(); +var x = 1; +let y = 1; +let [z1, z2] = [0, 0]; + +var [z3, z4] = [0, 0]; + `, false) + const ( + varLine = 3 + letLine = 4 + dstrLetLine = 5 + dstrVarLine = 7 + ) + linesOfInterest := map[int]string{ + varLine: "var", + letLine: "let", + dstrLetLine: "destruct let", + dstrVarLine: "destruct var", + } + for i := range prg.code { + loc := prg.src.Position(prg.sourceOffset(i)) + delete(linesOfInterest, loc.Line) + if len(linesOfInterest) == 0 { + break + } + } + for _, v := range linesOfInterest { + t.Fatalf("no %s line", v) + } +} + +func TestBadObjectKey(t *testing.T) { + _, err := Compile("", "({!:0})", false) + if err == nil { + t.Fatal("expected error") + } +} + +func TestConstantFolding(t *testing.T) { + testValues := func(prg *Program, result Value, t *testing.T) { + if len(prg.values) != 1 || !prg.values[0].SameAs(result) { + prg.dumpCode(t.Logf) + t.Fatalf("values: %v", prg.values) + } + } + f := func(src string, result Value, t *testing.T) { + prg := MustCompile("test.js", src, false) + testValues(prg, result, t) + New().testPrg(prg, result, t) + } + ff := func(src string, result Value, t *testing.T) { + prg := MustCompile("test.js", src, false) + fl := prg.code[0].(*newFunc) + testValues(fl.prg, result, t) + New().testPrg(prg, result, t) + } + + t.Run("lexical binding", func(t *testing.T) { + f("const x = 1 + 2; x", valueInt(3), t) + }) + t.Run("var binding", func(t *testing.T) { + f("var x = 1 + 2; x", valueInt(3), t) + }) + t.Run("assignment", func(t *testing.T) { + f("x = 1 + 2; x", valueInt(3), t) + }) + t.Run("object pattern", func(t *testing.T) { + f("const {x = 1 + 2} = {}; x", valueInt(3), t) + }) + t.Run("array pattern", func(t *testing.T) { + f("const [x = 1 + 2] = []; x", valueInt(3), t) + }) + t.Run("object literal", func(t *testing.T) { + f("var o = {x: 1 + 2}; o.x", valueInt(3), t) + }) + t.Run("array literal", func(t *testing.T) { + f("var a = [3, 3, 3, 1 + 2]; a[3]", valueInt(3), t) + }) + t.Run("default function parameter", func(t *testing.T) { + ff("function f(arg = 1 + 2) {return arg}; f()", valueInt(3), t) + }) + t.Run("return", func(t *testing.T) { + ff("function f() {return 1 + 2}; f()", valueInt(3), t) + }) +} + +func TestAssignBeforeInit(t *testing.T) { + const SCRIPT = ` + assert.throws(ReferenceError, () => { + a = 1; + let a; + }); + + assert.throws(ReferenceError, () => { + ({a, b} = {a: 1, b: 2}); + let a, b; + }); + + assert.throws(ReferenceError, () => { + (function() { + eval(""); + ({a} = {a: 1}); + })(); + let a; + }); + + assert.throws(ReferenceError, () => { + const ctx = {x: 1}; + function t() { + delete ctx.x; + return 42; + } + with(ctx) { + (function() { + 'use strict'; + ({x} = {x: t()}); + })(); + } + return ctx.x; + }); + + assert.throws(ReferenceError, () => { + const ctx = {x: 1}; + function t() { + delete ctx.x; + return 42; + } + with(ctx) { + (function() { + 'use strict'; + const src = {}; + Object.defineProperty(src, "x", { + get() { + return t(); + } + }); + ({x} = src); + })(); + } + return ctx.x; + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestOptChainCallee(t *testing.T) { + const SCRIPT = ` + var a; + assert.sameValue(a?.(true), undefined); + a = null; + assert.sameValue(a?.(), undefined); + var o = {n: null}; + assert.sameValue(o.m?.(true), undefined); + assert.sameValue(o.n?.(true), undefined); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjectLiteralSuper(t *testing.T) { + const SCRIPT = ` + const proto = { + m() { + return 40; + } + } + const o = { + m() { + return super.m() + 2; + } + } + o.__proto__ = proto; + o.m(); + ` + testScript(SCRIPT, intToValue(42), t) +} + +func TestClassCaptureThisInFieldInit(t *testing.T) { + const SCRIPT = ` + let capture; + + class C { + a = () => this + } + + let c = new C(); + c.a() === c; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestClassUseThisInFieldInit(t *testing.T) { + const SCRIPT = ` + let capture; + + class C { + a = this + } + + let c = new C(); + c.a === c; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestClassCaptureThisInStaticFieldInit(t *testing.T) { + const SCRIPT = ` + let capture; + + class C { + static a = (capture = () => this, 0) + } + + let c = new C(); + capture() === C; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestClassDynCaptureThisInStaticFieldInit(t *testing.T) { + const SCRIPT = ` + class C { + static a = eval("this") + } + + C.a === C; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestCompileClass(t *testing.T) { + const SCRIPT = ` + class C extends Error { + a = true; + b = 1; + ["b".toUpperCase()] = 2 + static A = Math.random() < 1 + constructor(message = "My Error") { + super(message); + } + static M() { + } + static M1() { + } + m() { + //return C.a; + } + m1() { + return true; + } + static { + this.supername = super.name; + } + } + let c = new C(); + c.a === true && c.b === 1 && c.B === 2 && c.m1() && C.A && C.supername === "Error"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestSuperInEval(t *testing.T) { + const SCRIPT = ` + class C extends Error { + constructor() { + eval("super()"); + } + m() { + return eval("super.name"); + } + } + let c = new C(); + c.m() === "Error"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestSuperRefDot(t *testing.T) { + const SCRIPT = ` + let thisGet, thisSet; + class P { + _p = 0 + get p() { + thisGet = this; + return this._p; + } + set p(v) { + thisSet = this; + this._p = v; + } + } + + class C extends P { + g() { + return super.p; + } + s(v) { + super.p = v; + } + + inc() { + super.p++; + } + incR() { + return super.p++; + } + + inc1() { + ++super.p; + } + + inc1R() { + return ++super.p; + } + unary() { + return +super.p; + } + pattern() { + [super.p] = [9]; + } + } + + let o = new C(); + assert.sameValue(o.g(), 0, "get value"); + assert.sameValue(thisGet, o, "get this"); + o.s(1); + assert.sameValue(o._p, 1, "set value"); + assert.sameValue(thisSet, o, "set this"); + + thisGet = undefined; + thisSet = undefined; + o.inc(); + assert.sameValue(o._p, 2, "inc value"); + assert.sameValue(thisGet, o, "inc thisGet"); + assert.sameValue(thisSet, o, "inc thisSet"); + + thisGet = undefined; + thisSet = undefined; + assert.sameValue(o.incR(), 2, "incR result"); + assert.sameValue(o._p, 3, "incR value"); + assert.sameValue(thisGet, o, "incR thisGet"); + assert.sameValue(thisSet, o, "incR thisSet"); + + thisGet = undefined; + thisSet = undefined; + o.inc1(); + assert.sameValue(o._p, 4, "inc1 value"); + assert.sameValue(thisGet, o, "inc1 thisGet"); + assert.sameValue(thisSet, o, "inc1 thisSet"); + + thisGet = undefined; + thisSet = undefined; + assert.sameValue(o.inc1R(), 5, "inc1R result"); + assert.sameValue(o._p, 5, "inc1R value"); + assert.sameValue(thisGet, o, "inc1R thisGet"); + assert.sameValue(thisSet, o, "inc1R thisSet"); + + assert.sameValue(o.unary(), 5, "unary"); + + o.pattern(); + assert.sameValue(o._p, 9, "pattern"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestPrivateRefDot(t *testing.T) { + const SCRIPT = ` + class C { + #p = 0; + g() { + return this.#p; + } + s(v) { + this.#p = v; + } + + inc() { + this.#p++; + } + incR() { + return this.#p++; + } + + inc1() { + ++this.#p; + } + + inc1R() { + return ++this.#p; + } + pattern() { + [this.#p] = [9]; + } + } + + let o = new C(); + assert.sameValue(o.g(), 0, "get value"); + o.s(1); + assert.sameValue(o.g(), 1, "set value"); + + o.inc(); + assert.sameValue(o.g(), 2, "inc value"); + + assert.sameValue(o.incR(), 2, "incR result"); + assert.sameValue(o.g(), 3, "incR value"); + + o.inc1(); + assert.sameValue(o.g(), 4, "inc1 value"); + + assert.sameValue(o.inc1R(), 5, "inc1R result"); + assert.sameValue(o.g(), 5, "inc1R value"); + + o.pattern(); + assert.sameValue(o.g(), 9, "pattern"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestPrivateRefDotEval(t *testing.T) { + const SCRIPT = ` + class C { + #p = 0; + g() { + return eval("this.#p"); + } + s(v) { + eval("this.#p = v"); + } + + incR() { + return eval("this.#p++"); + } + + inc1R() { + return eval("++this.#p"); + } + + pattern() { + eval("[this.#p] = [9]"); + } + } + + let o = new C(); + assert.sameValue(o.g(), 0, "get value"); + o.s(1); + assert.sameValue(o.g(), 1, "set value"); + + assert.sameValue(o.incR(), 1, "incR result"); + assert.sameValue(o.g(), 2, "incR value"); + + assert.sameValue(o.inc1R(), 3, "inc1R result"); + assert.sameValue(o.g(), 3, "inc1R value"); + + o.pattern(); + assert.sameValue(o.g(), 9, "pattern"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestSuperRefDotCallee(t *testing.T) { + const SCRIPT = ` + class P { + get p() { + return function() { + return this; + }; + } + } + + class C extends P { + m() { + return super.p(); + } + } + + let o = new C(); + o.m() === o; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestSuperRefBracket(t *testing.T) { + const SCRIPT = ` + let PROP = "p"; + let thisGet, thisSet; + class P { + _p = 0 + get p() { + thisGet = this; + return this._p; + } + set p(v) { + thisSet = this; + this._p = v; + } + } + + class C extends P { + g() { + return super[PROP]; + } + s(v) { + super[PROP] = v; + } + + inc() { + super[PROP]++; + } + incR() { + return super[PROP]++; + } + + inc1() { + ++super[PROP]; + } + + inc1R() { + return ++super[PROP]; + } + pattern() { + [super[PROP]] = [9]; + } + } + + let o = new C(); + assert.sameValue(o.g(), 0, "get value"); + assert.sameValue(thisGet, o, "get this"); + o.s(1); + assert.sameValue(o._p, 1, "set value"); + assert.sameValue(thisSet, o, "set this"); + + thisGet = undefined; + thisSet = undefined; + o.inc(); + assert.sameValue(o._p, 2, "inc value"); + assert.sameValue(thisGet, o, "inc thisGet"); + assert.sameValue(thisSet, o, "inc thisSet"); + + thisGet = undefined; + thisSet = undefined; + assert.sameValue(o.incR(), 2, "incR result"); + assert.sameValue(o._p, 3, "incR value"); + assert.sameValue(thisGet, o, "incR thisGet"); + assert.sameValue(thisSet, o, "incR thisSet"); + + thisGet = undefined; + thisSet = undefined; + o.inc1(); + assert.sameValue(o._p, 4, "inc1 value"); + assert.sameValue(thisGet, o, "inc1 thisGet"); + assert.sameValue(thisSet, o, "inc1 thisSet"); + + thisGet = undefined; + thisSet = undefined; + assert.sameValue(o.inc1R(), 5, "inc1R result"); + assert.sameValue(o._p, 5, "inc1R value"); + assert.sameValue(thisGet, o, "inc1R thisGet"); + assert.sameValue(thisSet, o, "inc1R thisSet"); + + o.pattern(); + assert.sameValue(o._p, 9, "pattern"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestSuperRefBracketEvalOrder(t *testing.T) { + const SCRIPT = ` + let keyCallCount = 0; + + function key() { + keyCallCount++; + C.prototype.__proto__ = null; + return "k"; + } + + class C { + constructor() { + super[key()]++; + } + } + + assert.throws(TypeError, () => new C()); + assert.sameValue(keyCallCount, 1); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestSuperRefBracketCallee(t *testing.T) { + const SCRIPT = ` + let PROP = "p"; + class P { + get p() { + return function() { + return this; + }; + } + } + + class C extends P { + m() { + return super[PROP](); + } + } + + let o = new C(); + o.m() === o; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestSuperBaseInCtor(t *testing.T) { + const SCRIPT = ` + let result; + class Derived extends Object { + constructor() { + super(); + result = super.constructor === Object; + } + } + new Derived(); + result; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestClassNamedEval(t *testing.T) { + const SCRIPT = ` + const C = class { + } + + C.name === "C"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestClassNonDerived(t *testing.T) { + const SCRIPT = ` + function initF() { + } + class C { + f = initF() + } + let c = new C(); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestClassExpr(t *testing.T) { + const SCRIPT = ` + typeof Object.getOwnPropertyDescriptor(class {get f() {}}.prototype, "f").get === "function"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestClassSuperInHeritage(t *testing.T) { + const SCRIPT = ` + class P { + a() { + return Error; + } + } + + class C extends P { + m() { + class Inner extends super.a() { + } + return new Inner(); + } + } + + new C().m() instanceof Error; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestClassSuperInHeritageExpr(t *testing.T) { + const SCRIPT = ` + class P { + a() { + return Error; + } + } + + class C extends P { + m() { + function f(cls) { + return new cls(); + } + return f(class Inner extends super.a() { + }) + } + } + + new C().m() instanceof Error; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestClassReferToBinding(t *testing.T) { + const SCRIPT = ` + const CC = class C { + static T = 40 + f = C.T + 2 + } + let c = new CC(); + c.f; + ` + + testScript(SCRIPT, intToValue(42), t) +} + +func TestClassReferToBindingInStaticDecl(t *testing.T) { + const SCRIPT = ` + class C { + static T = C.name + } + C.T; + ` + + testScript(SCRIPT, asciiString("C"), t) +} + +func TestClassReferToBindingInStaticEval(t *testing.T) { + const SCRIPT = ` + const CC = class C { + static T = eval("C.name") + } + CC.T; + ` + + testScript(SCRIPT, asciiString("C"), t) +} + +func TestClassReferToBindingFromHeritage(t *testing.T) { + const SCRIPT = ` + assert.throws(ReferenceError, () => { + class C extends C { + } + }); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestClassCaptureSuperCallInArrowFunc(t *testing.T) { + const SCRIPT = ` + let f; + class C extends class {} { + constructor() { + f = () => super(); + f(); + } + } + let c = new C(); + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestClassCaptureSuperCallInNestedArrowFunc(t *testing.T) { + const SCRIPT = ` + let f; + class P { + } + class C extends P { + constructor() { + f = () => () => super(); + f()(); + } + } + new C() instanceof P; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestThisInEval(t *testing.T) { + const SCRIPT = ` + assert.sameValue(eval("this"), this, "global"); + + let o = { + f() { + return eval("this"); + } + } + assert.sameValue(o.f(), o, "obj literal"); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestStaticAsBindingTarget(t *testing.T) { + const SCRIPT = ` + let [static] = []; + ` + testScript(SCRIPT, _undefined, t) +} + +func TestEvalInStaticFieldInit(t *testing.T) { + const SCRIPT = ` + var C = class { + static f = 'test'; + static g = this.f + '262'; + static h = eval('this.g') + 'test'; + } + C.f === "test" && C.g === "test262" && C.h === "test262test"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestClassPrivateElemInEval(t *testing.T) { + const SCRIPT = ` + let f1, f2; + + class C extends (f1 = o => eval("o.#a"), Object) { + static #a = 42; + static { + f2 = o => eval("o.#a"); + assert.sameValue(C.#a, 42); + assert.sameValue((() => C.#a)(), 42); + } + } + + assert.throws(SyntaxError, () => f1(C)); + assert.sameValue(f2(C), 42); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestClassPrivateElemInIndirectEval(t *testing.T) { + const SCRIPT = ` + let f1, f2; + + class C extends (f1 = o => (0, eval)("o.#a"), Object) { + static #a = 42; + static { + f2 = o => (0, eval)("o.#a"); + assert.throws(SyntaxError, () => (0, eval)("C.#a")); + } + } + + assert.throws(SyntaxError, () => f1(C)); + assert.throws(SyntaxError, () => f2(C)); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestClassPrivateElemInFunction(t *testing.T) { + const SCRIPT = ` + assert.throws(SyntaxError, () => { + class C { + static #a = 42; + static { + Function("o", "return o.#a"); + } + } + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestClassPrivateElementsDecl(t *testing.T) { + const SCRIPT = ` + class C { + #a = 42; + get #b() {} + set #b(_) {} + get c() { + return this.#a; + } + #m() { + return this.#a; + } + static getter(inst) { + return inst.#m(); + } + } + let c = new C(); + c.c + C.getter(c); + ` + testScript(SCRIPT, intToValue(84), t) +} + +func TestPrivateIn(t *testing.T) { + const SCRIPT = ` + class C { + #a = 42; + static check(inst) { + return #a in inst; + } + } + let c = new C(); + C.check(c); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestDeletePropOfNonObject(t *testing.T) { + const SCRIPT = ` + delete 'Test262'[100] && delete 'Test262'.a && delete 'Test262'['@']; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestKeywordsAsLabels(t *testing.T) { + const SCRIPT = ` + let: for (let i = 0; i < 2; i++) { + if (i === 0) continue let; + break let; + } + + \u006Cet: for (let i = 0; i < 2; i++) { + if (i === 0) continue \u006Cet; + break \u006Cet; + } + + yield: for (let i = 0; i < 2; i++) { + if (i === 0) continue yield; + break yield; + } + + yi\u0065ld: for (let i = 0; i < 2; i++) { + if (i === 0) continue yi\u0065ld; + break yi\u0065ld; + } +` + testScript(SCRIPT, _undefined, t) +} + +func TestThisResolutionWithArg(t *testing.T) { + const SCRIPT = ` + let capture; + function f(arg) { + capture = () => this; // move 'this' to stash + return [this, arg]; + } + const _this = {}; + const arg = {}; + const [_this1, arg1] = f.call(_this, arg); + _this1 === _this && arg1 === arg; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestThisResolutionArgInStash(t *testing.T) { + const SCRIPT = ` + let capture; + function f(arg) { + capture = () => this + arg; // move 'this' and arguments to stash + return [this, arg]; + } + const _this = {}; + const arg = {}; + const [_this1, arg1] = f.call(_this, arg); + _this1 === _this && arg1 === arg; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestThisResolutionWithStackVar(t *testing.T) { + const SCRIPT = ` + let capture; + function f(arg) { + const _ = 1; // a stack variable + capture = () => this + arg; // move 'this' and arguments to stash + return [this, arg]; + } + const _this = {}; + const arg = {}; + const [_this1, arg1] = f.call(_this, arg); + _this1 === _this && arg1 === arg; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestForInLoopContinue(t *testing.T) { + const SCRIPT = ` + var globalSink; + (function() { + const data = [{disabled: true}, {}]; + function dummy() {} + function f1() {} + + function f() { + dummy(); // move dummy to stash (so that f1 is at index 1) + for (const d of data) { + if (d.disabled) continue; + globalSink = () => d; // move d to stash + f1(); + } + } + + f(); + })(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestForInLoopContinueOuter(t *testing.T) { + const SCRIPT = ` + var globalSink; + (function() { + const data = [{disabled: true}, {}]; + function dummy1() {} + function f1() {} + + function f() { + dummy1(); + let counter = 0; + OUTER: for (let i = 0; i < 1; i++) { + for (const d of data) { + if (d.disabled) continue OUTER; + globalSink = () => d; + } + counter++; + } + f1(); + if (counter !== 0) { + throw new Error(counter); + } + } + + f(); + })(); + ` + testScript(SCRIPT, _undefined, t) +} + +func TestLexicalDeclInSwitch(t *testing.T) { + const SCRIPT = ` + switch(0) { + case 1: + if (false) b = 3; + case 2: + const c = 1; + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestClassFieldSpecial(t *testing.T) { + const SCRIPT = ` + class C { + get; + set; + async; + static; + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestClassMethodSpecial(t *testing.T) { + const SCRIPT = ` + class C { + get() {} + set() {} + async() {} + static() {} + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestClassMethodNumLiteral(t *testing.T) { + const SCRIPT = ` + class C { + 0() { + return true; + } + } + new C()[0](); + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestAsyncFunc(t *testing.T) { + const SCRIPT = ` + async (x = true, y) => {}; + async x => {}; + let passed = false; + async function f() { + return true; + } + async function f1(arg = true) { + passed = await f(); + } + await f1(); + return passed; + ` + testAsyncFunc(SCRIPT, valueTrue, t) +} + +func TestObjectLiteralComputedMethodKeys(t *testing.T) { + _, err := Compile("", ` + ({ + ["__proto__"]() {}, + ["__proto__"]() {} + }) + `, false) + if err != nil { + t.Fatal(err) + } + + _, err = Compile("", ` + ({ + get ["__proto__"]() {}, + get ["__proto__"]() {} + }) + `, false) + if err != nil { + t.Fatal(err) + } +} + +func TestGeneratorFunc(t *testing.T) { + const SCRIPT = ` + let trace = ""; + function defParam() { + trace += "1"; + return "def"; + } + function* g(param = defParam()) { + const THREE = 3; + trace += "2"; + assert.sameValue(Math.floor(yield 1), THREE); + return 42; + } + let iter = g(); + assert.sameValue(trace, "1"); + + let next = iter.next(); + assert.sameValue(next.value, 1); + assert.sameValue(next.done, false); + + next = iter.next(Math.PI); + assert.sameValue(next.value, 42); + assert.sameValue(next.done, true); + + assert.sameValue(trace, "12"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestGeneratorMethods(t *testing.T) { + const SCRIPT = ` + class C { + *g(param) { + yield 1; + yield 2; + } + } + let c = new C(); + let iter = c.g(); + let res = iter.next(); + assert.sameValue(res.value, 1); + assert.sameValue(res.done, false); + + res = iter.next(); + assert.sameValue(res.value, 2); + assert.sameValue(res.done, false); + + res = iter.next(); + assert.sameValue(res.value, undefined); + assert.sameValue(res.done, true); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestFunctionBodyClassDecl(t *testing.T) { + const SCRIPT = ` + function as(requiredArgument = {}) { + class something { } + }; + ` + _, err := Compile("", SCRIPT, false) + if err != nil { + t.Fatal(err) + } +} + +/* +func TestBabel(t *testing.T) { + src, err := os.ReadFile("babel7.js") + if err != nil { + t.Fatal(err) + } + vm := New() + _, err = vm.RunString(string(src)) + if err != nil { + t.Fatal(err) + } + _, err = vm.RunString(`var result = Babel.transform("", {presets: ["es2015"]});`) + if err != nil { + t.Fatal(err) + } +}*/ + +func BenchmarkCompile(b *testing.B) { + data, err := os.ReadFile("testdata/S15.10.2.12_A1_T1.js") + if err != nil { + b.Fatal(err) + } + + src := string(data) + + for i := 0; i < b.N; i++ { + _, err := Compile("test.js", src, false) + if err != nil { + b.Fatal(err) + } + } +} diff --git a/pkg/xscript/engine/date.go b/pkg/xscript/engine/date.go new file mode 100644 index 0000000..505fcf2 --- /dev/null +++ b/pkg/xscript/engine/date.go @@ -0,0 +1,192 @@ +package engine + +import ( + "math" + "reflect" + "time" +) + +const ( + dateTimeLayout = "Mon Jan 02 2006 15:04:05 GMT-0700 (MST)" + utcDateTimeLayout = "Mon, 02 Jan 2006 15:04:05 GMT" + isoDateTimeLayout = "2006-01-02T15:04:05.000Z" + dateLayout = "Mon Jan 02 2006" + timeLayout = "15:04:05 GMT-0700 (MST)" + datetimeLayout_en_GB = "01/02/2006, 15:04:05" + dateLayout_en_GB = "01/02/2006" + timeLayout_en_GB = "15:04:05" + + maxTime = 8.64e15 + timeUnset = math.MinInt64 +) + +type dateObject struct { + baseObject + msec int64 +} + +type dateLayoutDesc struct { + layout string + dateOnly bool +} + +var ( + dateLayoutsNumeric = []dateLayoutDesc{ + {layout: "2006-01-02T15:04:05Z0700"}, + {layout: "2006-01-02T15:04:05"}, + {layout: "2006-01-02", dateOnly: true}, + {layout: "2006-01-02 15:04:05"}, + + {layout: "2006", dateOnly: true}, + {layout: "2006-01", dateOnly: true}, + + {layout: "2006/01"}, + {layout: "2006/01/02"}, + {layout: "2006/01/02 15:04:05"}, + + {layout: "01/2006"}, + {layout: "02/01/2006"}, + {layout: "02/01/2006 15:04:05"}, + + {layout: "2006T15:04"}, + {layout: "2006-01T15:04"}, + {layout: "2006-01-02T15:04"}, + + {layout: "2006T15:04:05"}, + {layout: "2006-01T15:04:05"}, + + {layout: "2006T15:04Z0700"}, + {layout: "2006-01T15:04Z0700"}, + {layout: "2006-01-02T15:04Z0700"}, + + {layout: "2006T15:04:05Z0700"}, + {layout: "2006-01T15:04:05Z0700"}, + + {layout: "2006-01-02 15:04:05+00"}, + {layout: "2006-01-02 15:04"}, + {layout: "2006-01-02 15"}, + {layout: "20060102T150405"}, + {layout: "2006-01-02 15+04:05"}, + {layout: "2006-01-02 15-0405"}, + {layout: "2006-01-02 15Z"}, + {layout: "02-01-2006"}, + {layout: "02.01.2006 15:04:05"}, + } + + dateLayoutsAlpha = []dateLayoutDesc{ + {layout: time.RFC1123}, + {layout: time.RFC1123Z}, + {layout: dateTimeLayout}, + {layout: time.UnixDate}, + {layout: time.ANSIC}, + {layout: time.RubyDate}, + {layout: "Mon, _2 Jan 2006 15:04:05 GMT-0700 (MST)"}, + {layout: "Mon, _2 Jan 2006 15:04:05 -0700 (MST)"}, + {layout: "Jan _2, 2006", dateOnly: true}, + {layout: "Jan 02 2006"}, + {layout: "Jan 02 2006 15"}, + {layout: "Jan 02 2006 15:04"}, + {layout: "Jan 02 2006 15:04:05"}, + } +) + +func dateParse(date string) (time.Time, bool) { + var t time.Time + var err error + var layouts []dateLayoutDesc + if len(date) > 0 { + first := date[0] + if first <= '9' && (first >= '0' || first == '-' || first == '+') { + layouts = dateLayoutsNumeric + } else { + layouts = dateLayoutsAlpha + } + } else { + return time.Time{}, false + } + for _, desc := range layouts { + var defLoc *time.Location + if desc.dateOnly { + defLoc = time.UTC + } else { + defLoc = time.Local + } + t, err = parseDate(desc.layout, date, defLoc) + if err == nil { + break + } + } + if err != nil { + return time.Time{}, false + } + unix := timeToMsec(t) + return t, unix >= -maxTime && unix <= maxTime +} + +func (r *Runtime) newDateObject(t time.Time, isSet bool, proto *Object) *Object { + v := &Object{runtime: r} + d := &dateObject{} + v.self = d + d.val = v + d.class = classDate + d.prototype = proto + d.extensible = true + d.init() + if isSet { + d.msec = timeToMsec(t) + } else { + d.msec = timeUnset + } + return v +} + +func dateFormat(t time.Time) string { + return t.Local().Format(dateTimeLayout) +} + +func timeFromMsec(msec int64) time.Time { + sec := msec / 1000 + nsec := (msec % 1000) * 1e6 + return time.Unix(sec, nsec) +} + +func timeToMsec(t time.Time) int64 { + return t.Unix()*1000 + int64(t.Nanosecond())/1e6 +} + +func (d *dateObject) exportType() reflect.Type { + return typeTime +} + +func (d *dateObject) export(*objectExportCtx) any { + if d.isSet() { + return d.time() + } + return nil +} + +func (d *dateObject) setTimeMs(ms int64) Value { + if ms >= 0 && ms <= maxTime || ms < 0 && ms >= -maxTime { + d.msec = ms + return intToValue(ms) + } + + d.unset() + return _NaN +} + +func (d *dateObject) isSet() bool { + return d.msec != timeUnset +} + +func (d *dateObject) unset() { + d.msec = timeUnset +} + +func (d *dateObject) time() time.Time { + return timeFromMsec(d.msec) +} + +func (d *dateObject) timeUTC() time.Time { + return timeFromMsec(d.msec).In(time.UTC) +} diff --git a/pkg/xscript/engine/date_parser.go b/pkg/xscript/engine/date_parser.go new file mode 100644 index 0000000..6fb80b3 --- /dev/null +++ b/pkg/xscript/engine/date_parser.go @@ -0,0 +1,865 @@ +package engine + +// This is a slightly modified version of the standard Go parser to make it more compatible with ECMAScript 5.1 +// Changes: +// - 6-digit extended years are supported in place of long year (2006) in the form of +123456 +// - Timezone formats tolerate colons, e.g. -0700 will parse -07:00 +// - Short week day will also parse long week day +// - Short month ("Jan") will also parse long month ("January") +// - Long day ("02") will also parse short day ("2"). +// - Timezone in brackets, "(MST)", will match any string in brackets (e.g. "(GMT Standard Time)") +// - If offset is not set and timezone name is unknown, an error is returned +// - If offset and timezone name are both set the offset takes precedence and the resulting Location will be FixedZone("", offset) + +// Original copyright message: + +// Copyright 2010 The Go Authors. All rights reserved. +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +import ( + "errors" + "time" +) + +const ( + _ = iota + stdLongMonth = iota + stdNeedDate // "January" + stdMonth // "Jan" + stdNumMonth // "1" + stdZeroMonth // "01" + stdLongWeekDay // "Monday" + stdWeekDay // "Mon" + stdDay // "2" + stdUnderDay // "_2" + stdZeroDay // "02" + stdHour = iota + stdNeedClock // "15" + stdHour12 // "3" + stdZeroHour12 // "03" + stdMinute // "4" + stdZeroMinute // "04" + stdSecond // "5" + stdZeroSecond // "05" + stdLongYear = iota + stdNeedDate // "2006" + stdYear // "06" + stdPM = iota + stdNeedClock // "PM" + stdpm // "pm" + stdTZ = iota // "MST" + stdBracketTZ // "(MST)" + stdISO8601TZ // "Z0700" // prints Z for UTC + stdISO8601SecondsTZ // "Z070000" + stdISO8601ShortTZ // "Z07" + stdISO8601ColonTZ // "Z07:00" // prints Z for UTC + stdISO8601ColonSecondsTZ // "Z07:00:00" + stdNumTZ // "-0700" // always numeric + stdNumSecondsTz // "-070000" + stdNumShortTZ // "-07" // always numeric + stdNumColonTZ // "-07:00" // always numeric + stdNumColonSecondsTZ // "-07:00:00" + stdFracSecond0 // ".0", ".00", ... , trailing zeros included + stdFracSecond9 // ".9", ".99", ..., trailing zeros omitted + + stdNeedDate = 1 << 8 // need month, day, year + stdNeedClock = 2 << 8 // need hour, minute, second + stdArgShift = 16 // extra argument in high bits, above low stdArgShift + stdMask = 1<= 69 { // Unix time starts Dec 31 1969 in some time zones + year += 1900 + } else { + year += 2000 + } + case stdLongYear: + if len(value) >= 7 && (value[0] == '-' || value[0] == '+') { // extended year + neg := value[0] == '-' + p, value = value[1:7], value[7:] + year, err = atoi(p) + if neg { + year = -year + } + } else { + if len(value) < 4 || !isDigit(value, 0) { + err = errBad + break + } + p, value = value[0:4], value[4:] + year, err = atoi(p) + } + + case stdMonth: + month, value, err = lookup(longMonthNames, value) + if err != nil { + month, value, err = lookup(shortMonthNames, value) + } + month++ + case stdLongMonth: + month, value, err = lookup(longMonthNames, value) + month++ + case stdNumMonth, stdZeroMonth: + month, value, err = getnum(value, std == stdZeroMonth) + if month <= 0 || 12 < month { + rangeErrString = "month" + } + case stdWeekDay: + // Ignore weekday except for error checking. + _, value, err = lookup(longDayNames, value) + if err != nil { + _, value, err = lookup(shortDayNames, value) + } + case stdLongWeekDay: + _, value, err = lookup(longDayNames, value) + case stdDay, stdUnderDay, stdZeroDay: + if std == stdUnderDay && len(value) > 0 && value[0] == ' ' { + value = value[1:] + } + day, value, err = getnum(value, false) + if day < 0 { + // Note that we allow any one- or two-digit day here. + rangeErrString = "day" + } + case stdHour: + hour, value, err = getnum(value, false) + if hour < 0 || 24 <= hour { + rangeErrString = "hour" + } + case stdHour12, stdZeroHour12: + hour, value, err = getnum(value, std == stdZeroHour12) + if hour < 0 || 12 < hour { + rangeErrString = "hour" + } + case stdMinute, stdZeroMinute: + min, value, err = getnum(value, std == stdZeroMinute) + if min < 0 || 60 <= min { + rangeErrString = "minute" + } + case stdSecond, stdZeroSecond: + sec, value, err = getnum(value, std == stdZeroSecond) + if sec < 0 || 60 <= sec { + rangeErrString = "second" + break + } + // Special case: do we have a fractional second but no + // fractional second in the format? + if len(value) >= 2 && value[0] == '.' && isDigit(value, 1) { + _, std, _ = nextStdChunk(layout) + std &= stdMask + if std == stdFracSecond0 || std == stdFracSecond9 { + // Fractional second in the layout; proceed normally + break + } + // No fractional second in the layout but we have one in the input. + n := 2 + for ; n < len(value) && isDigit(value, n); n++ { + } + nsec, rangeErrString, err = parseNanoseconds(value, n) + value = value[n:] + } + case stdPM: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "PM": + pmSet = true + case "AM": + amSet = true + default: + err = errBad + } + case stdpm: + if len(value) < 2 { + err = errBad + break + } + p, value = value[0:2], value[2:] + switch p { + case "pm": + pmSet = true + case "am": + amSet = true + default: + err = errBad + } + case stdISO8601TZ, stdISO8601ColonTZ, stdISO8601SecondsTZ, stdISO8601ShortTZ, stdISO8601ColonSecondsTZ, stdNumTZ, stdNumShortTZ, stdNumColonTZ, stdNumSecondsTz, stdNumColonSecondsTZ: + if (std == stdISO8601TZ || std == stdISO8601ShortTZ || std == stdISO8601ColonTZ || + std == stdISO8601SecondsTZ || std == stdISO8601ColonSecondsTZ) && len(value) >= 1 && value[0] == 'Z' { + + value = value[1:] + z = time.UTC + break + } + var sign, hour, min, seconds string + if std == stdISO8601ColonTZ || std == stdNumColonTZ || std == stdNumTZ || std == stdISO8601TZ { + if len(value) < 4 { + err = errBad + break + } + if value[3] != ':' { + if std == stdNumColonTZ || std == stdISO8601ColonTZ || len(value) < 5 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], "00", value[5:] + } else { + if len(value) < 6 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], "00", value[6:] + } + } else if std == stdNumShortTZ || std == stdISO8601ShortTZ { + if len(value) < 3 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], "00", "00", value[3:] + } else if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ || std == stdISO8601SecondsTZ || std == stdNumSecondsTz { + if len(value) < 7 { + err = errBad + break + } + if value[3] != ':' || value[6] != ':' { + if std == stdISO8601ColonSecondsTZ || std == stdNumColonSecondsTZ || len(value) < 7 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[3:5], value[5:7], value[7:] + } else { + if len(value) < 9 { + err = errBad + break + } + sign, hour, min, seconds, value = value[0:1], value[1:3], value[4:6], value[7:9], value[9:] + } + } + var hr, mm, ss int + hr, err = atoi(hour) + if err == nil { + mm, err = atoi(min) + } + if err == nil { + ss, err = atoi(seconds) + } + zoneOffset = (hr*60+mm)*60 + ss // offset is in seconds + switch sign[0] { + case '+': + case '-': + zoneOffset = -zoneOffset + default: + err = errBad + } + case stdTZ: + // Does it look like a time zone? + if len(value) >= 3 && value[0:3] == "UTC" { + z = time.UTC + value = value[3:] + break + } + n, ok := parseTimeZone(value) + if !ok { + err = errBad + break + } + zoneName, value = value[:n], value[n:] + case stdBracketTZ: + if len(value) < 3 || value[0] != '(' { + err = errBad + break + } + i := 1 + for ; ; i++ { + if i >= len(value) { + err = errBad + break + } + if value[i] == ')' { + zoneName, value = value[1:i], value[i+1:] + break + } + } + + case stdFracSecond0: + // stdFracSecond0 requires the exact number of digits as specified in + // the layout. + ndigit := 1 + (std >> stdArgShift) + if len(value) < ndigit { + err = errBad + break + } + nsec, rangeErrString, err = parseNanoseconds(value, ndigit) + value = value[ndigit:] + + case stdFracSecond9: + if len(value) < 2 || value[0] != '.' || value[1] < '0' || '9' < value[1] { + // Fractional second omitted. + break + } + // Take any number of digits, even more than asked for, + // because it is what the stdSecond case would do. + i := 0 + for i < 9 && i+1 < len(value) && '0' <= value[i+1] && value[i+1] <= '9' { + i++ + } + nsec, rangeErrString, err = parseNanoseconds(value, 1+i) + value = value[1+i:] + } + if rangeErrString != "" { + return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, LayoutElem: stdstr, ValueElem: value, Message: ": " + rangeErrString + " out of range"} + } + if err != nil { + return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, LayoutElem: stdstr, ValueElem: value} + } + } + if pmSet && hour < 12 { + hour += 12 + } else if amSet && hour == 12 { + hour = 0 + } + + // Validate the day of the month. + if day < 1 || day > daysIn(time.Month(month), year) { + return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, ValueElem: value, Message: ": day out of range"} + } + + if z == nil { + if zoneOffset == -1 { + if zoneName != "" { + if z1, err := time.LoadLocation(zoneName); err == nil { + z = z1 + } else { + return time.Time{}, &time.ParseError{Layout: alayout, Value: avalue, ValueElem: value, Message: ": unknown timezone"} + } + } else { + z = defaultLocation + } + } else if zoneOffset == 0 { + z = time.UTC + } else { + z = time.FixedZone("", zoneOffset) + } + } + + return time.Date(year, time.Month(month), day, hour, min, sec, nsec, z), nil +} + +var errLeadingInt = errors.New("time: bad [0-9]*") // never printed + +func signedLeadingInt(s string) (x int64, rem string, err error) { + neg := false + if s != "" && (s[0] == '-' || s[0] == '+') { + neg = s[0] == '-' + s = s[1:] + } + x, rem, err = leadingInt(s) + if err != nil { + return + } + + if neg { + x = -x + } + return +} + +// leadingInt consumes the leading [0-9]* from s. +func leadingInt(s string) (x int64, rem string, err error) { + i := 0 + for ; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + break + } + if x > (1<<63-1)/10 { + // overflow + return 0, "", errLeadingInt + } + x = x*10 + int64(c) - '0' + if x < 0 { + // overflow + return 0, "", errLeadingInt + } + } + return x, s[i:], nil +} + +// nextStdChunk finds the first occurrence of a std string in +// layout and returns the text before, the std string, and the text after. +func nextStdChunk(layout string) (prefix string, std int, suffix string) { + for i := 0; i < len(layout); i++ { + switch c := int(layout[i]); c { + case 'J': // January, Jan + if len(layout) >= i+3 && layout[i:i+3] == "Jan" { + if len(layout) >= i+7 && layout[i:i+7] == "January" { + return layout[0:i], stdLongMonth, layout[i+7:] + } + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdMonth, layout[i+3:] + } + } + + case 'M': // Monday, Mon, MST + if len(layout) >= i+3 { + if layout[i:i+3] == "Mon" { + if len(layout) >= i+6 && layout[i:i+6] == "Monday" { + return layout[0:i], stdLongWeekDay, layout[i+6:] + } + if !startsWithLowerCase(layout[i+3:]) { + return layout[0:i], stdWeekDay, layout[i+3:] + } + } + if layout[i:i+3] == "MST" { + return layout[0:i], stdTZ, layout[i+3:] + } + } + + case '0': // 01, 02, 03, 04, 05, 06 + if len(layout) >= i+2 && '1' <= layout[i+1] && layout[i+1] <= '6' { + return layout[0:i], std0x[layout[i+1]-'1'], layout[i+2:] + } + + case '1': // 15, 1 + if len(layout) >= i+2 && layout[i+1] == '5' { + return layout[0:i], stdHour, layout[i+2:] + } + return layout[0:i], stdNumMonth, layout[i+1:] + + case '2': // 2006, 2 + if len(layout) >= i+4 && layout[i:i+4] == "2006" { + return layout[0:i], stdLongYear, layout[i+4:] + } + return layout[0:i], stdDay, layout[i+1:] + + case '_': // _2, _2006 + if len(layout) >= i+2 && layout[i+1] == '2' { + //_2006 is really a literal _, followed by stdLongYear + if len(layout) >= i+5 && layout[i+1:i+5] == "2006" { + return layout[0 : i+1], stdLongYear, layout[i+5:] + } + return layout[0:i], stdUnderDay, layout[i+2:] + } + + case '3': + return layout[0:i], stdHour12, layout[i+1:] + + case '4': + return layout[0:i], stdMinute, layout[i+1:] + + case '5': + return layout[0:i], stdSecond, layout[i+1:] + + case 'P': // PM + if len(layout) >= i+2 && layout[i+1] == 'M' { + return layout[0:i], stdPM, layout[i+2:] + } + + case 'p': // pm + if len(layout) >= i+2 && layout[i+1] == 'm' { + return layout[0:i], stdpm, layout[i+2:] + } + + case '-': // -070000, -07:00:00, -0700, -07:00, -07 + if len(layout) >= i+7 && layout[i:i+7] == "-070000" { + return layout[0:i], stdNumSecondsTz, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "-07:00:00" { + return layout[0:i], stdNumColonSecondsTZ, layout[i+9:] + } + if len(layout) >= i+5 && layout[i:i+5] == "-0700" { + return layout[0:i], stdNumTZ, layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == "-07:00" { + return layout[0:i], stdNumColonTZ, layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == "-07" { + return layout[0:i], stdNumShortTZ, layout[i+3:] + } + + case 'Z': // Z070000, Z07:00:00, Z0700, Z07:00, + if len(layout) >= i+7 && layout[i:i+7] == "Z070000" { + return layout[0:i], stdISO8601SecondsTZ, layout[i+7:] + } + if len(layout) >= i+9 && layout[i:i+9] == "Z07:00:00" { + return layout[0:i], stdISO8601ColonSecondsTZ, layout[i+9:] + } + if len(layout) >= i+5 && layout[i:i+5] == "Z0700" { + return layout[0:i], stdISO8601TZ, layout[i+5:] + } + if len(layout) >= i+6 && layout[i:i+6] == "Z07:00" { + return layout[0:i], stdISO8601ColonTZ, layout[i+6:] + } + if len(layout) >= i+3 && layout[i:i+3] == "Z07" { + return layout[0:i], stdISO8601ShortTZ, layout[i+3:] + } + + case '.': // .000 or .999 - repeated digits for fractional seconds. + if i+1 < len(layout) && (layout[i+1] == '0' || layout[i+1] == '9') { + ch := layout[i+1] + j := i + 1 + for j < len(layout) && layout[j] == ch { + j++ + } + // String of digits must end here - only fractional second is all digits. + if !isDigit(layout, j) { + std := stdFracSecond0 + if layout[i+1] == '9' { + std = stdFracSecond9 + } + std |= (j - (i + 1)) << stdArgShift + return layout[0:i], std, layout[j:] + } + } + case '(': + if len(layout) >= i+5 && layout[i:i+5] == "(MST)" { + return layout[0:i], stdBracketTZ, layout[i+5:] + } + } + } + return layout, 0, "" +} + +var longDayNames = []string{ + "Sunday", + "Monday", + "Tuesday", + "Wednesday", + "Thursday", + "Friday", + "Saturday", +} + +var shortDayNames = []string{ + "Sun", + "Mon", + "Tue", + "Wed", + "Thu", + "Fri", + "Sat", +} + +var shortMonthNames = []string{ + "Jan", + "Feb", + "Mar", + "Apr", + "May", + "Jun", + "Jul", + "Aug", + "Sep", + "Oct", + "Nov", + "Dec", +} + +var longMonthNames = []string{ + "January", + "February", + "March", + "April", + "May", + "June", + "July", + "August", + "September", + "October", + "November", + "December", +} + +// isDigit reports whether s[i] is in range and is a decimal digit. +func isDigit(s string, i int) bool { + if len(s) <= i { + return false + } + c := s[i] + return '0' <= c && c <= '9' +} + +// getnum parses s[0:1] or s[0:2] (fixed forces the latter) +// as a decimal integer and returns the integer and the +// remainder of the string. +func getnum(s string, fixed bool) (int, string, error) { + if !isDigit(s, 0) { + return 0, s, errBad + } + if !isDigit(s, 1) { + if fixed { + return 0, s, errBad + } + return int(s[0] - '0'), s[1:], nil + } + return int(s[0]-'0')*10 + int(s[1]-'0'), s[2:], nil +} + +func cutspace(s string) string { + for len(s) > 0 && s[0] == ' ' { + s = s[1:] + } + return s +} + +// skip removes the given prefix from value, +// treating runs of space characters as equivalent. +func skip(value, prefix string) (string, error) { + for len(prefix) > 0 { + if prefix[0] == ' ' { + if len(value) > 0 && value[0] != ' ' { + return value, errBad + } + prefix = cutspace(prefix) + value = cutspace(value) + continue + } + if len(value) == 0 || value[0] != prefix[0] { + return value, errBad + } + prefix = prefix[1:] + value = value[1:] + } + return value, nil +} + +// Never printed, just needs to be non-nil for return by atoi. +var atoiError = errors.New("time: invalid number") + +// Duplicates functionality in strconv, but avoids dependency. +func atoi(s string) (x int, err error) { + q, rem, err := signedLeadingInt(s) + x = int(q) + if err != nil || rem != "" { + return 0, atoiError + } + return x, nil +} + +// match reports whether s1 and s2 match ignoring case. +// It is assumed s1 and s2 are the same length. +func match(s1, s2 string) bool { + for i := 0; i < len(s1); i++ { + c1 := s1[i] + c2 := s2[i] + if c1 != c2 { + // Switch to lower-case; 'a'-'A' is known to be a single bit. + c1 |= 'a' - 'A' + c2 |= 'a' - 'A' + if c1 != c2 || c1 < 'a' || c1 > 'z' { + return false + } + } + } + return true +} + +func lookup(tab []string, val string) (int, string, error) { + for i, v := range tab { + if len(val) >= len(v) && match(val[0:len(v)], v) { + return i, val[len(v):], nil + } + } + return -1, val, errBad +} + +// daysBefore[m] counts the number of days in a non-leap year +// before month m begins. There is an entry for m=12, counting +// the number of days before January of next year (365). +var daysBefore = [...]int32{ + 0, + 31, + 31 + 28, + 31 + 28 + 31, + 31 + 28 + 31 + 30, + 31 + 28 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30, + 31 + 28 + 31 + 30 + 31 + 30 + 31 + 31 + 30 + 31 + 30 + 31, +} + +func isLeap(year int) bool { + return year%4 == 0 && (year%100 != 0 || year%400 == 0) +} + +func daysIn(m time.Month, year int) int { + if m == time.February && isLeap(year) { + return 29 + } + return int(daysBefore[m] - daysBefore[m-1]) +} + +// parseTimeZone parses a time zone string and returns its length. Time zones +// are human-generated and unpredictable. We can't do precise error checking. +// On the other hand, for a correct parse there must be a time zone at the +// beginning of the string, so it's almost always true that there's one +// there. We look at the beginning of the string for a run of upper-case letters. +// If there are more than 5, it's an error. +// If there are 4 or 5 and the last is a T, it's a time zone. +// If there are 3, it's a time zone. +// Otherwise, other than special cases, it's not a time zone. +// GMT is special because it can have an hour offset. +func parseTimeZone(value string) (length int, ok bool) { + if len(value) < 3 { + return 0, false + } + // Special case 1: ChST and MeST are the only zones with a lower-case letter. + if len(value) >= 4 && (value[:4] == "ChST" || value[:4] == "MeST") { + return 4, true + } + // Special case 2: GMT may have an hour offset; treat it specially. + if value[:3] == "GMT" { + length = parseGMT(value) + return length, true + } + // Special Case 3: Some time zones are not named, but have +/-00 format + if value[0] == '+' || value[0] == '-' { + length = parseSignedOffset(value) + return length, true + } + // How many upper-case letters are there? Need at least three, at most five. + var nUpper int + for nUpper = 0; nUpper < 6; nUpper++ { + if nUpper >= len(value) { + break + } + if c := value[nUpper]; c < 'A' || 'Z' < c { + break + } + } + switch nUpper { + case 0, 1, 2, 6: + return 0, false + case 5: // Must end in T to match. + if value[4] == 'T' { + return 5, true + } + case 4: + // Must end in T, except one special case. + if value[3] == 'T' || value[:4] == "WITA" { + return 4, true + } + case 3: + return 3, true + } + return 0, false +} + +// parseGMT parses a GMT time zone. The input string is known to start "GMT". +// The function checks whether that is followed by a sign and a number in the +// range -14 through 12 excluding zero. +func parseGMT(value string) int { + value = value[3:] + if len(value) == 0 { + return 3 + } + + return 3 + parseSignedOffset(value) +} + +// parseSignedOffset parses a signed timezone offset (e.g. "+03" or "-04"). +// The function checks for a signed number in the range -14 through +12 excluding zero. +// Returns length of the found offset string or 0 otherwise +func parseSignedOffset(value string) int { + sign := value[0] + if sign != '-' && sign != '+' { + return 0 + } + x, rem, err := leadingInt(value[1:]) + if err != nil { + return 0 + } + if sign == '-' { + x = -x + } + if x == 0 || x < -14 || 12 < x { + return 0 + } + return len(value) - len(rem) +} + +func parseNanoseconds(value string, nbytes int) (ns int, rangeErrString string, err error) { + if value[0] != '.' { + err = errBad + return + } + if ns, err = atoi(value[1:nbytes]); err != nil { + return + } + if ns < 0 || 1e9 <= ns { + rangeErrString = "fractional second" + return + } + // We need nanoseconds, which means scaling by the number + // of missing digits in the format, maximum length 10. If it's + // longer than 10, we won't scale. + scaleDigits := 10 - nbytes + for i := 0; i < scaleDigits; i++ { + ns *= 10 + } + return +} + +// std0x records the std values for "01", "02", ..., "06". +var std0x = [...]int{stdZeroMonth, stdZeroDay, stdZeroHour12, stdZeroMinute, stdZeroSecond, stdYear} + +// startsWithLowerCase reports whether the string has a lower-case letter at the beginning. +// Its purpose is to prevent matching strings like "Month" when looking for "Mon". +func startsWithLowerCase(str string) bool { + if len(str) == 0 { + return false + } + c := str[0] + return 'a' <= c && c <= 'z' +} diff --git a/pkg/xscript/engine/date_parser_test.go b/pkg/xscript/engine/date_parser_test.go new file mode 100644 index 0000000..26858ff --- /dev/null +++ b/pkg/xscript/engine/date_parser_test.go @@ -0,0 +1,33 @@ +package engine + +import ( + "testing" + "time" +) + +func TestParseDate(t *testing.T) { + + tst := func(layout, value string, expectedTs int64) func(t *testing.T) { + return func(t *testing.T) { + t.Parallel() + tm, err := parseDate(layout, value, time.UTC) + if err != nil { + t.Fatal(err) + } + if tm.Unix() != expectedTs { + t.Fatal(tm) + } + } + } + + t.Run("1", tst("2006-01-02T15:04:05.000Z070000", "2006-01-02T15:04:05.000+07:00:00", 1136189045)) + t.Run("2", tst("2006-01-02T15:04:05.000Z07:00:00", "2006-01-02T15:04:05.000+07:00:00", 1136189045)) + t.Run("3", tst("2006-01-02T15:04:05.000Z07:00", "2006-01-02T15:04:05.000+07:00", 1136189045)) + t.Run("4", tst("2006-01-02T15:04:05.000Z070000", "2006-01-02T15:04:05.000+070000", 1136189045)) + t.Run("5", tst("2006-01-02T15:04:05.000Z070000", "2006-01-02T15:04:05.000Z", 1136214245)) + t.Run("6", tst("2006-01-02T15:04:05.000Z0700", "2006-01-02T15:04:05.000Z", 1136214245)) + t.Run("7", tst("2006-01-02T15:04:05.000Z07", "2006-01-02T15:04:05.000Z", 1136214245)) + t.Run("8", tst("02/01/2006 15:04:05", "02/01/2006 15:04:05", 1136214245)) + t.Run("9", tst("2006/01/02 15:04:05", "2006/01/02 15:04:05", 1136214245)) + +} diff --git a/pkg/xscript/engine/date_test.go b/pkg/xscript/engine/date_test.go new file mode 100644 index 0000000..a82d14d --- /dev/null +++ b/pkg/xscript/engine/date_test.go @@ -0,0 +1,309 @@ +package engine + +import ( + "testing" + "time" +) + +func TestDateUTC(t *testing.T) { + const SCRIPT = ` + assert.sameValue(Date.UTC(1970, 0), 0, '1970, 0'); + assert.sameValue(Date.UTC(2016, 0), 1451606400000, '2016, 0'); + assert.sameValue(Date.UTC(2016, 6), 1467331200000, '2016, 6'); + + assert.sameValue(Date.UTC(2016, 6, 1), 1467331200000, '2016, 6, 1'); + assert.sameValue(Date.UTC(2016, 6, 5), 1467676800000, '2016, 6, 5'); + + assert.sameValue(Date.UTC(2016, 6, 5, 0), 1467676800000, '2016, 6, 5, 0'); + assert.sameValue(Date.UTC(2016, 6, 5, 15), 1467730800000, '2016, 6, 5, 15'); + + assert.sameValue( + Date.UTC(2016, 6, 5, 15, 0), 1467730800000, '2016, 6, 5, 15, 0' + ); + assert.sameValue( + Date.UTC(2016, 6, 5, 15, 34), 1467732840000, '2016, 6, 5, 15, 34' + ); + + assert.sameValue( + Date.UTC(2016, 6, 5, 15, 34, 0), 1467732840000, '2016, 6, 5, 15, 34, 0' + ); + assert.sameValue( + Date.UTC(2016, 6, 5, 15, 34, 45), 1467732885000, '2016, 6, 5, 15, 34, 45' + ); + + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestNewDate(t *testing.T) { + const SCRIPT = ` + var d1 = new Date("2016-09-01T12:34:56Z"); + d1.getUTCHours() === 12; + + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestNewDate0(t *testing.T) { + const SCRIPT = ` + (new Date(0)).toUTCString(); + + ` + testScript(SCRIPT, asciiString("Thu, 01 Jan 1970 00:00:00 GMT"), t) +} + +func TestSetHour(t *testing.T) { + l := time.Local + defer func() { + time.Local = l + }() + var err error + time.Local, err = time.LoadLocation("America/New_York") + if err != nil { + t.Fatal(err) + } + + const SCRIPT = ` + var d = new Date(2016, 8, 1, 12, 23, 45) + assert.sameValue(d.getHours(), 12); + assert.sameValue(d.getUTCHours(), 16); + + d.setHours(13); + assert.sameValue(d.getHours(), 13); + assert.sameValue(d.getMinutes(), 23); + assert.sameValue(d.getSeconds(), 45); + + d.setUTCHours(13); + assert.sameValue(d.getHours(), 9); + assert.sameValue(d.getMinutes(), 23); + assert.sameValue(d.getSeconds(), 45); + + ` + testScriptWithTestLib(SCRIPT, _undefined, t) + +} + +func TestSetMinute(t *testing.T) { + l := time.Local + defer func() { + time.Local = l + }() + time.Local = time.FixedZone("Asia/Delhi", 5*60*60+30*60) + + const SCRIPT = ` + var d = new Date(2016, 8, 1, 12, 23, 45) + assert.sameValue(d.getHours(), 12); + assert.sameValue(d.getUTCHours(), 6); + assert.sameValue(d.getMinutes(), 23); + assert.sameValue(d.getUTCMinutes(), 53); + + d.setMinutes(55); + assert.sameValue(d.getMinutes(), 55); + assert.sameValue(d.getSeconds(), 45); + + d.setUTCMinutes(52); + assert.sameValue(d.getMinutes(), 22); + assert.sameValue(d.getHours(), 13); + + ` + testScriptWithTestLib(SCRIPT, _undefined, t) + +} + +func TestTimezoneOffset(t *testing.T) { + const SCRIPT = ` + var d = new Date(0); + d.getTimezoneOffset(); + ` + + l := time.Local + defer func() { + time.Local = l + }() + var err error + time.Local, err = time.LoadLocation("Europe/London") + if err != nil { + t.Fatal(err) + } + + testScript(SCRIPT, intToValue(-60), t) +} + +func TestDateValueOf(t *testing.T) { + const SCRIPT = ` + var d9 = new Date(1.23e15); + d9.valueOf(); + ` + + testScript(SCRIPT, intToValue(1.23e15), t) +} + +func TestDateSetters(t *testing.T) { + const SCRIPT = ` + assert.sameValue((new Date(0)).setMilliseconds(2345), 2345, "setMilliseconds(2345)"); + assert.sameValue(new Date(1000).setMilliseconds(23450000000000), 23450000001000, "setMilliseconds(23450000000000)"); + assert.sameValue((new Date(0)).setUTCMilliseconds(2345), 2345, "setUTCMilliseconds()"); + assert.sameValue((new Date(0)).setSeconds(12), 12000, "setSeconds()"); + assert.sameValue((new Date(0)).setUTCSeconds(12), 12000, "setUTCSeconds()"); + assert.sameValue((new Date(0)).setMinutes(12), 12 * 60 * 1000, "setMinutes()"); + assert.sameValue((new Date(0)).setUTCMinutes(12), 12 * 60 * 1000, "setUTCMinutes()"); + assert.sameValue((new Date("2016-06-01")).setHours(1), 1464739200000, "setHours()"); + assert.sameValue((new Date("2016-06-01")).setUTCHours(1), 1464742800000, "setUTCHours()"); + assert.sameValue((new Date(0)).setDate(2), 86400000, "setDate()"); + assert.sameValue((new Date(0)).setUTCDate(2), 86400000, "setUTCDate()"); + assert.sameValue((new Date(0)).setMonth(2), 5097600000, "setMonth()"); + assert.sameValue((new Date(0)).setUTCMonth(2), 5097600000, "setUTCMonth()"); + assert.sameValue((new Date(0)).setFullYear(1971), 31536000000, "setFullYear()"); + assert.sameValue((new Date(0)).setFullYear(1971, 2, 3), 36806400000, "setFullYear(Y,M,D)"); + assert.sameValue((new Date(0)).setUTCFullYear(1971), 31536000000, "setUTCFullYear()"); + assert.sameValue((new Date(0)).setUTCFullYear(1971, 2, 3), 36806400000, "setUTCFullYear(Y,M,D)"); + + var d = new Date(); + d.setTime(1151877845000); + assert.sameValue(d.getHours(), 23, "d.getHours()"); + ` + + l := time.Local + defer func() { + time.Local = l + }() + var err error + time.Local, err = time.LoadLocation("Europe/London") + if err != nil { + t.Fatal(err) + } + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestDateParse(t *testing.T) { + const SCRIPT = ` + var zero = new Date(0); + + assert.sameValue(zero.valueOf(), Date.parse(zero.toString()), + "Date.parse(zeroDate.toString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toUTCString()), + "Date.parse(zeroDate.toUTCString())"); + assert.sameValue(zero.valueOf(), Date.parse(zero.toISOString()), + "Date.parse(zeroDate.toISOString())"); + + function testParse(str, expected) { + assert.sameValue(Date.parse(str), expected, str); + } + + testParse("Mon, 02 Jan 2006 15:04:05 MST", 1136239445000); + testParse("Tue, 22 Jun 2021 13:54:40 GMT", 1624370080000); + testParse("Tuesday, 22 Jun 2021 13:54:40 GMT", 1624370080000); + testParse("Mon, 02 Jan 2006 15:04:05 GMT-07:00 (MST)", 1136239445000); + testParse("Mon, 02 Jan 2006 15:04:05 -07:00 (MST)", 1136239445000); + testParse("Monday, 02 Jan 2006 15:04:05 -0700 (MST)", 1136239445000); + testParse("Mon Jan 02 2006 15:04:05 GMT-0700 (GMT Standard Time)", 1136239445000); + testParse("Mon Jan 2 15:04:05 MST 2006", 1136239445000); + testParse("Mon Jan 02 15:04:05 MST 2006", 1136239445000); + testParse("Mon Jan 02 15:04:05 -0700 2006", 1136239445000); + + testParse("December 04, 1986", 534038400000); + testParse("Dec 04, 1986", 534038400000); + testParse("Dec 4, 1986", 534038400000); + + testParse("2006-01-02T15:04:05.000Z", 1136214245000); + testParse("2006-06-02T15:04:05.000", 1149275045000); + testParse("2006-01-02T15:04:05", 1136232245000); + testParse("2006-01-02", 1136160000000); + testParse("2006T15:04-0700", 1136153040000); + testParse("2006T15:04Z", 1136127840000); + testParse("2019-01-01T12:00:00.52Z", 1546344000520); + + var d = new Date("Mon, 02 Jan 2006 15:04:05 MST"); + + assert.sameValue(d.getUTCHours(), 22, + "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getUTCHours()"); + + assert.sameValue(d.getHours(), 17, + "new Date(\"Mon, 02 Jan 2006 15:04:05 MST\").getHours()"); + + assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 zzz"), NaN, + "Date.parse(\"Mon, 02 Jan 2006 15:04:05 zzz\")"); + + assert.sameValue(Date.parse("Mon, 02 Jan 2006 15:04:05 ZZZ"), NaN, + "Date.parse(\"Mon, 02 Jan 2006 15:04:05 ZZZ\")"); + + var minDateStr = "-271821-04-20T00:00:00.000Z"; + var minDate = new Date(-8640000000000000); + + assert.sameValue(minDate.toISOString(), minDateStr, "minDateStr"); + assert.sameValue(Date.parse(minDateStr), minDate.valueOf(), "parse minDateStr"); + + var maxDateStr = "+275760-09-13T00:00:00.000Z"; + var maxDate = new Date(8640000000000000); + + assert.sameValue(maxDate.toISOString(), maxDateStr, "maxDateStr"); + assert.sameValue(Date.parse(maxDateStr), maxDate.valueOf(), "parse maxDateStr"); + + var belowRange = "-271821-04-19T23:59:59.999Z"; + var aboveRange = "+275760-09-13T00:00:00.001Z"; + + assert.sameValue(Date.parse(belowRange), NaN, "parse below minimum time value"); + assert.sameValue(Date.parse(aboveRange), NaN, "parse above maximum time value"); + ` + + l := time.Local + defer func() { + time.Local = l + }() + var err error + time.Local, err = time.LoadLocation("America/New_York") + if err != nil { + t.Fatal(err) + } + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestDateMaxValues(t *testing.T) { + const SCRIPT = ` + assert.sameValue((new Date(0)).setUTCMilliseconds(8.64e15), 8.64e15); + assert.sameValue((new Date(0)).setUTCSeconds(8640000000000), 8.64e15); + assert.sameValue((new Date(0)).setUTCMilliseconds(-8.64e15), -8.64e15); + assert.sameValue((new Date(0)).setUTCSeconds(-8640000000000), -8.64e15); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestDateExport(t *testing.T) { + vm := New() + res, err := vm.RunString(`new Date(1000)`) + if err != nil { + t.Fatal(err) + } + exp := res.Export() + if d, ok := exp.(time.Time); ok { + if d.UnixNano()/1e6 != 1000 { + t.Fatalf("Invalid exported date: %v", d) + } + if loc := d.Location(); loc != time.Local { + t.Fatalf("Invalid timezone: %v", loc) + } + } else { + t.Fatalf("Invalid export type: %T", exp) + } +} + +func TestDateToJSON(t *testing.T) { + const SCRIPT = ` + Date.prototype.toJSON.call({ toISOString: function () { return 1; } }) + ` + testScript(SCRIPT, intToValue(1), t) +} + +func TestDateExportType(t *testing.T) { + vm := New() + v, err := vm.RunString(`new Date()`) + if err != nil { + t.Fatal(err) + } + if typ := v.ExportType(); typ != typeTime { + t.Fatal(typ) + } +} diff --git a/pkg/xscript/engine/destruct.go b/pkg/xscript/engine/destruct.go new file mode 100644 index 0000000..5643240 --- /dev/null +++ b/pkg/xscript/engine/destruct.go @@ -0,0 +1,301 @@ +package engine + +import ( + "reflect" + + "pandax/pkg/xscript/engine/unistring" +) + +type destructKeyedSource struct { + r *Runtime + wrapped Value + usedKeys map[Value]struct{} +} + +func newDestructKeyedSource(r *Runtime, wrapped Value) *destructKeyedSource { + return &destructKeyedSource{ + r: r, + wrapped: wrapped, + } +} + +func (r *Runtime) newDestructKeyedSource(wrapped Value) *Object { + return &Object{ + runtime: r, + self: newDestructKeyedSource(r, wrapped), + } +} + +func (d *destructKeyedSource) w() objectImpl { + return d.wrapped.ToObject(d.r).self +} + +func (d *destructKeyedSource) recordKey(key Value) { + if d.usedKeys == nil { + d.usedKeys = make(map[Value]struct{}) + } + d.usedKeys[key] = struct{}{} +} + +func (d *destructKeyedSource) sortLen() int { + return d.w().sortLen() +} + +func (d *destructKeyedSource) sortGet(i int) Value { + return d.w().sortGet(i) +} + +func (d *destructKeyedSource) swap(i int, i2 int) { + d.w().swap(i, i2) +} + +func (d *destructKeyedSource) className() string { + return d.w().className() +} + +func (d *destructKeyedSource) typeOf() String { + return d.w().typeOf() +} + +func (d *destructKeyedSource) getStr(p unistring.String, receiver Value) Value { + d.recordKey(stringValueFromRaw(p)) + return d.w().getStr(p, receiver) +} + +func (d *destructKeyedSource) getIdx(p valueInt, receiver Value) Value { + d.recordKey(p.toString()) + return d.w().getIdx(p, receiver) +} + +func (d *destructKeyedSource) getSym(p *Symbol, receiver Value) Value { + d.recordKey(p) + return d.w().getSym(p, receiver) +} + +func (d *destructKeyedSource) getOwnPropStr(u unistring.String) Value { + d.recordKey(stringValueFromRaw(u)) + return d.w().getOwnPropStr(u) +} + +func (d *destructKeyedSource) getOwnPropIdx(v valueInt) Value { + d.recordKey(v.toString()) + return d.w().getOwnPropIdx(v) +} + +func (d *destructKeyedSource) getOwnPropSym(symbol *Symbol) Value { + d.recordKey(symbol) + return d.w().getOwnPropSym(symbol) +} + +func (d *destructKeyedSource) setOwnStr(p unistring.String, v Value, throw bool) bool { + return d.w().setOwnStr(p, v, throw) +} + +func (d *destructKeyedSource) setOwnIdx(p valueInt, v Value, throw bool) bool { + return d.w().setOwnIdx(p, v, throw) +} + +func (d *destructKeyedSource) setOwnSym(p *Symbol, v Value, throw bool) bool { + return d.w().setOwnSym(p, v, throw) +} + +func (d *destructKeyedSource) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { + return d.w().setForeignStr(p, v, receiver, throw) +} + +func (d *destructKeyedSource) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) { + return d.w().setForeignIdx(p, v, receiver, throw) +} + +func (d *destructKeyedSource) setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) { + return d.w().setForeignSym(p, v, receiver, throw) +} + +func (d *destructKeyedSource) hasPropertyStr(u unistring.String) bool { + return d.w().hasPropertyStr(u) +} + +func (d *destructKeyedSource) hasPropertyIdx(idx valueInt) bool { + return d.w().hasPropertyIdx(idx) +} + +func (d *destructKeyedSource) hasPropertySym(s *Symbol) bool { + return d.w().hasPropertySym(s) +} + +func (d *destructKeyedSource) hasOwnPropertyStr(u unistring.String) bool { + return d.w().hasOwnPropertyStr(u) +} + +func (d *destructKeyedSource) hasOwnPropertyIdx(v valueInt) bool { + return d.w().hasOwnPropertyIdx(v) +} + +func (d *destructKeyedSource) hasOwnPropertySym(s *Symbol) bool { + return d.w().hasOwnPropertySym(s) +} + +func (d *destructKeyedSource) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + return d.w().defineOwnPropertyStr(name, desc, throw) +} + +func (d *destructKeyedSource) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool { + return d.w().defineOwnPropertyIdx(name, desc, throw) +} + +func (d *destructKeyedSource) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool { + return d.w().defineOwnPropertySym(name, desc, throw) +} + +func (d *destructKeyedSource) deleteStr(name unistring.String, throw bool) bool { + return d.w().deleteStr(name, throw) +} + +func (d *destructKeyedSource) deleteIdx(idx valueInt, throw bool) bool { + return d.w().deleteIdx(idx, throw) +} + +func (d *destructKeyedSource) deleteSym(s *Symbol, throw bool) bool { + return d.w().deleteSym(s, throw) +} + +func (d *destructKeyedSource) assertCallable() (call func(FunctionCall) Value, ok bool) { + return d.w().assertCallable() +} + +func (d *destructKeyedSource) vmCall(vm *vm, n int) { + d.w().vmCall(vm, n) +} + +func (d *destructKeyedSource) assertConstructor() func(args []Value, newTarget *Object) *Object { + return d.w().assertConstructor() +} + +func (d *destructKeyedSource) proto() *Object { + return d.w().proto() +} + +func (d *destructKeyedSource) setProto(proto *Object, throw bool) bool { + return d.w().setProto(proto, throw) +} + +func (d *destructKeyedSource) hasInstance(v Value) bool { + return d.w().hasInstance(v) +} + +func (d *destructKeyedSource) isExtensible() bool { + return d.w().isExtensible() +} + +func (d *destructKeyedSource) preventExtensions(throw bool) bool { + return d.w().preventExtensions(throw) +} + +type destructKeyedSourceIter struct { + d *destructKeyedSource + wrapped iterNextFunc +} + +func (i *destructKeyedSourceIter) next() (propIterItem, iterNextFunc) { + for { + item, next := i.wrapped() + if next == nil { + return item, nil + } + i.wrapped = next + if _, exists := i.d.usedKeys[item.name]; !exists { + return item, i.next + } + } +} + +func (d *destructKeyedSource) iterateStringKeys() iterNextFunc { + return (&destructKeyedSourceIter{ + d: d, + wrapped: d.w().iterateStringKeys(), + }).next +} + +func (d *destructKeyedSource) iterateSymbols() iterNextFunc { + return (&destructKeyedSourceIter{ + d: d, + wrapped: d.w().iterateSymbols(), + }).next +} + +func (d *destructKeyedSource) iterateKeys() iterNextFunc { + return (&destructKeyedSourceIter{ + d: d, + wrapped: d.w().iterateKeys(), + }).next +} + +func (d *destructKeyedSource) export(ctx *objectExportCtx) any { + return d.w().export(ctx) +} + +func (d *destructKeyedSource) exportType() reflect.Type { + return d.w().exportType() +} + +func (d *destructKeyedSource) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + return d.w().exportToMap(dst, typ, ctx) +} + +func (d *destructKeyedSource) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + return d.w().exportToArrayOrSlice(dst, typ, ctx) +} + +func (d *destructKeyedSource) equal(impl objectImpl) bool { + return d.w().equal(impl) +} + +func (d *destructKeyedSource) stringKeys(all bool, accum []Value) []Value { + var next iterNextFunc + if all { + next = d.iterateStringKeys() + } else { + next = (&enumerableIter{ + o: d.wrapped.ToObject(d.r), + wrapped: d.iterateStringKeys(), + }).next + } + for item, next := next(); next != nil; item, next = next() { + accum = append(accum, item.name) + } + return accum +} + +func (d *destructKeyedSource) filterUsedKeys(keys []Value) []Value { + k := 0 + for i, key := range keys { + if _, exists := d.usedKeys[key]; exists { + continue + } + if k != i { + keys[k] = key + } + k++ + } + return keys[:k] +} + +func (d *destructKeyedSource) symbols(all bool, accum []Value) []Value { + return d.filterUsedKeys(d.w().symbols(all, accum)) +} + +func (d *destructKeyedSource) keys(all bool, accum []Value) []Value { + return d.filterUsedKeys(d.w().keys(all, accum)) +} + +func (d *destructKeyedSource) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { + return d.w()._putProp(name, value, writable, enumerable, configurable) +} + +func (d *destructKeyedSource) _putSym(s *Symbol, prop Value) { + d.w()._putSym(s, prop) +} + +func (d *destructKeyedSource) getPrivateEnv(typ *privateEnvType, create bool) *privateElements { + return d.w().getPrivateEnv(typ, create) +} diff --git a/pkg/xscript/engine/file/README.markdown b/pkg/xscript/engine/file/README.markdown new file mode 100644 index 0000000..e9228c2 --- /dev/null +++ b/pkg/xscript/engine/file/README.markdown @@ -0,0 +1,110 @@ +# file +-- + import "github.com/dop251/goja/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 diff --git a/pkg/xscript/engine/file/file.go b/pkg/xscript/engine/file/file.go new file mode 100644 index 0000000..2a0a15c --- /dev/null +++ b/pkg/xscript/engine/file/file.go @@ -0,0 +1,234 @@ +// Package file encapsulates the file abstractions used by the ast & parser. +package file + +import ( + "fmt" + "net/url" + "path" + "sort" + "strings" + "sync" + + "pandax/pkg/xscript/engine/sourcemap" +) + +// 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 Idx int + +// Position describes an arbitrary source position +// including the filename, line, and column location. +type Position struct { + Filename string // The filename where the error occurred, if any + 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 +} + +// 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 +func (self Position) String() string { + str := self.Filename + if self.isValid() { + if str != "" { + str += ":" + } + str += fmt.Sprintf("%d:%d", self.Line, self.Column) + } + if str == "" { + str = "-" + } + return str +} + +// FileSet + +// A FileSet represents a set of source files. +type FileSet struct { + files []*File + last *File +} + +// 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() + file := &File{ + name: filename, + src: src, + base: base, + } + self.files = append(self.files, file) + self.last = file + return base +} + +func (self *FileSet) nextBase() int { + if self.last == nil { + return 1 + } + return self.last.base + len(self.last.src) + 1 +} + +func (self *FileSet) File(idx Idx) *File { + for _, file := range self.files { + if idx <= Idx(file.base+len(file.src)) { + return file + } + } + return nil +} + +// Position converts an Idx in the FileSet into a Position. +func (self *FileSet) Position(idx Idx) Position { + for _, file := range self.files { + if idx <= Idx(file.base+len(file.src)) { + return file.Position(int(idx) - file.base) + } + } + return Position{} +} + +type File struct { + mu sync.Mutex + name string + src string + base int // This will always be 1 or greater + sourceMap *sourcemap.Consumer + lineOffsets []int + lastScannedOffset int +} + +func NewFile(filename, src string, base int) *File { + return &File{ + name: filename, + src: src, + base: base, + } +} + +func (fl *File) Name() string { + return fl.name +} + +func (fl *File) Source() string { + return fl.src +} + +func (fl *File) Base() int { + return fl.base +} + +func (fl *File) SetSourceMap(m *sourcemap.Consumer) { + fl.sourceMap = m +} + +func (fl *File) Position(offset int) Position { + var line int + var lineOffsets []int + fl.mu.Lock() + if offset > fl.lastScannedOffset { + line = fl.scanTo(offset) + lineOffsets = fl.lineOffsets + fl.mu.Unlock() + } else { + lineOffsets = fl.lineOffsets + fl.mu.Unlock() + line = sort.Search(len(lineOffsets), func(x int) bool { return lineOffsets[x] > offset }) - 1 + } + + var lineStart int + if line >= 0 { + lineStart = lineOffsets[line] + } + + row := line + 2 + col := offset - lineStart + 1 + + if fl.sourceMap != nil { + if source, _, row, col, ok := fl.sourceMap.Source(row, col); ok { + sourceUrlStr := source + sourceURL := ResolveSourcemapURL(fl.Name(), source) + if sourceURL != nil { + sourceUrlStr = sourceURL.String() + } + + return Position{ + Filename: sourceUrlStr, + Line: row, + Column: col, + } + } + } + + return Position{ + Filename: fl.name, + Line: row, + Column: col, + } +} + +func ResolveSourcemapURL(basename, source string) *url.URL { + // if the url is absolute(has scheme) there is nothing to do + smURL, err := url.Parse(strings.TrimSpace(source)) + if err == nil && !smURL.IsAbs() { + baseURL, err1 := url.Parse(strings.TrimSpace(basename)) + if err1 == nil && path.IsAbs(baseURL.Path) { + smURL = baseURL.ResolveReference(smURL) + } else { + // pathological case where both are not absolute paths and using Resolve + // as above will produce an absolute one + smURL, _ = url.Parse(path.Join(path.Dir(basename), smURL.Path)) + } + } + return smURL +} + +func findNextLineStart(s string) int { + for pos, ch := range s { + switch ch { + case '\r': + if pos < len(s)-1 && s[pos+1] == '\n' { + return pos + 2 + } + return pos + 1 + case '\n': + return pos + 1 + case '\u2028', '\u2029': + return pos + 3 + } + } + return -1 +} + +func (fl *File) scanTo(offset int) int { + o := fl.lastScannedOffset + for o < offset { + p := findNextLineStart(fl.src[o:]) + if p == -1 { + fl.lastScannedOffset = len(fl.src) + return len(fl.lineOffsets) - 1 + } + o = o + p + fl.lineOffsets = append(fl.lineOffsets, o) + } + fl.lastScannedOffset = o + + if o == offset { + return len(fl.lineOffsets) - 1 + } + + return len(fl.lineOffsets) - 2 +} diff --git a/pkg/xscript/engine/file/file_test.go b/pkg/xscript/engine/file/file_test.go new file mode 100644 index 0000000..c49c312 --- /dev/null +++ b/pkg/xscript/engine/file/file_test.go @@ -0,0 +1,75 @@ +package file + +import ( + "testing" +) + +func TestPosition(t *testing.T) { + const SRC = `line1 +line2 +line3` + f := NewFile("", SRC, 0) + + tests := []struct { + offset int + line int + col int + }{ + {0, 1, 1}, + {2, 1, 3}, + {2, 1, 3}, + {6, 2, 1}, + {7, 2, 2}, + {12, 3, 1}, + {12, 3, 1}, + {13, 3, 2}, + {13, 3, 2}, + {16, 3, 5}, + {17, 3, 6}, + } + + for i, test := range tests { + if p := f.Position(test.offset); p.Line != test.line || p.Column != test.col { + t.Fatalf("%d. Line: %d, col: %d", i, p.Line, p.Column) + } + } +} + +func TestFileConcurrency(t *testing.T) { + const SRC = `line1 +line2 +line3` + f := NewFile("", SRC, 0) + go func() { + f.Position(12) + }() + f.Position(2) +} + +func TestGetSourceFilename(t *testing.T) { + tests := []struct { + source, basename, result string + }{ + {"test.js", "base.js", "test.js"}, + {"test.js", "../base.js", "../test.js"}, + {"test.js", "/somewhere/base.js", "/somewhere/test.js"}, + {"/test.js", "/somewhere/base.js", "/test.js"}, + {"/test.js", "file:///somewhere/base.js", "file:///test.js"}, + {"file:///test.js", "base.js", "file:///test.js"}, + {"file:///test.js", "/somwehere/base.js", "file:///test.js"}, + {"file:///test.js", "file:///somewhere/base.js", "file:///test.js"}, + {"../test.js", "/somewhere/else/base.js", "/somewhere/test.js"}, + {"../test.js", "file:///somewhere/else/base.js", "file:///somewhere/test.js"}, + {"../test.js", "https://example.com/somewhere/else/base.js", "https://example.com/somewhere/test.js"}, + {"\ntest.js", "base123.js", "test.js"}, + {"\rtest2.js\t\n ", "base123.js", "test2.js"}, + // TODO find something that won't parse + } + for _, test := range tests { + resultURL := ResolveSourcemapURL(test.basename, test.source) + result := resultURL.String() + if result != test.result { + t.Fatalf("source: %q, basename %q produced %q instead of %q", test.source, test.basename, result, test.result) + } + } +} diff --git a/pkg/xscript/engine/ftoa/LICENSE_LUCENE b/pkg/xscript/engine/ftoa/LICENSE_LUCENE new file mode 100644 index 0000000..c8da489 --- /dev/null +++ b/pkg/xscript/engine/ftoa/LICENSE_LUCENE @@ -0,0 +1,21 @@ +Copyright (C) 1998, 1999 by Lucent Technologies +All Rights Reserved + +Permission to use, copy, modify, and distribute this software and +its documentation for any purpose and without fee is hereby +granted, provided that the above copyright notice appear in all +copies and that both that the copyright notice and this +permission notice and warranty disclaimer appear in supporting +documentation, and that the name of Lucent or any of its entities +not be used in advertising or publicity pertaining to +distribution of the software without specific, written prior +permission. + +LUCENT DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, +INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. +IN NO EVENT SHALL LUCENT OR ANY OF ITS ENTITIES BE LIABLE FOR ANY +SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES +WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER +IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, +ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF +THIS SOFTWARE. diff --git a/pkg/xscript/engine/ftoa/common.go b/pkg/xscript/engine/ftoa/common.go new file mode 100644 index 0000000..b59a93c --- /dev/null +++ b/pkg/xscript/engine/ftoa/common.go @@ -0,0 +1,150 @@ +/* +Package ftoa provides ECMAScript-compliant floating point number conversion to string. + +It contains code ported from Rhino (https://github.com/mozilla/rhino/blob/master/src/org/mozilla/javascript/DToA.java) +as well as from the original code by David M. Gay. + +See LICENSE_LUCENE for the original copyright message and disclaimer. +*/ +package ftoa + +import ( + "math" +) + +const ( + frac_mask = 0xfffff + exp_shift = 20 + exp_msk1 = 0x100000 + + exp_shiftL = 52 + exp_mask_shifted = 0x7ff + frac_maskL = 0xfffffffffffff + exp_msk1L = 0x10000000000000 + exp_shift1 = 20 + exp_mask = 0x7ff00000 + bias = 1023 + p = 53 + bndry_mask = 0xfffff + log2P = 1 +) + +func lo0bits(x uint32) (k int) { + + if (x & 7) != 0 { + if (x & 1) != 0 { + return 0 + } + if (x & 2) != 0 { + return 1 + } + return 2 + } + if (x & 0xffff) == 0 { + k = 16 + x >>= 16 + } + if (x & 0xff) == 0 { + k += 8 + x >>= 8 + } + if (x & 0xf) == 0 { + k += 4 + x >>= 4 + } + if (x & 0x3) == 0 { + k += 2 + x >>= 2 + } + if (x & 1) == 0 { + k++ + x >>= 1 + if (x & 1) == 0 { + return 32 + } + } + return +} + +func hi0bits(x uint32) (k int) { + + if (x & 0xffff0000) == 0 { + k = 16 + x <<= 16 + } + if (x & 0xff000000) == 0 { + k += 8 + x <<= 8 + } + if (x & 0xf0000000) == 0 { + k += 4 + x <<= 4 + } + if (x & 0xc0000000) == 0 { + k += 2 + x <<= 2 + } + if (x & 0x80000000) == 0 { + k++ + if (x & 0x40000000) == 0 { + return 32 + } + } + return +} + +func stuffBits(bits []byte, offset int, val uint32) { + bits[offset] = byte(val >> 24) + bits[offset+1] = byte(val >> 16) + bits[offset+2] = byte(val >> 8) + bits[offset+3] = byte(val) +} + +func d2b(d float64, b []byte) (e, bits int, dblBits []byte) { + dBits := math.Float64bits(d) + d0 := uint32(dBits >> 32) + d1 := uint32(dBits) + + z := d0 & frac_mask + d0 &= 0x7fffffff /* clear sign bit, which we ignore */ + + var de, k, i int + if de = int(d0 >> exp_shift); de != 0 { + z |= exp_msk1 + } + + y := d1 + if y != 0 { + dblBits = b[:8] + k = lo0bits(y) + y >>= k + if k != 0 { + stuffBits(dblBits, 4, y|z<<(32-k)) + z >>= k + } else { + stuffBits(dblBits, 4, y) + } + stuffBits(dblBits, 0, z) + if z != 0 { + i = 2 + } else { + i = 1 + } + } else { + dblBits = b[:4] + k = lo0bits(z) + z >>= k + stuffBits(dblBits, 0, z) + k += 32 + i = 1 + } + + if de != 0 { + e = de - bias - (p - 1) + k + bits = p - k + } else { + e = de - bias - (p - 1) + 1 + k + bits = 32*i - hi0bits(z) + } + return +} diff --git a/pkg/xscript/engine/ftoa/ftoa.go b/pkg/xscript/engine/ftoa/ftoa.go new file mode 100644 index 0000000..dd266ec --- /dev/null +++ b/pkg/xscript/engine/ftoa/ftoa.go @@ -0,0 +1,699 @@ +package ftoa + +import ( + "math" + "math/big" +) + +const ( + exp_11 = 0x3ff00000 + frac_mask1 = 0xfffff + bletch = 0x10 + quick_max = 14 + int_max = 14 +) + +var ( + tens = [...]float64{ + 1e0, 1e1, 1e2, 1e3, 1e4, 1e5, 1e6, 1e7, 1e8, 1e9, + 1e10, 1e11, 1e12, 1e13, 1e14, 1e15, 1e16, 1e17, 1e18, 1e19, + 1e20, 1e21, 1e22, + } + + bigtens = [...]float64{1e16, 1e32, 1e64, 1e128, 1e256} + + big5 = big.NewInt(5) + big10 = big.NewInt(10) + + p05 = []*big.Int{big5, big.NewInt(25), big.NewInt(125)} + pow5Cache [7]*big.Int + + dtoaModes = []int{ + ModeStandard: 0, + ModeStandardExponential: 0, + ModeFixed: 3, + ModeExponential: 2, + ModePrecision: 2, + } +) + +/* +d must be > 0 and must not be Inf + +mode: + + 0 ==> shortest string that yields d when read in + and rounded to nearest. + 1 ==> like 0, but with Steele & White stopping rule; + e.g. with IEEE P754 arithmetic , mode 0 gives + 1e23 whereas mode 1 gives 9.999999999999999e22. + 2 ==> max(1,ndigits) significant digits. This gives a + return value similar to that of ecvt, except + that trailing zeros are suppressed. + 3 ==> through ndigits past the decimal point. This + gives a return value similar to that from fcvt, + except that trailing zeros are suppressed, and + ndigits can be negative. + 4,5 ==> similar to 2 and 3, respectively, but (in + round-nearest mode) with the tests of mode 0 to + possibly return a shorter string that rounds to d. + With IEEE arithmetic and compilation with + -DHonor_FLT_ROUNDS, modes 4 and 5 behave the same + as modes 2 and 3 when FLT_ROUNDS != 1. + 6-9 ==> Debugging modes similar to mode - 4: don't try + fast floating-point estimate (if applicable). + + Values of mode other than 0-9 are treated as mode 0. +*/ +func ftoa(d float64, mode int, biasUp bool, ndigits int, buf []byte) ([]byte, int) { + startPos := len(buf) + dblBits := make([]byte, 0, 8) + be, bbits, dblBits := d2b(d, dblBits) + + dBits := math.Float64bits(d) + word0 := uint32(dBits >> 32) + word1 := uint32(dBits) + + i := int((word0 >> exp_shift1) & (exp_mask >> exp_shift1)) + var d2 float64 + var denorm bool + if i != 0 { + d2 = setWord0(d, (word0&frac_mask1)|exp_11) + i -= bias + denorm = false + } else { + /* d is denormalized */ + i = bbits + be + (bias + (p - 1) - 1) + var x uint64 + if i > 32 { + x = uint64(word0)<<(64-i) | uint64(word1)>>(i-32) + } else { + x = uint64(word1) << (32 - i) + } + d2 = setWord0(float64(x), uint32((x>>32)-31*exp_mask)) + i -= (bias + (p - 1) - 1) + 1 + denorm = true + } + /* At this point d = f*2^i, where 1 <= f < 2. d2 is an approximation of f. */ + ds := (d2-1.5)*0.289529654602168 + 0.1760912590558 + float64(i)*0.301029995663981 + k := int(ds) + if ds < 0.0 && ds != float64(k) { + k-- /* want k = floor(ds) */ + } + k_check := true + if k >= 0 && k < len(tens) { + if d < tens[k] { + k-- + } + k_check = false + } + /* At this point floor(log10(d)) <= k <= floor(log10(d))+1. + If k_check is zero, we're guaranteed that k = floor(log10(d)). */ + j := bbits - i - 1 + var b2, s2, b5, s5 int + /* At this point d = b/2^j, where b is an odd integer. */ + if j >= 0 { + b2 = 0 + s2 = j + } else { + b2 = -j + s2 = 0 + } + if k >= 0 { + b5 = 0 + s5 = k + s2 += k + } else { + b2 -= k + b5 = -k + s5 = 0 + } + /* At this point d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5), where b is an odd integer, + b2 >= 0, b5 >= 0, s2 >= 0, and s5 >= 0. */ + if mode < 0 || mode > 9 { + mode = 0 + } + try_quick := true + if mode > 5 { + mode -= 4 + try_quick = false + } + leftright := true + var ilim, ilim1 int + switch mode { + case 0, 1: + ilim, ilim1 = -1, -1 + ndigits = 0 + case 2: + leftright = false + fallthrough + case 4: + if ndigits <= 0 { + ndigits = 1 + } + ilim, ilim1 = ndigits, ndigits + case 3: + leftright = false + fallthrough + case 5: + i = ndigits + k + 1 + ilim = i + ilim1 = i - 1 + } + /* ilim is the maximum number of significant digits we want, based on k and ndigits. */ + /* ilim1 is the maximum number of significant digits we want, based on k and ndigits, + when it turns out that k was computed too high by one. */ + fast_failed := false + if ilim >= 0 && ilim <= quick_max && try_quick { + + /* Try to get by with floating-point arithmetic. */ + + i = 0 + d2 = d + k0 := k + ilim0 := ilim + ieps := 2 /* conservative */ + /* Divide d by 10^k, keeping track of the roundoff error and avoiding overflows. */ + if k > 0 { + ds = tens[k&0xf] + j = k >> 4 + if (j & bletch) != 0 { + /* prevent overflows */ + j &= bletch - 1 + d /= bigtens[len(bigtens)-1] + ieps++ + } + for ; j != 0; i++ { + if (j & 1) != 0 { + ieps++ + ds *= bigtens[i] + } + j >>= 1 + } + d /= ds + } else if j1 := -k; j1 != 0 { + d *= tens[j1&0xf] + for j = j1 >> 4; j != 0; i++ { + if (j & 1) != 0 { + ieps++ + d *= bigtens[i] + } + j >>= 1 + } + } + /* Check that k was computed correctly. */ + if k_check && d < 1.0 && ilim > 0 { + if ilim1 <= 0 { + fast_failed = true + } else { + ilim = ilim1 + k-- + d *= 10. + ieps++ + } + } + /* eps bounds the cumulative error. */ + eps := float64(ieps)*d + 7.0 + eps = setWord0(eps, _word0(eps)-(p-1)*exp_msk1) + if ilim == 0 { + d -= 5.0 + if d > eps { + buf = append(buf, '1') + k++ + return buf, k + 1 + } + if d < -eps { + buf = append(buf, '0') + return buf, 1 + } + fast_failed = true + } + if !fast_failed { + fast_failed = true + if leftright { + /* Use Steele & White method of only + * generating digits needed. + */ + eps = 0.5/tens[ilim-1] - eps + for i = 0; ; { + l := int64(d) + d -= float64(l) + buf = append(buf, byte('0'+l)) + if d < eps { + return buf, k + 1 + } + if 1.0-d < eps { + buf, k = bumpUp(buf, k) + return buf, k + 1 + } + i++ + if i >= ilim { + break + } + eps *= 10.0 + d *= 10.0 + } + } else { + /* Generate ilim digits, then fix them up. */ + eps *= tens[ilim-1] + for i = 1; ; i++ { + l := int64(d) + d -= float64(l) + buf = append(buf, byte('0'+l)) + if i == ilim { + if d > 0.5+eps { + buf, k = bumpUp(buf, k) + return buf, k + 1 + } else if d < 0.5-eps { + buf = stripTrailingZeroes(buf, startPos) + return buf, k + 1 + } + break + } + d *= 10.0 + } + } + } + if fast_failed { + buf = buf[:startPos] + d = d2 + k = k0 + ilim = ilim0 + } + } + + /* Do we have a "small" integer? */ + if be >= 0 && k <= int_max { + /* Yes. */ + ds = tens[k] + if ndigits < 0 && ilim <= 0 { + if ilim < 0 || d < 5*ds || (!biasUp && d == 5*ds) { + buf = buf[:startPos] + buf = append(buf, '0') + return buf, 1 + } + buf = append(buf, '1') + k++ + return buf, k + 1 + } + for i = 1; ; i++ { + l := int64(d / ds) + d -= float64(l) * ds + buf = append(buf, byte('0'+l)) + if i == ilim { + d += d + if (d > ds) || (d == ds && (((l & 1) != 0) || biasUp)) { + buf, k = bumpUp(buf, k) + } + break + } + d *= 10.0 + if d == 0 { + break + } + } + return buf, k + 1 + } + + m2 := b2 + m5 := b5 + var mhi, mlo *big.Int + if leftright { + if mode < 2 { + if denorm { + i = be + (bias + (p - 1) - 1 + 1) + } else { + i = 1 + p - bbits + } + /* i is 1 plus the number of trailing zero bits in d's significand. Thus, + (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 lsb of d)/10^k. */ + } else { + j = ilim - 1 + if m5 >= j { + m5 -= j + } else { + j -= m5 + s5 += j + b5 += j + m5 = 0 + } + i = ilim + if i < 0 { + m2 -= i + i = 0 + } + /* (2^m2 * 5^m5) / (2^(s2+i) * 5^s5) = (1/2 * 10^(1-ilim))/10^k. */ + } + b2 += i + s2 += i + mhi = big.NewInt(1) + /* (mhi * 2^m2 * 5^m5) / (2^s2 * 5^s5) = one-half of last printed (when mode >= 2) or + input (when mode < 2) significant digit, divided by 10^k. */ + } + + /* We still have d/10^k = (b * 2^b2 * 5^b5) / (2^s2 * 5^s5). Reduce common factors in + b2, m2, and s2 without changing the equalities. */ + if m2 > 0 && s2 > 0 { + if m2 < s2 { + i = m2 + } else { + i = s2 + } + b2 -= i + m2 -= i + s2 -= i + } + + b := new(big.Int).SetBytes(dblBits) + /* Fold b5 into b and m5 into mhi. */ + if b5 > 0 { + if leftright { + if m5 > 0 { + pow5mult(mhi, m5) + b.Mul(mhi, b) + } + j = b5 - m5 + if j != 0 { + pow5mult(b, j) + } + } else { + pow5mult(b, b5) + } + } + /* Now we have d/10^k = (b * 2^b2) / (2^s2 * 5^s5) and + (mhi * 2^m2) / (2^s2 * 5^s5) = one-half of last printed or input significant digit, divided by 10^k. */ + + S := big.NewInt(1) + if s5 > 0 { + pow5mult(S, s5) + } + /* Now we have d/10^k = (b * 2^b2) / (S * 2^s2) and + (mhi * 2^m2) / (S * 2^s2) = one-half of last printed or input significant digit, divided by 10^k. */ + + /* Check for special case that d is a normalized power of 2. */ + spec_case := false + if mode < 2 { + if (_word1(d) == 0) && ((_word0(d) & bndry_mask) == 0) && + ((_word0(d) & (exp_mask & (exp_mask << 1))) != 0) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the decimal output string's value is less than d. */ + b2 += log2P + s2 += log2P + spec_case = true + } + } + + /* Arrange for convenient computation of quotients: + * shift left if necessary so divisor has 4 leading 0 bits. + * + * Perhaps we should just compute leading 28 bits of S once + * and for all and pass them and a shift to quorem, so it + * can do shifts and ors to compute the numerator for q. + */ + var zz int + if s5 != 0 { + S_bytes := S.Bytes() + var S_hiWord uint32 + for idx := 0; idx < 4; idx++ { + S_hiWord = S_hiWord << 8 + if idx < len(S_bytes) { + S_hiWord |= uint32(S_bytes[idx]) + } + } + zz = 32 - hi0bits(S_hiWord) + } else { + zz = 1 + } + i = (zz + s2) & 0x1f + if i != 0 { + i = 32 - i + } + /* i is the number of leading zero bits in the most significant word of S*2^s2. */ + if i > 4 { + i -= 4 + b2 += i + m2 += i + s2 += i + } else if i < 4 { + i += 28 + b2 += i + m2 += i + s2 += i + } + /* Now S*2^s2 has exactly four leading zero bits in its most significant word. */ + if b2 > 0 { + b = b.Lsh(b, uint(b2)) + } + if s2 > 0 { + S.Lsh(S, uint(s2)) + } + /* Now we have d/10^k = b/S and + (mhi * 2^m2) / S = maximum acceptable error, divided by 10^k. */ + if k_check { + if b.Cmp(S) < 0 { + k-- + b.Mul(b, big10) /* we botched the k estimate */ + if leftright { + mhi.Mul(mhi, big10) + } + ilim = ilim1 + } + } + /* At this point 1 <= d/10^k = b/S < 10. */ + + if ilim <= 0 && mode > 2 { + /* We're doing fixed-mode output and d is less than the minimum nonzero output in this mode. + Output either zero or the minimum nonzero output depending on which is closer to d. */ + if ilim >= 0 { + i = b.Cmp(S.Mul(S, big5)) + } + if ilim < 0 || i < 0 || i == 0 && !biasUp { + /* Always emit at least one digit. If the number appears to be zero + using the current mode, then emit one '0' digit and set decpt to 1. */ + buf = buf[:startPos] + buf = append(buf, '0') + return buf, 1 + } + buf = append(buf, '1') + k++ + return buf, k + 1 + } + + var dig byte + if leftright { + if m2 > 0 { + mhi.Lsh(mhi, uint(m2)) + } + + /* Compute mlo -- check for special case + * that d is a normalized power of 2. + */ + + mlo = mhi + if spec_case { + mhi = mlo + mhi = new(big.Int).Lsh(mhi, log2P) + } + /* mlo/S = maximum acceptable error, divided by 10^k, if the output is less than d. */ + /* mhi/S = maximum acceptable error, divided by 10^k, if the output is greater than d. */ + var z, delta big.Int + for i = 1; ; i++ { + z.DivMod(b, S, b) + dig = byte(z.Int64() + '0') + /* Do we yet have the shortest decimal string + * that will round to d? + */ + j = b.Cmp(mlo) + /* j is b/S compared with mlo/S. */ + delta.Sub(S, mhi) + var j1 int + if delta.Sign() <= 0 { + j1 = 1 + } else { + j1 = b.Cmp(&delta) + } + /* j1 is b/S compared with 1 - mhi/S. */ + if (j1 == 0) && (mode == 0) && ((_word1(d) & 1) == 0) { + if dig == '9' { + var flag bool + buf = append(buf, '9') + if buf, flag = roundOff(buf, startPos); flag { + k++ + buf = append(buf, '1') + } + return buf, k + 1 + } + if j > 0 { + dig++ + } + buf = append(buf, dig) + return buf, k + 1 + } + if (j < 0) || ((j == 0) && (mode == 0) && ((_word1(d) & 1) == 0)) { + if j1 > 0 { + /* Either dig or dig+1 would work here as the least significant decimal digit. + Use whichever would produce a decimal value closer to d. */ + b.Lsh(b, 1) + j1 = b.Cmp(S) + if (j1 > 0) || (j1 == 0 && (((dig & 1) == 1) || biasUp)) { + dig++ + if dig == '9' { + buf = append(buf, '9') + buf, flag := roundOff(buf, startPos) + if flag { + k++ + buf = append(buf, '1') + } + return buf, k + 1 + } + } + } + buf = append(buf, dig) + return buf, k + 1 + } + if j1 > 0 { + if dig == '9' { /* possible if i == 1 */ + buf = append(buf, '9') + buf, flag := roundOff(buf, startPos) + if flag { + k++ + buf = append(buf, '1') + } + return buf, k + 1 + } + buf = append(buf, dig+1) + return buf, k + 1 + } + buf = append(buf, dig) + if i == ilim { + break + } + b.Mul(b, big10) + if mlo == mhi { + mhi.Mul(mhi, big10) + } else { + mlo.Mul(mlo, big10) + mhi.Mul(mhi, big10) + } + } + } else { + var z big.Int + for i = 1; ; i++ { + z.DivMod(b, S, b) + dig = byte(z.Int64() + '0') + buf = append(buf, dig) + if i >= ilim { + break + } + + b.Mul(b, big10) + } + } + /* Round off last digit */ + + b.Lsh(b, 1) + j = b.Cmp(S) + if (j > 0) || (j == 0 && (((dig & 1) == 1) || biasUp)) { + var flag bool + buf, flag = roundOff(buf, startPos) + if flag { + k++ + buf = append(buf, '1') + return buf, k + 1 + } + } else { + buf = stripTrailingZeroes(buf, startPos) + } + + return buf, k + 1 +} + +func bumpUp(buf []byte, k int) ([]byte, int) { + var lastCh byte + stop := 0 + if len(buf) > 0 && buf[0] == '-' { + stop = 1 + } + for { + lastCh = buf[len(buf)-1] + buf = buf[:len(buf)-1] + if lastCh != '9' { + break + } + if len(buf) == stop { + k++ + lastCh = '0' + break + } + } + buf = append(buf, lastCh+1) + return buf, k +} + +func setWord0(d float64, w uint32) float64 { + dBits := math.Float64bits(d) + return math.Float64frombits(uint64(w)<<32 | dBits&0xffffffff) +} + +func _word0(d float64) uint32 { + dBits := math.Float64bits(d) + return uint32(dBits >> 32) +} + +func _word1(d float64) uint32 { + dBits := math.Float64bits(d) + return uint32(dBits) +} + +func stripTrailingZeroes(buf []byte, startPos int) []byte { + bl := len(buf) - 1 + for bl >= startPos && buf[bl] == '0' { + bl-- + } + return buf[:bl+1] +} + +/* Set b = b * 5^k. k must be nonnegative. */ +func pow5mult(b *big.Int, k int) *big.Int { + if k < (1 << (len(pow5Cache) + 2)) { + i := k & 3 + if i != 0 { + b.Mul(b, p05[i-1]) + } + k >>= 2 + i = 0 + for { + if k&1 != 0 { + b.Mul(b, pow5Cache[i]) + } + k >>= 1 + if k == 0 { + break + } + i++ + } + return b + } + return b.Mul(b, new(big.Int).Exp(big5, big.NewInt(int64(k)), nil)) +} + +func roundOff(buf []byte, startPos int) ([]byte, bool) { + i := len(buf) + for i != startPos { + i-- + if buf[i] != '9' { + buf[i]++ + return buf[:i+1], false + } + } + return buf[:startPos], true +} + +func init() { + p := big.NewInt(625) + pow5Cache[0] = p + for i := 1; i < len(pow5Cache); i++ { + p = new(big.Int).Mul(p, p) + pow5Cache[i] = p + } +} diff --git a/pkg/xscript/engine/ftoa/ftobasestr.go b/pkg/xscript/engine/ftoa/ftobasestr.go new file mode 100644 index 0000000..9dc9b2d --- /dev/null +++ b/pkg/xscript/engine/ftoa/ftobasestr.go @@ -0,0 +1,153 @@ +package ftoa + +import ( + "fmt" + "math" + "math/big" + "strconv" + "strings" +) + +const ( + digits = "0123456789abcdefghijklmnopqrstuvwxyz" +) + +func FToBaseStr(num float64, radix int) string { + var negative bool + if num < 0 { + num = -num + negative = true + } + + dfloor := math.Floor(num) + ldfloor := int64(dfloor) + var intDigits string + if dfloor == float64(ldfloor) { + if negative { + ldfloor = -ldfloor + } + intDigits = strconv.FormatInt(ldfloor, radix) + } else { + floorBits := math.Float64bits(num) + exp := int(floorBits>>exp_shiftL) & exp_mask_shifted + var mantissa int64 + if exp == 0 { + mantissa = int64((floorBits & frac_maskL) << 1) + } else { + mantissa = int64((floorBits & frac_maskL) | exp_msk1L) + } + + if negative { + mantissa = -mantissa + } + exp -= 1075 + x := big.NewInt(mantissa) + if exp > 0 { + x.Lsh(x, uint(exp)) + } else if exp < 0 { + x.Rsh(x, uint(-exp)) + } + intDigits = x.Text(radix) + } + + if num == dfloor { + // No fraction part + return intDigits + } else { + /* We have a fraction. */ + var buffer strings.Builder + buffer.WriteString(intDigits) + buffer.WriteByte('.') + df := num - dfloor + + dBits := math.Float64bits(num) + word0 := uint32(dBits >> 32) + word1 := uint32(dBits) + + dblBits := make([]byte, 0, 8) + e, _, dblBits := d2b(df, dblBits) + // JS_ASSERT(e < 0); + /* At this point df = b * 2^e. e must be less than zero because 0 < df < 1. */ + + s2 := -int((word0 >> exp_shift1) & (exp_mask >> exp_shift1)) + if s2 == 0 { + s2 = -1 + } + s2 += bias + p + /* 1/2^s2 = (nextDouble(d) - d)/2 */ + // JS_ASSERT(-s2 < e); + if -s2 >= e { + panic(fmt.Errorf("-s2 >= e: %d, %d", -s2, e)) + } + mlo := big.NewInt(1) + mhi := mlo + if (word1 == 0) && ((word0 & bndry_mask) == 0) && ((word0 & (exp_mask & (exp_mask << 1))) != 0) { + /* The special case. Here we want to be within a quarter of the last input + significant digit instead of one half of it when the output string's value is less than d. */ + s2 += log2P + mhi = big.NewInt(1 << log2P) + } + + b := new(big.Int).SetBytes(dblBits) + b.Lsh(b, uint(e+s2)) + s := big.NewInt(1) + s.Lsh(s, uint(s2)) + /* At this point we have the following: + * s = 2^s2; + * 1 > df = b/2^s2 > 0; + * (d - prevDouble(d))/2 = mlo/2^s2; + * (nextDouble(d) - d)/2 = mhi/2^s2. */ + bigBase := big.NewInt(int64(radix)) + + done := false + m := &big.Int{} + delta := &big.Int{} + for !done { + b.Mul(b, bigBase) + b.DivMod(b, s, m) + digit := byte(b.Int64()) + b, m = m, b + mlo.Mul(mlo, bigBase) + if mlo != mhi { + mhi.Mul(mhi, bigBase) + } + + /* Do we yet have the shortest string that will round to d? */ + j := b.Cmp(mlo) + /* j is b/2^s2 compared with mlo/2^s2. */ + + delta.Sub(s, mhi) + var j1 int + if delta.Sign() <= 0 { + j1 = 1 + } else { + j1 = b.Cmp(delta) + } + /* j1 is b/2^s2 compared with 1 - mhi/2^s2. */ + if j1 == 0 && (word1&1) == 0 { + if j > 0 { + digit++ + } + done = true + } else if j < 0 || (j == 0 && ((word1 & 1) == 0)) { + if j1 > 0 { + /* Either dig or dig+1 would work here as the least significant digit. + Use whichever would produce an output value closer to d. */ + b.Lsh(b, 1) + j1 = b.Cmp(s) + if j1 > 0 { /* The even test (|| (j1 == 0 && (digit & 1))) is not here because it messes up odd base output such as 3.5 in base 3. */ + digit++ + } + } + done = true + } else if j1 > 0 { + digit++ + done = true + } + // JS_ASSERT(digit < (uint32)base); + buffer.WriteByte(digits[digit]) + } + + return buffer.String() + } +} diff --git a/pkg/xscript/engine/ftoa/ftobasestr_test.go b/pkg/xscript/engine/ftoa/ftobasestr_test.go new file mode 100644 index 0000000..0a3fecb --- /dev/null +++ b/pkg/xscript/engine/ftoa/ftobasestr_test.go @@ -0,0 +1,9 @@ +package ftoa + +import "testing" + +func TestFToBaseStr(t *testing.T) { + if s := FToBaseStr(0.8466400793967279, 36); s != "0.uh8u81s3fz" { + t.Fatal(s) + } +} diff --git a/pkg/xscript/engine/ftoa/ftostr.go b/pkg/xscript/engine/ftoa/ftostr.go new file mode 100644 index 0000000..d93b166 --- /dev/null +++ b/pkg/xscript/engine/ftoa/ftostr.go @@ -0,0 +1,147 @@ +package ftoa + +import ( + "math" + "strconv" + + "pandax/pkg/xscript/engine/ftoa/internal/fast" +) + +type FToStrMode int + +const ( + // Either fixed or exponential format; round-trip + ModeStandard FToStrMode = iota + // Always exponential format; round-trip + ModeStandardExponential + // Round to digits after the decimal point; exponential if number is large + ModeFixed + // Always exponential format; significant digits + ModeExponential + // Either fixed or exponential format; significant digits + ModePrecision +) + +func insert(b []byte, p int, c byte) []byte { + b = append(b, 0) + copy(b[p+1:], b[p:]) + b[p] = c + return b +} + +func expand(b []byte, delta int) []byte { + newLen := len(b) + delta + if newLen <= cap(b) { + return b[:newLen] + } + b1 := make([]byte, newLen) + copy(b1, b) + return b1 +} + +func FToStr(d float64, mode FToStrMode, precision int, buffer []byte) []byte { + if math.IsNaN(d) { + buffer = append(buffer, "NaN"...) + return buffer + } + if math.IsInf(d, 0) { + if math.Signbit(d) { + buffer = append(buffer, '-') + } + buffer = append(buffer, "Infinity"...) + return buffer + } + + if mode == ModeFixed && (d >= 1e21 || d <= -1e21) { + mode = ModeStandard + } + + var decPt int + var ok bool + startPos := len(buffer) + + if d != 0 { // also matches -0 + if d < 0 { + buffer = append(buffer, '-') + d = -d + startPos++ + } + switch mode { + case ModeStandard, ModeStandardExponential: + buffer, decPt, ok = fast.Dtoa(d, fast.ModeShortest, 0, buffer) + case ModeExponential, ModePrecision: + buffer, decPt, ok = fast.Dtoa(d, fast.ModePrecision, precision, buffer) + } + } else { + buffer = append(buffer, '0') + decPt, ok = 1, true + } + if !ok { + buffer, decPt = ftoa(d, dtoaModes[mode], mode >= ModeFixed, precision, buffer) + } + exponentialNotation := false + minNDigits := 0 /* Minimum number of significand digits required by mode and precision */ + nDigits := len(buffer) - startPos + + switch mode { + case ModeStandard: + if decPt < -5 || decPt > 21 { + exponentialNotation = true + } else { + minNDigits = decPt + } + case ModeFixed: + if precision >= 0 { + minNDigits = decPt + precision + } else { + minNDigits = decPt + } + case ModeExponential: + // JS_ASSERT(precision > 0); + minNDigits = precision + fallthrough + case ModeStandardExponential: + exponentialNotation = true + case ModePrecision: + // JS_ASSERT(precision > 0); + minNDigits = precision + if decPt < -5 || decPt > precision { + exponentialNotation = true + } + } + + for nDigits < minNDigits { + buffer = append(buffer, '0') + nDigits++ + } + + if exponentialNotation { + /* Insert a decimal point if more than one significand digit */ + if nDigits != 1 { + buffer = insert(buffer, startPos+1, '.') + } + buffer = append(buffer, 'e') + if decPt-1 >= 0 { + buffer = append(buffer, '+') + } + buffer = strconv.AppendInt(buffer, int64(decPt-1), 10) + } else if decPt != nDigits { + /* Some kind of a fraction in fixed notation */ + // JS_ASSERT(decPt <= nDigits); + if decPt > 0 { + /* dd...dd . dd...dd */ + buffer = insert(buffer, startPos+decPt, '.') + } else { + /* 0 . 00...00dd...dd */ + buffer = expand(buffer, 2-decPt) + copy(buffer[startPos+2-decPt:], buffer[startPos:]) + buffer[startPos] = '0' + buffer[startPos+1] = '.' + for i := startPos + 2; i < startPos+2-decPt; i++ { + buffer[i] = '0' + } + } + } + + return buffer +} diff --git a/pkg/xscript/engine/ftoa/ftostr_test.go b/pkg/xscript/engine/ftoa/ftostr_test.go new file mode 100644 index 0000000..db2441f --- /dev/null +++ b/pkg/xscript/engine/ftoa/ftostr_test.go @@ -0,0 +1,92 @@ +package ftoa + +import ( + "math" + "strconv" + "testing" +) + +func _testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) { + buf := FToStr(num, mode, precision, nil) + if s := string(buf); s != expected { + t.Fatalf("expected: '%s', actual: '%s", expected, s) + } + if !math.IsNaN(num) && num != 0 && !math.Signbit(num) { + _testFToStr(-num, mode, precision, "-"+expected, t) + } +} + +func testFToStr(num float64, mode FToStrMode, precision int, expected string, t *testing.T) { + t.Run("", func(t *testing.T) { + t.Parallel() + _testFToStr(num, mode, precision, expected, t) + }) +} + +func TestDtostr(t *testing.T) { + testFToStr(0, ModeStandard, 0, "0", t) + testFToStr(1, ModeStandard, 0, "1", t) + testFToStr(9007199254740991, ModeStandard, 0, "9007199254740991", t) + testFToStr(math.MaxInt64, ModeStandardExponential, 0, "9.223372036854776e+18", t) + testFToStr(1e-5, ModeFixed, 1, "0.0", t) + testFToStr(8.85, ModeExponential, 2, "8.8e+0", t) + testFToStr(885, ModeExponential, 2, "8.9e+2", t) + testFToStr(25, ModeExponential, 1, "3e+1", t) + testFToStr(1e-6, ModeFixed, 7, "0.0000010", t) + testFToStr(math.Pi, ModeStandardExponential, 0, "3.141592653589793e+0", t) + testFToStr(math.Inf(1), ModeStandard, 0, "Infinity", t) + testFToStr(math.NaN(), ModeStandard, 0, "NaN", t) + testFToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, "4.940656458412465441765687928682213723651e-324", t) + testFToStr(3.5844466002796428e+298, ModeStandard, 0, "3.5844466002796428e+298", t) + testFToStr(math.Float64frombits(0x0010000000000000), ModeStandard, 0, "2.2250738585072014e-308", t) // smallest normal + testFToStr(math.Float64frombits(0x000FFFFFFFFFFFFF), ModeStandard, 0, "2.225073858507201e-308", t) // largest denormal + testFToStr(4294967272.0, ModePrecision, 14, "4294967272.0000", t) +} + +func BenchmarkDtostrSmall(b *testing.B) { + var buf [128]byte + b.ReportAllocs() + for i := 0; i < b.N; i++ { + FToStr(math.Pi, ModeStandardExponential, 0, buf[:0]) + } +} + +func BenchmarkDtostrShort(b *testing.B) { + var buf [128]byte + b.ReportAllocs() + for i := 0; i < b.N; i++ { + FToStr(3.1415, ModeStandard, 0, buf[:0]) + } +} + +func BenchmarkDtostrFixed(b *testing.B) { + var buf [128]byte + b.ReportAllocs() + for i := 0; i < b.N; i++ { + FToStr(math.Pi, ModeFixed, 4, buf[:0]) + } +} + +func BenchmarkDtostrBig(b *testing.B) { + var buf [128]byte + b.ReportAllocs() + for i := 0; i < b.N; i++ { + FToStr(math.SmallestNonzeroFloat64, ModeExponential, 40, buf[:0]) + } +} + +func BenchmarkAppendFloatBig(b *testing.B) { + var buf [128]byte + b.ReportAllocs() + for i := 0; i < b.N; i++ { + strconv.AppendFloat(buf[:0], math.SmallestNonzeroFloat64, 'e', 40, 64) + } +} + +func BenchmarkAppendFloatSmall(b *testing.B) { + var buf [128]byte + b.ReportAllocs() + for i := 0; i < b.N; i++ { + strconv.AppendFloat(buf[:0], math.Pi, 'e', -1, 64) + } +} diff --git a/pkg/xscript/engine/ftoa/internal/fast/LICENSE_V8 b/pkg/xscript/engine/ftoa/internal/fast/LICENSE_V8 new file mode 100644 index 0000000..bbad266 --- /dev/null +++ b/pkg/xscript/engine/ftoa/internal/fast/LICENSE_V8 @@ -0,0 +1,26 @@ +Copyright 2014, the V8 project authors. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are +met: + + * Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above + copyright notice, this list of conditions and the following + disclaimer in the documentation and/or other materials provided + with the distribution. + * Neither the name of Google Inc. nor the names of its + contributors may be used to endorse or promote products derived + from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/pkg/xscript/engine/ftoa/internal/fast/cachedpower.go b/pkg/xscript/engine/ftoa/internal/fast/cachedpower.go new file mode 100644 index 0000000..4f7e49f --- /dev/null +++ b/pkg/xscript/engine/ftoa/internal/fast/cachedpower.go @@ -0,0 +1,120 @@ +package fast + +import "math" + +const ( + kCachedPowersOffset = 348 // -1 * the first decimal_exponent. + kD_1_LOG2_10 = 0.30102999566398114 // 1 / lg(10) + kDecimalExponentDistance = 8 +) + +type cachedPower struct { + significand uint64 + binary_exponent int16 + decimal_exponent int16 +} + +var ( + cachedPowers = [...]cachedPower{ + {0xFA8FD5A0081C0288, -1220, -348}, + {0xBAAEE17FA23EBF76, -1193, -340}, + {0x8B16FB203055AC76, -1166, -332}, + {0xCF42894A5DCE35EA, -1140, -324}, + {0x9A6BB0AA55653B2D, -1113, -316}, + {0xE61ACF033D1A45DF, -1087, -308}, + {0xAB70FE17C79AC6CA, -1060, -300}, + {0xFF77B1FCBEBCDC4F, -1034, -292}, + {0xBE5691EF416BD60C, -1007, -284}, + {0x8DD01FAD907FFC3C, -980, -276}, + {0xD3515C2831559A83, -954, -268}, + {0x9D71AC8FADA6C9B5, -927, -260}, + {0xEA9C227723EE8BCB, -901, -252}, + {0xAECC49914078536D, -874, -244}, + {0x823C12795DB6CE57, -847, -236}, + {0xC21094364DFB5637, -821, -228}, + {0x9096EA6F3848984F, -794, -220}, + {0xD77485CB25823AC7, -768, -212}, + {0xA086CFCD97BF97F4, -741, -204}, + {0xEF340A98172AACE5, -715, -196}, + {0xB23867FB2A35B28E, -688, -188}, + {0x84C8D4DFD2C63F3B, -661, -180}, + {0xC5DD44271AD3CDBA, -635, -172}, + {0x936B9FCEBB25C996, -608, -164}, + {0xDBAC6C247D62A584, -582, -156}, + {0xA3AB66580D5FDAF6, -555, -148}, + {0xF3E2F893DEC3F126, -529, -140}, + {0xB5B5ADA8AAFF80B8, -502, -132}, + {0x87625F056C7C4A8B, -475, -124}, + {0xC9BCFF6034C13053, -449, -116}, + {0x964E858C91BA2655, -422, -108}, + {0xDFF9772470297EBD, -396, -100}, + {0xA6DFBD9FB8E5B88F, -369, -92}, + {0xF8A95FCF88747D94, -343, -84}, + {0xB94470938FA89BCF, -316, -76}, + {0x8A08F0F8BF0F156B, -289, -68}, + {0xCDB02555653131B6, -263, -60}, + {0x993FE2C6D07B7FAC, -236, -52}, + {0xE45C10C42A2B3B06, -210, -44}, + {0xAA242499697392D3, -183, -36}, + {0xFD87B5F28300CA0E, -157, -28}, + {0xBCE5086492111AEB, -130, -20}, + {0x8CBCCC096F5088CC, -103, -12}, + {0xD1B71758E219652C, -77, -4}, + {0x9C40000000000000, -50, 4}, + {0xE8D4A51000000000, -24, 12}, + {0xAD78EBC5AC620000, 3, 20}, + {0x813F3978F8940984, 30, 28}, + {0xC097CE7BC90715B3, 56, 36}, + {0x8F7E32CE7BEA5C70, 83, 44}, + {0xD5D238A4ABE98068, 109, 52}, + {0x9F4F2726179A2245, 136, 60}, + {0xED63A231D4C4FB27, 162, 68}, + {0xB0DE65388CC8ADA8, 189, 76}, + {0x83C7088E1AAB65DB, 216, 84}, + {0xC45D1DF942711D9A, 242, 92}, + {0x924D692CA61BE758, 269, 100}, + {0xDA01EE641A708DEA, 295, 108}, + {0xA26DA3999AEF774A, 322, 116}, + {0xF209787BB47D6B85, 348, 124}, + {0xB454E4A179DD1877, 375, 132}, + {0x865B86925B9BC5C2, 402, 140}, + {0xC83553C5C8965D3D, 428, 148}, + {0x952AB45CFA97A0B3, 455, 156}, + {0xDE469FBD99A05FE3, 481, 164}, + {0xA59BC234DB398C25, 508, 172}, + {0xF6C69A72A3989F5C, 534, 180}, + {0xB7DCBF5354E9BECE, 561, 188}, + {0x88FCF317F22241E2, 588, 196}, + {0xCC20CE9BD35C78A5, 614, 204}, + {0x98165AF37B2153DF, 641, 212}, + {0xE2A0B5DC971F303A, 667, 220}, + {0xA8D9D1535CE3B396, 694, 228}, + {0xFB9B7CD9A4A7443C, 720, 236}, + {0xBB764C4CA7A44410, 747, 244}, + {0x8BAB8EEFB6409C1A, 774, 252}, + {0xD01FEF10A657842C, 800, 260}, + {0x9B10A4E5E9913129, 827, 268}, + {0xE7109BFBA19C0C9D, 853, 276}, + {0xAC2820D9623BF429, 880, 284}, + {0x80444B5E7AA7CF85, 907, 292}, + {0xBF21E44003ACDD2D, 933, 300}, + {0x8E679C2F5E44FF8F, 960, 308}, + {0xD433179D9C8CB841, 986, 316}, + {0x9E19DB92B4E31BA9, 1013, 324}, + {0xEB96BF6EBADF77D9, 1039, 332}, + {0xAF87023B9BF0EE6B, 1066, 340}, + } +) + +func getCachedPowerForBinaryExponentRange(min_exponent, max_exponent int) (power diyfp, decimal_exponent int) { + kQ := diyFpKSignificandSize + k := int(math.Ceil(float64(min_exponent+kQ-1) * kD_1_LOG2_10)) + index := (kCachedPowersOffset+k-1)/kDecimalExponentDistance + 1 + cached_power := cachedPowers[index] + _DCHECK(min_exponent <= int(cached_power.binary_exponent)) + _DCHECK(int(cached_power.binary_exponent) <= max_exponent) + decimal_exponent = int(cached_power.decimal_exponent) + power = diyfp{f: cached_power.significand, e: int(cached_power.binary_exponent)} + + return +} diff --git a/pkg/xscript/engine/ftoa/internal/fast/common.go b/pkg/xscript/engine/ftoa/internal/fast/common.go new file mode 100644 index 0000000..6ffaaf9 --- /dev/null +++ b/pkg/xscript/engine/ftoa/internal/fast/common.go @@ -0,0 +1,18 @@ +/* +Package fast contains code ported from V8 (https://github.com/v8/v8/blob/master/src/numbers/fast-dtoa.cc) + +See LICENSE_V8 for the original copyright message and disclaimer. +*/ +package fast + +import "errors" + +var ( + dcheckFailure = errors.New("DCHECK assertion failed") +) + +func _DCHECK(f bool) { + if !f { + panic(dcheckFailure) + } +} diff --git a/pkg/xscript/engine/ftoa/internal/fast/diyfp.go b/pkg/xscript/engine/ftoa/internal/fast/diyfp.go new file mode 100644 index 0000000..727a747 --- /dev/null +++ b/pkg/xscript/engine/ftoa/internal/fast/diyfp.go @@ -0,0 +1,152 @@ +package fast + +import "math" + +const ( + diyFpKSignificandSize = 64 + kSignificandSize = 53 + kUint64MSB uint64 = 1 << 63 + + kSignificandMask = 0x000FFFFFFFFFFFFF + kHiddenBit = 0x0010000000000000 + kExponentMask = 0x7FF0000000000000 + + kPhysicalSignificandSize = 52 // Excludes the hidden bit. + kExponentBias = 0x3FF + kPhysicalSignificandSize + kDenormalExponent = -kExponentBias + 1 +) + +type double float64 + +type diyfp struct { + f uint64 + e int +} + +// f =- o. +// The exponents of both numbers must be the same and the significand of this +// must be bigger than the significand of other. +// The result will not be normalized. +func (f *diyfp) subtract(o diyfp) { + _DCHECK(f.e == o.e) + _DCHECK(f.f >= o.f) + f.f -= o.f +} + +// Returns f - o +// The exponents of both numbers must be the same and this must be bigger +// than other. The result will not be normalized. +func (f diyfp) minus(o diyfp) diyfp { + res := f + res.subtract(o) + return res +} + +// f *= o +func (f *diyfp) mul(o diyfp) { + // Simply "emulates" a 128 bit multiplication. + // However: the resulting number only contains 64 bits. The least + // significant 64 bits are only used for rounding the most significant 64 + // bits. + const kM32 uint64 = 0xFFFFFFFF + a := f.f >> 32 + b := f.f & kM32 + c := o.f >> 32 + d := o.f & kM32 + ac := a * c + bc := b * c + ad := a * d + bd := b * d + tmp := (bd >> 32) + (ad & kM32) + (bc & kM32) + // By adding 1U << 31 to tmp we round the final result. + // Halfway cases will be round up. + tmp += 1 << 31 + result_f := ac + (ad >> 32) + (bc >> 32) + (tmp >> 32) + f.e += o.e + 64 + f.f = result_f +} + +// Returns f * o +func (f diyfp) times(o diyfp) diyfp { + res := f + res.mul(o) + return res +} + +func (f *diyfp) _normalize() { + f_, e := f.f, f.e + // This method is mainly called for normalizing boundaries. In general + // boundaries need to be shifted by 10 bits. We thus optimize for this case. + const k10MSBits uint64 = 0x3FF << 54 + for f_&k10MSBits == 0 { + f_ <<= 10 + e -= 10 + } + for f_&kUint64MSB == 0 { + f_ <<= 1 + e-- + } + f.f, f.e = f_, e +} + +func normalizeDiyfp(f diyfp) diyfp { + res := f + res._normalize() + return res +} + +// f must be strictly greater than 0. +func (d double) toNormalizedDiyfp() diyfp { + f, e := d.sigExp() + + // The current float could be a denormal. + for (f & kHiddenBit) == 0 { + f <<= 1 + e-- + } + // Do the final shifts in one go. + f <<= diyFpKSignificandSize - kSignificandSize + e -= diyFpKSignificandSize - kSignificandSize + return diyfp{f, e} +} + +// Returns the two boundaries of this. +// The bigger boundary (m_plus) is normalized. The lower boundary has the same +// exponent as m_plus. +// Precondition: the value encoded by this Double must be greater than 0. +func (d double) normalizedBoundaries() (m_minus, m_plus diyfp) { + v := d.toDiyFp() + significand_is_zero := v.f == kHiddenBit + m_plus = normalizeDiyfp(diyfp{f: (v.f << 1) + 1, e: v.e - 1}) + if significand_is_zero && v.e != kDenormalExponent { + // The boundary is closer. Think of v = 1000e10 and v- = 9999e9. + // Then the boundary (== (v - v-)/2) is not just at a distance of 1e9 but + // at a distance of 1e8. + // The only exception is for the smallest normal: the largest denormal is + // at the same distance as its successor. + // Note: denormals have the same exponent as the smallest normals. + m_minus = diyfp{f: (v.f << 2) - 1, e: v.e - 2} + } else { + m_minus = diyfp{f: (v.f << 1) - 1, e: v.e - 1} + } + m_minus.f <<= m_minus.e - m_plus.e + m_minus.e = m_plus.e + return +} + +func (d double) toDiyFp() diyfp { + f, e := d.sigExp() + return diyfp{f: f, e: e} +} + +func (d double) sigExp() (significand uint64, exponent int) { + d64 := math.Float64bits(float64(d)) + significand = d64 & kSignificandMask + if d64&kExponentMask != 0 { // not denormal + significand += kHiddenBit + exponent = int((d64&kExponentMask)>>kPhysicalSignificandSize) - kExponentBias + } else { + exponent = kDenormalExponent + } + return +} diff --git a/pkg/xscript/engine/ftoa/internal/fast/dtoa.go b/pkg/xscript/engine/ftoa/internal/fast/dtoa.go new file mode 100644 index 0000000..b12870a --- /dev/null +++ b/pkg/xscript/engine/ftoa/internal/fast/dtoa.go @@ -0,0 +1,642 @@ +package fast + +import ( + "fmt" + "strconv" +) + +const ( + kMinimalTargetExponent = -60 + kMaximalTargetExponent = -32 + + kTen4 = 10000 + kTen5 = 100000 + kTen6 = 1000000 + kTen7 = 10000000 + kTen8 = 100000000 + kTen9 = 1000000000 +) + +type Mode int + +const ( + ModeShortest Mode = iota + ModePrecision +) + +// Adjusts the last digit of the generated number, and screens out generated +// solutions that may be inaccurate. A solution may be inaccurate if it is +// outside the safe interval, or if we cannot prove that it is closer to the +// input than a neighboring representation of the same length. +// +// Input: * buffer containing the digits of too_high / 10^kappa +// - distance_too_high_w == (too_high - w).f() * unit +// - unsafe_interval == (too_high - too_low).f() * unit +// - rest = (too_high - buffer * 10^kappa).f() * unit +// - ten_kappa = 10^kappa * unit +// - unit = the common multiplier +// +// Output: returns true if the buffer is guaranteed to contain the closest +// +// representable number to the input. +// Modifies the generated digits in the buffer to approach (round towards) w. +func roundWeed(buffer []byte, distance_too_high_w, unsafe_interval, rest, ten_kappa, unit uint64) bool { + small_distance := distance_too_high_w - unit + big_distance := distance_too_high_w + unit + + // Let w_low = too_high - big_distance, and + // w_high = too_high - small_distance. + // Note: w_low < w < w_high + // + // The real w (* unit) must lie somewhere inside the interval + // ]w_low; w_high[ (often written as "(w_low; w_high)") + + // Basically the buffer currently contains a number in the unsafe interval + // ]too_low; too_high[ with too_low < w < too_high + // + // too_high - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // ^v 1 unit ^ ^ ^ ^ + // boundary_high --------------------- . . . . + // ^v 1 unit . . . . + // - - - - - - - - - - - - - - - - - - - + - - + - - - - - - . . + // . . ^ . . + // . big_distance . . . + // . . . . rest + // small_distance . . . . + // v . . . . + // w_high - - - - - - - - - - - - - - - - - - . . . . + // ^v 1 unit . . . . + // w ---------------------------------------- . . . . + // ^v 1 unit v . . . + // w_low - - - - - - - - - - - - - - - - - - - - - . . . + // . . v + // buffer --------------------------------------------------+-------+-------- + // . . + // safe_interval . + // v . + // - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - . + // ^v 1 unit . + // boundary_low ------------------------- unsafe_interval + // ^v 1 unit v + // too_low - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + // + // + // Note that the value of buffer could lie anywhere inside the range too_low + // to too_high. + // + // boundary_low, boundary_high and w are approximations of the real boundaries + // and v (the input number). They are guaranteed to be precise up to one unit. + // In fact the error is guaranteed to be strictly less than one unit. + // + // Anything that lies outside the unsafe interval is guaranteed not to round + // to v when read again. + // Anything that lies inside the safe interval is guaranteed to round to v + // when read again. + // If the number inside the buffer lies inside the unsafe interval but not + // inside the safe interval then we simply do not know and bail out (returning + // false). + // + // Similarly we have to take into account the imprecision of 'w' when finding + // the closest representation of 'w'. If we have two potential + // representations, and one is closer to both w_low and w_high, then we know + // it is closer to the actual value v. + // + // By generating the digits of too_high we got the largest (closest to + // too_high) buffer that is still in the unsafe interval. In the case where + // w_high < buffer < too_high we try to decrement the buffer. + // This way the buffer approaches (rounds towards) w. + // There are 3 conditions that stop the decrementation process: + // 1) the buffer is already below w_high + // 2) decrementing the buffer would make it leave the unsafe interval + // 3) decrementing the buffer would yield a number below w_high and farther + // away than the current number. In other words: + // (buffer{-1} < w_high) && w_high - buffer{-1} > buffer - w_high + // Instead of using the buffer directly we use its distance to too_high. + // Conceptually rest ~= too_high - buffer + // We need to do the following tests in this order to avoid over- and + // underflows. + _DCHECK(rest <= unsafe_interval) + for rest < small_distance && // Negated condition 1 + unsafe_interval-rest >= ten_kappa && // Negated condition 2 + (rest+ten_kappa < small_distance || // buffer{-1} > w_high + small_distance-rest >= rest+ten_kappa-small_distance) { + buffer[len(buffer)-1]-- + rest += ten_kappa + } + + // We have approached w+ as much as possible. We now test if approaching w- + // would require changing the buffer. If yes, then we have two possible + // representations close to w, but we cannot decide which one is closer. + if rest < big_distance && unsafe_interval-rest >= ten_kappa && + (rest+ten_kappa < big_distance || + big_distance-rest > rest+ten_kappa-big_distance) { + return false + } + + // Weeding test. + // The safe interval is [too_low + 2 ulp; too_high - 2 ulp] + // Since too_low = too_high - unsafe_interval this is equivalent to + // [too_high - unsafe_interval + 4 ulp; too_high - 2 ulp] + // Conceptually we have: rest ~= too_high - buffer + return (2*unit <= rest) && (rest <= unsafe_interval-4*unit) +} + +// Rounds the buffer upwards if the result is closer to v by possibly adding +// 1 to the buffer. If the precision of the calculation is not sufficient to +// round correctly, return false. +// The rounding might shift the whole buffer in which case the kappa is +// adjusted. For example "99", kappa = 3 might become "10", kappa = 4. +// +// If 2*rest > ten_kappa then the buffer needs to be round up. +// rest can have an error of +/- 1 unit. This function accounts for the +// imprecision and returns false, if the rounding direction cannot be +// unambiguously determined. +// +// Precondition: rest < ten_kappa. +func roundWeedCounted(buffer []byte, rest, ten_kappa, unit uint64, kappa *int) bool { + _DCHECK(rest < ten_kappa) + // The following tests are done in a specific order to avoid overflows. They + // will work correctly with any uint64 values of rest < ten_kappa and unit. + // + // If the unit is too big, then we don't know which way to round. For example + // a unit of 50 means that the real number lies within rest +/- 50. If + // 10^kappa == 40 then there is no way to tell which way to round. + if unit >= ten_kappa { + return false + } + // Even if unit is just half the size of 10^kappa we are already completely + // lost. (And after the previous test we know that the expression will not + // over/underflow.) + if ten_kappa-unit <= unit { + return false + } + // If 2 * (rest + unit) <= 10^kappa we can safely round down. + if (ten_kappa-rest > rest) && (ten_kappa-2*rest >= 2*unit) { + return true + } + + // If 2 * (rest - unit) >= 10^kappa, then we can safely round up. + if (rest > unit) && (ten_kappa-(rest-unit) <= (rest - unit)) { + // Increment the last digit recursively until we find a non '9' digit. + buffer[len(buffer)-1]++ + for i := len(buffer) - 1; i > 0; i-- { + if buffer[i] != '0'+10 { + break + } + buffer[i] = '0' + buffer[i-1]++ + } + // If the first digit is now '0'+ 10 we had a buffer with all '9's. With the + // exception of the first digit all digits are now '0'. Simply switch the + // first digit to '1' and adjust the kappa. Example: "99" becomes "10" and + // the power (the kappa) is increased. + if buffer[0] == '0'+10 { + buffer[0] = '1' + *kappa += 1 + } + return true + } + return false +} + +// Returns the biggest power of ten that is less than or equal than the given +// number. We furthermore receive the maximum number of bits 'number' has. +// If number_bits == 0 then 0^-1 is returned +// The number of bits must be <= 32. +// Precondition: number < (1 << (number_bits + 1)). +func biggestPowerTen(number uint32, number_bits int) (power uint32, exponent int) { + switch number_bits { + case 32, 31, 30: + if kTen9 <= number { + power = kTen9 + exponent = 9 + break + } + fallthrough + case 29, 28, 27: + if kTen8 <= number { + power = kTen8 + exponent = 8 + break + } + fallthrough + case 26, 25, 24: + if kTen7 <= number { + power = kTen7 + exponent = 7 + break + } + fallthrough + case 23, 22, 21, 20: + if kTen6 <= number { + power = kTen6 + exponent = 6 + break + } + fallthrough + case 19, 18, 17: + if kTen5 <= number { + power = kTen5 + exponent = 5 + break + } + fallthrough + case 16, 15, 14: + if kTen4 <= number { + power = kTen4 + exponent = 4 + break + } + fallthrough + case 13, 12, 11, 10: + if 1000 <= number { + power = 1000 + exponent = 3 + break + } + fallthrough + case 9, 8, 7: + if 100 <= number { + power = 100 + exponent = 2 + break + } + fallthrough + case 6, 5, 4: + if 10 <= number { + power = 10 + exponent = 1 + break + } + fallthrough + case 3, 2, 1: + if 1 <= number { + power = 1 + exponent = 0 + break + } + fallthrough + case 0: + power = 0 + exponent = -1 + } + return +} + +// Generates the digits of input number w. +// w is a floating-point number (DiyFp), consisting of a significand and an +// exponent. Its exponent is bounded by kMinimalTargetExponent and +// kMaximalTargetExponent. +// +// Hence -60 <= w.e() <= -32. +// +// Returns false if it fails, in which case the generated digits in the buffer +// should not be used. +// Preconditions: +// - low, w and high are correct up to 1 ulp (unit in the last place). That +// is, their error must be less than a unit of their last digits. +// - low.e() == w.e() == high.e() +// - low < w < high, and taking into account their error: low~ <= high~ +// - kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent +// +// Postconditions: returns false if procedure fails. +// +// otherwise: +// * buffer is not null-terminated, but len contains the number of digits. +// * buffer contains the shortest possible decimal digit-sequence +// such that LOW < buffer * 10^kappa < HIGH, where LOW and HIGH are the +// correct values of low and high (without their error). +// * if more than one decimal representation gives the minimal number of +// decimal digits then the one closest to W (where W is the correct value +// of w) is chosen. +// +// Remark: this procedure takes into account the imprecision of its input +// +// numbers. If the precision is not enough to guarantee all the postconditions +// then false is returned. This usually happens rarely (~0.5%). +// +// Say, for the sake of example, that +// +// w.e() == -48, and w.f() == 0x1234567890ABCDEF +// +// w's value can be computed by w.f() * 2^w.e() +// We can obtain w's integral digits by simply shifting w.f() by -w.e(). +// +// -> w's integral part is 0x1234 +// w's fractional part is therefore 0x567890ABCDEF. +// +// Printing w's integral part is easy (simply print 0x1234 in decimal). +// In order to print its fraction we repeatedly multiply the fraction by 10 and +// get each digit. Example the first digit after the point would be computed by +// +// (0x567890ABCDEF * 10) >> 48. -> 3 +// +// The whole thing becomes slightly more complicated because we want to stop +// once we have enough digits. That is, once the digits inside the buffer +// represent 'w' we can stop. Everything inside the interval low - high +// represents w. However we have to pay attention to low, high and w's +// imprecision. +func digitGen(low, w, high diyfp, buffer []byte) (kappa int, buf []byte, res bool) { + _DCHECK(low.e == w.e && w.e == high.e) + _DCHECK(low.f+1 <= high.f-1) + _DCHECK(kMinimalTargetExponent <= w.e && w.e <= kMaximalTargetExponent) + // low, w and high are imprecise, but by less than one ulp (unit in the last + // place). + // If we remove (resp. add) 1 ulp from low (resp. high) we are certain that + // the new numbers are outside of the interval we want the final + // representation to lie in. + // Inversely adding (resp. removing) 1 ulp from low (resp. high) would yield + // numbers that are certain to lie in the interval. We will use this fact + // later on. + // We will now start by generating the digits within the uncertain + // interval. Later we will weed out representations that lie outside the safe + // interval and thus _might_ lie outside the correct interval. + unit := uint64(1) + too_low := diyfp{f: low.f - unit, e: low.e} + too_high := diyfp{f: high.f + unit, e: high.e} + // too_low and too_high are guaranteed to lie outside the interval we want the + // generated number in. + unsafe_interval := too_high.minus(too_low) + // We now cut the input number into two parts: the integral digits and the + // fractionals. We will not write any decimal separator though, but adapt + // kappa instead. + // Reminder: we are currently computing the digits (stored inside the buffer) + // such that: too_low < buffer * 10^kappa < too_high + // We use too_high for the digit_generation and stop as soon as possible. + // If we stop early we effectively round down. + one := diyfp{f: 1 << -w.e, e: w.e} + // Division by one is a shift. + integrals := uint32(too_high.f >> -one.e) + // Modulo by one is an and. + fractionals := too_high.f & (one.f - 1) + divisor, divisor_exponent := biggestPowerTen(integrals, diyFpKSignificandSize-(-one.e)) + kappa = divisor_exponent + 1 + buf = buffer + for kappa > 0 { + digit := int(integrals / divisor) + buf = append(buf, byte('0'+digit)) + integrals %= divisor + kappa-- + // Note that kappa now equals the exponent of the divisor and that the + // invariant thus holds again. + rest := uint64(integrals)<<-one.e + fractionals + // Invariant: too_high = buffer * 10^kappa + DiyFp(rest, one.e) + // Reminder: unsafe_interval.e == one.e + if rest < unsafe_interval.f { + // Rounding down (by not emitting the remaining digits) yields a number + // that lies within the unsafe interval. + res = roundWeed(buf, too_high.minus(w).f, + unsafe_interval.f, rest, + uint64(divisor)<<-one.e, unit) + return + } + divisor /= 10 + } + // The integrals have been generated. We are at the point of the decimal + // separator. In the following loop we simply multiply the remaining digits by + // 10 and divide by one. We just need to pay attention to multiply associated + // data (like the interval or 'unit'), too. + // Note that the multiplication by 10 does not overflow, because w.e >= -60 + // and thus one.e >= -60. + _DCHECK(one.e >= -60) + _DCHECK(fractionals < one.f) + _DCHECK(0xFFFFFFFFFFFFFFFF/10 >= one.f) + for { + fractionals *= 10 + unit *= 10 + unsafe_interval.f *= 10 + // Integer division by one. + digit := byte(fractionals >> -one.e) + buf = append(buf, '0'+digit) + fractionals &= one.f - 1 // Modulo by one. + kappa-- + if fractionals < unsafe_interval.f { + res = roundWeed(buf, too_high.minus(w).f*unit, unsafe_interval.f, fractionals, one.f, unit) + return + } + } +} + +// Generates (at most) requested_digits of input number w. +// w is a floating-point number (DiyFp), consisting of a significand and an +// exponent. Its exponent is bounded by kMinimalTargetExponent and +// kMaximalTargetExponent. +// +// Hence -60 <= w.e() <= -32. +// +// Returns false if it fails, in which case the generated digits in the buffer +// should not be used. +// Preconditions: +// - w is correct up to 1 ulp (unit in the last place). That +// is, its error must be strictly less than a unit of its last digit. +// - kMinimalTargetExponent <= w.e() <= kMaximalTargetExponent +// +// Postconditions: returns false if procedure fails. +// +// otherwise: +// * buffer is not null-terminated, but length contains the number of +// digits. +// * the representation in buffer is the most precise representation of +// requested_digits digits. +// * buffer contains at most requested_digits digits of w. If there are less +// than requested_digits digits then some trailing '0's have been removed. +// * kappa is such that +// w = buffer * 10^kappa + eps with |eps| < 10^kappa / 2. +// +// Remark: This procedure takes into account the imprecision of its input +// +// numbers. If the precision is not enough to guarantee all the postconditions +// then false is returned. This usually happens rarely, but the failure-rate +// increases with higher requested_digits. +func digitGenCounted(w diyfp, requested_digits int, buffer []byte) (kappa int, buf []byte, res bool) { + _DCHECK(kMinimalTargetExponent <= w.e && w.e <= kMaximalTargetExponent) + + // w is assumed to have an error less than 1 unit. Whenever w is scaled we + // also scale its error. + w_error := uint64(1) + // We cut the input number into two parts: the integral digits and the + // fractional digits. We don't emit any decimal separator, but adapt kappa + // instead. Example: instead of writing "1.2" we put "12" into the buffer and + // increase kappa by 1. + one := diyfp{f: 1 << -w.e, e: w.e} + // Division by one is a shift. + integrals := uint32(w.f >> -one.e) + // Modulo by one is an and. + fractionals := w.f & (one.f - 1) + divisor, divisor_exponent := biggestPowerTen(integrals, diyFpKSignificandSize-(-one.e)) + kappa = divisor_exponent + 1 + buf = buffer + // Loop invariant: buffer = w / 10^kappa (integer division) + // The invariant holds for the first iteration: kappa has been initialized + // with the divisor exponent + 1. And the divisor is the biggest power of ten + // that is smaller than 'integrals'. + for kappa > 0 { + digit := byte(integrals / divisor) + buf = append(buf, '0'+digit) + requested_digits-- + integrals %= divisor + kappa-- + // Note that kappa now equals the exponent of the divisor and that the + // invariant thus holds again. + if requested_digits == 0 { + break + } + divisor /= 10 + } + + if requested_digits == 0 { + rest := uint64(integrals)<<-one.e + fractionals + res = roundWeedCounted(buf, rest, uint64(divisor)<<-one.e, w_error, &kappa) + return + } + + // The integrals have been generated. We are at the point of the decimal + // separator. In the following loop we simply multiply the remaining digits by + // 10 and divide by one. We just need to pay attention to multiply associated + // data (the 'unit'), too. + // Note that the multiplication by 10 does not overflow, because w.e >= -60 + // and thus one.e >= -60. + _DCHECK(one.e >= -60) + _DCHECK(fractionals < one.f) + _DCHECK(0xFFFFFFFFFFFFFFFF/10 >= one.f) + for requested_digits > 0 && fractionals > w_error { + fractionals *= 10 + w_error *= 10 + // Integer division by one. + digit := byte(fractionals >> -one.e) + buf = append(buf, '0'+digit) + requested_digits-- + fractionals &= one.f - 1 // Modulo by one. + kappa-- + } + if requested_digits != 0 { + res = false + } else { + res = roundWeedCounted(buf, fractionals, one.f, w_error, &kappa) + } + return +} + +// Provides a decimal representation of v. +// Returns true if it succeeds, otherwise the result cannot be trusted. +// There will be *length digits inside the buffer (not null-terminated). +// If the function returns true then +// +// v == (double) (buffer * 10^decimal_exponent). +// +// The digits in the buffer are the shortest representation possible: no +// 0.09999999999999999 instead of 0.1. The shorter representation will even be +// chosen even if the longer one would be closer to v. +// The last digit will be closest to the actual v. That is, even if several +// digits might correctly yield 'v' when read again, the closest will be +// computed. +func grisu3(f float64, buffer []byte) (digits []byte, decimal_exponent int, result bool) { + v := double(f) + w := v.toNormalizedDiyfp() + + // boundary_minus and boundary_plus are the boundaries between v and its + // closest floating-point neighbors. Any number strictly between + // boundary_minus and boundary_plus will round to v when convert to a double. + // Grisu3 will never output representations that lie exactly on a boundary. + boundary_minus, boundary_plus := v.normalizedBoundaries() + ten_mk_minimal_binary_exponent := kMinimalTargetExponent - (w.e + diyFpKSignificandSize) + ten_mk_maximal_binary_exponent := kMaximalTargetExponent - (w.e + diyFpKSignificandSize) + ten_mk, mk := getCachedPowerForBinaryExponentRange(ten_mk_minimal_binary_exponent, ten_mk_maximal_binary_exponent) + + _DCHECK( + (kMinimalTargetExponent <= + w.e+ten_mk.e+diyFpKSignificandSize) && + (kMaximalTargetExponent >= w.e+ten_mk.e+diyFpKSignificandSize)) + // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a + // 64 bit significand and ten_mk is thus only precise up to 64 bits. + + // The DiyFp::Times procedure rounds its result, and ten_mk is approximated + // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now + // off by a small amount. + // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w. + // In other words: let f = scaled_w.f() and e = scaled_w.e(), then + // (f-1) * 2^e < w*10^k < (f+1) * 2^e + scaled_w := w.times(ten_mk) + _DCHECK(scaled_w.e == + boundary_plus.e+ten_mk.e+diyFpKSignificandSize) + // In theory it would be possible to avoid some recomputations by computing + // the difference between w and boundary_minus/plus (a power of 2) and to + // compute scaled_boundary_minus/plus by subtracting/adding from + // scaled_w. However the code becomes much less readable and the speed + // enhancements are not terrific. + scaled_boundary_minus := boundary_minus.times(ten_mk) + scaled_boundary_plus := boundary_plus.times(ten_mk) + // DigitGen will generate the digits of scaled_w. Therefore we have + // v == (double) (scaled_w * 10^-mk). + // Set decimal_exponent == -mk and pass it to DigitGen. If scaled_w is not an + // integer than it will be updated. For instance if scaled_w == 1.23 then + // the buffer will be filled with "123" und the decimal_exponent will be + // decreased by 2. + var kappa int + kappa, digits, result = digitGen(scaled_boundary_minus, scaled_w, scaled_boundary_plus, buffer) + decimal_exponent = -mk + kappa + return +} + +// The "counted" version of grisu3 (see above) only generates requested_digits +// number of digits. This version does not generate the shortest representation, +// and with enough requested digits 0.1 will at some point print as 0.9999999... +// Grisu3 is too imprecise for real halfway cases (1.5 will not work) and +// therefore the rounding strategy for halfway cases is irrelevant. +func grisu3Counted(v float64, requested_digits int, buffer []byte) (digits []byte, decimal_exponent int, result bool) { + w := double(v).toNormalizedDiyfp() + ten_mk_minimal_binary_exponent := kMinimalTargetExponent - (w.e + diyFpKSignificandSize) + ten_mk_maximal_binary_exponent := kMaximalTargetExponent - (w.e + diyFpKSignificandSize) + ten_mk, mk := getCachedPowerForBinaryExponentRange(ten_mk_minimal_binary_exponent, ten_mk_maximal_binary_exponent) + + _DCHECK( + (kMinimalTargetExponent <= + w.e+ten_mk.e+diyFpKSignificandSize) && + (kMaximalTargetExponent >= w.e+ten_mk.e+diyFpKSignificandSize)) + // Note that ten_mk is only an approximation of 10^-k. A DiyFp only contains a + // 64 bit significand and ten_mk is thus only precise up to 64 bits. + + // The DiyFp::Times procedure rounds its result, and ten_mk is approximated + // too. The variable scaled_w (as well as scaled_boundary_minus/plus) are now + // off by a small amount. + // In fact: scaled_w - w*10^k < 1ulp (unit in the last place) of scaled_w. + // In other words: let f = scaled_w.f() and e = scaled_w.e(), then + // (f-1) * 2^e < w*10^k < (f+1) * 2^e + scaled_w := w.times(ten_mk) + // We now have (double) (scaled_w * 10^-mk). + // DigitGen will generate the first requested_digits digits of scaled_w and + // return together with a kappa such that scaled_w ~= buffer * 10^kappa. (It + // will not always be exactly the same since DigitGenCounted only produces a + // limited number of digits.) + var kappa int + kappa, digits, result = digitGenCounted(scaled_w, requested_digits, buffer) + decimal_exponent = -mk + kappa + + return +} + +// v must be > 0 and must not be Inf or NaN +func Dtoa(v float64, mode Mode, requested_digits int, buffer []byte) (digits []byte, decimal_point int, result bool) { + defer func() { + if x := recover(); x != nil { + if x == dcheckFailure { + panic(fmt.Errorf("DCHECK assertion failed while formatting %s in mode %d", strconv.FormatFloat(v, 'e', 50, 64), mode)) + } + panic(x) + } + }() + var decimal_exponent int + startPos := len(buffer) + switch mode { + case ModeShortest: + digits, decimal_exponent, result = grisu3(v, buffer) + case ModePrecision: + digits, decimal_exponent, result = grisu3Counted(v, requested_digits, buffer) + } + if result { + decimal_point = len(digits) - startPos + decimal_exponent + } else { + digits = digits[:startPos] + } + return +} diff --git a/pkg/xscript/engine/func.go b/pkg/xscript/engine/func.go new file mode 100644 index 0000000..4b84dfc --- /dev/null +++ b/pkg/xscript/engine/func.go @@ -0,0 +1,1105 @@ +package engine + +import ( + "fmt" + "reflect" + + "pandax/pkg/xscript/engine/unistring" +) + +type resultType uint8 + +const ( + resultNormal resultType = iota + resultYield + resultYieldRes // a yield that expects a value in return + resultYieldDelegate // yield* + resultYieldDelegateRes + resultAwait +) + +// used both as an instruction and as a Value +type yieldMarker struct { + valueNull + resultType resultType +} + +var ( + await = &yieldMarker{resultType: resultAwait} + + yield = &yieldMarker{resultType: resultYield} + yieldRes = &yieldMarker{resultType: resultYieldRes} + yieldDelegate = &yieldMarker{resultType: resultYieldDelegate} + yieldDelegateRes = &yieldMarker{resultType: resultYieldDelegateRes} + yieldEmpty = &yieldMarker{resultType: resultYield} +) + +// AsyncContextTracker is a handler that allows to track an async execution context to ensure it remains +// consistent across all callback invocations. +// Whenever a Promise reaction job is scheduled the Grab method is called. It is supposed to return the +// current context. The same context will be supplied to the Resumed method before the reaction job is +// executed. The Exited method is called after the reaction job is finished. +// This means that for each invocation of the Grab method there will be exactly one subsequent invocation +// of Resumed and then Exited methods (assuming the Promise is fulfilled or rejected). Also, the Resumed/Exited +// calls cannot be nested, so Exited can simply clear the current context instead of popping from a stack. +// Note, this works for both async functions and regular Promise.then()/Promise.catch() callbacks. +// See TestAsyncContextTracker for more insight. +// +// To register it call Runtime.SetAsyncContextTracker(). +type AsyncContextTracker interface { + Grab() (trackingObject any) + Resumed(trackingObject any) + Exited() +} + +type funcObjectImpl interface { + source() String +} + +type baseFuncObject struct { + baseObject + + lenProp valueProperty +} + +type baseJsFuncObject struct { + baseFuncObject + + stash *stash + privEnv *privateEnv + + prg *Program + src string + strict bool +} + +type funcObject struct { + baseJsFuncObject +} + +type generatorFuncObject struct { + baseJsFuncObject +} + +type asyncFuncObject struct { + baseJsFuncObject +} + +type classFuncObject struct { + baseJsFuncObject + initFields *Program + computedKeys []Value + + privateEnvType *privateEnvType + privateMethods []Value + + derived bool +} + +type methodFuncObject struct { + baseJsFuncObject + homeObject *Object +} + +type generatorMethodFuncObject struct { + methodFuncObject +} + +type asyncMethodFuncObject struct { + methodFuncObject +} + +type arrowFuncObject struct { + baseJsFuncObject + funcObj *Object + newTarget Value +} + +type asyncArrowFuncObject struct { + arrowFuncObject +} + +type nativeFuncObject struct { + baseFuncObject + + f func(FunctionCall) Value + construct func(args []Value, newTarget *Object) *Object +} + +type wrappedFuncObject struct { + nativeFuncObject + wrapped reflect.Value +} + +type boundFuncObject struct { + nativeFuncObject + wrapped *Object +} + +type generatorState uint8 + +const ( + genStateUndefined generatorState = iota + genStateSuspendedStart + genStateExecuting + genStateSuspendedYield + genStateSuspendedYieldRes + genStateCompleted +) + +type generatorObject struct { + baseObject + gen generator + delegated *iteratorRecord + state generatorState +} + +func (f *nativeFuncObject) source() String { + return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString())) +} + +func (f *nativeFuncObject) export(*objectExportCtx) any { + return f.f +} + +func (f *wrappedFuncObject) exportType() reflect.Type { + return f.wrapped.Type() +} + +func (f *wrappedFuncObject) export(*objectExportCtx) any { + return f.wrapped.Interface() +} + +func (f *funcObject) _addProto(n unistring.String) Value { + if n == "prototype" { + if _, exists := f.values[n]; !exists { + return f.addPrototype() + } + } + return nil +} + +func (f *funcObject) getStr(p unistring.String, receiver Value) Value { + return f.getStrWithOwnProp(f.getOwnPropStr(p), p, receiver) +} + +func (f *funcObject) getOwnPropStr(name unistring.String) Value { + if v := f._addProto(name); v != nil { + return v + } + + return f.baseObject.getOwnPropStr(name) +} + +func (f *funcObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + f._addProto(name) + return f.baseObject.setOwnStr(name, val, throw) +} + +func (f *funcObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return f._setForeignStr(name, f.getOwnPropStr(name), val, receiver, throw) +} + +func (f *funcObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + f._addProto(name) + return f.baseObject.defineOwnPropertyStr(name, descr, throw) +} + +func (f *funcObject) deleteStr(name unistring.String, throw bool) bool { + f._addProto(name) + return f.baseObject.deleteStr(name, throw) +} + +func (f *funcObject) addPrototype() Value { + proto := f.val.runtime.NewObject() + proto.self._putProp("constructor", f.val, true, false, true) + return f._putProp("prototype", proto, true, false, false) +} + +func (f *funcObject) hasOwnPropertyStr(name unistring.String) bool { + if f.baseObject.hasOwnPropertyStr(name) { + return true + } + + if name == "prototype" { + return true + } + return false +} + +func (f *funcObject) stringKeys(all bool, accum []Value) []Value { + if all { + if _, exists := f.values["prototype"]; !exists { + accum = append(accum, asciiString("prototype")) + } + } + return f.baseFuncObject.stringKeys(all, accum) +} + +func (f *funcObject) iterateStringKeys() iterNextFunc { + if _, exists := f.values["prototype"]; !exists { + f.addPrototype() + } + return f.baseFuncObject.iterateStringKeys() +} + +func (f *baseFuncObject) createInstance(newTarget *Object) *Object { + r := f.val.runtime + if newTarget == nil { + newTarget = f.val + } + proto := r.getPrototypeFromCtor(newTarget, nil, r.global.ObjectPrototype) + + return f.val.runtime.newBaseObject(proto, classObject).val +} + +func (f *baseJsFuncObject) source() String { + return newStringValue(f.src) +} + +func (f *baseJsFuncObject) construct(args []Value, newTarget *Object) *Object { + if newTarget == nil { + newTarget = f.val + } + proto := newTarget.self.getStr("prototype", nil) + var protoObj *Object + if p, ok := proto.(*Object); ok { + protoObj = p + } else { + protoObj = f.val.runtime.global.ObjectPrototype + } + + obj := f.val.runtime.newBaseObject(protoObj, classObject).val + ret := f.call(FunctionCall{ + This: obj, + Arguments: args, + }, newTarget) + + if ret, ok := ret.(*Object); ok { + return ret + } + return obj +} + +func (f *classFuncObject) Call(FunctionCall) Value { + panic(f.val.runtime.NewTypeError("Class constructor cannot be invoked without 'new'")) +} + +func (f *classFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *classFuncObject) vmCall(vm *vm, n int) { + f.Call(FunctionCall{}) +} + +func (f *classFuncObject) export(*objectExportCtx) any { + return f.Call +} + +func (f *classFuncObject) createInstance(args []Value, newTarget *Object) (instance *Object) { + if f.derived { + if ctor := f.prototype.self.assertConstructor(); ctor != nil { + instance = ctor(args, newTarget) + } else { + panic(f.val.runtime.NewTypeError("Super constructor is not a constructor")) + } + } else { + instance = f.baseFuncObject.createInstance(newTarget) + } + return +} + +func (f *classFuncObject) _initFields(instance *Object) { + if f.privateEnvType != nil { + penv := instance.self.getPrivateEnv(f.privateEnvType, true) + penv.methods = f.privateMethods + } + if f.initFields != nil { + vm := f.val.runtime.vm + vm.pushCtx() + vm.prg = f.initFields + vm.stash = f.stash + vm.privEnv = f.privEnv + vm.newTarget = nil + + // so that 'super' base could be correctly resolved (including from direct eval()) + vm.push(f.val) + + vm.sb = vm.sp + vm.push(instance) + vm.pc = 0 + ex := vm.runTry() + vm.popCtx() + if ex != nil { + panic(ex) + } + vm.sp -= 2 + } +} + +func (f *classFuncObject) construct(args []Value, newTarget *Object) *Object { + if newTarget == nil { + newTarget = f.val + } + if f.prg == nil { + instance := f.createInstance(args, newTarget) + f._initFields(instance) + return instance + } else { + var instance *Object + var thisVal Value + if !f.derived { + instance = f.createInstance(args, newTarget) + f._initFields(instance) + thisVal = instance + } + ret := f._call(args, newTarget, thisVal) + + if ret, ok := ret.(*Object); ok { + return ret + } + if f.derived { + r := f.val.runtime + if ret != _undefined { + panic(r.NewTypeError("Derived constructors may only return object or undefined")) + } + if v := r.vm.stack[r.vm.sp+1]; v != nil { // using residual 'this' value (a bit hacky) + instance = r.toObject(v) + } else { + panic(r.newError(r.getReferenceError(), "Must call super constructor in derived class before returning from derived constructor")) + } + } + return instance + } +} + +func (f *classFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return f.construct +} + +func (f *baseJsFuncObject) Call(call FunctionCall) Value { + return f.call(call, nil) +} + +func (f *arrowFuncObject) Call(call FunctionCall) Value { + return f._call(call.Arguments, f.newTarget, nil) +} + +func (f *baseJsFuncObject) __call(args []Value, newTarget, this Value) (Value, *Exception) { + vm := f.val.runtime.vm + + vm.stack.expand(vm.sp + len(args) + 1) + vm.stack[vm.sp] = f.val + vm.sp++ + vm.stack[vm.sp] = this + vm.sp++ + for _, arg := range args { + if arg != nil { + vm.stack[vm.sp] = arg + } else { + vm.stack[vm.sp] = _undefined + } + vm.sp++ + } + + vm.pushTryFrame(tryPanicMarker, -1) + defer vm.popTryFrame() + + var needPop bool + if vm.prg != nil { + vm.pushCtx() + vm.callStack = append(vm.callStack, context{pc: -2}) // extra frame so that run() halts after ret + needPop = true + } else { + vm.pc = -2 + vm.pushCtx() + } + + vm.args = len(args) + vm.prg = f.prg + vm.stash = f.stash + vm.privEnv = f.privEnv + vm.newTarget = newTarget + vm.pc = 0 + for { + ex := vm.runTryInner() + if ex != nil { + return nil, ex + } + if vm.halted() { + break + } + } + if needPop { + vm.popCtx() + } + + return vm.pop(), nil +} + +func (f *baseJsFuncObject) _call(args []Value, newTarget, this Value) Value { + res, ex := f.__call(args, newTarget, this) + if ex != nil { + panic(ex) + } + return res +} + +func (f *baseJsFuncObject) call(call FunctionCall, newTarget Value) Value { + return f._call(call.Arguments, newTarget, nilSafe(call.This)) +} + +func (f *baseJsFuncObject) export(*objectExportCtx) any { + return f.Call +} + +func (f *baseFuncObject) exportType() reflect.Type { + return reflectTypeFunc +} + +func (f *baseFuncObject) typeOf() String { + return stringFunction +} + +func (f *baseJsFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *funcObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return f.construct +} + +func (f *baseJsFuncObject) vmCall(vm *vm, n int) { + vm.pushCtx() + vm.args = n + vm.prg = f.prg + vm.stash = f.stash + vm.privEnv = f.privEnv + vm.pc = 0 + vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = vm.stack[vm.sp-n-2], vm.stack[vm.sp-n-1] +} + +func (f *arrowFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *arrowFuncObject) vmCall(vm *vm, n int) { + vm.pushCtx() + vm.args = n + vm.prg = f.prg + vm.stash = f.stash + vm.privEnv = f.privEnv + vm.pc = 0 + vm.stack[vm.sp-n-1], vm.stack[vm.sp-n-2] = nil, vm.stack[vm.sp-n-1] + vm.newTarget = f.newTarget +} + +func (f *arrowFuncObject) export(*objectExportCtx) any { + return f.Call +} + +func (f *baseFuncObject) init(name unistring.String, length Value) { + f.baseObject.init() + + f.lenProp.configurable = true + f.lenProp.value = length + f._put("length", &f.lenProp) + + f._putProp("name", stringValueFromRaw(name), false, false, true) +} + +func hasInstance(val *Object, v Value) bool { + if v, ok := v.(*Object); ok { + o := val.self.getStr("prototype", nil) + if o1, ok := o.(*Object); ok { + for { + v = v.self.proto() + if v == nil { + return false + } + if o1 == v { + return true + } + } + } else { + panic(val.runtime.NewTypeError("prototype is not an object")) + } + } + + return false +} + +func (f *baseFuncObject) hasInstance(v Value) bool { + return hasInstance(f.val, v) +} + +func (f *nativeFuncObject) defaultConstruct(ccall func(ConstructorCall) *Object, args []Value, newTarget *Object) *Object { + obj := f.createInstance(newTarget) + ret := ccall(ConstructorCall{ + This: obj, + Arguments: args, + NewTarget: newTarget, + }) + + if ret != nil { + return ret + } + return obj +} + +func (f *nativeFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + if f.f != nil { + return f.f, true + } + return nil, false +} + +func (f *nativeFuncObject) vmCall(vm *vm, n int) { + if f.f != nil { + vm.pushCtx() + vm.prg = nil + vm.sb = vm.sp - n // so that [sb-1] points to the callee + ret := f.f(FunctionCall{ + Arguments: vm.stack[vm.sp-n : vm.sp], + This: vm.stack[vm.sp-n-2], + }) + if ret == nil { + ret = _undefined + } + vm.stack[vm.sp-n-2] = ret + vm.popCtx() + } else { + vm.stack[vm.sp-n-2] = _undefined + } + vm.sp -= n + 1 + vm.pc++ +} + +func (f *nativeFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return f.construct +} + +func (f *boundFuncObject) hasInstance(v Value) bool { + return instanceOfOperator(v, f.wrapped) +} + +func (f *baseJsFuncObject) prepareForVmCall(call FunctionCall) { + vm := f.val.runtime.vm + args := call.Arguments + vm.stack.expand(vm.sp + len(args) + 1) + vm.stack[vm.sp] = call.This + vm.sp++ + vm.stack[vm.sp] = f.val + vm.sp++ + for _, arg := range args { + if arg != nil { + vm.stack[vm.sp] = arg + } else { + vm.stack[vm.sp] = _undefined + } + vm.sp++ + } +} + +func (f *baseJsFuncObject) asyncCall(call FunctionCall, vmCall func(*vm, int)) Value { + f.prepareForVmCall(call) + ar := &asyncRunner{ + f: f.val, + vmCall: vmCall, + } + ar.start(len(call.Arguments)) + return ar.promiseCap.promise +} + +func (f *asyncFuncObject) Call(call FunctionCall) Value { + return f.asyncCall(call, f.baseJsFuncObject.vmCall) +} + +func (f *asyncFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *asyncFuncObject) export(*objectExportCtx) any { + return f.Call +} + +func (f *asyncArrowFuncObject) Call(call FunctionCall) Value { + return f.asyncCall(call, f.arrowFuncObject.vmCall) +} + +func (f *asyncArrowFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *asyncArrowFuncObject) export(*objectExportCtx) any { + return f.Call +} + +func (f *asyncArrowFuncObject) vmCall(vm *vm, n int) { + f.asyncVmCall(vm, n, f.arrowFuncObject.vmCall) +} + +func (f *asyncMethodFuncObject) Call(call FunctionCall) Value { + return f.asyncCall(call, f.methodFuncObject.vmCall) +} + +func (f *asyncMethodFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *asyncMethodFuncObject) export(ctx *objectExportCtx) any { + return f.Call +} + +func (f *asyncMethodFuncObject) vmCall(vm *vm, n int) { + f.asyncVmCall(vm, n, f.methodFuncObject.vmCall) +} + +func (f *baseJsFuncObject) asyncVmCall(vm *vm, n int, vmCall func(*vm, int)) { + ar := &asyncRunner{ + f: f.val, + vmCall: vmCall, + } + ar.start(n) + vm.push(ar.promiseCap.promise) + vm.pc++ +} + +func (f *asyncFuncObject) vmCall(vm *vm, n int) { + f.asyncVmCall(vm, n, f.baseJsFuncObject.vmCall) +} + +type asyncRunner struct { + gen generator + promiseCap *promiseCapability + f *Object + vmCall func(*vm, int) +} + +func (ar *asyncRunner) onFulfilled(call FunctionCall) Value { + ar.gen.vm.curAsyncRunner = ar + defer func() { + ar.gen.vm.curAsyncRunner = nil + }() + arg := call.Argument(0) + res, resType, ex := ar.gen.next(arg) + ar.step(res, resType == resultNormal, ex) + return _undefined +} + +func (ar *asyncRunner) onRejected(call FunctionCall) Value { + ar.gen.vm.curAsyncRunner = ar + defer func() { + ar.gen.vm.curAsyncRunner = nil + }() + reason := call.Argument(0) + res, resType, ex := ar.gen.nextThrow(reason) + ar.step(res, resType == resultNormal, ex) + return _undefined +} + +func (ar *asyncRunner) step(res Value, done bool, ex *Exception) { + r := ar.f.runtime + if done || ex != nil { + if ex == nil { + ar.promiseCap.resolve(res) + } else { + ar.promiseCap.reject(ex.val) + } + return + } + + // await + promise := r.promiseResolve(r.getPromise(), res) + promise.self.(*Promise).addReactions(&promiseReaction{ + typ: promiseReactionFulfill, + handler: &jobCallback{callback: ar.onFulfilled}, + asyncRunner: ar, + }, &promiseReaction{ + typ: promiseReactionReject, + handler: &jobCallback{callback: ar.onRejected}, + asyncRunner: ar, + }) +} + +func (ar *asyncRunner) start(nArgs int) { + r := ar.f.runtime + ar.gen.vm = r.vm + ar.promiseCap = r.newPromiseCapability(r.getPromise()) + sp := r.vm.sp + ar.gen.enter() + ar.vmCall(r.vm, nArgs) + res, resType, ex := ar.gen.step() + ar.step(res, resType == resultNormal, ex) + if ex != nil { + r.vm.sp = sp - nArgs - 2 + } + r.vm.popTryFrame() + r.vm.popCtx() +} + +type generator struct { + ctx execCtx + vm *vm + + tryStackLen, iterStackLen, refStackLen uint32 +} + +func (g *generator) storeLengths() { + g.tryStackLen, g.iterStackLen, g.refStackLen = uint32(len(g.vm.tryStack)), uint32(len(g.vm.iterStack)), uint32(len(g.vm.refStack)) +} + +func (g *generator) enter() { + g.vm.pushCtx() + g.vm.pushTryFrame(tryPanicMarker, -1) + g.vm.prg, g.vm.sb, g.vm.pc = nil, -1, -2 // so that vm.run() halts after ret + g.storeLengths() +} + +func (g *generator) step() (res Value, resultType resultType, ex *Exception) { + for { + ex = g.vm.runTryInner() + if ex != nil { + return + } + if g.vm.halted() { + break + } + } + res = g.vm.pop() + if ym, ok := res.(*yieldMarker); ok { + resultType = ym.resultType + g.ctx = execCtx{} + g.vm.pc = -g.vm.pc + 1 + if res != yieldEmpty { + res = g.vm.pop() + } else { + res = nil + } + g.vm.suspend(&g.ctx, g.tryStackLen, g.iterStackLen, g.refStackLen) + g.vm.sp = g.vm.sb - 1 + g.vm.callStack = g.vm.callStack[:len(g.vm.callStack)-1] // remove the frame with pc == -2, as ret would do + } + return +} + +func (g *generator) enterNext() { + g.vm.pushCtx() + g.vm.pushTryFrame(tryPanicMarker, -1) + g.vm.callStack = append(g.vm.callStack, context{pc: -2}) // extra frame so that vm.run() halts after ret + g.storeLengths() + g.vm.resume(&g.ctx) +} + +func (g *generator) next(v Value) (Value, resultType, *Exception) { + g.enterNext() + if v != nil { + g.vm.push(v) + } + res, done, ex := g.step() + g.vm.popTryFrame() + g.vm.popCtx() + return res, done, ex +} + +func (g *generator) nextThrow(v any) (Value, resultType, *Exception) { + g.enterNext() + ex := g.vm.handleThrow(v) + if ex != nil { + g.vm.popTryFrame() + g.vm.popCtx() + return nil, resultNormal, ex + } + + res, resType, ex := g.step() + g.vm.popTryFrame() + g.vm.popCtx() + return res, resType, ex +} + +func (g *generatorObject) init(vmCall func(*vm, int), nArgs int) { + g.baseObject.init() + vm := g.val.runtime.vm + g.gen.vm = vm + + g.gen.enter() + vmCall(vm, nArgs) + + _, _, ex := g.gen.step() + + vm.popTryFrame() + if ex != nil { + panic(ex) + } + + g.state = genStateSuspendedStart + vm.popCtx() +} + +func (g *generatorObject) validate() { + if g.state == genStateExecuting { + panic(g.val.runtime.NewTypeError("Illegal generator state")) + } +} + +func (g *generatorObject) step(res Value, resType resultType, ex *Exception) Value { + if ex != nil { + g.delegated = nil + g.state = genStateCompleted + panic(ex) + } + switch resType { + case resultYield: + g.state = genStateSuspendedYield + return g.val.runtime.createIterResultObject(res, false) + case resultYieldDelegate: + g.state = genStateSuspendedYield + return g.delegate(res) + case resultYieldRes: + g.state = genStateSuspendedYieldRes + return g.val.runtime.createIterResultObject(res, false) + case resultYieldDelegateRes: + g.state = genStateSuspendedYieldRes + return g.delegate(res) + case resultNormal: + g.state = genStateCompleted + return g.val.runtime.createIterResultObject(res, true) + default: + panic(g.val.runtime.NewTypeError("Runtime bug: unexpected result type: %v", resType)) + } +} + +func (g *generatorObject) delegate(v Value) Value { + ex := g.val.runtime.try(func() { + g.delegated = g.val.runtime.getIterator(v, nil) + }) + if ex != nil { + g.delegated = nil + g.state = genStateCompleted + return g.step(g.gen.nextThrow(ex)) + } + return g.next(_undefined) +} + +func (g *generatorObject) tryCallDelegated(fn func() (Value, bool)) (ret Value, done bool) { + ex := g.val.runtime.try(func() { + ret, done = fn() + }) + if ex != nil { + g.delegated = nil + g.state = genStateExecuting + return g.step(g.gen.nextThrow(ex)), false + } + return +} + +func (g *generatorObject) callDelegated(method func(FunctionCall) Value, v Value) (Value, bool) { + res := g.val.runtime.toObject(method(FunctionCall{This: g.delegated.iterator, Arguments: []Value{v}})) + if iteratorComplete(res) { + g.delegated = nil + return iteratorValue(res), true + } + return res, false +} + +func (g *generatorObject) next(v Value) Value { + g.validate() + if g.state == genStateCompleted { + return g.val.runtime.createIterResultObject(_undefined, true) + } + if g.delegated != nil { + res, done := g.tryCallDelegated(func() (Value, bool) { + return g.callDelegated(g.delegated.next, v) + }) + if !done { + return res + } else { + v = res + } + } + if g.state != genStateSuspendedYieldRes { + v = nil + } + g.state = genStateExecuting + return g.step(g.gen.next(v)) +} + +func (g *generatorObject) throw(v Value) Value { + g.validate() + if g.state == genStateSuspendedStart { + g.state = genStateCompleted + } + if g.state == genStateCompleted { + panic(v) + } + if d := g.delegated; d != nil { + res, done := g.tryCallDelegated(func() (Value, bool) { + method := toMethod(g.delegated.iterator.self.getStr("throw", nil)) + if method != nil { + return g.callDelegated(method, v) + } + g.delegated = nil + d.returnIter() + panic(g.val.runtime.NewTypeError("The iterator does not provide a 'throw' method")) + }) + if !done { + return res + } + if g.state != genStateSuspendedYieldRes { + res = nil + } + g.state = genStateExecuting + return g.step(g.gen.next(res)) + } + g.state = genStateExecuting + return g.step(g.gen.nextThrow(v)) +} + +func (g *generatorObject) _return(v Value) Value { + g.validate() + if g.state == genStateSuspendedStart { + g.state = genStateCompleted + } + + if g.state == genStateCompleted { + return g.val.runtime.createIterResultObject(v, true) + } + + if d := g.delegated; d != nil { + res, done := g.tryCallDelegated(func() (Value, bool) { + method := toMethod(g.delegated.iterator.self.getStr("return", nil)) + if method != nil { + return g.callDelegated(method, v) + } + g.delegated = nil + return v, true + }) + if !done { + return res + } else { + v = res + } + } + + g.state = genStateExecuting + + g.gen.enterNext() + + vm := g.gen.vm + var ex *Exception + for len(vm.tryStack) > 0 { + tf := &vm.tryStack[len(vm.tryStack)-1] + if int(tf.callStackLen) != len(vm.callStack) { + break + } + + if tf.finallyPos >= 0 { + vm.sp = int(tf.sp) + vm.stash = tf.stash + vm.privEnv = tf.privEnv + ex1 := vm.restoreStacks(tf.iterLen, tf.refLen) + if ex1 != nil { + ex = ex1 + vm.popTryFrame() + continue + } + + vm.pc = int(tf.finallyPos) + tf.catchPos = tryPanicMarker + tf.finallyPos = -1 + tf.finallyRet = -2 // -1 would cause it to continue after leaveFinally + for { + ex1 := vm.runTryInner() + if ex1 != nil { + ex = ex1 + vm.popTryFrame() + break + } + if vm.halted() { + break + } + } + } else { + vm.popTryFrame() + } + } + + g.state = genStateCompleted + + vm.popTryFrame() + + if ex == nil { + ex = vm.restoreStacks(g.gen.iterStackLen, g.gen.refStackLen) + } + + if ex != nil { + panic(ex) + } + + vm.callStack = vm.callStack[:len(vm.callStack)-1] + vm.sp = vm.sb - 1 + vm.popCtx() + + return g.val.runtime.createIterResultObject(v, true) +} + +func (f *baseJsFuncObject) generatorCall(vmCall func(*vm, int), nArgs int) Value { + o := &Object{runtime: f.val.runtime} + + genObj := &generatorObject{ + baseObject: baseObject{ + class: classObject, + val: o, + extensible: true, + }, + } + o.self = genObj + genObj.init(vmCall, nArgs) + genObj.prototype = o.runtime.getPrototypeFromCtor(f.val, nil, o.runtime.getGeneratorPrototype()) + return o +} + +func (f *baseJsFuncObject) generatorVmCall(vmCall func(*vm, int), nArgs int) { + vm := f.val.runtime.vm + vm.push(f.generatorCall(vmCall, nArgs)) + vm.pc++ +} + +func (f *generatorFuncObject) vmCall(_ *vm, nArgs int) { + f.generatorVmCall(f.baseJsFuncObject.vmCall, nArgs) +} + +func (f *generatorFuncObject) Call(call FunctionCall) Value { + f.prepareForVmCall(call) + return f.generatorCall(f.baseJsFuncObject.vmCall, len(call.Arguments)) +} + +func (f *generatorFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *generatorFuncObject) export(*objectExportCtx) any { + return f.Call +} + +func (f *generatorFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return nil +} + +func (f *generatorMethodFuncObject) vmCall(_ *vm, nArgs int) { + f.generatorVmCall(f.methodFuncObject.vmCall, nArgs) +} + +func (f *generatorMethodFuncObject) Call(call FunctionCall) Value { + f.prepareForVmCall(call) + return f.generatorCall(f.methodFuncObject.vmCall, len(call.Arguments)) +} + +func (f *generatorMethodFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + return f.Call, true +} + +func (f *generatorMethodFuncObject) export(*objectExportCtx) any { + return f.Call +} diff --git a/pkg/xscript/engine/func_test.go b/pkg/xscript/engine/func_test.go new file mode 100644 index 0000000..dffd37d --- /dev/null +++ b/pkg/xscript/engine/func_test.go @@ -0,0 +1,309 @@ +package engine + +import ( + "errors" + "fmt" + "reflect" + "testing" +) + +func TestFuncProto(t *testing.T) { + const SCRIPT = ` + "use strict"; + function A() {} + A.__proto__ = Object; + A.prototype = {}; + + function B() {} + B.__proto__ = Object.create(null); + var thrown = false; + try { + delete B.prototype; + } catch (e) { + thrown = e instanceof TypeError; + } + thrown; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestFuncPrototypeRedefine(t *testing.T) { + const SCRIPT = ` + let thrown = false; + try { + Object.defineProperty(function() {}, "prototype", { + set: function(_value) {}, + }); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + thrown; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestFuncExport(t *testing.T) { + vm := New() + typ := reflect.TypeOf((func(FunctionCall) Value)(nil)) + + f := func(expr string, t *testing.T) { + v, err := vm.RunString(expr) + if err != nil { + t.Fatal(err) + } + if actualTyp := v.ExportType(); actualTyp != typ { + t.Fatalf("Invalid export type: %v", actualTyp) + } + ev := v.Export() + if actualTyp := reflect.TypeOf(ev); actualTyp != typ { + t.Fatalf("Invalid export value: %v", ev) + } + } + + t.Run("regular function", func(t *testing.T) { + f("(function() {})", t) + }) + + t.Run("arrow function", func(t *testing.T) { + f("(()=>{})", t) + }) + + t.Run("method", func(t *testing.T) { + f("({m() {}}).m", t) + }) + + t.Run("class", func(t *testing.T) { + f("(class {})", t) + }) +} + +func TestFuncWrapUnwrap(t *testing.T) { + vm := New() + f := func(a int, b string) bool { + return a > 0 && b != "" + } + var f1 func(int, string) bool + v := vm.ToValue(f) + if et := v.ExportType(); et != reflect.TypeOf(f1) { + t.Fatal(et) + } + err := vm.ExportTo(v, &f1) + if err != nil { + t.Fatal(err) + } + if !f1(1, "a") { + t.Fatal("not true") + } +} + +func TestWrappedFunc(t *testing.T) { + vm := New() + f := func(a int, b string) bool { + return a > 0 && b != "" + } + vm.Set("f", f) + const SCRIPT = ` + assert.sameValue(typeof f, "function"); + const s = f.toString() + assert(s.endsWith("TestWrappedFunc.func1() { [native code] }"), s); + assert(f(1, "a")); + assert(!f(0, "")); + ` + vm.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestWrappedFuncErrorPassthrough(t *testing.T) { + vm := New() + e := errors.New("test") + f := func(a int) error { + if a > 0 { + return e + } + return nil + } + + var f1 func(a int64) error + err := vm.ExportTo(vm.ToValue(f), &f1) + if err != nil { + t.Fatal(err) + } + if err := f1(1); err != e { + t.Fatal(err) + } +} + +func ExampleAssertConstructor() { + vm := New() + res, err := vm.RunString(` + (class C { + constructor(x) { + this.x = x; + } + }) + `) + if err != nil { + panic(err) + } + if ctor, ok := AssertConstructor(res); ok { + obj, err := ctor(nil, vm.ToValue("Test")) + if err != nil { + panic(err) + } + fmt.Print(obj.Get("x")) + } else { + panic("Not a constructor") + } + // Output: Test +} + +type testAsyncCtx struct { + group string + refCount int +} + +type testAsyncContextTracker struct { + ctx *testAsyncCtx + logFunc func(...any) + resumed bool +} + +func (s *testAsyncContextTracker) Grab() any { + ctx := s.ctx + if ctx != nil { + s.logFunc("Grab", ctx.group) + ctx.refCount++ + } + return ctx +} + +func (s *testAsyncContextTracker) Resumed(trackingObj any) { + s.logFunc("Resumed", trackingObj) + if s.resumed { + panic("Nested Resumed() calls") + } + s.ctx = trackingObj.(*testAsyncCtx) + s.resumed = true +} + +func (s *testAsyncContextTracker) releaseCtx() { + s.ctx.refCount-- + if s.ctx.refCount < 0 { + panic("refCount < 0") + } + if s.ctx.refCount == 0 { + s.logFunc(s.ctx.group, "is finished") + } +} + +func (s *testAsyncContextTracker) Exited() { + s.logFunc("Exited") + if s.ctx != nil { + s.releaseCtx() + s.ctx = nil + } + s.resumed = false +} + +func TestAsyncContextTracker(t *testing.T) { + r := New() + var tracker testAsyncContextTracker + tracker.logFunc = t.Log + + group := func(name string, asyncFunc func(FunctionCall) Value) Value { + prevCtx := tracker.ctx + defer func() { + t.Log("Returned", name) + tracker.releaseCtx() + tracker.ctx = prevCtx + }() + tracker.ctx = &testAsyncCtx{ + group: name, + refCount: 1, + } + t.Log("Set", name) + return asyncFunc(FunctionCall{}) + } + r.SetAsyncContextTracker(&tracker) + r.Set("group", group) + r.Set("check", func(expectedGroup, msg string) { + var groupName string + if tracker.ctx != nil { + groupName = tracker.ctx.group + } + if groupName != expectedGroup { + t.Fatalf("Unexpected group (%q), expected %q in %s", groupName, expectedGroup, msg) + } + t.Log("In", msg) + }) + + t.Run("", func(t *testing.T) { + _, err := r.RunString(` + group("1", async () => { + check("1", "line A"); + await 3; + check("1", "line B"); + group("2", async () => { + check("2", "line C"); + await 4; + check("2", "line D"); + }) + }).then(() => { + check("", "line E"); + }) + `) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("", func(t *testing.T) { + _, err := r.RunString(` + group("some", async () => { + check("some", "line A"); + (async () => { + check("some", "line B"); + await 1; + check("some", "line C"); + await 2; + check("some", "line D"); + })(); + check("some", "line E"); + }); + `) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("", func(t *testing.T) { + _, err := r.RunString(` + group("Main", async () => { + check("Main", "0.1"); + await Promise.all([ + group("A", async () => { + check("A", "1.1"); + await 1; + check("A", "1.2"); + }), + (async () => { + check("Main", "3.1"); + })(), + group("B", async () => { + check("B", "2.1"); + await 2; + check("B", "2.2"); + }) + ]); + check("Main", "0.2"); + }); + `) + if err != nil { + t.Fatal(err) + } + }) +} diff --git a/pkg/xscript/engine/ipow.go b/pkg/xscript/engine/ipow.go new file mode 100644 index 0000000..2d47aef --- /dev/null +++ b/pkg/xscript/engine/ipow.go @@ -0,0 +1,98 @@ +package engine + +// inspired by https://gist.github.com/orlp/3551590 + +var overflows = [64]int64{ + 9223372036854775807, 9223372036854775807, 3037000499, 2097151, + 55108, 6208, 1448, 511, + 234, 127, 78, 52, + 38, 28, 22, 18, + 15, 13, 11, 9, + 8, 7, 7, 6, + 6, 5, 5, 5, + 4, 4, 4, 4, + 3, 3, 3, 3, + 3, 3, 3, 3, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, + 2, 2, 2, 2, +} + +var highestBitSet = [63]byte{ + 0, 1, 2, 2, 3, 3, 3, 3, + 4, 4, 4, 4, 4, 4, 4, 4, + 5, 5, 5, 5, 5, 5, 5, 5, + 5, 5, 5, 5, 5, 5, 5, 5, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, 6, + 6, 6, 6, 6, 6, 6, 6, +} + +func ipow(base, exp int64) (result int64) { + if exp >= 63 { + if base == 1 { + return 1 + } + + if base == -1 { + return 1 - 2*(exp&1) + } + + return 0 + } + + if base > overflows[exp] || -base > overflows[exp] { + return 0 + } + + result = 1 + + switch highestBitSet[byte(exp)] { + case 6: + if exp&1 != 0 { + result *= base + } + exp >>= 1 + base *= base + fallthrough + case 5: + if exp&1 != 0 { + result *= base + } + exp >>= 1 + base *= base + fallthrough + case 4: + if exp&1 != 0 { + result *= base + } + exp >>= 1 + base *= base + fallthrough + case 3: + if exp&1 != 0 { + result *= base + } + exp >>= 1 + base *= base + fallthrough + case 2: + if exp&1 != 0 { + result *= base + } + exp >>= 1 + base *= base + fallthrough + case 1: + if exp&1 != 0 { + result *= base + } + fallthrough + default: + return result + } +} diff --git a/pkg/xscript/engine/map.go b/pkg/xscript/engine/map.go new file mode 100644 index 0000000..f39004b --- /dev/null +++ b/pkg/xscript/engine/map.go @@ -0,0 +1,169 @@ +package engine + +import ( + "hash/maphash" +) + +type mapEntry struct { + key, value Value + + iterPrev, iterNext *mapEntry + hNext *mapEntry +} + +type orderedMap struct { + hash *maphash.Hash + hashTable map[uint64]*mapEntry + iterFirst, iterLast *mapEntry + size int +} + +type orderedMapIter struct { + m *orderedMap + cur *mapEntry +} + +func (m *orderedMap) lookup(key Value) (h uint64, entry, hPrev *mapEntry) { + if key == _negativeZero { + key = intToValue(0) + } + h = key.hash(m.hash) + for entry = m.hashTable[h]; entry != nil && !entry.key.SameAs(key); hPrev, entry = entry, entry.hNext { + } + return +} + +func (m *orderedMap) set(key, value Value) { + h, entry, hPrev := m.lookup(key) + if entry != nil { + entry.value = value + } else { + if key == _negativeZero { + key = intToValue(0) + } + entry = &mapEntry{key: key, value: value} + if hPrev == nil { + m.hashTable[h] = entry + } else { + hPrev.hNext = entry + } + if m.iterLast != nil { + entry.iterPrev = m.iterLast + m.iterLast.iterNext = entry + } else { + m.iterFirst = entry + } + m.iterLast = entry + m.size++ + } +} + +func (m *orderedMap) get(key Value) Value { + _, entry, _ := m.lookup(key) + if entry != nil { + return entry.value + } + + return nil +} + +func (m *orderedMap) remove(key Value) bool { + h, entry, hPrev := m.lookup(key) + if entry != nil { + entry.key = nil + entry.value = nil + + // remove from the doubly-linked list + if entry.iterPrev != nil { + entry.iterPrev.iterNext = entry.iterNext + } else { + m.iterFirst = entry.iterNext + } + if entry.iterNext != nil { + entry.iterNext.iterPrev = entry.iterPrev + } else { + m.iterLast = entry.iterPrev + } + + // remove from the hashTable + if hPrev == nil { + if entry.hNext == nil { + delete(m.hashTable, h) + } else { + m.hashTable[h] = entry.hNext + } + } else { + hPrev.hNext = entry.hNext + } + + m.size-- + return true + } + + return false +} + +func (m *orderedMap) has(key Value) bool { + _, entry, _ := m.lookup(key) + return entry != nil +} + +func (iter *orderedMapIter) next() *mapEntry { + if iter.m == nil { + // closed iterator + return nil + } + + cur := iter.cur + // if the current item was deleted, track back to find the latest that wasn't + for cur != nil && cur.key == nil { + cur = cur.iterPrev + } + + if cur != nil { + cur = cur.iterNext + } else { + cur = iter.m.iterFirst + } + + if cur == nil { + iter.close() + } else { + iter.cur = cur + } + + return cur +} + +func (iter *orderedMapIter) close() { + iter.m = nil + iter.cur = nil +} + +func newOrderedMap(h *maphash.Hash) *orderedMap { + return &orderedMap{ + hash: h, + hashTable: make(map[uint64]*mapEntry), + } +} + +func (m *orderedMap) newIter() *orderedMapIter { + iter := &orderedMapIter{ + m: m, + } + return iter +} + +func (m *orderedMap) clear() { + for item := m.iterFirst; item != nil; item = item.iterNext { + item.key = nil + item.value = nil + if item.iterPrev != nil { + item.iterPrev.iterNext = nil + } + } + m.iterFirst = nil + m.iterLast = nil + m.hashTable = make(map[uint64]*mapEntry) + m.size = 0 +} diff --git a/pkg/xscript/engine/map_test.go b/pkg/xscript/engine/map_test.go new file mode 100644 index 0000000..dbaef7f --- /dev/null +++ b/pkg/xscript/engine/map_test.go @@ -0,0 +1,198 @@ +package engine + +import ( + "hash/maphash" + "math" + "strconv" + "testing" +) + +func testMapHashVal(v1, v2 Value, expected bool, t *testing.T) { + var h maphash.Hash + actual := v1.hash(&h) == v2.hash(&h) + if actual != expected { + t.Fatalf("testMapHashVal failed for %v, %v", v1, v2) + } +} + +func TestMapHash(t *testing.T) { + testMapHashVal(_NaN, _NaN, true, t) + testMapHashVal(valueTrue, valueFalse, false, t) + testMapHashVal(valueTrue, valueTrue, true, t) + testMapHashVal(intToValue(0), _negativeZero, true, t) + testMapHashVal(asciiString("Test"), asciiString("Test"), true, t) + testMapHashVal(newStringValue("Тест"), newStringValue("Тест"), true, t) + testMapHashVal(floatToValue(1.2345), floatToValue(1.2345), true, t) + testMapHashVal(SymIterator, SymToStringTag, false, t) + testMapHashVal(SymIterator, SymIterator, true, t) + + // The following tests introduce indeterministic behaviour + //testMapHashVal(asciiString("Test"), asciiString("Test1"), false, t) + //testMapHashVal(newStringValue("Тест"), asciiString("Test"), false, t) + //testMapHashVal(newStringValue("Тест"), newStringValue("Тест1"), false, t) +} + +func TestOrderedMap(t *testing.T) { + m := newOrderedMap(&maphash.Hash{}) + for i := int64(0); i < 50; i++ { + m.set(intToValue(i), asciiString(strconv.FormatInt(i, 10))) + } + if m.size != 50 { + t.Fatalf("Unexpected size: %d", m.size) + } + + for i := int64(0); i < 50; i++ { + expected := asciiString(strconv.FormatInt(i, 10)) + actual := m.get(intToValue(i)) + if !expected.SameAs(actual) { + t.Fatalf("Wrong value for %d", i) + } + } + + for i := int64(0); i < 50; i += 2 { + if !m.remove(intToValue(i)) { + t.Fatalf("remove(%d) return false", i) + } + } + if m.size != 25 { + t.Fatalf("Unexpected size: %d", m.size) + } + + iter := m.newIter() + count := 0 + for { + entry := iter.next() + if entry == nil { + break + } + m.remove(entry.key) + count++ + } + + if count != 25 { + t.Fatalf("Unexpected iter count: %d", count) + } + + if m.size != 0 { + t.Fatalf("Unexpected size: %d", m.size) + } +} + +func TestOrderedMapCollision(t *testing.T) { + m := newOrderedMap(&maphash.Hash{}) + n1 := uint64(123456789) + n2 := math.Float64frombits(n1) + n1Key := intToValue(int64(n1)) + n2Key := floatToValue(n2) + m.set(n1Key, asciiString("n1")) + m.set(n2Key, asciiString("n2")) + if m.size == len(m.hashTable) { + t.Fatal("Expected a collision but there wasn't one") + } + if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) { + t.Fatalf("unexpected n2Val: %v", n2Val) + } + if n1Val := m.get(n1Key); !asciiString("n1").SameAs(n1Val) { + t.Fatalf("unexpected nVal: %v", n1Val) + } + + if !m.remove(n1Key) { + t.Fatal("removing n1Key returned false") + } + if n2Val := m.get(n2Key); !asciiString("n2").SameAs(n2Val) { + t.Fatalf("2: unexpected n2Val: %v", n2Val) + } +} + +func TestOrderedMapIter(t *testing.T) { + m := newOrderedMap(&maphash.Hash{}) + iter := m.newIter() + ent := iter.next() + if ent != nil { + t.Fatal("entry should be nil") + } + iter1 := m.newIter() + m.set(intToValue(1), valueTrue) + ent = iter.next() + if ent != nil { + t.Fatal("2: entry should be nil") + } + ent = iter1.next() + if ent == nil { + t.Fatal("entry is nil") + } + if !intToValue(1).SameAs(ent.key) { + t.Fatal("unexpected key") + } + if !valueTrue.SameAs(ent.value) { + t.Fatal("unexpected value") + } +} + +func TestOrderedMapIterVisitAfterReAdd(t *testing.T) { + m := newOrderedMap(&maphash.Hash{}) + one := intToValue(1) + two := intToValue(2) + + m.set(one, valueTrue) + m.set(two, valueTrue) + iter := m.newIter() + entry := iter.next() + if !one.SameAs(entry.key) { + t.Fatalf("1: unexpected key: %v", entry.key) + } + if !m.remove(one) { + t.Fatal("remove returned false") + } + entry = iter.next() + if !two.SameAs(entry.key) { + t.Fatalf("2: unexpected key: %v", entry.key) + } + m.set(one, valueTrue) + entry = iter.next() + if entry == nil { + t.Fatal("entry is nil") + } + if !one.SameAs(entry.key) { + t.Fatalf("3: unexpected key: %v", entry.key) + } +} + +func TestOrderedMapIterAddAfterClear(t *testing.T) { + m := newOrderedMap(&maphash.Hash{}) + one := intToValue(1) + m.set(one, valueTrue) + iter := m.newIter() + iter.next() + m.clear() + m.set(one, valueTrue) + entry := iter.next() + if entry == nil { + t.Fatal("entry is nil") + } + if entry.key != one { + t.Fatalf("unexpected key: %v", entry.key) + } + entry = iter.next() + if entry != nil { + t.Fatalf("entry is not nil: %v", entry) + } +} + +func TestOrderedMapIterDeleteCurrent(t *testing.T) { + m := newOrderedMap(&maphash.Hash{}) + one := intToValue(1) + two := intToValue(2) + iter := m.newIter() + m.set(one, valueTrue) + m.set(two, valueTrue) + entry := iter.next() + if entry.key != one { + t.Fatalf("unexpected key: %v", entry.key) + } + m.remove(one) + entry = iter.next() + if entry.key != two { + t.Fatalf("2: unexpected key: %v", entry.key) + } +} diff --git a/pkg/xscript/engine/object.go b/pkg/xscript/engine/object.go new file mode 100644 index 0000000..08a0881 --- /dev/null +++ b/pkg/xscript/engine/object.go @@ -0,0 +1,1843 @@ +package engine + +import ( + "fmt" + "math" + "reflect" + "sort" + + "pandax/pkg/xscript/engine/unistring" +) + +const ( + classObject = "Object" + classArray = "Array" + classWeakSet = "WeakSet" + classWeakMap = "WeakMap" + classMap = "Map" + classMath = "Math" + classSet = "Set" + classFunction = "Function" + classAsyncFunction = "AsyncFunction" + classNumber = "Number" + classString = "String" + classBoolean = "Boolean" + classError = "Error" + classRegExp = "RegExp" + classDate = "Date" + classJSON = "JSON" + classGlobal = "global" + classPromise = "Promise" + + classArrayIterator = "Array Iterator" + classMapIterator = "Map Iterator" + classSetIterator = "Set Iterator" + classStringIterator = "String Iterator" + classRegExpStringIterator = "RegExp String Iterator" + + classGenerator = "Generator" + classGeneratorFunction = "GeneratorFunction" +) + +var ( + hintDefault Value = asciiString("default") + hintNumber Value = asciiString("number") + hintString Value = asciiString("string") +) + +type Object struct { + id uint64 + runtime *Runtime + self objectImpl + + weakRefs map[weakMap]Value +} + +type iterNextFunc func() (propIterItem, iterNextFunc) + +type PropertyDescriptor struct { + jsDescriptor *Object + + Value Value + + Writable, Configurable, Enumerable Flag + + Getter, Setter Value +} + +func (p *PropertyDescriptor) Empty() bool { + var empty PropertyDescriptor + return *p == empty +} + +func (p *PropertyDescriptor) IsAccessor() bool { + return p.Setter != nil || p.Getter != nil +} + +func (p *PropertyDescriptor) IsData() bool { + return p.Value != nil || p.Writable != FLAG_NOT_SET +} + +func (p *PropertyDescriptor) IsGeneric() bool { + return !p.IsAccessor() && !p.IsData() +} + +func (p *PropertyDescriptor) toValue(r *Runtime) Value { + if p.jsDescriptor != nil { + return p.jsDescriptor + } + if p.Empty() { + return _undefined + } + o := r.NewObject() + s := o.self + + if p.Value != nil { + s._putProp("value", p.Value, true, true, true) + } + + if p.Writable != FLAG_NOT_SET { + s._putProp("writable", valueBool(p.Writable.Bool()), true, true, true) + } + + if p.Enumerable != FLAG_NOT_SET { + s._putProp("enumerable", valueBool(p.Enumerable.Bool()), true, true, true) + } + + if p.Configurable != FLAG_NOT_SET { + s._putProp("configurable", valueBool(p.Configurable.Bool()), true, true, true) + } + + if p.Getter != nil { + s._putProp("get", p.Getter, true, true, true) + } + if p.Setter != nil { + s._putProp("set", p.Setter, true, true, true) + } + + return o +} + +func (p *PropertyDescriptor) complete() { + if p.Getter == nil && p.Setter == nil { + if p.Value == nil { + p.Value = _undefined + } + if p.Writable == FLAG_NOT_SET { + p.Writable = FLAG_FALSE + } + } else { + if p.Getter == nil { + p.Getter = _undefined + } + if p.Setter == nil { + p.Setter = _undefined + } + } + if p.Enumerable == FLAG_NOT_SET { + p.Enumerable = FLAG_FALSE + } + if p.Configurable == FLAG_NOT_SET { + p.Configurable = FLAG_FALSE + } +} + +type objectExportCacheItem map[reflect.Type]any + +type objectExportCtx struct { + cache map[*Object]any +} + +type objectImpl interface { + sortable + className() string + typeOf() String + getStr(p unistring.String, receiver Value) Value + getIdx(p valueInt, receiver Value) Value + getSym(p *Symbol, receiver Value) Value + + getOwnPropStr(unistring.String) Value + getOwnPropIdx(valueInt) Value + getOwnPropSym(*Symbol) Value + + setOwnStr(p unistring.String, v Value, throw bool) bool + setOwnIdx(p valueInt, v Value, throw bool) bool + setOwnSym(p *Symbol, v Value, throw bool) bool + + setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) + setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) + setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) + + hasPropertyStr(unistring.String) bool + hasPropertyIdx(idx valueInt) bool + hasPropertySym(s *Symbol) bool + + hasOwnPropertyStr(unistring.String) bool + hasOwnPropertyIdx(valueInt) bool + hasOwnPropertySym(s *Symbol) bool + + defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool + defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool + defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool + + deleteStr(name unistring.String, throw bool) bool + deleteIdx(idx valueInt, throw bool) bool + deleteSym(s *Symbol, throw bool) bool + + assertCallable() (call func(FunctionCall) Value, ok bool) + vmCall(vm *vm, n int) + assertConstructor() func(args []Value, newTarget *Object) *Object + proto() *Object + setProto(proto *Object, throw bool) bool + hasInstance(v Value) bool + isExtensible() bool + preventExtensions(throw bool) bool + + export(ctx *objectExportCtx) any + exportType() reflect.Type + exportToMap(m reflect.Value, typ reflect.Type, ctx *objectExportCtx) error + exportToArrayOrSlice(s reflect.Value, typ reflect.Type, ctx *objectExportCtx) error + equal(objectImpl) bool + + iterateStringKeys() iterNextFunc + iterateSymbols() iterNextFunc + iterateKeys() iterNextFunc + + stringKeys(all bool, accum []Value) []Value + symbols(all bool, accum []Value) []Value + keys(all bool, accum []Value) []Value + + _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value + _putSym(s *Symbol, prop Value) + getPrivateEnv(typ *privateEnvType, create bool) *privateElements +} + +type baseObject struct { + class string + val *Object + prototype *Object + extensible bool + + values map[unistring.String]Value + propNames []unistring.String + + lastSortedPropLen, idxPropCount int + + symValues *orderedMap + + privateElements map[*privateEnvType]*privateElements +} + +type guardedObject struct { + baseObject + guardedProps map[unistring.String]struct{} +} + +type primitiveValueObject struct { + baseObject + pValue Value +} + +func (o *primitiveValueObject) export(*objectExportCtx) any { + return o.pValue.Export() +} + +func (o *primitiveValueObject) exportType() reflect.Type { + return o.pValue.ExportType() +} + +type FunctionCall struct { + This Value + Arguments []Value +} + +type ConstructorCall struct { + This *Object + Arguments []Value + NewTarget *Object +} + +func (f FunctionCall) Argument(idx int) Value { + if idx < len(f.Arguments) { + return f.Arguments[idx] + } + return _undefined +} + +func (f ConstructorCall) Argument(idx int) Value { + if idx < len(f.Arguments) { + return f.Arguments[idx] + } + return _undefined +} + +func (o *baseObject) init() { + o.values = make(map[unistring.String]Value) +} + +func (o *baseObject) className() string { + return o.class +} + +func (o *baseObject) typeOf() String { + return stringObjectC +} + +func (o *baseObject) hasPropertyStr(name unistring.String) bool { + if o.val.self.hasOwnPropertyStr(name) { + return true + } + if o.prototype != nil { + return o.prototype.self.hasPropertyStr(name) + } + return false +} + +func (o *baseObject) hasPropertyIdx(idx valueInt) bool { + return o.val.self.hasPropertyStr(idx.string()) +} + +func (o *baseObject) hasPropertySym(s *Symbol) bool { + if o.hasOwnPropertySym(s) { + return true + } + if o.prototype != nil { + return o.prototype.self.hasPropertySym(s) + } + return false +} + +func (o *baseObject) getWithOwnProp(prop, p, receiver Value) Value { + if prop == nil && o.prototype != nil { + if receiver == nil { + return o.prototype.get(p, o.val) + } + return o.prototype.get(p, receiver) + } + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) + } + return prop +} + +func (o *baseObject) getStrWithOwnProp(prop Value, name unistring.String, receiver Value) Value { + if prop == nil && o.prototype != nil { + if receiver == nil { + return o.prototype.self.getStr(name, o.val) + } + return o.prototype.self.getStr(name, receiver) + } + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) + } + return prop +} + +func (o *baseObject) getIdx(idx valueInt, receiver Value) Value { + return o.val.self.getStr(idx.string(), receiver) +} + +func (o *baseObject) getSym(s *Symbol, receiver Value) Value { + return o.getWithOwnProp(o.getOwnPropSym(s), s, receiver) +} + +func (o *baseObject) getStr(name unistring.String, receiver Value) Value { + prop := o.values[name] + if prop == nil { + if o.prototype != nil { + if receiver == nil { + return o.prototype.self.getStr(name, o.val) + } + return o.prototype.self.getStr(name, receiver) + } + } + if prop, ok := prop.(*valueProperty); ok { + if receiver == nil { + return prop.get(o.val) + } + return prop.get(receiver) + } + return prop +} + +func (o *baseObject) getOwnPropIdx(idx valueInt) Value { + return o.val.self.getOwnPropStr(idx.string()) +} + +func (o *baseObject) getOwnPropSym(s *Symbol) Value { + if o.symValues != nil { + return o.symValues.get(s) + } + return nil +} + +func (o *baseObject) getOwnPropStr(name unistring.String) Value { + return o.values[name] +} + +func (o *baseObject) checkDeleteProp(name unistring.String, prop *valueProperty, throw bool) bool { + if !prop.configurable { + if throw { + r := o.val.runtime + panic(r.NewTypeError("Cannot delete property '%s' of %s", name, r.objectproto_toString(FunctionCall{This: o.val}))) + } + return false + } + return true +} + +func (o *baseObject) checkDelete(name unistring.String, val Value, throw bool) bool { + if val, ok := val.(*valueProperty); ok { + return o.checkDeleteProp(name, val, throw) + } + return true +} + +func (o *baseObject) _delete(name unistring.String) { + delete(o.values, name) + for i, n := range o.propNames { + if n == name { + names := o.propNames + if namesMarkedForCopy(names) { + newNames := make([]unistring.String, len(names)-1, shrinkCap(len(names), cap(names))) + copy(newNames, names[:i]) + copy(newNames[i:], names[i+1:]) + o.propNames = newNames + } else { + copy(names[i:], names[i+1:]) + names[len(names)-1] = "" + o.propNames = names[:len(names)-1] + } + if i < o.lastSortedPropLen { + o.lastSortedPropLen-- + if i < o.idxPropCount { + o.idxPropCount-- + } + } + break + } + } +} + +func (o *baseObject) deleteIdx(idx valueInt, throw bool) bool { + return o.val.self.deleteStr(idx.string(), throw) +} + +func (o *baseObject) deleteSym(s *Symbol, throw bool) bool { + if o.symValues != nil { + if val := o.symValues.get(s); val != nil { + if !o.checkDelete(s.descriptiveString().string(), val, throw) { + return false + } + o.symValues.remove(s) + } + } + return true +} + +func (o *baseObject) deleteStr(name unistring.String, throw bool) bool { + if val, exists := o.values[name]; exists { + if !o.checkDelete(name, val, throw) { + return false + } + o._delete(name) + } + return true +} + +func (o *baseObject) setProto(proto *Object, throw bool) bool { + current := o.prototype + if current.SameAs(proto) { + return true + } + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "%s is not extensible", o.val) + return false + } + for p := proto; p != nil; p = p.self.proto() { + if p.SameAs(o.val) { + o.val.runtime.typeErrorResult(throw, "Cyclic __proto__ value") + return false + } + if _, ok := p.self.(*proxyObject); ok { + break + } + } + o.prototype = proto + return true +} + +func (o *baseObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + ownDesc := o.values[name] + if ownDesc == nil { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignStr(name, val, o.val, throw); handled { + return res + } + } + // new property + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false + } else { + o.values[name] = val + names := copyNamesIfNeeded(o.propNames, 1) + o.propNames = append(names, name) + } + return true + } + if prop, ok := ownDesc.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } else { + prop.set(o.val, val) + } + } else { + o.values[name] = val + } + return true +} + +func (o *baseObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { + return o.val.self.setOwnStr(idx.string(), val, throw) +} + +func (o *baseObject) setOwnSym(name *Symbol, val Value, throw bool) bool { + var ownDesc Value + if o.symValues != nil { + ownDesc = o.symValues.get(name) + } + if ownDesc == nil { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignSym(name, val, o.val, throw); handled { + return res + } + } + // new property + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false + } else { + if o.symValues == nil { + o.symValues = newOrderedMap(nil) + } + o.symValues.set(name, val) + } + return true + } + if prop, ok := ownDesc.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } else { + prop.set(o.val, val) + } + } else { + o.symValues.set(name, val) + } + return true +} + +func (o *baseObject) _setForeignStr(name unistring.String, prop, val, receiver Value, throw bool) (bool, bool) { + if prop != nil { + if prop, ok := prop.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false, true + } + if prop.setterFunc != nil { + prop.set(receiver, val) + return true, true + } + } + } else { + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignStr(name, val, receiver, throw) + } + return proto.self.setOwnStr(name, val, throw), true + } + } + return false, false +} + +func (o *baseObject) _setForeignIdx(idx valueInt, prop, val, receiver Value, throw bool) (bool, bool) { + if prop != nil { + if prop, ok := prop.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d'", idx) + return false, true + } + if prop.setterFunc != nil { + prop.set(receiver, val) + return true, true + } + } + } else { + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignIdx(idx, val, receiver, throw) + } + return proto.self.setOwnIdx(idx, val, throw), true + } + } + return false, false +} + +func (o *baseObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, o.values[name], val, receiver, throw) +} + +func (o *baseObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) { + if idx := toIdx(name); idx != math.MaxUint32 { + o.ensurePropOrder() + if o.idxPropCount == 0 { + return o._setForeignIdx(name, name, nil, receiver, throw) + } + } + return o.setForeignStr(name.string(), val, receiver, throw) +} + +func (o *baseObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) { + var prop Value + if o.symValues != nil { + prop = o.symValues.get(name) + } + if prop != nil { + if prop, ok := prop.(*valueProperty); ok { + if !prop.isWritable() { + o.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false, true + } + if prop.setterFunc != nil { + prop.set(receiver, val) + return true, true + } + } + } else { + if proto := o.prototype; proto != nil { + if receiver != o.val { + return proto.self.setForeignSym(name, val, receiver, throw) + } + return proto.self.setOwnSym(name, val, throw), true + } + } + return false, false +} + +func (o *baseObject) hasOwnPropertySym(s *Symbol) bool { + if o.symValues != nil { + return o.symValues.has(s) + } + return false +} + +func (o *baseObject) hasOwnPropertyStr(name unistring.String) bool { + _, exists := o.values[name] + return exists +} + +func (o *baseObject) hasOwnPropertyIdx(idx valueInt) bool { + return o.val.self.hasOwnPropertyStr(idx.string()) +} + +func (o *baseObject) _defineOwnProperty(name unistring.String, existingValue Value, descr PropertyDescriptor, throw bool) (val Value, ok bool) { + + getterObj, _ := descr.Getter.(*Object) + setterObj, _ := descr.Setter.(*Object) + + var existing *valueProperty + + if existingValue == nil { + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", name) + return nil, false + } + existing = &valueProperty{} + } else { + if existing, ok = existingValue.(*valueProperty); !ok { + existing = &valueProperty{ + writable: true, + enumerable: true, + configurable: true, + value: existingValue, + } + } + + if !existing.configurable { + if descr.Configurable == FLAG_TRUE { + goto Reject + } + if descr.Enumerable != FLAG_NOT_SET && descr.Enumerable.Bool() != existing.enumerable { + goto Reject + } + } + if existing.accessor && descr.Value != nil || !existing.accessor && (getterObj != nil || setterObj != nil) { + if !existing.configurable { + goto Reject + } + } else if !existing.accessor { + if !existing.configurable { + if !existing.writable { + if descr.Writable == FLAG_TRUE { + goto Reject + } + if descr.Value != nil && !descr.Value.SameAs(existing.value) { + goto Reject + } + } + } + } else { + if !existing.configurable { + if descr.Getter != nil && existing.getterFunc != getterObj || descr.Setter != nil && existing.setterFunc != setterObj { + goto Reject + } + } + } + } + + if descr.Writable == FLAG_TRUE && descr.Enumerable == FLAG_TRUE && descr.Configurable == FLAG_TRUE && descr.Value != nil { + return descr.Value, true + } + + if descr.Writable != FLAG_NOT_SET { + existing.writable = descr.Writable.Bool() + } + if descr.Enumerable != FLAG_NOT_SET { + existing.enumerable = descr.Enumerable.Bool() + } + if descr.Configurable != FLAG_NOT_SET { + existing.configurable = descr.Configurable.Bool() + } + + if descr.Value != nil { + existing.value = descr.Value + existing.getterFunc = nil + existing.setterFunc = nil + } + + if descr.Value != nil || descr.Writable != FLAG_NOT_SET { + existing.accessor = false + } + + if descr.Getter != nil { + existing.getterFunc = propGetter(o.val, descr.Getter, o.val.runtime) + existing.value = nil + existing.accessor = true + } + + if descr.Setter != nil { + existing.setterFunc = propSetter(o.val, descr.Setter, o.val.runtime) + existing.value = nil + existing.accessor = true + } + + if !existing.accessor && existing.value == nil { + existing.value = _undefined + } + + return existing, true + +Reject: + o.val.runtime.typeErrorResult(throw, "Cannot redefine property: %s", name) + return nil, false + +} + +func (o *baseObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + existingVal := o.values[name] + if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok { + o.values[name] = v + if existingVal == nil { + names := copyNamesIfNeeded(o.propNames, 1) + o.propNames = append(names, name) + } + return true + } + return false +} + +func (o *baseObject) defineOwnPropertyIdx(idx valueInt, desc PropertyDescriptor, throw bool) bool { + return o.val.self.defineOwnPropertyStr(idx.string(), desc, throw) +} + +func (o *baseObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool { + var existingVal Value + if o.symValues != nil { + existingVal = o.symValues.get(s) + } + if v, ok := o._defineOwnProperty(s.descriptiveString().string(), existingVal, descr, throw); ok { + if o.symValues == nil { + o.symValues = newOrderedMap(nil) + } + o.symValues.set(s, v) + return true + } + return false +} + +func (o *baseObject) _put(name unistring.String, v Value) { + if _, exists := o.values[name]; !exists { + names := copyNamesIfNeeded(o.propNames, 1) + o.propNames = append(names, name) + } + + o.values[name] = v +} + +func valueProp(value Value, writable, enumerable, configurable bool) Value { + if writable && enumerable && configurable { + return value + } + return &valueProperty{ + value: value, + writable: writable, + enumerable: enumerable, + configurable: configurable, + } +} + +func (o *baseObject) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { + prop := valueProp(value, writable, enumerable, configurable) + o._put(name, prop) + return prop +} + +func (o *baseObject) _putSym(s *Symbol, prop Value) { + if o.symValues == nil { + o.symValues = newOrderedMap(nil) + } + o.symValues.set(s, prop) +} + +func (o *baseObject) getPrivateEnv(typ *privateEnvType, create bool) *privateElements { + env := o.privateElements[typ] + if env != nil && create { + panic(o.val.runtime.NewTypeError("Private fields for the class have already been set")) + } + if env == nil && create { + env = &privateElements{ + fields: make([]Value, typ.numFields), + } + if o.privateElements == nil { + o.privateElements = make(map[*privateEnvType]*privateElements) + } + o.privateElements[typ] = env + } + return env +} + +func (o *Object) tryPrimitive(methodName unistring.String) Value { + if method, ok := o.self.getStr(methodName, nil).(*Object); ok { + if call, ok := method.self.assertCallable(); ok { + v := call(FunctionCall{ + This: o, + }) + if _, fail := v.(*Object); !fail { + return v + } + } + } + return nil +} + +func (o *Object) ordinaryToPrimitiveNumber() Value { + if v := o.tryPrimitive("valueOf"); v != nil { + return v + } + + if v := o.tryPrimitive("toString"); v != nil { + return v + } + + panic(o.runtime.NewTypeError("Could not convert %v to primitive", o.self)) +} + +func (o *Object) ordinaryToPrimitiveString() Value { + if v := o.tryPrimitive("toString"); v != nil { + return v + } + + if v := o.tryPrimitive("valueOf"); v != nil { + return v + } + + panic(o.runtime.NewTypeError("Could not convert %v (%T) to primitive", o.self, o.self)) +} + +func (o *Object) tryExoticToPrimitive(hint Value) Value { + exoticToPrimitive := toMethod(o.self.getSym(SymToPrimitive, nil)) + if exoticToPrimitive != nil { + ret := exoticToPrimitive(FunctionCall{ + This: o, + Arguments: []Value{hint}, + }) + if _, fail := ret.(*Object); !fail { + return ret + } + panic(o.runtime.NewTypeError("Cannot convert object to primitive value")) + } + return nil +} + +func (o *Object) toPrimitiveNumber() Value { + if v := o.tryExoticToPrimitive(hintNumber); v != nil { + return v + } + + return o.ordinaryToPrimitiveNumber() +} + +func (o *Object) toPrimitiveString() Value { + if v := o.tryExoticToPrimitive(hintString); v != nil { + return v + } + + return o.ordinaryToPrimitiveString() +} + +func (o *Object) toPrimitive() Value { + if v := o.tryExoticToPrimitive(hintDefault); v != nil { + return v + } + return o.ordinaryToPrimitiveNumber() +} + +func (o *baseObject) assertCallable() (func(FunctionCall) Value, bool) { + return nil, false +} + +func (o *baseObject) vmCall(vm *vm, _ int) { + panic(vm.r.NewTypeError("Not a function: %s", o.val.toString())) +} + +func (o *baseObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return nil +} + +func (o *baseObject) proto() *Object { + return o.prototype +} + +func (o *baseObject) isExtensible() bool { + return o.extensible +} + +func (o *baseObject) preventExtensions(bool) bool { + o.extensible = false + return true +} + +func (o *baseObject) sortLen() int { + return toIntStrict(toLength(o.val.self.getStr("length", nil))) +} + +func (o *baseObject) sortGet(i int) Value { + return o.val.self.getIdx(valueInt(i), nil) +} + +func (o *baseObject) swap(i int, j int) { + ii := valueInt(i) + jj := valueInt(j) + + x := o.val.self.getIdx(ii, nil) + y := o.val.self.getIdx(jj, nil) + + o.val.self.setOwnIdx(ii, y, false) + o.val.self.setOwnIdx(jj, x, false) +} + +func (o *baseObject) export(ctx *objectExportCtx) any { + if v, exists := ctx.get(o.val); exists { + return v + } + keys := o.stringKeys(false, nil) + m := make(map[string]any, len(keys)) + ctx.put(o.val, m) + for _, itemName := range keys { + itemNameStr := itemName.String() + v := o.val.self.getStr(itemName.string(), nil) + if v != nil { + m[itemNameStr] = exportValue(v, ctx) + } else { + m[itemNameStr] = nil + } + } + + return m +} + +func (o *baseObject) exportType() reflect.Type { + return reflectTypeMap +} + +func genericExportToMap(o *Object, dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + dst.Set(reflect.MakeMap(typ)) + ctx.putTyped(o, typ, dst.Interface()) + keyTyp := typ.Key() + elemTyp := typ.Elem() + needConvertKeys := !reflectTypeString.AssignableTo(keyTyp) + iter := &enumerableIter{ + o: o, + wrapped: o.self.iterateStringKeys(), + } + r := o.runtime + for item, next := iter.next(); next != nil; item, next = next() { + var kv reflect.Value + var err error + if needConvertKeys { + kv = reflect.New(keyTyp).Elem() + err = r.toReflectValue(item.name, kv, ctx) + if err != nil { + return fmt.Errorf("could not convert map key %s to %v: %w", item.name.String(), typ, err) + } + } else { + kv = reflect.ValueOf(item.name.String()) + } + + ival := o.self.getStr(item.name.string(), nil) + if ival != nil { + vv := reflect.New(elemTyp).Elem() + err = r.toReflectValue(ival, vv, ctx) + if err != nil { + return fmt.Errorf("could not convert map value %v to %v at key %s: %w", ival, typ, item.name.String(), err) + } + dst.SetMapIndex(kv, vv) + } else { + dst.SetMapIndex(kv, reflect.Zero(elemTyp)) + } + } + + return nil +} + +func (o *baseObject) exportToMap(m reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + return genericExportToMap(o.val, m, typ, ctx) +} + +func genericExportToArrayOrSlice(o *Object, dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) (err error) { + r := o.runtime + + if method := toMethod(r.getV(o, SymIterator)); method != nil { + // iterable + + var values []Value + // cannot change (append to) the slice once it's been put into the cache, so we need to know its length beforehand + ex := r.try(func() { + values = r.iterableToList(o, method) + }) + if ex != nil { + return ex + } + if typ.Kind() == reflect.Array { + if dst.Len() != len(values) { + return fmt.Errorf("cannot convert an iterable into an array, lengths mismatch (have %d, need %d)", len(values), dst.Len()) + } + } else { + dst.Set(reflect.MakeSlice(typ, len(values), len(values))) + } + ctx.putTyped(o, typ, dst.Interface()) + for i, val := range values { + err = r.toReflectValue(val, dst.Index(i), ctx) + if err != nil { + return + } + } + } else { + // array-like + var lp Value + if _, ok := o.self.assertCallable(); !ok { + lp = o.self.getStr("length", nil) + } + if lp == nil { + return fmt.Errorf("cannot convert %v to %v: not an array or iterable", o, typ) + } + l := toIntStrict(toLength(lp)) + if dst.Len() != l { + if typ.Kind() == reflect.Array { + return fmt.Errorf("cannot convert an array-like object into an array, lengths mismatch (have %d, need %d)", l, dst.Len()) + } else { + dst.Set(reflect.MakeSlice(typ, l, l)) + } + } + ctx.putTyped(o, typ, dst.Interface()) + for i := 0; i < l; i++ { + val := nilSafe(o.self.getIdx(valueInt(i), nil)) + err = r.toReflectValue(val, dst.Index(i), ctx) + if err != nil { + return + } + } + } + + return +} + +func (o *baseObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + return genericExportToArrayOrSlice(o.val, dst, typ, ctx) +} + +type enumerableFlag int + +const ( + _ENUM_UNKNOWN enumerableFlag = iota + _ENUM_FALSE + _ENUM_TRUE +) + +type propIterItem struct { + name Value + value Value + enumerable enumerableFlag +} + +type objectPropIter struct { + o *baseObject + propNames []unistring.String + idx int +} + +type recursivePropIter struct { + o objectImpl + cur iterNextFunc + seen map[unistring.String]struct{} +} + +type enumerableIter struct { + o *Object + wrapped iterNextFunc +} + +func (i *enumerableIter) next() (propIterItem, iterNextFunc) { + for { + var item propIterItem + item, i.wrapped = i.wrapped() + if i.wrapped == nil { + return item, nil + } + if item.enumerable == _ENUM_FALSE { + continue + } + if item.enumerable == _ENUM_UNKNOWN { + var prop Value + if item.value == nil { + prop = i.o.getOwnProp(item.name) + } else { + prop = item.value + } + if prop == nil { + continue + } + if prop, ok := prop.(*valueProperty); ok { + if !prop.enumerable { + continue + } + } + } + return item, i.next + } +} + +func (i *recursivePropIter) next() (propIterItem, iterNextFunc) { + for { + var item propIterItem + item, i.cur = i.cur() + if i.cur == nil { + if proto := i.o.proto(); proto != nil { + i.cur = proto.self.iterateStringKeys() + i.o = proto.self + continue + } + return propIterItem{}, nil + } + name := item.name.string() + if _, exists := i.seen[name]; !exists { + i.seen[name] = struct{}{} + return item, i.next + } + } +} + +func enumerateRecursive(o *Object) iterNextFunc { + return (&enumerableIter{ + o: o, + wrapped: (&recursivePropIter{ + o: o.self, + cur: o.self.iterateStringKeys(), + seen: make(map[unistring.String]struct{}), + }).next, + }).next +} + +func (i *objectPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.propNames) { + name := i.propNames[i.idx] + i.idx++ + prop := i.o.values[name] + if prop != nil { + return propIterItem{name: stringValueFromRaw(name), value: prop}, i.next + } + } + clearNamesCopyMarker(i.propNames) + return propIterItem{}, nil +} + +var copyMarker = unistring.String(" ") + +// Set a copy-on-write flag so that any subsequent modifications of anything below the current length +// trigger a copy. +// The marker is a special value put at the index position of cap-1. Capacity is set so that the marker is +// beyond the current length (therefore invisible to normal slice operations). +// This function is called before an iteration begins to avoid copying of the names array if +// there are no modifications within the iteration. +// Note that the copying also occurs in two cases: nested iterations (on the same object) and +// iterations after a previously abandoned iteration (because there is currently no mechanism to close an +// iterator). It is still better than copying every time. +func prepareNamesForCopy(names []unistring.String) []unistring.String { + if len(names) == 0 { + return names + } + if namesMarkedForCopy(names) || cap(names) == len(names) { + var newcap int + if cap(names) == len(names) { + newcap = growCap(len(names)+1, len(names), cap(names)) + } else { + newcap = cap(names) + } + newNames := make([]unistring.String, len(names), newcap) + copy(newNames, names) + names = newNames + } + names[cap(names)-1 : cap(names)][0] = copyMarker + return names +} + +func namesMarkedForCopy(names []unistring.String) bool { + return cap(names) > len(names) && names[cap(names)-1 : cap(names)][0] == copyMarker +} + +func clearNamesCopyMarker(names []unistring.String) { + if cap(names) > len(names) { + names[cap(names)-1 : cap(names)][0] = "" + } +} + +func copyNamesIfNeeded(names []unistring.String, extraCap int) []unistring.String { + if namesMarkedForCopy(names) && len(names)+extraCap >= cap(names) { + var newcap int + newsize := len(names) + extraCap + 1 + if newsize > cap(names) { + newcap = growCap(newsize, len(names), cap(names)) + } else { + newcap = cap(names) + } + newNames := make([]unistring.String, len(names), newcap) + copy(newNames, names) + return newNames + } + return names +} + +func (o *baseObject) iterateStringKeys() iterNextFunc { + o.ensurePropOrder() + propNames := prepareNamesForCopy(o.propNames) + o.propNames = propNames + return (&objectPropIter{ + o: o, + propNames: propNames, + }).next +} + +type objectSymbolIter struct { + iter *orderedMapIter +} + +func (i *objectSymbolIter) next() (propIterItem, iterNextFunc) { + entry := i.iter.next() + if entry != nil { + return propIterItem{ + name: entry.key, + value: entry.value, + }, i.next + } + return propIterItem{}, nil +} + +func (o *baseObject) iterateSymbols() iterNextFunc { + if o.symValues != nil { + return (&objectSymbolIter{ + iter: o.symValues.newIter(), + }).next + } + return func() (propIterItem, iterNextFunc) { + return propIterItem{}, nil + } +} + +type objectAllPropIter struct { + o *Object + curStr iterNextFunc +} + +func (i *objectAllPropIter) next() (propIterItem, iterNextFunc) { + item, next := i.curStr() + if next != nil { + i.curStr = next + return item, i.next + } + return i.o.self.iterateSymbols()() +} + +func (o *baseObject) iterateKeys() iterNextFunc { + return (&objectAllPropIter{ + o: o.val, + curStr: o.val.self.iterateStringKeys(), + }).next +} + +func (o *baseObject) equal(objectImpl) bool { + // Rely on parent reference comparison + return false +} + +// hopefully this gets inlined +func (o *baseObject) ensurePropOrder() { + if o.lastSortedPropLen < len(o.propNames) { + o.fixPropOrder() + } +} + +// Reorder property names so that any integer properties are shifted to the beginning of the list +// in ascending order. This is to conform to https://262.ecma-international.org/#sec-ordinaryownpropertykeys. +// Personally I think this requirement is strange. I can sort of understand where they are coming from, +// this way arrays can be specified just as objects with a 'magic' length property. However, I think +// it's safe to assume most devs don't use Objects to store integer properties. Therefore, performing +// property type checks when adding (and potentially looking up) properties would be unreasonable. +// Instead, we keep insertion order and only change it when (if) the properties get enumerated. +func (o *baseObject) fixPropOrder() { + names := o.propNames + for i := o.lastSortedPropLen; i < len(names); i++ { + name := names[i] + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + k := sort.Search(o.idxPropCount, func(j int) bool { + return strToArrayIdx(names[j]) >= idx + }) + if k < i { + if namesMarkedForCopy(names) { + newNames := make([]unistring.String, len(names), cap(names)) + copy(newNames[:k], names) + copy(newNames[k+1:i+1], names[k:i]) + copy(newNames[i+1:], names[i+1:]) + names = newNames + o.propNames = names + } else { + copy(names[k+1:i+1], names[k:i]) + } + names[k] = name + } + o.idxPropCount++ + } + } + o.lastSortedPropLen = len(names) +} + +func (o *baseObject) stringKeys(all bool, keys []Value) []Value { + o.ensurePropOrder() + if all { + for _, k := range o.propNames { + keys = append(keys, stringValueFromRaw(k)) + } + } else { + for _, k := range o.propNames { + prop := o.values[k] + if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { + continue + } + keys = append(keys, stringValueFromRaw(k)) + } + } + return keys +} + +func (o *baseObject) symbols(all bool, accum []Value) []Value { + if o.symValues != nil { + iter := o.symValues.newIter() + if all { + for { + entry := iter.next() + if entry == nil { + break + } + accum = append(accum, entry.key) + } + } else { + for { + entry := iter.next() + if entry == nil { + break + } + if prop, ok := entry.value.(*valueProperty); ok { + if !prop.enumerable { + continue + } + } + accum = append(accum, entry.key) + } + } + } + + return accum +} + +func (o *baseObject) keys(all bool, accum []Value) []Value { + return o.symbols(all, o.val.self.stringKeys(all, accum)) +} + +func (o *baseObject) hasInstance(Value) bool { + panic(o.val.runtime.NewTypeError("Expecting a function in instanceof check, but got %s", o.val.toString())) +} + +func toMethod(v Value) func(FunctionCall) Value { + if v == nil || IsUndefined(v) || IsNull(v) { + return nil + } + if obj, ok := v.(*Object); ok { + if call, ok := obj.self.assertCallable(); ok { + return call + } + } + panic(newTypeError("%s is not a method", v.String())) +} + +func instanceOfOperator(o Value, c *Object) bool { + if instOfHandler := toMethod(c.self.getSym(SymHasInstance, c)); instOfHandler != nil { + return instOfHandler(FunctionCall{ + This: c, + Arguments: []Value{o}, + }).ToBoolean() + } + + return c.self.hasInstance(o) +} + +func (o *Object) defaultValue(hint Value) Value { + if hint == hintString { + if v := o.tryExoticToPrimitive(hint); v != nil { + return v + } + return o.ordinaryToPrimitiveString() + } else if hint == hintNumber { + if v := o.tryExoticToPrimitive(hint); v != nil { + return v + } + return o.ordinaryToPrimitiveNumber() + } else { + if v := o.tryExoticToPrimitive(hint); v != nil { + return v + } + return o.ordinaryToPrimitiveNumber() + } +} + +func (o *Object) get(p Value, receiver Value) Value { + switch p := p.(type) { + case valueInt: + return o.self.getIdx(p, receiver) + case *Symbol: + return o.self.getSym(p, receiver) + default: + return o.self.getStr(p.string(), receiver) + } +} + +func (o *Object) getOwnProp(p Value) Value { + switch p := p.(type) { + case valueInt: + return o.self.getOwnPropIdx(p) + case *Symbol: + return o.self.getOwnPropSym(p) + default: + return o.self.getOwnPropStr(p.string()) + } +} + +func (o *Object) hasOwnProperty(p Value) bool { + switch p := p.(type) { + case valueInt: + return o.self.hasOwnPropertyIdx(p) + case *Symbol: + return o.self.hasOwnPropertySym(p) + default: + return o.self.hasOwnPropertyStr(p.string()) + } +} + +func (o *Object) hasProperty(p Value) bool { + switch p := p.(type) { + case valueInt: + return o.self.hasPropertyIdx(p) + case *Symbol: + return o.self.hasPropertySym(p) + default: + return o.self.hasPropertyStr(p.string()) + } +} + +func (o *Object) setStr(name unistring.String, val, receiver Value, throw bool) bool { + if receiver == o { + return o.self.setOwnStr(name, val, throw) + } else { + if res, ok := o.self.setForeignStr(name, val, receiver, throw); !ok { + if robj, ok := receiver.(*Object); ok { + if prop := robj.self.getOwnPropStr(name); prop != nil { + if desc, ok := prop.(*valueProperty); ok { + if desc.accessor { + o.runtime.typeErrorResult(throw, "Receiver property %s is an accessor", name) + return false + } + if !desc.writable { + o.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } + } + return robj.self.defineOwnPropertyStr(name, PropertyDescriptor{Value: val}, throw) + } else { + return robj.self.defineOwnPropertyStr(name, PropertyDescriptor{ + Value: val, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, throw) + } + } else { + o.runtime.typeErrorResult(throw, "Receiver is not an object: %v", receiver) + return false + } + } else { + return res + } + } +} + +func (o *Object) set(name Value, val, receiver Value, throw bool) bool { + switch name := name.(type) { + case valueInt: + return o.setIdx(name, val, receiver, throw) + case *Symbol: + return o.setSym(name, val, receiver, throw) + default: + return o.setStr(name.string(), val, receiver, throw) + } +} + +func (o *Object) setOwn(name Value, val Value, throw bool) bool { + switch name := name.(type) { + case valueInt: + return o.self.setOwnIdx(name, val, throw) + case *Symbol: + return o.self.setOwnSym(name, val, throw) + default: + return o.self.setOwnStr(name.string(), val, throw) + } +} + +func (o *Object) setIdx(name valueInt, val, receiver Value, throw bool) bool { + if receiver == o { + return o.self.setOwnIdx(name, val, throw) + } else { + if res, ok := o.self.setForeignIdx(name, val, receiver, throw); !ok { + if robj, ok := receiver.(*Object); ok { + if prop := robj.self.getOwnPropIdx(name); prop != nil { + if desc, ok := prop.(*valueProperty); ok { + if desc.accessor { + o.runtime.typeErrorResult(throw, "Receiver property %s is an accessor", name) + return false + } + if !desc.writable { + o.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } + } + robj.self.defineOwnPropertyIdx(name, PropertyDescriptor{Value: val}, throw) + } else { + robj.self.defineOwnPropertyIdx(name, PropertyDescriptor{ + Value: val, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, throw) + } + } else { + o.runtime.typeErrorResult(throw, "Receiver is not an object: %v", receiver) + return false + } + } else { + return res + } + } + return true +} + +func (o *Object) setSym(name *Symbol, val, receiver Value, throw bool) bool { + if receiver == o { + return o.self.setOwnSym(name, val, throw) + } else { + if res, ok := o.self.setForeignSym(name, val, receiver, throw); !ok { + if robj, ok := receiver.(*Object); ok { + if prop := robj.self.getOwnPropSym(name); prop != nil { + if desc, ok := prop.(*valueProperty); ok { + if desc.accessor { + o.runtime.typeErrorResult(throw, "Receiver property %s is an accessor", name) + return false + } + if !desc.writable { + o.runtime.typeErrorResult(throw, "Cannot assign to read only property '%s'", name) + return false + } + } + robj.self.defineOwnPropertySym(name, PropertyDescriptor{Value: val}, throw) + } else { + robj.self.defineOwnPropertySym(name, PropertyDescriptor{ + Value: val, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, throw) + } + } else { + o.runtime.typeErrorResult(throw, "Receiver is not an object: %v", receiver) + return false + } + } else { + return res + } + } + return true +} + +func (o *Object) delete(n Value, throw bool) bool { + switch n := n.(type) { + case valueInt: + return o.self.deleteIdx(n, throw) + case *Symbol: + return o.self.deleteSym(n, throw) + default: + return o.self.deleteStr(n.string(), throw) + } +} + +func (o *Object) defineOwnProperty(n Value, desc PropertyDescriptor, throw bool) bool { + switch n := n.(type) { + case valueInt: + return o.self.defineOwnPropertyIdx(n, desc, throw) + case *Symbol: + return o.self.defineOwnPropertySym(n, desc, throw) + default: + return o.self.defineOwnPropertyStr(n.string(), desc, throw) + } +} + +func (o *Object) getWeakRefs() map[weakMap]Value { + refs := o.weakRefs + if refs == nil { + refs = make(map[weakMap]Value) + o.weakRefs = refs + } + return refs +} + +func (o *Object) getId() uint64 { + id := o.id + if id == 0 { + id = o.runtime.genId() + o.id = id + } + return id +} + +func (o *guardedObject) guard(props ...unistring.String) { + if o.guardedProps == nil { + o.guardedProps = make(map[unistring.String]struct{}) + } + for _, p := range props { + o.guardedProps[p] = struct{}{} + } +} + +func (o *guardedObject) check(p unistring.String) { + if _, exists := o.guardedProps[p]; exists { + o.val.self = &o.baseObject + } +} + +func (o *guardedObject) setOwnStr(p unistring.String, v Value, throw bool) bool { + res := o.baseObject.setOwnStr(p, v, throw) + if res { + o.check(p) + } + return res +} + +func (o *guardedObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + res := o.baseObject.defineOwnPropertyStr(name, desc, throw) + if res { + o.check(name) + } + return res +} + +func (o *guardedObject) deleteStr(name unistring.String, throw bool) bool { + res := o.baseObject.deleteStr(name, throw) + if res { + o.check(name) + } + return res +} + +func (ctx *objectExportCtx) get(key *Object) (any, bool) { + if v, exists := ctx.cache[key]; exists { + if item, ok := v.(objectExportCacheItem); ok { + r, exists := item[key.self.exportType()] + return r, exists + } else { + return v, true + } + } + return nil, false +} + +func (ctx *objectExportCtx) getTyped(key *Object, typ reflect.Type) (any, bool) { + if v, exists := ctx.cache[key]; exists { + if item, ok := v.(objectExportCacheItem); ok { + r, exists := item[typ] + return r, exists + } else { + if reflect.TypeOf(v) == typ { + return v, true + } + } + } + return nil, false +} + +func (ctx *objectExportCtx) put(key *Object, value any) { + if ctx.cache == nil { + ctx.cache = make(map[*Object]any) + } + if item, ok := ctx.cache[key].(objectExportCacheItem); ok { + item[key.self.exportType()] = value + } else { + ctx.cache[key] = value + } +} + +func (ctx *objectExportCtx) putTyped(key *Object, typ reflect.Type, value any) { + if ctx.cache == nil { + ctx.cache = make(map[*Object]any) + } + v, exists := ctx.cache[key] + if exists { + if item, ok := ctx.cache[key].(objectExportCacheItem); ok { + item[typ] = value + } else { + m := make(objectExportCacheItem, 2) + m[key.self.exportType()] = v + m[typ] = value + ctx.cache[key] = m + } + } else { + m := make(objectExportCacheItem) + m[typ] = value + ctx.cache[key] = m + } +} + +type enumPropertiesIter struct { + o *Object + wrapped iterNextFunc +} + +func (i *enumPropertiesIter) next() (propIterItem, iterNextFunc) { + for i.wrapped != nil { + item, next := i.wrapped() + i.wrapped = next + if next == nil { + break + } + if item.value == nil { + item.value = i.o.get(item.name, nil) + if item.value == nil { + continue + } + } else { + if prop, ok := item.value.(*valueProperty); ok { + item.value = prop.get(i.o) + } + } + return item, i.next + } + return propIterItem{}, nil +} + +func iterateEnumerableProperties(o *Object) iterNextFunc { + return (&enumPropertiesIter{ + o: o, + wrapped: (&enumerableIter{ + o: o, + wrapped: o.self.iterateKeys(), + }).next, + }).next +} + +func iterateEnumerableStringProperties(o *Object) iterNextFunc { + return (&enumPropertiesIter{ + o: o, + wrapped: (&enumerableIter{ + o: o, + wrapped: o.self.iterateStringKeys(), + }).next, + }).next +} + +type privateId struct { + typ *privateEnvType + name unistring.String + idx uint32 + isMethod bool +} + +type privateEnvType struct { + numFields, numMethods uint32 +} + +type privateNames map[unistring.String]*privateId + +type privateEnv struct { + instanceType, staticType *privateEnvType + + names privateNames + + outer *privateEnv +} + +type privateElements struct { + methods []Value + fields []Value +} + +func (i *privateId) String() string { + return "#" + i.name.String() +} + +func (i *privateId) string() unistring.String { + return privateIdString(i.name) +} diff --git a/pkg/xscript/engine/object_args.go b/pkg/xscript/engine/object_args.go new file mode 100644 index 0000000..e0db04d --- /dev/null +++ b/pkg/xscript/engine/object_args.go @@ -0,0 +1,139 @@ +package engine + +import "pandax/pkg/xscript/engine/unistring" + +type argumentsObject struct { + baseObject + length int +} + +type mappedProperty struct { + valueProperty + v *Value +} + +func (a *argumentsObject) getStr(name unistring.String, receiver Value) Value { + return a.getStrWithOwnProp(a.getOwnPropStr(name), name, receiver) +} + +func (a *argumentsObject) getOwnPropStr(name unistring.String) Value { + if mapped, ok := a.values[name].(*mappedProperty); ok { + if mapped.writable && mapped.enumerable && mapped.configurable { + return *mapped.v + } + return &valueProperty{ + value: *mapped.v, + writable: mapped.writable, + configurable: mapped.configurable, + enumerable: mapped.enumerable, + } + } + + return a.baseObject.getOwnPropStr(name) +} + +func (a *argumentsObject) init() { + a.baseObject.init() + a._putProp("length", intToValue(int64(a.length)), true, false, true) +} + +func (a *argumentsObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + if prop, ok := a.values[name].(*mappedProperty); ok { + if !prop.writable { + a.val.runtime.typeErrorResult(throw, "Property is not writable: %s", name) + return false + } + *prop.v = val + return true + } + return a.baseObject.setOwnStr(name, val, throw) +} + +func (a *argumentsObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return a._setForeignStr(name, a.getOwnPropStr(name), val, receiver, throw) +} + +func (a *argumentsObject) deleteStr(name unistring.String, throw bool) bool { + if prop, ok := a.values[name].(*mappedProperty); ok { + if !a.checkDeleteProp(name, &prop.valueProperty, throw) { + return false + } + a._delete(name) + return true + } + + return a.baseObject.deleteStr(name, throw) +} + +type argumentsPropIter struct { + wrapped iterNextFunc +} + +func (i *argumentsPropIter) next() (propIterItem, iterNextFunc) { + var item propIterItem + item, i.wrapped = i.wrapped() + if i.wrapped == nil { + return propIterItem{}, nil + } + if prop, ok := item.value.(*mappedProperty); ok { + item.value = *prop.v + } + return item, i.next +} + +func (a *argumentsObject) iterateStringKeys() iterNextFunc { + return (&argumentsPropIter{ + wrapped: a.baseObject.iterateStringKeys(), + }).next +} + +func (a *argumentsObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if mapped, ok := a.values[name].(*mappedProperty); ok { + existing := &valueProperty{ + configurable: mapped.configurable, + writable: true, + enumerable: mapped.enumerable, + value: *mapped.v, + } + + val, ok := a.baseObject._defineOwnProperty(name, existing, descr, throw) + if !ok { + return false + } + + if prop, ok := val.(*valueProperty); ok { + if !prop.accessor { + *mapped.v = prop.value + } + if prop.accessor || !prop.writable { + a._put(name, prop) + return true + } + mapped.configurable = prop.configurable + mapped.enumerable = prop.enumerable + } else { + *mapped.v = val + mapped.configurable = true + mapped.enumerable = true + } + + return true + } + + return a.baseObject.defineOwnPropertyStr(name, descr, throw) +} + +func (a *argumentsObject) export(ctx *objectExportCtx) any { + if v, exists := ctx.get(a.val); exists { + return v + } + arr := make([]any, a.length) + ctx.put(a.val, arr) + for i := range arr { + v := a.getIdx(valueInt(int64(i)), nil) + if v != nil { + arr[i] = exportValue(v, ctx) + } + } + return arr +} diff --git a/pkg/xscript/engine/object_dynamic.go b/pkg/xscript/engine/object_dynamic.go new file mode 100644 index 0000000..0010dce --- /dev/null +++ b/pkg/xscript/engine/object_dynamic.go @@ -0,0 +1,794 @@ +package engine + +import ( + "fmt" + "reflect" + "strconv" + + "pandax/pkg/xscript/engine/unistring" +) + +/* +DynamicObject is an interface representing a handler for a dynamic Object. Such an object can be created +using the Runtime.NewDynamicObject() method. + +Note that Runtime.ToValue() does not have any special treatment for DynamicObject. The only way to create +a dynamic object is by using the Runtime.NewDynamicObject() method. This is done deliberately to avoid +silent code breaks when this interface changes. +*/ +type DynamicObject interface { + // Get a property value for the key. May return nil if the property does not exist. + Get(key string) Value + // Set a property value for the key. Return true if success, false otherwise. + Set(key string, val Value) bool + // Has should return true if and only if the property exists. + Has(key string) bool + // Delete the property for the key. Returns true on success (note, that includes missing property). + Delete(key string) bool + // Keys returns a list of all existing property keys. There are no checks for duplicates or to make sure + // that the order conforms to https://262.ecma-international.org/#sec-ordinaryownpropertykeys + Keys() []string +} + +/* +DynamicArray is an interface representing a handler for a dynamic array Object. Such an object can be created +using the Runtime.NewDynamicArray() method. + +Any integer property key or a string property key that can be parsed into an int value (including negative +ones) is treated as an index and passed to the trap methods of the DynamicArray. Note this is different from +the regular ECMAScript arrays which only support positive indexes up to 2^32-1. + +DynamicArray cannot be sparse, i.e. hasOwnProperty(num) will return true for num >= 0 && num < Len(). Deleting +such a property is equivalent to setting it to undefined. Note that this creates a slight peculiarity because +hasOwnProperty() will still return true, even after deletion. + +Note that Runtime.ToValue() does not have any special treatment for DynamicArray. The only way to create +a dynamic array is by using the Runtime.NewDynamicArray() method. This is done deliberately to avoid +silent code breaks when this interface changes. +*/ +type DynamicArray interface { + // Len returns the current array length. + Len() int + // Get an item at index idx. Note that idx may be any integer, negative or beyond the current length. + Get(idx int) Value + // Set an item at index idx. Note that idx may be any integer, negative or beyond the current length. + // The expected behaviour when it's beyond length is that the array's length is increased to accommodate + // the item. All elements in the 'new' section of the array should be zeroed. + Set(idx int, val Value) bool + // SetLen is called when the array's 'length' property is changed. If the length is increased all elements in the + // 'new' section of the array should be zeroed. + SetLen(int) bool +} + +type baseDynamicObject struct { + val *Object + prototype *Object +} + +type dynamicObject struct { + baseDynamicObject + d DynamicObject +} + +type dynamicArray struct { + baseDynamicObject + a DynamicArray +} + +/* +NewDynamicObject creates an Object backed by the provided DynamicObject handler. + +All properties of this Object are Writable, Enumerable and Configurable data properties. Any attempt to define +a property that does not conform to this will fail. + +The Object is always extensible and cannot be made non-extensible. Object.preventExtensions() will fail. + +The Object's prototype is initially set to Object.prototype, but can be changed using regular mechanisms +(Object.SetPrototype() in Go or Object.setPrototypeOf() in JS). + +The Object cannot have own Symbol properties, however its prototype can. If you need an iterator support for +example, you could create a regular object, set Symbol.iterator on that object and then use it as a +prototype. See TestDynamicObjectCustomProto for more details. + +Export() returns the original DynamicObject. + +This mechanism is similar to ECMAScript Proxy, however because all properties are enumerable and the object +is always extensible there is no need for invariant checks which removes the need to have a target object and +makes it a lot more efficient. +*/ +func (r *Runtime) NewDynamicObject(d DynamicObject) *Object { + v := &Object{runtime: r} + o := &dynamicObject{ + d: d, + baseDynamicObject: baseDynamicObject{ + val: v, + prototype: r.global.ObjectPrototype, + }, + } + v.self = o + return v +} + +/* +NewSharedDynamicObject is similar to Runtime.NewDynamicObject but the resulting Object can be shared across multiple +Runtimes. The Object's prototype will be null. The provided DynamicObject must be goroutine-safe. +*/ +func NewSharedDynamicObject(d DynamicObject) *Object { + v := &Object{} + o := &dynamicObject{ + d: d, + baseDynamicObject: baseDynamicObject{ + val: v, + }, + } + v.self = o + return v +} + +/* +NewDynamicArray creates an array Object backed by the provided DynamicArray handler. +It is similar to NewDynamicObject, the differences are: + +- the Object is an array (i.e. Array.isArray() will return true and it will have the length property). + +- the prototype will be initially set to Array.prototype. + +- the Object cannot have any own string properties except for the 'length'. +*/ +func (r *Runtime) NewDynamicArray(a DynamicArray) *Object { + v := &Object{runtime: r} + o := &dynamicArray{ + a: a, + baseDynamicObject: baseDynamicObject{ + val: v, + prototype: r.getArrayPrototype(), + }, + } + v.self = o + return v +} + +/* +NewSharedDynamicArray is similar to Runtime.NewDynamicArray but the resulting Object can be shared across multiple +Runtimes. The Object's prototype will be null. If you need to run Array's methods on it, use Array.prototype.[...].call(a, ...). +The provided DynamicArray must be goroutine-safe. +*/ +func NewSharedDynamicArray(a DynamicArray) *Object { + v := &Object{} + o := &dynamicArray{ + a: a, + baseDynamicObject: baseDynamicObject{ + val: v, + }, + } + v.self = o + return v +} + +func (*dynamicObject) sortLen() int { + return 0 +} + +func (*dynamicObject) sortGet(i int) Value { + return nil +} + +func (*dynamicObject) swap(i int, i2 int) { +} + +func (*dynamicObject) className() string { + return classObject +} + +func (o *baseDynamicObject) getParentStr(p unistring.String, receiver Value) Value { + if proto := o.prototype; proto != nil { + if receiver == nil { + return proto.self.getStr(p, o.val) + } + return proto.self.getStr(p, receiver) + } + return nil +} + +func (o *dynamicObject) getStr(p unistring.String, receiver Value) Value { + prop := o.d.Get(p.String()) + if prop == nil { + return o.getParentStr(p, receiver) + } + return prop +} + +func (o *baseDynamicObject) getParentIdx(p valueInt, receiver Value) Value { + if proto := o.prototype; proto != nil { + if receiver == nil { + return proto.self.getIdx(p, o.val) + } + return proto.self.getIdx(p, receiver) + } + return nil +} + +func (o *dynamicObject) getIdx(p valueInt, receiver Value) Value { + prop := o.d.Get(p.String()) + if prop == nil { + return o.getParentIdx(p, receiver) + } + return prop +} + +func (o *baseDynamicObject) getSym(p *Symbol, receiver Value) Value { + if proto := o.prototype; proto != nil { + if receiver == nil { + return proto.self.getSym(p, o.val) + } + return proto.self.getSym(p, receiver) + } + return nil +} + +func (o *dynamicObject) getOwnPropStr(u unistring.String) Value { + return o.d.Get(u.String()) +} + +func (o *dynamicObject) getOwnPropIdx(v valueInt) Value { + return o.d.Get(v.String()) +} + +func (*baseDynamicObject) getOwnPropSym(*Symbol) Value { + return nil +} + +func (o *dynamicObject) _set(prop string, v Value, throw bool) bool { + if o.d.Set(prop, v) { + return true + } + typeErrorResult(throw, "'Set' on a dynamic object returned false") + return false +} + +func (o *baseDynamicObject) _setSym(throw bool) { + typeErrorResult(throw, "Dynamic objects do not support Symbol properties") +} + +func (o *dynamicObject) setOwnStr(p unistring.String, v Value, throw bool) bool { + prop := p.String() + if !o.d.Has(prop) { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignStr(p, v, o.val, throw); handled { + return res + } + } + } + return o._set(prop, v, throw) +} + +func (o *dynamicObject) setOwnIdx(p valueInt, v Value, throw bool) bool { + prop := p.String() + if !o.d.Has(prop) { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignIdx(p, v, o.val, throw); handled { + return res + } + } + } + return o._set(prop, v, throw) +} + +func (o *baseDynamicObject) setOwnSym(s *Symbol, v Value, throw bool) bool { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, handled := proto.self.setForeignSym(s, v, o.val, throw); handled { + return res + } + } + o._setSym(throw) + return false +} + +func (o *baseDynamicObject) setParentForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignStr(p, v, receiver, throw) + } + return proto.self.setOwnStr(p, v, throw), true + } + return false, false +} + +func (o *dynamicObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { + prop := p.String() + if !o.d.Has(prop) { + return o.setParentForeignStr(p, v, receiver, throw) + } + return false, false +} + +func (o *baseDynamicObject) setParentForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) { + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignIdx(p, v, receiver, throw) + } + return proto.self.setOwnIdx(p, v, throw), true + } + return false, false +} + +func (o *dynamicObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) { + prop := p.String() + if !o.d.Has(prop) { + return o.setParentForeignIdx(p, v, receiver, throw) + } + return false, false +} + +func (o *baseDynamicObject) setForeignSym(p *Symbol, v, receiver Value, throw bool) (res bool, handled bool) { + if proto := o.prototype; proto != nil { + if receiver != proto { + return proto.self.setForeignSym(p, v, receiver, throw) + } + return proto.self.setOwnSym(p, v, throw), true + } + return false, false +} + +func (o *dynamicObject) hasPropertyStr(u unistring.String) bool { + if o.hasOwnPropertyStr(u) { + return true + } + if proto := o.prototype; proto != nil { + return proto.self.hasPropertyStr(u) + } + return false +} + +func (o *dynamicObject) hasPropertyIdx(idx valueInt) bool { + if o.hasOwnPropertyIdx(idx) { + return true + } + if proto := o.prototype; proto != nil { + return proto.self.hasPropertyIdx(idx) + } + return false +} + +func (o *baseDynamicObject) hasPropertySym(s *Symbol) bool { + if proto := o.prototype; proto != nil { + return proto.self.hasPropertySym(s) + } + return false +} + +func (o *dynamicObject) hasOwnPropertyStr(u unistring.String) bool { + return o.d.Has(u.String()) +} + +func (o *dynamicObject) hasOwnPropertyIdx(v valueInt) bool { + return o.d.Has(v.String()) +} + +func (*baseDynamicObject) hasOwnPropertySym(_ *Symbol) bool { + return false +} + +func (o *baseDynamicObject) checkDynamicObjectPropertyDescr(name fmt.Stringer, descr PropertyDescriptor, throw bool) bool { + if descr.Getter != nil || descr.Setter != nil { + typeErrorResult(throw, "Dynamic objects do not support accessor properties") + return false + } + if descr.Writable == FLAG_FALSE { + typeErrorResult(throw, "Dynamic object field %q cannot be made read-only", name.String()) + return false + } + if descr.Enumerable == FLAG_FALSE { + typeErrorResult(throw, "Dynamic object field %q cannot be made non-enumerable", name.String()) + return false + } + if descr.Configurable == FLAG_FALSE { + typeErrorResult(throw, "Dynamic object field %q cannot be made non-configurable", name.String()) + return false + } + return true +} + +func (o *dynamicObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + if o.checkDynamicObjectPropertyDescr(name, desc, throw) { + return o._set(name.String(), desc.Value, throw) + } + return false +} + +func (o *dynamicObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool { + if o.checkDynamicObjectPropertyDescr(name, desc, throw) { + return o._set(name.String(), desc.Value, throw) + } + return false +} + +func (o *baseDynamicObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool { + o._setSym(throw) + return false +} + +func (o *dynamicObject) _delete(prop string, throw bool) bool { + if o.d.Delete(prop) { + return true + } + typeErrorResult(throw, "Could not delete property %q of a dynamic object", prop) + return false +} + +func (o *dynamicObject) deleteStr(name unistring.String, throw bool) bool { + return o._delete(name.String(), throw) +} + +func (o *dynamicObject) deleteIdx(idx valueInt, throw bool) bool { + return o._delete(idx.String(), throw) +} + +func (*baseDynamicObject) deleteSym(_ *Symbol, _ bool) bool { + return true +} + +func (o *baseDynamicObject) assertCallable() (call func(FunctionCall) Value, ok bool) { + return nil, false +} + +func (o *baseDynamicObject) vmCall(vm *vm, n int) { + panic(vm.r.NewTypeError("Dynamic object is not callable")) +} + +func (*baseDynamicObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return nil +} + +func (o *baseDynamicObject) proto() *Object { + return o.prototype +} + +func (o *baseDynamicObject) setProto(proto *Object, throw bool) bool { + o.prototype = proto + return true +} + +func (o *baseDynamicObject) hasInstance(v Value) bool { + panic(newTypeError("Expecting a function in instanceof check, but got a dynamic object")) +} + +func (*baseDynamicObject) isExtensible() bool { + return true +} + +func (o *baseDynamicObject) preventExtensions(throw bool) bool { + typeErrorResult(throw, "Cannot make a dynamic object non-extensible") + return false +} + +type dynamicObjectPropIter struct { + o *dynamicObject + propNames []string + idx int +} + +func (i *dynamicObjectPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.propNames) { + name := i.propNames[i.idx] + i.idx++ + if i.o.d.Has(name) { + return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next + } + } + return propIterItem{}, nil +} + +func (o *dynamicObject) iterateStringKeys() iterNextFunc { + keys := o.d.Keys() + return (&dynamicObjectPropIter{ + o: o, + propNames: keys, + }).next +} + +func (o *baseDynamicObject) iterateSymbols() iterNextFunc { + return func() (propIterItem, iterNextFunc) { + return propIterItem{}, nil + } +} + +func (o *dynamicObject) iterateKeys() iterNextFunc { + return o.iterateStringKeys() +} + +func (o *dynamicObject) export(ctx *objectExportCtx) any { + return o.d +} + +func (o *dynamicObject) exportType() reflect.Type { + return reflect.TypeOf(o.d) +} + +func (o *baseDynamicObject) exportToMap(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + return genericExportToMap(o.val, dst, typ, ctx) +} + +func (o *baseDynamicObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + return genericExportToArrayOrSlice(o.val, dst, typ, ctx) +} + +func (o *dynamicObject) equal(impl objectImpl) bool { + if other, ok := impl.(*dynamicObject); ok { + return o.d == other.d + } + return false +} + +func (o *dynamicObject) stringKeys(all bool, accum []Value) []Value { + keys := o.d.Keys() + if l := len(accum) + len(keys); l > cap(accum) { + oldAccum := accum + accum = make([]Value, len(accum), l) + copy(accum, oldAccum) + } + for _, key := range keys { + accum = append(accum, newStringValue(key)) + } + return accum +} + +func (*baseDynamicObject) symbols(all bool, accum []Value) []Value { + return accum +} + +func (o *dynamicObject) keys(all bool, accum []Value) []Value { + return o.stringKeys(all, accum) +} + +func (*baseDynamicObject) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { + return nil +} + +func (*baseDynamicObject) _putSym(s *Symbol, prop Value) { +} + +func (o *baseDynamicObject) getPrivateEnv(*privateEnvType, bool) *privateElements { + panic(newTypeError("Dynamic objects cannot have private elements")) +} + +func (o *baseDynamicObject) typeOf() String { + return stringObjectC +} + +func (a *dynamicArray) sortLen() int { + return a.a.Len() +} + +func (a *dynamicArray) sortGet(i int) Value { + return a.a.Get(i) +} + +func (a *dynamicArray) swap(i int, j int) { + x := a.sortGet(i) + y := a.sortGet(j) + a.a.Set(int(i), y) + a.a.Set(int(j), x) +} + +func (a *dynamicArray) className() string { + return classArray +} + +func (a *dynamicArray) getStr(p unistring.String, receiver Value) Value { + if p == "length" { + return intToValue(int64(a.a.Len())) + } + if idx, ok := strToInt(p); ok { + return a.a.Get(idx) + } + return a.getParentStr(p, receiver) +} + +func (a *dynamicArray) getIdx(p valueInt, receiver Value) Value { + if val := a.getOwnPropIdx(p); val != nil { + return val + } + return a.getParentIdx(p, receiver) +} + +func (a *dynamicArray) getOwnPropStr(u unistring.String) Value { + if u == "length" { + return &valueProperty{ + value: intToValue(int64(a.a.Len())), + writable: true, + } + } + if idx, ok := strToInt(u); ok { + return a.a.Get(idx) + } + return nil +} + +func (a *dynamicArray) getOwnPropIdx(v valueInt) Value { + return a.a.Get(toIntStrict(int64(v))) +} + +func (a *dynamicArray) _setLen(v Value, throw bool) bool { + if a.a.SetLen(toIntStrict(v.ToInteger())) { + return true + } + typeErrorResult(throw, "'SetLen' on a dynamic array returned false") + return false +} + +func (a *dynamicArray) setOwnStr(p unistring.String, v Value, throw bool) bool { + if p == "length" { + return a._setLen(v, throw) + } + if idx, ok := strToInt(p); ok { + return a._setIdx(idx, v, throw) + } + typeErrorResult(throw, "Cannot set property %q on a dynamic array", p.String()) + return false +} + +func (a *dynamicArray) _setIdx(idx int, v Value, throw bool) bool { + if a.a.Set(idx, v) { + return true + } + typeErrorResult(throw, "'Set' on a dynamic array returned false") + return false +} + +func (a *dynamicArray) setOwnIdx(p valueInt, v Value, throw bool) bool { + return a._setIdx(toIntStrict(int64(p)), v, throw) +} + +func (a *dynamicArray) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { + return a.setParentForeignStr(p, v, receiver, throw) +} + +func (a *dynamicArray) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) { + return a.setParentForeignIdx(p, v, receiver, throw) +} + +func (a *dynamicArray) hasPropertyStr(u unistring.String) bool { + if a.hasOwnPropertyStr(u) { + return true + } + if proto := a.prototype; proto != nil { + return proto.self.hasPropertyStr(u) + } + return false +} + +func (a *dynamicArray) hasPropertyIdx(idx valueInt) bool { + if a.hasOwnPropertyIdx(idx) { + return true + } + if proto := a.prototype; proto != nil { + return proto.self.hasPropertyIdx(idx) + } + return false +} + +func (a *dynamicArray) _has(idx int) bool { + return idx >= 0 && idx < a.a.Len() +} + +func (a *dynamicArray) hasOwnPropertyStr(u unistring.String) bool { + if u == "length" { + return true + } + if idx, ok := strToInt(u); ok { + return a._has(idx) + } + return false +} + +func (a *dynamicArray) hasOwnPropertyIdx(v valueInt) bool { + return a._has(toIntStrict(int64(v))) +} + +func (a *dynamicArray) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + if a.checkDynamicObjectPropertyDescr(name, desc, throw) { + if idx, ok := strToInt(name); ok { + return a._setIdx(idx, desc.Value, throw) + } + typeErrorResult(throw, "Cannot define property %q on a dynamic array", name.String()) + } + return false +} + +func (a *dynamicArray) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool { + if a.checkDynamicObjectPropertyDescr(name, desc, throw) { + return a._setIdx(toIntStrict(int64(name)), desc.Value, throw) + } + return false +} + +func (a *dynamicArray) _delete(idx int, throw bool) bool { + if a._has(idx) { + a._setIdx(idx, _undefined, throw) + } + return true +} + +func (a *dynamicArray) deleteStr(name unistring.String, throw bool) bool { + if idx, ok := strToInt(name); ok { + return a._delete(idx, throw) + } + if a.hasOwnPropertyStr(name) { + typeErrorResult(throw, "Cannot delete property %q on a dynamic array", name.String()) + return false + } + return true +} + +func (a *dynamicArray) deleteIdx(idx valueInt, throw bool) bool { + return a._delete(toIntStrict(int64(idx)), throw) +} + +type dynArrayPropIter struct { + a DynamicArray + idx, limit int +} + +func (i *dynArrayPropIter) next() (propIterItem, iterNextFunc) { + if i.idx < i.limit && i.idx < i.a.Len() { + name := strconv.Itoa(i.idx) + i.idx++ + return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next + } + + return propIterItem{}, nil +} + +func (a *dynamicArray) iterateStringKeys() iterNextFunc { + return (&dynArrayPropIter{ + a: a.a, + limit: a.a.Len(), + }).next +} + +func (a *dynamicArray) iterateKeys() iterNextFunc { + return a.iterateStringKeys() +} + +func (a *dynamicArray) export(ctx *objectExportCtx) any { + return a.a +} + +func (a *dynamicArray) exportType() reflect.Type { + return reflect.TypeOf(a.a) +} + +func (a *dynamicArray) equal(impl objectImpl) bool { + if other, ok := impl.(*dynamicArray); ok { + return a == other + } + return false +} + +func (a *dynamicArray) stringKeys(all bool, accum []Value) []Value { + al := a.a.Len() + l := len(accum) + al + if all { + l++ + } + if l > cap(accum) { + oldAccum := accum + accum = make([]Value, len(oldAccum), l) + copy(accum, oldAccum) + } + for i := 0; i < al; i++ { + accum = append(accum, asciiString(strconv.Itoa(i))) + } + if all { + accum = append(accum, asciiString("length")) + } + return accum +} + +func (a *dynamicArray) keys(all bool, accum []Value) []Value { + return a.stringKeys(all, accum) +} diff --git a/pkg/xscript/engine/object_dynamic_test.go b/pkg/xscript/engine/object_dynamic_test.go new file mode 100644 index 0000000..736518f --- /dev/null +++ b/pkg/xscript/engine/object_dynamic_test.go @@ -0,0 +1,420 @@ +package engine + +import ( + "sync" + "testing" +) + +type testDynObject struct { + r *Runtime + m map[string]Value +} + +func (t *testDynObject) Get(key string) Value { + return t.m[key] +} + +func (t *testDynObject) Set(key string, val Value) bool { + t.m[key] = val + return true +} + +func (t *testDynObject) Has(key string) bool { + _, exists := t.m[key] + return exists +} + +func (t *testDynObject) Delete(key string) bool { + delete(t.m, key) + return true +} + +func (t *testDynObject) Keys() []string { + keys := make([]string, 0, len(t.m)) + for k := range t.m { + keys = append(keys, k) + } + return keys +} + +type testDynArray struct { + r *Runtime + a []Value +} + +func (t *testDynArray) Len() int { + return len(t.a) +} + +func (t *testDynArray) Get(idx int) Value { + if idx < 0 { + idx += len(t.a) + } + if idx >= 0 && idx < len(t.a) { + return t.a[idx] + } + return nil +} + +func (t *testDynArray) expand(newLen int) { + if newLen > cap(t.a) { + a := make([]Value, newLen) + copy(a, t.a) + t.a = a + } else { + t.a = t.a[:newLen] + } +} + +func (t *testDynArray) Set(idx int, val Value) bool { + if idx < 0 { + idx += len(t.a) + } + if idx < 0 { + return false + } + if idx >= len(t.a) { + t.expand(idx + 1) + } + t.a[idx] = val + return true +} + +func (t *testDynArray) SetLen(i int) bool { + if i > len(t.a) { + t.expand(i) + return true + } + if i < 0 { + return false + } + if i < len(t.a) { + tail := t.a[i:len(t.a)] + for j := range tail { + tail[j] = nil + } + t.a = t.a[:i] + } + return true +} + +func TestDynamicObject(t *testing.T) { + vm := New() + dynObj := &testDynObject{ + r: vm, + m: make(map[string]Value), + } + o := vm.NewDynamicObject(dynObj) + vm.Set("o", o) + vm.testScriptWithTestLibX(` + assert(o instanceof Object, "instanceof Object"); + assert(o === o, "self equality"); + assert(o !== {}, "non-equality"); + + o.test = 42; + assert("test" in o, "'test' in o"); + assert(deepEqual(Object.getOwnPropertyDescriptor(o, "test"), {value: 42, writable: true, enumerable: true, configurable: true}), "prop desc"); + + assert.throws(TypeError, function() { + "use strict"; + Object.defineProperty(o, "test1", {value: 0, writable: false, enumerable: false, configurable: true}); + }, "define prop"); + + var keys = []; + for (var key in o) { + keys.push(key); + } + assert(compareArray(keys, ["test"]), "for-in"); + + assert(delete o.test, "delete"); + assert(!("test" in o), "'test' in o after delete"); + + assert("__proto__" in o, "__proto__ in o"); + assert.sameValue(o.__proto__, Object.prototype, "__proto__"); + o.__proto__ = null; + assert(!("__proto__" in o), "__proto__ in o after setting to null"); + `, _undefined, t) +} + +func TestDynamicObjectCustomProto(t *testing.T) { + vm := New() + m := make(map[string]Value) + dynObj := &testDynObject{ + r: vm, + m: m, + } + o := vm.NewDynamicObject(dynObj) + vm.Set("o", o) + vm.testScriptWithTestLib(` + var proto = { + valueOf: function() { + return this.num; + } + }; + proto[Symbol.toStringTag] = "GoObject"; + Object.setPrototypeOf(o, proto); + o.num = 41; + assert(o instanceof Object, "instanceof"); + assert.sameValue(o+1, 42); + assert.sameValue(o.toString(), "[object GoObject]"); + `, _undefined, t) + + if v := m["num"]; v.Export() != int64(41) { + t.Fatal(v) + } +} + +func TestDynamicArray(t *testing.T) { + vm := New() + dynObj := &testDynArray{ + r: vm, + } + a := vm.NewDynamicArray(dynObj) + vm.Set("a", a) + vm.testScriptWithTestLibX(` + assert(a instanceof Array, "instanceof Array"); + assert(a instanceof Object, "instanceof Object"); + assert(a === a, "self equality"); + assert(a !== [], "non-equality"); + assert(Array.isArray(a), "isArray()"); + assert("length" in a, "length in a"); + assert.sameValue(a.length, 0, "len == 0"); + assert.sameValue(a[0], undefined, "a[0] (1)"); + + a[0] = 0; + assert.sameValue(a[0], 0, "a[0] (2)"); + assert.sameValue(a.length, 1, "length"); + assert(deepEqual(Object.getOwnPropertyDescriptor(a, 0), {value: 0, writable: true, enumerable: true, configurable: true}), "prop desc"); + assert(deepEqual(Object.getOwnPropertyDescriptor(a, "length"), {value: 1, writable: true, enumerable: false, configurable: false}), "length prop desc"); + + assert("__proto__" in a, "__proto__ in a"); + assert.sameValue(a.__proto__, Array.prototype, "__proto__"); + + assert(compareArray(Object.keys(a), ["0"]), "Object.keys()"); + assert(compareArray(Reflect.ownKeys(a), ["0", "length"]), "Reflect.ownKeys()"); + + a.length = 2; + assert.sameValue(a.length, 2, "length after grow"); + assert.sameValue(a[1], undefined, "a[1]"); + + a[1] = 1; + assert.sameValue(a[1], 1, "a[1] after set"); + a.length = 1; + assert.sameValue(a.length, 1, "length after shrink"); + assert.sameValue(a[1], undefined, "a[1] after shrink"); + a.length = 2; + assert.sameValue(a.length, 2, "length after shrink and grow"); + assert.sameValue(a[1], undefined, "a[1] after grow"); + + a[0] = 3; a[1] = 1; a[2] = 2; + assert.sameValue(a.length, 3); + var keys = []; + for (var key in a) { + keys.push(key); + } + assert(compareArray(keys, ["0","1","2"]), "for-in"); + + var vals = []; + for (var val of a) { + vals.push(val); + } + assert(compareArray(vals, [3,1,2]), "for-of"); + + a.sort(); + assert(compareArray(a, [1,2,3]), "sort: "+a); + + assert.sameValue(a[-1], 3); + assert.sameValue(a[-4], undefined); + + assert.throws(TypeError, function() { + "use strict"; + delete a.length; + }, "delete length"); + + assert.throws(TypeError, function() { + "use strict"; + a.test = true; + }, "set string prop"); + + assert.throws(TypeError, function() { + "use strict"; + Object.defineProperty(a, 0, {value: 0, writable: false, enumerable: false, configurable: true}); + }, "define prop"); + + `, _undefined, t) +} + +type testSharedDynObject struct { + sync.RWMutex + m map[string]Value +} + +func (t *testSharedDynObject) Get(key string) Value { + t.RLock() + val := t.m[key] + t.RUnlock() + return val +} + +func (t *testSharedDynObject) Set(key string, val Value) bool { + t.Lock() + t.m[key] = val + t.Unlock() + return true +} + +func (t *testSharedDynObject) Has(key string) bool { + t.RLock() + _, exists := t.m[key] + t.RUnlock() + return exists +} + +func (t *testSharedDynObject) Delete(key string) bool { + t.Lock() + delete(t.m, key) + t.Unlock() + return true +} + +func (t *testSharedDynObject) Keys() []string { + t.RLock() + keys := make([]string, 0, len(t.m)) + for k := range t.m { + keys = append(keys, k) + } + t.RUnlock() + return keys +} + +func TestSharedDynamicObject(t *testing.T) { + dynObj := &testSharedDynObject{m: make(map[string]Value, 10000)} + o := NewSharedDynamicObject(dynObj) + ch := make(chan error, 1) + go func() { + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i; + } + `) + ch <- err + }() + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i+1; + } + `) + if err != nil { + t.Fatal(err) + } + + err = <-ch + if err != nil { + t.Fatal(err) + } +} + +type testSharedDynArray struct { + sync.RWMutex + a []Value +} + +func (t *testSharedDynArray) Len() int { + t.RLock() + l := len(t.a) + t.RUnlock() + return l +} + +func (t *testSharedDynArray) Get(idx int) Value { + t.RLock() + defer t.RUnlock() + if idx < 0 { + idx += len(t.a) + } + if idx >= 0 && idx < len(t.a) { + return t.a[idx] + } + return nil +} + +func (t *testSharedDynArray) expand(newLen int) { + if newLen > cap(t.a) { + a := make([]Value, newLen) + copy(a, t.a) + t.a = a + } else { + t.a = t.a[:newLen] + } +} + +func (t *testSharedDynArray) Set(idx int, val Value) bool { + t.Lock() + defer t.Unlock() + if idx < 0 { + idx += len(t.a) + } + if idx < 0 { + return false + } + if idx >= len(t.a) { + t.expand(idx + 1) + } + t.a[idx] = val + return true +} + +func (t *testSharedDynArray) SetLen(i int) bool { + t.Lock() + defer t.Unlock() + if i > len(t.a) { + t.expand(i) + return true + } + if i < 0 { + return false + } + if i < len(t.a) { + tail := t.a[i:len(t.a)] + for j := range tail { + tail[j] = nil + } + t.a = t.a[:i] + } + return true +} + +func TestSharedDynamicArray(t *testing.T) { + dynObj := &testSharedDynArray{a: make([]Value, 10000)} + o := NewSharedDynamicArray(dynObj) + ch := make(chan error, 1) + go func() { + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i; + } + `) + ch <- err + }() + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + for (let i = 0; i < 10000; i++) { + o[i] = i+1; + } + `) + if err != nil { + t.Fatal(err) + } + + err = <-ch + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/xscript/engine/object_goarray_reflect.go b/pkg/xscript/engine/object_goarray_reflect.go new file mode 100644 index 0000000..e0b4eda --- /dev/null +++ b/pkg/xscript/engine/object_goarray_reflect.go @@ -0,0 +1,358 @@ +package engine + +import ( + "reflect" + "strconv" + + "pandax/pkg/xscript/engine/unistring" +) + +type objectGoArrayReflect struct { + objectGoReflect + lengthProp valueProperty + + valueCache valueArrayCache + + putIdx func(idx int, v Value, throw bool) bool +} + +type valueArrayCache []reflectValueWrapper + +func (c *valueArrayCache) get(idx int) reflectValueWrapper { + if idx < len(*c) { + return (*c)[idx] + } + return nil +} + +func (c *valueArrayCache) grow(newlen int) { + oldcap := cap(*c) + if oldcap < newlen { + a := make([]reflectValueWrapper, newlen, growCap(newlen, len(*c), oldcap)) + copy(a, *c) + *c = a + } else { + *c = (*c)[:newlen] + } +} + +func (c *valueArrayCache) put(idx int, w reflectValueWrapper) { + if len(*c) <= idx { + c.grow(idx + 1) + } + (*c)[idx] = w +} + +func (c *valueArrayCache) shrink(newlen int) { + if len(*c) > newlen { + tail := (*c)[newlen:] + for i, item := range tail { + if item != nil { + copyReflectValueWrapper(item) + tail[i] = nil + } + } + *c = (*c)[:newlen] + } +} + +func (o *objectGoArrayReflect) _init() { + o.objectGoReflect.init() + o.class = classArray + o.prototype = o.val.runtime.getArrayPrototype() + o.baseObject._put("length", &o.lengthProp) +} + +func (o *objectGoArrayReflect) init() { + o._init() + o.updateLen() + o.putIdx = o._putIdx +} + +func (o *objectGoArrayReflect) updateLen() { + o.lengthProp.value = intToValue(int64(o.fieldsValue.Len())) +} + +func (o *objectGoArrayReflect) _hasIdx(idx valueInt) bool { + if idx := int64(idx); idx >= 0 && idx < int64(o.fieldsValue.Len()) { + return true + } + return false +} + +func (o *objectGoArrayReflect) _hasStr(name unistring.String) bool { + if idx := strToIdx64(name); idx >= 0 && idx < int64(o.fieldsValue.Len()) { + return true + } + return false +} + +func (o *objectGoArrayReflect) _getIdx(idx int) Value { + if v := o.valueCache.get(idx); v != nil { + return v.esValue() + } + + v := o.fieldsValue.Index(idx) + + res, w := o.elemToValue(v) + if w != nil { + o.valueCache.put(idx, w) + } + + return res +} + +func (o *objectGoArrayReflect) getIdx(idx valueInt, receiver Value) Value { + if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.fieldsValue.Len() { + return o._getIdx(idx) + } + return o.objectGoReflect.getStr(idx.string(), receiver) +} + +func (o *objectGoArrayReflect) getStr(name unistring.String, receiver Value) Value { + var ownProp Value + if idx := strToGoIdx(name); idx >= 0 && idx < o.fieldsValue.Len() { + ownProp = o._getIdx(idx) + } else if name == "length" { + if o.fieldsValue.Kind() == reflect.Slice { + o.updateLen() + } + ownProp = &o.lengthProp + } else { + ownProp = o.objectGoReflect.getOwnPropStr(name) + } + return o.getStrWithOwnProp(ownProp, name, receiver) +} + +func (o *objectGoArrayReflect) getOwnPropStr(name unistring.String) Value { + if idx := strToGoIdx(name); idx >= 0 { + if idx < o.fieldsValue.Len() { + return &valueProperty{ + value: o._getIdx(idx), + writable: true, + enumerable: true, + } + } + return nil + } + if name == "length" { + if o.fieldsValue.Kind() == reflect.Slice { + o.updateLen() + } + return &o.lengthProp + } + return o.objectGoReflect.getOwnPropStr(name) +} + +func (o *objectGoArrayReflect) getOwnPropIdx(idx valueInt) Value { + if idx := toIntStrict(int64(idx)); idx >= 0 && idx < o.fieldsValue.Len() { + return &valueProperty{ + value: o._getIdx(idx), + writable: true, + enumerable: true, + } + } + return nil +} + +func (o *objectGoArrayReflect) _putIdx(idx int, v Value, throw bool) bool { + cached := o.valueCache.get(idx) + if cached != nil { + copyReflectValueWrapper(cached) + } + + rv := o.fieldsValue.Index(idx) + err := o.val.runtime.toReflectValue(v, rv, &objectExportCtx{}) + if err != nil { + if cached != nil { + cached.setReflectValue(rv) + } + o.val.runtime.typeErrorResult(throw, "Go type conversion error: %v", err) + return false + } + if cached != nil { + o.valueCache[idx] = nil + } + return true +} + +func (o *objectGoArrayReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool { + if i := toIntStrict(int64(idx)); i >= 0 { + if i >= o.fieldsValue.Len() { + if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok { + return res + } + } + return o.putIdx(i, val, throw) + } else { + name := idx.string() + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { + o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name) + return false + } else { + return res + } + } +} + +func (o *objectGoArrayReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { + if idx := strToGoIdx(name); idx >= 0 { + if idx >= o.fieldsValue.Len() { + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok { + return res + } + } + return o.putIdx(idx, val, throw) + } else { + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { + o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name) + return false + } else { + return res + } + } +} + +func (o *objectGoArrayReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignIdx(idx, trueValIfPresent(o._hasIdx(idx)), val, receiver, throw) +} + +func (o *objectGoArrayReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw) +} + +func (o *objectGoArrayReflect) hasOwnPropertyIdx(idx valueInt) bool { + return o._hasIdx(idx) +} + +func (o *objectGoArrayReflect) hasOwnPropertyStr(name unistring.String) bool { + if o._hasStr(name) || name == "length" { + return true + } + return o.objectGoReflect.hasOwnPropertyStr(name) +} + +func (o *objectGoArrayReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + if i := toIntStrict(int64(idx)); i >= 0 { + if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) { + return false + } + val := descr.Value + if val == nil { + val = _undefined + } + return o.putIdx(i, val, throw) + } + o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx) + return false +} + +func (o *objectGoArrayReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if idx := strToGoIdx(name); idx >= 0 { + if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { + return false + } + val := descr.Value + if val == nil { + val = _undefined + } + return o.putIdx(idx, val, throw) + } + o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name) + return false +} + +func (o *objectGoArrayReflect) _deleteIdx(idx int) { + if idx < o.fieldsValue.Len() { + if cv := o.valueCache.get(idx); cv != nil { + copyReflectValueWrapper(cv) + o.valueCache[idx] = nil + } + + o.fieldsValue.Index(idx).Set(reflect.Zero(o.fieldsValue.Type().Elem())) + } +} + +func (o *objectGoArrayReflect) deleteStr(name unistring.String, throw bool) bool { + if idx := strToGoIdx(name); idx >= 0 { + o._deleteIdx(idx) + return true + } + + return o.objectGoReflect.deleteStr(name, throw) +} + +func (o *objectGoArrayReflect) deleteIdx(i valueInt, throw bool) bool { + idx := toIntStrict(int64(i)) + if idx >= 0 { + o._deleteIdx(idx) + } + return true +} + +type goArrayReflectPropIter struct { + o *objectGoArrayReflect + idx, limit int +} + +func (i *goArrayReflectPropIter) next() (propIterItem, iterNextFunc) { + if i.idx < i.limit && i.idx < i.o.fieldsValue.Len() { + name := strconv.Itoa(i.idx) + i.idx++ + return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next + } + + return i.o.objectGoReflect.iterateStringKeys()() +} + +func (o *objectGoArrayReflect) stringKeys(all bool, accum []Value) []Value { + for i := 0; i < o.fieldsValue.Len(); i++ { + accum = append(accum, asciiString(strconv.Itoa(i))) + } + + return o.objectGoReflect.stringKeys(all, accum) +} + +func (o *objectGoArrayReflect) iterateStringKeys() iterNextFunc { + return (&goArrayReflectPropIter{ + o: o, + limit: o.fieldsValue.Len(), + }).next +} + +func (o *objectGoArrayReflect) sortLen() int { + return o.fieldsValue.Len() +} + +func (o *objectGoArrayReflect) sortGet(i int) Value { + return o.getIdx(valueInt(i), nil) +} + +func (o *objectGoArrayReflect) swap(i int, j int) { + vi := o.fieldsValue.Index(i) + vj := o.fieldsValue.Index(j) + tmp := reflect.New(o.fieldsValue.Type().Elem()).Elem() + tmp.Set(vi) + vi.Set(vj) + vj.Set(tmp) + + cachedI := o.valueCache.get(i) + cachedJ := o.valueCache.get(j) + if cachedI != nil { + cachedI.setReflectValue(vj) + o.valueCache.put(j, cachedI) + } else { + if j < len(o.valueCache) { + o.valueCache[j] = nil + } + } + + if cachedJ != nil { + cachedJ.setReflectValue(vi) + o.valueCache.put(i, cachedJ) + } else { + if i < len(o.valueCache) { + o.valueCache[i] = nil + } + } +} diff --git a/pkg/xscript/engine/object_goarray_reflect_test.go b/pkg/xscript/engine/object_goarray_reflect_test.go new file mode 100644 index 0000000..889b5fe --- /dev/null +++ b/pkg/xscript/engine/object_goarray_reflect_test.go @@ -0,0 +1,304 @@ +package engine + +import ( + "testing" +) + +func TestGoReflectArray(t *testing.T) { + vm := New() + vm.Set("a", [...]int{1, 2, 3}) + _, err := vm.RunString(` + if (!Array.isArray(a)) { + throw new Error("isArray() returned false"); + } + if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) { + throw new Error("Array contents is incorrect"); + } + if (!a.hasOwnProperty("length")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(a, "length"); + if (desc.value !== 3 || desc.writable || desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoReflectArraySort(t *testing.T) { + vm := New() + vm.Set("a", [...]int{3, 1, 2}) + v, err := vm.RunString(` + a.sort(); + if (a[0] !== 1 || a[1] !== 2 || a[2] !== 3) { + throw new Error(a.toString()); + } + a; + `) + if err != nil { + t.Fatal(err) + } + res := v.Export() + if a, ok := res.([3]int); ok { + if a[0] != 1 || a[1] != 2 || a[2] != 3 { + t.Fatal(a) + } + } else { + t.Fatalf("Wrong type: %T", res) + } +} + +func TestGoReflectArrayCopyOnChange(t *testing.T) { + vm := New() + + v, err := vm.RunString(` + a => { + let tmp = a[0]; + if (tmp !== a[0]) { + throw new Error("tmp !== a[0]"); + } + + a[0] = a[1]; + if (tmp === a[0]) { + throw new Error("tmp === a[0]"); + } + if (tmp.Test !== "1") { + throw new Error("tmp.Test: " + tmp.Test + " (" + typeof tmp.Test + ")"); + } + if (a[0].Test !== "2") { + throw new Error("a[0].Test: " + a[0].Test); + } + + a[0].Test = "3"; + if (a[0].Test !== "3") { + throw new Error("a[0].Test (1): " + a[0].Test); + } + + tmp = a[0]; + tmp.Test = "4"; + if (a[0].Test !== "4") { + throw new Error("a[0].Test (2): " + a[0].Test); + } + + delete a[0]; + if (a[0] && a[0].Test !== "") { + throw new Error("a[0].Test (3): " + a[0].Test); + } + if (tmp.Test !== "4") { + throw new Error("tmp.Test (1): " + tmp.Test); + } + + a[1] = tmp; + if (a[1].Test !== "4") { + throw new Error("a[1].Test: " + a[1].Test); + } + + // grow + tmp = a[1]; + a.push(null); + if (a.length !== 3) { + throw new Error("a.length after push: " + a.length); + } + + tmp.Test = "5"; + if (a[1].Test !== "5") { + throw new Error("a[1].Test (1): " + a[1].Test); + } + + // shrink + a.length = 1; + if (a.length !== 1) { + throw new Error("a.length after shrink: " + a.length); + } + + if (tmp.Test !== "5") { + throw new Error("tmp.Test (shrink): " + tmp.Test); + } + } + `) + if err != nil { + t.Fatal(err) + } + + fn, ok := AssertFunction(v) + if !ok { + t.Fatal("Not a function") + } + + t.Run("[]struct", func(t *testing.T) { + a := []struct { + Test string + }{{"1"}, {"2"}} + _, err := fn(nil, vm.ToValue(a)) + if err != nil { + t.Fatal(err) + } + if a[0].Test != "" { + t.Fatalf("a[0]: %#v", a[0]) + } + + if a[1].Test != "4" { + t.Fatalf("a0[1]: %#v", a[1]) + } + }) + + // The copy-on-change mechanism doesn't apply to the types below because the contained values are references. + // These tests are here for completeness and to prove that the behaviour is consistent. + + t.Run("[]I", func(t *testing.T) { + type I interface { + Get() string + } + + a := []I{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}} + + _, err = fn(nil, vm.ToValue(a)) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("[]any", func(t *testing.T) { + a := []any{&testGoReflectMethod_O{Test: "1"}, &testGoReflectMethod_O{Test: "2"}} + + _, err = fn(nil, vm.ToValue(a)) + if err != nil { + t.Fatal(err) + } + }) +} + +func TestCopyOnChangeReflectSlice(t *testing.T) { + vm := New() + v, err := vm.RunString(` + s => { + s.A.push(1); + if (s.A.length !== 1) { + throw new Error("s.A.length: " + s.A.length); + } + if (s.A[0] !== 1) { + throw new Error("s.A[0]: " + s.A[0]); + } + let tmp = s.A; + if (tmp !== s.A) { + throw new Error("tmp !== s.A"); + } + s.A = [2]; + if (tmp === s.A) { + throw new Error("tmp === s.A"); + } + if (tmp[0] !== 1) { + throw new Error("tmp[0]: " + tmp[0]); + } + if (s.A[0] !== 2) { + throw new Error("s.A[0] (1): " + s.A[0]); + } + } + `) + if err != nil { + t.Fatal(err) + } + fn, ok := AssertFunction(v) + if !ok { + t.Fatal("Not a function") + } + + t.Run("[]int", func(t *testing.T) { + type S struct { + A []int + } + var s S + _, err := fn(nil, vm.ToValue(&s)) + if err != nil { + t.Fatal(err) + } + if len(s.A) != 1 { + t.Fatal(s) + } + if s.A[0] != 2 { + t.Fatal(s.A) + } + }) + + t.Run("[]any", func(t *testing.T) { + type S struct { + A []any + } + var s S + _, err := fn(nil, vm.ToValue(&s)) + if err != nil { + t.Fatal(err) + } + if len(s.A) != 1 { + t.Fatal(s) + } + if s.A[0] != int64(2) { + t.Fatal(s.A) + } + }) +} + +func TestCopyOnChangeSort(t *testing.T) { + a := []struct { + Test string + }{{"2"}, {"1"}} + + vm := New() + vm.Set("a", &a) + + _, err := vm.RunString(` + let a0 = a[0]; + let a1 = a[1]; + a.sort((a, b) => a.Test.localeCompare(b.Test)); + if (a[0].Test !== "1") { + throw new Error("a[0]: " + a[0]); + } + if (a[1].Test !== "2") { + throw new Error("a[1]: " + a[1]); + } + if (a0 !== a[1]) { + throw new Error("a0 !== a[1]"); + } + if (a1 !== a[0]) { + throw new Error("a1 !== a[0]"); + } + `) + if err != nil { + t.Fatal(err) + } + + if a[0].Test != "1" || a[1].Test != "2" { + t.Fatal(a) + } +} + +type testStringerArray [8]byte + +func (a testStringerArray) String() string { + return "X" +} + +func TestReflectArrayToString(t *testing.T) { + vm := New() + var a testStringerArray + vm.Set("a", &a) + res, err := vm.RunString("`${a}`") + if err != nil { + t.Fatal(err) + } + if exp := res.Export(); exp != "X" { + t.Fatal(exp) + } + + var a1 [2]byte + vm.Set("a", &a1) + res, err = vm.RunString("`${a}`") + if err != nil { + t.Fatal(err) + } + if exp := res.Export(); exp != "0,0" { + t.Fatal(exp) + } +} diff --git a/pkg/xscript/engine/object_gomap.go b/pkg/xscript/engine/object_gomap.go new file mode 100644 index 0000000..d58de5f --- /dev/null +++ b/pkg/xscript/engine/object_gomap.go @@ -0,0 +1,158 @@ +package engine + +import ( + "reflect" + + "pandax/pkg/xscript/engine/unistring" +) + +type objectGoMapSimple struct { + baseObject + data map[string]any +} + +func (o *objectGoMapSimple) init() { + o.baseObject.init() + o.prototype = o.val.runtime.global.ObjectPrototype + o.class = classObject + o.extensible = true +} + +func (o *objectGoMapSimple) _getStr(name string) Value { + v, exists := o.data[name] + if !exists { + return nil + } + return o.val.runtime.ToValue(v) +} + +func (o *objectGoMapSimple) getStr(name unistring.String, receiver Value) Value { + if v := o._getStr(name.String()); v != nil { + return v + } + return o.baseObject.getStr(name, receiver) +} + +func (o *objectGoMapSimple) getOwnPropStr(name unistring.String) Value { + if v := o._getStr(name.String()); v != nil { + return v + } + return nil +} + +func (o *objectGoMapSimple) setOwnStr(name unistring.String, val Value, throw bool) bool { + n := name.String() + if _, exists := o.data[n]; exists { + o.data[n] = val.Export() + return true + } + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok { + return res + } + } + // new property + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", name) + return false + } else { + o.data[n] = val.Export() + } + return true +} + +func trueValIfPresent(present bool) Value { + if present { + return valueTrue + } + return nil +} + +func (o *objectGoMapSimple) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o._hasStr(name.String())), val, receiver, throw) +} + +func (o *objectGoMapSimple) _hasStr(name string) bool { + _, exists := o.data[name] + return exists +} + +func (o *objectGoMapSimple) hasOwnPropertyStr(name unistring.String) bool { + return o._hasStr(name.String()) +} + +func (o *objectGoMapSimple) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { + return false + } + + n := name.String() + if o.extensible || o._hasStr(n) { + o.data[n] = descr.Value.Export() + return true + } + + o.val.runtime.typeErrorResult(throw, "Cannot define property %s, object is not extensible", n) + return false +} + +func (o *objectGoMapSimple) deleteStr(name unistring.String, _ bool) bool { + delete(o.data, name.String()) + return true +} + +type gomapPropIter struct { + o *objectGoMapSimple + propNames []string + idx int +} + +func (i *gomapPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.propNames) { + name := i.propNames[i.idx] + i.idx++ + if _, exists := i.o.data[name]; exists { + return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next + } + } + + return propIterItem{}, nil +} + +func (o *objectGoMapSimple) iterateStringKeys() iterNextFunc { + propNames := make([]string, len(o.data)) + i := 0 + for key := range o.data { + propNames[i] = key + i++ + } + + return (&gomapPropIter{ + o: o, + propNames: propNames, + }).next +} + +func (o *objectGoMapSimple) stringKeys(_ bool, accum []Value) []Value { + // all own keys are enumerable + for key := range o.data { + accum = append(accum, newStringValue(key)) + } + return accum +} + +func (o *objectGoMapSimple) export(*objectExportCtx) any { + return o.data +} + +func (o *objectGoMapSimple) exportType() reflect.Type { + return reflectTypeMap +} + +func (o *objectGoMapSimple) equal(other objectImpl) bool { + if other, ok := other.(*objectGoMapSimple); ok { + return o == other + } + return false +} diff --git a/pkg/xscript/engine/object_gomap_reflect.go b/pkg/xscript/engine/object_gomap_reflect.go new file mode 100644 index 0000000..a1cf5ce --- /dev/null +++ b/pkg/xscript/engine/object_gomap_reflect.go @@ -0,0 +1,272 @@ +package engine + +import ( + "reflect" + "sync" + + "pandax/pkg/xscript/engine/unistring" +) + +type objectGoMapReflect struct { + objectGoReflect + + keyType, valueType reflect.Type + + mu sync.RWMutex +} + +func (o *objectGoMapReflect) init() { + o.objectGoReflect.init() + o.keyType = o.fieldsValue.Type().Key() + o.valueType = o.fieldsValue.Type().Elem() +} + +func (o *objectGoMapReflect) toKey(n Value, throw bool) reflect.Value { + key := reflect.New(o.keyType).Elem() + err := o.val.runtime.toReflectValue(n, key, &objectExportCtx{}) + if err != nil { + o.val.runtime.typeErrorResult(throw, "map key conversion error: %v", err) + return reflect.Value{} + } + return key +} + +func (o *objectGoMapReflect) strToKey(name string, throw bool) reflect.Value { + if o.keyType.Kind() == reflect.String { + return reflect.ValueOf(name).Convert(o.keyType) + } + return o.toKey(newStringValue(name), throw) +} + +func (o *objectGoMapReflect) _getKey(key reflect.Value) Value { + o.mu.RLock() + defer o.mu.RUnlock() + if !key.IsValid() { + return nil + } + if v := o.fieldsValue.MapIndex(key); v.IsValid() { + rv := v + if rv.Kind() == reflect.Interface { + rv = rv.Elem() + } + return o.val.runtime.toValue(v.Interface(), rv) + } + + return nil +} + +func (o *objectGoMapReflect) _get(n Value) Value { + return o._getKey(o.toKey(n, false)) +} + +func (o *objectGoMapReflect) _getStr(name string) Value { + return o._getKey(o.strToKey(name, false)) +} + +func (o *objectGoMapReflect) getStr(name unistring.String, receiver Value) Value { + if v := o._getStr(name.String()); v != nil { + return v + } + return o.objectGoReflect.getStr(name, receiver) +} + +func (o *objectGoMapReflect) getIdx(idx valueInt, receiver Value) Value { + if v := o._get(idx); v != nil { + return v + } + return o.objectGoReflect.getIdx(idx, receiver) +} + +func (o *objectGoMapReflect) getOwnPropStr(name unistring.String) Value { + if v := o._getStr(name.String()); v != nil { + return &valueProperty{ + value: v, + writable: true, + enumerable: true, + } + } + return o.objectGoReflect.getOwnPropStr(name) +} + +func (o *objectGoMapReflect) getOwnPropIdx(idx valueInt) Value { + if v := o._get(idx); v != nil { + return &valueProperty{ + value: v, + writable: true, + enumerable: true, + } + } + return o.objectGoReflect.getOwnPropStr(idx.string()) +} + +func (o *objectGoMapReflect) toValue(val Value, throw bool) (reflect.Value, bool) { + v := reflect.New(o.valueType).Elem() + err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{}) + if err != nil { + o.val.runtime.typeErrorResult(throw, "map value conversion error: %v", err) + return reflect.Value{}, false + } + + return v, true +} + +func (o *objectGoMapReflect) _put(key reflect.Value, val Value, throw bool) bool { + o.mu.Lock() + defer o.mu.Unlock() + if key.IsValid() { + if o.extensible || o.fieldsValue.MapIndex(key).IsValid() { + v, ok := o.toValue(val, throw) + if !ok { + return false + } + o.fieldsValue.SetMapIndex(key, v) + } else { + o.val.runtime.typeErrorResult(throw, "Cannot set property %s, object is not extensible", key.String()) + return false + } + return true + } + return false +} + +func (o *objectGoMapReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { + n := name.String() + key := o.strToKey(n, false) + if !key.IsValid() || !o.fieldsValue.MapIndex(key).IsValid() { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, ok := proto.self.setForeignStr(name, val, o.val, throw); ok { + return res + } + } + // new property + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot add property %s, object is not extensible", n) + return false + } else { + if throw && !key.IsValid() { + o.strToKey(n, true) + return false + } + } + } + o._put(key, val, throw) + return true +} + +func (o *objectGoMapReflect) setOwnIdx(idx valueInt, val Value, throw bool) bool { + key := o.toKey(idx, false) + if !key.IsValid() || !o.fieldsValue.MapIndex(key).IsValid() { + if proto := o.prototype; proto != nil { + // we know it's foreign because prototype loops are not allowed + if res, ok := proto.self.setForeignIdx(idx, val, o.val, throw); ok { + return res + } + } + // new property + if !o.extensible { + o.val.runtime.typeErrorResult(throw, "Cannot add property %d, object is not extensible", idx) + return false + } else { + if throw && !key.IsValid() { + o.toKey(idx, true) + return false + } + } + } + o._put(key, val, throw) + return true +} + +func (o *objectGoMapReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw) +} + +func (o *objectGoMapReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw) +} + +func (o *objectGoMapReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { + return false + } + + return o._put(o.strToKey(name.String(), throw), descr.Value, throw) +} + +func (o *objectGoMapReflect) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) { + return false + } + + return o._put(o.toKey(idx, throw), descr.Value, throw) +} + +func (o *objectGoMapReflect) hasOwnPropertyStr(name unistring.String) bool { + key := o.strToKey(name.String(), false) + if key.IsValid() && o.fieldsValue.MapIndex(key).IsValid() { + return true + } + return false +} + +func (o *objectGoMapReflect) hasOwnPropertyIdx(idx valueInt) bool { + key := o.toKey(idx, false) + if key.IsValid() && o.fieldsValue.MapIndex(key).IsValid() { + return true + } + return false +} + +func (o *objectGoMapReflect) deleteStr(name unistring.String, throw bool) bool { + key := o.strToKey(name.String(), throw) + if !key.IsValid() { + return false + } + o.fieldsValue.SetMapIndex(key, reflect.Value{}) + return true +} + +func (o *objectGoMapReflect) deleteIdx(idx valueInt, throw bool) bool { + key := o.toKey(idx, throw) + if !key.IsValid() { + return false + } + o.fieldsValue.SetMapIndex(key, reflect.Value{}) + return true +} + +type gomapReflectPropIter struct { + o *objectGoMapReflect + keys []reflect.Value + idx int +} + +func (i *gomapReflectPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.keys) { + key := i.keys[i.idx] + v := i.o.fieldsValue.MapIndex(key) + i.idx++ + if v.IsValid() { + return propIterItem{name: newStringValue(key.String()), enumerable: _ENUM_TRUE}, i.next + } + } + + return propIterItem{}, nil +} + +func (o *objectGoMapReflect) iterateStringKeys() iterNextFunc { + return (&gomapReflectPropIter{ + o: o, + keys: o.fieldsValue.MapKeys(), + }).next +} + +func (o *objectGoMapReflect) stringKeys(_ bool, accum []Value) []Value { + // all own keys are enumerable + for _, key := range o.fieldsValue.MapKeys() { + accum = append(accum, newStringValue(key.String())) + } + + return accum +} diff --git a/pkg/xscript/engine/object_gomap_reflect_test.go b/pkg/xscript/engine/object_gomap_reflect_test.go new file mode 100644 index 0000000..75f0caf --- /dev/null +++ b/pkg/xscript/engine/object_gomap_reflect_test.go @@ -0,0 +1,309 @@ +package engine + +import ( + "testing" +) + +func TestGoMapReflectGetSet(t *testing.T) { + const SCRIPT = ` + m.c = m.a + m.b; + ` + + vm := New() + m := map[string]string{ + "a": "4", + "b": "2", + } + vm.Set("m", m) + + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if c := m["c"]; c != "42" { + t.Fatalf("Unexpected value: '%s'", c) + } +} + +func TestGoMapReflectIntKey(t *testing.T) { + const SCRIPT = ` + m[2] = m[0] + m[1]; + ` + + vm := New() + m := map[int]int{ + 0: 40, + 1: 2, + } + vm.Set("m", m) + + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if c := m[2]; c != 42 { + t.Fatalf("Unexpected value: '%d'", c) + } +} + +func TestGoMapReflectDelete(t *testing.T) { + const SCRIPT = ` + delete m.a; + ` + + vm := New() + m := map[string]string{ + "a": "4", + "b": "2", + } + vm.Set("m", m) + + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if _, exists := m["a"]; exists { + t.Fatal("a still exists") + } + + if b := m["b"]; b != "2" { + t.Fatalf("Unexpected b: '%s'", b) + } +} + +func TestGoMapReflectJSON(t *testing.T) { + const SCRIPT = ` + function f(m) { + return JSON.stringify(m); + } + ` + + vm := New() + m := map[string]string{ + "t": "42", + } + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + f := vm.Get("f") + if call, ok := AssertFunction(f); ok { + v, err := call(nil, ([]Value{vm.ToValue(m)})...) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(asciiString(`{"t":"42"}`)) { + t.Fatalf("Unexpected value: %v", v) + } + } else { + t.Fatalf("Not a function: %v", f) + } +} + +func TestGoMapReflectProto(t *testing.T) { + const SCRIPT = ` + m.hasOwnProperty("t"); + ` + + vm := New() + m := map[string]string{ + "t": "42", + } + vm.Set("m", m) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +type gomapReflect_noMethods map[string]any +type gomapReflect_withMethods map[string]any + +func (m gomapReflect_withMethods) Method() bool { + return true +} + +func TestGoMapReflectNoMethods(t *testing.T) { + const SCRIPT = ` + typeof m === "object" && m.hasOwnProperty("t") && m.t === 42; + ` + + vm := New() + m := make(gomapReflect_noMethods) + m["t"] = 42 + vm.Set("m", m) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + +} + +func TestGoMapReflectWithMethods(t *testing.T) { + const SCRIPT = ` + typeof m === "object" && !m.hasOwnProperty("t") && m.hasOwnProperty("Method") && m.Method(); + ` + + vm := New() + m := make(gomapReflect_withMethods) + m["t"] = 42 + vm.Set("m", m) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + +} + +func TestGoMapReflectWithProto(t *testing.T) { + vm := New() + m := map[string]string{ + "t": "42", + } + vm.Set("m", m) + vm.testScriptWithTestLib(` + (function() { + 'use strict'; + var proto = {}; + var getterAllowed = false; + var setterAllowed = false; + var tHolder = "proto t"; + Object.defineProperty(proto, "t", { + get: function() { + if (!getterAllowed) throw new Error("getter is called"); + return tHolder; + }, + set: function(v) { + if (!setterAllowed) throw new Error("setter is called"); + tHolder = v; + } + }); + var t1Holder; + Object.defineProperty(proto, "t1", { + get: function() { + return t1Holder; + }, + set: function(v) { + t1Holder = v; + } + }); + Object.setPrototypeOf(m, proto); + assert.sameValue(m.t, "42"); + m.t = 43; + assert.sameValue(m.t, "43"); + t1Holder = "test"; + assert.sameValue(m.t1, "test"); + m.t1 = "test1"; + assert.sameValue(m.t1, "test1"); + delete m.t; + getterAllowed = true; + assert.sameValue(m.t, "proto t", "after delete"); + setterAllowed = true; + m.t = true; + assert.sameValue(m.t, true, "m.t === true"); + assert.sameValue(tHolder, true, "tHolder === true"); + Object.preventExtensions(m); + assert.throws(TypeError, function() { + m.t2 = 1; + }); + m.t1 = "test2"; + assert.sameValue(m.t1, "test2"); + })(); + `, _undefined, t) +} + +func TestGoMapReflectProtoProp(t *testing.T) { + const SCRIPT = ` + (function() { + "use strict"; + var proto = {}; + Object.defineProperty(proto, "ro", {value: 42}); + Object.setPrototypeOf(m, proto); + assert.throws(TypeError, function() { + m.ro = 43; + }); + Object.defineProperty(m, "ro", {value: 43}); + assert.sameValue(m.ro, "43"); + })(); + ` + + r := New() + r.Set("m", map[string]string{}) + r.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestGoMapReflectUnicode(t *testing.T) { + const SCRIPT = ` + Object.setPrototypeOf(m, s); + if (m.Тест !== "passed") { + throw new Error("m.Тест: " + m.Тест); + } + m["é"]; + ` + type S struct { + Тест string + } + vm := New() + m := map[string]int{ + "é": 42, + } + s := S{ + Тест: "passed", + } + vm.Set("m", m) + vm.Set("s", &s) + res, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if res == nil || !res.StrictEquals(valueInt(42)) { + t.Fatalf("Unexpected value: %v", res) + } +} + +func TestGoMapReflectStruct(t *testing.T) { + type S struct { + Test int + } + + m := map[string]S{ + "1": {Test: 1}, + } + + vm := New() + vm.Set("m", m) + res, err := vm.RunString("m[1].Test = 2; m[1].Test") + if err != nil { + t.Fatal(err) + } + if res.Export() != int64(1) { + t.Fatal(res) + } +} + +func TestGoMapReflectElt(t *testing.T) { + type mapping map[string]any + + const SCRIPT = `a.s() && a.t === null && a.t1 === undefined;` + + r := New() + + r.Set("a", mapping{ + "s": func() bool { return true }, + "t": nil, + }) + + r.testScript(SCRIPT, valueTrue, t) +} diff --git a/pkg/xscript/engine/object_gomap_test.go b/pkg/xscript/engine/object_gomap_test.go new file mode 100644 index 0000000..e3c47d8 --- /dev/null +++ b/pkg/xscript/engine/object_gomap_test.go @@ -0,0 +1,328 @@ +package engine + +import "testing" + +func TestGomapProp(t *testing.T) { + const SCRIPT = ` + o.a + o.b; + ` + r := New() + r.Set("o", map[string]any{ + "a": 40, + "b": 2, + }) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if i := v.ToInteger(); i != 42 { + t.Fatalf("Expected 42, got: %d", i) + } +} + +func TestGomapEnumerate(t *testing.T) { + const SCRIPT = ` + var hasX = false; + var hasY = false; + for (var key in o) { + switch (key) { + case "x": + if (hasX) { + throw "Already have x"; + } + hasX = true; + break; + case "y": + if (hasY) { + throw "Already have y"; + } + hasY = true; + break; + default: + throw "Unexpected property: " + key; + } + } + hasX && hasY; + ` + r := New() + r.Set("o", map[string]any{ + "x": 40, + "y": 2, + }) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +func TestGomapDeleteWhileEnumerate(t *testing.T) { + const SCRIPT = ` + var hasX = false; + var hasY = false; + for (var key in o) { + switch (key) { + case "x": + if (hasX) { + throw "Already have x"; + } + hasX = true; + delete o.y; + break; + case "y": + if (hasY) { + throw "Already have y"; + } + hasY = true; + delete o.x; + break; + default: + throw "Unexpected property: " + key; + } + } + hasX && !hasY || hasY && !hasX; + ` + r := New() + r.Set("o", map[string]any{ + "x": 40, + "y": 2, + }) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +func TestGomapInstanceOf(t *testing.T) { + const SCRIPT = ` + (o instanceof Object) && !(o instanceof Error); + ` + r := New() + r.Set("o", map[string]any{}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +func TestGomapTypeOf(t *testing.T) { + const SCRIPT = ` + typeof o; + ` + r := New() + r.Set("o", map[string]any{}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(asciiString("object")) { + t.Fatalf("Expected object, got %v", v) + } +} + +func TestGomapProto(t *testing.T) { + const SCRIPT = ` + o.hasOwnProperty("test"); + ` + r := New() + r.Set("o", map[string]any{ + "test": 42, + }) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +func TestGoMapExtensibility(t *testing.T) { + const SCRIPT = ` + "use strict"; + o.test = 42; + Object.preventExtensions(o); + o.test = 43; + try { + o.test1 = 42; + } catch (e) { + if (!(e instanceof TypeError)) { + throw e; + } + } + o.test === 43 && o.test1 === undefined; + ` + + r := New() + r.Set("o", map[string]any{}) + v, err := r.RunString(SCRIPT) + if err != nil { + if ex, ok := err.(*Exception); ok { + t.Fatal(ex.String()) + } else { + t.Fatal(err) + } + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + +} + +func TestGoMapWithProto(t *testing.T) { + vm := New() + m := map[string]any{ + "t": "42", + } + vm.Set("m", m) + vm.testScriptWithTestLib(` + (function() { + 'use strict'; + var proto = {}; + var getterAllowed = false; + var setterAllowed = false; + var tHolder = "proto t"; + Object.defineProperty(proto, "t", { + get: function() { + if (!getterAllowed) throw new Error("getter is called"); + return tHolder; + }, + set: function(v) { + if (!setterAllowed) throw new Error("setter is called"); + tHolder = v; + } + }); + var t1Holder; + Object.defineProperty(proto, "t1", { + get: function() { + return t1Holder; + }, + set: function(v) { + t1Holder = v; + } + }); + Object.setPrototypeOf(m, proto); + assert.sameValue(m.t, "42"); + m.t = 43; + assert.sameValue(m.t, 43); + t1Holder = "test"; + assert.sameValue(m.t1, "test"); + m.t1 = "test1"; + assert.sameValue(m.t1, "test1"); + delete m.t; + getterAllowed = true; + assert.sameValue(m.t, "proto t", "after delete"); + setterAllowed = true; + m.t = true; + assert.sameValue(m.t, true); + assert.sameValue(tHolder, true); + Object.preventExtensions(m); + assert.throws(TypeError, function() { + m.t2 = 1; + }); + m.t1 = "test2"; + assert.sameValue(m.t1, "test2"); + })(); + `, _undefined, t) +} + +func TestGoMapProtoProp(t *testing.T) { + const SCRIPT = ` + (function() { + "use strict"; + var proto = {}; + Object.defineProperty(proto, "ro", {value: 42}); + Object.setPrototypeOf(m, proto); + assert.throws(TypeError, function() { + m.ro = 43; + }); + Object.defineProperty(m, "ro", {value: 43}); + assert.sameValue(m.ro, 43); + })(); + ` + + r := New() + r.Set("m", map[string]any{}) + r.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestGoMapProtoPropChain(t *testing.T) { + const SCRIPT = ` + (function() { + "use strict"; + var p1 = Object.create(null); + m.__proto__ = p1; + + Object.defineProperty(p1, "test", { + value: 42 + }); + + Object.defineProperty(m, "test", { + value: 43, + writable: true, + }); + var o = Object.create(m); + o.test = 44; + assert.sameValue(o.test, 44); + + var sym = Symbol(true); + Object.defineProperty(p1, sym, { + value: 42 + }); + + Object.defineProperty(m, sym, { + value: 43, + writable: true, + }); + o[sym] = 44; + assert.sameValue(o[sym], 44); + })(); + ` + + r := New() + r.Set("m", map[string]any{}) + r.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestGoMapUnicode(t *testing.T) { + const SCRIPT = ` + Object.setPrototypeOf(m, s); + if (m.Тест !== "passed") { + throw new Error("m.Тест: " + m.Тест); + } + m["é"]; + ` + type S struct { + Тест string + } + vm := New() + m := map[string]any{ + "é": 42, + } + s := S{ + Тест: "passed", + } + vm.Set("m", m) + vm.Set("s", &s) + res, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if res == nil || !res.StrictEquals(valueInt(42)) { + t.Fatalf("Unexpected value: %v", res) + } +} diff --git a/pkg/xscript/engine/object_goreflect.go b/pkg/xscript/engine/object_goreflect.go new file mode 100644 index 0000000..c3b18ce --- /dev/null +++ b/pkg/xscript/engine/object_goreflect.go @@ -0,0 +1,671 @@ +package engine + +import ( + "fmt" + "go/ast" + "reflect" + "strings" + + "pandax/pkg/xscript/engine/parser" + "pandax/pkg/xscript/engine/unistring" +) + +// JsonEncodable allows custom JSON encoding by JSON.stringify() +// Note that if the returned value itself also implements JsonEncodable, it won't have any effect. +type JsonEncodable interface { + JsonEncodable() any +} + +// FieldNameMapper provides custom mapping between Go and JavaScript property names. +type FieldNameMapper interface { + // FieldName returns a JavaScript name for the given struct field in the given type. + // If this method returns "" the field becomes hidden. + FieldName(t reflect.Type, f reflect.StructField) string + + // MethodName returns a JavaScript name for the given method in the given type. + // If this method returns "" the method becomes hidden. + MethodName(t reflect.Type, m reflect.Method) string +} + +type tagFieldNameMapper struct { + tagName string + uncapMethods bool +} + +func (tfm tagFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string { + tag := f.Tag.Get(tfm.tagName) + if idx := strings.IndexByte(tag, ','); idx != -1 { + tag = tag[:idx] + } + if parser.IsIdentifier(tag) { + return tag + } + return "" +} + +func uncapitalize(s string) string { + return strings.ToLower(s[0:1]) + s[1:] +} + +func (tfm tagFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string { + if tfm.uncapMethods { + return uncapitalize(m.Name) + } + return m.Name +} + +type uncapFieldNameMapper struct { +} + +func (u uncapFieldNameMapper) FieldName(_ reflect.Type, f reflect.StructField) string { + return uncapitalize(f.Name) +} + +func (u uncapFieldNameMapper) MethodName(_ reflect.Type, m reflect.Method) string { + return uncapitalize(m.Name) +} + +type reflectFieldInfo struct { + Index []int + Anonymous bool +} + +type reflectFieldsInfo struct { + Fields map[string]reflectFieldInfo + Names []string +} + +type reflectMethodsInfo struct { + Methods map[string]int + Names []string +} + +type reflectValueWrapper interface { + esValue() Value + reflectValue() reflect.Value + setReflectValue(reflect.Value) +} + +func isContainer(k reflect.Kind) bool { + switch k { + case reflect.Struct, reflect.Slice, reflect.Array: + return true + } + return false +} + +func copyReflectValueWrapper(w reflectValueWrapper) { + v := w.reflectValue() + c := reflect.New(v.Type()).Elem() + c.Set(v) + w.setReflectValue(c) +} + +type objectGoReflect struct { + baseObject + origValue, fieldsValue reflect.Value + + fieldsInfo *reflectFieldsInfo + methodsInfo *reflectMethodsInfo + + methodsValue reflect.Value + + valueCache map[string]reflectValueWrapper + + toString, valueOf func() Value + + toJson func() any +} + +func (o *objectGoReflect) init() { + o.baseObject.init() + switch o.fieldsValue.Kind() { + case reflect.Bool: + o.class = classBoolean + o.prototype = o.val.runtime.getBooleanPrototype() + o.toString = o._toStringBool + o.valueOf = o._valueOfBool + case reflect.String: + o.class = classString + o.prototype = o.val.runtime.getStringPrototype() + o.toString = o._toStringString + case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64: + o.class = classNumber + o.prototype = o.val.runtime.getNumberPrototype() + o.valueOf = o._valueOfInt + case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64: + o.class = classNumber + o.prototype = o.val.runtime.getNumberPrototype() + o.valueOf = o._valueOfUint + case reflect.Float32, reflect.Float64: + o.class = classNumber + o.prototype = o.val.runtime.getNumberPrototype() + o.valueOf = o._valueOfFloat + default: + o.class = classObject + o.prototype = o.val.runtime.global.ObjectPrototype + } + + if o.fieldsValue.Kind() == reflect.Struct { + o.fieldsInfo = o.val.runtime.fieldsInfo(o.fieldsValue.Type()) + } + + var methodsType reflect.Type + // Always use pointer type for non-interface values to be able to access both methods defined on + // the literal type and on the pointer. + if o.fieldsValue.Kind() != reflect.Interface { + methodsType = reflect.PtrTo(o.fieldsValue.Type()) + } else { + methodsType = o.fieldsValue.Type() + } + + o.methodsInfo = o.val.runtime.methodsInfo(methodsType) + + // Container values and values that have at least one method defined on the pointer type + // need to be addressable. + if !o.origValue.CanAddr() && (isContainer(o.origValue.Kind()) || len(o.methodsInfo.Names) > 0) { + value := reflect.New(o.origValue.Type()).Elem() + value.Set(o.origValue) + o.origValue = value + if value.Kind() != reflect.Ptr { + o.fieldsValue = value + } + } + + o.extensible = true + + switch o.origValue.Interface().(type) { + case fmt.Stringer: + o.toString = o._toStringStringer + case error: + o.toString = o._toStringError + } + + if len(o.methodsInfo.Names) > 0 && o.fieldsValue.Kind() != reflect.Interface { + o.methodsValue = o.fieldsValue.Addr() + } else { + o.methodsValue = o.fieldsValue + } + + if j, ok := o.origValue.Interface().(JsonEncodable); ok { + o.toJson = j.JsonEncodable + } +} + +func (o *objectGoReflect) getStr(name unistring.String, receiver Value) Value { + if v := o._get(name.String()); v != nil { + return v + } + return o.baseObject.getStr(name, receiver) +} + +func (o *objectGoReflect) _getField(jsName string) reflect.Value { + if o.fieldsInfo != nil { + if info, exists := o.fieldsInfo.Fields[jsName]; exists { + return o.fieldsValue.FieldByIndex(info.Index) + } + } + + return reflect.Value{} +} + +func (o *objectGoReflect) _getMethod(jsName string) reflect.Value { + if o.methodsInfo != nil { + if idx, exists := o.methodsInfo.Methods[jsName]; exists { + return o.methodsValue.Method(idx) + } + } + + return reflect.Value{} +} + +func (o *objectGoReflect) elemToValue(ev reflect.Value) (Value, reflectValueWrapper) { + if isContainer(ev.Kind()) { + ret := o.val.runtime.toValue(ev.Interface(), ev) + if obj, ok := ret.(*Object); ok { + if w, ok := obj.self.(reflectValueWrapper); ok { + return ret, w + } + } + return ret, nil + } + + if ev.Kind() == reflect.Interface { + ev = ev.Elem() + } + + if ev.Kind() == reflect.Invalid { + return _null, nil + } + + return o.val.runtime.toValue(ev.Interface(), ev), nil +} + +func (o *objectGoReflect) _getFieldValue(name string) Value { + if v := o.valueCache[name]; v != nil { + return v.esValue() + } + if v := o._getField(name); v.IsValid() { + res, w := o.elemToValue(v) + if w != nil { + if o.valueCache == nil { + o.valueCache = make(map[string]reflectValueWrapper) + } + o.valueCache[name] = w + } + return res + } + return nil +} + +func (o *objectGoReflect) _get(name string) Value { + if o.fieldsValue.Kind() == reflect.Struct { + if ret := o._getFieldValue(name); ret != nil { + return ret + } + } + + if v := o._getMethod(name); v.IsValid() { + return o.val.runtime.toValue(v.Interface(), v) + } + + return nil +} + +func (o *objectGoReflect) getOwnPropStr(name unistring.String) Value { + n := name.String() + if o.fieldsValue.Kind() == reflect.Struct { + if v := o._getFieldValue(n); v != nil { + return &valueProperty{ + value: v, + writable: true, + enumerable: true, + } + } + } + + if v := o._getMethod(n); v.IsValid() { + return &valueProperty{ + value: o.val.runtime.toValue(v.Interface(), v), + enumerable: true, + } + } + + return o.baseObject.getOwnPropStr(name) +} + +func (o *objectGoReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { + has, ok := o._put(name.String(), val, throw) + if !has { + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { + o.val.runtime.typeErrorResult(throw, "Cannot assign to property %s of a host object", name) + return false + } else { + return res + } + } + return ok +} + +func (o *objectGoReflect) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o._has(name.String())), val, receiver, throw) +} + +func (o *objectGoReflect) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignIdx(idx, nil, val, receiver, throw) +} + +func (o *objectGoReflect) _put(name string, val Value, throw bool) (has, ok bool) { + if o.fieldsValue.Kind() == reflect.Struct { + if v := o._getField(name); v.IsValid() { + cached := o.valueCache[name] + if cached != nil { + copyReflectValueWrapper(cached) + } + + err := o.val.runtime.toReflectValue(val, v, &objectExportCtx{}) + if err != nil { + if cached != nil { + cached.setReflectValue(v) + } + o.val.runtime.typeErrorResult(throw, "Go struct conversion error: %v", err) + return true, false + } + if cached != nil { + delete(o.valueCache, name) + } + return true, true + } + } + return false, false +} + +func (o *objectGoReflect) _putProp(name unistring.String, value Value, writable, enumerable, configurable bool) Value { + if _, ok := o._put(name.String(), value, false); ok { + return value + } + return o.baseObject._putProp(name, value, writable, enumerable, configurable) +} + +func (r *Runtime) checkHostObjectPropertyDescr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if descr.Getter != nil || descr.Setter != nil { + r.typeErrorResult(throw, "Host objects do not support accessor properties") + return false + } + if descr.Writable == FLAG_FALSE { + r.typeErrorResult(throw, "Host object field %s cannot be made read-only", name) + return false + } + if descr.Configurable == FLAG_TRUE { + r.typeErrorResult(throw, "Host object field %s cannot be made configurable", name) + return false + } + return true +} + +func (o *objectGoReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { + n := name.String() + if has, ok := o._put(n, descr.Value, throw); !has { + o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a host object", n) + return false + } else { + return ok + } + } + return false +} + +func (o *objectGoReflect) _has(name string) bool { + if o.fieldsValue.Kind() == reflect.Struct { + if v := o._getField(name); v.IsValid() { + return true + } + } + if v := o._getMethod(name); v.IsValid() { + return true + } + return false +} + +func (o *objectGoReflect) hasOwnPropertyStr(name unistring.String) bool { + return o._has(name.String()) || o.baseObject.hasOwnPropertyStr(name) +} + +func (o *objectGoReflect) _valueOfInt() Value { + return intToValue(o.fieldsValue.Int()) +} + +func (o *objectGoReflect) _valueOfUint() Value { + return intToValue(int64(o.fieldsValue.Uint())) +} + +func (o *objectGoReflect) _valueOfBool() Value { + if o.fieldsValue.Bool() { + return valueTrue + } else { + return valueFalse + } +} + +func (o *objectGoReflect) _valueOfFloat() Value { + return floatToValue(o.fieldsValue.Float()) +} + +func (o *objectGoReflect) _toStringStringer() Value { + return newStringValue(o.origValue.Interface().(fmt.Stringer).String()) +} + +func (o *objectGoReflect) _toStringString() Value { + return newStringValue(o.fieldsValue.String()) +} + +func (o *objectGoReflect) _toStringBool() Value { + if o.fieldsValue.Bool() { + return stringTrue + } else { + return stringFalse + } +} + +func (o *objectGoReflect) _toStringError() Value { + return newStringValue(o.origValue.Interface().(error).Error()) +} + +func (o *objectGoReflect) deleteStr(name unistring.String, throw bool) bool { + n := name.String() + if o._has(n) { + o.val.runtime.typeErrorResult(throw, "Cannot delete property %s from a Go type", n) + return false + } + return o.baseObject.deleteStr(name, throw) +} + +type goreflectPropIter struct { + o *objectGoReflect + idx int +} + +func (i *goreflectPropIter) nextField() (propIterItem, iterNextFunc) { + names := i.o.fieldsInfo.Names + if i.idx < len(names) { + name := names[i.idx] + i.idx++ + return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.nextField + } + + i.idx = 0 + return i.nextMethod() +} + +func (i *goreflectPropIter) nextMethod() (propIterItem, iterNextFunc) { + names := i.o.methodsInfo.Names + if i.idx < len(names) { + name := names[i.idx] + i.idx++ + return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.nextMethod + } + + return propIterItem{}, nil +} + +func (o *objectGoReflect) iterateStringKeys() iterNextFunc { + r := &goreflectPropIter{ + o: o, + } + if o.fieldsInfo != nil { + return r.nextField + } + + return r.nextMethod +} + +func (o *objectGoReflect) stringKeys(_ bool, accum []Value) []Value { + // all own keys are enumerable + if o.fieldsInfo != nil { + for _, name := range o.fieldsInfo.Names { + accum = append(accum, newStringValue(name)) + } + } + + for _, name := range o.methodsInfo.Names { + accum = append(accum, newStringValue(name)) + } + + return accum +} + +func (o *objectGoReflect) export(*objectExportCtx) any { + return o.origValue.Interface() +} + +func (o *objectGoReflect) exportType() reflect.Type { + return o.origValue.Type() +} + +func (o *objectGoReflect) equal(other objectImpl) bool { + if other, ok := other.(*objectGoReflect); ok { + k1, k2 := o.fieldsValue.Kind(), other.fieldsValue.Kind() + if k1 == k2 { + if isContainer(k1) { + return o.fieldsValue == other.fieldsValue + } + return o.fieldsValue.Interface() == other.fieldsValue.Interface() + } + } + return false +} + +func (o *objectGoReflect) reflectValue() reflect.Value { + return o.fieldsValue +} + +func (o *objectGoReflect) setReflectValue(v reflect.Value) { + o.fieldsValue = v + o.origValue = v + o.methodsValue = v.Addr() +} + +func (o *objectGoReflect) esValue() Value { + return o.val +} + +func (r *Runtime) buildFieldInfo(t reflect.Type, index []int, info *reflectFieldsInfo) { + n := t.NumField() + for i := 0; i < n; i++ { + field := t.Field(i) + name := field.Name + if !ast.IsExported(name) { + continue + } + if r.fieldNameMapper != nil { + name = r.fieldNameMapper.FieldName(t, field) + } + + if name != "" { + if inf, exists := info.Fields[name]; !exists { + info.Names = append(info.Names, name) + } else { + if len(inf.Index) <= len(index) { + continue + } + } + } + + if name != "" || field.Anonymous { + idx := make([]int, len(index)+1) + copy(idx, index) + idx[len(idx)-1] = i + + if name != "" { + info.Fields[name] = reflectFieldInfo{ + Index: idx, + Anonymous: field.Anonymous, + } + } + if field.Anonymous { + typ := field.Type + for typ.Kind() == reflect.Ptr { + typ = typ.Elem() + } + if typ.Kind() == reflect.Struct { + r.buildFieldInfo(typ, idx, info) + } + } + } + } +} + +var emptyMethodsInfo = reflectMethodsInfo{} + +func (r *Runtime) buildMethodsInfo(t reflect.Type) (info *reflectMethodsInfo) { + n := t.NumMethod() + if n == 0 { + return &emptyMethodsInfo + } + info = new(reflectMethodsInfo) + info.Methods = make(map[string]int, n) + info.Names = make([]string, 0, n) + for i := 0; i < n; i++ { + method := t.Method(i) + name := method.Name + if !ast.IsExported(name) { + continue + } + if r.fieldNameMapper != nil { + name = r.fieldNameMapper.MethodName(t, method) + if name == "" { + continue + } + } + + if _, exists := info.Methods[name]; !exists { + info.Names = append(info.Names, name) + } + + info.Methods[name] = i + } + return +} + +func (r *Runtime) buildFieldsInfo(t reflect.Type) (info *reflectFieldsInfo) { + info = new(reflectFieldsInfo) + n := t.NumField() + info.Fields = make(map[string]reflectFieldInfo, n) + info.Names = make([]string, 0, n) + r.buildFieldInfo(t, nil, info) + return +} + +func (r *Runtime) fieldsInfo(t reflect.Type) (info *reflectFieldsInfo) { + var exists bool + if info, exists = r.fieldsInfoCache[t]; !exists { + info = r.buildFieldsInfo(t) + if r.fieldsInfoCache == nil { + r.fieldsInfoCache = make(map[reflect.Type]*reflectFieldsInfo) + } + r.fieldsInfoCache[t] = info + } + + return +} + +func (r *Runtime) methodsInfo(t reflect.Type) (info *reflectMethodsInfo) { + var exists bool + if info, exists = r.methodsInfoCache[t]; !exists { + info = r.buildMethodsInfo(t) + if r.methodsInfoCache == nil { + r.methodsInfoCache = make(map[reflect.Type]*reflectMethodsInfo) + } + r.methodsInfoCache[t] = info + } + + return +} + +// SetFieldNameMapper sets a custom field name mapper for Go types. It can be called at any time, however +// the mapping for any given value is fixed at the point of creation. +// Setting this to nil restores the default behaviour which is all exported fields and methods are mapped to their +// original unchanged names. +func (r *Runtime) SetFieldNameMapper(mapper FieldNameMapper) { + r.fieldNameMapper = mapper + r.fieldsInfoCache = nil + r.methodsInfoCache = nil +} + +// TagFieldNameMapper returns a FieldNameMapper that uses the given tagName for struct fields and optionally +// uncapitalises (making the first letter lower case) method names. +// The common tag value syntax is supported (name[,options]), however options are ignored. +// Setting name to anything other than a valid ECMAScript identifier makes the field hidden. +func TagFieldNameMapper(tagName string, uncapMethods bool) FieldNameMapper { + return tagFieldNameMapper{ + tagName: tagName, + uncapMethods: uncapMethods, + } +} + +// UncapFieldNameMapper returns a FieldNameMapper that uncapitalises struct field and method names +// making the first letter lower case. +func UncapFieldNameMapper() FieldNameMapper { + return uncapFieldNameMapper{} +} diff --git a/pkg/xscript/engine/object_goreflect_test.go b/pkg/xscript/engine/object_goreflect_test.go new file mode 100644 index 0000000..d620a62 --- /dev/null +++ b/pkg/xscript/engine/object_goreflect_test.go @@ -0,0 +1,1595 @@ +package engine + +import ( + "errors" + "fmt" + "math" + "reflect" + "strings" + "testing" + "time" +) + +func TestGoReflectGet(t *testing.T) { + const SCRIPT = ` + o.X + o.Y; + ` + type O struct { + X int + Y string + } + r := New() + o := O{X: 4, Y: "2"} + r.Set("o", o) + + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if s, ok := v.(String); ok { + if s.String() != "42" { + t.Fatalf("Unexpected string: %s", s) + } + } else { + t.Fatalf("Unexpected type: %s", v) + } +} + +func TestGoReflectSet(t *testing.T) { + const SCRIPT = ` + o.X++; + o.Y += "P"; + ` + type O struct { + X int + Y string + } + r := New() + o := O{X: 4, Y: "2"} + r.Set("o", &o) + + _, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if o.X != 5 { + t.Fatalf("Unexpected X: %d", o.X) + } + + if o.Y != "2P" { + t.Fatalf("Unexpected Y: %s", o.Y) + } + + r.Set("o", o) + _, err = r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if res, ok := r.Get("o").Export().(O); ok { + if res.X != 6 { + t.Fatalf("Unexpected res.X: %d", res.X) + } + + if res.Y != "2PP" { + t.Fatalf("Unexpected res.Y: %s", res.Y) + } + } +} + +func TestGoReflectEnumerate(t *testing.T) { + const SCRIPT = ` + var hasX = false; + var hasY = false; + for (var key in o) { + switch (key) { + case "X": + if (hasX) { + throw "Already have X"; + } + hasX = true; + break; + case "Y": + if (hasY) { + throw "Already have Y"; + } + hasY = true; + break; + default: + throw "Unexpected property: " + key; + } + } + hasX && hasY; + ` + + type S struct { + X, Y int + } + + r := New() + r.Set("o", S{X: 40, Y: 2}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + +} + +func TestGoReflectCustomIntUnbox(t *testing.T) { + const SCRIPT = ` + i + 2; + ` + + type CustomInt int + var i CustomInt = 40 + + r := New() + r.Set("i", i) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(intToValue(42)) { + t.Fatalf("Expected int 42, got %v", v) + } +} + +func TestGoReflectPreserveCustomType(t *testing.T) { + const SCRIPT = ` + i; + ` + + type CustomInt int + var i CustomInt = 42 + + r := New() + r.Set("i", i) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + ve := v.Export() + + if ii, ok := ve.(CustomInt); ok { + if ii != i { + t.Fatalf("Wrong value: %v", ii) + } + } else { + t.Fatalf("Wrong type: %v", ve) + } +} + +func TestGoReflectCustomIntValueOf(t *testing.T) { + const SCRIPT = ` + if (i instanceof Number) { + i.valueOf(); + } else { + throw new Error("Value is not a number"); + } + ` + + type CustomInt int + var i CustomInt = 42 + + r := New() + r.Set("i", i) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(intToValue(42)) { + t.Fatalf("Expected int 42, got %v", v) + } +} + +func TestGoReflectEqual(t *testing.T) { + const SCRIPT = ` + x === y; + ` + + type CustomInt int + var x CustomInt = 42 + var y CustomInt = 42 + + r := New() + r.Set("x", x) + r.Set("y", y) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +type testGoReflectMethod_O struct { + field string + Test string +} + +func (o testGoReflectMethod_O) Method(s string) string { + return o.field + s +} + +func TestGoReflectMethod(t *testing.T) { + const SCRIPT = ` + o.Method(" 123") + ` + + o := testGoReflectMethod_O{ + field: "test", + } + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(asciiString("test 123")) { + t.Fatalf("Expected 'test 123', got %v", v) + } +} + +func (o *testGoReflectMethod_O) Set(s string) { + o.field = s +} + +func (o *testGoReflectMethod_O) Get() string { + return o.field +} + +func TestGoReflectMethodPtr(t *testing.T) { + const SCRIPT = ` + o.Set("42") + o.Get() + ` + + o := testGoReflectMethod_O{ + field: "test", + } + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(asciiString("42")) { + t.Fatalf("Expected '42', got %v", v) + } +} + +func (b *testBoolS) Method() bool { + return bool(*b) +} + +func TestGoReflectPtrMethodOnNonPtrValue(t *testing.T) { + var o testGoReflectMethod_O + o.Get() + vm := New() + vm.Set("o", o) + _, err := vm.RunString(`o.Get()`) + if err != nil { + t.Fatal(err) + } + _, err = vm.RunString(`o.Method()`) + if err != nil { + t.Fatal(err) + } + + var b testBoolS + vm.Set("b", b) + _, err = vm.RunString(`b.Method()`) + if err != nil { + t.Fatal(err) + } +} + +func TestGoReflectStructField(t *testing.T) { + type S struct { + F testGoReflectMethod_O + B testBoolS + } + var s S + vm := New() + vm.Set("s", &s) + + const SCRIPT = ` + s.F.Set("Test"); + assert.sameValue(s.F.Method(""), "Test", "1"); + + s.B = true; + assert.sameValue(s.B.Method(), true, "2"); + + assert.sameValue(s.B.toString(), "B", "3"); + ` + + vm.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestGoReflectProp(t *testing.T) { + const SCRIPT = ` + var d1 = Object.getOwnPropertyDescriptor(o, "Get"); + var d2 = Object.getOwnPropertyDescriptor(o, "Test"); + !d1.writable && !d1.configurable && d2.writable && !d2.configurable; + ` + + o := testGoReflectMethod_O{ + field: "test", + } + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +func TestGoReflectRedefineFieldSuccess(t *testing.T) { + const SCRIPT = ` + Object.defineProperty(o, "Test", {value: "AAA"}) === o; + ` + + o := testGoReflectMethod_O{} + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + + if o.Test != "AAA" { + t.Fatalf("Expected 'AAA', got '%s'", o.Test) + } + +} + +func TestGoReflectRedefineFieldNonWritable(t *testing.T) { + const SCRIPT = ` + var thrown = false; + try { + Object.defineProperty(o, "Test", {value: "AAA", writable: false}); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + thrown; + ` + + o := testGoReflectMethod_O{Test: "Test"} + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + + if o.Test != "Test" { + t.Fatalf("Expected 'Test', got: '%s'", o.Test) + } +} + +func TestGoReflectRedefineFieldConfigurable(t *testing.T) { + const SCRIPT = ` + var thrown = false; + try { + Object.defineProperty(o, "Test", {value: "AAA", configurable: true}); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + thrown; + ` + + o := testGoReflectMethod_O{Test: "Test"} + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + + if o.Test != "Test" { + t.Fatalf("Expected 'Test', got: '%s'", o.Test) + } +} + +func TestGoReflectRedefineMethod(t *testing.T) { + const SCRIPT = ` + var thrown = false; + try { + Object.defineProperty(o, "Method", {value: "AAA", configurable: true}); + } catch (e) { + if (e instanceof TypeError) { + thrown = true; + } else { + throw e; + } + } + thrown; + ` + + o := testGoReflectMethod_O{Test: "Test"} + + r := New() + r.Set("o", &o) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } +} + +func TestGoReflectEmbeddedStruct(t *testing.T) { + const SCRIPT = ` + if (o.ParentField2 !== "ParentField2") { + throw new Error("ParentField2 = " + o.ParentField2); + } + + if (o.Parent.ParentField2 !== 2) { + throw new Error("o.Parent.ParentField2 = " + o.Parent.ParentField2); + } + + if (o.ParentField1 !== 1) { + throw new Error("o.ParentField1 = " + o.ParentField1); + + } + + if (o.ChildField !== 3) { + throw new Error("o.ChildField = " + o.ChildField); + } + + var keys = {}; + for (var k in o) { + if (keys[k]) { + throw new Error("Duplicate key: " + k); + } + keys[k] = true; + } + + var expectedKeys = ["ParentField2", "ParentField1", "Parent", "ChildField"]; + for (var i in expectedKeys) { + if (!keys[expectedKeys[i]]) { + throw new Error("Missing key in enumeration: " + expectedKeys[i]); + } + delete keys[expectedKeys[i]]; + } + + var remainingKeys = Object.keys(keys); + if (remainingKeys.length > 0) { + throw new Error("Unexpected keys: " + remainingKeys); + } + + o.ParentField2 = "ParentField22"; + o.Parent.ParentField2 = 22; + o.ParentField1 = 11; + o.ChildField = 33; + ` + + type Parent struct { + ParentField1 int + ParentField2 int + } + + type Child struct { + ParentField2 string + Parent + ChildField int + } + + vm := New() + o := Child{ + Parent: Parent{ + ParentField1: 1, + ParentField2: 2, + }, + ParentField2: "ParentField2", + ChildField: 3, + } + vm.Set("o", &o) + + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if o.ParentField2 != "ParentField22" { + t.Fatalf("ParentField2 = %q", o.ParentField2) + } + + if o.Parent.ParentField2 != 22 { + t.Fatalf("Parent.ParentField2 = %d", o.Parent.ParentField2) + } + + if o.ParentField1 != 11 { + t.Fatalf("ParentField1 = %d", o.ParentField1) + } + + if o.ChildField != 33 { + t.Fatalf("ChildField = %d", o.ChildField) + } +} + +type jsonTagNamer struct{} + +func (jsonTagNamer) FieldName(_ reflect.Type, field reflect.StructField) string { + if jsonTag := field.Tag.Get("json"); jsonTag != "" { + return jsonTag + } + return field.Name +} + +func (jsonTagNamer) MethodName(_ reflect.Type, method reflect.Method) string { + return method.Name +} + +func TestGoReflectCustomNaming(t *testing.T) { + + type testStructWithJsonTags struct { + A string `json:"b"` // <-- script sees field "A" as property "b" + } + + o := &testStructWithJsonTags{"Hello world"} + r := New() + r.SetFieldNameMapper(&jsonTagNamer{}) + r.Set("fn", func() *testStructWithJsonTags { return o }) + + t.Run("get property", func(t *testing.T) { + v, err := r.RunString(`fn().b`) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(newStringValue(o.A)) { + t.Fatalf("Expected %q, got %v", o.A, v) + } + }) + + t.Run("set property", func(t *testing.T) { + _, err := r.RunString(`fn().b = "Hello universe"`) + if err != nil { + t.Fatal(err) + } + if o.A != "Hello universe" { + t.Fatalf("Expected \"Hello universe\", got %q", o.A) + } + }) + + t.Run("enumerate properties", func(t *testing.T) { + v, err := r.RunString(`Object.keys(fn())`) + if err != nil { + t.Fatal(err) + } + if !reflect.DeepEqual(v.Export(), []any{"b"}) { + t.Fatalf("Expected [\"b\"], got %v", v.Export()) + } + }) +} + +func TestGoReflectCustomObjNaming(t *testing.T) { + + type testStructWithJsonTags struct { + A string `json:"b"` // <-- script sees field "A" as property "b" + } + + r := New() + r.SetFieldNameMapper(&jsonTagNamer{}) + + t.Run("Set object in slice", func(t *testing.T) { + testSlice := &[]testStructWithJsonTags{{"Hello world"}} + r.Set("testslice", testSlice) + _, err := r.RunString(`testslice[0] = {b:"setted"}`) + if err != nil { + t.Fatal(err) + } + if (*testSlice)[0].A != "setted" { + t.Fatalf("Expected \"setted\", got %q", (*testSlice)[0]) + } + }) + + t.Run("Set object in map", func(t *testing.T) { + testMap := map[string]testStructWithJsonTags{"key": {"Hello world"}} + r.Set("testmap", testMap) + _, err := r.RunString(`testmap["key"] = {b:"setted"}`) + if err != nil { + t.Fatal(err) + } + if testMap["key"].A != "setted" { + t.Fatalf("Expected \"setted\", got %q", testMap["key"]) + } + }) + + t.Run("Add object to map", func(t *testing.T) { + testMap := map[string]testStructWithJsonTags{} + r.Set("testmap", testMap) + _, err := r.RunString(`testmap["newkey"] = {b:"setted"}`) + if err != nil { + t.Fatal(err) + } + if testMap["newkey"].A != "setted" { + t.Fatalf("Expected \"setted\", got %q", testMap["newkey"]) + } + }) +} + +type fieldNameMapper1 struct{} + +func (fieldNameMapper1) FieldName(_ reflect.Type, f reflect.StructField) string { + return strings.ToLower(f.Name) +} + +func (fieldNameMapper1) MethodName(_ reflect.Type, m reflect.Method) string { + return m.Name +} + +func TestNonStructAnonFields(t *testing.T) { + type Test1 struct { + M bool + } + type test3 []int + type Test4 []int + type Test2 struct { + test3 + Test4 + *Test1 + } + + const SCRIPT = ` + JSON.stringify(a); + a.m && a.test3 === undefined && a.test4.length === 2 + ` + vm := New() + vm.SetFieldNameMapper(fieldNameMapper1{}) + vm.Set("a", &Test2{Test1: &Test1{M: true}, Test4: []int{1, 2}, test3: nil}) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(valueTrue) { + t.Fatalf("Unexepected result: %v", v) + } +} + +func TestStructNonAddressable(t *testing.T) { + type S struct { + Field int + } + + const SCRIPT = ` + "use strict"; + + if (!Object.getOwnPropertyDescriptor(s, "Field").writable) { + throw new Error("s.Field is non-writable"); + } + + if (!Object.getOwnPropertyDescriptor(s1, "Field").writable) { + throw new Error("s1.Field is non-writable"); + } + + s1.Field = 42; + s.Field = 43; + s; +` + + var s S + vm := New() + vm.Set("s", s) + vm.Set("s1", &s) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + exp := v.Export() + if s1, ok := exp.(S); ok { + if s1.Field != 43 { + t.Fatal(s1) + } + } else { + t.Fatalf("Wrong type: %T", exp) + } + if s.Field != 42 { + t.Fatalf("Unexpected s.Field value: %d", s.Field) + } +} + +type testFieldMapper struct { +} + +func (testFieldMapper) FieldName(_ reflect.Type, f reflect.StructField) string { + if tag := f.Tag.Get("js"); tag != "" { + if tag == "-" { + return "" + } + return tag + } + + return f.Name +} + +func (testFieldMapper) MethodName(_ reflect.Type, m reflect.Method) string { + return m.Name +} + +func TestHidingAnonField(t *testing.T) { + type InnerType struct { + AnotherField string + } + + type OuterType struct { + InnerType `js:"-"` + SomeField string + } + + const SCRIPT = ` + var a = Object.getOwnPropertyNames(o); + if (a.length !== 2) { + throw new Error("unexpected length: " + a.length); + } + + if (a.indexOf("SomeField") === -1) { + throw new Error("no SomeField"); + } + + if (a.indexOf("AnotherField") === -1) { + throw new Error("no SomeField"); + } + ` + + var o OuterType + + vm := New() + vm.SetFieldNameMapper(testFieldMapper{}) + vm.Set("o", &o) + + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } +} + +func TestFieldOverriding(t *testing.T) { + type InnerType struct { + AnotherField string + AnotherField1 string + } + + type OuterType struct { + InnerType `js:"-"` + SomeField string + AnotherField string `js:"-"` + AnotherField1 string + } + + const SCRIPT = ` + if (o.SomeField !== "SomeField") { + throw new Error("SomeField"); + } + + if (o.AnotherField !== "AnotherField inner") { + throw new Error("AnotherField"); + } + + if (o.AnotherField1 !== "AnotherField1 outer") { + throw new Error("AnotherField1"); + } + + if (o.InnerType) { + throw new Error("InnerType is present"); + } + ` + + o := OuterType{ + InnerType: InnerType{ + AnotherField: "AnotherField inner", + AnotherField1: "AnotherField1 inner", + }, + SomeField: "SomeField", + AnotherField: "AnotherField outer", + AnotherField1: "AnotherField1 outer", + } + + vm := New() + vm.SetFieldNameMapper(testFieldMapper{}) + vm.Set("o", &o) + + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } +} + +func TestDefinePropertyUnexportedJsName(t *testing.T) { + type T struct { + Field int + unexported int + } + + vm := New() + vm.SetFieldNameMapper(fieldNameMapper1{}) + vm.Set("f", &T{unexported: 0}) + + _, err := vm.RunString(` + "use strict"; + Object.defineProperty(f, "field", {value: 42}); + if (f.field !== 42) { + throw new Error("Unexpected value: " + f.field); + } + if (f.hasOwnProperty("unexported")) { + throw new Error("hasOwnProperty('unexported') is true"); + } + var thrown; + try { + Object.defineProperty(f, "unexported", {value: 1}); + } catch (e) { + thrown = e; + } + if (!(thrown instanceof TypeError)) { + throw new Error("Unexpected error: ", thrown); + } + `) + if err != nil { + t.Fatal(err) + } +} + +type fieldNameMapperToLower struct{} + +func (fieldNameMapperToLower) FieldName(_ reflect.Type, f reflect.StructField) string { + return strings.ToLower(f.Name) +} + +func (fieldNameMapperToLower) MethodName(_ reflect.Type, m reflect.Method) string { + return strings.ToLower(m.Name) +} + +func TestHasOwnPropertyUnexportedJsName(t *testing.T) { + vm := New() + vm.SetFieldNameMapper(fieldNameMapperToLower{}) + vm.Set("f", &testGoReflectMethod_O{}) + + _, err := vm.RunString(` + "use strict"; + if (!f.hasOwnProperty("test")) { + throw new Error("hasOwnProperty('test') returned false"); + } + if (!f.hasOwnProperty("method")) { + throw new Error("hasOwnProperty('method') returned false"); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func BenchmarkGoReflectGet(b *testing.B) { + type parent struct { + field, Test1, Test2, Test3, Test4, Test5, Test string + } + + type child struct { + parent + Test6 string + } + + b.StopTimer() + vm := New() + + b.StartTimer() + for i := 0; i < b.N; i++ { + v := vm.ToValue(child{parent: parent{Test: "Test", field: ""}}).(*Object) + v.Get("Test") + } +} + +func TestNestedStructSet(t *testing.T) { + type B struct { + Field int + } + type A struct { + B B + } + + const SCRIPT = ` + 'use strict'; + a.B.Field++; + if (a1.B.Field != 1) { + throw new Error("a1.B.Field = " + a1.B.Field); + } + var d = Object.getOwnPropertyDescriptor(a1.B, "Field"); + if (!d.writable) { + throw new Error("a1.B is not writable"); + } + a1.B.Field = 42; + a1; + ` + a := A{ + B: B{ + Field: 1, + }, + } + vm := New() + vm.Set("a", &a) + vm.Set("a1", a) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + exp := v.Export() + if v, ok := exp.(A); ok { + if v.B.Field != 42 { + t.Fatal(v) + } + } else { + t.Fatalf("Wrong type: %T", exp) + } + + if v := a.B.Field; v != 2 { + t.Fatalf("Unexpected a.B.Field: %d", v) + } +} + +func TestStructNonAddressableAnonStruct(t *testing.T) { + + type C struct { + Z int64 + X string + } + + type B struct { + C + Y string + } + + type A struct { + B B + } + + a := A{ + B: B{ + C: C{ + Z: 1, + X: "X2", + }, + Y: "Y3", + }, + } + const SCRIPT = ` + "use strict"; + var s = JSON.stringify(a); + s; +` + + vm := New() + vm.Set("a", &a) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + expected := `{"B":{"C":{"Z":1,"X":"X2"},"Z":1,"X":"X2","Y":"Y3"}}` + if expected != v.String() { + t.Fatalf("Expected '%s', got '%s'", expected, v.String()) + } + +} + +func TestTagFieldNameMapperInvalidId(t *testing.T) { + vm := New() + vm.SetFieldNameMapper(TagFieldNameMapper("json", true)) + type S struct { + Field int `json:"-"` + } + vm.Set("s", S{Field: 42}) + res, err := vm.RunString(`s.hasOwnProperty("field") || s.hasOwnProperty("Field")`) + if err != nil { + t.Fatal(err) + } + if res != valueFalse { + t.Fatalf("Unexpected result: %v", res) + } +} + +func TestPrimitivePtr(t *testing.T) { + vm := New() + s := "test" + vm.Set("s", &s) + res, err := vm.RunString(`s instanceof String && s == "test"`) // note non-strict equality + if err != nil { + t.Fatal(err) + } + if v := res.ToBoolean(); !v { + t.Fatalf("value: %#v", res) + } + s = "test1" + res, err = vm.RunString(`s == "test1"`) + if err != nil { + t.Fatal(err) + } + if v := res.ToBoolean(); !v { + t.Fatalf("value: %#v", res) + } +} + +func TestStringer(t *testing.T) { + vm := New() + vm.Set("e", errors.New("test")) + res, err := vm.RunString("e.toString()") + if err != nil { + t.Fatal(err) + } + if v := res.Export(); v != "test" { + t.Fatalf("v: %v", v) + } +} + +func ExampleTagFieldNameMapper() { + vm := New() + vm.SetFieldNameMapper(TagFieldNameMapper("json", true)) + type S struct { + Field int `json:"field"` + } + vm.Set("s", S{Field: 42}) + res, _ := vm.RunString(`s.field`) + fmt.Println(res.Export()) + // Output: 42 +} + +func ExampleUncapFieldNameMapper() { + vm := New() + s := testGoReflectMethod_O{ + Test: "passed", + } + vm.SetFieldNameMapper(UncapFieldNameMapper()) + vm.Set("s", s) + res, _ := vm.RunString(`s.test + " and " + s.method("passed too")`) + fmt.Println(res.Export()) + // Output: passed and passed too +} + +func TestGoReflectWithProto(t *testing.T) { + type S struct { + Field int + } + var s S + vm := New() + vm.Set("s", &s) + vm.testScriptWithTestLib(` + (function() { + 'use strict'; + var proto = { + Field: "protoField", + test: 42 + }; + var test1Holder; + Object.defineProperty(proto, "test1", { + set: function(v) { + test1Holder = v; + }, + get: function() { + return test1Holder; + } + }); + Object.setPrototypeOf(s, proto); + assert.sameValue(s.Field, 0, "s.Field"); + s.Field = 2; + assert.sameValue(s.Field, 2, "s.Field"); + assert.sameValue(s.test, 42, "s.test"); + assert.throws(TypeError, function() { + Object.defineProperty(s, "test", {value: 43}); + }); + test1Holder = 1; + assert.sameValue(s.test1, 1, "s.test1"); + s.test1 = 2; + assert.sameValue(test1Holder, 2, "test1Holder"); + })(); + `, _undefined, t) +} + +func TestGoReflectSymbols(t *testing.T) { + type S struct { + Field int + } + var s S + vm := New() + vm.Set("s", &s) + _, err := vm.RunString(` + 'use strict'; + var sym = Symbol(66); + s[sym] = "Test"; + if (s[sym] !== "Test") { + throw new Error("s[sym]=" + s[sym]); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoReflectSymbolEqualityQuirk(t *testing.T) { + type Field struct { + } + type S struct { + Field *Field + } + var s = S{ + Field: &Field{}, + } + vm := New() + vm.Set("s", &s) + res, err := vm.RunString(` + var sym = Symbol(66); + var field1 = s.Field; + field1[sym] = true; + var field2 = s.Field; + // Because a wrapper is created every time the property is accessed + // field1 and field2 will be different instances of the wrapper. + // Symbol properties only exist in the wrapper, they cannot be placed into the original Go value, + // hence the following: + field1 === field2 && field1[sym] === true && field2[sym] === undefined; + `) + if err != nil { + t.Fatal(err) + } + if res != valueTrue { + t.Fatal(res) + } +} + +func TestGoObj__Proto__(t *testing.T) { + type S struct { + Field int + } + vm := New() + vm.Set("s", S{}) + vm.Set("m", map[string]any{}) + vm.Set("mr", map[int]string{}) + vm.Set("a", []any{}) + vm.Set("ar", []string{}) + _, err := vm.RunString(` + function f(s, expectedCtor, prefix) { + if (s.__proto__ !== expectedCtor.prototype) { + throw new Error(prefix + ": __proto__: " + s.__proto__); + } + s.__proto__ = null; + if (s.__proto__ !== undefined) { // as there is no longer a prototype, there is no longer the __proto__ property + throw new Error(prefix + ": __proto__ is not undefined: " + s.__proto__); + } + var proto = Object.getPrototypeOf(s); + if (proto !== null) { + throw new Error(prefix + ": proto is not null: " + proto); + } + } + f(s, Object, "struct"); + f(m, Object, "simple map"); + f(mr, Object, "reflect map"); + f(a, Array, "slice"); + f(ar, Array, "reflect slice"); + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoReflectUnicodeProps(t *testing.T) { + type S struct { + Тест string + } + vm := New() + var s S + vm.Set("s", &s) + _, err := vm.RunString(` + if (!s.hasOwnProperty("Тест")) { + throw new Error("hasOwnProperty"); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoReflectPreserveType(t *testing.T) { + vm := New() + var expect = time.Duration(math.MaxInt64) + vm.Set(`make`, func() time.Duration { + return expect + }) + vm.Set(`handle`, func(d time.Duration) { + if d.String() != expect.String() { + t.Fatal(`expect`, expect, `, but get`, d) + } + }) + _, e := vm.RunString(` + var d=make() + handle(d) + `) + if e != nil { + t.Fatal(e) + } +} + +func TestGoReflectCopyOnWrite(t *testing.T) { + type Inner struct { + Field int + } + type S struct { + I Inner + } + var s S + s.I.Field = 1 + + vm := New() + vm.Set("s", &s) + _, err := vm.RunString(` + if (s.I.Field !== 1) { + throw new Error("s.I.Field: " + s.I.Field); + } + + let tmp = s.I; // tmp becomes a reference to s.I + if (tmp.Field !== 1) { + throw new Error("tmp.Field: " + tmp.Field); + } + + s.I.Field = 2; + if (s.I.Field !== 2) { + throw new Error("s.I.Field (1): " + s.I.Field); + } + if (tmp.Field !== 2) { + throw new Error("tmp.Field (1): " + tmp.Field); + } + + s.I = {Field: 3}; // at this point tmp is changed to a copy + if (s.I.Field !== 3) { + throw new Error("s.I.Field (2): " + s.I.Field); + } + if (tmp.Field !== 2) { + throw new Error("tmp.Field (2): " + tmp.Field); + } + `) + + if err != nil { + t.Fatal(err) + } +} + +func TestReflectSetReflectValue(t *testing.T) { + o := []testGoReflectMethod_O{{}} + vm := New() + vm.Set("o", o) + _, err := vm.RunString(` + const t = o[0]; + t.Set("a"); + o[0] = {}; + o[0].Set("b"); + if (t.Get() !== "a") { + throw new Error(); + } + `) + + if err != nil { + t.Fatal(err) + } +} + +func TestReflectOverwriteReflectMap(t *testing.T) { + vm := New() + type S struct { + M map[int]any + } + var s S + s.M = map[int]any{ + 0: true, + } + vm.Set("s", &s) + _, err := vm.RunString(` + s.M = {1: false}; + `) + if err != nil { + t.Fatal(err) + } + if _, exists := s.M[0]; exists { + t.Fatal(s) + } +} + +type testBoolS bool + +func (testBoolS) String() string { + return "B" +} + +type testIntS int + +func (testIntS) String() string { + return "I" +} + +type testStringS string + +func (testStringS) String() string { + return "S" +} + +func TestGoReflectToPrimitive(t *testing.T) { + vm := New() + + f := func(expr string, expected Value, t *testing.T) { + v, err := vm.RunString(expr) + if err != nil { + t.Fatal(err) + } + if IsNaN(expected) { + if IsNaN(v) { + return + } + } else { + if v.StrictEquals(expected) { + return + } + } + t.Fatalf("%s: expected: %v, actual: %v", expr, expected, v) + } + + t.Run("Not Stringers", func(t *testing.T) { + type Bool bool + var b Bool = true + + t.Run("Bool", func(t *testing.T) { + vm.Set("b", b) + f("+b", intToValue(1), t) + f("`${b}`", asciiString("true"), t) + f("b.toString()", asciiString("true"), t) + f("b.valueOf()", valueTrue, t) + }) + + t.Run("*Bool", func(t *testing.T) { + vm.Set("b", &b) + f("+b", intToValue(1), t) + f("`${b}`", asciiString("true"), t) + f("b.toString()", asciiString("true"), t) + f("b.valueOf()", valueTrue, t) + }) + + type Int int + var i Int = 1 + + t.Run("Int", func(t *testing.T) { + vm.Set("i", i) + f("+i", intToValue(1), t) + f("`${i}`", asciiString("1"), t) + f("i.toString()", asciiString("1"), t) + f("i.valueOf()", intToValue(1), t) + }) + + t.Run("*Int", func(t *testing.T) { + vm.Set("i", &i) + f("+i", intToValue(1), t) + f("`${i}`", asciiString("1"), t) + f("i.toString()", asciiString("1"), t) + f("i.valueOf()", intToValue(1), t) + }) + + type Uint uint + var ui Uint = 1 + + t.Run("Uint", func(t *testing.T) { + vm.Set("ui", ui) + f("+ui", intToValue(1), t) + f("`${ui}`", asciiString("1"), t) + f("ui.toString()", asciiString("1"), t) + f("ui.valueOf()", intToValue(1), t) + }) + + t.Run("*Uint", func(t *testing.T) { + vm.Set("ui", &i) + f("+ui", intToValue(1), t) + f("`${ui}`", asciiString("1"), t) + f("ui.toString()", asciiString("1"), t) + f("ui.valueOf()", intToValue(1), t) + }) + + type Float float64 + var fl Float = 1.1 + + t.Run("Float", func(t *testing.T) { + vm.Set("fl", fl) + f("+fl", floatToValue(1.1), t) + f("`${fl}`", asciiString("1.1"), t) + f("fl.toString()", asciiString("1.1"), t) + f("fl.valueOf()", floatToValue(1.1), t) + }) + + t.Run("*Float", func(t *testing.T) { + vm.Set("fl", &fl) + f("+fl", floatToValue(1.1), t) + f("`${fl}`", asciiString("1.1"), t) + f("fl.toString()", asciiString("1.1"), t) + f("fl.valueOf()", floatToValue(1.1), t) + }) + + fl = Float(math.Inf(1)) + t.Run("FloatInf", func(t *testing.T) { + vm.Set("fl", fl) + f("+fl", _positiveInf, t) + f("fl.toString()", asciiString("Infinity"), t) + }) + + type Empty struct{} + + var e Empty + t.Run("Empty", func(t *testing.T) { + vm.Set("e", &e) + f("+e", _NaN, t) + f("`${e}`", asciiString("[object Object]"), t) + f("e.toString()", asciiString("[object Object]"), t) + f("e.valueOf()", vm.ToValue(&e), t) + }) + }) + + t.Run("Stringers", func(t *testing.T) { + var b testBoolS = true + t.Run("Bool", func(t *testing.T) { + vm.Set("b", b) + f("`${b}`", asciiString("B"), t) + f("b.toString()", asciiString("B"), t) + f("b.valueOf()", valueTrue, t) + f("+b", intToValue(1), t) + }) + + t.Run("*Bool", func(t *testing.T) { + vm.Set("b", &b) + f("`${b}`", asciiString("B"), t) + f("b.toString()", asciiString("B"), t) + f("b.valueOf()", valueTrue, t) + f("+b", intToValue(1), t) + }) + + var i testIntS = 1 + t.Run("Int", func(t *testing.T) { + vm.Set("i", i) + f("`${i}`", asciiString("I"), t) + f("i.toString()", asciiString("I"), t) + f("i.valueOf()", intToValue(1), t) + f("+i", intToValue(1), t) + }) + + t.Run("*Int", func(t *testing.T) { + vm.Set("i", &i) + f("`${i}`", asciiString("I"), t) + f("i.toString()", asciiString("I"), t) + f("i.valueOf()", intToValue(1), t) + f("+i", intToValue(1), t) + }) + + var s testStringS + t.Run("String", func(t *testing.T) { + vm.Set("s", s) + f("`${s}`", asciiString("S"), t) + f("s.toString()", asciiString("S"), t) + f("s.valueOf()", asciiString("S"), t) + f("+s", _NaN, t) + }) + + t.Run("*String", func(t *testing.T) { + vm.Set("s", &s) + f("`${s}`", asciiString("S"), t) + f("s.toString()", asciiString("S"), t) + f("s.valueOf()", asciiString("S"), t) + f("+s", _NaN, t) + }) + }) +} + +type testGoReflectFuncRt struct { +} + +func (*testGoReflectFuncRt) M(call FunctionCall, r *Runtime) Value { + if r == nil { + panic(typeError("Runtime is nil")) + } + return call.Argument(0) +} + +func (*testGoReflectFuncRt) C(call ConstructorCall, r *Runtime) *Object { + if r == nil { + panic(typeError("Runtime is nil in constructor")) + } + call.This.Set("r", call.Argument(0)) + return nil +} + +func TestGoReflectFuncWithRuntime(t *testing.T) { + vm := New() + var s testGoReflectFuncRt + vm.Set("s", &s) + res, err := vm.RunString("s.M(true)") + if err != nil { + t.Fatal(err) + } + if res != valueTrue { + t.Fatal(res) + } + + res, err = vm.RunString("new s.C(true).r") + if err != nil { + t.Fatal(err) + } + if res != valueTrue { + t.Fatal(res) + } +} + +func TestGoReflectDefaultToString(t *testing.T) { + var s testStringS + vm := New() + v := vm.ToValue(s).(*Object) + v.Delete("toString") + v.Delete("valueOf") + vm.Set("s", v) + _, err := vm.RunString(` + class S { + toString() { + return "X"; + } + } + + if (s.toString() !== "S") { + throw new Error(s.toString()); + } + if (("" + s) !== "S") { + throw new Error("" + s); + } + + Object.setPrototypeOf(s, S.prototype); + if (s.toString() !== "X") { + throw new Error(s.toString()); + } + if (("" + s) !== "X") { + throw new Error("" + s); + } + `) + if err != nil { + t.Fatal(err) + } +} diff --git a/pkg/xscript/engine/object_goslice.go b/pkg/xscript/engine/object_goslice.go new file mode 100644 index 0000000..5f81607 --- /dev/null +++ b/pkg/xscript/engine/object_goslice.go @@ -0,0 +1,343 @@ +package engine + +import ( + "math" + "math/bits" + "reflect" + "strconv" + + "pandax/pkg/xscript/engine/unistring" +) + +type objectGoSlice struct { + baseObject + data *[]any + lengthProp valueProperty + origIsPtr bool +} + +func (r *Runtime) newObjectGoSlice(data *[]any, isPtr bool) *objectGoSlice { + obj := &Object{runtime: r} + a := &objectGoSlice{ + baseObject: baseObject{ + val: obj, + }, + data: data, + origIsPtr: isPtr, + } + obj.self = a + a.init() + + return a +} + +func (o *objectGoSlice) init() { + o.baseObject.init() + o.class = classArray + o.prototype = o.val.runtime.getArrayPrototype() + o.lengthProp.writable = true + o.extensible = true + o.baseObject._put("length", &o.lengthProp) +} + +func (o *objectGoSlice) updateLen() { + o.lengthProp.value = intToValue(int64(len(*o.data))) +} + +func (o *objectGoSlice) _getIdx(idx int) Value { + return o.val.runtime.ToValue((*o.data)[idx]) +} + +func (o *objectGoSlice) getStr(name unistring.String, receiver Value) Value { + var ownProp Value + if idx := strToGoIdx(name); idx >= 0 && idx < len(*o.data) { + ownProp = o._getIdx(idx) + } else if name == "length" { + o.updateLen() + ownProp = &o.lengthProp + } + + return o.getStrWithOwnProp(ownProp, name, receiver) +} + +func (o *objectGoSlice) getIdx(idx valueInt, receiver Value) Value { + if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) { + return o._getIdx(int(idx)) + } + if o.prototype != nil { + if receiver == nil { + return o.prototype.self.getIdx(idx, o.val) + } + return o.prototype.self.getIdx(idx, receiver) + } + return nil +} + +func (o *objectGoSlice) getOwnPropStr(name unistring.String) Value { + if idx := strToGoIdx(name); idx >= 0 { + if idx < len(*o.data) { + return &valueProperty{ + value: o._getIdx(idx), + writable: true, + enumerable: true, + } + } + return nil + } + if name == "length" { + o.updateLen() + return &o.lengthProp + } + return nil +} + +func (o *objectGoSlice) getOwnPropIdx(idx valueInt) Value { + if idx := int64(idx); idx >= 0 && idx < int64(len(*o.data)) { + return &valueProperty{ + value: o._getIdx(int(idx)), + writable: true, + enumerable: true, + } + } + return nil +} + +func (o *objectGoSlice) grow(size int) { + oldcap := cap(*o.data) + if oldcap < size { + n := make([]any, size, growCap(size, len(*o.data), oldcap)) + copy(n, *o.data) + *o.data = n + } else { + tail := (*o.data)[len(*o.data):size] + for k := range tail { + tail[k] = nil + } + *o.data = (*o.data)[:size] + } +} + +func (o *objectGoSlice) shrink(size int) { + tail := (*o.data)[size:] + for k := range tail { + tail[k] = nil + } + *o.data = (*o.data)[:size] +} + +func (o *objectGoSlice) putIdx(idx int, v Value, throw bool) { + if idx >= len(*o.data) { + o.grow(idx + 1) + } + (*o.data)[idx] = v.Export() +} + +func (o *objectGoSlice) putLength(v uint32, throw bool) bool { + if bits.UintSize == 32 && v > math.MaxInt32 { + panic(rangeError("Integer value overflows 32-bit int")) + } + newLen := int(v) + curLen := len(*o.data) + if newLen > curLen { + o.grow(newLen) + } else if newLen < curLen { + o.shrink(newLen) + } + return true +} + +func (o *objectGoSlice) setOwnIdx(idx valueInt, val Value, throw bool) bool { + if i := toIntStrict(int64(idx)); i >= 0 { + if i >= len(*o.data) { + if res, ok := o._setForeignIdx(idx, nil, val, o.val, throw); ok { + return res + } + } + o.putIdx(i, val, throw) + } else { + name := idx.string() + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { + o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name) + return false + } else { + return res + } + } + return true +} + +func (o *objectGoSlice) setOwnStr(name unistring.String, val Value, throw bool) bool { + if idx := strToGoIdx(name); idx >= 0 { + if idx >= len(*o.data) { + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); ok { + return res + } + } + o.putIdx(idx, val, throw) + } else { + if name == "length" { + return o.putLength(o.val.runtime.toLengthUint32(val), throw) + } + if res, ok := o._setForeignStr(name, nil, val, o.val, throw); !ok { + o.val.runtime.typeErrorResult(throw, "Can't set property '%s' on Go slice", name) + return false + } else { + return res + } + } + return true +} + +func (o *objectGoSlice) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignIdx(idx, trueValIfPresent(o.hasOwnPropertyIdx(idx)), val, receiver, throw) +} + +func (o *objectGoSlice) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return o._setForeignStr(name, trueValIfPresent(o.hasOwnPropertyStr(name)), val, receiver, throw) +} + +func (o *objectGoSlice) hasOwnPropertyIdx(idx valueInt) bool { + if idx := int64(idx); idx >= 0 { + return idx < int64(len(*o.data)) + } + return false +} + +func (o *objectGoSlice) hasOwnPropertyStr(name unistring.String) bool { + if idx := strToIdx64(name); idx >= 0 { + return idx < int64(len(*o.data)) + } + return name == "length" +} + +func (o *objectGoSlice) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + if i := toIntStrict(int64(idx)); i >= 0 { + if !o.val.runtime.checkHostObjectPropertyDescr(idx.string(), descr, throw) { + return false + } + val := descr.Value + if val == nil { + val = _undefined + } + o.putIdx(i, val, throw) + return true + } + o.val.runtime.typeErrorResult(throw, "Cannot define property '%d' on a Go slice", idx) + return false +} + +func (o *objectGoSlice) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if idx := strToGoIdx(name); idx >= 0 { + if !o.val.runtime.checkHostObjectPropertyDescr(name, descr, throw) { + return false + } + val := descr.Value + if val == nil { + val = _undefined + } + o.putIdx(idx, val, throw) + return true + } + if name == "length" { + return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw) + } + o.val.runtime.typeErrorResult(throw, "Cannot define property '%s' on a Go slice", name) + return false +} + +func (o *objectGoSlice) _deleteIdx(idx int64) { + if idx < int64(len(*o.data)) { + (*o.data)[idx] = nil + } +} + +func (o *objectGoSlice) deleteStr(name unistring.String, throw bool) bool { + if idx := strToIdx64(name); idx >= 0 { + o._deleteIdx(idx) + return true + } + return o.baseObject.deleteStr(name, throw) +} + +func (o *objectGoSlice) deleteIdx(i valueInt, throw bool) bool { + idx := int64(i) + if idx >= 0 { + o._deleteIdx(idx) + } + return true +} + +type goslicePropIter struct { + o *objectGoSlice + idx, limit int +} + +func (i *goslicePropIter) next() (propIterItem, iterNextFunc) { + if i.idx < i.limit && i.idx < len(*i.o.data) { + name := strconv.Itoa(i.idx) + i.idx++ + return propIterItem{name: newStringValue(name), enumerable: _ENUM_TRUE}, i.next + } + + return propIterItem{}, nil +} + +func (o *objectGoSlice) iterateStringKeys() iterNextFunc { + return (&goslicePropIter{ + o: o, + limit: len(*o.data), + }).next +} + +func (o *objectGoSlice) stringKeys(_ bool, accum []Value) []Value { + for i := range *o.data { + accum = append(accum, asciiString(strconv.Itoa(i))) + } + + return accum +} + +func (o *objectGoSlice) export(*objectExportCtx) any { + if o.origIsPtr { + return o.data + } + return *o.data +} + +func (o *objectGoSlice) exportType() reflect.Type { + if o.origIsPtr { + return reflectTypeArrayPtr + } + return reflectTypeArray +} + +func (o *objectGoSlice) equal(other objectImpl) bool { + if other, ok := other.(*objectGoSlice); ok { + return o.data == other.data + } + return false +} + +func (o *objectGoSlice) esValue() Value { + return o.val +} + +func (o *objectGoSlice) reflectValue() reflect.Value { + return reflect.ValueOf(o.data).Elem() +} + +func (o *objectGoSlice) setReflectValue(value reflect.Value) { + o.data = value.Addr().Interface().(*[]any) +} + +func (o *objectGoSlice) sortLen() int { + return len(*o.data) +} + +func (o *objectGoSlice) sortGet(i int) Value { + return o.getIdx(valueInt(i), nil) +} + +func (o *objectGoSlice) swap(i int, j int) { + (*o.data)[i], (*o.data)[j] = (*o.data)[j], (*o.data)[i] +} diff --git a/pkg/xscript/engine/object_goslice_reflect.go b/pkg/xscript/engine/object_goslice_reflect.go new file mode 100644 index 0000000..2183b1c --- /dev/null +++ b/pkg/xscript/engine/object_goslice_reflect.go @@ -0,0 +1,95 @@ +package engine + +import ( + "math" + "math/bits" + "reflect" + "sync" + + "pandax/pkg/xscript/engine/unistring" +) + +type objectGoSliceReflect struct { + objectGoArrayReflect + + mu sync.Mutex +} + +func (o *objectGoSliceReflect) init() { + o.objectGoArrayReflect._init() + o.lengthProp.writable = true + o.putIdx = o._putIdx +} + +func (o *objectGoSliceReflect) _putIdx(idx int, v Value, throw bool) bool { + o.mu.Lock() + defer o.mu.Unlock() + + if idx >= o.fieldsValue.Len() { + o.grow(idx + 1) + } + return o.objectGoArrayReflect._putIdx(idx, v, throw) +} + +func (o *objectGoSliceReflect) grow(size int) { + oldcap := o.fieldsValue.Cap() + if oldcap < size { + n := reflect.MakeSlice(o.fieldsValue.Type(), size, growCap(size, o.fieldsValue.Len(), oldcap)) + reflect.Copy(n, o.fieldsValue) + o.fieldsValue.Set(n) + l := len(o.valueCache) + if l > size { + l = size + } + for i, w := range o.valueCache[:l] { + if w != nil { + w.setReflectValue(o.fieldsValue.Index(i)) + } + } + } else { + tail := o.fieldsValue.Slice(o.fieldsValue.Len(), size) + zero := reflect.Zero(o.fieldsValue.Type().Elem()) + for i := 0; i < tail.Len(); i++ { + tail.Index(i).Set(zero) + } + o.fieldsValue.SetLen(size) + } +} + +func (o *objectGoSliceReflect) shrink(size int) { + o.valueCache.shrink(size) + tail := o.fieldsValue.Slice(size, o.fieldsValue.Len()) + zero := reflect.Zero(o.fieldsValue.Type().Elem()) + for i := 0; i < tail.Len(); i++ { + tail.Index(i).Set(zero) + } + o.fieldsValue.SetLen(size) +} + +func (o *objectGoSliceReflect) putLength(v uint32, throw bool) bool { + if bits.UintSize == 32 && v > math.MaxInt32 { + panic(rangeError("Integer value overflows 32-bit int")) + } + newLen := int(v) + curLen := o.fieldsValue.Len() + if newLen > curLen { + o.grow(newLen) + } else if newLen < curLen { + o.shrink(newLen) + } + return true +} + +func (o *objectGoSliceReflect) setOwnStr(name unistring.String, val Value, throw bool) bool { + if name == "length" { + return o.putLength(o.val.runtime.toLengthUint32(val), throw) + } + return o.objectGoArrayReflect.setOwnStr(name, val, throw) +} + +func (o *objectGoSliceReflect) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if name == "length" { + return o.val.runtime.defineArrayLength(&o.lengthProp, descr, o.putLength, throw) + } + return o.objectGoArrayReflect.defineOwnPropertyStr(name, descr, throw) +} diff --git a/pkg/xscript/engine/object_goslice_reflect_test.go b/pkg/xscript/engine/object_goslice_reflect_test.go new file mode 100644 index 0000000..e8e4c4e --- /dev/null +++ b/pkg/xscript/engine/object_goslice_reflect_test.go @@ -0,0 +1,520 @@ +package engine + +import ( + "reflect" + "testing" +) + +func TestGoSliceReflectBasic(t *testing.T) { + const SCRIPT = ` + var sum = 0; + for (var i = 0; i < a.length; i++) { + sum += a[i]; + } + sum; + ` + r := New() + r.Set("a", []int{1, 2, 3, 4}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if i := v.ToInteger(); i != 10 { + t.Fatalf("Expected 10, got: %d", i) + } + +} + +func TestGoSliceReflectIn(t *testing.T) { + const SCRIPT = ` + var idx = ""; + for (var i in a) { + idx += i; + } + idx; + ` + r := New() + r.Set("a", []int{1, 2, 3, 4}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if i := v.String(); i != "0123" { + t.Fatalf("Expected '0123', got: '%s'", i) + } +} + +func TestGoSliceReflectSet(t *testing.T) { + const SCRIPT = ` + a[0] = 33; + a[1] = 333; + a[2] = "42"; + a[3] = {}; + a[4] = 0; + ` + r := New() + a := []int8{1, 2, 3, 4} + r.Set("a", a) + _, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if a[0] != 33 { + t.Fatalf("a[0] = %d, expected 33", a[0]) + } + if a[1] != 77 { + t.Fatalf("a[1] = %d, expected 77", a[1]) + } + if a[2] != 42 { + t.Fatalf("a[2] = %d, expected 42", a[2]) + } + if a[3] != 0 { + t.Fatalf("a[3] = %d, expected 0", a[3]) + } +} + +func TestGoSliceReflectPush(t *testing.T) { + + r := New() + + t.Run("Can push to array by array ptr", func(t *testing.T) { + a := []int8{1} + r.Set("a", &a) + _, err := r.RunString(`a.push (10)`) + if err != nil { + t.Fatal(err) + } + + if a[1] != 10 { + t.Fatalf("a[1] = %d, expected 10", a[1]) + } + }) + + t.Run("Can push to array by struct ptr", func(t *testing.T) { + type testStr struct { + A []int + } + a := testStr{ + A: []int{2}, + } + + r.Set("a", &a) + _, err := r.RunString(`a.A.push (10)`) + if err != nil { + t.Fatal(err) + } + + if a.A[1] != 10 { + t.Fatalf("a[1] = %v, expected 10", a) + } + }) + +} + +func TestGoSliceReflectStructField(t *testing.T) { + vm := New() + var s struct { + A []int + B *[]int + } + vm.Set("s", &s) + _, err := vm.RunString(` + 'use strict'; + s.A.push(1); + if (s.B !== null) { + throw new Error("s.B is not null: " + s.B); + } + s.B = [2]; + `) + if err != nil { + t.Fatal(err) + } + if len(s.A) != 1 || s.A[0] != 1 { + t.Fatalf("s.A: %v", s.A) + } + if len(*s.B) != 1 || (*s.B)[0] != 2 { + t.Fatalf("s.B: %v", *s.B) + } +} + +func TestGoSliceReflectExportToStructField(t *testing.T) { + vm := New() + v, err := vm.RunString(`({A: [1], B: [2]})`) + if err != nil { + t.Fatal(err) + } + var s struct { + A []int + B *[]int + } + err = vm.ExportTo(v, &s) + if err != nil { + t.Fatal(err) + } + if len(s.A) != 1 || s.A[0] != 1 { + t.Fatalf("s.A: %v", s.A) + } + if len(*s.B) != 1 || (*s.B)[0] != 2 { + t.Fatalf("s.B: %v", *s.B) + } +} + +func TestGoSliceReflectProtoMethod(t *testing.T) { + const SCRIPT = ` + a.join(",") + ` + + r := New() + a := []int8{1, 2, 3, 4} + r.Set("a", a) + ret, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if s := ret.String(); s != "1,2,3,4" { + t.Fatalf("Unexpected result: '%s'", s) + } +} + +type gosliceReflect_withMethods []any + +func (s gosliceReflect_withMethods) Method() bool { + return true +} + +func TestGoSliceReflectMethod(t *testing.T) { + const SCRIPT = ` + typeof a === "object" && a[0] === 42 && a.Method() === true; + ` + + vm := New() + a := make(gosliceReflect_withMethods, 1) + a[0] = 42 + vm.Set("a", a) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(valueTrue) { + t.Fatalf("Expected true, got %v", v) + } + +} + +func TestGoSliceReflectGetStr(t *testing.T) { + r := New() + v := r.ToValue([]string{"test"}) + if o, ok := v.(*Object); ok { + if e := o.Get("0").Export(); e != "test" { + t.Fatalf("Unexpected o.Get(\"0\"): %v", e) + } + } +} + +func TestGoSliceReflectNilObjectIfaceVal(t *testing.T) { + r := New() + a := []Value{(*Object)(nil)} + r.Set("a", a) + ret, err := r.RunString(` + ""+a[0]; + `) + if err != nil { + t.Fatal(err) + } + if !asciiString("null").SameAs(ret) { + t.Fatalf("ret: %v", ret) + } +} + +func TestGoSliceReflectSetLength(t *testing.T) { + r := New() + a := []int{1, 2, 3, 4} + b := []testing.TB{&testing.T{}, &testing.T{}, (*testing.T)(nil)} + r.Set("a", &a) + r.Set("b", &b) + _, err := r.RunString(` + 'use strict'; + a.length = 3; + if (a.length !== 3) { + throw new Error("length="+a.length); + } + if (a[3] !== undefined) { + throw new Error("a[3]="+a[3]); + } + a.length = 5; + if (a.length !== 5) { + throw new Error("a.length="+a.length); + } + if (a[3] !== 0) { + throw new Error("a[3]="+a[3]); + } + if (a[4] !== 0) { + throw new Error("a[4]="+a[4]); + } + + b.length = 3; + if (b.length !== 3) { + throw new Error("b.length="+b.length); + } + if (b[3] !== undefined) { + throw new Error("b[3]="+b[3]); + } + b.length = 5; + if (b.length !== 5) { + throw new Error("length="+b.length); + } + if (b[3] !== null) { + throw new Error("b[3]="+b[3]); + } + if (b[4] !== null) { + throw new Error("b[4]="+b[4]); + } + if (b[2] !== null) { + throw new Error("b[2]="+b[2]); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoSliceReflectProto(t *testing.T) { + r := New() + a := []*Object{{}, nil, {}} + r.Set("a", &a) + r.testScriptWithTestLib(` + var proto = [,2,,4]; + Object.setPrototypeOf(a, proto); + assert.sameValue(a[1], null, "a[1]"); + assert.sameValue(a[3], 4, "a[3]"); + var desc = Object.getOwnPropertyDescriptor(a, "1"); + assert.sameValue(desc.value, null, "desc.value"); + assert(desc.writable, "writable"); + assert(desc.enumerable, "enumerable"); + assert(!desc.configurable, "configurable"); + var v5; + Object.defineProperty(proto, "5", { + set: function(v) { + v5 = v; + } + }); + a[5] = "test"; + assert.sameValue(v5, "test", "v5"); + `, _undefined, t) +} + +func TestGoSliceReflectProtoProto(t *testing.T) { + r := New() + a := []*Object{{}, nil, {}} + proto := []*Object{{}, {}, {}, {}} + r.Set("a", &a) + r.Set("proto", proto) + _, err := r.RunString(` + "use strict"; + var protoproto = {}; + Object.defineProperty(protoproto, "3", { + value: 42 + }); + Object.setPrototypeOf(proto, protoproto); + Object.setPrototypeOf(a, proto); + if (a.hasOwnProperty("3")) { + throw new Error("a.hasOwnProperty(\"3\")"); + } + if (a[3] !== null) { + throw new Error("a[3]="+a[3]); + } + a[3] = null; + if (a[3] !== null) { + throw new Error("a[3]=" + a[3]); + } + `) + if err != nil { + t.Fatal(err) + } + +} + +func TestGoSliceReflectDelete(t *testing.T) { + r := New() + a := []*Object{{}, nil, {}} + r.Set("a", a) + v, err := r.RunString(` + delete a[0] && delete a[1] && delete a[3]; + `) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatalf("not true: %v", v) + } +} + +func TestGoSliceReflectPop(t *testing.T) { + r := New() + a := []string{"1", "", "3"} + r.Set("a", &a) + v, err := r.RunString(` + a.pop() + `) + if err != nil { + t.Fatal(err) + } + if !v.SameAs(asciiString("3")) { + t.Fatal(v) + } +} + +func TestGoSliceReflectPopNoPtr(t *testing.T) { + r := New() + a := []string{"1", "", "3"} + r.Set("a", a) + v, err := r.RunString(` + a.pop() + `) + if err != nil { + t.Fatal(err) + } + if !v.SameAs(asciiString("3")) { + t.Fatal(v) + } +} + +func TestGoSliceReflectLengthProperty(t *testing.T) { + vm := New() + vm.Set("s", []int{2, 3, 4}) + _, err := vm.RunString(` + if (!s.hasOwnProperty("length")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(s, "length"); + if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +} + +type testCustomSliceWithMethods []int + +func (a testCustomSliceWithMethods) Method() bool { + return true +} + +func TestGoSliceReflectMethods(t *testing.T) { + vm := New() + vm.Set("s", testCustomSliceWithMethods{1, 2, 3}) + _, err := vm.RunString(` + if (!s.hasOwnProperty("Method")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(s, "Method"); + if (desc.value() !== true || desc.writable || !desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoSliceReflectExportAfterGrow(t *testing.T) { + vm := New() + vm.Set("a", []int{1}) + v, err := vm.RunString(` + a.push(2); + a; + `) + if err != nil { + t.Fatal(err) + } + exp := v.Export() + if a, ok := exp.([]int); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 2 { + t.Fatal(a) + } + } else { + t.Fatalf("Wrong type: %T", exp) + } +} + +func TestGoSliceReflectSort(t *testing.T) { + vm := New() + type Thing struct{ Name string } + vm.Set("v", []*Thing{ + {Name: "log"}, + {Name: "etc"}, + {Name: "test"}, + {Name: "bin"}, + }) + ret, err := vm.RunString(` +//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name); + const tmp = v[0]; + v[0] = v[1]; + v[1] = tmp; + v[0].Name + v[1].Name; +`) + if err != nil { + panic(err) + } + t.Log(ret.Export()) +} + +func TestGoSliceReflect111(t *testing.T) { + vm := New() + vm.Set("v", []int32{ + 1, 2, + }) + ret, err := vm.RunString(` +//v.sort((a, b) => a.Name.localeCompare(b.Name)).map((x) => x.Name); + const tmp = v[0]; + v[0] = v[1]; + v[1] = tmp; + "" + v[0] + v[1]; +`) + if err != nil { + panic(err) + } + t.Log(ret.Export()) + a := []int{1, 2} + a0 := reflect.ValueOf(a).Index(0) + a0.Set(reflect.ValueOf(0)) + t.Log(a[0]) +} + +func TestGoSliceReflectExternalLenUpdate(t *testing.T) { + data := &[]int{1} + + vm := New() + vm.Set("data", data) + vm.Set("append", func(a *[]int, v int) { + if a != data { + panic(vm.NewTypeError("a != data")) + } + *a = append(*a, v) + }) + + vm.testScriptWithTestLib(` + assert.sameValue(data.length, 1); + + // modify with js + data.push(1); + assert.sameValue(data.length, 2); + + // modify with go + append(data, 2); + assert.sameValue(data.length, 3); + `, _undefined, t) +} + +func BenchmarkGoSliceReflectSet(b *testing.B) { + vm := New() + a := vm.ToValue([]int{1}).(*Object) + b.ResetTimer() + v := intToValue(0) + for i := 0; i < b.N; i++ { + a.Set("0", v) + } +} diff --git a/pkg/xscript/engine/object_goslice_test.go b/pkg/xscript/engine/object_goslice_test.go new file mode 100644 index 0000000..0315775 --- /dev/null +++ b/pkg/xscript/engine/object_goslice_test.go @@ -0,0 +1,298 @@ +package engine + +import ( + "testing" +) + +func TestGoSliceBasic(t *testing.T) { + const SCRIPT = ` + var sum = 0; + for (var i = 0; i < a.length; i++) { + sum += a[i]; + } + sum; + ` + r := New() + r.Set("a", []any{1, 2, 3, 4}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if i := v.ToInteger(); i != 10 { + t.Fatalf("Expected 10, got: %d", i) + } +} + +func TestGoSliceIn(t *testing.T) { + const SCRIPT = ` + var idx = ""; + for (var i in a) { + idx += i; + } + idx; + ` + r := New() + r.Set("a", []any{1, 2, 3, 4}) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if i := v.String(); i != "0123" { + t.Fatalf("Expected '0123', got: '%s'", i) + } +} + +func TestGoSliceExpand(t *testing.T) { + const SCRIPT = ` + var l = a.length; + for (var i = 0; i < l; i++) { + a[l + i] = a[i] * 2; + } + + var sum = 0; + for (var i = 0; i < a.length; i++) { + sum += a[i]; + } + sum; + ` + r := New() + a := []any{int64(1), int64(2), int64(3), int64(4)} + r.Set("a", &a) + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + sum := int64(0) + for _, v := range a { + sum += v.(int64) + } + if i := v.ToInteger(); i != sum { + t.Fatalf("Expected %d, got: %d", sum, i) + } +} + +func TestGoSliceProtoMethod(t *testing.T) { + const SCRIPT = ` + a.join(",") + ` + + r := New() + a := []any{1, 2, 3, 4} + r.Set("a", a) + ret, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if s := ret.String(); s != "1,2,3,4" { + t.Fatalf("Unexpected result: '%s'", s) + } +} + +func TestGoSliceSetLength(t *testing.T) { + r := New() + a := []any{1, 2, 3, 4} + r.Set("a", &a) + _, err := r.RunString(` + 'use strict'; + a.length = 3; + if (a.length !== 3) { + throw new Error("length="+a.length); + } + if (a[3] !== undefined) { + throw new Error("a[3](1)="+a[3]); + } + a.length = 5; + if (a.length !== 5) { + throw new Error("length="+a.length); + } + if (a[3] !== null) { + throw new Error("a[3](2)="+a[3]); + } + if (a[4] !== null) { + throw new Error("a[4]="+a[4]); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoSliceProto(t *testing.T) { + r := New() + a := []any{1, nil, 3} + r.Set("a", &a) + r.testScriptWithTestLib(` + var proto = [,2,,4]; + Object.setPrototypeOf(a, proto); + assert.sameValue(a[1], null, "a[1]"); + assert.sameValue(a[3], 4, "a[3]"); + var desc = Object.getOwnPropertyDescriptor(a, "1"); + assert.sameValue(desc.value, null, "desc.value"); + assert(desc.writable, "writable"); + assert(desc.enumerable, "enumerable"); + assert(!desc.configurable, "configurable"); + var v5; + Object.defineProperty(proto, "5", { + set: function(v) { + v5 = v; + } + }); + a[5] = "test"; + assert.sameValue(v5, "test", "v5"); + `, _undefined, t) +} + +func TestGoSliceProtoProto(t *testing.T) { + r := New() + a := []any{1, nil, 3} + proto := []any{1, 2, 3, 4} + r.Set("a", &a) + r.Set("proto", proto) + _, err := r.RunString(` + "use strict"; + var protoproto = Object.create(null); + Object.defineProperty(protoproto, "3", { + value: 42 + }); + Object.setPrototypeOf(proto, protoproto); + Object.setPrototypeOf(a, proto); + a[3] = 11; + if (a[3] !== 11) { + throw new Error("a[3]=" + a[3]); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoSliceDelete(t *testing.T) { + r := New() + a := []any{1, nil, 3} + r.Set("a", a) + v, err := r.RunString(` + delete a[0] && delete a[1] && delete a[3]; + `) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatalf("not true: %v", v) + } +} + +func TestGoSlicePop(t *testing.T) { + r := New() + a := []any{1, nil, 3} + r.Set("a", &a) + v, err := r.RunString(` + a.pop() + `) + if err != nil { + t.Fatal(err) + } + if !v.SameAs(intToValue(3)) { + t.Fatal(v) + } +} + +func TestGoSlicePopNoPtr(t *testing.T) { + r := New() + a := []any{1, nil, 3} + r.Set("a", a) + v, err := r.RunString(` + a.pop() + `) + if err != nil { + t.Fatal(err) + } + if !v.SameAs(intToValue(3)) { + t.Fatal(v) + } +} + +func TestGoSliceShift(t *testing.T) { + r := New() + a := []any{1, nil, 3} + r.Set("a", &a) + v, err := r.RunString(` + a.shift() + `) + if err != nil { + t.Fatal(err) + } + if !v.SameAs(intToValue(1)) { + t.Fatal(v) + } +} + +func TestGoSliceLengthProperty(t *testing.T) { + vm := New() + vm.Set("s", []any{2, 3, 4}) + _, err := vm.RunString(` + if (!s.hasOwnProperty("length")) { + throw new Error("hasOwnProperty() returned false"); + } + let desc = Object.getOwnPropertyDescriptor(s, "length"); + if (desc.value !== 3 || !desc.writable || desc.enumerable || desc.configurable) { + throw new Error("incorrect property descriptor: " + JSON.stringify(desc)); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestGoSliceSort(t *testing.T) { + vm := New() + s := []any{4, 2, 3} + vm.Set("s", &s) + _, err := vm.RunString(`s.sort()`) + if err != nil { + t.Fatal(err) + } + if len(s) != 3 { + t.Fatalf("len: %d", len(s)) + } + if s[0] != 2 || s[1] != 3 || s[2] != 4 { + t.Fatalf("val: %v", s) + } +} + +func TestGoSliceToString(t *testing.T) { + vm := New() + s := []any{4, 2, 3} + vm.Set("s", &s) + res, err := vm.RunString("`${s}`") + if err != nil { + t.Fatal(err) + } + if exp := res.Export(); exp != "4,2,3" { + t.Fatal(exp) + } +} + +func TestGoSliceExternalLenUpdate(t *testing.T) { + data := &[]any{1} + + vm := New() + vm.Set("data", data) + vm.Set("append", func(a *[]any, v int) { + if a != data { + panic(vm.NewTypeError("a != data")) + } + *a = append(*a, v) + }) + + vm.testScriptWithTestLib(` + assert.sameValue(data.length, 1); + + // modify with js + data.push(1); + assert.sameValue(data.length, 2); + + // modify with go + append(data, 2); + assert.sameValue(data.length, 3); + `, _undefined, t) +} diff --git a/pkg/xscript/engine/object_template.go b/pkg/xscript/engine/object_template.go new file mode 100644 index 0000000..47cea4a --- /dev/null +++ b/pkg/xscript/engine/object_template.go @@ -0,0 +1,470 @@ +package engine + +import ( + "fmt" + "math" + "reflect" + "sort" + + "pandax/pkg/xscript/engine/unistring" +) + +type templatePropFactory func(*Runtime) Value + +type objectTemplate struct { + propNames []unistring.String + props map[unistring.String]templatePropFactory + + symProps map[*Symbol]templatePropFactory + symPropNames []*Symbol + + protoFactory func(*Runtime) *Object +} + +type templatedObject struct { + baseObject + tmpl *objectTemplate + + protoMaterialised bool +} + +type templatedFuncObject struct { + templatedObject + + f func(FunctionCall) Value + construct func(args []Value, newTarget *Object) *Object +} + +// This type exists because Array.prototype is supposed to be an array itself and I could not find +// a different way of implementing it without either introducing another layer of interfaces or hoisting +// the templates to baseObject both of which would have had a negative effect on the performance. +// The implementation is as simple as possible and is not optimised in any way, but I very much doubt anybody +// uses Array.prototype as an actual array. +type templatedArrayObject struct { + templatedObject +} + +func newObjectTemplate() *objectTemplate { + return &objectTemplate{ + props: make(map[unistring.String]templatePropFactory), + } +} + +func (t *objectTemplate) putStr(name unistring.String, f templatePropFactory) { + t.props[name] = f + t.propNames = append(t.propNames, name) +} + +func (t *objectTemplate) putSym(s *Symbol, f templatePropFactory) { + if t.symProps == nil { + t.symProps = make(map[*Symbol]templatePropFactory) + } + t.symProps[s] = f + t.symPropNames = append(t.symPropNames, s) +} + +func (r *Runtime) newTemplatedObject(tmpl *objectTemplate, obj *Object) *templatedObject { + if obj == nil { + obj = &Object{runtime: r} + } + o := &templatedObject{ + baseObject: baseObject{ + class: classObject, + val: obj, + extensible: true, + }, + tmpl: tmpl, + } + obj.self = o + o.init() + return o +} + +func (o *templatedObject) materialiseProto() { + if !o.protoMaterialised { + if o.tmpl.protoFactory != nil { + o.prototype = o.tmpl.protoFactory(o.val.runtime) + } + o.protoMaterialised = true + } +} + +func (o *templatedObject) getStr(name unistring.String, receiver Value) Value { + ownProp := o.getOwnPropStr(name) + if ownProp == nil { + o.materialiseProto() + } + return o.getStrWithOwnProp(ownProp, name, receiver) +} + +func (o *templatedObject) getSym(s *Symbol, receiver Value) Value { + ownProp := o.getOwnPropSym(s) + if ownProp == nil { + o.materialiseProto() + } + return o.getWithOwnProp(ownProp, s, receiver) +} + +func (o *templatedObject) getOwnPropStr(p unistring.String) Value { + if v, exists := o.values[p]; exists { + return v + } + if f := o.tmpl.props[p]; f != nil { + v := f(o.val.runtime) + o.values[p] = v + return v + } + return nil +} + +func (o *templatedObject) materialiseSymbols() { + if o.symValues == nil { + o.symValues = newOrderedMap(nil) + for _, p := range o.tmpl.symPropNames { + o.symValues.set(p, o.tmpl.symProps[p](o.val.runtime)) + } + } +} + +func (o *templatedObject) getOwnPropSym(s *Symbol) Value { + if o.symValues == nil && o.tmpl.symProps[s] == nil { + return nil + } + o.materialiseSymbols() + return o.baseObject.getOwnPropSym(s) +} + +func (o *templatedObject) materialisePropNames() { + if o.propNames == nil { + o.propNames = append(([]unistring.String)(nil), o.tmpl.propNames...) + } +} + +func (o *templatedObject) setOwnStr(p unistring.String, v Value, throw bool) bool { + existing := o.getOwnPropStr(p) // materialise property (in case it's an accessor) + if existing == nil { + o.materialiseProto() + o.materialisePropNames() + } + return o.baseObject.setOwnStr(p, v, throw) +} + +func (o *templatedObject) setOwnSym(name *Symbol, val Value, throw bool) bool { + o.materialiseSymbols() + o.materialiseProto() + return o.baseObject.setOwnSym(name, val, throw) +} + +func (o *templatedObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + ownProp := o.getOwnPropStr(name) + if ownProp == nil { + o.materialiseProto() + } + return o._setForeignStr(name, ownProp, val, receiver, throw) +} + +func (o *templatedObject) proto() *Object { + o.materialiseProto() + return o.prototype +} + +func (o *templatedObject) setProto(proto *Object, throw bool) bool { + o.protoMaterialised = true + ret := o.baseObject.setProto(proto, throw) + if ret { + o.protoMaterialised = true + } + return ret +} + +func (o *templatedObject) setForeignIdx(name valueInt, val, receiver Value, throw bool) (bool, bool) { + return o.setForeignStr(name.string(), val, receiver, throw) +} + +func (o *templatedObject) setForeignSym(name *Symbol, val, receiver Value, throw bool) (bool, bool) { + o.materialiseProto() + o.materialiseSymbols() + return o.baseObject.setForeignSym(name, val, receiver, throw) +} + +func (o *templatedObject) hasPropertyStr(name unistring.String) bool { + if o.val.self.hasOwnPropertyStr(name) { + return true + } + o.materialiseProto() + if o.prototype != nil { + return o.prototype.self.hasPropertyStr(name) + } + return false +} + +func (o *templatedObject) hasPropertySym(s *Symbol) bool { + if o.hasOwnPropertySym(s) { + return true + } + o.materialiseProto() + if o.prototype != nil { + return o.prototype.self.hasPropertySym(s) + } + return false +} + +func (o *templatedObject) hasOwnPropertyStr(name unistring.String) bool { + if v, exists := o.values[name]; exists { + return v != nil + } + + _, exists := o.tmpl.props[name] + return exists +} + +func (o *templatedObject) hasOwnPropertySym(s *Symbol) bool { + if o.symValues != nil { + return o.symValues.has(s) + } + _, exists := o.tmpl.symProps[s] + return exists +} + +func (o *templatedObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + existingVal := o.getOwnPropStr(name) + if v, ok := o._defineOwnProperty(name, existingVal, descr, throw); ok { + o.values[name] = v + if existingVal == nil { + o.materialisePropNames() + names := copyNamesIfNeeded(o.propNames, 1) + o.propNames = append(names, name) + } + return true + } + return false +} + +func (o *templatedObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool { + o.materialiseSymbols() + return o.baseObject.defineOwnPropertySym(s, descr, throw) +} + +func (o *templatedObject) deleteStr(name unistring.String, throw bool) bool { + if val := o.getOwnPropStr(name); val != nil { + if !o.checkDelete(name, val, throw) { + return false + } + o.materialisePropNames() + o._delete(name) + if _, exists := o.tmpl.props[name]; exists { + o.values[name] = nil // white hole + } + } + return true +} + +func (o *templatedObject) deleteSym(s *Symbol, throw bool) bool { + o.materialiseSymbols() + return o.baseObject.deleteSym(s, throw) +} + +func (o *templatedObject) materialiseProps() { + for name, f := range o.tmpl.props { + if _, exists := o.values[name]; !exists { + o.values[name] = f(o.val.runtime) + } + } + o.materialisePropNames() +} + +func (o *templatedObject) iterateStringKeys() iterNextFunc { + o.materialiseProps() + return o.baseObject.iterateStringKeys() +} + +func (o *templatedObject) iterateSymbols() iterNextFunc { + o.materialiseSymbols() + return o.baseObject.iterateSymbols() +} + +func (o *templatedObject) stringKeys(all bool, keys []Value) []Value { + if all { + o.materialisePropNames() + } else { + o.materialiseProps() + } + return o.baseObject.stringKeys(all, keys) +} + +func (o *templatedObject) symbols(all bool, accum []Value) []Value { + o.materialiseSymbols() + return o.baseObject.symbols(all, accum) +} + +func (o *templatedObject) keys(all bool, accum []Value) []Value { + return o.symbols(all, o.stringKeys(all, accum)) +} + +func (r *Runtime) newTemplatedFuncObject(tmpl *objectTemplate, obj *Object, f func(FunctionCall) Value, ctor func([]Value, *Object) *Object) *templatedFuncObject { + if obj == nil { + obj = &Object{runtime: r} + } + o := &templatedFuncObject{ + templatedObject: templatedObject{ + baseObject: baseObject{ + class: classFunction, + val: obj, + extensible: true, + }, + tmpl: tmpl, + }, + f: f, + construct: ctor, + } + obj.self = o + o.init() + return o +} + +func (f *templatedFuncObject) source() String { + return newStringValue(fmt.Sprintf("function %s() { [native code] }", nilSafe(f.getStr("name", nil)).toString())) +} + +func (f *templatedFuncObject) export(*objectExportCtx) any { + return f.f +} + +func (f *templatedFuncObject) assertCallable() (func(FunctionCall) Value, bool) { + if f.f != nil { + return f.f, true + } + return nil, false +} + +func (f *templatedFuncObject) vmCall(vm *vm, n int) { + var nf nativeFuncObject + nf.f = f.f + nf.vmCall(vm, n) +} + +func (f *templatedFuncObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + return f.construct +} + +func (f *templatedFuncObject) exportType() reflect.Type { + return reflectTypeFunc +} + +func (f *templatedFuncObject) typeOf() String { + return stringFunction +} + +func (f *templatedFuncObject) hasInstance(v Value) bool { + return hasInstance(f.val, v) +} + +func (r *Runtime) newTemplatedArrayObject(tmpl *objectTemplate, obj *Object) *templatedArrayObject { + if obj == nil { + obj = &Object{runtime: r} + } + o := &templatedArrayObject{ + templatedObject: templatedObject{ + baseObject: baseObject{ + class: classArray, + val: obj, + extensible: true, + }, + tmpl: tmpl, + }, + } + obj.self = o + o.init() + return o +} + +func (a *templatedArrayObject) getLenProp() *valueProperty { + lenProp, _ := a.getOwnPropStr("length").(*valueProperty) + if lenProp == nil { + panic(a.val.runtime.NewTypeError("missing length property")) + } + return lenProp +} + +func (a *templatedArrayObject) _setOwnIdx(idx uint32) { + lenProp := a.getLenProp() + l := uint32(lenProp.value.ToInteger()) + if idx >= l { + lenProp.value = intToValue(int64(idx) + 1) + } +} + +func (a *templatedArrayObject) setLength(l uint32, throw bool) bool { + lenProp := a.getLenProp() + oldLen := uint32(lenProp.value.ToInteger()) + if l == oldLen { + return true + } + if !lenProp.writable { + a.val.runtime.typeErrorResult(throw, "length is not writable") + return false + } + ret := true + if l < oldLen { + a.materialisePropNames() + a.fixPropOrder() + i := sort.Search(a.idxPropCount, func(idx int) bool { + return strToArrayIdx(a.propNames[idx]) >= l + }) + for j := a.idxPropCount - 1; j >= i; j-- { + if !a.deleteStr(a.propNames[j], false) { + l = strToArrayIdx(a.propNames[j]) + 1 + ret = false + break + } + } + } + lenProp.value = intToValue(int64(l)) + return ret +} + +func (a *templatedArrayObject) setOwnStr(name unistring.String, value Value, throw bool) bool { + if name == "length" { + return a.setLength(a.val.runtime.toLengthUint32(value), throw) + } + if !a.templatedObject.setOwnStr(name, value, throw) { + return false + } + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + a._setOwnIdx(idx) + } + return true +} + +func (a *templatedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool { + if !a.templatedObject.setOwnStr(p.string(), v, throw) { + return false + } + if idx := toIdx(p); idx != math.MaxUint32 { + a._setOwnIdx(idx) + } + return true +} + +func (a *templatedArrayObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if name == "length" { + return a.val.runtime.defineArrayLength(a.getLenProp(), descr, a.setLength, throw) + } + if !a.templatedObject.defineOwnPropertyStr(name, descr, throw) { + return false + } + if idx := strToArrayIdx(name); idx != math.MaxUint32 { + a._setOwnIdx(idx) + } + return true +} + +func (a *templatedArrayObject) defineOwnPropertyIdx(p valueInt, desc PropertyDescriptor, throw bool) bool { + if !a.templatedObject.defineOwnPropertyStr(p.string(), desc, throw) { + return false + } + if idx := toIdx(p); idx != math.MaxUint32 { + a._setOwnIdx(idx) + } + return true +} diff --git a/pkg/xscript/engine/object_test.go b/pkg/xscript/engine/object_test.go new file mode 100644 index 0000000..eb087f8 --- /dev/null +++ b/pkg/xscript/engine/object_test.go @@ -0,0 +1,660 @@ +package engine + +import ( + "fmt" + "reflect" + "strings" + "testing" +) + +func TestDefineProperty(t *testing.T) { + r := New() + o := r.NewObject() + + err := o.DefineDataProperty("data", r.ToValue(42), FLAG_TRUE, FLAG_TRUE, FLAG_TRUE) + if err != nil { + t.Fatal(err) + } + + err = o.DefineAccessorProperty("accessor_ro", r.ToValue(func() int { + return 1 + }), nil, FLAG_TRUE, FLAG_TRUE) + if err != nil { + t.Fatal(err) + } + + err = o.DefineAccessorProperty("accessor_rw", + r.ToValue(func(call FunctionCall) Value { + return o.Get("__hidden") + }), + r.ToValue(func(call FunctionCall) (ret Value) { + o.Set("__hidden", call.Argument(0)) + return + }), + FLAG_TRUE, FLAG_TRUE) + + if err != nil { + t.Fatal(err) + } + + if v := o.Get("accessor_ro"); v.ToInteger() != 1 { + t.Fatalf("Unexpected accessor value: %v", v) + } + + err = o.Set("accessor_ro", r.ToValue(2)) + if err == nil { + t.Fatal("Expected an error") + } + if ex, ok := err.(*Exception); ok { + if msg := ex.Error(); msg != "TypeError: Cannot assign to read only property 'accessor_ro'" { + t.Fatalf("Unexpected error: '%s'", msg) + } + } else { + t.Fatalf("Unexected error type: %T", err) + } + + err = o.Set("accessor_rw", 42) + if err != nil { + t.Fatal(err) + } + + if v := o.Get("accessor_rw"); v.ToInteger() != 42 { + t.Fatalf("Unexpected value: %v", v) + } +} + +func TestPropertyOrder(t *testing.T) { + const SCRIPT = ` + var o = {}; + var sym1 = Symbol(1); + var sym2 = Symbol(2); + o[sym2] = 1; + o[4294967294] = 1; + o[2] = 1; + o[1] = 1; + o[0] = 1; + o["02"] = 1; + o[4294967295] = 1; + o["01"] = 1; + o["00"] = 1; + o[sym1] = 1; + var expected = ["0", "1", "2", "4294967294", "02", "4294967295", "01", "00", sym2, sym1]; + var actual = Reflect.ownKeys(o); + if (actual.length !== expected.length) { + throw new Error("Unexpected length: "+actual.length); + } + for (var i = 0; i < actual.length; i++) { + if (actual[i] !== expected[i]) { + throw new Error("Unexpected list: " + actual); + } + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestDefinePropertiesSymbol(t *testing.T) { + const SCRIPT = ` + var desc = {}; + desc[Symbol.toStringTag] = {value: "Test"}; + var o = {}; + Object.defineProperties(o, desc); + o[Symbol.toStringTag] === "Test"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestObjectShorthandProperties(t *testing.T) { + const SCRIPT = ` + var b = 1; + var a = {b, get() {return "c"}}; + + assert.sameValue(a.b, b, "#1"); + assert.sameValue(a.get(), "c", "#2"); + + var obj = { + w\u0069th() { return 42; } + }; + + assert.sameValue(obj['with'](), 42, 'property exists'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjectAssign(t *testing.T) { + const SCRIPT = ` + assert.sameValue(Object.assign({ b: 1 }, { get a() { + Object.defineProperty(this, "b", { + value: 3, + enumerable: false + }); + }, b: 2 }).b, 1, "#1"); + + assert.sameValue(Object.assign({ b: 1 }, { get a() { + delete this.b; + }, b: 2 }).b, 1, "#2"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestExportCircular(t *testing.T) { + vm := New() + o := vm.NewObject() + o.Set("o", o) + v := o.Export() + if m, ok := v.(map[string]any); ok { + if reflect.ValueOf(m["o"]).Pointer() != reflect.ValueOf(v).Pointer() { + t.Fatal("Unexpected value") + } + } else { + t.Fatal("Unexpected type") + } + + res, err := vm.RunString(`var a = []; a[0] = a;`) + if err != nil { + t.Fatal(err) + } + v = res.Export() + if a, ok := v.([]any); ok { + if reflect.ValueOf(a[0]).Pointer() != reflect.ValueOf(v).Pointer() { + t.Fatal("Unexpected value") + } + } else { + t.Fatal("Unexpected type") + } +} + +type test_s struct { + S *test_s1 +} +type test_s1 struct { + S *test_s +} + +func TestExportToCircular(t *testing.T) { + vm := New() + o := vm.NewObject() + o.Set("o", o) + var m map[string]any + err := vm.ExportTo(o, &m) + if err != nil { + t.Fatal(err) + } + + type K string + type T map[K]T + var m1 T + err = vm.ExportTo(o, &m1) + if err != nil { + t.Fatal(err) + } + + type A []A + var a A + res, err := vm.RunString("var a = []; a[0] = a;") + if err != nil { + t.Fatal(err) + } + err = vm.ExportTo(res, &a) + if err != nil { + t.Fatal(err) + } + if &a[0] != &a[0][0] { + t.Fatal("values do not match") + } + + o = vm.NewObject() + o.Set("S", o) + var s test_s + err = vm.ExportTo(o, &s) + if err != nil { + t.Fatal(err) + } + if s.S.S != &s { + t.Fatalf("values do not match: %v, %v", s.S.S, &s) + } + + type test_s2 struct { + S any + S1 *test_s2 + } + + var s2 test_s2 + o.Set("S1", o) + + err = vm.ExportTo(o, &s2) + if err != nil { + t.Fatal(err) + } + + if m, ok := s2.S.(map[string]any); ok { + if reflect.ValueOf(m["S"]).Pointer() != reflect.ValueOf(m).Pointer() { + t.Fatal("Unexpected m.S") + } + } else { + t.Fatalf("Unexpected s2.S type: %T", s2.S) + } + if s2.S1 != &s2 { + t.Fatal("Unexpected s2.S1") + } + + o1 := vm.NewObject() + o1.Set("S", o) + o1.Set("S1", o) + err = vm.ExportTo(o1, &s2) + if err != nil { + t.Fatal(err) + } + if s2.S1.S1 != s2.S1 { + t.Fatal("Unexpected s2.S1.S1") + } +} + +func TestExportWrappedMap(t *testing.T) { + vm := New() + m := map[string]any{ + "test": "failed", + } + exported := vm.ToValue(m).Export() + if exportedMap, ok := exported.(map[string]any); ok { + exportedMap["test"] = "passed" + if v := m["test"]; v != "passed" { + t.Fatalf("Unexpected m[\"test\"]: %v", v) + } + } else { + t.Fatalf("Unexpected export type: %T", exported) + } +} + +func TestExportToWrappedMap(t *testing.T) { + vm := New() + m := map[string]any{ + "test": "failed", + } + var exported map[string]any + err := vm.ExportTo(vm.ToValue(m), &exported) + if err != nil { + t.Fatal(err) + } + exported["test"] = "passed" + if v := m["test"]; v != "passed" { + t.Fatalf("Unexpected m[\"test\"]: %v", v) + } +} + +func TestExportToWrappedMapCustom(t *testing.T) { + type CustomMap map[string]bool + vm := New() + m := CustomMap{} + var exported CustomMap + err := vm.ExportTo(vm.ToValue(m), &exported) + if err != nil { + t.Fatal(err) + } + exported["test"] = true + if v := m["test"]; v != true { + t.Fatalf("Unexpected m[\"test\"]: %v", v) + } +} + +func TestExportToSliceNonIterable(t *testing.T) { + vm := New() + o := vm.NewObject() + var a []any + err := vm.ExportTo(o, &a) + if err == nil { + t.Fatal("Expected an error") + } + if len(a) != 0 { + t.Fatalf("a: %v", a) + } + if msg := err.Error(); msg != "cannot convert [object Object] to []interface {}: not an array or iterable" { + t.Fatalf("Unexpected error: %v", err) + } +} + +func ExampleRuntime_ExportTo_iterableToSlice() { + vm := New() + v, err := vm.RunString(` + function reverseIterator() { + const arr = this; + let idx = arr.length; + return { + next: () => idx > 0 ? {value: arr[--idx]} : {done: true} + } + } + const arr = [1,2,3]; + arr[Symbol.iterator] = reverseIterator; + arr; + `) + if err != nil { + panic(err) + } + + var arr []int + err = vm.ExportTo(v, &arr) + if err != nil { + panic(err) + } + + fmt.Println(arr) + // Output: [3 2 1] +} + +func TestRuntime_ExportTo_proxiedIterableToSlice(t *testing.T) { + vm := New() + v, err := vm.RunString(` + function reverseIterator() { + const arr = this; + let idx = arr.length; + return { + next: () => idx > 0 ? {value: arr[--idx]} : {done: true} + } + } + const arr = [1,2,3]; + arr[Symbol.iterator] = reverseIterator; + new Proxy(arr, {}); + `) + if err != nil { + t.Fatal(err) + } + + var arr []int + err = vm.ExportTo(v, &arr) + if err != nil { + t.Fatal(err) + } + if out := fmt.Sprint(arr); out != "[3 2 1]" { + t.Fatal(out) + } +} + +func ExampleRuntime_ExportTo_arrayLikeToSlice() { + vm := New() + v, err := vm.RunString(` + ({ + length: 3, + 0: 1, + 1: 2, + 2: 3 + }); + `) + if err != nil { + panic(err) + } + + var arr []int + err = vm.ExportTo(v, &arr) + if err != nil { + panic(err) + } + + fmt.Println(arr) + // Output: [1 2 3] +} + +func TestExportArrayToArrayMismatchedLengths(t *testing.T) { + vm := New() + a := vm.NewArray(1, 2) + var a1 [3]int + err := vm.ExportTo(a, &a1) + if err == nil { + t.Fatal("expected error") + } + if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestExportIterableToArrayMismatchedLengths(t *testing.T) { + vm := New() + a, err := vm.RunString(` + new Map([[1, true], [2, true]]); + `) + if err != nil { + t.Fatal(err) + } + + var a1 [3]any + err = vm.ExportTo(a, &a1) + if err == nil { + t.Fatal("expected error") + } + if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestExportArrayLikeToArrayMismatchedLengths(t *testing.T) { + vm := New() + a, err := vm.RunString(` + ({ + length: 2, + 0: true, + 1: true + }); + `) + if err != nil { + t.Fatal(err) + } + + var a1 [3]any + err = vm.ExportTo(a, &a1) + if err == nil { + t.Fatal("expected error") + } + if msg := err.Error(); !strings.Contains(msg, "lengths mismatch") { + t.Fatalf("unexpected error: %v", err) + } +} + +func TestSetForeignReturnValue(t *testing.T) { + const SCRIPT = ` + var array = [1, 2, 3]; + var arrayTarget = new Proxy(array, {}); + + Object.preventExtensions(array); + + !Reflect.set(arrayTarget, "foo", 2); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestDefinePropertiesUndefinedVal(t *testing.T) { + const SCRIPT = ` +var target = {}; +var sym = Symbol(); +target[sym] = 1; +target.foo = 2; +target[0] = 3; + +var getOwnKeys = []; +var proxy = new Proxy(target, { + getOwnPropertyDescriptor: function(_target, key) { + getOwnKeys.push(key); + }, +}); + +Object.defineProperties({}, proxy); + true; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func ExampleObject_Delete() { + vm := New() + obj := vm.NewObject() + _ = obj.Set("test", true) + before := obj.Get("test") + _ = obj.Delete("test") + after := obj.Get("test") + fmt.Printf("before: %v, after: %v", before, after) + // Output: before: true, after: +} + +func TestObjectEquality(t *testing.T) { + type CustomInt int + type S struct { + F CustomInt + } + vm := New() + vm.Set("s", S{}) + // indexOf() and includes() use different equality checks (StrictEquals and SameValueZero respectively), + // but for objects they must behave the same. De-referencing s.F creates a new wrapper each time. + vm.testScriptWithTestLib(` + assert.sameValue([s.F].indexOf(s.F), 0, "indexOf"); + assert([s.F].includes(s.F)); + `, _undefined, t) +} + +func BenchmarkPut(b *testing.B) { + v := &Object{} + + o := &baseObject{ + val: v, + extensible: true, + } + v.self = o + + o.init() + + var key Value = asciiString("test") + var val Value = valueInt(123) + + for i := 0; i < b.N; i++ { + v.setOwn(key, val, false) + } +} + +func BenchmarkPutStr(b *testing.B) { + v := &Object{} + + o := &baseObject{ + val: v, + extensible: true, + } + + o.init() + + v.self = o + + var val Value = valueInt(123) + + for i := 0; i < b.N; i++ { + o.setOwnStr("test", val, false) + } +} + +func BenchmarkGet(b *testing.B) { + v := &Object{} + + o := &baseObject{ + val: v, + extensible: true, + } + + o.init() + + v.self = o + var n Value = asciiString("test") + + for i := 0; i < b.N; i++ { + v.get(n, nil) + } + +} + +func BenchmarkGetStr(b *testing.B) { + v := &Object{} + + o := &baseObject{ + val: v, + extensible: true, + } + v.self = o + + o.init() + + for i := 0; i < b.N; i++ { + o.getStr("test", nil) + } +} + +func __toString(v Value) string { + switch v := v.(type) { + case asciiString: + return string(v) + default: + return "" + } +} + +func BenchmarkToString1(b *testing.B) { + v := asciiString("test") + + for i := 0; i < b.N; i++ { + v.toString() + } +} + +func BenchmarkToString2(b *testing.B) { + v := asciiString("test") + + for i := 0; i < b.N; i++ { + __toString(v) + } +} + +func BenchmarkConv(b *testing.B) { + count := int64(0) + for i := 0; i < b.N; i++ { + count += valueInt(123).ToInteger() + } + if count == 0 { + b.Fatal("zero") + } +} + +func BenchmarkToUTF8String(b *testing.B) { + var s String = asciiString("test") + for i := 0; i < b.N; i++ { + _ = s.String() + } +} + +func BenchmarkAdd(b *testing.B) { + var x, y Value + x = valueInt(2) + y = valueInt(2) + + for i := 0; i < b.N; i++ { + if xi, ok := x.(valueInt); ok { + if yi, ok := y.(valueInt); ok { + x = xi + yi + } + } + } +} + +func BenchmarkAddString(b *testing.B) { + var x, y Value + + tst := asciiString("22") + x = asciiString("2") + y = asciiString("2") + + for i := 0; i < b.N; i++ { + var z Value + if xi, ok := x.(String); ok { + if yi, ok := y.(String); ok { + z = xi.Concat(yi) + } + } + if !z.StrictEquals(tst) { + b.Fatalf("Unexpected result %v", x) + } + } +} diff --git a/pkg/xscript/engine/parser/README.markdown b/pkg/xscript/engine/parser/README.markdown new file mode 100644 index 0000000..ec1186d --- /dev/null +++ b/pkg/xscript/engine/parser/README.markdown @@ -0,0 +1,184 @@ +# parser +-- + import "github.com/dop251/goja/parser" + +Package parser implements a parser for JavaScript. Borrowed from https://github.com/robertkrimen/otto/tree/master/parser + + import ( + "github.com/dop251/goja/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. + +-- +**godocdown** http://github.com/robertkrimen/godocdown diff --git a/pkg/xscript/engine/parser/error.go b/pkg/xscript/engine/parser/error.go new file mode 100644 index 0000000..10aa386 --- /dev/null +++ b/pkg/xscript/engine/parser/error.go @@ -0,0 +1,176 @@ +package parser + +import ( + "fmt" + "sort" + + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" +) + +const ( + err_UnexpectedToken = "Unexpected token %v" + err_UnexpectedEndOfInput = "Unexpected end of input" + err_UnexpectedEscape = "Unexpected escape" +) + +// UnexpectedNumber: 'Unexpected number', +// UnexpectedString: 'Unexpected string', +// UnexpectedIdentifier: 'Unexpected identifier', +// UnexpectedReserved: 'Unexpected reserved word', +// NewlineAfterThrow: 'Illegal newline after throw', +// InvalidRegExp: 'Invalid regular expression', +// UnterminatedRegExp: 'Invalid regular expression: missing /', +// 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', +// UnknownLabel: 'Undefined label \'%0\'', +// Redeclaration: '%0 \'%1\' has already been declared', +// IllegalContinue: 'Illegal continue statement', +// IllegalBreak: 'Illegal break statement', +// IllegalReturn: 'Illegal return statement', +// StrictModeWith: 'Strict mode code may not include a with statement', +// StrictCatchVariable: 'Catch variable may not be eval or arguments in strict mode', +// StrictVarName: 'Variable name may not be eval or arguments in strict mode', +// StrictParamName: 'Parameter name eval or arguments is not allowed in strict mode', +// StrictParamDupe: 'Strict mode function may not have duplicate parameter names', +// StrictFunctionName: 'Function name may not be eval or arguments in strict mode', +// StrictOctalLiteral: 'Octal literals are not allowed in strict mode.', +// StrictDelete: 'Delete of an unqualified identifier in strict mode.', +// StrictDuplicateProperty: 'Duplicate data property in object literal not allowed in strict mode', +// AccessorDataProperty: 'Object literal may not have data and accessor property with the same name', +// AccessorGetSet: 'Object literal may not have multiple get/set accessors with the same name', +// StrictLHSAssignment: 'Assignment to eval or arguments is not allowed in strict mode', +// StrictLHSPostfix: 'Postfix increment/decrement may not have eval or arguments operand in strict mode', +// StrictLHSPrefix: 'Prefix increment/decrement may not have eval or arguments operand in strict mode', +// StrictReservedWord: 'Use of future reserved word in strict mode' + +// A SyntaxError is a description of an ECMAScript syntax error. + +// An Error represents a parsing error. It includes the position where the error occurred and a message/description. +type Error struct { + Position file.Position + Message string +} + +// FIXME Should this be "SyntaxError"? + +func (self Error) Error() string { + filename := self.Position.Filename + if filename == "" { + filename = "(anonymous)" + } + return fmt.Sprintf("%s: Line %d:%d %s", + filename, + self.Position.Line, + self.Position.Column, + self.Message, + ) +} + +func (self *_parser) error(place any, msg string, msgValues ...any) *Error { + idx := file.Idx(0) + switch place := place.(type) { + case int: + idx = self.idxOf(place) + case file.Idx: + if place == 0 { + idx = self.idxOf(self.chrOffset) + } else { + idx = place + } + default: + panic(fmt.Errorf("error(%T, ...)", place)) + } + + position := self.position(idx) + msg = fmt.Sprintf(msg, msgValues...) + self.errors.Add(position, msg) + return self.errors[len(self.errors)-1] +} + +func (self *_parser) errorUnexpected(idx file.Idx, chr rune) error { + if chr == -1 { + return self.error(idx, err_UnexpectedEndOfInput) + } + return self.error(idx, err_UnexpectedToken, token.ILLEGAL) +} + +func (self *_parser) errorUnexpectedToken(tkn token.Token) error { + switch tkn { + case token.EOF: + return self.error(file.Idx(0), err_UnexpectedEndOfInput) + } + value := tkn.String() + switch tkn { + case token.BOOLEAN, token.NULL: + value = self.literal + case token.IDENTIFIER: + return self.error(self.idx, "Unexpected identifier") + case token.KEYWORD: + // TODO Might be a future reserved word + return self.error(self.idx, "Unexpected reserved word") + case token.ESCAPED_RESERVED_WORD: + return self.error(self.idx, "Keyword must not contain escaped characters") + case token.NUMBER: + return self.error(self.idx, "Unexpected number") + case token.STRING: + return self.error(self.idx, "Unexpected string") + } + return self.error(self.idx, err_UnexpectedToken, value) +} + +// ErrorList is a list of *Errors. +type ErrorList []*Error + +// 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}) +} + +// Reset resets an ErrorList to no errors. +func (self *ErrorList) Reset() { *self = (*self)[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 + if x.Filename < y.Filename { + return true + } + if x.Filename == y.Filename { + if x.Line < y.Line { + return true + } + if x.Line == y.Line { + return x.Column < y.Column + } + } + return false +} + +func (self ErrorList) Sort() { + sort.Sort(self) +} + +// Error implements the Error interface. +func (self ErrorList) Error() string { + switch len(self) { + case 0: + return "no errors" + case 1: + return self[0].Error() + } + 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 { + return nil + } + return self +} diff --git a/pkg/xscript/engine/parser/expression.go b/pkg/xscript/engine/parser/expression.go new file mode 100644 index 0000000..9f41732 --- /dev/null +++ b/pkg/xscript/engine/parser/expression.go @@ -0,0 +1,1660 @@ +package parser + +import ( + "strings" + + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +func (self *_parser) parseIdentifier() *ast.Identifier { + literal := self.parsedLiteral + idx := self.idx + self.next() + return &ast.Identifier{ + Name: literal, + Idx: idx, + } +} + +func (self *_parser) parsePrimaryExpression() ast.Expression { + literal, parsedLiteral := self.literal, self.parsedLiteral + idx := self.idx + switch self.token { + case token.IDENTIFIER: + self.next() + return &ast.Identifier{ + Name: parsedLiteral, + Idx: idx, + } + case token.NULL: + self.next() + return &ast.NullLiteral{ + Idx: idx, + Literal: literal, + } + case token.BOOLEAN: + self.next() + value := false + switch parsedLiteral { + case "true": + value = true + case "false": + value = false + default: + self.error(idx, "Illegal boolean literal") + } + return &ast.BooleanLiteral{ + Idx: idx, + Literal: literal, + Value: value, + } + case token.STRING: + self.next() + return &ast.StringLiteral{ + Idx: idx, + Literal: literal, + Value: parsedLiteral, + } + case token.NUMBER: + self.next() + value, err := parseNumberLiteral(literal) + if err != nil { + self.error(idx, err.Error()) + value = 0 + } + return &ast.NumberLiteral{ + Idx: idx, + Literal: literal, + Value: value, + } + case token.SLASH, token.QUOTIENT_ASSIGN: + return self.parseRegExpLiteral() + case token.LEFT_BRACE: + return self.parseObjectLiteral() + case token.LEFT_BRACKET: + return self.parseArrayLiteral() + case token.LEFT_PARENTHESIS: + return self.parseParenthesisedExpression() + case token.BACKTICK: + return self.parseTemplateLiteral(false) + case token.THIS: + self.next() + return &ast.ThisExpression{ + Idx: idx, + } + case token.SUPER: + return self.parseSuperProperty() + case token.ASYNC: + if f := self.parseMaybeAsyncFunction(false); f != nil { + return f + } + case token.FUNCTION: + return self.parseFunction(false, false, idx) + case token.CLASS: + return self.parseClass(false) + } + + if self.isBindingId(self.token) { + self.next() + return &ast.Identifier{ + Name: parsedLiteral, + Idx: idx, + } + } + + self.errorUnexpectedToken(self.token) + self.nextStatement() + return &ast.BadExpression{From: idx, To: self.idx} +} + +func (self *_parser) parseSuperProperty() ast.Expression { + idx := self.idx + self.next() + switch self.token { + case token.PERIOD: + self.next() + if !token.IsId(self.token) { + self.expect(token.IDENTIFIER) + self.nextStatement() + return &ast.BadExpression{From: idx, To: self.idx} + } + idIdx := self.idx + parsedLiteral := self.parsedLiteral + self.next() + return &ast.DotExpression{ + Left: &ast.SuperExpression{ + Idx: idx, + }, + Identifier: ast.Identifier{ + Name: parsedLiteral, + Idx: idIdx, + }, + } + case token.LEFT_BRACKET: + return self.parseBracketMember(&ast.SuperExpression{ + Idx: idx, + }) + case token.LEFT_PARENTHESIS: + return self.parseCallExpression(&ast.SuperExpression{ + Idx: idx, + }) + default: + self.error(idx, "'super' keyword unexpected here") + self.nextStatement() + return &ast.BadExpression{From: idx, To: self.idx} + } +} + +func (self *_parser) reinterpretSequenceAsArrowFuncParams(list []ast.Expression) *ast.ParameterList { + firstRestIdx := -1 + params := make([]*ast.Binding, 0, len(list)) + for i, item := range list { + if _, ok := item.(*ast.SpreadElement); ok { + if firstRestIdx == -1 { + firstRestIdx = i + continue + } + } + if firstRestIdx != -1 { + self.error(list[firstRestIdx].Idx0(), "Rest parameter must be last formal parameter") + return &ast.ParameterList{} + } + params = append(params, self.reinterpretAsBinding(item)) + } + var rest ast.Expression + if firstRestIdx != -1 { + rest = self.reinterpretAsBindingRestElement(list[firstRestIdx]) + } + return &ast.ParameterList{ + List: params, + Rest: rest, + } +} + +func (self *_parser) parseParenthesisedExpression() ast.Expression { + opening := self.idx + self.expect(token.LEFT_PARENTHESIS) + var list []ast.Expression + if self.token != token.RIGHT_PARENTHESIS { + for { + if self.token == token.ELLIPSIS { + start := self.idx + self.errorUnexpectedToken(token.ELLIPSIS) + self.next() + expr := self.parseAssignmentExpression() + list = append(list, &ast.BadExpression{ + From: start, + To: expr.Idx1(), + }) + } else { + list = append(list, self.parseAssignmentExpression()) + } + if self.token != token.COMMA { + break + } + self.next() + if self.token == token.RIGHT_PARENTHESIS { + self.errorUnexpectedToken(token.RIGHT_PARENTHESIS) + break + } + } + } + self.expect(token.RIGHT_PARENTHESIS) + if len(list) == 1 && len(self.errors) == 0 { + return list[0] + } + if len(list) == 0 { + self.errorUnexpectedToken(token.RIGHT_PARENTHESIS) + return &ast.BadExpression{ + From: opening, + To: self.idx, + } + } + return &ast.SequenceExpression{ + Sequence: list, + } +} + +func (self *_parser) parseRegExpLiteral() *ast.RegExpLiteral { + + offset := self.chrOffset - 1 // Opening slash already gotten + if self.token == token.QUOTIENT_ASSIGN { + offset -= 1 // = + } + idx := self.idxOf(offset) + + pattern, _, err := self.scanString(offset, false) + endOffset := self.chrOffset + + if err == "" { + pattern = pattern[1 : len(pattern)-1] + } + + flags := "" + if !isLineTerminator(self.chr) && !isLineWhiteSpace(self.chr) { + self.next() + + if self.token == token.IDENTIFIER { // gim + + flags = self.literal + self.next() + endOffset = self.chrOffset - 1 + } + } else { + self.next() + } + + literal := self.str[offset:endOffset] + + return &ast.RegExpLiteral{ + Idx: idx, + Literal: literal, + Pattern: pattern, + Flags: flags, + } +} + +func (self *_parser) isBindingId(tok token.Token) bool { + if tok == token.IDENTIFIER { + return true + } + + if tok == token.AWAIT { + return !self.scope.allowAwait + } + if tok == token.YIELD { + return !self.scope.allowYield + } + + if token.IsUnreservedWord(tok) { + return true + } + return false +} + +func (self *_parser) tokenToBindingId() { + if self.isBindingId(self.token) { + self.token = token.IDENTIFIER + } +} + +func (self *_parser) parseBindingTarget() (target ast.BindingTarget) { + self.tokenToBindingId() + switch self.token { + case token.IDENTIFIER: + target = &ast.Identifier{ + Name: self.parsedLiteral, + Idx: self.idx, + } + self.next() + case token.LEFT_BRACKET: + target = self.parseArrayBindingPattern() + case token.LEFT_BRACE: + target = self.parseObjectBindingPattern() + default: + idx := self.expect(token.IDENTIFIER) + self.nextStatement() + target = &ast.BadExpression{From: idx, To: self.idx} + } + + return +} + +func (self *_parser) parseVariableDeclaration(declarationList *[]*ast.Binding) *ast.Binding { + node := &ast.Binding{ + Target: self.parseBindingTarget(), + } + + if declarationList != nil { + *declarationList = append(*declarationList, node) + } + + if self.token == token.ASSIGN { + self.next() + node.Initializer = self.parseAssignmentExpression() + } + + return node +} + +func (self *_parser) parseVariableDeclarationList() (declarationList []*ast.Binding) { + for { + self.parseVariableDeclaration(&declarationList) + if self.token != token.COMMA { + break + } + self.next() + } + return +} + +func (self *_parser) parseVarDeclarationList(var_ file.Idx) []*ast.Binding { + declarationList := self.parseVariableDeclarationList() + + self.scope.declare(&ast.VariableDeclaration{ + Var: var_, + List: declarationList, + }) + + return declarationList +} + +func (self *_parser) parseObjectPropertyKey() (string, unistring.String, ast.Expression, token.Token) { + if self.token == token.LEFT_BRACKET { + self.next() + expr := self.parseAssignmentExpression() + self.expect(token.RIGHT_BRACKET) + return "", "", expr, token.ILLEGAL + } + idx, tkn, literal, parsedLiteral := self.idx, self.token, self.literal, self.parsedLiteral + var value ast.Expression + self.next() + switch tkn { + case token.IDENTIFIER, token.STRING, token.KEYWORD, token.ESCAPED_RESERVED_WORD: + value = &ast.StringLiteral{ + Idx: idx, + Literal: literal, + Value: parsedLiteral, + } + case token.NUMBER: + num, err := parseNumberLiteral(literal) + if err != nil { + self.error(idx, err.Error()) + } else { + value = &ast.NumberLiteral{ + Idx: idx, + Literal: literal, + Value: num, + } + } + case token.PRIVATE_IDENTIFIER: + value = &ast.PrivateIdentifier{ + Identifier: ast.Identifier{ + Idx: idx, + Name: parsedLiteral, + }, + } + default: + // null, false, class, etc. + if token.IsId(tkn) { + value = &ast.StringLiteral{ + Idx: idx, + Literal: literal, + Value: unistring.String(literal), + } + } else { + self.errorUnexpectedToken(tkn) + } + } + return literal, parsedLiteral, value, tkn +} + +func (self *_parser) parseObjectProperty() ast.Property { + if self.token == token.ELLIPSIS { + self.next() + return &ast.SpreadElement{ + Expression: self.parseAssignmentExpression(), + } + } + keyStartIdx := self.idx + generator := false + if self.token == token.MULTIPLY { + generator = true + self.next() + } + literal, parsedLiteral, value, tkn := self.parseObjectPropertyKey() + if value == nil { + return nil + } + if token.IsId(tkn) || tkn == token.STRING || tkn == token.NUMBER || tkn == token.ILLEGAL { + if generator { + return &ast.PropertyKeyed{ + Key: value, + Kind: ast.PropertyKindMethod, + Value: self.parseMethodDefinition(keyStartIdx, ast.PropertyKindMethod, true, false), + Computed: tkn == token.ILLEGAL, + } + } + switch { + case self.token == token.LEFT_PARENTHESIS: + return &ast.PropertyKeyed{ + Key: value, + Kind: ast.PropertyKindMethod, + Value: self.parseMethodDefinition(keyStartIdx, ast.PropertyKindMethod, false, false), + Computed: tkn == token.ILLEGAL, + } + case self.token == token.COMMA || self.token == token.RIGHT_BRACE || self.token == token.ASSIGN: // shorthand property + if self.isBindingId(tkn) { + var initializer ast.Expression + if self.token == token.ASSIGN { + // allow the initializer syntax here in case the object literal + // needs to be reinterpreted as an assignment pattern, enforce later if it doesn't. + self.next() + initializer = self.parseAssignmentExpression() + } + return &ast.PropertyShort{ + Name: ast.Identifier{ + Name: parsedLiteral, + Idx: value.Idx0(), + }, + Initializer: initializer, + } + } else { + self.errorUnexpectedToken(self.token) + } + case (literal == "get" || literal == "set" || tkn == token.ASYNC) && self.token != token.COLON: + _, _, keyValue, tkn1 := self.parseObjectPropertyKey() + if keyValue == nil { + return nil + } + + var kind ast.PropertyKind + var async bool + if tkn == token.ASYNC { + async = true + kind = ast.PropertyKindMethod + } else if literal == "get" { + kind = ast.PropertyKindGet + } else { + kind = ast.PropertyKindSet + } + + return &ast.PropertyKeyed{ + Key: keyValue, + Kind: kind, + Value: self.parseMethodDefinition(keyStartIdx, kind, false, async), + Computed: tkn1 == token.ILLEGAL, + } + } + } + + self.expect(token.COLON) + return &ast.PropertyKeyed{ + Key: value, + Kind: ast.PropertyKindValue, + Value: self.parseAssignmentExpression(), + Computed: tkn == token.ILLEGAL, + } +} + +func (self *_parser) parseMethodDefinition(keyStartIdx file.Idx, kind ast.PropertyKind, generator, async bool) *ast.FunctionLiteral { + idx1 := self.idx + if generator != self.scope.allowYield { + self.scope.allowYield = generator + defer func() { + self.scope.allowYield = !generator + }() + } + if async != self.scope.allowAwait { + self.scope.allowAwait = async + defer func() { + self.scope.allowAwait = !async + }() + } + parameterList := self.parseFunctionParameterList() + switch kind { + case ast.PropertyKindGet: + if len(parameterList.List) > 0 || parameterList.Rest != nil { + self.error(idx1, "Getter must not have any formal parameters.") + } + case ast.PropertyKindSet: + if len(parameterList.List) != 1 || parameterList.Rest != nil { + self.error(idx1, "Setter must have exactly one formal parameter.") + } + } + node := &ast.FunctionLiteral{ + Function: keyStartIdx, + ParameterList: parameterList, + Generator: generator, + Async: async, + } + node.Body, node.DeclarationList = self.parseFunctionBlock(async, async, generator) + node.Source = self.slice(keyStartIdx, node.Body.Idx1()) + return node +} + +func (self *_parser) parseObjectLiteral() *ast.ObjectLiteral { + var value []ast.Property + idx0 := self.expect(token.LEFT_BRACE) + for self.token != token.RIGHT_BRACE && self.token != token.EOF { + property := self.parseObjectProperty() + if property != nil { + value = append(value, property) + } + if self.token != token.RIGHT_BRACE { + self.expect(token.COMMA) + } else { + break + } + } + idx1 := self.expect(token.RIGHT_BRACE) + + return &ast.ObjectLiteral{ + LeftBrace: idx0, + RightBrace: idx1, + Value: value, + } +} + +func (self *_parser) parseArrayLiteral() *ast.ArrayLiteral { + + idx0 := self.expect(token.LEFT_BRACKET) + var value []ast.Expression + for self.token != token.RIGHT_BRACKET && self.token != token.EOF { + if self.token == token.COMMA { + self.next() + value = append(value, nil) + continue + } + if self.token == token.ELLIPSIS { + self.next() + value = append(value, &ast.SpreadElement{ + Expression: self.parseAssignmentExpression(), + }) + } else { + value = append(value, self.parseAssignmentExpression()) + } + if self.token != token.RIGHT_BRACKET { + self.expect(token.COMMA) + } + } + idx1 := self.expect(token.RIGHT_BRACKET) + + return &ast.ArrayLiteral{ + LeftBracket: idx0, + RightBracket: idx1, + Value: value, + } +} + +func (self *_parser) parseTemplateLiteral(tagged bool) *ast.TemplateLiteral { + res := &ast.TemplateLiteral{ + OpenQuote: self.idx, + } + for { + start := self.offset + literal, parsed, finished, parseErr, err := self.parseTemplateCharacters() + if err != "" { + self.error(self.offset, err) + } + res.Elements = append(res.Elements, &ast.TemplateElement{ + Idx: self.idxOf(start), + Literal: literal, + Parsed: parsed, + Valid: parseErr == "", + }) + if !tagged && parseErr != "" { + self.error(self.offset, parseErr) + } + end := self.chrOffset - 1 + self.next() + if finished { + res.CloseQuote = self.idxOf(end) + break + } + expr := self.parseExpression() + res.Expressions = append(res.Expressions, expr) + if self.token != token.RIGHT_BRACE { + self.errorUnexpectedToken(self.token) + } + } + return res +} + +func (self *_parser) parseTaggedTemplateLiteral(tag ast.Expression) *ast.TemplateLiteral { + l := self.parseTemplateLiteral(true) + l.Tag = tag + return l +} + +func (self *_parser) parseArgumentList() (argumentList []ast.Expression, idx0, idx1 file.Idx) { + idx0 = self.expect(token.LEFT_PARENTHESIS) + for self.token != token.RIGHT_PARENTHESIS { + var item ast.Expression + if self.token == token.ELLIPSIS { + self.next() + item = &ast.SpreadElement{ + Expression: self.parseAssignmentExpression(), + } + } else { + item = self.parseAssignmentExpression() + } + argumentList = append(argumentList, item) + if self.token != token.COMMA { + break + } + self.next() + } + idx1 = self.expect(token.RIGHT_PARENTHESIS) + return +} + +func (self *_parser) parseCallExpression(left ast.Expression) ast.Expression { + argumentList, idx0, idx1 := self.parseArgumentList() + return &ast.CallExpression{ + Callee: left, + LeftParenthesis: idx0, + ArgumentList: argumentList, + RightParenthesis: idx1, + } +} + +func (self *_parser) parseDotMember(left ast.Expression) ast.Expression { + period := self.idx + self.next() + + literal := self.parsedLiteral + idx := self.idx + + if self.token == token.PRIVATE_IDENTIFIER { + self.next() + return &ast.PrivateDotExpression{ + Left: left, + Identifier: ast.PrivateIdentifier{ + Identifier: ast.Identifier{ + Idx: idx, + Name: literal, + }, + }, + } + } + + if !token.IsId(self.token) { + self.expect(token.IDENTIFIER) + self.nextStatement() + return &ast.BadExpression{From: period, To: self.idx} + } + + self.next() + + return &ast.DotExpression{ + Left: left, + Identifier: ast.Identifier{ + Idx: idx, + Name: literal, + }, + } +} + +func (self *_parser) parseBracketMember(left ast.Expression) ast.Expression { + idx0 := self.expect(token.LEFT_BRACKET) + member := self.parseExpression() + idx1 := self.expect(token.RIGHT_BRACKET) + return &ast.BracketExpression{ + LeftBracket: idx0, + Left: left, + Member: member, + RightBracket: idx1, + } +} + +func (self *_parser) parseNewExpression() ast.Expression { + idx := self.expect(token.NEW) + if self.token == token.PERIOD { + self.next() + if self.literal == "target" { + return &ast.MetaProperty{ + Meta: &ast.Identifier{ + Name: unistring.String(token.NEW.String()), + Idx: idx, + }, + Property: self.parseIdentifier(), + } + } + self.errorUnexpectedToken(token.IDENTIFIER) + } + callee := self.parseLeftHandSideExpression() + if bad, ok := callee.(*ast.BadExpression); ok { + bad.From = idx + return bad + } + node := &ast.NewExpression{ + New: idx, + Callee: callee, + } + if self.token == token.LEFT_PARENTHESIS { + argumentList, idx0, idx1 := self.parseArgumentList() + node.ArgumentList = argumentList + node.LeftParenthesis = idx0 + node.RightParenthesis = idx1 + } + return node +} + +func (self *_parser) parseLeftHandSideExpression() ast.Expression { + + var left ast.Expression + if self.token == token.NEW { + left = self.parseNewExpression() + } else { + left = self.parsePrimaryExpression() + } +L: + for { + switch self.token { + case token.PERIOD: + left = self.parseDotMember(left) + case token.LEFT_BRACKET: + left = self.parseBracketMember(left) + case token.BACKTICK: + left = self.parseTaggedTemplateLiteral(left) + default: + break L + } + } + + return left +} + +func (self *_parser) parseLeftHandSideExpressionAllowCall() ast.Expression { + + allowIn := self.scope.allowIn + self.scope.allowIn = true + defer func() { + self.scope.allowIn = allowIn + }() + + var left ast.Expression + start := self.idx + if self.token == token.NEW { + left = self.parseNewExpression() + } else { + left = self.parsePrimaryExpression() + } + + optionalChain := false +L: + for { + switch self.token { + case token.PERIOD: + left = self.parseDotMember(left) + case token.LEFT_BRACKET: + left = self.parseBracketMember(left) + case token.LEFT_PARENTHESIS: + left = self.parseCallExpression(left) + case token.BACKTICK: + if optionalChain { + self.error(self.idx, "Invalid template literal on optional chain") + self.nextStatement() + return &ast.BadExpression{From: start, To: self.idx} + } + left = self.parseTaggedTemplateLiteral(left) + case token.QUESTION_DOT: + optionalChain = true + left = &ast.Optional{Expression: left} + + switch self.peek() { + case token.LEFT_BRACKET, token.LEFT_PARENTHESIS, token.BACKTICK: + self.next() + default: + left = self.parseDotMember(left) + } + default: + break L + } + } + + if optionalChain { + left = &ast.OptionalChain{Expression: left} + } + return left +} + +func (self *_parser) parseUpdateExpression() ast.Expression { + switch self.token { + case token.INCREMENT, token.DECREMENT: + tkn := self.token + idx := self.idx + self.next() + operand := self.parseUnaryExpression() + switch operand.(type) { + case *ast.Identifier, *ast.DotExpression, *ast.PrivateDotExpression, *ast.BracketExpression: + default: + self.error(idx, "Invalid left-hand side in assignment") + self.nextStatement() + return &ast.BadExpression{From: idx, To: self.idx} + } + return &ast.UnaryExpression{ + Operator: tkn, + Idx: idx, + Operand: operand, + } + default: + operand := self.parseLeftHandSideExpressionAllowCall() + if self.token == token.INCREMENT || self.token == token.DECREMENT { + // Make sure there is no line terminator here + if self.implicitSemicolon { + return operand + } + tkn := self.token + idx := self.idx + self.next() + switch operand.(type) { + case *ast.Identifier, *ast.DotExpression, *ast.PrivateDotExpression, *ast.BracketExpression: + default: + self.error(idx, "Invalid left-hand side in assignment") + self.nextStatement() + return &ast.BadExpression{From: idx, To: self.idx} + } + return &ast.UnaryExpression{ + Operator: tkn, + Idx: idx, + Operand: operand, + Postfix: true, + } + } + return operand + } +} + +func (self *_parser) parseUnaryExpression() ast.Expression { + + switch self.token { + case token.PLUS, token.MINUS, token.NOT, token.BITWISE_NOT: + fallthrough + case token.DELETE, token.VOID, token.TYPEOF: + tkn := self.token + idx := self.idx + self.next() + return &ast.UnaryExpression{ + Operator: tkn, + Idx: idx, + Operand: self.parseUnaryExpression(), + } + case token.AWAIT: + if self.scope.allowAwait { + idx := self.idx + self.next() + if !self.scope.inAsync { + self.errorUnexpectedToken(token.AWAIT) + return &ast.BadExpression{ + From: idx, + To: self.idx, + } + } + if self.scope.inFuncParams { + self.error(idx, "Illegal await-expression in formal parameters of async function") + } + return &ast.AwaitExpression{ + Await: idx, + Argument: self.parseUnaryExpression(), + } + } + } + + return self.parseUpdateExpression() +} + +func (self *_parser) parseExponentiationExpression() ast.Expression { + parenthesis := self.token == token.LEFT_PARENTHESIS + + left := self.parseUnaryExpression() + + if self.token == token.EXPONENT { + if !parenthesis { + if u, isUnary := left.(*ast.UnaryExpression); isUnary && u.Operator != token.INCREMENT && u.Operator != token.DECREMENT { + self.error(self.idx, "Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence") + } + } + for { + self.next() + left = &ast.BinaryExpression{ + Operator: token.EXPONENT, + Left: left, + Right: self.parseExponentiationExpression(), + } + if self.token != token.EXPONENT { + break + } + } + } + + return left +} + +func (self *_parser) parseMultiplicativeExpression() ast.Expression { + left := self.parseExponentiationExpression() + + for self.token == token.MULTIPLY || self.token == token.SLASH || + self.token == token.REMAINDER { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseExponentiationExpression(), + } + } + + return left +} + +func (self *_parser) parseAdditiveExpression() ast.Expression { + left := self.parseMultiplicativeExpression() + + for self.token == token.PLUS || self.token == token.MINUS { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseMultiplicativeExpression(), + } + } + + return left +} + +func (self *_parser) parseShiftExpression() ast.Expression { + left := self.parseAdditiveExpression() + + for self.token == token.SHIFT_LEFT || self.token == token.SHIFT_RIGHT || + self.token == token.UNSIGNED_SHIFT_RIGHT { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseAdditiveExpression(), + } + } + + return left +} + +func (self *_parser) parseRelationalExpression() ast.Expression { + if self.scope.allowIn && self.token == token.PRIVATE_IDENTIFIER { + left := &ast.PrivateIdentifier{ + Identifier: ast.Identifier{ + Idx: self.idx, + Name: self.parsedLiteral, + }, + } + self.next() + if self.token == token.IN { + self.next() + return &ast.BinaryExpression{ + Operator: self.token, + Left: left, + Right: self.parseShiftExpression(), + } + } + return left + } + left := self.parseShiftExpression() + + allowIn := self.scope.allowIn + self.scope.allowIn = true + defer func() { + self.scope.allowIn = allowIn + }() + + switch self.token { + case token.LESS, token.LESS_OR_EQUAL, token.GREATER, token.GREATER_OR_EQUAL: + tkn := self.token + self.next() + return &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseRelationalExpression(), + Comparison: true, + } + case token.INSTANCEOF: + tkn := self.token + self.next() + return &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseRelationalExpression(), + } + case token.IN: + if !allowIn { + return left + } + tkn := self.token + self.next() + return &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseRelationalExpression(), + } + } + + return left +} + +func (self *_parser) parseEqualityExpression() ast.Expression { + left := self.parseRelationalExpression() + + for self.token == token.EQUAL || self.token == token.NOT_EQUAL || + self.token == token.STRICT_EQUAL || self.token == token.STRICT_NOT_EQUAL { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseRelationalExpression(), + Comparison: true, + } + } + + return left +} + +func (self *_parser) parseBitwiseAndExpression() ast.Expression { + left := self.parseEqualityExpression() + + for self.token == token.AND { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseEqualityExpression(), + } + } + + return left +} + +func (self *_parser) parseBitwiseExclusiveOrExpression() ast.Expression { + left := self.parseBitwiseAndExpression() + + for self.token == token.EXCLUSIVE_OR { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseBitwiseAndExpression(), + } + } + + return left +} + +func (self *_parser) parseBitwiseOrExpression() ast.Expression { + left := self.parseBitwiseExclusiveOrExpression() + + for self.token == token.OR { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseBitwiseExclusiveOrExpression(), + } + } + + return left +} + +func (self *_parser) parseLogicalAndExpression() ast.Expression { + left := self.parseBitwiseOrExpression() + + for self.token == token.LOGICAL_AND { + tkn := self.token + self.next() + left = &ast.BinaryExpression{ + Operator: tkn, + Left: left, + Right: self.parseBitwiseOrExpression(), + } + } + + return left +} + +func isLogicalAndExpr(expr ast.Expression) bool { + if bexp, ok := expr.(*ast.BinaryExpression); ok && bexp.Operator == token.LOGICAL_AND { + return true + } + return false +} + +func (self *_parser) parseLogicalOrExpression() ast.Expression { + var idx file.Idx + parenthesis := self.token == token.LEFT_PARENTHESIS + left := self.parseLogicalAndExpression() + + if self.token == token.LOGICAL_OR || !parenthesis && isLogicalAndExpr(left) { + for { + switch self.token { + case token.LOGICAL_OR: + self.next() + left = &ast.BinaryExpression{ + Operator: token.LOGICAL_OR, + Left: left, + Right: self.parseLogicalAndExpression(), + } + case token.COALESCE: + idx = self.idx + goto mixed + default: + return left + } + } + } else { + for { + switch self.token { + case token.COALESCE: + idx = self.idx + self.next() + + parenthesis := self.token == token.LEFT_PARENTHESIS + right := self.parseLogicalAndExpression() + if !parenthesis && isLogicalAndExpr(right) { + goto mixed + } + + left = &ast.BinaryExpression{ + Operator: token.COALESCE, + Left: left, + Right: right, + } + case token.LOGICAL_OR: + idx = self.idx + goto mixed + default: + return left + } + } + } + +mixed: + self.error(idx, "Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses") + return left +} + +func (self *_parser) parseConditionalExpression() ast.Expression { + left := self.parseLogicalOrExpression() + + if self.token == token.QUESTION_MARK { + self.next() + allowIn := self.scope.allowIn + self.scope.allowIn = true + consequent := self.parseAssignmentExpression() + self.scope.allowIn = allowIn + self.expect(token.COLON) + return &ast.ConditionalExpression{ + Test: left, + Consequent: consequent, + Alternate: self.parseAssignmentExpression(), + } + } + + return left +} + +func (self *_parser) parseArrowFunction(start file.Idx, paramList *ast.ParameterList, async bool) ast.Expression { + self.expect(token.ARROW) + node := &ast.ArrowFunctionLiteral{ + Start: start, + ParameterList: paramList, + Async: async, + } + node.Body, node.DeclarationList = self.parseArrowFunctionBody(async) + node.Source = self.slice(start, node.Body.Idx1()) + return node +} + +func (self *_parser) parseSingleArgArrowFunction(start file.Idx, async bool) ast.Expression { + if async != self.scope.allowAwait { + self.scope.allowAwait = async + defer func() { + self.scope.allowAwait = !async + }() + } + self.tokenToBindingId() + if self.token != token.IDENTIFIER { + self.errorUnexpectedToken(self.token) + self.next() + return &ast.BadExpression{ + From: start, + To: self.idx, + } + } + + id := self.parseIdentifier() + + paramList := &ast.ParameterList{ + Opening: id.Idx, + Closing: id.Idx1(), + List: []*ast.Binding{{ + Target: id, + }}, + } + + return self.parseArrowFunction(start, paramList, async) +} + +func (self *_parser) parseAssignmentExpression() ast.Expression { + start := self.idx + parenthesis := false + async := false + var state parserState + switch self.token { + case token.LEFT_PARENTHESIS: + self.mark(&state) + parenthesis = true + case token.ASYNC: + tok := self.peek() + if self.isBindingId(tok) { + // async x => ... + self.next() + return self.parseSingleArgArrowFunction(start, true) + } else if tok == token.LEFT_PARENTHESIS { + self.mark(&state) + async = true + } + case token.YIELD: + if self.scope.allowYield { + return self.parseYieldExpression() + } + fallthrough + default: + self.tokenToBindingId() + } + left := self.parseConditionalExpression() + var operator token.Token + switch self.token { + case token.ASSIGN: + operator = self.token + case token.ADD_ASSIGN: + operator = token.PLUS + case token.SUBTRACT_ASSIGN: + operator = token.MINUS + case token.MULTIPLY_ASSIGN: + operator = token.MULTIPLY + case token.EXPONENT_ASSIGN: + operator = token.EXPONENT + case token.QUOTIENT_ASSIGN: + operator = token.SLASH + case token.REMAINDER_ASSIGN: + operator = token.REMAINDER + case token.AND_ASSIGN: + operator = token.AND + case token.OR_ASSIGN: + operator = token.OR + case token.EXCLUSIVE_OR_ASSIGN: + operator = token.EXCLUSIVE_OR + case token.SHIFT_LEFT_ASSIGN: + operator = token.SHIFT_LEFT + case token.SHIFT_RIGHT_ASSIGN: + operator = token.SHIFT_RIGHT + case token.UNSIGNED_SHIFT_RIGHT_ASSIGN: + operator = token.UNSIGNED_SHIFT_RIGHT + case token.ARROW: + var paramList *ast.ParameterList + if id, ok := left.(*ast.Identifier); ok { + paramList = &ast.ParameterList{ + Opening: id.Idx, + Closing: id.Idx1() - 1, + List: []*ast.Binding{{ + Target: id, + }}, + } + } else if parenthesis { + if seq, ok := left.(*ast.SequenceExpression); ok && len(self.errors) == 0 { + paramList = self.reinterpretSequenceAsArrowFuncParams(seq.Sequence) + } else { + self.restore(&state) + paramList = self.parseFunctionParameterList() + } + } else if async { + // async (x, y) => ... + if !self.scope.allowAwait { + self.scope.allowAwait = true + defer func() { + self.scope.allowAwait = false + }() + } + if _, ok := left.(*ast.CallExpression); ok { + self.restore(&state) + self.next() // skip "async" + paramList = self.parseFunctionParameterList() + } + } + if paramList == nil { + self.error(left.Idx0(), "Malformed arrow function parameter list") + return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()} + } + return self.parseArrowFunction(start, paramList, async) + } + + if operator != 0 { + idx := self.idx + self.next() + ok := false + switch l := left.(type) { + case *ast.Identifier, *ast.DotExpression, *ast.PrivateDotExpression, *ast.BracketExpression: + ok = true + case *ast.ArrayLiteral: + if !parenthesis && operator == token.ASSIGN { + left = self.reinterpretAsArrayAssignmentPattern(l) + ok = true + } + case *ast.ObjectLiteral: + if !parenthesis && operator == token.ASSIGN { + left = self.reinterpretAsObjectAssignmentPattern(l) + ok = true + } + } + if ok { + return &ast.AssignExpression{ + Left: left, + Operator: operator, + Right: self.parseAssignmentExpression(), + } + } + self.error(left.Idx0(), "Invalid left-hand side in assignment") + self.nextStatement() + return &ast.BadExpression{From: idx, To: self.idx} + } + + return left +} + +func (self *_parser) parseYieldExpression() ast.Expression { + idx := self.expect(token.YIELD) + + if self.scope.inFuncParams { + self.error(idx, "Yield expression not allowed in formal parameter") + } + + node := &ast.YieldExpression{ + Yield: idx, + } + + if !self.implicitSemicolon && self.token == token.MULTIPLY { + node.Delegate = true + self.next() + } + + if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE && self.token != token.EOF { + var state parserState + self.mark(&state) + expr := self.parseAssignmentExpression() + if _, bad := expr.(*ast.BadExpression); bad { + expr = nil + self.restore(&state) + } + node.Argument = expr + } + + return node +} + +func (self *_parser) parseExpression() ast.Expression { + left := self.parseAssignmentExpression() + + if self.token == token.COMMA { + sequence := []ast.Expression{left} + for { + if self.token != token.COMMA { + break + } + self.next() + sequence = append(sequence, self.parseAssignmentExpression()) + } + return &ast.SequenceExpression{ + Sequence: sequence, + } + } + + return left +} + +func (self *_parser) checkComma(from, to file.Idx) { + if pos := strings.IndexByte(self.str[int(from)-self.base:int(to)-self.base], ','); pos >= 0 { + self.error(from+file.Idx(pos), "Comma is not allowed here") + } +} + +func (self *_parser) reinterpretAsArrayAssignmentPattern(left *ast.ArrayLiteral) ast.Expression { + value := left.Value + var rest ast.Expression + for i, item := range value { + if spread, ok := item.(*ast.SpreadElement); ok { + if i != len(value)-1 { + self.error(item.Idx0(), "Rest element must be last element") + return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()} + } + self.checkComma(spread.Expression.Idx1(), left.RightBracket) + rest = self.reinterpretAsDestructAssignTarget(spread.Expression) + value = value[:len(value)-1] + } else { + value[i] = self.reinterpretAsAssignmentElement(item) + } + } + return &ast.ArrayPattern{ + LeftBracket: left.LeftBracket, + RightBracket: left.RightBracket, + Elements: value, + Rest: rest, + } +} + +func (self *_parser) reinterpretArrayAssignPatternAsBinding(pattern *ast.ArrayPattern) *ast.ArrayPattern { + for i, item := range pattern.Elements { + pattern.Elements[i] = self.reinterpretAsDestructBindingTarget(item) + } + if pattern.Rest != nil { + pattern.Rest = self.reinterpretAsDestructBindingTarget(pattern.Rest) + } + return pattern +} + +func (self *_parser) reinterpretAsArrayBindingPattern(left *ast.ArrayLiteral) ast.BindingTarget { + value := left.Value + var rest ast.Expression + for i, item := range value { + if spread, ok := item.(*ast.SpreadElement); ok { + if i != len(value)-1 { + self.error(item.Idx0(), "Rest element must be last element") + return &ast.BadExpression{From: left.Idx0(), To: left.Idx1()} + } + self.checkComma(spread.Expression.Idx1(), left.RightBracket) + rest = self.reinterpretAsDestructBindingTarget(spread.Expression) + value = value[:len(value)-1] + } else { + value[i] = self.reinterpretAsBindingElement(item) + } + } + return &ast.ArrayPattern{ + LeftBracket: left.LeftBracket, + RightBracket: left.RightBracket, + Elements: value, + Rest: rest, + } +} + +func (self *_parser) parseArrayBindingPattern() ast.BindingTarget { + return self.reinterpretAsArrayBindingPattern(self.parseArrayLiteral()) +} + +func (self *_parser) parseObjectBindingPattern() ast.BindingTarget { + return self.reinterpretAsObjectBindingPattern(self.parseObjectLiteral()) +} + +func (self *_parser) reinterpretArrayObjectPatternAsBinding(pattern *ast.ObjectPattern) *ast.ObjectPattern { + for _, prop := range pattern.Properties { + if keyed, ok := prop.(*ast.PropertyKeyed); ok { + keyed.Value = self.reinterpretAsBindingElement(keyed.Value) + } + } + if pattern.Rest != nil { + pattern.Rest = self.reinterpretAsBindingRestElement(pattern.Rest) + } + return pattern +} + +func (self *_parser) reinterpretAsObjectBindingPattern(expr *ast.ObjectLiteral) ast.BindingTarget { + var rest ast.Expression + value := expr.Value + for i, prop := range value { + ok := false + switch prop := prop.(type) { + case *ast.PropertyKeyed: + if prop.Kind == ast.PropertyKindValue { + prop.Value = self.reinterpretAsBindingElement(prop.Value) + ok = true + } + case *ast.PropertyShort: + ok = true + case *ast.SpreadElement: + if i != len(expr.Value)-1 { + self.error(prop.Idx0(), "Rest element must be last element") + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} + } + // TODO make sure there is no trailing comma + rest = self.reinterpretAsBindingRestElement(prop.Expression) + value = value[:i] + ok = true + } + if !ok { + self.error(prop.Idx0(), "Invalid destructuring binding target") + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} + } + } + return &ast.ObjectPattern{ + LeftBrace: expr.LeftBrace, + RightBrace: expr.RightBrace, + Properties: value, + Rest: rest, + } +} + +func (self *_parser) reinterpretAsObjectAssignmentPattern(l *ast.ObjectLiteral) ast.Expression { + var rest ast.Expression + value := l.Value + for i, prop := range value { + ok := false + switch prop := prop.(type) { + case *ast.PropertyKeyed: + if prop.Kind == ast.PropertyKindValue { + prop.Value = self.reinterpretAsAssignmentElement(prop.Value) + ok = true + } + case *ast.PropertyShort: + ok = true + case *ast.SpreadElement: + if i != len(l.Value)-1 { + self.error(prop.Idx0(), "Rest element must be last element") + return &ast.BadExpression{From: l.Idx0(), To: l.Idx1()} + } + // TODO make sure there is no trailing comma + rest = prop.Expression + value = value[:i] + ok = true + } + if !ok { + self.error(prop.Idx0(), "Invalid destructuring assignment target") + return &ast.BadExpression{From: l.Idx0(), To: l.Idx1()} + } + } + return &ast.ObjectPattern{ + LeftBrace: l.LeftBrace, + RightBrace: l.RightBrace, + Properties: value, + Rest: rest, + } +} + +func (self *_parser) reinterpretAsAssignmentElement(expr ast.Expression) ast.Expression { + switch expr := expr.(type) { + case *ast.AssignExpression: + if expr.Operator == token.ASSIGN { + expr.Left = self.reinterpretAsDestructAssignTarget(expr.Left) + return expr + } else { + self.error(expr.Idx0(), "Invalid destructuring assignment target") + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} + } + default: + return self.reinterpretAsDestructAssignTarget(expr) + } +} + +func (self *_parser) reinterpretAsBindingElement(expr ast.Expression) ast.Expression { + switch expr := expr.(type) { + case *ast.AssignExpression: + if expr.Operator == token.ASSIGN { + expr.Left = self.reinterpretAsDestructBindingTarget(expr.Left) + return expr + } else { + self.error(expr.Idx0(), "Invalid destructuring assignment target") + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} + } + default: + return self.reinterpretAsDestructBindingTarget(expr) + } +} + +func (self *_parser) reinterpretAsBinding(expr ast.Expression) *ast.Binding { + switch expr := expr.(type) { + case *ast.AssignExpression: + if expr.Operator == token.ASSIGN { + return &ast.Binding{ + Target: self.reinterpretAsDestructBindingTarget(expr.Left), + Initializer: expr.Right, + } + } else { + self.error(expr.Idx0(), "Invalid destructuring assignment target") + return &ast.Binding{ + Target: &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()}, + } + } + default: + return &ast.Binding{ + Target: self.reinterpretAsDestructBindingTarget(expr), + } + } +} + +func (self *_parser) reinterpretAsDestructAssignTarget(item ast.Expression) ast.Expression { + switch item := item.(type) { + case nil: + return nil + case *ast.ArrayLiteral: + return self.reinterpretAsArrayAssignmentPattern(item) + case *ast.ObjectLiteral: + return self.reinterpretAsObjectAssignmentPattern(item) + case ast.Pattern, *ast.Identifier, *ast.DotExpression, *ast.PrivateDotExpression, *ast.BracketExpression: + return item + } + self.error(item.Idx0(), "Invalid destructuring assignment target") + return &ast.BadExpression{From: item.Idx0(), To: item.Idx1()} +} + +func (self *_parser) reinterpretAsDestructBindingTarget(item ast.Expression) ast.BindingTarget { + switch item := item.(type) { + case nil: + return nil + case *ast.ArrayPattern: + return self.reinterpretArrayAssignPatternAsBinding(item) + case *ast.ObjectPattern: + return self.reinterpretArrayObjectPatternAsBinding(item) + case *ast.ArrayLiteral: + return self.reinterpretAsArrayBindingPattern(item) + case *ast.ObjectLiteral: + return self.reinterpretAsObjectBindingPattern(item) + case *ast.Identifier: + if !self.scope.allowAwait || item.Name != "await" { + return item + } + } + self.error(item.Idx0(), "Invalid destructuring binding target") + return &ast.BadExpression{From: item.Idx0(), To: item.Idx1()} +} + +func (self *_parser) reinterpretAsBindingRestElement(expr ast.Expression) ast.Expression { + if _, ok := expr.(*ast.Identifier); ok { + return expr + } + self.error(expr.Idx0(), "Invalid binding rest") + return &ast.BadExpression{From: expr.Idx0(), To: expr.Idx1()} +} diff --git a/pkg/xscript/engine/parser/lexer.go b/pkg/xscript/engine/parser/lexer.go new file mode 100644 index 0000000..cc4467d --- /dev/null +++ b/pkg/xscript/engine/parser/lexer.go @@ -0,0 +1,1179 @@ +package parser + +import ( + "errors" + "fmt" + "strconv" + "strings" + "unicode" + "unicode/utf16" + "unicode/utf8" + + "golang.org/x/text/unicode/rangetable" + + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +var ( + unicodeRangeIdNeg = rangetable.Merge(unicode.Pattern_Syntax, unicode.Pattern_White_Space) + unicodeRangeIdStartPos = rangetable.Merge(unicode.Letter, unicode.Nl, unicode.Other_ID_Start) + unicodeRangeIdContPos = rangetable.Merge(unicodeRangeIdStartPos, unicode.Mn, unicode.Mc, unicode.Nd, unicode.Pc, unicode.Other_ID_Continue) +) + +func isDecimalDigit(chr rune) bool { + return '0' <= chr && chr <= '9' +} + +func IsIdentifier(s string) bool { + if s == "" { + return false + } + r, size := utf8.DecodeRuneInString(s) + if !isIdentifierStart(r) { + return false + } + for _, r := range s[size:] { + if !isIdentifierPart(r) { + return false + } + } + return true +} + +func digitValue(chr rune) int { + switch { + case '0' <= chr && chr <= '9': + return int(chr - '0') + case 'a' <= chr && chr <= 'f': + return int(chr - 'a' + 10) + case 'A' <= chr && chr <= 'F': + return int(chr - 'A' + 10) + } + return 16 // Larger than any legal digit value +} + +func isDigit(chr rune, base int) bool { + return digitValue(chr) < base +} + +func isIdStartUnicode(r rune) bool { + return unicode.Is(unicodeRangeIdStartPos, r) && !unicode.Is(unicodeRangeIdNeg, r) +} + +func isIdPartUnicode(r rune) bool { + return unicode.Is(unicodeRangeIdContPos, r) && !unicode.Is(unicodeRangeIdNeg, r) || r == '\u200C' || r == '\u200D' +} + +func isIdentifierStart(chr rune) bool { + return chr == '$' || chr == '_' || chr == '\\' || + 'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' || + chr >= utf8.RuneSelf && isIdStartUnicode(chr) +} + +func isIdentifierPart(chr rune) bool { + return chr == '$' || chr == '_' || chr == '\\' || + 'a' <= chr && chr <= 'z' || 'A' <= chr && chr <= 'Z' || + '0' <= chr && chr <= '9' || + chr >= utf8.RuneSelf && isIdPartUnicode(chr) +} + +func (self *_parser) scanIdentifier() (string, unistring.String, bool, string) { + offset := self.chrOffset + hasEscape := false + isUnicode := false + length := 0 + for isIdentifierPart(self.chr) { + r := self.chr + length++ + if r == '\\' { + hasEscape = true + distance := self.chrOffset - offset + self.read() + if self.chr != 'u' { + return "", "", false, fmt.Sprintf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) + } + var value rune + if self._peek() == '{' { + self.read() + value = -1 + for value <= utf8.MaxRune { + self.read() + if self.chr == '}' { + break + } + decimal, ok := hex2decimal(byte(self.chr)) + if !ok { + return "", "", false, "Invalid Unicode escape sequence" + } + if value == -1 { + value = decimal + } else { + value = value<<4 | decimal + } + } + if value == -1 { + return "", "", false, "Invalid Unicode escape sequence" + } + } else { + for j := 0; j < 4; j++ { + self.read() + decimal, ok := hex2decimal(byte(self.chr)) + if !ok { + return "", "", false, fmt.Sprintf("Invalid identifier escape character: %c (%s)", self.chr, string(self.chr)) + } + value = value<<4 | decimal + } + } + if value == '\\' { + return "", "", false, fmt.Sprintf("Invalid identifier escape value: %c (%s)", value, string(value)) + } else if distance == 0 { + if !isIdentifierStart(value) { + return "", "", false, fmt.Sprintf("Invalid identifier escape value: %c (%s)", value, string(value)) + } + } else if distance > 0 { + if !isIdentifierPart(value) { + return "", "", false, fmt.Sprintf("Invalid identifier escape value: %c (%s)", value, string(value)) + } + } + r = value + } + if r >= utf8.RuneSelf { + isUnicode = true + if r > 0xFFFF { + length++ + } + } + self.read() + } + + literal := self.str[offset:self.chrOffset] + var parsed unistring.String + if hasEscape || isUnicode { + var err string + // TODO strict + parsed, err = parseStringLiteral(literal, length, isUnicode, false) + if err != "" { + return "", "", false, err + } + } else { + parsed = unistring.String(literal) + } + + return literal, parsed, hasEscape, "" +} + +// 7.2 +func isLineWhiteSpace(chr rune) bool { + switch chr { + case '\u0009', '\u000b', '\u000c', '\u0020', '\u00a0', '\ufeff': + return true + case '\u000a', '\u000d', '\u2028', '\u2029': + return false + case '\u0085': + return false + } + return unicode.IsSpace(chr) +} + +// 7.3 +func isLineTerminator(chr rune) bool { + switch chr { + case '\u000a', '\u000d', '\u2028', '\u2029': + return true + } + return false +} + +type parserState struct { + idx file.Idx + tok token.Token + literal string + parsedLiteral unistring.String + implicitSemicolon, insertSemicolon bool + chr rune + chrOffset, offset int + errorCount int +} + +func (self *_parser) mark(state *parserState) *parserState { + if state == nil { + state = &parserState{} + } + state.idx, state.tok, state.literal, state.parsedLiteral, state.implicitSemicolon, state.insertSemicolon, state.chr, state.chrOffset, state.offset = + self.idx, self.token, self.literal, self.parsedLiteral, self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset + + state.errorCount = len(self.errors) + return state +} + +func (self *_parser) restore(state *parserState) { + self.idx, self.token, self.literal, self.parsedLiteral, self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset = + state.idx, state.tok, state.literal, state.parsedLiteral, state.implicitSemicolon, state.insertSemicolon, state.chr, state.chrOffset, state.offset + self.errors = self.errors[:state.errorCount] +} + +func (self *_parser) peek() token.Token { + implicitSemicolon, insertSemicolon, chr, chrOffset, offset := self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset + tok, _, _, _ := self.scan() + self.implicitSemicolon, self.insertSemicolon, self.chr, self.chrOffset, self.offset = implicitSemicolon, insertSemicolon, chr, chrOffset, offset + return tok +} + +func (self *_parser) scan() (tkn token.Token, literal string, parsedLiteral unistring.String, idx file.Idx) { + + self.implicitSemicolon = false + + for { + self.skipWhiteSpace() + + idx = self.idxOf(self.chrOffset) + insertSemicolon := false + + switch chr := self.chr; { + case isIdentifierStart(chr): + var err string + var hasEscape bool + literal, parsedLiteral, hasEscape, err = self.scanIdentifier() + if err != "" { + tkn = token.ILLEGAL + break + } + if len(parsedLiteral) > 1 { + // Keywords are longer than 1 character, avoid lookup otherwise + var strict bool + tkn, strict = token.IsKeyword(string(parsedLiteral)) + if hasEscape { + self.insertSemicolon = true + if tkn == 0 || self.isBindingId(tkn) { + tkn = token.IDENTIFIER + } else { + tkn = token.ESCAPED_RESERVED_WORD + } + return + } + switch tkn { + case 0: // Not a keyword + // no-op + case token.KEYWORD: + if strict { + // TODO If strict and in strict mode, then this is not a break + break + } + return + + case + token.BOOLEAN, + token.NULL, + token.THIS, + token.BREAK, + token.THROW, // A newline after a throw is not allowed, but we need to detect it + token.YIELD, + token.RETURN, + token.CONTINUE, + token.DEBUGGER: + self.insertSemicolon = true + return + + case token.ASYNC: + // async only has special meaning if not followed by a LineTerminator + if self.skipWhiteSpaceCheckLineTerminator() { + self.insertSemicolon = true + tkn = token.IDENTIFIER + } + return + default: + return + + } + } + self.insertSemicolon = true + tkn = token.IDENTIFIER + return + case '0' <= chr && chr <= '9': + self.insertSemicolon = true + tkn, literal = self.scanNumericLiteral(false) + return + default: + self.read() + switch chr { + case -1: + if self.insertSemicolon { + self.insertSemicolon = false + self.implicitSemicolon = true + } + tkn = token.EOF + case '\r', '\n', '\u2028', '\u2029': + self.insertSemicolon = false + self.implicitSemicolon = true + continue + case ':': + tkn = token.COLON + case '.': + if digitValue(self.chr) < 10 { + insertSemicolon = true + tkn, literal = self.scanNumericLiteral(true) + } else { + if self.chr == '.' { + self.read() + if self.chr == '.' { + self.read() + tkn = token.ELLIPSIS + } else { + tkn = token.ILLEGAL + } + } else { + tkn = token.PERIOD + } + } + case ',': + tkn = token.COMMA + case ';': + tkn = token.SEMICOLON + case '(': + tkn = token.LEFT_PARENTHESIS + case ')': + tkn = token.RIGHT_PARENTHESIS + insertSemicolon = true + case '[': + tkn = token.LEFT_BRACKET + case ']': + tkn = token.RIGHT_BRACKET + insertSemicolon = true + case '{': + tkn = token.LEFT_BRACE + case '}': + tkn = token.RIGHT_BRACE + insertSemicolon = true + case '+': + tkn = self.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) + if tkn == token.DECREMENT { + insertSemicolon = true + } + case '*': + if self.chr == '*' { + self.read() + tkn = self.switch2(token.EXPONENT, token.EXPONENT_ASSIGN) + } else { + tkn = self.switch2(token.MULTIPLY, token.MULTIPLY_ASSIGN) + } + case '/': + if self.chr == '/' { + self.skipSingleLineComment() + continue + } else if self.chr == '*' { + if self.skipMultiLineComment() { + self.insertSemicolon = false + self.implicitSemicolon = true + } + continue + } else { + // Could be division, could be RegExp literal + tkn = self.switch2(token.SLASH, token.QUOTIENT_ASSIGN) + insertSemicolon = true + } + case '%': + tkn = self.switch2(token.REMAINDER, token.REMAINDER_ASSIGN) + case '^': + tkn = self.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) + 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) + case '=': + if self.chr == '>' { + self.read() + if self.implicitSemicolon { + tkn = token.ILLEGAL + } else { + tkn = token.ARROW + } + } else { + tkn = self.switch2(token.ASSIGN, token.EQUAL) + if tkn == token.EQUAL && self.chr == '=' { + self.read() + tkn = token.STRICT_EQUAL + } + } + case '!': + tkn = self.switch2(token.NOT, token.NOT_EQUAL) + if tkn == token.NOT_EQUAL && self.chr == '=' { + self.read() + tkn = token.STRICT_NOT_EQUAL + } + case '&': + tkn = self.switch3(token.AND, token.AND_ASSIGN, '&', token.LOGICAL_AND) + case '|': + tkn = self.switch3(token.OR, token.OR_ASSIGN, '|', token.LOGICAL_OR) + case '~': + tkn = token.BITWISE_NOT + case '?': + if self.chr == '.' && !isDecimalDigit(self._peek()) { + self.read() + tkn = token.QUESTION_DOT + } else if self.chr == '?' { + self.read() + tkn = token.COALESCE + } else { + tkn = token.QUESTION_MARK + } + case '"', '\'': + insertSemicolon = true + tkn = token.STRING + var err string + literal, parsedLiteral, err = self.scanString(self.chrOffset-1, true) + if err != "" { + tkn = token.ILLEGAL + } + case '`': + tkn = token.BACKTICK + case '#': + if self.chrOffset == 1 && self.chr == '!' { + self.skipSingleLineComment() + continue + } + + var err string + literal, parsedLiteral, _, err = self.scanIdentifier() + if err != "" || literal == "" { + tkn = token.ILLEGAL + break + } + self.insertSemicolon = true + tkn = token.PRIVATE_IDENTIFIER + return + default: + self.errorUnexpected(idx, chr) + tkn = token.ILLEGAL + } + } + self.insertSemicolon = insertSemicolon + return + } +} + +func (self *_parser) switch2(tkn0, tkn1 token.Token) token.Token { + if self.chr == '=' { + self.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() + return tkn1 + } + if self.chr == chr2 { + self.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() + return tkn1 + } + if self.chr == chr2 { + self.read() + if self.chr == '=' { + self.read() + return tkn3 + } + return tkn2 + } + 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() + return tkn1 + } + if self.chr == chr2 { + self.read() + if self.chr == '=' { + self.read() + return tkn3 + } + if self.chr == chr3 { + self.read() + if self.chr == '=' { + self.read() + return tkn5 + } + return tkn4 + } + return tkn2 + } + return tkn0 +} + +func (self *_parser) _peek() rune { + if self.offset < self.length { + return rune(self.str[self.offset]) + } + return -1 +} + +func (self *_parser) read() { + if self.offset < self.length { + self.chrOffset = self.offset + chr, width := rune(self.str[self.offset]), 1 + if chr >= utf8.RuneSelf { // !ASCII + chr, width = utf8.DecodeRuneInString(self.str[self.offset:]) + if chr == utf8.RuneError && width == 1 { + self.error(self.chrOffset, "Invalid UTF-8 character") + } + } + self.offset += width + self.chr = chr + } else { + self.chrOffset = self.length + self.chr = -1 // EOF + } +} + +func (self *_parser) skipSingleLineComment() { + for self.chr != -1 { + self.read() + if isLineTerminator(self.chr) { + return + } + } +} + +func (self *_parser) skipMultiLineComment() (hasLineTerminator bool) { + self.read() + for self.chr >= 0 { + chr := self.chr + if chr == '\r' || chr == '\n' || chr == '\u2028' || chr == '\u2029' { + hasLineTerminator = true + break + } + self.read() + if chr == '*' && self.chr == '/' { + self.read() + return + } + } + for self.chr >= 0 { + chr := self.chr + self.read() + if chr == '*' && self.chr == '/' { + self.read() + return + } + } + + self.errorUnexpected(0, self.chr) + return +} + +func (self *_parser) skipWhiteSpaceCheckLineTerminator() bool { + for { + switch self.chr { + case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff': + self.read() + continue + case '\r': + if self._peek() == '\n' { + self.read() + } + fallthrough + case '\u2028', '\u2029', '\n': + return true + } + if self.chr >= utf8.RuneSelf { + if unicode.IsSpace(self.chr) { + self.read() + continue + } + } + break + } + return false +} + +func (self *_parser) skipWhiteSpace() { + for { + switch self.chr { + case ' ', '\t', '\f', '\v', '\u00a0', '\ufeff': + self.read() + continue + case '\r': + if self._peek() == '\n' { + self.read() + } + fallthrough + case '\u2028', '\u2029', '\n': + if self.insertSemicolon { + return + } + self.read() + continue + } + if self.chr >= utf8.RuneSelf { + if unicode.IsSpace(self.chr) { + self.read() + continue + } + } + break + } +} + +func (self *_parser) scanMantissa(base int) { + for digitValue(self.chr) < base { + self.read() + } +} + +func (self *_parser) scanEscape(quote rune) (int, bool) { + + var length, base uint32 + chr := self.chr + switch chr { + case '0', '1', '2', '3', '4', '5', '6', '7': + // Octal: + length, base = 3, 8 + case 'a', 'b', 'f', 'n', 'r', 't', 'v', '\\', '"', '\'': + self.read() + return 1, false + case '\r': + self.read() + if self.chr == '\n' { + self.read() + return 2, false + } + return 1, false + case '\n': + self.read() + return 1, false + case '\u2028', '\u2029': + self.read() + return 1, true + case 'x': + self.read() + length, base = 2, 16 + case 'u': + self.read() + if self.chr == '{' { + self.read() + length, base = 0, 16 + } else { + length, base = 4, 16 + } + default: + self.read() // Always make progress + } + + if base > 0 { + var value uint32 + if length > 0 { + for ; length > 0 && self.chr != quote && self.chr >= 0; length-- { + digit := uint32(digitValue(self.chr)) + if digit >= base { + break + } + value = value*base + digit + self.read() + } + } else { + for self.chr != quote && self.chr >= 0 && value < utf8.MaxRune { + if self.chr == '}' { + self.read() + break + } + digit := uint32(digitValue(self.chr)) + if digit >= base { + break + } + value = value*base + digit + self.read() + } + } + chr = rune(value) + } + if chr >= utf8.RuneSelf { + if chr > 0xFFFF { + return 2, true + } + return 1, true + } + return 1, false +} + +func (self *_parser) scanString(offset int, parse bool) (literal string, parsed unistring.String, err string) { + // " ' / + quote := rune(self.str[offset]) + length := 0 + isUnicode := false + for self.chr != quote { + chr := self.chr + if chr == '\n' || chr == '\r' || chr < 0 { + goto newline + } + if quote == '/' && (self.chr == '\u2028' || self.chr == '\u2029') { + goto newline + } + self.read() + if chr == '\\' { + if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 { + if quote == '/' { + goto newline + } + self.scanNewline() + } else { + l, u := self.scanEscape(quote) + length += l + if u { + isUnicode = true + } + } + continue + } else if chr == '[' && quote == '/' { + // Allow a slash (/) in a bracket character class ([...]) + // TODO Fix this, this is hacky... + quote = -1 + } else if chr == ']' && quote == -1 { + quote = '/' + } + if chr >= utf8.RuneSelf { + isUnicode = true + if chr > 0xFFFF { + length++ + } + } + length++ + } + + // " ' / + self.read() + literal = self.str[offset:self.chrOffset] + if parse { + // TODO strict + parsed, err = parseStringLiteral(literal[1:len(literal)-1], length, isUnicode, false) + } + return + +newline: + self.scanNewline() + errStr := "String not terminated" + if quote == '/' { + errStr = "Invalid regular expression: missing /" + self.error(self.idxOf(offset), errStr) + } + return "", "", errStr +} + +func (self *_parser) scanNewline() { + if self.chr == '\u2028' || self.chr == '\u2029' { + self.read() + return + } + if self.chr == '\r' { + self.read() + if self.chr != '\n' { + return + } + } + self.read() +} + +func (self *_parser) parseTemplateCharacters() (literal string, parsed unistring.String, finished bool, parseErr, err string) { + offset := self.chrOffset + var end int + length := 0 + isUnicode := false + hasCR := false + for { + chr := self.chr + if chr < 0 { + goto unterminated + } + self.read() + if chr == '`' { + finished = true + end = self.chrOffset - 1 + break + } + if chr == '\\' { + if self.chr == '\n' || self.chr == '\r' || self.chr == '\u2028' || self.chr == '\u2029' || self.chr < 0 { + if self.chr == '\r' { + hasCR = true + } + self.scanNewline() + } else { + if self.chr == '8' || self.chr == '9' { + if parseErr == "" { + parseErr = "\\8 and \\9 are not allowed in template strings." + } + } + l, u := self.scanEscape('`') + length += l + if u { + isUnicode = true + } + } + continue + } + if chr == '$' && self.chr == '{' { + self.read() + end = self.chrOffset - 2 + break + } + if chr >= utf8.RuneSelf { + isUnicode = true + if chr > 0xFFFF { + length++ + } + } else if chr == '\r' { + hasCR = true + if self.chr == '\n' { + length-- + } + } + length++ + } + literal = self.str[offset:end] + if hasCR { + literal = normaliseCRLF(literal) + } + if parseErr == "" { + parsed, parseErr = parseStringLiteral(literal, length, isUnicode, true) + } + self.insertSemicolon = true + return +unterminated: + err = err_UnexpectedEndOfInput + finished = true + return +} + +func normaliseCRLF(s string) string { + var buf strings.Builder + buf.Grow(len(s)) + for i := 0; i < len(s); i++ { + if s[i] == '\r' { + buf.WriteByte('\n') + if i < len(s)-1 && s[i+1] == '\n' { + i++ + } + } else { + buf.WriteByte(s[i]) + } + } + return buf.String() +} + +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 parseNumberLiteral(literal string) (value any, err error) { + // TODO Is Uint okay? What about -MAX_UINT + value, err = strconv.ParseInt(literal, 0, 64) + if err == nil { + return + } + + parseIntErr := err // Save this first error, just in case + + value, err = strconv.ParseFloat(literal, 64) + if err == nil { + return + } else if err.(*strconv.NumError).Err == strconv.ErrRange { + // Infinity, etc. + return value, nil + } + + err = parseIntErr + + if err.(*strconv.NumError).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 + literal = literal[2:] + for _, chr := range literal { + digit := digitValue(chr) + if digit >= 16 { + goto error + } + value = value*16 + float64(digit) + } + return value, nil + } + } + +error: + return nil, errors.New("Illegal numeric literal") +} + +func parseStringLiteral(literal string, length int, unicode, strict bool) (unistring.String, string) { + var sb strings.Builder + var chars []uint16 + if unicode { + chars = make([]uint16, 1, length+1) + chars[0] = unistring.BOM + } else { + sb.Grow(length) + } + str := literal + for len(str) > 0 { + switch chr := str[0]; { + // We do not explicitly handle the case of the quote + // value, which can be: " ' / + // This assumes we're already passed a partially well-formed literal + case chr >= utf8.RuneSelf: + chr, size := utf8.DecodeRuneInString(str) + if chr <= 0xFFFF { + chars = append(chars, uint16(chr)) + } else { + first, second := utf16.EncodeRune(chr) + chars = append(chars, uint16(first), uint16(second)) + } + str = str[size:] + continue + case chr != '\\': + if unicode { + chars = append(chars, uint16(chr)) + } else { + sb.WriteByte(chr) + } + str = str[1:] + continue + } + + if len(str) <= 1 { + panic("len(str) <= 1") + } + chr := str[1] + var value rune + if chr >= utf8.RuneSelf { + str = str[1:] + var size int + value, size = utf8.DecodeRuneInString(str) + str = str[size:] // \ + + if value == '\u2028' || value == '\u2029' { + continue + } + } else { + str = str[2:] // \ + switch chr { + case 'b': + value = '\b' + case 'f': + value = '\f' + case 'n': + value = '\n' + case 'r': + value = '\r' + case 't': + value = '\t' + case 'v': + value = '\v' + case 'x', 'u': + size := 0 + switch chr { + case 'x': + size = 2 + case 'u': + if str == "" || str[0] != '{' { + size = 4 + } + } + if size > 0 { + if len(str) < size { + return "", fmt.Sprintf("invalid escape: \\%s: len(%q) != %d", string(chr), str, size) + } + for j := 0; j < size; j++ { + decimal, ok := hex2decimal(str[j]) + if !ok { + return "", fmt.Sprintf("invalid escape: \\%s: %q", string(chr), str[:size]) + } + value = value<<4 | decimal + } + } else { + str = str[1:] + var val rune + value = -1 + for ; size < len(str); size++ { + if str[size] == '}' { + if size == 0 { + return "", fmt.Sprintf("invalid escape: \\%s", string(chr)) + } + size++ + value = val + break + } + decimal, ok := hex2decimal(str[size]) + if !ok { + return "", fmt.Sprintf("invalid escape: \\%s: %q", string(chr), str[:size+1]) + } + val = val<<4 | decimal + if val > utf8.MaxRune { + return "", fmt.Sprintf("undefined Unicode code-point: %q", str[:size+1]) + } + } + if value == -1 { + return "", fmt.Sprintf("unterminated \\u{: %q", str) + } + } + str = str[size:] + if chr == 'x' { + break + } + if value > utf8.MaxRune { + panic("value > utf8.MaxRune") + } + case '0': + if len(str) == 0 || '0' > str[0] || str[0] > '7' { + value = 0 + break + } + fallthrough + case '1', '2', '3', '4', '5', '6', '7': + if strict { + return "", "Octal escape sequences are not allowed in this context" + } + value = rune(chr) - '0' + j := 0 + for ; j < 2; j++ { + if len(str) < j+1 { + break + } + chr := str[j] + if '0' > chr || chr > '7' { + break + } + decimal := rune(str[j]) - '0' + value = (value << 3) | decimal + } + str = str[j:] + case '\\': + value = '\\' + case '\'', '"': + value = rune(chr) + case '\r': + if len(str) > 0 { + if str[0] == '\n' { + str = str[1:] + } + } + fallthrough + case '\n': + continue + default: + value = rune(chr) + } + } + if unicode { + if value <= 0xFFFF { + chars = append(chars, uint16(value)) + } else { + first, second := utf16.EncodeRune(value) + chars = append(chars, uint16(first), uint16(second)) + } + } else { + if value >= utf8.RuneSelf { + return "", "Unexpected unicode character" + } + sb.WriteByte(byte(value)) + } + } + + if unicode { + if len(chars) != length+1 { + panic(fmt.Errorf("unexpected unicode length while parsing '%s'", literal)) + } + return unistring.FromUtf16(chars), "" + } + if sb.Len() != length { + panic(fmt.Errorf("unexpected length while parsing '%s'", literal)) + } + return unistring.String(sb.String()), "" +} + +func (self *_parser) scanNumericLiteral(decimalPoint bool) (token.Token, string) { + + offset := self.chrOffset + tkn := token.NUMBER + + if decimalPoint { + offset-- + self.scanMantissa(10) + } else { + if self.chr == '0' { + self.read() + base := 0 + switch self.chr { + case 'x', 'X': + base = 16 + case 'o', 'O': + base = 8 + case 'b', 'B': + base = 2 + case '.', 'e', 'E': + // no-op + default: + // legacy octal + self.scanMantissa(8) + goto end + } + if base > 0 { + self.read() + if !isDigit(self.chr, base) { + return token.ILLEGAL, self.str[offset:self.chrOffset] + } + self.scanMantissa(base) + goto end + } + } else { + self.scanMantissa(10) + } + if self.chr == '.' { + self.read() + self.scanMantissa(10) + } + } + + if self.chr == 'e' || self.chr == 'E' { + self.read() + if self.chr == '-' || self.chr == '+' { + self.read() + } + if isDecimalDigit(self.chr) { + self.read() + self.scanMantissa(10) + } else { + return token.ILLEGAL, self.str[offset:self.chrOffset] + } + } +end: + if isIdentifierStart(self.chr) || isDecimalDigit(self.chr) { + return token.ILLEGAL, self.str[offset:self.chrOffset] + } + + return tkn, self.str[offset:self.chrOffset] +} diff --git a/pkg/xscript/engine/parser/lexer_test.go b/pkg/xscript/engine/parser/lexer_test.go new file mode 100644 index 0000000..09ced75 --- /dev/null +++ b/pkg/xscript/engine/parser/lexer_test.go @@ -0,0 +1,401 @@ +package parser + +import ( + "testing" + + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +func TestLexer(t *testing.T) { + tt(t, func() { + setup := func(src string) *_parser { + parser := newParser("", src) + return parser + } + + test := func(src string, test ...any) { + parser := setup(src) + for len(test) > 0 { + tkn, literal, _, idx := parser.scan() + if len(test) > 0 { + is(tkn, test[0].(token.Token)) + test = test[1:] + } + if len(test) > 0 { + is(literal, unistring.String(test[0].(string))) + test = test[1:] + } + if len(test) > 0 { + // FIXME terst, Fix this so that cast to file.Idx is not necessary? + is(idx, file.Idx(test[0].(int))) + test = test[1:] + } + } + } + + test("", + token.EOF, "", 1, + ) + + test("#!", + token.EOF, "", 3, + ) + + test("#!\n1", + token.NUMBER, "1", 4, + token.EOF, "", 5, + ) + + test("1", + token.NUMBER, "1", 1, + token.EOF, "", 2, + ) + + test(".0", + token.NUMBER, ".0", 1, + token.EOF, "", 3, + ) + + test("abc", + token.IDENTIFIER, "abc", 1, + token.EOF, "", 4, + ) + + test("abc(1)", + token.IDENTIFIER, "abc", 1, + token.LEFT_PARENTHESIS, "", 4, + token.NUMBER, "1", 5, + token.RIGHT_PARENTHESIS, "", 6, + token.EOF, "", 7, + ) + + test(".", + token.PERIOD, "", 1, + token.EOF, "", 2, + ) + + test("===.", + token.STRICT_EQUAL, "", 1, + token.PERIOD, "", 4, + token.EOF, "", 5, + ) + + test(">>>=.0", + token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1, + token.NUMBER, ".0", 5, + token.EOF, "", 7, + ) + + test(">>>=0.0.", + token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1, + token.NUMBER, "0.0", 5, + token.PERIOD, "", 8, + token.EOF, "", 9, + ) + + test("\"abc\"", + token.STRING, "\"abc\"", 1, + token.EOF, "", 6, + ) + + test("abc = //", + token.IDENTIFIER, "abc", 1, + token.ASSIGN, "", 5, + token.EOF, "", 9, + ) + + test("abc = 1 / 2", + token.IDENTIFIER, "abc", 1, + token.ASSIGN, "", 5, + token.NUMBER, "1", 7, + token.SLASH, "", 9, + token.NUMBER, "2", 11, + token.EOF, "", 12, + ) + + test("xyzzy = 'Nothing happens.'", + token.IDENTIFIER, "xyzzy", 1, + token.ASSIGN, "", 7, + token.STRING, "'Nothing happens.'", 9, + token.EOF, "", 27, + ) + + test("abc = !false", + token.IDENTIFIER, "abc", 1, + token.ASSIGN, "", 5, + token.NOT, "", 7, + token.BOOLEAN, "false", 8, + token.EOF, "", 13, + ) + + test("abc = !!true", + token.IDENTIFIER, "abc", 1, + token.ASSIGN, "", 5, + token.NOT, "", 7, + token.NOT, "", 8, + token.BOOLEAN, "true", 9, + token.EOF, "", 13, + ) + + test("abc *= 1", + token.IDENTIFIER, "abc", 1, + token.MULTIPLY_ASSIGN, "", 5, + token.NUMBER, "1", 8, + token.EOF, "", 9, + ) + + test("if 1 else", + token.IF, "if", 1, + token.NUMBER, "1", 4, + token.ELSE, "else", 6, + token.EOF, "", 10, + ) + + test("null", + token.NULL, "null", 1, + token.EOF, "", 5, + ) + + test(`"\u007a\x79\u000a\x78"`, + token.STRING, "\"\\u007a\\x79\\u000a\\x78\"", 1, + token.EOF, "", 23, + ) + + test(`"[First line \ +Second line \ + Third line\ +. ]" + `, + token.STRING, "\"[First line \\\nSecond line \\\n Third line\\\n. ]\"", 1, + token.EOF, "", 53, + ) + + test("/", + token.SLASH, "", 1, + token.EOF, "", 2, + ) + + test("var abc = \"abc\uFFFFabc\"", + token.VAR, "var", 1, + token.IDENTIFIER, "abc", 5, + token.ASSIGN, "", 9, + token.STRING, "\"abc\uFFFFabc\"", 11, + token.EOF, "", 22, + ) + + test(`'\t' === '\r'`, + token.STRING, "'\\t'", 1, + token.STRICT_EQUAL, "", 6, + token.STRING, "'\\r'", 10, + token.EOF, "", 14, + ) + + test(`var \u0024 = 1`, + token.VAR, "var", 1, + token.IDENTIFIER, "\\u0024", 5, + token.ASSIGN, "", 12, + token.NUMBER, "1", 14, + token.EOF, "", 15, + ) + + test("10e10000", + token.NUMBER, "10e10000", 1, + token.EOF, "", 9, + ) + + test(`var if var class`, + token.VAR, "var", 1, + token.IF, "if", 5, + token.VAR, "var", 8, + token.CLASS, "class", 12, + token.EOF, "", 17, + ) + + test(`-0`, + token.MINUS, "", 1, + token.NUMBER, "0", 2, + token.EOF, "", 3, + ) + + test(`.01`, + token.NUMBER, ".01", 1, + token.EOF, "", 4, + ) + + test(`.01e+2`, + token.NUMBER, ".01e+2", 1, + token.EOF, "", 7, + ) + + test(";", + token.SEMICOLON, "", 1, + token.EOF, "", 2, + ) + + test(";;", + token.SEMICOLON, "", 1, + token.SEMICOLON, "", 2, + token.EOF, "", 3, + ) + + test("//", + token.EOF, "", 3, + ) + + test(";;//", + token.SEMICOLON, "", 1, + token.SEMICOLON, "", 2, + token.EOF, "", 5, + ) + + test("1", + token.NUMBER, "1", 1, + ) + + test("12 123", + token.NUMBER, "12", 1, + token.NUMBER, "123", 4, + ) + + test("1.2 12.3", + token.NUMBER, "1.2", 1, + token.NUMBER, "12.3", 5, + ) + + test("/ /=", + token.SLASH, "", 1, + token.QUOTIENT_ASSIGN, "", 3, + ) + + test(`"abc"`, + token.STRING, `"abc"`, 1, + ) + + test(`'abc'`, + token.STRING, `'abc'`, 1, + ) + + test("++", + token.INCREMENT, "", 1, + ) + + test(">", + token.GREATER, "", 1, + ) + + test(">=", + token.GREATER_OR_EQUAL, "", 1, + ) + + test(">>", + token.SHIFT_RIGHT, "", 1, + ) + + test(">>=", + token.SHIFT_RIGHT_ASSIGN, "", 1, + ) + + test(">>>", + token.UNSIGNED_SHIFT_RIGHT, "", 1, + ) + + test(">>>=", + token.UNSIGNED_SHIFT_RIGHT_ASSIGN, "", 1, + ) + + test("1 \"abc\"", + token.NUMBER, "1", 1, + token.STRING, "\"abc\"", 3, + ) + + test(",", + token.COMMA, "", 1, + ) + + test("1, \"abc\"", + token.NUMBER, "1", 1, + token.COMMA, "", 2, + token.STRING, "\"abc\"", 4, + ) + + test("new abc(1, 3.14159);", + token.NEW, "new", 1, + token.IDENTIFIER, "abc", 5, + token.LEFT_PARENTHESIS, "", 8, + token.NUMBER, "1", 9, + token.COMMA, "", 10, + token.NUMBER, "3.14159", 12, + token.RIGHT_PARENTHESIS, "", 19, + token.SEMICOLON, "", 20, + ) + + test("1 == \"1\"", + token.NUMBER, "1", 1, + token.EQUAL, "", 3, + token.STRING, "\"1\"", 6, + ) + + test("1\n[]\n", + token.NUMBER, "1", 1, + token.LEFT_BRACKET, "", 3, + token.RIGHT_BRACKET, "", 4, + ) + + test("1\ufeff[]\ufeff", + token.NUMBER, "1", 1, + token.LEFT_BRACKET, "", 5, + token.RIGHT_BRACKET, "", 6, + ) + + test("x ?.30 : false", + token.IDENTIFIER, "x", 1, + token.QUESTION_MARK, "", 3, + token.NUMBER, ".30", 4, + token.COLON, "", 8, + token.BOOLEAN, "false", 10, + ) + + test("a\n?.b", + token.IDENTIFIER, "a", 1, + token.QUESTION_DOT, "", 3, + token.IDENTIFIER, "b", 5, + ) + + // ILLEGAL + + test(`3ea`, + token.ILLEGAL, "3e", 1, + token.IDENTIFIER, "a", 3, + token.EOF, "", 4, + ) + + test(`3in`, + token.ILLEGAL, "3", 1, + token.IN, "in", 2, + token.EOF, "", 4, + ) + + test("\"Hello\nWorld\"", + token.ILLEGAL, "", 1, + token.IDENTIFIER, "World", 8, + token.ILLEGAL, "", 13, + token.EOF, "", 14, + ) + + test("\u203f = 10", + token.ILLEGAL, "", 1, + token.ASSIGN, "", 5, + token.NUMBER, "10", 7, + token.EOF, "", 9, + ) + + test(`"\x0G"`, + token.ILLEGAL, "\"\\x0G\"", 1, + //token.STRING, "\"\\x0G\"", 1, + token.EOF, "", 7, + ) + + }) +} diff --git a/pkg/xscript/engine/parser/marshal_test.go b/pkg/xscript/engine/parser/marshal_test.go new file mode 100644 index 0000000..977be3a --- /dev/null +++ b/pkg/xscript/engine/parser/marshal_test.go @@ -0,0 +1,890 @@ +package parser + +import ( + "bytes" + "encoding/json" + "reflect" + "strings" + "testing" + + "pandax/pkg/xscript/engine/ast" +) + +func marshal(name string, children ...any) any { + if len(children) == 1 { + if name == "" { + return testMarshalNode(children[0]) + } + return map[string]any{ + name: children[0], + } + } + map_ := map[string]any{} + length := len(children) / 2 + for i := 0; i < length; i++ { + name := children[i*2].(string) + value := children[i*2+1] + map_[name] = value + } + if name == "" { + return map_ + } + return map[string]any{ + name: map_, + } +} + +func testMarshalNode(node any) any { + switch node := node.(type) { + + // Expression + + case *ast.ArrayLiteral: + return marshal("Array", testMarshalNode(node.Value)) + + case *ast.AssignExpression: + return marshal("Assign", + "Left", testMarshalNode(node.Left), + "Right", testMarshalNode(node.Right), + ) + + case *ast.BinaryExpression: + return marshal("BinaryExpression", + "Operator", node.Operator.String(), + "Left", testMarshalNode(node.Left), + "Right", testMarshalNode(node.Right), + ) + + case *ast.BooleanLiteral: + return marshal("Literal", node.Value) + + case *ast.CallExpression: + return marshal("Call", + "Callee", testMarshalNode(node.Callee), + "ArgumentList", testMarshalNode(node.ArgumentList), + ) + + case *ast.ConditionalExpression: + return marshal("Conditional", + "Test", testMarshalNode(node.Test), + "Consequent", testMarshalNode(node.Consequent), + "Alternate", testMarshalNode(node.Alternate), + ) + + case *ast.DotExpression: + return marshal("Dot", + "Left", testMarshalNode(node.Left), + "Member", node.Identifier.Name, + ) + + case *ast.NewExpression: + return marshal("New", + "Callee", testMarshalNode(node.Callee), + "ArgumentList", testMarshalNode(node.ArgumentList), + ) + + case *ast.NullLiteral: + return marshal("Literal", nil) + + case *ast.NumberLiteral: + return marshal("Literal", node.Value) + + case *ast.ObjectLiteral: + return marshal("Object", testMarshalNode(node.Value)) + + case *ast.RegExpLiteral: + return marshal("Literal", node.Literal) + + case *ast.StringLiteral: + return marshal("Literal", node.Literal) + + case *ast.Binding: + return marshal("Binding", "Target", testMarshalNode(node.Target), + "Initializer", testMarshalNode(node.Initializer)) + + // Statement + + case *ast.Program: + return testMarshalNode(node.Body) + + case *ast.BlockStatement: + return marshal("BlockStatement", testMarshalNode(node.List)) + + case *ast.EmptyStatement: + return "EmptyStatement" + + case *ast.ExpressionStatement: + return testMarshalNode(node.Expression) + + case *ast.ForInStatement: + return marshal("ForIn", + "Into", testMarshalNode(node.Into), + "Source", marshal("", node.Source), + "Body", marshal("", node.Body), + ) + + case *ast.FunctionLiteral: + return marshal("Function", testMarshalNode(node.Body)) + + case *ast.Identifier: + return marshal("Identifier", node.Name) + + case *ast.IfStatement: + if_ := marshal("", + "Test", testMarshalNode(node.Test), + "Consequent", testMarshalNode(node.Consequent), + ).(map[string]any) + if node.Alternate != nil { + if_["Alternate"] = testMarshalNode(node.Alternate) + } + return marshal("If", if_) + + case *ast.LabelledStatement: + return marshal("Label", + "Name", node.Label.Name, + "Statement", testMarshalNode(node.Statement), + ) + case *ast.PropertyKeyed: + return marshal("", + "Key", node.Key, + "Value", testMarshalNode(node.Value), + ) + + case *ast.ReturnStatement: + return marshal("Return", testMarshalNode(node.Argument)) + + case *ast.SequenceExpression: + return marshal("Sequence", testMarshalNode(node.Sequence)) + + case *ast.ThrowStatement: + return marshal("Throw", testMarshalNode(node.Argument)) + + case *ast.VariableStatement: + return marshal("Var", testMarshalNode(node.List)) + + // Special + case *ast.ForDeclaration: + return marshal("For-Into-Decl", testMarshalNode(node.Target)) + + case *ast.ForIntoVar: + return marshal("For-Into-Var", testMarshalNode(node.Binding)) + + } + + { + value := reflect.ValueOf(node) + if value.Kind() == reflect.Slice { + tmp0 := []any{} + for index := 0; index < value.Len(); index++ { + tmp0 = append(tmp0, testMarshalNode(value.Index(index).Interface())) + } + return tmp0 + } + } + + return nil +} + +func testMarshal(node any) string { + value, err := json.Marshal(testMarshalNode(node)) + if err != nil { + panic(err) + } + return string(value) +} + +func TestParserAST(t *testing.T) { + tt(t, func() { + + test := func(inputOutput string) { + match := matchBeforeAfterSeparator.FindStringIndex(inputOutput) + input := strings.TrimSpace(inputOutput[0:match[0]]) + wantOutput := strings.TrimSpace(inputOutput[match[1]:]) + _, program, err := testParse(input) + is(err, nil) + haveOutput := testMarshal(program) + tmp0, tmp1 := bytes.Buffer{}, bytes.Buffer{} + json.Indent(&tmp0, []byte(haveOutput), "\t\t", " ") + json.Indent(&tmp1, []byte(wantOutput), "\t\t", " ") + is("\n\t\t"+tmp0.String(), "\n\t\t"+tmp1.String()) + } + + test(` + --- +[] + `) + + test(` + ; + --- +[ + "EmptyStatement" +] + `) + + test(` + ;;; + --- +[ + "EmptyStatement", + "EmptyStatement", + "EmptyStatement" +] + `) + + test(` + 1; true; abc; "abc"; null; + --- +[ + { + "Literal": 1 + }, + { + "Literal": true + }, + { + "Identifier": "abc" + }, + { + "Literal": "\"abc\"" + }, + { + "Literal": null + } +] + `) + + test(` + { 1; null; 3.14159; ; } + --- +[ + { + "BlockStatement": [ + { + "Literal": 1 + }, + { + "Literal": null + }, + { + "Literal": 3.14159 + }, + "EmptyStatement" + ] + } +] + `) + + test(` + new abc(); + --- +[ + { + "New": { + "ArgumentList": [], + "Callee": { + "Identifier": "abc" + } + } + } +] + `) + + test(` + new abc(1, 3.14159) + --- +[ + { + "New": { + "ArgumentList": [ + { + "Literal": 1 + }, + { + "Literal": 3.14159 + } + ], + "Callee": { + "Identifier": "abc" + } + } + } +] + `) + + test(` + true ? false : true + --- +[ + { + "Conditional": { + "Alternate": { + "Literal": true + }, + "Consequent": { + "Literal": false + }, + "Test": { + "Literal": true + } + } + } +] + `) + + test(` + true || false + --- +[ + { + "BinaryExpression": { + "Left": { + "Literal": true + }, + "Operator": "||", + "Right": { + "Literal": false + } + } + } +] + `) + + test(` + 0 + { abc: true } + --- +[ + { + "BinaryExpression": { + "Left": { + "Literal": 0 + }, + "Operator": "+", + "Right": { + "Object": [ + { + "Key": { + "Idx": 7, + "Literal": "abc", + "Value": "abc" + }, + "Value": { + "Literal": true + } + } + ] + } + } + } +] + `) + + test(` + 1 == "1" + --- +[ + { + "BinaryExpression": { + "Left": { + "Literal": 1 + }, + "Operator": "==", + "Right": { + "Literal": "\"1\"" + } + } + } +] + `) + + test(` + abc(1) + --- +[ + { + "Call": { + "ArgumentList": [ + { + "Literal": 1 + } + ], + "Callee": { + "Identifier": "abc" + } + } + } +] + `) + + test(` + Math.pow(3, 2) + --- +[ + { + "Call": { + "ArgumentList": [ + { + "Literal": 3 + }, + { + "Literal": 2 + } + ], + "Callee": { + "Dot": { + "Left": { + "Identifier": "Math" + }, + "Member": "pow" + } + } + } + } +] + `) + + test(` + 1, 2, 3 + --- +[ + { + "Sequence": [ + { + "Literal": 1 + }, + { + "Literal": 2 + }, + { + "Literal": 3 + } + ] + } +] + `) + + test(` + / abc /gim; + --- +[ + { + "Literal": "/ abc /gim" + } +] + `) + + test(` + if (0) + 1; + --- +[ + { + "If": { + "Consequent": { + "Literal": 1 + }, + "Test": { + "Literal": 0 + } + } + } +] + `) + + test(` + 0+function(){ + return; + } + --- +[ + { + "BinaryExpression": { + "Left": { + "Literal": 0 + }, + "Operator": "+", + "Right": { + "Function": { + "BlockStatement": [ + { + "Return": null + } + ] + } + } + } + } +] + `) + + test(` + xyzzy // Ignore it + // Ignore this + // And this + /* And all.. + + + + ... of this! + */ + "Nothing happens." + // And finally this + --- +[ + { + "Identifier": "xyzzy" + }, + { + "Literal": "\"Nothing happens.\"" + } +] + `) + + test(` + ((x & (x = 1)) !== 0) + --- +[ + { + "BinaryExpression": { + "Left": { + "BinaryExpression": { + "Left": { + "Identifier": "x" + }, + "Operator": "\u0026", + "Right": { + "Assign": { + "Left": { + "Identifier": "x" + }, + "Right": { + "Literal": 1 + } + } + } + } + }, + "Operator": "!==", + "Right": { + "Literal": 0 + } + } + } +] + `) + + test(` + { abc: 'def' } + --- +[ + { + "BlockStatement": [ + { + "Label": { + "Name": "abc", + "Statement": { + "Literal": "'def'" + } + } + } + ] + } +] + `) + + test(` + // This is not an object, this is a string literal with a label! + ({ abc: 'def' }) + --- +[ + { + "Object": [ + { + "Key": { + "Idx": 77, + "Literal": "abc", + "Value": "abc" + }, + "Value": { + "Literal": "'def'" + } + } + ] + } +] + `) + + test(` + [,] + --- +[ + { + "Array": [ + null + ] + } +] + `) + + test(` + [,,] + --- +[ + { + "Array": [ + null, + null + ] + } +] + `) + + test(` + ({ get abc() {} }) + --- +[ + { + "Object": [ + { + "Key": { + "Idx": 8, + "Literal": "abc", + "Value": "abc" + }, + "Value": { + "Function": { + "BlockStatement": [] + } + } + } + ] + } +] + `) + + test(` + /abc/.source + --- +[ + { + "Dot": { + "Left": { + "Literal": "/abc/" + }, + "Member": "source" + } + } +] + `) + + test(` + xyzzy + + throw new TypeError("Nothing happens.") + --- +[ + { + "Identifier": "xyzzy" + }, + { + "Throw": { + "New": { + "ArgumentList": [ + { + "Literal": "\"Nothing happens.\"" + } + ], + "Callee": { + "Identifier": "TypeError" + } + } + } + } +] + `) + + // When run, this will call a type error to be thrown + // This is essentially the same as: + // + // var abc = 1(function(){})() + // + test(` + var abc = 1 + (function(){ + })() + --- +[ + { + "Var": [ + { + "Binding": { + "Initializer": { + "Call": { + "ArgumentList": [], + "Callee": { + "Call": { + "ArgumentList": [ + { + "Function": { + "BlockStatement": [] + } + } + ], + "Callee": { + "Literal": 1 + } + } + } + } + }, + "Target": { + "Identifier": "abc" + } + } + } + ] + } +] + `) + + test(` + "use strict" + --- +[ + { + "Literal": "\"use strict\"" + } +] + `) + + test(` + "use strict" + abc = 1 + 2 + 11 + --- +[ + { + "Literal": "\"use strict\"" + }, + { + "Assign": { + "Left": { + "Identifier": "abc" + }, + "Right": { + "BinaryExpression": { + "Left": { + "BinaryExpression": { + "Left": { + "Literal": 1 + }, + "Operator": "+", + "Right": { + "Literal": 2 + } + } + }, + "Operator": "+", + "Right": { + "Literal": 11 + } + } + } + } + } +] + `) + + test(` + abc = function() { 'use strict' } + --- +[ + { + "Assign": { + "Left": { + "Identifier": "abc" + }, + "Right": { + "Function": { + "BlockStatement": [ + { + "Literal": "'use strict'" + } + ] + } + } + } + } +] + `) + + test(` + for (var abc in def) { + } + --- +[ + { + "ForIn": { + "Body": { + "BlockStatement": [] + }, + "Into": { + "For-Into-Var": { + "Binding": { + "Initializer": null, + "Target": { + "Identifier": "abc" + } + } + } + }, + "Source": { + "Identifier": "def" + } + } + } +] + `) + + test(` + abc = { + '"': "'", + "'": '"', + } + --- +[ + { + "Assign": { + "Left": { + "Identifier": "abc" + }, + "Right": { + "Object": [ + { + "Key": { + "Idx": 21, + "Literal": "'\"'", + "Value": "\"" + }, + "Value": { + "Literal": "\"'\"" + } + }, + { + "Key": { + "Idx": 43, + "Literal": "\"'\"", + "Value": "'" + }, + "Value": { + "Literal": "'\"'" + } + } + ] + } + } + } +] + `) + + }) +} diff --git a/pkg/xscript/engine/parser/parser.go b/pkg/xscript/engine/parser/parser.go new file mode 100644 index 0000000..dc340d3 --- /dev/null +++ b/pkg/xscript/engine/parser/parser.go @@ -0,0 +1,268 @@ +/* +Package parser implements a parser for JavaScript. + + import ( + "pandax/pkg/xscript/service/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. +*/ +package parser + +import ( + "bytes" + "errors" + "io" + "os" + + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +// A Mode value is a set of flags (or 0). They control optional parser functionality. +type Mode uint + +const ( + IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking) +) + +type options struct { + disableSourceMaps bool + sourceMapLoader func(path string) ([]byte, error) +} + +// Option represents one of the options for the parser to use in the Parse methods. Currently supported are: +// WithDisableSourceMaps and WithSourceMapLoader. +type Option func(*options) + +// WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps +// are not in use. +func WithDisableSourceMaps(opts *options) { + opts.disableSourceMaps = true +} + +// WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a +// URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name +// of the file being parsed. Any error returned by the loader will fail the parsing. +// Note that setting this to nil does not disable source map support, there is a default loader which reads +// from the filesystem. Use WithDisableSourceMaps to disable source map support. +func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option { + return func(opts *options) { + opts.sourceMapLoader = loader + } +} + +type _parser struct { + str string + length int + base int + + chr rune // The current character + chrOffset int // The offset of current character + offset int // The offset after current character (may be greater than 1) + + idx file.Idx // The index of token + token token.Token // The token + literal string // The literal of the token, if any + parsedLiteral unistring.String + + scope *_scope + insertSemicolon bool // If we see a newline, then insert an implicit semicolon + implicitSemicolon bool // An implicit semicolon exists + + errors ErrorList + + recover struct { + // Scratch when trying to seek to the next statement, etc. + idx file.Idx + count int + } + + mode Mode + opts options + + file *file.File +} + +func _newParser(filename, src string, base int, opts ...Option) *_parser { + p := &_parser{ + chr: ' ', // This is set so we can start scanning by skipping whitespace + str: src, + length: len(src), + base: base, + file: file.NewFile(filename, src, base), + } + for _, opt := range opts { + opt(&p.opts) + } + return p +} + +func newParser(filename, src string) *_parser { + return _newParser(filename, src, 1) +} + +func ReadSource(filename string, src any) ([]byte, error) { + if src != nil { + switch src := src.(type) { + case string: + return []byte(src), nil + case []byte: + return src, nil + case *bytes.Buffer: + if src != nil { + return src.Bytes(), nil + } + case io.Reader: + var bfr bytes.Buffer + if _, err := io.Copy(&bfr, src); err != nil { + return nil, err + } + return bfr.Bytes(), nil + } + return nil, errors.New("invalid source") + } + return os.ReadFile(filename) +} + +// 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 ParseFile(fileSet *file.FileSet, filename string, src any, mode Mode, options ...Option) (*ast.Program, error) { + str, err := ReadSource(filename, src) + if err != nil { + return nil, err + } + { + str := string(str) + + base := 1 + if fileSet != nil { + base = fileSet.AddFile(filename, str) + } + + parser := _newParser(filename, str, base, options...) + parser.mode = mode + return parser.parse() + } +} + +// 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 ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) { + + src := "(function(" + parameterList + ") {\n" + body + "\n})" + + parser := _newParser("", src, 1, options...) + program, err := parser.parse() + if err != nil { + return nil, err + } + + return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil +} + +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] + } + + return "" +} + +func (self *_parser) parse() (*ast.Program, error) { + self.openScope() + defer self.closeScope() + self.next() + program := self.parseProgram() + if false { + self.errors.Sort() + } + return program, self.errors.Err() +} + +func (self *_parser) next() { + self.token, self.literal, self.parsedLiteral, self.idx = self.scan() +} + +func (self *_parser) optionalSemicolon() { + if self.token == token.SEMICOLON { + self.next() + return + } + + if self.implicitSemicolon { + self.implicitSemicolon = false + return + } + + if self.token != token.EOF && self.token != token.RIGHT_BRACE { + self.expect(token.SEMICOLON) + } +} + +func (self *_parser) semicolon() { + if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE { + if self.implicitSemicolon { + self.implicitSemicolon = false + return + } + + self.expect(token.SEMICOLON) + } +} + +func (self *_parser) idxOf(offset int) file.Idx { + return file.Idx(self.base + offset) +} + +func (self *_parser) expect(value token.Token) file.Idx { + idx := self.idx + if self.token != value { + self.errorUnexpectedToken(self.token) + } + self.next() + return idx +} + +func (self *_parser) position(idx file.Idx) file.Position { + return self.file.Position(int(idx) - self.base) +} diff --git a/pkg/xscript/engine/parser/parser_test.go b/pkg/xscript/engine/parser/parser_test.go new file mode 100644 index 0000000..7c83ba0 --- /dev/null +++ b/pkg/xscript/engine/parser/parser_test.go @@ -0,0 +1,1321 @@ +package parser + +import ( + "errors" + "regexp" + "strings" + "testing" + + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/token" + "pandax/pkg/xscript/engine/unistring" +) + +func firstErr(err error) error { + switch err := err.(type) { + case ErrorList: + return err[0] + } + return err +} + +var matchBeforeAfterSeparator = regexp.MustCompile(`(?m)^[ \t]*---$`) + +func testParse(src string) (parser *_parser, program *ast.Program, err error) { + defer func() { + if tmp := recover(); tmp != nil { + switch tmp := tmp.(type) { + case string: + if strings.HasPrefix(tmp, "SyntaxError:") { + parser = nil + program = nil + err = errors.New(tmp) + return + } + } + panic(tmp) + } + }() + parser = newParser("", src) + program, err = parser.parse() + return +} + +func TestParseFile(t *testing.T) { + tt(t, func() { + _, err := ParseFile(nil, "", `/abc/`, 0) + is(err, nil) + + _, err = ParseFile(nil, "", `/(?!def)abc/`, IgnoreRegExpErrors) + is(err, nil) + + _, err = ParseFile(nil, "", `/(?!def)abc/; return`, IgnoreRegExpErrors) + is(err, "(anonymous): Line 1:15 Illegal return statement") + }) +} + +func TestParseFunction(t *testing.T) { + tt(t, func() { + test := func(prm, bdy string, expect any) *ast.FunctionLiteral { + function, err := ParseFunction(prm, bdy) + is(firstErr(err), expect) + return function + } + + test("a, b,c,d", "", nil) + + test("a, b;,c,d", "", "(anonymous): Line 1:15 Unexpected token ;") + + test("this", "", "(anonymous): Line 1:11 Unexpected token this") + + test("a, b, c, null", "", "(anonymous): Line 1:20 Unexpected token null") + + test("a, b,c,d", "return;", nil) + + test("a, b,c,d", "break;", "(anonymous): Line 2:1 Illegal break statement") + + test("a, b,c,d", "{}", nil) + }) +} + +func TestParserErr(t *testing.T) { + tt(t, func() { + test := func(input string, expect any) (*ast.Program, *_parser) { + parser := newParser("", input) + program, err := parser.parse() + is(firstErr(err), expect) + return program, parser + } + + test("", nil) + + program, parser := test(` + var abc; + break; do { + } while(true); + `, "(anonymous): Line 3:9 Illegal break statement") + { + stmt := program.Body[1].(*ast.BadStatement) + is(parser.position(stmt.From).Column, 9) + is(parser.position(stmt.To).Column, 16) + is(parser.slice(stmt.From, stmt.To), "break; ") + } + + s := string([]byte{0x22, 0x25, 0x21, 0x63, 0x28, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3d, 0x25, 0x63, 0x25, 0x9c, 0x29, 0x25, 0x21, 0x5c, 0x28, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x3d, 0x5c, 0xe2, 0x80, 0xa9, 0x29, 0x78, 0x39, 0x63, 0x22}) + test(s, `(anonymous): Line 1:16 Invalid UTF-8 character`) + + test("{", "(anonymous): Line 1:2 Unexpected end of input") + + test("}", "(anonymous): Line 1:1 Unexpected token }") + + test("3ea", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3in", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3in []", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3e", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3e+", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3e-", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3x", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("3x0", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("0x", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("09", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("018", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("01.0", "(anonymous): Line 1:3 Unexpected number") + + test(".0.9", "(anonymous): Line 1:3 Unexpected number") + + test("0o3e1", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("01a", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("0x3in[]", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\"Hello\nWorld\"", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\u203f = 10", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("x\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("x\\\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("x\\u005c", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("x\\u002a", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("x\\\\u002a", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("/\n", "(anonymous): Line 1:1 Invalid regular expression: missing /") + + test("0 = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment") + + test("func() = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment") + + test("(1 + 1) = 2", "(anonymous): Line 1:2 Invalid left-hand side in assignment") + + test("1++", "(anonymous): Line 1:2 Invalid left-hand side in assignment") + + test("1--", "(anonymous): Line 1:2 Invalid left-hand side in assignment") + + test("--1", "(anonymous): Line 1:1 Invalid left-hand side in assignment") + + test("for((1 + 1) in abc) def();", "(anonymous): Line 1:1 Invalid left-hand side in for-in or for-of") + + test("[", "(anonymous): Line 1:2 Unexpected end of input") + + test("[,", "(anonymous): Line 1:3 Unexpected end of input") + + test("1 + {", "(anonymous): Line 1:6 Unexpected end of input") + + test("1 + { abc:abc", "(anonymous): Line 1:14 Unexpected end of input") + + test("1 + { abc:abc,", "(anonymous): Line 1:15 Unexpected end of input") + + test("var abc = /\n/", "(anonymous): Line 1:11 Invalid regular expression: missing /") + + test("var abc = \"\n", "(anonymous): Line 1:11 Unexpected token ILLEGAL") + + test("var if = 0", "(anonymous): Line 1:5 Unexpected token if") + + test("abc + 0 = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment") + + test("+abc = 1", "(anonymous): Line 1:1 Invalid left-hand side in assignment") + + test("1 + (", "(anonymous): Line 1:6 Unexpected end of input") + + test("\n\n\n{", "(anonymous): Line 4:2 Unexpected end of input") + + test("\n/* Some multiline\ncomment */\n)", "(anonymous): Line 4:1 Unexpected token )") + + test("+1 ** 2", "(anonymous): Line 1:4 Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence") + test("typeof 1 ** 2", "(anonymous): Line 1:10 Unary operator used immediately before exponentiation expression. Parenthesis must be used to disambiguate operator precedence") + + // TODO + //{ set 1 } + //{ get 2 } + //({ set: s(if) { } }) + //({ set s(.) { } }) + //({ set: s() { } }) + //({ set: s(a, b) { } }) + //({ get: g(d) { } }) + //({ get i() { }, i: 42 }) + //({ i: 42, get i() { } }) + //({ set i(x) { }, i: 42 }) + //({ i: 42, set i(x) { } }) + //({ get i() { }, get i() { } }) + //({ set i(x) { }, set i(x) { } }) + + test("function abc(if) {}", "(anonymous): Line 1:14 Unexpected token if") + + test("function abc(true) {}", "(anonymous): Line 1:14 Unexpected token true") + + test("function abc(false) {}", "(anonymous): Line 1:14 Unexpected token false") + + test("function abc(null) {}", "(anonymous): Line 1:14 Unexpected token null") + + test("function null() {}", "(anonymous): Line 1:10 Unexpected token null") + + test("function true() {}", "(anonymous): Line 1:10 Unexpected token true") + + test("function false() {}", "(anonymous): Line 1:10 Unexpected token false") + + test("function if() {}", "(anonymous): Line 1:10 Unexpected token if") + + test("a b;", "(anonymous): Line 1:3 Unexpected identifier") + + test("if.a", "(anonymous): Line 1:3 Unexpected token .") + + test("a if", "(anonymous): Line 1:3 Unexpected token if") + + test("a class", "(anonymous): Line 1:3 Unexpected token class") + + test("break\n", "(anonymous): Line 1:1 Illegal break statement") + + test("break 1;", "(anonymous): Line 1:7 Unexpected number") + + test("for (;;) { break 1; }", "(anonymous): Line 1:18 Unexpected number") + + test("continue\n", "(anonymous): Line 1:1 Illegal continue statement") + + test("continue 1;", "(anonymous): Line 1:10 Unexpected number") + + test("for (;;) { continue 1; }", "(anonymous): Line 1:21 Unexpected number") + + test("throw", "(anonymous): Line 1:1 Unexpected end of input") + + test("throw;", "(anonymous): Line 1:6 Unexpected token ;") + + test("throw \n", "(anonymous): Line 1:1 Unexpected end of input") + + test("for (var abc, def in {});", "(anonymous): Line 1:19 Unexpected token in") + + test("for ((abc in {});;);", nil) + + test("for ((abc in {}));", "(anonymous): Line 1:17 Unexpected token )") + + test("for (+abc in {});", "(anonymous): Line 1:1 Invalid left-hand side in for-in or for-of") + + test("if (false)", "(anonymous): Line 1:11 Unexpected end of input") + + test("if (false) abc(); else", "(anonymous): Line 1:23 Unexpected end of input") + + test("do", "(anonymous): Line 1:3 Unexpected end of input") + + test("while (false)", "(anonymous): Line 1:14 Unexpected end of input") + + test("for (;;)", "(anonymous): Line 1:9 Unexpected end of input") + + test("with (abc)", "(anonymous): Line 1:11 Unexpected end of input") + + test("try {}", "(anonymous): Line 1:1 Missing catch or finally after try") + + test("try {} catch () {}", "(anonymous): Line 1:15 Unexpected token )") + + test("\u203f = 1", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + // TODO + // const x = 12, y; + // const x, y = 12; + // const x; + // if(true) let a = 1; + // if(true) const a = 1; + + test(`new abc()."def"`, "(anonymous): Line 1:11 Unexpected string") + + test("/*", "(anonymous): Line 1:3 Unexpected end of input") + + test("/**", "(anonymous): Line 1:4 Unexpected end of input") + + test("/*\n\n\n", "(anonymous): Line 4:1 Unexpected end of input") + + test("/*\n\n\n*", "(anonymous): Line 4:2 Unexpected end of input") + + test("/*abc", "(anonymous): Line 1:6 Unexpected end of input") + + test("/*abc *", "(anonymous): Line 1:9 Unexpected end of input") + + test("\n]", "(anonymous): Line 2:1 Unexpected token ]") + + test("\r\n]", "(anonymous): Line 2:1 Unexpected token ]") + + test("\n\r]", "(anonymous): Line 3:1 Unexpected token ]") + + test("//\r\n]", "(anonymous): Line 2:1 Unexpected token ]") + + test("//\n\r]", "(anonymous): Line 3:1 Unexpected token ]") + + test("/abc\\\n/", "(anonymous): Line 1:1 Invalid regular expression: missing /") + + test("//\r \n]", "(anonymous): Line 3:1 Unexpected token ]") + + test("/*\r\n*/]", "(anonymous): Line 2:3 Unexpected token ]") + + test("/*\r \n*/]", "(anonymous): Line 3:3 Unexpected token ]") + + test("\\\\", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\\u005c", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\\abc", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\\u0000", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\\u200c = []", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("\\u200D = []", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test(`"\`, "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test(`"\u`, "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("return", "(anonymous): Line 1:1 Illegal return statement") + + test("continue", "(anonymous): Line 1:1 Illegal continue statement") + + test("break", "(anonymous): Line 1:1 Illegal break statement") + + test("switch (abc) { default: continue; }", "(anonymous): Line 1:25 Illegal continue statement") + + test("do { abc } *", "(anonymous): Line 1:12 Unexpected token *") + + test("while (true) { break abc; }", "(anonymous): Line 1:16 Undefined label 'abc'") + + test("while (true) { continue abc; }", "(anonymous): Line 1:16 Undefined label 'abc'") + + test("abc: while (true) { (function(){ break abc; }); }", "(anonymous): Line 1:34 Undefined label 'abc'") + + test("abc: while (true) { (function(){ abc: break abc; }); }", nil) + + test("abc: while (true) { (function(){ continue abc; }); }", "(anonymous): Line 1:34 Undefined label 'abc'") + + test(`abc: if (0) break abc; else {}`, nil) + + test(`abc: if (0) { break abc; } else {}`, nil) + + test(`abc: if (0) { break abc } else {}`, nil) + + test("abc: while (true) { abc: while (true) {} }", "(anonymous): Line 1:21 Label 'abc' already exists") + + test(`if(0) { do { } while(0) } else { do { } while(0) }`, nil) + + test(`if(0) do { } while(0); else do { } while(0)`, nil) + + test("_: _: while (true) {]", "(anonymous): Line 1:4 Label '_' already exists") + + test("_:\n_:\nwhile (true) {]", "(anonymous): Line 2:1 Label '_' already exists") + + test("_:\n _:\nwhile (true) {]", "(anonymous): Line 2:4 Label '_' already exists") + + test("function(){}", "(anonymous): Line 1:9 Unexpected token (") + + test("\n/*/", "(anonymous): Line 2:4 Unexpected end of input") + + test("/*/.source", "(anonymous): Line 1:11 Unexpected end of input") + + test("var class", "(anonymous): Line 1:5 Unexpected token class") + + test("var if", "(anonymous): Line 1:5 Unexpected token if") + + test("object Object", "(anonymous): Line 1:8 Unexpected identifier") + + test("[object Object]", "(anonymous): Line 1:9 Unexpected identifier") + + test("\\u0xyz", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test(`for (var abc, def in {}) {}`, "(anonymous): Line 1:19 Unexpected token in") + + test(`for (abc, def in {}) {}`, "(anonymous): Line 1:1 Invalid left-hand side in for-in or for-of") + + test(`for (var abc=def, ghi=("abc" in {}); true;) {}`, nil) + + { + // Semicolon insertion + + test("this\nif (1);", nil) + + test("while (1) { break\nif (1); }", nil) + + test("throw\nif (1);", "(anonymous): Line 1:1 Illegal newline after throw") + + test("(function(){ return\nif (1); })", nil) + + test("while (1) { continue\nif (1); }", nil) + + test("debugger\nif (1);", nil) + } + + { // Reserved words + + test("class", "(anonymous): Line 1:6 Unexpected end of input") + test("abc.class = 1", nil) + test("var class;", "(anonymous): Line 1:5 Unexpected token class") + + test("const", "(anonymous): Line 1:6 Unexpected end of input") + test("abc.const = 1", nil) + test("var const;", "(anonymous): Line 1:5 Unexpected token const") + + test("enum", "(anonymous): Line 1:1 Unexpected reserved word") + test("abc.enum = 1", nil) + test("var enum;", "(anonymous): Line 1:5 Unexpected reserved word") + + test("export", "(anonymous): Line 1:1 Unexpected reserved word") + test("abc.export = 1", nil) + test("var export;", "(anonymous): Line 1:5 Unexpected reserved word") + + test("extends", "(anonymous): Line 1:1 Unexpected token extends") + test("abc.extends = 1", nil) + test("var extends;", "(anonymous): Line 1:5 Unexpected token extends") + + test("import", "(anonymous): Line 1:1 Unexpected reserved word") + test("abc.import = 1", nil) + test("var import;", "(anonymous): Line 1:5 Unexpected reserved word") + + test("super", "(anonymous): Line 1:1 'super' keyword unexpected here") + test("abc.super = 1", nil) + test("var super;", "(anonymous): Line 1:5 Unexpected token super") + test(` + obj = { + aaa: 1 + bbb: "string" + };`, "(anonymous): Line 4:6 Unexpected identifier") + test("{}", nil) + test("{a: 1}", nil) + test("{a: 1,}", "(anonymous): Line 1:7 Unexpected token }") + test("{a: 1, b: 2}", "(anonymous): Line 1:9 Unexpected token :") + test("{a: 1, b: 2,}", "(anonymous): Line 1:9 Unexpected token :") + test(`let f = () => new import('');`, "(anonymous): Line 1:19 Unexpected reserved word") + + } + + { // Reserved words (strict) + + test(`implements`, nil) + test(`abc.implements = 1`, nil) + test(`var implements;`, nil) + + test(`interface`, nil) + test(`abc.interface = 1`, nil) + test(`var interface;`, nil) + + test(`let`, nil) + test(`abc.let = 1`, nil) + test(`var let;`, nil) + + test(`package`, nil) + test(`abc.package = 1`, nil) + test(`var package;`, nil) + + test(`private`, nil) + test(`abc.private = 1`, nil) + test(`var private;`, nil) + + test(`protected`, nil) + test(`abc.protected = 1`, nil) + test(`var protected;`, nil) + + test(`public`, nil) + test(`abc.public = 1`, nil) + test(`var public;`, nil) + + test(`static`, nil) + test(`abc.static = 1`, nil) + test(`var static;`, nil) + + test(`yield`, nil) + test(`abc.yield = 1`, nil) + test(`var yield;`, nil) + } + test(`0, { get a(param = null) {} };`, "(anonymous): Line 1:11 Getter must not have any formal parameters.") + test(`let{f(`, "(anonymous): Line 1:7 Unexpected end of input") + test("`", "(anonymous): Line 1:2 Unexpected end of input") + test(" `", "(anonymous): Line 1:3 Unexpected end of input") + test("` ", "(anonymous): Line 1:3 Unexpected end of input") + test(`var{..(`, "(anonymous): Line 1:7 Unexpected token ILLEGAL") + test(`var{get..(`, "(anonymous): Line 1:10 Unexpected token ILLEGAL") + test(`var{set..(`, "(anonymous): Line 1:10 Unexpected token ILLEGAL") + test(`(0 ?? 0 || true)`, "(anonymous): Line 1:9 Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses") + test(`(a || b ?? c)`, "(anonymous): Line 1:9 Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses") + test(`2 ?? 2 && 3 + 3`, "(anonymous): Line 1:3 Logical expressions and coalesce expressions cannot be mixed. Wrap either by parentheses") + test(` + class C { + st\u0061tic m() {} + } + `, "(anonymous): Line 3:25 Unexpected identifier") + }) +} + +func TestParser(t *testing.T) { + tt(t, func() { + test := func(source string, chk any) *ast.Program { + _, program, err := testParse(source) + is(firstErr(err), chk) + return program + } + test(`new (() => {});`, nil) + + test(` + abc + -- + [] + `, "(anonymous): Line 3:13 Invalid left-hand side in assignment") + + test(` + abc-- + [] + `, nil) + + test("1\n[]\n", "(anonymous): Line 2:2 Unexpected token ]") + + test(` + function abc() { + } + abc() + `, nil) + + test("", nil) + + test("//", nil) + + test("/* */", nil) + + test("/** **/", nil) + + test("/*****/", nil) + + test("/*", "(anonymous): Line 1:3 Unexpected end of input") + + test("#", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("/**/#", "(anonymous): Line 1:5 Unexpected token ILLEGAL") + + test("new +", "(anonymous): Line 1:5 Unexpected token +") + + program := test(";", nil) + is(len(program.Body), 1) + is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1)) + + program = test(";;", nil) + is(len(program.Body), 2) + is(program.Body[0].(*ast.EmptyStatement).Semicolon, file.Idx(1)) + is(program.Body[1].(*ast.EmptyStatement).Semicolon, file.Idx(2)) + + program = test("1.2", nil) + is(len(program.Body), 1) + is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.NumberLiteral).Literal, "1.2") + + program = test("/* */1.2", nil) + is(len(program.Body), 1) + is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.NumberLiteral).Literal, "1.2") + + program = test("\n", nil) + is(len(program.Body), 0) + + test(` + if (0) { + abc = 0 + } + else abc = 0 + `, nil) + + test("if (0) abc = 0 else abc = 0", "(anonymous): Line 1:16 Unexpected token else") + + test(` + if (0) { + abc = 0 + } else abc = 0 + `, nil) + + test(` + if (0) { + abc = 1 + } else { + } + `, nil) + + test(` + do { + } while (true) + `, nil) + + test(` + try { + } finally { + } + `, nil) + + test(` + try { + } catch (abc) { + } finally { + } + `, nil) + + test(` + try { + } + catch (abc) { + } + finally { + } + `, nil) + + test(`try {} catch (abc) {} finally {}`, nil) + + test("try {} catch {}", nil) + + test(` + do { + do { + } while (0) + } while (0) + `, nil) + + test(` + (function(){ + try { + if ( + 1 + ) { + return 1 + } + return 0 + } finally { + } + })() + `, nil) + + test("abc = ''\ndef", nil) + + test("abc = 1\ndef", nil) + + test("abc = Math\ndef", nil) + + test(`"\'"`, nil) + + test(` + abc = function(){ + } + abc = 0 + `, nil) + + test("abc.null = 0", nil) + + test("0x41", nil) + + test(`"\d"`, nil) + + test(`(function(){return this})`, nil) + + test(` + Object.defineProperty(Array.prototype, "0", { + value: 100, + writable: false, + configurable: true + }); + abc = [101]; + abc.hasOwnProperty("0") && abc[0] === 101; + `, nil) + + test(`new abc()`, nil) + test(`new {}`, nil) + + test(` + limit = 4 + result = 0 + while (limit) { + limit = limit - 1 + if (limit) { + } + else { + break + } + result = result + 1 + } + `, nil) + + test(` + while (0) { + if (0) { + continue + } + } + `, nil) + + test("var \u0061\u0062\u0063 = 0", nil) + + // 7_3_1 + test("var test7_3_1\nabc = 66;", nil) + test("var test7_3_1\u2028abc = 66;", nil) + + // 7_3_3 + test("//\u2028 =;", "(anonymous): Line 2:2 Unexpected token =") + + // 7_3_10 + test("var abc = \u2029;", "(anonymous): Line 2:1 Unexpected token ;") + test("var abc = \\u2029;", "(anonymous): Line 1:11 Unexpected token ILLEGAL") + test("var \\u0061\\u0062\\u0063 = 0;", nil) + + test("'", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + test("'\nstr\ning\n'", "(anonymous): Line 1:1 Unexpected token ILLEGAL") + + // S7.6_A4.3_T1 + test(`var $\u0030 = 0;`, nil) + + // S7.6.1.1_A1.1 + test(`switch = 1`, "(anonymous): Line 1:8 Unexpected token =") + + // S7.8.3_A2.1_T1 + test(`.0 === 0.0`, nil) + + // 7.8.5-1 + test("var regExp = /\\\rn/;", "(anonymous): Line 1:14 Invalid regular expression: missing /") + + // S7.8.5_A1.1_T2 + test("var regExp = /=/;", nil) + + // S7.8.5_A1.2_T1 + test("/*/", "(anonymous): Line 1:4 Unexpected end of input") + + // Sbp_7.9_A9_T3 + test(` + do { + ; + } while (false) true + `, nil) + + // S7.9_A10_T10 + test(` + {a:1 + } 3 + `, nil) + + test(` + abc + ++def + `, nil) + + // S7.9_A5.2_T1 + test(` + for(false;false + ) { + break; + } + `, "(anonymous): Line 3:13 Unexpected token )") + + // S7.9_A9_T8 + test(` + do {}; + while (false) + `, "(anonymous): Line 2:18 Unexpected token ;") + + // S8.4_A5 + test(` + "x\0y" + `, nil) + + // S9.3.1_A6_T1 + test(` + 10e10000 + `, nil) + + // 10.4.2-1-5 + test(` + "abc\ + def" + `, nil) + + test("'\\\n'", nil) + + test("'\\\r\n'", nil) + + //// 11.13.1-1-1 + test("42 = 42;", "(anonymous): Line 1:1 Invalid left-hand side in assignment") + test("s &^= 42;", "(anonymous): Line 1:4 Unexpected token ^=") + + // S11.13.2_A4.2_T1.3 + test(` + abc /= "1" + `, nil) + + // 12.1-1 + test(` + try{};catch(){} + `, "(anonymous): Line 2:13 Missing catch or finally after try") + + // 12.1-3 + test(` + try{};finally{} + `, "(anonymous): Line 2:13 Missing catch or finally after try") + + // S12.6.3_A11.1_T3 + test(` + while (true) { + break abc; + } + `, "(anonymous): Line 3:17 Undefined label 'abc'") + + // S15.3_A2_T1 + test(`var x / = 1;`, "(anonymous): Line 1:7 Unexpected token /") + + test(` + function abc() { + if (0) + return; + else { + } + } + `, nil) + + test("//\u2028 var =;", "(anonymous): Line 2:6 Unexpected token =") + + test(` + throw + {} + `, "(anonymous): Line 2:13 Illegal newline after throw") + + // S7.6.1.1_A1.11 + test(` + function = 1 + `, "(anonymous): Line 2:22 Unexpected token =") + + // S7.8.3_A1.2_T1 + test(`0e1`, nil) + + test("abc = 1; abc\n++", "(anonymous): Line 2:3 Unexpected end of input") + + // --- + + test("({ get abc() {} })", nil) + + test(`for (abc.def in {}) {}`, nil) + + test(`while (true) { break }`, nil) + + test(`while (true) { continue }`, nil) + + test(`abc=/^(?:(\w+:)\/{2}(\w+(?:\.\w+)*\/?)|(.{0,2}\/{1}))?([/.]*?(?:[^?]+)?\/)?((?:[^/?]+)\.(\w+))(?:\?(\S+)?)?$/,def=/^(?:(\w+:)\/{2})|(.{0,2}\/{1})?([/.]*?(?:[^?]+)?\/?)?$/`, nil) + + test(`(function() { try {} catch (err) {} finally {} return })`, nil) + + test(`0xde0b6b3a7640080.toFixed(0)`, nil) + + test(`/[^-._0-9A-Za-z\xb7\xc0-\xd6\xd8-\xf6\xf8-\u037d\u37f-\u1fff\u200c-\u200d\u203f\u2040\u2070-\u218f]/`, nil) + + test(`/[\u0000-\u0008\u000B-\u000C\u000E-\u001F\uD800-\uDFFF\uFFFE-\uFFFF]/`, nil) + + test("var abc = 1;\ufeff", nil) + + test("\ufeff/* var abc = 1; */", nil) + + test(`if (-0x8000000000000000<=abc&&abc<=0x8000000000000000) {}`, nil) + + test(`(function(){debugger;return this;})`, nil) + + test(` + + `, nil) + + test(` + var abc = "" + debugger + `, nil) + + test(` + var abc = /\[\]$/ + debugger + `, nil) + + test(` + var abc = 1 / + 2 + debugger + `, nil) + + test("'ё\\\u2029'", nil) + + test(`[a, b] = [1, 2]`, nil) + test(`({"a b": {}} = {})`, nil) + + test(`ref = (a, b = 39,) => { + };`, nil) + test(`(a,) => {}`, nil) + + test(`2 ?? (2 && 3) + 3`, nil) + test(`(2 ?? 2) && 3 + 3`, nil) + program = test(`a ?? b ?? c`, nil) + is(len(program.Body), 1) + is(program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression).Right.(*ast.Identifier).Name, "c") + + program = test(` + class C { + a + b + #c + m() { + return this.#c; + } + } + `, nil) + is(len(program.Body), 1) + + { + program := test(`(-2)**53`, nil) + st := program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.BinaryExpression) + is(st.Operator, token.EXPONENT) + left := st.Left.(*ast.UnaryExpression) + is(left.Operator, token.MINUS) + op1 := left.Operand.(*ast.NumberLiteral) + is(op1.Literal, "2") + + right := st.Right.(*ast.NumberLiteral) + is(right.Literal, "53") + } + + }) +} + +func TestParseDestruct(t *testing.T) { + parser := newParser("", `({a: (a.b), ...spread,} = {})`) + prg, err := parser.parse() + if err != nil { + t.Fatal(err) + } + _ = prg +} + +func Test_parseStringLiteral(t *testing.T) { + tt(t, func() { + test := func(have string, want unistring.String) { + parser := newParser("", have) + parser.read() + parser.read() + _, res, err := parser.scanString(0, true) + is(err, "") + is(res, want) + } + + test(`""`, "") + test(`/=/`, "=") + + test("'1(\\\\d+)'", "1(\\d+)") + + test("'\\u2029'", "\u2029") + + test("'abc\\uFFFFabc'", "abc\uFFFFabc") + + test("'[First line \\\nSecond line \\\n Third line\\\n. ]'", + "[First line Second line Third line. ]") + + test("'\\u007a\\x79\\u000a\\x78'", "zy\nx") + + // S7.8.4_A4.2_T3 + test("'\\a'", "a") + test("'\u0410'", "\u0410") + + // S7.8.4_A5.1_T1 + test("'\\0'", "\u0000") + + // S8.4_A5 + test("'\u0000'", "\u0000") + + // 15.5.4.20 + test("\"'abc'\\\n'def'\"", "'abc''def'") + + // 15.5.4.20-4-1 + test("\"'abc'\\\r\n'def'\"", "'abc''def'") + + // Octal + test("'\\0'", "\000") + test("'\\00'", "\000") + test("'\\000'", "\000") + test("'\\09'", "\0009") + test("'\\009'", "\0009") + test("'\\0009'", "\0009") + test("'\\1'", "\001") + test("'\\01'", "\001") + test("'\\001'", "\001") + test("'\\0011'", "\0011") + test("'\\1abc'", "\001abc") + + test("'\\\u4e16'", "\u4e16") + + // err + test = func(have string, want unistring.String) { + parser := newParser("", have) + parser.read() + parser.read() + _, res, err := parser.scanString(0, true) + is(err, want) + is(res, "") + } + + test(`"\u"`, `invalid escape: \u: len("") != 4`) + test(`"\u0"`, `invalid escape: \u: len("0") != 4`) + test(`"\u00"`, `invalid escape: \u: len("00") != 4`) + test(`"\u000"`, `invalid escape: \u: len("000") != 4`) + + test(`"\x"`, `invalid escape: \x: len("") != 2`) + test(`"\x0"`, `invalid escape: \x: len("0") != 2`) + }) +} + +func Test_parseNumberLiteral(t *testing.T) { + tt(t, func() { + test := func(input string, expect any) { + result, err := parseNumberLiteral(input) + is(err, nil) + is(result, expect) + } + + test("0", 0) + + test("0x8000000000000000", float64(9.223372036854776e+18)) + }) +} + +func TestPosition(t *testing.T) { + tt(t, func() { + parser := newParser("", "// Lorem ipsum") + + // Out of range, idx0 (error condition) + is(parser.slice(0, 1), "") + is(parser.slice(0, 10), "") + + // Out of range, idx1 (error condition) + is(parser.slice(1, 128), "") + + is(parser.str[0:0], "") + is(parser.slice(1, 1), "") + + is(parser.str[0:1], "/") + is(parser.slice(1, 2), "/") + + is(parser.str[0:14], "// Lorem ipsum") + is(parser.slice(1, 15), "// Lorem ipsum") + + parser = newParser("", "(function(){ return 0; })") + program, err := parser.parse() + is(err, nil) + + var node ast.Node + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral) + is(node.Idx0(), file.Idx(2)) + is(node.Idx1(), file.Idx(25)) + is(parser.slice(node.Idx0(), node.Idx1()), "function(){ return 0; }") + is(parser.slice(node.Idx0(), node.Idx1()+1), "function(){ return 0; })") + is(parser.slice(node.Idx0(), node.Idx1()+2), "") + is(node.(*ast.FunctionLiteral).Source, "function(){ return 0; }") + + node = program + is(node.Idx0(), file.Idx(2)) + is(node.Idx1(), file.Idx(25)) + is(parser.slice(node.Idx0(), node.Idx1()), "function(){ return 0; }") + + parser = newParser("", "(function(){ return abc; })") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral) + is(node.(*ast.FunctionLiteral).Source, "function(){ return abc; }") + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral).Body.List[0].(*ast.ReturnStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "return abc") + + parser = newParser("", "(function(){ return; })") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral).Body.List[0].(*ast.ReturnStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "return") + + parser = newParser("", "true ? 1 : 2") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.ConditionalExpression) + is(parser.slice(node.Idx0(), node.Idx1()), "true ? 1 : 2") + + parser = newParser("", "a++") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.UnaryExpression) + is(parser.slice(node.Idx0(), node.Idx1()), "a++") + + parser = newParser("", "++a") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.UnaryExpression) + is(parser.slice(node.Idx0(), node.Idx1()), "++a") + + parser = newParser("", "xyz: for (i = 0; i < 10; i++) { if (i == 5) continue xyz; }") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.LabelledStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "xyz: for (i = 0; i < 10; i++) { if (i == 5) continue xyz; }") + node = program.Body[0].(*ast.LabelledStatement).Statement.(*ast.ForStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "for (i = 0; i < 10; i++) { if (i == 5) continue xyz; }") + block := program.Body[0].(*ast.LabelledStatement).Statement.(*ast.ForStatement).Body.(*ast.BlockStatement) + node = block.List[0].(*ast.IfStatement).Consequent.(*ast.BranchStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "continue xyz") + + parser = newParser("", "for (p in o) { break; }") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.ForInStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "for (p in o) { break; }") + node = program.Body[0].(*ast.ForInStatement).Body.(*ast.BlockStatement).List[0].(*ast.BranchStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "break") + + parser = newParser("", "while (i < 10) { i++; }") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.WhileStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "while (i < 10) { i++; }") + + parser = newParser("", "do { i++; } while (i < 10 )") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.DoWhileStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "do { i++; } while (i < 10 )") + + parser = newParser("", "with (1) {}") + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.WithStatement) + is(parser.slice(node.Idx0(), node.Idx1()), "with (1) {}") + + parser = newParser("", `switch (a) { + case 1: x--; + case 2: + default: x++; +}`) + program, err = parser.parse() + is(err, nil) + node = program.Body[0].(*ast.SwitchStatement) + is(parser.slice(node.Idx0(), node.Idx1()), `switch (a) { + case 1: x--; + case 2: + default: x++; +}`) + }) +} + +func TestExtractSourceMapLine(t *testing.T) { + tt(t, func() { + is(extractSourceMapLine(""), "") + is(extractSourceMapLine("\n"), "") + is(extractSourceMapLine(" "), "") + is(extractSourceMapLine("1\n2\n3\n4\n"), "") + + src := `"use strict"; +var x = {}; +//# sourceMappingURL=delme.js.map` + modSrc := `(function(exports, require, module) {` + src + ` +})` + is(extractSourceMapLine(modSrc), "//# sourceMappingURL=delme.js.map") + is(extractSourceMapLine(modSrc+"\n\n\n\n"), "//# sourceMappingURL=delme.js.map") + }) +} + +func TestSourceMapOptions(t *testing.T) { + tt(t, func() { + count := 0 + requestedPath := "" + loader := func(p string) ([]byte, error) { + count++ + requestedPath = p + return nil, nil + } + src := `"use strict"; +var x = {}; +//# sourceMappingURL=delme.js.map` + _, err := ParseFile(nil, "delme.js", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "delme.js.map") + + count = 0 + _, err = ParseFile(nil, "", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "delme.js.map") + + count = 0 + _, err = ParseFile(nil, "delme.js", src, 0, WithDisableSourceMaps) + is(err, nil) + is(count, 0) + + _, err = ParseFile(nil, "/home/user/src/delme.js", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "/home/user/src/delme.js.map") + + count = 0 + _, err = ParseFile(nil, "https://site.com/delme.js", src, 0, WithSourceMapLoader(loader)) + is(err, nil) + is(count, 1) + is(requestedPath, "https://site.com/delme.js.map") + }) +} + +func TestParseTemplateCharacters(t *testing.T) { + parser := newParser("", "`test\\\r\\\n${a}`") + parser.next() + if parser.token != token.BACKTICK { + t.Fatalf("Token: %s", parser.token) + } + checkParseTemplateChars := func(expectedLiteral string, expectedParsed unistring.String, expectedFinished, expectParseErr, expectErr bool) { + literal, parsed, finished, parseErr, err := parser.parseTemplateCharacters() + if err != "" != expectErr { + t.Fatal(err) + } + if literal != expectedLiteral { + t.Fatalf("Literal: %q", literal) + } + if parsed != expectedParsed { + t.Fatalf("Parsed: %q", parsed) + } + if finished != expectedFinished { + t.Fatal(finished) + } + if parseErr != "" != expectParseErr { + t.Fatalf("parseErr: %v", parseErr) + } + } + checkParseTemplateChars("test\\\n\\\n", "test", false, false, false) + parser.next() + parser.expect(token.IDENTIFIER) + if len(parser.errors) > 0 { + t.Fatal(parser.errors) + } + if parser.token != token.RIGHT_BRACE { + t.Fatal("Expected }") + } + if len(parser.errors) > 0 { + t.Fatal(parser.errors) + } + checkParseTemplateChars("", "", true, false, false) + if parser.chr != -1 { + t.Fatal("Expected EOF") + } +} + +func TestParseTemplateLiteral(t *testing.T) { + parser := newParser("", "f()\n`test${a}`") + prg, err := parser.parse() + if err != nil { + t.Fatal(err) + } + if st, ok := prg.Body[0].(*ast.ExpressionStatement); ok { + if expr, ok := st.Expression.(*ast.TemplateLiteral); ok { + if expr.Tag == nil { + t.Fatal("tag is nil") + } + if idx0 := expr.Tag.Idx0(); idx0 != 1 { + t.Fatalf("Tag.Idx0(): %d", idx0) + } + if expr.OpenQuote != 5 { + t.Fatalf("OpenQuote: %d", expr.OpenQuote) + } + if expr.CloseQuote != 14 { + t.Fatalf("CloseQuote: %d", expr.CloseQuote) + } + if l := len(expr.Elements); l != 2 { + t.Fatalf("len elements: %d", l) + } + if l := len(expr.Expressions); l != 1 { + t.Fatalf("len expressions: %d", l) + } + } else { + t.Fatal(st) + } + } else { + t.Fatal(prg.Body[0]) + } +} + +func TestParseTemplateLiteralWithTail(t *testing.T) { + parser := newParser("", "f()\n`test${a}tail` ") + prg, err := parser.parse() + if err != nil { + t.Fatal(err) + } + if st, ok := prg.Body[0].(*ast.ExpressionStatement); ok { + if expr, ok := st.Expression.(*ast.TemplateLiteral); ok { + if expr.CloseQuote != 18 { + t.Fatalf("CloseQuote: %d", expr.CloseQuote) + } + } else { + t.Fatal(st) + } + } else { + t.Fatal(prg.Body[0]) + } +} diff --git a/pkg/xscript/engine/parser/regexp.go b/pkg/xscript/engine/parser/regexp.go new file mode 100644 index 0000000..68e5c26 --- /dev/null +++ b/pkg/xscript/engine/parser/regexp.go @@ -0,0 +1,458 @@ +package parser + +import ( + "fmt" + "strconv" + "strings" + "unicode/utf8" +) + +const ( + WhitespaceChars = " \f\n\r\t\v\u00a0\u1680\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u2028\u2029\u202f\u205f\u3000\ufeff" + Re2Dot = "[^\r\n\u2028\u2029]" +) + +type regexpParseError struct { + offset int + err string +} + +type RegexpErrorIncompatible struct { + regexpParseError +} +type RegexpSyntaxError struct { + regexpParseError +} + +func (s regexpParseError) Error() string { + return s.err +} + +type _RegExp_parser struct { + str string + length int + + chr rune // The current character + chrOffset int // The offset of current character + offset int // The offset after current character (may be greater than 1) + + err error + + goRegexp strings.Builder + passOffset int +} + +// 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 valid, but incompatible (contains a lookahead or backreference), +// then this function returns an empty string an error of type RegexpErrorIncompatible. +// +// If the pattern is invalid (not valid even in JavaScript), then this function +// returns an empty string and a generic error. +func TransformRegExp(pattern string) (transformed string, err error) { + + if pattern == "" { + return "", nil + } + + parser := _RegExp_parser{ + str: pattern, + length: len(pattern), + } + err = parser.parse() + if err != nil { + return "", err + } + + return parser.ResultString(), nil +} + +func (self *_RegExp_parser) ResultString() string { + if self.passOffset != -1 { + return self.str[:self.passOffset] + } + return self.goRegexp.String() +} + +func (self *_RegExp_parser) parse() (err error) { + self.read() // Pull in the first character + self.scan() + return self.err +} + +func (self *_RegExp_parser) read() { + if self.offset < self.length { + self.chrOffset = self.offset + chr, width := rune(self.str[self.offset]), 1 + if chr >= utf8.RuneSelf { // !ASCII + chr, width = utf8.DecodeRuneInString(self.str[self.offset:]) + if chr == utf8.RuneError && width == 1 { + self.error(true, "Invalid UTF-8 character") + return + } + } + self.offset += width + self.chr = chr + } else { + self.chrOffset = self.length + self.chr = -1 // EOF + } +} + +func (self *_RegExp_parser) stopPassing() { + self.goRegexp.Grow(3 * len(self.str) / 2) + self.goRegexp.WriteString(self.str[:self.passOffset]) + self.passOffset = -1 +} + +func (self *_RegExp_parser) write(p []byte) { + if self.passOffset != -1 { + self.stopPassing() + } + self.goRegexp.Write(p) +} + +func (self *_RegExp_parser) writeByte(b byte) { + if self.passOffset != -1 { + self.stopPassing() + } + self.goRegexp.WriteByte(b) +} + +func (self *_RegExp_parser) writeString(s string) { + if self.passOffset != -1 { + self.stopPassing() + } + self.goRegexp.WriteString(s) +} + +func (self *_RegExp_parser) scan() { + for self.chr != -1 { + switch self.chr { + case '\\': + self.read() + self.scanEscape(false) + case '(': + self.pass() + self.scanGroup() + case '[': + self.scanBracket() + case ')': + self.error(true, "Unmatched ')'") + return + case '.': + self.writeString(Re2Dot) + self.read() + default: + self.pass() + } + } +} + +// (...) +func (self *_RegExp_parser) scanGroup() { + str := self.str[self.chrOffset:] + if len(str) > 1 { // A possibility of (?= or (?! + if str[0] == '?' { + ch := str[1] + switch { + case ch == '=' || ch == '!': + self.error(false, "re2: Invalid (%s) ", self.str[self.chrOffset:self.chrOffset+2]) + return + case ch == '<': + self.error(false, "re2: Invalid (%s) ", self.str[self.chrOffset:self.chrOffset+2]) + return + case ch != ':': + self.error(true, "Invalid group") + return + } + } + } + for self.chr != -1 && self.chr != ')' { + switch self.chr { + case '\\': + self.read() + self.scanEscape(false) + case '(': + self.pass() + self.scanGroup() + case '[': + self.scanBracket() + case '.': + self.writeString(Re2Dot) + self.read() + default: + self.pass() + continue + } + } + if self.chr != ')' { + self.error(true, "Unterminated group") + return + } + self.pass() +} + +// [...] +func (self *_RegExp_parser) scanBracket() { + str := self.str[self.chrOffset:] + if strings.HasPrefix(str, "[]") { + // [] -- Empty character class + self.writeString("[^\u0000-\U0001FFFF]") + self.offset += 1 + self.read() + return + } + + if strings.HasPrefix(str, "[^]") { + self.writeString("[\u0000-\U0001FFFF]") + self.offset += 2 + self.read() + return + } + + self.pass() + for self.chr != -1 { + if self.chr == ']' { + break + } else if self.chr == '\\' { + self.read() + self.scanEscape(true) + continue + } + self.pass() + } + if self.chr != ']' { + self.error(true, "Unterminated character class") + return + } + self.pass() +} + +// \... +func (self *_RegExp_parser) scanEscape(inClass bool) { + offset := self.chrOffset + + var length, base uint32 + switch self.chr { + + case '0', '1', '2', '3', '4', '5', '6', '7': + var value int64 + size := 0 + for { + digit := int64(digitValue(self.chr)) + if digit >= 8 { + // Not a valid digit + break + } + value = value*8 + digit + self.read() + size += 1 + } + if size == 1 { // The number of characters read + if value != 0 { + // An invalid backreference + self.error(false, "re2: Invalid \\%d ", value) + return + } + self.passString(offset-1, self.chrOffset) + return + } + tmp := []byte{'\\', 'x', '0', 0} + if value >= 16 { + tmp = tmp[0:2] + } else { + tmp = tmp[0:3] + } + tmp = strconv.AppendInt(tmp, value, 16) + self.write(tmp) + return + + case '8', '9': + self.read() + self.error(false, "re2: Invalid \\%s ", self.str[offset:self.chrOffset]) + return + + case 'x': + self.read() + length, base = 2, 16 + + case 'u': + self.read() + if self.chr == '{' { + self.read() + length, base = 0, 16 + } else { + length, base = 4, 16 + } + + case 'b': + if inClass { + self.write([]byte{'\\', 'x', '0', '8'}) + self.read() + return + } + fallthrough + + case 'B': + fallthrough + + case 'd', 'D', 'w', 'W': + // This is slightly broken, because ECMAScript + // includes \v in \s, \S, while re2 does not + fallthrough + + case '\\': + fallthrough + + case 'f', 'n', 'r', 't', 'v': + self.passString(offset-1, self.offset) + self.read() + return + + case 'c': + self.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 { + self.writeByte('c') + return + } + tmp := []byte{'\\', 'x', '0', 0} + if value >= 16 { + tmp = tmp[0:2] + } else { + tmp = tmp[0:3] + } + tmp = strconv.AppendInt(tmp, value, 16) + self.write(tmp) + self.read() + return + case 's': + if inClass { + self.writeString(WhitespaceChars) + } else { + self.writeString("[" + WhitespaceChars + "]") + } + self.read() + return + case 'S': + if inClass { + self.error(false, "S in class") + return + } else { + self.writeString("[^" + WhitespaceChars + "]") + } + self.read() + return + default: + // $ is an identifier character, so we have to have + // a special case for it here + if self.chr == '$' || self.chr < utf8.RuneSelf && !isIdentifierPart(self.chr) { + // A non-identifier character needs escaping + self.passString(offset-1, self.offset) + self.read() + return + } + // Unescape the character for re2 + self.pass() + return + } + + // Otherwise, we're a \u.... or \x... + valueOffset := self.chrOffset + + if length > 0 { + for length := length; length > 0; length-- { + digit := uint32(digitValue(self.chr)) + if digit >= base { + // Not a valid digit + goto skip + } + self.read() + } + } else { + for self.chr != '}' && self.chr != -1 { + digit := uint32(digitValue(self.chr)) + if digit >= base { + // Not a valid digit + goto skip + } + self.read() + } + } + + if length == 4 || length == 0 { + self.write([]byte{ + '\\', + 'x', + '{', + }) + self.passString(valueOffset, self.chrOffset) + if length != 0 { + self.writeByte('}') + } + } else if length == 2 { + self.passString(offset-1, valueOffset+2) + } else { + // Should never, ever get here... + self.error(true, "re2: Illegal branch in scanEscape") + return + } + + return + +skip: + self.passString(offset, self.chrOffset) +} + +func (self *_RegExp_parser) pass() { + if self.passOffset == self.chrOffset { + self.passOffset = self.offset + } else { + if self.passOffset != -1 { + self.stopPassing() + } + if self.chr != -1 { + self.goRegexp.WriteRune(self.chr) + } + } + self.read() +} + +func (self *_RegExp_parser) passString(start, end int) { + if self.passOffset == start { + self.passOffset = end + return + } + if self.passOffset != -1 { + self.stopPassing() + } + self.goRegexp.WriteString(self.str[start:end]) +} + +func (self *_RegExp_parser) error(fatal bool, msg string, msgValues ...any) { + if self.err != nil { + return + } + e := regexpParseError{ + offset: self.offset, + err: fmt.Sprintf(msg, msgValues...), + } + if fatal { + self.err = RegexpSyntaxError{e} + } else { + self.err = RegexpErrorIncompatible{e} + } + self.offset = self.length + self.chr = -1 +} diff --git a/pkg/xscript/engine/parser/regexp_test.go b/pkg/xscript/engine/parser/regexp_test.go new file mode 100644 index 0000000..8bfc6d2 --- /dev/null +++ b/pkg/xscript/engine/parser/regexp_test.go @@ -0,0 +1,191 @@ +package parser + +import ( + "regexp" + "testing" +) + +func TestRegExp(t *testing.T) { + tt(t, func() { + { + // err + test := func(input string, expect any) { + _, err := TransformRegExp(input) + _, incompat := err.(RegexpErrorIncompatible) + is(incompat, false) + is(err, expect) + } + + test("[", "Unterminated character class") + + test("(", "Unterminated group") + + test("\\(?=)", "Unmatched ')'") + + test(")", "Unmatched ')'") + test("0:(?)", "Invalid group") + test("(?)", "Invalid group") + test("(?U)", "Invalid group") + test("(?)|(?i)", "Invalid group") + test("(?P)(?P)(?P)", "Invalid group") + } + + { + // incompatible + test := func(input string, expectErr any) { + _, err := TransformRegExp(input) + _, incompat := err.(RegexpErrorIncompatible) + is(incompat, true) + is(err, expectErr) + } + + test(`<%([\s\S]+?)%>`, "S in class") + + test("(?<=y)x", "re2: Invalid (?<) ") + + test(`(?!test)`, "re2: Invalid (?!) ") + + test(`\1`, "re2: Invalid \\1 ") + + test(`\8`, "re2: Invalid \\8 ") + + } + + { + // err + test := func(input string, expect string) { + result, err := TransformRegExp(input) + is(err, nil) + _, incompat := err.(RegexpErrorIncompatible) + is(incompat, false) + is(result, expect) + _, err = regexp.Compile(result) + is(err, nil) + } + + test("", "") + + test("abc", "abc") + + test(`\abc`, `abc`) + + test(`\a\b\c`, `a\bc`) + + test(`\x`, `x`) + + test(`\c`, `c`) + + test(`\cA`, `\x01`) + + test(`\cz`, `\x1a`) + + test(`\ca`, `\x01`) + + test(`\cj`, `\x0a`) + + test(`\ck`, `\x0b`) + + test(`\+`, `\+`) + + test(`[\b]`, `[\x08]`) + + test(`\u0z01\x\undefined`, `u0z01xundefined`) + + test(`\\|'|\r|\n|\t|\u2028|\u2029`, `\\|'|\r|\n|\t|\x{2028}|\x{2029}`) + + test("]", "]") + + test("}", "}") + + test("%", "%") + + test("(%)", "(%)") + + test("(?:[%\\s])", "(?:[%"+WhitespaceChars+"])") + + test("[[]", "[[]") + + test("\\101", "\\x41") + + test("\\51", "\\x29") + + test("\\051", "\\x29") + + test("\\175", "\\x7d") + + test("\\0", "\\0") + + test("\\04", "\\x04") + + test(`(.)^`, "("+Re2Dot+")^") + + test(`\$`, `\$`) + + test(`[G-b]`, `[G-b]`) + + test(`[G-b\0]`, `[G-b\0]`) + + test(`\k`, `k`) + + test(`\x20`, `\x20`) + + test(`😊`, `😊`) + + test(`^.*`, `^`+Re2Dot+`*`) + + test(`(\n)`, `(\n)`) + + test(`(a(bc))`, `(a(bc))`) + + test(`[]`, "[^\u0000-\U0001FFFF]") + + test(`[^]`, "[\u0000-\U0001FFFF]") + + test(`\s+`, "["+WhitespaceChars+"]+") + + test(`\S+`, "[^"+WhitespaceChars+"]+") + + } + }) +} + +func TestTransformRegExp(t *testing.T) { + tt(t, func() { + pattern, err := TransformRegExp(`\s+abc\s+`) + is(err, nil) + is(pattern, `[`+WhitespaceChars+`]+abc[`+WhitespaceChars+`]+`) + is(regexp.MustCompile(pattern).MatchString("\t abc def"), true) + }) + tt(t, func() { + pattern, err := TransformRegExp(`\u{1d306}`) + is(err, nil) + is(pattern, `\x{1d306}`) + }) + tt(t, func() { + pattern, err := TransformRegExp(`\u1234`) + is(err, nil) + is(pattern, `\x{1234}`) + }) +} + +func BenchmarkTransformRegExp(b *testing.B) { + f := func(reStr string, b *testing.B) { + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, _ = TransformRegExp(reStr) + } + } + + b.Run("Re", func(b *testing.B) { + f(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, b) + }) + + b.Run("Re2-1", func(b *testing.B) { + f(`(?=)^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`, b) + }) + + b.Run("Re2-1", func(b *testing.B) { + f(`^(([^<>()\[\]\\.,;:\s@"]+(\.[^<>()\[\]\\.,;:\s@"]+)*)|(".+"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$(?=)`, b) + }) +} diff --git a/pkg/xscript/engine/parser/scope.go b/pkg/xscript/engine/parser/scope.go new file mode 100644 index 0000000..d04fb62 --- /dev/null +++ b/pkg/xscript/engine/parser/scope.go @@ -0,0 +1,50 @@ +package parser + +import ( + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/unistring" +) + +type _scope struct { + outer *_scope + allowIn bool + allowLet bool + inIteration bool + inSwitch bool + inFuncParams bool + inFunction bool + inAsync bool + allowAwait bool + allowYield bool + declarationList []*ast.VariableDeclaration + + labels []unistring.String +} + +func (self *_parser) openScope() { + self.scope = &_scope{ + outer: self.scope, + allowIn: true, + } +} + +func (self *_parser) closeScope() { + self.scope = self.scope.outer +} + +func (self *_scope) declare(declaration *ast.VariableDeclaration) { + self.declarationList = append(self.declarationList, declaration) +} + +func (self *_scope) hasLabel(name unistring.String) bool { + for _, label := range self.labels { + if label == name { + return true + } + } + if self.outer != nil && !self.inFunction { + // Crossing a function boundary to look for a label is verboten + return self.outer.hasLabel(name) + } + return false +} diff --git a/pkg/xscript/engine/parser/statement.go b/pkg/xscript/engine/parser/statement.go new file mode 100644 index 0000000..f2f11df --- /dev/null +++ b/pkg/xscript/engine/parser/statement.go @@ -0,0 +1,1078 @@ +package parser + +import ( + "encoding/base64" + "fmt" + "os" + "strings" + + "pandax/pkg/xscript/engine/ast" + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/sourcemap" + "pandax/pkg/xscript/engine/token" +) + +func (self *_parser) parseBlockStatement() *ast.BlockStatement { + node := &ast.BlockStatement{} + node.LeftBrace = self.expect(token.LEFT_BRACE) + node.List = self.parseStatementList() + node.RightBrace = self.expect(token.RIGHT_BRACE) + + return node +} + +func (self *_parser) parseEmptyStatement() ast.Statement { + idx := self.expect(token.SEMICOLON) + return &ast.EmptyStatement{Semicolon: idx} +} + +func (self *_parser) parseStatementList() (list []ast.Statement) { + for self.token != token.RIGHT_BRACE && self.token != token.EOF { + self.scope.allowLet = true + list = append(list, self.parseStatement()) + } + + return +} + +func (self *_parser) parseStatement() ast.Statement { + + if self.token == token.EOF { + self.errorUnexpectedToken(self.token) + return &ast.BadStatement{From: self.idx, To: self.idx + 1} + } + + switch self.token { + case token.SEMICOLON: + return self.parseEmptyStatement() + case token.LEFT_BRACE: + return self.parseBlockStatement() + case token.IF: + return self.parseIfStatement() + case token.DO: + return self.parseDoWhileStatement() + case token.WHILE: + return self.parseWhileStatement() + case token.FOR: + return self.parseForOrForInStatement() + case token.BREAK: + return self.parseBreakStatement() + case token.CONTINUE: + return self.parseContinueStatement() + case token.DEBUGGER: + return self.parseDebuggerStatement() + case token.WITH: + return self.parseWithStatement() + case token.VAR: + return self.parseVariableStatement() + case token.LET: + tok := self.peek() + if tok == token.LEFT_BRACKET || self.scope.allowLet && (token.IsId(tok) || tok == token.LEFT_BRACE) { + return self.parseLexicalDeclaration(self.token) + } + self.insertSemicolon = true + case token.CONST: + return self.parseLexicalDeclaration(self.token) + case token.ASYNC: + if f := self.parseMaybeAsyncFunction(true); f != nil { + return &ast.FunctionDeclaration{ + Function: f, + } + } + case token.FUNCTION: + return &ast.FunctionDeclaration{ + Function: self.parseFunction(true, false, self.idx), + } + case token.CLASS: + return &ast.ClassDeclaration{ + Class: self.parseClass(true), + } + case token.SWITCH: + return self.parseSwitchStatement() + case token.RETURN: + return self.parseReturnStatement() + case token.THROW: + return self.parseThrowStatement() + case token.TRY: + return self.parseTryStatement() + } + + expression := self.parseExpression() + + if identifier, isIdentifier := expression.(*ast.Identifier); isIdentifier && self.token == token.COLON { + // LabelledStatement + colon := self.idx + self.next() // : + label := identifier.Name + for _, value := range self.scope.labels { + if label == value { + self.error(identifier.Idx0(), "Label '%s' already exists", label) + } + } + self.scope.labels = append(self.scope.labels, label) // Push the label + self.scope.allowLet = false + statement := self.parseStatement() + self.scope.labels = self.scope.labels[:len(self.scope.labels)-1] // Pop the label + return &ast.LabelledStatement{ + Label: identifier, + Colon: colon, + Statement: statement, + } + } + + self.optionalSemicolon() + + return &ast.ExpressionStatement{ + Expression: expression, + } +} + +func (self *_parser) parseTryStatement() ast.Statement { + + node := &ast.TryStatement{ + Try: self.expect(token.TRY), + Body: self.parseBlockStatement(), + } + + if self.token == token.CATCH { + catch := self.idx + self.next() + var parameter ast.BindingTarget + if self.token == token.LEFT_PARENTHESIS { + self.next() + parameter = self.parseBindingTarget() + self.expect(token.RIGHT_PARENTHESIS) + } + node.Catch = &ast.CatchStatement{ + Catch: catch, + Parameter: parameter, + Body: self.parseBlockStatement(), + } + } + + if self.token == token.FINALLY { + self.next() + node.Finally = self.parseBlockStatement() + } + + if node.Catch == nil && node.Finally == nil { + self.error(node.Try, "Missing catch or finally after try") + return &ast.BadStatement{From: node.Try, To: node.Body.Idx1()} + } + + return node +} + +func (self *_parser) parseFunctionParameterList() *ast.ParameterList { + opening := self.expect(token.LEFT_PARENTHESIS) + var list []*ast.Binding + var rest ast.Expression + if !self.scope.inFuncParams { + self.scope.inFuncParams = true + defer func() { + self.scope.inFuncParams = false + }() + } + for self.token != token.RIGHT_PARENTHESIS && self.token != token.EOF { + if self.token == token.ELLIPSIS { + self.next() + rest = self.reinterpretAsDestructBindingTarget(self.parseAssignmentExpression()) + break + } + self.parseVariableDeclaration(&list) + if self.token != token.RIGHT_PARENTHESIS { + self.expect(token.COMMA) + } + } + closing := self.expect(token.RIGHT_PARENTHESIS) + + return &ast.ParameterList{ + Opening: opening, + List: list, + Rest: rest, + Closing: closing, + } +} + +func (self *_parser) parseMaybeAsyncFunction(declaration bool) *ast.FunctionLiteral { + if self.peek() == token.FUNCTION { + idx := self.idx + self.next() + return self.parseFunction(declaration, true, idx) + } + return nil +} + +func (self *_parser) parseFunction(declaration, async bool, start file.Idx) *ast.FunctionLiteral { + + node := &ast.FunctionLiteral{ + Function: start, + Async: async, + } + self.expect(token.FUNCTION) + + if self.token == token.MULTIPLY { + node.Generator = true + self.next() + } + + if !declaration { + if async != self.scope.allowAwait { + self.scope.allowAwait = async + defer func() { + self.scope.allowAwait = !async + }() + } + if node.Generator != self.scope.allowYield { + self.scope.allowYield = node.Generator + defer func() { + self.scope.allowYield = !node.Generator + }() + } + } + + self.tokenToBindingId() + var name *ast.Identifier + if self.token == token.IDENTIFIER { + name = self.parseIdentifier() + } else if declaration { + // Use expect error handling + self.expect(token.IDENTIFIER) + } + node.Name = name + + if declaration { + if async != self.scope.allowAwait { + self.scope.allowAwait = async + defer func() { + self.scope.allowAwait = !async + }() + } + if node.Generator != self.scope.allowYield { + self.scope.allowYield = node.Generator + defer func() { + self.scope.allowYield = !node.Generator + }() + } + } + + node.ParameterList = self.parseFunctionParameterList() + node.Body, node.DeclarationList = self.parseFunctionBlock(async, async, self.scope.allowYield) + node.Source = self.slice(node.Idx0(), node.Idx1()) + + return node +} + +func (self *_parser) parseFunctionBlock(async, allowAwait, allowYield bool) (body *ast.BlockStatement, declarationList []*ast.VariableDeclaration) { + self.openScope() + self.scope.inFunction = true + self.scope.inAsync = async + self.scope.allowAwait = allowAwait + self.scope.allowYield = allowYield + defer self.closeScope() + body = self.parseBlockStatement() + declarationList = self.scope.declarationList + return +} + +func (self *_parser) parseArrowFunctionBody(async bool) (ast.ConciseBody, []*ast.VariableDeclaration) { + if self.token == token.LEFT_BRACE { + return self.parseFunctionBlock(async, async, false) + } + if async != self.scope.inAsync || async != self.scope.allowAwait { + inAsync := self.scope.inAsync + allowAwait := self.scope.allowAwait + self.scope.inAsync = async + self.scope.allowAwait = async + allowYield := self.scope.allowYield + self.scope.allowYield = false + defer func() { + self.scope.inAsync = inAsync + self.scope.allowAwait = allowAwait + self.scope.allowYield = allowYield + }() + } + + return &ast.ExpressionBody{ + Expression: self.parseAssignmentExpression(), + }, nil +} + +func (self *_parser) parseClass(declaration bool) *ast.ClassLiteral { + if !self.scope.allowLet && self.token == token.CLASS { + self.errorUnexpectedToken(token.CLASS) + } + + node := &ast.ClassLiteral{ + Class: self.expect(token.CLASS), + } + + self.tokenToBindingId() + var name *ast.Identifier + if self.token == token.IDENTIFIER { + name = self.parseIdentifier() + } else if declaration { + // Use expect error handling + self.expect(token.IDENTIFIER) + } + + node.Name = name + + if self.token != token.LEFT_BRACE { + self.expect(token.EXTENDS) + node.SuperClass = self.parseLeftHandSideExpressionAllowCall() + } + + self.expect(token.LEFT_BRACE) + + for self.token != token.RIGHT_BRACE && self.token != token.EOF { + if self.token == token.SEMICOLON { + self.next() + continue + } + start := self.idx + static := false + if self.token == token.STATIC { + switch self.peek() { + case token.ASSIGN, token.SEMICOLON, token.RIGHT_BRACE, token.LEFT_PARENTHESIS: + // treat as identifier + default: + self.next() + if self.token == token.LEFT_BRACE { + b := &ast.ClassStaticBlock{ + Static: start, + } + b.Block, b.DeclarationList = self.parseFunctionBlock(false, true, false) + b.Source = self.slice(b.Block.LeftBrace, b.Block.Idx1()) + node.Body = append(node.Body, b) + continue + } + static = true + } + } + + var kind ast.PropertyKind + var async bool + methodBodyStart := self.idx + if self.literal == "get" || self.literal == "set" { + if tok := self.peek(); tok != token.SEMICOLON && tok != token.LEFT_PARENTHESIS { + if self.literal == "get" { + kind = ast.PropertyKindGet + } else { + kind = ast.PropertyKindSet + } + self.next() + } + } else if self.token == token.ASYNC { + if tok := self.peek(); tok != token.SEMICOLON && tok != token.LEFT_PARENTHESIS { + async = true + kind = ast.PropertyKindMethod + self.next() + } + } + generator := false + if self.token == token.MULTIPLY && (kind == "" || kind == ast.PropertyKindMethod) { + generator = true + kind = ast.PropertyKindMethod + self.next() + } + + _, keyName, value, tkn := self.parseObjectPropertyKey() + if value == nil { + continue + } + computed := tkn == token.ILLEGAL + _, private := value.(*ast.PrivateIdentifier) + + if static && !private && keyName == "prototype" { + self.error(value.Idx0(), "Classes may not have a static property named 'prototype'") + } + + if kind == "" && self.token == token.LEFT_PARENTHESIS { + kind = ast.PropertyKindMethod + } + + if kind != "" { + // method + if keyName == "constructor" && !computed { + if !static { + if kind != ast.PropertyKindMethod { + self.error(value.Idx0(), "Class constructor may not be an accessor") + } else if async { + self.error(value.Idx0(), "Class constructor may not be an async method") + } else if generator { + self.error(value.Idx0(), "Class constructor may not be a generator") + } + } else if private { + self.error(value.Idx0(), "Class constructor may not be a private method") + } + } + md := &ast.MethodDefinition{ + Idx: start, + Key: value, + Kind: kind, + Body: self.parseMethodDefinition(methodBodyStart, kind, generator, async), + Static: static, + Computed: computed, + } + node.Body = append(node.Body, md) + } else { + // field + isCtor := !computed && keyName == "constructor" + if !isCtor { + if name, ok := value.(*ast.PrivateIdentifier); ok { + isCtor = name.Name == "constructor" + } + } + if isCtor { + self.error(value.Idx0(), "Classes may not have a field named 'constructor'") + } + var initializer ast.Expression + if self.token == token.ASSIGN { + self.next() + initializer = self.parseExpression() + } + + if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE { + self.errorUnexpectedToken(self.token) + break + } + node.Body = append(node.Body, &ast.FieldDefinition{ + Idx: start, + Key: value, + Initializer: initializer, + Static: static, + Computed: computed, + }) + } + } + + node.RightBrace = self.expect(token.RIGHT_BRACE) + node.Source = self.slice(node.Class, node.RightBrace+1) + + return node +} + +func (self *_parser) parseDebuggerStatement() ast.Statement { + idx := self.expect(token.DEBUGGER) + + node := &ast.DebuggerStatement{ + Debugger: idx, + } + + self.semicolon() + + return node +} + +func (self *_parser) parseReturnStatement() ast.Statement { + idx := self.expect(token.RETURN) + + if !self.scope.inFunction { + self.error(idx, "Illegal return statement") + self.nextStatement() + return &ast.BadStatement{From: idx, To: self.idx} + } + + node := &ast.ReturnStatement{ + Return: idx, + } + + if !self.implicitSemicolon && self.token != token.SEMICOLON && self.token != token.RIGHT_BRACE && self.token != token.EOF { + node.Argument = self.parseExpression() + } + + self.semicolon() + + return node +} + +func (self *_parser) parseThrowStatement() ast.Statement { + idx := self.expect(token.THROW) + + if self.implicitSemicolon { + if self.chr == -1 { // Hackish + self.error(idx, "Unexpected end of input") + } else { + self.error(idx, "Illegal newline after throw") + } + self.nextStatement() + return &ast.BadStatement{From: idx, To: self.idx} + } + + node := &ast.ThrowStatement{ + Throw: idx, + Argument: self.parseExpression(), + } + + self.semicolon() + + return node +} + +func (self *_parser) parseSwitchStatement() ast.Statement { + idx := self.expect(token.SWITCH) + self.expect(token.LEFT_PARENTHESIS) + node := &ast.SwitchStatement{ + Switch: idx, + Discriminant: self.parseExpression(), + Default: -1, + } + self.expect(token.RIGHT_PARENTHESIS) + + self.expect(token.LEFT_BRACE) + + inSwitch := self.scope.inSwitch + self.scope.inSwitch = true + defer func() { + self.scope.inSwitch = inSwitch + }() + + for index := 0; self.token != token.EOF; index++ { + if self.token == token.RIGHT_BRACE { + node.RightBrace = self.idx + self.next() + break + } + + clause := self.parseCaseStatement() + if clause.Test == nil { + if node.Default != -1 { + self.error(clause.Case, "Already saw a default in switch") + } + node.Default = index + } + node.Body = append(node.Body, clause) + } + + return node +} + +func (self *_parser) parseWithStatement() ast.Statement { + node := &ast.WithStatement{} + node.With = self.expect(token.WITH) + self.expect(token.LEFT_PARENTHESIS) + node.Object = self.parseExpression() + self.expect(token.RIGHT_PARENTHESIS) + self.scope.allowLet = false + node.Body = self.parseStatement() + + return node +} + +func (self *_parser) parseCaseStatement() *ast.CaseStatement { + + node := &ast.CaseStatement{ + Case: self.idx, + } + if self.token == token.DEFAULT { + self.next() + } else { + self.expect(token.CASE) + node.Test = self.parseExpression() + } + self.expect(token.COLON) + + for { + if self.token == token.EOF || + self.token == token.RIGHT_BRACE || + self.token == token.CASE || + self.token == token.DEFAULT { + break + } + self.scope.allowLet = true + node.Consequent = append(node.Consequent, self.parseStatement()) + + } + + return node +} + +func (self *_parser) parseIterationStatement() ast.Statement { + inIteration := self.scope.inIteration + self.scope.inIteration = true + defer func() { + self.scope.inIteration = inIteration + }() + self.scope.allowLet = false + return self.parseStatement() +} + +func (self *_parser) parseForIn(idx file.Idx, into ast.ForInto) *ast.ForInStatement { + + // Already have consumed " in" + + source := self.parseExpression() + self.expect(token.RIGHT_PARENTHESIS) + + return &ast.ForInStatement{ + For: idx, + Into: into, + Source: source, + Body: self.parseIterationStatement(), + } +} + +func (self *_parser) parseForOf(idx file.Idx, into ast.ForInto) *ast.ForOfStatement { + + // Already have consumed " of" + + source := self.parseAssignmentExpression() + self.expect(token.RIGHT_PARENTHESIS) + + return &ast.ForOfStatement{ + For: idx, + Into: into, + Source: source, + Body: self.parseIterationStatement(), + } +} + +func (self *_parser) parseFor(idx file.Idx, initializer ast.ForLoopInitializer) *ast.ForStatement { + + // Already have consumed " ;" + + var test, update ast.Expression + + if self.token != token.SEMICOLON { + test = self.parseExpression() + } + self.expect(token.SEMICOLON) + + if self.token != token.RIGHT_PARENTHESIS { + update = self.parseExpression() + } + self.expect(token.RIGHT_PARENTHESIS) + + return &ast.ForStatement{ + For: idx, + Initializer: initializer, + Test: test, + Update: update, + Body: self.parseIterationStatement(), + } +} + +func (self *_parser) parseForOrForInStatement() ast.Statement { + idx := self.expect(token.FOR) + self.expect(token.LEFT_PARENTHESIS) + + var initializer ast.ForLoopInitializer + + forIn := false + forOf := false + var into ast.ForInto + if self.token != token.SEMICOLON { + + allowIn := self.scope.allowIn + self.scope.allowIn = false + tok := self.token + if tok == token.LET { + switch self.peek() { + case token.IDENTIFIER, token.LEFT_BRACKET, token.LEFT_BRACE: + default: + tok = token.IDENTIFIER + } + } + if tok == token.VAR || tok == token.LET || tok == token.CONST { + idx := self.idx + self.next() + var list []*ast.Binding + if tok == token.VAR { + list = self.parseVarDeclarationList(idx) + } else { + list = self.parseVariableDeclarationList() + } + if len(list) == 1 { + if self.token == token.IN { + self.next() // in + forIn = true + } else if self.token == token.IDENTIFIER && self.literal == "of" { + self.next() + forOf = true + } + } + if forIn || forOf { + if list[0].Initializer != nil { + self.error(list[0].Initializer.Idx0(), "for-in loop variable declaration may not have an initializer") + } + if tok == token.VAR { + into = &ast.ForIntoVar{ + Binding: list[0], + } + } else { + into = &ast.ForDeclaration{ + Idx: idx, + IsConst: tok == token.CONST, + Target: list[0].Target, + } + } + } else { + self.ensurePatternInit(list) + if tok == token.VAR { + initializer = &ast.ForLoopInitializerVarDeclList{ + List: list, + } + } else { + initializer = &ast.ForLoopInitializerLexicalDecl{ + LexicalDeclaration: ast.LexicalDeclaration{ + Idx: idx, + Token: tok, + List: list, + }, + } + } + } + } else { + expr := self.parseExpression() + if self.token == token.IN { + self.next() + forIn = true + } else if self.token == token.IDENTIFIER && self.literal == "of" { + self.next() + forOf = true + } + if forIn || forOf { + switch e := expr.(type) { + case *ast.Identifier, *ast.DotExpression, *ast.PrivateDotExpression, *ast.BracketExpression, *ast.Binding: + // These are all acceptable + case *ast.ObjectLiteral: + expr = self.reinterpretAsObjectAssignmentPattern(e) + case *ast.ArrayLiteral: + expr = self.reinterpretAsArrayAssignmentPattern(e) + default: + self.error(idx, "Invalid left-hand side in for-in or for-of") + self.nextStatement() + return &ast.BadStatement{From: idx, To: self.idx} + } + into = &ast.ForIntoExpression{ + Expression: expr, + } + } else { + initializer = &ast.ForLoopInitializerExpression{ + Expression: expr, + } + } + } + self.scope.allowIn = allowIn + } + + if forIn { + return self.parseForIn(idx, into) + } + if forOf { + return self.parseForOf(idx, into) + } + + self.expect(token.SEMICOLON) + return self.parseFor(idx, initializer) +} + +func (self *_parser) ensurePatternInit(list []*ast.Binding) { + for _, item := range list { + if _, ok := item.Target.(ast.Pattern); ok { + if item.Initializer == nil { + self.error(item.Idx1(), "Missing initializer in destructuring declaration") + break + } + } + } +} + +func (self *_parser) parseVariableStatement() *ast.VariableStatement { + + idx := self.expect(token.VAR) + + list := self.parseVarDeclarationList(idx) + self.ensurePatternInit(list) + self.semicolon() + + return &ast.VariableStatement{ + Var: idx, + List: list, + } +} + +func (self *_parser) parseLexicalDeclaration(tok token.Token) *ast.LexicalDeclaration { + idx := self.expect(tok) + if !self.scope.allowLet { + self.error(idx, "Lexical declaration cannot appear in a single-statement context") + } + + list := self.parseVariableDeclarationList() + self.ensurePatternInit(list) + self.semicolon() + + return &ast.LexicalDeclaration{ + Idx: idx, + Token: tok, + List: list, + } +} + +func (self *_parser) parseDoWhileStatement() ast.Statement { + inIteration := self.scope.inIteration + self.scope.inIteration = true + defer func() { + self.scope.inIteration = inIteration + }() + + node := &ast.DoWhileStatement{} + node.Do = self.expect(token.DO) + if self.token == token.LEFT_BRACE { + node.Body = self.parseBlockStatement() + } else { + self.scope.allowLet = false + node.Body = self.parseStatement() + } + + self.expect(token.WHILE) + self.expect(token.LEFT_PARENTHESIS) + node.Test = self.parseExpression() + node.RightParenthesis = self.expect(token.RIGHT_PARENTHESIS) + if self.token == token.SEMICOLON { + self.next() + } + + return node +} + +func (self *_parser) parseWhileStatement() ast.Statement { + idx := self.expect(token.WHILE) + self.expect(token.LEFT_PARENTHESIS) + node := &ast.WhileStatement{ + While: idx, + Test: self.parseExpression(), + } + self.expect(token.RIGHT_PARENTHESIS) + node.Body = self.parseIterationStatement() + + return node +} + +func (self *_parser) parseIfStatement() ast.Statement { + self.expect(token.IF) + self.expect(token.LEFT_PARENTHESIS) + node := &ast.IfStatement{ + Test: self.parseExpression(), + } + self.expect(token.RIGHT_PARENTHESIS) + + if self.token == token.LEFT_BRACE { + node.Consequent = self.parseBlockStatement() + } else { + self.scope.allowLet = false + node.Consequent = self.parseStatement() + } + + if self.token == token.ELSE { + self.next() + self.scope.allowLet = false + node.Alternate = self.parseStatement() + } + + return node +} + +func (self *_parser) parseSourceElements() (body []ast.Statement) { + for self.token != token.EOF { + self.scope.allowLet = true + body = append(body, self.parseStatement()) + } + + return body +} + +func (self *_parser) parseProgram() *ast.Program { + prg := &ast.Program{ + Body: self.parseSourceElements(), + DeclarationList: self.scope.declarationList, + File: self.file, + } + self.file.SetSourceMap(self.parseSourceMap()) + return prg +} + +func extractSourceMapLine(str string) string { + for { + p := strings.LastIndexByte(str, '\n') + line := str[p+1:] + if line != "" && line != "})" { + if strings.HasPrefix(line, "//# sourceMappingURL=") { + return line + } + break + } + if p >= 0 { + str = str[:p] + } else { + break + } + } + return "" +} + +func (self *_parser) parseSourceMap() *sourcemap.Consumer { + if self.opts.disableSourceMaps { + return nil + } + if smLine := extractSourceMapLine(self.str); smLine != "" { + urlIndex := strings.Index(smLine, "=") + urlStr := smLine[urlIndex+1:] + + var data []byte + var err error + if strings.HasPrefix(urlStr, "data:application/json") { + b64Index := strings.Index(urlStr, ",") + b64 := urlStr[b64Index+1:] + data, err = base64.StdEncoding.DecodeString(b64) + } else { + if sourceURL := file.ResolveSourcemapURL(self.file.Name(), urlStr); sourceURL != nil { + if self.opts.sourceMapLoader != nil { + data, err = self.opts.sourceMapLoader(sourceURL.String()) + } else { + if sourceURL.Scheme == "" || sourceURL.Scheme == "file" { + data, err = os.ReadFile(sourceURL.Path) + } else { + err = fmt.Errorf("unsupported source map URL scheme: %s", sourceURL.Scheme) + } + } + } + } + + if err != nil { + self.error(file.Idx(0), "Could not load source map: %v", err) + return nil + } + if data == nil { + return nil + } + + if sm, err := sourcemap.Parse(self.file.Name(), data); err == nil { + return sm + } else { + self.error(file.Idx(0), "Could not parse source map: %v", err) + } + } + return nil +} + +func (self *_parser) parseBreakStatement() ast.Statement { + idx := self.expect(token.BREAK) + semicolon := self.implicitSemicolon + if self.token == token.SEMICOLON { + semicolon = true + self.next() + } + + if semicolon || self.token == token.RIGHT_BRACE { + self.implicitSemicolon = false + if !self.scope.inIteration && !self.scope.inSwitch { + goto illegal + } + return &ast.BranchStatement{ + Idx: idx, + Token: token.BREAK, + } + } + + self.tokenToBindingId() + if self.token == token.IDENTIFIER { + identifier := self.parseIdentifier() + if !self.scope.hasLabel(identifier.Name) { + self.error(idx, "Undefined label '%s'", identifier.Name) + return &ast.BadStatement{From: idx, To: identifier.Idx1()} + } + self.semicolon() + return &ast.BranchStatement{ + Idx: idx, + Token: token.BREAK, + Label: identifier, + } + } + + self.expect(token.IDENTIFIER) + +illegal: + self.error(idx, "Illegal break statement") + self.nextStatement() + return &ast.BadStatement{From: idx, To: self.idx} +} + +func (self *_parser) parseContinueStatement() ast.Statement { + idx := self.expect(token.CONTINUE) + semicolon := self.implicitSemicolon + if self.token == token.SEMICOLON { + semicolon = true + self.next() + } + + if semicolon || self.token == token.RIGHT_BRACE { + self.implicitSemicolon = false + if !self.scope.inIteration { + goto illegal + } + return &ast.BranchStatement{ + Idx: idx, + Token: token.CONTINUE, + } + } + + self.tokenToBindingId() + if self.token == token.IDENTIFIER { + identifier := self.parseIdentifier() + if !self.scope.hasLabel(identifier.Name) { + self.error(idx, "Undefined label '%s'", identifier.Name) + return &ast.BadStatement{From: idx, To: identifier.Idx1()} + } + if !self.scope.inIteration { + goto illegal + } + self.semicolon() + return &ast.BranchStatement{ + Idx: idx, + Token: token.CONTINUE, + Label: identifier, + } + } + + self.expect(token.IDENTIFIER) + +illegal: + self.error(idx, "Illegal continue statement") + self.nextStatement() + return &ast.BadStatement{From: idx, To: self.idx} +} + +// Find the next statement after an error (recover) +func (self *_parser) nextStatement() { + for { + switch self.token { + case token.BREAK, token.CONTINUE, + token.FOR, token.IF, token.RETURN, token.SWITCH, + token.VAR, token.DO, token.TRY, token.WITH, + token.WHILE, token.THROW, token.CATCH, token.FINALLY: + // Return only if parser made some progress since last + // sync or if it has not reached 10 next calls without + // progress. Otherwise consume at least one token to + // avoid an endless parser loop + if self.idx == self.recover.idx && self.recover.count < 10 { + self.recover.count++ + return + } + if self.idx > self.recover.idx { + self.recover.idx = self.idx + self.recover.count = 0 + return + } + // Reaching here indicates a parser bug, likely an + // incorrect token list in this function, but it only + // leads to skipping of possibly correct code if a + // previous error is present, and thus is preferred + // over a non-terminating parse. + case token.EOF: + return + } + self.next() + } +} diff --git a/pkg/xscript/engine/parser/testutil_test.go b/pkg/xscript/engine/parser/testutil_test.go new file mode 100644 index 0000000..f4c9819 --- /dev/null +++ b/pkg/xscript/engine/parser/testutil_test.go @@ -0,0 +1,49 @@ +package parser + +import ( + "fmt" + "path/filepath" + "runtime" + "testing" +) + +// Quick and dirty replacement for terst + +func tt(t *testing.T, f func()) { + defer func() { + if x := recover(); x != nil { + pcs := make([]uintptr, 16) + pcs = pcs[:runtime.Callers(1, pcs)] + frames := runtime.CallersFrames(pcs) + var file string + var line int + for { + frame, more := frames.Next() + // The line number here must match the line where f() is called (see below) + if frame.Line == 40 && filepath.Base(frame.File) == "testutil_test.go" { + break + } + + if !more { + break + } + file, line = frame.File, frame.Line + } + if line > 0 { + t.Errorf("Error at %s:%d: %v", filepath.Base(file), line, x) + } else { + t.Errorf("Error at : %v", x) + } + } + }() + + f() +} + +func is(a, b any) { + as := fmt.Sprintf("%v", a) + bs := fmt.Sprintf("%v", b) + if as != bs { + panic(fmt.Errorf("%+v(%T) != %+v(%T)", a, a, b, b)) + } +} diff --git a/pkg/xscript/engine/profiler.go b/pkg/xscript/engine/profiler.go new file mode 100644 index 0000000..dcc6cf6 --- /dev/null +++ b/pkg/xscript/engine/profiler.go @@ -0,0 +1,350 @@ +package engine + +import ( + "errors" + "io" + "strconv" + "sync" + "sync/atomic" + "time" + + "github.com/google/pprof/profile" +) + +const profInterval = 10 * time.Millisecond +const profMaxStackDepth = 64 + +const ( + profReqNone int32 = iota + profReqDoSample + profReqSampleReady + profReqStop +) + +type _globalProfiler struct { + p profiler + w io.Writer + + enabled int32 +} + +var globalProfiler _globalProfiler + +type profTracker struct { + req, finished int32 + start, stop time.Time + numFrames int + frames [profMaxStackDepth]StackFrame +} + +type profiler struct { + mu sync.Mutex + trackers []*profTracker + buf *profBuffer + running bool +} + +type profFunc struct { + f profile.Function + locs map[int32]*profile.Location +} + +type profSampleNode struct { + loc *profile.Location + sample *profile.Sample + parent *profSampleNode + children map[*profile.Location]*profSampleNode +} + +type profBuffer struct { + funcs map[*Program]*profFunc + root profSampleNode +} + +func (pb *profBuffer) addSample(pt *profTracker) { + sampleFrames := pt.frames[:pt.numFrames] + n := &pb.root + for j := len(sampleFrames) - 1; j >= 0; j-- { + frame := sampleFrames[j] + if frame.prg == nil { + continue + } + var f *profFunc + if f = pb.funcs[frame.prg]; f == nil { + f = &profFunc{ + locs: make(map[int32]*profile.Location), + } + if pb.funcs == nil { + pb.funcs = make(map[*Program]*profFunc) + } + pb.funcs[frame.prg] = f + } + var loc *profile.Location + if loc = f.locs[int32(frame.pc)]; loc == nil { + loc = &profile.Location{} + f.locs[int32(frame.pc)] = loc + } + if nn := n.children[loc]; nn == nil { + if n.children == nil { + n.children = make(map[*profile.Location]*profSampleNode, 1) + } + nn = &profSampleNode{ + parent: n, + loc: loc, + } + n.children[loc] = nn + n = nn + } else { + n = nn + } + } + smpl := n.sample + if smpl == nil { + locs := make([]*profile.Location, 0, len(sampleFrames)) + for n1 := n; n1.loc != nil; n1 = n1.parent { + locs = append(locs, n1.loc) + } + smpl = &profile.Sample{ + Location: locs, + Value: make([]int64, 2), + } + n.sample = smpl + } + smpl.Value[0]++ + smpl.Value[1] += int64(pt.stop.Sub(pt.start)) +} + +func (pb *profBuffer) profile() *profile.Profile { + pr := profile.Profile{} + pr.SampleType = []*profile.ValueType{ + {Type: "samples", Unit: "count"}, + {Type: "cpu", Unit: "nanoseconds"}, + } + pr.PeriodType = pr.SampleType[1] + pr.Period = int64(profInterval) + mapping := &profile.Mapping{ + ID: 1, + File: "[ECMAScript code]", + } + pr.Mapping = make([]*profile.Mapping, 1, len(pb.funcs)+1) + pr.Mapping[0] = mapping + + pr.Function = make([]*profile.Function, 0, len(pb.funcs)) + funcNames := make(map[string]struct{}) + var funcId, locId uint64 + for prg, f := range pb.funcs { + fileName := prg.src.Name() + funcId++ + f.f.ID = funcId + f.f.Filename = fileName + var funcName string + if prg.funcName != "" { + funcName = prg.funcName.String() + } else { + funcName = "" + } + // Make sure the function name is unique, otherwise the graph display merges them into one node, even + // if they are in different mappings. + if _, exists := funcNames[funcName]; exists { + funcName += "." + strconv.FormatUint(f.f.ID, 10) + } else { + funcNames[funcName] = struct{}{} + } + f.f.Name = funcName + pr.Function = append(pr.Function, &f.f) + for pc, loc := range f.locs { + locId++ + loc.ID = locId + pos := prg.src.Position(prg.sourceOffset(int(pc))) + loc.Line = []profile.Line{ + { + Function: &f.f, + Line: int64(pos.Line), + }, + } + + loc.Mapping = mapping + pr.Location = append(pr.Location, loc) + } + } + pb.addSamples(&pr, &pb.root) + return &pr +} + +func (pb *profBuffer) addSamples(p *profile.Profile, n *profSampleNode) { + if n.sample != nil { + p.Sample = append(p.Sample, n.sample) + } + for _, child := range n.children { + pb.addSamples(p, child) + } +} + +func (p *profiler) run() { + ticker := time.NewTicker(profInterval) + counter := 0 + + for ts := range ticker.C { + p.mu.Lock() + left := len(p.trackers) + if left == 0 { + break + } + for { + // This loop runs until either one of the VMs is signalled or all of the VMs are scanned and found + // busy or deleted. + if counter >= len(p.trackers) { + counter = 0 + } + tracker := p.trackers[counter] + req := atomic.LoadInt32(&tracker.req) + if req == profReqSampleReady { + p.buf.addSample(tracker) + } + if atomic.LoadInt32(&tracker.finished) != 0 { + p.trackers[counter] = p.trackers[len(p.trackers)-1] + p.trackers[len(p.trackers)-1] = nil + p.trackers = p.trackers[:len(p.trackers)-1] + } else { + counter++ + if req != profReqDoSample { + // signal the VM to take a sample + tracker.start = ts + atomic.StoreInt32(&tracker.req, profReqDoSample) + break + } + } + left-- + if left <= 0 { + // all VMs are busy + break + } + } + p.mu.Unlock() + } + ticker.Stop() + p.running = false + p.mu.Unlock() +} + +func (p *profiler) registerVm() *profTracker { + pt := new(profTracker) + p.mu.Lock() + if p.buf != nil { + p.trackers = append(p.trackers, pt) + if !p.running { + go p.run() + p.running = true + } + } else { + pt.req = profReqStop + } + p.mu.Unlock() + return pt +} + +func (p *profiler) start() error { + p.mu.Lock() + if p.buf != nil { + p.mu.Unlock() + return errors.New("profiler is already active") + } + p.buf = new(profBuffer) + p.mu.Unlock() + return nil +} + +func (p *profiler) stop() *profile.Profile { + p.mu.Lock() + trackers, buf := p.trackers, p.buf + p.trackers, p.buf = nil, nil + p.mu.Unlock() + if buf != nil { + k := 0 + for i, tracker := range trackers { + req := atomic.LoadInt32(&tracker.req) + if req == profReqSampleReady { + buf.addSample(tracker) + } else if req == profReqDoSample { + // In case the VM is requested to do a sample, there is a small chance of a race + // where we set profReqStop in between the read and the write, so that the req + // ends up being set to profReqSampleReady. It's no such a big deal if we do nothing, + // it just means the VM remains in tracing mode until it finishes the current run, + // but we do an extra cleanup step later just in case. + if i != k { + trackers[k] = trackers[i] + } + k++ + } + atomic.StoreInt32(&tracker.req, profReqStop) + } + + if k > 0 { + trackers = trackers[:k] + go func() { + // Make sure all VMs are requested to stop tracing. + for { + k := 0 + for i, tracker := range trackers { + req := atomic.LoadInt32(&tracker.req) + if req != profReqStop { + atomic.StoreInt32(&tracker.req, profReqStop) + if i != k { + trackers[k] = trackers[i] + } + k++ + } + } + + if k == 0 { + return + } + trackers = trackers[:k] + time.Sleep(100 * time.Millisecond) + } + }() + } + return buf.profile() + } + return nil +} + +/* +StartProfile enables execution time profiling for all Runtimes within the current process. +This works similar to pprof.StartCPUProfile and produces the same format which can be consumed by `go tool pprof`. +There are, however, a few notable differences. Firstly, it's not a CPU profile, rather "execution time" profile. +It measures the time the VM spends executing an instruction. If this instruction happens to be a call to a +blocking Go function, the waiting time will be measured. Secondly, the 'cpu' sample isn't simply `count*period`, +it's the time interval between when sampling was requested and when the instruction has finished. If a VM is still +executing the same instruction when the time comes for the next sample, the sampling is skipped (i.e. `count` doesn't +grow). + +If there are multiple functions with the same name, their names get a '.N' suffix, where N is a unique number, +because otherwise the graph view merges them together (even if they are in different mappings). This includes +"" functions. + +The sampling period is set to 10ms. + +It returns an error if profiling is already active. +*/ +func StartProfile(w io.Writer) error { + err := globalProfiler.p.start() + if err != nil { + return err + } + globalProfiler.w = w + atomic.StoreInt32(&globalProfiler.enabled, 1) + return nil +} + +/* +StopProfile stops the current profile initiated by StartProfile, if any. +*/ +func StopProfile() { + atomic.StoreInt32(&globalProfiler.enabled, 0) + pr := globalProfiler.p.stop() + if pr != nil { + _ = pr.Write(globalProfiler.w) + } + globalProfiler.w = nil +} diff --git a/pkg/xscript/engine/profiler_test.go b/pkg/xscript/engine/profiler_test.go new file mode 100644 index 0000000..89b0e1b --- /dev/null +++ b/pkg/xscript/engine/profiler_test.go @@ -0,0 +1,102 @@ +package engine + +import ( + "sync/atomic" + "testing" + "time" +) + +func TestProfiler(t *testing.T) { + + err := StartProfile(nil) + if err != nil { + t.Fatal(err) + } + + vm := New() + go func() { + _, err := vm.RunScript("test123.js", ` + const a = 2 + 2; + function loop() { + for(;;) {} + } + loop(); + `) + if err != nil { + if _, ok := err.(*InterruptedError); !ok { + panic(err) + } + } + }() + + time.Sleep(200 * time.Millisecond) + + atomic.StoreInt32(&globalProfiler.enabled, 0) + pr := globalProfiler.p.stop() + + if len(pr.Sample) == 0 { + t.Fatal("No samples were recorded") + } + + var running bool + for i := 0; i < 10; i++ { + time.Sleep(10 * time.Millisecond) + globalProfiler.p.mu.Lock() + running = globalProfiler.p.running + globalProfiler.p.mu.Unlock() + if !running { + break + } + } + if running { + t.Fatal("The profiler is still running") + } + vm.Interrupt(nil) +} + +func TestProfiler1(t *testing.T) { + t.Skip("This test takes too long with race detector enabled and is non-deterministic. It's left here mostly for documentation purposes.") + + err := StartProfile(nil) + if err != nil { + t.Fatal(err) + } + + go func() { + sleep := func() { + time.Sleep(1 * time.Second) + } + // Spawn new vms faster than once every 10ms (the profiler interval) and make sure they don't finish too soon. + // This means (*profiler).run() won't be fast enough to collect the samples, so they must be collected + // after the profiler is stopped. + for i := 0; i < 500; i++ { + go func() { + vm := New() + vm.Set("sleep", sleep) + _, err := vm.RunScript("test123.js", ` + function loop() { + for (let i = 0; i < 50000; i++) { + const a = Math.pow(Math.Pi, Math.Pi); + } + } + loop(); + sleep(); + `) + if err != nil { + if _, ok := err.(*InterruptedError); !ok { + panic(err) + } + } + }() + time.Sleep(1 * time.Millisecond) + } + }() + + time.Sleep(500 * time.Millisecond) + atomic.StoreInt32(&globalProfiler.enabled, 0) + pr := globalProfiler.p.stop() + + if len(pr.Sample) == 0 { + t.Fatal("No samples were recorded") + } +} diff --git a/pkg/xscript/engine/proxy.go b/pkg/xscript/engine/proxy.go new file mode 100644 index 0000000..1b7c158 --- /dev/null +++ b/pkg/xscript/engine/proxy.go @@ -0,0 +1,1074 @@ +package engine + +import ( + "fmt" + "reflect" + + "pandax/pkg/xscript/engine/unistring" +) + +// Proxy is a Go wrapper around ECMAScript Proxy. Calling Runtime.ToValue() on it +// returns the underlying Proxy. Calling Export() on an ECMAScript Proxy returns a wrapper. +// Use Runtime.NewProxy() to create one. +type Proxy struct { + proxy *proxyObject +} + +var ( + proxyType = reflect.TypeOf(Proxy{}) +) + +type proxyPropIter struct { + p *proxyObject + names []Value + idx int +} + +func (i *proxyPropIter) next() (propIterItem, iterNextFunc) { + for i.idx < len(i.names) { + name := i.names[i.idx] + i.idx++ + return propIterItem{name: name}, i.next + } + return propIterItem{}, nil +} + +func (r *Runtime) newProxyObject(target, handler, proto *Object) *proxyObject { + return r._newProxyObject(target, &jsProxyHandler{handler: handler}, proto) +} + +func (r *Runtime) _newProxyObject(target *Object, handler proxyHandler, proto *Object) *proxyObject { + v := &Object{runtime: r} + p := &proxyObject{} + v.self = p + p.val = v + p.class = classObject + if proto == nil { + p.prototype = r.global.ObjectPrototype + } else { + p.prototype = proto + } + p.extensible = false + p.init() + p.target = target + p.handler = handler + if call, ok := target.self.assertCallable(); ok { + p.call = call + } + if ctor := target.self.assertConstructor(); ctor != nil { + p.ctor = ctor + } + return p +} + +func (p Proxy) Revoke() { + p.proxy.revoke() +} + +func (p Proxy) Handler() *Object { + if handler := p.proxy.handler; handler != nil { + return handler.toObject(p.proxy.val.runtime) + } + return nil +} + +func (p Proxy) Target() *Object { + return p.proxy.target +} + +func (p Proxy) toValue(r *Runtime) Value { + if p.proxy == nil { + return _null + } + proxy := p.proxy.val + if proxy.runtime != r { + panic(r.NewTypeError("Illegal runtime transition of a Proxy")) + } + return proxy +} + +type proxyTrap string + +const ( + proxy_trap_getPrototypeOf = "getPrototypeOf" + proxy_trap_setPrototypeOf = "setPrototypeOf" + proxy_trap_isExtensible = "isExtensible" + proxy_trap_preventExtensions = "preventExtensions" + proxy_trap_getOwnPropertyDescriptor = "getOwnPropertyDescriptor" + proxy_trap_defineProperty = "defineProperty" + proxy_trap_has = "has" + proxy_trap_get = "get" + proxy_trap_set = "set" + proxy_trap_deleteProperty = "deleteProperty" + proxy_trap_ownKeys = "ownKeys" + proxy_trap_apply = "apply" + proxy_trap_construct = "construct" +) + +func (p proxyTrap) String() (name string) { + return string(p) +} + +type proxyHandler interface { + getPrototypeOf(target *Object) (Value, bool) + setPrototypeOf(target *Object, proto *Object) (bool, bool) + isExtensible(target *Object) (bool, bool) + preventExtensions(target *Object) (bool, bool) + + getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) + getOwnPropertyDescriptorIdx(target *Object, prop valueInt) (Value, bool) + getOwnPropertyDescriptorSym(target *Object, prop *Symbol) (Value, bool) + + definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) + definePropertyIdx(target *Object, prop valueInt, desc PropertyDescriptor) (bool, bool) + definePropertySym(target *Object, prop *Symbol, desc PropertyDescriptor) (bool, bool) + + hasStr(target *Object, prop unistring.String) (bool, bool) + hasIdx(target *Object, prop valueInt) (bool, bool) + hasSym(target *Object, prop *Symbol) (bool, bool) + + getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) + getIdx(target *Object, prop valueInt, receiver Value) (Value, bool) + getSym(target *Object, prop *Symbol, receiver Value) (Value, bool) + + setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) + setIdx(target *Object, prop valueInt, value Value, receiver Value) (bool, bool) + setSym(target *Object, prop *Symbol, value Value, receiver Value) (bool, bool) + + deleteStr(target *Object, prop unistring.String) (bool, bool) + deleteIdx(target *Object, prop valueInt) (bool, bool) + deleteSym(target *Object, prop *Symbol) (bool, bool) + + ownKeys(target *Object) (*Object, bool) + apply(target *Object, this Value, args []Value) (Value, bool) + construct(target *Object, args []Value, newTarget *Object) (Value, bool) + + toObject(*Runtime) *Object +} + +type jsProxyHandler struct { + handler *Object +} + +func (h *jsProxyHandler) toObject(*Runtime) *Object { + return h.handler +} + +func (h *jsProxyHandler) proxyCall(trap proxyTrap, args ...Value) (Value, bool) { + r := h.handler.runtime + + if m := toMethod(r.getVStr(h.handler, unistring.String(trap.String()))); m != nil { + return m(FunctionCall{ + This: h.handler, + Arguments: args, + }), true + } + + return nil, false +} + +func (h *jsProxyHandler) boolProxyCall(trap proxyTrap, args ...Value) (bool, bool) { + if v, ok := h.proxyCall(trap, args...); ok { + return v.ToBoolean(), true + } + return false, false +} + +func (h *jsProxyHandler) getPrototypeOf(target *Object) (Value, bool) { + return h.proxyCall(proxy_trap_getPrototypeOf, target) +} + +func (h *jsProxyHandler) setPrototypeOf(target *Object, proto *Object) (bool, bool) { + var protoVal Value + if proto != nil { + protoVal = proto + } else { + protoVal = _null + } + return h.boolProxyCall(proxy_trap_setPrototypeOf, target, protoVal) +} + +func (h *jsProxyHandler) isExtensible(target *Object) (bool, bool) { + return h.boolProxyCall(proxy_trap_isExtensible, target) +} + +func (h *jsProxyHandler) preventExtensions(target *Object) (bool, bool) { + return h.boolProxyCall(proxy_trap_preventExtensions, target) +} + +func (h *jsProxyHandler) getOwnPropertyDescriptorStr(target *Object, prop unistring.String) (Value, bool) { + return h.proxyCall(proxy_trap_getOwnPropertyDescriptor, target, stringValueFromRaw(prop)) +} + +func (h *jsProxyHandler) getOwnPropertyDescriptorIdx(target *Object, prop valueInt) (Value, bool) { + return h.proxyCall(proxy_trap_getOwnPropertyDescriptor, target, prop.toString()) +} + +func (h *jsProxyHandler) getOwnPropertyDescriptorSym(target *Object, prop *Symbol) (Value, bool) { + return h.proxyCall(proxy_trap_getOwnPropertyDescriptor, target, prop) +} + +func (h *jsProxyHandler) definePropertyStr(target *Object, prop unistring.String, desc PropertyDescriptor) (bool, bool) { + return h.boolProxyCall(proxy_trap_defineProperty, target, stringValueFromRaw(prop), desc.toValue(h.handler.runtime)) +} + +func (h *jsProxyHandler) definePropertyIdx(target *Object, prop valueInt, desc PropertyDescriptor) (bool, bool) { + return h.boolProxyCall(proxy_trap_defineProperty, target, prop.toString(), desc.toValue(h.handler.runtime)) +} + +func (h *jsProxyHandler) definePropertySym(target *Object, prop *Symbol, desc PropertyDescriptor) (bool, bool) { + return h.boolProxyCall(proxy_trap_defineProperty, target, prop, desc.toValue(h.handler.runtime)) +} + +func (h *jsProxyHandler) hasStr(target *Object, prop unistring.String) (bool, bool) { + return h.boolProxyCall(proxy_trap_has, target, stringValueFromRaw(prop)) +} + +func (h *jsProxyHandler) hasIdx(target *Object, prop valueInt) (bool, bool) { + return h.boolProxyCall(proxy_trap_has, target, prop.toString()) +} + +func (h *jsProxyHandler) hasSym(target *Object, prop *Symbol) (bool, bool) { + return h.boolProxyCall(proxy_trap_has, target, prop) +} + +func (h *jsProxyHandler) getStr(target *Object, prop unistring.String, receiver Value) (Value, bool) { + return h.proxyCall(proxy_trap_get, target, stringValueFromRaw(prop), receiver) +} + +func (h *jsProxyHandler) getIdx(target *Object, prop valueInt, receiver Value) (Value, bool) { + return h.proxyCall(proxy_trap_get, target, prop.toString(), receiver) +} + +func (h *jsProxyHandler) getSym(target *Object, prop *Symbol, receiver Value) (Value, bool) { + return h.proxyCall(proxy_trap_get, target, prop, receiver) +} + +func (h *jsProxyHandler) setStr(target *Object, prop unistring.String, value Value, receiver Value) (bool, bool) { + return h.boolProxyCall(proxy_trap_set, target, stringValueFromRaw(prop), value, receiver) +} + +func (h *jsProxyHandler) setIdx(target *Object, prop valueInt, value Value, receiver Value) (bool, bool) { + return h.boolProxyCall(proxy_trap_set, target, prop.toString(), value, receiver) +} + +func (h *jsProxyHandler) setSym(target *Object, prop *Symbol, value Value, receiver Value) (bool, bool) { + return h.boolProxyCall(proxy_trap_set, target, prop, value, receiver) +} + +func (h *jsProxyHandler) deleteStr(target *Object, prop unistring.String) (bool, bool) { + return h.boolProxyCall(proxy_trap_deleteProperty, target, stringValueFromRaw(prop)) +} + +func (h *jsProxyHandler) deleteIdx(target *Object, prop valueInt) (bool, bool) { + return h.boolProxyCall(proxy_trap_deleteProperty, target, prop.toString()) +} + +func (h *jsProxyHandler) deleteSym(target *Object, prop *Symbol) (bool, bool) { + return h.boolProxyCall(proxy_trap_deleteProperty, target, prop) +} + +func (h *jsProxyHandler) ownKeys(target *Object) (*Object, bool) { + if v, ok := h.proxyCall(proxy_trap_ownKeys, target); ok { + return h.handler.runtime.toObject(v), true + } + return nil, false +} + +func (h *jsProxyHandler) apply(target *Object, this Value, args []Value) (Value, bool) { + return h.proxyCall(proxy_trap_apply, target, this, h.handler.runtime.newArrayValues(args)) +} + +func (h *jsProxyHandler) construct(target *Object, args []Value, newTarget *Object) (Value, bool) { + return h.proxyCall(proxy_trap_construct, target, h.handler.runtime.newArrayValues(args), newTarget) +} + +type proxyObject struct { + baseObject + target *Object + handler proxyHandler + call func(FunctionCall) Value + ctor func(args []Value, newTarget *Object) *Object +} + +func (p *proxyObject) checkHandler() proxyHandler { + r := p.val.runtime + if handler := p.handler; handler != nil { + return handler + } + panic(r.NewTypeError("Proxy already revoked")) +} + +func (p *proxyObject) proto() *Object { + target := p.target + if v, ok := p.checkHandler().getPrototypeOf(target); ok { + var handlerProto *Object + if v != _null { + handlerProto = p.val.runtime.toObject(v) + } + if !target.self.isExtensible() && !p.__sameValue(handlerProto, target.self.proto()) { + panic(p.val.runtime.NewTypeError("'getPrototypeOf' on proxy: proxy target is non-extensible but the trap did not return its actual prototype")) + } + return handlerProto + } + + return target.self.proto() +} + +func (p *proxyObject) setProto(proto *Object, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().setPrototypeOf(target, proto); ok { + if v { + if !target.self.isExtensible() && !p.__sameValue(proto, target.self.proto()) { + panic(p.val.runtime.NewTypeError("'setPrototypeOf' on proxy: trap returned truish for setting a new prototype on the non-extensible proxy target")) + } + return true + } else { + p.val.runtime.typeErrorResult(throw, "'setPrototypeOf' on proxy: trap returned falsish") + return false + } + } + + return target.self.setProto(proto, throw) +} + +func (p *proxyObject) isExtensible() bool { + target := p.target + if booleanTrapResult, ok := p.checkHandler().isExtensible(p.target); ok { + if te := target.self.isExtensible(); booleanTrapResult != te { + panic(p.val.runtime.NewTypeError("'isExtensible' on proxy: trap result does not reflect extensibility of proxy target (which is '%v')", te)) + } + return booleanTrapResult + } + + return target.self.isExtensible() +} + +func (p *proxyObject) preventExtensions(throw bool) bool { + target := p.target + if booleanTrapResult, ok := p.checkHandler().preventExtensions(target); ok { + if !booleanTrapResult { + p.val.runtime.typeErrorResult(throw, "'preventExtensions' on proxy: trap returned falsish") + return false + } + if te := target.self.isExtensible(); booleanTrapResult && te { + panic(p.val.runtime.NewTypeError("'preventExtensions' on proxy: trap returned truish but the proxy target is extensible")) + } + } + + return target.self.preventExtensions(throw) +} + +func propToValueProp(v Value) *valueProperty { + if v == nil { + return nil + } + if v, ok := v.(*valueProperty); ok { + return v + } + return &valueProperty{ + value: v, + writable: true, + configurable: true, + enumerable: true, + } +} + +func (p *proxyObject) proxyDefineOwnPropertyPreCheck(trapResult, throw bool) bool { + if !trapResult { + p.val.runtime.typeErrorResult(throw, "'defineProperty' on proxy: trap returned falsish") + return false + } + return true +} + +func (p *proxyObject) proxyDefineOwnPropertyPostCheck(prop Value, target *Object, descr PropertyDescriptor) { + targetDesc := propToValueProp(prop) + extensibleTarget := target.self.isExtensible() + settingConfigFalse := descr.Configurable == FLAG_FALSE + if targetDesc == nil { + if !extensibleTarget { + panic(p.val.runtime.NewTypeError()) + } + if settingConfigFalse { + panic(p.val.runtime.NewTypeError()) + } + } else { + if !p.__isCompatibleDescriptor(extensibleTarget, &descr, targetDesc) { + panic(p.val.runtime.NewTypeError()) + } + if settingConfigFalse && targetDesc.configurable { + panic(p.val.runtime.NewTypeError()) + } + if targetDesc.value != nil && !targetDesc.configurable && targetDesc.writable { + if descr.Writable == FLAG_FALSE { + panic(p.val.runtime.NewTypeError()) + } + } + } +} + +func (p *proxyObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + target := p.target + if booleanTrapResult, ok := p.checkHandler().definePropertyStr(target, name, descr); ok { + if !p.proxyDefineOwnPropertyPreCheck(booleanTrapResult, throw) { + return false + } + p.proxyDefineOwnPropertyPostCheck(target.self.getOwnPropStr(name), target, descr) + return true + } + return target.self.defineOwnPropertyStr(name, descr, throw) +} + +func (p *proxyObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + target := p.target + if booleanTrapResult, ok := p.checkHandler().definePropertyIdx(target, idx, descr); ok { + if !p.proxyDefineOwnPropertyPreCheck(booleanTrapResult, throw) { + return false + } + p.proxyDefineOwnPropertyPostCheck(target.self.getOwnPropIdx(idx), target, descr) + return true + } + + return target.self.defineOwnPropertyIdx(idx, descr, throw) +} + +func (p *proxyObject) defineOwnPropertySym(s *Symbol, descr PropertyDescriptor, throw bool) bool { + target := p.target + if booleanTrapResult, ok := p.checkHandler().definePropertySym(target, s, descr); ok { + if !p.proxyDefineOwnPropertyPreCheck(booleanTrapResult, throw) { + return false + } + p.proxyDefineOwnPropertyPostCheck(target.self.getOwnPropSym(s), target, descr) + return true + } + + return target.self.defineOwnPropertySym(s, descr, throw) +} + +func (p *proxyObject) proxyHasChecks(targetProp Value, target *Object, name fmt.Stringer) { + targetDesc := propToValueProp(targetProp) + if targetDesc != nil { + if !targetDesc.configurable { + panic(p.val.runtime.NewTypeError("'has' on proxy: trap returned falsish for property '%s' which exists in the proxy target as non-configurable", name.String())) + } + if !target.self.isExtensible() { + panic(p.val.runtime.NewTypeError("'has' on proxy: trap returned falsish for property '%s' but the proxy target is not extensible", name.String())) + } + } +} + +func (p *proxyObject) hasPropertyStr(name unistring.String) bool { + target := p.target + if b, ok := p.checkHandler().hasStr(target, name); ok { + if !b { + p.proxyHasChecks(target.self.getOwnPropStr(name), target, name) + } + return b + } + + return target.self.hasPropertyStr(name) +} + +func (p *proxyObject) hasPropertyIdx(idx valueInt) bool { + target := p.target + if b, ok := p.checkHandler().hasIdx(target, idx); ok { + if !b { + p.proxyHasChecks(target.self.getOwnPropIdx(idx), target, idx) + } + return b + } + + return target.self.hasPropertyIdx(idx) +} + +func (p *proxyObject) hasPropertySym(s *Symbol) bool { + target := p.target + if b, ok := p.checkHandler().hasSym(target, s); ok { + if !b { + p.proxyHasChecks(target.self.getOwnPropSym(s), target, s) + } + return b + } + + return target.self.hasPropertySym(s) +} + +func (p *proxyObject) hasOwnPropertyStr(name unistring.String) bool { + return p.getOwnPropStr(name) != nil +} + +func (p *proxyObject) hasOwnPropertyIdx(idx valueInt) bool { + return p.getOwnPropIdx(idx) != nil +} + +func (p *proxyObject) hasOwnPropertySym(s *Symbol) bool { + return p.getOwnPropSym(s) != nil +} + +func (p *proxyObject) proxyGetOwnPropertyDescriptor(targetProp Value, target *Object, trapResult Value, name fmt.Stringer) Value { + r := p.val.runtime + targetDesc := propToValueProp(targetProp) + var trapResultObj *Object + if trapResult != nil && trapResult != _undefined { + if obj, ok := trapResult.(*Object); ok { + trapResultObj = obj + } else { + panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap returned neither object nor undefined for property '%s'", name.String())) + } + } + if trapResultObj == nil { + if targetDesc == nil { + return nil + } + if !targetDesc.configurable { + panic(r.NewTypeError()) + } + if !target.self.isExtensible() { + panic(r.NewTypeError()) + } + return nil + } + extensibleTarget := target.self.isExtensible() + resultDesc := r.toPropertyDescriptor(trapResultObj) + resultDesc.complete() + if !p.__isCompatibleDescriptor(extensibleTarget, &resultDesc, targetDesc) { + panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap returned descriptor for property '%s' that is incompatible with the existing property in the proxy target", name.String())) + } + + if resultDesc.Configurable == FLAG_FALSE { + if targetDesc == nil { + panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '%s' which is non-existent in the proxy target", name.String())) + } + + if targetDesc.configurable { + panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap reported non-configurability for property '%s' which is configurable in the proxy target", name.String())) + } + + if resultDesc.Writable == FLAG_FALSE && targetDesc.writable { + panic(r.NewTypeError("'getOwnPropertyDescriptor' on proxy: trap reported non-configurable and writable for property '%s' which is non-configurable, non-writable in the proxy target", name.String())) + } + } + + if resultDesc.Writable == FLAG_TRUE && resultDesc.Configurable == FLAG_TRUE && + resultDesc.Enumerable == FLAG_TRUE { + return resultDesc.Value + } + return r.toValueProp(trapResultObj) +} + +func (p *proxyObject) getOwnPropStr(name unistring.String) Value { + target := p.target + if v, ok := p.checkHandler().getOwnPropertyDescriptorStr(target, name); ok { + return p.proxyGetOwnPropertyDescriptor(target.self.getOwnPropStr(name), target, v, name) + } + + return target.self.getOwnPropStr(name) +} + +func (p *proxyObject) getOwnPropIdx(idx valueInt) Value { + target := p.target + if v, ok := p.checkHandler().getOwnPropertyDescriptorIdx(target, idx); ok { + return p.proxyGetOwnPropertyDescriptor(target.self.getOwnPropIdx(idx), target, v, idx) + } + + return target.self.getOwnPropIdx(idx) +} + +func (p *proxyObject) getOwnPropSym(s *Symbol) Value { + target := p.target + if v, ok := p.checkHandler().getOwnPropertyDescriptorSym(target, s); ok { + return p.proxyGetOwnPropertyDescriptor(target.self.getOwnPropSym(s), target, v, s) + } + + return target.self.getOwnPropSym(s) +} + +func (p *proxyObject) proxyGetChecks(targetProp, trapResult Value, name fmt.Stringer) { + if targetDesc, ok := targetProp.(*valueProperty); ok { + if !targetDesc.accessor { + if !targetDesc.writable && !targetDesc.configurable && !trapResult.SameAs(targetDesc.value) { + panic(p.val.runtime.NewTypeError("'get' on proxy: property '%s' is a read-only and non-configurable data property on the proxy target but the proxy did not return its actual value (expected '%s' but got '%s')", name.String(), nilSafe(targetDesc.value), ret)) + } + } else { + if !targetDesc.configurable && targetDesc.getterFunc == nil && trapResult != _undefined { + panic(p.val.runtime.NewTypeError("'get' on proxy: property '%s' is a non-configurable accessor property on the proxy target and does not have a getter function, but the trap did not return 'undefined' (got '%s')", name.String(), ret)) + } + } + } +} + +func (p *proxyObject) getStr(name unistring.String, receiver Value) Value { + target := p.target + if receiver == nil { + receiver = p.val + } + if v, ok := p.checkHandler().getStr(target, name, receiver); ok { + p.proxyGetChecks(target.self.getOwnPropStr(name), v, name) + return v + } + return target.self.getStr(name, receiver) +} + +func (p *proxyObject) getIdx(idx valueInt, receiver Value) Value { + target := p.target + if receiver == nil { + receiver = p.val + } + if v, ok := p.checkHandler().getIdx(target, idx, receiver); ok { + p.proxyGetChecks(target.self.getOwnPropIdx(idx), v, idx) + return v + } + return target.self.getIdx(idx, receiver) +} + +func (p *proxyObject) getSym(s *Symbol, receiver Value) Value { + target := p.target + if receiver == nil { + receiver = p.val + } + if v, ok := p.checkHandler().getSym(target, s, receiver); ok { + p.proxyGetChecks(target.self.getOwnPropSym(s), v, s) + return v + } + + return target.self.getSym(s, receiver) +} + +func (p *proxyObject) proxySetPreCheck(trapResult, throw bool, name fmt.Stringer) bool { + if !trapResult { + p.val.runtime.typeErrorResult(throw, "'set' on proxy: trap returned falsish for property '%s'", name.String()) + } + return trapResult +} + +func (p *proxyObject) proxySetPostCheck(targetProp, value Value, name fmt.Stringer) { + if prop, ok := targetProp.(*valueProperty); ok { + if prop.accessor { + if !prop.configurable && prop.setterFunc == nil { + panic(p.val.runtime.NewTypeError("'set' on proxy: trap returned truish for property '%s' which exists in the proxy target as a non-configurable and non-writable accessor property without a setter", name.String())) + } + } else if !prop.configurable && !prop.writable && !p.__sameValue(prop.value, value) { + panic(p.val.runtime.NewTypeError("'set' on proxy: trap returned truish for property '%s' which exists in the proxy target as a non-configurable and non-writable data property with a different value", name.String())) + } + } +} + +func (p *proxyObject) proxySetStr(name unistring.String, value, receiver Value, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().setStr(target, name, value, receiver); ok { + if p.proxySetPreCheck(v, throw, name) { + p.proxySetPostCheck(target.self.getOwnPropStr(name), value, name) + return true + } + return false + } + return target.setStr(name, value, receiver, throw) +} + +func (p *proxyObject) proxySetIdx(idx valueInt, value, receiver Value, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().setIdx(target, idx, value, receiver); ok { + if p.proxySetPreCheck(v, throw, idx) { + p.proxySetPostCheck(target.self.getOwnPropIdx(idx), value, idx) + return true + } + return false + } + return target.setIdx(idx, value, receiver, throw) +} + +func (p *proxyObject) proxySetSym(s *Symbol, value, receiver Value, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().setSym(target, s, value, receiver); ok { + if p.proxySetPreCheck(v, throw, s) { + p.proxySetPostCheck(target.self.getOwnPropSym(s), value, s) + return true + } + return false + } + return target.setSym(s, value, receiver, throw) +} + +func (p *proxyObject) setOwnStr(name unistring.String, v Value, throw bool) bool { + return p.proxySetStr(name, v, p.val, throw) +} + +func (p *proxyObject) setOwnIdx(idx valueInt, v Value, throw bool) bool { + return p.proxySetIdx(idx, v, p.val, throw) +} + +func (p *proxyObject) setOwnSym(s *Symbol, v Value, throw bool) bool { + return p.proxySetSym(s, v, p.val, throw) +} + +func (p *proxyObject) setForeignStr(name unistring.String, v, receiver Value, throw bool) (bool, bool) { + return p.proxySetStr(name, v, receiver, throw), true +} + +func (p *proxyObject) setForeignIdx(idx valueInt, v, receiver Value, throw bool) (bool, bool) { + return p.proxySetIdx(idx, v, receiver, throw), true +} + +func (p *proxyObject) setForeignSym(s *Symbol, v, receiver Value, throw bool) (bool, bool) { + return p.proxySetSym(s, v, receiver, throw), true +} + +func (p *proxyObject) proxyDeleteCheck(trapResult bool, targetProp Value, name fmt.Stringer, target *Object, throw bool) { + if trapResult { + if targetProp == nil { + return + } + if targetDesc, ok := targetProp.(*valueProperty); ok { + if !targetDesc.configurable { + panic(p.val.runtime.NewTypeError("'deleteProperty' on proxy: property '%s' is a non-configurable property but the trap returned truish", name.String())) + } + } + if !target.self.isExtensible() { + panic(p.val.runtime.NewTypeError("'deleteProperty' on proxy: trap returned truish for property '%s' but the proxy target is non-extensible", name.String())) + } + } else { + p.val.runtime.typeErrorResult(throw, "'deleteProperty' on proxy: trap returned falsish for property '%s'", name.String()) + } +} + +func (p *proxyObject) deleteStr(name unistring.String, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().deleteStr(target, name); ok { + p.proxyDeleteCheck(v, target.self.getOwnPropStr(name), name, target, throw) + return v + } + + return target.self.deleteStr(name, throw) +} + +func (p *proxyObject) deleteIdx(idx valueInt, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().deleteIdx(target, idx); ok { + p.proxyDeleteCheck(v, target.self.getOwnPropIdx(idx), idx, target, throw) + return v + } + + return target.self.deleteIdx(idx, throw) +} + +func (p *proxyObject) deleteSym(s *Symbol, throw bool) bool { + target := p.target + if v, ok := p.checkHandler().deleteSym(target, s); ok { + p.proxyDeleteCheck(v, target.self.getOwnPropSym(s), s, target, throw) + return v + } + + return target.self.deleteSym(s, throw) +} + +func (p *proxyObject) keys(all bool, _ []Value) []Value { + if v, ok := p.proxyOwnKeys(); ok { + if !all { + k := 0 + for i, key := range v { + prop := p.val.getOwnProp(key) + if prop == nil || prop == _undefined { + continue + } + if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { + continue + } + if k != i { + v[k] = v[i] + } + k++ + } + v = v[:k] + } + return v + } + return p.target.self.keys(all, nil) +} + +func (p *proxyObject) proxyOwnKeys() ([]Value, bool) { + target := p.target + if v, ok := p.checkHandler().ownKeys(target); ok { + keys := p.val.runtime.toObject(v) + var keyList []Value + keySet := make(map[Value]struct{}) + l := toLength(keys.self.getStr("length", nil)) + for k := int64(0); k < l; k++ { + item := keys.self.getIdx(valueInt(k), nil) + if _, ok := item.(String); !ok { + if _, ok := item.(*Symbol); !ok { + panic(p.val.runtime.NewTypeError("%s is not a valid property name", item.String())) + } + } + if _, exists := keySet[item]; exists { + panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap returned duplicate entries")) + } + keyList = append(keyList, item) + keySet[item] = struct{}{} + } + ext := target.self.isExtensible() + for item, next := target.self.iterateKeys()(); next != nil; item, next = next() { + if _, exists := keySet[item.name]; exists { + delete(keySet, item.name) + } else { + if !ext { + panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap result did not include '%s'", item.name.String())) + } + var prop Value + if item.value == nil { + prop = target.getOwnProp(item.name) + } else { + prop = item.value + } + if prop, ok := prop.(*valueProperty); ok && !prop.configurable { + panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap result did not include non-configurable '%s'", item.name.String())) + } + } + } + if !ext && len(keyList) > 0 && len(keySet) > 0 { + panic(p.val.runtime.NewTypeError("'ownKeys' on proxy: trap returned extra keys but proxy target is non-extensible")) + } + + return keyList, true + } + + return nil, false +} + +func (p *proxyObject) iterateStringKeys() iterNextFunc { + return (&proxyPropIter{ + p: p, + names: p.stringKeys(true, nil), + }).next +} + +func (p *proxyObject) iterateSymbols() iterNextFunc { + return (&proxyPropIter{ + p: p, + names: p.symbols(true, nil), + }).next +} + +func (p *proxyObject) iterateKeys() iterNextFunc { + return (&proxyPropIter{ + p: p, + names: p.keys(true, nil), + }).next +} + +func (p *proxyObject) assertCallable() (call func(FunctionCall) Value, ok bool) { + if p.call != nil { + return func(call FunctionCall) Value { + return p.apply(call) + }, true + } + return nil, false +} + +func (p *proxyObject) vmCall(vm *vm, n int) { + vm.pushCtx() + vm.prg = nil + vm.sb = vm.sp - n // so that [sb-1] points to the callee + ret := p.apply(FunctionCall{This: vm.stack[vm.sp-n-2], Arguments: vm.stack[vm.sp-n : vm.sp]}) + if ret == nil { + ret = _undefined + } + vm.stack[vm.sp-n-2] = ret + vm.popCtx() + vm.sp -= n + 1 + vm.pc++ +} + +func (p *proxyObject) assertConstructor() func(args []Value, newTarget *Object) *Object { + if p.ctor != nil { + return p.construct + } + return nil +} + +func (p *proxyObject) apply(call FunctionCall) Value { + if p.call == nil { + panic(p.val.runtime.NewTypeError("proxy target is not a function")) + } + if v, ok := p.checkHandler().apply(p.target, nilSafe(call.This), call.Arguments); ok { + return v + } + return p.call(call) +} + +func (p *proxyObject) construct(args []Value, newTarget *Object) *Object { + if p.ctor == nil { + panic(p.val.runtime.NewTypeError("proxy target is not a constructor")) + } + if newTarget == nil { + newTarget = p.val + } + if v, ok := p.checkHandler().construct(p.target, args, newTarget); ok { + return p.val.runtime.toObject(v) + } + return p.ctor(args, newTarget) +} + +func (p *proxyObject) __isCompatibleDescriptor(extensible bool, desc *PropertyDescriptor, current *valueProperty) bool { + if current == nil { + return extensible + } + + if !current.configurable { + if desc.Configurable == FLAG_TRUE { + return false + } + + if desc.Enumerable != FLAG_NOT_SET && desc.Enumerable.Bool() != current.enumerable { + return false + } + + if desc.IsGeneric() { + return true + } + + if desc.IsData() != !current.accessor { + return desc.Configurable != FLAG_FALSE + } + + if desc.IsData() && !current.accessor { + if !current.configurable { + if desc.Writable == FLAG_TRUE && !current.writable { + return false + } + if !current.writable { + if desc.Value != nil && !desc.Value.SameAs(current.value) { + return false + } + } + } + return true + } + if desc.IsAccessor() && current.accessor { + if !current.configurable { + if desc.Setter != nil && desc.Setter.SameAs(current.setterFunc) { + return false + } + if desc.Getter != nil && desc.Getter.SameAs(current.getterFunc) { + return false + } + } + } + } + return true +} + +func (p *proxyObject) __sameValue(val1, val2 Value) bool { + if val1 == nil && val2 == nil { + return true + } + if val1 != nil { + return val1.SameAs(val2) + } + return false +} + +func (p *proxyObject) filterKeys(vals []Value, all, symbols bool) []Value { + if !all { + k := 0 + for i, val := range vals { + var prop Value + if symbols { + if s, ok := val.(*Symbol); ok { + prop = p.getOwnPropSym(s) + } else { + continue + } + } else { + if _, ok := val.(*Symbol); !ok { + prop = p.getOwnPropStr(val.string()) + } else { + continue + } + } + if prop == nil { + continue + } + if prop, ok := prop.(*valueProperty); ok && !prop.enumerable { + continue + } + if k != i { + vals[k] = vals[i] + } + k++ + } + vals = vals[:k] + } else { + k := 0 + for i, val := range vals { + if _, ok := val.(*Symbol); ok != symbols { + continue + } + if k != i { + vals[k] = vals[i] + } + k++ + } + vals = vals[:k] + } + return vals +} + +func (p *proxyObject) stringKeys(all bool, _ []Value) []Value { // we can assume accum is empty + var keys []Value + if vals, ok := p.proxyOwnKeys(); ok { + keys = vals + } else { + keys = p.target.self.stringKeys(true, nil) + } + + return p.filterKeys(keys, all, false) +} + +func (p *proxyObject) symbols(all bool, accum []Value) []Value { + var symbols []Value + if vals, ok := p.proxyOwnKeys(); ok { + symbols = vals + } else { + symbols = p.target.self.symbols(true, nil) + } + symbols = p.filterKeys(symbols, all, true) + if accum == nil { + return symbols + } + accum = append(accum, symbols...) + return accum +} + +func (p *proxyObject) className() string { + if p.target == nil { + panic(p.val.runtime.NewTypeError("proxy has been revoked")) + } + if p.call != nil || p.ctor != nil { + return classFunction + } + return classObject +} + +func (p *proxyObject) typeOf() String { + if p.call == nil { + return stringObjectC + } + + return stringFunction +} + +func (p *proxyObject) exportType() reflect.Type { + return proxyType +} + +func (p *proxyObject) export(*objectExportCtx) any { + return Proxy{ + proxy: p, + } +} + +func (p *proxyObject) revoke() { + p.handler = nil + p.target = nil +} diff --git a/pkg/xscript/engine/regexp.go b/pkg/xscript/engine/regexp.go new file mode 100644 index 0000000..480758a --- /dev/null +++ b/pkg/xscript/engine/regexp.go @@ -0,0 +1,645 @@ +package engine + +import ( + "fmt" + "io" + "regexp" + "sort" + "strings" + "unicode/utf16" + + "pandax/pkg/xscript/engine/unistring" + + "github.com/dlclark/regexp2" +) + +type regexp2MatchCache struct { + target String + runes []rune + posMap []int +} + +// Not goroutine-safe. Use regexp2Wrapper.clone() +type regexp2Wrapper struct { + rx *regexp2.Regexp + cache *regexp2MatchCache +} + +type regexpWrapper regexp.Regexp + +type positionMapItem struct { + src, dst int +} +type positionMap []positionMapItem + +func (m positionMap) get(src int) int { + if src <= 0 { + return src + } + res := sort.Search(len(m), func(n int) bool { return m[n].src >= src }) + if res >= len(m) || m[res].src != src { + panic("index not found") + } + return m[res].dst +} + +type arrayRuneReader struct { + runes []rune + pos int +} + +func (rd *arrayRuneReader) ReadRune() (r rune, size int, err error) { + if rd.pos < len(rd.runes) { + r = rd.runes[rd.pos] + size = 1 + rd.pos++ + } else { + err = io.EOF + } + return +} + +// Not goroutine-safe. Use regexpPattern.clone() +type regexpPattern struct { + src string + + global, ignoreCase, multiline, sticky, unicode bool + + regexpWrapper *regexpWrapper + regexp2Wrapper *regexp2Wrapper +} + +func compileRegexp2(src string, multiline, ignoreCase bool) (*regexp2Wrapper, error) { + var opts regexp2.RegexOptions = regexp2.ECMAScript + if multiline { + opts |= regexp2.Multiline + } + if ignoreCase { + opts |= regexp2.IgnoreCase + } + regexp2Pattern, err1 := regexp2.Compile(src, opts) + if err1 != nil { + return nil, fmt.Errorf("Invalid regular expression (regexp2): %s (%v)", src, err1) + } + + return ®exp2Wrapper{rx: regexp2Pattern}, nil +} + +func (p *regexpPattern) createRegexp2() { + if p.regexp2Wrapper != nil { + return + } + rx, err := compileRegexp2(p.src, p.multiline, p.ignoreCase) + if err != nil { + // At this point the regexp should have been successfully converted to re2, if it fails now, it's a bug. + panic(err) + } + p.regexp2Wrapper = rx +} + +func buildUTF8PosMap(s unicodeString) (positionMap, string) { + pm := make(positionMap, 0, s.Length()) + rd := s.Reader() + sPos, utf8Pos := 0, 0 + var sb strings.Builder + for { + r, size, err := rd.ReadRune() + if err == io.EOF { + break + } + if err != nil { + // the string contains invalid UTF-16, bailing out + return nil, "" + } + utf8Size, _ := sb.WriteRune(r) + sPos += size + utf8Pos += utf8Size + pm = append(pm, positionMapItem{src: utf8Pos, dst: sPos}) + } + return pm, sb.String() +} + +func (p *regexpPattern) findSubmatchIndex(s String, start int) []int { + if p.regexpWrapper == nil { + return p.regexp2Wrapper.findSubmatchIndex(s, start, p.unicode, p.global || p.sticky) + } + if start != 0 { + // Unfortunately Go's regexp library does not allow starting from an arbitrary position. + // If we just drop the first _start_ characters of the string the assertions (^, $, \b and \B) will not + // work correctly. + p.createRegexp2() + return p.regexp2Wrapper.findSubmatchIndex(s, start, p.unicode, p.global || p.sticky) + } + return p.regexpWrapper.findSubmatchIndex(s, p.unicode) +} + +func (p *regexpPattern) findAllSubmatchIndex(s String, start int, limit int, sticky bool) [][]int { + if p.regexpWrapper == nil { + return p.regexp2Wrapper.findAllSubmatchIndex(s, start, limit, sticky, p.unicode) + } + if start == 0 { + a, u := devirtualizeString(s) + if u == nil { + return p.regexpWrapper.findAllSubmatchIndex(string(a), limit, sticky) + } + if limit == 1 { + result := p.regexpWrapper.findSubmatchIndexUnicode(u, p.unicode) + if result == nil { + return nil + } + return [][]int{result} + } + // Unfortunately Go's regexp library lacks FindAllReaderSubmatchIndex(), so we have to use a UTF-8 string as an + // input. + if p.unicode { + // Try to convert s to UTF-8. If it does not contain any invalid UTF-16 we can do the matching in UTF-8. + pm, str := buildUTF8PosMap(u) + if pm != nil { + res := p.regexpWrapper.findAllSubmatchIndex(str, limit, sticky) + for _, result := range res { + for i, idx := range result { + result[i] = pm.get(idx) + } + } + return res + } + } + } + + p.createRegexp2() + return p.regexp2Wrapper.findAllSubmatchIndex(s, start, limit, sticky, p.unicode) +} + +// clone creates a copy of the regexpPattern which can be used concurrently. +func (p *regexpPattern) clone() *regexpPattern { + ret := ®expPattern{ + src: p.src, + global: p.global, + ignoreCase: p.ignoreCase, + multiline: p.multiline, + sticky: p.sticky, + unicode: p.unicode, + } + if p.regexpWrapper != nil { + ret.regexpWrapper = p.regexpWrapper.clone() + } + if p.regexp2Wrapper != nil { + ret.regexp2Wrapper = p.regexp2Wrapper.clone() + } + return ret +} + +type regexpObject struct { + baseObject + pattern *regexpPattern + source String + + standard bool +} + +func (r *regexp2Wrapper) findSubmatchIndex(s String, start int, fullUnicode, doCache bool) (result []int) { + if fullUnicode { + return r.findSubmatchIndexUnicode(s, start, doCache) + } + return r.findSubmatchIndexUTF16(s, start, doCache) +} + +func (r *regexp2Wrapper) findUTF16Cached(s String, start int, doCache bool) (match *regexp2.Match, runes []rune, err error) { + wrapped := r.rx + cache := r.cache + if cache != nil && cache.posMap == nil && cache.target.SameAs(s) { + runes = cache.runes + } else { + runes = s.utf16Runes() + cache = nil + } + match, err = wrapped.FindRunesMatchStartingAt(runes, start) + if doCache && match != nil && err == nil { + if cache == nil { + if r.cache == nil { + r.cache = new(regexp2MatchCache) + } + *r.cache = regexp2MatchCache{ + target: s, + runes: runes, + } + } + } else { + r.cache = nil + } + return +} + +func (r *regexp2Wrapper) findSubmatchIndexUTF16(s String, start int, doCache bool) (result []int) { + match, _, err := r.findUTF16Cached(s, start, doCache) + if err != nil { + return + } + + if match == nil { + return + } + groups := match.Groups() + + result = make([]int, 0, len(groups)<<1) + for _, group := range groups { + if len(group.Captures) > 0 { + result = append(result, group.Index, group.Index+group.Length) + } else { + result = append(result, -1, 0) + } + } + return +} + +func (r *regexp2Wrapper) findUnicodeCached(s String, start int, doCache bool) (match *regexp2.Match, posMap []int, err error) { + var ( + runes []rune + mappedStart int + splitPair bool + savedRune rune + ) + wrapped := r.rx + cache := r.cache + if cache != nil && cache.posMap != nil && cache.target.SameAs(s) { + runes, posMap = cache.runes, cache.posMap + mappedStart, splitPair = posMapReverseLookup(posMap, start) + } else { + posMap, runes, mappedStart, splitPair = buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.Length(), start) + cache = nil + } + if splitPair { + // temporarily set the rune at mappedStart to the second code point of the pair + _, second := utf16.EncodeRune(runes[mappedStart]) + savedRune, runes[mappedStart] = runes[mappedStart], second + } + match, err = wrapped.FindRunesMatchStartingAt(runes, mappedStart) + if doCache && match != nil && err == nil { + if splitPair { + runes[mappedStart] = savedRune + } + if cache == nil { + if r.cache == nil { + r.cache = new(regexp2MatchCache) + } + *r.cache = regexp2MatchCache{ + target: s, + runes: runes, + posMap: posMap, + } + } + } else { + r.cache = nil + } + + return +} + +func (r *regexp2Wrapper) findSubmatchIndexUnicode(s String, start int, doCache bool) (result []int) { + match, posMap, err := r.findUnicodeCached(s, start, doCache) + if match == nil || err != nil { + return + } + + groups := match.Groups() + + result = make([]int, 0, len(groups)<<1) + for _, group := range groups { + if len(group.Captures) > 0 { + result = append(result, posMap[group.Index], posMap[group.Index+group.Length]) + } else { + result = append(result, -1, 0) + } + } + return +} + +func (r *regexp2Wrapper) findAllSubmatchIndexUTF16(s String, start, limit int, sticky bool) [][]int { + wrapped := r.rx + match, runes, err := r.findUTF16Cached(s, start, false) + if match == nil || err != nil { + return nil + } + if limit < 0 { + limit = len(runes) + 1 + } + results := make([][]int, 0, limit) + for match != nil { + groups := match.Groups() + + result := make([]int, 0, len(groups)<<1) + + for _, group := range groups { + if len(group.Captures) > 0 { + startPos := group.Index + endPos := group.Index + group.Length + result = append(result, startPos, endPos) + } else { + result = append(result, -1, 0) + } + } + + if sticky && len(result) > 1 { + if result[0] != start { + break + } + start = result[1] + } + + results = append(results, result) + limit-- + if limit <= 0 { + break + } + match, err = wrapped.FindNextMatch(match) + if err != nil { + return nil + } + } + return results +} + +func buildPosMap(rd io.RuneReader, l, start int) (posMap []int, runes []rune, mappedStart int, splitPair bool) { + posMap = make([]int, 0, l+1) + curPos := 0 + runes = make([]rune, 0, l) + startFound := false + for { + if !startFound { + if curPos == start { + mappedStart = len(runes) + startFound = true + } + if curPos > start { + // start position splits a surrogate pair + mappedStart = len(runes) - 1 + splitPair = true + startFound = true + } + } + rn, size, err := rd.ReadRune() + if err != nil { + break + } + runes = append(runes, rn) + posMap = append(posMap, curPos) + curPos += size + } + posMap = append(posMap, curPos) + return +} + +func posMapReverseLookup(posMap []int, pos int) (int, bool) { + mapped := sort.SearchInts(posMap, pos) + if mapped < len(posMap) && posMap[mapped] != pos { + return mapped - 1, true + } + return mapped, false +} + +func (r *regexp2Wrapper) findAllSubmatchIndexUnicode(s unicodeString, start, limit int, sticky bool) [][]int { + wrapped := r.rx + if limit < 0 { + limit = len(s) + 1 + } + results := make([][]int, 0, limit) + match, posMap, err := r.findUnicodeCached(s, start, false) + if err != nil { + return nil + } + for match != nil { + groups := match.Groups() + + result := make([]int, 0, len(groups)<<1) + + for _, group := range groups { + if len(group.Captures) > 0 { + start := posMap[group.Index] + end := posMap[group.Index+group.Length] + result = append(result, start, end) + } else { + result = append(result, -1, 0) + } + } + + if sticky && len(result) > 1 { + if result[0] != start { + break + } + start = result[1] + } + + results = append(results, result) + match, err = wrapped.FindNextMatch(match) + if err != nil { + return nil + } + } + return results +} + +func (r *regexp2Wrapper) findAllSubmatchIndex(s String, start, limit int, sticky, fullUnicode bool) [][]int { + a, u := devirtualizeString(s) + if u != nil { + if fullUnicode { + return r.findAllSubmatchIndexUnicode(u, start, limit, sticky) + } + return r.findAllSubmatchIndexUTF16(u, start, limit, sticky) + } + return r.findAllSubmatchIndexUTF16(a, start, limit, sticky) +} + +func (r *regexp2Wrapper) clone() *regexp2Wrapper { + return ®exp2Wrapper{ + rx: r.rx, + } +} + +func (r *regexpWrapper) findAllSubmatchIndex(s string, limit int, sticky bool) (results [][]int) { + wrapped := (*regexp.Regexp)(r) + results = wrapped.FindAllStringSubmatchIndex(s, limit) + pos := 0 + if sticky { + for i, result := range results { + if len(result) > 1 { + if result[0] != pos { + return results[:i] + } + pos = result[1] + } + } + } + return +} + +func (r *regexpWrapper) findSubmatchIndex(s String, fullUnicode bool) []int { + a, u := devirtualizeString(s) + if u != nil { + return r.findSubmatchIndexUnicode(u, fullUnicode) + } + return r.findSubmatchIndexASCII(string(a)) +} + +func (r *regexpWrapper) findSubmatchIndexASCII(s string) []int { + wrapped := (*regexp.Regexp)(r) + return wrapped.FindStringSubmatchIndex(s) +} + +func (r *regexpWrapper) findSubmatchIndexUnicode(s unicodeString, fullUnicode bool) (result []int) { + wrapped := (*regexp.Regexp)(r) + if fullUnicode { + posMap, runes, _, _ := buildPosMap(&lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, s.Length(), 0) + res := wrapped.FindReaderSubmatchIndex(&arrayRuneReader{runes: runes}) + for i, item := range res { + if item >= 0 { + res[i] = posMap[item] + } + } + return res + } + return wrapped.FindReaderSubmatchIndex(s.utf16RuneReader()) +} + +func (r *regexpWrapper) clone() *regexpWrapper { + return r +} + +func (r *regexpObject) execResultToArray(target String, result []int) Value { + captureCount := len(result) >> 1 + valueArray := make([]Value, captureCount) + matchIndex := result[0] + valueArray[0] = target.Substring(result[0], result[1]) + lowerBound := 0 + for index := 1; index < captureCount; index++ { + offset := index << 1 + if result[offset] >= 0 && result[offset+1] >= lowerBound { + valueArray[index] = target.Substring(result[offset], result[offset+1]) + lowerBound = result[offset] + } else { + valueArray[index] = _undefined + } + } + match := r.val.runtime.newArrayValues(valueArray) + match.self.setOwnStr("input", target, false) + match.self.setOwnStr("index", intToValue(int64(matchIndex)), false) + return match +} + +func (r *regexpObject) getLastIndex() int64 { + lastIndex := toLength(r.getStr("lastIndex", nil)) + if !r.pattern.global && !r.pattern.sticky { + return 0 + } + return lastIndex +} + +func (r *regexpObject) updateLastIndex(index int64, firstResult, lastResult []int) bool { + if r.pattern.sticky { + if firstResult == nil || int64(firstResult[0]) != index { + r.setOwnStr("lastIndex", intToValue(0), true) + return false + } + } else { + if firstResult == nil { + if r.pattern.global { + r.setOwnStr("lastIndex", intToValue(0), true) + } + return false + } + } + + if r.pattern.global || r.pattern.sticky { + r.setOwnStr("lastIndex", intToValue(int64(lastResult[1])), true) + } + return true +} + +func (r *regexpObject) execRegexp(target String) (match bool, result []int) { + index := r.getLastIndex() + if index >= 0 && index <= int64(target.Length()) { + result = r.pattern.findSubmatchIndex(target, int(index)) + } + match = r.updateLastIndex(index, result, result) + return +} + +func (r *regexpObject) exec(target String) Value { + match, result := r.execRegexp(target) + if match { + return r.execResultToArray(target, result) + } + return _null +} + +func (r *regexpObject) test(target String) bool { + match, _ := r.execRegexp(target) + return match +} + +func (r *regexpObject) clone() *regexpObject { + r1 := r.val.runtime.newRegexpObject(r.prototype) + r1.source = r.source + r1.pattern = r.pattern + + return r1 +} + +func (r *regexpObject) init() { + r.baseObject.init() + r.standard = true + r._putProp("lastIndex", intToValue(0), true, false, false) +} + +func (r *regexpObject) setProto(proto *Object, throw bool) bool { + res := r.baseObject.setProto(proto, throw) + if res { + r.standard = false + } + return res +} + +func (r *regexpObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + res := r.baseObject.defineOwnPropertyStr(name, desc, throw) + if res { + r.standard = false + } + return res +} + +func (r *regexpObject) defineOwnPropertySym(name *Symbol, desc PropertyDescriptor, throw bool) bool { + res := r.baseObject.defineOwnPropertySym(name, desc, throw) + if res && r.standard { + switch name { + case SymMatch, SymMatchAll, SymSearch, SymSplit, SymReplace: + r.standard = false + } + } + return res +} + +func (r *regexpObject) deleteStr(name unistring.String, throw bool) bool { + res := r.baseObject.deleteStr(name, throw) + if res { + r.standard = false + } + return res +} + +func (r *regexpObject) setOwnStr(name unistring.String, value Value, throw bool) bool { + res := r.baseObject.setOwnStr(name, value, throw) + if res && r.standard && name == "exec" { + r.standard = false + } + return res +} + +func (r *regexpObject) setOwnSym(name *Symbol, value Value, throw bool) bool { + res := r.baseObject.setOwnSym(name, value, throw) + if res && r.standard { + switch name { + case SymMatch, SymMatchAll, SymSearch, SymSplit, SymReplace: + r.standard = false + } + } + return res +} diff --git a/pkg/xscript/engine/regexp_test.go b/pkg/xscript/engine/regexp_test.go new file mode 100644 index 0000000..2c3ab51 --- /dev/null +++ b/pkg/xscript/engine/regexp_test.go @@ -0,0 +1,918 @@ +package engine + +import ( + "testing" +) + +func TestRegexp1(t *testing.T) { + const SCRIPT = ` + var r = new RegExp("(['\"])(.*?)\\1"); + var m = r.exec("'test'"); + m !== null && m.length == 3 && m[2] === "test"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexp2(t *testing.T) { + const SCRIPT = ` + var r = new RegExp("(['\"])(.*?)['\"]"); + var m = r.exec("'test'"); + m !== null && m.length == 3 && m[2] === "test"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpLiteral(t *testing.T) { + const SCRIPT = ` + var r = /(['\"])(.*?)\1/; + var m = r.exec("'test'"); + m !== null && m.length == 3 && m[2] === "test"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpRe2Unicode(t *testing.T) { + const SCRIPT = ` + var r = /(тест)/i; + var m = r.exec("'Тест'"); + m !== null && m.length == 2 && m[1] === "Тест"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpRe2UnicodeTarget(t *testing.T) { + const SCRIPT = ` + var r = /(['\"])(.*?)['\"]/i; + var m = r.exec("'Тест'"); + m !== null && m.length == 3 && m[2] === "Тест"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpRegexp2Unicode(t *testing.T) { + const SCRIPT = ` + var r = /(['\"])(тест)\1/i; + var m = r.exec("'Тест'"); + m !== null && m.length == 3 && m[2] === "Тест"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpRegexp2UnicodeTarget(t *testing.T) { + const SCRIPT = ` + var r = /(['\"])(.*?)\1/; + var m = r.exec("'Тест'"); + m !== null && m.length == 3 && m[2] === "Тест"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpRe2Whitespace(t *testing.T) { + const SCRIPT = ` + "\u2000\u2001\u2002\u200b".replace(/\s+/g, "") === "\u200b"; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpRegexp2Whitespace(t *testing.T) { + const SCRIPT = ` + "A\u2000\u2001\u2002A\u200b".replace(/(A)\s+\1/g, "") === "\u200b" + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestEmptyCharClassRe2(t *testing.T) { + const SCRIPT = ` + /[]/.test("\u0000"); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestNegatedEmptyCharClassRe2(t *testing.T) { + const SCRIPT = ` + /[^]/.test("\u0000"); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestEmptyCharClassRegexp2(t *testing.T) { + const SCRIPT = ` + /([])\1/.test("\u0000\u0000"); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestRegexp2Negate(t *testing.T) { + const SCRIPT = ` + /([\D1])\1/.test("aa"); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestAlternativeRe2(t *testing.T) { + const SCRIPT = ` + /()|/.exec("") !== null; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpReplaceGlobal(t *testing.T) { + const SCRIPT = ` + "QBZPbage\ny_cynprubyqre".replace(/^\s*|\s*$/g, '') + ` + + testScript(SCRIPT, asciiString("QBZPbage\ny_cynprubyqre"), t) +} + +func TestRegexpNumCaptures(t *testing.T) { + const SCRIPT = ` + "Fubpxjnir Synfu 9.0 e115".replace(/([a-zA-Z]|\s)+/, '') + ` + testScript(SCRIPT, asciiString("9.0 e115"), t) +} + +func TestRegexpNumCaptures1(t *testing.T) { + const SCRIPT = ` + "Fubpxjnir Sy\tfu 9.0 e115".replace(/^.*\s+(\S+\s+\S+$)/, '') + ` + testScript(SCRIPT, asciiString(""), t) +} + +func TestRegexpSInClass(t *testing.T) { + const SCRIPT = ` + /[\S]/.test("\u2028"); + ` + testScript(SCRIPT, valueFalse, t) +} + +func TestRegexpDotMatchCR(t *testing.T) { + const SCRIPT = ` + /./.test("\r"); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestRegexpDotMatchCRInGroup(t *testing.T) { + const SCRIPT = ` + /(.)/.test("\r"); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestRegexpDotMatchLF(t *testing.T) { + const SCRIPT = ` + /./.test("\n"); + ` + + testScript(SCRIPT, valueFalse, t) +} + +func TestRegexpSplitWithBackRef(t *testing.T) { + const SCRIPT = ` + "a++b+-c".split(/([+-])\1/).join(" $$ ") + ` + + testScript(SCRIPT, asciiString("a $$ + $$ b+-c"), t) +} + +func TestEscapeNonASCII(t *testing.T) { + const SCRIPT = ` + /\⩓/.test("⩓") + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpUTF16(t *testing.T) { + const SCRIPT = ` + var str = "\uD800\uDC00"; + + assert(/\uD800/g.test(str), "#1"); + assert(/\uD800/.test(str), "#2"); + assert(/𐀀/.test(str), "#3"); + + var re = /\uD800/; + + assert(compareArray(str.replace(re, "X"), ["X", "\uDC00"]), "#4"); + assert(compareArray(str.split(re), ["", "\uDC00"]), "#5"); + assert(compareArray("a\uD800\uDC00b".split(/\uD800/g), ["a", "\uDC00b"]), "#6"); + assert(compareArray("a\uD800\uDC00b".split(/(?:)/g), ["a", "\uD800", "\uDC00", "b"]), "#7"); + assert(compareArray("0\x80".split(/(0){0}/g), ["0", undefined, "\x80"]), "#7+"); + + re = /(?=)a/; // a hack to use regexp2 + assert.sameValue(re.exec('\ud83d\ude02a').index, 2, "#8"); + + assert.sameValue(/./.exec('\ud83d\ude02')[0], '\ud83d', "#9"); + + assert(RegExp("\uD800").test("\uD800"), "#10"); + + var cu = 0xD800; + var xx = "a\\" + String.fromCharCode(cu); + var pattern = eval("/" + xx + "/"); + assert.sameValue(pattern.source, "a\\\\\\ud800", "Code unit: " + cu.toString(16), "#11"); + assert(pattern.test("a\\\uD800"), "#12"); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestRegexpUnicode(t *testing.T) { + const SCRIPT = ` + + assert(!/\uD800/u.test("\uD800\uDC00"), "#1"); + assert(!/\uFFFD/u.test("\uD800\uDC00"), "#2"); + + assert(/\uD800\uDC00/u.test("\uD800\uDC00"), "#3"); + + assert(/\uD800/u.test("\uD800"), "#4"); + + assert(compareArray("a\uD800\uDC00b".split(/\uD800/gu), ["a\uD800\uDC00b"]), "#5"); + + assert(compareArray("a\uD800\uDC00b".split(/(?:)/gu), ["a", "𐀀", "b"]), "#6"); + + assert(compareArray("0\x80".split(/(0){0}/gu), ["0", undefined, "\x80"]), "#7"); + + var re = eval('/' + /\ud834\udf06/u.source + '/u'); + assert(re.test('\ud834\udf06'), "#9"); + + /*re = RegExp("\\p{L}", "u"); + if (!re.test("A")) { + throw new Error("Test 9 failed"); + }*/ + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestConvertRegexpToUnicode(t *testing.T) { + if s := convertRegexpToUnicode(`test\uD800\u0C00passed`); s != `test\uD800\u0C00passed` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`test\uD800\uDC00passed`); s != `test𐀀passed` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`test\u0023passed`); s != `test\u0023passed` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`test\u0passed`); s != `test\u0passed` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`test\uD800passed`); s != `test\uD800passed` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`test\uD800`); s != `test\uD800` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`test\uD80`); s != `test\uD80` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`\\uD800\uDC00passed`); s != `\\uD800\uDC00passed` { + t.Fatal(s) + } + if s := convertRegexpToUnicode(`testpassed`); s != `testpassed` { + t.Fatal(s) + } +} + +func TestConvertRegexpToUtf16(t *testing.T) { + if s := convertRegexpToUtf16(`𐀀`); s != `\ud800\udc00` { + t.Fatal(s) + } + if s := convertRegexpToUtf16(`\𐀀`); s != `\\\ud800\udc00` { + t.Fatal(s) + } +} + +func TestEscapeInvalidUtf16(t *testing.T) { + if s := escapeInvalidUtf16(asciiString("test")); s != "test" { + t.Fatal(s) + } + if s := escapeInvalidUtf16(newStringValue("test\U00010000")); s != "test\U00010000" { + t.Fatal(s) + } + if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{'t', 0xD800})); s != "t\\ud800" { + t.Fatal(s) + } + if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{'t', 0xD800, 'p'})); s != "t\\ud800p" { + t.Fatal(s) + } + if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{0xD800, 'p'})); s != "\\ud800p" { + t.Fatal(s) + } + if s := escapeInvalidUtf16(unicodeStringFromRunes([]rune{'t', '\\', 0xD800, 'p'})); s != `t\\\ud800p` { + t.Fatal(s) + } +} + +func TestRegexpAssertion(t *testing.T) { + const SCRIPT = ` + var res = 'aaa'.match(/^a/g); + res.length === 1 || res[0] === 'a'; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestRegexpUnicodeAdvanceStringIndex(t *testing.T) { + const SCRIPT = ` + // deoptimise RegExp + var origExec = RegExp.prototype.exec; + RegExp.prototype.exec = function(s) { + return origExec.call(this, s); + }; + + var re = /(?:)/gu; + var str = "a\uD800\uDC00b"; + assert(compareArray(str.split(re), ["a", "𐀀", "b"]), "#1"); + + re.lastIndex = 3; + assert.sameValue(re.exec(str).index, 3, "#2"); + + re.lastIndex = 2; + assert.sameValue(re.exec(str).index, 1, "#3"); + + re.lastIndex = 4; + assert.sameValue(re.exec(str).index, 4, "#4"); + + re.lastIndex = 5; + assert.sameValue(re.exec(str), null, "#5"); + + var iterator = str.matchAll(re); // regexp is copied by matchAll, but resets lastIndex + var matches = []; + for (var v of iterator) {matches.push(v);} + assert.sameValue(matches.length, 4, "#6"); + assert.sameValue(matches[0].index, 0, "#7 index"); + assert.sameValue(matches[0][0], "", "#7 value"); + assert.sameValue(matches[1].index, 1, "#8 index"); + assert.sameValue(matches[1][0], "", "#8 value"); + assert.sameValue(matches[2].index, 3, "#9 index"); + assert.sameValue(matches[2][0], "", "#9 value"); + assert.sameValue(matches[3].index, 4, "#10 index"); + assert.sameValue(matches[3][0], "", "#10 value"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestRegexpInit(t *testing.T) { + const SCRIPT = ` + RegExp(".").lastIndex; + ` + testScript(SCRIPT, intToValue(0), t) +} + +func TestRegexpToString(t *testing.T) { + const SCRIPT = ` + RegExp.prototype.toString.call({ + source: 'foo', + flags: 'bar'}); + ` + testScript(SCRIPT, asciiString("/foo/bar"), t) +} + +func TestRegexpEscapeSource(t *testing.T) { + const SCRIPT = ` + /href="(.+?)(\/.*\/\S+?)\/"/.source; + ` + testScript(SCRIPT, asciiString(`href="(.+?)(\/.*\/\S+?)\/"`), t) +} + +func TestRegexpConsecutiveMatchCache(t *testing.T) { + const SCRIPT = ` + (function test(unicode) { + var regex = new RegExp('t(e)(st(\\d?))', unicode?'gu':'g'); + var string = 'test1test2'; + var match; + var matches = []; + while (match = regex.exec(string)) { + matches.push(match); + } + var expectedMatches = [ + [ + 'test1', + 'e', + 'st1', + '1' + ], + [ + 'test2', + 'e', + 'st2', + '2' + ] + ]; + expectedMatches[0].index = 0; + expectedMatches[0].input = 'test1test2'; + expectedMatches[1].index = 5; + expectedMatches[1].input = 'test1test2'; + + assert(deepEqual(matches, expectedMatches), "#1"); + + // try the same regexp with a different string + regex.lastIndex = 0; + match = regex.exec(' test5'); + var expectedMatch = [ + 'test5', + 'e', + 'st5', + '5' + ]; + expectedMatch.index = 1; + expectedMatch.input = ' test5'; + assert(deepEqual(match, expectedMatch), "#2"); + assert.sameValue(regex.lastIndex, 6, "#3"); + + // continue matching with a different string + match = regex.exec(' test5test6'); + expectedMatch = [ + 'test6', + 'e', + 'st6', + '6' + ]; + expectedMatch.index = 6; + expectedMatch.input = ' test5test6'; + assert(deepEqual(match, expectedMatch), "#4"); + assert.sameValue(regex.lastIndex, 11, "#5"); + + match = regex.exec(' test5test6'); + assert.sameValue(match, null, "#6"); + return regex; + }); + ` + vm := New() + _, _ = vm.RunProgram(testLib()) + _, _ = vm.RunProgram(testLibX()) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + var f func(bool) (*Object, error) + err = vm.ExportTo(v, &f) + if err != nil { + t.Fatal(err) + } + + regex, err := f(false) + if err != nil { + t.Fatal(err) + } + if regex.self.(*regexpObject).pattern.regexp2Wrapper.cache != nil { + t.Fatal("Cache is not nil (non-unicode)") + } + + regex, err = f(true) + if err != nil { + t.Fatal(err) + } + if regex.self.(*regexpObject).pattern.regexp2Wrapper.cache != nil { + t.Fatal("Cache is not nil (unicode)") + } +} + +func TestRegexpMatchAll(t *testing.T) { + const SCRIPT = ` + (function test(unicode) { + var regex = new RegExp('t(e)(st(\\d?))', unicode?'gu':'g'); + var string = 'test1test2'; + var matches = []; + for (var match of string.matchAll(regex)) { + matches.push(match); + } + var expectedMatches = [ + [ + 'test1', + 'e', + 'st1', + '1' + ], + [ + 'test2', + 'e', + 'st2', + '2' + ] + ]; + expectedMatches[0].index = 0; + expectedMatches[0].input = 'test1test2'; + expectedMatches[1].index = 5; + expectedMatches[1].input = 'test1test2'; + + assert(deepEqual(matches, expectedMatches), "#1"); + assert.sameValue(regex.lastIndex, 0, "#1 lastIndex"); + + // try the same regexp with a different string + string = ' test5'; + matches = []; + for (var match of string.matchAll(regex)) { + matches.push(match); + } + expectedMatches = [ + [ + 'test5', + 'e', + 'st5', + '5' + ] + ]; + expectedMatches[0].index = 1; + expectedMatches[0].input = ' test5'; + assert(deepEqual(matches, expectedMatches), "#2"); + assert.sameValue(regex.lastIndex, 0, "#2 lastIndex"); + + // continue matching with a different string + string = ' test5test6'; + matches = []; + for (var match of string.matchAll(regex)) { + matches.push(match); + } + var expectedMatches = [ + [ + 'test5', + 'e', + 'st5', + '5' + ], + [ + 'test6', + 'e', + 'st6', + '6' + ] + ]; + expectedMatches[0].index = 1; + expectedMatches[0].input = ' test5test6'; + expectedMatches[1].index = 6; + expectedMatches[1].input = ' test5test6'; + assert(deepEqual(matches, expectedMatches), "#3"); + assert.sameValue(regex.lastIndex, 0, "#3 lastindex"); + }); + ` + vm := New() + _, _ = vm.RunProgram(testLib()) + _, _ = vm.RunProgram(testLibX()) + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + var f func(bool) (*Object, error) + err = vm.ExportTo(v, &f) + if err != nil { + t.Fatal(err) + } + + _, err = f(false) + if err != nil { + t.Fatal(err) + } + + _, err = f(true) + if err != nil { + t.Fatal(err) + } +} + +func TestRegexpOverrideSpecies(t *testing.T) { + const SCRIPT = ` + Object.defineProperty(RegExp, Symbol.species, { + configurable: true, + value: function() { + throw "passed"; + } + }); + try { + "ab".split(/a/); + throw new Error("Expected error"); + } catch(e) { + if (e !== "passed") { + throw e; + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestRegexpSymbolMatchAllCallsIsRegexp(t *testing.T) { + // This is tc39's test/built-ins/RegExp/prototype/Symbol.matchAll/isregexp-this-throws.js + const SCRIPT = ` + var a = new Object(); + Object.defineProperty(a, Symbol.match, { + get: function() { + throw "passed"; + } + }); + try { + RegExp.prototype[Symbol.matchAll].call(a, ''); + throw new Error("Expected error"); + } catch(e) { + if (e !== "passed") { + throw e; + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestRegexpMatchAllConstructor(t *testing.T) { + // This is tc39's test/built-ins/RegExp/prototype/Symbol.matchAll/species-constuctor.js + const SCRIPT = ` + var callCount = 0; + var callArgs; + var regexp = /\d/u; + var obj = {} + Object.defineProperty(obj, Symbol.species, { + value: function() { + callCount++; + callArgs = arguments; + return /\w/g; + } + }); + regexp.constructor = obj; + var str = 'a*b'; + var iter = regexp[Symbol.matchAll](str); + + assert.sameValue(callCount, 1); + assert.sameValue(callArgs.length, 2); + assert.sameValue(callArgs[0], regexp); + assert.sameValue(callArgs[1], 'u'); + + var first = iter.next() + assert.sameValue(first.done, false); + assert.sameValue(first.value.length, 1); + assert.sameValue(first.value[0], "a"); + var second = iter.next() + assert.sameValue(second.done, true); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestRegexp2InvalidEscape(t *testing.T) { + testScript(`/(?=)\x0/.test("x0")`, valueTrue, t) +} + +func TestRegexpUnicodeEmptyMatch(t *testing.T) { + testScript(`/(0)0|/gu.exec("0\xef").length === 2`, valueTrue, t) +} + +func TestRegexpInvalidGroup(t *testing.T) { + const SCRIPT = ` + ["?", "(?)"].forEach(function(s) { + assert.throws(SyntaxError, function() {new RegExp(s)}, s); + }); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestRegexpLookbehindAssertion(t *testing.T) { + const SCRIPT = ` + var re = /(?<=Jack|Tom)Sprat/; + assert(re.test("JackSprat"), "#1"); + assert(!re.test("JohnSprat"), "#2"); + + re = /(?" + } + return f.prg.src.Name() +} + +func (f *StackFrame) FuncName() string { + if f.funcName == "" && f.prg == nil { + return "" + } + if f.funcName == "" { + return "" + } + return f.funcName.String() +} + +func (f *StackFrame) Position() file.Position { + if f.prg == nil || f.prg.src == nil { + return file.Position{} + } + return f.prg.src.Position(f.prg.sourceOffset(f.pc)) +} + +func (f *StackFrame) WriteToValueBuilder(b *StringBuilder) { + if f.prg != nil { + if n := f.prg.funcName; n != "" { + b.WriteString(stringValueFromRaw(n)) + b.writeASCII(" (") + } + p := f.Position() + if p.Filename != "" { + b.WriteUTF8String(p.Filename) + } else { + b.writeASCII("") + } + b.WriteRune(':') + b.writeASCII(strconv.Itoa(p.Line)) + b.WriteRune(':') + b.writeASCII(strconv.Itoa(p.Column)) + b.WriteRune('(') + b.writeASCII(strconv.Itoa(f.pc)) + b.WriteRune(')') + if f.prg.funcName != "" { + b.WriteRune(')') + } + } else { + if f.funcName != "" { + b.WriteString(stringValueFromRaw(f.funcName)) + b.writeASCII(" (") + } + b.writeASCII("native") + if f.funcName != "" { + b.WriteRune(')') + } + } +} + +func (f *StackFrame) Write(b *bytes.Buffer) { + if f.prg != nil { + if n := f.prg.funcName; n != "" { + b.WriteString(n.String()) + b.WriteString(" (") + } + p := f.Position() + if p.Filename != "" { + b.WriteString(p.Filename) + } else { + b.WriteString("") + } + b.WriteByte(':') + b.WriteString(strconv.Itoa(p.Line)) + b.WriteByte(':') + b.WriteString(strconv.Itoa(p.Column)) + b.WriteByte('(') + b.WriteString(strconv.Itoa(f.pc)) + b.WriteByte(')') + if f.prg.funcName != "" { + b.WriteByte(')') + } + } else { + if f.funcName != "" { + b.WriteString(f.funcName.String()) + b.WriteString(" (") + } + b.WriteString("native") + if f.funcName != "" { + b.WriteByte(')') + } + } +} + +// An un-catchable exception is not catchable by try/catch statements (finally is not executed either), +// but it is returned as an error to a Go caller rather than causing a panic. +type uncatchableException interface { + error + _uncatchableException() +} + +type Exception struct { + val Value + stack []StackFrame +} + +type baseUncatchableException struct { + Exception +} + +func (e *baseUncatchableException) _uncatchableException() {} + +type InterruptedError struct { + baseUncatchableException + iface any +} + +func (e *InterruptedError) Unwrap() error { + if err, ok := e.iface.(error); ok { + return err + } + return nil +} + +type StackOverflowError struct { + baseUncatchableException +} + +func (e *InterruptedError) Value() any { + return e.iface +} + +func (e *InterruptedError) String() string { + if e == nil { + return "" + } + var b bytes.Buffer + if e.iface != nil { + b.WriteString(fmt.Sprint(e.iface)) + b.WriteByte('\n') + } + e.writeFullStack(&b) + return b.String() +} + +func (e *InterruptedError) Error() string { + if e == nil || e.iface == nil { + return "" + } + var b bytes.Buffer + b.WriteString(fmt.Sprint(e.iface)) + e.writeShortStack(&b) + return b.String() +} + +func (e *Exception) writeFullStack(b *bytes.Buffer) { + for _, frame := range e.stack { + b.WriteString("\tat ") + frame.Write(b) + b.WriteByte('\n') + } +} + +func (e *Exception) writeShortStack(b *bytes.Buffer) { + if len(e.stack) > 0 && (e.stack[0].prg != nil || e.stack[0].funcName != "") { + b.WriteString(" at ") + e.stack[0].Write(b) + } +} + +func (e *Exception) String() string { + if e == nil { + return "" + } + var b bytes.Buffer + if e.val != nil { + b.WriteString(e.val.String()) + b.WriteByte('\n') + } + e.writeFullStack(&b) + return b.String() +} + +func (e *Exception) Error() string { + if e == nil || e.val == nil { + return "" + } + var b bytes.Buffer + b.WriteString(e.val.String()) + e.writeShortStack(&b) + return b.String() +} + +func (e *Exception) Value() Value { + return e.val +} + +func (e *Exception) Unwrap() error { + if obj, ok := e.val.(*Object); ok { + if obj.runtime.getGoError().self.hasInstance(obj) { + if val := obj.Get("value"); val != nil { + e1, _ := val.Export().(error) + return e1 + } + } + } + return nil +} + +func (r *Runtime) createIterProto(val *Object) objectImpl { + o := newBaseObjectObj(val, r.global.ObjectPrototype, classObject) + + o._putSym(SymIterator, valueProp(r.newNativeFunc(r.returnThis, "[Symbol.iterator]", 0), true, false, true)) + return o +} + +func (r *Runtime) getIteratorPrototype() *Object { + var o *Object + if o = r.global.IteratorPrototype; o == nil { + o = &Object{runtime: r} + r.global.IteratorPrototype = o + o.self = r.createIterProto(o) + } + return o +} + +func (r *Runtime) init() { + r.rand = rand.Float64 + r.now = time.Now + + r.global.ObjectPrototype = &Object{runtime: r} + r.newTemplatedObject(getObjectProtoTemplate(), r.global.ObjectPrototype) + + r.globalObject = &Object{runtime: r} + r.newTemplatedObject(getGlobalObjectTemplate(), r.globalObject) + + r.vm = &vm{ + r: r, + } + r.vm.init() +} + +func (r *Runtime) typeErrorResult(throw bool, args ...any) { + if throw { + panic(r.NewTypeError(args...)) + } +} + +func (r *Runtime) newError(typ *Object, format string, args ...any) Value { + var msg string + if len(args) > 0 { + msg = fmt.Sprintf(format, args...) + } else { + msg = format + } + return r.builtin_new(typ, []Value{newStringValue(msg)}) +} + +func (r *Runtime) throwReferenceError(name unistring.String) { + panic(r.newReferenceError(name)) +} + +func (r *Runtime) newReferenceError(name unistring.String) Value { + return r.newError(r.getReferenceError(), "%s is not defined", name) +} + +func (r *Runtime) newSyntaxError(msg string, offset int) Value { + return r.builtin_new(r.getSyntaxError(), []Value{newStringValue(msg)}) +} + +func newBaseObjectObj(obj, proto *Object, class string) *baseObject { + o := &baseObject{ + class: class, + val: obj, + extensible: true, + prototype: proto, + } + obj.self = o + o.init() + return o +} + +func newGuardedObj(proto *Object, class string) *guardedObject { + return &guardedObject{ + baseObject: baseObject{ + class: class, + extensible: true, + prototype: proto, + }, + } +} + +func (r *Runtime) newBaseObject(proto *Object, class string) (o *baseObject) { + v := &Object{runtime: r} + return newBaseObjectObj(v, proto, class) +} + +func (r *Runtime) newGuardedObject(proto *Object, class string) (o *guardedObject) { + v := &Object{runtime: r} + o = newGuardedObj(proto, class) + v.self = o + o.val = v + o.init() + return +} + +func (r *Runtime) NewObject() (v *Object) { + return r.newBaseObject(r.global.ObjectPrototype, classObject).val +} + +// CreateObject creates an object with given prototype. Equivalent of Object.create(proto). +func (r *Runtime) CreateObject(proto *Object) *Object { + return r.newBaseObject(proto, classObject).val +} + +func (r *Runtime) NewArray(items ...any) *Object { + values := make([]Value, len(items)) + for i, item := range items { + values[i] = r.ToValue(item) + } + return r.newArrayValues(values) +} + +func (r *Runtime) NewTypeError(args ...any) *Object { + msg := "" + if len(args) > 0 { + f, _ := args[0].(string) + msg = fmt.Sprintf(f, args[1:]...) + } + return r.builtin_new(r.getTypeError(), []Value{newStringValue(msg)}) +} + +func (r *Runtime) NewGoError(err error) *Object { + e := r.newError(r.getGoError(), err.Error()).(*Object) + e.Set("value", err) + return e +} + +func (r *Runtime) newFunc(name unistring.String, length int, strict bool) (f *funcObject) { + f = &funcObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.val.self = f + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) newAsyncFunc(name unistring.String, length int, strict bool) (f *asyncFuncObject) { + f = &asyncFuncObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.class = classFunction + f.prototype = r.getAsyncFunctionPrototype() + f.val.self = f + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) newGeneratorFunc(name unistring.String, length int, strict bool) (f *generatorFuncObject) { + f = &generatorFuncObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.class = classFunction + f.prototype = r.getGeneratorFunctionPrototype() + f.val.self = f + f.init(name, intToValue(int64(length))) + f._putProp("prototype", r.newBaseObject(r.getGeneratorPrototype(), classObject).val, true, false, false) + return +} + +func (r *Runtime) newClassFunc(name unistring.String, length int, proto *Object, derived bool) (f *classFuncObject) { + v := &Object{runtime: r} + + f = &classFuncObject{} + f.class = classFunction + f.val = v + f.extensible = true + f.strict = true + f.derived = derived + v.self = f + f.prototype = proto + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) initBaseJsFunction(f *baseJsFuncObject, strict bool) { + v := &Object{runtime: r} + + f.class = classFunction + f.val = v + f.extensible = true + f.strict = strict + f.prototype = r.getFunctionPrototype() +} + +func (r *Runtime) newMethod(name unistring.String, length int, strict bool) (f *methodFuncObject) { + f = &methodFuncObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.val.self = f + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) newGeneratorMethod(name unistring.String, length int, strict bool) (f *generatorMethodFuncObject) { + f = &generatorMethodFuncObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.prototype = r.getGeneratorFunctionPrototype() + f.val.self = f + f.init(name, intToValue(int64(length))) + f._putProp("prototype", r.newBaseObject(r.getGeneratorPrototype(), classObject).val, true, false, false) + return +} + +func (r *Runtime) newAsyncMethod(name unistring.String, length int, strict bool) (f *asyncMethodFuncObject) { + f = &asyncMethodFuncObject{} + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.val.self = f + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) initArrowFunc(f *arrowFuncObject, strict bool) { + r.initBaseJsFunction(&f.baseJsFuncObject, strict) + f.newTarget = r.vm.newTarget +} + +func (r *Runtime) newArrowFunc(name unistring.String, length int, strict bool) (f *arrowFuncObject) { + f = &arrowFuncObject{} + r.initArrowFunc(f, strict) + f.val.self = f + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) newAsyncArrowFunc(name unistring.String, length int, strict bool) (f *asyncArrowFuncObject) { + f = &asyncArrowFuncObject{} + r.initArrowFunc(&f.arrowFuncObject, strict) + f.class = classObject + f.prototype = r.getAsyncFunctionPrototype() + f.val.self = f + f.init(name, intToValue(int64(length))) + return +} + +func (r *Runtime) newNativeConstructor(call func(ConstructorCall) *Object, name unistring.String, length int64) *Object { + v := &Object{runtime: r} + + f := &nativeFuncObject{ + baseFuncObject: baseFuncObject{ + baseObject: baseObject{ + class: classFunction, + val: v, + extensible: true, + prototype: r.getFunctionPrototype(), + }, + }, + } + + f.f = func(c FunctionCall) Value { + thisObj, _ := c.This.(*Object) + if thisObj != nil { + res := call(ConstructorCall{ + This: thisObj, + Arguments: c.Arguments, + }) + if res == nil { + return _undefined + } + return res + } + return f.defaultConstruct(call, c.Arguments, nil) + } + + f.construct = func(args []Value, newTarget *Object) *Object { + return f.defaultConstruct(call, args, newTarget) + } + + v.self = f + f.init(name, intToValue(length)) + + proto := r.NewObject() + proto.self._putProp("constructor", v, true, false, true) + f._putProp("prototype", proto, true, false, false) + + return v +} + +func (r *Runtime) newNativeConstructOnly(v *Object, ctor func(args []Value, newTarget *Object) *Object, defaultProto *Object, name unistring.String, length int64) *nativeFuncObject { + return r.newNativeFuncAndConstruct(v, func(call FunctionCall) Value { + return ctor(call.Arguments, nil) + }, + func(args []Value, newTarget *Object) *Object { + if newTarget == nil { + newTarget = v + } + return ctor(args, newTarget) + }, defaultProto, name, intToValue(length)) +} + +func (r *Runtime) newNativeFuncAndConstruct(v *Object, call func(call FunctionCall) Value, ctor func(args []Value, newTarget *Object) *Object, defaultProto *Object, name unistring.String, l Value) *nativeFuncObject { + if v == nil { + v = &Object{runtime: r} + } + + f := &nativeFuncObject{ + baseFuncObject: baseFuncObject{ + baseObject: baseObject{ + class: classFunction, + val: v, + extensible: true, + prototype: r.getFunctionPrototype(), + }, + }, + f: call, + construct: ctor, + } + v.self = f + f.init(name, l) + if defaultProto != nil { + f._putProp("prototype", defaultProto, false, false, false) + } + + return f +} + +func (r *Runtime) newNativeFunc(call func(FunctionCall) Value, name unistring.String, length int) *Object { + v := &Object{runtime: r} + + f := &nativeFuncObject{ + baseFuncObject: baseFuncObject{ + baseObject: baseObject{ + class: classFunction, + val: v, + extensible: true, + prototype: r.getFunctionPrototype(), + }, + }, + f: call, + } + v.self = f + f.init(name, intToValue(int64(length))) + return v +} + +func (r *Runtime) newWrappedFunc(value reflect.Value) *Object { + + v := &Object{runtime: r} + + f := &wrappedFuncObject{ + nativeFuncObject: nativeFuncObject{ + baseFuncObject: baseFuncObject{ + baseObject: baseObject{ + class: classFunction, + val: v, + extensible: true, + prototype: r.getFunctionPrototype(), + }, + }, + f: r.wrapReflectFunc(value), + }, + wrapped: value, + } + v.self = f + name := unistring.NewFromString(runtime.FuncForPC(value.Pointer()).Name()) + f.init(name, intToValue(int64(value.Type().NumIn()))) + return v +} + +func (r *Runtime) newNativeFuncConstructObj(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, proto *Object, length int) *nativeFuncObject { + f := &nativeFuncObject{ + baseFuncObject: baseFuncObject{ + baseObject: baseObject{ + class: classFunction, + val: v, + extensible: true, + prototype: r.getFunctionPrototype(), + }, + }, + f: r.constructToCall(construct, proto), + construct: r.wrapNativeConstruct(construct, v, proto), + } + + f.init(name, intToValue(int64(length))) + if proto != nil { + f._putProp("prototype", proto, false, false, false) + } + return f +} + +func (r *Runtime) newNativeFuncConstruct(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, prototype *Object, length int64) *Object { + return r.newNativeFuncConstructProto(v, construct, name, prototype, r.getFunctionPrototype(), length) +} + +func (r *Runtime) newNativeFuncConstructProto(v *Object, construct func(args []Value, proto *Object) *Object, name unistring.String, prototype, proto *Object, length int64) *Object { + f := &nativeFuncObject{} + f.class = classFunction + f.val = v + f.extensible = true + v.self = f + f.prototype = proto + f.f = r.constructToCall(construct, prototype) + f.construct = r.wrapNativeConstruct(construct, v, prototype) + f.init(name, intToValue(length)) + if prototype != nil { + f._putProp("prototype", prototype, false, false, false) + } + return v +} + +func (r *Runtime) newPrimitiveObject(value Value, proto *Object, class string) *Object { + v := &Object{runtime: r} + + o := &primitiveValueObject{} + o.class = class + o.val = v + o.extensible = true + v.self = o + o.prototype = proto + o.pValue = value + o.init() + return v +} + +func (r *Runtime) builtin_Number(call FunctionCall) Value { + if len(call.Arguments) > 0 { + return call.Arguments[0].ToNumber() + } else { + return valueInt(0) + } +} + +func (r *Runtime) builtin_newNumber(args []Value, proto *Object) *Object { + var v Value + if len(args) > 0 { + v = args[0].ToNumber() + } else { + v = intToValue(0) + } + return r.newPrimitiveObject(v, proto, classNumber) +} + +func (r *Runtime) builtin_Boolean(call FunctionCall) Value { + if len(call.Arguments) > 0 { + if call.Arguments[0].ToBoolean() { + return valueTrue + } else { + return valueFalse + } + } else { + return valueFalse + } +} + +func (r *Runtime) builtin_newBoolean(args []Value, proto *Object) *Object { + var v Value + if len(args) > 0 { + if args[0].ToBoolean() { + v = valueTrue + } else { + v = valueFalse + } + } else { + v = valueFalse + } + return r.newPrimitiveObject(v, proto, classBoolean) +} + +func (r *Runtime) builtin_new(construct *Object, args []Value) *Object { + return r.toConstructor(construct)(args, construct) +} + +func (r *Runtime) builtin_thrower(call FunctionCall) Value { + obj := r.toObject(call.This) + strict := true + switch fn := obj.self.(type) { + case *funcObject: + strict = fn.strict + } + r.typeErrorResult(strict, "'caller', 'callee', and 'arguments' properties may not be accessed on strict mode functions or the arguments objects for calls to them") + return nil +} + +func (r *Runtime) eval(srcVal String, direct, strict bool) Value { + src := escapeInvalidUtf16(srcVal) + vm := r.vm + inGlobal := true + if direct { + for s := vm.stash; s != nil; s = s.outer { + if s.isVariable() { + inGlobal = false + break + } + } + } + vm.pushCtx() + funcObj := _undefined + if !direct { + vm.stash = &r.global.stash + vm.privEnv = nil + } else { + if sb := vm.sb; sb > 0 { + funcObj = vm.stack[sb-1] + } + } + p, err := r.compile("", src, strict, inGlobal, r.vm) + if err != nil { + panic(err) + } + + vm.prg = p + vm.pc = 0 + vm.args = 0 + vm.result = _undefined + vm.push(funcObj) + vm.sb = vm.sp + vm.push(nil) // this + ex := vm.runTry() + retval := vm.result + vm.popCtx() + if ex != nil { + panic(ex) + } + vm.sp -= 2 + return retval +} + +func (r *Runtime) builtin_eval(call FunctionCall) Value { + if len(call.Arguments) == 0 { + return _undefined + } + if str, ok := call.Arguments[0].(String); ok { + return r.eval(str, false, false) + } + return call.Arguments[0] +} + +func (r *Runtime) constructToCall(construct func(args []Value, proto *Object) *Object, proto *Object) func(call FunctionCall) Value { + return func(call FunctionCall) Value { + return construct(call.Arguments, proto) + } +} + +func (r *Runtime) wrapNativeConstruct(c func(args []Value, proto *Object) *Object, ctorObj, defProto *Object) func(args []Value, newTarget *Object) *Object { + if c == nil { + return nil + } + return func(args []Value, newTarget *Object) *Object { + var proto *Object + if newTarget != nil { + proto = r.getPrototypeFromCtor(newTarget, ctorObj, defProto) + } else { + proto = defProto + } + return c(args, proto) + } +} + +func (r *Runtime) toCallable(v Value) func(FunctionCall) Value { + if call, ok := r.toObject(v).self.assertCallable(); ok { + return call + } + r.typeErrorResult(true, "Value is not callable: %s", v.toString()) + return nil +} + +func (r *Runtime) toPrimitive(v Value, hint Value) Value { + if o, ok := v.(*Object); ok { + return r.toObject(o).defaultValue(hint) + } + return v +} + +func (r *Runtime) checkObjectCoercible(v Value) { + switch v.(type) { + case valueUndefined, valueNull: + r.typeErrorResult(true, "Value is not object coercible") + } +} + +func toInt8(v Value) int8 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return int8(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return int8(int64(f)) + } + } + return 0 +} + +func toUint8(v Value) uint8 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return uint8(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return uint8(int64(f)) + } + } + return 0 +} + +func toUint8Clamp(v Value) uint8 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + if i < 0 { + return 0 + } + if i <= 255 { + return uint8(i) + } + return 255 + } + + if num, ok := v.(valueFloat); ok { + num := float64(num) + if !math.IsNaN(num) { + if num < 0 { + return 0 + } + if num > 255 { + return 255 + } + f := math.Floor(num) + f1 := f + 0.5 + if f1 < num { + return uint8(f + 1) + } + if f1 > num { + return uint8(f) + } + r := uint8(f) + if r&1 != 0 { + return r + 1 + } + return r + } + } + return 0 +} + +func toInt16(v Value) int16 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return int16(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return int16(int64(f)) + } + } + return 0 +} + +func toUint16(v Value) uint16 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return uint16(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return uint16(int64(f)) + } + } + return 0 +} + +func toInt32(v Value) int32 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return int32(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return int32(int64(f)) + } + } + return 0 +} + +func toUint32(v Value) uint32 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return uint32(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return uint32(int64(f)) + } + } + return 0 +} + +func toInt64(v Value) int64 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return int64(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return int64(f) + } + } + return 0 +} + +func toUint64(v Value) uint64 { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return uint64(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return uint64(int64(f)) + } + } + return 0 +} + +func toInt(v Value) int { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return int(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return int(f) + } + } + return 0 +} + +func toUint(v Value) uint { + v = v.ToNumber() + if i, ok := v.(valueInt); ok { + return uint(i) + } + + if f, ok := v.(valueFloat); ok { + f := float64(f) + if !math.IsNaN(f) && !math.IsInf(f, 0) { + return uint(int64(f)) + } + } + return 0 +} + +func toFloat32(v Value) float32 { + return float32(v.ToFloat()) +} + +func toLength(v Value) int64 { + if v == nil { + return 0 + } + i := v.ToInteger() + if i < 0 { + return 0 + } + if i >= maxInt { + return maxInt - 1 + } + return i +} + +func (r *Runtime) toLengthUint32(v Value) uint32 { + var intVal int64 +repeat: + switch num := v.(type) { + case valueInt: + intVal = int64(num) + case valueFloat: + if v != _negativeZero { + if i, ok := floatToInt(float64(num)); ok { + intVal = i + } else { + goto fail + } + } + case String: + v = num.ToNumber() + goto repeat + default: + // Legacy behaviour as specified in https://tc39.es/ecma262/#sec-arraysetlength (see the note) + n2 := toUint32(v) + n1 := v.ToNumber() + if f, ok := n1.(valueFloat); ok { + f := float64(f) + if f != 0 || !math.Signbit(f) { + goto fail + } + } + if n1.ToInteger() != int64(n2) { + goto fail + } + return n2 + } + if intVal >= 0 && intVal <= math.MaxUint32 { + return uint32(intVal) + } +fail: + panic(r.newError(r.getRangeError(), "Invalid array length")) +} + +func toIntStrict(i int64) int { + if bits.UintSize == 32 { + if i > math.MaxInt32 || i < math.MinInt32 { + panic(rangeError("Integer value overflows 32-bit int")) + } + } + return int(i) +} + +func toIntClamp(i int64) int { + if bits.UintSize == 32 { + if i > math.MaxInt32 { + return math.MaxInt32 + } + if i < math.MinInt32 { + return math.MinInt32 + } + } + return int(i) +} + +func (r *Runtime) toIndex(v Value) int { + num := v.ToInteger() + if num >= 0 && num < maxInt { + if bits.UintSize == 32 && num >= math.MaxInt32 { + panic(r.newError(r.getRangeError(), "Index %s overflows int", v.String())) + } + return int(num) + } + panic(r.newError(r.getRangeError(), "Invalid index %s", v.String())) +} + +func (r *Runtime) toBoolean(b bool) Value { + if b { + return valueTrue + } else { + return valueFalse + } +} + +// New creates an instance of a Javascript runtime that can be used to run code. Multiple instances may be created and +// used simultaneously, however it is not possible to pass JS values across runtimes. +func New() *Runtime { + r := &Runtime{CreateAt: time.Now()} + r.init() + return r +} + +// Compile creates an internal representation of the JavaScript code that can be later run using the Runtime.RunProgram() +// method. This representation is not linked to a runtime in any way and can be run in multiple runtimes (possibly +// at the same time). +func Compile(name, src string, strict bool) (*Program, error) { + return compile(name, src, strict, true, nil) +} + +// CompileAST creates an internal representation of the JavaScript code that can be later run using the Runtime.RunProgram() +// method. This representation is not linked to a runtime in any way and can be run in multiple runtimes (possibly +// at the same time). +func CompileAST(prg *js_ast.Program, strict bool) (*Program, error) { + return compileAST(prg, strict, true, nil) +} + +// MustCompile is like Compile but panics if the code cannot be compiled. +// It simplifies safe initialization of global variables holding compiled JavaScript code. +func MustCompile(name, src string, strict bool) *Program { + prg, err := Compile(name, src, strict) + if err != nil { + panic(err) + } + + return prg +} + +// Parse takes a source string and produces a parsed AST. Use this function if you want to pass options +// to the parser, e.g.: +// +// p, err := Parse("test.js", "var a = true", parser.WithDisableSourceMaps) +// if err != nil { /* ... */ } +// prg, err := CompileAST(p, true) +// // ... +// +// Otherwise use Compile which combines both steps. +func Parse(name, src string, options ...parser.Option) (prg *js_ast.Program, err error) { + prg, err1 := parser.ParseFile(nil, name, src, 0, options...) + if err1 != nil { + // FIXME offset + err = &CompilerSyntaxError{ + CompilerError: CompilerError{ + Message: err1.Error(), + }, + } + } + return +} + +func compile(name, src string, strict, inGlobal bool, evalVm *vm, parserOptions ...parser.Option) (p *Program, err error) { + prg, err := Parse(name, src, parserOptions...) + if err != nil { + return + } + + return compileAST(prg, strict, inGlobal, evalVm) +} + +func compileAST(prg *js_ast.Program, strict, inGlobal bool, evalVm *vm) (p *Program, err error) { + c := newCompiler() + + defer func() { + if x := recover(); x != nil { + p = nil + switch x1 := x.(type) { + case *CompilerSyntaxError: + err = x1 + default: + panic(x) + } + } + }() + + c.compile(prg, strict, inGlobal, evalVm) + p = c.p + return +} + +func (r *Runtime) compile(name, src string, strict, inGlobal bool, evalVm *vm) (p *Program, err error) { + p, err = compile(name, src, strict, inGlobal, evalVm, r.parserOptions...) + if err != nil { + switch x1 := err.(type) { + case *CompilerSyntaxError: + err = &Exception{ + val: r.builtin_new(r.getSyntaxError(), []Value{newStringValue(x1.Error())}), + } + case *CompilerReferenceError: + err = &Exception{ + val: r.newError(r.getReferenceError(), x1.Message), + } // TODO proper message + } + } + return +} + +// RunString executes the given string in the global context. +func (r *Runtime) RunString(str string) (Value, error) { + return r.RunScript("", str) +} + +// RunScript executes the given string in the global context. +func (r *Runtime) RunScript(name, src string) (Value, error) { + p, err := r.compile(name, src, false, true, nil) + + if err != nil { + return nil, err + } + + return r.RunProgram(p) +} + +func isUncatchableException(e error) bool { + for ; e != nil; e = errors.Unwrap(e) { + if _, ok := e.(uncatchableException); ok { + return true + } + } + return false +} + +func asUncatchableException(v any) error { + switch v := v.(type) { + case uncatchableException: + return v + case error: + if isUncatchableException(v) { + return v + } + } + return nil +} + +// RunProgram executes a pre-compiled (see Compile()) code in the global context. +func (r *Runtime) RunProgram(p *Program) (result Value, err error) { + vm := r.vm + recursive := len(vm.callStack) > 0 + defer func() { + if recursive { + vm.sp -= 2 + vm.popCtx() + } else { + vm.callStack = vm.callStack[:len(vm.callStack)-1] + } + if x := recover(); x != nil { + if ex := asUncatchableException(x); ex != nil { + err = ex + if len(vm.callStack) == 0 { + r.leaveAbrupt() + } + } else { + panic(x) + } + } + }() + if recursive { + vm.pushCtx() + vm.stash = &r.global.stash + vm.privEnv = nil + vm.newTarget = nil + vm.args = 0 + sp := vm.sp + vm.stack.expand(sp + 1) + vm.stack[sp] = _undefined // 'callee' + vm.stack[sp+1] = nil // 'this' + vm.sb = sp + 1 + vm.sp = sp + 2 + } else { + vm.callStack = append(vm.callStack, context{}) + } + vm.prg = p + vm.pc = 0 + vm.result = _undefined + ex := vm.runTry() + if ex == nil { + result = r.vm.result + } else { + err = ex + } + if recursive { + vm.clearStack() + } else { + vm.prg = nil + vm.sb = -1 + r.leave() + } + return +} + +// CaptureCallStack appends the current call stack frames to the stack slice (which may be nil) up to the specified depth. +// The most recent frame will be the first one. +// If depth <= 0 or more than the number of available frames, returns the entire stack. +// This method is not safe for concurrent use and should only be called by a Go function that is +// called from a running script. +func (r *Runtime) CaptureCallStack(depth int, stack []StackFrame) []StackFrame { + l := len(r.vm.callStack) + var offset int + if depth > 0 { + offset = l - depth + 1 + if offset < 0 { + offset = 0 + } + } + if stack == nil { + stack = make([]StackFrame, 0, l-offset+1) + } + return r.vm.captureStack(stack, offset) +} + +// Interrupt a running JavaScript. The corresponding Go call will return an *InterruptedError containing v. +// If the interrupt propagates until the stack is empty the currently queued promise resolve/reject jobs will be cleared +// without being executed. This is the same time they would be executed otherwise. +// Note, it only works while in JavaScript code, it does not interrupt native Go functions (which includes all built-ins). +// If the runtime is currently not running, it will be immediately interrupted on the next Run*() call. +// To avoid that use ClearInterrupt() +func (r *Runtime) Interrupt(v any) { + r.vm.Interrupt(v) +} + +// ClearInterrupt resets the interrupt flag. Typically this needs to be called before the runtime +// is made available for re-use if there is a chance it could have been interrupted with Interrupt(). +// Otherwise if Interrupt() was called when runtime was not running (e.g. if it had already finished) +// so that Interrupt() didn't actually trigger, an attempt to use the runtime will immediately cause +// an interruption. It is up to the user to ensure proper synchronisation so that ClearInterrupt() is +// only called when the runtime has finished and there is no chance of a concurrent Interrupt() call. +func (r *Runtime) ClearInterrupt() { + r.vm.ClearInterrupt() +} + +/* +ToValue converts a Go value into a JavaScript value of a most appropriate type. Structural types (such as structs, maps +and slices) are wrapped so that changes are reflected on the original value which can be retrieved using Value.Export(). + +WARNING! These wrapped Go values do not behave in the same way as native ECMAScript values. If you plan to modify +them in ECMAScript, bear in mind the following caveats: + +1. If a regular JavaScript Object is assigned as an element of a wrapped Go struct, map or array, it is +Export()'ed and therefore copied. This may result in an unexpected behaviour in JavaScript: + + m := map[string]any{} + vm.Set("m", m) + vm.RunString(` + var obj = {test: false}; + m.obj = obj; // obj gets Export()'ed, i.e. copied to a new map[string]any and then this map is set as m["obj"] + obj.test = true; // note, m.obj.test is still false + `) + fmt.Println(m["obj"].(map[string]any)["test"]) // prints "false" + +2. Be careful with nested non-pointer compound types (structs, slices and arrays) if you modify them in +ECMAScript. Better avoid it at all if possible. One of the fundamental differences between ECMAScript and Go is in +the former all Objects are references whereas in Go you can have a literal struct or array. Consider the following +example: + + type S struct { + Field int + } + + a := []S{{1}, {2}} // slice of literal structs + vm.Set("a", &a) + vm.RunString(` + let tmp = {Field: 1}; + a[0] = tmp; + a[1] = tmp; + tmp.Field = 2; + `) + +In ECMAScript one would expect a[0].Field and a[1].Field to be equal to 2, but this is really not possible +(or at least non-trivial without some complex reference tracking). + +To cover the most common use cases and to avoid excessive memory allocation, the following 'copy-on-change' mechanism +is implemented (for both arrays and structs): + +* When a nested compound value is accessed, the returned ES value becomes a reference to the literal value. +This ensures that things like 'a[0].Field = 1' work as expected and simple access to 'a[0].Field' does not result +in copying of a[0]. + +* The original container ('a' in our case) keeps track of the returned reference value and if a[0] is reassigned +(e.g. by direct assignment, deletion or shrinking the array) the old a[0] is copied and the earlier returned value +becomes a reference to the copy: + + let tmp = a[0]; // no copy, tmp is a reference to a[0] + tmp.Field = 1; // a[0].Field === 1 after this + a[0] = {Field: 2}; // tmp is now a reference to a copy of the old value (with Field === 1) + a[0].Field === 2 && tmp.Field === 1; // true + +* Array value swaps caused by in-place sort (using Array.prototype.sort()) do not count as re-assignments, instead +the references are adjusted to point to the new indices. + +* Assignment to an inner compound value always does a copy (and sometimes type conversion): + + a[1] = tmp; // a[1] is now a copy of tmp + tmp.Field = 3; // does not affect a[1].Field + +3. Non-addressable structs, slices and arrays get copied. This sometimes may lead to a confusion as assigning to +inner fields does not appear to work: + + a1 := []any{S{1}, S{2}} + vm.Set("a1", &a1) + vm.RunString(` + a1[0].Field === 1; // true + a1[0].Field = 2; + a1[0].Field === 2; // FALSE, because what it really did was copy a1[0] set its Field to 2 and immediately drop it + `) + +An alternative would be making a1[0].Field a non-writable property which would probably be more in line with +ECMAScript, however it would require to manually copy the value if it does need to be modified which may be +impractical. + +Note, the same applies to slices. If a slice is passed by value (not as a pointer), resizing the slice does not reflect on the original +value. Moreover, extending the slice may result in the underlying array being re-allocated and copied. +For example: + + a := []any{1} + vm.Set("a", a) + vm.RunString(`a.push(2); a[0] = 0;`) + fmt.Println(a[0]) // prints "1" + +Notes on individual types: + +# Primitive types + +Primitive types (numbers, string, bool) are converted to the corresponding JavaScript primitives. These values +are goroutine-safe and can be transferred between runtimes. + +# Strings + +Because of the difference in internal string representation between ECMAScript (which uses UTF-16) and Go (which uses +UTF-8) conversion from JS to Go may be lossy. In particular, code points that can be part of UTF-16 surrogate pairs +(0xD800-0xDFFF) cannot be represented in UTF-8 unless they form a valid surrogate pair and are replaced with +utf8.RuneError. + +The string value must be a valid UTF-8. If it is not, invalid characters are replaced with utf8.RuneError, but +the behaviour of a subsequent Export() is unspecified (it may return the original value, or a value with replaced +invalid characters). + +# Nil + +Nil is converted to null. + +# Functions + +func(FunctionCall) Value is treated as a native JavaScript function. This increases performance because there are no +automatic argument and return value type conversions (which involves reflect). Attempting to use +the function as a constructor will result in a TypeError. Note: implementations must not retain and use references +to FunctionCall.Arguments after the function returns. + +func(FunctionCall, *Runtime) Value is treated as above, except the *Runtime is also passed as a parameter. + +func(ConstructorCall) *Object is treated as a native constructor, allowing to use it with the new +operator: + + func MyObject(call xjs.ConstructorCall) *xjs.Object { + // call.This contains the newly created object as per http://www.ecma-international.org/ecma-262/5.1/index.html#sec-13.2.2 + // call.Arguments contain arguments passed to the function + + call.This.Set("method", method) + + //... + + // If return value is a non-nil *Object, it will be used instead of call.This + // This way it is possible to return a Go struct or a map converted + // into xjs.Value using ToValue(), however in this case + // instanceof will not work as expected, unless you set the prototype: + // + // instance := &myCustomStruct{} + // instanceValue := vm.ToValue(instance).(*Object) + // instanceValue.SetPrototype(call.This.Prototype()) + // return instanceValue + return nil + } + + runtime.Set("MyObject", MyObject) + +Then it can be used in JS as follows: + + var o = new MyObject(arg); + var o1 = MyObject(arg); // same thing + o instanceof MyObject && o1 instanceof MyObject; // true + +When a native constructor is called directly (without the new operator) its behavior depends on +this value: if it's an Object, it is passed through, otherwise a new one is created exactly as +if it was called with the new operator. In either case call.NewTarget will be nil. + +func(ConstructorCall, *Runtime) *Object is treated as above, except the *Runtime is also passed as a parameter. + +Any other Go function is wrapped so that the arguments are automatically converted into the required Go types and the +return value is converted to a JavaScript value (using this method). If conversion is not possible, a TypeError is +thrown. + +Functions with multiple return values return an Array. If the last return value is an `error` it is not returned but +converted into a JS exception. If the error is *Exception, it is thrown as is, otherwise it's wrapped in a GoEerror. +Note that if there are exactly two return values and the last is an `error`, the function returns the first value as is, +not an Array. + +# Structs + +Structs are converted to Object-like values. Fields and methods are available as properties, their values are +results of this method (ToValue()) applied to the corresponding Go value. + +Field properties are writable and non-configurable. Method properties are non-writable and non-configurable. + +Attempt to define a new property or delete an existing property will fail (throw in strict mode) unless it's a Symbol +property. Symbol properties only exist in the wrapper and do not affect the underlying Go value. +Note that because a wrapper is created every time a property is accessed it may lead to unexpected results such as this: + + type Field struct{ + } + type S struct { + Field *Field + } + var s = S{ + Field: &Field{}, + } + vm := New() + vm.Set("s", &s) + res, err := vm.RunString(` + var sym = Symbol(66); + var field1 = s.Field; + field1[sym] = true; + var field2 = s.Field; + field1 === field2; // true, because the equality operation compares the wrapped values, not the wrappers + field1[sym] === true; // true + field2[sym] === undefined; // also true + `) + +The same applies to values from maps and slices as well. + +# Handling of time.Time + +time.Time does not get special treatment and therefore is converted just like any other `struct` providing access to +all its methods. This is done deliberately instead of converting it to a `Date` because these two types are not fully +compatible: `time.Time` includes zone, whereas JS `Date` doesn't. Doing the conversion implicitly therefore would +result in a loss of information. + +If you need to convert it to a `Date`, it can be done either in JS: + + var d = new Date(goval.UnixNano()/1e6); + +... or in Go: + + now := time.Now() + vm := New() + val, err := vm.New(vm.Get("Date").ToObject(vm), vm.ToValue(now.UnixNano()/1e6)) + if err != nil { + ... + } + vm.Set("d", val) + +Note that Value.Export() for a `Date` value returns time.Time in local timezone. + +# Maps + +Maps with string or integer key type are converted into host objects that largely behave like a JavaScript Object. + +# Maps with methods + +If a map type has at least one method defined, the properties of the resulting Object represent methods, not map keys. +This is because in JavaScript there is no distinction between 'object.key` and `object[key]`, unlike Go. +If access to the map values is required, it can be achieved by defining another method or, if it's not possible, by +defining an external getter function. + +# Slices + +Slices are converted into host objects that behave largely like JavaScript Array. It has the appropriate +prototype and all the usual methods should work. There is, however, a caveat: converted Arrays may not contain holes +(because Go slices cannot). This means that hasOwnProperty(n) always returns `true` if n < length. Deleting an item with +an index < length will set it to a zero value (but the property will remain). Nil slice elements are be converted to +`null`. Accessing an element beyond `length` returns `undefined`. Also see the warning above about passing slices as +values (as opposed to pointers). + +# Arrays + +Arrays are converted similarly to slices, except the resulting Arrays are not resizable (and therefore the 'length' +property is non-writable). + +Any other type is converted to a generic reflect based host object. Depending on the underlying type it behaves similar +to a Number, String, Boolean or Object. + +Note that the underlying type is not lost, calling Export() returns the original Go value. This applies to all +reflect based types. +*/ +func (r *Runtime) ToValue(i any) Value { + return r.toValue(i, reflect.Value{}) +} + +func (r *Runtime) toValue(i any, origValue reflect.Value) Value { + switch i := i.(type) { + case nil: + return _null + case *Object: + if i == nil || i.self == nil { + return _null + } + if i.runtime != nil && i.runtime != r { + panic(r.NewTypeError("Illegal runtime transition of an Object")) + } + return i + case valueContainer: + return i.toValue(r) + case Value: + return i + case string: + if len(i) <= 16 { + if u := unistring.Scan(i); u != nil { + return &importedString{s: i, u: u, scanned: true} + } + return asciiString(i) + } + return &importedString{s: i} + case bool: + if i { + return valueTrue + } else { + return valueFalse + } + case func(FunctionCall) Value: + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) + return r.newNativeFunc(i, name, 0) + case func(FunctionCall, *Runtime) Value: + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) + return r.newNativeFunc(func(call FunctionCall) Value { + return i(call, r) + }, name, 0) + case func(ConstructorCall) *Object: + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) + return r.newNativeConstructor(i, name, 0) + case func(ConstructorCall, *Runtime) *Object: + name := unistring.NewFromString(runtime.FuncForPC(reflect.ValueOf(i).Pointer()).Name()) + return r.newNativeConstructor(func(call ConstructorCall) *Object { + return i(call, r) + }, name, 0) + case int: + return intToValue(int64(i)) + case int8: + return intToValue(int64(i)) + case int16: + return intToValue(int64(i)) + case int32: + return intToValue(int64(i)) + case int64: + return intToValue(i) + case uint: + if uint64(i) <= math.MaxInt64 { + return intToValue(int64(i)) + } else { + return floatToValue(float64(i)) + } + case uint8: + return intToValue(int64(i)) + case uint16: + return intToValue(int64(i)) + case uint32: + return intToValue(int64(i)) + case uint64: + if i <= math.MaxInt64 { + return intToValue(int64(i)) + } + return floatToValue(float64(i)) + case float32: + return floatToValue(float64(i)) + case float64: + return floatToValue(i) + case map[string]any: + if i == nil { + return _null + } + obj := &Object{runtime: r} + m := &objectGoMapSimple{ + baseObject: baseObject{ + val: obj, + extensible: true, + }, + data: i, + } + obj.self = m + m.init() + return obj + case []any: + return r.newObjectGoSlice(&i, false).val + case *[]any: + if i == nil { + return _null + } + return r.newObjectGoSlice(i, true).val + } + + if !origValue.IsValid() { + origValue = reflect.ValueOf(i) + } + + value := origValue + for value.Kind() == reflect.Ptr { + value = value.Elem() + } + + if !value.IsValid() { + return _null + } + + switch value.Kind() { + case reflect.Map: + if value.Type().NumMethod() == 0 { + switch value.Type().Key().Kind() { + case reflect.String, reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64, + reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64, + reflect.Float64, reflect.Float32: + + obj := &Object{runtime: r} + m := &objectGoMapReflect{ + objectGoReflect: objectGoReflect{ + baseObject: baseObject{ + val: obj, + extensible: true, + }, + origValue: origValue, + fieldsValue: value, + }, + } + m.init() + obj.self = m + return obj + } + } + case reflect.Array: + obj := &Object{runtime: r} + a := &objectGoArrayReflect{ + objectGoReflect: objectGoReflect{ + baseObject: baseObject{ + val: obj, + }, + origValue: origValue, + fieldsValue: value, + }, + } + a.init() + obj.self = a + return obj + case reflect.Slice: + obj := &Object{runtime: r} + a := &objectGoSliceReflect{ + objectGoArrayReflect: objectGoArrayReflect{ + objectGoReflect: objectGoReflect{ + baseObject: baseObject{ + val: obj, + }, + origValue: origValue, + fieldsValue: value, + }, + }, + } + a.init() + obj.self = a + return obj + case reflect.Func: + return r.newWrappedFunc(value) + } + + obj := &Object{runtime: r} + o := &objectGoReflect{ + baseObject: baseObject{ + val: obj, + }, + origValue: origValue, + fieldsValue: value, + } + obj.self = o + o.init() + return obj +} + +func (r *Runtime) wrapReflectFunc(value reflect.Value) func(FunctionCall) Value { + return func(call FunctionCall) Value { + typ := value.Type() + nargs := typ.NumIn() + var in []reflect.Value + + if l := len(call.Arguments); l < nargs { + // fill missing arguments with zero values + n := nargs + if typ.IsVariadic() { + n-- + } + in = make([]reflect.Value, n) + for i := l; i < n; i++ { + in[i] = reflect.Zero(typ.In(i)) + } + } else { + if l > nargs && !typ.IsVariadic() { + l = nargs + } + in = make([]reflect.Value, l) + } + + for i, a := range call.Arguments { + 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 if n > nargs-1 { // ignore extra arguments + break + } else { + t = typ.In(n) + } + + v := reflect.New(t).Elem() + err := r.toReflectValue(a, v, &objectExportCtx{}) + if err != nil { + panic(r.NewTypeError("could not convert function call parameter %d: %v", i, err)) + } + in[i] = v + } + + out := value.Call(in) + if len(out) == 0 { + return _undefined + } + + if last := out[len(out)-1]; last.Type() == reflectTypeError { + if !last.IsNil() { + err := last.Interface().(error) + if _, ok := err.(*Exception); ok { + panic(err) + } + if isUncatchableException(err) { + panic(err) + } + panic(r.NewGoError(err)) + } + out = out[:len(out)-1] + } + + switch len(out) { + case 0: + return _undefined + case 1: + return r.ToValue(out[0].Interface()) + default: + s := make([]any, len(out)) + for i, v := range out { + s[i] = v.Interface() + } + + return r.ToValue(s) + } + } +} + +func (r *Runtime) toReflectValue(v Value, dst reflect.Value, ctx *objectExportCtx) error { + typ := dst.Type() + + if typ == typeValue { + dst.Set(reflect.ValueOf(v)) + return nil + } + + if typ == typeObject { + if obj, ok := v.(*Object); ok { + dst.Set(reflect.ValueOf(obj)) + return nil + } + } + + if typ == typeCallable { + if fn, ok := AssertFunction(v); ok { + dst.Set(reflect.ValueOf(fn)) + return nil + } + } + + et := v.ExportType() + if et == nil || et == reflectTypeNil { + dst.Set(reflect.Zero(typ)) + return nil + } + + kind := typ.Kind() + for i := 0; ; i++ { + if et.AssignableTo(typ) { + ev := reflect.ValueOf(exportValue(v, ctx)) + for ; i > 0; i-- { + ev = ev.Elem() + } + dst.Set(ev) + return nil + } + expKind := et.Kind() + if expKind == kind && et.ConvertibleTo(typ) || expKind == reflect.String && typ == typeBytes { + ev := reflect.ValueOf(exportValue(v, ctx)) + for ; i > 0; i-- { + ev = ev.Elem() + } + dst.Set(ev.Convert(typ)) + return nil + } + if expKind == reflect.Ptr { + et = et.Elem() + } else { + break + } + } + + if typ == typeTime { + if obj, ok := v.(*Object); ok { + if d, ok := obj.self.(*dateObject); ok { + dst.Set(reflect.ValueOf(d.time())) + return nil + } + } + if et.Kind() == reflect.String { + tme, ok := dateParse(v.String()) + if !ok { + return fmt.Errorf("could not convert string %v to %v", v, typ) + } + dst.Set(reflect.ValueOf(tme)) + return nil + } + } + + switch kind { + case reflect.String: + dst.Set(reflect.ValueOf(v.String()).Convert(typ)) + return nil + case reflect.Bool: + dst.Set(reflect.ValueOf(v.ToBoolean()).Convert(typ)) + return nil + case reflect.Int: + dst.Set(reflect.ValueOf(toInt(v)).Convert(typ)) + return nil + case reflect.Int64: + dst.Set(reflect.ValueOf(toInt64(v)).Convert(typ)) + return nil + case reflect.Int32: + dst.Set(reflect.ValueOf(toInt32(v)).Convert(typ)) + return nil + case reflect.Int16: + dst.Set(reflect.ValueOf(toInt16(v)).Convert(typ)) + return nil + case reflect.Int8: + dst.Set(reflect.ValueOf(toInt8(v)).Convert(typ)) + return nil + case reflect.Uint: + dst.Set(reflect.ValueOf(toUint(v)).Convert(typ)) + return nil + case reflect.Uint64: + dst.Set(reflect.ValueOf(toUint64(v)).Convert(typ)) + return nil + case reflect.Uint32: + dst.Set(reflect.ValueOf(toUint32(v)).Convert(typ)) + return nil + case reflect.Uint16: + dst.Set(reflect.ValueOf(toUint16(v)).Convert(typ)) + return nil + case reflect.Uint8: + dst.Set(reflect.ValueOf(toUint8(v)).Convert(typ)) + return nil + case reflect.Float64: + dst.Set(reflect.ValueOf(v.ToFloat()).Convert(typ)) + return nil + case reflect.Float32: + dst.Set(reflect.ValueOf(toFloat32(v)).Convert(typ)) + return nil + case reflect.Slice, reflect.Array: + if o, ok := v.(*Object); ok { + if v, exists := ctx.getTyped(o, typ); exists { + dst.Set(reflect.ValueOf(v)) + return nil + } + return o.self.exportToArrayOrSlice(dst, typ, ctx) + } + case reflect.Map: + if o, ok := v.(*Object); ok { + if v, exists := ctx.getTyped(o, typ); exists { + dst.Set(reflect.ValueOf(v)) + return nil + } + return o.self.exportToMap(dst, typ, ctx) + } + case reflect.Struct: + if o, ok := v.(*Object); ok { + t := reflect.PtrTo(typ) + if v, exists := ctx.getTyped(o, t); exists { + dst.Set(reflect.ValueOf(v).Elem()) + return nil + } + s := dst + ctx.putTyped(o, t, s.Addr().Interface()) + for i := 0; i < typ.NumField(); i++ { + field := typ.Field(i) + if ast.IsExported(field.Name) { + name := field.Name + if r.fieldNameMapper != nil { + name = r.fieldNameMapper.FieldName(typ, field) + } + var v Value + if field.Anonymous { + v = o + } else { + v = o.self.getStr(unistring.NewFromString(name), nil) + } + + if v != nil { + err := r.toReflectValue(v, s.Field(i), ctx) + if err != nil { + return fmt.Errorf("could not convert struct value %v to %v for field %s: %w", v, field.Type, field.Name, err) + } + } + } + } + return nil + } + case reflect.Func: + if fn, ok := AssertFunction(v); ok { + dst.Set(reflect.MakeFunc(typ, r.wrapJSFunc(fn, typ))) + return nil + } + case reflect.Ptr: + if o, ok := v.(*Object); ok { + if v, exists := ctx.getTyped(o, typ); exists { + dst.Set(reflect.ValueOf(v)) + return nil + } + } + if dst.IsNil() { + dst.Set(reflect.New(typ.Elem())) + } + return r.toReflectValue(v, dst.Elem(), ctx) + } + + return fmt.Errorf("could not convert %v to %v", v, typ) +} + +func (r *Runtime) wrapJSFunc(fn Callable, typ reflect.Type) func(args []reflect.Value) (results []reflect.Value) { + return func(args []reflect.Value) (results []reflect.Value) { + jsArgs := make([]Value, len(args)) + for i, arg := range args { + jsArgs[i] = r.ToValue(arg.Interface()) + } + + numOut := typ.NumOut() + results = make([]reflect.Value, numOut) + res, err := fn(_undefined, jsArgs...) + if err == nil { + if numOut > 0 { + v := reflect.New(typ.Out(0)).Elem() + err = r.toReflectValue(res, v, &objectExportCtx{}) + if err == nil { + results[0] = v + } + } + } + + if err != nil { + if numOut > 0 && typ.Out(numOut-1) == reflectTypeError { + if ex, ok := err.(*Exception); ok { + if exo, ok := ex.val.(*Object); ok { + if v := exo.self.getStr("value", nil); v != nil { + if v.ExportType().AssignableTo(reflectTypeError) { + err = v.Export().(error) + } + } + } + } + results[numOut-1] = reflect.ValueOf(err).Convert(typ.Out(numOut - 1)) + } else { + panic(err) + } + } + + for i, v := range results { + if !v.IsValid() { + results[i] = reflect.Zero(typ.Out(i)) + } + } + + return + } +} + +// ExportTo converts a JavaScript value into the specified Go value. The second parameter must be a non-nil pointer. +// Returns error if conversion is not possible. +// +// Notes on specific cases: +// +// # Empty interface +// +// Exporting to an any results in a value of the same type as Value.Export() would produce. +// +// # Numeric types +// +// Exporting to numeric types uses the standard ECMAScript conversion operations, same as used when assigning +// values to non-clamped typed array items, e.g. https://262.ecma-international.org/#sec-toint32. +// +// # Functions +// +// Exporting to a 'func' creates a strictly typed 'gateway' into an ES function which can be called from Go. +// The arguments are converted into ES values using Runtime.ToValue(). If the func has no return values, +// the return value is ignored. If the func has exactly one return value, it is converted to the appropriate +// type using ExportTo(). If the last return value is 'error', exceptions are caught and returned as *Exception +// (instances of GoError are unwrapped, i.e. their 'value' is returned instead). In all other cases exceptions +// result in a panic. Any extra return values are zeroed. +// +// 'this' value will always be set to 'undefined'. +// +// For a more low-level mechanism see AssertFunction(). +// +// # Map types +// +// An ES Map can be exported into a Go map type. If any exported key value is non-hashable, the operation panics +// (as reflect.Value.SetMapIndex() would). Symbol.iterator is ignored. +// +// Exporting an ES Set into a map type results in the map being populated with (element) -> (zero value) key/value +// pairs. If any value is non-hashable, the operation panics (as reflect.Value.SetMapIndex() would). +// Symbol.iterator is ignored. +// +// Any other Object populates the map with own enumerable non-symbol properties. +// +// # Slice types +// +// Exporting an ES Set into a slice type results in its elements being exported. +// +// Exporting any Object that implements the iterable protocol (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol) +// into a slice type results in the slice being populated with the results of the iteration. +// +// Array is treated as iterable (i.e. overwriting Symbol.iterator affects the result). +// +// If an object has a 'length' property and is not a function it is treated as array-like. The resulting slice +// will contain obj[0], ... obj[length-1]. +// +// ArrayBuffer and ArrayBuffer-backed types (i.e. typed arrays and DataView) can be exported into []byte. The result +// is backed by the original data, no copy is performed. +// +// For any other Object an error is returned. +// +// # Array types +// +// Anything that can be exported to a slice type can also be exported to an array type, as long as the lengths +// match. If they do not, an error is returned. +// +// # Proxy +// +// Proxy objects are treated the same way as if they were accessed from ES code in regard to their properties +// (such as 'length' or [Symbol.iterator]). This means exporting them to slice types works, however +// exporting a proxied Map into a map type does not produce its contents, because the Proxy is not recognised +// as a Map. Same applies to a proxied Set. +func (r *Runtime) ExportTo(v Value, target any) error { + tval := reflect.ValueOf(target) + if tval.Kind() != reflect.Ptr || tval.IsNil() { + return errors.New("target must be a non-nil pointer") + } + return r.toReflectValue(v, tval.Elem(), &objectExportCtx{}) +} + +// GlobalObject returns the global object. +func (r *Runtime) GlobalObject() *Object { + return r.globalObject +} + +// Set the specified variable in the global context. +// Equivalent to running "name = value" in non-strict mode. +// The value is first converted using ToValue(). +// Note, this is not the same as GlobalObject().Set(name, value), +// because if a global lexical binding (let or const) exists, it is set instead. +func (r *Runtime) Set(name string, value any) error { + return r.try(func() { + name := unistring.NewFromString(name) + v := r.ToValue(value) + if ref := r.global.stash.getRefByName(name, false); ref != nil { + ref.set(v) + } else { + r.globalObject.self.setOwnStr(name, v, true) + } + }) +} + +// Get the specified variable in the global context. +// Equivalent to dereferencing a variable by name in non-strict mode. If variable is not defined returns nil. +// Note, this is not the same as GlobalObject().Get(name), +// because if a global lexical binding (let or const) exists, it is used instead. +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (r *Runtime) Get(name string) Value { + n := unistring.NewFromString(name) + if v, exists := r.global.stash.getByName(n); exists { + return v + } else { + return r.globalObject.self.getStr(n, nil) + } +} + +// SetRandSource sets random source for this Runtime. If not called, the default math/rand is used. +func (r *Runtime) SetRandSource(source RandSource) { + r.rand = source +} + +// SetTimeSource sets the current time source for this Runtime. +// If not called, the default time.Now() is used. +func (r *Runtime) SetTimeSource(now Now) { + r.now = now +} + +// SetParserOptions sets parser options to be used by RunString, RunScript and eval() within the code. +func (r *Runtime) SetParserOptions(opts ...parser.Option) { + r.parserOptions = opts +} + +// SetMaxCallStackSize sets the maximum function call depth. When exceeded, a *StackOverflowError is thrown and +// returned by RunProgram or by a Callable call. This is useful to prevent memory exhaustion caused by an +// infinite recursion. The default value is math.MaxInt32. +// This method (as the rest of the Set* methods) is not safe for concurrent use and may only be called +// from the vm goroutine or when the vm is not running. +func (r *Runtime) SetMaxCallStackSize(size int) { + r.vm.maxCallStackSize = size +} + +// New is an equivalent of the 'new' operator allowing to call it directly from Go. +func (r *Runtime) New(construct Value, args ...Value) (o *Object, err error) { + err = r.try(func() { + o = r.builtin_new(r.toObject(construct), args) + }) + return +} + +// Callable represents a JavaScript function that can be called from Go. +type Callable func(this Value, args ...Value) (Value, error) + +// AssertFunction checks if the Value is a function and returns a Callable. +// Note, for classes this returns a callable and a 'true', however calling it will always result in a TypeError. +// For classes use AssertConstructor(). +func AssertFunction(v Value) (Callable, bool) { + if obj, ok := v.(*Object); ok { + if f, ok := obj.self.assertCallable(); ok { + return func(this Value, args ...Value) (ret Value, err error) { + err = obj.runtime.runWrapped(func() { + ret = f(FunctionCall{ + This: this, + Arguments: args, + }) + }) + return + }, true + } + } + return nil, false +} + +// Constructor is a type that can be used to call constructors. The first argument (newTarget) can be nil +// which sets it to the constructor function itself. +type Constructor func(newTarget *Object, args ...Value) (*Object, error) + +// AssertConstructor checks if the Value is a constructor and returns a Constructor. +func AssertConstructor(v Value) (Constructor, bool) { + if obj, ok := v.(*Object); ok { + if ctor := obj.self.assertConstructor(); ctor != nil { + return func(newTarget *Object, args ...Value) (ret *Object, err error) { + err = obj.runtime.runWrapped(func() { + ret = ctor(args, newTarget) + }) + return + }, true + } + } + return nil, false +} + +func (r *Runtime) runWrapped(f func()) (err error) { + defer func() { + if x := recover(); x != nil { + if ex := asUncatchableException(x); ex != nil { + err = ex + if len(r.vm.callStack) == 0 { + r.leaveAbrupt() + } + } else { + fmt.Println("PANIC", x) + // panic(x) + return + } + } + }() + ex := r.vm.try(f) + if ex != nil { + err = ex + } + if len(r.vm.callStack) == 0 { + r.leave() + } else { + r.vm.clearStack() + } + return +} + +// IsUndefined returns true if the supplied Value is undefined. Note, it checks against the real undefined, not +// against the global object's 'undefined' property. +func IsUndefined(v Value) bool { + return v == _undefined +} + +// IsNull returns true if the supplied Value is null. +func IsNull(v Value) bool { + return v == _null +} + +// IsNaN returns true if the supplied value is NaN. +func IsNaN(v Value) bool { + f, ok := v.(valueFloat) + return ok && math.IsNaN(float64(f)) +} + +// IsInfinity returns true if the supplied is (+/-)Infinity +func IsInfinity(v Value) bool { + return v == _positiveInf || v == _negativeInf +} + +// Undefined returns JS undefined value. Note if global 'undefined' property is changed this still returns the original value. +func Undefined() Value { + return _undefined +} + +// Null returns JS null value. +func Null() Value { + return _null +} + +// NaN returns a JS NaN value. +func NaN() Value { + return _NaN +} + +// PositiveInf returns a JS +Inf value. +func PositiveInf() Value { + return _positiveInf +} + +// NegativeInf returns a JS -Inf value. +func NegativeInf() Value { + return _negativeInf +} + +func tryFunc(f func()) (ret any) { + defer func() { + ret = recover() + }() + + f() + return +} + +// Try runs a given function catching and returning any JS exception. Use this method to run any code +// that may throw exceptions (such as Object.Get, Object.String, Object.ToInteger, Object.Export, Runtime.Get, Runtime.InstanceOf, etc.) +// outside the Runtime execution context (i.e. when calling directly from Go, not from a JS function implemented in Go). +func (r *Runtime) Try(f func()) *Exception { + return r.vm.try(f) +} + +func (r *Runtime) try(f func()) error { + if ex := r.vm.try(f); ex != nil { + return ex + } + return nil +} + +func (r *Runtime) toObject(v Value, args ...any) *Object { + if obj, ok := v.(*Object); ok { + return obj + } + if len(args) > 0 { + panic(r.NewTypeError(args...)) + } else { + var s string + if v == nil { + s = "undefined" + } else { + s = v.String() + } + panic(r.NewTypeError("Value is not an object: %s", s)) + } +} + +func (r *Runtime) speciesConstructor(o, defaultConstructor *Object) func(args []Value, newTarget *Object) *Object { + c := o.self.getStr("constructor", nil) + if c != nil && c != _undefined { + c = r.toObject(c).self.getSym(SymSpecies, nil) + } + if c == nil || c == _undefined || c == _null { + c = defaultConstructor + } + return r.toConstructor(c) +} + +func (r *Runtime) speciesConstructorObj(o, defaultConstructor *Object) *Object { + c := o.self.getStr("constructor", nil) + if c != nil && c != _undefined { + c = r.toObject(c).self.getSym(SymSpecies, nil) + } + if c == nil || c == _undefined || c == _null { + return defaultConstructor + } + obj := r.toObject(c) + if obj.self.assertConstructor() == nil { + panic(r.NewTypeError("Value is not a constructor")) + } + return obj +} + +func (r *Runtime) returnThis(call FunctionCall) Value { + return call.This +} + +func createDataProperty(o *Object, p Value, v Value) { + o.defineOwnProperty(p, PropertyDescriptor{ + Writable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Value: v, + }, false) +} + +func createDataPropertyOrThrow(o *Object, p Value, v Value) { + o.defineOwnProperty(p, PropertyDescriptor{ + Writable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Value: v, + }, true) +} + +func toPropertyKey(key Value) Value { + return key.ToString() +} + +func (r *Runtime) getVStr(v Value, p unistring.String) Value { + o := v.ToObject(r) + return o.self.getStr(p, v) +} + +func (r *Runtime) getV(v Value, p Value) Value { + o := v.ToObject(r) + return o.get(p, v) +} + +type iteratorRecord struct { + iterator *Object + next func(FunctionCall) Value +} + +func (r *Runtime) getIterator(obj Value, method func(FunctionCall) Value) *iteratorRecord { + if method == nil { + method = toMethod(r.getV(obj, SymIterator)) + if method == nil { + panic(r.NewTypeError("object is not iterable")) + } + } + + iter := r.toObject(method(FunctionCall{ + This: obj, + })) + + var next func(FunctionCall) Value + + if obj, ok := iter.self.getStr("next", nil).(*Object); ok { + if call, ok := obj.self.assertCallable(); ok { + next = call + } + } + + return &iteratorRecord{ + iterator: iter, + next: next, + } +} + +func iteratorComplete(iterResult *Object) bool { + return nilSafe(iterResult.self.getStr("done", nil)).ToBoolean() +} + +func iteratorValue(iterResult *Object) Value { + return nilSafe(iterResult.self.getStr("value", nil)) +} + +func (ir *iteratorRecord) iterate(step func(Value)) { + r := ir.iterator.runtime + for { + if ir.next == nil { + panic(r.NewTypeError("iterator.next is missing or not a function")) + } + res := r.toObject(ir.next(FunctionCall{This: ir.iterator})) + if iteratorComplete(res) { + break + } + value := iteratorValue(res) + ret := tryFunc(func() { + step(value) + }) + if ret != nil { + _ = tryFunc(func() { + ir.returnIter() + }) + panic(ret) + } + } +} + +func (ir *iteratorRecord) step() (value Value, ex *Exception) { + r := ir.iterator.runtime + ex = r.vm.try(func() { + res := r.toObject(ir.next(FunctionCall{This: ir.iterator})) + done := iteratorComplete(res) + if !done { + value = iteratorValue(res) + } else { + ir.close() + } + }) + return +} + +func (ir *iteratorRecord) returnIter() { + if ir.iterator == nil { + return + } + retMethod := toMethod(ir.iterator.self.getStr("return", nil)) + if retMethod != nil { + ir.iterator.runtime.toObject(retMethod(FunctionCall{This: ir.iterator})) + } + ir.iterator = nil + ir.next = nil +} + +func (ir *iteratorRecord) close() { + ir.iterator = nil + ir.next = nil +} + +// ForOf is a Go equivalent of for-of loop. The function panics if an exception is thrown at any point +// while iterating, including if the supplied value is not iterable +// (https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#the_iterable_protocol). +// When using outside of Runtime.Run (i.e. when calling directly from Go code, not from a JS function implemented +// in Go) it must be enclosed in Try. See the example. +func (r *Runtime) ForOf(iterable Value, step func(curValue Value) (continueIteration bool)) { + iter := r.getIterator(iterable, nil) + for { + value, ex := iter.step() + if ex != nil { + panic(ex) + } + if value != nil { + var continueIteration bool + ex := r.vm.try(func() { + continueIteration = step(value) + }) + if ex != nil { + iter.returnIter() + panic(ex) + } + if !continueIteration { + iter.returnIter() + break + } + } else { + break + } + } +} + +func (r *Runtime) createIterResultObject(value Value, done bool) Value { + o := r.NewObject() + o.self.setOwnStr("value", value, false) + o.self.setOwnStr("done", r.toBoolean(done), false) + return o +} + +func (r *Runtime) getHash() *maphash.Hash { + if r.hash == nil { + r.hash = &maphash.Hash{} + } + return r.hash +} + +// called when the top level function returns normally (i.e. control is passed outside the Runtime). +func (r *Runtime) leave() { + var jobs []func() + for len(r.jobQueue) > 0 { + jobs, r.jobQueue = r.jobQueue, jobs[:0] + for _, job := range jobs { + job() + } + } + r.jobQueue = nil + r.vm.stack = nil +} + +// called when the top level function returns (i.e. control is passed outside the Runtime) but it was due to an interrupt +func (r *Runtime) leaveAbrupt() { + r.jobQueue = nil + r.ClearInterrupt() +} + +func nilSafe(v Value) Value { + if v != nil { + return v + } + return _undefined +} + +func isArray(object *Object) bool { + self := object.self + if proxy, ok := self.(*proxyObject); ok { + if proxy.target == nil { + panic(typeError("Cannot perform 'IsArray' on a proxy that has been revoked")) + } + return isArray(proxy.target) + } + switch self.className() { + case classArray: + return true + default: + return false + } +} + +func isRegexp(v Value) bool { + if o, ok := v.(*Object); ok { + matcher := o.self.getSym(SymMatch, nil) + if matcher != nil && matcher != _undefined { + return matcher.ToBoolean() + } + _, reg := o.self.(*regexpObject) + return reg + } + return false +} + +func limitCallArgs(call FunctionCall, n int) FunctionCall { + if len(call.Arguments) > n { + return FunctionCall{This: call.This, Arguments: call.Arguments[:n]} + } else { + return call + } +} + +func shrinkCap(newSize, oldCap int) int { + if oldCap > 8 { + if cap := oldCap / 2; cap >= newSize { + return cap + } + } + return oldCap +} + +func growCap(newSize, oldSize, oldCap int) int { + // Use the same algorithm as in runtime.growSlice + doublecap := oldCap + oldCap + if newSize > doublecap { + return newSize + } else { + if oldSize < 1024 { + return doublecap + } else { + cap := oldCap + // Check 0 < cap to detect overflow + // and prevent an infinite loop. + for 0 < cap && cap < newSize { + cap += cap / 4 + } + // Return the requested cap when + // the calculation overflowed. + if cap <= 0 { + return newSize + } + return cap + } + } +} + +func (r *Runtime) genId() (ret uint64) { + if r.hash == nil { + h := r.getHash() + r.idSeq = h.Sum64() + } + if r.idSeq == 0 { + r.idSeq = 1 + } + ret = r.idSeq + r.idSeq++ + return +} + +func (r *Runtime) setGlobal(name unistring.String, v Value, strict bool) { + if ref := r.global.stash.getRefByName(name, strict); ref != nil { + ref.set(v) + } else { + o := r.globalObject.self + if strict { + if o.hasOwnPropertyStr(name) { + o.setOwnStr(name, v, true) + } else { + r.throwReferenceError(name) + } + } else { + o.setOwnStr(name, v, false) + } + } +} + +func (r *Runtime) trackPromiseRejection(p *Promise, operation PromiseRejectionOperation) { + if r.promiseRejectionTracker != nil { + r.promiseRejectionTracker(p, operation) + } +} + +func (r *Runtime) callJobCallback(job *jobCallback, this Value, args ...Value) Value { + return job.callback(FunctionCall{This: this, Arguments: args}) +} + +func (r *Runtime) invoke(v Value, p unistring.String, args ...Value) Value { + o := v.ToObject(r) + return r.toCallable(o.self.getStr(p, nil))(FunctionCall{This: v, Arguments: args}) +} + +func (r *Runtime) iterableToList(iterable Value, method func(FunctionCall) Value) []Value { + iter := r.getIterator(iterable, method) + var values []Value + iter.iterate(func(item Value) { + values = append(values, item) + }) + return values +} + +func (r *Runtime) putSpeciesReturnThis(o objectImpl) { + o._putSym(SymSpecies, &valueProperty{ + getterFunc: r.newNativeFunc(r.returnThis, "get [Symbol.species]", 0), + accessor: true, + configurable: true, + }) +} + +func strToArrayIdx(s unistring.String) uint32 { + if s == "" { + return math.MaxUint32 + } + l := len(s) + if s[0] == '0' { + if l == 1 { + return 0 + } + return math.MaxUint32 + } + var n uint32 + if l < 10 { + // guaranteed not to overflow + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return math.MaxUint32 + } + n = n*10 + uint32(c-'0') + } + return n + } + if l > 10 { + // guaranteed to overflow + return math.MaxUint32 + } + c9 := s[9] + if c9 < '0' || c9 > '9' { + return math.MaxUint32 + } + for i := 0; i < 9; i++ { + c := s[i] + if c < '0' || c > '9' { + return math.MaxUint32 + } + n = n*10 + uint32(c-'0') + } + if n >= math.MaxUint32/10+1 { + return math.MaxUint32 + } + n *= 10 + n1 := n + uint32(c9-'0') + if n1 < n { + return math.MaxUint32 + } + + return n1 +} + +func strToInt32(s unistring.String) (int32, bool) { + if s == "" { + return -1, false + } + neg := s[0] == '-' + if neg { + s = s[1:] + } + l := len(s) + if s[0] == '0' { + if l == 1 { + return 0, !neg + } + return -1, false + } + var n uint32 + if l < 10 { + // guaranteed not to overflow + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint32(c-'0') + } + } else if l > 10 { + // guaranteed to overflow + return -1, false + } else { + c9 := s[9] + if c9 >= '0' { + if !neg && c9 > '7' || c9 > '8' { + // guaranteed to overflow + return -1, false + } + for i := 0; i < 9; i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint32(c-'0') + } + if n >= math.MaxInt32/10+1 { + // valid number, but it overflows integer + return 0, false + } + n = n*10 + uint32(c9-'0') + } else { + return -1, false + } + } + if neg { + return int32(-n), true + } + return int32(n), true +} + +func strToInt64(s unistring.String) (int64, bool) { + if s == "" { + return -1, false + } + neg := s[0] == '-' + if neg { + s = s[1:] + } + l := len(s) + if s[0] == '0' { + if l == 1 { + return 0, !neg + } + return -1, false + } + var n uint64 + if l < 19 { + // guaranteed not to overflow + for i := 0; i < len(s); i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint64(c-'0') + } + } else if l > 19 { + // guaranteed to overflow + return -1, false + } else { + c18 := s[18] + if c18 >= '0' { + if !neg && c18 > '7' || c18 > '8' { + // guaranteed to overflow + return -1, false + } + for i := 0; i < 18; i++ { + c := s[i] + if c < '0' || c > '9' { + return -1, false + } + n = n*10 + uint64(c-'0') + } + if n >= math.MaxInt64/10+1 { + // valid number, but it overflows integer + return 0, false + } + n = n*10 + uint64(c18-'0') + } else { + return -1, false + } + } + if neg { + return int64(-n), true + } + return int64(n), true +} + +func strToInt(s unistring.String) (int, bool) { + if bits.UintSize == 32 { + n, ok := strToInt32(s) + return int(n), ok + } + n, ok := strToInt64(s) + return int(n), ok +} + +// Attempts to convert a string into a canonical integer. +// On success returns (number, true). +// If it was a canonical number, but not an integer returns (0, false). This includes -0 and overflows. +// In all other cases returns (-1, false). +// See https://262.ecma-international.org/#sec-canonicalnumericindexstring +func strToIntNum(s unistring.String) (int, bool) { + n, ok := strToInt64(s) + if n == 0 { + return 0, ok + } + if ok && n >= -maxInt && n <= maxInt { + if bits.UintSize == 32 { + if n > math.MaxInt32 || n < math.MinInt32 { + return 0, false + } + } + return int(n), true + } + str := stringValueFromRaw(s) + if str.ToNumber().toString().SameAs(str) { + return 0, false + } + return -1, false +} + +func strToGoIdx(s unistring.String) int { + if n, ok := strToInt(s); ok { + return n + } + return -1 +} + +func strToIdx64(s unistring.String) int64 { + if n, ok := strToInt64(s); ok { + return n + } + return -1 +} + +func assertCallable(v Value) (func(FunctionCall) Value, bool) { + if obj, ok := v.(*Object); ok { + return obj.self.assertCallable() + } + return nil, false +} + +// InstanceOf is an equivalent of "left instanceof right". +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (r *Runtime) InstanceOf(left Value, right *Object) (res bool) { + return instanceOfOperator(left, right) +} + +func (r *Runtime) methodProp(f func(FunctionCall) Value, name unistring.String, nArgs int) Value { + return valueProp(r.newNativeFunc(f, name, nArgs), true, false, true) +} + +func (r *Runtime) getPrototypeFromCtor(newTarget, defCtor, defProto *Object) *Object { + if newTarget == defCtor { + return defProto + } + proto := newTarget.self.getStr("prototype", nil) + if obj, ok := proto.(*Object); ok { + return obj + } + return defProto +} diff --git a/pkg/xscript/engine/runtime_test.go b/pkg/xscript/engine/runtime_test.go new file mode 100644 index 0000000..ad96de5 --- /dev/null +++ b/pkg/xscript/engine/runtime_test.go @@ -0,0 +1,3111 @@ +package engine + +import ( + "errors" + "fmt" + "math" + "reflect" + "runtime" + "strconv" + "strings" + "testing" + "time" + + "pandax/pkg/xscript/engine/parser" +) + +func TestGlobalObjectProto(t *testing.T) { + const SCRIPT = ` + this instanceof Object + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestUnicodeString(t *testing.T) { + const SCRIPT = ` + var s = "Тест"; + s.length === 4 && s[1] === "е"; + + ` + + testScript(SCRIPT, valueTrue, t) +} + +func Test2TierHierarchyProp(t *testing.T) { + const SCRIPT = ` + var a = {}; + Object.defineProperty(a, "test", { + value: 42, + writable: false, + enumerable: false, + configurable: true + }); + var b = Object.create(a); + var c = Object.create(b); + c.test = 43; + c.test === 42 && !b.hasOwnProperty("test"); + + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestConstStringIter(t *testing.T) { + const SCRIPT = ` + + var count = 0; + + for (var i in "1234") { + for (var j in "1234567") { + count++ + } + } + + count; + ` + + testScript(SCRIPT, intToValue(28), t) +} + +func TestUnicodeConcat(t *testing.T) { + const SCRIPT = ` + + var s = "тест"; + var s1 = "test"; + var s2 = "абвгд"; + + s.concat(s1) === "тестtest" && s.concat(s1, s2) === "тестtestабвгд" && s1.concat(s, s2) === "testтестабвгд" + && s.concat(s2) === "тестабвгд"; + + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestIndexOf(t *testing.T) { + const SCRIPT = ` + + "abc".indexOf("", 4) + ` + + testScript(SCRIPT, intToValue(3), t) +} + +func TestUnicodeIndexOf(t *testing.T) { + const SCRIPT = ` + "абвгд".indexOf("вг", 1) === 2 && '中国'.indexOf('国') === 1 + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestLastIndexOf(t *testing.T) { + const SCRIPT = ` + + "abcabab".lastIndexOf("ab", 3) + ` + + testScript(SCRIPT, intToValue(3), t) +} + +func TestUnicodeLastIndexOf(t *testing.T) { + const SCRIPT = ` + "абвабаб".lastIndexOf("аб", 3) + ` + + testScript(SCRIPT, intToValue(3), t) +} + +func TestUnicodeLastIndexOf1(t *testing.T) { + const SCRIPT = ` + "abꞐcde".lastIndexOf("cd"); + ` + + testScript(SCRIPT, intToValue(3), t) +} + +func TestNumber(t *testing.T) { + const SCRIPT = ` + (new Number(100111122133144155)).toString() + ` + + testScript(SCRIPT, asciiString("100111122133144160"), t) +} + +func TestFractionalNumberToStringRadix(t *testing.T) { + const SCRIPT = ` + (new Number(123.456)).toString(36) + ` + + testScript(SCRIPT, asciiString("3f.gez4w97ry"), t) +} + +func TestNumberFormatRounding(t *testing.T) { + const SCRIPT = ` + assert.sameValue((123.456).toExponential(undefined), "1.23456e+2", "undefined"); + assert.sameValue((0.000001).toPrecision(2), "0.0000010") + assert.sameValue((-7).toPrecision(1), "-7"); + assert.sameValue((-42).toPrecision(1), "-4e+1"); + assert.sameValue((0.000001).toPrecision(1), "0.000001"); + assert.sameValue((123.456).toPrecision(1), "1e+2", "1"); + assert.sameValue((123.456).toPrecision(2), "1.2e+2", "2"); + + var n = new Number("0.000000000000000000001"); // 1e-21 + assert.sameValue((n).toPrecision(1), "1e-21"); + assert.sameValue((25).toExponential(0), "3e+1"); + assert.sameValue((-25).toExponential(0), "-3e+1"); + assert.sameValue((12345).toExponential(3), "1.235e+4"); + assert.sameValue((25.5).toFixed(0), "26"); + assert.sameValue((-25.5).toFixed(0), "-26"); + assert.sameValue((99.9).toFixed(0), "100"); + assert.sameValue((99.99).toFixed(1), "100.0"); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestBinOctalNumbers(t *testing.T) { + const SCRIPT = ` + 0b111; + ` + + testScript(SCRIPT, valueInt(7), t) +} + +func TestSetFunc(t *testing.T) { + const SCRIPT = ` + sum(40, 2); + ` + r := New() + err := r.Set("sum", func(call FunctionCall) Value { + return r.ToValue(call.Argument(0).ToInteger() + call.Argument(1).ToInteger()) + }) + if err != nil { + t.Fatal(err) + } + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if i := v.ToInteger(); i != 42 { + t.Fatalf("Expected 42, got: %d", i) + } +} + +func ExampleRuntime_Set_lexical() { + r := New() + _, err := r.RunString("let x") + if err != nil { + panic(err) + } + err = r.Set("x", 1) + if err != nil { + panic(err) + } + fmt.Print(r.Get("x"), r.GlobalObject().Get("x")) + // Output: 1 +} + +func TestRecursiveRun(t *testing.T) { + // Make sure that a recursive call to Run*() correctly sets the environment and no stash or stack + // corruptions occur. + vm := New() + vm.Set("f", func() (Value, error) { + return vm.RunString("let x = 1; { let z = 100, z1 = 200, z2 = 300, z3 = 400; x = x + z3} x;") + }) + res, err := vm.RunString(` + function f1() { + let x = 2; + eval(''); + { + let y = 3; + let res = f(); + if (x !== 2) { // check for stash corruption + throw new Error("x="+x); + } + if (y !== 3) { // check for stack corruption + throw new Error("y="+y); + } + return res; + } + }; + f1(); + `) + if err != nil { + t.Fatal(err) + } + if !res.SameAs(valueInt(401)) { + t.Fatal(res) + } +} + +func TestRecursiveRunWithNArgs(t *testing.T) { + vm := New() + vm.Set("f", func() (Value, error) { + return vm.RunString(` + { + let a = 0; + let b = 1; + a = 2; // this used to to corrupt b, because its location on the stack was off by vm.args (1 in our case) + b; + } + `) + }) + _, err := vm.RunString(` + (function(arg) { // need an ES function call with an argument to set vm.args + let res = f(); + if (res !== 1) { + throw new Error(res); + } + })(123); + `) + if err != nil { + t.Fatal(err) + } +} + +func TestRecursiveRunCallee(t *testing.T) { + // Make sure that a recursive call to Run*() correctly sets the callee (i.e. stack[sb-1]) + vm := New() + vm.Set("f", func() (Value, error) { + return vm.RunString("this; (() => 1)()") + }) + res, err := vm.RunString(` + f(123, 123); + `) + if err != nil { + t.Fatal(err) + } + if !res.SameAs(valueInt(1)) { + t.Fatal(res) + } +} + +func TestObjectGetSet(t *testing.T) { + const SCRIPT = ` + input.test++; + input; + ` + r := New() + o := r.NewObject() + o.Set("test", 42) + r.Set("input", o) + + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + if o1, ok := v.(*Object); ok { + if v1 := o1.Get("test"); v1.Export() != int64(43) { + t.Fatalf("Unexpected test value: %v (%T)", v1, v1.Export()) + } + } +} + +func TestThrowFromNativeFunc(t *testing.T) { + const SCRIPT = ` + var thrown; + try { + f(); + } catch (e) { + thrown = e; + } + thrown; + ` + r := New() + r.Set("f", func(call FunctionCall) Value { + panic(r.ToValue("testError")) + }) + + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.Equals(asciiString("testError")) { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestSetGoFunc(t *testing.T) { + const SCRIPT = ` + f(40, 2) + ` + r := New() + r.Set("f", func(a, b int) int { + return a + b + }) + + v, err := r.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if v.ToInteger() != 42 { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestSetFuncVariadic(t *testing.T) { + vm := New() + vm.Set("f", func(s string, g ...Value) { + something := g[0].ToObject(vm).Get(s).ToInteger() + if something != 5 { + t.Fatal() + } + }) + _, err := vm.RunString(` + f("something", {something: 5}) + `) + if err != nil { + t.Fatal(err) + } +} + +func TestSetFuncVariadicFuncArg(t *testing.T) { + vm := New() + vm.Set("f", func(s string, g ...Value) { + if f, ok := AssertFunction(g[0]); ok { + v, err := f(nil) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatal(v) + } + } + }) + _, err := vm.RunString(` + f("something", () => true) + `) + if err != nil { + t.Fatal(err) + } +} + +func TestArgsKeys(t *testing.T) { + const SCRIPT = ` + function testArgs2(x, y, z) { + // Properties of the arguments object are enumerable. + return Object.keys(arguments); + } + + testArgs2(1,2).length + ` + + testScript(SCRIPT, intToValue(2), t) +} + +func TestIPowOverflow(t *testing.T) { + const SCRIPT = ` + assert.sameValue(Math.pow(65536, 6), 7.922816251426434e+28); + assert.sameValue(Math.pow(10, 19), 1e19); + assert.sameValue(Math.pow(2097151, 3), 9223358842721534000); + assert.sameValue(Math.pow(2097152, 3), 9223372036854776000); + assert.sameValue(Math.pow(-2097151, 3), -9223358842721534000); + assert.sameValue(Math.pow(-2097152, 3), -9223372036854776000); + assert.sameValue(Math.pow(9007199254740992, 0), 1); + assert.sameValue(Math.pow(-9007199254740992, 0), 1); + assert.sameValue(Math.pow(0, 0), 1); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestIPow(t *testing.T) { + if res := ipow(-9223372036854775808, 1); res != -9223372036854775808 { + t.Fatal(res) + } + + if res := ipow(9223372036854775807, 1); res != 9223372036854775807 { + t.Fatal(res) + } + + if res := ipow(-9223372036854775807, 1); res != -9223372036854775807 { + t.Fatal(res) + } + + if res := ipow(9223372036854775807, 0); res != 1 { + t.Fatal(res) + } + + if res := ipow(-9223372036854775807, 0); res != 1 { + t.Fatal(res) + } + + if res := ipow(-9223372036854775808, 0); res != 1 { + t.Fatal(res) + } +} + +func TestInterrupt(t *testing.T) { + const SCRIPT = ` + var i = 0; + for (;;) { + i++; + } + ` + + vm := New() + time.AfterFunc(200*time.Millisecond, func() { + vm.Interrupt("halt") + }) + + _, err := vm.RunString(SCRIPT) + if err == nil { + t.Fatal("Err is nil") + } +} + +func TestRuntime_ExportToNumbers(t *testing.T) { + vm := New() + t.Run("int8/no overflow", func(t *testing.T) { + var i8 int8 + err := vm.ExportTo(vm.ToValue(-123), &i8) + if err != nil { + t.Fatal(err) + } + if i8 != -123 { + t.Fatalf("i8: %d", i8) + } + }) + + t.Run("int8/overflow", func(t *testing.T) { + var i8 int8 + err := vm.ExportTo(vm.ToValue(333), &i8) + if err != nil { + t.Fatal(err) + } + if i8 != 77 { + t.Fatalf("i8: %d", i8) + } + }) + + t.Run("int64/uint64", func(t *testing.T) { + var ui64 uint64 + err := vm.ExportTo(vm.ToValue(-1), &ui64) + if err != nil { + t.Fatal(err) + } + if ui64 != math.MaxUint64 { + t.Fatalf("ui64: %d", ui64) + } + }) + + t.Run("int8/float", func(t *testing.T) { + var i8 int8 + err := vm.ExportTo(vm.ToValue(333.9234), &i8) + if err != nil { + t.Fatal(err) + } + if i8 != 77 { + t.Fatalf("i8: %d", i8) + } + }) + + t.Run("int8/object", func(t *testing.T) { + var i8 int8 + err := vm.ExportTo(vm.NewObject(), &i8) + if err != nil { + t.Fatal(err) + } + if i8 != 0 { + t.Fatalf("i8: %d", i8) + } + }) + + t.Run("int/object_cust_valueOf", func(t *testing.T) { + var i int + obj, err := vm.RunString(` + ({ + valueOf: function() { return 42; } + }) + `) + if err != nil { + t.Fatal(err) + } + err = vm.ExportTo(obj, &i) + if err != nil { + t.Fatal(err) + } + if i != 42 { + t.Fatalf("i: %d", i) + } + }) + + t.Run("float32/no_trunc", func(t *testing.T) { + var f float32 + err := vm.ExportTo(vm.ToValue(1.234567), &f) + if err != nil { + t.Fatal(err) + } + if f != 1.234567 { + t.Fatalf("f: %f", f) + } + }) + + t.Run("float32/trunc", func(t *testing.T) { + var f float32 + err := vm.ExportTo(vm.ToValue(1.234567890), &f) + if err != nil { + t.Fatal(err) + } + if f != float32(1.234567890) { + t.Fatalf("f: %f", f) + } + }) + + t.Run("float64", func(t *testing.T) { + var f float64 + err := vm.ExportTo(vm.ToValue(1.234567), &f) + if err != nil { + t.Fatal(err) + } + if f != 1.234567 { + t.Fatalf("f: %f", f) + } + }) + + t.Run("float32/object", func(t *testing.T) { + var f float32 + err := vm.ExportTo(vm.NewObject(), &f) + if err != nil { + t.Fatal(err) + } + if f == f { // expecting NaN + t.Fatalf("f: %f", f) + } + }) + + t.Run("float64/object", func(t *testing.T) { + var f float64 + err := vm.ExportTo(vm.NewObject(), &f) + if err != nil { + t.Fatal(err) + } + if f == f { // expecting NaN + t.Fatalf("f: %f", f) + } + }) + +} + +func TestRuntime_ExportToSlice(t *testing.T) { + const SCRIPT = ` + var a = [1, 2, 3]; + a; + ` + + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + var a []string + err = vm.ExportTo(v, &a) + if err != nil { + t.Fatal(err) + } + if l := len(a); l != 3 { + t.Fatalf("Unexpected len: %d", l) + } + if a[0] != "1" || a[1] != "2" || a[2] != "3" { + t.Fatalf("Unexpected value: %+v", a) + } +} + +func TestRuntime_ExportToMap(t *testing.T) { + const SCRIPT = ` + var m = { + "0": 1, + "1": 2, + "2": 3, + } + m; + ` + + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + var m map[int]string + err = vm.ExportTo(v, &m) + if err != nil { + t.Fatal(err) + } + if l := len(m); l != 3 { + t.Fatalf("Unexpected len: %d", l) + } + if m[0] != "1" || m[1] != "2" || m[2] != "3" { + t.Fatalf("Unexpected value: %+v", m) + } +} + +func TestRuntime_ExportToMap1(t *testing.T) { + const SCRIPT = ` + var m = { + "0": 1, + "1": 2, + "2": 3, + } + m; + ` + + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + var m map[string]string + err = vm.ExportTo(v, &m) + if err != nil { + t.Fatal(err) + } + if l := len(m); l != 3 { + t.Fatalf("Unexpected len: %d", l) + } + if m["0"] != "1" || m["1"] != "2" || m["2"] != "3" { + t.Fatalf("Unexpected value: %+v", m) + } +} + +func TestRuntime_ExportToStruct(t *testing.T) { + const SCRIPT = ` + var m = { + Test: 1, + } + m; + ` + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + var o testGoReflectMethod_O + err = vm.ExportTo(v, &o) + if err != nil { + t.Fatal(err) + } + + if o.Test != "1" { + t.Fatalf("Unexpected value: '%s'", o.Test) + } + +} + +func TestRuntime_ExportToStructPtr(t *testing.T) { + const SCRIPT = ` + var m = { + Test: 1, + } + m; + ` + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + var o *testGoReflectMethod_O + err = vm.ExportTo(v, &o) + if err != nil { + t.Fatal(err) + } + + if o.Test != "1" { + t.Fatalf("Unexpected value: '%s'", o.Test) + } + +} + +func TestRuntime_ExportToStructAnonymous(t *testing.T) { + type BaseTestStruct struct { + A int64 + B int64 + } + + type TestStruct struct { + BaseTestStruct + C string + } + + const SCRIPT = ` + var m = { + A: 1, + B: 2, + C: "testC" + } + m; + ` + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + test := &TestStruct{} + err = vm.ExportTo(v, test) + if err != nil { + t.Fatal(err) + } + + if test.A != 1 { + t.Fatalf("Unexpected value: '%d'", test.A) + } + if test.B != 2 { + t.Fatalf("Unexpected value: '%d'", test.B) + } + if test.C != "testC" { + t.Fatalf("Unexpected value: '%s'", test.C) + } + +} + +func TestRuntime_ExportToStructFromPtr(t *testing.T) { + vm := New() + v := vm.ToValue(&testGoReflectMethod_O{ + field: "5", + Test: "12", + }) + + var o testGoReflectMethod_O + err := vm.ExportTo(v, &o) + if err != nil { + t.Fatal(err) + } + + if o.Test != "12" { + t.Fatalf("Unexpected value: '%s'", o.Test) + } + if o.field != "5" { + t.Fatalf("Unexpected value for field: '%s'", o.field) + } +} + +func TestRuntime_ExportToStructWithPtrValues(t *testing.T) { + type BaseTestStruct struct { + A int64 + B *int64 + } + + type TestStruct2 struct { + E string + } + + type TestStruct struct { + BaseTestStruct + C *string + D *TestStruct2 + } + + const SCRIPT = ` + var m = { + A: 1, + B: 2, + C: "testC", + D: { + E: "testE", + } + } + m; + ` + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + test := &TestStruct{} + err = vm.ExportTo(v, test) + if err != nil { + t.Fatal(err) + } + + if test.A != 1 { + t.Fatalf("Unexpected value: '%d'", test.A) + } + if test.B == nil || *test.B != 2 { + t.Fatalf("Unexpected value: '%v'", test.B) + } + if test.C == nil || *test.C != "testC" { + t.Fatalf("Unexpected value: '%v'", test.C) + } + if test.D == nil || test.D.E != "testE" { + t.Fatalf("Unexpected value: '%s'", test.D.E) + } + +} + +type loadAverage [3]float64 + +func (la *loadAverage) UnmarshalValue(v Value, vm *Runtime) error { + obj := v.(*Object) + forEach, ok := AssertFunction(obj.Get("forEach")) + if !ok { + return errors.New("value is not iterable") + } + + *la = loadAverage{} + cb := vm.ToValue(func(val, i Value) { + index := int(i.ToInteger()) + la[index] = val.ToFloat() + }) + + _, err := forEach(obj, cb) + return err +} + +func TestRuntime_ExportToValueUnmarshaler(t *testing.T) { + const SCRIPT = ` + var testData = { + LoadAverage: [1,2,3], + OtherValue: "foobar" + } + testData; + ` + + vm := New() + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + type expectValue struct { + LoadAverage *loadAverage + OtherValue string + } + + expect := expectValue{ + LoadAverage: &loadAverage{1, 2, 3}, + OtherValue: "foobar", + } + got := new(expectValue) + if err := vm.ExportTo(v, &got); err != nil { + t.Fatal(err) + } + + if *expect.LoadAverage != *got.LoadAverage { + t.Fatal("LoadAverage mismatch") + } + + if expect.OtherValue != got.OtherValue { + t.Fatal("OtherValue mismatch") + } +} + +func TestRuntime_ExportToTime(t *testing.T) { + const SCRIPT = ` + var dateStr = "2018-08-13T15:02:13+02:00"; + var str = "test123"; + ` + + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + var ti time.Time + err = vm.ExportTo(vm.Get("dateStr"), &ti) + if err != nil { + t.Fatal(err) + } + if ti.Format(time.RFC3339) != "2018-08-13T15:02:13+02:00" { + t.Fatalf("Unexpected value: '%s'", ti.Format(time.RFC3339)) + } + + err = vm.ExportTo(vm.Get("str"), &ti) + if err == nil { + t.Fatal("Expected err to not be nil") + } + + var str string + err = vm.ExportTo(vm.Get("dateStr"), &str) + if err != nil { + t.Fatal(err) + } + if str != "2018-08-13T15:02:13+02:00" { + t.Fatalf("Unexpected value: '%s'", str) + } + + d, err := vm.RunString(`new Date(1000)`) + if err != nil { + t.Fatal(err) + } + + ti = time.Time{} + err = vm.ExportTo(d, &ti) + if err != nil { + t.Fatal(err) + } + + if ti.UnixNano() != 1000*1e6 { + t.Fatal(ti) + } + if ti.Location() != time.Local { + t.Fatalf("Wrong location: %v", ti) + } +} + +func ExampleRuntime_ExportTo_func() { + const SCRIPT = ` + function f(param) { + return +param + 2; + } + ` + + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + panic(err) + } + + var fn func(string) string + err = vm.ExportTo(vm.Get("f"), &fn) + if err != nil { + panic(err) + } + + fmt.Println(fn("40")) // note, _this_ value in the function will be undefined. + // Output: 42 +} + +func ExampleRuntime_ExportTo_funcThrow() { + const SCRIPT = ` + function f(param) { + throw new Error("testing"); + } + ` + + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + panic(err) + } + + var fn func(string) (string, error) + err = vm.ExportTo(vm.Get("f"), &fn) + if err != nil { + panic(err) + } + _, err = fn("") + + fmt.Println(err) + // Output: Error: testing at f (:3:9(3)) +} + +func ExampleRuntime_ExportTo_funcVariadic() { + const SCRIPT = ` + function f() { + return Array.prototype.join.call(arguments, ","); + } + ` + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + panic(err) + } + + var fn func(args ...any) string + err = vm.ExportTo(vm.Get("f"), &fn) + if err != nil { + panic(err) + } + fmt.Println(fn("a", "b", 42)) + // Output: a,b,42 +} + +func TestRuntime_ExportToFuncFail(t *testing.T) { + const SCRIPT = ` + function f(param) { + return +param + 2; + } + ` + + type T struct { + Field1 int + } + + var fn func(string) (T, error) + + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + err = vm.ExportTo(vm.Get("f"), &fn) + if err != nil { + t.Fatal(err) + } + + if _, err := fn("40"); err == nil { + t.Fatal("Expected error") + } +} + +func TestRuntime_ExportToCallable(t *testing.T) { + const SCRIPT = ` + function f(param) { + return +param + 2; + } + ` + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + var c Callable + err = vm.ExportTo(vm.Get("f"), &c) + if err != nil { + t.Fatal(err) + } + + res, err := c(Undefined(), vm.ToValue("40")) + if err != nil { + t.Fatal(err) + } else if !res.StrictEquals(vm.ToValue(42)) { + t.Fatalf("Unexpected value: %v", res) + } +} + +func TestRuntime_ExportToObject(t *testing.T) { + const SCRIPT = ` + var o = {"test": 42}; + o; + ` + vm := New() + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + var o *Object + err = vm.ExportTo(vm.Get("o"), &o) + if err != nil { + t.Fatal(err) + } + + if v := o.Get("test"); !v.StrictEquals(vm.ToValue(42)) { + t.Fatalf("Unexpected value: %v", v) + } +} + +func ExampleAssertFunction() { + vm := New() + _, err := vm.RunString(` + function sum(a, b) { + return a+b; + } + `) + if err != nil { + panic(err) + } + sum, ok := AssertFunction(vm.Get("sum")) + if !ok { + panic("Not a function") + } + + res, err := sum(Undefined(), vm.ToValue(40), vm.ToValue(2)) + if err != nil { + panic(err) + } + fmt.Println(res) + // Output: 42 +} + +func TestGoFuncError(t *testing.T) { + const SCRIPT = ` + try { + f(); + } catch (e) { + if (!(e instanceof GoError)) { + throw(e); + } + if (e.value.Error() !== "Test") { + throw("Unexpected value: " + e.value.Error()); + } + } + ` + + f := func() error { + return errors.New("Test") + } + + vm := New() + vm.Set("f", f) + _, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } +} + +func TestToValueNil(t *testing.T) { + type T struct{} + var a *T + vm := New() + + if v := vm.ToValue(nil); !IsNull(v) { + t.Fatalf("nil: %v", v) + } + + if v := vm.ToValue(a); !IsNull(v) { + t.Fatalf("struct ptr: %v", v) + } + + var m map[string]any + if v := vm.ToValue(m); !IsNull(v) { + t.Fatalf("map[string]any: %v", v) + } + + var ar []any + if v := vm.ToValue(ar); IsNull(v) { + t.Fatalf("[]any: %v", v) + } + + var arptr *[]any + if v := vm.ToValue(arptr); !IsNull(v) { + t.Fatalf("*[]any: %v", v) + } +} + +func TestToValueFloat(t *testing.T) { + vm := New() + vm.Set("f64", float64(123)) + vm.Set("f32", float32(321)) + + v, err := vm.RunString("f64 === 123 && f32 === 321") + if err != nil { + t.Fatal(err) + } + if v.Export().(bool) != true { + t.Fatalf("StrictEquals for golang float failed") + } +} + +func TestToValueInterface(t *testing.T) { + + f := func(i any) bool { + return i == t + } + vm := New() + vm.Set("f", f) + vm.Set("t", t) + v, err := vm.RunString(`f(t)`) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatalf("v: %v", v) + } +} + +func TestJSONEscape(t *testing.T) { + const SCRIPT = ` + var a = "\\+1"; + JSON.stringify(a); + ` + + testScript(SCRIPT, asciiString(`"\\+1"`), t) +} + +func TestJSONObjectInArray(t *testing.T) { + const SCRIPT = ` + var a = "[{\"a\":1},{\"a\":2}]"; + JSON.stringify(JSON.parse(a)) == a; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestJSONQuirkyNumbers(t *testing.T) { + const SCRIPT = ` + var s; + s = JSON.stringify(NaN); + if (s != "null") { + throw new Error("NaN: " + s); + } + + s = JSON.stringify(Infinity); + if (s != "null") { + throw new Error("Infinity: " + s); + } + + s = JSON.stringify(-Infinity); + if (s != "null") { + throw new Error("-Infinity: " + s); + } + + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestJSONNil(t *testing.T) { + const SCRIPT = ` + JSON.stringify(i); + ` + + vm := New() + var i any + vm.Set("i", i) + ret, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if ret.String() != "null" { + t.Fatalf("Expected 'null', got: %v", ret) + } +} + +type customJsonEncodable struct{} + +func (*customJsonEncodable) JsonEncodable() any { + return "Test" +} + +func TestJsonEncodable(t *testing.T) { + var s customJsonEncodable + + vm := New() + vm.Set("s", &s) + + ret, err := vm.RunString("JSON.stringify(s)") + if err != nil { + t.Fatal(err) + } + if !ret.StrictEquals(vm.ToValue("\"Test\"")) { + t.Fatalf("Expected \"Test\", got: %v", ret) + } +} + +func TestSortComparatorReturnValues(t *testing.T) { + const SCRIPT = ` + var a = []; + for (var i = 0; i < 12; i++) { + a[i] = i; + } + + a.sort(function(x, y) { return y - x }); + + for (var i = 0; i < 12; i++) { + if (a[i] !== 11-i) { + throw new Error("Value at index " + i + " is incorrect: " + a[i]); + } + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestSortComparatorReturnValueFloats(t *testing.T) { + const SCRIPT = ` + var a = [ + 5.97, + 9.91, + 4.13, + 9.28, + 3.29, + ]; + a.sort( function(a, b) { return a - b; } ); + for (var i = 1; i < a.length; i++) { + if (a[i] < a[i-1]) { + throw new Error("Array is not sorted: " + a); + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestSortComparatorReturnValueNegZero(t *testing.T) { + const SCRIPT = ` + var a = [2, 1]; + a.sort( function(a, b) { return a > b ? 0 : -0; } ); + for (var i = 1; i < a.length; i++) { + if (a[i] < a[i-1]) { + throw new Error("Array is not sorted: " + a); + } + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestNilApplyArg(t *testing.T) { + const SCRIPT = ` + (function x(a, b) { + return a === undefined && b === 1; + }).apply(this, [,1]) + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestNilCallArg(t *testing.T) { + const SCRIPT = ` + "use strict"; + function f(a) { + return this === undefined && a === undefined; + } + ` + vm := New() + prg := MustCompile("test.js", SCRIPT, false) + vm.RunProgram(prg) + if f, ok := AssertFunction(vm.Get("f")); ok { + v, err := f(nil, nil) + if err != nil { + t.Fatal(err) + } + if !v.StrictEquals(valueTrue) { + t.Fatalf("Unexpected result: %v", v) + } + } +} + +func TestNullCallArg(t *testing.T) { + const SCRIPT = ` + f(null); + ` + vm := New() + prg := MustCompile("test.js", SCRIPT, false) + vm.Set("f", func(x *int) bool { + return x == nil + }) + + v, err := vm.RunProgram(prg) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(valueTrue) { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestObjectKeys(t *testing.T) { + const SCRIPT = ` + var o = { a: 1, b: 2, c: 3, d: 4 }; + o; + ` + + vm := New() + prg := MustCompile("test.js", SCRIPT, false) + + res, err := vm.RunProgram(prg) + if err != nil { + t.Fatal(err) + } + + if o, ok := res.(*Object); ok { + keys := o.Keys() + if !reflect.DeepEqual(keys, []string{"a", "b", "c", "d"}) { + t.Fatalf("Unexpected keys: %v", keys) + } + } +} + +func TestReflectCallExtraArgs(t *testing.T) { + const SCRIPT = ` + f(41, "extra") + ` + f := func(x int) int { + return x + 1 + } + + vm := New() + vm.Set("f", f) + + prg := MustCompile("test.js", SCRIPT, false) + + res, err := vm.RunProgram(prg) + if err != nil { + t.Fatal(err) + } + if !res.StrictEquals(intToValue(42)) { + t.Fatalf("Unexpected result: %v", res) + } +} + +func TestReflectCallNotEnoughArgs(t *testing.T) { + const SCRIPT = ` + f(42) + ` + vm := New() + + f := func(x, y int, z *int, s string) (int, error) { + if z != nil { + return 0, fmt.Errorf("z is not nil") + } + if s != "" { + return 0, fmt.Errorf("s is not \"\"") + } + return x + y, nil + } + + vm.Set("f", f) + + prg := MustCompile("test.js", SCRIPT, false) + + res, err := vm.RunProgram(prg) + if err != nil { + t.Fatal(err) + } + if !res.StrictEquals(intToValue(42)) { + t.Fatalf("Unexpected result: %v", res) + } +} + +func TestReflectCallVariadic(t *testing.T) { + const SCRIPT = ` + var r = f("Hello %s, %d", "test", 42); + if (r !== "Hello test, 42") { + throw new Error("test 1 has failed: " + r); + } + + r = f("Hello %s, %s", "test"); + if (r !== "Hello test, %!s(MISSING)") { + throw new Error("test 2 has failed: " + r); + } + + r = f(); + if (r !== "") { + throw new Error("test 3 has failed: " + r); + } + + ` + + vm := New() + vm.Set("f", fmt.Sprintf) + + prg := MustCompile("test.js", SCRIPT, false) + + _, err := vm.RunProgram(prg) + if err != nil { + t.Fatal(err) + } +} + +func TestReflectNullValueArgument(t *testing.T) { + rt := New() + rt.Set("fn", func(v Value) { + if v == nil { + t.Error("null becomes nil") + } + if !IsNull(v) { + t.Error("null is not null") + } + }) + rt.RunString(`fn(null);`) +} + +type testNativeConstructHelper struct { + rt *Runtime + base int64 + // any other state +} + +func (t *testNativeConstructHelper) calc(call FunctionCall) Value { + return t.rt.ToValue(t.base + call.Argument(0).ToInteger()) +} + +func TestNativeConstruct(t *testing.T) { + const SCRIPT = ` + var f = new F(40); + f instanceof F && f.method() === 42 && f.calc(2) === 42; + ` + + rt := New() + + method := func(call FunctionCall) Value { + return rt.ToValue(42) + } + + rt.Set("F", func(call ConstructorCall) *Object { // constructor signature (as opposed to 'func(FunctionCall) Value') + h := &testNativeConstructHelper{ + rt: rt, + base: call.Argument(0).ToInteger(), + } + call.This.Set("method", method) + call.This.Set("calc", h.calc) + return nil // or any other *Object which will be used instead of call.This + }) + + prg := MustCompile("test.js", SCRIPT, false) + + res, err := rt.RunProgram(prg) + if err != nil { + t.Fatal(err) + } + + if !res.StrictEquals(valueTrue) { + t.Fatalf("Unexpected result: %v", res) + } + + if fn, ok := AssertFunction(rt.Get("F")); ok { + v, err := fn(nil, rt.ToValue(42)) + if err != nil { + t.Fatal(err) + } + if o, ok := v.(*Object); ok { + if o.Get("method") == nil { + t.Fatal("No method") + } + } else { + t.Fatal("Not an object") + } + } else { + t.Fatal("Not a function") + } + + resp := &testNativeConstructHelper{} + value := rt.ToValue(resp) + if value.Export() != resp { + t.Fatal("no") + } +} + +func TestCreateObject(t *testing.T) { + const SCRIPT = ` + inst instanceof C; + ` + + r := New() + c := r.ToValue(func(call ConstructorCall) *Object { + return nil + }) + + proto := c.(*Object).Get("prototype").(*Object) + + inst := r.CreateObject(proto) + + r.Set("C", c) + r.Set("inst", inst) + + prg := MustCompile("test.js", SCRIPT, false) + + res, err := r.RunProgram(prg) + if err != nil { + t.Fatal(err) + } + + if !res.StrictEquals(valueTrue) { + t.Fatalf("Unexpected result: %v", res) + } +} + +func TestInterruptInWrappedFunction(t *testing.T) { + rt := New() + v, err := rt.RunString(` + var fn = function() { + while (true) {} + }; + fn; + `) + if err != nil { + t.Fatal(err) + } + fn, ok := AssertFunction(v) + if !ok { + t.Fatal("Not a function") + } + go func() { + <-time.After(10 * time.Millisecond) + rt.Interrupt(errors.New("hi")) + }() + + _, err = fn(nil) + if err == nil { + t.Fatal("expected error") + } + if _, ok := err.(*InterruptedError); !ok { + t.Fatalf("Wrong error type: %T", err) + } +} + +func TestInterruptInWrappedFunction2(t *testing.T) { + rt := New() + // this test panics as otherwise goja will recover and possibly loop + var called bool + rt.Set("v", rt.ToValue(func() { + if called { + go func() { + panic("this should never get called twice") + }() + } + called = true + rt.Interrupt("here is the error") + })) + + rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { + return a(nil) + })) + + rt.Set("k", rt.ToValue(func(e Value) { + go func() { + panic("this should never get called actually") + }() + })) + _, err := rt.RunString(` + Promise.resolve().then(()=>k()); // this should never resolve + while(true) { + try{ + s(() =>{ + v(); + }) + break; + } catch (e) { + k(e); + } + } + `) + if err == nil { + t.Fatal("expected error but got no error") + } + intErr := new(InterruptedError) + if !errors.As(err, &intErr) { + t.Fatalf("Wrong error type: %T", err) + } + if !strings.Contains(intErr.Error(), "here is the error") { + t.Fatalf("Wrong error message: %q", intErr.Error()) + } + _, err = rt.RunString(`Promise.resolve().then(()=>globalThis.S=5)`) + if err != nil { + t.Fatal(err) + } + s := rt.Get("S") + if s == nil || s.ToInteger() != 5 { + t.Fatalf("Wrong value for S %v", s) + } +} + +func TestInterruptInWrappedFunction2Recover(t *testing.T) { + rt := New() + // this test panics as otherwise goja will recover and possibly loop + var vCalled int + rt.Set("v", rt.ToValue(func() { + if vCalled == 0 { + rt.Interrupt("here is the error") + } + vCalled++ + })) + + rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { + v, err := a(nil) + if err != nil { + intErr := new(InterruptedError) + if errors.As(err, &intErr) { + rt.ClearInterrupt() + return nil, errors.New("oops we got interrupted let's not that") + } + } + return v, err + })) + var kCalled int + + rt.Set("k", rt.ToValue(func(e Value) { + kCalled++ + })) + _, err := rt.RunString(` + Promise.resolve().then(()=>k()); + while(true) { + try{ + s(() => { + v(); + }) + break; + } catch (e) { + k(e); + } + } + `) + if err != nil { + t.Fatal(err) + } + if vCalled != 2 { + t.Fatalf("v was not called exactly twice but %d times", vCalled) + } + if kCalled != 2 { + t.Fatalf("k was not called exactly twice but %d times", kCalled) + } + _, err = rt.RunString(`Promise.resolve().then(()=>globalThis.S=5)`) + if err != nil { + t.Fatal(err) + } + s := rt.Get("S") + if s == nil || s.ToInteger() != 5 { + t.Fatalf("Wrong value for S %v", s) + } +} + +func TestInterruptInWrappedFunctionExpectInteruptError(t *testing.T) { + rt := New() + // this test panics as otherwise goja will recover and possibly loop + rt.Set("v", rt.ToValue(func() { + rt.Interrupt("here is the error") + })) + + rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { + return a(nil) + })) + + _, err := rt.RunString(` + s(() =>{ + v(); + }) + `) + if err == nil { + t.Fatal("expected error but got no error") + } + var intErr *InterruptedError + if !errors.As(err, &intErr) { + t.Fatalf("Wrong error type: %T", err) + } + if !strings.Contains(intErr.Error(), "here is the error") { + t.Fatalf("Wrong error message: %q", intErr.Error()) + } +} + +func TestInterruptInWrappedFunctionExpectStackOverflowError(t *testing.T) { + rt := New() + rt.SetMaxCallStackSize(5) + // this test panics as otherwise goja will recover and possibly loop + rt.Set("v", rt.ToValue(func() { + _, err := rt.RunString(` + (function loop() { loop() })(); + `) + if err != nil { + panic(err) + } + })) + + rt.Set("s", rt.ToValue(func(a Callable) (Value, error) { + return a(nil) + })) + + _, err := rt.RunString(` + s(() =>{ + v(); + }) + `) + if err == nil { + t.Fatal("expected error but got no error") + } + var soErr *StackOverflowError + if !errors.As(err, &soErr) { + t.Fatalf("Wrong error type: %T", err) + } +} + +func TestRunLoopPreempt(t *testing.T) { + vm := New() + v, err := vm.RunString("(function() {for (;;) {}})") + if err != nil { + t.Fatal(err) + } + + fn, ok := AssertFunction(v) + if !ok { + t.Fatal("Not a function") + } + + go func() { + <-time.After(100 * time.Millisecond) + runtime.GC() // this hangs if the vm loop does not have any preemption points + vm.Interrupt(errors.New("hi")) + }() + + _, err = fn(nil) + if err == nil { + t.Fatal("expected error") + } + if _, ok := err.(*InterruptedError); !ok { + t.Fatalf("Wrong error type: %T", err) + } +} + +func TestNaN(t *testing.T) { + if !IsNaN(_NaN) { + t.Fatal("IsNaN() doesn't detect NaN") + } + if IsNaN(Undefined()) { + t.Fatal("IsNaN() says undefined is a NaN") + } + if !IsNaN(NaN()) { + t.Fatal("NaN() doesn't return NaN") + } +} + +func TestInf(t *testing.T) { + if !IsInfinity(_positiveInf) { + t.Fatal("IsInfinity() doesn't detect +Inf") + } + if !IsInfinity(_negativeInf) { + t.Fatal("IsInfinity() doesn't detect -Inf") + } + if IsInfinity(Undefined()) { + t.Fatal("IsInfinity() says undefined is a Infinity") + } + if !IsInfinity(PositiveInf()) { + t.Fatal("PositiveInfinity() doesn't return Inf") + } + if !IsInfinity(NegativeInf()) { + t.Fatal("NegativeInfinity() doesn't return Inf") + } +} + +func TestRuntimeNew(t *testing.T) { + vm := New() + v, err := vm.New(vm.Get("Number"), vm.ToValue("12345")) + if err != nil { + t.Fatal(err) + } + if n, ok := v.Export().(int64); ok { + if n != 12345 { + t.Fatalf("n: %v", n) + } + } else { + t.Fatalf("v: %T", v) + } +} + +func TestAutoBoxing(t *testing.T) { + const SCRIPT = ` + function f() { + 'use strict'; + var a = 1; + var thrown1 = false; + var thrown2 = false; + try { + a.test = 42; + } catch (e) { + thrown1 = e instanceof TypeError; + } + try { + a["test1"] = 42; + } catch (e) { + thrown2 = e instanceof TypeError; + } + return thrown1 && thrown2; + } + var a = 1; + a.test = 42; // should not throw + a["test1"] = 42; // should not throw + a.test === undefined && a.test1 === undefined && f(); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestProtoGetter(t *testing.T) { + const SCRIPT = ` + ({}).__proto__ === Object.prototype && [].__proto__ === Array.prototype; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestSymbol1(t *testing.T) { + const SCRIPT = ` + Symbol.toPrimitive[Symbol.toPrimitive]() === Symbol.toPrimitive; + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestFreezeSymbol(t *testing.T) { + const SCRIPT = ` + var s = Symbol(1); + var o = {}; + o[s] = 42; + Object.freeze(o); + o[s] = 43; + o[s] === 42 && Object.isFrozen(o); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestToPropertyKey(t *testing.T) { + const SCRIPT = ` + var sym = Symbol(42); + var callCount = 0; + + var wrapper = { + toString: function() { + callCount += 1; + return sym; + }, + valueOf: function() { + $ERROR("valueOf() called"); + } + }; + + var o = {}; + o[wrapper] = function() { return "test" }; + assert.sameValue(o[wrapper], o[sym], "o[wrapper] === o[sym]"); + assert.sameValue(o[wrapper](), "test", "o[wrapper]()"); + assert.sameValue(o[sym](), "test", "o[sym]()"); + + var wrapper1 = {}; + wrapper1[Symbol.toPrimitive] = function(hint) { + if (hint === "string" || hint === "default") { + return "1"; + } + if (hint === "number") { + return 2; + } + $ERROR("Unknown hint value "+hint); + }; + var a = []; + a[wrapper1] = 42; + assert.sameValue(a[1], 42, "a[1]"); + assert.sameValue(a[1], a[wrapper1], "a[1] === a[wrapper1]"); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestPrimThisValue(t *testing.T) { + const SCRIPT = ` + function t() { + 'use strict'; + + Boolean.prototype.toString = function() { + return typeof this; + }; + + assert.sameValue(true.toLocaleString(), "boolean"); + + Boolean.prototype[Symbol.iterator] = function() { + return [typeof this][Symbol.iterator](); + } + var s = new Set(true); + assert.sameValue(s.size, 1, "size"); + assert.sameValue(s.has("boolean"), true, "s.has('boolean')"); + } + t(); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestPrimThisValueGetter(t *testing.T) { + const SCRIPT = ` + function t() { + 'use strict'; + Object.defineProperty(Boolean.prototype, "toString", { + get: function() { + var v = typeof this; + return function() { + return v; + }; + } + }); + + assert.sameValue(true.toLocaleString(), "boolean"); + } + t(); + ` + + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestObjSetSym(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var sym = Symbol(true); + var p1 = Object.create(null); + var p2 = Object.create(p1); + + Object.defineProperty(p1, sym, { + value: 42 + }); + + Object.defineProperty(p2, sym, { + value: 43, + writable: true, + }); + var o = Object.create(p2); + o[sym] = 44; + o[sym]; + ` + testScript(SCRIPT, intToValue(44), t) +} + +func TestObjSet(t *testing.T) { + const SCRIPT = ` + 'use strict'; + var p1 = Object.create(null); + var p2 = Object.create(p1); + + Object.defineProperty(p1, "test", { + value: 42 + }); + + Object.defineProperty(p2, "test", { + value: 43, + writable: true, + }); + var o = Object.create(p2); + o.test = 44; + o.test; + ` + testScript(SCRIPT, intToValue(44), t) +} + +func TestToValueNilValue(t *testing.T) { + r := New() + var a Value + r.Set("a", a) + ret, err := r.RunString(` + ""+a; + `) + if err != nil { + t.Fatal(err) + } + if !asciiString("null").SameAs(ret) { + t.Fatalf("ret: %v", ret) + } +} + +func TestDateConversion(t *testing.T) { + now := time.Now() + vm := New() + val, err := vm.New(vm.Get("Date").ToObject(vm), vm.ToValue(now.UnixNano()/1e6)) + if err != nil { + t.Fatal(err) + } + vm.Set("d", val) + res, err := vm.RunString(`+d`) + if err != nil { + t.Fatal(err) + } + if exp := res.Export(); exp != now.UnixNano()/1e6 { + t.Fatalf("Value does not match: %v", exp) + } + vm.Set("goval", now) + res, err = vm.RunString(`+(new Date(goval.UnixNano()/1e6))`) + if err != nil { + t.Fatal(err) + } + if exp := res.Export(); exp != now.UnixNano()/1e6 { + t.Fatalf("Value does not match: %v", exp) + } +} + +func TestNativeCtorNewTarget(t *testing.T) { + const SCRIPT = ` + function NewTarget() { + } + + var o = Reflect.construct(Number, [1], NewTarget); + o.__proto__ === NewTarget.prototype && o.toString() === "[object Number]"; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestNativeCtorNonNewCall(t *testing.T) { + vm := New() + vm.Set(`Animal`, func(call ConstructorCall) *Object { + obj := call.This + obj.Set(`name`, call.Argument(0).String()) + obj.Set(`eat`, func(call FunctionCall) Value { + self := call.This.(*Object) + return vm.ToValue(fmt.Sprintf("%s eat", self.Get(`name`))) + }) + return nil + }) + v, err := vm.RunString(` + + function __extends(d, b){ + function __() { + this.constructor = d; + } + d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); + } + + var Cat = (function (_super) { + __extends(Cat, _super); + function Cat() { + return _super.call(this, "cat") || this; + } + return Cat; + }(Animal)); + + var cat = new Cat(); + cat instanceof Cat && cat.eat() === "cat eat"; + `) + if err != nil { + t.Fatal(err) + } + if v != valueTrue { + t.Fatal(v) + } +} + +func ExampleNewSymbol() { + sym1 := NewSymbol("66") + sym2 := NewSymbol("66") + fmt.Printf("%s %s %v", sym1, sym2, sym1.Equals(sym2)) + // Output: 66 66 false +} + +func ExampleObject_SetSymbol() { + type IterResult struct { + Done bool + Value Value + } + + vm := New() + vm.SetFieldNameMapper(UncapFieldNameMapper()) // to use IterResult + + o := vm.NewObject() + o.SetSymbol(SymIterator, func() *Object { + count := 0 + iter := vm.NewObject() + iter.Set("next", func() IterResult { + if count < 10 { + count++ + return IterResult{ + Value: vm.ToValue(count), + } + } + return IterResult{ + Done: true, + } + }) + return iter + }) + vm.Set("o", o) + + res, err := vm.RunString(` + var acc = ""; + for (var v of o) { + acc += v + " "; + } + acc; + `) + if err != nil { + panic(err) + } + fmt.Println(res) + // Output: 1 2 3 4 5 6 7 8 9 10 +} + +func ExampleRuntime_NewArray() { + vm := New() + array := vm.NewArray(1, 2, true) + vm.Set("array", array) + res, err := vm.RunString(` + var acc = ""; + for (var v of array) { + acc += v + " "; + } + acc; + `) + if err != nil { + panic(err) + } + fmt.Println(res) + // Output: 1 2 true +} + +func ExampleRuntime_SetParserOptions() { + vm := New() + vm.SetParserOptions(parser.WithDisableSourceMaps) + + res, err := vm.RunString(` + "I did not hang!"; +//# sourceMappingURL=/dev/zero`) + + if err != nil { + panic(err) + } + fmt.Println(res.String()) + // Output: I did not hang! +} + +func TestRuntime_SetParserOptions_Eval(t *testing.T) { + vm := New() + vm.SetParserOptions(parser.WithDisableSourceMaps) + + _, err := vm.RunString(` + eval("//# sourceMappingURL=/dev/zero"); + `) + if err != nil { + t.Fatal(err) + } +} + +func TestNativeCallWithRuntimeParameter(t *testing.T) { + vm := New() + vm.Set("f", func(_ FunctionCall, r *Runtime) Value { + if r == vm { + return valueTrue + } + return valueFalse + }) + ret, err := vm.RunString(`f()`) + if err != nil { + t.Fatal(err) + } + if ret != valueTrue { + t.Fatal(ret) + } +} + +func TestNestedEnumerate(t *testing.T) { + const SCRIPT = ` + var o = {baz: true, foo: true, bar: true}; + var res = ""; + for (var i in o) { + delete o.baz; + Object.defineProperty(o, "hidden", {value: true, configurable: true}); + for (var j in o) { + Object.defineProperty(o, "0", {value: true, configurable: true}); + Object.defineProperty(o, "1", {value: true, configurable: true}); + for (var k in o) {} + res += i + "-" + j + " "; + } + } + assert(compareArray(Reflect.ownKeys(o), ["0","1","foo","bar","hidden"]), "keys"); + res; + ` + testScriptWithTestLib(SCRIPT, asciiString("baz-foo baz-bar foo-foo foo-bar bar-foo bar-bar "), t) +} + +func TestAbandonedEnumerate(t *testing.T) { + const SCRIPT = ` + var o = {baz: true, foo: true, bar: true}; + var res = ""; + for (var i in o) { + delete o.baz; + for (var j in o) { + res += i + "-" + j + " "; + break; + } + } + res; + ` + testScript(SCRIPT, asciiString("baz-foo foo-foo bar-foo "), t) +} + +func TestIterCloseThrows(t *testing.T) { + const SCRIPT = ` + var returnCount = 0; + var iterable = {}; + var iterator = { + next: function() { + return { value: true }; + }, + return: function() { + returnCount += 1; + throw new Error(); + } + }; + iterable[Symbol.iterator] = function() { + return iterator; + }; + + try { + for (var i of iterable) { + break; + } + } catch (e) {}; + returnCount; + ` + testScript(SCRIPT, valueInt(1), t) +} + +func TestDeclareGlobalFunc(t *testing.T) { + const SCRIPT = ` + var initial; + + Object.defineProperty(this, 'f', { + enumerable: true, + writable: true, + configurable: false + }); + + (0,eval)('initial = f; function f() { return 2222; }'); + var desc = Object.getOwnPropertyDescriptor(this, "f"); + assert(desc.enumerable, "enumerable"); + assert(desc.writable, "writable"); + assert(!desc.configurable, "configurable"); + assert.sameValue(initial(), 2222); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestStackOverflowError(t *testing.T) { + vm := New() + vm.SetMaxCallStackSize(3) + _, err := vm.RunString(` + function f() { + f(); + } + f(); + `) + if _, ok := err.(*StackOverflowError); !ok { + t.Fatal(err) + } +} + +func TestStacktraceLocationThrowFromCatch(t *testing.T) { + vm := New() + _, err := vm.RunString(` + function main(arg) { + try { + if (arg === 1) { + return f1(); + } + if (arg === 2) { + return f2(); + } + if (arg === 3) { + return f3(); + } + } catch (e) { + throw e; + } + } + function f1() {} + function f2() { + throw new Error(); + } + function f3() {} + main(2); + `) + if err == nil { + t.Fatal("Expected error") + } + stack := err.(*Exception).stack + if len(stack) != 3 { + t.Fatalf("Unexpected stack len: %v", stack) + } + if frame := stack[0]; frame.funcName != "f2" || frame.pc != 2 { + t.Fatalf("Unexpected stack frame 0: %#v", frame) + } + if frame := stack[1]; frame.funcName != "main" || frame.pc != 17 { + t.Fatalf("Unexpected stack frame 1: %#v", frame) + } + if frame := stack[2]; frame.funcName != "" || frame.pc != 7 { + t.Fatalf("Unexpected stack frame 2: %#v", frame) + } +} + +func TestErrorStackRethrow(t *testing.T) { + const SCRIPT = ` + function f(e) { + throw e; + } + try { + f(new Error()); + } catch(e) { + assertStack(e, [["test.js", "", 6, 5]]); + } + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestStacktraceLocationThrowFromGo(t *testing.T) { + vm := New() + f := func() { + panic(vm.ToValue("Test")) + } + vm.Set("f", f) + _, err := vm.RunString(` + function main() { + (function noop() {})(); + return callee(); + } + function callee() { + return f(); + } + main(); + `) + if err == nil { + t.Fatal("Expected error") + } + stack := err.(*Exception).stack + if len(stack) != 4 { + t.Fatalf("Unexpected stack len: %v", stack) + } + if frame := stack[0]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowFromGo.func1") { + t.Fatalf("Unexpected stack frame 0: %#v", frame) + } + if frame := stack[1]; frame.funcName != "callee" || frame.pc != 2 { + t.Fatalf("Unexpected stack frame 1: %#v", frame) + } + if frame := stack[2]; frame.funcName != "main" || frame.pc != 6 { + t.Fatalf("Unexpected stack frame 2: %#v", frame) + } + if frame := stack[3]; frame.funcName != "" || frame.pc != 4 { + t.Fatalf("Unexpected stack frame 3: %#v", frame) + } +} + +func TestStacktraceLocationThrowNativeInTheMiddle(t *testing.T) { + vm := New() + v, err := vm.RunString(`(function f1() { + throw new Error("test") + })`) + if err != nil { + t.Fatal(err) + } + + var f1 func() + err = vm.ExportTo(v, &f1) + if err != nil { + t.Fatal(err) + } + + f := func() { + f1() + } + vm.Set("f", f) + _, err = vm.RunString(` + function main() { + (function noop() {})(); + return callee(); + } + function callee() { + return f(); + } + main(); + `) + if err == nil { + t.Fatal("Expected error") + } + stack := err.(*Exception).stack + if len(stack) != 5 { + t.Fatalf("Unexpected stack len: %v", stack) + } + if frame := stack[0]; frame.funcName != "f1" || frame.pc != 7 { + t.Fatalf("Unexpected stack frame 0: %#v", frame) + } + if frame := stack[1]; !strings.HasSuffix(frame.funcName.String(), "TestStacktraceLocationThrowNativeInTheMiddle.func1") { + t.Fatalf("Unexpected stack frame 1: %#v", frame) + } + if frame := stack[2]; frame.funcName != "callee" || frame.pc != 2 { + t.Fatalf("Unexpected stack frame 2: %#v", frame) + } + if frame := stack[3]; frame.funcName != "main" || frame.pc != 6 { + t.Fatalf("Unexpected stack frame 3: %#v", frame) + } + if frame := stack[4]; frame.funcName != "" || frame.pc != 4 { + t.Fatalf("Unexpected stack frame 4: %#v", frame) + } +} + +func TestStrToInt64(t *testing.T) { + if _, ok := strToInt64(""); ok { + t.Fatal("") + } + if n, ok := strToInt64("0"); !ok || n != 0 { + t.Fatal("0", n, ok) + } + if n, ok := strToInt64("-0"); ok { + t.Fatal("-0", n, ok) + } + if n, ok := strToInt64("-1"); !ok || n != -1 { + t.Fatal("-1", n, ok) + } + if n, ok := strToInt64("9223372036854775808"); ok { + t.Fatal("max+1", n, ok) + } + if n, ok := strToInt64("9223372036854775817"); ok { + t.Fatal("9223372036854775817", n, ok) + } + if n, ok := strToInt64("-9223372036854775818"); ok { + t.Fatal("-9223372036854775818", n, ok) + } + if n, ok := strToInt64("9223372036854775807"); !ok || n != 9223372036854775807 { + t.Fatal("max", n, ok) + } + if n, ok := strToInt64("-9223372036854775809"); ok { + t.Fatal("min-1", n, ok) + } + if n, ok := strToInt64("-9223372036854775808"); !ok || n != -9223372036854775808 { + t.Fatal("min", n, ok) + } + if n, ok := strToInt64("-00"); ok { + t.Fatal("-00", n, ok) + } + if n, ok := strToInt64("-01"); ok { + t.Fatal("-01", n, ok) + } +} + +func TestStrToInt32(t *testing.T) { + if _, ok := strToInt32(""); ok { + t.Fatal("") + } + if n, ok := strToInt32("0"); !ok || n != 0 { + t.Fatal("0", n, ok) + } + if n, ok := strToInt32("-0"); ok { + t.Fatal("-0", n, ok) + } + if n, ok := strToInt32("-1"); !ok || n != -1 { + t.Fatal("-1", n, ok) + } + if n, ok := strToInt32("2147483648"); ok { + t.Fatal("max+1", n, ok) + } + if n, ok := strToInt32("2147483657"); ok { + t.Fatal("2147483657", n, ok) + } + if n, ok := strToInt32("-2147483658"); ok { + t.Fatal("-2147483658", n, ok) + } + if n, ok := strToInt32("2147483647"); !ok || n != 2147483647 { + t.Fatal("max", n, ok) + } + if n, ok := strToInt32("-2147483649"); ok { + t.Fatal("min-1", n, ok) + } + if n, ok := strToInt32("-2147483648"); !ok || n != -2147483648 { + t.Fatal("min", n, ok) + } + if n, ok := strToInt32("-00"); ok { + t.Fatal("-00", n, ok) + } + if n, ok := strToInt32("-01"); ok { + t.Fatal("-01", n, ok) + } +} + +func TestDestructSymbol(t *testing.T) { + const SCRIPT = ` + var S = Symbol("S"); + var s, rest; + + ({[S]: s, ...rest} = {[S]: true, test: 1}); + assert.sameValue(s, true, "S"); + assert(deepEqual(rest, {test: 1}), "rest"); + ` + testScriptWithTestLibX(SCRIPT, _undefined, t) +} + +func TestAccessorFuncName(t *testing.T) { + const SCRIPT = ` + const namedSym = Symbol('test262'); + const emptyStrSym = Symbol(""); + const anonSym = Symbol(); + + const o = { + get id() {}, + get [anonSym]() {}, + get [namedSym]() {}, + get [emptyStrSym]() {}, + set id(v) {}, + set [anonSym](v) {}, + set [namedSym](v) {}, + set [emptyStrSym](v) {} + }; + + let prop; + prop = Object.getOwnPropertyDescriptor(o, 'id'); + assert.sameValue(prop.get.name, 'get id'); + assert.sameValue(prop.set.name, 'set id'); + + prop = Object.getOwnPropertyDescriptor(o, anonSym); + assert.sameValue(prop.get.name, 'get '); + assert.sameValue(prop.set.name, 'set '); + + prop = Object.getOwnPropertyDescriptor(o, emptyStrSym); + assert.sameValue(prop.get.name, 'get []'); + assert.sameValue(prop.set.name, 'set []'); + + prop = Object.getOwnPropertyDescriptor(o, namedSym); + assert.sameValue(prop.get.name, 'get [test262]'); + assert.sameValue(prop.set.name, 'set [test262]'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestCoverFuncName(t *testing.T) { + const SCRIPT = ` + var namedSym = Symbol(''); + var anonSym = Symbol(); + var o; + + o = { + xId: (0, function() {}), + id: (function() {}), + id1: function x() {}, + [anonSym]: (function() {}), + [namedSym]: (function() {}) + }; + + assert(o.xId.name !== 'xId'); + assert.sameValue(o.id1.name, 'x'); + assert.sameValue(o.id.name, 'id', 'via IdentifierName'); + assert.sameValue(o[anonSym].name, '', 'via anonymous Symbol'); + assert.sameValue(o[namedSym].name, '[]', 'via Symbol'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestAnonFuncName(t *testing.T) { + const SCRIPT = ` + const d = Object.getOwnPropertyDescriptor((function() {}), 'name'); + d !== undefined && d.value === ''; + ` + testScript(SCRIPT, valueTrue, t) +} + +func TestStringToBytesConversion(t *testing.T) { + vm := New() + v := vm.ToValue("Test") + var b []byte + err := vm.ExportTo(v, &b) + if err != nil { + t.Fatal(err) + } + if string(b) != "Test" { + t.Fatal(b) + } +} + +func TestPromiseAll(t *testing.T) { + const SCRIPT = ` +var p1 = new Promise(function() {}); +var p2 = new Promise(function() {}); +var p3 = new Promise(function() {}); +var callCount = 0; +var currentThis = p1; +var nextThis = p2; +var afterNextThis = p3; + +p1.then = p2.then = p3.then = function(a, b) { + assert.sameValue(typeof a, 'function', 'type of first argument'); + assert.sameValue( + a.length, + 1, + 'ES6 25.4.1.3.2: The length property of a promise resolve function is 1.' + ); + assert.sameValue(typeof b, 'function', 'type of second argument'); + assert.sameValue( + b.length, + 1, + 'ES6 25.4.1.3.1: The length property of a promise reject function is 1.' + ); + assert.sameValue(arguments.length, 2, '"then"" invoked with two arguments'); + assert.sameValue(this, currentThis, '"this" value'); + + currentThis = nextThis; + nextThis = afterNextThis; + afterNextThis = null; + + callCount += 1; +}; + +Promise.all([p1, p2, p3]); + +assert.sameValue(callCount, 3, '"then"" invoked once for every iterated value'); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestPromiseExport(t *testing.T) { + vm := New() + p, _, _ := vm.NewPromise() + pv := vm.ToValue(p) + if actual := pv.ExportType(); actual != reflect.TypeOf((*Promise)(nil)) { + t.Fatalf("Export type: %v", actual) + } + + if ev := pv.Export(); ev != p { + t.Fatalf("Export value: %v", ev) + } +} + +func TestErrorStack(t *testing.T) { + const SCRIPT = ` + const err = new Error("test"); + if (!("stack" in err)) { + throw new Error("in"); + } + if (Reflect.ownKeys(err)[0] !== "stack") { + throw new Error("property order"); + } + const stack = err.stack; + if (stack !== "Error: test\n\tat test.js:2:14(3)\n") { + throw new Error(stack); + } + delete err.stack; + if ("stack" in err) { + throw new Error("stack still in err after delete"); + } + ` + testScript(SCRIPT, _undefined, t) +} + +func TestErrorFormatSymbols(t *testing.T) { + vm := New() + vm.Set("a", func() (Value, error) { return nil, errors.New("something %s %f") }) + _, err := vm.RunString("a()") + if !strings.Contains(err.Error(), "something %s %f") { + t.Fatalf("Wrong value %q", err.Error()) + } +} + +func TestPanicPassthrough(t *testing.T) { + const panicString = "Test panic" + r := New() + r.Set("f", func() { + panic(panicString) + }) + defer func() { + if x := recover(); x != nil { + if x != panicString { + t.Fatalf("Wrong panic value: %v", x) + } + if len(r.vm.callStack) > 0 { + t.Fatal("vm.callStack is not empty") + } + } else { + t.Fatal("No panic") + } + }() + _, _ = r.RunString("f()") + t.Fatal("Should not reach here") +} + +func TestSuspendResumeRelStackLen(t *testing.T) { + const SCRIPT = ` + async function f2() { + throw new Error("test"); + } + + async function f1() { + let a = [1]; + for (let i of a) { + try { + await f2(); + } catch { + return true; + } + } + } + + async function f() { + let a = [1]; + for (let i of a) { + return await f1(); + } + } + return f(); + ` + testAsyncFunc(SCRIPT, valueTrue, t) +} + +func TestSuspendResumeStacks(t *testing.T) { + const SCRIPT = ` +async function f1() { + throw new Error(); +} +async function f() { + try { + await f1(); + } catch {} +} + +result = await f(); + ` + testAsyncFunc(SCRIPT, _undefined, t) +} + +func TestNestedTopLevelConstructorCall(t *testing.T) { + r := New() + c := func(call ConstructorCall, rt *Runtime) *Object { + if _, err := rt.RunString("(5)"); err != nil { + panic(err) + } + return nil + } + if err := r.Set("C", c); err != nil { + panic(err) + } + if _, err := r.RunString("new C()"); err != nil { + panic(err) + } +} + +func TestNestedTopLevelConstructorPanicAsync(t *testing.T) { + r := New() + c := func(call ConstructorCall, rt *Runtime) *Object { + c, ok := AssertFunction(rt.ToValue(func() {})) + if !ok { + panic("wat") + } + if _, err := c(Undefined()); err != nil { + panic(err) + } + return nil + } + if err := r.Set("C", c); err != nil { + panic(err) + } + if _, err := r.RunString("new C()"); err != nil { + panic(err) + } +} + +func TestAsyncFuncThrow(t *testing.T) { + const SCRIPT = ` + class TestError extends Error { + } + + async function f() { + throw new TestError(); + } + + async function f1() { + try { + await f(); + } catch (e) { + assert.sameValue(e.constructor.name, TestError.name); + return; + } + throw new Error("No exception was thrown"); + } + await f1(); + return undefined; + ` + testAsyncFuncWithTestLib(SCRIPT, _undefined, t) +} + +func TestAsyncStacktrace(t *testing.T) { + // Do not reformat, assertions depend on the line and column numbers + const SCRIPT = ` + let ex; + async function foo(x) { + await bar(x); + } + + async function bar(x) { + await x; + throw new Error("Let's have a look..."); + } + + try { + await foo(1); + } catch (e) { + assertStack(e, [ + ["test.js", "bar", 9, 10], + ["test.js", "foo", 4, 13], + ["test.js", "test", 13, 12], + ]); + } + ` + testAsyncFuncWithTestLibX(SCRIPT, _undefined, t) +} + +func TestPanicPropagation(t *testing.T) { + r := New() + r.Set("doPanic", func() { + panic(true) + }) + v, err := r.RunString(`(function() { + doPanic(); + })`) + if err != nil { + t.Fatal(err) + } + f, ok := AssertFunction(v) + if !ok { + t.Fatal("not a function") + } + defer func() { + if x := recover(); x != nil { + if x != true { + t.Fatal("Invalid panic value") + } + } + }() + _, _ = f(nil) + t.Fatal("Expected panic") +} + +func TestAwaitInParameters(t *testing.T) { + _, err := Compile("", ` + async function g() { + async function inner(a = 1 + await 1) { + } + } + `, false) + if err == nil { + t.Fatal("Expected error") + } +} + +func ExampleRuntime_ForOf() { + r := New() + v, err := r.RunString(` + new Map().set("a", 1).set("b", 2); + `) + if err != nil { + panic(err) + } + + var sb strings.Builder + ex := r.Try(func() { + r.ForOf(v, func(v Value) bool { + o := v.ToObject(r) + key := o.Get("0") + value := o.Get("1") + + sb.WriteString(key.String()) + sb.WriteString("=") + sb.WriteString(value.String()) + sb.WriteString(",") + + return true + }) + }) + if ex != nil { + panic(ex) + } + fmt.Println(sb.String()) + // Output: a=1,b=2, +} + +/* +func TestArrayConcatSparse(t *testing.T) { +function foo(a,b,c) + { + arguments[0] = 1; arguments[1] = 'str'; arguments[2] = 2.1; + if(1 === a && 'str' === b && 2.1 === c) + return true; + } + + + const SCRIPT = ` + var a1 = []; + var a2 = []; + a1[500000] = 1; + a2[1000000] = 2; + var a3 = a1.concat(a2); + a3.length === 1500002 && a3[500000] === 1 && a3[1500001] == 2; + ` + + testScript(SCRIPT, valueTrue, t) +} +*/ + +func BenchmarkCallReflect(b *testing.B) { + vm := New() + vm.Set("f", func(v Value) { + + }) + + prg := MustCompile("test.js", "f(null)", true) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vm.RunProgram(prg) + } +} + +func BenchmarkCallNative(b *testing.B) { + vm := New() + vm.Set("f", func(call FunctionCall) (ret Value) { + return + }) + + prg := MustCompile("test.js", "f(null)", true) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vm.RunProgram(prg) + } +} + +func BenchmarkCallJS(b *testing.B) { + vm := New() + _, err := vm.RunString(` + function f() { + return 42; + } + `) + + if err != nil { + b.Fatal(err) + } + + prg := MustCompile("test.js", "f(null)", true) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vm.RunProgram(prg) + } +} + +func BenchmarkMainLoop(b *testing.B) { + vm := New() + + const SCRIPT = ` + for (var i=0; i<100000; i++) { + } + ` + + prg := MustCompile("test.js", SCRIPT, true) + + b.ResetTimer() + for i := 0; i < b.N; i++ { + vm.RunProgram(prg) + } +} + +func BenchmarkStringMapGet(b *testing.B) { + m := make(map[string]Value) + for i := 0; i < 100; i++ { + m[strconv.Itoa(i)] = intToValue(int64(i)) + } + b.ResetTimer() + for i := 0; i < b.N; i++ { + if m["50"] == nil { + b.Fatal() + } + } +} + +func BenchmarkValueStringMapGet(b *testing.B) { + m := make(map[String]Value) + for i := 0; i < 100; i++ { + m[asciiString(strconv.Itoa(i))] = intToValue(int64(i)) + } + b.ResetTimer() + var key String = asciiString("50") + for i := 0; i < b.N; i++ { + if m[key] == nil { + b.Fatal() + } + } +} + +func BenchmarkAsciiStringMapGet(b *testing.B) { + m := make(map[asciiString]Value) + for i := 0; i < 100; i++ { + m[asciiString(strconv.Itoa(i))] = intToValue(int64(i)) + } + b.ResetTimer() + var key = asciiString("50") + for i := 0; i < b.N; i++ { + if m[key] == nil { + b.Fatal() + } + } +} + +func BenchmarkNew(b *testing.B) { + b.ReportAllocs() + for i := 0; i < b.N; i++ { + New() + } +} diff --git a/pkg/xscript/engine/sourcemap/bench_test.go b/pkg/xscript/engine/sourcemap/bench_test.go new file mode 100644 index 0000000..d6d9328 --- /dev/null +++ b/pkg/xscript/engine/sourcemap/bench_test.go @@ -0,0 +1,30 @@ +package sourcemap_test + +import ( + "testing" + + "pandax/pkg/xscript/engine/sourcemap" +) + +func BenchmarkParse(b *testing.B) { + for i := 0; i < b.N; i++ { + _, err := sourcemap.Parse(jqSourceMapURL, jqSourceMapBytes) + if err != nil { + b.Fatal(err) + } + } +} + +func BenchmarkSource(b *testing.B) { + smap, err := sourcemap.Parse(jqSourceMapURL, jqSourceMapBytes) + if err != nil { + b.Fatal(err) + } + b.ResetTimer() + + for i := 0; i < b.N; i++ { + for j := 0; j < 10; j++ { + smap.Source(j, 100*j) + } + } +} diff --git a/pkg/xscript/engine/sourcemap/consumer.go b/pkg/xscript/engine/sourcemap/consumer.go new file mode 100644 index 0000000..935a4a9 --- /dev/null +++ b/pkg/xscript/engine/sourcemap/consumer.go @@ -0,0 +1,258 @@ +package sourcemap + +import ( + "encoding/json" + "fmt" + "net/url" + "path" + "sort" +) + +type sourceMap struct { + Version int `json:"version"` + File string `json:"file"` + SourceRoot string `json:"sourceRoot"` + Sources []string `json:"sources"` + SourcesContent []string `json:"sourcesContent"` + Names []json.RawMessage `json:"names,string"` + Mappings string `json:"mappings"` + + mappings []mapping +} + +type v3 struct { + sourceMap + Sections []section `json:"sections"` +} + +func (m *sourceMap) parse(sourcemapURL string) error { + if err := checkVersion(m.Version); err != nil { + return err + } + + var sourceRootURL *url.URL + if m.SourceRoot != "" { + u, err := url.Parse(m.SourceRoot) + if err != nil { + return err + } + if u.IsAbs() { + sourceRootURL = u + } + } else if sourcemapURL != "" { + u, err := url.Parse(sourcemapURL) + if err != nil { + return err + } + if u.IsAbs() { + u.Path = path.Dir(u.Path) + sourceRootURL = u + } + } + + for i, src := range m.Sources { + m.Sources[i] = m.absSource(sourceRootURL, src) + } + + mappings, err := parseMappings(m.Mappings) + if err != nil { + return err + } + + m.mappings = mappings + // Free memory. + m.Mappings = "" + + return nil +} + +func (m *sourceMap) absSource(root *url.URL, source string) string { + if path.IsAbs(source) { + return source + } + + if u, err := url.Parse(source); err == nil && u.IsAbs() { + return source + } + + if root != nil { + u := *root + u.Path = path.Join(u.Path, source) + return u.String() + } + + if m.SourceRoot != "" { + return path.Join(m.SourceRoot, source) + } + + return source +} + +func (m *sourceMap) name(idx int) string { + if idx >= len(m.Names) { + return "" + } + + raw := m.Names[idx] + if len(raw) == 0 { + return "" + } + + if raw[0] == '"' && raw[len(raw)-1] == '"' { + var str string + if err := json.Unmarshal(raw, &str); err == nil { + return str + } + } + + return string(raw) +} + +type section struct { + Offset struct { + Line int `json:"line"` + Column int `json:"column"` + } `json:"offset"` + Map *sourceMap `json:"map"` +} + +type Consumer struct { + sourcemapURL string + file string + sections []section +} + +func Parse(sourcemapURL string, b []byte) (*Consumer, error) { + v3 := new(v3) + err := json.Unmarshal(b, v3) + if err != nil { + return nil, err + } + + if err := checkVersion(v3.Version); err != nil { + return nil, err + } + + if len(v3.Sections) == 0 { + v3.Sections = append(v3.Sections, section{ + Map: &v3.sourceMap, + }) + } + + for _, s := range v3.Sections { + err := s.Map.parse(sourcemapURL) + if err != nil { + return nil, err + } + } + + reverse(v3.Sections) + return &Consumer{ + sourcemapURL: sourcemapURL, + file: v3.File, + sections: v3.Sections, + }, nil +} + +func (c *Consumer) SourcemapURL() string { + return c.sourcemapURL +} + +// File returns an optional name of the generated code +// that this source map is associated with. +func (c *Consumer) File() string { + return c.file +} + +// Source returns the original source, name, line, and column information +// for the generated source's line and column positions. +func (c *Consumer) Source( + genLine, genColumn int, +) (source, name string, line, column int, ok bool) { + for i := range c.sections { + s := &c.sections[i] + if s.Offset.Line < genLine || + (s.Offset.Line+1 == genLine && s.Offset.Column <= genColumn) { + genLine -= s.Offset.Line + genColumn -= s.Offset.Column + return c.source(s.Map, genLine, genColumn) + } + } + return +} + +func (c *Consumer) source( + m *sourceMap, genLine, genColumn int, +) (source, name string, line, column int, ok bool) { + i := sort.Search(len(m.mappings), func(i int) bool { + m := &m.mappings[i] + if int(m.genLine) == genLine { + return int(m.genColumn) >= genColumn + } + return int(m.genLine) >= genLine + }) + + var match *mapping + // Mapping not found + if i == len(m.mappings) { + // lets see if the line is correct but the column is bigger + match = &m.mappings[i-1] + if int(match.genLine) != genLine { + return + } + } else { + match = &m.mappings[i] + + // Fuzzy match. + if int(match.genLine) > genLine || int(match.genColumn) > genColumn { + if i == 0 { + return + } + match = &m.mappings[i-1] + } + } + + if match.sourcesInd >= 0 { + source = m.Sources[match.sourcesInd] + } + if match.namesInd >= 0 { + name = m.name(int(match.namesInd)) + } + line = int(match.sourceLine) + column = int(match.sourceColumn) + ok = true + return +} + +// SourceContent returns the original source content for the source. +func (c *Consumer) SourceContent(source string) string { + for i := range c.sections { + s := &c.sections[i] + for i, src := range s.Map.Sources { + if src == source { + if i < len(s.Map.SourcesContent) { + return s.Map.SourcesContent[i] + } + break + } + } + } + return "" +} + +func checkVersion(version int) error { + if version == 3 || version == 0 { + return nil + } + return fmt.Errorf( + "sourcemap: got version=%d, but only 3rd version is supported", + version, + ) +} + +func reverse(ss []section) { + last := len(ss) - 1 + for i := 0; i < len(ss)/2; i++ { + ss[i], ss[last-i] = ss[last-i], ss[i] + } +} diff --git a/pkg/xscript/engine/sourcemap/consumer_test.go b/pkg/xscript/engine/sourcemap/consumer_test.go new file mode 100644 index 0000000..9c8a85f --- /dev/null +++ b/pkg/xscript/engine/sourcemap/consumer_test.go @@ -0,0 +1,285 @@ +package sourcemap_test + +import ( + "encoding/json" + "fmt" + "io/ioutil" + "net/http" + "strings" + "testing" + + "pandax/pkg/xscript/engine/sourcemap" +) + +const jqSourceMapURL = "http://code.jquery.com/jquery-2.0.3.min.map" + +var jqSourceMapBytes []byte + +func init() { + resp, err := http.Get(jqSourceMapURL) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + jqSourceMapBytes, err = ioutil.ReadAll(resp.Body) + if err != nil { + panic(err) + } +} + +type sourceMapTest struct { + genLine int + genColumn int + wantedSource string + wantedName string + wantedLine int + wantedColumn int +} + +func (test *sourceMapTest) String() string { + return fmt.Sprintf("line=%d col=%d in file=%s", test.genLine, test.genColumn, test.wantedSource) +} + +func (test *sourceMapTest) assert(t *testing.T, smap *sourcemap.Consumer) { + source, name, line, col, ok := smap.Source(test.genLine, test.genColumn) + if !ok { + if test.wantedSource == "" && + test.wantedName == "" && + test.wantedLine == 0 && + test.wantedColumn == 0 { + return + } + t.Fatalf("Source not found for %s", test) + } + if source != test.wantedSource { + t.Fatalf("file: got %q, wanted %q (%s)", source, test.wantedSource, test) + } + if name != test.wantedName { + t.Fatalf("func: got %q, wanted %q (%s)", name, test.wantedName, test) + } + if line != test.wantedLine { + t.Fatalf("line: got %d, wanted %d (%s)", line, test.wantedLine, test) + } + if col != test.wantedColumn { + t.Fatalf("column: got %d, wanted %d (%s)", col, test.wantedColumn, test) + } +} + +func TestSourceMap(t *testing.T) { + testSourceMap(t, sourceMapJSON) +} + +func TestIndexedSourceMap(t *testing.T) { + testSourceMap(t, indexedSourceMapJSON) +} + +func testSourceMap(t *testing.T, json string) { + smap, err := sourcemap.Parse("", []byte(json)) + if err != nil { + t.Fatal(err) + } + + tests := []sourceMapTest{ + {1, 1, "/the/root/one.js", "", 1, 1}, + {1, 5, "/the/root/one.js", "", 1, 5}, + {1, 9, "/the/root/one.js", "", 1, 11}, + {1, 18, "/the/root/one.js", "bar", 1, 21}, + {1, 21, "/the/root/one.js", "", 2, 3}, + {1, 28, "/the/root/one.js", "baz", 2, 10}, + {1, 32, "/the/root/one.js", "bar", 2, 14}, + + {2, 1, "/the/root/two.js", "", 1, 1}, + {2, 5, "/the/root/two.js", "", 1, 5}, + {2, 9, "/the/root/two.js", "", 1, 11}, + {2, 18, "/the/root/two.js", "n", 1, 21}, + {2, 21, "/the/root/two.js", "", 2, 3}, + {2, 28, "/the/root/two.js", "n", 2, 10}, + + // line correct, column bigger than last mapping + {2, 29, "/the/root/two.js", "n", 2, 10}, + + // Fuzzy match. + {1, 20, "/the/root/one.js", "bar", 1, 21}, + {1, 30, "/the/root/one.js", "baz", 2, 10}, + {2, 12, "/the/root/two.js", "", 1, 11}, + } + for i := range tests { + tests[i].assert(t, smap) + } + + content := smap.SourceContent("/the/root/one.js") + if content != oneSourceContent { + t.Fatalf("%q != %q", content, oneSourceContent) + } + + content = smap.SourceContent("/the/root/two.js") + if content != twoSourceContent { + t.Fatalf("%q != %q", content, twoSourceContent) + } + + _, _, _, _, ok := smap.Source(3, 0) + if ok { + t.Fatal("source must not exist") + } +} + +func TestSourceRootURL(t *testing.T) { + jsonStr := sourceMapJSON + jsonStr = strings.Replace(jsonStr, "/the/root", "http://the/root", 1) + jsonStr = strings.Replace(jsonStr, "one.js", "../one.js", 1) + + smap, err := sourcemap.Parse("", []byte(jsonStr)) + if err != nil { + t.Fatal(err) + } + + tests := []*sourceMapTest{ + {1, 1, "http://the/one.js", "", 1, 1}, + {2, 1, "http://the/root/two.js", "", 1, 1}, + } + for _, test := range tests { + test.assert(t, smap) + } +} + +func TestEmptySourceRootURL(t *testing.T) { + jsonStr := sourceMapJSON + jsonStr = strings.Replace(jsonStr, "/the/root", "", 1) + jsonStr = strings.Replace(jsonStr, "one.js", "../one.js", 1) + + smap, err := sourcemap.Parse("http://the/root/app.min.map", []byte(jsonStr)) + if err != nil { + t.Fatal(err) + } + + tests := []*sourceMapTest{ + {1, 1, "http://the/one.js", "", 1, 1}, + {2, 1, "http://the/root/two.js", "", 1, 1}, + } + for _, test := range tests { + test.assert(t, smap) + } +} + +func TestAbsSourceURL(t *testing.T) { + jsonStr := sourceMapJSON + jsonStr = strings.Replace(jsonStr, "/the/root", "", 1) + jsonStr = strings.Replace(jsonStr, "one.js", "http://the/root/one.js", 1) + jsonStr = strings.Replace(jsonStr, "two.js", "/another/root/two.js", 1) + + testAbsSourceURL(t, "", jsonStr) + testAbsSourceURL(t, "http://path/to/map", jsonStr) +} + +func testAbsSourceURL(t *testing.T, mapURL, jsonStr string) { + smap, err := sourcemap.Parse(mapURL, []byte(jsonStr)) + if err != nil { + t.Fatal(err) + } + + tests := []*sourceMapTest{ + {1, 1, "http://the/root/one.js", "", 1, 1}, + {2, 1, "/another/root/two.js", "", 1, 1}, + } + for _, test := range tests { + test.assert(t, smap) + } +} + +func TestJQuerySourceMap(t *testing.T) { + smap, err := sourcemap.Parse(jqSourceMapURL, jqSourceMapBytes) + if err != nil { + t.Fatal(err) + } + + tests := []*sourceMapTest{ + {1, 1, "", "", 0, 0}, + {4, 0, "", "", 0, 0}, + {4, 1, "http://code.jquery.com/jquery-2.0.3.js", "", 14, 0}, + {4, 10, "http://code.jquery.com/jquery-2.0.3.js", "window", 14, 11}, + {5, 6789, "http://code.jquery.com/jquery-2.0.3.js", "apply", 4360, 27}, + {5, 10006, "http://code.jquery.com/jquery-2.0.3.js", "apply", 4676, 8}, + {4, 553, "http://code.jquery.com/jquery-2.0.3.js", "ready", 93, 9}, + {999999, 0, "", "", 0, 0}, + } + for _, test := range tests { + test.assert(t, smap) + } +} + +// https://github.com/mozilla/source-map/blob/master/test/util.js +// +// This is a test mapping which maps functions from two different files +// (one.js and two.js) to a minified generated source. +// +// Here is one.js: +// +// ONE.foo = function (bar) { +// return baz(bar); +// }; +// +// Here is two.js: +// +// TWO.inc = function (n) { +// return n + 1; +// }; +// +// And here is the generated code (min.js): +// +// ONE.foo=function(a){return baz(a);}; +// TWO.inc=function(a){return a+1;}; + +const genCode = `exports.testGeneratedCode = "ONE.foo=function(a){return baz(a);}; +TWO.inc=function(a){return a+1;};` + +var oneSourceContent = `ONE.foo = function (bar) { + return baz(bar); +};` + +var twoSourceContent = `TWO.inc = function (n) { + return n + 1; +};` + +var sourceMapJSON = `{ + "version": 3, + "file": "min.js", + "sources": ["one.js", "two.js"], + "sourcesContent": ` + j([]string{oneSourceContent, twoSourceContent}) + `, + "sourceRoot": "/the/root", + "names": ["bar", "baz", "n"], + "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID;CCDb,IAAI,IAAM,SAAUE,GAClB,OAAOA" +}` + +func j(v any) string { + b, _ := json.Marshal(v) + return string(b) +} + +var indexedSourceMapJSON = `{ + "version": 3, + "file": "min.js", + "sections": [{ + "offset": {"line": 0, "column": 0}, + "map": { + "version": 3, + "file": "min.js", + "sources": ["one.js"], + "sourcesContent": ` + j([]string{oneSourceContent}) + `, + "sourceRoot": "/the/root", + "names": ["bar", "baz"], + "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOC,IAAID" + } + }, { + "offset": {"line": 1, "column": 0}, + "map": { + "version": 3, + "file": "min.js", + "sources": ["two.js"], + "sourcesContent": ` + j([]string{twoSourceContent}) + `, + "sourceRoot": "/the/root", + "names": ["n"], + "mappings": "CAAC,IAAI,IAAM,SAAUA,GAClB,OAAOA" + } + }] +}` diff --git a/pkg/xscript/engine/sourcemap/example_test.go b/pkg/xscript/engine/sourcemap/example_test.go new file mode 100644 index 0000000..e8f7b38 --- /dev/null +++ b/pkg/xscript/engine/sourcemap/example_test.go @@ -0,0 +1,33 @@ +package sourcemap_test + +import ( + "fmt" + "io/ioutil" + "net/http" + + "pandax/pkg/xscript/engine/sourcemap" +) + +func ExampleParse() { + mapURL := "http://code.jquery.com/jquery-2.0.3.min.map" + resp, err := http.Get(mapURL) + if err != nil { + panic(err) + } + defer resp.Body.Close() + + b, err := ioutil.ReadAll(resp.Body) + if err != nil { + panic(err) + } + + smap, err := sourcemap.Parse(mapURL, b) + if err != nil { + panic(err) + } + + line, column := 5, 6789 + file, fn, line, col, ok := smap.Source(line, column) + fmt.Println(file, fn, line, col, ok) + // Output: http://code.jquery.com/jquery-2.0.3.js apply 4360 27 true +} diff --git a/pkg/xscript/engine/sourcemap/internal/base64vlq/base64vlq.go b/pkg/xscript/engine/sourcemap/internal/base64vlq/base64vlq.go new file mode 100644 index 0000000..4804f5a --- /dev/null +++ b/pkg/xscript/engine/sourcemap/internal/base64vlq/base64vlq.go @@ -0,0 +1,90 @@ +package base64vlq + +import "io" + +const encodeStd = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/" + +const ( + vlqBaseShift = 5 + vlqBase = 1 << vlqBaseShift + vlqBaseMask = vlqBase - 1 + vlqSignBit = 1 + vlqContinuationBit = vlqBase +) + +var decodeMap [256]byte + +func init() { + for i := 0; i < len(encodeStd); i++ { + decodeMap[encodeStd[i]] = byte(i) + } +} + +func toVLQSigned(n int32) int32 { + if n < 0 { + return -n<<1 + 1 + } + return n << 1 +} + +func fromVLQSigned(n int32) int32 { + isNeg := n&vlqSignBit != 0 + n >>= 1 + if isNeg { + return -n + } + return n +} + +type Encoder struct { + w io.ByteWriter +} + +func NewEncoder(w io.ByteWriter) *Encoder { + return &Encoder{ + w: w, + } +} + +func (enc Encoder) Encode(n int32) error { + n = toVLQSigned(n) + for digit := int32(vlqContinuationBit); digit&vlqContinuationBit != 0; { + digit = n & vlqBaseMask + n >>= vlqBaseShift + if n > 0 { + digit |= vlqContinuationBit + } + + err := enc.w.WriteByte(encodeStd[digit]) + if err != nil { + return err + } + } + return nil +} + +type Decoder struct { + r io.ByteReader +} + +func NewDecoder(r io.ByteReader) Decoder { + return Decoder{ + r: r, + } +} + +func (dec Decoder) Decode() (n int32, err error) { + shift := uint(0) + for continuation := true; continuation; { + c, err := dec.r.ReadByte() + if err != nil { + return 0, err + } + + c = decodeMap[c] + continuation = c&vlqContinuationBit != 0 + n += int32(c&vlqBaseMask) << shift + shift += vlqBaseShift + } + return fromVLQSigned(n), nil +} diff --git a/pkg/xscript/engine/sourcemap/internal/base64vlq/base64vlq_test.go b/pkg/xscript/engine/sourcemap/internal/base64vlq/base64vlq_test.go new file mode 100644 index 0000000..953b378 --- /dev/null +++ b/pkg/xscript/engine/sourcemap/internal/base64vlq/base64vlq_test.go @@ -0,0 +1,63 @@ +package base64vlq_test + +import ( + "bytes" + "testing" + + "pandax/pkg/xscript/engine/sourcemap/internal/base64vlq" +) + +func TestEncodeDecode(t *testing.T) { + buf := new(bytes.Buffer) + enc := base64vlq.NewEncoder(buf) + dec := base64vlq.NewDecoder(buf) + + for n := int32(-1000); n < 1000; n++ { + if err := enc.Encode(n); err != nil { + panic(err) + } + } + + for n := int32(-1000); n < 1000; n++ { + nn, err := dec.Decode() + if err != nil { + panic(err) + } + + if nn != n { + t.Errorf("%d != %d", nn, n) + } + } +} + +func BenchmarkEncode(b *testing.B) { + buf := new(bytes.Buffer) + enc := base64vlq.NewEncoder(buf) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if err := enc.Encode(1000); err != nil { + panic(err) + } + } +} + +func BenchmarkEncodeDecode(b *testing.B) { + buf := new(bytes.Buffer) + enc := base64vlq.NewEncoder(buf) + dec := base64vlq.NewDecoder(buf) + + b.ResetTimer() + + for i := 0; i < b.N; i++ { + if err := enc.Encode(1000); err != nil { + panic(err) + } + + _, err := dec.Decode() + if err != nil { + panic(err) + } + } +} diff --git a/pkg/xscript/engine/sourcemap/mappings.go b/pkg/xscript/engine/sourcemap/mappings.go new file mode 100644 index 0000000..a0fe7ae --- /dev/null +++ b/pkg/xscript/engine/sourcemap/mappings.go @@ -0,0 +1,164 @@ +package sourcemap + +import ( + "errors" + "io" + "strings" + + "pandax/pkg/xscript/engine/sourcemap/internal/base64vlq" +) + +type fn func(m *mappings) (fn, error) + +type mapping struct { + genLine int32 + genColumn int32 + sourcesInd int32 + sourceLine int32 + sourceColumn int32 + namesInd int32 +} + +type mappings struct { + rd *strings.Reader + dec base64vlq.Decoder + + hasValue bool + hasName bool + value mapping + + values []mapping +} + +func parseMappings(s string) ([]mapping, error) { + if s == "" { + return nil, errors.New("sourcemap: mappings are empty") + } + + rd := strings.NewReader(s) + m := &mappings{ + rd: rd, + dec: base64vlq.NewDecoder(rd), + + values: make([]mapping, 0, mappingsNumber(s)), + } + m.value.genLine = 1 + m.value.sourceLine = 1 + + err := m.parse() + if err != nil { + return nil, err + } + + values := m.values + m.values = nil + return values, nil +} + +func mappingsNumber(s string) int { + return strings.Count(s, ",") + strings.Count(s, ";") +} + +func (m *mappings) parse() error { + next := parseGenCol + for { + c, err := m.rd.ReadByte() + if err == io.EOF { + m.pushValue() + return nil + } + if err != nil { + return err + } + + switch c { + case ',': + m.pushValue() + next = parseGenCol + case ';': + m.pushValue() + + m.value.genLine++ + m.value.genColumn = 0 + + next = parseGenCol + default: + err := m.rd.UnreadByte() + if err != nil { + return err + } + + next, err = next(m) + if err != nil { + return err + } + m.hasValue = true + } + } +} + +func parseGenCol(m *mappings) (fn, error) { + n, err := m.dec.Decode() + if err != nil { + return nil, err + } + m.value.genColumn += n + return parseSourcesInd, nil +} + +func parseSourcesInd(m *mappings) (fn, error) { + n, err := m.dec.Decode() + if err != nil { + return nil, err + } + m.value.sourcesInd += n + return parseSourceLine, nil +} + +func parseSourceLine(m *mappings) (fn, error) { + n, err := m.dec.Decode() + if err != nil { + return nil, err + } + m.value.sourceLine += n + return parseSourceCol, nil +} + +func parseSourceCol(m *mappings) (fn, error) { + n, err := m.dec.Decode() + if err != nil { + return nil, err + } + m.value.sourceColumn += n + return parseNamesInd, nil +} + +func parseNamesInd(m *mappings) (fn, error) { + n, err := m.dec.Decode() + if err != nil { + return nil, err + } + m.hasName = true + m.value.namesInd += n + return parseGenCol, nil +} + +func (m *mappings) pushValue() { + if !m.hasValue { + return + } + m.hasValue = false + if m.hasName { + m.values = append(m.values, m.value) + m.hasName = false + } else { + m.values = append(m.values, mapping{ + genLine: m.value.genLine, + genColumn: m.value.genColumn, + sourcesInd: m.value.sourcesInd, + sourceLine: m.value.sourceLine, + sourceColumn: m.value.sourceColumn, + namesInd: -1, + }) + } +} diff --git a/pkg/xscript/engine/sourcemap/mappings_test.go b/pkg/xscript/engine/sourcemap/mappings_test.go new file mode 100644 index 0000000..f35cb61 --- /dev/null +++ b/pkg/xscript/engine/sourcemap/mappings_test.go @@ -0,0 +1,32 @@ +package sourcemap + +import ( + "reflect" + "testing" +) + +func TestParseMappings(t *testing.T) { + t.Parallel() + cases := map[string][]mapping{ + ";;;;;;kBAEe,YAAY,CAC1B,C;;AAHD": { + {genLine: 7, genColumn: 18, sourceLine: 3, sourceColumn: 15, namesInd: -1}, + {genLine: 7, genColumn: 30, sourceLine: 3, sourceColumn: 27, namesInd: -1}, + {genLine: 7, genColumn: 31, sourceLine: 4, sourceColumn: 1, namesInd: -1}, + {genLine: 7, genColumn: 32, sourceLine: 4, sourceColumn: 1, namesInd: -1}, + {genLine: 9, genColumn: 0, sourceLine: 1, sourceColumn: 0, namesInd: -1}, + }, + } + for k, c := range cases { + k, c := k, c + t.Run(k, func(t *testing.T) { + t.Parallel() + v, err := parseMappings(k) + if err != nil { + t.Fatalf("got error %s", err) + } + if !reflect.DeepEqual(v, c) { + t.Fatalf("expected %v got %v", c, v) + } + }) + } +} diff --git a/pkg/xscript/engine/staticcheck.conf b/pkg/xscript/engine/staticcheck.conf new file mode 100644 index 0000000..2441b9d --- /dev/null +++ b/pkg/xscript/engine/staticcheck.conf @@ -0,0 +1 @@ +checks = ["all", "-ST1000", "-ST1003", "-ST1005", "-ST1006", "-ST1012", "-ST1021", "-ST1020", "-ST1008"] diff --git a/pkg/xscript/engine/string.go b/pkg/xscript/engine/string.go new file mode 100644 index 0000000..62a88f1 --- /dev/null +++ b/pkg/xscript/engine/string.go @@ -0,0 +1,363 @@ +package engine + +import ( + "io" + "strconv" + "strings" + "unicode/utf8" + + "pandax/pkg/xscript/engine/unistring" +) + +const ( + __proto__ = "__proto__" +) + +var ( + stringTrue String = asciiString("true") + stringFalse String = asciiString("false") + stringNull String = asciiString("null") + stringUndefined String = asciiString("undefined") + stringObjectC String = asciiString("object") + stringFunction String = asciiString("function") + stringBoolean String = asciiString("boolean") + stringString String = asciiString("string") + stringSymbol String = asciiString("symbol") + stringNumber String = asciiString("number") + stringNaN String = asciiString("NaN") + stringInfinity = asciiString("Infinity") + stringNegInfinity = asciiString("-Infinity") + stringBound_ String = asciiString("bound ") + stringEmpty String = asciiString("") + + stringError String = asciiString("Error") + stringAggregateError String = asciiString("AggregateError") + stringTypeError String = asciiString("TypeError") + stringReferenceError String = asciiString("ReferenceError") + stringSyntaxError String = asciiString("SyntaxError") + stringRangeError String = asciiString("RangeError") + stringEvalError String = asciiString("EvalError") + stringURIError String = asciiString("URIError") + stringGoError String = asciiString("GoError") + + stringObjectNull String = asciiString("[object Null]") + stringObjectUndefined String = asciiString("[object Undefined]") + stringInvalidDate String = asciiString("Invalid Date") +) + +type utf16Reader interface { + readChar() (c uint16, err error) +} + +// String represents an ECMAScript string Value. Its internal representation depends on the contents of the +// string, but in any case it is capable of holding any UTF-16 string, either valid or invalid. +// Instances of this type, as any other primitive values, are goroutine-safe and can be passed between runtimes. +// Strings can be created using Runtime.ToValue(goString) or StringFromUTF16. +type String interface { + Value + CharAt(int) uint16 + Length() int + Concat(String) String + Substring(start, end int) String + CompareTo(String) int + Reader() io.RuneReader + utf16Reader() utf16Reader + utf16RuneReader() io.RuneReader + utf16Runes() []rune + index(String, int) int + lastIndex(String, int) int + toLower() String + toUpper() String + toTrimmedUTF8() string +} + +type stringIterObject struct { + baseObject + reader io.RuneReader +} + +func isUTF16FirstSurrogate(c uint16) bool { + return c >= 0xD800 && c <= 0xDBFF +} + +func isUTF16SecondSurrogate(c uint16) bool { + return c >= 0xDC00 && c <= 0xDFFF +} + +func (si *stringIterObject) next() Value { + if si.reader == nil { + return si.val.runtime.createIterResultObject(_undefined, true) + } + r, _, err := si.reader.ReadRune() + if err == io.EOF { + si.reader = nil + return si.val.runtime.createIterResultObject(_undefined, true) + } + return si.val.runtime.createIterResultObject(stringFromRune(r), false) +} + +func stringFromRune(r rune) String { + if r < utf8.RuneSelf { + var sb strings.Builder + sb.WriteByte(byte(r)) + return asciiString(sb.String()) + } + var sb unicodeStringBuilder + sb.WriteRune(r) + return sb.String() +} + +func (r *Runtime) createStringIterator(s String) Value { + o := &Object{runtime: r} + + si := &stringIterObject{ + reader: &lenientUtf16Decoder{utf16Reader: s.utf16Reader()}, + } + si.class = classObject + si.val = o + si.extensible = true + o.self = si + si.prototype = r.getStringIteratorPrototype() + si.init() + + return o +} + +type stringObject struct { + baseObject + value String + length int + lengthProp valueProperty +} + +func newStringValue(s string) String { + if u := unistring.Scan(s); u != nil { + return unicodeString(u) + } + return asciiString(s) +} + +func stringValueFromRaw(raw unistring.String) String { + if b := raw.AsUtf16(); b != nil { + return unicodeString(b) + } + return asciiString(raw) +} + +func (s *stringObject) init() { + s.baseObject.init() + s.setLength() +} + +func (s *stringObject) setLength() { + if s.value != nil { + s.length = s.value.Length() + } + s.lengthProp.value = intToValue(int64(s.length)) + s._put("length", &s.lengthProp) +} + +func (s *stringObject) getStr(name unistring.String, receiver Value) Value { + if i := strToGoIdx(name); i >= 0 && i < s.length { + return s._getIdx(i) + } + return s.baseObject.getStr(name, receiver) +} + +func (s *stringObject) getIdx(idx valueInt, receiver Value) Value { + i := int(idx) + if i >= 0 && i < s.length { + return s._getIdx(i) + } + return s.baseObject.getStr(idx.string(), receiver) +} + +func (s *stringObject) getOwnPropStr(name unistring.String) Value { + if i := strToGoIdx(name); i >= 0 && i < s.length { + val := s._getIdx(i) + return &valueProperty{ + value: val, + enumerable: true, + } + } + + return s.baseObject.getOwnPropStr(name) +} + +func (s *stringObject) getOwnPropIdx(idx valueInt) Value { + i := int64(idx) + if i >= 0 { + if i < int64(s.length) { + val := s._getIdx(int(i)) + return &valueProperty{ + value: val, + enumerable: true, + } + } + return nil + } + + return s.baseObject.getOwnPropStr(idx.string()) +} + +func (s *stringObject) _getIdx(idx int) Value { + return s.value.Substring(idx, idx+1) +} + +func (s *stringObject) setOwnStr(name unistring.String, val Value, throw bool) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { + s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i) + return false + } + + return s.baseObject.setOwnStr(name, val, throw) +} + +func (s *stringObject) setOwnIdx(idx valueInt, val Value, throw bool) bool { + i := int64(idx) + if i >= 0 && i < int64(s.length) { + s.val.runtime.typeErrorResult(throw, "Cannot assign to read only property '%d' of a String", i) + return false + } + + return s.baseObject.setOwnStr(idx.string(), val, throw) +} + +func (s *stringObject) setForeignStr(name unistring.String, val, receiver Value, throw bool) (bool, bool) { + return s._setForeignStr(name, s.getOwnPropStr(name), val, receiver, throw) +} + +func (s *stringObject) setForeignIdx(idx valueInt, val, receiver Value, throw bool) (bool, bool) { + return s._setForeignIdx(idx, s.getOwnPropIdx(idx), val, receiver, throw) +} + +func (s *stringObject) defineOwnPropertyStr(name unistring.String, descr PropertyDescriptor, throw bool) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { + _, ok := s._defineOwnProperty(name, &valueProperty{enumerable: true}, descr, throw) + return ok + } + + return s.baseObject.defineOwnPropertyStr(name, descr, throw) +} + +func (s *stringObject) defineOwnPropertyIdx(idx valueInt, descr PropertyDescriptor, throw bool) bool { + i := int64(idx) + if i >= 0 && i < int64(s.length) { + s.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", i) + return false + } + + return s.baseObject.defineOwnPropertyStr(idx.string(), descr, throw) +} + +type stringPropIter struct { + str String // separate, because obj can be the singleton + obj *stringObject + idx, length int +} + +func (i *stringPropIter) next() (propIterItem, iterNextFunc) { + if i.idx < i.length { + name := strconv.Itoa(i.idx) + i.idx++ + return propIterItem{name: asciiString(name), enumerable: _ENUM_TRUE}, i.next + } + + return i.obj.baseObject.iterateStringKeys()() +} + +func (s *stringObject) iterateStringKeys() iterNextFunc { + return (&stringPropIter{ + str: s.value, + obj: s, + length: s.length, + }).next +} + +func (s *stringObject) stringKeys(all bool, accum []Value) []Value { + for i := 0; i < s.length; i++ { + accum = append(accum, asciiString(strconv.Itoa(i))) + } + + return s.baseObject.stringKeys(all, accum) +} + +func (s *stringObject) deleteStr(name unistring.String, throw bool) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { + s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i) + return false + } + + return s.baseObject.deleteStr(name, throw) +} + +func (s *stringObject) deleteIdx(idx valueInt, throw bool) bool { + i := int64(idx) + if i >= 0 && i < int64(s.length) { + s.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of a String", i) + return false + } + + return s.baseObject.deleteStr(idx.string(), throw) +} + +func (s *stringObject) hasOwnPropertyStr(name unistring.String) bool { + if i := strToGoIdx(name); i >= 0 && i < s.length { + return true + } + return s.baseObject.hasOwnPropertyStr(name) +} + +func (s *stringObject) hasOwnPropertyIdx(idx valueInt) bool { + i := int64(idx) + if i >= 0 && i < int64(s.length) { + return true + } + return s.baseObject.hasOwnPropertyStr(idx.string()) +} + +func devirtualizeString(s String) (asciiString, unicodeString) { + switch s := s.(type) { + case asciiString: + return s, nil + case unicodeString: + return "", s + case *importedString: + s.ensureScanned() + if s.u != nil { + return "", s.u + } + return asciiString(s.s), nil + default: + panic(unknownStringTypeErr(s)) + } +} + +func unknownStringTypeErr(v Value) any { + return newTypeError("Internal bug: unknown string type: %T", v) +} + +// StringFromUTF16 creates a string value from an array of UTF-16 code units. The result is a copy, so the initial +// slice can be modified after calling this function (but it must not be modified while the function is running). +// No validation of any kind is performed. +func StringFromUTF16(chars []uint16) String { + isAscii := true + for _, c := range chars { + if c >= utf8.RuneSelf { + isAscii = false + break + } + } + if isAscii { + var sb strings.Builder + sb.Grow(len(chars)) + for _, c := range chars { + sb.WriteByte(byte(c)) + } + return asciiString(sb.String()) + } + buf := make([]uint16, len(chars)+1) + buf[0] = unistring.BOM + copy(buf[1:], chars) + return unicodeString(buf) +} diff --git a/pkg/xscript/engine/string_ascii.go b/pkg/xscript/engine/string_ascii.go new file mode 100644 index 0000000..2f79abc --- /dev/null +++ b/pkg/xscript/engine/string_ascii.go @@ -0,0 +1,364 @@ +package engine + +import ( + "hash/maphash" + "io" + "math" + "reflect" + "strconv" + "strings" + + "pandax/pkg/xscript/engine/unistring" +) + +type asciiString string + +type asciiRuneReader struct { + s asciiString + pos int +} + +func (rr *asciiRuneReader) ReadRune() (r rune, size int, err error) { + if rr.pos < len(rr.s) { + r = rune(rr.s[rr.pos]) + size = 1 + rr.pos++ + } else { + err = io.EOF + } + return +} + +type asciiUtf16Reader struct { + s asciiString + pos int +} + +func (rr *asciiUtf16Reader) readChar() (c uint16, err error) { + if rr.pos < len(rr.s) { + c = uint16(rr.s[rr.pos]) + rr.pos++ + } else { + err = io.EOF + } + return +} + +func (rr *asciiUtf16Reader) ReadRune() (r rune, size int, err error) { + if rr.pos < len(rr.s) { + r = rune(rr.s[rr.pos]) + rr.pos++ + size = 1 + } else { + err = io.EOF + } + return +} + +func (s asciiString) Reader() io.RuneReader { + return &asciiRuneReader{ + s: s, + } +} + +func (s asciiString) utf16Reader() utf16Reader { + return &asciiUtf16Reader{ + s: s, + } +} + +func (s asciiString) utf16RuneReader() io.RuneReader { + return &asciiUtf16Reader{ + s: s, + } +} + +func (s asciiString) utf16Runes() []rune { + runes := make([]rune, len(s)) + for i := 0; i < len(s); i++ { + runes[i] = rune(s[i]) + } + return runes +} + +// ss must be trimmed +func stringToInt(ss string) (int64, error) { + if ss == "" { + return 0, nil + } + if ss == "-0" { + return 0, strconv.ErrSyntax + } + if len(ss) > 2 { + switch ss[:2] { + case "0x", "0X": + return strconv.ParseInt(ss[2:], 16, 64) + case "0b", "0B": + return strconv.ParseInt(ss[2:], 2, 64) + case "0o", "0O": + return strconv.ParseInt(ss[2:], 8, 64) + } + } + return strconv.ParseInt(ss, 10, 64) +} + +func (s asciiString) _toInt() (int64, error) { + return stringToInt(strings.TrimSpace(string(s))) +} + +func isRangeErr(err error) bool { + if err, ok := err.(*strconv.NumError); ok { + return err.Err == strconv.ErrRange + } + return false +} + +func (s asciiString) _toFloat() (float64, error) { + ss := strings.TrimSpace(string(s)) + if ss == "" { + return 0, nil + } + if ss == "-0" { + var f float64 + return -f, nil + } + f, err := strconv.ParseFloat(ss, 64) + if isRangeErr(err) { + err = nil + } + return f, err +} + +func (s asciiString) ToInteger() int64 { + if s == "" { + return 0 + } + if s == "Infinity" || s == "+Infinity" { + return math.MaxInt64 + } + if s == "-Infinity" { + return math.MinInt64 + } + i, err := s._toInt() + if err != nil { + f, err := s._toFloat() + if err == nil { + return int64(f) + } + } + return i +} + +func (s asciiString) toString() String { + return s +} + +func (s asciiString) ToString() Value { + return s +} + +func (s asciiString) String() string { + return string(s) +} + +func (s asciiString) ToFloat() float64 { + if s == "" { + return 0 + } + if s == "Infinity" || s == "+Infinity" { + return math.Inf(1) + } + if s == "-Infinity" { + return math.Inf(-1) + } + f, err := s._toFloat() + if err != nil { + i, err := s._toInt() + if err == nil { + return float64(i) + } + f = math.NaN() + } + return f +} + +func (s asciiString) ToBoolean() bool { + return s != "" +} + +func (s asciiString) ToNumber() Value { + if s == "" { + return intToValue(0) + } + if s == "Infinity" || s == "+Infinity" { + return _positiveInf + } + if s == "-Infinity" { + return _negativeInf + } + + if i, err := s._toInt(); err == nil { + return intToValue(i) + } + + if f, err := s._toFloat(); err == nil { + return floatToValue(f) + } + + return _NaN +} + +func (s asciiString) ToObject(r *Runtime) *Object { + return r._newString(s, r.getStringPrototype()) +} + +func (s asciiString) SameAs(other Value) bool { + return s.StrictEquals(other) +} + +func (s asciiString) Equals(other Value) bool { + if s.StrictEquals(other) { + return true + } + + if o, ok := other.(valueInt); ok { + if o1, e := s._toInt(); e == nil { + return o1 == int64(o) + } + return false + } + + if o, ok := other.(valueFloat); ok { + return s.ToFloat() == float64(o) + } + + if o, ok := other.(valueBool); ok { + if o1, e := s._toFloat(); e == nil { + return o1 == o.ToFloat() + } + return false + } + + if o, ok := other.(*Object); ok { + return s.Equals(o.toPrimitive()) + } + return false +} + +func (s asciiString) StrictEquals(other Value) bool { + if otherStr, ok := other.(asciiString); ok { + return s == otherStr + } + if otherStr, ok := other.(*importedString); ok { + if otherStr.u == nil { + return string(s) == otherStr.s + } + } + return false +} + +func (s asciiString) baseObject(r *Runtime) *Object { + ss := r.getStringSingleton() + ss.value = s + ss.setLength() + return ss.val +} + +func (s asciiString) hash(hash *maphash.Hash) uint64 { + _, _ = hash.WriteString(string(s)) + h := hash.Sum64() + hash.Reset() + return h +} + +func (s asciiString) CharAt(idx int) uint16 { + return uint16(s[idx]) +} + +func (s asciiString) Length() int { + return len(s) +} + +func (s asciiString) Concat(other String) String { + a, u := devirtualizeString(other) + if u != nil { + b := make([]uint16, len(s)+len(u)) + b[0] = unistring.BOM + for i := 0; i < len(s); i++ { + b[i+1] = uint16(s[i]) + } + copy(b[len(s)+1:], u[1:]) + return unicodeString(b) + } + return s + a +} + +func (s asciiString) Substring(start, end int) String { + return s[start:end] +} + +func (s asciiString) CompareTo(other String) int { + switch other := other.(type) { + case asciiString: + return strings.Compare(string(s), string(other)) + case unicodeString: + return strings.Compare(string(s), other.String()) + case *importedString: + return strings.Compare(string(s), other.s) + default: + panic(newTypeError("Internal bug: unknown string type: %T", other)) + } +} + +func (s asciiString) index(substr String, start int) int { + a, u := devirtualizeString(substr) + if u == nil { + if start > len(s) { + return -1 + } + p := strings.Index(string(s[start:]), string(a)) + if p >= 0 { + return p + start + } + } + return -1 +} + +func (s asciiString) lastIndex(substr String, pos int) int { + a, u := devirtualizeString(substr) + if u == nil { + end := pos + len(a) + var ss string + if end > len(s) { + ss = string(s) + } else { + ss = string(s[:end]) + } + return strings.LastIndex(ss, string(a)) + } + return -1 +} + +func (s asciiString) toLower() String { + return asciiString(strings.ToLower(string(s))) +} + +func (s asciiString) toUpper() String { + return asciiString(strings.ToUpper(string(s))) +} + +func (s asciiString) toTrimmedUTF8() string { + return strings.TrimSpace(string(s)) +} + +func (s asciiString) string() unistring.String { + return unistring.String(s) +} + +func (s asciiString) Export() any { + return string(s) +} + +func (s asciiString) ExportType() reflect.Type { + return reflectTypeString +} diff --git a/pkg/xscript/engine/string_imported.go b/pkg/xscript/engine/string_imported.go new file mode 100644 index 0000000..6fd1db9 --- /dev/null +++ b/pkg/xscript/engine/string_imported.go @@ -0,0 +1,307 @@ +package engine + +import ( + "hash/maphash" + "io" + "math" + "reflect" + "strings" + "unicode/utf16" + "unicode/utf8" + + "pandax/pkg/xscript/engine/parser" + "pandax/pkg/xscript/engine/unistring" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +// Represents a string imported from Go. The idea is to delay the scanning for unicode characters and converting +// to unicodeString until necessary. This way strings that are merely passed through never get scanned which +// saves CPU and memory. +// Currently, importedString is created in 2 cases: Runtime.ToValue() for strings longer than 16 bytes and as a result +// of JSON.stringify() if it may contain unicode characters. More cases could be added in the future. +type importedString struct { + s string + u unicodeString + + scanned bool +} + +func (i *importedString) scan() { + i.u = unistring.Scan(i.s) + i.scanned = true +} + +func (i *importedString) ensureScanned() { + if !i.scanned { + i.scan() + } +} + +func (i *importedString) ToInteger() int64 { + i.ensureScanned() + if i.u != nil { + return 0 + } + return asciiString(i.s).ToInteger() +} + +func (i *importedString) toString() String { + return i +} + +func (i *importedString) string() unistring.String { + i.ensureScanned() + if i.u != nil { + return unistring.FromUtf16(i.u) + } + return unistring.String(i.s) +} + +func (i *importedString) ToString() Value { + return i +} + +func (i *importedString) String() string { + return i.s +} + +func (i *importedString) ToFloat() float64 { + i.ensureScanned() + if i.u != nil { + return math.NaN() + } + return asciiString(i.s).ToFloat() +} + +func (i *importedString) ToNumber() Value { + i.ensureScanned() + if i.u != nil { + return i.u.ToNumber() + } + return asciiString(i.s).ToNumber() +} + +func (i *importedString) ToBoolean() bool { + return len(i.s) != 0 +} + +func (i *importedString) ToObject(r *Runtime) *Object { + return r._newString(i, r.getStringPrototype()) +} + +func (i *importedString) SameAs(other Value) bool { + return i.StrictEquals(other) +} + +func (i *importedString) Equals(other Value) bool { + if i.StrictEquals(other) { + return true + } + i.ensureScanned() + if i.u != nil { + return i.u.Equals(other) + } + return asciiString(i.s).Equals(other) +} + +func (i *importedString) StrictEquals(other Value) bool { + switch otherStr := other.(type) { + case asciiString: + if i.u != nil { + return false + } + return i.s == string(otherStr) + case unicodeString: + i.ensureScanned() + if i.u != nil && i.u.equals(otherStr) { + return true + } + case *importedString: + return i.s == otherStr.s + } + return false +} + +func (i *importedString) Export() any { + return i.s +} + +func (i *importedString) ExportType() reflect.Type { + return reflectTypeString +} + +func (i *importedString) baseObject(r *Runtime) *Object { + i.ensureScanned() + if i.u != nil { + return i.u.baseObject(r) + } + return asciiString(i.s).baseObject(r) +} + +func (i *importedString) hash(hasher *maphash.Hash) uint64 { + i.ensureScanned() + if i.u != nil { + return i.u.hash(hasher) + } + return asciiString(i.s).hash(hasher) +} + +func (i *importedString) CharAt(idx int) uint16 { + i.ensureScanned() + if i.u != nil { + return i.u.CharAt(idx) + } + return asciiString(i.s).CharAt(idx) +} + +func (i *importedString) Length() int { + i.ensureScanned() + if i.u != nil { + return i.u.Length() + } + return asciiString(i.s).Length() +} + +func (i *importedString) Concat(v String) String { + if !i.scanned { + if v, ok := v.(*importedString); ok { + if !v.scanned { + return &importedString{s: i.s + v.s} + } + } + i.ensureScanned() + } + if i.u != nil { + return i.u.Concat(v) + } + return asciiString(i.s).Concat(v) +} + +func (i *importedString) Substring(start, end int) String { + i.ensureScanned() + if i.u != nil { + return i.u.Substring(start, end) + } + return asciiString(i.s).Substring(start, end) +} + +func (i *importedString) CompareTo(v String) int { + return strings.Compare(i.s, v.String()) +} + +func (i *importedString) Reader() io.RuneReader { + if i.scanned { + if i.u != nil { + return i.u.Reader() + } + return asciiString(i.s).Reader() + } + return strings.NewReader(i.s) +} + +type stringUtf16Reader struct { + s string + pos int + second uint16 +} + +func (s *stringUtf16Reader) readChar() (c uint16, err error) { + if s.second != 0 { + c, s.second = s.second, 0 + return + } + if s.pos < len(s.s) { + r1, size1 := utf8.DecodeRuneInString(s.s[s.pos:]) + s.pos += size1 + if r1 <= 0xFFFF { + c = uint16(r1) + } else { + first, second := utf16.EncodeRune(r1) + c, s.second = uint16(first), uint16(second) + } + } else { + err = io.EOF + } + return +} + +func (s *stringUtf16Reader) ReadRune() (r rune, size int, err error) { + c, err := s.readChar() + if err != nil { + return + } + r = rune(c) + size = 1 + return +} + +func (i *importedString) utf16Reader() utf16Reader { + if i.scanned { + if i.u != nil { + return i.u.utf16Reader() + } + return asciiString(i.s).utf16Reader() + } + return &stringUtf16Reader{ + s: i.s, + } +} + +func (i *importedString) utf16RuneReader() io.RuneReader { + if i.scanned { + if i.u != nil { + return i.u.utf16RuneReader() + } + return asciiString(i.s).utf16RuneReader() + } + return &stringUtf16Reader{ + s: i.s, + } +} + +func (i *importedString) utf16Runes() []rune { + i.ensureScanned() + if i.u != nil { + return i.u.utf16Runes() + } + return asciiString(i.s).utf16Runes() +} + +func (i *importedString) index(v String, start int) int { + i.ensureScanned() + if i.u != nil { + return i.u.index(v, start) + } + return asciiString(i.s).index(v, start) +} + +func (i *importedString) lastIndex(v String, pos int) int { + i.ensureScanned() + if i.u != nil { + return i.u.lastIndex(v, pos) + } + return asciiString(i.s).lastIndex(v, pos) +} + +func (i *importedString) toLower() String { + i.ensureScanned() + if i.u != nil { + return toLower(i.s) + } + return asciiString(i.s).toLower() +} + +func (i *importedString) toUpper() String { + i.ensureScanned() + if i.u != nil { + caser := cases.Upper(language.Und) + return newStringValue(caser.String(i.s)) + } + return asciiString(i.s).toUpper() +} + +func (i *importedString) toTrimmedUTF8() string { + return strings.Trim(i.s, parser.WhitespaceChars) +} diff --git a/pkg/xscript/engine/string_test.go b/pkg/xscript/engine/string_test.go new file mode 100644 index 0000000..7a4e75f --- /dev/null +++ b/pkg/xscript/engine/string_test.go @@ -0,0 +1,194 @@ +package engine + +import ( + "strings" + "testing" + "unicode/utf16" +) + +func TestStringOOBProperties(t *testing.T) { + const SCRIPT = ` + var string = new String("str"); + + string[4] = 1; + string[4]; + ` + + testScript(SCRIPT, valueInt(1), t) +} + +func TestImportedString(t *testing.T) { + vm := New() + + testUnaryOp := func(a, expr string, result any, t *testing.T) { + v, err := vm.RunString("a => " + expr) + if err != nil { + t.Fatal(err) + } + var fn func(a Value) (Value, error) + err = vm.ExportTo(v, &fn) + if err != nil { + t.Fatal(err) + } + for _, aa := range []Value{newStringValue(a), vm.ToValue(a)} { + res, err := fn(aa) + if err != nil { + t.Fatal(err) + } + if res.Export() != result { + t.Fatalf("%s, a:%v(%T). expected: %v, actual: %v", expr, aa, aa, result, res) + } + } + } + + testBinaryOp := func(a, b, expr string, result any, t *testing.T) { + v, err := vm.RunString("(a, b) => " + expr) + if err != nil { + t.Fatal(err) + } + var fn func(a, b Value) (Value, error) + err = vm.ExportTo(v, &fn) + if err != nil { + t.Fatal(err) + } + for _, aa := range []Value{newStringValue(a), vm.ToValue(a)} { + for _, bb := range []Value{newStringValue(b), vm.ToValue(b)} { + res, err := fn(aa, bb) + if err != nil { + t.Fatal(err) + } + if res.Export() != result { + t.Fatalf("%s, a:%v(%T), b:%v(%T). expected: %v, actual: %v", expr, aa, aa, bb, bb, result, res) + } + } + } + } + + strs := []string{"shortAscii", "longlongAscii1234567890123456789", "short юникод", "long юникод 1234567890 юникод \U0001F600", "юникод", "Ascii", "long", "код"} + indexOfResults := [][]int{ + /* + const strs = ["shortAscii", "longlongAscii1234567890123456789", "short юникод", "long юникод 1234567890 юникод \u{1F600}", "юникод", "Ascii", "long", "код"]; + + strs.forEach(a => { + console.log("{", strs.map(b => a.indexOf(b)).join(", "), "},"); + }); + */ + {0, -1, -1, -1, -1, 5, -1, -1}, + {-1, 0, -1, -1, -1, 8, 0, -1}, + {-1, -1, 0, -1, 6, -1, -1, 9}, + {-1, -1, -1, 0, 5, -1, 0, 8}, + {-1, -1, -1, -1, 0, -1, -1, 3}, + {-1, -1, -1, -1, -1, 0, -1, -1}, + {-1, -1, -1, -1, -1, -1, 0, -1}, + {-1, -1, -1, -1, -1, -1, -1, 0}, + } + + lastIndexOfResults := [][]int{ + /* + strs.forEach(a => { + console.log("{", strs.map(b => a.lastIndexOf(b)).join(", "), "},"); + }); + */ + {0, -1, -1, -1, -1, 5, -1, -1}, + {-1, 0, -1, -1, -1, 8, 4, -1}, + {-1, -1, 0, -1, 6, -1, -1, 9}, + {-1, -1, -1, 0, 23, -1, 0, 26}, + {-1, -1, -1, -1, 0, -1, -1, 3}, + {-1, -1, -1, -1, -1, 0, -1, -1}, + {-1, -1, -1, -1, -1, -1, 0, -1}, + {-1, -1, -1, -1, -1, -1, -1, 0}, + } + + pad := func(s, p string, n int, start bool) string { + if n == 0 { + return s + } + if p == "" { + p = " " + } + var b strings.Builder + ss := utf16.Encode([]rune(s)) + b.Grow(n) + n -= len(ss) + if !start { + b.WriteString(s) + } + if n > 0 { + pp := utf16.Encode([]rune(p)) + for n > 0 { + if n > len(pp) { + b.WriteString(p) + n -= len(pp) + } else { + b.WriteString(string(utf16.Decode(pp[:n]))) + n = 0 + } + } + } + if start { + b.WriteString(s) + } + return b.String() + } + + for i, a := range strs { + testUnaryOp(a, "JSON.parse(JSON.stringify(a))", a, t) + testUnaryOp(a, "a.length", int64(len(utf16.Encode([]rune(a)))), t) + for j, b := range strs { + testBinaryOp(a, b, "a === b", a == b, t) + testBinaryOp(a, b, "a == b", a == b, t) + testBinaryOp(a, b, "a + b", a+b, t) + testBinaryOp(a, b, "a > b", strings.Compare(a, b) > 0, t) + testBinaryOp(a, b, "`A${a}B${b}C`", "A"+a+"B"+b+"C", t) + testBinaryOp(a, b, "a.indexOf(b)", int64(indexOfResults[i][j]), t) + testBinaryOp(a, b, "a.lastIndexOf(b)", int64(lastIndexOfResults[i][j]), t) + testBinaryOp(a, b, "a.padStart(32, b)", pad(a, b, 32, true), t) + testBinaryOp(a, b, "a.padEnd(32, b)", pad(a, b, 32, false), t) + testBinaryOp(a, b, "a.replace(b, '')", strings.Replace(a, b, "", 1), t) + } + } +} + +func TestStringFromUTF16(t *testing.T) { + s := StringFromUTF16([]uint16{}) + if s.Length() != 0 || !s.SameAs(asciiString("")) { + t.Fatal(s) + } + + s = StringFromUTF16([]uint16{0xD800}) + if s.Length() != 1 || s.CharAt(0) != 0xD800 { + t.Fatal(s) + } + + s = StringFromUTF16([]uint16{'A', 'B'}) + if !s.SameAs(asciiString("AB")) { + t.Fatal(s) + } +} + +func TestStringBuilder(t *testing.T) { + t.Run("writeUTF8String-switch", func(t *testing.T) { + var sb StringBuilder + sb.WriteUTF8String("Head") + sb.WriteUTF8String("1ábc") + if res := sb.String().String(); res != "Head1ábc" { + t.Fatal(res) + } + }) +} + +func BenchmarkASCIIConcat(b *testing.B) { + vm := New() + + b.ResetTimer() + b.ReportAllocs() + for i := 0; i < b.N; i++ { + _, err := vm.RunString(`{let result = "ab"; + for (let i = 0 ; i < 10;i++) { + result += result; + }}`) + if err != nil { + b.Fatalf("Unexpected errors %s", err) + } + } +} diff --git a/pkg/xscript/engine/string_unicode.go b/pkg/xscript/engine/string_unicode.go new file mode 100644 index 0000000..f92671b --- /dev/null +++ b/pkg/xscript/engine/string_unicode.go @@ -0,0 +1,620 @@ +package engine + +import ( + "errors" + "hash/maphash" + "io" + "math" + "reflect" + "strings" + "unicode/utf16" + "unicode/utf8" + + "pandax/pkg/xscript/engine/parser" + "pandax/pkg/xscript/engine/unistring" + + "golang.org/x/text/cases" + "golang.org/x/text/language" +) + +type unicodeString []uint16 + +type unicodeRuneReader struct { + s unicodeString + pos int +} + +type utf16RuneReader struct { + s unicodeString + pos int +} + +// passes through invalid surrogate pairs +type lenientUtf16Decoder struct { + utf16Reader utf16Reader + prev uint16 + prevSet bool +} + +// StringBuilder serves similar purpose to strings.Builder, except it works with ECMAScript String. +// Use it to efficiently build 'native' ECMAScript values that either contain invalid UTF-16 surrogate pairs +// (and therefore cannot be represented as UTF-8) or never expected to be exported to Go. See also +// StringFromUTF16. +type StringBuilder struct { + asciiBuilder strings.Builder + unicodeBuilder unicodeStringBuilder +} + +type unicodeStringBuilder struct { + buf []uint16 + unicode bool +} + +var ( + InvalidRuneError = errors.New("invalid rune") +) + +func (rr *utf16RuneReader) readChar() (c uint16, err error) { + if rr.pos < len(rr.s) { + c = rr.s[rr.pos] + rr.pos++ + return + } + err = io.EOF + return +} + +func (rr *utf16RuneReader) ReadRune() (r rune, size int, err error) { + if rr.pos < len(rr.s) { + r = rune(rr.s[rr.pos]) + rr.pos++ + size = 1 + return + } + err = io.EOF + return +} + +func (rr *lenientUtf16Decoder) ReadRune() (r rune, size int, err error) { + var c uint16 + if rr.prevSet { + c = rr.prev + rr.prevSet = false + } else { + c, err = rr.utf16Reader.readChar() + if err != nil { + return + } + } + size = 1 + if isUTF16FirstSurrogate(c) { + second, err1 := rr.utf16Reader.readChar() + if err1 != nil { + if err1 != io.EOF { + err = err1 + } else { + r = rune(c) + } + return + } + if isUTF16SecondSurrogate(second) { + r = utf16.DecodeRune(rune(c), rune(second)) + size++ + return + } else { + rr.prev = second + rr.prevSet = true + } + } + r = rune(c) + return +} + +func (rr *unicodeRuneReader) ReadRune() (r rune, size int, err error) { + if rr.pos < len(rr.s) { + c := rr.s[rr.pos] + size++ + rr.pos++ + if isUTF16FirstSurrogate(c) { + if rr.pos < len(rr.s) { + second := rr.s[rr.pos] + if isUTF16SecondSurrogate(second) { + r = utf16.DecodeRune(rune(c), rune(second)) + size++ + rr.pos++ + return + } + } + err = InvalidRuneError + } else if isUTF16SecondSurrogate(c) { + err = InvalidRuneError + } + r = rune(c) + } else { + err = io.EOF + } + return +} + +func (b *unicodeStringBuilder) Grow(n int) { + if len(b.buf) == 0 { + n++ + } + if cap(b.buf)-len(b.buf) < n { + buf := make([]uint16, len(b.buf), 2*cap(b.buf)+n) + copy(buf, b.buf) + b.buf = buf + } +} + +func (b *unicodeStringBuilder) ensureStarted(initialSize int) { + b.Grow(initialSize) + if len(b.buf) == 0 { + b.buf = append(b.buf, unistring.BOM) + } +} + +// assumes already started +func (b *unicodeStringBuilder) writeString(s String) { + a, u := devirtualizeString(s) + if u != nil { + b.buf = append(b.buf, u[1:]...) + b.unicode = true + } else { + for i := 0; i < len(a); i++ { + b.buf = append(b.buf, uint16(a[i])) + } + } +} + +func (b *unicodeStringBuilder) String() String { + if b.unicode { + return unicodeString(b.buf) + } + if len(b.buf) < 2 { + return stringEmpty + } + buf := make([]byte, 0, len(b.buf)-1) + for _, c := range b.buf[1:] { + buf = append(buf, byte(c)) + } + return asciiString(buf) +} + +func (b *unicodeStringBuilder) WriteRune(r rune) { + b.ensureStarted(2) + b.writeRuneFast(r) +} + +// assumes already started +func (b *unicodeStringBuilder) writeRuneFast(r rune) { + if r <= 0xFFFF { + b.buf = append(b.buf, uint16(r)) + if !b.unicode && r >= utf8.RuneSelf { + b.unicode = true + } + } else { + first, second := utf16.EncodeRune(r) + b.buf = append(b.buf, uint16(first), uint16(second)) + b.unicode = true + } +} + +func (b *unicodeStringBuilder) writeASCIIString(bytes string) { + for _, c := range bytes { + b.buf = append(b.buf, uint16(c)) + } +} + +func (b *unicodeStringBuilder) writeUnicodeString(str unicodeString) { + b.buf = append(b.buf, str[1:]...) + b.unicode = true +} + +func (b *StringBuilder) ascii() bool { + return len(b.unicodeBuilder.buf) == 0 +} + +func (b *StringBuilder) WriteString(s String) { + a, u := devirtualizeString(s) + if u != nil { + b.switchToUnicode(u.Length()) + b.unicodeBuilder.writeUnicodeString(u) + } else { + if b.ascii() { + b.asciiBuilder.WriteString(string(a)) + } else { + b.unicodeBuilder.writeASCIIString(string(a)) + } + } +} + +func (b *StringBuilder) WriteUTF8String(s string) { + firstUnicodeIdx := 0 + if b.ascii() { + for i := 0; i < len(s); i++ { + if s[i] >= utf8.RuneSelf { + b.switchToUnicode(len(s)) + b.unicodeBuilder.writeASCIIString(s[:i]) + firstUnicodeIdx = i + goto unicode + } + } + b.asciiBuilder.WriteString(s) + return + } +unicode: + for _, r := range s[firstUnicodeIdx:] { + b.unicodeBuilder.writeRuneFast(r) + } +} + +func (b *StringBuilder) writeASCII(s string) { + if b.ascii() { + b.asciiBuilder.WriteString(s) + } else { + b.unicodeBuilder.writeASCIIString(s) + } +} + +func (b *StringBuilder) WriteRune(r rune) { + if r < utf8.RuneSelf { + if b.ascii() { + b.asciiBuilder.WriteByte(byte(r)) + } else { + b.unicodeBuilder.writeRuneFast(r) + } + } else { + var extraLen int + if r <= 0xFFFF { + extraLen = 1 + } else { + extraLen = 2 + } + b.switchToUnicode(extraLen) + b.unicodeBuilder.writeRuneFast(r) + } +} + +func (b *StringBuilder) String() String { + if b.ascii() { + return asciiString(b.asciiBuilder.String()) + } + return b.unicodeBuilder.String() +} + +func (b *StringBuilder) Grow(n int) { + if b.ascii() { + b.asciiBuilder.Grow(n) + } else { + b.unicodeBuilder.Grow(n) + } +} + +// LikelyUnicode hints to the builder that the resulting string is likely to contain Unicode (non-ASCII) characters. +// The argument is an extra capacity (in characters) to reserve on top of the current length (it's like calling +// Grow() afterwards). +// This method may be called at any point (not just when the buffer is empty), although for efficiency it should +// be called as early as possible. +func (b *StringBuilder) LikelyUnicode(extraLen int) { + b.switchToUnicode(extraLen) +} + +func (b *StringBuilder) switchToUnicode(extraLen int) { + if b.ascii() { + c := b.asciiBuilder.Cap() + newCap := b.asciiBuilder.Len() + extraLen + if newCap < c { + newCap = c + } + b.unicodeBuilder.ensureStarted(newCap) + b.unicodeBuilder.writeASCIIString(b.asciiBuilder.String()) + b.asciiBuilder.Reset() + } +} + +func (b *StringBuilder) WriteSubstring(source String, start int, end int) { + a, us := devirtualizeString(source) + if us == nil { + if b.ascii() { + b.asciiBuilder.WriteString(string(a[start:end])) + } else { + b.unicodeBuilder.writeASCIIString(string(a[start:end])) + } + return + } + if b.ascii() { + uc := false + for i := start; i < end; i++ { + if us.CharAt(i) >= utf8.RuneSelf { + uc = true + break + } + } + if uc { + b.switchToUnicode(end - start + 1) + } else { + b.asciiBuilder.Grow(end - start + 1) + for i := start; i < end; i++ { + b.asciiBuilder.WriteByte(byte(us.CharAt(i))) + } + return + } + } + b.unicodeBuilder.buf = append(b.unicodeBuilder.buf, us[start+1:end+1]...) + b.unicodeBuilder.unicode = true +} + +func (s unicodeString) Reader() io.RuneReader { + return &unicodeRuneReader{ + s: s[1:], + } +} + +func (s unicodeString) utf16Reader() utf16Reader { + return &utf16RuneReader{ + s: s[1:], + } +} + +func (s unicodeString) utf16RuneReader() io.RuneReader { + return &utf16RuneReader{ + s: s[1:], + } +} + +func (s unicodeString) utf16Runes() []rune { + runes := make([]rune, len(s)-1) + for i, ch := range s[1:] { + runes[i] = rune(ch) + } + return runes +} + +func (s unicodeString) ToInteger() int64 { + return 0 +} + +func (s unicodeString) toString() String { + return s +} + +func (s unicodeString) ToString() Value { + return s +} + +func (s unicodeString) ToFloat() float64 { + return math.NaN() +} + +func (s unicodeString) ToBoolean() bool { + return len(s) > 0 +} + +func (s unicodeString) toTrimmedUTF8() string { + if len(s) == 0 { + return "" + } + return strings.Trim(s.String(), parser.WhitespaceChars) +} + +func (s unicodeString) ToNumber() Value { + return asciiString(s.toTrimmedUTF8()).ToNumber() +} + +func (s unicodeString) ToObject(r *Runtime) *Object { + return r._newString(s, r.getStringPrototype()) +} + +func (s unicodeString) equals(other unicodeString) bool { + if len(s) != len(other) { + return false + } + for i, r := range s { + if r != other[i] { + return false + } + } + return true +} + +func (s unicodeString) SameAs(other Value) bool { + return s.StrictEquals(other) +} + +func (s unicodeString) Equals(other Value) bool { + if s.StrictEquals(other) { + return true + } + + if o, ok := other.(*Object); ok { + return s.Equals(o.toPrimitive()) + } + return false +} + +func (s unicodeString) StrictEquals(other Value) bool { + if otherStr, ok := other.(unicodeString); ok { + return s.equals(otherStr) + } + if otherStr, ok := other.(*importedString); ok { + otherStr.ensureScanned() + if otherStr.u != nil { + return s.equals(otherStr.u) + } + } + + return false +} + +func (s unicodeString) baseObject(r *Runtime) *Object { + ss := r.getStringSingleton() + ss.value = s + ss.setLength() + return ss.val +} + +func (s unicodeString) CharAt(idx int) uint16 { + return s[idx+1] +} + +func (s unicodeString) Length() int { + return len(s) - 1 +} + +func (s unicodeString) Concat(other String) String { + a, u := devirtualizeString(other) + if u != nil { + b := make(unicodeString, len(s)+len(u)-1) + copy(b, s) + copy(b[len(s):], u[1:]) + return b + } + b := make([]uint16, len(s)+len(a)) + copy(b, s) + b1 := b[len(s):] + for i := 0; i < len(a); i++ { + b1[i] = uint16(a[i]) + } + return unicodeString(b) +} + +func (s unicodeString) Substring(start, end int) String { + ss := s[start+1 : end+1] + for _, c := range ss { + if c >= utf8.RuneSelf { + b := make(unicodeString, end-start+1) + b[0] = unistring.BOM + copy(b[1:], ss) + return b + } + } + as := make([]byte, end-start) + for i, c := range ss { + as[i] = byte(c) + } + return asciiString(as) +} + +func (s unicodeString) String() string { + return string(utf16.Decode(s[1:])) +} + +func (s unicodeString) CompareTo(other String) int { + // TODO handle invalid UTF-16 + return strings.Compare(s.String(), other.String()) +} + +func (s unicodeString) index(substr String, start int) int { + var ss []uint16 + a, u := devirtualizeString(substr) + if u != nil { + ss = u[1:] + } else { + ss = make([]uint16, len(a)) + for i := 0; i < len(a); i++ { + ss[i] = uint16(a[i]) + } + } + s1 := s[1:] + // TODO: optimise + end := len(s1) - len(ss) + for start <= end { + for i := 0; i < len(ss); i++ { + if s1[start+i] != ss[i] { + goto nomatch + } + } + + return start + nomatch: + start++ + } + return -1 +} + +func (s unicodeString) lastIndex(substr String, start int) int { + var ss []uint16 + a, u := devirtualizeString(substr) + if u != nil { + ss = u[1:] + } else { + ss = make([]uint16, len(a)) + for i := 0; i < len(a); i++ { + ss[i] = uint16(a[i]) + } + } + + s1 := s[1:] + if maxStart := len(s1) - len(ss); start > maxStart { + start = maxStart + } + // TODO: optimise + for start >= 0 { + for i := 0; i < len(ss); i++ { + if s1[start+i] != ss[i] { + goto nomatch + } + } + + return start + nomatch: + start-- + } + return -1 +} + +func unicodeStringFromRunes(r []rune) unicodeString { + return unistring.NewFromRunes(r).AsUtf16() +} + +func toLower(s string) String { + caser := cases.Lower(language.Und) + r := []rune(caser.String(s)) + // Workaround + ascii := true + for i := 0; i < len(r)-1; i++ { + if (i == 0 || r[i-1] != 0x3b1) && r[i] == 0x345 && r[i+1] == 0x3c2 { + i++ + r[i] = 0x3c3 + } + if r[i] >= utf8.RuneSelf { + ascii = false + } + } + if ascii { + ascii = r[len(r)-1] < utf8.RuneSelf + } + if ascii { + return asciiString(r) + } + return unicodeStringFromRunes(r) +} + +func (s unicodeString) toLower() String { + return toLower(s.String()) +} + +func (s unicodeString) toUpper() String { + caser := cases.Upper(language.Und) + return newStringValue(caser.String(s.String())) +} + +func (s unicodeString) Export() any { + return s.String() +} + +func (s unicodeString) ExportType() reflect.Type { + return reflectTypeString +} + +func (s unicodeString) hash(hash *maphash.Hash) uint64 { + _, _ = hash.WriteString(string(unistring.FromUtf16(s))) + h := hash.Sum64() + hash.Reset() + return h +} + +func (s unicodeString) string() unistring.String { + return unistring.FromUtf16(s) +} diff --git a/pkg/xscript/engine/tc39_norace_test.go b/pkg/xscript/engine/tc39_norace_test.go new file mode 100644 index 0000000..fed76f3 --- /dev/null +++ b/pkg/xscript/engine/tc39_norace_test.go @@ -0,0 +1,19 @@ +//go:build !race +// +build !race + +package engine + +import "testing" + +// Prevent linter warnings about unused type +var _ = tc39Test{name: "", f: nil} + +func (ctx *tc39TestCtx) runTest(name string, f func(t *testing.T)) { + ctx.t.Run(name, func(t *testing.T) { + t.Parallel() + f(t) + }) +} + +func (ctx *tc39TestCtx) flush() { +} diff --git a/pkg/xscript/engine/tc39_race_test.go b/pkg/xscript/engine/tc39_race_test.go new file mode 100644 index 0000000..48c5dd1 --- /dev/null +++ b/pkg/xscript/engine/tc39_race_test.go @@ -0,0 +1,32 @@ +//go:build race +// +build race + +package xjs + +import ( + "testing" +) + +const ( + tc39MaxTestGroupSize = 8000 // to prevent race detector complaining about too many goroutines +) + +func (ctx *tc39TestCtx) runTest(name string, f func(t *testing.T)) { + ctx.testQueue = append(ctx.testQueue, tc39Test{name: name, f: f}) + if len(ctx.testQueue) >= tc39MaxTestGroupSize { + ctx.flush() + } +} + +func (ctx *tc39TestCtx) flush() { + ctx.t.Run("tc39", func(t *testing.T) { + for _, tc := range ctx.testQueue { + tc := tc + t.Run(tc.name, func(t *testing.T) { + t.Parallel() + tc.f(t) + }) + } + }) + ctx.testQueue = ctx.testQueue[:0] +} diff --git a/pkg/xscript/engine/tc39_test.go b/pkg/xscript/engine/tc39_test.go new file mode 100644 index 0000000..6d0c955 --- /dev/null +++ b/pkg/xscript/engine/tc39_test.go @@ -0,0 +1,739 @@ +package engine + +import ( + "errors" + "fmt" + "io" + "os" + "path" + "sort" + "strings" + "sync" + "testing" + "time" + + "gopkg.in/yaml.v3" +) + +const ( + tc39BASE = "testdata/test262" +) + +var ( + invalidFormatError = errors.New("Invalid file format") + + ignorableTestError = newSymbol(stringEmpty) +) + +var ( + skipPrefixes prefixList + + skipList = map[string]bool{ + + // out-of-date (https://github.com/tc39/test262/issues/3407) + "test/language/expressions/prefix-increment/S11.4.4_A6_T3.js": true, + "test/language/expressions/prefix-increment/S11.4.4_A6_T2.js": true, + "test/language/expressions/prefix-increment/S11.4.4_A6_T1.js": true, + "test/language/expressions/prefix-decrement/S11.4.5_A6_T3.js": true, + "test/language/expressions/prefix-decrement/S11.4.5_A6_T2.js": true, + "test/language/expressions/prefix-decrement/S11.4.5_A6_T1.js": true, + "test/language/expressions/postfix-increment/S11.3.1_A6_T3.js": true, + "test/language/expressions/postfix-increment/S11.3.1_A6_T2.js": true, + "test/language/expressions/postfix-increment/S11.3.1_A6_T1.js": true, + "test/language/expressions/postfix-decrement/S11.3.2_A6_T3.js": true, + "test/language/expressions/postfix-decrement/S11.3.2_A6_T2.js": true, + "test/language/expressions/postfix-decrement/S11.3.2_A6_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.1_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.1_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.1_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.11_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.11_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.11_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.10_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.10_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.10_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.9_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.9_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.9_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.8_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.8_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.8_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.7_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.7_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.7_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.6_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.6_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.6_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.5_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.5_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.5_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.4_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.4_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.4_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.3_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.3_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.3_T1.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.2_T4.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.2_T2.js": true, + "test/language/expressions/compound-assignment/S11.13.2_A7.2_T1.js": true, + "test/language/expressions/assignment/S11.13.1_A7_T3.js": true, + + // timezone + "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-8.js": true, + "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-9.js": true, + "test/built-ins/Date/prototype/toISOString/15.9.5.43-0-10.js": true, + + // floating point date calculations + "test/built-ins/Date/UTC/fp-evaluation-order.js": true, + + // quantifier integer limit in regexp + "test/built-ins/RegExp/quantifier-integer-limit.js": true, + + // GetFunctionRealm + "test/built-ins/Function/internals/Construct/base-ctor-revoked-proxy.js": true, + + // Uses deprecated __lookupGetter__/__lookupSetter__ + "test/language/expressions/class/elements/private-getter-is-not-a-own-property.js": true, + "test/language/expressions/class/elements/private-setter-is-not-a-own-property.js": true, + "test/language/statements/class/elements/private-setter-is-not-a-own-property.js": true, + "test/language/statements/class/elements/private-getter-is-not-a-own-property.js": true, + + // restricted unicode regexp syntax + "test/built-ins/RegExp/unicode_restricted_quantifiable_assertion.js": true, + "test/built-ins/RegExp/unicode_restricted_octal_escape.js": true, + "test/built-ins/RegExp/unicode_restricted_incomple_quantifier.js": true, + "test/built-ins/RegExp/unicode_restricted_incomplete_quantifier.js": true, + "test/built-ins/RegExp/unicode_restricted_identity_escape_x.js": true, + "test/built-ins/RegExp/unicode_restricted_identity_escape_u.js": true, + "test/built-ins/RegExp/unicode_restricted_identity_escape_c.js": true, + "test/built-ins/RegExp/unicode_restricted_identity_escape_alpha.js": true, + "test/built-ins/RegExp/unicode_restricted_identity_escape.js": true, + "test/built-ins/RegExp/unicode_restricted_brackets.js": true, + "test/built-ins/RegExp/unicode_restricted_character_class_escape.js": true, + "test/annexB/built-ins/RegExp/prototype/compile/pattern-string-invalid-u.js": true, + + // Because goja parser works in UTF-8 it is not possible to pass strings containing invalid UTF-16 code points. + // This is mitigated by escaping them as \uXXXX, however because of this the RegExp source becomes + // `\uXXXX` instead of ``. + // The resulting RegExp will work exactly the same, but it causes these two tests to fail. + "test/annexB/built-ins/RegExp/RegExp-leading-escape-BMP.js": true, + "test/annexB/built-ins/RegExp/RegExp-trailing-escape-BMP.js": true, + "test/language/literals/regexp/S7.8.5_A1.4_T2.js": true, + "test/language/literals/regexp/S7.8.5_A1.1_T2.js": true, + "test/language/literals/regexp/S7.8.5_A2.1_T2.js": true, + "test/language/literals/regexp/S7.8.5_A2.4_T2.js": true, + + // async generator + "test/language/expressions/optional-chaining/member-expression.js": true, + "test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/destructuring/binding/syntax/destructuring-object-parameters-function-arguments-length.js": true, + "test/language/destructuring/binding/syntax/destructuring-array-parameters-function-arguments-length.js": true, + "test/language/comments/hashbang/function-constructor.js": true, + "test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/statements/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/statements/class/elements/same-line-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/after-same-line-static-async-method-rs-static-async-generator-method-privatename-identifier.js": true, + "test/built-ins/Object/seal/seal-asyncgeneratorfunction.js": true, + "test/language/statements/switch/scope-lex-async-generator.js": true, + "test/language/statements/class/elements/private-async-generator-method-name.js": true, + "test/language/expressions/class/elements/private-async-generator-method-name.js": true, + "test/language/expressions/async-generator/name.js": true, + "test/language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/statements/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/statements/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/statements/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/statements/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/expressions/class/elements/same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/expressions/class/elements/new-sc-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/expressions/class/elements/after-same-line-static-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier.js": true, + "test/language/expressions/class/elements/after-same-line-gen-rs-static-async-generator-method-privatename-identifier-alt.js": true, + "test/built-ins/GeneratorFunction/is-a-constructor.js": true, + + // async iterator + "test/language/expressions/optional-chaining/iteration-statement-for-await-of.js": true, + + // legacy number literals + "test/language/literals/numeric/non-octal-decimal-integer.js": true, + "test/language/literals/string/S7.8.4_A4.3_T2.js": true, + "test/language/literals/string/S7.8.4_A4.3_T1.js": true, + + // integer separators + "test/language/expressions/object/cpn-obj-lit-computed-property-name-from-integer-separators.js": true, + "test/language/expressions/class/cpn-class-expr-accessors-computed-property-name-from-integer-separators.js": true, + "test/language/statements/class/cpn-class-decl-fields-computed-property-name-from-integer-separators.js": true, + "test/language/statements/class/cpn-class-decl-computed-property-name-from-integer-separators.js": true, + "test/language/statements/class/cpn-class-decl-accessors-computed-property-name-from-integer-separators.js": true, + "test/language/statements/class/cpn-class-decl-fields-methods-computed-property-name-from-integer-separators.js": true, + "test/language/expressions/class/cpn-class-expr-fields-computed-property-name-from-integer-separators.js": true, + "test/language/expressions/class/cpn-class-expr-computed-property-name-from-integer-separators.js": true, + "test/language/expressions/class/cpn-class-expr-fields-methods-computed-property-name-from-integer-separators.js": true, + + // BigInt + "test/built-ins/Object/seal/seal-biguint64array.js": true, + "test/built-ins/Object/seal/seal-bigint64array.js": true, + + // Regexp + "test/language/literals/regexp/invalid-range-negative-lookbehind.js": true, + "test/language/literals/regexp/invalid-range-lookbehind.js": true, + "test/language/literals/regexp/invalid-optional-negative-lookbehind.js": true, + "test/language/literals/regexp/invalid-optional-lookbehind.js": true, + + // FIXME bugs + + // Left-hand side as a CoverParenthesizedExpression + "test/language/expressions/assignment/fn-name-lhs-cover.js": true, + + // Character \ missing from character class [\c] + "test/annexB/built-ins/RegExp/RegExp-invalid-control-escape-character-class.js": true, + "test/annexB/built-ins/RegExp/RegExp-control-escape-russian-letter.js": true, + + // Skip due to regexp named groups + "test/built-ins/String/prototype/replaceAll/searchValue-replacer-RegExp-call.js": true, + } + + featuresBlackList = []string{ + "async-iteration", + "Symbol.asyncIterator", + "BigInt", + "resizable-arraybuffer", + "regexp-named-groups", + "regexp-dotall", + "regexp-unicode-property-escapes", + "regexp-match-indices", + "legacy-regexp", + "tail-call-optimization", + "Temporal", + "import-assertions", + "dynamic-import", + "logical-assignment-operators", + "import.meta", + "Atomics", + "Atomics.waitAsync", + "FinalizationRegistry", + "WeakRef", + "numeric-separator-literal", + "__getter__", + "__setter__", + "ShadowRealm", + "SharedArrayBuffer", + "error-cause", + "decorators", + "regexp-v-flag", + } +) + +func init() { + + skip := func(prefixes ...string) { + for _, prefix := range prefixes { + skipPrefixes.Add(prefix) + } + } + + skip( + // Go 1.16 only supports unicode 13 + "test/language/identifiers/start-unicode-14.", + "test/language/identifiers/part-unicode-14.", + + // generators and async generators (harness/hidden-constructors.js) + "test/built-ins/Async", + + // async generators + "test/language/statements/class/elements/wrapped-in-sc-rs-static-async-generator-", + "test/language/statements/class/elements/same-line-method-rs-static-async-generator-", + "test/language/statements/class/elements/regular-definitions-rs-static-async-generator-", + "test/language/statements/class/elements/private-static-async-generator-", + "test/language/statements/class/elements/new-sc-line-method-rs-static-async-generator-", + "test/language/statements/class/elements/multiple-stacked-definitions-rs-static-async-generator-", + "test/language/statements/class/elements/new-no-sc-line-method-rs-static-async-generator-", + "test/language/statements/class/elements/multiple-definitions-rs-static-async-generator-", + "test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-", + "test/language/statements/class/elements/after-same-line-method-rs-static-async-generator-", + "test/language/statements/class/elements/after-same-line-static-method-rs-static-async-generator-", + + "test/language/expressions/class/elements/wrapped-in-sc-rs-static-async-generator-", + "test/language/expressions/class/elements/same-line-method-rs-static-async-generator-", + "test/language/expressions/class/elements/regular-definitions-rs-static-async-generator-", + "test/language/expressions/class/elements/private-static-async-generator-", + "test/language/expressions/class/elements/new-sc-line-method-rs-static-async-generator-", + "test/language/expressions/class/elements/multiple-stacked-definitions-rs-static-async-generator-", + "test/language/expressions/class/elements/new-no-sc-line-method-rs-static-async-generator-", + "test/language/expressions/class/elements/multiple-definitions-rs-static-async-generator-", + "test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-", + "test/language/expressions/class/elements/after-same-line-method-rs-static-async-generator-", + "test/language/expressions/class/elements/after-same-line-static-method-rs-static-async-generator-", + + "test/language/eval-code/direct/async-gen-", + + // BigInt + "test/built-ins/TypedArrayConstructors/BigUint64Array/", + "test/built-ins/TypedArrayConstructors/BigInt64Array/", + + // restricted unicode regexp syntax + "test/language/literals/regexp/u-", + + // legacy octal escape in strings in strict mode + "test/language/literals/string/legacy-octal-", + "test/language/literals/string/legacy-non-octal-", + + // modules + "test/language/export/", + "test/language/import/", + "test/language/module-code/", + ) + +} + +type tc39Test struct { + name string + f func(t *testing.T) +} + +type tc39BenchmarkItem struct { + name string + duration time.Duration +} + +type tc39BenchmarkData []tc39BenchmarkItem + +type tc39TestCtx struct { + base string + t *testing.T + prgCache map[string]*Program + prgCacheLock sync.Mutex + enableBench bool + benchmark tc39BenchmarkData + benchLock sync.Mutex + sabStub *Program + //lint:ignore U1000 Only used with race + testQueue []tc39Test +} + +type TC39MetaNegative struct { + Phase, Type string +} + +type tc39Meta struct { + Negative TC39MetaNegative + Includes []string + Flags []string + Features []string + Es5id string + Es6id string + Esid string +} + +type prefixList struct { + prefixes map[int]map[string]struct{} +} + +func (pl *prefixList) Add(prefix string) { + l := pl.prefixes[len(prefix)] + if l == nil { + l = make(map[string]struct{}) + if pl.prefixes == nil { + pl.prefixes = make(map[int]map[string]struct{}) + } + pl.prefixes[len(prefix)] = l + } + l[prefix] = struct{}{} +} + +func (pl *prefixList) Match(s string) bool { + for l, prefixes := range pl.prefixes { + if len(s) >= l { + if _, exists := prefixes[s[:l]]; exists { + return true + } + } + } + return false +} + +func (m *tc39Meta) hasFlag(flag string) bool { + for _, f := range m.Flags { + if f == flag { + return true + } + } + return false +} + +func parseTC39File(name string) (*tc39Meta, string, error) { + f, err := os.Open(name) + if err != nil { + return nil, "", err + } + defer f.Close() + + b, err := io.ReadAll(f) + if err != nil { + return nil, "", err + } + + str := string(b) + metaStart := strings.Index(str, "/*---") + if metaStart == -1 { + return nil, "", invalidFormatError + } else { + metaStart += 5 + } + metaEnd := strings.Index(str, "---*/") + if metaEnd == -1 || metaEnd <= metaStart { + return nil, "", invalidFormatError + } + + var meta tc39Meta + err = yaml.Unmarshal([]byte(str[metaStart:metaEnd]), &meta) + if err != nil { + return nil, "", err + } + + if meta.Negative.Type != "" && meta.Negative.Phase == "" { + return nil, "", errors.New("negative type is set, but phase isn't") + } + + return &meta, str, nil +} + +func (*tc39TestCtx) detachArrayBuffer(call FunctionCall) Value { + if obj, ok := call.Argument(0).(*Object); ok { + if buf, ok := obj.self.(*arrayBufferObject); ok { + buf.detach() + return _undefined + } + } + panic(typeError("detachArrayBuffer() is called with incompatible argument")) +} + +func (*tc39TestCtx) throwIgnorableTestError(FunctionCall) Value { + panic(ignorableTestError) +} + +func (ctx *tc39TestCtx) runTC39Test(name, src string, meta *tc39Meta, t testing.TB) { + defer func() { + if x := recover(); x != nil { + panic(fmt.Sprintf("panic while running %s: %v", name, x)) + } + }() + vm := New() + _262 := vm.NewObject() + _262.Set("detachArrayBuffer", ctx.detachArrayBuffer) + _262.Set("createRealm", ctx.throwIgnorableTestError) + _262.Set("evalScript", func(call FunctionCall) Value { + script := call.Argument(0).String() + result, err := vm.RunString(script) + if err != nil { + panic(err) + } + return result + }) + vm.Set("$262", _262) + vm.Set("IgnorableTestError", ignorableTestError) + vm.RunProgram(ctx.sabStub) + var out []string + async := meta.hasFlag("async") + if async { + err := ctx.runFile(ctx.base, path.Join("harness", "doneprintHandle.js"), vm) + if err != nil { + t.Fatal(err) + } + vm.Set("print", func(msg string) { + out = append(out, msg) + }) + } else { + vm.Set("print", t.Log) + } + + err, early := ctx.runTC39Script(name, src, meta.Includes, vm) + + if err != nil { + if meta.Negative.Type == "" { + if err, ok := err.(*Exception); ok { + if err.Value() == ignorableTestError { + t.Skip("Test threw IgnorableTestError") + } + } + t.Fatalf("%s: %v", name, err) + } else { + if (meta.Negative.Phase == "early" || meta.Negative.Phase == "parse") && !early || meta.Negative.Phase == "runtime" && early { + t.Fatalf("%s: error %v happened at the wrong phase (expected %s)", name, err, meta.Negative.Phase) + } + var errType string + + switch err := err.(type) { + case *Exception: + if o, ok := err.Value().(*Object); ok { + if c := o.Get("constructor"); c != nil { + if c, ok := c.(*Object); ok { + errType = c.Get("name").String() + } else { + t.Fatalf("%s: error constructor is not an object (%v)", name, o) + } + } else { + t.Fatalf("%s: error does not have a constructor (%v)", name, o) + } + } else { + t.Fatalf("%s: error is not an object (%v)", name, err.Value()) + } + case *CompilerSyntaxError: + errType = "SyntaxError" + case *CompilerReferenceError: + errType = "ReferenceError" + default: + t.Fatalf("%s: error is not a JS error: %v", name, err) + } + + if errType != meta.Negative.Type { + vm.vm.prg.dumpCode(t.Logf) + t.Fatalf("%s: unexpected error type (%s), expected (%s)", name, errType, meta.Negative.Type) + } + } + } else { + if meta.Negative.Type != "" { + vm.vm.prg.dumpCode(t.Logf) + t.Fatalf("%s: Expected error: %v", name, err) + } + } + + if vm.vm.sp != 0 { + t.Fatalf("sp: %d", vm.vm.sp) + } + + if l := len(vm.vm.iterStack); l > 0 { + t.Fatalf("iter stack is not empty: %d", l) + } + if async { + complete := false + for _, line := range out { + if strings.HasPrefix(line, "Test262:AsyncTestFailure:") { + t.Fatal(line) + } else if line == "Test262:AsyncTestComplete" { + complete = true + } + } + if !complete { + for _, line := range out { + t.Log(line) + } + t.Fatal("Test262:AsyncTestComplete was not printed") + } + } +} + +func (ctx *tc39TestCtx) runTC39File(name string, t testing.TB) { + if skipList[name] { + t.Skip("Excluded") + } + if skipPrefixes.Match(name) { + t.Skip("Excluded") + } + p := path.Join(ctx.base, name) + meta, src, err := parseTC39File(p) + if err != nil { + //t.Fatalf("Could not parse %s: %v", name, err) + t.Errorf("Could not parse %s: %v", name, err) + return + } + if meta.hasFlag("module") { + t.Skip("module") + } + if meta.Es5id == "" { + for _, feature := range meta.Features { + for _, bl := range featuresBlackList { + if feature == bl { + t.Skip("Blacklisted feature") + } + } + } + } + + var startTime time.Time + if ctx.enableBench { + startTime = time.Now() + } + + hasRaw := meta.hasFlag("raw") + + if hasRaw || !meta.hasFlag("onlyStrict") { + //log.Printf("Running normal test: %s", name) + t.Logf("Running normal test: %s", name) + ctx.runTC39Test(name, src, meta, t) + } + + if !hasRaw && !meta.hasFlag("noStrict") { + //log.Printf("Running strict test: %s", name) + t.Logf("Running strict test: %s", name) + ctx.runTC39Test(name, "'use strict';\n"+src, meta, t) + } + + if ctx.enableBench { + ctx.benchLock.Lock() + ctx.benchmark = append(ctx.benchmark, tc39BenchmarkItem{ + name: name, + duration: time.Since(startTime), + }) + ctx.benchLock.Unlock() + } + +} + +func (ctx *tc39TestCtx) init() { + ctx.prgCache = make(map[string]*Program) + ctx.sabStub = MustCompile("sabStub.js", ` + Object.defineProperty(this, "SharedArrayBuffer", { + get: function() { + throw IgnorableTestError; + } + });`, + false) +} + +func (ctx *tc39TestCtx) compile(base, name string) (*Program, error) { + ctx.prgCacheLock.Lock() + defer ctx.prgCacheLock.Unlock() + + prg := ctx.prgCache[name] + if prg == nil { + fname := path.Join(base, name) + f, err := os.Open(fname) + if err != nil { + return nil, err + } + defer f.Close() + + b, err := io.ReadAll(f) + if err != nil { + return nil, err + } + + str := string(b) + prg, err = Compile(name, str, false) + if err != nil { + return nil, err + } + ctx.prgCache[name] = prg + } + + return prg, nil +} + +func (ctx *tc39TestCtx) runFile(base, name string, vm *Runtime) error { + prg, err := ctx.compile(base, name) + if err != nil { + return err + } + _, err = vm.RunProgram(prg) + return err +} + +func (ctx *tc39TestCtx) runTC39Script(name, src string, includes []string, vm *Runtime) (err error, early bool) { + early = true + err = ctx.runFile(ctx.base, path.Join("harness", "assert.js"), vm) + if err != nil { + return + } + + err = ctx.runFile(ctx.base, path.Join("harness", "sta.js"), vm) + if err != nil { + return + } + + for _, include := range includes { + err = ctx.runFile(ctx.base, path.Join("harness", include), vm) + if err != nil { + return + } + } + + var p *Program + p, err = Compile(name, src, false) + + if err != nil { + return + } + + early = false + _, err = vm.RunProgram(p) + + return +} + +func (ctx *tc39TestCtx) runTC39Tests(name string) { + files, err := os.ReadDir(path.Join(ctx.base, name)) + if err != nil { + ctx.t.Fatal(err) + } + + for _, file := range files { + if file.Name()[0] == '.' { + continue + } + if file.IsDir() { + ctx.runTC39Tests(path.Join(name, file.Name())) + } else { + fileName := file.Name() + if strings.HasSuffix(fileName, ".js") && !strings.HasSuffix(fileName, "_FIXTURE.js") { + name := path.Join(name, fileName) + ctx.runTest(name, func(t *testing.T) { + ctx.runTC39File(name, t) + }) + } + } + } + +} + +func TestTC39(t *testing.T) { + if testing.Short() { + t.Skip() + } + + if _, err := os.Stat(tc39BASE); err != nil { + t.Skipf("If you want to run tc39 tests, download them from https://github.com/tc39/test262 and put into %s. See .tc39_test262_checkout.sh for the latest working commit id. (%v)", tc39BASE, err) + } + + ctx := &tc39TestCtx{ + base: tc39BASE, + } + ctx.init() + //ctx.enableBench = true + + t.Run("tc39", func(t *testing.T) { + ctx.t = t + //ctx.runTC39File("test/language/types/number/8.5.1.js", t) + ctx.runTC39Tests("test/language") + ctx.runTC39Tests("test/built-ins") + ctx.runTC39Tests("test/annexB/built-ins/String/prototype/substr") + ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimLeft") + ctx.runTC39Tests("test/annexB/built-ins/String/prototype/trimRight") + ctx.runTC39Tests("test/annexB/built-ins/escape") + ctx.runTC39Tests("test/annexB/built-ins/unescape") + ctx.runTC39Tests("test/annexB/built-ins/RegExp") + + ctx.flush() + }) + + if ctx.enableBench { + sort.Slice(ctx.benchmark, func(i, j int) bool { + return ctx.benchmark[i].duration > ctx.benchmark[j].duration + }) + bench := ctx.benchmark + if len(bench) > 50 { + bench = bench[:50] + } + for _, item := range bench { + fmt.Printf("%s\t%d\n", item.name, item.duration/time.Millisecond) + } + } +} diff --git a/pkg/xscript/engine/testdata/S15.10.2.12_A1_T1.js b/pkg/xscript/engine/testdata/S15.10.2.12_A1_T1.js new file mode 100644 index 0000000..e5e641d --- /dev/null +++ b/pkg/xscript/engine/testdata/S15.10.2.12_A1_T1.js @@ -0,0 +1,529 @@ +// Copyright 2009 the Sputnik authors. All rights reserved. +// This code is governed by the BSD license found in the LICENSE file. + +/*--- +info: > + The production CharacterClassEscape :: s evaluates by returning the set of characters + containing the characters that are on the right-hand side of the WhiteSpace (7.2) or LineTerminator (7.3) productions +es5id: 15.10.2.12_A1_T1 +description: WhiteSpace +---*/ + +var i0 = ""; +for (var j = 0; j < 1024; j++) + i0 += String.fromCharCode(j); +var o0 = "\u0000\u0001\u0002\u0003\u0004\u0005\u0006\u0007\u0008\u000E\u000F\u0010\u0011\u0012\u0013\u0014\u0015\u0016\u0017\u0018\u0019\u001A\u001B\u001C\u001D\u001E\u001F\u0021\u0022\u0023\u0024\u0025\u0026\u0027\u0028\u0029\u002A\u002B\u002C\u002D\u002E\u002F\u0030\u0031\u0032\u0033\u0034\u0035\u0036\u0037\u0038\u0039\u003A\u003B\u003C\u003D\u003E\u003F\u0040\u0041\u0042\u0043\u0044\u0045\u0046\u0047\u0048\u0049\u004A\u004B\u004C\u004D\u004E\u004F\u0050\u0051\u0052\u0053\u0054\u0055\u0056\u0057\u0058\u0059\u005A\u005B\u005C\u005D\u005E\u005F\u0060\u0061\u0062\u0063\u0064\u0065\u0066\u0067\u0068\u0069\u006A\u006B\u006C\u006D\u006E\u006F\u0070\u0071\u0072\u0073\u0074\u0075\u0076\u0077\u0078\u0079\u007A\u007B\u007C\u007D\u007E\u007F\u0080\u0081\u0082\u0083\u0084\u0085\u0086\u0087\u0088\u0089\u008A\u008B\u008C\u008D\u008E\u008F\u0090\u0091\u0092\u0093\u0094\u0095\u0096\u0097\u0098\u0099\u009A\u009B\u009C\u009D\u009E\u009F\u00A1\u00A2\u00A3\u00A4\u00A5\u00A6\u00A7\u00A8\u00A9\u00AA\u00AB\u00AC\u00AD\u00AE\u00AF\u00B0\u00B1\u00B2\u00B3\u00B4\u00B5\u00B6\u00B7\u00B8\u00B9\u00BA\u00BB\u00BC\u00BD\u00BE\u00BF\u00C0\u00C1\u00C2\u00C3\u00C4\u00C5\u00C6\u00C7\u00C8\u00C9\u00CA\u00CB\u00CC\u00CD\u00CE\u00CF\u00D0\u00D1\u00D2\u00D3\u00D4\u00D5\u00D6\u00D7\u00D8\u00D9\u00DA\u00DB\u00DC\u00DD\u00DE\u00DF\u00E0\u00E1\u00E2\u00E3\u00E4\u00E5\u00E6\u00E7\u00E8\u00E9\u00EA\u00EB\u00EC\u00ED\u00EE\u00EF\u00F0\u00F1\u00F2\u00F3\u00F4\u00F5\u00F6\u00F7\u00F8\u00F9\u00FA\u00FB\u00FC\u00FD\u00FE\u00FF\u0100\u0101\u0102\u0103\u0104\u0105\u0106\u0107\u0108\u0109\u010A\u010B\u010C\u010D\u010E\u010F\u0110\u0111\u0112\u0113\u0114\u0115\u0116\u0117\u0118\u0119\u011A\u011B\u011C\u011D\u011E\u011F\u0120\u0121\u0122\u0123\u0124\u0125\u0126\u0127\u0128\u0129\u012A\u012B\u012C\u012D\u012E\u012F\u0130\u0131\u0132\u0133\u0134\u0135\u0136\u0137\u0138\u0139\u013A\u013B\u013C\u013D\u013E\u013F\u0140\u0141\u0142\u0143\u0144\u0145\u0146\u0147\u0148\u0149\u014A\u014B\u014C\u014D\u014E\u014F\u0150\u0151\u0152\u0153\u0154\u0155\u0156\u0157\u0158\u0159\u015A\u015B\u015C\u015D\u015E\u015F\u0160\u0161\u0162\u0163\u0164\u0165\u0166\u0167\u0168\u0169\u016A\u016B\u016C\u016D\u016E\u016F\u0170\u0171\u0172\u0173\u0174\u0175\u0176\u0177\u0178\u0179\u017A\u017B\u017C\u017D\u017E\u017F\u0180\u0181\u0182\u0183\u0184\u0185\u0186\u0187\u0188\u0189\u018A\u018B\u018C\u018D\u018E\u018F\u0190\u0191\u0192\u0193\u0194\u0195\u0196\u0197\u0198\u0199\u019A\u019B\u019C\u019D\u019E\u019F\u01A0\u01A1\u01A2\u01A3\u01A4\u01A5\u01A6\u01A7\u01A8\u01A9\u01AA\u01AB\u01AC\u01AD\u01AE\u01AF\u01B0\u01B1\u01B2\u01B3\u01B4\u01B5\u01B6\u01B7\u01B8\u01B9\u01BA\u01BB\u01BC\u01BD\u01BE\u01BF\u01C0\u01C1\u01C2\u01C3\u01C4\u01C5\u01C6\u01C7\u01C8\u01C9\u01CA\u01CB\u01CC\u01CD\u01CE\u01CF\u01D0\u01D1\u01D2\u01D3\u01D4\u01D5\u01D6\u01D7\u01D8\u01D9\u01DA\u01DB\u01DC\u01DD\u01DE\u01DF\u01E0\u01E1\u01E2\u01E3\u01E4\u01E5\u01E6\u01E7\u01E8\u01E9\u01EA\u01EB\u01EC\u01ED\u01EE\u01EF\u01F0\u01F1\u01F2\u01F3\u01F4\u01F5\u01F6\u01F7\u01F8\u01F9\u01FA\u01FB\u01FC\u01FD\u01FE\u01FF\u0200\u0201\u0202\u0203\u0204\u0205\u0206\u0207\u0208\u0209\u020A\u020B\u020C\u020D\u020E\u020F\u0210\u0211\u0212\u0213\u0214\u0215\u0216\u0217\u0218\u0219\u021A\u021B\u021C\u021D\u021E\u021F\u0220\u0221\u0222\u0223\u0224\u0225\u0226\u0227\u0228\u0229\u022A\u022B\u022C\u022D\u022E\u022F\u0230\u0231\u0232\u0233\u0234\u0235\u0236\u0237\u0238\u0239\u023A\u023B\u023C\u023D\u023E\u023F\u0240\u0241\u0242\u0243\u0244\u0245\u0246\u0247\u0248\u0249\u024A\u024B\u024C\u024D\u024E\u024F\u0250\u0251\u0252\u0253\u0254\u0255\u0256\u0257\u0258\u0259\u025A\u025B\u025C\u025D\u025E\u025F\u0260\u0261\u0262\u0263\u0264\u0265\u0266\u0267\u0268\u0269\u026A\u026B\u026C\u026D\u026E\u026F\u0270\u0271\u0272\u0273\u0274\u0275\u0276\u0277\u0278\u0279\u027A\u027B\u027C\u027D\u027E\u027F\u0280\u0281\u0282\u0283\u0284\u0285\u0286\u0287\u0288\u0289\u028A\u028B\u028C\u028D\u028E\u028F\u0290\u0291\u0292\u0293\u0294\u0295\u0296\u0297\u0298\u0299\u029A\u029B\u029C\u029D\u029E\u029F\u02A0\u02A1\u02A2\u02A3\u02A4\u02A5\u02A6\u02A7\u02A8\u02A9\u02AA\u02AB\u02AC\u02AD\u02AE\u02AF\u02B0\u02B1\u02B2\u02B3\u02B4\u02B5\u02B6\u02B7\u02B8\u02B9\u02BA\u02BB\u02BC\u02BD\u02BE\u02BF\u02C0\u02C1\u02C2\u02C3\u02C4\u02C5\u02C6\u02C7\u02C8\u02C9\u02CA\u02CB\u02CC\u02CD\u02CE\u02CF\u02D0\u02D1\u02D2\u02D3\u02D4\u02D5\u02D6\u02D7\u02D8\u02D9\u02DA\u02DB\u02DC\u02DD\u02DE\u02DF\u02E0\u02E1\u02E2\u02E3\u02E4\u02E5\u02E6\u02E7\u02E8\u02E9\u02EA\u02EB\u02EC\u02ED\u02EE\u02EF\u02F0\u02F1\u02F2\u02F3\u02F4\u02F5\u02F6\u02F7\u02F8\u02F9\u02FA\u02FB\u02FC\u02FD\u02FE\u02FF\u0300\u0301\u0302\u0303\u0304\u0305\u0306\u0307\u0308\u0309\u030A\u030B\u030C\u030D\u030E\u030F\u0310\u0311\u0312\u0313\u0314\u0315\u0316\u0317\u0318\u0319\u031A\u031B\u031C\u031D\u031E\u031F\u0320\u0321\u0322\u0323\u0324\u0325\u0326\u0327\u0328\u0329\u032A\u032B\u032C\u032D\u032E\u032F\u0330\u0331\u0332\u0333\u0334\u0335\u0336\u0337\u0338\u0339\u033A\u033B\u033C\u033D\u033E\u033F\u0340\u0341\u0342\u0343\u0344\u0345\u0346\u0347\u0348\u0349\u034A\u034B\u034C\u034D\u034E\u034F\u0350\u0351\u0352\u0353\u0354\u0355\u0356\u0357\u0358\u0359\u035A\u035B\u035C\u035D\u035E\u035F\u0360\u0361\u0362\u0363\u0364\u0365\u0366\u0367\u0368\u0369\u036A\u036B\u036C\u036D\u036E\u036F\u0370\u0371\u0372\u0373\u0374\u0375\u0376\u0377\u0378\u0379\u037A\u037B\u037C\u037D\u037E\u037F\u0380\u0381\u0382\u0383\u0384\u0385\u0386\u0387\u0388\u0389\u038A\u038B\u038C\u038D\u038E\u038F\u0390\u0391\u0392\u0393\u0394\u0395\u0396\u0397\u0398\u0399\u039A\u039B\u039C\u039D\u039E\u039F\u03A0\u03A1\u03A2\u03A3\u03A4\u03A5\u03A6\u03A7\u03A8\u03A9\u03AA\u03AB\u03AC\u03AD\u03AE\u03AF\u03B0\u03B1\u03B2\u03B3\u03B4\u03B5\u03B6\u03B7\u03B8\u03B9\u03BA\u03BB\u03BC\u03BD\u03BE\u03BF\u03C0\u03C1\u03C2\u03C3\u03C4\u03C5\u03C6\u03C7\u03C8\u03C9\u03CA\u03CB\u03CC\u03CD\u03CE\u03CF\u03D0\u03D1\u03D2\u03D3\u03D4\u03D5\u03D6\u03D7\u03D8\u03D9\u03DA\u03DB\u03DC\u03DD\u03DE\u03DF\u03E0\u03E1\u03E2\u03E3\u03E4\u03E5\u03E6\u03E7\u03E8\u03E9\u03EA\u03EB\u03EC\u03ED\u03EE\u03EF\u03F0\u03F1\u03F2\u03F3\u03F4\u03F5\u03F6\u03F7\u03F8\u03F9\u03FA\u03FB\u03FC\u03FD\u03FE\u03FF"; +if (i0.replace(/\s+/g, "") !== o0) { + $ERROR("#0: Error matching character class \s between character 0 and 3ff"); +} + +var i1 = ""; +for (var j = 1024; j < 2048; j++) + i1 += String.fromCharCode(j); +var o1 = i1; +if (i1.replace(/\s+/g, "") !== o1) { + $ERROR("#1: Error matching character class \s between character 400 and 7ff"); +} + +var i2 = ""; +for (var j = 2048; j < 3072; j++) + i2 += String.fromCharCode(j); +var o2 = i2; +if (i2.replace(/\s+/g, "") !== o2) { + $ERROR("#2: Error matching character class \s between character 800 and bff"); +} + +var i3 = ""; +for (var j = 3072; j < 4096; j++) + i3 += String.fromCharCode(j); +var o3 = i3; +if (i3.replace(/\s+/g, "") !== o3) { + $ERROR("#3: Error matching character class \s between character c00 and fff"); +} + +var i4 = ""; +for (var j = 4096; j < 5120; j++) + i4 += String.fromCharCode(j); +var o4 = i4; +if (i4.replace(/\s+/g, "") !== o4) { + $ERROR("#4: Error matching character class \s between character 1000 and 13ff"); +} + +var i5 = ""; +for (var j = 5120; j < 6144; j++) + i5 += String.fromCharCode(j); +var o5 = "\u1400\u1401\u1402\u1403\u1404\u1405\u1406\u1407\u1408\u1409\u140A\u140B\u140C\u140D\u140E\u140F\u1410\u1411\u1412\u1413\u1414\u1415\u1416\u1417\u1418\u1419\u141A\u141B\u141C\u141D\u141E\u141F\u1420\u1421\u1422\u1423\u1424\u1425\u1426\u1427\u1428\u1429\u142A\u142B\u142C\u142D\u142E\u142F\u1430\u1431\u1432\u1433\u1434\u1435\u1436\u1437\u1438\u1439\u143A\u143B\u143C\u143D\u143E\u143F\u1440\u1441\u1442\u1443\u1444\u1445\u1446\u1447\u1448\u1449\u144A\u144B\u144C\u144D\u144E\u144F\u1450\u1451\u1452\u1453\u1454\u1455\u1456\u1457\u1458\u1459\u145A\u145B\u145C\u145D\u145E\u145F\u1460\u1461\u1462\u1463\u1464\u1465\u1466\u1467\u1468\u1469\u146A\u146B\u146C\u146D\u146E\u146F\u1470\u1471\u1472\u1473\u1474\u1475\u1476\u1477\u1478\u1479\u147A\u147B\u147C\u147D\u147E\u147F\u1480\u1481\u1482\u1483\u1484\u1485\u1486\u1487\u1488\u1489\u148A\u148B\u148C\u148D\u148E\u148F\u1490\u1491\u1492\u1493\u1494\u1495\u1496\u1497\u1498\u1499\u149A\u149B\u149C\u149D\u149E\u149F\u14A0\u14A1\u14A2\u14A3\u14A4\u14A5\u14A6\u14A7\u14A8\u14A9\u14AA\u14AB\u14AC\u14AD\u14AE\u14AF\u14B0\u14B1\u14B2\u14B3\u14B4\u14B5\u14B6\u14B7\u14B8\u14B9\u14BA\u14BB\u14BC\u14BD\u14BE\u14BF\u14C0\u14C1\u14C2\u14C3\u14C4\u14C5\u14C6\u14C7\u14C8\u14C9\u14CA\u14CB\u14CC\u14CD\u14CE\u14CF\u14D0\u14D1\u14D2\u14D3\u14D4\u14D5\u14D6\u14D7\u14D8\u14D9\u14DA\u14DB\u14DC\u14DD\u14DE\u14DF\u14E0\u14E1\u14E2\u14E3\u14E4\u14E5\u14E6\u14E7\u14E8\u14E9\u14EA\u14EB\u14EC\u14ED\u14EE\u14EF\u14F0\u14F1\u14F2\u14F3\u14F4\u14F5\u14F6\u14F7\u14F8\u14F9\u14FA\u14FB\u14FC\u14FD\u14FE\u14FF\u1500\u1501\u1502\u1503\u1504\u1505\u1506\u1507\u1508\u1509\u150A\u150B\u150C\u150D\u150E\u150F\u1510\u1511\u1512\u1513\u1514\u1515\u1516\u1517\u1518\u1519\u151A\u151B\u151C\u151D\u151E\u151F\u1520\u1521\u1522\u1523\u1524\u1525\u1526\u1527\u1528\u1529\u152A\u152B\u152C\u152D\u152E\u152F\u1530\u1531\u1532\u1533\u1534\u1535\u1536\u1537\u1538\u1539\u153A\u153B\u153C\u153D\u153E\u153F\u1540\u1541\u1542\u1543\u1544\u1545\u1546\u1547\u1548\u1549\u154A\u154B\u154C\u154D\u154E\u154F\u1550\u1551\u1552\u1553\u1554\u1555\u1556\u1557\u1558\u1559\u155A\u155B\u155C\u155D\u155E\u155F\u1560\u1561\u1562\u1563\u1564\u1565\u1566\u1567\u1568\u1569\u156A\u156B\u156C\u156D\u156E\u156F\u1570\u1571\u1572\u1573\u1574\u1575\u1576\u1577\u1578\u1579\u157A\u157B\u157C\u157D\u157E\u157F\u1580\u1581\u1582\u1583\u1584\u1585\u1586\u1587\u1588\u1589\u158A\u158B\u158C\u158D\u158E\u158F\u1590\u1591\u1592\u1593\u1594\u1595\u1596\u1597\u1598\u1599\u159A\u159B\u159C\u159D\u159E\u159F\u15A0\u15A1\u15A2\u15A3\u15A4\u15A5\u15A6\u15A7\u15A8\u15A9\u15AA\u15AB\u15AC\u15AD\u15AE\u15AF\u15B0\u15B1\u15B2\u15B3\u15B4\u15B5\u15B6\u15B7\u15B8\u15B9\u15BA\u15BB\u15BC\u15BD\u15BE\u15BF\u15C0\u15C1\u15C2\u15C3\u15C4\u15C5\u15C6\u15C7\u15C8\u15C9\u15CA\u15CB\u15CC\u15CD\u15CE\u15CF\u15D0\u15D1\u15D2\u15D3\u15D4\u15D5\u15D6\u15D7\u15D8\u15D9\u15DA\u15DB\u15DC\u15DD\u15DE\u15DF\u15E0\u15E1\u15E2\u15E3\u15E4\u15E5\u15E6\u15E7\u15E8\u15E9\u15EA\u15EB\u15EC\u15ED\u15EE\u15EF\u15F0\u15F1\u15F2\u15F3\u15F4\u15F5\u15F6\u15F7\u15F8\u15F9\u15FA\u15FB\u15FC\u15FD\u15FE\u15FF\u1600\u1601\u1602\u1603\u1604\u1605\u1606\u1607\u1608\u1609\u160A\u160B\u160C\u160D\u160E\u160F\u1610\u1611\u1612\u1613\u1614\u1615\u1616\u1617\u1618\u1619\u161A\u161B\u161C\u161D\u161E\u161F\u1620\u1621\u1622\u1623\u1624\u1625\u1626\u1627\u1628\u1629\u162A\u162B\u162C\u162D\u162E\u162F\u1630\u1631\u1632\u1633\u1634\u1635\u1636\u1637\u1638\u1639\u163A\u163B\u163C\u163D\u163E\u163F\u1640\u1641\u1642\u1643\u1644\u1645\u1646\u1647\u1648\u1649\u164A\u164B\u164C\u164D\u164E\u164F\u1650\u1651\u1652\u1653\u1654\u1655\u1656\u1657\u1658\u1659\u165A\u165B\u165C\u165D\u165E\u165F\u1660\u1661\u1662\u1663\u1664\u1665\u1666\u1667\u1668\u1669\u166A\u166B\u166C\u166D\u166E\u166F\u1670\u1671\u1672\u1673\u1674\u1675\u1676\u1677\u1678\u1679\u167A\u167B\u167C\u167D\u167E\u167F\u1681\u1682\u1683\u1684\u1685\u1686\u1687\u1688\u1689\u168A\u168B\u168C\u168D\u168E\u168F\u1690\u1691\u1692\u1693\u1694\u1695\u1696\u1697\u1698\u1699\u169A\u169B\u169C\u169D\u169E\u169F\u16A0\u16A1\u16A2\u16A3\u16A4\u16A5\u16A6\u16A7\u16A8\u16A9\u16AA\u16AB\u16AC\u16AD\u16AE\u16AF\u16B0\u16B1\u16B2\u16B3\u16B4\u16B5\u16B6\u16B7\u16B8\u16B9\u16BA\u16BB\u16BC\u16BD\u16BE\u16BF\u16C0\u16C1\u16C2\u16C3\u16C4\u16C5\u16C6\u16C7\u16C8\u16C9\u16CA\u16CB\u16CC\u16CD\u16CE\u16CF\u16D0\u16D1\u16D2\u16D3\u16D4\u16D5\u16D6\u16D7\u16D8\u16D9\u16DA\u16DB\u16DC\u16DD\u16DE\u16DF\u16E0\u16E1\u16E2\u16E3\u16E4\u16E5\u16E6\u16E7\u16E8\u16E9\u16EA\u16EB\u16EC\u16ED\u16EE\u16EF\u16F0\u16F1\u16F2\u16F3\u16F4\u16F5\u16F6\u16F7\u16F8\u16F9\u16FA\u16FB\u16FC\u16FD\u16FE\u16FF\u1700\u1701\u1702\u1703\u1704\u1705\u1706\u1707\u1708\u1709\u170A\u170B\u170C\u170D\u170E\u170F\u1710\u1711\u1712\u1713\u1714\u1715\u1716\u1717\u1718\u1719\u171A\u171B\u171C\u171D\u171E\u171F\u1720\u1721\u1722\u1723\u1724\u1725\u1726\u1727\u1728\u1729\u172A\u172B\u172C\u172D\u172E\u172F\u1730\u1731\u1732\u1733\u1734\u1735\u1736\u1737\u1738\u1739\u173A\u173B\u173C\u173D\u173E\u173F\u1740\u1741\u1742\u1743\u1744\u1745\u1746\u1747\u1748\u1749\u174A\u174B\u174C\u174D\u174E\u174F\u1750\u1751\u1752\u1753\u1754\u1755\u1756\u1757\u1758\u1759\u175A\u175B\u175C\u175D\u175E\u175F\u1760\u1761\u1762\u1763\u1764\u1765\u1766\u1767\u1768\u1769\u176A\u176B\u176C\u176D\u176E\u176F\u1770\u1771\u1772\u1773\u1774\u1775\u1776\u1777\u1778\u1779\u177A\u177B\u177C\u177D\u177E\u177F\u1780\u1781\u1782\u1783\u1784\u1785\u1786\u1787\u1788\u1789\u178A\u178B\u178C\u178D\u178E\u178F\u1790\u1791\u1792\u1793\u1794\u1795\u1796\u1797\u1798\u1799\u179A\u179B\u179C\u179D\u179E\u179F\u17A0\u17A1\u17A2\u17A3\u17A4\u17A5\u17A6\u17A7\u17A8\u17A9\u17AA\u17AB\u17AC\u17AD\u17AE\u17AF\u17B0\u17B1\u17B2\u17B3\u17B4\u17B5\u17B6\u17B7\u17B8\u17B9\u17BA\u17BB\u17BC\u17BD\u17BE\u17BF\u17C0\u17C1\u17C2\u17C3\u17C4\u17C5\u17C6\u17C7\u17C8\u17C9\u17CA\u17CB\u17CC\u17CD\u17CE\u17CF\u17D0\u17D1\u17D2\u17D3\u17D4\u17D5\u17D6\u17D7\u17D8\u17D9\u17DA\u17DB\u17DC\u17DD\u17DE\u17DF\u17E0\u17E1\u17E2\u17E3\u17E4\u17E5\u17E6\u17E7\u17E8\u17E9\u17EA\u17EB\u17EC\u17ED\u17EE\u17EF\u17F0\u17F1\u17F2\u17F3\u17F4\u17F5\u17F6\u17F7\u17F8\u17F9\u17FA\u17FB\u17FC\u17FD\u17FE\u17FF"; +if (i5.replace(/\s+/g, "") !== o5) { + $ERROR("#5: Error matching character class \s between character 1400 and 17ff"); +} + +var i6 = ""; +for (var j = 6144; j < 7168; j++) + i6 += String.fromCharCode(j); +var o6 = "\u1800\u1801\u1802\u1803\u1804\u1805\u1806\u1807\u1808\u1809\u180A\u180B\u180C\u180D\u180E\u180F\u1810\u1811\u1812\u1813\u1814\u1815\u1816\u1817\u1818\u1819\u181A\u181B\u181C\u181D\u181E\u181F\u1820\u1821\u1822\u1823\u1824\u1825\u1826\u1827\u1828\u1829\u182A\u182B\u182C\u182D\u182E\u182F\u1830\u1831\u1832\u1833\u1834\u1835\u1836\u1837\u1838\u1839\u183A\u183B\u183C\u183D\u183E\u183F\u1840\u1841\u1842\u1843\u1844\u1845\u1846\u1847\u1848\u1849\u184A\u184B\u184C\u184D\u184E\u184F\u1850\u1851\u1852\u1853\u1854\u1855\u1856\u1857\u1858\u1859\u185A\u185B\u185C\u185D\u185E\u185F\u1860\u1861\u1862\u1863\u1864\u1865\u1866\u1867\u1868\u1869\u186A\u186B\u186C\u186D\u186E\u186F\u1870\u1871\u1872\u1873\u1874\u1875\u1876\u1877\u1878\u1879\u187A\u187B\u187C\u187D\u187E\u187F\u1880\u1881\u1882\u1883\u1884\u1885\u1886\u1887\u1888\u1889\u188A\u188B\u188C\u188D\u188E\u188F\u1890\u1891\u1892\u1893\u1894\u1895\u1896\u1897\u1898\u1899\u189A\u189B\u189C\u189D\u189E\u189F\u18A0\u18A1\u18A2\u18A3\u18A4\u18A5\u18A6\u18A7\u18A8\u18A9\u18AA\u18AB\u18AC\u18AD\u18AE\u18AF\u18B0\u18B1\u18B2\u18B3\u18B4\u18B5\u18B6\u18B7\u18B8\u18B9\u18BA\u18BB\u18BC\u18BD\u18BE\u18BF\u18C0\u18C1\u18C2\u18C3\u18C4\u18C5\u18C6\u18C7\u18C8\u18C9\u18CA\u18CB\u18CC\u18CD\u18CE\u18CF\u18D0\u18D1\u18D2\u18D3\u18D4\u18D5\u18D6\u18D7\u18D8\u18D9\u18DA\u18DB\u18DC\u18DD\u18DE\u18DF\u18E0\u18E1\u18E2\u18E3\u18E4\u18E5\u18E6\u18E7\u18E8\u18E9\u18EA\u18EB\u18EC\u18ED\u18EE\u18EF\u18F0\u18F1\u18F2\u18F3\u18F4\u18F5\u18F6\u18F7\u18F8\u18F9\u18FA\u18FB\u18FC\u18FD\u18FE\u18FF\u1900\u1901\u1902\u1903\u1904\u1905\u1906\u1907\u1908\u1909\u190A\u190B\u190C\u190D\u190E\u190F\u1910\u1911\u1912\u1913\u1914\u1915\u1916\u1917\u1918\u1919\u191A\u191B\u191C\u191D\u191E\u191F\u1920\u1921\u1922\u1923\u1924\u1925\u1926\u1927\u1928\u1929\u192A\u192B\u192C\u192D\u192E\u192F\u1930\u1931\u1932\u1933\u1934\u1935\u1936\u1937\u1938\u1939\u193A\u193B\u193C\u193D\u193E\u193F\u1940\u1941\u1942\u1943\u1944\u1945\u1946\u1947\u1948\u1949\u194A\u194B\u194C\u194D\u194E\u194F\u1950\u1951\u1952\u1953\u1954\u1955\u1956\u1957\u1958\u1959\u195A\u195B\u195C\u195D\u195E\u195F\u1960\u1961\u1962\u1963\u1964\u1965\u1966\u1967\u1968\u1969\u196A\u196B\u196C\u196D\u196E\u196F\u1970\u1971\u1972\u1973\u1974\u1975\u1976\u1977\u1978\u1979\u197A\u197B\u197C\u197D\u197E\u197F\u1980\u1981\u1982\u1983\u1984\u1985\u1986\u1987\u1988\u1989\u198A\u198B\u198C\u198D\u198E\u198F\u1990\u1991\u1992\u1993\u1994\u1995\u1996\u1997\u1998\u1999\u199A\u199B\u199C\u199D\u199E\u199F\u19A0\u19A1\u19A2\u19A3\u19A4\u19A5\u19A6\u19A7\u19A8\u19A9\u19AA\u19AB\u19AC\u19AD\u19AE\u19AF\u19B0\u19B1\u19B2\u19B3\u19B4\u19B5\u19B6\u19B7\u19B8\u19B9\u19BA\u19BB\u19BC\u19BD\u19BE\u19BF\u19C0\u19C1\u19C2\u19C3\u19C4\u19C5\u19C6\u19C7\u19C8\u19C9\u19CA\u19CB\u19CC\u19CD\u19CE\u19CF\u19D0\u19D1\u19D2\u19D3\u19D4\u19D5\u19D6\u19D7\u19D8\u19D9\u19DA\u19DB\u19DC\u19DD\u19DE\u19DF\u19E0\u19E1\u19E2\u19E3\u19E4\u19E5\u19E6\u19E7\u19E8\u19E9\u19EA\u19EB\u19EC\u19ED\u19EE\u19EF\u19F0\u19F1\u19F2\u19F3\u19F4\u19F5\u19F6\u19F7\u19F8\u19F9\u19FA\u19FB\u19FC\u19FD\u19FE\u19FF\u1A00\u1A01\u1A02\u1A03\u1A04\u1A05\u1A06\u1A07\u1A08\u1A09\u1A0A\u1A0B\u1A0C\u1A0D\u1A0E\u1A0F\u1A10\u1A11\u1A12\u1A13\u1A14\u1A15\u1A16\u1A17\u1A18\u1A19\u1A1A\u1A1B\u1A1C\u1A1D\u1A1E\u1A1F\u1A20\u1A21\u1A22\u1A23\u1A24\u1A25\u1A26\u1A27\u1A28\u1A29\u1A2A\u1A2B\u1A2C\u1A2D\u1A2E\u1A2F\u1A30\u1A31\u1A32\u1A33\u1A34\u1A35\u1A36\u1A37\u1A38\u1A39\u1A3A\u1A3B\u1A3C\u1A3D\u1A3E\u1A3F\u1A40\u1A41\u1A42\u1A43\u1A44\u1A45\u1A46\u1A47\u1A48\u1A49\u1A4A\u1A4B\u1A4C\u1A4D\u1A4E\u1A4F\u1A50\u1A51\u1A52\u1A53\u1A54\u1A55\u1A56\u1A57\u1A58\u1A59\u1A5A\u1A5B\u1A5C\u1A5D\u1A5E\u1A5F\u1A60\u1A61\u1A62\u1A63\u1A64\u1A65\u1A66\u1A67\u1A68\u1A69\u1A6A\u1A6B\u1A6C\u1A6D\u1A6E\u1A6F\u1A70\u1A71\u1A72\u1A73\u1A74\u1A75\u1A76\u1A77\u1A78\u1A79\u1A7A\u1A7B\u1A7C\u1A7D\u1A7E\u1A7F\u1A80\u1A81\u1A82\u1A83\u1A84\u1A85\u1A86\u1A87\u1A88\u1A89\u1A8A\u1A8B\u1A8C\u1A8D\u1A8E\u1A8F\u1A90\u1A91\u1A92\u1A93\u1A94\u1A95\u1A96\u1A97\u1A98\u1A99\u1A9A\u1A9B\u1A9C\u1A9D\u1A9E\u1A9F\u1AA0\u1AA1\u1AA2\u1AA3\u1AA4\u1AA5\u1AA6\u1AA7\u1AA8\u1AA9\u1AAA\u1AAB\u1AAC\u1AAD\u1AAE\u1AAF\u1AB0\u1AB1\u1AB2\u1AB3\u1AB4\u1AB5\u1AB6\u1AB7\u1AB8\u1AB9\u1ABA\u1ABB\u1ABC\u1ABD\u1ABE\u1ABF\u1AC0\u1AC1\u1AC2\u1AC3\u1AC4\u1AC5\u1AC6\u1AC7\u1AC8\u1AC9\u1ACA\u1ACB\u1ACC\u1ACD\u1ACE\u1ACF\u1AD0\u1AD1\u1AD2\u1AD3\u1AD4\u1AD5\u1AD6\u1AD7\u1AD8\u1AD9\u1ADA\u1ADB\u1ADC\u1ADD\u1ADE\u1ADF\u1AE0\u1AE1\u1AE2\u1AE3\u1AE4\u1AE5\u1AE6\u1AE7\u1AE8\u1AE9\u1AEA\u1AEB\u1AEC\u1AED\u1AEE\u1AEF\u1AF0\u1AF1\u1AF2\u1AF3\u1AF4\u1AF5\u1AF6\u1AF7\u1AF8\u1AF9\u1AFA\u1AFB\u1AFC\u1AFD\u1AFE\u1AFF\u1B00\u1B01\u1B02\u1B03\u1B04\u1B05\u1B06\u1B07\u1B08\u1B09\u1B0A\u1B0B\u1B0C\u1B0D\u1B0E\u1B0F\u1B10\u1B11\u1B12\u1B13\u1B14\u1B15\u1B16\u1B17\u1B18\u1B19\u1B1A\u1B1B\u1B1C\u1B1D\u1B1E\u1B1F\u1B20\u1B21\u1B22\u1B23\u1B24\u1B25\u1B26\u1B27\u1B28\u1B29\u1B2A\u1B2B\u1B2C\u1B2D\u1B2E\u1B2F\u1B30\u1B31\u1B32\u1B33\u1B34\u1B35\u1B36\u1B37\u1B38\u1B39\u1B3A\u1B3B\u1B3C\u1B3D\u1B3E\u1B3F\u1B40\u1B41\u1B42\u1B43\u1B44\u1B45\u1B46\u1B47\u1B48\u1B49\u1B4A\u1B4B\u1B4C\u1B4D\u1B4E\u1B4F\u1B50\u1B51\u1B52\u1B53\u1B54\u1B55\u1B56\u1B57\u1B58\u1B59\u1B5A\u1B5B\u1B5C\u1B5D\u1B5E\u1B5F\u1B60\u1B61\u1B62\u1B63\u1B64\u1B65\u1B66\u1B67\u1B68\u1B69\u1B6A\u1B6B\u1B6C\u1B6D\u1B6E\u1B6F\u1B70\u1B71\u1B72\u1B73\u1B74\u1B75\u1B76\u1B77\u1B78\u1B79\u1B7A\u1B7B\u1B7C\u1B7D\u1B7E\u1B7F\u1B80\u1B81\u1B82\u1B83\u1B84\u1B85\u1B86\u1B87\u1B88\u1B89\u1B8A\u1B8B\u1B8C\u1B8D\u1B8E\u1B8F\u1B90\u1B91\u1B92\u1B93\u1B94\u1B95\u1B96\u1B97\u1B98\u1B99\u1B9A\u1B9B\u1B9C\u1B9D\u1B9E\u1B9F\u1BA0\u1BA1\u1BA2\u1BA3\u1BA4\u1BA5\u1BA6\u1BA7\u1BA8\u1BA9\u1BAA\u1BAB\u1BAC\u1BAD\u1BAE\u1BAF\u1BB0\u1BB1\u1BB2\u1BB3\u1BB4\u1BB5\u1BB6\u1BB7\u1BB8\u1BB9\u1BBA\u1BBB\u1BBC\u1BBD\u1BBE\u1BBF\u1BC0\u1BC1\u1BC2\u1BC3\u1BC4\u1BC5\u1BC6\u1BC7\u1BC8\u1BC9\u1BCA\u1BCB\u1BCC\u1BCD\u1BCE\u1BCF\u1BD0\u1BD1\u1BD2\u1BD3\u1BD4\u1BD5\u1BD6\u1BD7\u1BD8\u1BD9\u1BDA\u1BDB\u1BDC\u1BDD\u1BDE\u1BDF\u1BE0\u1BE1\u1BE2\u1BE3\u1BE4\u1BE5\u1BE6\u1BE7\u1BE8\u1BE9\u1BEA\u1BEB\u1BEC\u1BED\u1BEE\u1BEF\u1BF0\u1BF1\u1BF2\u1BF3\u1BF4\u1BF5\u1BF6\u1BF7\u1BF8\u1BF9\u1BFA\u1BFB\u1BFC\u1BFD\u1BFE\u1BFF"; +if (i6.replace(/\s+/g, "") !== o6) { + $ERROR("#6: Error matching character class \s between character 1800 and 1bff"); +} + +var i7 = ""; +for (var j = 7168; j < 8192; j++) + i7 += String.fromCharCode(j); +var o7 = i7; +if (i7.replace(/\s+/g, "") !== o7) { + $ERROR("#7: Error matching character class \s between character 1c00 and 1fff"); +} + +var i8 = ""; +for (var j = 8192; j < 9216; j++) + i8 += String.fromCharCode(j); +var o8 = "\u200B\u200C\u200D\u200E\u200F\u2010\u2011\u2012\u2013\u2014\u2015\u2016\u2017\u2018\u2019\u201A\u201B\u201C\u201D\u201E\u201F\u2020\u2021\u2022\u2023\u2024\u2025\u2026\u2027\u202A\u202B\u202C\u202D\u202E\u2030\u2031\u2032\u2033\u2034\u2035\u2036\u2037\u2038\u2039\u203A\u203B\u203C\u203D\u203E\u203F\u2040\u2041\u2042\u2043\u2044\u2045\u2046\u2047\u2048\u2049\u204A\u204B\u204C\u204D\u204E\u204F\u2050\u2051\u2052\u2053\u2054\u2055\u2056\u2057\u2058\u2059\u205A\u205B\u205C\u205D\u205E\u2060\u2061\u2062\u2063\u2064\u2065\u2066\u2067\u2068\u2069\u206A\u206B\u206C\u206D\u206E\u206F\u2070\u2071\u2072\u2073\u2074\u2075\u2076\u2077\u2078\u2079\u207A\u207B\u207C\u207D\u207E\u207F\u2080\u2081\u2082\u2083\u2084\u2085\u2086\u2087\u2088\u2089\u208A\u208B\u208C\u208D\u208E\u208F\u2090\u2091\u2092\u2093\u2094\u2095\u2096\u2097\u2098\u2099\u209A\u209B\u209C\u209D\u209E\u209F\u20A0\u20A1\u20A2\u20A3\u20A4\u20A5\u20A6\u20A7\u20A8\u20A9\u20AA\u20AB\u20AC\u20AD\u20AE\u20AF\u20B0\u20B1\u20B2\u20B3\u20B4\u20B5\u20B6\u20B7\u20B8\u20B9\u20BA\u20BB\u20BC\u20BD\u20BE\u20BF\u20C0\u20C1\u20C2\u20C3\u20C4\u20C5\u20C6\u20C7\u20C8\u20C9\u20CA\u20CB\u20CC\u20CD\u20CE\u20CF\u20D0\u20D1\u20D2\u20D3\u20D4\u20D5\u20D6\u20D7\u20D8\u20D9\u20DA\u20DB\u20DC\u20DD\u20DE\u20DF\u20E0\u20E1\u20E2\u20E3\u20E4\u20E5\u20E6\u20E7\u20E8\u20E9\u20EA\u20EB\u20EC\u20ED\u20EE\u20EF\u20F0\u20F1\u20F2\u20F3\u20F4\u20F5\u20F6\u20F7\u20F8\u20F9\u20FA\u20FB\u20FC\u20FD\u20FE\u20FF\u2100\u2101\u2102\u2103\u2104\u2105\u2106\u2107\u2108\u2109\u210A\u210B\u210C\u210D\u210E\u210F\u2110\u2111\u2112\u2113\u2114\u2115\u2116\u2117\u2118\u2119\u211A\u211B\u211C\u211D\u211E\u211F\u2120\u2121\u2122\u2123\u2124\u2125\u2126\u2127\u2128\u2129\u212A\u212B\u212C\u212D\u212E\u212F\u2130\u2131\u2132\u2133\u2134\u2135\u2136\u2137\u2138\u2139\u213A\u213B\u213C\u213D\u213E\u213F\u2140\u2141\u2142\u2143\u2144\u2145\u2146\u2147\u2148\u2149\u214A\u214B\u214C\u214D\u214E\u214F\u2150\u2151\u2152\u2153\u2154\u2155\u2156\u2157\u2158\u2159\u215A\u215B\u215C\u215D\u215E\u215F\u2160\u2161\u2162\u2163\u2164\u2165\u2166\u2167\u2168\u2169\u216A\u216B\u216C\u216D\u216E\u216F\u2170\u2171\u2172\u2173\u2174\u2175\u2176\u2177\u2178\u2179\u217A\u217B\u217C\u217D\u217E\u217F\u2180\u2181\u2182\u2183\u2184\u2185\u2186\u2187\u2188\u2189\u218A\u218B\u218C\u218D\u218E\u218F\u2190\u2191\u2192\u2193\u2194\u2195\u2196\u2197\u2198\u2199\u219A\u219B\u219C\u219D\u219E\u219F\u21A0\u21A1\u21A2\u21A3\u21A4\u21A5\u21A6\u21A7\u21A8\u21A9\u21AA\u21AB\u21AC\u21AD\u21AE\u21AF\u21B0\u21B1\u21B2\u21B3\u21B4\u21B5\u21B6\u21B7\u21B8\u21B9\u21BA\u21BB\u21BC\u21BD\u21BE\u21BF\u21C0\u21C1\u21C2\u21C3\u21C4\u21C5\u21C6\u21C7\u21C8\u21C9\u21CA\u21CB\u21CC\u21CD\u21CE\u21CF\u21D0\u21D1\u21D2\u21D3\u21D4\u21D5\u21D6\u21D7\u21D8\u21D9\u21DA\u21DB\u21DC\u21DD\u21DE\u21DF\u21E0\u21E1\u21E2\u21E3\u21E4\u21E5\u21E6\u21E7\u21E8\u21E9\u21EA\u21EB\u21EC\u21ED\u21EE\u21EF\u21F0\u21F1\u21F2\u21F3\u21F4\u21F5\u21F6\u21F7\u21F8\u21F9\u21FA\u21FB\u21FC\u21FD\u21FE\u21FF\u2200\u2201\u2202\u2203\u2204\u2205\u2206\u2207\u2208\u2209\u220A\u220B\u220C\u220D\u220E\u220F\u2210\u2211\u2212\u2213\u2214\u2215\u2216\u2217\u2218\u2219\u221A\u221B\u221C\u221D\u221E\u221F\u2220\u2221\u2222\u2223\u2224\u2225\u2226\u2227\u2228\u2229\u222A\u222B\u222C\u222D\u222E\u222F\u2230\u2231\u2232\u2233\u2234\u2235\u2236\u2237\u2238\u2239\u223A\u223B\u223C\u223D\u223E\u223F\u2240\u2241\u2242\u2243\u2244\u2245\u2246\u2247\u2248\u2249\u224A\u224B\u224C\u224D\u224E\u224F\u2250\u2251\u2252\u2253\u2254\u2255\u2256\u2257\u2258\u2259\u225A\u225B\u225C\u225D\u225E\u225F\u2260\u2261\u2262\u2263\u2264\u2265\u2266\u2267\u2268\u2269\u226A\u226B\u226C\u226D\u226E\u226F\u2270\u2271\u2272\u2273\u2274\u2275\u2276\u2277\u2278\u2279\u227A\u227B\u227C\u227D\u227E\u227F\u2280\u2281\u2282\u2283\u2284\u2285\u2286\u2287\u2288\u2289\u228A\u228B\u228C\u228D\u228E\u228F\u2290\u2291\u2292\u2293\u2294\u2295\u2296\u2297\u2298\u2299\u229A\u229B\u229C\u229D\u229E\u229F\u22A0\u22A1\u22A2\u22A3\u22A4\u22A5\u22A6\u22A7\u22A8\u22A9\u22AA\u22AB\u22AC\u22AD\u22AE\u22AF\u22B0\u22B1\u22B2\u22B3\u22B4\u22B5\u22B6\u22B7\u22B8\u22B9\u22BA\u22BB\u22BC\u22BD\u22BE\u22BF\u22C0\u22C1\u22C2\u22C3\u22C4\u22C5\u22C6\u22C7\u22C8\u22C9\u22CA\u22CB\u22CC\u22CD\u22CE\u22CF\u22D0\u22D1\u22D2\u22D3\u22D4\u22D5\u22D6\u22D7\u22D8\u22D9\u22DA\u22DB\u22DC\u22DD\u22DE\u22DF\u22E0\u22E1\u22E2\u22E3\u22E4\u22E5\u22E6\u22E7\u22E8\u22E9\u22EA\u22EB\u22EC\u22ED\u22EE\u22EF\u22F0\u22F1\u22F2\u22F3\u22F4\u22F5\u22F6\u22F7\u22F8\u22F9\u22FA\u22FB\u22FC\u22FD\u22FE\u22FF\u2300\u2301\u2302\u2303\u2304\u2305\u2306\u2307\u2308\u2309\u230A\u230B\u230C\u230D\u230E\u230F\u2310\u2311\u2312\u2313\u2314\u2315\u2316\u2317\u2318\u2319\u231A\u231B\u231C\u231D\u231E\u231F\u2320\u2321\u2322\u2323\u2324\u2325\u2326\u2327\u2328\u2329\u232A\u232B\u232C\u232D\u232E\u232F\u2330\u2331\u2332\u2333\u2334\u2335\u2336\u2337\u2338\u2339\u233A\u233B\u233C\u233D\u233E\u233F\u2340\u2341\u2342\u2343\u2344\u2345\u2346\u2347\u2348\u2349\u234A\u234B\u234C\u234D\u234E\u234F\u2350\u2351\u2352\u2353\u2354\u2355\u2356\u2357\u2358\u2359\u235A\u235B\u235C\u235D\u235E\u235F\u2360\u2361\u2362\u2363\u2364\u2365\u2366\u2367\u2368\u2369\u236A\u236B\u236C\u236D\u236E\u236F\u2370\u2371\u2372\u2373\u2374\u2375\u2376\u2377\u2378\u2379\u237A\u237B\u237C\u237D\u237E\u237F\u2380\u2381\u2382\u2383\u2384\u2385\u2386\u2387\u2388\u2389\u238A\u238B\u238C\u238D\u238E\u238F\u2390\u2391\u2392\u2393\u2394\u2395\u2396\u2397\u2398\u2399\u239A\u239B\u239C\u239D\u239E\u239F\u23A0\u23A1\u23A2\u23A3\u23A4\u23A5\u23A6\u23A7\u23A8\u23A9\u23AA\u23AB\u23AC\u23AD\u23AE\u23AF\u23B0\u23B1\u23B2\u23B3\u23B4\u23B5\u23B6\u23B7\u23B8\u23B9\u23BA\u23BB\u23BC\u23BD\u23BE\u23BF\u23C0\u23C1\u23C2\u23C3\u23C4\u23C5\u23C6\u23C7\u23C8\u23C9\u23CA\u23CB\u23CC\u23CD\u23CE\u23CF\u23D0\u23D1\u23D2\u23D3\u23D4\u23D5\u23D6\u23D7\u23D8\u23D9\u23DA\u23DB\u23DC\u23DD\u23DE\u23DF\u23E0\u23E1\u23E2\u23E3\u23E4\u23E5\u23E6\u23E7\u23E8\u23E9\u23EA\u23EB\u23EC\u23ED\u23EE\u23EF\u23F0\u23F1\u23F2\u23F3\u23F4\u23F5\u23F6\u23F7\u23F8\u23F9\u23FA\u23FB\u23FC\u23FD\u23FE\u23FF"; +if (i8.replace(/\s+/g, "") !== o8) { + $ERROR("#8: Error matching character class \s between character 2000 and 23ff"); +} + +var i9 = ""; +for (var j = 9216; j < 10240; j++) + i9 += String.fromCharCode(j); +var o9 = i9; +if (i9.replace(/\s+/g, "") !== o9) { + $ERROR("#9: Error matching character class \s between character 2400 and 27ff"); +} + +var i10 = ""; +for (var j = 10240; j < 11264; j++) + i10 += String.fromCharCode(j); +var o10 = i10; +if (i10.replace(/\s+/g, "") !== o10) { + $ERROR("#10: Error matching character class \s between character 2800 and 2bff"); +} + +var i11 = ""; +for (var j = 11264; j < 12288; j++) + i11 += String.fromCharCode(j); +var o11 = i11; +if (i11.replace(/\s+/g, "") !== o11) { + $ERROR("#11: Error matching character class \s between character 2c00 and 2fff"); +} + +var i12 = ""; +for (var j = 12288; j < 13312; j++) + i12 += String.fromCharCode(j); +var o12 = "\u3001\u3002\u3003\u3004\u3005\u3006\u3007\u3008\u3009\u300A\u300B\u300C\u300D\u300E\u300F\u3010\u3011\u3012\u3013\u3014\u3015\u3016\u3017\u3018\u3019\u301A\u301B\u301C\u301D\u301E\u301F\u3020\u3021\u3022\u3023\u3024\u3025\u3026\u3027\u3028\u3029\u302A\u302B\u302C\u302D\u302E\u302F\u3030\u3031\u3032\u3033\u3034\u3035\u3036\u3037\u3038\u3039\u303A\u303B\u303C\u303D\u303E\u303F\u3040\u3041\u3042\u3043\u3044\u3045\u3046\u3047\u3048\u3049\u304A\u304B\u304C\u304D\u304E\u304F\u3050\u3051\u3052\u3053\u3054\u3055\u3056\u3057\u3058\u3059\u305A\u305B\u305C\u305D\u305E\u305F\u3060\u3061\u3062\u3063\u3064\u3065\u3066\u3067\u3068\u3069\u306A\u306B\u306C\u306D\u306E\u306F\u3070\u3071\u3072\u3073\u3074\u3075\u3076\u3077\u3078\u3079\u307A\u307B\u307C\u307D\u307E\u307F\u3080\u3081\u3082\u3083\u3084\u3085\u3086\u3087\u3088\u3089\u308A\u308B\u308C\u308D\u308E\u308F\u3090\u3091\u3092\u3093\u3094\u3095\u3096\u3097\u3098\u3099\u309A\u309B\u309C\u309D\u309E\u309F\u30A0\u30A1\u30A2\u30A3\u30A4\u30A5\u30A6\u30A7\u30A8\u30A9\u30AA\u30AB\u30AC\u30AD\u30AE\u30AF\u30B0\u30B1\u30B2\u30B3\u30B4\u30B5\u30B6\u30B7\u30B8\u30B9\u30BA\u30BB\u30BC\u30BD\u30BE\u30BF\u30C0\u30C1\u30C2\u30C3\u30C4\u30C5\u30C6\u30C7\u30C8\u30C9\u30CA\u30CB\u30CC\u30CD\u30CE\u30CF\u30D0\u30D1\u30D2\u30D3\u30D4\u30D5\u30D6\u30D7\u30D8\u30D9\u30DA\u30DB\u30DC\u30DD\u30DE\u30DF\u30E0\u30E1\u30E2\u30E3\u30E4\u30E5\u30E6\u30E7\u30E8\u30E9\u30EA\u30EB\u30EC\u30ED\u30EE\u30EF\u30F0\u30F1\u30F2\u30F3\u30F4\u30F5\u30F6\u30F7\u30F8\u30F9\u30FA\u30FB\u30FC\u30FD\u30FE\u30FF\u3100\u3101\u3102\u3103\u3104\u3105\u3106\u3107\u3108\u3109\u310A\u310B\u310C\u310D\u310E\u310F\u3110\u3111\u3112\u3113\u3114\u3115\u3116\u3117\u3118\u3119\u311A\u311B\u311C\u311D\u311E\u311F\u3120\u3121\u3122\u3123\u3124\u3125\u3126\u3127\u3128\u3129\u312A\u312B\u312C\u312D\u312E\u312F\u3130\u3131\u3132\u3133\u3134\u3135\u3136\u3137\u3138\u3139\u313A\u313B\u313C\u313D\u313E\u313F\u3140\u3141\u3142\u3143\u3144\u3145\u3146\u3147\u3148\u3149\u314A\u314B\u314C\u314D\u314E\u314F\u3150\u3151\u3152\u3153\u3154\u3155\u3156\u3157\u3158\u3159\u315A\u315B\u315C\u315D\u315E\u315F\u3160\u3161\u3162\u3163\u3164\u3165\u3166\u3167\u3168\u3169\u316A\u316B\u316C\u316D\u316E\u316F\u3170\u3171\u3172\u3173\u3174\u3175\u3176\u3177\u3178\u3179\u317A\u317B\u317C\u317D\u317E\u317F\u3180\u3181\u3182\u3183\u3184\u3185\u3186\u3187\u3188\u3189\u318A\u318B\u318C\u318D\u318E\u318F\u3190\u3191\u3192\u3193\u3194\u3195\u3196\u3197\u3198\u3199\u319A\u319B\u319C\u319D\u319E\u319F\u31A0\u31A1\u31A2\u31A3\u31A4\u31A5\u31A6\u31A7\u31A8\u31A9\u31AA\u31AB\u31AC\u31AD\u31AE\u31AF\u31B0\u31B1\u31B2\u31B3\u31B4\u31B5\u31B6\u31B7\u31B8\u31B9\u31BA\u31BB\u31BC\u31BD\u31BE\u31BF\u31C0\u31C1\u31C2\u31C3\u31C4\u31C5\u31C6\u31C7\u31C8\u31C9\u31CA\u31CB\u31CC\u31CD\u31CE\u31CF\u31D0\u31D1\u31D2\u31D3\u31D4\u31D5\u31D6\u31D7\u31D8\u31D9\u31DA\u31DB\u31DC\u31DD\u31DE\u31DF\u31E0\u31E1\u31E2\u31E3\u31E4\u31E5\u31E6\u31E7\u31E8\u31E9\u31EA\u31EB\u31EC\u31ED\u31EE\u31EF\u31F0\u31F1\u31F2\u31F3\u31F4\u31F5\u31F6\u31F7\u31F8\u31F9\u31FA\u31FB\u31FC\u31FD\u31FE\u31FF\u3200\u3201\u3202\u3203\u3204\u3205\u3206\u3207\u3208\u3209\u320A\u320B\u320C\u320D\u320E\u320F\u3210\u3211\u3212\u3213\u3214\u3215\u3216\u3217\u3218\u3219\u321A\u321B\u321C\u321D\u321E\u321F\u3220\u3221\u3222\u3223\u3224\u3225\u3226\u3227\u3228\u3229\u322A\u322B\u322C\u322D\u322E\u322F\u3230\u3231\u3232\u3233\u3234\u3235\u3236\u3237\u3238\u3239\u323A\u323B\u323C\u323D\u323E\u323F\u3240\u3241\u3242\u3243\u3244\u3245\u3246\u3247\u3248\u3249\u324A\u324B\u324C\u324D\u324E\u324F\u3250\u3251\u3252\u3253\u3254\u3255\u3256\u3257\u3258\u3259\u325A\u325B\u325C\u325D\u325E\u325F\u3260\u3261\u3262\u3263\u3264\u3265\u3266\u3267\u3268\u3269\u326A\u326B\u326C\u326D\u326E\u326F\u3270\u3271\u3272\u3273\u3274\u3275\u3276\u3277\u3278\u3279\u327A\u327B\u327C\u327D\u327E\u327F\u3280\u3281\u3282\u3283\u3284\u3285\u3286\u3287\u3288\u3289\u328A\u328B\u328C\u328D\u328E\u328F\u3290\u3291\u3292\u3293\u3294\u3295\u3296\u3297\u3298\u3299\u329A\u329B\u329C\u329D\u329E\u329F\u32A0\u32A1\u32A2\u32A3\u32A4\u32A5\u32A6\u32A7\u32A8\u32A9\u32AA\u32AB\u32AC\u32AD\u32AE\u32AF\u32B0\u32B1\u32B2\u32B3\u32B4\u32B5\u32B6\u32B7\u32B8\u32B9\u32BA\u32BB\u32BC\u32BD\u32BE\u32BF\u32C0\u32C1\u32C2\u32C3\u32C4\u32C5\u32C6\u32C7\u32C8\u32C9\u32CA\u32CB\u32CC\u32CD\u32CE\u32CF\u32D0\u32D1\u32D2\u32D3\u32D4\u32D5\u32D6\u32D7\u32D8\u32D9\u32DA\u32DB\u32DC\u32DD\u32DE\u32DF\u32E0\u32E1\u32E2\u32E3\u32E4\u32E5\u32E6\u32E7\u32E8\u32E9\u32EA\u32EB\u32EC\u32ED\u32EE\u32EF\u32F0\u32F1\u32F2\u32F3\u32F4\u32F5\u32F6\u32F7\u32F8\u32F9\u32FA\u32FB\u32FC\u32FD\u32FE\u32FF\u3300\u3301\u3302\u3303\u3304\u3305\u3306\u3307\u3308\u3309\u330A\u330B\u330C\u330D\u330E\u330F\u3310\u3311\u3312\u3313\u3314\u3315\u3316\u3317\u3318\u3319\u331A\u331B\u331C\u331D\u331E\u331F\u3320\u3321\u3322\u3323\u3324\u3325\u3326\u3327\u3328\u3329\u332A\u332B\u332C\u332D\u332E\u332F\u3330\u3331\u3332\u3333\u3334\u3335\u3336\u3337\u3338\u3339\u333A\u333B\u333C\u333D\u333E\u333F\u3340\u3341\u3342\u3343\u3344\u3345\u3346\u3347\u3348\u3349\u334A\u334B\u334C\u334D\u334E\u334F\u3350\u3351\u3352\u3353\u3354\u3355\u3356\u3357\u3358\u3359\u335A\u335B\u335C\u335D\u335E\u335F\u3360\u3361\u3362\u3363\u3364\u3365\u3366\u3367\u3368\u3369\u336A\u336B\u336C\u336D\u336E\u336F\u3370\u3371\u3372\u3373\u3374\u3375\u3376\u3377\u3378\u3379\u337A\u337B\u337C\u337D\u337E\u337F\u3380\u3381\u3382\u3383\u3384\u3385\u3386\u3387\u3388\u3389\u338A\u338B\u338C\u338D\u338E\u338F\u3390\u3391\u3392\u3393\u3394\u3395\u3396\u3397\u3398\u3399\u339A\u339B\u339C\u339D\u339E\u339F\u33A0\u33A1\u33A2\u33A3\u33A4\u33A5\u33A6\u33A7\u33A8\u33A9\u33AA\u33AB\u33AC\u33AD\u33AE\u33AF\u33B0\u33B1\u33B2\u33B3\u33B4\u33B5\u33B6\u33B7\u33B8\u33B9\u33BA\u33BB\u33BC\u33BD\u33BE\u33BF\u33C0\u33C1\u33C2\u33C3\u33C4\u33C5\u33C6\u33C7\u33C8\u33C9\u33CA\u33CB\u33CC\u33CD\u33CE\u33CF\u33D0\u33D1\u33D2\u33D3\u33D4\u33D5\u33D6\u33D7\u33D8\u33D9\u33DA\u33DB\u33DC\u33DD\u33DE\u33DF\u33E0\u33E1\u33E2\u33E3\u33E4\u33E5\u33E6\u33E7\u33E8\u33E9\u33EA\u33EB\u33EC\u33ED\u33EE\u33EF\u33F0\u33F1\u33F2\u33F3\u33F4\u33F5\u33F6\u33F7\u33F8\u33F9\u33FA\u33FB\u33FC\u33FD\u33FE\u33FF"; +if (i12.replace(/\s+/g, "") !== o12) { + $ERROR("#12: Error matching character class \s between character 3000 and 33ff"); +} + +var i13 = ""; +for (var j = 13312; j < 14336; j++) + i13 += String.fromCharCode(j); +var o13 = i13; +if (i13.replace(/\s+/g, "") !== o13) { + $ERROR("#13: Error matching character class \s between character 3400 and 37ff"); +} + +var i14 = ""; +for (var j = 14336; j < 15360; j++) + i14 += String.fromCharCode(j); +var o14 = i14; +if (i14.replace(/\s+/g, "") !== o14) { + $ERROR("#14: Error matching character class \s between character 3800 and 3bff"); +} + +var i15 = ""; +for (var j = 15360; j < 16384; j++) + i15 += String.fromCharCode(j); +var o15 = i15; +if (i15.replace(/\s+/g, "") !== o15) { + $ERROR("#15: Error matching character class \s between character 3c00 and 3fff"); +} + +var i16 = ""; +for (var j = 16384; j < 17408; j++) + i16 += String.fromCharCode(j); +var o16 = i16; +if (i16.replace(/\s+/g, "") !== o16) { + $ERROR("#16: Error matching character class \s between character 4000 and 43ff"); +} + +var i17 = ""; +for (var j = 17408; j < 18432; j++) + i17 += String.fromCharCode(j); +var o17 = i17; +if (i17.replace(/\s+/g, "") !== o17) { + $ERROR("#17: Error matching character class \s between character 4400 and 47ff"); +} + +var i18 = ""; +for (var j = 18432; j < 19456; j++) + i18 += String.fromCharCode(j); +var o18 = i18; +if (i18.replace(/\s+/g, "") !== o18) { + $ERROR("#18: Error matching character class \s between character 4800 and 4bff"); +} + +var i19 = ""; +for (var j = 19456; j < 20480; j++) + i19 += String.fromCharCode(j); +var o19 = i19; +if (i19.replace(/\s+/g, "") !== o19) { + $ERROR("#19: Error matching character class \s between character 4c00 and 4fff"); +} + +var i20 = ""; +for (var j = 20480; j < 21504; j++) + i20 += String.fromCharCode(j); +var o20 = i20; +if (i20.replace(/\s+/g, "") !== o20) { + $ERROR("#20: Error matching character class \s between character 5000 and 53ff"); +} + +var i21 = ""; +for (var j = 21504; j < 22528; j++) + i21 += String.fromCharCode(j); +var o21 = i21; +if (i21.replace(/\s+/g, "") !== o21) { + $ERROR("#21: Error matching character class \s between character 5400 and 57ff"); +} + +var i22 = ""; +for (var j = 22528; j < 23552; j++) + i22 += String.fromCharCode(j); +var o22 = i22; +if (i22.replace(/\s+/g, "") !== o22) { + $ERROR("#22: Error matching character class \s between character 5800 and 5bff"); +} + +var i23 = ""; +for (var j = 23552; j < 24576; j++) + i23 += String.fromCharCode(j); +var o23 = i23; +if (i23.replace(/\s+/g, "") !== o23) { + $ERROR("#23: Error matching character class \s between character 5c00 and 5fff"); +} + +var i24 = ""; +for (var j = 24576; j < 25600; j++) + i24 += String.fromCharCode(j); +var o24 = i24; +if (i24.replace(/\s+/g, "") !== o24) { + $ERROR("#24: Error matching character class \s between character 6000 and 63ff"); +} + +var i25 = ""; +for (var j = 25600; j < 26624; j++) + i25 += String.fromCharCode(j); +var o25 = i25; +if (i25.replace(/\s+/g, "") !== o25) { + $ERROR("#25: Error matching character class \s between character 6400 and 67ff"); +} + +var i26 = ""; +for (var j = 26624; j < 27648; j++) + i26 += String.fromCharCode(j); +var o26 = i26; +if (i26.replace(/\s+/g, "") !== o26) { + $ERROR("#26: Error matching character class \s between character 6800 and 6bff"); +} + +var i27 = ""; +for (var j = 27648; j < 28672; j++) + i27 += String.fromCharCode(j); +var o27 = i27; +if (i27.replace(/\s+/g, "") !== o27) { + $ERROR("#27: Error matching character class \s between character 6c00 and 6fff"); +} + +var i28 = ""; +for (var j = 28672; j < 29696; j++) + i28 += String.fromCharCode(j); +var o28 = i28; +if (i28.replace(/\s+/g, "") !== o28) { + $ERROR("#28: Error matching character class \s between character 7000 and 73ff"); +} + +var i29 = ""; +for (var j = 29696; j < 30720; j++) + i29 += String.fromCharCode(j); +var o29 = i29; +if (i29.replace(/\s+/g, "") !== o29) { + $ERROR("#29: Error matching character class \s between character 7400 and 77ff"); +} + +var i30 = ""; +for (var j = 30720; j < 31744; j++) + i30 += String.fromCharCode(j); +var o30 = i30; +if (i30.replace(/\s+/g, "") !== o30) { + $ERROR("#30: Error matching character class \s between character 7800 and 7bff"); +} + +var i31 = ""; +for (var j = 31744; j < 32768; j++) + i31 += String.fromCharCode(j); +var o31 = i31; +if (i31.replace(/\s+/g, "") !== o31) { + $ERROR("#31: Error matching character class \s between character 7c00 and 7fff"); +} + +var i32 = ""; +for (var j = 32768; j < 33792; j++) + i32 += String.fromCharCode(j); +var o32 = i32; +if (i32.replace(/\s+/g, "") !== o32) { + $ERROR("#32: Error matching character class \s between character 8000 and 83ff"); +} + +var i33 = ""; +for (var j = 33792; j < 34816; j++) + i33 += String.fromCharCode(j); +var o33 = i33; +if (i33.replace(/\s+/g, "") !== o33) { + $ERROR("#33: Error matching character class \s between character 8400 and 87ff"); +} + +var i34 = ""; +for (var j = 34816; j < 35840; j++) + i34 += String.fromCharCode(j); +var o34 = i34; +if (i34.replace(/\s+/g, "") !== o34) { + $ERROR("#34: Error matching character class \s between character 8800 and 8bff"); +} + +var i35 = ""; +for (var j = 35840; j < 36864; j++) + i35 += String.fromCharCode(j); +var o35 = i35; +if (i35.replace(/\s+/g, "") !== o35) { + $ERROR("#35: Error matching character class \s between character 8c00 and 8fff"); +} + +var i36 = ""; +for (var j = 36864; j < 37888; j++) + i36 += String.fromCharCode(j); +var o36 = i36; +if (i36.replace(/\s+/g, "") !== o36) { + $ERROR("#36: Error matching character class \s between character 9000 and 93ff"); +} + +var i37 = ""; +for (var j = 37888; j < 38912; j++) + i37 += String.fromCharCode(j); +var o37 = i37; +if (i37.replace(/\s+/g, "") !== o37) { + $ERROR("#37: Error matching character class \s between character 9400 and 97ff"); +} + +var i38 = ""; +for (var j = 38912; j < 39936; j++) + i38 += String.fromCharCode(j); +var o38 = i38; +if (i38.replace(/\s+/g, "") !== o38) { + $ERROR("#38: Error matching character class \s between character 9800 and 9bff"); +} + +var i39 = ""; +for (var j = 39936; j < 40960; j++) + i39 += String.fromCharCode(j); +var o39 = i39; +if (i39.replace(/\s+/g, "") !== o39) { + $ERROR("#39: Error matching character class \s between character 9c00 and 9fff"); +} + +var i40 = ""; +for (var j = 40960; j < 41984; j++) + i40 += String.fromCharCode(j); +var o40 = i40; +if (i40.replace(/\s+/g, "") !== o40) { + $ERROR("#40: Error matching character class \s between character a000 and a3ff"); +} + +var i41 = ""; +for (var j = 41984; j < 43008; j++) + i41 += String.fromCharCode(j); +var o41 = i41; +if (i41.replace(/\s+/g, "") !== o41) { + $ERROR("#41: Error matching character class \s between character a400 and a7ff"); +} + +var i42 = ""; +for (var j = 43008; j < 44032; j++) + i42 += String.fromCharCode(j); +var o42 = i42; +if (i42.replace(/\s+/g, "") !== o42) { + $ERROR("#42: Error matching character class \s between character a800 and abff"); +} + +var i43 = ""; +for (var j = 44032; j < 45056; j++) + i43 += String.fromCharCode(j); +var o43 = i43; +if (i43.replace(/\s+/g, "") !== o43) { + $ERROR("#43: Error matching character class \s between character ac00 and afff"); +} + +var i44 = ""; +for (var j = 45056; j < 46080; j++) + i44 += String.fromCharCode(j); +var o44 = i44; +if (i44.replace(/\s+/g, "") !== o44) { + $ERROR("#44: Error matching character class \s between character b000 and b3ff"); +} + +var i45 = ""; +for (var j = 46080; j < 47104; j++) + i45 += String.fromCharCode(j); +var o45 = i45; +if (i45.replace(/\s+/g, "") !== o45) { + $ERROR("#45: Error matching character class \s between character b400 and b7ff"); +} + +var i46 = ""; +for (var j = 47104; j < 48128; j++) + i46 += String.fromCharCode(j); +var o46 = i46; +if (i46.replace(/\s+/g, "") !== o46) { + $ERROR("#46: Error matching character class \s between character b800 and bbff"); +} + +var i47 = ""; +for (var j = 48128; j < 49152; j++) + i47 += String.fromCharCode(j); +var o47 = i47; +if (i47.replace(/\s+/g, "") !== o47) { + $ERROR("#47: Error matching character class \s between character bc00 and bfff"); +} + +var i48 = ""; +for (var j = 49152; j < 50176; j++) + i48 += String.fromCharCode(j); +var o48 = i48; +if (i48.replace(/\s+/g, "") !== o48) { + $ERROR("#48: Error matching character class \s between character c000 and c3ff"); +} + +var i49 = ""; +for (var j = 50176; j < 51200; j++) + i49 += String.fromCharCode(j); +var o49 = i49; +if (i49.replace(/\s+/g, "") !== o49) { + $ERROR("#49: Error matching character class \s between character c400 and c7ff"); +} + +var i50 = ""; +for (var j = 51200; j < 52224; j++) + i50 += String.fromCharCode(j); +var o50 = i50; +if (i50.replace(/\s+/g, "") !== o50) { + $ERROR("#50: Error matching character class \s between character c800 and cbff"); +} + +var i51 = ""; +for (var j = 52224; j < 53248; j++) + i51 += String.fromCharCode(j); +var o51 = i51; +if (i51.replace(/\s+/g, "") !== o51) { + $ERROR("#51: Error matching character class \s between character cc00 and cfff"); +} + +var i52 = ""; +for (var j = 53248; j < 54272; j++) + i52 += String.fromCharCode(j); +var o52 = i52; +if (i52.replace(/\s+/g, "") !== o52) { + $ERROR("#52: Error matching character class \s between character d000 and d3ff"); +} + +var i53 = ""; +for (var j = 54272; j < 55296; j++) + i53 += String.fromCharCode(j); +var o53 = i53; +if (i53.replace(/\s+/g, "") !== o53) { + $ERROR("#53: Error matching character class \s between character d400 and d7ff"); +} + +var i54 = ""; +for (var j = 55296; j < 56320; j++) + i54 += String.fromCharCode(j); +var o54 = i54; +if (i54.replace(/\s+/g, "") !== o54) { + $ERROR("#54: Error matching character class \s between character d800 and dbff"); +} + +var i55 = ""; +for (var j = 56320; j < 57344; j++) + i55 += String.fromCharCode(j); +var o55 = i55; +if (i55.replace(/\s+/g, "") !== o55) { + $ERROR("#55: Error matching character class \s between character dc00 and dfff"); +} + +var i56 = ""; +for (var j = 57344; j < 58368; j++) + i56 += String.fromCharCode(j); +var o56 = i56; +if (i56.replace(/\s+/g, "") !== o56) { + $ERROR("#56: Error matching character class \s between character e000 and e3ff"); +} + +var i57 = ""; +for (var j = 58368; j < 59392; j++) + i57 += String.fromCharCode(j); +var o57 = i57; +if (i57.replace(/\s+/g, "") !== o57) { + $ERROR("#57: Error matching character class \s between character e400 and e7ff"); +} + +var i58 = ""; +for (var j = 59392; j < 60416; j++) + i58 += String.fromCharCode(j); +var o58 = i58; +if (i58.replace(/\s+/g, "") !== o58) { + $ERROR("#58: Error matching character class \s between character e800 and ebff"); +} + +var i59 = ""; +for (var j = 60416; j < 61440; j++) + i59 += String.fromCharCode(j); +var o59 = i59; +if (i59.replace(/\s+/g, "") !== o59) { + $ERROR("#59: Error matching character class \s between character ec00 and efff"); +} + +var i60 = ""; +for (var j = 61440; j < 62464; j++) + i60 += String.fromCharCode(j); +var o60 = i60; +if (i60.replace(/\s+/g, "") !== o60) { + $ERROR("#60: Error matching character class \s between character f000 and f3ff"); +} + +var i61 = ""; +for (var j = 62464; j < 63488; j++) + i61 += String.fromCharCode(j); +var o61 = i61; +if (i61.replace(/\s+/g, "") !== o61) { + $ERROR("#61: Error matching character class \s between character f400 and f7ff"); +} + +var i62 = ""; +for (var j = 63488; j < 64512; j++) + i62 += String.fromCharCode(j); +var o62 = i62; +if (i62.replace(/\s+/g, "") !== o62) { + $ERROR("#62: Error matching character class \s between character f800 and fbff"); +} + +var i63 = ""; +for (var j = 64512; j < 65536; j++) { + if (j === 0xFEFF) { continue; } //Ignore BOM + i63 += String.fromCharCode(j); +} +var o63 = i63; +if (i63.replace(/\s+/g, "") !== o63) { + $ERROR("#63: Error matching character class \s between character fc00 and ffff"); +} + +var i64 = String.fromCharCode(0xFEFF); +if (i64.replace(/\s/g, "") !== "") { + $ERROR("#64: Error matching character class \s for BOM (feff)"); +} diff --git a/pkg/xscript/engine/token/Makefile b/pkg/xscript/engine/token/Makefile new file mode 100644 index 0000000..1e85c73 --- /dev/null +++ b/pkg/xscript/engine/token/Makefile @@ -0,0 +1,2 @@ +token_const.go: tokenfmt + ./$^ | gofmt > $@ diff --git a/pkg/xscript/engine/token/README.markdown b/pkg/xscript/engine/token/README.markdown new file mode 100644 index 0000000..66dd2ab --- /dev/null +++ b/pkg/xscript/engine/token/README.markdown @@ -0,0 +1,171 @@ +# token +-- + import "github.com/dop251/goja/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 diff --git a/pkg/xscript/engine/token/token.go b/pkg/xscript/engine/token/token.go new file mode 100644 index 0000000..8527137 --- /dev/null +++ b/pkg/xscript/engine/token/token.go @@ -0,0 +1,122 @@ +// Package token defines constants representing the lexical tokens of JavaScript (ECMA5). +package token + +import ( + "strconv" +) + +// Token is the set of lexical tokens in JavaScript (ECMA5). +type Token int + +// 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"). +func (tkn Token) String() string { + if tkn == 0 { + return "UNKNOWN" + } + if tkn < Token(len(token2string)) { + return token2string[tkn] + } + return "token(" + strconv.Itoa(int(tkn)) + ")" +} + +//lint:ignore U1000 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: + 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 { + token Token + futureKeyword bool + strict 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 IsKeyword(literal string) (Token, bool) { + if keyword, exists := keywordTable[literal]; exists { + if keyword.futureKeyword { + return KEYWORD, keyword.strict + } + return keyword.token, false + } + return 0, false +} + +func IsId(tkn Token) bool { + return tkn >= IDENTIFIER +} + +func IsUnreservedWord(tkn Token) bool { + return tkn > ESCAPED_RESERVED_WORD +} diff --git a/pkg/xscript/engine/token/token_const.go b/pkg/xscript/engine/token/token_const.go new file mode 100644 index 0000000..2591427 --- /dev/null +++ b/pkg/xscript/engine/token/token_const.go @@ -0,0 +1,400 @@ +package token + +const ( + _ Token = iota + + ILLEGAL + EOF + COMMENT + + STRING + NUMBER + + PLUS // + + MINUS // - + MULTIPLY // * + EXPONENT // ** + SLASH // / + REMAINDER // % + + AND // & + OR // | + EXCLUSIVE_OR // ^ + SHIFT_LEFT // << + SHIFT_RIGHT // >> + UNSIGNED_SHIFT_RIGHT // >>> + + ADD_ASSIGN // += + SUBTRACT_ASSIGN // -= + MULTIPLY_ASSIGN // *= + EXPONENT_ASSIGN // **= + QUOTIENT_ASSIGN // /= + REMAINDER_ASSIGN // %= + + AND_ASSIGN // &= + OR_ASSIGN // |= + EXCLUSIVE_OR_ASSIGN // ^= + SHIFT_LEFT_ASSIGN // <<= + SHIFT_RIGHT_ASSIGN // >>= + UNSIGNED_SHIFT_RIGHT_ASSIGN // >>>= + + LOGICAL_AND // && + LOGICAL_OR // || + COALESCE // ?? + 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 // ? + QUESTION_DOT // ?. + ARROW // => + ELLIPSIS // ... + BACKTICK // ` + + PRIVATE_IDENTIFIER + + // tokens below (and only them) are syntactically valid identifiers + + IDENTIFIER + KEYWORD + BOOLEAN + NULL + + IF + IN + OF + DO + + VAR + FOR + NEW + TRY + + THIS + ELSE + CASE + VOID + WITH + + CONST + WHILE + BREAK + CATCH + THROW + CLASS + SUPER + + RETURN + TYPEOF + DELETE + SWITCH + + DEFAULT + FINALLY + EXTENDS + + FUNCTION + CONTINUE + DEBUGGER + + INSTANCEOF + + ESCAPED_RESERVED_WORD + // Non-reserved keywords below + + LET + STATIC + ASYNC + AWAIT + YIELD +) + +var token2string = [...]string{ + ILLEGAL: "ILLEGAL", + EOF: "EOF", + COMMENT: "COMMENT", + KEYWORD: "KEYWORD", + STRING: "STRING", + BOOLEAN: "BOOLEAN", + NULL: "NULL", + NUMBER: "NUMBER", + IDENTIFIER: "IDENTIFIER", + PRIVATE_IDENTIFIER: "PRIVATE_IDENTIFIER", + PLUS: "+", + MINUS: "-", + EXPONENT: "**", + MULTIPLY: "*", + SLASH: "/", + REMAINDER: "%", + AND: "&", + OR: "|", + EXCLUSIVE_OR: "^", + SHIFT_LEFT: "<<", + SHIFT_RIGHT: ">>", + UNSIGNED_SHIFT_RIGHT: ">>>", + ADD_ASSIGN: "+=", + SUBTRACT_ASSIGN: "-=", + MULTIPLY_ASSIGN: "*=", + EXPONENT_ASSIGN: "**=", + QUOTIENT_ASSIGN: "/=", + REMAINDER_ASSIGN: "%=", + AND_ASSIGN: "&=", + OR_ASSIGN: "|=", + EXCLUSIVE_OR_ASSIGN: "^=", + SHIFT_LEFT_ASSIGN: "<<=", + SHIFT_RIGHT_ASSIGN: ">>=", + UNSIGNED_SHIFT_RIGHT_ASSIGN: ">>>=", + LOGICAL_AND: "&&", + LOGICAL_OR: "||", + COALESCE: "??", + 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: "?", + QUESTION_DOT: "?.", + ARROW: "=>", + ELLIPSIS: "...", + BACKTICK: "`", + IF: "if", + IN: "in", + OF: "of", + DO: "do", + VAR: "var", + LET: "let", + FOR: "for", + NEW: "new", + TRY: "try", + THIS: "this", + ELSE: "else", + CASE: "case", + VOID: "void", + WITH: "with", + ASYNC: "async", + AWAIT: "await", + YIELD: "yield", + CONST: "const", + WHILE: "while", + BREAK: "break", + CATCH: "catch", + THROW: "throw", + CLASS: "class", + SUPER: "super", + RETURN: "return", + TYPEOF: "typeof", + DELETE: "delete", + SWITCH: "switch", + STATIC: "static", + DEFAULT: "default", + FINALLY: "finally", + EXTENDS: "extends", + FUNCTION: "function", + CONTINUE: "continue", + DEBUGGER: "debugger", + INSTANCEOF: "instanceof", +} + +var keywordTable = map[string]_keyword{ + "if": { + token: IF, + }, + "in": { + token: IN, + }, + "do": { + token: DO, + }, + "var": { + token: VAR, + }, + "for": { + token: FOR, + }, + "new": { + token: NEW, + }, + "try": { + token: TRY, + }, + "this": { + token: THIS, + }, + "else": { + token: ELSE, + }, + "case": { + token: CASE, + }, + "void": { + token: VOID, + }, + "with": { + token: WITH, + }, + "async": { + token: ASYNC, + }, + "while": { + token: WHILE, + }, + "break": { + token: BREAK, + }, + "catch": { + token: CATCH, + }, + "throw": { + token: THROW, + }, + "return": { + token: RETURN, + }, + "typeof": { + token: TYPEOF, + }, + "delete": { + token: DELETE, + }, + "switch": { + token: SWITCH, + }, + "default": { + token: DEFAULT, + }, + "finally": { + token: FINALLY, + }, + "function": { + token: FUNCTION, + }, + "continue": { + token: CONTINUE, + }, + "debugger": { + token: DEBUGGER, + }, + "instanceof": { + token: INSTANCEOF, + }, + "const": { + token: CONST, + }, + "class": { + token: CLASS, + }, + "enum": { + token: KEYWORD, + futureKeyword: true, + }, + "export": { + token: KEYWORD, + futureKeyword: true, + }, + "extends": { + token: EXTENDS, + }, + "import": { + token: KEYWORD, + futureKeyword: true, + }, + "super": { + token: SUPER, + }, + /* + "implements": { + token: KEYWORD, + futureKeyword: true, + strict: true, + }, + "interface": { + token: KEYWORD, + futureKeyword: true, + strict: true, + },*/ + "let": { + token: LET, + strict: true, + }, + /*"package": { + token: KEYWORD, + futureKeyword: true, + strict: true, + }, + "private": { + token: KEYWORD, + futureKeyword: true, + strict: true, + }, + "protected": { + token: KEYWORD, + futureKeyword: true, + strict: true, + }, + "public": { + token: KEYWORD, + futureKeyword: true, + strict: true, + },*/ + "static": { + token: STATIC, + strict: true, + }, + "await": { + token: AWAIT, + }, + "yield": { + token: YIELD, + }, + "false": { + token: BOOLEAN, + }, + "true": { + token: BOOLEAN, + }, + "null": { + token: NULL, + }, +} diff --git a/pkg/xscript/engine/token/tokenfmt b/pkg/xscript/engine/token/tokenfmt new file mode 100644 index 0000000..63dd5d9 --- /dev/null +++ b/pkg/xscript/engine/token/tokenfmt @@ -0,0 +1,222 @@ +#!/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_ +} diff --git a/pkg/xscript/engine/typedarrays.go b/pkg/xscript/engine/typedarrays.go new file mode 100644 index 0000000..80f77dd --- /dev/null +++ b/pkg/xscript/engine/typedarrays.go @@ -0,0 +1,1135 @@ +package engine + +import ( + "math" + "reflect" + "strconv" + "unsafe" + + "pandax/pkg/xscript/engine/unistring" +) + +type byteOrder bool + +const ( + bigEndian byteOrder = false + littleEndian byteOrder = true +) + +var ( + nativeEndian byteOrder + + arrayBufferType = reflect.TypeOf(ArrayBuffer{}) +) + +type typedArrayObjectCtor func(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject + +type arrayBufferObject struct { + baseObject + detached bool + data []byte +} + +// ArrayBuffer is a Go wrapper around ECMAScript ArrayBuffer. Calling Runtime.ToValue() on it +// returns the underlying ArrayBuffer. Calling Export() on an ECMAScript ArrayBuffer returns a wrapper. +// Use Runtime.NewArrayBuffer([]byte) to create one. +type ArrayBuffer struct { + buf *arrayBufferObject +} + +type dataViewObject struct { + baseObject + viewedArrayBuf *arrayBufferObject + byteLen, byteOffset int +} + +type typedArray interface { + toRaw(Value) uint64 + get(idx int) Value + set(idx int, value Value) + getRaw(idx int) uint64 + setRaw(idx int, raw uint64) + less(i, j int) bool + swap(i, j int) + typeMatch(v Value) bool + export(offset int, length int) any + exportType() reflect.Type +} + +type uint8Array []byte +type uint8ClampedArray []byte +type int8Array []byte +type uint16Array []byte +type int16Array []byte +type uint32Array []byte +type int32Array []byte +type float32Array []byte +type float64Array []byte + +type typedArrayObject struct { + baseObject + viewedArrayBuf *arrayBufferObject + defaultCtor *Object + length, offset int + elemSize int + typedArray typedArray +} + +func (a ArrayBuffer) toValue(r *Runtime) Value { + if a.buf == nil { + return _null + } + v := a.buf.val + if v.runtime != r { + panic(r.NewTypeError("Illegal runtime transition of an ArrayBuffer")) + } + return v +} + +// Bytes returns the underlying []byte for this ArrayBuffer. +// For detached ArrayBuffers returns nil. +func (a ArrayBuffer) Bytes() []byte { + return a.buf.data +} + +// Detach the ArrayBuffer. After this, the underlying []byte becomes unreferenced and any attempt +// to use this ArrayBuffer results in a TypeError. +// Returns false if it was already detached, true otherwise. +// Note, this method may only be called from the goroutine that 'owns' the Runtime, it may not +// be called concurrently. +func (a ArrayBuffer) Detach() bool { + if a.buf.detached { + return false + } + a.buf.detach() + return true +} + +// Detached returns true if the ArrayBuffer is detached. +func (a ArrayBuffer) Detached() bool { + return a.buf.detached +} + +// NewArrayBuffer creates a new instance of ArrayBuffer backed by the provided byte slice. +// +// Warning: be careful when using unaligned slices (sub-slices that do not start at word boundaries). If later a +// typed array of a multibyte type (uint16, uint32, etc.) is created from a buffer backed by an unaligned slice, +// using this typed array will result in unaligned access which may cause performance degradation or runtime panics +// on some architectures or configurations. +func (r *Runtime) NewArrayBuffer(data []byte) ArrayBuffer { + buf := r._newArrayBuffer(r.getArrayBufferPrototype(), nil) + buf.data = data + return ArrayBuffer{ + buf: buf, + } +} + +func (a *uint8Array) toRaw(v Value) uint64 { + return uint64(toUint8(v)) +} + +func (a *uint8Array) ptr(idx int) *uint8 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*uint8)(unsafe.Pointer(uintptr(p) + uintptr(idx))) +} + +func (a *uint8Array) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *uint8Array) set(idx int, value Value) { + *(a.ptr(idx)) = toUint8(value) +} + +func (a *uint8Array) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *uint8Array) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = uint8(raw) +} + +func (a *uint8Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *uint8Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *uint8Array) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= 0 && i <= 255 + } + return false +} + +func (a *uint8Array) export(offset int, length int) any { + return ([]uint8)(*a)[offset : offset+length : offset+length] +} + +func (a *uint8Array) exportType() reflect.Type { + return typeBytes +} + +func (a *uint8ClampedArray) toRaw(v Value) uint64 { + return uint64(toUint8Clamp(v)) +} + +func (a *uint8ClampedArray) ptr(idx int) *uint8 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*uint8)(unsafe.Pointer(uintptr(p) + uintptr(idx))) +} + +func (a *uint8ClampedArray) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *uint8ClampedArray) set(idx int, value Value) { + *(a.ptr(idx)) = toUint8Clamp(value) +} + +func (a *uint8ClampedArray) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *uint8ClampedArray) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = uint8(raw) +} + +func (a *uint8ClampedArray) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *uint8ClampedArray) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *uint8ClampedArray) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= 0 && i <= 255 + } + return false +} + +func (a *uint8ClampedArray) export(offset int, length int) any { + return ([]uint8)(*a)[offset : offset+length : offset+length] +} + +func (a *uint8ClampedArray) exportType() reflect.Type { + return typeBytes +} + +func (a *int8Array) ptr(idx int) *int8 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*int8)(unsafe.Pointer(uintptr(p) + uintptr(idx))) +} + +func (a *int8Array) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *int8Array) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *int8Array) set(idx int, value Value) { + *(a.ptr(idx)) = toInt8(value) +} + +func (a *int8Array) toRaw(v Value) uint64 { + return uint64(toInt8(v)) +} + +func (a *int8Array) setRaw(idx int, v uint64) { + *(a.ptr(idx)) = int8(v) +} + +func (a *int8Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *int8Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *int8Array) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= math.MinInt8 && i <= math.MaxInt8 + } + return false +} + +func (a *int8Array) export(offset int, length int) any { + var res []int8 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset) + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeInt8Array = reflect.TypeOf(([]int8)(nil)) + +func (a *int8Array) exportType() reflect.Type { + return typeInt8Array +} + +func (a *uint16Array) toRaw(v Value) uint64 { + return uint64(toUint16(v)) +} + +func (a *uint16Array) ptr(idx int) *uint16 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*uint16)(unsafe.Pointer(uintptr(p) + uintptr(idx)*2)) +} + +func (a *uint16Array) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *uint16Array) set(idx int, value Value) { + *(a.ptr(idx)) = toUint16(value) +} + +func (a *uint16Array) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *uint16Array) setRaw(idx int, raw uint64) { + *(a.ptr(idx)) = uint16(raw) +} + +func (a *uint16Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *uint16Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *uint16Array) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= 0 && i <= math.MaxUint16 + } + return false +} + +var typeUint16Array = reflect.TypeOf(([]uint16)(nil)) + +func (a *uint16Array) export(offset int, length int) any { + var res []uint16 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*2 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +func (a *uint16Array) exportType() reflect.Type { + return typeUint16Array +} + +func (a *int16Array) ptr(idx int) *int16 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*int16)(unsafe.Pointer(uintptr(p) + uintptr(idx)*2)) +} + +func (a *int16Array) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *int16Array) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *int16Array) set(idx int, value Value) { + *(a.ptr(idx)) = toInt16(value) +} + +func (a *int16Array) toRaw(v Value) uint64 { + return uint64(toInt16(v)) +} + +func (a *int16Array) setRaw(idx int, v uint64) { + *(a.ptr(idx)) = int16(v) +} + +func (a *int16Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *int16Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *int16Array) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= math.MinInt16 && i <= math.MaxInt16 + } + return false +} + +func (a *int16Array) export(offset int, length int) any { + var res []int16 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*2 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeInt16Array = reflect.TypeOf(([]int16)(nil)) + +func (a *int16Array) exportType() reflect.Type { + return typeInt16Array +} + +func (a *uint32Array) ptr(idx int) *uint32 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*uint32)(unsafe.Pointer(uintptr(p) + uintptr(idx)*4)) +} + +func (a *uint32Array) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *uint32Array) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *uint32Array) set(idx int, value Value) { + *(a.ptr(idx)) = toUint32(value) +} + +func (a *uint32Array) toRaw(v Value) uint64 { + return uint64(toUint32(v)) +} + +func (a *uint32Array) setRaw(idx int, v uint64) { + *(a.ptr(idx)) = uint32(v) +} + +func (a *uint32Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *uint32Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *uint32Array) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= 0 && i <= math.MaxUint32 + } + return false +} + +func (a *uint32Array) export(offset int, length int) any { + var res []uint32 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*4 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeUint32Array = reflect.TypeOf(([]uint32)(nil)) + +func (a *uint32Array) exportType() reflect.Type { + return typeUint32Array +} + +func (a *int32Array) ptr(idx int) *int32 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*int32)(unsafe.Pointer(uintptr(p) + uintptr(idx)*4)) +} + +func (a *int32Array) get(idx int) Value { + return intToValue(int64(*(a.ptr(idx)))) +} + +func (a *int32Array) getRaw(idx int) uint64 { + return uint64(*(a.ptr(idx))) +} + +func (a *int32Array) set(idx int, value Value) { + *(a.ptr(idx)) = toInt32(value) +} + +func (a *int32Array) toRaw(v Value) uint64 { + return uint64(toInt32(v)) +} + +func (a *int32Array) setRaw(idx int, v uint64) { + *(a.ptr(idx)) = int32(v) +} + +func (a *int32Array) less(i, j int) bool { + return *(a.ptr(i)) < *(a.ptr(j)) +} + +func (a *int32Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *int32Array) typeMatch(v Value) bool { + if i, ok := v.(valueInt); ok { + return i >= math.MinInt32 && i <= math.MaxInt32 + } + return false +} + +func (a *int32Array) export(offset int, length int) any { + var res []int32 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*4 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeInt32Array = reflect.TypeOf(([]int32)(nil)) + +func (a *int32Array) exportType() reflect.Type { + return typeInt32Array +} + +func (a *float32Array) ptr(idx int) *float32 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*float32)(unsafe.Pointer(uintptr(p) + uintptr(idx)*4)) +} + +func (a *float32Array) get(idx int) Value { + return floatToValue(float64(*(a.ptr(idx)))) +} + +func (a *float32Array) getRaw(idx int) uint64 { + return uint64(math.Float32bits(*(a.ptr(idx)))) +} + +func (a *float32Array) set(idx int, value Value) { + *(a.ptr(idx)) = toFloat32(value) +} + +func (a *float32Array) toRaw(v Value) uint64 { + return uint64(math.Float32bits(toFloat32(v))) +} + +func (a *float32Array) setRaw(idx int, v uint64) { + *(a.ptr(idx)) = math.Float32frombits(uint32(v)) +} + +func typedFloatLess(x, y float64) bool { + xNan := math.IsNaN(x) + yNan := math.IsNaN(y) + if yNan { + return !xNan + } else if xNan { + return false + } + if x == 0 && y == 0 { // handle neg zero + return math.Signbit(x) + } + return x < y +} + +func (a *float32Array) less(i, j int) bool { + return typedFloatLess(float64(*(a.ptr(i))), float64(*(a.ptr(j)))) +} + +func (a *float32Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *float32Array) typeMatch(v Value) bool { + switch v.(type) { + case valueInt, valueFloat: + return true + } + return false +} + +func (a *float32Array) export(offset int, length int) any { + var res []float32 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*4 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeFloat32Array = reflect.TypeOf(([]float32)(nil)) + +func (a *float32Array) exportType() reflect.Type { + return typeFloat32Array +} + +func (a *float64Array) ptr(idx int) *float64 { + p := unsafe.Pointer((*reflect.SliceHeader)(unsafe.Pointer(a)).Data) + return (*float64)(unsafe.Pointer(uintptr(p) + uintptr(idx)*8)) +} + +func (a *float64Array) get(idx int) Value { + return floatToValue(*(a.ptr(idx))) +} + +func (a *float64Array) getRaw(idx int) uint64 { + return math.Float64bits(*(a.ptr(idx))) +} + +func (a *float64Array) set(idx int, value Value) { + *(a.ptr(idx)) = value.ToFloat() +} + +func (a *float64Array) toRaw(v Value) uint64 { + return math.Float64bits(v.ToFloat()) +} + +func (a *float64Array) setRaw(idx int, v uint64) { + *(a.ptr(idx)) = math.Float64frombits(v) +} + +func (a *float64Array) less(i, j int) bool { + return typedFloatLess(*(a.ptr(i)), *(a.ptr(j))) +} + +func (a *float64Array) swap(i, j int) { + pi, pj := a.ptr(i), a.ptr(j) + *pi, *pj = *pj, *pi +} + +func (a *float64Array) typeMatch(v Value) bool { + switch v.(type) { + case valueInt, valueFloat: + return true + } + return false +} + +func (a *float64Array) export(offset int, length int) any { + var res []float64 + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&res)) + sliceHeader.Data = (*reflect.SliceHeader)(unsafe.Pointer(a)).Data + uintptr(offset)*8 + sliceHeader.Len = length + sliceHeader.Cap = length + return res +} + +var typeFloat64Array = reflect.TypeOf(([]float64)(nil)) + +func (a *float64Array) exportType() reflect.Type { + return typeFloat64Array +} + +func (a *typedArrayObject) _getIdx(idx int) Value { + if 0 <= idx && idx < a.length { + if !a.viewedArrayBuf.ensureNotDetached(false) { + return nil + } + return a.typedArray.get(idx + a.offset) + } + return nil +} + +func (a *typedArrayObject) getOwnPropStr(name unistring.String) Value { + idx, ok := strToIntNum(name) + if ok { + v := a._getIdx(idx) + if v != nil { + return &valueProperty{ + value: v, + writable: true, + enumerable: true, + configurable: true, + } + } + return nil + } + if idx == 0 { + return nil + } + return a.baseObject.getOwnPropStr(name) +} + +func (a *typedArrayObject) getOwnPropIdx(idx valueInt) Value { + v := a._getIdx(toIntClamp(int64(idx))) + if v != nil { + return &valueProperty{ + value: v, + writable: true, + enumerable: true, + configurable: true, + } + } + return nil +} + +func (a *typedArrayObject) getStr(name unistring.String, receiver Value) Value { + idx, ok := strToIntNum(name) + if ok { + return a._getIdx(idx) + } + if idx == 0 { + return nil + } + return a.baseObject.getStr(name, receiver) +} + +func (a *typedArrayObject) getIdx(idx valueInt, receiver Value) Value { + return a._getIdx(toIntClamp(int64(idx))) +} + +func (a *typedArrayObject) isValidIntegerIndex(idx int) bool { + if a.viewedArrayBuf.ensureNotDetached(false) { + if idx >= 0 && idx < a.length { + return true + } + } + return false +} + +func (a *typedArrayObject) _putIdx(idx int, v Value) { + v = v.ToNumber() + if a.isValidIntegerIndex(idx) { + a.typedArray.set(idx+a.offset, v) + } +} + +func (a *typedArrayObject) _hasIdx(idx int) bool { + return a.isValidIntegerIndex(idx) +} + +func (a *typedArrayObject) setOwnStr(p unistring.String, v Value, throw bool) bool { + idx, ok := strToIntNum(p) + if ok { + a._putIdx(idx, v) + return true + } + if idx == 0 { + v.ToNumber() // make sure it throws + return true + } + return a.baseObject.setOwnStr(p, v, throw) +} + +func (a *typedArrayObject) setOwnIdx(p valueInt, v Value, throw bool) bool { + a._putIdx(toIntClamp(int64(p)), v) + return true +} + +func (a *typedArrayObject) setForeignStr(p unistring.String, v, receiver Value, throw bool) (res bool, handled bool) { + return a._setForeignStr(p, a.getOwnPropStr(p), v, receiver, throw) +} + +func (a *typedArrayObject) setForeignIdx(p valueInt, v, receiver Value, throw bool) (res bool, handled bool) { + return a._setForeignIdx(p, trueValIfPresent(a.hasOwnPropertyIdx(p)), v, receiver, throw) +} + +func (a *typedArrayObject) hasOwnPropertyStr(name unistring.String) bool { + idx, ok := strToIntNum(name) + if ok { + return a._hasIdx(idx) + } + if idx == 0 { + return false + } + return a.baseObject.hasOwnPropertyStr(name) +} + +func (a *typedArrayObject) hasOwnPropertyIdx(idx valueInt) bool { + return a._hasIdx(toIntClamp(int64(idx))) +} + +func (a *typedArrayObject) hasPropertyStr(name unistring.String) bool { + idx, ok := strToIntNum(name) + if ok { + return a._hasIdx(idx) + } + if idx == 0 { + return false + } + return a.baseObject.hasPropertyStr(name) +} + +func (a *typedArrayObject) hasPropertyIdx(idx valueInt) bool { + return a.hasOwnPropertyIdx(idx) +} + +func (a *typedArrayObject) _defineIdxProperty(idx int, desc PropertyDescriptor, throw bool) bool { + if desc.Configurable == FLAG_FALSE || desc.Enumerable == FLAG_FALSE || desc.IsAccessor() || desc.Writable == FLAG_FALSE { + a.val.runtime.typeErrorResult(throw, "Cannot redefine property: %d", idx) + return false + } + _, ok := a._defineOwnProperty(unistring.String(strconv.Itoa(idx)), a.getOwnPropIdx(valueInt(idx)), desc, throw) + if ok { + if !a.isValidIntegerIndex(idx) { + a.val.runtime.typeErrorResult(throw, "Invalid typed array index") + return false + } + a._putIdx(idx, desc.Value) + return true + } + return ok +} + +func (a *typedArrayObject) defineOwnPropertyStr(name unistring.String, desc PropertyDescriptor, throw bool) bool { + idx, ok := strToIntNum(name) + if ok { + return a._defineIdxProperty(idx, desc, throw) + } + if idx == 0 { + a.viewedArrayBuf.ensureNotDetached(throw) + a.val.runtime.typeErrorResult(throw, "Invalid typed array index") + return false + } + return a.baseObject.defineOwnPropertyStr(name, desc, throw) +} + +func (a *typedArrayObject) defineOwnPropertyIdx(name valueInt, desc PropertyDescriptor, throw bool) bool { + return a._defineIdxProperty(toIntClamp(int64(name)), desc, throw) +} + +func (a *typedArrayObject) deleteStr(name unistring.String, throw bool) bool { + idx, ok := strToIntNum(name) + if ok { + if a.isValidIntegerIndex(idx) { + a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) + return false + } + return true + } + if idx == 0 { + return true + } + return a.baseObject.deleteStr(name, throw) +} + +func (a *typedArrayObject) deleteIdx(idx valueInt, throw bool) bool { + if a.viewedArrayBuf.ensureNotDetached(false) && idx >= 0 && int64(idx) < int64(a.length) { + a.val.runtime.typeErrorResult(throw, "Cannot delete property '%d' of %s", idx, a.val.String()) + return false + } + + return true +} + +func (a *typedArrayObject) stringKeys(all bool, accum []Value) []Value { + if accum == nil { + accum = make([]Value, 0, a.length) + } + for i := 0; i < a.length; i++ { + accum = append(accum, asciiString(strconv.Itoa(i))) + } + return a.baseObject.stringKeys(all, accum) +} + +type typedArrayPropIter struct { + a *typedArrayObject + idx int +} + +func (i *typedArrayPropIter) next() (propIterItem, iterNextFunc) { + if i.idx < i.a.length { + name := strconv.Itoa(i.idx) + prop := i.a._getIdx(i.idx) + i.idx++ + return propIterItem{name: asciiString(name), value: prop}, i.next + } + + return i.a.baseObject.iterateStringKeys()() +} + +func (a *typedArrayObject) iterateStringKeys() iterNextFunc { + return (&typedArrayPropIter{ + a: a, + }).next +} + +func (a *typedArrayObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + if typ == typeBytes { + dst.Set(reflect.ValueOf(a.viewedArrayBuf.data)) + return nil + } + return a.baseObject.exportToArrayOrSlice(dst, typ, ctx) +} + +func (a *typedArrayObject) export(_ *objectExportCtx) any { + return a.typedArray.export(a.offset, a.length) +} + +func (a *typedArrayObject) exportType() reflect.Type { + return a.typedArray.exportType() +} + +func (o *dataViewObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + if typ == typeBytes { + dst.Set(reflect.ValueOf(o.viewedArrayBuf.data)) + return nil + } + return o.baseObject.exportToArrayOrSlice(dst, typ, ctx) +} + +func (r *Runtime) _newTypedArrayObject(buf *arrayBufferObject, offset, length, elemSize int, defCtor *Object, arr typedArray, proto *Object) *typedArrayObject { + o := &Object{runtime: r} + a := &typedArrayObject{ + baseObject: baseObject{ + val: o, + class: classObject, + prototype: proto, + extensible: true, + }, + viewedArrayBuf: buf, + offset: offset, + length: length, + elemSize: elemSize, + defaultCtor: defCtor, + typedArray: arr, + } + o.self = a + a.init() + return a + +} + +func (r *Runtime) newUint8ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + // Note, no need to use r.getUint8Array() here or in the similar methods below, because the value is already set + // by the time they are called. + return r._newTypedArrayObject(buf, offset, length, 1, r.global.Uint8Array, (*uint8Array)(&buf.data), proto) +} + +func (r *Runtime) newUint8ClampedArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 1, r.global.Uint8ClampedArray, (*uint8ClampedArray)(&buf.data), proto) +} + +func (r *Runtime) newInt8ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 1, r.global.Int8Array, (*int8Array)(&buf.data), proto) +} + +func (r *Runtime) newUint16ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 2, r.global.Uint16Array, (*uint16Array)(&buf.data), proto) +} + +func (r *Runtime) newInt16ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 2, r.global.Int16Array, (*int16Array)(&buf.data), proto) +} + +func (r *Runtime) newUint32ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 4, r.global.Uint32Array, (*uint32Array)(&buf.data), proto) +} + +func (r *Runtime) newInt32ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 4, r.global.Int32Array, (*int32Array)(&buf.data), proto) +} + +func (r *Runtime) newFloat32ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 4, r.global.Float32Array, (*float32Array)(&buf.data), proto) +} + +func (r *Runtime) newFloat64ArrayObject(buf *arrayBufferObject, offset, length int, proto *Object) *typedArrayObject { + return r._newTypedArrayObject(buf, offset, length, 8, r.global.Float64Array, (*float64Array)(&buf.data), proto) +} + +func (o *dataViewObject) getIdxAndByteOrder(getIdx int, littleEndianVal Value, size int) (int, byteOrder) { + o.viewedArrayBuf.ensureNotDetached(true) + if getIdx+size > o.byteLen { + panic(o.val.runtime.newError(o.val.runtime.getRangeError(), "Index %d is out of bounds", getIdx)) + } + getIdx += o.byteOffset + var bo byteOrder + if littleEndianVal != nil { + if littleEndianVal.ToBoolean() { + bo = littleEndian + } else { + bo = bigEndian + } + } else { + bo = nativeEndian + } + return getIdx, bo +} + +func (o *arrayBufferObject) ensureNotDetached(throw bool) bool { + if o.detached { + o.val.runtime.typeErrorResult(throw, "ArrayBuffer is detached") + return false + } + return true +} + +func (o *arrayBufferObject) getFloat32(idx int, byteOrder byteOrder) float32 { + return math.Float32frombits(o.getUint32(idx, byteOrder)) +} + +func (o *arrayBufferObject) setFloat32(idx int, val float32, byteOrder byteOrder) { + o.setUint32(idx, math.Float32bits(val), byteOrder) +} + +func (o *arrayBufferObject) getFloat64(idx int, byteOrder byteOrder) float64 { + return math.Float64frombits(o.getUint64(idx, byteOrder)) +} + +func (o *arrayBufferObject) setFloat64(idx int, val float64, byteOrder byteOrder) { + o.setUint64(idx, math.Float64bits(val), byteOrder) +} + +func (o *arrayBufferObject) getUint64(idx int, byteOrder byteOrder) uint64 { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+8] + } else { + b = make([]byte, 8) + d := o.data[idx : idx+8] + b[0], b[1], b[2], b[3], b[4], b[5], b[6], b[7] = d[7], d[6], d[5], d[4], d[3], d[2], d[1], d[0] + } + return *((*uint64)(unsafe.Pointer(&b[0]))) +} + +func (o *arrayBufferObject) setUint64(idx int, val uint64, byteOrder byteOrder) { + if byteOrder == nativeEndian { + *(*uint64)(unsafe.Pointer(&o.data[idx])) = val + } else { + b := (*[8]byte)(unsafe.Pointer(&val)) + d := o.data[idx : idx+8] + d[0], d[1], d[2], d[3], d[4], d[5], d[6], d[7] = b[7], b[6], b[5], b[4], b[3], b[2], b[1], b[0] + } +} + +func (o *arrayBufferObject) getUint32(idx int, byteOrder byteOrder) uint32 { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+4] + } else { + b = make([]byte, 4) + d := o.data[idx : idx+4] + b[0], b[1], b[2], b[3] = d[3], d[2], d[1], d[0] + } + return *((*uint32)(unsafe.Pointer(&b[0]))) +} + +func (o *arrayBufferObject) setUint32(idx int, val uint32, byteOrder byteOrder) { + o.ensureNotDetached(true) + if byteOrder == nativeEndian { + *(*uint32)(unsafe.Pointer(&o.data[idx])) = val + } else { + b := (*[4]byte)(unsafe.Pointer(&val)) + d := o.data[idx : idx+4] + d[0], d[1], d[2], d[3] = b[3], b[2], b[1], b[0] + } +} + +func (o *arrayBufferObject) getUint16(idx int, byteOrder byteOrder) uint16 { + var b []byte + if byteOrder == nativeEndian { + b = o.data[idx : idx+2] + } else { + b = make([]byte, 2) + d := o.data[idx : idx+2] + b[0], b[1] = d[1], d[0] + } + return *((*uint16)(unsafe.Pointer(&b[0]))) +} + +func (o *arrayBufferObject) setUint16(idx int, val uint16, byteOrder byteOrder) { + if byteOrder == nativeEndian { + *(*uint16)(unsafe.Pointer(&o.data[idx])) = val + } else { + b := (*[2]byte)(unsafe.Pointer(&val)) + d := o.data[idx : idx+2] + d[0], d[1] = b[1], b[0] + } +} + +func (o *arrayBufferObject) getUint8(idx int) uint8 { + return o.data[idx] +} + +func (o *arrayBufferObject) setUint8(idx int, val uint8) { + o.data[idx] = val +} + +func (o *arrayBufferObject) getInt32(idx int, byteOrder byteOrder) int32 { + return int32(o.getUint32(idx, byteOrder)) +} + +func (o *arrayBufferObject) setInt32(idx int, val int32, byteOrder byteOrder) { + o.setUint32(idx, uint32(val), byteOrder) +} + +func (o *arrayBufferObject) getInt16(idx int, byteOrder byteOrder) int16 { + return int16(o.getUint16(idx, byteOrder)) +} + +func (o *arrayBufferObject) setInt16(idx int, val int16, byteOrder byteOrder) { + o.setUint16(idx, uint16(val), byteOrder) +} + +func (o *arrayBufferObject) getInt8(idx int) int8 { + return int8(o.data[idx]) +} + +func (o *arrayBufferObject) setInt8(idx int, val int8) { + o.setUint8(idx, uint8(val)) +} + +func (o *arrayBufferObject) detach() { + o.data = nil + o.detached = true +} + +func (o *arrayBufferObject) exportType() reflect.Type { + return arrayBufferType +} + +func (o *arrayBufferObject) export(*objectExportCtx) any { + return ArrayBuffer{ + buf: o, + } +} + +func (o *arrayBufferObject) exportToArrayOrSlice(dst reflect.Value, typ reflect.Type, ctx *objectExportCtx) error { + if typ == typeBytes { + dst.Set(reflect.ValueOf(o.data)) + return nil + } + return o.baseObject.exportToArrayOrSlice(dst, typ, ctx) +} + +func (r *Runtime) _newArrayBuffer(proto *Object, o *Object) *arrayBufferObject { + if o == nil { + o = &Object{runtime: r} + } + b := &arrayBufferObject{ + baseObject: baseObject{ + class: classObject, + val: o, + prototype: proto, + extensible: true, + }, + } + o.self = b + b.init() + return b +} + +func init() { + buf := [2]byte{} + *(*uint16)(unsafe.Pointer(&buf[0])) = uint16(0xCAFE) + + switch buf { + case [2]byte{0xFE, 0xCA}: + nativeEndian = littleEndian + case [2]byte{0xCA, 0xFE}: + nativeEndian = bigEndian + default: + panic("Could not determine native endianness.") + } +} diff --git a/pkg/xscript/engine/typedarrays_test.go b/pkg/xscript/engine/typedarrays_test.go new file mode 100644 index 0000000..eda7780 --- /dev/null +++ b/pkg/xscript/engine/typedarrays_test.go @@ -0,0 +1,516 @@ +package engine + +import ( + "bytes" + "testing" +) + +func TestUint16ArrayObject(t *testing.T) { + vm := New() + buf := vm._newArrayBuffer(vm.global.ArrayBufferPrototype, nil) + buf.data = make([]byte, 16) + if nativeEndian == littleEndian { + buf.data[2] = 0xFE + buf.data[3] = 0xCA + } else { + buf.data[2] = 0xCA + buf.data[3] = 0xFE + } + a := vm.newUint16ArrayObject(buf, 1, 1, nil) + v := a.getIdx(valueInt(0), nil) + if v != valueInt(0xCAFE) { + t.Fatalf("v: %v", v) + } +} + +func TestArrayBufferGoWrapper(t *testing.T) { + vm := New() + data := []byte{0xAA, 0xBB} + buf := vm.NewArrayBuffer(data) + vm.Set("buf", buf) + _, err := vm.RunString(` + var a = new Uint8Array(buf); + if (a.length !== 2 || a[0] !== 0xAA || a[1] !== 0xBB) { + throw new Error(a); + } + `) + if err != nil { + t.Fatal(err) + } + ret, err := vm.RunString(` + var b = Uint8Array.of(0xCC, 0xDD); + b.buffer; + `) + if err != nil { + t.Fatal(err) + } + buf1 := ret.Export().(ArrayBuffer) + data1 := buf1.Bytes() + if len(data1) != 2 || data1[0] != 0xCC || data1[1] != 0xDD { + t.Fatal(data1) + } + if buf1.Detached() { + t.Fatal("buf1.Detached() returned true") + } + if !buf1.Detach() { + t.Fatal("buf1.Detach() returned false") + } + if !buf1.Detached() { + t.Fatal("buf1.Detached() returned false") + } + _, err = vm.RunString(` + if (b[0] !== undefined) { + throw new Error("b[0] !== undefined"); + } + `) + if err != nil { + t.Fatal(err) + } +} + +func TestTypedArrayIdx(t *testing.T) { + const SCRIPT = ` + var a = new Uint8Array(1); + + // 32-bit integer overflow, should not panic on 32-bit architectures + if (a[4294967297] !== undefined) { + throw new Error("4294967297"); + } + + // Canonical non-integer + a["Infinity"] = 8; + if (a["Infinity"] !== undefined) { + throw new Error("Infinity"); + } + a["NaN"] = 1; + if (a["NaN"] !== undefined) { + throw new Error("NaN"); + } + + // Non-canonical integer + a["00"] = "00"; + if (a["00"] !== "00") { + throw new Error("00"); + } + + // Non-canonical non-integer + a["1e-3"] = "1e-3"; + if (a["1e-3"] !== "1e-3") { + throw new Error("1e-3"); + } + if (a["0.001"] !== undefined) { + throw new Error("0.001"); + } + + // Negative zero + a["-0"] = 88; + if (a["-0"] !== undefined) { + throw new Error("-0"); + } + + if (a[0] !== 0) { + throw new Error("0"); + } + + a["9007199254740992"] = 1; + if (a["9007199254740992"] !== undefined) { + throw new Error("9007199254740992"); + } + a["-9007199254740992"] = 1; + if (a["-9007199254740992"] !== undefined) { + throw new Error("-9007199254740992"); + } + + // Safe integer overflow, not canonical (Number("9007199254740993") === 9007199254740992) + a["9007199254740993"] = 1; + if (a["9007199254740993"] !== 1) { + throw new Error("9007199254740993"); + } + a["-9007199254740993"] = 1; + if (a["-9007199254740993"] !== 1) { + throw new Error("-9007199254740993"); + } + + // Safe integer overflow, canonical Number("9007199254740994") == 9007199254740994 + a["9007199254740994"] = 1; + if (a["9007199254740994"] !== undefined) { + throw new Error("9007199254740994"); + } + a["-9007199254740994"] = 1; + if (a["-9007199254740994"] !== undefined) { + throw new Error("-9007199254740994"); + } + ` + + testScript(SCRIPT, _undefined, t) +} + +func TestTypedArraySetDetachedBuffer(t *testing.T) { + const SCRIPT = ` + let sample = new Uint8Array([42]); + $DETACHBUFFER(sample.buffer); + sample[0] = 1; + + assert.sameValue(sample[0], undefined, 'sample[0] = 1 is undefined'); + sample['1.1'] = 1; + assert.sameValue(sample['1.1'], undefined, 'sample[\'1.1\'] = 1 is undefined'); + sample['-0'] = 1; + assert.sameValue(sample['-0'], undefined, 'sample[\'-0\'] = 1 is undefined'); + sample['-1'] = 1; + assert.sameValue(sample['-1'], undefined, 'sample[\'-1\'] = 1 is undefined'); + sample['1'] = 1; + assert.sameValue(sample['1'], undefined, 'sample[\'1\'] = 1 is undefined'); + sample['2'] = 1; + assert.sameValue(sample['2'], undefined, 'sample[\'2\'] = 1 is undefined'); + ` + vm := New() + vm.Set("$DETACHBUFFER", func(buf *ArrayBuffer) { + buf.Detach() + }) + vm.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestTypedArrayDefinePropDetachedBuffer(t *testing.T) { + const SCRIPT = ` + var desc = { + value: 0, + configurable: false, + enumerable: true, + writable: true + }; + + var obj = { + valueOf: function() { + throw new Error("valueOf() was called"); + } + }; + let sample = new Uint8Array(42); + $DETACHBUFFER(sample.buffer); + + assert.sameValue( + Reflect.defineProperty(sample, "0", desc), + false, + 'Reflect.defineProperty(sample, "0", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "-1", desc), + false, + 'Reflect.defineProperty(sample, "-1", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "1.1", desc), + false, + 'Reflect.defineProperty(sample, "1.1", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "-0", desc), + false, + 'Reflect.defineProperty(sample, "-0", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "2", { + configurable: true, + enumerable: true, + writable: true, + value: obj + }), + false, + 'Reflect.defineProperty(sample, "2", {configurable: true, enumerable: true, writable: true, value: obj}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "3", { + configurable: false, + enumerable: false, + writable: true, + value: obj + }), + false, + 'Reflect.defineProperty(sample, "3", {configurable: false, enumerable: false, writable: true, value: obj}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "4", { + writable: false, + configurable: false, + enumerable: true, + value: obj + }), + false, + 'Reflect.defineProperty("new TA(42)", "4", {writable: false, configurable: false, enumerable: true, value: obj}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "42", desc), + false, + 'Reflect.defineProperty(sample, "42", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "43", desc), + false, + 'Reflect.defineProperty(sample, "43", {value: 0, configurable: false, enumerable: true, writable: true} ) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "5", { + get: function() {} + }), + false, + 'Reflect.defineProperty(sample, "5", {get: function() {}}) must return false' + ); + + assert.sameValue( + Reflect.defineProperty(sample, "6", { + configurable: false, + enumerable: true, + writable: true + }), + false, + 'Reflect.defineProperty(sample, "6", {configurable: false, enumerable: true, writable: true}) must return false' + ); + ` + vm := New() + vm.Set("$DETACHBUFFER", func(buf *ArrayBuffer) { + buf.Detach() + }) + vm.testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestTypedArrayDefineProperty(t *testing.T) { + const SCRIPT = ` + var a = new Uint8Array(1); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "1", {value: 1}); + }); + assert.sameValue(Reflect.defineProperty(a, "1", {value: 1}), false, "1"); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "Infinity", {value: 8}); + }); + assert.sameValue(Reflect.defineProperty(a, "Infinity", {value: 8}), false, "Infinity"); + + Object.defineProperty(a, "test", {value: "passed"}); + assert.sameValue(a.test, "passed", "string property"); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "0", {value: 1, writable: false}); + }, "define non-writable"); + + assert.throws(TypeError, function() { + Object.defineProperty(a, "0", {get() { return 1; }}); + }, "define accessor"); + + var sample = new Uint8Array([42, 42]); + + assert.sameValue( + Reflect.defineProperty(sample, "0", { + value: 8, + configurable: true, + enumerable: true, + writable: true + }), + true + ); + + assert.sameValue(sample[0], 8, "property value was set"); + let descriptor0 = Object.getOwnPropertyDescriptor(sample, "0"); + assert.sameValue(descriptor0.value, 8); + assert.sameValue(descriptor0.configurable, true, "configurable"); + assert.sameValue(descriptor0.enumerable, true); + assert.sameValue(descriptor0.writable, true); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestTypedArrayGetInvalidIndex(t *testing.T) { + const SCRIPT = ` + var TypedArray = Object.getPrototypeOf(Int8Array); + var proto = TypedArray.prototype; + Object.defineProperty(proto, "1", { + get: function() { + throw new Error("OrdinaryGet was called!"); + } + }); + var a = new Uint8Array(1); + assert.sameValue(a[1], undefined); + assert.sameValue(a["1"], undefined); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func TestExportArrayBufferToBytes(t *testing.T) { + vm := New() + bb := []byte("test") + ab := vm.NewArrayBuffer(bb) + var b []byte + err := vm.ExportTo(vm.ToValue(ab), &b) + if err != nil { + t.Fatal(err) + } + if !bytes.Equal(b, bb) { + t.Fatal("Not equal") + } + + err = vm.ExportTo(vm.ToValue(123), &b) + if err == nil { + t.Fatal("expected error") + } +} + +func TestTypedArrayExport(t *testing.T) { + vm := New() + + t.Run("uint8", func(t *testing.T) { + v, err := vm.RunString("new Uint8Array([1, 2])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]uint8); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 2 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + _, err = vm.RunString(`{ + let a = new Uint8Array([1, 2]); + if (a[0] !== 1 || a[1] !== 2) { + throw new Error(a); + } + }`) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("uint8-slice", func(t *testing.T) { + v, err := vm.RunString(`{ + const buf = new Uint8Array([1, 2]).buffer; + new Uint8Array(buf, 1, 1); + }`) + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]uint8); ok { + if len(a) != 1 || a[0] != 2 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + _, err = vm.RunString(`{ + let a = new Uint8Array([1, 2]); + if (a[0] !== 1 || a[1] !== 2) { + throw new Error(a); + } + }`) + if err != nil { + t.Fatal(err) + } + }) + + t.Run("int8", func(t *testing.T) { + v, err := vm.RunString("new Int8Array([1, -2])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]int8); ok { + if len(a) != 2 || a[0] != 1 || a[1] != -2 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("uint16", func(t *testing.T) { + v, err := vm.RunString("new Uint16Array([1, 63000])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]uint16); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 63000 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("int16", func(t *testing.T) { + v, err := vm.RunString("new Int16Array([1, -31000])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]int16); ok { + if len(a) != 2 || a[0] != 1 || a[1] != -31000 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("uint32", func(t *testing.T) { + v, err := vm.RunString("new Uint32Array([1, 123456])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]uint32); ok { + if len(a) != 2 || a[0] != 1 || a[1] != 123456 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("int32", func(t *testing.T) { + v, err := vm.RunString("new Int32Array([1, -123456])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]int32); ok { + if len(a) != 2 || a[0] != 1 || a[1] != -123456 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("float32", func(t *testing.T) { + v, err := vm.RunString("new Float32Array([1, -1.23456])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]float32); ok { + if len(a) != 2 || a[0] != 1 || a[1] != -1.23456 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + + t.Run("float64", func(t *testing.T) { + v, err := vm.RunString("new Float64Array([1, -1.23456789])") + if err != nil { + t.Fatal(err) + } + if a, ok := v.Export().([]float64); ok { + if len(a) != 2 || a[0] != 1 || a[1] != -1.23456789 { + t.Fatal(a) + } + } else { + t.Fatal("Wrong export type") + } + }) + +} diff --git a/pkg/xscript/engine/unistring/string.go b/pkg/xscript/engine/unistring/string.go new file mode 100644 index 0000000..4628299 --- /dev/null +++ b/pkg/xscript/engine/unistring/string.go @@ -0,0 +1,137 @@ +// Package unistring contains an implementation of a hybrid ASCII/UTF-16 string. +// For ASCII strings the underlying representation is equivalent to a normal Go string. +// For unicode strings the underlying representation is UTF-16 as []uint16 with 0th element set to 0xFEFF. +// unicode.String allows representing malformed UTF-16 values (e.g. stand-alone parts of surrogate pairs) +// which cannot be represented in UTF-8. +// At the same time it is possible to use unicode.String as property keys just as efficiently as simple strings, +// (the leading 0xFEFF ensures there is no clash with ASCII string), and it is possible to convert it +// to valueString without extra allocations. +package unistring + +import ( + "reflect" + "unicode/utf16" + "unicode/utf8" + "unsafe" +) + +const ( + BOM = 0xFEFF +) + +type String string + +// Scan checks if the string contains any unicode characters. If it does, converts to an array suitable for creating +// a String using FromUtf16, otherwise returns nil. +func Scan(s string) []uint16 { + utf16Size := 0 + for ; utf16Size < len(s); utf16Size++ { + if s[utf16Size] >= utf8.RuneSelf { + goto unicode + } + } + return nil +unicode: + for _, chr := range s[utf16Size:] { + utf16Size++ + if chr > 0xFFFF { + utf16Size++ + } + } + + buf := make([]uint16, utf16Size+1) + buf[0] = BOM + c := 1 + for _, chr := range s { + if chr <= 0xFFFF { + buf[c] = uint16(chr) + } else { + first, second := utf16.EncodeRune(chr) + buf[c] = uint16(first) + c++ + buf[c] = uint16(second) + } + c++ + } + + return buf +} + +func NewFromString(s string) String { + if buf := Scan(s); buf != nil { + return FromUtf16(buf) + } + return String(s) +} + +func NewFromRunes(s []rune) String { + ascii := true + size := 0 + for _, c := range s { + if c >= utf8.RuneSelf { + ascii = false + if c > 0xFFFF { + size++ + } + } + size++ + } + if ascii { + return String(s) + } + b := make([]uint16, size+1) + b[0] = BOM + i := 1 + for _, c := range s { + if c <= 0xFFFF { + b[i] = uint16(c) + } else { + first, second := utf16.EncodeRune(c) + b[i] = uint16(first) + i++ + b[i] = uint16(second) + } + i++ + } + return FromUtf16(b) +} + +func FromUtf16(b []uint16) String { + var str string + hdr := (*reflect.StringHeader)(unsafe.Pointer(&str)) + hdr.Data = uintptr(unsafe.Pointer(&b[0])) + hdr.Len = len(b) * 2 + + return String(str) +} + +func (s String) String() string { + if b := s.AsUtf16(); b != nil { + return string(utf16.Decode(b[1:])) + } + + return string(s) +} + +func (s String) AsUtf16() []uint16 { + if len(s) < 4 || len(s)&1 != 0 { + return nil + } + + var a []uint16 + raw := string(s) + + sliceHeader := (*reflect.SliceHeader)(unsafe.Pointer(&a)) + sliceHeader.Data = (*reflect.StringHeader)(unsafe.Pointer(&raw)).Data + + l := len(raw) / 2 + + sliceHeader.Len = l + sliceHeader.Cap = l + + if a[0] == BOM { + return a + } + + return nil +} diff --git a/pkg/xscript/engine/unistring/string_test.go b/pkg/xscript/engine/unistring/string_test.go new file mode 100644 index 0000000..2e4f420 --- /dev/null +++ b/pkg/xscript/engine/unistring/string_test.go @@ -0,0 +1,16 @@ +package unistring + +import "testing" + +func TestString_AsUtf16(t *testing.T) { + const str = "más" + s := NewFromString(str) + + if b := s.AsUtf16(); len(b) != 4 || b[0] != BOM { + t.Fatal(b) + } + + if s.String() != str { + t.Fatal(s) + } +} diff --git a/pkg/xscript/engine/value.go b/pkg/xscript/engine/value.go new file mode 100644 index 0000000..8ac42cf --- /dev/null +++ b/pkg/xscript/engine/value.go @@ -0,0 +1,1173 @@ +package engine + +import ( + "fmt" + "hash/maphash" + "math" + "reflect" + "strconv" + "unsafe" + + "pandax/pkg/xscript/engine/ftoa" + "pandax/pkg/xscript/engine/unistring" +) + +var ( + // Not goroutine-safe, do not use for anything other than package level init + pkgHasher maphash.Hash + + hashFalse = randomHash() + hashTrue = randomHash() + hashNull = randomHash() + hashUndef = randomHash() +) + +// Not goroutine-safe, do not use for anything other than package level init +func randomHash() uint64 { + pkgHasher.WriteByte(0) + return pkgHasher.Sum64() +} + +var ( + valueFalse Value = valueBool(false) + valueTrue Value = valueBool(true) + _null Value = valueNull{} + _NaN Value = valueFloat(math.NaN()) + _positiveInf Value = valueFloat(math.Inf(+1)) + _negativeInf Value = valueFloat(math.Inf(-1)) + _positiveZero Value = valueInt(0) + negativeZero = math.Float64frombits(0 | (1 << 63)) + _negativeZero Value = valueFloat(negativeZero) + _epsilon = valueFloat(2.2204460492503130808472633361816e-16) + _undefined Value = valueUndefined{} +) + +var ( + reflectTypeInt = reflect.TypeOf(int64(0)) + reflectTypeBool = reflect.TypeOf(false) + reflectTypeNil = reflect.TypeOf(nil) + reflectTypeFloat = reflect.TypeOf(float64(0)) + reflectTypeMap = reflect.TypeOf(map[string]any{}) + reflectTypeArray = reflect.TypeOf([]any{}) + reflectTypeArrayPtr = reflect.TypeOf((*[]any)(nil)) + reflectTypeString = reflect.TypeOf("") + reflectTypeFunc = reflect.TypeOf((func(FunctionCall) Value)(nil)) + reflectTypeError = reflect.TypeOf((*error)(nil)).Elem() +) + +var intCache [256]Value + +// Value represents an ECMAScript value. +// +// Export returns a "plain" Go value which type depends on the type of the Value. +// +// For integer numbers it's int64. +// +// For any other numbers (including Infinities, NaN and negative zero) it's float64. +// +// For string it's a string. Note that unicode strings are converted into UTF-8 with invalid code points replaced with utf8.RuneError. +// +// For boolean it's bool. +// +// For null and undefined it's nil. +// +// For Object it depends on the Object type, see Object.Export() for more details. +type Value interface { + ToInteger() int64 + toString() String + string() unistring.String + ToString() Value + String() string + ToFloat() float64 + ToNumber() Value + ToBoolean() bool + ToObject(*Runtime) *Object + SameAs(Value) bool + Equals(Value) bool + StrictEquals(Value) bool + Export() any + ExportType() reflect.Type + + baseObject(r *Runtime) *Object + + hash(hasher *maphash.Hash) uint64 +} + +type valueContainer interface { + toValue(*Runtime) Value +} + +type typeError string +type rangeError string +type referenceError string +type syntaxError string + +type valueInt int64 +type valueFloat float64 +type valueBool bool +type valueNull struct{} +type valueUndefined struct { + valueNull +} + +// *Symbol is a Value containing ECMAScript Symbol primitive. Symbols must only be created +// using NewSymbol(). Zero values and copying of values (i.e. *s1 = *s2) are not permitted. +// Well-known Symbols can be accessed using Sym* package variables (SymIterator, etc...) +// Symbols can be shared by multiple Runtimes. +type Symbol struct { + h uintptr + desc String +} + +type valueUnresolved struct { + r *Runtime + ref unistring.String +} + +type memberUnresolved struct { + valueUnresolved +} + +type valueProperty struct { + value Value + writable bool + configurable bool + enumerable bool + accessor bool + getterFunc *Object + setterFunc *Object +} + +var ( + errAccessBeforeInit = referenceError("Cannot access a variable before initialization") + errAssignToConst = typeError("Assignment to constant variable.") +) + +func propGetter(o Value, v Value, r *Runtime) *Object { + if v == _undefined { + return nil + } + if obj, ok := v.(*Object); ok { + if _, ok := obj.self.assertCallable(); ok { + return obj + } + } + r.typeErrorResult(true, "Getter must be a function: %s", v.toString()) + return nil +} + +func propSetter(o Value, v Value, r *Runtime) *Object { + if v == _undefined { + return nil + } + if obj, ok := v.(*Object); ok { + if _, ok := obj.self.assertCallable(); ok { + return obj + } + } + r.typeErrorResult(true, "Setter must be a function: %s", v.toString()) + return nil +} + +func fToStr(num float64, mode ftoa.FToStrMode, prec int) string { + var buf1 [128]byte + return string(ftoa.FToStr(num, mode, prec, buf1[:0])) +} + +func (i valueInt) ToInteger() int64 { + return int64(i) +} + +func (i valueInt) toString() String { + return asciiString(i.String()) +} + +func (i valueInt) string() unistring.String { + return unistring.String(i.String()) +} + +func (i valueInt) ToString() Value { + return i +} + +func (i valueInt) String() string { + return strconv.FormatInt(int64(i), 10) +} + +func (i valueInt) ToFloat() float64 { + return float64(i) +} + +func (i valueInt) ToBoolean() bool { + return i != 0 +} + +func (i valueInt) ToObject(r *Runtime) *Object { + return r.newPrimitiveObject(i, r.getNumberPrototype(), classNumber) +} + +func (i valueInt) ToNumber() Value { + return i +} + +func (i valueInt) SameAs(other Value) bool { + return i == other +} + +func (i valueInt) Equals(other Value) bool { + switch o := other.(type) { + case valueInt: + return i == o + case valueFloat: + return float64(i) == float64(o) + case String: + return o.ToNumber().Equals(i) + case valueBool: + return int64(i) == o.ToInteger() + case *Object: + return i.Equals(o.toPrimitive()) + } + + return false +} + +func (i valueInt) StrictEquals(other Value) bool { + switch o := other.(type) { + case valueInt: + return i == o + case valueFloat: + return float64(i) == float64(o) + } + + return false +} + +func (i valueInt) baseObject(r *Runtime) *Object { + return r.getNumberPrototype() +} + +func (i valueInt) Export() any { + return int64(i) +} + +func (i valueInt) ExportType() reflect.Type { + return reflectTypeInt +} + +func (i valueInt) hash(*maphash.Hash) uint64 { + return uint64(i) +} + +func (b valueBool) ToInteger() int64 { + if b { + return 1 + } + return 0 +} + +func (b valueBool) toString() String { + if b { + return stringTrue + } + return stringFalse +} + +func (b valueBool) ToString() Value { + return b +} + +func (b valueBool) String() string { + if b { + return "true" + } + return "false" +} + +func (b valueBool) string() unistring.String { + return unistring.String(b.String()) +} + +func (b valueBool) ToFloat() float64 { + if b { + return 1.0 + } + return 0 +} + +func (b valueBool) ToBoolean() bool { + return bool(b) +} + +func (b valueBool) ToObject(r *Runtime) *Object { + return r.newPrimitiveObject(b, r.getBooleanPrototype(), "Boolean") +} + +func (b valueBool) ToNumber() Value { + if b { + return valueInt(1) + } + return valueInt(0) +} + +func (b valueBool) SameAs(other Value) bool { + if other, ok := other.(valueBool); ok { + return b == other + } + return false +} + +func (b valueBool) Equals(other Value) bool { + if o, ok := other.(valueBool); ok { + return b == o + } + + if b { + return other.Equals(intToValue(1)) + } else { + return other.Equals(intToValue(0)) + } + +} + +func (b valueBool) StrictEquals(other Value) bool { + if other, ok := other.(valueBool); ok { + return b == other + } + return false +} + +func (b valueBool) baseObject(r *Runtime) *Object { + return r.getBooleanPrototype() +} + +func (b valueBool) Export() any { + return bool(b) +} + +func (b valueBool) ExportType() reflect.Type { + return reflectTypeBool +} + +func (b valueBool) hash(*maphash.Hash) uint64 { + if b { + return hashTrue + } + + return hashFalse +} + +func (n valueNull) ToInteger() int64 { + return 0 +} + +func (n valueNull) toString() String { + return stringNull +} + +func (n valueNull) string() unistring.String { + return stringNull.string() +} + +func (n valueNull) ToString() Value { + return n +} + +func (n valueNull) String() string { + return "null" +} + +func (u valueUndefined) toString() String { + return stringUndefined +} + +func (u valueUndefined) ToString() Value { + return u +} + +func (u valueUndefined) String() string { + return "undefined" +} + +func (u valueUndefined) string() unistring.String { + return "undefined" +} + +func (u valueUndefined) ToNumber() Value { + return _NaN +} + +func (u valueUndefined) SameAs(other Value) bool { + _, same := other.(valueUndefined) + return same +} + +func (u valueUndefined) StrictEquals(other Value) bool { + _, same := other.(valueUndefined) + return same +} + +func (u valueUndefined) ToFloat() float64 { + return math.NaN() +} + +func (u valueUndefined) hash(*maphash.Hash) uint64 { + return hashUndef +} + +func (n valueNull) ToFloat() float64 { + return 0 +} + +func (n valueNull) ToBoolean() bool { + return false +} + +func (n valueNull) ToObject(r *Runtime) *Object { + r.typeErrorResult(true, "Cannot convert undefined or null to object") + return nil + //return r.newObject() +} + +func (n valueNull) ToNumber() Value { + return intToValue(0) +} + +func (n valueNull) SameAs(other Value) bool { + _, same := other.(valueNull) + return same +} + +func (n valueNull) Equals(other Value) bool { + switch other.(type) { + case valueUndefined, valueNull: + return true + } + return false +} + +func (n valueNull) StrictEquals(other Value) bool { + _, same := other.(valueNull) + return same +} + +func (n valueNull) baseObject(*Runtime) *Object { + return nil +} + +func (n valueNull) Export() any { + return nil +} + +func (n valueNull) ExportType() reflect.Type { + return reflectTypeNil +} + +func (n valueNull) hash(*maphash.Hash) uint64 { + return hashNull +} + +func (p *valueProperty) ToInteger() int64 { + return 0 +} + +func (p *valueProperty) toString() String { + return stringEmpty +} + +func (p *valueProperty) string() unistring.String { + return "" +} + +func (p *valueProperty) ToString() Value { + return _undefined +} + +func (p *valueProperty) String() string { + return "" +} + +func (p *valueProperty) ToFloat() float64 { + return math.NaN() +} + +func (p *valueProperty) ToBoolean() bool { + return false +} + +func (p *valueProperty) ToObject(*Runtime) *Object { + return nil +} + +func (p *valueProperty) ToNumber() Value { + return nil +} + +func (p *valueProperty) isWritable() bool { + return p.writable || p.setterFunc != nil +} + +func (p *valueProperty) get(this Value) Value { + if p.getterFunc == nil { + if p.value != nil { + return p.value + } + return _undefined + } + call, _ := p.getterFunc.self.assertCallable() + return call(FunctionCall{ + This: this, + }) +} + +func (p *valueProperty) set(this, v Value) { + if p.setterFunc == nil { + p.value = v + return + } + call, _ := p.setterFunc.self.assertCallable() + call(FunctionCall{ + This: this, + Arguments: []Value{v}, + }) +} + +func (p *valueProperty) SameAs(other Value) bool { + if otherProp, ok := other.(*valueProperty); ok { + return p == otherProp + } + return false +} + +func (p *valueProperty) Equals(Value) bool { + return false +} + +func (p *valueProperty) StrictEquals(Value) bool { + return false +} + +func (p *valueProperty) baseObject(r *Runtime) *Object { + r.typeErrorResult(true, "BUG: baseObject() is called on valueProperty") // TODO error message + return nil +} + +func (p *valueProperty) Export() any { + panic("Cannot export valueProperty") +} + +func (p *valueProperty) ExportType() reflect.Type { + panic("Cannot export valueProperty") +} + +func (p *valueProperty) hash(*maphash.Hash) uint64 { + panic("valueProperty should never be used in maps or sets") +} + +func floatToIntClip(n float64) int64 { + switch { + case math.IsNaN(n): + return 0 + case n >= math.MaxInt64: + return math.MaxInt64 + case n <= math.MinInt64: + return math.MinInt64 + } + return int64(n) +} + +func (f valueFloat) ToInteger() int64 { + return floatToIntClip(float64(f)) +} + +func (f valueFloat) toString() String { + return asciiString(f.String()) +} + +func (f valueFloat) string() unistring.String { + return unistring.String(f.String()) +} + +func (f valueFloat) ToString() Value { + return f +} + +func (f valueFloat) String() string { + return fToStr(float64(f), ftoa.ModeStandard, 0) +} + +func (f valueFloat) ToFloat() float64 { + return float64(f) +} + +func (f valueFloat) ToBoolean() bool { + return float64(f) != 0.0 && !math.IsNaN(float64(f)) +} + +func (f valueFloat) ToObject(r *Runtime) *Object { + return r.newPrimitiveObject(f, r.getNumberPrototype(), "Number") +} + +func (f valueFloat) ToNumber() Value { + return f +} + +func (f valueFloat) SameAs(other Value) bool { + switch o := other.(type) { + case valueFloat: + this := float64(f) + o1 := float64(o) + if math.IsNaN(this) && math.IsNaN(o1) { + return true + } else { + ret := this == o1 + if ret && this == 0 { + ret = math.Signbit(this) == math.Signbit(o1) + } + return ret + } + case valueInt: + this := float64(f) + ret := this == float64(o) + if ret && this == 0 { + ret = !math.Signbit(this) + } + return ret + } + + return false +} + +func (f valueFloat) Equals(other Value) bool { + switch o := other.(type) { + case valueFloat: + return f == o + case valueInt: + return float64(f) == float64(o) + case String, valueBool: + return float64(f) == o.ToFloat() + case *Object: + return f.Equals(o.toPrimitive()) + } + + return false +} + +func (f valueFloat) StrictEquals(other Value) bool { + switch o := other.(type) { + case valueFloat: + return f == o + case valueInt: + return float64(f) == float64(o) + } + + return false +} + +func (f valueFloat) baseObject(r *Runtime) *Object { + return r.getNumberPrototype() +} + +func (f valueFloat) Export() any { + return float64(f) +} + +func (f valueFloat) ExportType() reflect.Type { + return reflectTypeFloat +} + +func (f valueFloat) hash(*maphash.Hash) uint64 { + if f == _negativeZero { + return 0 + } + return math.Float64bits(float64(f)) +} + +func (o *Object) ToInteger() int64 { + return o.toPrimitiveNumber().ToNumber().ToInteger() +} + +func (o *Object) toString() String { + return o.toPrimitiveString().toString() +} + +func (o *Object) string() unistring.String { + return o.toPrimitiveString().string() +} + +func (o *Object) ToString() Value { + return o.toPrimitiveString().ToString() +} + +func (o *Object) String() string { + return o.toPrimitiveString().String() +} + +func (o *Object) ToFloat() float64 { + return o.toPrimitiveNumber().ToFloat() +} + +func (o *Object) ToBoolean() bool { + return true +} + +func (o *Object) ToObject(*Runtime) *Object { + return o +} + +func (o *Object) ToNumber() Value { + return o.toPrimitiveNumber().ToNumber() +} + +func (o *Object) SameAs(other Value) bool { + return o.StrictEquals(other) +} + +func (o *Object) Equals(other Value) bool { + if other, ok := other.(*Object); ok { + return o == other || o.self.equal(other.self) + } + + switch o1 := other.(type) { + case valueInt, valueFloat, String, *Symbol: + return o.toPrimitive().Equals(other) + case valueBool: + return o.Equals(o1.ToNumber()) + } + + return false +} + +func (o *Object) StrictEquals(other Value) bool { + if other, ok := other.(*Object); ok { + return o == other || o != nil && other != nil && o.self.equal(other.self) + } + return false +} + +func (o *Object) baseObject(*Runtime) *Object { + return o +} + +// Export the Object to a plain Go type. +// If the Object is a wrapped Go value (created using ToValue()) returns the original value. +// +// If the Object is a function, returns func(FunctionCall) Value. Note that exceptions thrown inside the function +// result in panics, which can also leave the Runtime in an unusable state. Therefore, these values should only +// be used inside another ES function implemented in Go. For calling a function from Go, use AssertFunction() or +// Runtime.ExportTo() as described in the README. +// +// For a Map, returns the list of entries as [][2]any. +// +// For a Set, returns the list of elements as []any. +// +// For a Proxy, returns Proxy. +// +// For a Promise, returns Promise. +// +// For a DynamicObject or a DynamicArray, returns the underlying handler. +// +// For typed arrays it returns a slice of the corresponding type backed by the original data (i.e. it does not copy). +// +// For an untyped array, returns its items exported into a newly created []any. +// +// In all other cases returns own enumerable non-symbol properties as map[string]any. +// +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (o *Object) Export() any { + return o.self.export(&objectExportCtx{}) +} + +// ExportType returns the type of the value that is returned by Export(). +func (o *Object) ExportType() reflect.Type { + return o.self.exportType() +} + +func (o *Object) hash(*maphash.Hash) uint64 { + return o.getId() +} + +// Get an object's property by name. +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (o *Object) Get(name string) Value { + return o.self.getStr(unistring.NewFromString(name), nil) +} + +// GetSymbol returns the value of a symbol property. Use one of the Sym* values for well-known +// symbols (such as SymIterator, SymToStringTag, etc...). +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (o *Object) GetSymbol(sym *Symbol) Value { + return o.self.getSym(sym, nil) +} + +// Keys returns a list of Object's enumerable keys. +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (o *Object) Keys() (keys []string) { + iter := &enumerableIter{ + o: o, + wrapped: o.self.iterateStringKeys(), + } + for item, next := iter.next(); next != nil; item, next = next() { + keys = append(keys, item.name.String()) + } + + return +} + +// Symbols returns a list of Object's enumerable symbol properties. +// This method will panic with an *Exception if a JavaScript exception is thrown in the process. +func (o *Object) Symbols() []*Symbol { + symbols := o.self.symbols(false, nil) + ret := make([]*Symbol, len(symbols)) + for i, sym := range symbols { + ret[i], _ = sym.(*Symbol) + } + return ret +} + +// DefineDataProperty is a Go equivalent of Object.defineProperty(o, name, {value: value, writable: writable, +// configurable: configurable, enumerable: enumerable}) +func (o *Object) DefineDataProperty(name string, value Value, writable, configurable, enumerable Flag) error { + return o.runtime.try(func() { + o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{ + Value: value, + Writable: writable, + Configurable: configurable, + Enumerable: enumerable, + }, true) + }) +} + +// DefineAccessorProperty is a Go equivalent of Object.defineProperty(o, name, {get: getter, set: setter, +// configurable: configurable, enumerable: enumerable}) +func (o *Object) DefineAccessorProperty(name string, getter, setter Value, configurable, enumerable Flag) error { + return o.runtime.try(func() { + o.self.defineOwnPropertyStr(unistring.NewFromString(name), PropertyDescriptor{ + Getter: getter, + Setter: setter, + Configurable: configurable, + Enumerable: enumerable, + }, true) + }) +} + +// DefineDataPropertySymbol is a Go equivalent of Object.defineProperty(o, name, {value: value, writable: writable, +// configurable: configurable, enumerable: enumerable}) +func (o *Object) DefineDataPropertySymbol(name *Symbol, value Value, writable, configurable, enumerable Flag) error { + return o.runtime.try(func() { + o.self.defineOwnPropertySym(name, PropertyDescriptor{ + Value: value, + Writable: writable, + Configurable: configurable, + Enumerable: enumerable, + }, true) + }) +} + +// DefineAccessorPropertySymbol is a Go equivalent of Object.defineProperty(o, name, {get: getter, set: setter, +// configurable: configurable, enumerable: enumerable}) +func (o *Object) DefineAccessorPropertySymbol(name *Symbol, getter, setter Value, configurable, enumerable Flag) error { + return o.runtime.try(func() { + o.self.defineOwnPropertySym(name, PropertyDescriptor{ + Getter: getter, + Setter: setter, + Configurable: configurable, + Enumerable: enumerable, + }, true) + }) +} + +func (o *Object) Set(name string, value any) error { + return o.runtime.try(func() { + o.self.setOwnStr(unistring.NewFromString(name), o.runtime.ToValue(value), true) + }) +} + +func (o *Object) SetSymbol(name *Symbol, value any) error { + return o.runtime.try(func() { + o.self.setOwnSym(name, o.runtime.ToValue(value), true) + }) +} + +func (o *Object) Delete(name string) error { + return o.runtime.try(func() { + o.self.deleteStr(unistring.NewFromString(name), true) + }) +} + +func (o *Object) DeleteSymbol(name *Symbol) error { + return o.runtime.try(func() { + o.self.deleteSym(name, true) + }) +} + +// Prototype returns the Object's prototype, same as Object.getPrototypeOf(). If the prototype is null +// returns nil. +func (o *Object) Prototype() *Object { + return o.self.proto() +} + +// SetPrototype sets the Object's prototype, same as Object.setPrototypeOf(). Setting proto to nil +// is an equivalent of Object.setPrototypeOf(null). +func (o *Object) SetPrototype(proto *Object) error { + return o.runtime.try(func() { + o.self.setProto(proto, true) + }) +} + +// MarshalJSON returns JSON representation of the Object. It is equivalent to JSON.stringify(o). +// Note, this implements json.Marshaler so that json.Marshal() can be used without the need to Export(). +func (o *Object) MarshalJSON() ([]byte, error) { + ctx := _builtinJSON_stringifyContext{ + r: o.runtime, + } + ex := o.runtime.vm.try(func() { + if !ctx.do(o) { + ctx.buf.WriteString("null") + } + }) + if ex != nil { + return nil, ex + } + return ctx.buf.Bytes(), nil +} + +// UnmarshalJSON implements the json.Unmarshaler interface. It is added to compliment MarshalJSON, because +// some alternative JSON encoders refuse to use MarshalJSON unless UnmarshalJSON is also present. +// It is a no-op and always returns nil. +func (o *Object) UnmarshalJSON([]byte) error { + return nil +} + +// ClassName returns the class name +func (o *Object) ClassName() string { + return o.self.className() +} + +func (o valueUnresolved) throw() { + o.r.throwReferenceError(o.ref) +} + +func (o valueUnresolved) ToInteger() int64 { + o.throw() + return 0 +} + +func (o valueUnresolved) toString() String { + o.throw() + return nil +} + +func (o valueUnresolved) string() unistring.String { + o.throw() + return "" +} + +func (o valueUnresolved) ToString() Value { + o.throw() + return nil +} + +func (o valueUnresolved) String() string { + o.throw() + return "" +} + +func (o valueUnresolved) ToFloat() float64 { + o.throw() + return 0 +} + +func (o valueUnresolved) ToBoolean() bool { + o.throw() + return false +} + +func (o valueUnresolved) ToObject(*Runtime) *Object { + o.throw() + return nil +} + +func (o valueUnresolved) ToNumber() Value { + o.throw() + return nil +} + +func (o valueUnresolved) SameAs(Value) bool { + o.throw() + return false +} + +func (o valueUnresolved) Equals(Value) bool { + o.throw() + return false +} + +func (o valueUnresolved) StrictEquals(Value) bool { + o.throw() + return false +} + +func (o valueUnresolved) baseObject(*Runtime) *Object { + o.throw() + return nil +} + +func (o valueUnresolved) Export() any { + o.throw() + return nil +} + +func (o valueUnresolved) ExportType() reflect.Type { + o.throw() + return nil +} + +func (o valueUnresolved) hash(*maphash.Hash) uint64 { + o.throw() + return 0 +} + +func (s *Symbol) ToInteger() int64 { + panic(typeError("Cannot convert a Symbol value to a number")) +} + +func (s *Symbol) toString() String { + panic(typeError("Cannot convert a Symbol value to a string")) +} + +func (s *Symbol) ToString() Value { + return s +} + +func (s *Symbol) String() string { + if s.desc != nil { + return s.desc.String() + } + return "" +} + +func (s *Symbol) string() unistring.String { + if s.desc != nil { + return s.desc.string() + } + return "" +} + +func (s *Symbol) ToFloat() float64 { + panic(typeError("Cannot convert a Symbol value to a number")) +} + +func (s *Symbol) ToNumber() Value { + panic(typeError("Cannot convert a Symbol value to a number")) +} + +func (s *Symbol) ToBoolean() bool { + return true +} + +func (s *Symbol) ToObject(r *Runtime) *Object { + return s.baseObject(r) +} + +func (s *Symbol) SameAs(other Value) bool { + if s1, ok := other.(*Symbol); ok { + return s == s1 + } + return false +} + +func (s *Symbol) Equals(o Value) bool { + switch o := o.(type) { + case *Object: + return s.Equals(o.toPrimitive()) + } + return s.SameAs(o) +} + +func (s *Symbol) StrictEquals(o Value) bool { + return s.SameAs(o) +} + +func (s *Symbol) Export() any { + return s.String() +} + +func (s *Symbol) ExportType() reflect.Type { + return reflectTypeString +} + +func (s *Symbol) baseObject(r *Runtime) *Object { + return r.newPrimitiveObject(s, r.getSymbolPrototype(), classObject) +} + +func (s *Symbol) hash(*maphash.Hash) uint64 { + return uint64(s.h) +} + +func exportValue(v Value, ctx *objectExportCtx) any { + if obj, ok := v.(*Object); ok { + return obj.self.export(ctx) + } + return v.Export() +} + +func newSymbol(s String) *Symbol { + r := &Symbol{ + desc: s, + } + // This may need to be reconsidered in the future. + // Depending on changes in Go's allocation policy and/or introduction of a compacting GC + // this may no longer provide sufficient dispersion. The alternative, however, is a globally + // synchronised random generator/hasher/sequencer and I don't want to go down that route just yet. + r.h = uintptr(unsafe.Pointer(r)) + return r +} + +func NewSymbol(s string) *Symbol { + return newSymbol(newStringValue(s)) +} + +func (s *Symbol) descriptiveString() String { + desc := s.desc + if desc == nil { + desc = stringEmpty + } + return asciiString("Symbol(").Concat(desc).Concat(asciiString(")")) +} + +func funcName(prefix string, n Value) String { + var b StringBuilder + b.WriteString(asciiString(prefix)) + if sym, ok := n.(*Symbol); ok { + if sym.desc != nil { + b.WriteRune('[') + b.WriteString(sym.desc) + b.WriteRune(']') + } + } else { + b.WriteString(n.toString()) + } + return b.String() +} + +func newTypeError(args ...any) typeError { + msg := "" + if len(args) > 0 { + f, _ := args[0].(string) + msg = fmt.Sprintf(f, args[1:]...) + } + return typeError(msg) +} + +func typeErrorResult(throw bool, args ...any) { + if throw { + panic(newTypeError(args...)) + } + +} + +func init() { + for i := 0; i < 256; i++ { + intCache[i] = valueInt(i - 256) + } +} diff --git a/pkg/xscript/engine/vm.go b/pkg/xscript/engine/vm.go new file mode 100644 index 0000000..4632f6d --- /dev/null +++ b/pkg/xscript/engine/vm.go @@ -0,0 +1,5613 @@ +package engine + +import ( + "fmt" + "math" + "strconv" + "strings" + "sync" + "sync/atomic" + "time" + + "pandax/pkg/xscript/engine/unistring" +) + +const ( + maxInt = 1 << 53 + + tryPanicMarker = -2 +) + +type valueStack []Value + +type stash struct { + values []Value + extraArgs []Value + names map[unistring.String]uint32 + obj *Object + + outer *stash + + // If this is a top-level function stash, sets the type of the function. If set, dynamic var declarations + // created by direct eval go here. + funcType funcType +} + +type context struct { + prg *Program + stash *stash + privEnv *privateEnv + newTarget Value + result Value + pc, sb int + args int +} + +type tryFrame struct { + // holds an uncaught exception for the 'finally' block + exception *Exception + + callStackLen, iterLen, refLen uint32 + + sp int32 + stash *stash + privEnv *privateEnv + + catchPos, finallyPos, finallyRet int32 +} + +type execCtx struct { + context + stack []Value + tryStack []tryFrame + iterStack []iterStackItem + refStack []ref +} + +func (vm *vm) suspend(ectx *execCtx, tryStackLen, iterStackLen, refStackLen uint32) { + vm.saveCtx(&ectx.context) + ectx.stack = append(ectx.stack[:0], vm.stack[vm.sb-1:vm.sp]...) + if len(vm.tryStack) > int(tryStackLen) { + ectx.tryStack = append(ectx.tryStack[:0], vm.tryStack[tryStackLen:]...) + vm.tryStack = vm.tryStack[:tryStackLen] + sp := int32(vm.sb - 1) + for i := range ectx.tryStack { + tf := &ectx.tryStack[i] + tf.iterLen -= iterStackLen + tf.refLen -= refStackLen + tf.sp -= sp + } + } + if len(vm.iterStack) > int(iterStackLen) { + ectx.iterStack = append(ectx.iterStack[:0], vm.iterStack[iterStackLen:]...) + vm.iterStack = vm.iterStack[:iterStackLen] + } + if len(vm.refStack) > int(refStackLen) { + ectx.refStack = append(ectx.refStack[:0], vm.refStack[refStackLen:]...) + vm.refStack = vm.refStack[:refStackLen] + } +} + +func (vm *vm) resume(ctx *execCtx) { + vm.restoreCtx(&ctx.context) + sp := vm.sp + vm.sb = sp + 1 + vm.stack.expand(sp + len(ctx.stack)) + copy(vm.stack[sp:], ctx.stack) + vm.sp += len(ctx.stack) + for i := range ctx.tryStack { + tf := &ctx.tryStack[i] + tf.callStackLen = uint32(len(vm.callStack)) + tf.iterLen += uint32(len(vm.iterStack)) + tf.refLen += uint32(len(vm.refStack)) + tf.sp += int32(sp) + } + vm.tryStack = append(vm.tryStack, ctx.tryStack...) + vm.iterStack = append(vm.iterStack, ctx.iterStack...) + vm.refStack = append(vm.refStack, ctx.refStack...) +} + +type iterStackItem struct { + val Value + f iterNextFunc + iter *iteratorRecord +} + +type ref interface { + get() Value + set(Value) + init(Value) + refname() unistring.String +} + +type stashRef struct { + n unistring.String + v *[]Value + idx int +} + +func (r *stashRef) get() Value { + return nilSafe((*r.v)[r.idx]) +} + +func (r *stashRef) set(v Value) { + (*r.v)[r.idx] = v +} + +func (r *stashRef) init(v Value) { + r.set(v) +} + +func (r *stashRef) refname() unistring.String { + return r.n +} + +type thisRef struct { + v *[]Value + idx int +} + +func (r *thisRef) get() Value { + v := (*r.v)[r.idx] + if v == nil { + panic(referenceError("Must call super constructor in derived class before accessing 'this'")) + } + + return v +} + +func (r *thisRef) set(v Value) { + ptr := &(*r.v)[r.idx] + if *ptr != nil { + panic(referenceError("Super constructor may only be called once")) + } + *ptr = v +} + +func (r *thisRef) init(v Value) { + r.set(v) +} + +func (r *thisRef) refname() unistring.String { + return thisBindingName +} + +type stashRefLex struct { + stashRef +} + +func (r *stashRefLex) get() Value { + v := (*r.v)[r.idx] + if v == nil { + panic(errAccessBeforeInit) + } + return v +} + +func (r *stashRefLex) set(v Value) { + p := &(*r.v)[r.idx] + if *p == nil { + panic(errAccessBeforeInit) + } + *p = v +} + +func (r *stashRefLex) init(v Value) { + (*r.v)[r.idx] = v +} + +type stashRefConst struct { + stashRefLex + strictConst bool +} + +func (r *stashRefConst) set(v Value) { + if r.strictConst { + panic(errAssignToConst) + } +} + +type objRef struct { + base *Object + name unistring.String + this Value + strict bool + binding bool +} + +func (r *objRef) get() Value { + return r.base.self.getStr(r.name, r.this) +} + +func (r *objRef) set(v Value) { + if r.strict && r.binding && !r.base.self.hasOwnPropertyStr(r.name) { + panic(referenceError(fmt.Sprintf("%s is not defined", r.name))) + } + if r.this != nil { + r.base.setStr(r.name, v, r.this, r.strict) + } else { + r.base.self.setOwnStr(r.name, v, r.strict) + } +} + +func (r *objRef) init(v Value) { + if r.this != nil { + r.base.setStr(r.name, v, r.this, r.strict) + } else { + r.base.self.setOwnStr(r.name, v, r.strict) + } +} + +func (r *objRef) refname() unistring.String { + return r.name +} + +type privateRefRes struct { + base *Object + name *resolvedPrivateName +} + +func (p *privateRefRes) get() Value { + return (*getPrivatePropRes)(p.name)._get(p.base, p.base.runtime.vm) +} + +func (p *privateRefRes) set(value Value) { + (*setPrivatePropRes)(p.name)._set(p.base, value, p.base.runtime.vm) +} + +func (p *privateRefRes) init(value Value) { + panic("not supported") +} + +func (p *privateRefRes) refname() unistring.String { + return p.name.string() +} + +type privateRefId struct { + base *Object + id *privateId +} + +func (p *privateRefId) get() Value { + return p.base.runtime.vm.getPrivateProp(p.base, p.id.name, p.id.typ, p.id.idx, p.id.isMethod) +} + +func (p *privateRefId) set(value Value) { + p.base.runtime.vm.setPrivateProp(p.base, p.id.name, p.id.typ, p.id.idx, p.id.isMethod, value) +} + +func (p *privateRefId) init(value Value) { + panic("not supported") +} + +func (p *privateRefId) refname() unistring.String { + return p.id.string() +} + +type unresolvedRef struct { + runtime *Runtime + name unistring.String +} + +func (r *unresolvedRef) get() Value { + r.runtime.throwReferenceError(r.name) + panic("Unreachable") +} + +func (r *unresolvedRef) set(Value) { + r.get() +} + +func (r *unresolvedRef) init(Value) { + r.get() +} + +func (r *unresolvedRef) refname() unistring.String { + return r.name +} + +type vm struct { + r *Runtime + prg *Program + pc int + stack valueStack + sp, sb, args int + + stash *stash + privEnv *privateEnv + callStack []context + iterStack []iterStackItem + refStack []ref + tryStack []tryFrame + newTarget Value + result Value + + maxCallStackSize int + + stashAllocs int + + interrupted uint32 + interruptVal any + interruptLock sync.Mutex + + curAsyncRunner *asyncRunner + + profTracker *profTracker +} + +type instruction interface { + exec(*vm) +} + +func intToValue(i int64) Value { + if idx := 256 + i; idx >= 0 && idx < 256 { + return intCache[idx] + } + if i >= -maxInt && i <= maxInt { + return valueInt(i) + } + return valueFloat(i) +} + +func floatToInt(f float64) (result int64, ok bool) { + if (f != 0 || !math.Signbit(f)) && !math.IsInf(f, 0) && f == math.Trunc(f) && f >= -maxInt && f <= maxInt { + return int64(f), true + } + return 0, false +} + +func floatToValue(f float64) (result Value) { + if i, ok := floatToInt(f); ok { + return intToValue(i) + } + switch { + case f == 0: + return _negativeZero + case math.IsNaN(f): + return _NaN + case math.IsInf(f, 1): + return _positiveInf + case math.IsInf(f, -1): + return _negativeInf + } + return valueFloat(f) +} + +func assertInt64(v Value) (int64, bool) { + num := v.ToNumber() + if i, ok := num.(valueInt); ok { + return int64(i), true + } + if f, ok := num.(valueFloat); ok { + if i, ok := floatToInt(float64(f)); ok { + return i, true + } + } + return 0, false +} + +func (s *valueStack) expand(idx int) { + if idx < len(*s) { + return + } + idx++ + if idx < cap(*s) { + *s = (*s)[:idx] + } else { + var newCap int + if idx < 1024 { + newCap = idx * 2 + } else { + newCap = (idx + 1025) &^ 1023 + } + n := make([]Value, idx, newCap) + copy(n, *s) + *s = n + } +} + +func stashObjHas(obj *Object, name unistring.String) bool { + if obj.self.hasPropertyStr(name) { + if unscopables, ok := obj.self.getSym(SymUnscopables, nil).(*Object); ok { + if b := unscopables.self.getStr(name, nil); b != nil { + return !b.ToBoolean() + } + } + return true + } + return false +} + +func (s *stash) isVariable() bool { + return s.funcType != funcNone +} + +func (s *stash) initByIdx(idx uint32, v Value) { + if s.obj != nil { + panic("Attempt to init by idx into an object scope") + } + s.values[idx] = v +} + +func (s *stash) initByName(name unistring.String, v Value) { + if idx, exists := s.names[name]; exists { + s.values[idx&^maskTyp] = v + } else { + panic(referenceError(fmt.Sprintf("%s is not defined", name))) + } +} + +func (s *stash) getByIdx(idx uint32) Value { + return s.values[idx] +} + +func (s *stash) getByName(name unistring.String) (v Value, exists bool) { + if s.obj != nil { + if stashObjHas(s.obj, name) { + return nilSafe(s.obj.self.getStr(name, nil)), true + } + return nil, false + } + if idx, exists := s.names[name]; exists { + v := s.values[idx&^maskTyp] + if v == nil { + if idx&maskVar == 0 { + panic(errAccessBeforeInit) + } else { + v = _undefined + } + } + return v, true + } + return nil, false +} + +func (s *stash) getRefByName(name unistring.String, strict bool) ref { + if obj := s.obj; obj != nil { + if stashObjHas(obj, name) { + return &objRef{ + base: obj, + name: name, + strict: strict, + binding: true, + } + } + } else { + if idx, exists := s.names[name]; exists { + if idx&maskVar == 0 { + if idx&maskConst == 0 { + return &stashRefLex{ + stashRef: stashRef{ + n: name, + v: &s.values, + idx: int(idx &^ maskTyp), + }, + } + } else { + return &stashRefConst{ + stashRefLex: stashRefLex{ + stashRef: stashRef{ + n: name, + v: &s.values, + idx: int(idx &^ maskTyp), + }, + }, + strictConst: strict || (idx&maskStrict != 0), + } + } + } else { + return &stashRef{ + n: name, + v: &s.values, + idx: int(idx &^ maskTyp), + } + } + } + } + return nil +} + +func (s *stash) createBinding(name unistring.String, deletable bool) { + if s.names == nil { + s.names = make(map[unistring.String]uint32) + } + if _, exists := s.names[name]; !exists { + idx := uint32(len(s.names)) | maskVar + if deletable { + idx |= maskDeletable + } + s.names[name] = idx + s.values = append(s.values, _undefined) + } +} + +func (s *stash) createLexBinding(name unistring.String, isConst bool) { + if s.names == nil { + s.names = make(map[unistring.String]uint32) + } + if _, exists := s.names[name]; !exists { + idx := uint32(len(s.names)) + if isConst { + idx |= maskConst | maskStrict + } + s.names[name] = idx + s.values = append(s.values, nil) + } +} + +func (s *stash) deleteBinding(name unistring.String) { + delete(s.names, name) +} + +func (vm *vm) newStash() { + vm.stash = &stash{ + outer: vm.stash, + } + vm.stashAllocs++ +} + +func (vm *vm) init() { + vm.sb = -1 + vm.stash = &vm.r.global.stash + vm.maxCallStackSize = math.MaxInt32 +} + +func (vm *vm) halted() bool { + pc := vm.pc + return pc < 0 || pc >= len(vm.prg.code) +} + +func (vm *vm) run() { + if vm.profTracker != nil && !vm.runWithProfiler() { + return + } + count := 0 + interrupted := false + for { + if count == 0 { + if atomic.LoadInt32(&globalProfiler.enabled) == 1 && !vm.runWithProfiler() { + return + } + count = 100 + } else { + count-- + } + if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { + break + } + pc := vm.pc + if pc < 0 || pc >= len(vm.prg.code) { + break + } + vm.prg.code[pc].exec(vm) + } + + if interrupted { + vm.interruptLock.Lock() + v := &InterruptedError{ + iface: vm.interruptVal, + } + v.stack = vm.captureStack(nil, 0) + vm.interruptLock.Unlock() + panic(v) + } + vm = nil +} + +func (vm *vm) runWithProfiler() bool { + pt := vm.profTracker + if pt == nil { + pt = globalProfiler.p.registerVm() + vm.profTracker = pt + defer func() { + atomic.StoreInt32(&vm.profTracker.finished, 1) + vm.profTracker = nil + }() + } + interrupted := false + for { + if interrupted = atomic.LoadUint32(&vm.interrupted) != 0; interrupted { + return true + } + pc := vm.pc + if pc < 0 || pc >= len(vm.prg.code) { + break + } + vm.prg.code[pc].exec(vm) + req := atomic.LoadInt32(&pt.req) + if req == profReqStop { + return true + } + if req == profReqDoSample { + pt.stop = time.Now() + + pt.numFrames = len(vm.r.CaptureCallStack(len(pt.frames), pt.frames[:0])) + pt.frames[0].pc = pc + atomic.StoreInt32(&pt.req, profReqSampleReady) + } + } + + return false +} + +func (vm *vm) Interrupt(v any) { + vm.interruptLock.Lock() + vm.interruptVal = v + atomic.StoreUint32(&vm.interrupted, 1) + vm.interruptLock.Unlock() +} + +func (vm *vm) ClearInterrupt() { + atomic.StoreUint32(&vm.interrupted, 0) +} + +func getFuncName(stack []Value, sb int) unistring.String { + if sb > 0 { + if f, ok := stack[sb-1].(*Object); ok { + if _, isProxy := f.self.(*proxyObject); isProxy { + return "proxy" + } + return nilSafe(f.self.getStr("name", nil)).string() + } + } + return "" +} + +func (vm *vm) captureStack(stack []StackFrame, ctxOffset int) []StackFrame { + // Unroll the context stack + if vm.prg != nil || vm.sb > 0 { + var funcName unistring.String + if vm.prg != nil { + funcName = vm.prg.funcName + } else { + funcName = getFuncName(vm.stack, vm.sb) + } + stack = append(stack, StackFrame{prg: vm.prg, pc: vm.pc, funcName: funcName}) + } + for i := len(vm.callStack) - 1; i > ctxOffset-1; i-- { + frame := &vm.callStack[i] + if frame.prg != nil || frame.sb > 0 { + var funcName unistring.String + if prg := frame.prg; prg != nil { + funcName = prg.funcName + } else { + funcName = getFuncName(vm.stack, frame.sb) + } + stack = append(stack, StackFrame{prg: vm.callStack[i].prg, pc: frame.pc, funcName: funcName}) + } + } + if ctxOffset == 0 && vm.curAsyncRunner != nil { + stack = vm.captureAsyncStack(stack, vm.curAsyncRunner) + } + return stack +} + +func (vm *vm) captureAsyncStack(stack []StackFrame, runner *asyncRunner) []StackFrame { + if promise, _ := runner.promiseCap.promise.self.(*Promise); promise != nil { + if len(promise.fulfillReactions) == 1 { + if r := promise.fulfillReactions[0].asyncRunner; r != nil { + ctx := &r.gen.ctx + if ctx.prg != nil || ctx.sb > 0 { + var funcName unistring.String + if prg := ctx.prg; prg != nil { + funcName = prg.funcName + } else { + funcName = getFuncName(ctx.stack, 1) + } + stack = append(stack, StackFrame{prg: ctx.prg, pc: ctx.pc, funcName: funcName}) + } + stack = vm.captureAsyncStack(stack, r) + } + } + } + + return stack +} + +func (vm *vm) pushTryFrame(catchPos, finallyPos int32) { + vm.tryStack = append(vm.tryStack, tryFrame{ + callStackLen: uint32(len(vm.callStack)), + iterLen: uint32(len(vm.iterStack)), + refLen: uint32(len(vm.refStack)), + sp: int32(vm.sp), + stash: vm.stash, + privEnv: vm.privEnv, + catchPos: catchPos, + finallyPos: finallyPos, + finallyRet: -1, + }) +} + +func (vm *vm) popTryFrame() { + vm.tryStack = vm.tryStack[:len(vm.tryStack)-1] +} + +func (vm *vm) restoreStacks(iterLen, refLen uint32) (ex *Exception) { + // Restore other stacks + iterTail := vm.iterStack[iterLen:] + for i := len(iterTail) - 1; i >= 0; i-- { + if iter := iterTail[i].iter; iter != nil { + ex1 := vm.try(func() { + iter.returnIter() + }) + if ex1 != nil && ex == nil { + ex = ex1 + } + } + iterTail[i] = iterStackItem{} + } + vm.iterStack = vm.iterStack[:iterLen] + refTail := vm.refStack[refLen:] + for i := range refTail { + refTail[i] = nil + } + vm.refStack = vm.refStack[:refLen] + return +} + +func (vm *vm) handleThrow(arg any) *Exception { + ex := vm.exceptionFromValue(arg) + for len(vm.tryStack) > 0 { + tf := &vm.tryStack[len(vm.tryStack)-1] + if tf.catchPos == -1 && tf.finallyPos == -1 || ex == nil && tf.catchPos != tryPanicMarker { + tf.exception = nil + vm.popTryFrame() + continue + } + if int(tf.callStackLen) < len(vm.callStack) { + ctx := &vm.callStack[tf.callStackLen] + vm.prg, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = + ctx.prg, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args + vm.callStack = vm.callStack[:tf.callStackLen] + } + vm.sp = int(tf.sp) + vm.stash = tf.stash + vm.privEnv = tf.privEnv + _ = vm.restoreStacks(tf.iterLen, tf.refLen) + + if tf.catchPos == tryPanicMarker { + break + } + + if tf.catchPos >= 0 { + // exception is caught + vm.push(ex.val) + vm.pc = int(tf.catchPos) + tf.catchPos = -1 + return nil + } + if tf.finallyPos >= 0 { + // no 'catch' block, but there is a 'finally' block + tf.exception = ex + vm.pc = int(tf.finallyPos) + tf.finallyPos = -1 + tf.finallyRet = -1 + return nil + } + } + if ex == nil { + panic(arg) + } + return ex +} + +// Calls to this method must be made from the run() loop and must be the last statement before 'return'. +// In all other cases exceptions must be thrown using panic(). +func (vm *vm) throw(v any) { + if ex := vm.handleThrow(v); ex != nil { + panic(ex) + } +} + +func (vm *vm) try(f func()) (ex *Exception) { + vm.pushTryFrame(tryPanicMarker, -1) + defer vm.popTryFrame() + + defer func() { + if x := recover(); x != nil { + ex = vm.handleThrow(x) + } + }() + + f() + return +} + +func (vm *vm) runTry() (ex *Exception) { + vm.pushTryFrame(tryPanicMarker, -1) + defer vm.popTryFrame() + + for { + ex = vm.runTryInner() + if ex != nil || vm.halted() { + return + } + } +} + +func (vm *vm) runTryInner() (ex *Exception) { + defer func() { + if x := recover(); x != nil { + ex = vm.handleThrow(x) + } + }() + + vm.run() + return +} + +func (vm *vm) push(v Value) { + vm.stack.expand(vm.sp) + vm.stack[vm.sp] = v + vm.sp++ +} + +func (vm *vm) pop() Value { + vm.sp-- + return vm.stack[vm.sp] +} + +func (vm *vm) peek() Value { + return vm.stack[vm.sp-1] +} + +func (vm *vm) saveCtx(ctx *context) { + ctx.prg, ctx.stash, ctx.privEnv, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args = + vm.prg, vm.stash, vm.privEnv, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args +} + +func (vm *vm) pushCtx() { + if len(vm.callStack) > vm.maxCallStackSize { + ex := &StackOverflowError{} + ex.stack = vm.captureStack(nil, 0) + panic(ex) + } + vm.callStack = append(vm.callStack, context{}) + ctx := &vm.callStack[len(vm.callStack)-1] + vm.saveCtx(ctx) +} + +func (vm *vm) restoreCtx(ctx *context) { + vm.prg, vm.stash, vm.privEnv, vm.newTarget, vm.result, vm.pc, vm.sb, vm.args = + ctx.prg, ctx.stash, ctx.privEnv, ctx.newTarget, ctx.result, ctx.pc, ctx.sb, ctx.args +} + +func (vm *vm) popCtx() { + l := len(vm.callStack) - 1 + ctx := &vm.callStack[l] + vm.restoreCtx(ctx) + + if ctx.prg != nil { + *ctx = context{} + } + + vm.callStack = vm.callStack[:l] +} + +func (vm *vm) toCallee(v Value) *Object { + if obj, ok := v.(*Object); ok { + return obj + } + switch unresolved := v.(type) { + case valueUnresolved: + unresolved.throw() + panic("Unreachable") + case memberUnresolved: + panic(vm.r.NewTypeError("Object has no member '%s'", unresolved.ref)) + } + panic(vm.r.NewTypeError("Value is not an object: %s", v.toString())) +} + +type loadVal uint32 + +func (l loadVal) exec(vm *vm) { + vm.push(vm.prg.values[l]) + vm.pc++ +} + +type _loadUndef struct{} + +var loadUndef _loadUndef + +func (_loadUndef) exec(vm *vm) { + vm.push(_undefined) + vm.pc++ +} + +type _loadNil struct{} + +var loadNil _loadNil + +func (_loadNil) exec(vm *vm) { + vm.push(nil) + vm.pc++ +} + +type _saveResult struct{} + +var saveResult _saveResult + +func (_saveResult) exec(vm *vm) { + vm.sp-- + vm.result = vm.stack[vm.sp] + vm.pc++ +} + +type _loadResult struct{} + +var loadResult _loadResult + +func (_loadResult) exec(vm *vm) { + vm.push(vm.result) + vm.pc++ +} + +type _clearResult struct{} + +var clearResult _clearResult + +func (_clearResult) exec(vm *vm) { + vm.result = _undefined + vm.pc++ +} + +type _loadGlobalObject struct{} + +var loadGlobalObject _loadGlobalObject + +func (_loadGlobalObject) exec(vm *vm) { + vm.push(vm.r.globalObject) + vm.pc++ +} + +type loadStack int + +func (l loadStack) exec(vm *vm) { + // l > 0 -- var + // l == 0 -- this + + if l > 0 { + vm.push(nilSafe(vm.stack[vm.sb+vm.args+int(l)])) + } else { + vm.push(vm.stack[vm.sb]) + } + vm.pc++ +} + +type loadStack1 int + +func (l loadStack1) exec(vm *vm) { + // args are in stash + // l > 0 -- var + // l == 0 -- this + + if l > 0 { + vm.push(nilSafe(vm.stack[vm.sb+int(l)])) + } else { + vm.push(vm.stack[vm.sb]) + } + vm.pc++ +} + +type loadStackLex int + +func (l loadStackLex) exec(vm *vm) { + // l < 0 -- arg<-l-1> + // l > 0 -- var + // l == 0 -- this + var p *Value + if l <= 0 { + arg := int(-l) + if arg > vm.args { + vm.push(_undefined) + vm.pc++ + return + } else { + p = &vm.stack[vm.sb+arg] + } + } else { + p = &vm.stack[vm.sb+vm.args+int(l)] + } + if *p == nil { + vm.throw(errAccessBeforeInit) + return + } + vm.push(*p) + vm.pc++ +} + +type loadStack1Lex int + +func (l loadStack1Lex) exec(vm *vm) { + p := &vm.stack[vm.sb+int(l)] + if *p == nil { + vm.throw(errAccessBeforeInit) + return + } + vm.push(*p) + vm.pc++ +} + +type _loadCallee struct{} + +var loadCallee _loadCallee + +func (_loadCallee) exec(vm *vm) { + vm.push(vm.stack[vm.sb-1]) + vm.pc++ +} + +func (vm *vm) storeStack(s int) { + // l > 0 -- var + + if s > 0 { + vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1] + } else { + panic("Illegal stack var index") + } + vm.pc++ +} + +func (vm *vm) storeStack1(s int) { + // args are in stash + // l > 0 -- var + + if s > 0 { + vm.stack[vm.sb+s] = vm.stack[vm.sp-1] + } else { + panic("Illegal stack var index") + } + vm.pc++ +} + +func (vm *vm) storeStackLex(s int) { + // l < 0 -- arg<-l-1> + // l > 0 -- var + var p *Value + if s < 0 { + p = &vm.stack[vm.sb-s] + } else { + p = &vm.stack[vm.sb+vm.args+s] + } + + if *p != nil { + *p = vm.stack[vm.sp-1] + } else { + panic(errAccessBeforeInit) + } + vm.pc++ +} + +func (vm *vm) storeStack1Lex(s int) { + // args are in stash + // s > 0 -- var + if s <= 0 { + panic("Illegal stack var index") + } + p := &vm.stack[vm.sb+s] + if *p != nil { + *p = vm.stack[vm.sp-1] + } else { + panic(errAccessBeforeInit) + } + vm.pc++ +} + +func (vm *vm) initStack(s int) { + if s <= 0 { + vm.stack[vm.sb-s] = vm.stack[vm.sp-1] + } else { + vm.stack[vm.sb+vm.args+s] = vm.stack[vm.sp-1] + } + vm.pc++ +} + +func (vm *vm) initStack1(s int) { + if s <= 0 { + panic("Illegal stack var index") + } + vm.stack[vm.sb+s] = vm.stack[vm.sp-1] + vm.pc++ +} + +type storeStack int + +func (s storeStack) exec(vm *vm) { + vm.storeStack(int(s)) +} + +type storeStack1 int + +func (s storeStack1) exec(vm *vm) { + vm.storeStack1(int(s)) +} + +type storeStackLex int + +func (s storeStackLex) exec(vm *vm) { + vm.storeStackLex(int(s)) +} + +type storeStack1Lex int + +func (s storeStack1Lex) exec(vm *vm) { + vm.storeStack1Lex(int(s)) +} + +type initStack int + +func (s initStack) exec(vm *vm) { + vm.initStack(int(s)) +} + +type initStackP int + +func (s initStackP) exec(vm *vm) { + vm.initStack(int(s)) + vm.sp-- +} + +type initStack1 int + +func (s initStack1) exec(vm *vm) { + vm.initStack1(int(s)) +} + +type initStack1P int + +func (s initStack1P) exec(vm *vm) { + vm.initStack1(int(s)) + vm.sp-- +} + +type storeStackP int + +func (s storeStackP) exec(vm *vm) { + vm.storeStack(int(s)) + vm.sp-- +} + +type storeStack1P int + +func (s storeStack1P) exec(vm *vm) { + vm.storeStack1(int(s)) + vm.sp-- +} + +type storeStackLexP int + +func (s storeStackLexP) exec(vm *vm) { + vm.storeStackLex(int(s)) + vm.sp-- +} + +type storeStack1LexP int + +func (s storeStack1LexP) exec(vm *vm) { + vm.storeStack1Lex(int(s)) + vm.sp-- +} + +type _toNumber struct{} + +var toNumber _toNumber + +func (_toNumber) exec(vm *vm) { + vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber() + vm.pc++ +} + +type _add struct{} + +var add _add + +func (_add) exec(vm *vm) { + right := vm.stack[vm.sp-1] + left := vm.stack[vm.sp-2] + + if o, ok := left.(*Object); ok { + left = o.toPrimitive() + } + + if o, ok := right.(*Object); ok { + right = o.toPrimitive() + } + + var ret Value + + leftString, isLeftString := left.(String) + rightString, isRightString := right.(String) + + if isLeftString || isRightString { + if !isLeftString { + leftString = left.toString() + } + if !isRightString { + rightString = right.toString() + } + ret = leftString.Concat(rightString) + } else { + if leftInt, ok := left.(valueInt); ok { + if rightInt, ok := right.(valueInt); ok { + ret = intToValue(int64(leftInt) + int64(rightInt)) + } else { + ret = floatToValue(float64(leftInt) + right.ToFloat()) + } + } else { + ret = floatToValue(left.ToFloat() + right.ToFloat()) + } + } + + vm.stack[vm.sp-2] = ret + vm.sp-- + vm.pc++ +} + +type _sub struct{} + +var sub _sub + +func (_sub) exec(vm *vm) { + right := vm.stack[vm.sp-1] + left := vm.stack[vm.sp-2] + + var result Value + + if left, ok := left.(valueInt); ok { + if right, ok := right.(valueInt); ok { + result = intToValue(int64(left) - int64(right)) + goto end + } + } + + result = floatToValue(left.ToFloat() - right.ToFloat()) +end: + vm.sp-- + vm.stack[vm.sp-1] = result + vm.pc++ +} + +type _mul struct{} + +var mul _mul + +func (_mul) exec(vm *vm) { + left := vm.stack[vm.sp-2] + right := vm.stack[vm.sp-1] + + var result Value + + if left, ok := assertInt64(left); ok { + if right, ok := assertInt64(right); ok { + if left == 0 && right == -1 || left == -1 && right == 0 { + result = _negativeZero + goto end + } + res := left * right + // check for overflow + if left == 0 || right == 0 || res/left == right { + result = intToValue(res) + goto end + } + + } + } + + result = floatToValue(left.ToFloat() * right.ToFloat()) + +end: + vm.sp-- + vm.stack[vm.sp-1] = result + vm.pc++ +} + +type _exp struct{} + +var exp _exp + +func (_exp) exec(vm *vm) { + vm.sp-- + vm.stack[vm.sp-1] = pow(vm.stack[vm.sp-1], vm.stack[vm.sp]) + vm.pc++ +} + +type _div struct{} + +var div _div + +func (_div) exec(vm *vm) { + left := vm.stack[vm.sp-2].ToFloat() + right := vm.stack[vm.sp-1].ToFloat() + + var result Value + + if math.IsNaN(left) || math.IsNaN(right) { + result = _NaN + goto end + } + if math.IsInf(left, 0) && math.IsInf(right, 0) { + result = _NaN + goto end + } + if left == 0 && right == 0 { + result = _NaN + goto end + } + + if math.IsInf(left, 0) { + if math.Signbit(left) == math.Signbit(right) { + result = _positiveInf + goto end + } else { + result = _negativeInf + goto end + } + } + if math.IsInf(right, 0) { + if math.Signbit(left) == math.Signbit(right) { + result = _positiveZero + goto end + } else { + result = _negativeZero + goto end + } + } + if right == 0 { + if math.Signbit(left) == math.Signbit(right) { + result = _positiveInf + goto end + } else { + result = _negativeInf + goto end + } + } + + result = floatToValue(left / right) + +end: + vm.sp-- + vm.stack[vm.sp-1] = result + vm.pc++ +} + +type _mod struct{} + +var mod _mod + +func (_mod) exec(vm *vm) { + left := vm.stack[vm.sp-2] + right := vm.stack[vm.sp-1] + + var result Value + + if leftInt, ok := assertInt64(left); ok { + if rightInt, ok := assertInt64(right); ok { + if rightInt == 0 { + result = _NaN + goto end + } + r := leftInt % rightInt + if r == 0 && leftInt < 0 { + result = _negativeZero + } else { + result = intToValue(leftInt % rightInt) + } + goto end + } + } + + result = floatToValue(math.Mod(left.ToFloat(), right.ToFloat())) +end: + vm.sp-- + vm.stack[vm.sp-1] = result + vm.pc++ +} + +type _neg struct{} + +var neg _neg + +func (_neg) exec(vm *vm) { + operand := vm.stack[vm.sp-1] + + var result Value + + if i, ok := assertInt64(operand); ok { + if i == 0 { + result = _negativeZero + } else { + result = valueInt(-i) + } + } else { + f := operand.ToFloat() + if !math.IsNaN(f) { + f = -f + } + result = valueFloat(f) + } + + vm.stack[vm.sp-1] = result + vm.pc++ +} + +type _plus struct{} + +var plus _plus + +func (_plus) exec(vm *vm) { + vm.stack[vm.sp-1] = vm.stack[vm.sp-1].ToNumber() + vm.pc++ +} + +type _inc struct{} + +var inc _inc + +func (_inc) exec(vm *vm) { + v := vm.stack[vm.sp-1] + + if i, ok := assertInt64(v); ok { + v = intToValue(i + 1) + goto end + } + + v = valueFloat(v.ToFloat() + 1) + +end: + vm.stack[vm.sp-1] = v + vm.pc++ +} + +type _dec struct{} + +var dec _dec + +func (_dec) exec(vm *vm) { + v := vm.stack[vm.sp-1] + + if i, ok := assertInt64(v); ok { + v = intToValue(i - 1) + goto end + } + + v = valueFloat(v.ToFloat() - 1) + +end: + vm.stack[vm.sp-1] = v + vm.pc++ +} + +type _and struct{} + +var and _and + +func (_and) exec(vm *vm) { + left := toInt32(vm.stack[vm.sp-2]) + right := toInt32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-2] = intToValue(int64(left & right)) + vm.sp-- + vm.pc++ +} + +type _or struct{} + +var or _or + +func (_or) exec(vm *vm) { + left := toInt32(vm.stack[vm.sp-2]) + right := toInt32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-2] = intToValue(int64(left | right)) + vm.sp-- + vm.pc++ +} + +type _xor struct{} + +var xor _xor + +func (_xor) exec(vm *vm) { + left := toInt32(vm.stack[vm.sp-2]) + right := toInt32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-2] = intToValue(int64(left ^ right)) + vm.sp-- + vm.pc++ +} + +type _bnot struct{} + +var bnot _bnot + +func (_bnot) exec(vm *vm) { + op := toInt32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-1] = intToValue(int64(^op)) + vm.pc++ +} + +type _sal struct{} + +var sal _sal + +func (_sal) exec(vm *vm) { + left := toInt32(vm.stack[vm.sp-2]) + right := toUint32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-2] = intToValue(int64(left << (right & 0x1F))) + vm.sp-- + vm.pc++ +} + +type _sar struct{} + +var sar _sar + +func (_sar) exec(vm *vm) { + left := toInt32(vm.stack[vm.sp-2]) + right := toUint32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F))) + vm.sp-- + vm.pc++ +} + +type _shr struct{} + +var shr _shr + +func (_shr) exec(vm *vm) { + left := toUint32(vm.stack[vm.sp-2]) + right := toUint32(vm.stack[vm.sp-1]) + vm.stack[vm.sp-2] = intToValue(int64(left >> (right & 0x1F))) + vm.sp-- + vm.pc++ +} + +type jump int32 + +func (j jump) exec(vm *vm) { + vm.pc += int(j) +} + +type _toPropertyKey struct{} + +func (_toPropertyKey) exec(vm *vm) { + p := vm.sp - 1 + vm.stack[p] = toPropertyKey(vm.stack[p]) + vm.pc++ +} + +type _toString struct{} + +func (_toString) exec(vm *vm) { + p := vm.sp - 1 + vm.stack[p] = vm.stack[p].toString() + vm.pc++ +} + +type _getElemRef struct{} + +var getElemRef _getElemRef + +func (_getElemRef) exec(vm *vm) { + obj := vm.stack[vm.sp-2].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + vm.refStack = append(vm.refStack, &objRef{ + base: obj, + name: propName.string(), + }) + vm.sp -= 2 + vm.pc++ +} + +type _getElemRefRecv struct{} + +var getElemRefRecv _getElemRefRecv + +func (_getElemRefRecv) exec(vm *vm) { + obj := vm.stack[vm.sp-1].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-2]) + vm.refStack = append(vm.refStack, &objRef{ + base: obj, + name: propName.string(), + this: vm.stack[vm.sp-3], + }) + vm.sp -= 3 + vm.pc++ +} + +type _getElemRefStrict struct{} + +var getElemRefStrict _getElemRefStrict + +func (_getElemRefStrict) exec(vm *vm) { + obj := vm.stack[vm.sp-2].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + vm.refStack = append(vm.refStack, &objRef{ + base: obj, + name: propName.string(), + strict: true, + }) + vm.sp -= 2 + vm.pc++ +} + +type _getElemRefRecvStrict struct{} + +var getElemRefRecvStrict _getElemRefRecvStrict + +func (_getElemRefRecvStrict) exec(vm *vm) { + obj := vm.stack[vm.sp-1].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-2]) + vm.refStack = append(vm.refStack, &objRef{ + base: obj, + name: propName.string(), + this: vm.stack[vm.sp-3], + strict: true, + }) + vm.sp -= 3 + vm.pc++ +} + +type _setElem struct{} + +var setElem _setElem + +func (_setElem) exec(vm *vm) { + obj := vm.stack[vm.sp-3].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-2]) + val := vm.stack[vm.sp-1] + + obj.setOwn(propName, val, false) + + vm.sp -= 2 + vm.stack[vm.sp-1] = val + vm.pc++ +} + +type _setElem1 struct{} + +var setElem1 _setElem1 + +func (_setElem1) exec(vm *vm) { + obj := vm.stack[vm.sp-3].ToObject(vm.r) + propName := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + + obj.setOwn(propName, val, true) + + vm.sp -= 2 + vm.pc++ +} + +type _setElem1Named struct{} + +var setElem1Named _setElem1Named + +func (_setElem1Named) exec(vm *vm) { + receiver := vm.stack[vm.sp-3] + base := receiver.ToObject(vm.r) + propName := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + vm.r.toObject(val).self.defineOwnPropertyStr("name", PropertyDescriptor{ + Value: funcName("", propName), + Configurable: FLAG_TRUE, + }, true) + base.set(propName, val, receiver, true) + + vm.sp -= 2 + vm.pc++ +} + +type defineMethod struct { + enumerable bool +} + +func (d *defineMethod) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-3]) + propName := vm.stack[vm.sp-2] + method := vm.r.toObject(vm.stack[vm.sp-1]) + method.self.defineOwnPropertyStr("name", PropertyDescriptor{ + Value: funcName("", propName), + Configurable: FLAG_TRUE, + }, true) + obj.defineOwnProperty(propName, PropertyDescriptor{ + Value: method, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: ToFlag(d.enumerable), + }, true) + + vm.sp -= 2 + vm.pc++ +} + +type _setElemP struct{} + +var setElemP _setElemP + +func (_setElemP) exec(vm *vm) { + obj := vm.stack[vm.sp-3].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-2]) + val := vm.stack[vm.sp-1] + + obj.setOwn(propName, val, false) + + vm.sp -= 3 + vm.pc++ +} + +type _setElemStrict struct{} + +var setElemStrict _setElemStrict + +func (_setElemStrict) exec(vm *vm) { + propName := toPropertyKey(vm.stack[vm.sp-2]) + receiver := vm.stack[vm.sp-3] + val := vm.stack[vm.sp-1] + if receiverObj, ok := receiver.(*Object); ok { + receiverObj.setOwn(propName, val, true) + } else { + base := receiver.ToObject(vm.r) + base.set(propName, val, receiver, true) + } + + vm.sp -= 2 + vm.stack[vm.sp-1] = val + vm.pc++ +} + +type _setElemRecv struct{} + +var setElemRecv _setElemRecv + +func (_setElemRecv) exec(vm *vm) { + receiver := vm.stack[vm.sp-4] + propName := toPropertyKey(vm.stack[vm.sp-3]) + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + if obj, ok := o.(*Object); ok { + obj.set(propName, val, receiver, false) + } else { + base := o.ToObject(vm.r) + base.set(propName, val, receiver, false) + } + + vm.sp -= 3 + vm.stack[vm.sp-1] = val + vm.pc++ +} + +type _setElemRecvStrict struct{} + +var setElemRecvStrict _setElemRecvStrict + +func (_setElemRecvStrict) exec(vm *vm) { + receiver := vm.stack[vm.sp-4] + propName := toPropertyKey(vm.stack[vm.sp-3]) + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + if obj, ok := o.(*Object); ok { + obj.set(propName, val, receiver, true) + } else { + base := o.ToObject(vm.r) + base.set(propName, val, receiver, true) + } + + vm.sp -= 3 + vm.stack[vm.sp-1] = val + vm.pc++ +} + +type _setElemStrictP struct{} + +var setElemStrictP _setElemStrictP + +func (_setElemStrictP) exec(vm *vm) { + propName := toPropertyKey(vm.stack[vm.sp-2]) + receiver := vm.stack[vm.sp-3] + val := vm.stack[vm.sp-1] + if receiverObj, ok := receiver.(*Object); ok { + receiverObj.setOwn(propName, val, true) + } else { + base := receiver.ToObject(vm.r) + base.set(propName, val, receiver, true) + } + + vm.sp -= 3 + vm.pc++ +} + +type _setElemRecvP struct{} + +var setElemRecvP _setElemRecvP + +func (_setElemRecvP) exec(vm *vm) { + receiver := vm.stack[vm.sp-4] + propName := toPropertyKey(vm.stack[vm.sp-3]) + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + if obj, ok := o.(*Object); ok { + obj.set(propName, val, receiver, false) + } else { + base := o.ToObject(vm.r) + base.set(propName, val, receiver, false) + } + + vm.sp -= 4 + vm.pc++ +} + +type _setElemRecvStrictP struct{} + +var setElemRecvStrictP _setElemRecvStrictP + +func (_setElemRecvStrictP) exec(vm *vm) { + receiver := vm.stack[vm.sp-4] + propName := toPropertyKey(vm.stack[vm.sp-3]) + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + if obj, ok := o.(*Object); ok { + obj.set(propName, val, receiver, true) + } else { + base := o.ToObject(vm.r) + base.set(propName, val, receiver, true) + } + + vm.sp -= 4 + vm.pc++ +} + +type _deleteElem struct{} + +var deleteElem _deleteElem + +func (_deleteElem) exec(vm *vm) { + obj := vm.stack[vm.sp-2].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + if obj.delete(propName, false) { + vm.stack[vm.sp-2] = valueTrue + } else { + vm.stack[vm.sp-2] = valueFalse + } + vm.sp-- + vm.pc++ +} + +type _deleteElemStrict struct{} + +var deleteElemStrict _deleteElemStrict + +func (_deleteElemStrict) exec(vm *vm) { + obj := vm.stack[vm.sp-2].ToObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + obj.delete(propName, true) + vm.stack[vm.sp-2] = valueTrue + vm.sp-- + vm.pc++ +} + +type deleteProp unistring.String + +func (d deleteProp) exec(vm *vm) { + obj := vm.stack[vm.sp-1].ToObject(vm.r) + if obj.self.deleteStr(unistring.String(d), false) { + vm.stack[vm.sp-1] = valueTrue + } else { + vm.stack[vm.sp-1] = valueFalse + } + vm.pc++ +} + +type deletePropStrict unistring.String + +func (d deletePropStrict) exec(vm *vm) { + obj := vm.stack[vm.sp-1].ToObject(vm.r) + obj.self.deleteStr(unistring.String(d), true) + vm.stack[vm.sp-1] = valueTrue + vm.pc++ +} + +type getPropRef unistring.String + +func (p getPropRef) exec(vm *vm) { + vm.refStack = append(vm.refStack, &objRef{ + base: vm.stack[vm.sp-1].ToObject(vm.r), + name: unistring.String(p), + }) + vm.sp-- + vm.pc++ +} + +type getPropRefRecv unistring.String + +func (p getPropRefRecv) exec(vm *vm) { + vm.refStack = append(vm.refStack, &objRef{ + this: vm.stack[vm.sp-2], + base: vm.stack[vm.sp-1].ToObject(vm.r), + name: unistring.String(p), + }) + vm.sp -= 2 + vm.pc++ +} + +type getPropRefStrict unistring.String + +func (p getPropRefStrict) exec(vm *vm) { + vm.refStack = append(vm.refStack, &objRef{ + base: vm.stack[vm.sp-1].ToObject(vm.r), + name: unistring.String(p), + strict: true, + }) + vm.sp-- + vm.pc++ +} + +type getPropRefRecvStrict unistring.String + +func (p getPropRefRecvStrict) exec(vm *vm) { + vm.refStack = append(vm.refStack, &objRef{ + this: vm.stack[vm.sp-2], + base: vm.stack[vm.sp-1].ToObject(vm.r), + name: unistring.String(p), + strict: true, + }) + vm.sp -= 2 + vm.pc++ +} + +type setProp unistring.String + +func (p setProp) exec(vm *vm) { + val := vm.stack[vm.sp-1] + vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false) + vm.stack[vm.sp-2] = val + vm.sp-- + vm.pc++ +} + +type setPropP unistring.String + +func (p setPropP) exec(vm *vm) { + val := vm.stack[vm.sp-1] + vm.stack[vm.sp-2].ToObject(vm.r).self.setOwnStr(unistring.String(p), val, false) + vm.sp -= 2 + vm.pc++ +} + +type setPropStrict unistring.String + +func (p setPropStrict) exec(vm *vm) { + receiver := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + propName := unistring.String(p) + if receiverObj, ok := receiver.(*Object); ok { + receiverObj.self.setOwnStr(propName, val, true) + } else { + base := receiver.ToObject(vm.r) + base.setStr(propName, val, receiver, true) + } + + vm.stack[vm.sp-2] = val + vm.sp-- + vm.pc++ +} + +type setPropRecv unistring.String + +func (p setPropRecv) exec(vm *vm) { + receiver := vm.stack[vm.sp-3] + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + propName := unistring.String(p) + if obj, ok := o.(*Object); ok { + obj.setStr(propName, val, receiver, false) + } else { + base := o.ToObject(vm.r) + base.setStr(propName, val, receiver, false) + } + + vm.stack[vm.sp-3] = val + vm.sp -= 2 + vm.pc++ +} + +type setPropRecvStrict unistring.String + +func (p setPropRecvStrict) exec(vm *vm) { + receiver := vm.stack[vm.sp-3] + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + propName := unistring.String(p) + if obj, ok := o.(*Object); ok { + obj.setStr(propName, val, receiver, true) + } else { + base := o.ToObject(vm.r) + base.setStr(propName, val, receiver, true) + } + + vm.stack[vm.sp-3] = val + vm.sp -= 2 + vm.pc++ +} + +type setPropRecvP unistring.String + +func (p setPropRecvP) exec(vm *vm) { + receiver := vm.stack[vm.sp-3] + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + propName := unistring.String(p) + if obj, ok := o.(*Object); ok { + obj.setStr(propName, val, receiver, false) + } else { + base := o.ToObject(vm.r) + base.setStr(propName, val, receiver, false) + } + + vm.sp -= 3 + vm.pc++ +} + +type setPropRecvStrictP unistring.String + +func (p setPropRecvStrictP) exec(vm *vm) { + receiver := vm.stack[vm.sp-3] + o := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + propName := unistring.String(p) + if obj, ok := o.(*Object); ok { + obj.setStr(propName, val, receiver, true) + } else { + base := o.ToObject(vm.r) + base.setStr(propName, val, receiver, true) + } + + vm.sp -= 3 + vm.pc++ +} + +type setPropStrictP unistring.String + +func (p setPropStrictP) exec(vm *vm) { + receiver := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + propName := unistring.String(p) + if receiverObj, ok := receiver.(*Object); ok { + receiverObj.self.setOwnStr(propName, val, true) + } else { + base := receiver.ToObject(vm.r) + base.setStr(propName, val, receiver, true) + } + + vm.sp -= 2 + vm.pc++ +} + +type putProp unistring.String + +func (p putProp) exec(vm *vm) { + vm.r.toObject(vm.stack[vm.sp-2]).self._putProp(unistring.String(p), vm.stack[vm.sp-1], true, true, true) + + vm.sp-- + vm.pc++ +} + +// used in class declarations instead of putProp because DefineProperty must be observable by Proxy +type definePropKeyed unistring.String + +func (p definePropKeyed) exec(vm *vm) { + vm.r.toObject(vm.stack[vm.sp-2]).self.defineOwnPropertyStr(unistring.String(p), PropertyDescriptor{ + Value: vm.stack[vm.sp-1], + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, true) + + vm.sp-- + vm.pc++ +} + +type defineProp struct{} + +func (defineProp) exec(vm *vm) { + vm.r.toObject(vm.stack[vm.sp-3]).defineOwnProperty(vm.stack[vm.sp-2], PropertyDescriptor{ + Value: vm.stack[vm.sp-1], + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + }, true) + + vm.sp -= 2 + vm.pc++ +} + +type defineMethodKeyed struct { + key unistring.String + enumerable bool +} + +func (d *defineMethodKeyed) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-2]) + method := vm.r.toObject(vm.stack[vm.sp-1]) + + obj.self.defineOwnPropertyStr(d.key, PropertyDescriptor{ + Value: method, + Writable: FLAG_TRUE, + Configurable: FLAG_TRUE, + Enumerable: ToFlag(d.enumerable), + }, true) + + vm.sp-- + vm.pc++ +} + +type _setProto struct{} + +var setProto _setProto + +func (_setProto) exec(vm *vm) { + vm.r.setObjectProto(vm.stack[vm.sp-2], vm.stack[vm.sp-1]) + + vm.sp-- + vm.pc++ +} + +type defineGetterKeyed struct { + key unistring.String + enumerable bool +} + +func (s *defineGetterKeyed) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-2]) + val := vm.stack[vm.sp-1] + method := vm.r.toObject(val) + method.self.defineOwnPropertyStr("name", PropertyDescriptor{ + Value: asciiString("get ").Concat(stringValueFromRaw(s.key)), + Configurable: FLAG_TRUE, + }, true) + descr := PropertyDescriptor{ + Getter: val, + Configurable: FLAG_TRUE, + Enumerable: ToFlag(s.enumerable), + } + + obj.self.defineOwnPropertyStr(s.key, descr, true) + + vm.sp-- + vm.pc++ +} + +type defineSetterKeyed struct { + key unistring.String + enumerable bool +} + +func (s *defineSetterKeyed) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-2]) + val := vm.stack[vm.sp-1] + method := vm.r.toObject(val) + method.self.defineOwnPropertyStr("name", PropertyDescriptor{ + Value: asciiString("set ").Concat(stringValueFromRaw(s.key)), + Configurable: FLAG_TRUE, + }, true) + + descr := PropertyDescriptor{ + Setter: val, + Configurable: FLAG_TRUE, + Enumerable: ToFlag(s.enumerable), + } + + obj.self.defineOwnPropertyStr(s.key, descr, true) + + vm.sp-- + vm.pc++ +} + +type defineGetter struct { + enumerable bool +} + +func (s *defineGetter) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-3]) + propName := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + method := vm.r.toObject(val) + method.self.defineOwnPropertyStr("name", PropertyDescriptor{ + Value: funcName("get ", propName), + Configurable: FLAG_TRUE, + }, true) + + descr := PropertyDescriptor{ + Getter: val, + Configurable: FLAG_TRUE, + Enumerable: ToFlag(s.enumerable), + } + + obj.defineOwnProperty(propName, descr, true) + + vm.sp -= 2 + vm.pc++ +} + +type defineSetter struct { + enumerable bool +} + +func (s *defineSetter) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-3]) + propName := vm.stack[vm.sp-2] + val := vm.stack[vm.sp-1] + method := vm.r.toObject(val) + + method.self.defineOwnPropertyStr("name", PropertyDescriptor{ + Value: funcName("set ", propName), + Configurable: FLAG_TRUE, + }, true) + + descr := PropertyDescriptor{ + Setter: val, + Configurable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + } + + obj.defineOwnProperty(propName, descr, true) + + vm.sp -= 2 + vm.pc++ +} + +type getProp unistring.String + +func (g getProp) exec(vm *vm) { + v := vm.stack[vm.sp-1] + obj := v.baseObject(vm.r) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) + return + } + vm.stack[vm.sp-1] = nilSafe(obj.self.getStr(unistring.String(g), v)) + + vm.pc++ +} + +type getPropRecv unistring.String + +func (g getPropRecv) exec(vm *vm) { + recv := vm.stack[vm.sp-2] + v := vm.stack[vm.sp-1] + obj := v.baseObject(vm.r) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) + return + } + vm.stack[vm.sp-2] = nilSafe(obj.self.getStr(unistring.String(g), recv)) + vm.sp-- + vm.pc++ +} + +type getPropRecvCallee unistring.String + +func (g getPropRecvCallee) exec(vm *vm) { + recv := vm.stack[vm.sp-2] + v := vm.stack[vm.sp-1] + obj := v.baseObject(vm.r) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", g)) + return + } + + n := unistring.String(g) + prop := obj.self.getStr(n, recv) + if prop == nil { + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}} + } + + vm.stack[vm.sp-1] = prop + vm.pc++ +} + +type getPropCallee unistring.String + +func (g getPropCallee) exec(vm *vm) { + v := vm.stack[vm.sp-1] + obj := v.baseObject(vm.r) + n := unistring.String(g) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined or null", n)) + return + } + prop := obj.self.getStr(n, v) + if prop == nil { + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: n}} + } + vm.push(prop) + + vm.pc++ +} + +type _getElem struct{} + +var getElem _getElem + +func (_getElem) exec(vm *vm) { + v := vm.stack[vm.sp-2] + obj := v.baseObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + return + } + + vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v)) + + vm.sp-- + vm.pc++ +} + +type _getElemRecv struct{} + +var getElemRecv _getElemRecv + +func (_getElemRecv) exec(vm *vm) { + recv := vm.stack[vm.sp-3] + propName := toPropertyKey(vm.stack[vm.sp-2]) + v := vm.stack[vm.sp-1] + obj := v.baseObject(vm.r) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + return + } + + vm.stack[vm.sp-3] = nilSafe(obj.get(propName, recv)) + + vm.sp -= 2 + vm.pc++ +} + +type _getKey struct{} + +var getKey _getKey + +func (_getKey) exec(vm *vm) { + v := vm.stack[vm.sp-2] + obj := v.baseObject(vm.r) + propName := vm.stack[vm.sp-1] + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + return + } + + vm.stack[vm.sp-2] = nilSafe(obj.get(propName, v)) + + vm.sp-- + vm.pc++ +} + +type _getElemCallee struct{} + +var getElemCallee _getElemCallee + +func (_getElemCallee) exec(vm *vm) { + v := vm.stack[vm.sp-2] + obj := v.baseObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + return + } + + prop := obj.get(propName, v) + if prop == nil { + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} + } + vm.stack[vm.sp-1] = prop + + vm.pc++ +} + +type _getElemRecvCallee struct{} + +var getElemRecvCallee _getElemRecvCallee + +func (_getElemRecvCallee) exec(vm *vm) { + recv := vm.stack[vm.sp-3] + v := vm.stack[vm.sp-2] + obj := v.baseObject(vm.r) + propName := toPropertyKey(vm.stack[vm.sp-1]) + if obj == nil { + vm.throw(vm.r.NewTypeError("Cannot read property '%s' of undefined", propName.String())) + return + } + + prop := obj.get(propName, recv) + if prop == nil { + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: propName.string()}} + } + vm.stack[vm.sp-2] = prop + vm.sp-- + + vm.pc++ +} + +type _dup struct{} + +var dup _dup + +func (_dup) exec(vm *vm) { + vm.push(vm.stack[vm.sp-1]) + vm.pc++ +} + +type dupN uint32 + +func (d dupN) exec(vm *vm) { + vm.push(vm.stack[vm.sp-1-int(d)]) + vm.pc++ +} + +type rdupN uint32 + +func (d rdupN) exec(vm *vm) { + vm.stack[vm.sp-1-int(d)] = vm.stack[vm.sp-1] + vm.pc++ +} + +type dupLast uint32 + +func (d dupLast) exec(vm *vm) { + e := vm.sp + int(d) + vm.stack.expand(e) + copy(vm.stack[vm.sp:e], vm.stack[vm.sp-int(d):]) + vm.sp = e + vm.pc++ +} + +type _newObject struct{} + +var newObject _newObject + +func (_newObject) exec(vm *vm) { + vm.push(vm.r.NewObject()) + vm.pc++ +} + +type newArray uint32 + +func (l newArray) exec(vm *vm) { + values := make([]Value, 0, l) + vm.push(vm.r.newArrayValues(values)) + vm.pc++ +} + +type _pushArrayItem struct{} + +var pushArrayItem _pushArrayItem + +func (_pushArrayItem) exec(vm *vm) { + arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject) + if arr.length < math.MaxUint32 { + arr.length++ + } else { + vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length")) + return + } + val := vm.stack[vm.sp-1] + arr.values = append(arr.values, val) + if val != nil { + arr.objCount++ + } + vm.sp-- + vm.pc++ +} + +type _pushArraySpread struct{} + +var pushArraySpread _pushArraySpread + +func (_pushArraySpread) exec(vm *vm) { + arr := vm.stack[vm.sp-2].(*Object).self.(*arrayObject) + vm.r.getIterator(vm.stack[vm.sp-1], nil).iterate(func(val Value) { + if arr.length < math.MaxUint32 { + arr.length++ + } else { + vm.throw(vm.r.newError(vm.r.getRangeError(), "Invalid array length")) + return + } + arr.values = append(arr.values, val) + arr.objCount++ + }) + vm.sp-- + vm.pc++ +} + +type _pushSpread struct{} + +var pushSpread _pushSpread + +func (_pushSpread) exec(vm *vm) { + vm.sp-- + obj := vm.stack[vm.sp] + vm.r.getIterator(obj, nil).iterate(func(val Value) { + vm.push(val) + }) + vm.pc++ +} + +type _newArrayFromIter struct{} + +var newArrayFromIter _newArrayFromIter + +func (_newArrayFromIter) exec(vm *vm) { + var values []Value + l := len(vm.iterStack) - 1 + iter := vm.iterStack[l].iter + vm.iterStack[l] = iterStackItem{} + vm.iterStack = vm.iterStack[:l] + if iter.iterator != nil { + iter.iterate(func(val Value) { + values = append(values, val) + }) + } + vm.push(vm.r.newArrayValues(values)) + vm.pc++ +} + +type newRegexp struct { + pattern *regexpPattern + src String +} + +func (n *newRegexp) exec(vm *vm) { + vm.push(vm.r.newRegExpp(n.pattern.clone(), n.src, vm.r.getRegExpPrototype()).val) + vm.pc++ +} + +func (vm *vm) setLocalLex(s int) { + v := vm.stack[vm.sp-1] + level := s >> 24 + idx := uint32(s & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + p := &stash.values[idx] + if *p == nil { + panic(errAccessBeforeInit) + } + *p = v + vm.pc++ +} + +func (vm *vm) initLocal(s int) { + v := vm.stack[vm.sp-1] + level := s >> 24 + idx := uint32(s & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + stash.initByIdx(idx, v) + vm.pc++ +} + +type storeStash uint32 + +func (s storeStash) exec(vm *vm) { + vm.initLocal(int(s)) +} + +type storeStashP uint32 + +func (s storeStashP) exec(vm *vm) { + vm.initLocal(int(s)) + vm.sp-- +} + +type storeStashLex uint32 + +func (s storeStashLex) exec(vm *vm) { + vm.setLocalLex(int(s)) +} + +type storeStashLexP uint32 + +func (s storeStashLexP) exec(vm *vm) { + vm.setLocalLex(int(s)) + vm.sp-- +} + +type initStash uint32 + +func (s initStash) exec(vm *vm) { + vm.initLocal(int(s)) +} + +type initStashP uint32 + +func (s initStashP) exec(vm *vm) { + vm.initLocal(int(s)) + vm.sp-- +} + +type initGlobalP unistring.String + +func (s initGlobalP) exec(vm *vm) { + vm.sp-- + vm.r.global.stash.initByName(unistring.String(s), vm.stack[vm.sp]) + vm.pc++ +} + +type initGlobal unistring.String + +func (s initGlobal) exec(vm *vm) { + vm.r.global.stash.initByName(unistring.String(s), vm.stack[vm.sp]) + vm.pc++ +} + +type resolveVar1 unistring.String + +func (s resolveVar1) exec(vm *vm) { + name := unistring.String(s) + var ref ref + for stash := vm.stash; stash != nil; stash = stash.outer { + ref = stash.getRefByName(name, false) + if ref != nil { + goto end + } + } + + ref = &objRef{ + base: vm.r.globalObject, + name: name, + binding: true, + } + +end: + vm.refStack = append(vm.refStack, ref) + vm.pc++ +} + +type deleteVar unistring.String + +func (d deleteVar) exec(vm *vm) { + name := unistring.String(d) + ret := true + for stash := vm.stash; stash != nil; stash = stash.outer { + if stash.obj != nil { + if stashObjHas(stash.obj, name) { + ret = stash.obj.self.deleteStr(name, false) + goto end + } + } else { + if idx, exists := stash.names[name]; exists { + if idx&(maskVar|maskDeletable) == maskVar|maskDeletable { + stash.deleteBinding(name) + } else { + ret = false + } + goto end + } + } + } + + if vm.r.globalObject.self.hasPropertyStr(name) { + ret = vm.r.globalObject.self.deleteStr(name, false) + } + +end: + if ret { + vm.push(valueTrue) + } else { + vm.push(valueFalse) + } + vm.pc++ +} + +type deleteGlobal unistring.String + +func (d deleteGlobal) exec(vm *vm) { + name := unistring.String(d) + var ret bool + if vm.r.globalObject.self.hasPropertyStr(name) { + ret = vm.r.globalObject.self.deleteStr(name, false) + if ret { + delete(vm.r.global.varNames, name) + } + } else { + ret = true + } + if ret { + vm.push(valueTrue) + } else { + vm.push(valueFalse) + } + vm.pc++ +} + +type resolveVar1Strict unistring.String + +func (s resolveVar1Strict) exec(vm *vm) { + name := unistring.String(s) + var ref ref + for stash := vm.stash; stash != nil; stash = stash.outer { + ref = stash.getRefByName(name, true) + if ref != nil { + goto end + } + } + + if vm.r.globalObject.self.hasPropertyStr(name) { + ref = &objRef{ + base: vm.r.globalObject, + name: name, + binding: true, + strict: true, + } + goto end + } + + ref = &unresolvedRef{ + runtime: vm.r, + name: name, + } + +end: + vm.refStack = append(vm.refStack, ref) + vm.pc++ +} + +type setGlobal unistring.String + +func (s setGlobal) exec(vm *vm) { + vm.r.setGlobal(unistring.String(s), vm.peek(), false) + vm.pc++ +} + +type setGlobalStrict unistring.String + +func (s setGlobalStrict) exec(vm *vm) { + vm.r.setGlobal(unistring.String(s), vm.peek(), true) + vm.pc++ +} + +// Load a var from stash +type loadStash uint32 + +func (g loadStash) exec(vm *vm) { + level := int(g >> 24) + idx := uint32(g & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + + vm.push(nilSafe(stash.getByIdx(idx))) + vm.pc++ +} + +// Load a lexical binding from stash +type loadStashLex uint32 + +func (g loadStashLex) exec(vm *vm) { + level := int(g >> 24) + idx := uint32(g & 0x00FFFFFF) + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + + v := stash.getByIdx(idx) + if v == nil { + vm.throw(errAccessBeforeInit) + return + } + vm.push(v) + vm.pc++ +} + +// scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found +// return the indexed var binding value from stash +type loadMixed struct { + name unistring.String + idx uint32 + callee bool +} + +func (g *loadMixed) exec(vm *vm) { + level := int(g.idx >> 24) + idx := g.idx & 0x00FFFFFF + stash := vm.stash + name := g.name + for i := 0; i < level; i++ { + if v, found := stash.getByName(name); found { + if g.callee { + if stash.obj != nil { + vm.push(stash.obj) + } else { + vm.push(_undefined) + } + } + vm.push(v) + goto end + } + stash = stash.outer + } + if g.callee { + vm.push(_undefined) + } + if stash != nil { + vm.push(nilSafe(stash.getByIdx(idx))) + } +end: + vm.pc++ +} + +// scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found +// return the indexed lexical binding value from stash +type loadMixedLex loadMixed + +func (g *loadMixedLex) exec(vm *vm) { + level := int(g.idx >> 24) + idx := g.idx & 0x00FFFFFF + stash := vm.stash + name := g.name + for i := 0; i < level; i++ { + if v, found := stash.getByName(name); found { + if g.callee { + if stash.obj != nil { + vm.push(stash.obj) + } else { + vm.push(_undefined) + } + } + vm.push(v) + goto end + } + stash = stash.outer + } + if g.callee { + vm.push(_undefined) + } + if stash != nil { + v := stash.getByIdx(idx) + if v == nil { + vm.throw(errAccessBeforeInit) + return + } + vm.push(v) + } +end: + vm.pc++ +} + +// scan dynamic stashes up to the given level (encoded as 8 most significant bits of idx), if not found +// return the indexed var binding value from stack +type loadMixedStack struct { + name unistring.String + idx int + level uint8 + callee bool +} + +// same as loadMixedStack, but the args have been moved to stash (therefore stack layout is different) +type loadMixedStack1 loadMixedStack + +func (g *loadMixedStack) exec(vm *vm) { + stash := vm.stash + name := g.name + level := int(g.level) + for i := 0; i < level; i++ { + if v, found := stash.getByName(name); found { + if g.callee { + if stash.obj != nil { + vm.push(stash.obj) + } else { + vm.push(_undefined) + } + } + vm.push(v) + goto end + } + stash = stash.outer + } + if g.callee { + vm.push(_undefined) + } + loadStack(g.idx).exec(vm) + return +end: + vm.pc++ +} + +func (g *loadMixedStack1) exec(vm *vm) { + stash := vm.stash + name := g.name + level := int(g.level) + for i := 0; i < level; i++ { + if v, found := stash.getByName(name); found { + if g.callee { + if stash.obj != nil { + vm.push(stash.obj) + } else { + vm.push(_undefined) + } + } + vm.push(v) + goto end + } + stash = stash.outer + } + if g.callee { + vm.push(_undefined) + } + loadStack1(g.idx).exec(vm) + return +end: + vm.pc++ +} + +type loadMixedStackLex loadMixedStack + +// same as loadMixedStackLex but when the arguments have been moved into stash +type loadMixedStack1Lex loadMixedStack + +func (g *loadMixedStackLex) exec(vm *vm) { + stash := vm.stash + name := g.name + level := int(g.level) + for i := 0; i < level; i++ { + if v, found := stash.getByName(name); found { + if g.callee { + if stash.obj != nil { + vm.push(stash.obj) + } else { + vm.push(_undefined) + } + } + vm.push(v) + goto end + } + stash = stash.outer + } + if g.callee { + vm.push(_undefined) + } + loadStackLex(g.idx).exec(vm) + return +end: + vm.pc++ +} + +func (g *loadMixedStack1Lex) exec(vm *vm) { + stash := vm.stash + name := g.name + level := int(g.level) + for i := 0; i < level; i++ { + if v, found := stash.getByName(name); found { + if g.callee { + if stash.obj != nil { + vm.push(stash.obj) + } else { + vm.push(_undefined) + } + } + vm.push(v) + goto end + } + stash = stash.outer + } + if g.callee { + vm.push(_undefined) + } + loadStack1Lex(g.idx).exec(vm) + return +end: + vm.pc++ +} + +type resolveMixed struct { + name unistring.String + idx uint32 + typ varType + strict bool +} + +func newStashRef(typ varType, name unistring.String, v *[]Value, idx int) ref { + switch typ { + case varTypeVar: + return &stashRef{ + n: name, + v: v, + idx: idx, + } + case varTypeLet: + return &stashRefLex{ + stashRef: stashRef{ + n: name, + v: v, + idx: idx, + }, + } + case varTypeConst, varTypeStrictConst: + return &stashRefConst{ + stashRefLex: stashRefLex{ + stashRef: stashRef{ + n: name, + v: v, + idx: idx, + }, + }, + strictConst: typ == varTypeStrictConst, + } + } + panic("unsupported var type") +} + +func (r *resolveMixed) exec(vm *vm) { + level := int(r.idx >> 24) + idx := r.idx & 0x00FFFFFF + stash := vm.stash + var ref ref + for i := 0; i < level; i++ { + ref = stash.getRefByName(r.name, r.strict) + if ref != nil { + goto end + } + stash = stash.outer + } + + if stash != nil { + ref = newStashRef(r.typ, r.name, &stash.values, int(idx)) + goto end + } + + ref = &unresolvedRef{ + runtime: vm.r, + name: r.name, + } + +end: + vm.refStack = append(vm.refStack, ref) + vm.pc++ +} + +type resolveMixedStack struct { + name unistring.String + idx int + typ varType + level uint8 + strict bool +} + +type resolveMixedStack1 resolveMixedStack + +func (r *resolveMixedStack) exec(vm *vm) { + level := int(r.level) + stash := vm.stash + var ref ref + var idx int + for i := 0; i < level; i++ { + ref = stash.getRefByName(r.name, r.strict) + if ref != nil { + goto end + } + stash = stash.outer + } + + if r.idx > 0 { + idx = vm.sb + vm.args + r.idx + } else { + idx = vm.sb - r.idx + } + + ref = newStashRef(r.typ, r.name, (*[]Value)(&vm.stack), idx) + +end: + vm.refStack = append(vm.refStack, ref) + vm.pc++ +} + +func (r *resolveMixedStack1) exec(vm *vm) { + level := int(r.level) + stash := vm.stash + var ref ref + for i := 0; i < level; i++ { + ref = stash.getRefByName(r.name, r.strict) + if ref != nil { + goto end + } + stash = stash.outer + } + + ref = newStashRef(r.typ, r.name, (*[]Value)(&vm.stack), vm.sb+r.idx) + +end: + vm.refStack = append(vm.refStack, ref) + vm.pc++ +} + +type _getValue struct{} + +var getValue _getValue + +func (_getValue) exec(vm *vm) { + ref := vm.refStack[len(vm.refStack)-1] + if v := ref.get(); v != nil { + vm.push(v) + } else { + vm.throw(vm.r.newReferenceError(ref.refname())) + return + } + vm.pc++ +} + +type _putValue struct{} + +var putValue _putValue + +func (_putValue) exec(vm *vm) { + l := len(vm.refStack) - 1 + ref := vm.refStack[l] + vm.refStack[l] = nil + vm.refStack = vm.refStack[:l] + ref.set(vm.stack[vm.sp-1]) + vm.pc++ +} + +type _putValueP struct{} + +var putValueP _putValueP + +func (_putValueP) exec(vm *vm) { + l := len(vm.refStack) - 1 + ref := vm.refStack[l] + vm.refStack[l] = nil + vm.refStack = vm.refStack[:l] + ref.set(vm.stack[vm.sp-1]) + vm.sp-- + vm.pc++ +} + +type _initValueP struct{} + +var initValueP _initValueP + +func (_initValueP) exec(vm *vm) { + l := len(vm.refStack) - 1 + ref := vm.refStack[l] + vm.refStack[l] = nil + vm.refStack = vm.refStack[:l] + ref.init(vm.stack[vm.sp-1]) + vm.sp-- + vm.pc++ +} + +type loadDynamic unistring.String + +func (n loadDynamic) exec(vm *vm) { + name := unistring.String(n) + var val Value + for stash := vm.stash; stash != nil; stash = stash.outer { + if v, exists := stash.getByName(name); exists { + val = v + break + } + } + if val == nil { + val = vm.r.globalObject.self.getStr(name, nil) + if val == nil { + vm.throw(vm.r.newReferenceError(name)) + return + } + } + vm.push(val) + vm.pc++ +} + +type loadDynamicRef unistring.String + +func (n loadDynamicRef) exec(vm *vm) { + name := unistring.String(n) + var val Value + for stash := vm.stash; stash != nil; stash = stash.outer { + if v, exists := stash.getByName(name); exists { + val = v + break + } + } + if val == nil { + val = vm.r.globalObject.self.getStr(name, nil) + if val == nil { + val = valueUnresolved{r: vm.r, ref: name} + } + } + vm.push(val) + vm.pc++ +} + +type loadDynamicCallee unistring.String + +func (n loadDynamicCallee) exec(vm *vm) { + name := unistring.String(n) + var val Value + var callee *Object + for stash := vm.stash; stash != nil; stash = stash.outer { + if v, exists := stash.getByName(name); exists { + callee = stash.obj + val = v + break + } + } + if val == nil { + val = vm.r.globalObject.self.getStr(name, nil) + if val == nil { + val = valueUnresolved{r: vm.r, ref: name} + } + } + if callee != nil { + vm.push(callee) + } else { + vm.push(_undefined) + } + vm.push(val) + vm.pc++ +} + +type _pop struct{} + +var pop _pop + +func (_pop) exec(vm *vm) { + vm.sp-- + vm.pc++ +} + +func (vm *vm) callEval(n int, strict bool) { + if vm.r.toObject(vm.stack[vm.sp-n-1]) == vm.r.global.Eval { + if n > 0 { + srcVal := vm.stack[vm.sp-n] + if src, ok := srcVal.(String); ok { + ret := vm.r.eval(src, true, strict) + vm.stack[vm.sp-n-2] = ret + } else { + vm.stack[vm.sp-n-2] = srcVal + } + } else { + vm.stack[vm.sp-n-2] = _undefined + } + + vm.sp -= n + 1 + vm.pc++ + } else { + call(n).exec(vm) + } +} + +type callEval uint32 + +func (numargs callEval) exec(vm *vm) { + vm.callEval(int(numargs), false) +} + +type callEvalStrict uint32 + +func (numargs callEvalStrict) exec(vm *vm) { + vm.callEval(int(numargs), true) +} + +type _callEvalVariadic struct{} + +var callEvalVariadic _callEvalVariadic + +func (_callEvalVariadic) exec(vm *vm) { + vm.callEval(vm.countVariadicArgs()-2, false) +} + +type _callEvalVariadicStrict struct{} + +var callEvalVariadicStrict _callEvalVariadicStrict + +func (_callEvalVariadicStrict) exec(vm *vm) { + vm.callEval(vm.countVariadicArgs()-2, true) +} + +type _boxThis struct{} + +var boxThis _boxThis + +func (_boxThis) exec(vm *vm) { + v := vm.stack[vm.sb] + if v == _undefined || v == _null { + vm.stack[vm.sb] = vm.r.globalObject + } else { + vm.stack[vm.sb] = v.ToObject(vm.r) + } + vm.pc++ +} + +var variadicMarker Value = newSymbol(asciiString("[variadic marker]")) + +type _startVariadic struct{} + +var startVariadic _startVariadic + +func (_startVariadic) exec(vm *vm) { + vm.push(variadicMarker) + vm.pc++ +} + +type _callVariadic struct{} + +var callVariadic _callVariadic + +func (vm *vm) countVariadicArgs() int { + count := 0 + for i := vm.sp - 1; i >= 0; i-- { + if vm.stack[i] == variadicMarker { + return count + } + count++ + } + panic("Variadic marker was not found. Compiler bug.") +} + +func (_callVariadic) exec(vm *vm) { + call(vm.countVariadicArgs() - 2).exec(vm) +} + +type _endVariadic struct{} + +var endVariadic _endVariadic + +func (_endVariadic) exec(vm *vm) { + vm.sp-- + vm.stack[vm.sp-1] = vm.stack[vm.sp] + vm.pc++ +} + +type call uint32 + +func (numargs call) exec(vm *vm) { + // this + // callee + // arg0 + // ... + // arg + n := int(numargs) + v := vm.stack[vm.sp-n-1] // callee + obj := vm.toCallee(v) + obj.self.vmCall(vm, n) +} + +func (vm *vm) clearStack() { + sp := vm.sp + stackTail := vm.stack[sp:] + for i := range stackTail { + stackTail[i] = nil + } + vm.stack = vm.stack[:sp] +} + +type enterBlock struct { + names map[unistring.String]uint32 + stashSize uint32 + stackSize uint32 +} + +func (e *enterBlock) exec(vm *vm) { + if e.stashSize > 0 { + vm.newStash() + vm.stash.values = make([]Value, e.stashSize) + if len(e.names) > 0 { + vm.stash.names = e.names + } + } + ss := int(e.stackSize) + vm.stack.expand(vm.sp + ss - 1) + vv := vm.stack[vm.sp : vm.sp+ss] + for i := range vv { + vv[i] = nil + } + vm.sp += ss + vm.pc++ +} + +type enterCatchBlock struct { + names map[unistring.String]uint32 + stashSize uint32 + stackSize uint32 +} + +func (e *enterCatchBlock) exec(vm *vm) { + vm.newStash() + vm.stash.values = make([]Value, e.stashSize) + if len(e.names) > 0 { + vm.stash.names = e.names + } + vm.sp-- + vm.stash.values[0] = vm.stack[vm.sp] + ss := int(e.stackSize) + vm.stack.expand(vm.sp + ss - 1) + vv := vm.stack[vm.sp : vm.sp+ss] + for i := range vv { + vv[i] = nil + } + vm.sp += ss + vm.pc++ +} + +type leaveBlock struct { + stackSize uint32 + popStash bool +} + +func (l *leaveBlock) exec(vm *vm) { + if l.popStash { + vm.stash = vm.stash.outer + } + if ss := l.stackSize; ss > 0 { + vm.sp -= int(ss) + } + vm.pc++ +} + +type enterFunc struct { + names map[unistring.String]uint32 + stashSize uint32 + stackSize uint32 + numArgs uint32 + funcType funcType + argsToStash bool + extensible bool +} + +func (e *enterFunc) exec(vm *vm) { + // Input stack: + // + // callee + // this + // arg0 + // ... + // argN + // <- sp + + // Output stack: + // + // this <- sb + // + // <- sp + sp := vm.sp + vm.sb = sp - vm.args - 1 + vm.newStash() + stash := vm.stash + stash.funcType = e.funcType + stash.values = make([]Value, e.stashSize) + if len(e.names) > 0 { + if e.extensible { + m := make(map[unistring.String]uint32, len(e.names)) + for name, idx := range e.names { + m[name] = idx + } + stash.names = m + } else { + stash.names = e.names + } + } + + ss := int(e.stackSize) + ea := 0 + if e.argsToStash { + offset := vm.args - int(e.numArgs) + copy(stash.values, vm.stack[sp-vm.args:sp]) + if offset > 0 { + vm.stash.extraArgs = make([]Value, offset) + copy(stash.extraArgs, vm.stack[sp-offset:]) + } else { + vv := stash.values[vm.args:e.numArgs] + for i := range vv { + vv[i] = _undefined + } + } + sp -= vm.args + } else { + d := int(e.numArgs) - vm.args + if d > 0 { + ss += d + ea = d + vm.args = int(e.numArgs) + } + } + vm.stack.expand(sp + ss - 1) + if ea > 0 { + vv := vm.stack[sp : vm.sp+ea] + for i := range vv { + vv[i] = _undefined + } + } + vv := vm.stack[sp+ea : sp+ss] + for i := range vv { + vv[i] = nil + } + vm.sp = sp + ss + vm.pc++ +} + +// Similar to enterFunc, but for when arguments may be accessed before they are initialised, +// e.g. by an eval() code or from a closure, or from an earlier initialiser code. +// In this case the arguments remain on stack, first argsToCopy of them are copied to the stash. +type enterFunc1 struct { + names map[unistring.String]uint32 + stashSize uint32 + numArgs uint32 + argsToCopy uint32 + funcType funcType + extensible bool +} + +func (e *enterFunc1) exec(vm *vm) { + sp := vm.sp + vm.sb = sp - vm.args - 1 + vm.newStash() + stash := vm.stash + stash.funcType = e.funcType + stash.values = make([]Value, e.stashSize) + if len(e.names) > 0 { + if e.extensible { + m := make(map[unistring.String]uint32, len(e.names)) + for name, idx := range e.names { + m[name] = idx + } + stash.names = m + } else { + stash.names = e.names + } + } + offset := vm.args - int(e.argsToCopy) + if offset > 0 { + copy(stash.values, vm.stack[sp-vm.args:sp-offset]) + if offset := vm.args - int(e.numArgs); offset > 0 { + vm.stash.extraArgs = make([]Value, offset) + copy(stash.extraArgs, vm.stack[sp-offset:]) + } + } else { + copy(stash.values, vm.stack[sp-vm.args:sp]) + if int(e.argsToCopy) > vm.args { + vv := stash.values[vm.args:e.argsToCopy] + for i := range vv { + vv[i] = _undefined + } + } + } + + vm.pc++ +} + +// Finalises the initialisers section and starts the function body which has its own +// scope. When used in conjunction with enterFunc1 adjustStack is set to true which +// causes the arguments to be removed from the stack. +type enterFuncBody struct { + enterBlock + funcType funcType + extensible bool + adjustStack bool +} + +func (e *enterFuncBody) exec(vm *vm) { + if e.stashSize > 0 || e.extensible { + vm.newStash() + stash := vm.stash + stash.funcType = e.funcType + stash.values = make([]Value, e.stashSize) + if len(e.names) > 0 { + if e.extensible { + m := make(map[unistring.String]uint32, len(e.names)) + for name, idx := range e.names { + m[name] = idx + } + stash.names = m + } else { + stash.names = e.names + } + } + } + sp := vm.sp + if e.adjustStack { + sp -= vm.args + } + nsp := sp + int(e.stackSize) + if e.stackSize > 0 { + vm.stack.expand(nsp - 1) + vv := vm.stack[sp:nsp] + for i := range vv { + vv[i] = nil + } + } + vm.sp = nsp + vm.pc++ +} + +type _ret struct{} + +var ret _ret + +func (_ret) exec(vm *vm) { + // callee -3 + // this -2 <- sb + // retval -1 + + vm.stack[vm.sb-1] = vm.stack[vm.sp-1] + vm.sp = vm.sb + vm.popCtx() + vm.pc++ +} + +type cret uint32 + +func (c cret) exec(vm *vm) { + vm.stack[vm.sb] = *vm.getStashPtr(uint32(c)) + ret.exec(vm) +} + +type enterFuncStashless struct { + stackSize uint32 + args uint32 +} + +func (e *enterFuncStashless) exec(vm *vm) { + sp := vm.sp + vm.sb = sp - vm.args - 1 + d := int(e.args) - vm.args + if d > 0 { + ss := sp + int(e.stackSize) + d + vm.stack.expand(ss) + vv := vm.stack[sp : sp+d] + for i := range vv { + vv[i] = _undefined + } + vv = vm.stack[sp+d : ss] + for i := range vv { + vv[i] = nil + } + vm.args = int(e.args) + vm.sp = ss + } else { + if e.stackSize > 0 { + ss := sp + int(e.stackSize) + vm.stack.expand(ss) + vv := vm.stack[sp:ss] + for i := range vv { + vv[i] = nil + } + vm.sp = ss + } + } + vm.pc++ +} + +type newFuncInstruction interface { + getPrg() *Program +} + +type newFunc struct { + prg *Program + name unistring.String + source string + + length int + strict bool +} + +func (n *newFunc) exec(vm *vm) { + obj := vm.r.newFunc(n.name, n.length, n.strict) + obj.prg = n.prg + obj.stash = vm.stash + obj.privEnv = vm.privEnv + obj.src = n.source + vm.push(obj.val) + vm.pc++ +} + +func (n *newFunc) getPrg() *Program { + return n.prg +} + +type newAsyncFunc struct { + newFunc +} + +func (n *newAsyncFunc) exec(vm *vm) { + obj := vm.r.newAsyncFunc(n.name, n.length, n.strict) + obj.prg = n.prg + obj.stash = vm.stash + obj.privEnv = vm.privEnv + obj.src = n.source + vm.push(obj.val) + vm.pc++ +} + +type newGeneratorFunc struct { + newFunc +} + +func (n *newGeneratorFunc) exec(vm *vm) { + obj := vm.r.newGeneratorFunc(n.name, n.length, n.strict) + obj.prg = n.prg + obj.stash = vm.stash + obj.privEnv = vm.privEnv + obj.src = n.source + vm.push(obj.val) + vm.pc++ +} + +type newMethod struct { + newFunc + homeObjOffset uint32 +} + +func (n *newMethod) _exec(vm *vm, obj *methodFuncObject) { + obj.prg = n.prg + obj.stash = vm.stash + obj.privEnv = vm.privEnv + obj.src = n.source + if n.homeObjOffset > 0 { + obj.homeObject = vm.r.toObject(vm.stack[vm.sp-int(n.homeObjOffset)]) + } + vm.push(obj.val) + vm.pc++ +} + +func (n *newMethod) exec(vm *vm) { + n._exec(vm, vm.r.newMethod(n.name, n.length, n.strict)) +} + +type newAsyncMethod struct { + newMethod +} + +func (n *newAsyncMethod) exec(vm *vm) { + obj := vm.r.newAsyncMethod(n.name, n.length, n.strict) + n._exec(vm, &obj.methodFuncObject) +} + +type newGeneratorMethod struct { + newMethod +} + +func (n *newGeneratorMethod) exec(vm *vm) { + obj := vm.r.newGeneratorMethod(n.name, n.length, n.strict) + n._exec(vm, &obj.methodFuncObject) +} + +type newArrowFunc struct { + newFunc +} + +type newAsyncArrowFunc struct { + newArrowFunc +} + +func getFuncObject(v Value) *Object { + if o, ok := v.(*Object); ok { + if fn, ok := o.self.(*arrowFuncObject); ok { + return fn.funcObj + } + return o + } + if v == _undefined { + return nil + } + panic(typeError("Value is not an Object")) +} + +func getHomeObject(v Value) *Object { + if o, ok := v.(*Object); ok { + switch fn := o.self.(type) { + case *methodFuncObject: + return fn.homeObject + case *generatorMethodFuncObject: + return fn.homeObject + case *asyncMethodFuncObject: + return fn.homeObject + case *classFuncObject: + return o.runtime.toObject(fn.getStr("prototype", nil)) + case *arrowFuncObject: + return getHomeObject(fn.funcObj) + case *asyncArrowFuncObject: + return getHomeObject(fn.funcObj) + } + } + panic(newTypeError("Compiler bug: getHomeObject() on the wrong value: %T", v)) +} + +func (n *newArrowFunc) _exec(vm *vm, obj *arrowFuncObject) { + obj.prg = n.prg + obj.stash = vm.stash + obj.privEnv = vm.privEnv + obj.src = n.source + if vm.sb > 0 { + obj.funcObj = getFuncObject(vm.stack[vm.sb-1]) + } + vm.push(obj.val) + vm.pc++ +} + +func (n *newArrowFunc) exec(vm *vm) { + n._exec(vm, vm.r.newArrowFunc(n.name, n.length, n.strict)) +} + +func (n *newAsyncArrowFunc) exec(vm *vm) { + obj := vm.r.newAsyncArrowFunc(n.name, n.length, n.strict) + n._exec(vm, &obj.arrowFuncObject) +} + +func (vm *vm) alreadyDeclared(name unistring.String) Value { + return vm.r.newError(vm.r.getSyntaxError(), "Identifier '%s' has already been declared", name) +} + +func (vm *vm) checkBindVarsGlobal(names []unistring.String) { + o := vm.r.globalObject.self + sn := vm.r.global.stash.names + if bo, ok := o.(*baseObject); ok { + // shortcut + if bo.extensible { + for _, name := range names { + if _, exists := sn[name]; exists { + panic(vm.alreadyDeclared(name)) + } + } + } else { + for _, name := range names { + if !bo.hasOwnPropertyStr(name) { + panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name)) + } + if _, exists := sn[name]; exists { + panic(vm.alreadyDeclared(name)) + } + } + } + } else { + for _, name := range names { + if !o.hasOwnPropertyStr(name) && !o.isExtensible() { + panic(vm.r.NewTypeError("Cannot define global variable '%s', global object is not extensible", name)) + } + if _, exists := sn[name]; exists { + panic(vm.alreadyDeclared(name)) + } + } + } +} + +func (vm *vm) createGlobalVarBindings(names []unistring.String, d bool) { + globalVarNames := vm.r.global.varNames + if globalVarNames == nil { + globalVarNames = make(map[unistring.String]struct{}) + vm.r.global.varNames = globalVarNames + } + o := vm.r.globalObject.self + if bo, ok := o.(*baseObject); ok { + for _, name := range names { + if !bo.hasOwnPropertyStr(name) && bo.extensible { + bo._putProp(name, _undefined, true, true, d) + } + globalVarNames[name] = struct{}{} + } + } else { + var cf Flag + if d { + cf = FLAG_TRUE + } else { + cf = FLAG_FALSE + } + for _, name := range names { + if !o.hasOwnPropertyStr(name) && o.isExtensible() { + o.defineOwnPropertyStr(name, PropertyDescriptor{ + Value: _undefined, + Writable: FLAG_TRUE, + Enumerable: FLAG_TRUE, + Configurable: cf, + }, true) + o.setOwnStr(name, _undefined, false) + } + globalVarNames[name] = struct{}{} + } + } +} + +func (vm *vm) createGlobalFuncBindings(names []unistring.String, d bool) { + globalVarNames := vm.r.global.varNames + if globalVarNames == nil { + globalVarNames = make(map[unistring.String]struct{}) + vm.r.global.varNames = globalVarNames + } + o := vm.r.globalObject.self + b := vm.sp - len(names) + var shortcutObj *baseObject + if o, ok := o.(*baseObject); ok { + shortcutObj = o + } + for i, name := range names { + var desc PropertyDescriptor + prop := o.getOwnPropStr(name) + desc.Value = vm.stack[b+i] + if shortcutObj != nil && prop == nil && shortcutObj.extensible { + shortcutObj._putProp(name, desc.Value, true, true, d) + } else { + if prop, ok := prop.(*valueProperty); ok && !prop.configurable { + // no-op + } else { + desc.Writable = FLAG_TRUE + desc.Enumerable = FLAG_TRUE + if d { + desc.Configurable = FLAG_TRUE + } else { + desc.Configurable = FLAG_FALSE + } + } + if shortcutObj != nil { + shortcutObj.defineOwnPropertyStr(name, desc, true) + } else { + o.defineOwnPropertyStr(name, desc, true) + o.setOwnStr(name, desc.Value, false) // not a bug, see https://262.ecma-international.org/#sec-createglobalfunctionbinding + } + } + globalVarNames[name] = struct{}{} + } + vm.sp = b +} + +func (vm *vm) checkBindFuncsGlobal(names []unistring.String) { + o := vm.r.globalObject.self + sn := vm.r.global.stash.names + for _, name := range names { + if _, exists := sn[name]; exists { + panic(vm.alreadyDeclared(name)) + } + prop := o.getOwnPropStr(name) + allowed := true + switch prop := prop.(type) { + case nil: + allowed = o.isExtensible() + case *valueProperty: + allowed = prop.configurable || prop.getterFunc == nil && prop.setterFunc == nil && prop.writable && prop.enumerable + } + if !allowed { + panic(vm.r.NewTypeError("Cannot redefine global function '%s'", name)) + } + } +} + +func (vm *vm) checkBindLexGlobal(names []unistring.String) { + o := vm.r.globalObject.self + s := &vm.r.global.stash + for _, name := range names { + if _, exists := vm.r.global.varNames[name]; exists { + goto fail + } + if _, exists := s.names[name]; exists { + goto fail + } + if prop, ok := o.getOwnPropStr(name).(*valueProperty); ok && !prop.configurable { + goto fail + } + continue + fail: + panic(vm.alreadyDeclared(name)) + } +} + +type bindVars struct { + names []unistring.String + deletable bool +} + +func (d *bindVars) exec(vm *vm) { + var target *stash + for _, name := range d.names { + for s := vm.stash; s != nil; s = s.outer { + if idx, exists := s.names[name]; exists && idx&maskVar == 0 { + vm.throw(vm.alreadyDeclared(name)) + return + } + if s.isVariable() { + target = s + break + } + } + } + if target == nil { + target = vm.stash + } + deletable := d.deletable + for _, name := range d.names { + target.createBinding(name, deletable) + } + vm.pc++ +} + +type bindGlobal struct { + vars, funcs, lets, consts []unistring.String + + deletable bool +} + +func (b *bindGlobal) exec(vm *vm) { + vm.checkBindFuncsGlobal(b.funcs) + vm.checkBindLexGlobal(b.lets) + vm.checkBindLexGlobal(b.consts) + vm.checkBindVarsGlobal(b.vars) + + s := &vm.r.global.stash + for _, name := range b.lets { + s.createLexBinding(name, false) + } + for _, name := range b.consts { + s.createLexBinding(name, true) + } + vm.createGlobalFuncBindings(b.funcs, b.deletable) + vm.createGlobalVarBindings(b.vars, b.deletable) + vm.pc++ +} + +type jne int32 + +func (j jne) exec(vm *vm) { + vm.sp-- + if !vm.stack[vm.sp].ToBoolean() { + vm.pc += int(j) + } else { + vm.pc++ + } +} + +type jeq int32 + +func (j jeq) exec(vm *vm) { + vm.sp-- + if vm.stack[vm.sp].ToBoolean() { + vm.pc += int(j) + } else { + vm.pc++ + } +} + +type jeq1 int32 + +func (j jeq1) exec(vm *vm) { + if vm.stack[vm.sp-1].ToBoolean() { + vm.pc += int(j) + } else { + vm.sp-- + vm.pc++ + } +} + +type jneq1 int32 + +func (j jneq1) exec(vm *vm) { + if !vm.stack[vm.sp-1].ToBoolean() { + vm.pc += int(j) + } else { + vm.sp-- + vm.pc++ + } +} + +type jdef int32 + +func (j jdef) exec(vm *vm) { + if vm.stack[vm.sp-1] != _undefined { + vm.pc += int(j) + } else { + vm.sp-- + vm.pc++ + } +} + +type jdefP int32 + +func (j jdefP) exec(vm *vm) { + if vm.stack[vm.sp-1] != _undefined { + vm.pc += int(j) + } else { + vm.pc++ + } + vm.sp-- +} + +type jopt int32 + +func (j jopt) exec(vm *vm) { + switch vm.stack[vm.sp-1] { + case _null: + vm.stack[vm.sp-1] = _undefined + fallthrough + case _undefined: + vm.pc += int(j) + default: + vm.pc++ + } +} + +type joptc int32 + +func (j joptc) exec(vm *vm) { + switch vm.stack[vm.sp-1].(type) { + case valueNull, valueUndefined, memberUnresolved: + vm.sp-- + vm.stack[vm.sp-1] = _undefined + vm.pc += int(j) + default: + vm.pc++ + } +} + +type jcoalesc int32 + +func (j jcoalesc) exec(vm *vm) { + switch vm.stack[vm.sp-1] { + case _undefined, _null: + vm.sp-- + vm.pc++ + default: + vm.pc += int(j) + } +} + +type _not struct{} + +var not _not + +func (_not) exec(vm *vm) { + if vm.stack[vm.sp-1].ToBoolean() { + vm.stack[vm.sp-1] = valueFalse + } else { + vm.stack[vm.sp-1] = valueTrue + } + vm.pc++ +} + +func toPrimitiveNumber(v Value) Value { + if o, ok := v.(*Object); ok { + return o.toPrimitiveNumber() + } + return v +} + +func toPrimitive(v Value) Value { + if o, ok := v.(*Object); ok { + return o.toPrimitive() + } + return v +} + +func cmp(px, py Value) Value { + var ret bool + var nx, ny float64 + + if script, ok := px.(String); ok { + if ys, ok := py.(String); ok { + ret = script.CompareTo(ys) < 0 + goto end + } + } + + if xi, ok := px.(valueInt); ok { + if yi, ok := py.(valueInt); ok { + ret = xi < yi + goto end + } + } + + nx = px.ToFloat() + ny = py.ToFloat() + + if math.IsNaN(nx) || math.IsNaN(ny) { + return _undefined + } + + ret = nx < ny + +end: + if ret { + return valueTrue + } + return valueFalse + +} + +type _op_lt struct{} + +var op_lt _op_lt + +func (_op_lt) exec(vm *vm) { + left := toPrimitiveNumber(vm.stack[vm.sp-2]) + right := toPrimitiveNumber(vm.stack[vm.sp-1]) + + r := cmp(left, right) + if r == _undefined { + vm.stack[vm.sp-2] = valueFalse + } else { + vm.stack[vm.sp-2] = r + } + vm.sp-- + vm.pc++ +} + +type _op_lte struct{} + +var op_lte _op_lte + +func (_op_lte) exec(vm *vm) { + left := toPrimitiveNumber(vm.stack[vm.sp-2]) + right := toPrimitiveNumber(vm.stack[vm.sp-1]) + + r := cmp(right, left) + if r == _undefined || r == valueTrue { + vm.stack[vm.sp-2] = valueFalse + } else { + vm.stack[vm.sp-2] = valueTrue + } + + vm.sp-- + vm.pc++ +} + +type _op_gt struct{} + +var op_gt _op_gt + +func (_op_gt) exec(vm *vm) { + left := toPrimitiveNumber(vm.stack[vm.sp-2]) + right := toPrimitiveNumber(vm.stack[vm.sp-1]) + + r := cmp(right, left) + if r == _undefined { + vm.stack[vm.sp-2] = valueFalse + } else { + vm.stack[vm.sp-2] = r + } + vm.sp-- + vm.pc++ +} + +type _op_gte struct{} + +var op_gte _op_gte + +func (_op_gte) exec(vm *vm) { + left := toPrimitiveNumber(vm.stack[vm.sp-2]) + right := toPrimitiveNumber(vm.stack[vm.sp-1]) + + r := cmp(left, right) + if r == _undefined || r == valueTrue { + vm.stack[vm.sp-2] = valueFalse + } else { + vm.stack[vm.sp-2] = valueTrue + } + + vm.sp-- + vm.pc++ +} + +type _op_eq struct{} + +var op_eq _op_eq + +func (_op_eq) exec(vm *vm) { + if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) { + vm.stack[vm.sp-2] = valueTrue + } else { + vm.stack[vm.sp-2] = valueFalse + } + vm.sp-- + vm.pc++ +} + +type _op_neq struct{} + +var op_neq _op_neq + +func (_op_neq) exec(vm *vm) { + if vm.stack[vm.sp-2].Equals(vm.stack[vm.sp-1]) { + vm.stack[vm.sp-2] = valueFalse + } else { + vm.stack[vm.sp-2] = valueTrue + } + vm.sp-- + vm.pc++ +} + +type _op_strict_eq struct{} + +var op_strict_eq _op_strict_eq + +func (_op_strict_eq) exec(vm *vm) { + if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) { + vm.stack[vm.sp-2] = valueTrue + } else { + vm.stack[vm.sp-2] = valueFalse + } + vm.sp-- + vm.pc++ +} + +type _op_strict_neq struct{} + +var op_strict_neq _op_strict_neq + +func (_op_strict_neq) exec(vm *vm) { + if vm.stack[vm.sp-2].StrictEquals(vm.stack[vm.sp-1]) { + vm.stack[vm.sp-2] = valueFalse + } else { + vm.stack[vm.sp-2] = valueTrue + } + vm.sp-- + vm.pc++ +} + +type _op_instanceof struct{} + +var op_instanceof _op_instanceof + +func (_op_instanceof) exec(vm *vm) { + left := vm.stack[vm.sp-2] + right := vm.r.toObject(vm.stack[vm.sp-1]) + + if instanceOfOperator(left, right) { + vm.stack[vm.sp-2] = valueTrue + } else { + vm.stack[vm.sp-2] = valueFalse + } + + vm.sp-- + vm.pc++ +} + +type _op_in struct{} + +var op_in _op_in + +func (_op_in) exec(vm *vm) { + left := vm.stack[vm.sp-2] + right := vm.r.toObject(vm.stack[vm.sp-1]) + + if right.hasProperty(left) { + vm.stack[vm.sp-2] = valueTrue + } else { + vm.stack[vm.sp-2] = valueFalse + } + + vm.sp-- + vm.pc++ +} + +type try struct { + catchOffset int32 + finallyOffset int32 +} + +func (t try) exec(vm *vm) { + var catchPos, finallyPos int32 + if t.catchOffset > 0 { + catchPos = int32(vm.pc) + t.catchOffset + } else { + catchPos = -1 + } + if t.finallyOffset > 0 { + finallyPos = int32(vm.pc) + t.finallyOffset + } else { + finallyPos = -1 + } + vm.pushTryFrame(catchPos, finallyPos) + vm.pc++ +} + +type leaveTry struct{} + +func (leaveTry) exec(vm *vm) { + tf := &vm.tryStack[len(vm.tryStack)-1] + if tf.finallyPos >= 0 { + tf.finallyRet = int32(vm.pc + 1) + vm.pc = int(tf.finallyPos) + tf.finallyPos = -1 + tf.catchPos = -1 + } else { + vm.popTryFrame() + vm.pc++ + } +} + +type enterFinally struct{} + +func (enterFinally) exec(vm *vm) { + tf := &vm.tryStack[len(vm.tryStack)-1] + tf.finallyPos = -1 + vm.pc++ +} + +type leaveFinally struct{} + +func (leaveFinally) exec(vm *vm) { + tf := &vm.tryStack[len(vm.tryStack)-1] + ex, ret := tf.exception, tf.finallyRet + tf.exception = nil + vm.popTryFrame() + if ex != nil { + vm.throw(ex) + return + } else { + if ret != -1 { + vm.pc = int(ret) + } else { + vm.pc++ + } + } +} + +type _throw struct{} + +var throw _throw + +func (_throw) exec(vm *vm) { + v := vm.stack[vm.sp-1] + ex := &Exception{ + val: v, + } + if o, ok := v.(*Object); ok { + if e, ok := o.self.(*errorObject); ok { + if len(e.stack) > 0 { + ex.stack = e.stack + } + } + } + if ex.stack == nil { + ex.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0) + } + + if ex = vm.handleThrow(ex); ex != nil { + panic(ex) + } +} + +type _newVariadic struct{} + +var newVariadic _newVariadic + +func (_newVariadic) exec(vm *vm) { + _new(vm.countVariadicArgs() - 1).exec(vm) +} + +type _new uint32 + +func (n _new) exec(vm *vm) { + sp := vm.sp - int(n) + obj := vm.stack[sp-1] + ctor := vm.r.toConstructor(obj) + vm.stack[sp-1] = ctor(vm.stack[sp:vm.sp], nil) + vm.sp = sp + vm.pc++ +} + +type superCall uint32 + +func (s superCall) exec(vm *vm) { + l := len(vm.refStack) - 1 + thisRef := vm.refStack[l] + vm.refStack[l] = nil + vm.refStack = vm.refStack[:l] + + obj := vm.r.toObject(vm.stack[vm.sb-1]) + var cls *classFuncObject + switch fn := obj.self.(type) { + case *classFuncObject: + cls = fn + case *arrowFuncObject: + cls, _ = fn.funcObj.self.(*classFuncObject) + } + if cls == nil { + vm.throw(vm.r.NewTypeError("wrong callee type for super()")) + return + } + sp := vm.sp - int(s) + newTarget := vm.r.toObject(vm.newTarget) + v := cls.createInstance(vm.stack[sp:vm.sp], newTarget) + thisRef.set(v) + vm.sp = sp + cls._initFields(v) + vm.push(v) + vm.pc++ +} + +type _superCallVariadic struct{} + +var superCallVariadic _superCallVariadic + +func (_superCallVariadic) exec(vm *vm) { + superCall(vm.countVariadicArgs()).exec(vm) +} + +type _loadNewTarget struct{} + +var loadNewTarget _loadNewTarget + +func (_loadNewTarget) exec(vm *vm) { + if t := vm.newTarget; t != nil { + vm.push(t) + } else { + vm.push(_undefined) + } + vm.pc++ +} + +type _typeof struct{} + +var typeof _typeof + +func (_typeof) exec(vm *vm) { + var r Value + switch v := vm.stack[vm.sp-1].(type) { + case valueUndefined, valueUnresolved: + r = stringUndefined + case valueNull: + r = stringObjectC + case *Object: + r = v.self.typeOf() + case valueBool: + r = stringBoolean + case String: + r = stringString + case valueInt, valueFloat: + r = stringNumber + case *Symbol: + r = stringSymbol + default: + panic(newTypeError("Compiler bug: unknown type: %T", v)) + } + vm.stack[vm.sp-1] = r + vm.pc++ +} + +type createArgsMapped uint32 + +func (formalArgs createArgsMapped) exec(vm *vm) { + v := &Object{runtime: vm.r} + args := &argumentsObject{} + args.extensible = true + args.prototype = vm.r.global.ObjectPrototype + args.class = "Arguments" + v.self = args + args.val = v + args.length = vm.args + args.init() + i := 0 + c := int(formalArgs) + if vm.args < c { + c = vm.args + } + for ; i < c; i++ { + args._put(unistring.String(strconv.Itoa(i)), &mappedProperty{ + valueProperty: valueProperty{ + writable: true, + configurable: true, + enumerable: true, + }, + v: &vm.stash.values[i], + }) + } + + for _, v := range vm.stash.extraArgs { + args._put(unistring.String(strconv.Itoa(i)), v) + i++ + } + + args._putProp("callee", vm.stack[vm.sb-1], true, false, true) + args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true)) + vm.push(v) + vm.pc++ +} + +type createArgsUnmapped uint32 + +func (formalArgs createArgsUnmapped) exec(vm *vm) { + args := vm.r.newBaseObject(vm.r.global.ObjectPrototype, "Arguments") + i := 0 + c := int(formalArgs) + if vm.args < c { + c = vm.args + } + for _, v := range vm.stash.values[:c] { + args._put(unistring.String(strconv.Itoa(i)), v) + i++ + } + + for _, v := range vm.stash.extraArgs { + args._put(unistring.String(strconv.Itoa(i)), v) + i++ + } + + args._putProp("length", intToValue(int64(vm.args)), true, false, true) + args._put("callee", vm.r.newThrowerProperty(false)) + args._putSym(SymIterator, valueProp(vm.r.getArrayValues(), true, false, true)) + vm.push(args.val) + vm.pc++ +} + +type _enterWith struct{} + +var enterWith _enterWith + +func (_enterWith) exec(vm *vm) { + vm.newStash() + vm.stash.obj = vm.stack[vm.sp-1].ToObject(vm.r) + vm.sp-- + vm.pc++ +} + +type _leaveWith struct{} + +var leaveWith _leaveWith + +func (_leaveWith) exec(vm *vm) { + vm.stash = vm.stash.outer + vm.pc++ +} + +func emptyIter() (propIterItem, iterNextFunc) { + return propIterItem{}, nil +} + +type _enumerate struct{} + +var enumerate _enumerate + +func (_enumerate) exec(vm *vm) { + v := vm.stack[vm.sp-1] + if v == _undefined || v == _null { + vm.iterStack = append(vm.iterStack, iterStackItem{f: emptyIter}) + } else { + vm.iterStack = append(vm.iterStack, iterStackItem{f: enumerateRecursive(v.ToObject(vm.r))}) + } + vm.sp-- + vm.pc++ +} + +type enumNext int32 + +func (jmp enumNext) exec(vm *vm) { + l := len(vm.iterStack) - 1 + item, n := vm.iterStack[l].f() + if n != nil { + vm.iterStack[l].val = item.name + vm.iterStack[l].f = n + vm.pc++ + } else { + vm.pc += int(jmp) + } +} + +type _enumGet struct{} + +var enumGet _enumGet + +func (_enumGet) exec(vm *vm) { + l := len(vm.iterStack) - 1 + vm.push(vm.iterStack[l].val) + vm.pc++ +} + +type _enumPop struct{} + +var enumPop _enumPop + +func (_enumPop) exec(vm *vm) { + l := len(vm.iterStack) - 1 + vm.iterStack[l] = iterStackItem{} + vm.iterStack = vm.iterStack[:l] + vm.pc++ +} + +type _enumPopClose struct{} + +var enumPopClose _enumPopClose + +func (_enumPopClose) exec(vm *vm) { + l := len(vm.iterStack) - 1 + item := vm.iterStack[l] + vm.iterStack[l] = iterStackItem{} + vm.iterStack = vm.iterStack[:l] + if iter := item.iter; iter != nil { + iter.returnIter() + } + vm.pc++ +} + +type _iterateP struct{} + +var iterateP _iterateP + +func (_iterateP) exec(vm *vm) { + iter := vm.r.getIterator(vm.stack[vm.sp-1], nil) + vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter}) + vm.sp-- + vm.pc++ +} + +type _iterate struct{} + +var iterate _iterate + +func (_iterate) exec(vm *vm) { + iter := vm.r.getIterator(vm.stack[vm.sp-1], nil) + vm.iterStack = append(vm.iterStack, iterStackItem{iter: iter}) + vm.pc++ +} + +type iterNext int32 + +func (jmp iterNext) exec(vm *vm) { + l := len(vm.iterStack) - 1 + iter := vm.iterStack[l].iter + value, ex := iter.step() + if ex == nil { + if value == nil { + vm.pc += int(jmp) + } else { + vm.iterStack[l].val = value + vm.pc++ + } + } else { + l := len(vm.iterStack) - 1 + vm.iterStack[l] = iterStackItem{} + vm.iterStack = vm.iterStack[:l] + vm.throw(ex.val) + return + } +} + +type iterGetNextOrUndef struct{} + +func (iterGetNextOrUndef) exec(vm *vm) { + l := len(vm.iterStack) - 1 + iter := vm.iterStack[l].iter + var value Value + if iter.iterator != nil { + var ex *Exception + value, ex = iter.step() + if ex != nil { + l := len(vm.iterStack) - 1 + vm.iterStack[l] = iterStackItem{} + vm.iterStack = vm.iterStack[:l] + vm.throw(ex.val) + return + } + } + vm.push(nilSafe(value)) + vm.pc++ +} + +type copyStash struct{} + +func (copyStash) exec(vm *vm) { + oldStash := vm.stash + newStash := &stash{ + outer: oldStash.outer, + } + vm.stashAllocs++ + newStash.values = append([]Value(nil), oldStash.values...) + newStash.names = oldStash.names + vm.stash = newStash + vm.pc++ +} + +type _throwAssignToConst struct{} + +var throwAssignToConst _throwAssignToConst + +func (_throwAssignToConst) exec(vm *vm) { + vm.throw(errAssignToConst) +} + +func (r *Runtime) copyDataProperties(target, source Value) { + targetObj := r.toObject(target) + if source == _null || source == _undefined { + return + } + sourceObj := source.ToObject(r) + for item, next := iterateEnumerableProperties(sourceObj)(); next != nil; item, next = next() { + createDataPropertyOrThrow(targetObj, item.name, item.value) + } +} + +type _copySpread struct{} + +var copySpread _copySpread + +func (_copySpread) exec(vm *vm) { + vm.r.copyDataProperties(vm.stack[vm.sp-2], vm.stack[vm.sp-1]) + vm.sp-- + vm.pc++ +} + +type _copyRest struct{} + +var copyRest _copyRest + +func (_copyRest) exec(vm *vm) { + vm.push(vm.r.NewObject()) + vm.r.copyDataProperties(vm.stack[vm.sp-1], vm.stack[vm.sp-2]) + vm.pc++ +} + +type _createDestructSrc struct{} + +var createDestructSrc _createDestructSrc + +func (_createDestructSrc) exec(vm *vm) { + v := vm.stack[vm.sp-1] + vm.r.checkObjectCoercible(v) + vm.push(vm.r.newDestructKeyedSource(v)) + vm.pc++ +} + +type _checkObjectCoercible struct{} + +var checkObjectCoercible _checkObjectCoercible + +func (_checkObjectCoercible) exec(vm *vm) { + vm.r.checkObjectCoercible(vm.stack[vm.sp-1]) + vm.pc++ +} + +type createArgsRestStack int + +func (n createArgsRestStack) exec(vm *vm) { + var values []Value + delta := vm.args - int(n) + if delta > 0 { + values = make([]Value, delta) + copy(values, vm.stack[vm.sb+int(n)+1:]) + } + vm.push(vm.r.newArrayValues(values)) + vm.pc++ +} + +type _createArgsRestStash struct{} + +var createArgsRestStash _createArgsRestStash + +func (_createArgsRestStash) exec(vm *vm) { + vm.push(vm.r.newArrayValues(vm.stash.extraArgs)) + vm.stash.extraArgs = nil + vm.pc++ +} + +type concatStrings int + +func (n concatStrings) exec(vm *vm) { + strs := vm.stack[vm.sp-int(n) : vm.sp] + length := 0 + allAscii := true + for i, s := range strs { + switch s := s.(type) { + case asciiString: + length += s.Length() + case unicodeString: + length += s.Length() + allAscii = false + case *importedString: + s.ensureScanned() + if s.u != nil { + strs[i] = s.u + length += s.u.Length() + allAscii = false + } else { + strs[i] = asciiString(s.s) + length += len(s.s) + } + default: + panic(unknownStringTypeErr(s)) + } + } + + vm.sp -= int(n) - 1 + if allAscii { + var buf strings.Builder + buf.Grow(length) + for _, s := range strs { + buf.WriteString(string(s.(asciiString))) + } + vm.stack[vm.sp-1] = asciiString(buf.String()) + } else { + var buf unicodeStringBuilder + buf.ensureStarted(length) + for _, s := range strs { + buf.writeString(s.(String)) + } + vm.stack[vm.sp-1] = buf.String() + } + vm.pc++ +} + +type getTaggedTmplObject struct { + raw, cooked []Value +} + +// As tagged template objects are not cached (because it's hard to ensure the cache is cleaned without using +// finalizers) this wrapper is needed to override the equality method so that two objects for the same template +// literal appeared to be equal from the code's point of view. +type taggedTemplateArray struct { + *arrayObject + idPtr *[]Value +} + +func (a *taggedTemplateArray) equal(other objectImpl) bool { + if o, ok := other.(*taggedTemplateArray); ok { + return a.idPtr == o.idPtr + } + return false +} + +func (c *getTaggedTmplObject) exec(vm *vm) { + cooked := vm.r.newArrayObject() + setArrayValues(cooked, c.cooked) + raw := vm.r.newArrayObject() + setArrayValues(raw, c.raw) + + cooked.propValueCount = len(c.cooked) + cooked.lengthProp.writable = false + + raw.propValueCount = len(c.raw) + raw.lengthProp.writable = false + + raw.preventExtensions(true) + raw.val.self = &taggedTemplateArray{ + arrayObject: raw, + idPtr: &c.raw, + } + + cooked._putProp("raw", raw.val, false, false, false) + cooked.preventExtensions(true) + cooked.val.self = &taggedTemplateArray{ + arrayObject: cooked, + idPtr: &c.cooked, + } + + vm.push(cooked.val) + vm.pc++ +} + +type _loadSuper struct{} + +var loadSuper _loadSuper + +func (_loadSuper) exec(vm *vm) { + homeObject := getHomeObject(vm.stack[vm.sb-1]) + if proto := homeObject.Prototype(); proto != nil { + vm.push(proto) + } else { + vm.push(_undefined) + } + vm.pc++ +} + +type newClass struct { + ctor *Program + name unistring.String + source string + initFields *Program + + privateFields, privateMethods []unistring.String // only set when dynamic resolution is needed + numPrivateFields, numPrivateMethods uint32 + + length int + hasPrivateEnv bool +} + +type newDerivedClass struct { + newClass +} + +func (vm *vm) createPrivateType(f *classFuncObject, numFields, numMethods uint32) { + typ := &privateEnvType{} + typ.numFields = numFields + typ.numMethods = numMethods + f.privateEnvType = typ + f.privateMethods = make([]Value, numMethods) +} + +func (vm *vm) fillPrivateNamesMap(typ *privateEnvType, privateFields, privateMethods []unistring.String) { + if len(privateFields) > 0 || len(privateMethods) > 0 { + penv := vm.privEnv.names + if penv == nil { + penv = make(privateNames) + vm.privEnv.names = penv + } + for idx, field := range privateFields { + penv[field] = &privateId{ + typ: typ, + idx: uint32(idx), + } + } + for idx, method := range privateMethods { + penv[method] = &privateId{ + typ: typ, + idx: uint32(idx), + isMethod: true, + } + } + } +} + +func (c *newClass) create(protoParent, ctorParent *Object, vm *vm, derived bool) (prototype, cls *Object) { + proto := vm.r.newBaseObject(protoParent, classObject) + f := vm.r.newClassFunc(c.name, c.length, ctorParent, derived) + f._putProp("prototype", proto.val, false, false, false) + proto._putProp("constructor", f.val, true, false, true) + f.prg = c.ctor + f.stash = vm.stash + f.src = c.source + f.initFields = c.initFields + if c.hasPrivateEnv { + vm.privEnv = &privateEnv{ + outer: vm.privEnv, + } + vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods) + vm.fillPrivateNamesMap(f.privateEnvType, c.privateFields, c.privateMethods) + vm.privEnv.instanceType = f.privateEnvType + } + f.privEnv = vm.privEnv + return proto.val, f.val +} + +func (c *newClass) exec(vm *vm) { + proto, cls := c.create(vm.r.global.ObjectPrototype, vm.r.getFunctionPrototype(), vm, false) + sp := vm.sp + vm.stack.expand(sp + 1) + vm.stack[sp] = proto + vm.stack[sp+1] = cls + vm.sp = sp + 2 + vm.pc++ +} + +func (c *newDerivedClass) exec(vm *vm) { + var protoParent *Object + var superClass *Object + if o := vm.stack[vm.sp-1]; o != _null { + if sc, ok := o.(*Object); !ok || sc.self.assertConstructor() == nil { + vm.throw(vm.r.NewTypeError("Class extends value is not a constructor or null")) + return + } else { + v := sc.self.getStr("prototype", nil) + if v != _null { + if o, ok := v.(*Object); ok { + protoParent = o + } else { + vm.throw(vm.r.NewTypeError("Class extends value does not have valid prototype property")) + return + } + } + superClass = sc + } + } else { + superClass = vm.r.getFunctionPrototype() + } + + proto, cls := c.create(protoParent, superClass, vm, true) + vm.stack[vm.sp-1] = proto + vm.push(cls) + vm.pc++ +} + +// Creates a special instance of *classFuncObject which is only used during evaluation of a class declaration +// to initialise static fields and instance private methods of another class. +type newStaticFieldInit struct { + initFields *Program + numPrivateFields, numPrivateMethods uint32 +} + +func (c *newStaticFieldInit) exec(vm *vm) { + f := vm.r.newClassFunc("", 0, vm.r.getFunctionPrototype(), false) + if c.numPrivateFields > 0 || c.numPrivateMethods > 0 { + vm.createPrivateType(f, c.numPrivateFields, c.numPrivateMethods) + } + f.initFields = c.initFields + f.stash = vm.stash + vm.push(f.val) + vm.pc++ +} + +func (vm *vm) loadThis(v Value) { + if v != nil { + vm.push(v) + } else { + vm.throw(vm.r.newError(vm.r.getReferenceError(), "Must call super constructor in derived class before accessing 'this'")) + return + } + vm.pc++ +} + +type loadThisStash uint32 + +func (l loadThisStash) exec(vm *vm) { + vm.loadThis(*vm.getStashPtr(uint32(l))) +} + +type loadThisStack struct{} + +func (loadThisStack) exec(vm *vm) { + vm.loadThis(vm.stack[vm.sb]) +} + +func (vm *vm) getStashPtr(s uint32) *Value { + level := int(s) >> 24 + idx := s & 0x00FFFFFF + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + + return &stash.values[idx] +} + +type getThisDynamic struct{} + +func (getThisDynamic) exec(vm *vm) { + for stash := vm.stash; stash != nil; stash = stash.outer { + if stash.obj == nil { + if v, exists := stash.getByName(thisBindingName); exists { + vm.push(v) + vm.pc++ + return + } + } + } + vm.push(vm.r.globalObject) + vm.pc++ +} + +type throwConst struct { + v any +} + +func (t throwConst) exec(vm *vm) { + vm.throw(t.v) +} + +type resolveThisStack struct{} + +func (r resolveThisStack) exec(vm *vm) { + vm.refStack = append(vm.refStack, &thisRef{v: (*[]Value)(&vm.stack), idx: vm.sb}) + vm.pc++ +} + +type resolveThisStash uint32 + +func (r resolveThisStash) exec(vm *vm) { + level := int(r) >> 24 + idx := r & 0x00FFFFFF + stash := vm.stash + for i := 0; i < level; i++ { + stash = stash.outer + } + vm.refStack = append(vm.refStack, &thisRef{v: &stash.values, idx: int(idx)}) + vm.pc++ +} + +type resolveThisDynamic struct{} + +func (resolveThisDynamic) exec(vm *vm) { + for stash := vm.stash; stash != nil; stash = stash.outer { + if stash.obj == nil { + if idx, exists := stash.names[thisBindingName]; exists { + vm.refStack = append(vm.refStack, &thisRef{v: &stash.values, idx: int(idx &^ maskTyp)}) + vm.pc++ + return + } + } + } + panic(vm.r.newError(vm.r.getReferenceError(), "Compiler bug: 'this' reference is not found in resolveThisDynamic")) +} + +type defineComputedKey int + +func (offset defineComputedKey) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-int(offset)]) + if h, ok := obj.self.(*classFuncObject); ok { + key := toPropertyKey(vm.stack[vm.sp-1]) + h.computedKeys = append(h.computedKeys, key) + vm.sp-- + vm.pc++ + return + } + panic(vm.r.NewTypeError("Compiler bug: unexpected target for defineComputedKey: %v", obj)) +} + +type loadComputedKey int + +func (idx loadComputedKey) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sb-1]) + if h, ok := obj.self.(*classFuncObject); ok { + vm.push(h.computedKeys[idx]) + vm.pc++ + return + } + panic(vm.r.NewTypeError("Compiler bug: unexpected target for loadComputedKey: %v", obj)) +} + +type initStaticElements struct { + privateFields, privateMethods []unistring.String +} + +func (i *initStaticElements) exec(vm *vm) { + cls := vm.stack[vm.sp-1] + staticInit := vm.r.toObject(vm.stack[vm.sp-3]) + vm.sp -= 2 + if h, ok := staticInit.self.(*classFuncObject); ok { + h._putProp("prototype", cls, true, true, true) // so that 'super' resolution work + h.privEnv = vm.privEnv + if h.privateEnvType != nil { + vm.privEnv.staticType = h.privateEnvType + vm.fillPrivateNamesMap(h.privateEnvType, i.privateFields, i.privateMethods) + } + h._initFields(vm.r.toObject(cls)) + vm.stack[vm.sp-1] = cls + + vm.pc++ + return + } + panic(vm.r.NewTypeError("Compiler bug: unexpected target for initStaticElements: %v", staticInit)) +} + +type definePrivateMethod struct { + idx int + targetOffset int +} + +func (d *definePrivateMethod) getPrivateMethods(vm *vm) []Value { + obj := vm.r.toObject(vm.stack[vm.sp-d.targetOffset]) + if cls, ok := obj.self.(*classFuncObject); ok { + return cls.privateMethods + } else { + panic(vm.r.NewTypeError("Compiler bug: wrong target type for definePrivateMethod: %T", obj.self)) + } +} + +func (d *definePrivateMethod) exec(vm *vm) { + methods := d.getPrivateMethods(vm) + methods[d.idx] = vm.stack[vm.sp-1] + vm.sp-- + vm.pc++ +} + +type definePrivateGetter struct { + definePrivateMethod +} + +func (d *definePrivateGetter) exec(vm *vm) { + methods := d.getPrivateMethods(vm) + val := vm.stack[vm.sp-1] + method := vm.r.toObject(val) + p, _ := methods[d.idx].(*valueProperty) + if p == nil { + p = &valueProperty{ + accessor: true, + } + methods[d.idx] = p + } + if p.getterFunc != nil { + vm.throw(vm.r.NewTypeError("Private getter has already been declared")) + return + } + p.getterFunc = method + vm.sp-- + vm.pc++ +} + +type definePrivateSetter struct { + definePrivateMethod +} + +func (d *definePrivateSetter) exec(vm *vm) { + methods := d.getPrivateMethods(vm) + val := vm.stack[vm.sp-1] + method := vm.r.toObject(val) + p, _ := methods[d.idx].(*valueProperty) + if p == nil { + p = &valueProperty{ + accessor: true, + } + methods[d.idx] = p + } + if p.setterFunc != nil { + vm.throw(vm.r.NewTypeError("Private setter has already been declared")) + return + } + p.setterFunc = method + vm.sp-- + vm.pc++ +} + +type definePrivateProp struct { + idx int +} + +func (d *definePrivateProp) exec(vm *vm) { + f := vm.r.toObject(vm.stack[vm.sb-1]).self.(*classFuncObject) + obj := vm.r.toObject(vm.stack[vm.sp-2]) + penv := obj.self.getPrivateEnv(f.privateEnvType, false) + penv.fields[d.idx] = vm.stack[vm.sp-1] + vm.sp-- + vm.pc++ +} + +type getPrivatePropRes resolvedPrivateName + +func (vm *vm) getPrivateType(level uint8, isStatic bool) *privateEnvType { + e := vm.privEnv + for i := uint8(0); i < level; i++ { + e = e.outer + } + if isStatic { + return e.staticType + } + return e.instanceType +} + +func (g *getPrivatePropRes) _get(base Value, vm *vm) Value { + return vm.getPrivateProp(base, g.name, vm.getPrivateType(g.level, g.isStatic), g.idx, g.isMethod) +} + +func (g *getPrivatePropRes) exec(vm *vm) { + vm.stack[vm.sp-1] = g._get(vm.stack[vm.sp-1], vm) + vm.pc++ +} + +type getPrivatePropId privateId + +func (g *getPrivatePropId) exec(vm *vm) { + vm.stack[vm.sp-1] = vm.getPrivateProp(vm.stack[vm.sp-1], g.name, g.typ, g.idx, g.isMethod) + vm.pc++ +} + +type getPrivatePropIdCallee privateId + +func (g *getPrivatePropIdCallee) exec(vm *vm) { + prop := vm.getPrivateProp(vm.stack[vm.sp-1], g.name, g.typ, g.idx, g.isMethod) + if prop == nil { + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: (*privateId)(g).string()}} + } + vm.push(prop) + + vm.pc++ +} + +func (vm *vm) getPrivateProp(base Value, name unistring.String, typ *privateEnvType, idx uint32, isMethod bool) Value { + obj := vm.r.toObject(base) + penv := obj.self.getPrivateEnv(typ, false) + var v Value + if penv != nil { + if isMethod { + v = penv.methods[idx] + } else { + v = penv.fields[idx] + if v == nil { + panic(vm.r.NewTypeError("Private member #%s is accessed before it is initialized", name)) + } + } + } else { + panic(vm.r.NewTypeError("Cannot read private member #%s from an object whose class did not declare it", name)) + } + if prop, ok := v.(*valueProperty); ok { + if prop.getterFunc == nil { + panic(vm.r.NewTypeError("'#%s' was defined without a getter", name)) + } + v = prop.get(obj) + } + return v +} + +type getPrivatePropResCallee getPrivatePropRes + +func (g *getPrivatePropResCallee) exec(vm *vm) { + prop := (*getPrivatePropRes)(g)._get(vm.stack[vm.sp-1], vm) + if prop == nil { + prop = memberUnresolved{valueUnresolved{r: vm.r, ref: (*resolvedPrivateName)(g).string()}} + } + vm.push(prop) + + vm.pc++ +} + +func (vm *vm) setPrivateProp(base Value, name unistring.String, typ *privateEnvType, idx uint32, isMethod bool, val Value) { + obj := vm.r.toObject(base) + penv := obj.self.getPrivateEnv(typ, false) + if penv != nil { + if isMethod { + v := penv.methods[idx] + if prop, ok := v.(*valueProperty); ok { + if prop.setterFunc != nil { + prop.set(base, val) + } else { + panic(vm.r.NewTypeError("Cannot assign to read only property '#%s'", name)) + } + } else { + panic(vm.r.NewTypeError("Private method '#%s' is not writable", name)) + } + } else { + ptr := &penv.fields[idx] + if *ptr == nil { + panic(vm.r.NewTypeError("Private member #%s is accessed before it is initialized", name)) + } + *ptr = val + } + } else { + panic(vm.r.NewTypeError("Cannot write private member #%s from an object whose class did not declare it", name)) + } +} + +func (vm *vm) exceptionFromValue(x any) *Exception { + var ex *Exception + switch x1 := x.(type) { + case *Object: + ex = &Exception{ + val: x1, + } + if er, ok := x1.self.(*errorObject); ok { + ex.stack = er.stack + } + case Value: + ex = &Exception{ + val: x1, + } + case *Exception: + ex = x1 + case typeError: + ex = &Exception{ + val: vm.r.NewTypeError(string(x1)), + } + case referenceError: + ex = &Exception{ + val: vm.r.newError(vm.r.getReferenceError(), string(x1)), + } + case rangeError: + ex = &Exception{ + val: vm.r.newError(vm.r.getRangeError(), string(x1)), + } + case syntaxError: + ex = &Exception{ + val: vm.r.newError(vm.r.getSyntaxError(), string(x1)), + } + default: + /* + if vm.prg != nil { + vm.prg.dumpCode(log.Printf) + } + log.Print("Stack: ", string(debug.Stack())) + panic(fmt.Errorf("Panic at %d: %v", vm.pc, x)) + */ + return nil + } + if ex.stack == nil { + ex.stack = vm.captureStack(make([]StackFrame, 0, len(vm.callStack)+1), 0) + } + return ex +} + +type setPrivatePropRes resolvedPrivateName + +func (p *setPrivatePropRes) _set(base Value, val Value, vm *vm) { + vm.setPrivateProp(base, p.name, vm.getPrivateType(p.level, p.isStatic), p.idx, p.isMethod, val) +} + +func (p *setPrivatePropRes) exec(vm *vm) { + v := vm.stack[vm.sp-1] + p._set(vm.stack[vm.sp-2], v, vm) + vm.stack[vm.sp-2] = v + vm.sp-- + vm.pc++ +} + +type setPrivatePropResP setPrivatePropRes + +func (p *setPrivatePropResP) exec(vm *vm) { + v := vm.stack[vm.sp-1] + (*setPrivatePropRes)(p)._set(vm.stack[vm.sp-2], v, vm) + vm.sp -= 2 + vm.pc++ +} + +type setPrivatePropId privateId + +func (p *setPrivatePropId) exec(vm *vm) { + v := vm.stack[vm.sp-1] + vm.setPrivateProp(vm.stack[vm.sp-2], p.name, p.typ, p.idx, p.isMethod, v) + vm.stack[vm.sp-2] = v + vm.sp-- + vm.pc++ +} + +type setPrivatePropIdP privateId + +func (p *setPrivatePropIdP) exec(vm *vm) { + v := vm.stack[vm.sp-1] + vm.setPrivateProp(vm.stack[vm.sp-2], p.name, p.typ, p.idx, p.isMethod, v) + vm.sp -= 2 + vm.pc++ +} + +type popPrivateEnv struct{} + +func (popPrivateEnv) exec(vm *vm) { + vm.privEnv = vm.privEnv.outer + vm.pc++ +} + +type privateInRes resolvedPrivateName + +func (i *privateInRes) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-1]) + pe := obj.self.getPrivateEnv(vm.getPrivateType(i.level, i.isStatic), false) + if pe != nil && (i.isMethod && pe.methods[i.idx] != nil || !i.isMethod && pe.fields[i.idx] != nil) { + vm.stack[vm.sp-1] = valueTrue + } else { + vm.stack[vm.sp-1] = valueFalse + } + vm.pc++ +} + +type privateInId privateId + +func (i *privateInId) exec(vm *vm) { + obj := vm.r.toObject(vm.stack[vm.sp-1]) + pe := obj.self.getPrivateEnv(i.typ, false) + if pe != nil && (i.isMethod && pe.methods[i.idx] != nil || !i.isMethod && pe.fields[i.idx] != nil) { + vm.stack[vm.sp-1] = valueTrue + } else { + vm.stack[vm.sp-1] = valueFalse + } + vm.pc++ +} + +type getPrivateRefRes resolvedPrivateName + +func (r *getPrivateRefRes) exec(vm *vm) { + vm.refStack = append(vm.refStack, &privateRefRes{ + base: vm.stack[vm.sp-1].ToObject(vm.r), + name: (*resolvedPrivateName)(r), + }) + vm.sp-- + vm.pc++ +} + +type getPrivateRefId privateId + +func (r *getPrivateRefId) exec(vm *vm) { + vm.refStack = append(vm.refStack, &privateRefId{ + base: vm.stack[vm.sp-1].ToObject(vm.r), + id: (*privateId)(r), + }) + vm.sp-- + vm.pc++ +} + +func (y *yieldMarker) exec(vm *vm) { + vm.pc = -vm.pc // this will terminate the run loop + vm.push(y) // marker so the caller knows it's a yield, not a return +} + +func (y *yieldMarker) String() string { + if y == yieldEmpty { + return "empty" + } + switch y.resultType { + case resultYield: + return "yield" + case resultYieldRes: + return "yieldRes" + case resultYieldDelegate: + return "yield*" + case resultYieldDelegateRes: + return "yield*Res" + case resultAwait: + return "await" + default: + return "unknown" + } +} diff --git a/pkg/xscript/engine/vm_test.go b/pkg/xscript/engine/vm_test.go new file mode 100644 index 0000000..6bc1669 --- /dev/null +++ b/pkg/xscript/engine/vm_test.go @@ -0,0 +1,281 @@ +package engine + +import ( + "testing" + + "pandax/pkg/xscript/engine/file" + "pandax/pkg/xscript/engine/parser" + "pandax/pkg/xscript/engine/unistring" +) + +func TestTaggedTemplateArgExport(t *testing.T) { + vm := New() + vm.Set("f", func(v Value) { + v.Export() + }) + vm.RunString("f`test`") +} + +func TestVM1(t *testing.T) { + r := &Runtime{} + r.init() + + vm := r.vm + + vm.prg = &Program{ + src: file.NewFile("dummy", "", 1), + values: []Value{valueInt(2), valueInt(3), asciiString("test")}, + code: []instruction{ + &bindGlobal{vars: []unistring.String{"v"}}, + newObject, + setGlobal("v"), + loadVal(2), + loadVal(1), + loadVal(0), + add, + setElem, + pop, + loadDynamic("v"), + }, + } + + vm.run() + + rv := vm.pop() + + if obj, ok := rv.(*Object); ok { + if v := obj.self.getStr("test", nil).ToInteger(); v != 5 { + t.Fatalf("Unexpected property value: %v", v) + } + } else { + t.Fatalf("Unexpected result: %v", rv) + } + +} + +func TestEvalVar(t *testing.T) { + const SCRIPT = ` + function test() { + var a; + return eval("var a = 'yes'; var z = 'no'; a;") === "yes" && a === "yes"; + } + test(); + ` + + testScript(SCRIPT, valueTrue, t) +} + +func TestResolveMixedStack1(t *testing.T) { + const SCRIPT = ` + function test(arg) { + var a = 1; + var scope = {}; + (function() {return arg})(); // move arguments to stash + with (scope) { + a++; // resolveMixedStack1 here + return a + arg; + } + } + test(40); + ` + + testScript(SCRIPT, valueInt(42), t) +} + +func TestNewArrayFromIterClosed(t *testing.T) { + const SCRIPT = ` + const [a, ...other] = []; + assert.sameValue(a, undefined); + assert(Array.isArray(other)); + assert.sameValue(other.length, 0); + ` + testScriptWithTestLib(SCRIPT, _undefined, t) +} + +func BenchmarkVmNOP2(b *testing.B) { + prg := []func(*vm){ + //loadVal(0).exec, + //loadVal(1).exec, + //add.exec, + jump(1).exec, + } + + r := &Runtime{} + r.init() + + vm := r.vm + vm.prg = &Program{ + values: []Value{intToValue(2), intToValue(3)}, + } + + for i := 0; i < b.N; i++ { + vm.pc = 0 + for !vm.halted() { + prg[vm.pc](vm) + } + //vm.sp-- + /*r := vm.pop() + if r.ToInteger() != 5 { + b.Fatalf("Unexpected result: %+v", r) + } + if vm.sp != 0 { + b.Fatalf("Unexpected sp: %d", vm.sp) + }*/ + } +} + +func BenchmarkVmNOP(b *testing.B) { + r := &Runtime{} + r.init() + + vm := r.vm + vm.prg = &Program{ + code: []instruction{ + jump(1), + //jump(1), + }, + } + + for i := 0; i < b.N; i++ { + vm.pc = 0 + vm.run() + } + +} + +func BenchmarkVm1(b *testing.B) { + r := &Runtime{} + r.init() + + vm := r.vm + + //ins1 := loadVal1(0) + //ins2 := loadVal1(1) + + vm.prg = &Program{ + values: []Value{valueInt(2), valueInt(3)}, + code: []instruction{ + loadVal(0), + loadVal(1), + add, + }, + } + + for i := 0; i < b.N; i++ { + vm.pc = 0 + vm.run() + r := vm.pop() + if r.ToInteger() != 5 { + b.Fatalf("Unexpected result: %+v", r) + } + if vm.sp != 0 { + b.Fatalf("Unexpected sp: %d", vm.sp) + } + } +} + +func BenchmarkFib(b *testing.B) { + const TEST_FIB = ` +function fib(n) { +if (n < 2) return n; +return fib(n - 2) + fib(n - 1); +} + +fib(35); +` + b.StopTimer() + prg, err := parser.ParseFile(nil, "test.js", TEST_FIB, 0) + if err != nil { + b.Fatal(err) + } + + c := newCompiler() + c.compile(prg, false, true, nil) + c.p.dumpCode(b.Logf) + + r := &Runtime{} + r.init() + + vm := r.vm + + var expectedResult Value = valueInt(9227465) + + b.StartTimer() + + vm.prg = c.p + vm.run() + v := vm.result + + b.Logf("stack size: %d", len(vm.stack)) + b.Logf("stashAllocs: %d", vm.stashAllocs) + + if !v.SameAs(expectedResult) { + b.Fatalf("Result: %+v, expected: %+v", v, expectedResult) + } + +} + +func BenchmarkEmptyLoop(b *testing.B) { + const SCRIPT = ` + function f() { + for (var i = 0; i < 100; i++) { + } + } + f() + ` + b.StopTimer() + vm := New() + prg := MustCompile("test.js", SCRIPT, false) + // prg.dumpCode(log.Printf) + b.StartTimer() + for i := 0; i < b.N; i++ { + vm.RunProgram(prg) + } +} + +func BenchmarkVMAdd(b *testing.B) { + vm := &vm{} + vm.stack = append(vm.stack, nil, nil) + vm.sp = len(vm.stack) + + var v1 Value = valueInt(3) + var v2 Value = valueInt(5) + + for i := 0; i < b.N; i++ { + vm.stack[0] = v1 + vm.stack[1] = v2 + add.exec(vm) + vm.sp++ + } +} + +func BenchmarkFuncCall(b *testing.B) { + const SCRIPT = ` + function f(a, b, c, d) { + } + ` + + b.StopTimer() + + vm := New() + prg := MustCompile("test.js", SCRIPT, false) + + vm.RunProgram(prg) + if f, ok := AssertFunction(vm.Get("f")); ok { + b.StartTimer() + for i := 0; i < b.N; i++ { + f(nil, nil, intToValue(1), intToValue(2), intToValue(3), intToValue(4), intToValue(5), intToValue(6)) + } + } else { + b.Fatal("f is not a function") + } +} + +func BenchmarkAssertInt(b *testing.B) { + v := intToValue(42) + for i := 0; i < b.N; i++ { + if i, ok := v.(valueInt); !ok || int64(i) != 42 { + b.Fatal() + } + } +} diff --git a/pkg/xscript/errors/errors.go b/pkg/xscript/errors/errors.go new file mode 100644 index 0000000..2c2ea68 --- /dev/null +++ b/pkg/xscript/errors/errors.go @@ -0,0 +1,70 @@ +package errors + +import ( + "fmt" + + "pandax/pkg/xscript/engine" +) + +const ( + ErrCodeInvalidArgType = "ERR_INVALID_ARG_TYPE" + ErrCodeInvalidThis = "ERR_INVALID_THIS" + ErrCodeMissingArgs = "ERR_MISSING_ARGS" +) + +func error_toString(call engine.FunctionCall, r *engine.Runtime) engine.Value { + this := call.This.ToObject(r) + var name, msg string + if n := this.Get("name"); n != nil && !engine.IsUndefined(n) { + name = n.String() + } else { + name = "Error" + } + if m := this.Get("message"); m != nil && !engine.IsUndefined(m) { + msg = m.String() + } + if code := this.Get("code"); code != nil && !engine.IsUndefined(code) { + if name != "" { + name += " " + } + name += "[" + code.String() + "]" + } + if msg != "" { + if name != "" { + name += ": " + } + name += msg + } + return r.ToValue(name) +} + +func addProps(r *engine.Runtime, e *engine.Object, code string) { + e.Set("code", code) + e.DefineDataProperty("toString", r.ToValue(error_toString), engine.FLAG_TRUE, engine.FLAG_TRUE, engine.FLAG_FALSE) +} + +func NewTypeError(r *engine.Runtime, code string, params ...any) *engine.Object { + e := r.NewTypeError(params...) + addProps(r, e, code) + return e +} + +func NewError(r *engine.Runtime, ctor *engine.Object, code string, args ...any) *engine.Object { + if ctor == nil { + ctor, _ = r.Get("Error").(*engine.Object) + } + if ctor == nil { + return nil + } + msg := "" + if len(args) > 0 { + f, _ := args[0].(string) + msg = fmt.Sprintf(f, args[1:]...) + } + o, err := r.New(ctor, r.ToValue(msg)) + if err != nil { + panic(err) + } + addProps(r, o, code) + return o +} diff --git a/pkg/xscript/extension/export.go b/pkg/xscript/extension/export.go new file mode 100644 index 0000000..fb4a92e --- /dev/null +++ b/pkg/xscript/extension/export.go @@ -0,0 +1,44 @@ +package extension + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "ex" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("randString", e.randStringBytesMask) + obj.Set("set", e.set) + obj.Set("getUUID", e.get_uuid) + obj.Set("flagSplit", e.flagSplit) + obj.Set("getIpGeo", e.get_ip_geo) + obj.Set("geoCalc", e.geo_calc) + obj.Set("hrtime", e.hrtime) + // o.Get("hrtime").ToObject(runtime).Set("bigint", p.hrtime_bigint) + obj.Set("getGeoLocation", e.get_geo_info) + obj.Set("parseUserAgent", e.ParseUA) + obj.Set("getPhoneInfo", e.get_phone_info) + obj.Set("verifyChinaIdCard", e.verifyChinaIdCard) + obj.Set("sendCode", e.sendCode) + obj.Set("verifyCode", e.verifyCode) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/extension/function.go b/pkg/xscript/extension/function.go new file mode 100644 index 0000000..a43a883 --- /dev/null +++ b/pkg/xscript/extension/function.go @@ -0,0 +1,444 @@ +package extension + +import ( + "errors" + "fmt" + "io" + "math/rand" + "strings" + "sync" + "time" + + "pandax/pkg/http_client" + "pandax/pkg/xscript/engine" + + "git.weixin.qq.com/__/vlan/lib/utils/http/ua" + "github.com/google/uuid" + "github.com/tidwall/gjson" + "golang.org/x/text/encoding/simplifiedchinese" + "golang.org/x/text/transform" +) + +const letterBytes = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" +const ( + letterIdxBits = 6 // 6 bits to represent a letter index + letterIdxMask = 1<= 0; { + if remain == 0 { + cache, remain = randSource.Int63(), letterIdxMax + } + if idx := int(cache & letterIdxMask); idx < len(letterBytes) { + b[i] = letterBytes[idx] + i-- + } + cache >>= letterIdxBits + remain-- + } + return e.runtime.ToValue(string(b)) +} + +func (e *Extension) flagSplit(call engine.FunctionCall) engine.Value { + input := call.Argument(0).String() + chunkSize := call.Argument(1).ToInteger() + flag := call.Argument(2).String() + if input == "" { + return e.runtime.ToValue(errors.New("input is required")) + } + if flag == "" { + flag = "-" + } + if len(flag) > 1 { + return e.runtime.ToValue(errors.New("flag must be a single character")) + } + if chunkSize <= 0 { + return e.runtime.ToValue(errors.New("chunkSize must be greater than 0")) + } + if int(chunkSize) > len(input) { + return e.runtime.ToValue(errors.New("chunkSize must be less than or equal to input length")) + } + + result := splitString(input, int(chunkSize)) + var builder strings.Builder + for i, chunk := range result { + builder.WriteString(chunk) + if i < len(result)-1 { + builder.WriteString(flag) + } + } + return e.runtime.ToValue(builder.String()) +} + +func (e *Extension) ParseUA(call engine.FunctionCall) engine.Value { + uaString := call.Argument(0).String() + uaString = strings.ReplaceAll(uaString, `"`, "") + ua := ua.New(uaString) + var deviceType string + if ua.Mobile() { + deviceType = "Mobile" + } else { + deviceType = "PC" + } + engine, ver := ua.Browser() + if strings.Contains(uaString, "WeChat") { + engine = "WeChat" + // 取MicroMessenger后面的版本号,截止到第一个( + versionStart := strings.Index(uaString, "MicroMessenger/") + len("MicroMessenger/") + versionEnd := strings.Index(uaString[versionStart:], "(") + ver = uaString[versionStart : versionStart+versionEnd] + } + + var deviceModel = ua.Model() + if strings.Contains(uaString, "iPhone") { + deviceType = "iPhone" + } + if strings.Contains(uaString, "iPad") { + deviceType = "iPad" + } + + uaString = fmt.Sprintf(`{"os": "%s", "deviceType": "%s", "deviceModel": "%s", "browser": "%s %s"}`, ua.OS(), deviceType, deviceModel, engine, ver) + return e.runtime.ToValue(uaString) +} + +// hrtime in nodejs returns arbitrary time used for measuring performance between intervals. +// this function uses Go's time.Now() +func (e *Extension) hrtime(t []int64) ([]int64, error) { + var seconds, nanoseconds int64 + if t != nil { + if len(t) != 2 { + return nil, fmt.Errorf("the value for time must be of length 2, received %d", len(t)) + } + seconds, nanoseconds = t[0], t[1] + } + + now := time.Now().UnixNano() - int64(seconds)*1e9 - nanoseconds + return []int64{now / 1e9, now % 1e9}, nil +} + +func (e *Extension) get_uuid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(uuid.New().String()) +} + +func (e *Extension) set(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1) + err := e.runtime.Set(key, value) + return e.runtime.ToValue(err == nil) +} + +// // this is commented out because it's supposed to return bigint, which goja doesn't currently support. +// func (p *Process) hrtime_bigint() int64 { +// return time.Now().UnixNano() +// } + +func (e *Extension) get_phone_info(call engine.FunctionCall) engine.Value { + model := call.Argument(0).String() + + if model == "" { + return e.runtime.ToValue("") + } + + info := make(map[string]string) // Initialize the info map + + if model == "iPhone" { + info["brand"] = "Apple" + info["model"] = "iPhone" + return e.runtime.ToValue(info) + } + + cli, _ := http_client.NewRequest().SetUrl("https://www.gsmchoice.com/js/searchy.xhtml?search=%s", model).Do() + if cli.GetStatusCode() != 200 { + return e.runtime.ToValue("") + } + + if strings.Contains(cli.GetBodyString(), "error") { + return e.runtime.ToValue("") + } + + info["brand"] = gjson.Get(cli.GetBodyString(), "0.brand").Str + info["model"] = gjson.Get(cli.GetBodyString(), "0.model").Str + return e.runtime.ToValue(info) +} + +// func (e *Extension) get_ip_info(call xe.FunctionCall) xe.Value { +// ip := call.Argument(0).String() +// if ip == "" { +// return e.runtime.ToValue("") +// } + +// apis := []string{ +// fmt.Sprintf("https://opendata.baidu.com/api.php?query=%s&resource_id=6006&oe=utf8", ip), +// fmt.Sprintf("https://api.vore.top/api/IPdata?ip=%s", ip), +// fmt.Sprintf("https://www.fkcoder.com/ip?ip=%s", ip), +// fmt.Sprintf("https://searchplugin.csdn.net/api/v1/ip/get?ip=%s", ip), +// fmt.Sprintf("https://api.vvhan.com/api/getIpInfo?ip=%s", ip), +// } + +// resultChan := make(chan string, 1) // 创建带缓冲的通道 + +// for _, api := range apis { +// go func(api string) { +// cli, err := http_client.NewRequest().SetUrl(api).Do() +// if err == nil { +// switch api { +// case apis[0]: +// if gjson.Get(cli.GetBodyString(), "status").Int() == 0 { +// if info := gjson.Get(cli.GetBodyString(), "data[0].location").Str; info != "" { +// resultChan <- info // 将非空结果发送到通道中 +// } +// } +// case apis[1]: +// if gjson.Get(cli.GetBodyString(), "code").Int() == 200 { +// info := fmt.Sprintf("%s%s %s", gjson.Get(cli.GetBodyString(), "ipdata.info1").Str, gjson.Get(cli.GetBodyString(), "ipdata.info2").Str, gjson.Get(cli.GetBodyString(), "ipdata.isp").Str) +// resultChan <- info +// } +// case apis[2]: +// if !gjson.Get(cli.GetBodyString(), "err").Bool() { +// info := fmt.Sprintf("%s%s%s %s", gjson.Get(cli.GetBodyString(), "country").Str, gjson.Get(cli.GetBodyString(), "province").Str, gjson.Get(cli.GetBodyString(), "city").Str, gjson.Get(cli.GetBodyString(), "isp").Str) +// resultChan <- info +// } +// case apis[3]: +// if gjson.Get(cli.GetBodyString(), "code").Int() == 200 { +// info := gjson.Get(cli.GetBodyString(), "data.address").Str +// resultChan <- info +// } +// case apis[4]: +// if gjson.Get(cli.GetBodyString(), "success").Bool() { +// info := fmt.Sprintf("%s%s%s %s", gjson.Get(cli.GetBodyString(), "info.country").Str, gjson.Get(cli.GetBodyString(), "info.prov").Str, gjson.Get(cli.GetBodyString(), "info.city").Str, gjson.Get(cli.GetBodyString(), "info.lsp").Str) +// resultChan <- info +// } +// } +// } +// }(api) +// } + +// select { +// case info := <-resultChan: // 从通道中接收结果 +// return e.runtime.ToValue(info) +// case <-time.After(500 * time.Millisecond): // 设置一个超时时间,防止无限等待 +// return e.runtime.ToValue("") +// } +// } + +func (e *Extension) get_ip_geo(call engine.FunctionCall) engine.Value { + ip := call.Argument(0).String() + + ipApi := fmt.Sprintf("https://api.map.baidu.com/location/ip?ak=1CgrT8hhsdHdVYoUQeFyr6oA&ip=%s&coor=bd09ll", ip) + cli, _ := http_client.NewRequest().SetUrl(ipApi).Do() + + lng := gjson.Get(cli.GetBodyString(), "content.point.x").Str + lat := gjson.Get(cli.GetBodyString(), "content.point.y").Str + + // fmt.Printf("%s,%s,%s,%s\n", cli.GetBodyString(), ip, lng, lat) + + geoApi := fmt.Sprintf("http://apis.map.qq.com/jsapi?qt=rgeoc&lnglat=%s,%s", lng, lat) + + cli, err := http_client.NewRequest().SetUrl(geoApi).Do() + if err != nil { + return nil + } + + var result = make(map[string]string) + if gjson.Get(cli.GetBodyString(), "status").Int() == 0 { + result["country"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.n").Str) + result["province"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.p").Str) + result["city"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.c").Str) + result["district"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.d").Str) + result["addr"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.1.address_name").Str) + result["all_addr"] = fmt.Sprintf("%s%s%s", result["province"], result["city"], result["addr"]) + } + + return e.runtime.ToValue(result) +} + +func (e *Extension) get_geo_info(call engine.FunctionCall) engine.Value { + lng := call.Argument(0).ToFloat() + lat := call.Argument(1).ToFloat() + + apis := []string{ + fmt.Sprintf("http://apis.map.qq.com/jsapi?qt=rgeoc&lnglat=%f,%f", lng, lat), + } + + resultChan := make(chan map[string]string, 1) // 创建带缓冲的通道 + for _, api := range apis { + go func(api string) { + cli, err := http_client.NewRequest().SetUrl(api).Do() + if err == nil { + switch api { + case apis[0]: + if gjson.Get(cli.GetBodyString(), "status").Int() == 0 { + if err := gjson.Get(cli.GetBodyString(), "info.err").Num; err == 0 { + var r = make(map[string]string) + r["country"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.n").Str) + r["province"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.p").Str) + r["city"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.c").Str) + r["district"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.d").Str) + r["addr"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.1.address_name").Str) + r["all_addr"] = fmt.Sprintf("%s%s%s%s", r["country"], r["province"], r["city"], r["addr"]) + resultChan <- r + } + } + // case apis[1]: + // if gjson.Get(cli.GetBodyString(), "code").Int() == 200 { + // info := fmt.Sprintf("%s%s %s", gjson.Get(cli.GetBodyString(), "ipdata.info1").Str, gjson.Get(cli.GetBodyString(), "ipdata.info2").Str, gjson.Get(cli.GetBodyString(), "ipdata.isp").Str) + // if info != "" { + // result = info + // wg.Done() + // } + // } + } + } + }(api) + } + + select { + case info := <-resultChan: // 从通道中接收结果 + return e.runtime.ToValue(info) + case <-time.After(500 * time.Millisecond): // 设置一个超时时间,防止无限等待 + return e.runtime.ToValue("") + } +} + +func (e *Extension) geo_calc(call engine.FunctionCall) engine.Value { + originLat := call.Argument(0).ToFloat() + originLng := call.Argument(1).ToFloat() + destLat := call.Argument(2).ToFloat() + destLng := call.Argument(3).ToFloat() + + cli, _ := http_client.NewRequest().SetUrl("https://api.map.baidu.com/directionlite/v1/driving?ak=LrsHHZ2OArsUFeqIG9hc6vf8pHe57ZQD&origin=%f,%f&destination=%f,%f", originLng, originLat, destLng, destLat).Do() + + return e.runtime.ToValue(gjson.Get(cli.GetBodyString(), "result.routes[0].distance").Int()) +} + +func (e *Extension) verifyChinaIdCard(call engine.FunctionCall) engine.Value { + name := call.Argument(0).String() + idNumber := call.Argument(1).String() + uid := call.Argument(2).String() + + if name == "" { + return e.runtime.ToValue(false) + } + if len(idNumber) != 18 { + return e.runtime.ToValue(false) + } + if uid == "" { + uid = "d571a611-d8a7-440e-8a32-295df3e221e2" + } + + apis := []string{ + "https://wuyi.xiaokangcq.com/api/userInfo/userInfoWrite/two", + // fmt.Sprintf("https://api.vore.top/api/IPdata?ip=%s", ip), + // fmt.Sprintf("https://www.fkcoder.com/ip?ip=%s", ip), + // fmt.Sprintf("https://searchplugin.csdn.net/api/v1/ip/get?ip=%s", ip), + // fmt.Sprintf("https://api.vvhan.com/api/getIpInfo?ip=%s", ip), + } + + var wg sync.WaitGroup + result := make(chan bool, len(apis)) + + for _, api := range apis { + wg.Add(1) + go func(api string) { + defer wg.Done() + cli := http_client.NewRequest().SetUrl(api) + switch api { + case apis[0]: + cli.Post().AddHeader("Content-Type", "application/json;charset=UTF-8").AddHeader("authorization", uid) + cli.SetBody(strings.NewReader(fmt.Sprintf(`{"userName":"%s","idCard":"%s"}`, name, idNumber))).Do() + if r := gjson.Get(cli.GetBodyString(), "status").Int() == 200; r { + result <- r + } + // case apis[1]: + // if gjson.Get(cli.GetBodyString(), "code").Int() == 200 { + // info := fmt.Sprintf("%s%s %s", gjson.Get(cli.GetBodyString(), "ipdata.info1").Str, gjson.Get(cli.GetBodyString(), "ipdata.info2").Str, gjson.Get(cli.GetBodyString(), "ipdata.isp").Str) + // if info != "" { + // result <- info + // } + // } + } + }(api) + } + + go func() { + wg.Wait() + close(result) + }() + + return e.runtime.ToValue(<-result) +} + +// func (e *Extension) sendToAdmin(call xe.FunctionCall) xe.Value { +// path := call.Argument(0).String() +// function := call.Argument(1).String() +// level := call.Argument(2).String() +// msg := call.Argument(3).String() + +// if msg == "" { +// return e.runtime.ToValue(false) +// } + +// cli, _ := http_client.NewRequest().Post().AddHeader("Content-Type", "application/x-www-form-urlencoded;charset=UTF-8"). +// SetUrl("https://api.netease.im/sms/sendtemplate.action?mobiles=[%s]&templateid=12111¶ms=['%s','%s','%s','%s']&needUp=true", path, function, level, msg). +// Do() + +// return e.runtime.ToValue(gjson.Get(cli.GetBodyString(), "code").Int() == 200) +// } + +func (e *Extension) sendCode(call engine.FunctionCall) engine.Value { + phone := call.Argument(0).String() + + if len(phone) != 11 { + return e.runtime.ToValue(false) + } + + cli, _ := http_client.NewRequest().SetUrl("https://wuyi.xiaokangcq.com/api/login/getCode?phone=%s&isH5=1", phone).Do() + + return e.runtime.ToValue(gjson.Get(cli.GetBodyString(), "code").Int() == 200) +} + +func (e *Extension) verifyCode(call engine.FunctionCall) engine.Value { + phone := call.Argument(0).String() + code := call.Argument(1).String() + + if len(phone) != 11 || len(code) != 4 { + return e.runtime.ToValue(false) + } + + cli, _ := http_client.NewRequest().Post().AddHeader("Content-Type", "application/json;charset=UTF-8"). + SetUrl("https://wuyi.xiaokangcq.com/api/login/signup-login"). + SetBody(strings.NewReader(fmt.Sprintf(`{"phone":"%s","code":"%s","phoneType":1,"isH5":1}`, phone, code))). + Do() + if gjson.Get(cli.GetBodyString(), "code").Int() == 200 { + return e.runtime.ToValue(gjson.Get(cli.GetBodyString(), "data.only").Str) + } + return e.runtime.ToValue(false) +} + +func splitString(input string, chunkSize int) []string { + var result []string + for i := 0; i < len(input); i += chunkSize { + end := i + chunkSize + if end > len(input) { + end = len(input) + } + result = append(result, input[i:end]) + } + return result +} + +func gb18030ToUtf8(s string) string { + reader := transform.NewReader(strings.NewReader(s), simplifiedchinese.GB18030.NewDecoder()) + d, e := io.ReadAll(reader) + if e != nil { + return "" + } + return string(d) +} diff --git a/pkg/xscript/extension/function_test.go b/pkg/xscript/extension/function_test.go new file mode 100644 index 0000000..a869799 --- /dev/null +++ b/pkg/xscript/extension/function_test.go @@ -0,0 +1,68 @@ +package extension + +import ( + "fmt" + "pandax/pkg/http_client" + "strings" + "testing" + + "git.weixin.qq.com/__/vlan/lib/utils/http/ua" + "github.com/google/uuid" + "github.com/tidwall/gjson" +) + +func TestGeo(t *testing.T) { + ip := "223.148.71.76" + + ipApi := fmt.Sprintf("https://api.map.baidu.com/location/ip?ak=1CgrT8hhsdHdVYoUQeFyr6oA&ip=%s&coor=bd09ll", ip) + cli, _ := http_client.NewRequest().SetUrl(ipApi).Do() + + lng := gjson.Get(cli.GetBodyString(), "content.point.x").Str + lat := gjson.Get(cli.GetBodyString(), "content.point.y").Str + + fmt.Printf("%s,%s,%s,%s\n", cli.GetBodyString(), ip, lng, lat) + + geoApi := fmt.Sprintf("http://apis.map.qq.com/jsapi?qt=rgeoc&lnglat=%s,%s", lng, lat) + + cli, err := http_client.NewRequest().SetUrl(geoApi).Do() + if err != nil { + return + } + + var result = make(map[string]string) + if gjson.Get(cli.GetBodyString(), "status").Int() == 0 { + result["country"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.n").Str) + result["province"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.p").Str) + result["city"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.c").Str) + result["district"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.0.d").Str) + result["addr"] = gb18030ToUtf8(gjson.Get(cli.GetBodyString(), "detail.results.1.address_name").Str) + result["all_addr"] = fmt.Sprintf("%s%s%s%s", result["country"], result["province"], result["city"], result["addr"]) + } + + fmt.Println(result) + +} +func TestUA(t *testing.T) { + uaString := "Mozilla/5.0 (Linux; Android 10; HJC-AN90 Build/HONORHJC-AN90; wv) AppleWebKit/537.36 (KHTML, like Gecko) Version/4.0 Chrome/88.0.4324.93 Mobile Safari/537.36;huanbei/1052701 NetType/WIFI SHApp/5.27.1 SHAppInner/1052701 OS/29 AppChannel/hb" + userAgent := ua.New(uaString) + var deviceType string + if userAgent.Mobile() { + deviceType = "Mobile" + } else { + deviceType = "PC" + } + engine, ver := userAgent.Browser() + if strings.Contains(uaString, "WeChat") { + engine = "WeChat" + // 取MicroMessenger后面的版本号,截止到第一个( + versionStart := strings.Index(uaString, "MicroMessenger/") + len("MicroMessenger/") + versionEnd := strings.Index(uaString[versionStart:], "(") + ver = uaString[versionStart : versionStart+versionEnd] + } + result := fmt.Sprintf("{os: %s, deviceType: %s, deviceModel: %s, browser: %s %s}", userAgent.OS(), deviceType, userAgent.Model(), engine, ver) + fmt.Println(result) +} + +func TestUUID(t *testing.T) { + fmt.Println(uuid.New().String()) +} diff --git a/pkg/xscript/fs/export.go b/pkg/xscript/fs/export.go new file mode 100644 index 0000000..4389848 --- /dev/null +++ b/pkg/xscript/fs/export.go @@ -0,0 +1,49 @@ +package fs + +import ( + "sync" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "fs" + +type Extension struct { + runtime *engine.Runtime + mu *sync.Mutex +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + mu: &sync.Mutex{}, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("write", e.write) + obj.Set("writeFrom", e.writeFrom) + obj.Set("append", e.append) + obj.Set("read", e.read) + obj.Set("cwd", e.cwd) + obj.Set("chdir", e.chdir) + obj.Set("delete", e.delete) + obj.Set("dir", e.dir) + obj.Set("mkdir", e.mkdir) + obj.Set("exists", e.exists) + obj.Set("isDir", e.isDir) + obj.Set("pathInfo", e.pathInfo) + obj.Set("userHomePath", e.userHomePath) + +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + mu: &sync.Mutex{}, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/fs/function.go b/pkg/xscript/fs/function.go new file mode 100644 index 0000000..907c3b7 --- /dev/null +++ b/pkg/xscript/fs/function.go @@ -0,0 +1,167 @@ +package fs + +import ( + "fmt" + "io" + "net" + "os" + "path/filepath" + + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" +) + +func (e *Extension) write(call engine.FunctionCall) engine.Value { + e.mu.Lock() + defer e.mu.Unlock() + + content := call.Argument(0).Export() + path := call.Argument(1).String() + + data, ok := content.([]byte) + if !ok { + str, ok := content.(string) + if !ok { + return e.runtime.ToValue(false) + } + data = []byte(str) + } + + os.MkdirAll(filepath.Dir(path), os.ModePerm) + + if err := os.WriteFile(path, data, os.ModePerm); err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(true) +} + +func (e *Extension) writeFrom(call engine.FunctionCall) engine.Value { + e.mu.Lock() + defer e.mu.Unlock() + + content := call.Argument(0).Export().(net.Conn) + path := call.Argument(1).String() + + os.MkdirAll(filepath.Dir(path), os.ModePerm) + + if bits, err := io.ReadAll(content); err != nil { + fmt.Println("os.writeFrom error", err.Error()) + return e.runtime.ToValue(false) + } else if err := os.WriteFile(path, bits, os.ModePerm); err != nil { + fmt.Println("os.writeFrom error", err.Error()) + return e.runtime.ToValue(false) + } + + return e.runtime.ToValue(true) +} + +func (e *Extension) append(call engine.FunctionCall) engine.Value { + e.mu.Lock() + defer e.mu.Unlock() + + content := call.Argument(0).Export() + path := call.Argument(1).String() + + data, ok := content.([]byte) + if !ok { + str, ok := content.(string) + if !ok { + return e.runtime.ToValue(false) + } + data = []byte(str) + } + + os.MkdirAll(filepath.Dir(path), os.ModePerm) + + if err := os.WriteFile(path, data, os.ModePerm); err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(true) +} + +func (e *Extension) read(call engine.FunctionCall) engine.Value { + e.mu.Lock() + defer e.mu.Unlock() + + path := call.Argument(0).String() + buff, err := os.ReadFile(path) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return buffer.Format(e.runtime, buff) +} + +func (e *Extension) delete(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + if err := os.Remove(path); err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) mkdir(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + if err := os.Mkdir(path, os.ModePerm); err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(true) +} + +func (e *Extension) dir(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + files, err := os.ReadDir(path) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(files) +} + +func (e *Extension) exists(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + if _, err := os.Stat(path); err != nil { + return e.runtime.ToValue(false) + } + return e.runtime.ToValue(true) +} + +func (e *Extension) isDir(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + fi, err := os.Stat(path) + if err != nil { + return e.runtime.ToValue(false) + } + return e.runtime.ToValue(fi.IsDir()) +} + +func (e *Extension) pathInfo(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + fi, err := os.Stat(path) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(fi) +} + +func (e *Extension) cwd(call engine.FunctionCall) engine.Value { + path, err := os.Getwd() + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(path) +} + +func (e *Extension) chdir(call engine.FunctionCall) engine.Value { + path := call.Argument(0).String() + if err := os.Chdir(path); err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(true) +} + +func (e *Extension) userHomePath(call engine.FunctionCall) engine.Value { + home, err := os.UserHomeDir() + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(home) +} diff --git a/pkg/xscript/jwt/export.go b/pkg/xscript/jwt/export.go new file mode 100644 index 0000000..ece6d7e --- /dev/null +++ b/pkg/xscript/jwt/export.go @@ -0,0 +1,33 @@ +package jwt + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "jwt" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("new", e.new) + obj.Set("refresh", e.refresh) + obj.Set("parse", e.parse) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/jwt/function.go b/pkg/xscript/jwt/function.go new file mode 100644 index 0000000..a514d61 --- /dev/null +++ b/pkg/xscript/jwt/function.go @@ -0,0 +1,67 @@ +package jwt + +import ( + "fmt" + "pandax/pkg/global" + "pandax/pkg/xscript/engine" + "time" + + "github.com/dgrijalva/jwt-go" +) + +func (e *Extension) new(call engine.FunctionCall) engine.Value { + userInfo := call.Argument(0).ToObject(e.runtime) + + j := NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + token, err := j.CreateToken(Claims{ + UserId: userInfo.Get("userId").ToInteger(), + UserName: userInfo.Get("user_name").String(), + RoleId: userInfo.Get("role_id").ToInteger(), + RoleKey: userInfo.Get("role_key").String(), + OrganizationId: userInfo.Get("organization_id").ToInteger(), + PostId: userInfo.Get("post_id").ToInteger(), + StandardClaims: jwt.StandardClaims{ + NotBefore: time.Now().Unix() - 1000, // 签名生效时间 + ExpiresAt: time.Now().Unix() + global.Conf.Jwt.ExpireTime, // 过期时间 7天 配置文件 + Issuer: global.Conf.Jwt.Key, // 签名的发行者 + }, + }) + + if err != nil { + fmt.Printf("生成Token失败: %s\n", err.Error()) + return engine.Undefined() + } + + return e.runtime.ToValue(map[string]any{ + "token": token, + "expire": time.Now().Unix() + global.Conf.Jwt.ExpireTime, + }) +} + +func (e *Extension) refresh(call engine.FunctionCall) engine.Value { + oldToken := call.Argument(0).String() + j := NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + newToken, err := j.RefreshToken(oldToken) + if err != nil { + fmt.Printf("刷新Token失败: %s\n", err.Error()) + return engine.Undefined() + } + + return e.runtime.ToValue(map[string]any{ + "token": newToken, + "expire": time.Now().Unix() + global.Conf.Jwt.ExpireTime, + }) +} + +func (e *Extension) parse(call engine.FunctionCall) engine.Value { + token := call.Argument(0).String() + + j := NewJWT("", []byte(global.Conf.Jwt.Key), jwt.SigningMethodHS256) + claims, err := j.ParseToken(token) + if err != nil { + fmt.Printf("解析Token失败: %s\n", err.Error()) + return engine.Undefined() + } + + return e.runtime.ToValue(claims) +} diff --git a/pkg/xscript/jwt/token.go b/pkg/xscript/jwt/token.go new file mode 100644 index 0000000..6dd1f75 --- /dev/null +++ b/pkg/xscript/jwt/token.go @@ -0,0 +1,128 @@ +package jwt + +import ( + "errors" + "fmt" + "strings" + "time" + + "github.com/dgrijalva/jwt-go" +) + +type Claims struct { + UserId int64 + TenantId int64 + OrganizationId int64 //组织Id + UserName string + RoleId int64 + RoleKey string + DeptId int64 + PostId int64 + jwt.StandardClaims +} + +type JWT struct { + SignedKeyID string + SignedKey []byte + SignedMethod jwt.SigningMethod +} + +func NewJWT(kid string, key []byte, method jwt.SigningMethod) *JWT { + return &JWT{ + SignedKeyID: kid, + SignedKey: key, + SignedMethod: method, + } +} + +// CreateToken 创建一个token +func (j *JWT) CreateToken(claims Claims) (string, error) { + token := jwt.NewWithClaims(jwt.SigningMethodHS256, &claims) + var key interface{} + if j.isEs() { + v, err := jwt.ParseECPrivateKeyFromPEM(j.SignedKey) + if err != nil { + return "", err + } + key = v + } else if j.isRsOrPS() { + v, err := jwt.ParseRSAPrivateKeyFromPEM(j.SignedKey) + if err != nil { + return "", err + } + key = v + } else if j.isHs() { + key = j.SignedKey + } else { + return "", errors.New("unsupported sign method") + } + return token.SignedString(key) +} + +// ParseToken 解析 token +func (j *JWT) ParseToken(tokenString string) (*Claims, error) { + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (i interface{}, e error) { + if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok { + return nil, fmt.Errorf("parse error") + } + return j.SignedKey, nil + }) + if err != nil { + if ve, ok := err.(*jwt.ValidationError); ok { + if ve.Errors&jwt.ValidationErrorMalformed != 0 { + return nil, fmt.Errorf("parse error") + } else if ve.Errors&jwt.ValidationErrorExpired != 0 { + // Token is expired + return nil, fmt.Errorf("token is expired") + } else if ve.Errors&jwt.ValidationErrorNotValidYet != 0 { + return nil, fmt.Errorf("token not active yet") + } else { + return nil, fmt.Errorf("token is expired") + } + } + } + if token != nil { + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + return claims, nil + } + return nil, fmt.Errorf("token is expired") + + } else { + return nil, fmt.Errorf("token is expired") + } + +} + +// 更新token +func (j *JWT) RefreshToken(tokenString string) (string, error) { + jwt.TimeFunc = func() time.Time { + return time.Unix(0, 0) + } + token, err := jwt.ParseWithClaims(tokenString, &Claims{}, func(token *jwt.Token) (interface{}, error) { + return j.SignedKey, nil + }) + if err != nil { + return "", err + } + + if claims, ok := token.Claims.(*Claims); ok && token.Valid { + jwt.TimeFunc = time.Now + claims.StandardClaims.ExpiresAt = time.Now().Unix() + 60*60*24*7 + return j.CreateToken(*claims) + } + return "", fmt.Errorf("parse error") +} + +func (a *JWT) isEs() bool { + return strings.HasPrefix(a.SignedMethod.Alg(), "ES") +} + +func (a *JWT) isRsOrPS() bool { + isRs := strings.HasPrefix(a.SignedMethod.Alg(), "RS") + isPs := strings.HasPrefix(a.SignedMethod.Alg(), "PS") + return isRs || isPs +} + +func (a *JWT) isHs() bool { + return strings.HasPrefix(a.SignedMethod.Alg(), "HS") +} diff --git a/pkg/xscript/net/export.go b/pkg/xscript/net/export.go new file mode 100644 index 0000000..4cae9c6 --- /dev/null +++ b/pkg/xscript/net/export.go @@ -0,0 +1,35 @@ +package net + +import ( + "sync" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "net" + +type Extension struct { + sync.Mutex + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("NewServer", e.NewServer) + obj.Set("NewClient", e.NewClient) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/net/function.go b/pkg/xscript/net/function.go new file mode 100644 index 0000000..f931663 --- /dev/null +++ b/pkg/xscript/net/function.go @@ -0,0 +1,136 @@ +package net + +import ( + "fmt" + "net" + "sync" + + "pandax/pkg/xscript/engine" +) + +type session struct { + conn net.Conn + mu sync.Mutex + onConnect func() + onReceive func([]byte) + onError func(error) + onClose func() +} + +func (e *Extension) NewClient(call *engine.FunctionCall) *session { + var protocol, address string + + switch len(call.Arguments) { + case 1: + protocol = "tcp" + address = call.Arguments[0].String() + case 2: + protocol = call.Arguments[0].String() + address = call.Arguments[1].String() + default: + fmt.Println("NewClient: invalid arguments") + return nil + } + + conn, err := net.Dial(protocol, address) + if err != nil { + return nil + } + + session := &session{ + conn: conn, + } + + go session.listen() + + return session +} + +func (e *Extension) NewServer(call *engine.FunctionCall) *session { + var protocol, address string + + switch len(call.Arguments) { + case 1: + protocol = "tcp" + address = call.Arguments[0].String() + case 2: + protocol = call.Arguments[0].String() + address = call.Arguments[1].String() + default: + fmt.Println("NewServer: invalid arguments") + return nil + } + + ln, err := net.Listen(protocol, address) + if err != nil { + return nil + } + + conn, err := ln.Accept() + if err != nil { + return nil + } + + session := &session{ + conn: conn, + } + + go session.listen() + + return session +} + +func (tcp *session) listen() { + if tcp.onConnect != nil { + tcp.onConnect() + } + + for { + buffer := make([]byte, 1024) + n, err := tcp.conn.Read(buffer) + if err != nil { + if tcp.onError != nil { + tcp.onError(err) + } + break + } + + if tcp.onReceive != nil { + tcp.onReceive(buffer[:n]) + } + } + + tcp.conn.Close() + + if tcp.onClose != nil { + tcp.onClose() + } +} + +func (tcp *session) OnConnect(callback func()) { + tcp.onConnect = callback +} + +func (tcp *session) OnReceive(callback func([]byte)) { + tcp.onReceive = callback +} + +func (tcp *session) OnError(callback func(error)) { + tcp.onError = callback +} + +func (tcp *session) OnClose(callback func()) { + tcp.onClose = callback +} + +func (tcp *session) Send(data []byte) error { + tcp.mu.Lock() + defer tcp.mu.Unlock() + + _, err := tcp.conn.Write(data) + if err != nil { + return err + } + + return nil +} diff --git a/pkg/xscript/os/export.go b/pkg/xscript/os/export.go new file mode 100644 index 0000000..4b3f8c4 --- /dev/null +++ b/pkg/xscript/os/export.go @@ -0,0 +1,52 @@ +package os + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "os" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("gc", e.gc) + obj.Set("exec", e.exec) + obj.Set("exit", e.exit) + obj.Set("kill", e.kill) + obj.Set("sleep", e.sleep) + obj.Set("execTo", e.execTo) + obj.Set("getPid", e.getPid) + obj.Set("getUid", e.getUid) + obj.Set("getGid", e.getGid) + obj.Set("getEnv", e.getEnv) + obj.Set("setEnv", e.setEnv) + obj.Set("getEuid", e.getEuid) + obj.Set("getEgid", e.getEgid) + obj.Set("getPPid", e.getPPid) + obj.Set("getArch", e.getArch) + obj.Set("unsetEnv", e.unsetEnv) + obj.Set("getGroups", e.getGroups) + obj.Set("getHostname", e.getHostname) + obj.Set("getPlatform", e.getPlatform) + obj.Set("exitWithCode", e.exitWithCode) + obj.Set("getMaxThread", e.getMaxThread) + obj.Set("setMaxThread", e.setMaxThread) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/os/function.go b/pkg/xscript/os/function.go new file mode 100644 index 0000000..a1e9dd2 --- /dev/null +++ b/pkg/xscript/os/function.go @@ -0,0 +1,172 @@ +package os + +import ( + "fmt" + "net" + "os" + "os/exec" + "runtime" + "runtime/debug" + "time" + + "pandax/pkg/xscript/engine" +) + +func (e *Extension) exec(call engine.FunctionCall) engine.Value { + var args []string + for i := range call.Arguments { + args = append(args, call.Arguments[i].String()) + } + var cmd *exec.Cmd + if len(args) == 1 { + cmd = exec.Command(args[0]) + } else { + cmd = exec.Command(args[0], args[1:]...) + } + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + output, err := cmd.Output() + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(string(output)) +} + +func (e *Extension) execTo(call engine.FunctionCall) engine.Value { + if len(call.Arguments) < 2 { + fmt.Println("os.execTo requires at least 2 args") + return nil + } + var args []string + for i := range call.Arguments[1:] { + args = append(args, call.Arguments[i].String()) + } + var cmd *exec.Cmd + if len(args) == 2 { + cmd = exec.Command(args[1]) + } else { + cmd = exec.Command(args[1], args[2:]...) + } + cmd.Stdin = call.Argument(0).Export().(net.Conn) + cmd.Stdout = call.Argument(0).Export().(net.Conn) + cmd.Stderr = call.Argument(0).Export().(net.Conn) + output, err := cmd.Output() + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(string(output)) +} + +func (e *Extension) getEnv(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Environ()) +} + +func (e *Extension) setEnv(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + value := call.Argument(1).String() + os.Setenv(key, value) + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) unsetEnv(call engine.FunctionCall) engine.Value { + key := call.Argument(0).String() + os.Unsetenv(key) + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) getHostname(call engine.FunctionCall) engine.Value { + name, err := os.Hostname() + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(name) +} + +func (e *Extension) getPlatform(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(runtime.GOOS) +} + +func (e *Extension) getArch(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(runtime.GOARCH) +} + +func (e *Extension) exit(call engine.FunctionCall) engine.Value { + os.Exit(0) + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) exitWithCode(call engine.FunctionCall) engine.Value { + code := call.Argument(0).ToInteger() + if code < 0 || code > 255 { + return e.runtime.ToValue(engine.Null()) + } + os.Exit(int(code)) + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) kill(call engine.FunctionCall) engine.Value { + pid := call.Argument(0).ToInteger() + if pid < 0 || pid > 65535 { + return e.runtime.ToValue(engine.Null()) + } + process, err := os.FindProcess(int(pid)) + if err != nil { + return e.runtime.ToValue(err.Error()) + } + if err := process.Kill(); err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(true) +} + +func (e *Extension) sleep(call engine.FunctionCall) engine.Value { + time.Sleep(time.Duration(call.Argument(0).ToInteger()) * time.Millisecond) + // <-time.After(time.Duration(call.Argument(0).ToInteger()) * time.Millisecond) + return e.runtime.ToValue(engine.Null()) +} + +func (e *Extension) gc(call engine.FunctionCall) engine.Value { + debug.FreeOSMemory() + return e.runtime.ToValue(true) +} + +func (e *Extension) setMaxThread(call engine.FunctionCall) engine.Value { + runtime.GOMAXPROCS(int(call.Argument(0).ToInteger())) + return e.runtime.ToValue(true) +} + +func (e *Extension) getMaxThread(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(runtime.GOMAXPROCS(0)) +} + +func (e *Extension) getPid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Getpid()) +} + +func (e *Extension) getPPid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Getppid()) +} + +func (e *Extension) getUid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Getuid()) +} + +func (e *Extension) getGid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Getgid()) +} + +func (e *Extension) getEuid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Geteuid()) +} + +func (e *Extension) getEgid(call engine.FunctionCall) engine.Value { + return e.runtime.ToValue(os.Getegid()) +} + +func (e *Extension) getGroups(call engine.FunctionCall) engine.Value { + groups, err := os.Getgroups() + if err != nil { + return e.runtime.ToValue(err.Error()) + } + return e.runtime.ToValue(groups) +} diff --git a/pkg/xscript/querystring/export.go b/pkg/xscript/querystring/export.go new file mode 100644 index 0000000..bca4948 --- /dev/null +++ b/pkg/xscript/querystring/export.go @@ -0,0 +1,34 @@ +package querystring + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "querystring" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("stringify", e.stringify) + obj.Set("parse", e.parse) + obj.Set("escape", e.escape) + obj.Set("unescape", e.unescape) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/querystring/function.go b/pkg/xscript/querystring/function.go new file mode 100644 index 0000000..77f7e7a --- /dev/null +++ b/pkg/xscript/querystring/function.go @@ -0,0 +1,47 @@ +package querystring + +import ( + "net/url" + + "pandax/pkg/xscript/engine" +) + +// stringify converts an object to a query string. +func (e *Extension) stringify(call engine.FunctionCall) engine.Value { + obj := call.Argument(0).Export().(map[string]interface{}) + values := url.Values{} + for key, value := range obj { + values.Add(key, value.(string)) + } + return e.runtime.ToValue(values.Encode()) +} + +// parse converts a query string to an object. +func (e *Extension) parse(call engine.FunctionCall) engine.Value { + str := call.Argument(0).String() + values, err := url.ParseQuery(str) + if err != nil { + panic(err) + } + obj := map[string]interface{}{} + for key, value := range values { + obj[key] = value[0] + } + return e.runtime.ToValue(obj) +} + +// escape escapes a string. +func (e *Extension) escape(call engine.FunctionCall) engine.Value { + str := call.Argument(0).String() + return e.runtime.ToValue(url.QueryEscape(str)) +} + +// unescape unescapes a string. +func (e *Extension) unescape(call engine.FunctionCall) engine.Value { + str := call.Argument(0).String() + value, err := url.QueryUnescape(str) + if err != nil { + panic(err) + } + return e.runtime.ToValue(value) +} diff --git a/pkg/xscript/request/client.go b/pkg/xscript/request/client.go new file mode 100644 index 0000000..541e480 --- /dev/null +++ b/pkg/xscript/request/client.go @@ -0,0 +1,410 @@ +package request + +import ( + "bufio" + "crypto/tls" + "fmt" + "io" + "net/http" + "net/url" + "os" + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" + "strings" + "time" +) + +// The Client type represents a client with a request, cookie, and result. +// @property Request - The `Request` property is a pointer to a `Request` struct. This struct likely +// contains information about the client's HTTP request, such as the method (GET, POST, etc.), URL, +// headers, and body. +// @property Cookie - The `Cookie` property is a pointer to an `http.Cookie` object. This object +// represents an HTTP cookie that is sent by the client to the server with each request. It contains +// information such as the name, value, expiration time, and other attributes of the cookie. The +// `Cookie` property +// @property {Result} Result - The `Result` property is a variable that stores the result of a client's +// request. It could be any data type that represents the response or outcome of the request, such as a +// string, struct, or custom data type. +type Client struct { + Request *Request + Cookie []*http.Cookie + Response Respone + Runtime *engine.Runtime +} + +// The Request type represents a HTTP request with various properties such as URL, method, data, +// headers, timeout, and proxy settings. +// @property {string} Url - The `Url` property is a string that represents the URL of the request. It +// specifies the location of the resource that the request is being made to. +// @property {string} Method - The HTTP method to be used for the request (e.g., GET, POST, PUT, +// DELETE, etc.). +// @property Data - The `Data` property is of type `io.Reader` and is used to provide the data to be +// sent in the request body. It can be any type that implements the `io.Reader` interface, such as a +// `bytes.Buffer`, `strings.Reader`, or a file reader. +// @property {string} ContentType - The ContentType property is used to specify the type of data being +// sent in the request body. It is typically used in conjunction with the Data property to set the +// content type header of the request. Common content types include "application/json", +// "application/xml", "application/x-www-form-urlencoded", etc. +// @property {string} Authorization - The "Authorization" property is used to specify the +// authentication credentials for the request. It is typically used to include a token or +// username/password combination to authenticate the request with the server. +// @property {string} UserAgent - The UserAgent property is used to specify the user agent string that +// will be sent in the HTTP request header. The user agent string identifies the client software and +// version that is making the request. It is often used by servers to determine how to handle the +// request or to provide customized content based on the client +// @property Header - The `Header` property is a map that contains additional headers to be included in +// the HTTP request. Each key-value pair in the map represents a header field and its corresponding +// value. These headers can be used to provide additional information or instructions to the server +// handling the request. +// @property Timeout - The Timeout property is of type time.Duration and represents the maximum amount +// of time allowed for the request to complete. It specifies the duration after which the request will +// be canceled if it hasn't completed. +// @property ProxyUrl - The `ProxyUrl` property is of type `url.URL` and represents the URL of the +// proxy server to be used for the request. The type `url.URL` is a struct that contains various +// properties such as `Scheme`, `Host`, `Path`, etc. In this case, the ` +type Request struct { + Url string `json:"url"` + Method string `json:"method"` + Data io.Reader `json:"-"` + ContentType string `json:"content_type"` + Authorization string `json:"authorization"` + UserAgent string `json:"user_agent"` + Headers map[string]string `json:"headers"` + // The proxy type is determined by the URL scheme. "http", + // "https", and "socks5" are supported. If the scheme is empty, + // + // If Proxy is nil or nil *URL, no proxy is used. + ProxyUrl url.URL `json:"proxy"` + + client http.Client + MaxIdleConns int `json:"max_idle_conns"` + MaxIdleConnsPerHost int `json:"max_idle_conns_per_host"` + IdleConnTimeout time.Duration `json:"idle_conn_timeout"` + + // The `StreamCallBack` field is a function that takes a pointer to an `io.ReadCloser` and returns an + // error. This function is used to handle streaming responses from the server. The `io.ReadCloser` is + // a type that represents a readable and closable stream of data. By providing a callback function, + // the client can specify how to handle the streamed response data. + StreamCallBack func(line *[]byte) error +} + +// The function creates a new HTTP client request with default values. +func NewRequest(vm *engine.Runtime) *Client { + return &Client{Request: &Request{Method: "GET", UserAgent: "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36 HttpClient", MaxIdleConns: 100, MaxIdleConnsPerHost: 100, IdleConnTimeout: 15 * time.Second}, Response: Respone{}, Runtime: vm} +} + +// The `Do()` function is a method of the `Client` struct. It is used to execute the HTTP request +// specified in the `Client` object and return the result. +func (c *Client) Do() (*Client, error) { + if !strings.Contains(c.Request.Url, "://") { + c.Request.Url = "http://" + c.Request.Url + } + request, err := http.NewRequest(c.Request.Method, c.Request.Url, c.Request.Data) + if err != nil { + fmt.Println(err) + return c, err + } + request.Header.Set("Content-Type", c.Request.ContentType) + request.Header.Set("Referer", c.Request.Url) + if c.Request.Authorization != "" { + request.Header.Set("Authorization", c.Request.Authorization) + } + if c.Request.UserAgent != "" { + request.Header.Set("User-Agent", c.Request.UserAgent) + } + for _, v := range c.Cookie { + request.AddCookie(v) + } + for k, v := range c.Request.Headers { + request.Header.Set(k, v) + } + + transport := &http.Transport{ + TLSClientConfig: &tls.Config{InsecureSkipVerify: true}, + MaxIdleConns: c.Request.MaxIdleConns, + MaxIdleConnsPerHost: c.Request.MaxIdleConnsPerHost, + IdleConnTimeout: c.Request.IdleConnTimeout, + } + if c.Request.ProxyUrl.String() != "" { + transport.Proxy = http.ProxyURL(&c.Request.ProxyUrl) + } + c.Request.client.Transport = transport + + res, err := c.Request.client.Do(request) + if err != nil { + fmt.Println(err) + return c, err + } + for _, v := range res.Cookies() { + c.AddCookie(v) + } + defer res.Body.Close() + c.Response.StatusCode = res.StatusCode + c.Response.Method = res.Request.Method + c.Response.URL = res.Request.URL.String() + c.Response.Path = res.Request.URL.Path + c.Response.Header = res.Header + c.Response.Proto = res.Proto + c.Response.ProtoMajor = res.ProtoMajor + c.Response.ContentLength = res.ContentLength + c.Response.Host = res.Request.Host + c.Response.RemoteAddr = res.Request.RemoteAddr + if c.Request.StreamCallBack != nil { + reader := bufio.NewReader(res.Body) + for { + line, err := reader.ReadBytes('\n') + if err != nil && err != io.EOF { + fmt.Println(err) + continue + } + if len(line) > 0 { + c.Request.StreamCallBack(&line) + } + if err == io.EOF { + break + } + } + } + + data, err := io.ReadAll(res.Body) + if err != nil { + fmt.Println(err) + return c, err + } + c.Response.Body = buffer.Format(c.Runtime, data) + return c, nil +} + +// Get sets the HTTP request method to "GET" and returns the client instance. +// +// No parameters. +// Returns the client instance. +func (c *Client) Get() *Client { + c.Request.Method = "GET" + return c +} + +// The `Post()` function is a method of the `Client` struct. It sets the HTTP request method to "POST" +// and returns the client instance. This method is used to specify that the client should make a POST +// request instead of a GET request. +func (c *Client) Post() *Client { + c.Request.Method = "POST" + return c +} + +// The `Put()` function is a method of the `Client` struct. It sets the HTTP request method to "PUT" +// and returns the client instance. This method is used to specify that the client should make a PUT +// request instead of a GET or POST request. +func (c *Client) Put() *Client { + c.Request.Method = "PUT" + return c +} + +// The `Delete()` function is a method of the `Client` struct. It sets the HTTP request method to +// "DELETE" and returns the client instance. This method is used to specify that the client should make +// a DELETE request instead of a GET or POST request. +func (c *Client) Delete() *Client { + c.Request.Method = "DELETE" + return c +} + +// The `SetUrl` function is a method of the `Client` struct. It is used to set the URL of the HTTP +// request. +func (c *Client) SetUrl(url ...any) *Client { + c.Request.Url = fmt.Sprintf(url[0].(string), url[1:]...) + return c +} + +// The `SetMethod` function is a method of the `Client` struct. It is used to set the HTTP request +// method for the client. It takes a `method` parameter, which is a string representing the desired +// HTTP method (e.g., "GET", "POST", "PUT", "DELETE", etc.). +func (c *Client) SetMethod(method string) *Client { + c.Request.Method = method + return c +} + +// The `SetContentType` function is a method of the `Client` struct. It is used to set the content type +// of the HTTP request. +func (c *Client) SetContentType(contentType string) *Client { + c.Request.ContentType = contentType + return c +} + +// The `SetBody` function is a method of the `Client` struct. It is used to set the body of the HTTP +// request. The `body` parameter is of type `io.Reader`, which means it can accept any type that +// implements the `io.Reader` interface, such as a `bytes.Buffer`, `strings.Reader`, or a file reader. +func (c *Client) SetBody(body io.Reader) *Client { + c.Request.Data = body + return c +} + +func (c *Client) SetStreamCallback(call func(line *[]byte) error) *Client { + if call != nil { + c.Request.StreamCallBack = call + } + return c +} + +// The `SetAuthorization` function is a method of the `Client` struct. It is used to set the +// authorization credentials for the HTTP request. The `credentials` parameter is a string that +// represents the authentication credentials, such as a token or username/password combination. +func (c *Client) SetAuthorization(credentials string) *Client { + if credentials != "" { + c.Request.Authorization = credentials + } + return c +} + +// The above code is defining a method called "AddHeader" for a struct type "Client". This method takes +// two parameters, "key" and "value", both of type string. It adds a header to the client by setting +// the key-value pair in the headers map of the client. The method returns a pointer to the client, +// allowing for method chaining. +func (c *Client) AddHeader(key, value string) *Client { + if c.Request.Headers == nil { + c.Request.Headers = make(map[string]string) + } + c.Request.Headers[key] = value + return c +} + +// The above code is defining a method called "AddCookie" for a struct type "Client" in the Go +// programming language. This method takes a pointer to an http.Cookie object as a parameter and +// returns a pointer to a Client object. The purpose of this method is to add a cookie to the client's +// cookie jar. +func (c *Client) AddCookie(cookie *http.Cookie) *Client { + c.Cookie = append(c.Cookie, cookie) + return c +} + +// The above code is defining a method called "SetProxy" for a struct type "Client" in the Go +// programming language. This method takes a parameter "proxyUrl" of type "url.URL" and returns a +// pointer to a "Client" object. The purpose of this method is to set the proxy URL for the client. +func (c *Client) SetProxy(proxyUrl url.URL) *Client { + c.Request.ProxyUrl = proxyUrl + return c +} + +// The above code is defining a method called `SetUserAgent` for a struct type `Client`. This method +// takes a string parameter `userAgent` and returns a pointer to the `Client` struct. The purpose of +// this method is to set the user agent for the client. +func (c *Client) SetUserAgent(userAgent string) *Client { + c.Request.UserAgent = userAgent + return c +} + +// The above code is defining a method called `SetBasicAuth` for a struct type `Client`. This method +// takes two parameters, `username` and `password`, both of type string. The method does not return +// anything (`*Client` is the receiver type). The purpose of this method is to set the basic +// authentication credentials (username and password) for the client. +func (c *Client) SetBasicAuth(username, password string) *Client { + c.Request.Authorization = "Basic " + username + ":" + password + return c +} + +// The above code is defining a method called "SetDigestAuth" for a struct type "Client". This method +// takes two parameters, "username" and "password", both of type string. The method does not return +// anything. +func (c *Client) SetDigestAuth(username, password string) *Client { + c.Request.Authorization = "Digest " + username + ":" + password + return c +} + +// The above code is defining a method called `SetBearerAuth` for a struct type `Client`. This method +// takes a string parameter `token` and returns a pointer to the `Client` struct. The purpose of this +// method is to set the `Authorization` header of the HTTP requests made by the client to use the +// provided `token` as a bearer token. +func (c *Client) SetBearerAuth(token string) *Client { + c.Request.Authorization = "Bearer " + token + return c +} + +// The `SetTimeout` function is a method of the `Client` struct. It is used to set the timeout duration +// for the HTTP request. The `timeout` parameter is of type `time.Duration`, which represents a +// duration of time. +func (c *Client) SetTimeout(timeout time.Duration) *Client { + c.Request.client.Timeout = timeout + return c +} + +// SetMaxIdleConns sets the maximum number of idle connections in the connection pool for the Client. +// +// maxIdleConns: The maximum number of idle connections. +// Returns: A pointer to the Client object. +func (c *Client) SetMaxIdleConns(maxIdleConns int) *Client { + c.Request.MaxIdleConns = maxIdleConns + return c +} + +// SetMaxIdleConnsPerHost sets the maximum number of idle connections per host for the client. +// +// maxIdleConnsPerHost: The maximum number of idle connections per host (int). +// Returns: The client itself (pointer to Client). +func (c *Client) SetMaxIdleConnsPerHost(maxIdleConnsPerHost int) *Client { + c.Request.MaxIdleConnsPerHost = maxIdleConnsPerHost + return c +} + +// SetIdleConnTimeout sets the idle connection timeout for the Client. +// +// idleConnTimeout: The duration after which idle connections are closed. +// Return: The updated Client object. +func (c *Client) SetIdleConnTimeout(idleConnTimeout time.Duration) *Client { + c.Request.IdleConnTimeout = idleConnTimeout + return c +} + +// The `GetStatusCode()` function is a method of the `Client` struct. It returns the HTTP status code +// of the response received from the server. It retrieves the status code from the `Result` property of +// the `Client` object and returns it as an integer. + +func (c *Client) GetStatusCode() int { + return c.Response.StatusCode +} + +// The above code is defining a method called "GetHeaders" for a struct type "Client". This method +// returns a map of strings to slices of strings. +func (c *Client) GetHeaders() map[string][]string { + return c.Response.Header +} + +// The above code is defining a method called GetHeader on a struct type called Client. This method +// takes a string parameter called key and returns a string. +func (c *Client) GetHeader(key string, index int) string { + return c.Response.Header[key][index] +} + +// The above code is defining a method called "GetCookie" for a struct type "Client". This method takes +// a string parameter called "key" and returns a string. +func (c *Client) GetCookie(key string) string { + for _, v := range c.Cookie { + if v.Name == key { + return v.Value + } + } + return "" +} + +// The `GetCookies()` function is a method of the `Client` struct. It returns a slice of pointers to +// `http.Cookie` objects. It retrieves the cookies from the `Cookie` property of the `Client` object +// and returns them. +func (c *Client) GetCookies() []*http.Cookie { + return c.Cookie +} + +// The `GetBody()` function is a method of the `Client` struct. It returns the response body of the +// HTTP request as a byte array. It retrieves the body from the `Result` property of the `Client` +// object and returns it. +func (c *Client) GetBody() engine.Value { + return c.Response.Body +} + +// The `SaveToFile` function is a method of the `Client` struct. It is used to save the response body +// of the HTTP request to a file on the local filesystem. +func (c *Client) SaveToFile(filepath string) (err error) { + // Write the body to file + err = os.WriteFile(filepath, c.GetBody().Export().([]byte), os.ModePerm) + if err != nil { + return err + } + return nil +} diff --git a/pkg/xscript/request/export.go b/pkg/xscript/request/export.go new file mode 100644 index 0000000..1fed306 --- /dev/null +++ b/pkg/xscript/request/export.go @@ -0,0 +1,33 @@ +package request + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "request" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("parse", e.parse) + obj.Set("toCURL", e.toCURL) + obj.Set("sent", e.sent) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/request/function.go b/pkg/xscript/request/function.go new file mode 100644 index 0000000..c931aa9 --- /dev/null +++ b/pkg/xscript/request/function.go @@ -0,0 +1,80 @@ +package request + +import ( + "bytes" + "encoding/json" + "fmt" + "net/url" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" + + "github.com/tidwall/gjson" +) + +func (e *Extension) parse(call engine.FunctionCall) engine.Value { + request, _ := call.Argument(0).ToObject(e.runtime).MarshalJSON() + client := NewRequest(e.runtime) + if err := json.Unmarshal(request, client.Request); err != nil { + return e.newInvalidValueError("request is invalid", string(request)) + } + if data := gjson.GetBytes(request, "data").Str; data != "" { + client.Request.Data = bytes.NewReader([]byte(data)) + } + if data := gjson.GetBytes(request, "proxy_url"); data.Exists() { + if proxyUrl, err := url.Parse(data.Str); err == nil { + client.Request.ProxyUrl = *proxyUrl + } + } + if _, err := client.Do(); err != nil { + return e.newInvalidValueError(err.Error(), string(request)) + } + return e.runtime.ToValue(client.Response) +} + +func (e *Extension) toCURL(call engine.FunctionCall) engine.Value { + request, _ := call.Argument(0).ToObject(e.runtime).MarshalJSON() + + cmd := fmt.Sprintf("curl -L -X %s '%s'", gjson.GetBytes(request, "method").Str, gjson.GetBytes(request, "url").Str) + + gjson.GetBytes(request, "headers").ForEach(func(key, value gjson.Result) bool { + if key.Str == "Accept-Encoding" { + return true + } + cmd += fmt.Sprintf(" -H '%s: %s'", key.Str, value.Str) + return true + }) + + if ct := gjson.GetBytes(request, "content_type").Str; ct != "" { + cmd += " -H 'Content-Type: " + ct + "'" + } + + if data := gjson.GetBytes(request, "data").Str; data != "" { + cmd += " -d '" + data + "'" + } + return e.runtime.ToValue(cmd) +} + +func (e *Extension) sent(call engine.FunctionCall) engine.Value { + request, _ := call.Argument(0).ToObject(e.runtime).MarshalJSON() + client := NewRequest(e.runtime) + if err := json.Unmarshal(request, client.Request); err != nil { + return e.newInvalidValueError("request is invalid", string(request)) + } + if data := gjson.GetBytes(request, "data"); data.Exists() { + client.Request.Data = bytes.NewBufferString(data.Str) + } + go func() { + if _, err := client.Do(); err != nil { + fmt.Println(err) + return + } + }() + return e.runtime.ToValue(true) +} + +func (e *Extension) newInvalidValueError(msg, input string) *engine.Object { + o := errors.NewTypeError(e.runtime, "ERR_INVALID_VALUE", msg, input) + o.Set("input", e.runtime.ToValue(input)) + return o +} diff --git a/pkg/xscript/request/respone.go b/pkg/xscript/request/respone.go new file mode 100644 index 0000000..e8457a6 --- /dev/null +++ b/pkg/xscript/request/respone.go @@ -0,0 +1,126 @@ +package request + +import ( + "pandax/pkg/xscript/engine" +) + +type Respone struct { + // Status specifies the HTTP status code + // returned by the server. + // + // For client requests, these fields are ignored. The + // HTTP client code always uses either StatusCode + // or Status. + StatusCode int `json:"status_code"` + // Method specifies the HTTP method (GET, POST, PUT, etc.). + // For client requests, an empty string means GET. + // + // Go's HTTP client does not support sending a request with + // the CONNECT method. See the documentation on Transport for + // details. + Method string `json:"method"` + + // URL specifies either the URI being requested (for server + // requests) or the URL to access (for client requests). + // + // For server requests, the URL is parsed from the URI + // supplied on the Request-Line as stored in RequestURI. For + // most requests, fields other than Path and RawQuery will be + // empty. (See RFC 7230, Section 5.3) + // + // For client requests, the URL's Host specifies the server to + // connect to, while the Request's Host field optionally + // specifies the Host header value to send in the HTTP + // request. + URL string `json:"url"` + + Path string // path (relative paths may omit leading slash) + + // The protocol version for incoming server requests. + // + // For client requests, these fields are ignored. The HTTP + // client code always uses either HTTP/1.1 or HTTP/2. + // See the docs on Transport for details. + Proto string `json:"proto"` // "HTTP/1.0" + ProtoMajor int `json:"proto_major"` // 1 + ProtoMinor int `json:"proro_minor"` // 0 + + // Header contains the request header fields either received + // by the server or to be sent by the client. + // + // If a server received a request with header lines, + // + // Host: example.com + // accept-encoding: gzip, deflate + // Accept-Language: en-us + // fOO: Bar + // foo: two + // + // then + // + // Header = map[string][]string{ + // "Accept-Encoding": {"gzip, deflate"}, + // "Accept-Language": {"en-us"}, + // "Foo": {"Bar", "two"}, + // } + // + // For incoming requests, the Host header is promoted to the + // Request.Host field and removed from the Header map. + // + // HTTP defines that header names are case-insensitive. The + // request parser implements this by using CanonicalHeaderKey, + // making the first character and any characters following a + // hyphen uppercase and the rest lowercase. + // + // For client requests, certain headers such as Content-Length + // and Connection are automatically written when needed and + // values in Header may be ignored. See the documentation + // for the Request.Write method. + Header map[string][]string `json:"headers"` + + // Body is the request's body. + // + // For client requests, a nil body means the request has no + // body, such as a GET request. + + Body engine.Value `json:"body"` + + // ContentLength records the length of the associated content. + // The value -1 indicates that the length is unknown. + // Values >= 0 indicate that the given number of bytes may + // be read from Body. + // + // For client requests, a value of 0 with a non-nil Body is + // also treated as unknown. + ContentLength int64 `json:"content_length"` + + // For server requests, Host specifies the host on which the + // URL is sought. For HTTP/1 (per RFC 7230, section 5.4), this + // is either the value of the "Host" header or the host name + // given in the URL itself. For HTTP/2, it is the value of the + // ":authority" pseudo-header field. + // It may be of the form "host:port". For international domain + // names, Host may be in Punycode or Unicode form. Use + // golang.org/x/net/idna to convert it to either format if + // needed. + // To prevent DNS rebinding attacks, server Handlers should + // validate that the Host header has a value for which the + // Handler considers itself authoritative. The included + // ServeMux supports patterns registered to particular host + // names and thus protects its registered Handlers. + // + // For client requests, Host optionally overrides the Host + // header to send. If empty, the Request.Write method uses + // the value of URL.Host. Host may contain an international + // domain name. + Host string `json:"host"` + + // RemoteAddr allows HTTP servers and other software to record + // the network address that sent the request, usually for + // logging. This field is not filled in by ReadRequest and + // has no defined format. The HTTP server in this package + // sets RemoteAddr to an "IP:port" address before invoking a + // handler. + // This field is ignored by the HTTP client. + RemoteAddr string `json:"remote_addr"` +} diff --git a/pkg/xscript/require/module.go b/pkg/xscript/require/module.go new file mode 100644 index 0000000..c8aa3cb --- /dev/null +++ b/pkg/xscript/require/module.go @@ -0,0 +1,246 @@ +package require + +import ( + "errors" + "io" + "io/fs" + "os" + "path" + "path/filepath" + "runtime" + "sync" + "syscall" + "text/template" + + js "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/engine/parser" +) + +type ModuleLoader func(*js.Runtime, *js.Object) + +// SourceLoader represents a function that returns a file data at a given path. +// The function should return ModuleFileDoesNotExistError if the file either doesn't exist or is a directory. +// This error will be ignored by the resolver and the search will continue. Any other errors will be propagated. +type SourceLoader func(path string) ([]byte, error) + +var ( + InvalidModuleError = errors.New("Invalid module") + IllegalModuleNameError = errors.New("Illegal module name") + NoSuchBuiltInModuleError = errors.New("No such built-in module") + ModuleFileDoesNotExistError = errors.New("module file does not exist") +) + +var native, builtin map[string]ModuleLoader + +// Registry contains a cache of compiled modules which can be used by multiple Runtimes +type Registry struct { + sync.Mutex + native map[string]ModuleLoader + compiled map[string]*js.Program + + srcLoader SourceLoader + globalFolders []string +} + +type RequireModule struct { + r *Registry + runtime *js.Runtime + modules map[string]*js.Object + nodeModules map[string]*js.Object +} + +func NewRegistry(opts ...Option) *Registry { + r := &Registry{} + + for _, opt := range opts { + opt(r) + } + + return r +} + +func NewRegistryWithLoader(srcLoader SourceLoader) *Registry { + return NewRegistry(WithLoader(srcLoader)) +} + +type Option func(*Registry) + +// WithLoader sets a function which will be called by the require() function in order to get a source code for a +// module at the given path. The same function will be used to get external source maps. +// Note, this only affects the modules loaded by the require() function. If you need to use it as a source map +// loader for code parsed in a different way (such as runtime.RunString() or eval()), use (*Runtime).SetParserOptions() +func WithLoader(srcLoader SourceLoader) Option { + return func(r *Registry) { + r.srcLoader = srcLoader + } +} + +// WithGlobalFolders appends the given paths to the registry's list of +// global folders to search if the requested module is not found +// elsewhere. By default, a registry's global folders list is empty. +// In the reference Node.js implementation, the default global folders +// list is $NODE_PATH, $HOME/.node_modules, $HOME/.node_libraries and +// $PREFIX/lib/node, see +// https://nodejs.org/api/modules.html#modules_loading_from_the_global_folders. +func WithGlobalFolders(globalFolders ...string) Option { + return func(r *Registry) { + r.globalFolders = globalFolders + } +} + +// Enable adds the require() function to the specified runtime. +func (r *Registry) Enable(runtime *js.Runtime) *RequireModule { + rrt := &RequireModule{ + r: r, + runtime: runtime, + modules: make(map[string]*js.Object), + nodeModules: make(map[string]*js.Object), + } + + runtime.Set("require", rrt.require) + return rrt +} + +func (r *Registry) RegisterNativeModule(name string, loader ModuleLoader) { + r.Lock() + defer r.Unlock() + + if r.native == nil { + r.native = make(map[string]ModuleLoader) + } + name = filepathClean(name) + r.native[name] = loader +} + +// DefaultSourceLoader is used if none was set (see WithLoader()). It simply loads files from the host's filesystem. +func DefaultSourceLoader(filename string) ([]byte, error) { + fp := filepath.FromSlash(filename) + f, err := os.Open(fp) + if err != nil { + if errors.Is(err, fs.ErrNotExist) { + err = ModuleFileDoesNotExistError + } else if runtime.GOOS == "windows" { + if errors.Is(err, syscall.Errno(0x7b)) { // ERROR_INVALID_NAME, The filename, directory name, or volume label syntax is incorrect. + err = ModuleFileDoesNotExistError + } + } + return nil, err + } + + defer f.Close() + // On some systems (e.g. plan9 and FreeBSD) it is possible to use the standard read() call on directories + // which means we cannot rely on read() returning an error, we have to do stat() instead. + if fi, err := f.Stat(); err == nil { + if fi.IsDir() { + return nil, ModuleFileDoesNotExistError + } + } else { + return nil, err + } + return io.ReadAll(f) +} + +func (r *Registry) getSource(p string) ([]byte, error) { + srcLoader := r.srcLoader + if srcLoader == nil { + srcLoader = DefaultSourceLoader + } + return srcLoader(p) +} + +func (r *Registry) getCompiledSource(p string) (*js.Program, error) { + r.Lock() + defer r.Unlock() + + prg := r.compiled[p] + if prg == nil { + buf, err := r.getSource(p) + if err != nil { + return nil, err + } + s := string(buf) + + if path.Ext(p) == ".json" { + s = "module.exports = JSON.parse('" + template.JSEscapeString(s) + "')" + } + + source := "(function(exports, require, module) {" + s + "\n})" + parsed, err := js.Parse(p, source, parser.WithSourceMapLoader(r.srcLoader)) + if err != nil { + return nil, err + } + prg, err = js.CompileAST(parsed, false) + if err == nil { + if r.compiled == nil { + r.compiled = make(map[string]*js.Program) + } + r.compiled[p] = prg + } + return prg, err + } + return prg, nil +} + +func (r *RequireModule) require(call js.FunctionCall) js.Value { + ret, err := r.Require(call.Argument(0).String()) + if err != nil { + if _, ok := err.(*js.Exception); !ok { + panic(r.runtime.NewGoError(err)) + } + panic(err) + } + return ret +} + +func filepathClean(p string) string { + return path.Clean(p) +} + +// Require can be used to import modules from Go source (similar to JS require() function). +func (r *RequireModule) Require(p string) (ret js.Value, err error) { + module, err := r.resolve(p) + if err != nil { + return + } + ret = module.Get("exports") + return +} + +func Require(runtime *js.Runtime, name string) js.Value { + if r, ok := js.AssertFunction(runtime.Get("require")); ok { + mod, err := r(js.Undefined(), runtime.ToValue(name)) + if err != nil { + panic(err) + } + return mod + } + panic(runtime.NewTypeError("Please enable require for this runtime using new(require.Registry).Enable(runtime)")) +} + +// RegisterNativeModule registers a module that isn't loaded through a SourceLoader, but rather through +// a provided ModuleLoader. Typically, this will be a module implemented in Go (although theoretically +// it can be anything, depending on the ModuleLoader implementation). +// Such modules take precedence over modules loaded through a SourceLoader, i.e. if a module name resolves as +// native, the native module is loaded, and the SourceLoader is not consulted. +// The binding is global and affects all instances of Registry. +// It should be called from a package init() function as it may not be used concurrently with require() calls. +// For registry-specific bindings see Registry.RegisterNativeModule. +func RegisterNativeModule(name string, loader ModuleLoader) { + if native == nil { + native = make(map[string]ModuleLoader) + } + name = filepathClean(name) + native[name] = loader +} + +// RegisterCoreModule registers a nodejs core module. If the name does not start with "node:", the module +// will also be loadable as "node:". Hence, for "builtin" modules (such as buffer, console, etc.) +// the name should not include the "node:" prefix, but for prefix-only core modules (such as "node:test") +// it should include the prefix. +func RegisterCoreModule(name string, loader ModuleLoader) { + if builtin == nil { + builtin = make(map[string]ModuleLoader) + } + name = filepathClean(name) + builtin[name] = loader +} diff --git a/pkg/xscript/require/module_test.go b/pkg/xscript/require/module_test.go new file mode 100644 index 0000000..255e978 --- /dev/null +++ b/pkg/xscript/require/module_test.go @@ -0,0 +1,534 @@ +package require + +import ( + "bytes" + "errors" + "fmt" + "io" + "os" + "path" + "path/filepath" + "runtime" + "testing" + + js "pandax/pkg/xscript/engine" +) + +func mapFileSystemSourceLoader(files map[string]string) SourceLoader { + return func(path string) ([]byte, error) { + s, ok := files[path] + if !ok { + return nil, ModuleFileDoesNotExistError + } + return []byte(s), nil + } +} + +func TestRequireNativeModule(t *testing.T) { + const SCRIPT = ` + var m = require("test/m"); + m.test(); + ` + + vm := js.New() + + registry := new(Registry) + registry.Enable(vm) + + RegisterNativeModule("test/m", func(runtime *js.Runtime, module *js.Object) { + o := module.Get("exports").(*js.Object) + o.Set("test", func(call js.FunctionCall) js.Value { + return runtime.ToValue("passed") + }) + }) + + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(vm.ToValue("passed")) { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestRegisterCoreModule(t *testing.T) { + vm := js.New() + + registry := new(Registry) + registry.Enable(vm) + + RegisterCoreModule("coremod", func(runtime *js.Runtime, module *js.Object) { + o := module.Get("exports").(*js.Object) + o.Set("test", func(call js.FunctionCall) js.Value { + return runtime.ToValue("passed") + }) + }) + + RegisterCoreModule("coremod1", func(runtime *js.Runtime, module *js.Object) { + o := module.Get("exports").(*js.Object) + o.Set("test", func(call js.FunctionCall) js.Value { + return runtime.ToValue("passed1") + }) + }) + + RegisterCoreModule("node:test1", func(runtime *js.Runtime, module *js.Object) { + o := module.Get("exports").(*js.Object) + o.Set("test", func(call js.FunctionCall) js.Value { + return runtime.ToValue("test1 passed") + }) + }) + + registry.RegisterNativeModule("bob", func(runtime *js.Runtime, module *js.Object) { + + }) + + _, err := vm.RunString(` + const m1 = require("coremod"); + const m2 = require("node:coremod"); + if (m1 !== m2) { + throw new Error("Modules are not equal"); + } + if (m1.test() !== "passed") { + throw new Error("m1.test() has failed"); + } + + const m3 = require("node:coremod1"); + const m4 = require("coremod1"); + if (m3 !== m4) { + throw new Error("Modules are not equal (1)"); + } + if (m3.test() !== "passed1") { + throw new Error("m3.test() has failed"); + } + + try { + require("node:bob"); + } catch (e) { + if (!e.message.includes("No such built-in module")) { + throw e; + } + } + require("bob"); + + try { + require("test1"); + throw new Error("Expected exception"); + } catch (e) { + if (!e.message.includes("Invalid module")) { + throw e; + } + } + + if (require("node:test1").test() !== "test1 passed") { + throw new Error("test1.test() has failed"); + } + `) + + if err != nil { + t.Fatal(err) + } +} + +func TestRequireRegistryNativeModule(t *testing.T) { + const SCRIPT = ` + var log = require("test/log"); + log.print('passed'); + ` + + logWithOutput := func(w io.Writer, prefix string) ModuleLoader { + return func(vm *js.Runtime, module *js.Object) { + o := module.Get("exports").(*js.Object) + o.Set("print", func(call js.FunctionCall) js.Value { + fmt.Fprint(w, prefix, call.Argument(0).String()) + return js.Undefined() + }) + } + } + + vm1 := js.New() + buf1 := &bytes.Buffer{} + + registry1 := new(Registry) + registry1.Enable(vm1) + + registry1.RegisterNativeModule("test/log", logWithOutput(buf1, "vm1 ")) + + vm2 := js.New() + buf2 := &bytes.Buffer{} + + registry2 := new(Registry) + registry2.Enable(vm2) + + registry2.RegisterNativeModule("test/log", logWithOutput(buf2, "vm2 ")) + + _, err := vm1.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + s := buf1.String() + if s != "vm1 passed" { + t.Fatalf("vm1: Unexpected result: %q", s) + } + + _, err = vm2.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + s = buf2.String() + if s != "vm2 passed" { + t.Fatalf("vm2: Unexpected result: %q", s) + } +} + +func TestRequire(t *testing.T) { + absPath, err := filepath.Abs("./testdata/m.js") + if err != nil { + t.Fatal(err) + } + + isWindows := runtime.GOOS == "windows" + + tests := []struct { + path string + ok bool + }{ + { + "./testdata/m.js", + true, + }, + { + "../require/testdata/m.js", + true, + }, + { + absPath, + true, + }, + { + `.\testdata\m.js`, + isWindows, + }, + { + `..\require\testdata\m.js`, + isWindows, + }, + } + + const SCRIPT = ` + var m = require(testPath); + m.test(); + ` + + for _, test := range tests { + t.Run(test.path, func(t *testing.T) { + vm := js.New() + vm.Set("testPath", test.path) + + registry := new(Registry) + registry.Enable(vm) + + v, err := vm.RunString(SCRIPT) + + ok := err == nil + + if ok != test.ok { + t.Fatalf("Expected ok to be %v, got %v (%v)", test.ok, ok, err) + } + + if !ok { + return + } + + if !v.StrictEquals(vm.ToValue("passed")) { + t.Fatalf("Unexpected result: %v", v) + } + }) + } +} + +func TestSourceLoader(t *testing.T) { + const SCRIPT = ` + var m = require("m.js"); + m.test(); + ` + + const MODULE = ` + function test() { + return "passed1"; + } + + exports.test = test; + ` + + vm := js.New() + + registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) { + if name == "m.js" { + return []byte(MODULE), nil + } + return nil, errors.New("Module does not exist") + })) + registry.Enable(vm) + + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(vm.ToValue("passed1")) { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestStrictModule(t *testing.T) { + const SCRIPT = ` + var m = require("m.js"); + m.test(); + ` + + const MODULE = ` + "use strict"; + + function test() { + var a = "passed1"; + eval("var a = 'not passed'"); + return a; + } + + exports.test = test; + ` + + vm := js.New() + + registry := NewRegistry(WithGlobalFolders("."), WithLoader(func(name string) ([]byte, error) { + if name == "m.js" { + return []byte(MODULE), nil + } + return nil, errors.New("Module does not exist") + })) + registry.Enable(vm) + + v, err := vm.RunString(SCRIPT) + if err != nil { + t.Fatal(err) + } + + if !v.StrictEquals(vm.ToValue("passed1")) { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestResolve(t *testing.T) { + testRequire := func(src, fpath string, globalFolders []string, fs map[string]string) (*js.Runtime, js.Value, error) { + vm := js.New() + r := NewRegistry(WithGlobalFolders(globalFolders...), WithLoader(mapFileSystemSourceLoader(fs))) + r.Enable(vm) + t.Logf("Require(%s)", fpath) + ret, err := vm.RunScript(path.Join(src, "test.js"), fmt.Sprintf("require('%s')", fpath)) + if err != nil { + return nil, nil, err + } + return vm, ret, nil + } + + globalFolders := []string{ + "/usr/lib/node_modules", + "/home/src/.node_modules", + } + + fs := map[string]string{ + "/home/src/app/app.js": `exports.name = "app"`, + "/home/src/app2/app2.json": `{"name": "app2"}`, + "/home/src/app3/index.js": `exports.name = "app3"`, + "/home/src/app4/index.json": `{"name": "app4"}`, + "/home/src/app5/package.json": `{"main": "app5.js"}`, + "/home/src/app5/app5.js": `exports.name = "app5"`, + "/home/src/app6/package.json": `{"main": "."}`, + "/home/src/app6/index.js": `exports.name = "app6"`, + "/home/src/app7/package.json": `{"main": "./a/b/c/file.js"}`, + "/home/src/app7/a/b/c/file.js": `exports.name = "app7"`, + "/usr/lib/node_modules/app8": `exports.name = "app8"`, + "/home/src/app9/app9.js": `exports.name = require('./a/file.js').name`, + "/home/src/app9/a/file.js": `exports.name = require('./b/file.js').name`, + "/home/src/app9/a/b/file.js": `exports.name = require('./c/file.js').name`, + "/home/src/app9/a/b/c/file.js": `exports.name = "app9"`, + "/home/src/.node_modules/app10": `exports.name = "app10"`, + "/home/src/app11/app11.js": `exports.name = require('d/file.js').name`, + "/home/src/app11/a/b/c/app11.js": `exports.name = require('d/file.js').name`, + "/home/src/app11/node_modules/d/file.js": `exports.name = "app11"`, + "/app12.js": `exports.name = require('a/file.js').name`, + "/node_modules/a/file.js": `exports.name = "app12"`, + "/app13/app13.js": `exports.name = require('b/file.js').name`, + "/node_modules/b/file.js": `exports.name = "app13"`, + "node_modules/app14/index.js": `exports.name = "app14"`, + "../node_modules/app15/index.js": `exports.name = "app15"`, + } + + for i, tc := range []struct { + src string + path string + ok bool + field string + value string + }{ + {"/home/src", "./app/app", true, "name", "app"}, + {"/home/src", "./app/app.js", true, "name", "app"}, + {"/home/src", "./app/bad.js", false, "", ""}, + {"/home/src", "./app2/app2", true, "name", "app2"}, + {"/home/src", "./app2/app2.json", true, "name", "app2"}, + {"/home/src", "./app/bad.json", false, "", ""}, + {"/home/src", "./app3", true, "name", "app3"}, + {"/home/src", "./appbad", false, "", ""}, + {"/home/src", "./app4", true, "name", "app4"}, + {"/home/src", "./appbad", false, "", ""}, + {"/home/src", "./app5", true, "name", "app5"}, + {"/home/src", "./app6", true, "name", "app6"}, + {"/home/src", "./app7", true, "name", "app7"}, + {"/home/src", "app8", true, "name", "app8"}, + {"/home/src", "./app9/app9", true, "name", "app9"}, + {"/home/src", "app10", true, "name", "app10"}, + {"/home/src", "./app11/app11.js", true, "name", "app11"}, + {"/home/src", "./app11/a/b/c/app11.js", true, "name", "app11"}, + {"/", "./app12", true, "name", "app12"}, + {"/", "./app13/app13", true, "name", "app13"}, + {".", "app14", true, "name", "app14"}, + {"..", "nonexistent", false, "", ""}, + } { + vm, mod, err := testRequire(tc.src, tc.path, globalFolders, fs) + if err != nil { + if tc.ok { + t.Errorf("%d: require() failed: %v", i, err) + } + continue + } else { + if !tc.ok { + t.Errorf("%d: expected to fail, but did not", i) + continue + } + } + f := mod.ToObject(vm).Get(tc.field) + if f == nil { + t.Errorf("%v: field %q not found", i, tc.field) + continue + } + value := f.String() + if value != tc.value { + t.Errorf("%v: got %q expected %q", i, value, tc.value) + } + } +} + +func TestRequireCycle(t *testing.T) { + vm := js.New() + r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{ + "a.js": `var b = require('./b.js'); exports.done = true;`, + "b.js": `var a = require('./a.js'); exports.done = true;`, + }))) + r.Enable(vm) + res, err := vm.RunString(` + var a = require('./a.js'); + var b = require('./b.js'); + a.done && b.done; + `) + if err != nil { + t.Fatal(err) + } + if v := res.Export(); v != true { + t.Fatalf("Unexpected result: %v", v) + } +} + +func TestErrorPropagation(t *testing.T) { + vm := js.New() + r := NewRegistry(WithLoader(mapFileSystemSourceLoader(map[string]string{ + "m.js": `throw 'test passed';`, + }))) + rr := r.Enable(vm) + _, err := rr.Require("./m") + if err == nil { + t.Fatal("Expected an error") + } + if ex, ok := err.(*js.Exception); ok { + if !ex.Value().StrictEquals(vm.ToValue("test passed")) { + t.Fatalf("Unexpected Exception: %v", ex) + } + } else { + t.Fatal(err) + } +} + +func TestSourceMapLoader(t *testing.T) { + vm := js.New() + r := NewRegistry(WithLoader(func(p string) ([]byte, error) { + switch p { + case "dir/m.js": + return []byte(`throw 'test passed'; +//# sourceMappingURL=m.js.map`), nil + case "dir/m.js.map": + return []byte(`{"version":3,"file":"m.js","sourceRoot":"","sources":["m.ts"],"names":[],"mappings":";AAAA"} +`), nil + } + return nil, ModuleFileDoesNotExistError + })) + + rr := r.Enable(vm) + _, err := rr.Require("./dir/m") + if err == nil { + t.Fatal("Expected an error") + } + if ex, ok := err.(*js.Exception); ok { + if !ex.Value().StrictEquals(vm.ToValue("test passed")) { + t.Fatalf("Unexpected Exception: %v", ex) + } + } else { + t.Fatal(err) + } +} + +func testsetup() (string, func(), error) { + name, err := os.MkdirTemp("", "goja-nodejs-require-test") + if err != nil { + return "", nil, err + } + return name, func() { + os.RemoveAll(name) + }, nil +} + +func TestDefaultModuleLoader(t *testing.T) { + workdir, teardown, err := testsetup() + if err != nil { + t.Fatal(err) + } + defer teardown() + + err = os.Chdir(workdir) + if err != nil { + t.Fatal(err) + } + err = os.Mkdir("module", 0755) + if err != nil { + t.Fatal(err) + } + err = os.WriteFile("module/index.js", []byte(`throw 'test passed';`), 0644) + if err != nil { + t.Fatal(err) + } + vm := js.New() + r := NewRegistry() + rr := r.Enable(vm) + _, err = rr.Require("./module") + if err == nil { + t.Fatal("Expected an error") + } + if ex, ok := err.(*js.Exception); ok { + if !ex.Value().StrictEquals(vm.ToValue("test passed")) { + t.Fatalf("Unexpected Exception: %v", ex) + } + } else { + t.Fatal(err) + } +} diff --git a/pkg/xscript/require/resolve.go b/pkg/xscript/require/resolve.go new file mode 100644 index 0000000..a9529a5 --- /dev/null +++ b/pkg/xscript/require/resolve.go @@ -0,0 +1,276 @@ +package require + +import ( + "encoding/json" + "errors" + "path" + "path/filepath" + "runtime" + "strings" + + js "pandax/pkg/xscript/engine" +) + +const NodePrefix = "node:" + +// NodeJS module search algorithm described by +// https://nodejs.org/api/modules.html#modules_all_together +func (r *RequireModule) resolve(modpath string) (module *js.Object, err error) { + origPath, modpath := modpath, filepathClean(modpath) + if modpath == "" { + return nil, IllegalModuleNameError + } + + var start string + err = nil + if path.IsAbs(origPath) { + start = "/" + } else { + start = r.getCurrentModulePath() + } + + p := path.Join(start, modpath) + if isFileOrDirectoryPath(origPath) { + if module = r.modules[p]; module != nil { + return + } + module, err = r.loadAsFileOrDirectory(p) + if err == nil && module != nil { + r.modules[p] = module + } + } else { + module, err = r.loadNative(origPath) + if err == nil { + return + } else { + if err == InvalidModuleError { + err = nil + } else { + return + } + } + if module = r.nodeModules[p]; module != nil { + return + } + module, err = r.loadNodeModules(modpath, start) + if err == nil && module != nil { + r.nodeModules[p] = module + } + } + + if module == nil && err == nil { + err = InvalidModuleError + } + return +} + +func (r *RequireModule) loadNative(path string) (*js.Object, error) { + module := r.modules[path] + if module != nil { + return module, nil + } + + var ldr ModuleLoader + if ldr = r.r.native[path]; ldr == nil { + ldr = native[path] + } + + var isBuiltIn, withPrefix bool + if ldr == nil { + ldr = builtin[path] + if ldr == nil && strings.HasPrefix(path, NodePrefix) { + ldr = builtin[path[len(NodePrefix):]] + if ldr == nil { + return nil, NoSuchBuiltInModuleError + } + withPrefix = true + } + isBuiltIn = true + } + + if ldr != nil { + module = r.createModuleObject() + r.modules[path] = module + if isBuiltIn { + if withPrefix { + r.modules[path[len(NodePrefix):]] = module + } else { + if !strings.HasPrefix(path, NodePrefix) { + r.modules[NodePrefix+path] = module + } + } + } + ldr(r.runtime, module) + return module, nil + } + + return nil, InvalidModuleError +} + +func (r *RequireModule) loadAsFileOrDirectory(path string) (module *js.Object, err error) { + if module, err = r.loadAsFile(path); module != nil || err != nil { + return + } + + return r.loadAsDirectory(path) +} + +func (r *RequireModule) loadAsFile(path string) (module *js.Object, err error) { + if module, err = r.loadModule(path); module != nil || err != nil { + return + } + + p := path + ".js" + if module, err = r.loadModule(p); module != nil || err != nil { + return + } + + p = path + ".json" + return r.loadModule(p) +} + +func (r *RequireModule) loadIndex(modpath string) (module *js.Object, err error) { + p := path.Join(modpath, "index.js") + if module, err = r.loadModule(p); module != nil || err != nil { + return + } + + p = path.Join(modpath, "index.json") + return r.loadModule(p) +} + +func (r *RequireModule) loadAsDirectory(modpath string) (module *js.Object, err error) { + p := path.Join(modpath, "package.json") + buf, err := r.r.getSource(p) + if err != nil { + return r.loadIndex(modpath) + } + var pkg struct { + Main string + } + err = json.Unmarshal(buf, &pkg) + if err != nil || len(pkg.Main) == 0 { + return r.loadIndex(modpath) + } + + m := path.Join(modpath, pkg.Main) + if module, err = r.loadAsFile(m); module != nil || err != nil { + return + } + + return r.loadIndex(m) +} + +func (r *RequireModule) loadNodeModule(modpath, start string) (*js.Object, error) { + return r.loadAsFileOrDirectory(path.Join(start, modpath)) +} + +func (r *RequireModule) loadNodeModules(modpath, start string) (module *js.Object, err error) { + for _, dir := range r.r.globalFolders { + if module, err = r.loadNodeModule(modpath, dir); module != nil || err != nil { + return + } + } + for { + var p string + if path.Base(start) != "node_modules" { + p = path.Join(start, "node_modules") + } else { + p = start + } + if module, err = r.loadNodeModule(modpath, p); module != nil || err != nil { + return + } + if start == ".." { // Dir('..') is '.' + break + } + parent := path.Dir(start) + if parent == start { + break + } + start = parent + } + + return +} + +func (r *RequireModule) getCurrentModulePath() string { + var buf [2]js.StackFrame + frames := r.runtime.CaptureCallStack(2, buf[:0]) + if len(frames) < 2 { + return "." + } + return path.Dir(frames[1].SrcName()) +} + +func (r *RequireModule) createModuleObject() *js.Object { + module := r.runtime.NewObject() + module.Set("exports", r.runtime.NewObject()) + return module +} + +func (r *RequireModule) loadModule(path string) (*js.Object, error) { + module := r.modules[path] + if module == nil { + module = r.createModuleObject() + r.modules[path] = module + err := r.loadModuleFile(path, module) + if err != nil { + module = nil + delete(r.modules, path) + if errors.Is(err, ModuleFileDoesNotExistError) { + err = nil + } + } + return module, err + } + return module, nil +} + +func (r *RequireModule) loadModuleFile(path string, jsModule *js.Object) error { + + prg, err := r.r.getCompiledSource(path) + + if err != nil { + return err + } + + f, err := r.runtime.RunProgram(prg) + if err != nil { + return err + } + + if call, ok := js.AssertFunction(f); ok { + jsExports := jsModule.Get("exports") + jsRequire := r.runtime.Get("require") + + // Run the module source, with "jsExports" as "this", + // "jsExports" as the "exports" variable, "jsRequire" + // as the "require" variable and "jsModule" as the + // "module" variable (Nodejs capable). + _, err = call(jsExports, jsExports, jsRequire, jsModule) + if err != nil { + return err + } + } else { + return InvalidModuleError + } + + return nil +} + +func isFileOrDirectoryPath(path string) bool { + result := path == "." || path == ".." || + strings.HasPrefix(path, "/") || + strings.HasPrefix(path, "./") || + strings.HasPrefix(path, "../") + + if runtime.GOOS == "windows" { + result = result || + strings.HasPrefix(path, `.\`) || + strings.HasPrefix(path, `..\`) || + filepath.IsAbs(path) + } + + return result +} diff --git a/pkg/xscript/require/testdata/m.js b/pkg/xscript/require/testdata/m.js new file mode 100644 index 0000000..97d9995 --- /dev/null +++ b/pkg/xscript/require/testdata/m.js @@ -0,0 +1,7 @@ +function test() { + return "passed"; +} + +module.exports = { + test: test +} diff --git a/pkg/xscript/runtime.go b/pkg/xscript/runtime.go new file mode 100644 index 0000000..2c5852a --- /dev/null +++ b/pkg/xscript/runtime.go @@ -0,0 +1,218 @@ +package xscript + +import ( + "fmt" + "os" + "pandax/pkg/http_client" + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/console" + "pandax/pkg/xscript/crypto" + "pandax/pkg/xscript/database/cache" + "pandax/pkg/xscript/database/mongo" + "pandax/pkg/xscript/database/redis" + "pandax/pkg/xscript/database/sql" + "pandax/pkg/xscript/encoding" + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/extension" + "pandax/pkg/xscript/fs" + "pandax/pkg/xscript/net" + jos "pandax/pkg/xscript/os" + "pandax/pkg/xscript/querystring" + "pandax/pkg/xscript/request" + "pandax/pkg/xscript/require" + jsUrl "pandax/pkg/xscript/url" + "pandax/pkg/xscript/ws" + "pandax/pkg/xscript/zip" + "path" + "path/filepath" + "regexp" + "strings" + "sync" + "time" + + "github.com/sirupsen/logrus" +) + +type Engine struct { + l *logrus.Logger + ThreadPool *sync.Map +} + +func New() *Engine { + return &Engine{ + l: logrus.New(), + ThreadPool: &sync.Map{}, + } +} + +func NewRuntime() *engine.Runtime { + vm := engine.New() + dir, _ := os.Getwd() + vm.Set("runtimePath", dir) + + vm.SetFieldNameMapper(engine.TagFieldNameMapper("json", true)) + registry := new(require.Registry) // this can be shared by multiple runtimes + + // 创建一个通道用于控制协程的执行 + pauseChan := make(chan struct{}) + + // 注册pause函数到Goja的运行时环境中 + vm.Set("pause", func(call engine.FunctionCall) engine.Value { + // 向通道发送信号,挂起当前协程 + pauseChan <- struct{}{} + return engine.Null() + }) + + // 定义外部库 + querystring.Enable(vm) + extension.Enable(vm) + registry.Enable(vm) + encoding.Enable(vm) + console.Enable(vm) + request.Enable(vm) + buffer.Enable(vm) + crypto.Enable(vm) + jsUrl.Enable(vm) + cache.Enable(vm) + redis.Enable(vm) + mongo.Enable(vm) + sql.Enable(vm) + net.Enable(vm) + jos.Enable(vm) + zip.Enable(vm) + fs.Enable(vm) + ws.Enable(vm) + return vm +} + +func loadScript(filepath string) (string, error) { + scriptData, err := os.ReadFile(filepath) + if err != nil { + return "", err + } + return string(scriptData), nil +} + +func getScriptFromURL(url, jsPath string) error { + // 判断文件是否存在,如果存在取修改时间,若修改时间小于1小时则返回nil + if _, err := os.Stat(jsPath); err == nil { + fileInfo, err := os.Stat(jsPath) + if err == nil { + if time.Since(fileInfo.ModTime()) < time.Hour { + return nil + } + } + } + + _, err := http_client.DefaultClient.SetUrl(url).Do() + if err != nil { + return err + } + + http_client.DefaultClient.SaveToFile(jsPath) + + return nil +} + +func removeComments(scriptData string) string { + // 删除单行注释 + commentRegExp := regexp.MustCompile(`(^| )\/\/.*`) + scriptData = commentRegExp.ReplaceAllString(scriptData, "") + + // 删除多行注释 + multilineCommentRegExp := regexp.MustCompile(`\/\*(.|\n)*?\*\/\n`) + scriptData = multilineCommentRegExp.ReplaceAllString(scriptData, "") + + // 删除HTML注释 + commentRegex := regexp.MustCompile(``) + scriptData = commentRegex.ReplaceAllString(scriptData, "") + + return scriptData +} + +func loadAndCheckScript(jsPath, secret string) (string, error) { + // 加载脚本内容 + scriptData, err := loadScript(jsPath) + if err != nil { + return "", err + } + + jsPath = filepath.Dir(jsPath) + + // 删除注释 + scriptData = removeComments(scriptData) + + requireRegExp := regexp.MustCompile(`require\(["']([^"']+)["']\)`) + requireMatches := requireRegExp.FindAllStringSubmatch(scriptData, -1) + + jsPath += "/moudles" + + var filename string + filenameRegExp := regexp.MustCompile(`\?.*`) + for _, match := range requireMatches { + if !strings.HasPrefix(match[1], "//") { + continue + } + + // 如果jsPath不存在则创建 + if _, err := os.Stat(jsPath); os.IsNotExist(err) { + os.MkdirAll(jsPath, os.ModePerm) + } + + url := strings.Replace(match[1], "//", "", 1) + parts := strings.Split(url, "/") + filename := path.Base(parts[len(parts)-1]) + filename = filenameRegExp.ReplaceAllString(filename, "") + filename = filepath.Join(jsPath, filename) + + err = getScriptFromURL(url, filename) + if err != nil { + fmt.Printf("无法从url获取脚本: %s\n", url) + fmt.Printf("尝试使用本地缓存脚本文件: %s\n", filename) + } + + // 合并requireScript到scriptData变量中 + scriptData = strings.ReplaceAll(scriptData, match[1], filename) + } + + useRegExp := regexp.MustCompile(`(?m)(^|^\s+)'use\s+(.*?.js)'`) + useMatches := useRegExp.FindAllStringSubmatch(scriptData, -1) + + for _, match := range useMatches { + filename = match[2] + if strings.HasPrefix(filename, "//") { + // 如果jsPath不存在则创建 + if _, err := os.Stat(jsPath); os.IsNotExist(err) { + os.MkdirAll(jsPath, os.ModePerm) + } + + url := strings.Replace(filename, "//", "", 1) + parts := strings.Split(url, "/") + filename := path.Base(parts[len(parts)-1]) + filename = filenameRegExp.ReplaceAllString(filename, "") + filename = filepath.Join(jsPath, filename) + + err = getScriptFromURL(url, filename) + if err != nil { + fmt.Printf("无法从url获取脚本: %s\n", url) + fmt.Printf("尝试使用本地缓存脚本文件: %s\n", filename) + } + } + // 读取文件内容 + scriptContent, err := loadAndCheckScript(filename, secret) + if err != nil { + fmt.Printf("脚本文件读取失败: %s\n", filename) + return "", err + } + + // 合并requireScript到scriptData变量中 + scriptData = strings.ReplaceAll(scriptData, match[0], fmt.Sprintf("\n%s\n", string(scriptContent))) + } + + // 删除注释 + scriptData = removeComments(scriptData) + + // fmt.Println(fmt.Sprintf("合并后的脚本: %s\n", scriptData)) + + return scriptData, nil +} diff --git a/pkg/xscript/script.go b/pkg/xscript/script.go new file mode 100644 index 0000000..29ea36d --- /dev/null +++ b/pkg/xscript/script.go @@ -0,0 +1,51 @@ +package xscript + +import ( + "fmt" + "pandax/pkg/xscript/buffer" + "pandax/pkg/xscript/engine" + "path/filepath" + "strings" +) + +func (e *Engine) ExecuteScriptWithSecret(path, secret string, args []string) engine.Value { + functionScript, err := loadAndCheckScript(path, secret) + if strings.Contains(functionScript, " main ") || strings.Contains(functionScript, " main(") { + functionScript = fmt.Sprintf("%s\n%s()", functionScript, "main") + } + + if err != nil { + return engine.Null() + } + vm := NewRuntime() + vm.Set("rootPath", filepath.Dir(path)) + vm.Set("scriptPath", path) + vm.Set("args", args) + + // 创建一个通道用于控制协程的执行 + pauseChan := make(chan struct{}) + + // 注册pause函数到Goja的运行时环境中 + vm.Set("pause", func(call engine.FunctionCall) engine.Value { + // 向通道发送信号,挂起当前协程 + pauseChan <- struct{}{} + return engine.Undefined() + }) + + e.responeCall(vm, path) + result, err := vm.RunScript(path, functionScript) + if err != nil { + // e.l.Echo().WithError(err).Warnf("Failed to preload script %s", path) + e.l.WithError(err).Warnf("Failed to preload script %s", path) + return engine.Null() + } + + // 从通道中接收信号,以恢复执行 + <-pauseChan + + return buffer.Format(vm, result.String()) +} + +func (e *Engine) ExecuteScript(path string, args []string) engine.Value { + return e.ExecuteScriptWithSecret(path, "", args) +} diff --git a/pkg/xscript/url/escape.go b/pkg/xscript/url/escape.go new file mode 100644 index 0000000..3d288c2 --- /dev/null +++ b/pkg/xscript/url/escape.go @@ -0,0 +1,134 @@ +package url + +import "strings" + +var tblEscapeURLQuery = [128]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 1, 0, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, +} + +var tblEscapeURLQueryParam = [128]byte{ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 1, + 0, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, +} + +// The code below is mostly borrowed from the standard Go url package + +const upperhex = "0123456789ABCDEF" + +func ishex(c byte) bool { + switch { + case '0' <= c && c <= '9': + return true + case 'a' <= c && c <= 'f': + return true + case 'A' <= c && c <= 'F': + return true + } + return false +} + +func unhex(c byte) byte { + switch { + case '0' <= c && c <= '9': + return c - '0' + case 'a' <= c && c <= 'f': + return c - 'a' + 10 + case 'A' <= c && c <= 'F': + return c - 'A' + 10 + } + return 0 +} + +func escape(s string, table *[128]byte, spaceToPlus bool) string { + spaceCount, hexCount := 0, 0 + for i := 0; i < len(s); i++ { + c := s[i] + if c > 127 || table[c] == 0 { + if c == ' ' && spaceToPlus { + spaceCount++ + } else { + hexCount++ + } + } + } + + if spaceCount == 0 && hexCount == 0 { + return s + } + + var sb strings.Builder + hexBuf := [3]byte{'%', 0, 0} + + sb.Grow(len(s) + 2*hexCount) + + for i := 0; i < len(s); i++ { + switch c := s[i]; { + case c == ' ' && spaceToPlus: + sb.WriteByte('+') + case c > 127 || table[c] == 0: + hexBuf[1] = upperhex[c>>4] + hexBuf[2] = upperhex[c&15] + sb.Write(hexBuf[:]) + default: + sb.WriteByte(c) + } + } + return sb.String() +} + +func unescapeSearchParam(s string) string { + n := 0 + hasPlus := false + for i := 0; i < len(s); { + switch s[i] { + case '%': + if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { + i++ + continue + } + n++ + i += 3 + case '+': + hasPlus = true + i++ + default: + i++ + } + } + + if n == 0 && !hasPlus { + return s + } + + var t strings.Builder + t.Grow(len(s) - 2*n) + for i := 0; i < len(s); i++ { + switch s[i] { + case '%': + if i+2 >= len(s) || !ishex(s[i+1]) || !ishex(s[i+2]) { + t.WriteByte('%') + } else { + t.WriteByte(unhex(s[i+1])<<4 | unhex(s[i+2])) + i += 2 + } + case '+': + t.WriteByte(' ') + default: + t.WriteByte(s[i]) + } + } + return t.String() +} diff --git a/pkg/xscript/url/module.go b/pkg/xscript/url/module.go new file mode 100644 index 0000000..ab215d9 --- /dev/null +++ b/pkg/xscript/url/module.go @@ -0,0 +1,36 @@ +package url + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "url" + +type urlModule struct { + r *engine.Runtime + + URLSearchParamsPrototype *engine.Object + URLSearchParamsIteratorPrototype *engine.Object +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + exports := module.Get("exports").(*engine.Object) + m := &urlModule{ + r: runtime, + } + exports.Set("URL", m.createURLConstructor()) + exports.Set("URLSearchParams", m.createURLSearchParamsConstructor()) + exports.Set("domainToASCII", m.domainToASCII) + exports.Set("domainToUnicode", m.domainToUnicode) +} + +func Enable(runtime *engine.Runtime) { + m := require.Require(runtime, ModuleName).ToObject(runtime) + runtime.Set("URL", m.Get("URL")) + runtime.Set("URLSearchParams", m.Get("URLSearchParams")) +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/url/nodeurl.go b/pkg/xscript/url/nodeurl.go new file mode 100644 index 0000000..538cccf --- /dev/null +++ b/pkg/xscript/url/nodeurl.go @@ -0,0 +1,148 @@ +package url + +import ( + "net/url" + "strings" +) + +type searchParam struct { + name string + value string +} + +func (sp *searchParam) Encode() string { + return sp.string(true) +} + +func escapeSearchParam(s string) string { + return escape(s, &tblEscapeURLQueryParam, true) +} + +func (sp *searchParam) string(encode bool) string { + if encode { + return escapeSearchParam(sp.name) + "=" + escapeSearchParam(sp.value) + } else { + return sp.name + "=" + sp.value + } +} + +type searchParams []searchParam + +func (s searchParams) Len() int { + return len(s) +} + +func (s searchParams) Swap(i, j int) { + s[i], s[j] = s[j], s[i] +} + +func (s searchParams) Less(i, j int) bool { + return strings.Compare(s[i].name, s[j].name) < 0 +} + +func (s searchParams) Encode() string { + var sb strings.Builder + for i, v := range s { + if i > 0 { + sb.WriteByte('&') + } + sb.WriteString(v.Encode()) + } + return sb.String() +} + +func (s searchParams) String() string { + var sb strings.Builder + for i, v := range s { + if i > 0 { + sb.WriteByte('&') + } + sb.WriteString(v.string(false)) + } + return sb.String() +} + +type nodeURL struct { + url *url.URL + searchParams searchParams +} + +type urlSearchParams nodeURL + +// This methods ensures that the url.URL has the proper RawQuery based on the searchParam +// structs. If a change is made to the searchParams we need to keep them in sync. +func (nu *nodeURL) syncSearchParams() { + if nu.rawQueryUpdateNeeded() { + nu.url.RawQuery = nu.searchParams.Encode() + } +} + +func (nu *nodeURL) rawQueryUpdateNeeded() bool { + return len(nu.searchParams) > 0 && nu.url.RawQuery == "" +} + +func (nu *nodeURL) String() string { + return nu.url.String() +} + +func (sp *urlSearchParams) hasName(name string) bool { + for _, v := range sp.searchParams { + if v.name == name { + return true + } + } + return false +} + +func (sp *urlSearchParams) hasValue(name, value string) bool { + for _, v := range sp.searchParams { + if v.name == name && v.value == value { + return true + } + } + return false +} + +func (sp *urlSearchParams) getValues(name string) []string { + vals := make([]string, 0, len(sp.searchParams)) + for _, v := range sp.searchParams { + if v.name == name { + vals = append(vals, v.value) + } + } + + return vals +} + +func (sp *urlSearchParams) getFirstValue(name string) (string, bool) { + for _, v := range sp.searchParams { + if v.name == name { + return v.value, true + } + } + + return "", false +} + +func parseSearchQuery(query string) (ret searchParams) { + if query == "" { + return + } + + query = strings.TrimPrefix(query, "?") + + for _, v := range strings.Split(query, "&") { + if v == "" { + continue + } + pair := strings.SplitN(v, "=", 2) + l := len(pair) + if l == 1 { + ret = append(ret, searchParam{name: unescapeSearchParam(pair[0]), value: ""}) + } else if l == 2 { + ret = append(ret, searchParam{name: unescapeSearchParam(pair[0]), value: unescapeSearchParam(pair[1])}) + } + } + + return +} diff --git a/pkg/xscript/url/testdata/url_search_params.js b/pkg/xscript/url/testdata/url_search_params.js new file mode 100644 index 0000000..198d113 --- /dev/null +++ b/pkg/xscript/url/testdata/url_search_params.js @@ -0,0 +1,385 @@ +"use strict"; + +const assert = require("../../assert.js"); + +let params; + +function testCtor(value, expected) { + assert.sameValue(new URLSearchParams(value).toString(), expected); +} + +testCtor("user=abc&query=xyz", "user=abc&query=xyz"); +testCtor("?user=abc&query=xyz", "user=abc&query=xyz"); + +testCtor( + { + num: 1, + user: "abc", + query: ["first", "second"], + obj: { prop: "value" }, + b: true, + }, + "num=1&user=abc&query=first%2Csecond&obj=%5Bobject+Object%5D&b=true" +); + +const map = new Map(); +map.set("user", "abc"); +map.set("query", "xyz"); +testCtor(map, "user=abc&query=xyz"); + +testCtor( + [ + ["user", "abc"], + ["query", "first"], + ["query", "second"], + ], + "user=abc&query=first&query=second" +); + +// Each key-value pair must have exactly two elements +assert.throwsNodeError(() => new URLSearchParams([["single_value"]]), TypeError, "ERR_INVALID_TUPLE"); +assert.throwsNodeError(() => new URLSearchParams([["too", "many", "values"]]), TypeError, "ERR_INVALID_TUPLE"); + +params = new URLSearchParams("a=b&cc=d"); +params.forEach((value, name, searchParams) => { + if (name === "a") { + assert.sameValue(value, "b"); + } + if (name === "cc") { + assert.sameValue(value, "d"); + } + assert.sameValue(searchParams, params); +}); + +params.forEach((value, name, searchParams) => { + if (name === "a") { + assert.sameValue(value, "b"); + searchParams.set("cc", "d1"); + } + if (name === "cc") { + assert.sameValue(value, "d1"); + } + assert.sameValue(searchParams, params); +}); + +assert.throwsNodeError(() => params.forEach(123), TypeError, "ERR_INVALID_ARG_TYPE"); + +assert.throwsNodeError(() => params.forEach.call(1, 2), TypeError, "ERR_INVALID_THIS"); + +params = new URLSearchParams("a=1=2&b=3"); +assert.sameValue(params.size, 2); +assert.sameValue(params.get("a"), "1=2"); +assert.sameValue(params.get("b"), "3"); + +params = new URLSearchParams("&"); +assert.sameValue(params.size, 0); + +params = new URLSearchParams("& "); +assert.sameValue(params.size, 1); +assert.sameValue(params.get(" "), ""); + +params = new URLSearchParams(" &"); +assert.sameValue(params.size, 1); +assert.sameValue(params.get(" "), ""); + +params = new URLSearchParams("="); +assert.sameValue(params.size, 1); +assert.sameValue(params.get(""), ""); + +params = new URLSearchParams("&=2"); +assert.sameValue(params.size, 1); +assert.sameValue(params.get(""), "2"); + +params = new URLSearchParams("?user=abc"); +assert.throwsNodeError(() => params.append(), TypeError, "ERR_MISSING_ARGS"); +params.append("query", "first"); +assert.sameValue(params.toString(), "user=abc&query=first"); + +params = new URLSearchParams("first=one&second=two&third=three"); +assert.throwsNodeError(() => params.delete(), TypeError, "ERR_MISSING_ARGS"); +params.delete("second", "fake-value"); +assert.sameValue(params.toString(), "first=one&second=two&third=three"); +params.delete("third", "three"); +assert.sameValue(params.toString(), "first=one&second=two"); +params.delete("second"); +assert.sameValue(params.toString(), "first=one"); + +params = new URLSearchParams("user=abc&query=xyz"); +assert.throwsNodeError(() => params.get(), TypeError, "ERR_MISSING_ARGS"); +assert.sameValue(params.get("user"), "abc"); +assert.sameValue(params.get("non-existant"), null); + +params = new URLSearchParams("query=first&query=second"); +assert.throwsNodeError(() => params.getAll(), TypeError, "ERR_MISSING_ARGS"); +const all = params.getAll("query"); +assert.sameValue(all.includes("first"), true); +assert.sameValue(all.includes("second"), true); +assert.sameValue(all.length, 2); +const getAllUndefined = params.getAll(undefined); +assert.sameValue(getAllUndefined.length, 0); +const getAllNonExistant = params.getAll("does_not_exists"); +assert.sameValue(getAllNonExistant.length, 0); + +params = new URLSearchParams("user=abc&query=xyz"); +assert.throwsNodeError(() => params.has(), TypeError, "ERR_MISSING_ARGS"); +assert.sameValue(params.has(undefined), false); +assert.sameValue(params.has("user"), true); +assert.sameValue(params.has("user", "abc"), true); +assert.sameValue(params.has("user", "abc", "extra-param"), true); +assert.sameValue(params.has("user", "efg"), false); +assert.sameValue(params.has("user", undefined), true); + +params = new URLSearchParams(); +params.append("foo", "bar"); +params.append("foo", "baz"); +params.append("abc", "def"); +assert.sameValue(params.toString(), "foo=bar&foo=baz&abc=def"); +params.set("foo", "def"); +params.set("xyz", "opq"); +assert.sameValue(params.toString(), "foo=def&abc=def&xyz=opq"); + +params = new URLSearchParams("query=first&query=second&user=abc&double=first,second"); +const URLSearchIteratorPrototype = params.entries().__proto__; +assert.sameValue(typeof URLSearchIteratorPrototype, "object"); + +assert.sameValue(params[Symbol.iterator], params.entries); + +{ + const entries = params.entries(); + assert.sameValue(entries.toString(), "[object URLSearchParams Iterator]"); + assert.sameValue(entries.__proto__, URLSearchIteratorPrototype); + + let item = entries.next(); + assert.sameValue(item.value.toString(), ["query", "first"].toString()); + assert.sameValue(item.done, false); + + item = entries.next(); + assert.sameValue(item.value.toString(), ["query", "second"].toString()); + assert.sameValue(item.done, false); + + item = entries.next(); + assert.sameValue(item.value.toString(), ["user", "abc"].toString()); + assert.sameValue(item.done, false); + + item = entries.next(); + assert.sameValue(item.value.toString(), ["double", "first,second"].toString()); + assert.sameValue(item.done, false); + + item = entries.next(); + assert.sameValue(item.value, undefined); + assert.sameValue(item.done, true); +} + +params = new URLSearchParams("query=first&query=second&user=abc"); +{ + const keys = params.keys(); + assert.sameValue(keys.__proto__, URLSearchIteratorPrototype); + + let item = keys.next(); + assert.sameValue(item.value, "query"); + assert.sameValue(item.done, false); + + item = keys.next(); + assert.sameValue(item.value, "query"); + assert.sameValue(item.done, false); + + item = keys.next(); + assert.sameValue(item.value, "user"); + assert.sameValue(item.done, false); + + item = keys.next(); + assert.sameValue(item.value, undefined); + assert.sameValue(item.done, true); +} + +params = new URLSearchParams("query=first&query=second&user=abc"); +{ + const values = params.values(); + assert.sameValue(values.__proto__, URLSearchIteratorPrototype); + + let item = values.next(); + assert.sameValue(item.value, "first"); + assert.sameValue(item.done, false); + + item = values.next(); + assert.sameValue(item.value, "second"); + assert.sameValue(item.done, false); + + item = values.next(); + assert.sameValue(item.value, "abc"); + assert.sameValue(item.done, false); + + item = values.next(); + assert.sameValue(item.value, undefined); + assert.sameValue(item.done, true); +} + + +params = new URLSearchParams("query[]=abc&type=search&query[]=123"); +params.sort(); +assert.sameValue(params.toString(), "query%5B%5D=abc&query%5B%5D=123&type=search"); + +params = new URLSearchParams("query=first&query=second&user=abc"); +assert.sameValue(params.size, 3); + +params = new URLSearchParams("%"); +assert.sameValue(params.has("%"), true); +assert.sameValue(params.toString(), "%25="); + +{ + const params = new URLSearchParams(""); + assert.sameValue(params.size, 0); + assert.sameValue(params.toString(), ""); + assert.sameValue(params.get(undefined), null); + params.set(undefined, true); + assert.sameValue(params.has(undefined), true); + assert.sameValue(params.has("undefined"), true); + assert.sameValue(params.get("undefined"), "true"); + assert.sameValue(params.get(undefined), "true"); + assert.sameValue(params.getAll(undefined).toString(), ["true"].toString()); + params.delete(undefined); + assert.sameValue(params.has(undefined), false); + assert.sameValue(params.has("undefined"), false); + + assert.sameValue(params.has(null), false); + params.set(null, "nullval"); + assert.sameValue(params.has(null), true); + assert.sameValue(params.has("null"), true); + assert.sameValue(params.get(null), "nullval"); + assert.sameValue(params.get("null"), "nullval"); + params.delete(null); + assert.sameValue(params.has(null), false); + assert.sameValue(params.has("null"), false); +} + +function* functionGeneratorExample() { + yield ["user", "abc"]; + yield ["query", "first"]; + yield ["query", "second"]; +} + +params = new URLSearchParams(functionGeneratorExample()); +assert.sameValue(params.toString(), "user=abc&query=first&query=second"); + +assert.sameValue(params.__proto__.constructor, URLSearchParams); +assert.sameValue(params instanceof URLSearchParams, true); + +{ + const params = new URLSearchParams("1=2&1=3"); + assert.sameValue(params.get(1), "2"); + assert.sameValue(params.getAll(1).toString(), ["2", "3"].toString()); + assert.sameValue(params.getAll("x").toString(), [].toString()); +} + +// Sync +{ + const url = new URL("https://test.com/"); + const params = url.searchParams; + assert.sameValue(params.size, 0); + url.search = "a=1"; + assert.sameValue(params.size, 1); + assert.sameValue(params.get("a"), "1"); +} + +{ + const url = new URL("https://test.com/?a=1"); + const params = url.searchParams; + assert.sameValue(params.size, 1); + url.search = ""; + assert.sameValue(params.size, 0); + url.search = "b=2"; + assert.sameValue(params.size, 1); +} + +{ + const url = new URL("https://test.com/"); + const params = url.searchParams; + params.append("a", "1"); + assert.sameValue(url.toString(), "https://test.com/?a=1"); +} + +{ + const url = new URL("https://test.com/"); + url.searchParams.append("a", "1"); + url.searchParams.append("b", "1"); + assert.sameValue(url.toString(), "https://test.com/?a=1&b=1"); +} + +{ + const url = new URL("https://test.com/"); + const params = url.searchParams; + url.searchParams.append("a", "1"); + assert.sameValue(url.search, "?a=1"); +} + +{ + const url = new URL("https://test.com/?a=1"); + const params = url.searchParams; + params.append("a", "2"); + assert.sameValue(url.search, "?a=1&a=2"); +} + +{ + const url = new URL("https://test.com/"); + const params = url.searchParams; + params.set("a", "1"); + assert.sameValue(url.search, "?a=1"); +} + +{ + const url = new URL("https://test.com/"); + url.searchParams.set("a", "1"); + url.searchParams.set("b", "1"); + assert.sameValue(url.toString(), "https://test.com/?a=1&b=1"); +} + +{ + const url = new URL("https://test.com/?a=1&b=2"); + const params = url.searchParams; + params.delete("a"); + assert.sameValue(url.search, "?b=2"); +} + +{ + const url = new URL("https://test.com/?b=2&a=1"); + const params = url.searchParams; + params.sort(); + assert.sameValue(url.search, "?a=1&b=2"); +} + +{ + const url = new URL("https://test.com/?a=1"); + const params = url.searchParams; + params.delete("a"); + assert.sameValue(url.search, ""); + + params.set("a", 2); + assert.sameValue(url.search, "?a=2"); +} + +// FAILING: no custom properties on wrapped Go structs +/* +{ + const params = new URLSearchParams(""); + assert.sameValue(Object.isExtensible(params), true); + assert.sameValue(Reflect.defineProperty(params, "customField", {value: 42, configurable: true}), true); + assert.sameValue(params.customField, 42); + const desc = Reflect.getOwnPropertyDescriptor(params, "customField"); + assert.sameValue(desc.value, 42); + assert.sameValue(desc.writable, false); + assert.sameValue(desc.enumerable, false); + assert.sameValue(desc.configurable, true); +} +*/ + +// Escape +{ + const myURL = new URL('https://example.org/abc?fo~o=~ba r%z'); + + assert.sameValue(myURL.search, "?fo~o=~ba%20r%z"); + + // Modify the URL via searchParams... + myURL.searchParams.sort(); + + assert.sameValue(myURL.search, "?fo%7Eo=%7Eba+r%25z"); +} diff --git a/pkg/xscript/url/testdata/url_test.js b/pkg/xscript/url/testdata/url_test.js new file mode 100644 index 0000000..988472d --- /dev/null +++ b/pkg/xscript/url/testdata/url_test.js @@ -0,0 +1,229 @@ +"use strict"; + +const assert = require("../../../../../../goja_nodejs/assert.js"); + +function testURLCtor(str, expected) { + assert.sameValue(new URL(str).toString(), expected); +} + +function testURLCtorBase(ref, base, expected, message) { + assert.sameValue(new URL(ref, base).toString(), expected, message); +} + +testURLCtorBase("https://example.org/", undefined, "https://example.org/"); +testURLCtorBase("/foo", "https://example.org/", "https://example.org/foo"); +testURLCtorBase("http://Example.com/", "https://example.org/", "http://example.com/"); +testURLCtorBase("https://Example.com/", "https://example.org/", "https://example.com/"); +testURLCtorBase("foo://Example.com/", "https://example.org/", "foo://Example.com/"); +testURLCtorBase("foo:Example.com/", "https://example.org/", "foo:Example.com/"); +testURLCtorBase("#hash", "https://example.org/", "https://example.org/#hash"); + +testURLCtor("HTTP://test.com", "http://test.com/"); +testURLCtor("HTTPS://á.com", "https://xn--1ca.com/"); +testURLCtor("HTTPS://á.com:123", "https://xn--1ca.com:123/"); +testURLCtor("https://test.com#asdfá", "https://test.com/#asdf%C3%A1"); +testURLCtor("HTTPS://á.com:123/á", "https://xn--1ca.com:123/%C3%A1"); +testURLCtor("fish://á.com", "fish://%C3%A1.com"); +testURLCtor("https://test.com/?a=1 /2", "https://test.com/?a=1%20/2"); +testURLCtor("https://test.com/á=1?á=1&ü=2#é", "https://test.com/%C3%A1=1?%C3%A1=1&%C3%BC=2#%C3%A9"); + +assert.throws(() => new URL("test"), TypeError); +assert.throws(() => new URL("ssh://EEE:ddd"), TypeError); + +{ + let u = new URL("https://example.org/"); + assert.sameValue(u.__proto__.constructor, URL); + assert.sameValue(u instanceof URL, true); +} + +{ + let u = new URL("https://example.org/"); + assert.sameValue(u.searchParams, u.searchParams); +} + +let myURL; + +// Hash +myURL = new URL("https://example.org/foo#bar"); +myURL.hash = "baz"; +assert.sameValue(myURL.href, "https://example.org/foo#baz"); + +myURL.hash = "#baz"; +assert.sameValue(myURL.href, "https://example.org/foo#baz"); + +myURL.hash = "#á=1 2"; +assert.sameValue(myURL.href, "https://example.org/foo#%C3%A1=1%202"); + +myURL.hash = "#a/#b"; +// FAILING: the second # gets escaped +//assert.sameValue(myURL.href, "https://example.org/foo#a/#b"); +assert.sameValue(myURL.search, ""); +// FAILING: the second # gets escaped +//assert.sameValue(myURL.hash, "#a/#b"); + +// Host +myURL = new URL("https://example.org:81/foo"); +myURL.host = "example.com:82"; +assert.sameValue(myURL.href, "https://example.com:82/foo"); + +// Hostname +myURL = new URL("https://example.org:81/foo"); +myURL.hostname = "example.com:82"; +assert.sameValue(myURL.href, "https://example.org:81/foo"); + +myURL.hostname = "á.com"; +assert.sameValue(myURL.href, "https://xn--1ca.com:81/foo"); + +// href +myURL = new URL("https://example.org/foo"); +myURL.href = "https://example.com/bar"; +assert.sameValue(myURL.href, "https://example.com/bar"); + +// Password +myURL = new URL("https://abc:xyz@example.com"); +myURL.password = "123"; +assert.sameValue(myURL.href, "https://abc:123@example.com/"); + +// pathname +myURL = new URL("https://example.org/abc/xyz?123"); +myURL.pathname = "/abcdef"; +assert.sameValue(myURL.href, "https://example.org/abcdef?123"); + +myURL.pathname = ""; +assert.sameValue(myURL.href, "https://example.org/?123"); + +myURL.pathname = "á"; +assert.sameValue(myURL.pathname, "/%C3%A1"); +assert.sameValue(myURL.href, "https://example.org/%C3%A1?123"); + +// port + +myURL = new URL("https://example.org:8888"); +assert.sameValue(myURL.port, "8888"); + +function testSetPort(port, expected) { + const url = new URL("https://example.org:8888"); + url.port = port; + assert.sameValue(url.port, expected); +} + +testSetPort(0, "0"); +testSetPort(-0, "0"); + +// Default ports are automatically transformed to the empty string +// (HTTPS protocol's default port is 443) +testSetPort("443", ""); +testSetPort(443, ""); + +// Empty string is the same as default port +testSetPort("", ""); + +// Completely invalid port strings are ignored +testSetPort("abcd", "8888"); +testSetPort("-123", ""); +testSetPort(-123, ""); +testSetPort(-123.45, ""); +testSetPort(undefined, "8888"); +testSetPort(null, "8888"); +testSetPort(+Infinity, "8888"); +testSetPort(-Infinity, "8888"); +testSetPort(NaN, "8888"); + +// Leading numbers are treated as a port number +testSetPort("5678abcd", "5678"); +testSetPort("a5678abcd", ""); + +// Non-integers are truncated +testSetPort(1234.5678, "1234"); + +// Out-of-range numbers which are not represented in scientific notation +// will be ignored. +testSetPort(1e10, "8888"); +testSetPort("123456", "8888"); +testSetPort(123456, "8888"); +testSetPort(4.567e21, "4"); + +// toString() takes precedence over valueOf(), even if it returns a valid integer +testSetPort( + { + toString() { + return "2"; + }, + valueOf() { + return 1; + }, + }, + "2" +); + +// Protocol +function testSetProtocol(url, protocol, expected) { + url.protocol = protocol; + assert.sameValue(url.protocol, expected); +} +testSetProtocol(new URL("https://example.org"), "ftp", "ftp:"); +testSetProtocol(new URL("https://example.org"), "ftp:", "ftp:"); +testSetProtocol(new URL("https://example.org"), "FTP:", "ftp:"); +testSetProtocol(new URL("https://example.org"), "ftp: blah", "ftp:"); +// special to non-special +testSetProtocol(new URL("https://example.org"), "foo", "https:"); +// non-special to special +testSetProtocol(new URL("fish://example.org"), "https", "fish:"); + +// Search +myURL = new URL("https://example.org/abc?123"); +myURL.search = "abc=xyz"; +assert.sameValue(myURL.href, "https://example.org/abc?abc=xyz"); + +myURL.search = "a=1 2"; +assert.sameValue(myURL.href, "https://example.org/abc?a=1%202"); + +myURL.search = "á=ú"; +assert.sameValue(myURL.search, "?%C3%A1=%C3%BA"); +assert.sameValue(myURL.href, "https://example.org/abc?%C3%A1=%C3%BA"); + +myURL.hash = "hash"; +myURL.search = "a=#b"; +assert.sameValue(myURL.href, "https://example.org/abc?a=%23b#hash"); +assert.sameValue(myURL.search, "?a=%23b"); +assert.sameValue(myURL.hash, "#hash"); + +// Username +myURL = new URL("https://abc:xyz@example.com/"); +myURL.username = "123"; +assert.sameValue(myURL.href, "https://123:xyz@example.com/"); + +// Origin, read-only +assert.throws(() => { + myURL.origin = "abc"; +}, TypeError); + +// href +myURL = new URL("https://example.org"); +myURL.href = "https://example.com"; +assert.sameValue(myURL.href, "https://example.com/"); + +assert.throws(() => { + myURL.href = "test"; +}, TypeError); + +// Search Params +myURL = new URL("https://example.com/"); +myURL.searchParams.append("user", "abc"); +assert.sameValue(myURL.toString(), "https://example.com/?user=abc"); +myURL.searchParams.append("first", "one"); +assert.sameValue(myURL.toString(), "https://example.com/?user=abc&first=one"); +myURL.searchParams.delete("user"); +assert.sameValue(myURL.toString(), "https://example.com/?first=one"); + +{ + const url = require("url"); + + assert.sameValue(url.domainToASCII('español.com'), "xn--espaol-zwa.com"); + assert.sameValue(url.domainToASCII('中文.com'), "xn--fiq228c.com"); + assert.sameValue(url.domainToASCII('xn--iñvalid.com'), ""); + + assert.sameValue(url.domainToUnicode('xn--espaol-zwa.com'), "español.com"); + assert.sameValue(url.domainToUnicode('xn--fiq228c.com'), "中文.com"); + assert.sameValue(url.domainToUnicode('xn--iñvalid.com'), ""); +} diff --git a/pkg/xscript/url/url.go b/pkg/xscript/url/url.go new file mode 100644 index 0000000..b628d47 --- /dev/null +++ b/pkg/xscript/url/url.go @@ -0,0 +1,407 @@ +package url + +import ( + "math" + "net/url" + "reflect" + "strconv" + "strings" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" + + "golang.org/x/net/idna" +) + +const ( + URLNotAbsolute = "URL is not absolute" + InvalidURL = "Invalid URL" + InvalidBaseURL = "Invalid base URL" + InvalidHostname = "Invalid hostname" +) + +var ( + reflectTypeURL = reflect.TypeOf((*nodeURL)(nil)) + reflectTypeInt = reflect.TypeOf(int64(0)) +) + +func toURL(r *engine.Runtime, v engine.Value) *nodeURL { + if v.ExportType() == reflectTypeURL { + if u := v.Export().(*nodeURL); u != nil { + return u + } + } + + panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URL`)) +} + +func (m *urlModule) newInvalidURLError(msg, input string) *engine.Object { + o := errors.NewTypeError(m.r, "ERR_INVALID_URL", msg) + o.Set("input", m.r.ToValue(input)) + return o +} + +func (m *urlModule) defineURLAccessorProp(p *engine.Object, name string, getter func(*nodeURL) any, setter func(*nodeURL, engine.Value)) { + var getterVal, setterVal engine.Value + if getter != nil { + getterVal = m.r.ToValue(func(call engine.FunctionCall) engine.Value { + return m.r.ToValue(getter(toURL(m.r, call.This))) + }) + } + if setter != nil { + setterVal = m.r.ToValue(func(call engine.FunctionCall) engine.Value { + setter(toURL(m.r, call.This), call.Argument(0)) + return engine.Undefined() + }) + } + p.DefineAccessorProperty(name, getterVal, setterVal, engine.FLAG_FALSE, engine.FLAG_TRUE) +} + +func valueToURLPort(v engine.Value) (portNum int, empty bool) { + portNum = -1 + if et := v.ExportType(); et == reflectTypeInt { + num := v.ToInteger() + if num < 0 { + empty = true + } else if num <= math.MaxUint16 { + portNum = int(num) + } + } else { + s := v.String() + if s == "" { + return 0, true + } + firstDigitIdx := -1 + for i := 0; i < len(s); i++ { + if c := s[i]; c >= '0' && c <= '9' { + firstDigitIdx = i + break + } + } + + if firstDigitIdx == -1 { + return -1, false + } + + if firstDigitIdx > 0 { + return 0, true + } + + for i := 0; i < len(s); i++ { + if c := s[i]; c >= '0' && c <= '9' { + if portNum == -1 { + portNum = 0 + } + portNum = portNum*10 + int(c-'0') + if portNum > math.MaxUint16 { + portNum = -1 + break + } + } else { + break + } + } + } + return +} + +func isDefaultURLPort(protocol string, port int) bool { + switch port { + case 21: + if protocol == "ftp" { + return true + } + case 80: + if protocol == "http" || protocol == "ws" { + return true + } + case 443: + if protocol == "https" || protocol == "wss" { + return true + } + } + return false +} + +func isSpecialProtocol(protocol string) bool { + switch protocol { + case "ftp", "file", "http", "https", "ws", "wss": + return true + } + return false +} + +func clearURLPort(u *url.URL) { + u.Host = u.Hostname() +} + +func setURLPort(nu *nodeURL, v engine.Value) { + u := nu.url + if u.Scheme == "file" { + return + } + portNum, empty := valueToURLPort(v) + if empty { + clearURLPort(u) + return + } + if portNum == -1 { + return + } + if isDefaultURLPort(u.Scheme, portNum) { + clearURLPort(u) + } else { + u.Host = u.Hostname() + ":" + strconv.Itoa(portNum) + } +} + +func (m *urlModule) parseURL(s string, isBase bool) *url.URL { + u, err := url.Parse(s) + if err != nil { + if isBase { + panic(m.newInvalidURLError(InvalidBaseURL, s)) + } else { + panic(m.newInvalidURLError(InvalidURL, s)) + } + } + if isBase && !u.IsAbs() { + panic(m.newInvalidURLError(URLNotAbsolute, s)) + } + if portStr := u.Port(); portStr != "" { + if port, err := strconv.Atoi(portStr); err != nil || isDefaultURLPort(u.Scheme, port) { + u.Host = u.Hostname() // Clear port + } + } + m.fixURL(u) + return u +} + +func fixRawQuery(u *url.URL) { + if u.RawQuery != "" { + u.RawQuery = escape(u.RawQuery, &tblEscapeURLQuery, false) + } +} + +func (m *urlModule) fixURL(u *url.URL) { + switch u.Scheme { + case "https", "http", "ftp", "wss", "ws": + if u.Path == "" { + u.Path = "/" + } + hostname := u.Hostname() + lh := strings.ToLower(hostname) + ch, err := idna.Punycode.ToASCII(lh) + if err != nil { + panic(m.newInvalidURLError(InvalidHostname, lh)) + } + if ch != hostname { + if port := u.Port(); port != "" { + u.Host = ch + ":" + port + } else { + u.Host = ch + } + } + } + fixRawQuery(u) +} + +func (m *urlModule) createURLPrototype() *engine.Object { + p := m.r.NewObject() + + // host + m.defineURLAccessorProp(p, "host", func(u *nodeURL) any { + return u.url.Host + }, func(u *nodeURL, arg engine.Value) { + host := arg.String() + if _, err := url.ParseRequestURI(u.url.Scheme + "://" + host); err == nil { + u.url.Host = host + m.fixURL(u.url) + } + }) + + // hash + m.defineURLAccessorProp(p, "hash", func(u *nodeURL) any { + if u.url.Fragment != "" { + return "#" + u.url.EscapedFragment() + } + return "" + }, func(u *nodeURL, arg engine.Value) { + h := arg.String() + if len(h) > 0 && h[0] == '#' { + h = h[1:] + } + u.url.Fragment = h + }) + + // hostname + m.defineURLAccessorProp(p, "hostname", func(u *nodeURL) any { + return strings.Split(u.url.Host, ":")[0] + }, func(u *nodeURL, arg engine.Value) { + h := arg.String() + if strings.IndexByte(h, ':') >= 0 { + return + } + if _, err := url.ParseRequestURI(u.url.Scheme + "://" + h); err == nil { + if port := u.url.Port(); port != "" { + u.url.Host = h + ":" + port + } else { + u.url.Host = h + } + m.fixURL(u.url) + } + }) + + // href + m.defineURLAccessorProp(p, "href", func(u *nodeURL) any { + return u.String() + }, func(u *nodeURL, arg engine.Value) { + u.url = m.parseURL(arg.String(), true) + }) + + // pathname + m.defineURLAccessorProp(p, "pathname", func(u *nodeURL) any { + return u.url.EscapedPath() + }, func(u *nodeURL, arg engine.Value) { + p := arg.String() + if _, err := url.Parse(p); err == nil { + switch u.url.Scheme { + case "https", "http", "ftp", "ws", "wss": + if !strings.HasPrefix(p, "/") { + p = "/" + p + } + } + u.url.Path = p + } + }) + + // origin + m.defineURLAccessorProp(p, "origin", func(u *nodeURL) any { + return u.url.Scheme + "://" + u.url.Hostname() + }, nil) + + // password + m.defineURLAccessorProp(p, "password", func(u *nodeURL) any { + p, _ := u.url.User.Password() + return p + }, func(u *nodeURL, arg engine.Value) { + user := u.url.User + u.url.User = url.UserPassword(user.Username(), arg.String()) + }) + + // username + m.defineURLAccessorProp(p, "username", func(u *nodeURL) any { + return u.url.User.Username() + }, func(u *nodeURL, arg engine.Value) { + p, has := u.url.User.Password() + if !has { + u.url.User = url.User(arg.String()) + } else { + u.url.User = url.UserPassword(arg.String(), p) + } + }) + + // port + m.defineURLAccessorProp(p, "port", func(u *nodeURL) any { + return u.url.Port() + }, func(u *nodeURL, arg engine.Value) { + setURLPort(u, arg) + }) + + // protocol + m.defineURLAccessorProp(p, "protocol", func(u *nodeURL) any { + return u.url.Scheme + ":" + }, func(u *nodeURL, arg engine.Value) { + s := arg.String() + pos := strings.IndexByte(s, ':') + if pos >= 0 { + s = s[:pos] + } + s = strings.ToLower(s) + if isSpecialProtocol(u.url.Scheme) == isSpecialProtocol(s) { + if _, err := url.ParseRequestURI(s + "://" + u.url.Host); err == nil { + u.url.Scheme = s + } + } + }) + + // Search + m.defineURLAccessorProp(p, "search", func(u *nodeURL) any { + u.syncSearchParams() + if u.url.RawQuery != "" { + return "?" + u.url.RawQuery + } + return "" + }, func(u *nodeURL, arg engine.Value) { + u.url.RawQuery = arg.String() + fixRawQuery(u.url) + if u.searchParams != nil { + u.searchParams = parseSearchQuery(u.url.RawQuery) + if u.searchParams == nil { + u.searchParams = make(searchParams, 0) + } + } + }) + + // search Params + m.defineURLAccessorProp(p, "searchParams", func(u *nodeURL) any { + if u.searchParams == nil { + sp := parseSearchQuery(u.url.RawQuery) + if sp == nil { + sp = make(searchParams, 0) + } + u.searchParams = sp + } + return m.newURLSearchParams((*urlSearchParams)(u)) + }, nil) + + p.Set("toString", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toURL(m.r, call.This) + u.syncSearchParams() + return m.r.ToValue(u.url.String()) + })) + + p.Set("toJSON", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toURL(m.r, call.This) + u.syncSearchParams() + return m.r.ToValue(u.url.String()) + })) + + return p +} + +func (m *urlModule) createURLConstructor() engine.Value { + f := m.r.ToValue(func(call engine.ConstructorCall) *engine.Object { + var u *url.URL + if baseArg := call.Argument(1); !engine.IsUndefined(baseArg) { + base := m.parseURL(baseArg.String(), true) + ref := m.parseURL(call.Argument(0).String(), false) + u = base.ResolveReference(ref) + } else { + u = m.parseURL(call.Argument(0).String(), true) + } + res := m.r.ToValue(&nodeURL{url: u}).(*engine.Object) + res.SetPrototype(call.This.Prototype()) + return res + }).(*engine.Object) + + proto := m.createURLPrototype() + f.Set("prototype", proto) + proto.DefineDataProperty("constructor", f, engine.FLAG_FALSE, engine.FLAG_FALSE, engine.FLAG_FALSE) + return f +} + +func (m *urlModule) domainToASCII(domUnicode string) string { + res, err := idna.ToASCII(domUnicode) + if err != nil { + return "" + } + return res +} + +func (m *urlModule) domainToUnicode(domASCII string) string { + res, err := idna.ToUnicode(domASCII) + if err != nil { + return "" + } + return res +} diff --git a/pkg/xscript/url/url_test.go b/pkg/xscript/url/url_test.go new file mode 100644 index 0000000..47bf143 --- /dev/null +++ b/pkg/xscript/url/url_test.go @@ -0,0 +1,122 @@ +package url + +import ( + _ "embed" + "testing" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +func TestURL(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + Enable(vm) + + if c := vm.Get("URL"); c == nil { + t.Fatal("URL not found") + } + + script := `const url = new URL("https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hash");` + + if _, err := vm.RunString(script); err != nil { + t.Fatal("Failed to process url script.", err) + } +} + +func TestGetters(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + Enable(vm) + + if c := vm.Get("URL"); c == nil { + t.Fatal("URL not found") + } + + script := ` + new URL("https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hashed"); + ` + + v, err := vm.RunString(script) + if err != nil { + t.Fatal("Failed to process url script.", err) + } + + url := v.ToObject(vm) + + tests := []struct { + prop string + expected string + }{ + { + prop: "hash", + expected: "#hashed", + }, + { + prop: "host", + expected: "sub.example.com:8080", + }, + { + prop: "hostname", + expected: "sub.example.com", + }, + { + prop: "href", + expected: "https://user:pass@sub.example.com:8080/p/a/t/h?query=string#hashed", + }, + { + prop: "origin", + expected: "https://sub.example.com", + }, + { + prop: "password", + expected: "pass", + }, + { + prop: "username", + expected: "user", + }, + { + prop: "port", + expected: "8080", + }, + { + prop: "protocol", + expected: "https:", + }, + { + prop: "search", + expected: "?query=string", + }, + } + + for _, test := range tests { + v := url.Get(test.prop).String() + if v != test.expected { + t.Fatal("failed to match " + test.prop + " property. got: " + v + ", expected: " + test.expected) + } + } +} + +//go:embed testdata/url_test.js +var urlTest string + +func TestJs(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + Enable(vm) + + if c := vm.Get("URL"); c == nil { + t.Fatal("URL not found") + } + + // Script will throw an error on failed validation + + _, err := vm.RunScript("testdata/url_test.js", urlTest) + if err != nil { + if ex, ok := err.(*engine.Exception); ok { + t.Fatal(ex.String()) + } + t.Fatal("Failed to process url script.", err) + } +} diff --git a/pkg/xscript/url/urlsearchparams.go b/pkg/xscript/url/urlsearchparams.go new file mode 100644 index 0000000..fba4770 --- /dev/null +++ b/pkg/xscript/url/urlsearchparams.go @@ -0,0 +1,389 @@ +package url + +import ( + "reflect" + "sort" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/errors" +) + +var ( + reflectTypeURLSearchParams = reflect.TypeOf((*urlSearchParams)(nil)) + reflectTypeURLSearchParamsIterator = reflect.TypeOf((*urlSearchParamsIterator)(nil)) +) + +func newInvalidTupleError(r *engine.Runtime) *engine.Object { + return errors.NewTypeError(r, "ERR_INVALID_TUPLE", "Each query pair must be an iterable [name, value] tuple") +} + +func newMissingArgsError(r *engine.Runtime, msg string) *engine.Object { + return errors.NewTypeError(r, errors.ErrCodeMissingArgs, msg) +} + +func newInvalidArgsError(r *engine.Runtime) *engine.Object { + return errors.NewTypeError(r, "ERR_INVALID_ARG_TYPE", `The "callback" argument must be of type function.`) +} + +func toUrlSearchParams(r *engine.Runtime, v engine.Value) *urlSearchParams { + if v.ExportType() == reflectTypeURLSearchParams { + if u := v.Export().(*urlSearchParams); u != nil { + return u + } + } + panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParams`)) +} + +func (m *urlModule) newURLSearchParams(sp *urlSearchParams) *engine.Object { + v := m.r.ToValue(sp).(*engine.Object) + v.SetPrototype(m.URLSearchParamsPrototype) + return v +} + +func (m *urlModule) createURLSearchParamsConstructor() engine.Value { + f := m.r.ToValue(func(call engine.ConstructorCall) *engine.Object { + var sp searchParams + v := call.Argument(0) + if o, ok := v.(*engine.Object); ok { + sp = m.buildParamsFromObject(o) + } else if !engine.IsUndefined(v) { + sp = parseSearchQuery(v.String()) + } + + return m.newURLSearchParams(&urlSearchParams{searchParams: sp}) + }).(*engine.Object) + + m.URLSearchParamsPrototype = m.createURLSearchParamsPrototype() + f.Set("prototype", m.URLSearchParamsPrototype) + m.URLSearchParamsPrototype.DefineDataProperty("constructor", f, engine.FLAG_FALSE, engine.FLAG_FALSE, engine.FLAG_FALSE) + + return f +} + +func (m *urlModule) buildParamsFromObject(o *engine.Object) searchParams { + var query searchParams + + if o.GetSymbol(engine.SymIterator) != nil { + return m.buildParamsFromIterable(o) + } + + for _, k := range o.Keys() { + val := o.Get(k).String() + query = append(query, searchParam{name: k, value: val}) + } + + return query +} + +func (m *urlModule) buildParamsFromIterable(o *engine.Object) searchParams { + var query searchParams + + m.r.ForOf(o, func(val engine.Value) bool { + obj := val.ToObject(m.r) + var name, value string + i := 0 + // Use ForOf to determine if the object is iterable + m.r.ForOf(obj, func(val engine.Value) bool { + if i == 0 { + name = val.String() + i++ + return true + } + if i == 1 { + value = val.String() + i++ + return true + } + // Array isn't a tuple + panic(newInvalidTupleError(m.r)) + }) + + // Ensure we have two values + if i <= 1 { + panic(newInvalidTupleError(m.r)) + } + + query = append(query, searchParam{ + name: name, + value: value, + }) + + return true + }) + + return query +} + +func (m *urlModule) createURLSearchParamsPrototype() *engine.Object { + p := m.r.NewObject() + + p.Set("append", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + if len(call.Arguments) < 2 { + panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`)) + } + + u := toUrlSearchParams(m.r, call.This) + u.searchParams = append(u.searchParams, searchParam{ + name: call.Argument(0).String(), + value: call.Argument(1).String(), + }) + u.markUpdated() + + return engine.Undefined() + })) + + p.Set("delete", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + + if len(call.Arguments) < 1 { + panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) + } + + name := call.Argument(0).String() + isValid := func(v searchParam) bool { + if len(call.Arguments) == 1 { + return v.name != name + } else if v.name == name { + arg := call.Argument(1) + if !engine.IsUndefined(arg) && v.value == arg.String() { + return false + } + } + return true + } + + j := 0 + for i, v := range u.searchParams { + if isValid(v) { + if i != j { + u.searchParams[j] = v + } + j++ + } + } + u.searchParams = u.searchParams[:j] + u.markUpdated() + + return engine.Undefined() + })) + + entries := m.r.ToValue(func(call engine.FunctionCall) engine.Value { + return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorEntries) + }) + p.Set("entries", entries) + p.DefineDataPropertySymbol(engine.SymIterator, entries, engine.FLAG_TRUE, engine.FLAG_FALSE, engine.FLAG_TRUE) + + p.Set("forEach", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + + if len(call.Arguments) != 1 { + panic(newInvalidArgsError(m.r)) + } + + if fn, ok := engine.AssertFunction(call.Argument(0)); ok { + for _, pair := range u.searchParams { + // name, value, searchParams + _, err := fn( + nil, + m.r.ToValue(pair.name), + m.r.ToValue(pair.value), + call.This, + ) + + if err != nil { + panic(err) + } + } + } else { + panic(newInvalidArgsError(m.r)) + } + + return engine.Undefined() + })) + + p.Set("get", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + + if len(call.Arguments) == 0 { + panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) + } + + if val, exists := u.getFirstValue(call.Argument(0).String()); exists { + return m.r.ToValue(val) + } + + return engine.Null() + })) + + p.Set("getAll", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + + if len(call.Arguments) == 0 { + panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) + } + + vals := u.getValues(call.Argument(0).String()) + return m.r.ToValue(vals) + })) + + p.Set("has", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + + if len(call.Arguments) == 0 { + panic(newMissingArgsError(m.r, `The "name" argument must be specified`)) + } + + name := call.Argument(0).String() + value := call.Argument(1) + var res bool + if engine.IsUndefined(value) { + res = u.hasName(name) + } else { + res = u.hasValue(name, value.String()) + } + return m.r.ToValue(res) + })) + + p.Set("keys", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorKeys) + })) + + p.Set("set", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + + if len(call.Arguments) < 2 { + panic(newMissingArgsError(m.r, `The "name" and "value" arguments must be specified`)) + } + + name := call.Argument(0).String() + found := false + j := 0 + for i, sp := range u.searchParams { + if sp.name == name { + if found { + continue // Remove all values + } + + u.searchParams[i].value = call.Argument(1).String() + found = true + } + if i != j { + u.searchParams[j] = sp + } + j++ + } + + if !found { + u.searchParams = append(u.searchParams, searchParam{ + name: name, + value: call.Argument(1).String(), + }) + } else { + u.searchParams = u.searchParams[:j] + } + + u.markUpdated() + + return engine.Undefined() + })) + + p.Set("sort", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + sort.Stable(u.searchParams) + u.markUpdated() + return engine.Undefined() + })) + + p.DefineAccessorProperty("size", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + return m.r.ToValue(len(u.searchParams)) + }), nil, engine.FLAG_FALSE, engine.FLAG_TRUE) + + p.Set("toString", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + u := toUrlSearchParams(m.r, call.This) + str := u.searchParams.Encode() + return m.r.ToValue(str) + })) + + p.Set("values", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + return m.newURLSearchParamsIterator(toUrlSearchParams(m.r, call.This), urlSearchParamsIteratorValues) + })) + + return p +} + +func (sp *urlSearchParams) markUpdated() { + if sp.url != nil && sp.url.RawQuery != "" { + sp.url.RawQuery = "" + } +} + +type urlSearchParamsIteratorType int + +const ( + urlSearchParamsIteratorKeys urlSearchParamsIteratorType = iota + urlSearchParamsIteratorValues + urlSearchParamsIteratorEntries +) + +type urlSearchParamsIterator struct { + typ urlSearchParamsIteratorType + sp *urlSearchParams + idx int +} + +func toURLSearchParamsIterator(r *engine.Runtime, v engine.Value) *urlSearchParamsIterator { + if v.ExportType() == reflectTypeURLSearchParamsIterator { + if u := v.Export().(*urlSearchParamsIterator); u != nil { + return u + } + } + + panic(errors.NewTypeError(r, errors.ErrCodeInvalidThis, `Value of "this" must be of type URLSearchParamIterator`)) +} + +func (m *urlModule) getURLSearchParamsIteratorPrototype() *engine.Object { + if m.URLSearchParamsIteratorPrototype != nil { + return m.URLSearchParamsIteratorPrototype + } + + p := m.r.NewObject() + + p.Set("next", m.r.ToValue(func(call engine.FunctionCall) engine.Value { + it := toURLSearchParamsIterator(m.r, call.This) + res := m.r.NewObject() + if it.idx < len(it.sp.searchParams) { + param := it.sp.searchParams[it.idx] + switch it.typ { + case urlSearchParamsIteratorKeys: + res.Set("value", param.name) + case urlSearchParamsIteratorValues: + res.Set("value", param.value) + default: + res.Set("value", m.r.NewArray(param.name, param.value)) + } + res.Set("done", false) + it.idx++ + } else { + res.Set("value", engine.Undefined()) + res.Set("done", true) + } + return res + })) + + p.DefineDataPropertySymbol(engine.SymToStringTag, m.r.ToValue("URLSearchParams Iterator"), engine.FLAG_FALSE, engine.FLAG_FALSE, engine.FLAG_TRUE) + + m.URLSearchParamsIteratorPrototype = p + return p +} + +func (m *urlModule) newURLSearchParamsIterator(sp *urlSearchParams, typ urlSearchParamsIteratorType) engine.Value { + it := m.r.ToValue(&urlSearchParamsIterator{ + typ: typ, + sp: sp, + }).(*engine.Object) + + it.SetPrototype(m.getURLSearchParamsIteratorPrototype()) + + return it +} diff --git a/pkg/xscript/url/urlsearchparams_test.go b/pkg/xscript/url/urlsearchparams_test.go new file mode 100644 index 0000000..0e8e31b --- /dev/null +++ b/pkg/xscript/url/urlsearchparams_test.go @@ -0,0 +1,53 @@ +package url + +import ( + _ "embed" + "testing" + + "pandax/pkg/xscript/console" + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +func createVM() *engine.Runtime { + vm := engine.New() + new(require.Registry).Enable(vm) + console.Enable(vm) + Enable(vm) + return vm +} + +func TestURLSearchParams(t *testing.T) { + vm := createVM() + + if c := vm.Get("URLSearchParams"); c == nil { + t.Fatal("URLSearchParams not found") + } + + script := `const params = new URLSearchParams();` + + if _, err := vm.RunString(script); err != nil { + t.Fatal("Failed to process url script.", err) + } +} + +//go:embed testdata/url_search_params.js +var url_search_params string + +func TestURLSearchParameters(t *testing.T) { + vm := createVM() + + if c := vm.Get("URLSearchParams"); c == nil { + t.Fatal("URLSearchParams not found") + } + + // Script will throw an error on failed validation + + _, err := vm.RunScript("testdata/url_search_params.js", url_search_params) + if err != nil { + if ex, ok := err.(*engine.Exception); ok { + t.Fatal(ex.String()) + } + t.Fatal("Failed to process url script.", err) + } +} diff --git a/pkg/xscript/util/function.go b/pkg/xscript/util/function.go new file mode 100644 index 0000000..e495e5b --- /dev/null +++ b/pkg/xscript/util/function.go @@ -0,0 +1,80 @@ +package util + +import ( + "bytes" + + "pandax/pkg/xscript/engine" +) + +func (u *Util) format(f rune, val engine.Value, w *bytes.Buffer) bool { + switch f { + case 's': + w.WriteString(val.String()) + case 'd': + w.WriteString(val.ToNumber().String()) + case 'j': + if json, ok := u.runtime.Get("JSON").(*engine.Object); ok { + if stringify, ok := engine.AssertFunction(json.Get("stringify")); ok { + res, err := stringify(json, val) + if err != nil { + panic(err) + } + w.WriteString(res.String()) + } + } + case '%': + w.WriteByte('%') + return false + default: + w.WriteByte('%') + w.WriteRune(f) + return false + } + return true +} + +func (u *Util) Format(b *bytes.Buffer, f string, args ...engine.Value) { + pct := false + argNum := 0 + for _, chr := range f { + if pct { + if argNum < len(args) { + if u.format(chr, args[argNum], b) { + argNum++ + } + } else { + b.WriteByte('%') + b.WriteRune(chr) + } + pct = false + } else { + if chr == '%' { + pct = true + } else { + b.WriteRune(chr) + } + } + } + + for _, arg := range args[argNum:] { + b.WriteByte(' ') + b.WriteString(arg.String()) + } +} + +func (u *Util) js_format(call engine.FunctionCall) engine.Value { + var b bytes.Buffer + var fmt string + + if arg := call.Argument(0); !engine.IsUndefined(arg) { + fmt = arg.String() + } + + var args []engine.Value + if len(call.Arguments) > 0 { + args = call.Arguments[1:] + } + u.Format(&b, fmt, args...) + + return u.runtime.ToValue(b.String()) +} diff --git a/pkg/xscript/util/module.go b/pkg/xscript/util/module.go new file mode 100644 index 0000000..dc3c7c9 --- /dev/null +++ b/pkg/xscript/util/module.go @@ -0,0 +1,30 @@ +package util + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "util" + +type Util struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + u := &Util{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + obj.Set("format", u.js_format) +} + +func New(runtime *engine.Runtime) *Util { + return &Util{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/util/module_test.go b/pkg/xscript/util/module_test.go new file mode 100644 index 0000000..c1c5c2c --- /dev/null +++ b/pkg/xscript/util/module_test.go @@ -0,0 +1,74 @@ +package util + +import ( + "bytes" + "testing" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +func TestUtil_Format(t *testing.T) { + vm := engine.New() + util := New(vm) + + var b bytes.Buffer + util.Format(&b, "Test: %% %д %s %d, %j", vm.ToValue("string"), vm.ToValue(42), vm.NewObject()) + + if res := b.String(); res != "Test: % %д string 42, {}" { + t.Fatalf("Unexpected result: '%s'", res) + } +} + +func TestUtil_Format_NoArgs(t *testing.T) { + vm := engine.New() + util := New(vm) + + var b bytes.Buffer + util.Format(&b, "Test: %s %d, %j") + + if res := b.String(); res != "Test: %s %d, %j" { + t.Fatalf("Unexpected result: '%s'", res) + } +} + +func TestUtil_Format_LessArgs(t *testing.T) { + vm := engine.New() + util := New(vm) + + var b bytes.Buffer + util.Format(&b, "Test: %s %d, %j", vm.ToValue("string"), vm.ToValue(42)) + + if res := b.String(); res != "Test: string 42, %j" { + t.Fatalf("Unexpected result: '%s'", res) + } +} + +func TestUtil_Format_MoreArgs(t *testing.T) { + vm := engine.New() + util := New(vm) + + var b bytes.Buffer + util.Format(&b, "Test: %s %d, %j", vm.ToValue("string"), vm.ToValue(42), vm.NewObject(), vm.ToValue(42.42)) + + if res := b.String(); res != "Test: string 42, {} 42.42" { + t.Fatalf("Unexpected result: '%s'", res) + } +} + +func TestJSNoArgs(t *testing.T) { + vm := engine.New() + new(require.Registry).Enable(vm) + + if util, ok := require.Require(vm, ModuleName).(*engine.Object); ok { + if format, ok := engine.AssertFunction(util.Get("format")); ok { + res, err := format(util) + if err != nil { + t.Fatal(err) + } + if v := res.Export(); v != "" { + t.Fatalf("Unexpected result: %v", v) + } + } + } +} diff --git a/pkg/xscript/ws/export.go b/pkg/xscript/ws/export.go new file mode 100644 index 0000000..71d8652 --- /dev/null +++ b/pkg/xscript/ws/export.go @@ -0,0 +1,35 @@ +package ws + +import ( + "sync" + + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "ws" + +type Extension struct { + sync.Mutex + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("NewServer", e.NewServer) + obj.Set("NewClient", e.NewClient) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/ws/function.go b/pkg/xscript/ws/function.go new file mode 100644 index 0000000..e29e408 --- /dev/null +++ b/pkg/xscript/ws/function.go @@ -0,0 +1,95 @@ +package ws + +import ( + "fmt" + "net/http" + + "github.com/gorilla/websocket" +) + +type session struct { + conn *websocket.Conn + onConnect func() + onReceive func([]byte) + onError func(error) + onClose func() +} + +func (e *Extension) NewClient(url string) (*session, error) { + conn, _, err := websocket.DefaultDialer.Dial(url, nil) + if err != nil { + return nil, err + } + + session := &session{ + conn: conn, + } + + go session.listen() + + return session, nil +} + +func (e *Extension) NewServer(path string) http.Handler { + upgrader := websocket.Upgrader{} + + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + conn, err := upgrader.Upgrade(w, r, nil) + if err != nil { + fmt.Println("NewServer: failed to upgrade connection:", err) + return + } + + session := &session{ + conn: conn, + } + + go session.listen() + }) +} + +func (ws *session) listen() { + if ws.onConnect != nil { + ws.onConnect() + } + + for { + _, message, err := ws.conn.ReadMessage() + if err != nil { + if ws.onError != nil { + ws.onError(err) + } + break + } + + if ws.onReceive != nil { + ws.onReceive(message) + } + } + + ws.conn.Close() + + if ws.onClose != nil { + ws.onClose() + } +} + +func (ws *session) OnConnect(callback func()) { + ws.onConnect = callback +} + +func (ws *session) OnReceive(callback func([]byte)) { + ws.onReceive = callback +} + +func (ws *session) OnError(callback func(error)) { + ws.onError = callback +} + +func (ws *session) OnClose(callback func()) { + ws.onClose = callback +} + +func (ws *session) Send(data []byte) error { + return ws.conn.WriteMessage(websocket.TextMessage, data) +} diff --git a/pkg/xscript/zip/export.go b/pkg/xscript/zip/export.go new file mode 100644 index 0000000..da82d2e --- /dev/null +++ b/pkg/xscript/zip/export.go @@ -0,0 +1,34 @@ +package zip + +import ( + "pandax/pkg/xscript/engine" + "pandax/pkg/xscript/require" +) + +const ModuleName = "zip" + +type Extension struct { + runtime *engine.Runtime +} + +func Require(runtime *engine.Runtime, module *engine.Object) { + e := &Extension{ + runtime: runtime, + } + obj := module.Get("exports").(*engine.Object) + + obj.Set("list", e.list) + obj.Set("extract", e.extract) + obj.Set("add", e.add) + obj.Set("del", e.del) +} + +func Enable(runtime *engine.Runtime) *Extension { + return &Extension{ + runtime: runtime, + } +} + +func init() { + require.RegisterCoreModule(ModuleName, Require) +} diff --git a/pkg/xscript/zip/function.go b/pkg/xscript/zip/function.go new file mode 100644 index 0000000..37b0c34 --- /dev/null +++ b/pkg/xscript/zip/function.go @@ -0,0 +1,57 @@ +package zip + +import ( + "encoding/json" + "pandax/pkg/xscript/engine" +) + +func (e *Extension) list(call engine.FunctionCall) engine.Value { + zipFile := call.Arguments[0].String() + filesList, err := List(zipFile) + if err != nil { + return engine.Null() + } + + // 转换 filesList 为 json 字符串 + jsonString, err := json.Marshal(filesList) + if err == nil { + return e.runtime.ToValue(string(jsonString)) + } + return e.runtime.ToValue(false) +} + +func (e *Extension) extract(call engine.FunctionCall) engine.Value { + zipFile := call.Arguments[0].String() + destFolder := call.Arguments[1].String() + filesList := call.Arguments[2].String() + + err := Extract(zipFile, destFolder, filesList) + + return e.runtime.ToValue(err == nil) +} + +func (e *Extension) add(call engine.FunctionCall) engine.Value { + zipFile := call.Arguments[0].String() + filesList := call.Arguments[1].String() + oldFrom, newFrom := "", "" + + if len(call.Arguments) > 2 { + oldFrom = call.Arguments[2].String() + } + + if len(call.Arguments) > 3 { + newFrom = call.Arguments[3].String() + } + + err := Add(zipFile, filesList, oldFrom, newFrom) + return e.runtime.ToValue(err == nil) +} + +func (e *Extension) del(call engine.FunctionCall) engine.Value { + zipFile := call.Arguments[0].String() + filesList := call.Arguments[1].String() + + err := Del(zipFile, filesList) + return e.runtime.ToValue(err == nil) + +} diff --git a/pkg/xscript/zip/zip.go b/pkg/xscript/zip/zip.go new file mode 100644 index 0000000..3f725f2 --- /dev/null +++ b/pkg/xscript/zip/zip.go @@ -0,0 +1,174 @@ +package zip + +import ( + "archive/zip" + "io" + "os" + "path/filepath" + "strings" +) + +// 获取zip中的文件列表 +func List(zipFile string) ([]*zip.File, error) { + reader, err := zip.OpenReader(zipFile) + if err != nil { + return nil, err + } + defer reader.Close() + + return reader.File, nil +} + +// 解压zip到指定文件夹 +func Extract(zipFile, destFolder string, filesToExtract string) error { + reader, err := zip.OpenReader(zipFile) + if err != nil { + return err + } + defer reader.Close() + + for _, file := range reader.File { + // 解压特定文件到指定目录 + if filesToExtract != "" && !strings.Contains(filesToExtract, file.Name) { + continue + } + + path := filepath.Join(destFolder, file.Name) + + if file.FileInfo().IsDir() { + os.MkdirAll(path, os.ModePerm) + continue + } + + fileReader, err := file.Open() + if err != nil { + return err + } + defer fileReader.Close() + + targetFile, err := os.Create(path) + if err != nil { + return err + } + defer targetFile.Close() + + if _, err = io.Copy(targetFile, fileReader); err != nil { + return err + } + } + + return nil +} + +// 添加文件到压缩 +func Add(filename string, filesToAdded string, oldForm, newForm string) error { + + newZipFile, err := os.Create(filename) + if err != nil { + return err + } + defer func() { + _ = newZipFile.Close() + }() + + zipWriter := zip.NewWriter(newZipFile) + defer func() { + _ = zipWriter.Close() + }() + + // 把files添加到zip中 + for _, file := range strings.Split(filesToAdded, ",") { + err = func(file string) error { + zipFile, err := os.Open(file) + if err != nil { + return err + } + defer zipFile.Close() + // 获取file的基础信息 + info, err := zipFile.Stat() + if err != nil { + return err + } + + header, err := zip.FileInfoHeader(info) + if err != nil { + return err + } + + // 使用上面的FileInforHeader() 就可以把文件保存的路径替换成我们自己想要的了,如下面 + header.Name = strings.Replace(file, oldForm, newForm, -1) + + // 优化压缩 + // 更多参考see http://golang.org/pkg/archive/zip/#pkg-constants + header.Method = zip.Deflate + + writer, err := zipWriter.CreateHeader(header) + if err != nil { + return err + } + if _, err = io.Copy(writer, zipFile); err != nil { + return err + } + return nil + }(file) + if err != nil { + return err + } + } + return nil +} + +// 从zip中删除指定文件 +func Del(zipFile, filesToDelete string) error { + // 打开zip文件进行读取 + reader, err := zip.OpenReader(zipFile) + if err != nil { + return err + } + defer reader.Close() + + // 创建一个新的zip文件用于写入 + newZipFile, err := os.Create(zipFile + ".tmp") + if err != nil { + return err + } + defer newZipFile.Close() + + zipWriter := zip.NewWriter(newZipFile) + defer zipWriter.Close() + + // 复制原zip文件中的内容到新zip文件中,但不包括要删除的文件 + for _, file := range reader.File { + if filesToDelete != "" && !strings.Contains(filesToDelete, file.Name) { + continue + } + writer, err := zipWriter.Create(file.Name) + if err != nil { + return err + } + + // 打开原zip文件中的文件进行读取 + oldFile, err := file.Open() + if err != nil { + return err + } + defer oldFile.Close() + + // 将内容复制到新zip文件中 + if _, err = io.Copy(writer, oldFile); err != nil { + return err + } + } + + // 删除原zip文件 + if err = os.Remove(zipFile); err != nil { + return err + } + + // 重命名新zip文件为原zip文件名 + if err = os.Rename(zipFile+".tmp", zipFile); err != nil { + return err + } + + return nil +} diff --git a/shutdown.sh b/shutdown.sh new file mode 100644 index 0000000..e69de29