1
0
mirror of https://github.com/MontFerret/ferret.git synced 2024-12-12 11:15:14 +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
@ -249,7 +257,7 @@ mathOperator
unaryOperator unaryOperator
: Not : Not
| Plus | Plus
| Minus | Minus
| Like | Like
; ;

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,30 +2,38 @@ package values
import "github.com/MontFerret/ferret/pkg/runtime/core" import "github.com/MontFerret/ferret/pkg/runtime/core"
type HtmlNode interface { type (
core.Value HtmlNode interface {
core.Value
NodeType() Int NodeType() Int
NodeName() String NodeName() String
Length() Int Length() Int
InnerText() String InnerText() String
InnerHtml() String InnerHtml() String
Value() core.Value Value() core.Value
GetAttributes() core.Value GetAttributes() core.Value
GetAttribute(name String) core.Value GetAttribute(name String) core.Value
GetChildNodes() core.Value GetChildNodes() core.Value
GetChildNode(idx Int) core.Value GetChildNode(idx Int) core.Value
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,
} }
} }