diff --git a/ast/ast_xgo.go b/ast/ast_xgo.go index 3a5de1ff0..ab3388704 100644 --- a/ast/ast_xgo.go +++ b/ast/ast_xgo.go @@ -250,10 +250,14 @@ func (*AnySelectorExpr) exprNode() {} // ----------------------------------------------------------------------------- // A CondExpr node represents a conditional expression: `expr @ cond`. +// - ns@(condExpr) +// - ns@fn(args) +// - ns@"elem-name" (Cond will be a *Ident with name "elem-name", not a *BasicLit) +// - ns@name type CondExpr struct { X Expr // expression OpPos token.Pos // position of "@" - Cond Expr // condition expression + Cond Expr // condition expression (can be *CallExpr, *ParenExpr or *Ident) } // Pos - position of first character belonging to the node. diff --git a/cl/_testgop/dql3/in.xgo b/cl/_testgop/dql3/in.xgo new file mode 100644 index 000000000..abd207dbd --- /dev/null +++ b/cl/_testgop/dql3/in.xgo @@ -0,0 +1,4 @@ +import "github.com/goplus/xgo/cl/internal/dql" + +doc := dql.new +echo doc.*@users@`users`.$name diff --git a/cl/_testgop/dql3/out.go b/cl/_testgop/dql3/out.go new file mode 100644 index 000000000..56eb89ebc --- /dev/null +++ b/cl/_testgop/dql3/out.go @@ -0,0 +1,11 @@ +package main + +import ( + "fmt" + "github.com/goplus/xgo/cl/internal/dql" +) + +func main() { + doc := dql.New() + fmt.Println(doc.XGo_Child().XGo_Select("users").XGo_Select("users").XGo_Attr__0("name")) +} diff --git a/cl/error_msg_test.go b/cl/error_msg_test.go index dc4bc49d0..0eaeff7ed 100644 --- a/cl/error_msg_test.go +++ b/cl/error_msg_test.go @@ -679,6 +679,17 @@ b := *a `) } +func TestErrCondExpr(t *testing.T) { + codeErrorTest(t, + `bar.xgo:5:6: assignment mismatch: 2 variables but self.XGo_first returns 1 values +don't call End(), please use EndInit() instead`, ` +import "github.com/goplus/xgo/cl/internal/dql" + +doc := dql.new2 +echo doc.users@($age < 18).$name +`) +} + func TestErrMember(t *testing.T) { codeErrorTest(t, `bar.xgo:3:6: a.$x undefined (type string has no field or method XGo_Attr)`, diff --git a/cl/expr.go b/cl/expr.go index f6e668595..d4201273c 100644 --- a/cl/expr.go +++ b/cl/expr.go @@ -508,8 +508,19 @@ func compileCondExpr(ctx *blockCtx, v *ast.CondExpr) { nameErr = "_xgo_err" ) xExpr := v.X + condExpr := v.Cond + cb := ctx.cb compileExpr(ctx, 1, xExpr) - cb, pkg := ctx.cb, ctx.pkg + if id, ok := condExpr.(*ast.Ident); ok { + name := id.Name + switch name[0] { + case '"', '`': // @"elem-name" + name = unquote(name) + } + cb.MemberVal("XGo_Select", 0, v).Val(name, id).CallWith(1, 1, 0, v) + return + } + pkg := ctx.pkg x := cb.Get(-1) // x.Type is NodeSet nsType := x.Type pkgTypes := pkg.Types @@ -518,13 +529,12 @@ func compileCondExpr(ctx *blockCtx, v *ast.CondExpr) { yieldParams := types.NewTuple(varSelf) yieldRets := types.NewTuple(types.NewParam(0, nil, "", types.Typ[types.Bool])) sigYield := types.NewSignatureType(nil, nil, nil, yieldParams, yieldRets, false) - condExpr := v.Cond cb.NewClosureWith(sigYield).BodyStart(pkg, condExpr). If(condExpr) compileExpr(ctx, 1, condExpr) cb.Then(condExpr). If().DefineVarStart(0, nameVal, nameErr). - Val(varSelf).MemberVal("XGo_first", 0).CallWith(0, 2, 0) + Val(varSelf).MemberVal("XGo_first", 0, v).CallWith(0, 2, 0, v) firstRet := cb.Get(-1) nodeType := firstRet.Type.(*types.Tuple).At(0).Type() varYield := newNodeSeqParam(pkgTypes, nodeType) @@ -556,13 +566,13 @@ func newNodeSeqParam(pkgTypes *types.Package, nodeType types.Type) *types.Var { func compileAnySelectorExpr(ctx *blockCtx, lhs int, v *ast.AnySelectorExpr) { compileExpr(ctx, 0, v.X) // DQL (DOM Query Language) rules: - // - selector.**.name -> XGo_Any(name) - descendants by name - // - selector.**."name" -> XGo_Any(name) - descendants by name - // - selector.**.* -> XGo_Any("") - all descendants + // - selector.**.name -> XGo_Any("name") - descendants by name + // - selector.**."elem-name" -> XGo_Any("elem-name") - descendants by name + // - selector.**.* -> XGo_Any("") - all descendants cb, sel := ctx.cb, v.Sel name := sel.Name switch name[0] { - case '"': + case '"', '`': // ."elem-name" name = unquote(name) case '*': name = "" @@ -601,7 +611,7 @@ func compileSelectorExpr(ctx *blockCtx, lhs int, v *ast.SelectorExpr, flags int) if err := compileAttr(cb, lhs, name[1:], v); err != nil { panic(err) // throw error } - case '"': + case '"', '`': name = unquote(name) fallthrough default: @@ -617,7 +627,8 @@ func compileSelectorExpr(ctx *blockCtx, lhs int, v *ast.SelectorExpr, flags int) func compileAttr(cb *gogen.CodeBuilder, lhs int, name string, v ast.Node) error { _, e := cb.Member("XGo_Attr", 1, gogen.MemberFlagVal, v) if e == nil { - if strings.HasPrefix(name, `"`) { + switch name[0] { + case '"', '`': // @"attr-name" name = unquote(name) } cb.Val(name).CallWith(1, lhs, 0, v) diff --git a/cl/internal/dql/dql.go b/cl/internal/dql/dql.go index 4768e63f1..4dd5e49eb 100644 --- a/cl/internal/dql/dql.go +++ b/cl/internal/dql/dql.go @@ -31,6 +31,10 @@ func (p NodeSet) XGo_Any(name string) NodeSet { return NodeSet{} } +func (p NodeSet) XGo_Select(name string) NodeSet { + return NodeSet{} +} + // XGo_Child returns a NodeSet containing all child nodes of the nodes in the NodeSet. func (p NodeSet) XGo_Child() NodeSet { return NodeSet{} @@ -53,3 +57,30 @@ func (p NodeSet) XGo_Attr__0(name string) int { func (p NodeSet) XGo_Attr__1(name string) (int, error) { return 0, nil } + +type NodeSet2 struct { +} + +func New2() NodeSet2 { + return NodeSet2{} +} + +func NodeSet2_Cast(func(yield func(*Node) bool)) NodeSet2 { + return NodeSet2{} +} + +func (p NodeSet2) XGo_first() *Node { + return nil +} + +func (p NodeSet2) XGo_Enum() iter.Seq[NodeSet2] { + return nil +} + +func (p NodeSet2) XGo_Elem(name string) NodeSet2 { + return NodeSet2{} +} + +func (p NodeSet2) XGo_Attr(name string) int { + return 0 +} diff --git a/parser/_testdata/dql/dql.xgo b/parser/_testdata/dql1/dql.xgo similarity index 100% rename from parser/_testdata/dql/dql.xgo rename to parser/_testdata/dql1/dql.xgo diff --git a/parser/_testdata/dql/parser.expect b/parser/_testdata/dql1/parser.expect similarity index 100% rename from parser/_testdata/dql/parser.expect rename to parser/_testdata/dql1/parser.expect diff --git a/parser/_testdata/dql2/dql.xgo b/parser/_testdata/dql2/dql.xgo new file mode 100644 index 000000000..f747bd850 --- /dev/null +++ b/parser/_testdata/dql2/dql.xgo @@ -0,0 +1 @@ +echo doc.*@users@"users".$name diff --git a/parser/_testdata/dql2/parser.expect b/parser/_testdata/dql2/parser.expect new file mode 100644 index 000000000..ee108b680 --- /dev/null +++ b/parser/_testdata/dql2/parser.expect @@ -0,0 +1,44 @@ +package main + +file dql.xgo +noEntrypoint +ast.FuncDecl: + Name: + ast.Ident: + Name: main + Type: + ast.FuncType: + Params: + ast.FieldList: + Body: + ast.BlockStmt: + List: + ast.ExprStmt: + X: + ast.CallExpr: + Fun: + ast.Ident: + Name: echo + Args: + ast.SelectorExpr: + X: + ast.CondExpr: + X: + ast.CondExpr: + X: + ast.SelectorExpr: + X: + ast.Ident: + Name: doc + Sel: + ast.Ident: + Name: * + Cond: + ast.Ident: + Name: users + Cond: + ast.Ident: + Name: "users" + Sel: + ast.Ident: + Name: $name diff --git a/parser/parser.go b/parser/parser.go index 896d16788..14c6f03b5 100644 --- a/parser/parser.go +++ b/parser/parser.go @@ -2634,15 +2634,22 @@ L: ce := &ast.CondExpr{X: x, OpPos: p.pos} p.next() switch p.tok { - case token.IDENT: - fun := p.parseIdent() - ce.Cond = p.parseCallOrConversion(fun, false) // @fun(...) case token.LPAREN: cond, kind := p.parseOperand(0) // @(cond) if kind != exprNormal { p.error(cond.Pos(), "invalid condition expression") } ce.Cond = p.checkExpr(cond) + case token.IDENT: + fun := p.parseIdent() + if p.tok == token.LPAREN { + ce.Cond = p.parseCallOrConversion(fun, false) // @fun(...) + } else { + ce.Cond = fun // @name + } + case token.STRING: // @"elem-name" + ce.Cond = &ast.Ident{NamePos: p.pos, Name: p.lit} + p.next() default: pos := p.pos p.errorExpected(pos, "condition expression", 2)