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
}
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) {
// Convey("Should load a document", t, func() {
// c := compiler.New()

View File

@ -117,7 +117,13 @@ func (v *visitor) doVisitReturnExpression(ctx *fql.ReturnExpressionContext, scop
}
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())
if err != nil {
@ -125,9 +131,23 @@ func (v *visitor) doVisitReturnExpression(ctx *fql.ReturnExpressionContext, scop
}
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) {
@ -609,7 +629,9 @@ func (v *visitor) doVisitVariableDeclaration(ctx *fql.VariableDeclarationContext
if exp != nil {
init, err = v.doVisitExpression(ctx.Expression().(*fql.ExpressionContext), scope)
} else {
}
if init == nil && err == nil {
forIn := ctx.ForExpression()
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 {
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
}
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)
if err != nil {
return nil, err
}
result[idx] = out
result = append(result, out)
}
return result, nil
@ -794,24 +830,10 @@ func (v *visitor) doVisitExpression(ctx *fql.ExpressionContext, scope *scope) (c
return nil, err
}
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(
return v.createTernaryOperator(
v.getSourceMap(ctx),
test,
consequent,
alternate,
exps,
scope,
)
}
@ -860,14 +882,6 @@ func (v *visitor) doVisitExpression(ctx *fql.ExpressionContext, scope *scope) (c
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
return nil, ErrNotImplemented
}
@ -981,6 +995,42 @@ func (v *visitor) visit(node antlr.Tree, scope *scope) (core.Expression, error)
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 {
name := "undefined"
ctx, ok := node.(antlr.RuleContext)

View File

@ -23,6 +23,7 @@ bodyExpression
returnExpression
: Return (Distinct)? expression
| Return (Distinct)? OpenParen forExpression CloseParen
| Return forTernaryExpression
;
forExpression
@ -124,6 +125,7 @@ forExpressionReturn
variableDeclaration
: Let Identifier Assign expression
| Let Identifier Assign OpenParen forExpression CloseParen
| Let Identifier Assign forTernaryExpression
;
variable
@ -225,6 +227,12 @@ expression
| 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
: Gt
| Lt
@ -249,7 +257,7 @@ mathOperator
unaryOperator
: Not
| Plus
| Plus
| Minus
| 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.
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.
func (s *BaseFqlParserListener) EnterEqualityOperator(ctx *EqualityOperatorContext) {}

View File

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

View File

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

View File

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

View File

@ -40,3 +40,13 @@ func (p *Program) Run(ctx context.Context, setters ...Option) ([]byte, error) {
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()
case "length":
result = el.Length()
case "url":
if result.Type() == core.HtmlDocumentType {
doc, ok := result.(HtmlDocument)
if ok {
result = doc.Url()
}
}
default:
result = None
}

View File

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

View File

@ -164,11 +164,17 @@ func (el *HtmlElement) MarshalJSON() ([]byte, error) {
el.Lock()
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 {
return el.value.String()
return el.InnerHtml().String()
}
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")
}
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 {
el.Lock()
defer el.Unlock()

View File

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

View File

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