1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-14 11:23:02 +02:00

Added possibility to use FOR loop in ternary expression

This commit is contained in:
Tim Voronov 2018-09-27 19:05:56 -04:00
parent ef29241aa6
commit 8b2e210317
20 changed files with 1175 additions and 561 deletions

24
docs/examples/input.fql Normal file
View File

@ -0,0 +1,24 @@
LET g = DOCUMENT("https://www.google.com/", true)
LET inputBox = ELEMENT(g, 'input[name="q"]')
INPUT(inputBox, "ferrer")
LET searchBtn = ELEMENT(g, 'input[name="btnK"]')
CLICK(searchBtn)
WAIT_NAVIGATION(g)
LET result = ELEMENTS(g, '.g')
LOG(result ? 'element' : 'no element')
RETURN result ? (
FOR result IN ELEMENTS(g, '.g')
LOG('iterate')
RETURN {
title: ELEMENT(result, 'h3 > a'),
description: ELEMENT(result, '.st'),
url: ELEMENT(result, 'cite')
}
) : 'no results'

View File

@ -90,3 +90,13 @@ func (c *FqlCompiler) Compile(query string) (program *runtime.Program, err error
return program, err return program, err
} }
func (c *FqlCompiler) CompileP(query string) *runtime.Program {
program, err := c.Compile(query)
if err != nil {
panic(err)
}
return program
}

View File

@ -1602,6 +1602,92 @@ func TestInOperator(t *testing.T) {
}) })
} }
func TestForTernaryExpression(t *testing.T) {
Convey("RETURN foo ? TRUE : (FOR i IN 1..5 RETURN i*2)", t, func() {
c := compiler.New()
out1, err := c.CompileP(`
LET foo = FALSE
RETURN foo ? TRUE : (FOR i IN 1..5 RETURN i*2)
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out1), ShouldEqual, `[2,4,6,8,10]`)
out2, err := c.CompileP(`
LET foo = TRUE
RETURN foo ? TRUE : (FOR i IN 1..5 RETURN i*2)
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out2), ShouldEqual, `true`)
})
Convey("RETURN foo ? (FOR i IN 1..5 RETURN i) : (FOR i IN 1..5 RETURN i*2)", t, func() {
c := compiler.New()
out1, err := c.CompileP(`
LET foo = FALSE
RETURN foo ? (FOR i IN 1..5 RETURN i) : (FOR i IN 1..5 RETURN i*2)
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out1), ShouldEqual, `[2,4,6,8,10]`)
out2, err := c.CompileP(`
LET foo = TRUE
RETURN foo ? (FOR i IN 1..5 RETURN i) : (FOR i IN 1..5 RETURN i*2)
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out2), ShouldEqual, `[1,2,3,4,5]`)
})
Convey("LET res = foo ? TRUE : (FOR i IN 1..5 RETURN i*2)", t, func() {
c := compiler.New()
out1, err := c.CompileP(`
LET foo = FALSE
LET res = foo ? TRUE : (FOR i IN 1..5 RETURN i*2)
RETURN res
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out1), ShouldEqual, `[2,4,6,8,10]`)
out2, err := c.CompileP(`
LET foo = TRUE
LET res = foo ? TRUE : (FOR i IN 1..5 RETURN i*2)
RETURN res
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out2), ShouldEqual, `true`)
})
Convey("LET res = foo ? (FOR i IN 1..5 RETURN i) : (FOR i IN 1..5 RETURN i*2)", t, func() {
c := compiler.New()
out1, err := c.CompileP(`
LET foo = FALSE
LET res = foo ? (FOR i IN 1..5 RETURN i) : (FOR i IN 1..5 RETURN i*2)
RETURN res
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out1), ShouldEqual, `[2,4,6,8,10]`)
out2, err := c.CompileP(`
LET foo = TRUE
LET res = foo ? (FOR i IN 1..5 RETURN i) : (FOR i IN 1..5 RETURN i*2)
RETURN res
`).Run(context.Background())
So(err, ShouldBeNil)
So(string(out2), ShouldEqual, `[1,2,3,4,5]`)
})
}
//func TestHtml(t *testing.T) { //func TestHtml(t *testing.T) {
// Convey("Should load a document", t, func() { // Convey("Should load a document", t, func() {
// c := compiler.New() // c := compiler.New()

View File

@ -117,7 +117,13 @@ func (v *visitor) doVisitReturnExpression(ctx *fql.ReturnExpressionContext, scop
} }
exp = out exp = out
} else {
return expressions.NewReturnExpression(v.getSourceMap(ctx), exp)
}
forIn := ctx.ForExpression()
if forIn != nil {
out, err := v.doVisitForExpression(ctx.ForExpression().(*fql.ForExpressionContext), scope.Fork()) out, err := v.doVisitForExpression(ctx.ForExpression().(*fql.ForExpressionContext), scope.Fork())
if err != nil { if err != nil {
@ -125,9 +131,23 @@ func (v *visitor) doVisitReturnExpression(ctx *fql.ReturnExpressionContext, scop
} }
exp = out exp = out
}
return expressions.NewReturnExpression(v.getSourceMap(ctx), exp) return expressions.NewReturnExpression(v.getSourceMap(ctx), exp)
}
forInTernary := ctx.ForTernaryExpression()
if forInTernary != nil {
out, err := v.doVisitForTernaryExpression(forInTernary.(*fql.ForTernaryExpressionContext), scope)
if err != nil {
return nil, err
}
return expressions.NewReturnExpression(v.getSourceMap(ctx), out)
}
return nil, ErrNotImplemented
} }
func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *scope) (core.Expression, error) { func (v *visitor) doVisitForExpression(ctx *fql.ForExpressionContext, scope *scope) (core.Expression, error) {
@ -609,7 +629,9 @@ func (v *visitor) doVisitVariableDeclaration(ctx *fql.VariableDeclarationContext
if exp != nil { if exp != nil {
init, err = v.doVisitExpression(ctx.Expression().(*fql.ExpressionContext), scope) init, err = v.doVisitExpression(ctx.Expression().(*fql.ExpressionContext), scope)
} else { }
if init == nil && err == nil {
forIn := ctx.ForExpression() forIn := ctx.ForExpression()
if forIn != nil { if forIn != nil {
@ -617,6 +639,14 @@ func (v *visitor) doVisitVariableDeclaration(ctx *fql.VariableDeclarationContext
} }
} }
if init == nil && err == nil {
forTer := ctx.ForTernaryExpression()
if forTer != nil {
init, err = v.doVisitForTernaryExpression(forTer.(*fql.ForTernaryExpressionContext), scope)
}
}
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -661,16 +691,22 @@ func (v *visitor) doVisitChildren(node antlr.RuleNode, scope *scope) ([]core.Exp
return make([]core.Expression, 0, 0), nil return make([]core.Expression, 0, 0), nil
} }
result := make([]core.Expression, len(children)) result := make([]core.Expression, 0, len(children))
for _, child := range children {
_, ok := child.(antlr.TerminalNode)
if ok {
continue
}
for idx, child := range children {
out, err := v.visit(child, scope) out, err := v.visit(child, scope)
if err != nil { if err != nil {
return nil, err return nil, err
} }
result[idx] = out result = append(result, out)
} }
return result, nil return result, nil
@ -794,24 +830,10 @@ func (v *visitor) doVisitExpression(ctx *fql.ExpressionContext, scope *scope) (c
return nil, err return nil, err
} }
var test core.Expression return v.createTernaryOperator(
var consequent core.Expression
var alternate core.Expression
if len(exps) == 3 {
test = exps[0]
consequent = exps[1]
alternate = exps[2]
} else {
test = exps[0]
alternate = exps[1]
}
return expressions.NewConditionExpression(
v.getSourceMap(ctx), v.getSourceMap(ctx),
test, exps,
consequent, scope,
alternate,
) )
} }
@ -860,14 +882,6 @@ func (v *visitor) doVisitExpression(ctx *fql.ExpressionContext, scope *scope) (c
return v.doVisitRangeOperator(rangeOp.(*fql.RangeOperatorContext), scope) return v.doVisitRangeOperator(rangeOp.(*fql.RangeOperatorContext), scope)
} }
seq := ctx.ExpressionSequence()
if seq != nil {
// seq := seq.(*fql.ExpressionSequenceContext)
return nil, core.Error(ErrNotImplemented, "expression sequence")
}
// TODO: Complete it // TODO: Complete it
return nil, ErrNotImplemented return nil, ErrNotImplemented
} }
@ -981,6 +995,42 @@ func (v *visitor) visit(node antlr.Tree, scope *scope) (core.Expression, error)
return out, err return out, err
} }
func (v *visitor) doVisitForTernaryExpression(ctx *fql.ForTernaryExpressionContext, scope *scope) (*expressions.ConditionExpression, error) {
exps, err := v.doVisitChildren(ctx, scope)
if err != nil {
return nil, err
}
return v.createTernaryOperator(
v.getSourceMap(ctx),
exps,
scope,
)
}
func (v *visitor) createTernaryOperator(src core.SourceMap, exps []core.Expression, scope *scope) (*expressions.ConditionExpression, error) {
var test core.Expression
var consequent core.Expression
var alternate core.Expression
if len(exps) == 3 {
test = exps[0]
consequent = exps[1]
alternate = exps[2]
} else {
test = exps[0]
alternate = exps[1]
}
return expressions.NewConditionExpression(
src,
test,
consequent,
alternate,
)
}
func (v *visitor) unexpectedToken(node antlr.Tree) error { func (v *visitor) unexpectedToken(node antlr.Tree) error {
name := "undefined" name := "undefined"
ctx, ok := node.(antlr.RuleContext) ctx, ok := node.(antlr.RuleContext)

View File

@ -23,6 +23,7 @@ bodyExpression
returnExpression returnExpression
: Return (Distinct)? expression : Return (Distinct)? expression
| Return (Distinct)? OpenParen forExpression CloseParen | Return (Distinct)? OpenParen forExpression CloseParen
| Return forTernaryExpression
; ;
forExpression forExpression
@ -124,6 +125,7 @@ forExpressionReturn
variableDeclaration variableDeclaration
: Let Identifier Assign expression : Let Identifier Assign expression
| Let Identifier Assign OpenParen forExpression CloseParen | Let Identifier Assign OpenParen forExpression CloseParen
| Let Identifier Assign forTernaryExpression
; ;
variable variable
@ -225,6 +227,12 @@ expression
| noneLiteral | noneLiteral
; ;
forTernaryExpression
: expression QuestionMark expression? Colon OpenParen forExpression CloseParen
| expression QuestionMark OpenParen forExpression CloseParen Colon expression
| expression QuestionMark OpenParen forExpression CloseParen Colon OpenParen forExpression CloseParen
;
equalityOperator equalityOperator
: Gt : Gt
| Lt | Lt

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load Diff

View File

@ -288,6 +288,12 @@ func (s *BaseFqlParserListener) EnterExpression(ctx *ExpressionContext) {}
// ExitExpression is called when production expression is exited. // ExitExpression is called when production expression is exited.
func (s *BaseFqlParserListener) ExitExpression(ctx *ExpressionContext) {} func (s *BaseFqlParserListener) ExitExpression(ctx *ExpressionContext) {}
// EnterForTernaryExpression is called when production forTernaryExpression is entered.
func (s *BaseFqlParserListener) EnterForTernaryExpression(ctx *ForTernaryExpressionContext) {}
// ExitForTernaryExpression is called when production forTernaryExpression is exited.
func (s *BaseFqlParserListener) ExitForTernaryExpression(ctx *ForTernaryExpressionContext) {}
// EnterEqualityOperator is called when production equalityOperator is entered. // EnterEqualityOperator is called when production equalityOperator is entered.
func (s *BaseFqlParserListener) EnterEqualityOperator(ctx *EqualityOperatorContext) {} func (s *BaseFqlParserListener) EnterEqualityOperator(ctx *EqualityOperatorContext) {}

View File

@ -183,6 +183,10 @@ func (v *BaseFqlParserVisitor) VisitExpression(ctx *ExpressionContext) interface
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }
func (v *BaseFqlParserVisitor) VisitForTernaryExpression(ctx *ForTernaryExpressionContext) interface{} {
return v.VisitChildren(ctx)
}
func (v *BaseFqlParserVisitor) VisitEqualityOperator(ctx *EqualityOperatorContext) interface{} { func (v *BaseFqlParserVisitor) VisitEqualityOperator(ctx *EqualityOperatorContext) interface{} {
return v.VisitChildren(ctx) return v.VisitChildren(ctx)
} }

View File

@ -139,6 +139,9 @@ type FqlParserListener interface {
// EnterExpression is called when entering the expression production. // EnterExpression is called when entering the expression production.
EnterExpression(c *ExpressionContext) EnterExpression(c *ExpressionContext)
// EnterForTernaryExpression is called when entering the forTernaryExpression production.
EnterForTernaryExpression(c *ForTernaryExpressionContext)
// EnterEqualityOperator is called when entering the equalityOperator production. // EnterEqualityOperator is called when entering the equalityOperator production.
EnterEqualityOperator(c *EqualityOperatorContext) EnterEqualityOperator(c *EqualityOperatorContext)
@ -283,6 +286,9 @@ type FqlParserListener interface {
// ExitExpression is called when exiting the expression production. // ExitExpression is called when exiting the expression production.
ExitExpression(c *ExpressionContext) ExitExpression(c *ExpressionContext)
// ExitForTernaryExpression is called when exiting the forTernaryExpression production.
ExitForTernaryExpression(c *ForTernaryExpressionContext)
// ExitEqualityOperator is called when exiting the equalityOperator production. // ExitEqualityOperator is called when exiting the equalityOperator production.
ExitEqualityOperator(c *EqualityOperatorContext) ExitEqualityOperator(c *EqualityOperatorContext)

View File

@ -139,6 +139,9 @@ type FqlParserVisitor interface {
// Visit a parse tree produced by FqlParser#expression. // Visit a parse tree produced by FqlParser#expression.
VisitExpression(ctx *ExpressionContext) interface{} VisitExpression(ctx *ExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#forTernaryExpression.
VisitForTernaryExpression(ctx *ForTernaryExpressionContext) interface{}
// Visit a parse tree produced by FqlParser#equalityOperator. // Visit a parse tree produced by FqlParser#equalityOperator.
VisitEqualityOperator(ctx *EqualityOperatorContext) interface{} VisitEqualityOperator(ctx *EqualityOperatorContext) interface{}

View File

@ -40,3 +40,13 @@ func (p *Program) Run(ctx context.Context, setters ...Option) ([]byte, error) {
return out.MarshalJSON() return out.MarshalJSON()
} }
func (p *Program) RunP(ctx context.Context, setters ...Option) []byte {
out, err := p.Run(ctx, setters...)
if err != nil {
panic(err)
}
return out
}

View File

@ -67,6 +67,14 @@ func GetIn(from core.Value, byPath []core.Value) (core.Value, error) {
result = el.GetChildNodes() result = el.GetChildNodes()
case "length": case "length":
result = el.Length() result = el.Length()
case "url":
if result.Type() == core.HtmlDocumentType {
doc, ok := result.(HtmlDocument)
if ok {
result = doc.Url()
}
}
default: default:
result = None result = None
} }

View File

@ -2,7 +2,8 @@ package values
import "github.com/MontFerret/ferret/pkg/runtime/core" import "github.com/MontFerret/ferret/pkg/runtime/core"
type HtmlNode interface { type (
HtmlNode interface {
core.Value core.Value
NodeType() Int NodeType() Int
@ -28,4 +29,11 @@ type HtmlNode interface {
QuerySelector(selector String) core.Value QuerySelector(selector String) core.Value
QuerySelectorAll(selector String) core.Value QuerySelectorAll(selector String) core.Value
} }
HtmlDocument interface {
HtmlNode
Url() core.Value
}
)

View File

@ -91,3 +91,32 @@ func Navigate(_ context.Context, args ...core.Value) (core.Value, error) {
return values.None, doc.Navigate(args[1].(values.String)) return values.None, doc.Navigate(args[1].(values.String))
} }
func Input(_ context.Context, args ...core.Value) (core.Value, error) {
err := core.ValidateArgs(args, 2, 3)
if err != nil {
return values.None, err
}
// TYPE(el, "foobar")
if len(args) == 2 {
arg1 := args[0]
err := core.ValidateType(arg1, core.HtmlElementType)
if err != nil {
return values.False, err
}
el, ok := arg1.(*dynamic.HtmlElement)
if !ok {
return values.False, core.Error(core.ErrInvalidType, "expected dynamic element")
}
return values.None, el.Input(args[1], values.NewInt(100))
}
return values.None, nil
}

View File

@ -15,7 +15,6 @@ import (
"github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/protocol/page"
"github.com/mafredri/cdp/rpcc" "github.com/mafredri/cdp/rpcc"
"github.com/pkg/errors" "github.com/pkg/errors"
"strings"
"sync" "sync"
"time" "time"
) )
@ -25,7 +24,7 @@ type HtmlDocument struct {
conn *rpcc.Conn conn *rpcc.Conn
client *cdp.Client client *cdp.Client
events *events.EventBroker events *events.EventBroker
url string url values.String
element *HtmlElement element *HtmlElement
} }
@ -132,7 +131,7 @@ func NewHtmlDocument(
doc.url = "" doc.url = ""
if root.BaseURL != nil { if root.BaseURL != nil {
doc.url = *root.BaseURL doc.url = values.NewString(*root.BaseURL)
} }
broker.AddEventListener("load", func(_ interface{}) { broker.AddEventListener("load", func(_ interface{}) {
@ -154,7 +153,7 @@ func NewHtmlDocument(
doc.url = "" doc.url = ""
if updated.BaseURL != nil { if updated.BaseURL != nil {
doc.url = *updated.BaseURL doc.url = values.NewString(*updated.BaseURL)
} }
}) })
@ -176,7 +175,7 @@ func (doc *HtmlDocument) String() string {
doc.Lock() doc.Lock()
defer doc.Unlock() defer doc.Unlock()
return doc.url return doc.url.String()
} }
func (doc *HtmlDocument) Unwrap() interface{} { func (doc *HtmlDocument) Unwrap() interface{} {
@ -213,7 +212,7 @@ func (doc *HtmlDocument) Compare(other core.Value) int {
case core.HtmlDocumentType: case core.HtmlDocumentType:
other := other.(*HtmlDocument) other := other.(*HtmlDocument)
return strings.Compare(doc.url, other.url) return doc.url.Compare(other.url)
default: default:
if other.Type() > core.HtmlDocumentType { if other.Type() > core.HtmlDocumentType {
return -1 return -1
@ -320,6 +319,10 @@ func (doc *HtmlDocument) QuerySelectorAll(selector values.String) core.Value {
return doc.element.QuerySelectorAll(selector) return doc.element.QuerySelectorAll(selector)
} }
func (doc *HtmlDocument) Url() core.Value {
return doc.url
}
func (doc *HtmlDocument) ClickBySelector(selector values.String) (values.Boolean, error) { func (doc *HtmlDocument) ClickBySelector(selector values.String) (values.Boolean, error) {
res, err := eval.Eval( res, err := eval.Eval(
doc.client, doc.client,

View File

@ -164,11 +164,17 @@ func (el *HtmlElement) MarshalJSON() ([]byte, error) {
el.Lock() el.Lock()
defer el.Unlock() defer el.Unlock()
return json.Marshal(el.innerHtml) val, err := el.innerText.Value()
if err != nil {
return nil, err
}
return json.Marshal(val.String())
} }
func (el *HtmlElement) String() string { func (el *HtmlElement) String() string {
return el.value.String() return el.InnerHtml().String()
} }
func (el *HtmlElement) Compare(other core.Value) int { func (el *HtmlElement) Compare(other core.Value) int {
@ -372,6 +378,13 @@ func (el *HtmlElement) Click() (values.Boolean, error) {
return events.DispatchEvent(ctx, el.client, el.id, "click") return events.DispatchEvent(ctx, el.client, el.id, "click")
} }
func (el *HtmlElement) Input(value core.Value, timeout values.Int) error {
ctx, cancel := contextWithTimeout()
defer cancel()
return el.client.DOM.SetAttributeValue(ctx, dom.NewSetAttributeValueArgs(el.id, "value", value.String()))
}
func (el *HtmlElement) IsConnected() values.Boolean { func (el *HtmlElement) IsConnected() values.Boolean {
el.Lock() el.Lock()
defer el.Unlock() defer el.Unlock()

View File

@ -2,12 +2,13 @@ package static
import ( import (
"github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/core"
"github.com/MontFerret/ferret/pkg/runtime/values"
"github.com/PuerkitoBio/goquery" "github.com/PuerkitoBio/goquery"
) )
type HtmlDocument struct { type HtmlDocument struct {
*HtmlElement *HtmlElement
url string url values.String
} }
func NewHtmlDocument( func NewHtmlDocument(
@ -28,7 +29,7 @@ func NewHtmlDocument(
return nil, err return nil, err
} }
return &HtmlDocument{el, url}, nil return &HtmlDocument{el, values.NewString(url)}, nil
} }
func (el *HtmlDocument) Type() core.Type { func (el *HtmlDocument) Type() core.Type {
@ -38,8 +39,9 @@ func (el *HtmlDocument) Type() core.Type {
func (el *HtmlDocument) Compare(other core.Value) int { func (el *HtmlDocument) Compare(other core.Value) int {
switch other.Type() { switch other.Type() {
case core.HtmlDocumentType: case core.HtmlDocumentType:
// TODO: complete the comparison otherDoc := other.(values.HtmlDocument)
return -1
return el.url.Compare(otherDoc.Url())
default: default:
if other.Type() > core.HtmlDocumentType { if other.Type() > core.HtmlDocumentType {
return -1 return -1
@ -48,3 +50,7 @@ func (el *HtmlDocument) Compare(other core.Value) int {
return 1 return 1
} }
} }
func (el *HtmlDocument) Url() core.Value {
return el.url
}

View File

@ -24,13 +24,7 @@ func NewHtmlElement(node *goquery.Selection) (*HtmlElement, error) {
} }
func (el *HtmlElement) MarshalJSON() ([]byte, error) { func (el *HtmlElement) MarshalJSON() ([]byte, error) {
html, err := el.selection.Html() return json.Marshal(el.InnerText().String())
if err != nil {
return nil, err
}
return json.Marshal(html)
} }
func (el *HtmlElement) Type() core.Type { func (el *HtmlElement) Type() core.Type {
@ -38,7 +32,7 @@ func (el *HtmlElement) Type() core.Type {
} }
func (el *HtmlElement) String() string { func (el *HtmlElement) String() string {
return el.selection.Text() return el.InnerHtml().String()
} }
func (el *HtmlElement) Compare(other core.Value) int { func (el *HtmlElement) Compare(other core.Value) int {

View File

@ -12,5 +12,6 @@ func NewLib() map[string]core.Function {
"WAIT_NAVIGATION": WaitNavigation, "WAIT_NAVIGATION": WaitNavigation,
"CLICK": Click, "CLICK": Click,
"NAVIGATE": Navigate, "NAVIGATE": Navigate,
"INPUT": Input,
} }
} }