github.com/voedger/voedger@v0.0.0-20240520144910-273e84102129/pkg/parser/utils.go (about)

     1  /*
     2  * Copyright (c) 2023-present unTill Pro, Ltd.
     3  * @author Michael Saigachenko
     4   */
     5  
     6  package parser
     7  
     8  import (
     9  	"fmt"
    10  	"reflect"
    11  	"strings"
    12  
    13  	"github.com/voedger/voedger/pkg/appdef"
    14  )
    15  
    16  func extractStatement(s any) interface{} {
    17  	v := reflect.ValueOf(s)
    18  	for i := 0; i < v.NumField(); i++ {
    19  		field := v.Field(i)
    20  		if field.Kind() == reflect.Ptr && !field.IsNil() {
    21  			return field.Interface()
    22  		}
    23  	}
    24  	panic("undefined statement")
    25  }
    26  
    27  func CompareParam(left, right FunctionParam) bool {
    28  	var lt, rt DataTypeOrDef
    29  	if left.NamedParam != nil {
    30  		lt = left.NamedParam.Type
    31  	} else {
    32  		lt = *left.UnnamedParamType
    33  	}
    34  	if right.NamedParam != nil {
    35  		rt = right.NamedParam.Type
    36  	} else {
    37  		rt = *right.UnnamedParamType
    38  	}
    39  	return lt == rt
    40  }
    41  
    42  func CompareParams(params []FunctionParam, f *FunctionStmt) error {
    43  	if len(params) != len(f.Params) {
    44  		return ErrFunctionParamsIncorrect
    45  	}
    46  	for i := 0; i < len(params); i++ {
    47  		if !CompareParam(params[i], f.Params[i]) {
    48  			return ErrFunctionParamsIncorrect
    49  		}
    50  	}
    51  	return nil
    52  }
    53  
    54  func iterate(c IStatementCollection, callback func(stmt interface{})) {
    55  	c.Iterate(func(stmt interface{}) {
    56  		callback(stmt)
    57  		if collection, ok := stmt.(IStatementCollection); ok {
    58  			iterate(collection, callback)
    59  		}
    60  	})
    61  }
    62  
    63  func resolveInCtx[stmtType *TableStmt | *TypeStmt | *FunctionStmt | *CommandStmt | *ProjectorStmt |
    64  	*RateStmt | *TagStmt | *WorkspaceStmt | *StorageStmt | *ViewStmt | *LimitStmt | *QueryStmt | *RoleStmt | *DeclareStmt](fn DefQName, ictx *iterateCtx, cb func(f stmtType, schema *PackageSchemaAST) error) error {
    65  	var err error
    66  	var item stmtType
    67  	var p *PackageSchemaAST
    68  	item, p, err = lookupInCtx[stmtType](fn, ictx)
    69  	if err != nil {
    70  		return err
    71  	}
    72  
    73  	if item == nil {
    74  		var value interface{} = item
    75  		switch value.(type) {
    76  		case *TableStmt:
    77  			return ErrUndefinedTable(fn)
    78  		case *CommandStmt:
    79  			return ErrUndefinedCommand(fn)
    80  		case *QueryStmt:
    81  			return ErrUndefinedQuery(fn)
    82  		case *TagStmt:
    83  			return ErrUndefinedTag(fn)
    84  		case *RoleStmt:
    85  			return ErrUndefinedRole(fn)
    86  		case *TypeStmt:
    87  			return ErrUndefinedType(fn)
    88  		case *WorkspaceStmt:
    89  			return ErrUndefinedWorkspace(fn)
    90  		case *RateStmt:
    91  			return ErrUndefinedRate(fn)
    92  		default:
    93  			return ErrUndefined(fn.String())
    94  		}
    95  	}
    96  	return cb(item, p)
    97  }
    98  
    99  func lookupInSysPackage[stmtType *WorkspaceStmt](ctx *basicContext, fn DefQName) (stmtType, error) {
   100  	sysSchema := ctx.app.Packages[appdef.SysPackage]
   101  	if sysSchema == nil {
   102  		return nil, ErrCouldNotImport(appdef.SysPackage)
   103  	}
   104  	ictx := &iterateCtx{
   105  		basicContext: ctx,
   106  		collection:   sysSchema.Ast,
   107  		pkg:          sysSchema,
   108  		parent:       nil,
   109  	}
   110  	s, _, e := lookupInCtx[stmtType](fn, ictx)
   111  	return s, e
   112  }
   113  
   114  func getCurrentAlterWorkspace(ictx *iterateCtx) *AlterWorkspaceStmt {
   115  	var ic *iterateCtx = ictx
   116  	for ic != nil {
   117  		if aw, isAlterWorkspace := ic.collection.(*AlterWorkspaceStmt); isAlterWorkspace {
   118  			return aw
   119  		}
   120  		ic = ic.parent
   121  	}
   122  	return nil
   123  }
   124  
   125  func getCurrentWorkspace(ictx *iterateCtx) *WorkspaceStmt {
   126  	var ic *iterateCtx = ictx
   127  	var ws *WorkspaceStmt = nil
   128  	for ic != nil {
   129  		if _, isWorkspace := ic.collection.(*WorkspaceStmt); isWorkspace {
   130  			ws = ic.collection.(*WorkspaceStmt)
   131  			break
   132  		}
   133  		ic = ic.parent
   134  	}
   135  	return ws
   136  }
   137  
   138  func lookupInCtx[stmtType *TableStmt | *TypeStmt | *FunctionStmt | *CommandStmt | *RateStmt | *TagStmt | *ProjectorStmt |
   139  	*WorkspaceStmt | *ViewStmt | *StorageStmt | *LimitStmt | *QueryStmt | *RoleStmt | *WsDescriptorStmt | *DeclareStmt](fn DefQName, ictx *iterateCtx) (stmtType, *PackageSchemaAST, error) {
   140  	schema, err := getTargetSchema(fn, ictx)
   141  	if err != nil {
   142  		return nil, nil, err
   143  	}
   144  
   145  	var item stmtType
   146  	var lookupCallback func(stmt interface{})
   147  	lookupCallback = func(stmt interface{}) {
   148  		if f, ok := stmt.(stmtType); ok && item == nil {
   149  			named := any(f).(INamedStatement)
   150  			if named.GetName() == string(fn.Name) {
   151  				item = f
   152  			}
   153  		}
   154  		if collection, ok := stmt.(IStatementCollection); ok && item == nil {
   155  			if _, isWorkspace := stmt.(*WorkspaceStmt); !isWorkspace { // do not go into workspaces
   156  				collection.Iterate(lookupCallback)
   157  			}
   158  		}
   159  		if t, ok := stmt.(*TableStmt); ok && item == nil {
   160  			for i := range t.Items {
   161  				if t.Items[i].NestedTable != nil {
   162  					lookupCallback(&t.Items[i].NestedTable.Table)
   163  				}
   164  			}
   165  		}
   166  	}
   167  
   168  	if schema == ictx.pkg {
   169  		ws := getCurrentWorkspace(ictx)
   170  		// First look in the current workspace
   171  		if ws != nil {
   172  			ws.Iterate(lookupCallback)
   173  			if item == nil {
   174  				var value interface{} = item
   175  				if _, ok := value.(*WorkspaceStmt); !ok { //  when looking for something else than a workspace, look in the inherited workspaces
   176  					for _, dq := range ws.Inherits {
   177  						err := resolveInCtx[*WorkspaceStmt](dq, ictx, func(f *WorkspaceStmt, schema *PackageSchemaAST) error {
   178  							f.Iterate(lookupCallback)
   179  							return nil
   180  						})
   181  						if err != nil {
   182  							return nil, nil, err
   183  						}
   184  					}
   185  				}
   186  			}
   187  		}
   188  
   189  		// Look in the package
   190  		if item == nil {
   191  			schema.Ast.Iterate(lookupCallback)
   192  		}
   193  
   194  		// Look in the sys package
   195  		if item == nil && maybeSysPkg(fn.Package) { // Look in sys pkg
   196  			schema = ictx.app.Packages[appdef.SysPackage]
   197  			if schema == nil {
   198  				return nil, nil, ErrCouldNotImport(appdef.SysPackage)
   199  			}
   200  			iterPkg := func(coll IStatementCollection) {
   201  				coll.Iterate(lookupCallback)
   202  			}
   203  			iterPkg(schema.Ast)
   204  		}
   205  		return item, schema, nil
   206  	}
   207  
   208  	schema.Ast.Iterate(lookupCallback)
   209  	return item, schema, nil
   210  }
   211  
   212  func iteratePackage(pkg *PackageSchemaAST, ctx *basicContext, callback func(stmt interface{}, ctx *iterateCtx)) {
   213  	ictx := &iterateCtx{
   214  		basicContext: ctx,
   215  		collection:   pkg.Ast,
   216  		pkg:          pkg,
   217  		parent:       nil,
   218  	}
   219  	iterateContext(ictx, callback)
   220  }
   221  
   222  func iteratePackageStmt[stmtType *TableStmt | *TypeStmt | *ViewStmt | *CommandStmt | *QueryStmt |
   223  	*WorkspaceStmt | *AlterWorkspaceStmt | *ProjectorStmt | *RateStmt](pkg *PackageSchemaAST, ctx *basicContext, callback func(stmt stmtType, ctx *iterateCtx)) {
   224  	iteratePackage(pkg, ctx, func(stmt interface{}, ctx *iterateCtx) {
   225  		if s, ok := stmt.(stmtType); ok {
   226  			callback(s, ctx)
   227  		}
   228  	})
   229  }
   230  
   231  func iterateContext(ictx *iterateCtx, callback func(stmt interface{}, ctx *iterateCtx)) {
   232  	ictx.collection.Iterate(func(stmt interface{}) {
   233  		callback(stmt, ictx)
   234  		if collection, ok := stmt.(IStatementCollection); ok {
   235  			iNestedCtx := &iterateCtx{
   236  				basicContext: ictx.basicContext,
   237  				collection:   collection,
   238  				pkg:          ictx.pkg,
   239  				parent:       ictx,
   240  				wsCtxs:       ictx.wsCtxs,
   241  			}
   242  			iterateContext(iNestedCtx, callback)
   243  		}
   244  	})
   245  }
   246  
   247  func isInternalName(pkgName Ident, pkgAst *PackageSchemaAST) bool {
   248  	pkg := strings.TrimSpace(string(pkgName))
   249  	return pkg == "" || pkg == pkgAst.Name
   250  }
   251  
   252  func getPackageName(pkgQN string) string {
   253  	parts := strings.Split(pkgQN, "/")
   254  	if len(parts) == 0 {
   255  		return ""
   256  	}
   257  	return parts[len(parts)-1]
   258  }
   259  
   260  func GetQualifiedPackageName(pkgName Ident, schema *SchemaAST) string {
   261  	for i := 0; i < len(schema.Imports); i++ {
   262  		imp := schema.Imports[i]
   263  		if imp.Alias != nil && *imp.Alias == pkgName {
   264  			return imp.Name
   265  		}
   266  	}
   267  	suffix := fmt.Sprintf("/%s", pkgName)
   268  	for i := 0; i < len(schema.Imports); i++ {
   269  		imp := schema.Imports[i]
   270  		if strings.HasSuffix(imp.Name, suffix) || imp.Name == string(pkgName) {
   271  			return imp.Name
   272  		}
   273  	}
   274  	return ""
   275  }
   276  
   277  func findPackage(pnkName Ident, c *iterateCtx) (*PackageSchemaAST, error) {
   278  	var targetPkgSch *PackageSchemaAST
   279  	if isInternalName(pnkName, c.pkg) {
   280  		return c.pkg, nil
   281  	}
   282  
   283  	if pnkName == appdef.SysPackage {
   284  		sysSchema := c.app.Packages[appdef.SysPackage]
   285  		if sysSchema == nil {
   286  			return nil, ErrCouldNotImport(appdef.SysPackage)
   287  		}
   288  		return sysSchema, nil
   289  	}
   290  
   291  	pkgQN := GetQualifiedPackageName(pnkName, c.pkg.Ast)
   292  	if pkgQN == "" {
   293  		return nil, ErrUndefined(string(pnkName))
   294  	}
   295  	targetPkgSch = c.app.Packages[pkgQN]
   296  	if targetPkgSch == nil {
   297  		return nil, ErrCouldNotImport(pkgQN)
   298  	}
   299  	return targetPkgSch, nil
   300  
   301  }
   302  
   303  func getTargetSchema(n DefQName, c *iterateCtx) (*PackageSchemaAST, error) {
   304  	return findPackage(n.Package, c)
   305  }
   306  
   307  func maybeSysPkg(pkg Ident) bool {
   308  	return (pkg == "" || pkg == appdef.SysPackage)
   309  }
   310  
   311  func getNestedTableKind(rootTableKind appdef.TypeKind) (appdef.TypeKind, error) {
   312  	switch rootTableKind {
   313  	case appdef.TypeKind_CDoc, appdef.TypeKind_CRecord:
   314  		return appdef.TypeKind_CRecord, nil
   315  	case appdef.TypeKind_ODoc, appdef.TypeKind_ORecord:
   316  		return appdef.TypeKind_ORecord, nil
   317  	case appdef.TypeKind_WDoc, appdef.TypeKind_WRecord:
   318  		return appdef.TypeKind_WRecord, nil
   319  	default:
   320  		return appdef.TypeKind_null, ErrUnexpectedRootTableKind(int(rootTableKind))
   321  	}
   322  }
   323  
   324  func dataTypeToDataKind(t DataType) appdef.DataKind {
   325  	if t.Blob {
   326  		return appdef.DataKind_RecordID
   327  	}
   328  	if t.Bool {
   329  		return appdef.DataKind_bool
   330  	}
   331  	if t.Bytes != nil {
   332  		return appdef.DataKind_bytes
   333  	}
   334  	if t.Currency {
   335  		return appdef.DataKind_int64
   336  	}
   337  	if t.Float32 {
   338  		return appdef.DataKind_float32
   339  	}
   340  	if t.Float64 {
   341  		return appdef.DataKind_float64
   342  	}
   343  	if t.Int32 {
   344  		return appdef.DataKind_int32
   345  	}
   346  	if t.Int64 {
   347  		return appdef.DataKind_int64
   348  	}
   349  	if t.QName {
   350  		return appdef.DataKind_QName
   351  	}
   352  	if t.Varchar != nil {
   353  		return appdef.DataKind_string
   354  	}
   355  	if t.Timestamp {
   356  		return appdef.DataKind_int64
   357  	}
   358  	if t.Record {
   359  		return appdef.DataKind_Record
   360  	}
   361  	return appdef.DataKind_null
   362  }
   363  
   364  func buildQname(ctx *iterateCtx, pkg Ident, name Ident) appdef.QName {
   365  	if pkg == "" {
   366  		pkg = Ident(ctx.pkg.Name)
   367  	}
   368  	return appdef.NewQName(string(pkg), string(name))
   369  }
   370  
   371  func contains(s []Identifier, e Ident) bool {
   372  	for _, a := range s {
   373  		if a.Value == e {
   374  			return true
   375  		}
   376  	}
   377  	return false
   378  }