diff --git a/e2e/cli.go b/e2e/cli.go index a2e4b742..25a0b38e 100644 --- a/e2e/cli.go +++ b/e2e/cli.go @@ -6,16 +6,18 @@ import ( "encoding/json" "flag" "fmt" + "github.com/rs/zerolog" "io/ioutil" "os" + "path/filepath" "strings" "github.com/MontFerret/ferret" - "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp" "github.com/MontFerret/ferret/pkg/drivers/http" "github.com/MontFerret/ferret/pkg/runtime" "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/logging" ) type Params []string @@ -61,8 +63,16 @@ var ( "", "set CDP address", ) + + logLevel = flag.String( + "log-level", + logging.ErrorLevel.String(), + "log level", + ) ) +var logger zerolog.Logger + func main() { var params Params @@ -74,10 +84,21 @@ func main() { flag.Parse() - var query string + console := zerolog.ConsoleWriter{ + Out: os.Stderr, + TimeFormat: "15:04:05.999", + } + logger = zerolog.New(console). + Level(zerolog.Level(logging.MustParseLevel(*logLevel))). + With(). + Timestamp(). + Logger() stat, _ := os.Stdin.Stat() + var query string + var files []string + if (stat.Mode() & os.ModeCharDevice) == 0 { // check whether the app is getting a query via standard input std := bufio.NewReader(os.Stdin) @@ -91,18 +112,10 @@ func main() { query = string(b) } else if flag.NArg() > 0 { - // backward compatibility - content, err := ioutil.ReadFile(flag.Arg(0)) - - if err != nil { - fmt.Println(err) - os.Exit(1) - } - - query = string(content) + files = flag.Args() } else { fmt.Println(flag.NArg()) - fmt.Println("Missed file") + fmt.Println("File or input stream are required") os.Exit(1) } @@ -113,26 +126,121 @@ func main() { os.Exit(1) } - if err := execFile(query, p); err != nil { + engine := ferret.New() + _ = engine.Drivers().Register(http.NewDriver()) + _ = engine.Drivers().Register(cdp.NewDriver(cdp.WithAddress(*conn))) + + opts := []runtime.Option{ + runtime.WithParams(p), + runtime.WithLog(console), + runtime.WithLogLevel(logging.MustParseLevel(*logLevel)), + } + + if query != "" { + err = execQuery(engine, opts, query) + } else { + err = execFiles(engine, opts, files) + } + + if err != nil { fmt.Println(err) os.Exit(1) } } -func execFile(query string, params map[string]interface{}) error { - ctx := drivers.WithContext( - context.Background(), - http.NewDriver(), - drivers.AsDefault(), - ) +func execFiles(engine *ferret.Instance, opts []runtime.Option, files []string) error { + errList := make([]error, 0, len(files)) - ctx = drivers.WithContext( - ctx, - cdp.NewDriver(cdp.WithAddress(*conn)), - ) + for _, path := range files { + log := logger.With().Str("path", path).Logger() + log.Debug().Msg("checking path...") - i := ferret.New() - out, err := i.Exec(ctx, query, runtime.WithParams(params)) + info, err := os.Stat(path) + + if err != nil { + log.Debug().Err(err).Msg("failed to get path info") + + errList = append(errList, err) + continue + } + + if info.IsDir() { + log.Debug().Msg("path points to a directory. retrieving list of files...") + + fileInfos, err := ioutil.ReadDir(path) + + if err != nil { + log.Debug().Err(err).Msg("failed to retrieve list of files") + + errList = append(errList, err) + continue + } + + log.Debug().Int("size", len(fileInfos)).Msg("retrieved list of files. starting to iterate...") + + dirFiles := make([]string, 0, len(fileInfos)) + + for _, info := range fileInfos { + if filepath.Ext(info.Name()) == ".fql" { + dirFiles = append(dirFiles, filepath.Join(path, info.Name())) + } + } + + if len(dirFiles) > 0 { + if err := execFiles(engine, opts, dirFiles); err != nil { + log.Debug().Err(err).Msg("failed to execute files") + + errList = append(errList, err) + } else { + log.Debug().Int("size", len(fileInfos)).Err(err).Msg("successfully executed files") + } + } else { + log.Debug().Int("size", len(fileInfos)).Err(err).Msg("no FQL files found") + } + + continue + } + + log.Debug().Msg("path points to a file. starting to read content") + + out, err := ioutil.ReadFile(path) + + if err != nil { + log.Debug().Err(err).Msg("failed to read content") + + errList = append(errList, err) + continue + } + + log.Debug().Msg("successfully read file") + log.Debug().Msg("executing file...") + err = execQuery(engine, opts, string(out)) + + if err != nil { + log.Debug().Err(err).Msg("failed to execute file") + + errList = append(errList, err) + continue + } + + log.Debug().Msg("successfully executed file") + } + + if len(errList) > 0 { + if len(errList) == len(files) { + logger.Debug().Errs("errors", errList).Msg("failed to execute file(s)") + } else { + logger.Debug().Errs("errors", errList).Msg("executed with errors") + } + + return core.Errors(errList...) + } + + return nil +} + +func execQuery(engine *ferret.Instance, opts []runtime.Option, query string) error { + out, err := engine.Exec(context.Background(), query, opts...) if err != nil { return err diff --git a/e2e/tests/dynamic/doc/click/click.fql b/e2e/tests/dynamic/doc/click/click.fql index 785ec89a..69271580 100644 --- a/e2e/tests/dynamic/doc/click/click.fql +++ b/e2e/tests/dynamic/doc/click/click.fql @@ -5,4 +5,4 @@ CLICK(page, "#wait-class-random-btn") WAIT_CLASS(page, "#wait-class-random-content", "alert-success") -RETURN "" \ No newline at end of file +RETURN TRUE \ No newline at end of file diff --git a/e2e/tests/dynamic/doc/wait/frame_navigation.fql b/e2e/tests/dynamic/doc/wait/frame_navigation.fqlx similarity index 52% rename from e2e/tests/dynamic/doc/wait/frame_navigation.fql rename to e2e/tests/dynamic/doc/wait/frame_navigation.fqlx index a83e1dd6..c392e857 100644 --- a/e2e/tests/dynamic/doc/wait/frame_navigation.fql +++ b/e2e/tests/dynamic/doc/wait/frame_navigation.fqlx @@ -1,13 +1,17 @@ LET url = @lab.cdn.dynamic + "?redirect=/iframe&src=/iframe" +// LET url = "http://192.168.4.23:8080/?redirect=/iframe&src=/iframe" LET page = DOCUMENT(url, { driver: 'cdp' }) -LET original = FIRST(FRAMES(page, "name", "nested")) +LET original = FIRST(FRAMES(page, "url", "/\?redirect=/iframe$")) INPUT(original, "#url_input", "https://getbootstrap.com/") +// WAIT(3000) + +// LET btn = ELEMENT(original, "#submit") +// CLICK(btn) + CLICK(original, "#submit") -WAIT_NAVIGATION(page, { - frame: original -}) +WAITFOR EVENT "navigation" IN page OPTIONS { frame: original } 10000 LET current = FIRST(FRAMES(page, "name", "nested")) diff --git a/e2e/tests/dynamic/element/wait/style.fql b/e2e/tests/dynamic/element/wait/style.fql index 8b8170d7..f6fef396 100644 --- a/e2e/tests/dynamic/element/wait/style.fql +++ b/e2e/tests/dynamic/element/wait/style.fql @@ -4,18 +4,19 @@ LET doc = DOCUMENT(url, true) WAIT_ELEMENT(doc, "#page-events") LET el = ELEMENT(doc, "#wait-class-content") +LET original = el.style.color ATTR_SET(el, "style", "color: black") -WAIT_STYLE(el, "color", "black") +WAIT_STYLE(el, "color", "rgb(0, 0, 0)") LET prev = el.style ATTR_REMOVE(el, "style") -WAIT_STYLE(el, "color", NONE) +WAIT_STYLE(el, "color", original) LET curr = el.style -T::EQ(prev.color, "black") -T::NONE(curr.color, "style should be removed") +T::EQ(prev.color, "rgb(0, 0, 0)") +T::EQ(curr.color, original, "style should be returned to original") RETURN NONE \ No newline at end of file diff --git a/e2e/tests/examples/navigate_back.yamlx b/e2e/tests/examples/navigate_back.yaml similarity index 100% rename from e2e/tests/examples/navigate_back.yamlx rename to e2e/tests/examples/navigate_back.yaml diff --git a/e2e/tests/examples/navigate_back_2.yamlx b/e2e/tests/examples/navigate_back_2.yaml similarity index 100% rename from e2e/tests/examples/navigate_back_2.yamlx rename to e2e/tests/examples/navigate_back_2.yaml diff --git a/e2e/tests/examples/navigate_forward.yamlx b/e2e/tests/examples/navigate_forward.yaml similarity index 100% rename from e2e/tests/examples/navigate_forward.yamlx rename to e2e/tests/examples/navigate_forward.yaml diff --git a/e2e/tests/examples/navigate_forward_2.yamlx b/e2e/tests/examples/navigate_forward_2.yaml similarity index 100% rename from e2e/tests/examples/navigate_forward_2.yamlx rename to e2e/tests/examples/navigate_forward_2.yaml diff --git a/e2e/tests/examples/pagination.yamlx b/e2e/tests/examples/pagination.yaml similarity index 100% rename from e2e/tests/examples/pagination.yamlx rename to e2e/tests/examples/pagination.yaml diff --git a/e2e/tests/examples/pagination_uncontrolled.yamlx b/e2e/tests/examples/pagination_uncontrolled.yaml similarity index 100% rename from e2e/tests/examples/pagination_uncontrolled.yamlx rename to e2e/tests/examples/pagination_uncontrolled.yaml diff --git a/examples/click.fql b/examples/click.fql index 71f8a6a4..127fe6b4 100644 --- a/examples/click.fql +++ b/examples/click.fql @@ -1,10 +1,8 @@ -LET doc = DOCUMENT("https://github.com/", { driver: "cdp" }) +LET doc = DOCUMENT("https://www.montferret.dev/", { driver: "cdp" }) -HOVER(doc, ".HeaderMenu-details") -CLICK(doc, ".HeaderMenu a") +CLICK(doc, "#repl") -WAIT_NAVIGATION(doc) -WAIT_ELEMENT(doc, 'main nav') +WAITFOR EVENT "navigation" IN doc +WAIT_ELEMENT(doc, '.code-editor-text') -FOR el IN ELEMENTS(doc, 'main nav a') - RETURN TRIM(el.innerText) +RETURN doc.url diff --git a/examples/crawler.fql b/examples/crawler.fql index 22322787..84f1340b 100644 --- a/examples/crawler.fql +++ b/examples/crawler.fql @@ -1,15 +1,26 @@ LET doc = DOCUMENT('https://www.theverge.com/tech', { - driver: "cdp" + driver: "cdp", + ignore: { + resources: [ + { + url: "*", + type: "image" + } + ] + } }) + WAIT_ELEMENT(doc, '.c-compact-river__entry', 5000) LET articles = ELEMENTS(doc, '.c-entry-box--compact__image-wrapper') LET links = ( FOR article IN articles + FILTER article.attributes?.href LIKE 'https://www.theverge.com/*' RETURN article.attributes.href ) + FOR link IN links // The Verge has pretty heavy pages, so let's increase the navigation wait time NAVIGATE(doc, link, 20000) - WAIT_ELEMENT(doc, '.c-entry-content', 5000) + WAIT_ELEMENT(doc, '.c-entry-content', 15000) LET texter = ELEMENT(doc, '.c-entry-content') RETURN texter.innerText \ No newline at end of file diff --git a/examples/disable-images.fql b/examples/disable-images.fql index 20b23855..77c32a37 100644 --- a/examples/disable-images.fql +++ b/examples/disable-images.fql @@ -10,4 +10,4 @@ LET p = DOCUMENT("https://www.gettyimages.com/", { } }) -RETURN NONE +RETURN TRUE diff --git a/examples/google-search.fql b/examples/google-search.fql index a0c6834a..4ce72eea 100644 --- a/examples/google-search.fql +++ b/examples/google-search.fql @@ -6,20 +6,22 @@ LET google = DOCUMENT("https://www.google.com/", { HOVER(google, 'input[name="q"]') WAIT(RAND(100)) INPUT(google, 'input[name="q"]', @criteria, 30) - -WAIT(RAND(100)) - -WAIT_ELEMENT(google, '.UUbT9') WAIT(RAND(100)) CLICK(google, 'input[name="btnK"]') -WAIT_NAVIGATION(google) +WAITFOR EVENT "navigation" IN google + +WAIT_ELEMENT(google, "#res") + +FOR el IN ELEMENTS(google, '#kp-wp-tab-overview > [jsdata]') + // filter out extra elements like media and 'People also ask' + FILTER ELEMENT_EXISTS(el, "#media_result_group") == FALSE + FILTER ELEMENT_EXISTS(el, '[role="heading"]') == FALSE + + LET descr = (FOR i IN ELEMENTS(el, "span") FILTER LENGTH(i.attributes) == 0 RETURN i) -FOR result IN ELEMENTS(google, '.g') - // filter out extra elements like videos and 'People also ask' - FILTER TRIM(result.attributes.class) == 'g' RETURN { - title: INNER_TEXT(result, 'h3'), - description: INNER_TEXT(result, '.rc > div:nth-child(2) span'), - url: INNER_TEXT(result, 'cite') + title: INNER_TEXT(el, 'h3'), + description: FIRST(descr), + url: ELEMENT(el, 'a')?.attributes.href } \ No newline at end of file diff --git a/examples/history-api.fql b/examples/history-api.fql index 57300481..c7ef0372 100644 --- a/examples/history-api.fql +++ b/examples/history-api.fql @@ -11,6 +11,6 @@ CLICK(song) WAIT_ELEMENT(doc, ".l-listen-hero") RETURN { - page: page.url, + current: page.url, first: doc.url } diff --git a/examples/pagination.fql b/examples/pagination.fql index 1e4a6dd2..20a07e6b 100644 --- a/examples/pagination.fql +++ b/examples/pagination.fql @@ -2,15 +2,15 @@ LET baseURL = 'https://www.amazon.com/' LET amazon = DOCUMENT(baseURL, { driver: "cdp" }) INPUT(amazon, '#twotabsearchtextbox', @criteria) -CLICK(amazon, '.nav-search-submit input[type="submit"]') +CLICK(amazon, '#nav-search-submit-button') WAIT_NAVIGATION(amazon) -LET resultListSelector = 'div.s-result-list' +LET resultListSelector = '[data-component-type="s-search-results"]' LET resultItemSelector = '[data-component-type="s-search-result"]' -LET nextBtnSelector = 'ul.a-pagination .a-last a' +LET nextBtnSelector = '.s-pagination-next:not(.s-pagination-disabled)' LET priceWholeSelector = '.a-price-whole' LET priceFracSelector = '.a-price-fraction' -LET pagers = ELEMENTS(amazon, 'ul.a-pagination li.a-disabled') +LET pagers = ELEMENTS(amazon, '.s-pagination-item.s-pagination-disabled') LET pages = LENGTH(pagers) > 0 ? TO_INT(INNER_TEXT(LAST(pagers))) : 0 LET result = ( @@ -18,8 +18,7 @@ LET result = ( LIMIT @pages LET clicked = pageNum == 1 ? false : CLICK(amazon, nextBtnSelector) - LET wait = clicked ? WAIT_NAVIGATION(amazon, 10000) : false - LET waitSelector = wait ? WAIT_ELEMENT(amazon, resultListSelector) : false + LET waitSelector = clicked ? WAIT_ELEMENT(amazon, resultListSelector) : false PRINT("page:", pageNum, "clicked", clicked) @@ -32,6 +31,7 @@ LET result = ( LET anchor = ELEMENT(el, "a") RETURN { + page: pageNum, url: baseURL + anchor.attributes.href, title: INNER_TEXT(el, 'h2'), price diff --git a/examples/pagination_uncontrolled.fql b/examples/pagination_uncontrolled.fql index 18ef563a..c4d4a184 100644 --- a/examples/pagination_uncontrolled.fql +++ b/examples/pagination_uncontrolled.fql @@ -2,12 +2,12 @@ LET baseURL = 'https://www.amazon.com/' LET amazon = DOCUMENT(baseURL, { driver: "cdp" }) INPUT(amazon, '#twotabsearchtextbox', @criteria) -CLICK(amazon, '.nav-search-submit input[type="submit"]') +CLICK(amazon, '#nav-search-submit-button') WAIT_NAVIGATION(amazon) -LET resultListSelector = '#s-results-list-atf' +LET resultListSelector = '[data-component-type="s-search-results"]' LET resultItemSelector = '[data-component-type="s-search-result"]' -LET nextBtnSelector = 'ul.a-pagination .a-last a' +LET nextBtnSelector = '.s-pagination-next:not(.s-pagination-disabled)' LET priceWholeSelector = '.a-price-whole' LET priceFracSelector = '.a-price-fraction' @@ -15,8 +15,7 @@ LET result = ( FOR pageNum IN PAGINATION(amazon, nextBtnSelector) LIMIT @pages - LET wait = pageNum > 0 ? WAIT_NAVIGATION(amazon, 20000) : false - LET waitSelector = wait ? WAIT_ELEMENT(amazon, resultListSelector) : false + LET waitSelector = pageNum > 0 ? WAIT_ELEMENT(amazon, resultListSelector) : false LET items = ( FOR el IN ELEMENTS(amazon, resultItemSelector) @@ -26,9 +25,8 @@ LET result = ( LET price = TO_FLOAT(priceWholeTxt + "." + priceFracTxt) LET anchor = ELEMENT(el, "a") - PRINT(priceWholeTxt, priceFracTxt) - RETURN { + page: pageNum, url: baseURL + anchor.attributes.href, title: INNER_TEXT(el, 'h2'), price diff --git a/examples/query-all.fql b/examples/query-all.fql new file mode 100644 index 00000000..afca7ac6 --- /dev/null +++ b/examples/query-all.fql @@ -0,0 +1,3 @@ +let doc = document("https://github.com/MontFerret/ferret", { driver: "cdp" }) + +return elements(doc, '[role="row"]') \ No newline at end of file diff --git a/examples/redirects.fql b/examples/redirects.fql index 837b4ccf..026dcf57 100644 --- a/examples/redirects.fql +++ b/examples/redirects.fql @@ -1,15 +1,7 @@ -LET doc = DOCUMENT(@url, { - driver: 'cdp', - viewport: { - width: 1920, - height: 1080 - } -}) +LET doc = DOCUMENT(@url, { driver: 'cdp' }) CLICK(doc, '.click') -WAIT_NAVIGATION(doc, { - target: @targetURL -}) +WAITFOR EVENT "navigation" IN doc { target: @targetURL } RETURN ELEMENT(doc, '.title') \ No newline at end of file diff --git a/examples/while.fql b/examples/while.fql index 40114630..3d8ab130 100644 --- a/examples/while.fql +++ b/examples/while.fql @@ -1,17 +1,17 @@ LET doc = DOCUMENT("https://github.com/MontFerret/ferret/stargazers", { driver: "cdp" }) -LET nextSelector = ".paginate-container .BtnGroup a:nth-child(2)" -LET elementsSelector = '.follow-list li' +LET nextSelector = '[data-test-selector="pagination"] .btn:nth-child(2):not([disabled])' +LET elementsSelector = '#repos ol li' FOR i DO WHILE ELEMENT_EXISTS(doc, nextSelector) LIMIT 3 LET wait = i > 0 ? CLICK(doc, nextSelector) : false - LET nav = wait ? WAIT_NAVIGATION(doc) : false + LET nav = wait ? (WAITFOR EVENT "navigation" IN doc) : false FOR el IN ELEMENTS(doc, elementsSelector) FILTER ELEMENT_EXISTS(el, ".octicon-organization") RETURN { - name: INNER_TEXT(el, ".follow-list-name"), - company: INNER_TEXT(el, ".follow-list-info span") + name: INNER_TEXT(el, 'div > div:nth-child(2) [data-hovercard-type="user"]'), + company: INNER_TEXT(el, "div > div:nth-child(2) p") } diff --git a/go.mod b/go.mod index 72e3f64a..aab4699b 100644 --- a/go.mod +++ b/go.mod @@ -13,11 +13,12 @@ require ( github.com/jarcoal/httpmock v1.0.8 github.com/mafredri/cdp v0.32.0 github.com/pkg/errors v0.9.1 - github.com/rs/zerolog v1.23.0 + github.com/rs/zerolog v1.24.0 github.com/sethgrid/pester v1.1.0 github.com/smartystreets/goconvey v1.6.4 - github.com/wI2L/jettison v0.7.1 + github.com/stretchr/testify v1.5.1 + github.com/wI2L/jettison v0.7.2 golang.org/x/net v0.0.0-20210614182718-04defd469f4e golang.org/x/sync v0.0.0-20210220032951-036812b2e83c - golang.org/x/text v0.3.6 + golang.org/x/text v0.3.7 ) diff --git a/go.sum b/go.sum index 642e76e0..06100207 100644 --- a/go.sum +++ b/go.sum @@ -17,7 +17,6 @@ github.com/codegangsta/cli v1.20.0/go.mod h1:/qJNoX69yVSKu5o4jLyXAENLRyk1uhi7zkb github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= github.com/corpix/uarand v0.1.1 h1:RMr1TWc9F4n5jiPDzFHtmaUXLKLNUFK0SgCLo4BhX/U= github.com/corpix/uarand v0.1.1/go.mod h1:SFKZvkcRoLqVRFZ4u25xPmp6m9ktANfbpXZ7SJ0/FNU= -github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= @@ -37,14 +36,13 @@ github.com/gorilla/websocket v1.4.2 h1:+/TMaTYc4QFitKJxsQ7Yye35DkWvkdLcvGKqM+x0U github.com/gorilla/websocket v1.4.2/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= github.com/jarcoal/httpmock v1.0.8 h1:8kI16SoO6LQKgPE7PvQuV+YuD/inwHd7fOOe2zMbo4k= github.com/jarcoal/httpmock v1.0.8/go.mod h1:ATjnClrvW/3tijVmpL/va5Z3aAyGvqU3gCT8nX0Txik= -github.com/json-iterator/go v1.1.9 h1:9yzud/Ht36ygwatGx56VwCZtlI/2AD15T1X2sjSuGns= -github.com/json-iterator/go v1.1.9/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= +github.com/json-iterator/go v1.1.11 h1:uVUAXhF2To8cbw/3xN3pxj6kk7TYKs98NIrTqPlMWAQ= +github.com/json-iterator/go v1.1.11/go.mod h1:KdQUCv79m/52Kvf8AW2vK1V8akMuk1QjK/uOdHXbAo4= github.com/jtolds/gls v4.20.0+incompatible h1:xdiiI2gbIgH/gLH7ADydsJ1uDOEzR8yvV7C0MuV77Wo= github.com/jtolds/gls v4.20.0+incompatible/go.mod h1:QJZ7F/aHp+rZTRtaJ1ow/lLfFfVYBRgL+9YlvaHOwJU= -github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= -github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= -github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= -github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/klauspost/cpuid/v2 v2.0.5/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= +github.com/klauspost/cpuid/v2 v2.0.9 h1:lgaqFMSdTdQYdZ04uHyN2d/eKdOMyi2YLSvlQIBFYa4= +github.com/klauspost/cpuid/v2 v2.0.9/go.mod h1:FInQzS24/EEf25PyTYn52gqo7WaD8xa0213Md/qVLRg= github.com/mafredri/cdp v0.32.0 h1:JzW2F+zVK2y9ZhbNWyjrwafZLL9oNnl9Tf6JQ149Og8= github.com/mafredri/cdp v0.32.0/go.mod h1:YTCwLXkZSa18SGSIxCPMOGZcUJODZSNlAhiMqbyxWJg= github.com/mafredri/go-lint v0.0.0-20180911205320-920981dfc79e/go.mod h1:k/zdyxI3q6dup24o8xpYjJKTCf2F7rfxLp6w/efTiWs= @@ -56,30 +54,30 @@ github.com/modern-go/reflect2 v0.0.0-20180701023420-4b7aa43c6742/go.mod h1:bx2lN github.com/modern-go/reflect2 v1.0.1 h1:9f412s+6RmYXLWZSEzVVgPGK7C2PphHj5RJrvfx9AWI= github.com/modern-go/reflect2 v1.0.1/go.mod h1:bx2lNnkwVCuqBIxFjflWJWanXIb3RllmbCylyMrvgv0= github.com/ngdinhtoan/glide-cleanup v0.2.0/go.mod h1:UQzsmiDOb8YV3nOsCxK/c9zPpCZVNoHScRE3EO9pVMM= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e h1:fD57ERR4JtEqsWbfPhv4DMiApHyliiK5xCTNVSPiaAs= -github.com/niemeyer/pretty v0.0.0-20200227124842-a10e7caefd8e/go.mod h1:zD1mROLANZcx1PVRCS0qkT7pwLkGfwJo4zjcN/Tysno= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/rs/xid v1.2.1/go.mod h1:+uKXf+4Djp6Md1KODXJxgGQPKngRmWyn10oCKFzNHOQ= -github.com/rs/zerolog v1.23.0 h1:UskrK+saS9P9Y789yNNulYKdARjPZuS35B8gJF2x60g= -github.com/rs/zerolog v1.23.0/go.mod h1:6c7hFfxPOy7TacJc4Fcdi24/J0NKYGzjG8FWRI916Qo= -github.com/segmentio/encoding v0.1.10 h1:0b8dva47cSuNQR5ZcU3d0pfi9EnPpSK6q7y5ZGEW36Q= -github.com/segmentio/encoding v0.1.10/go.mod h1:RWhr02uzMB9gQC1x+MfYxedtmBibb9cZ6Vv9VxRSSbw= +github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= +github.com/rs/zerolog v1.24.0 h1:76ivFxmVSRs1u2wUwJVg5VZDYQgeH1JpoS6ndgr9Wy8= +github.com/rs/zerolog v1.24.0/go.mod h1:7KHcEGe0QZPOm2IE4Kpb5rTh6n1h2hIgS5OOnu1rUaI= +github.com/segmentio/encoding v0.2.19 h1:Kshkmoz080qvUtdtakR8Bjk2sIlLS8wSvijFMEHRGow= +github.com/segmentio/encoding v0.2.19/go.mod h1:7E68jTSWMnNoYhHi1JbLd7NBSB6XfE4vzqhR88hDBQc= github.com/sethgrid/pester v1.1.0 h1:IyEAVvwSUPjs2ACFZkBe5N59BBUpSIkQ71Hr6cM5A+w= github.com/sethgrid/pester v1.1.0/go.mod h1:Ad7IjTpvzZO8Fl0vh9AzQ+j/jYZfyp2diGwI8m5q+ns= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d h1:zE9ykElWQ6/NYmHa3jpm/yHnI4xSofP+UP6SpjHcSeM= github.com/smartystreets/assertions v0.0.0-20180927180507-b2de0cb4f26d/go.mod h1:OnSkiWE9lh6wB0YB77sQom3nweQdgAjqCqsofrRNTgc= github.com/smartystreets/goconvey v1.6.4 h1:fv0U8FUIMPNf1L9lnHLvLhgicrIVChEkdzIKYqbNC9s= github.com/smartystreets/goconvey v1.6.4/go.mod h1:syvi0/a8iFYH4r/RixwvyeAJjdLS9QV7WQ/tjFTllLA= +github.com/stretchr/objx v0.1.0 h1:4G4v2dO3VZwixGIRoQ5Lfboy6nUhCyYzaqnIAPPhYs4= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.5.1 h1:nOGnQDM7FYENwehXlg/kFVnos3rEvtKTjRvOWSzb6H4= github.com/stretchr/testify v1.5.1/go.mod h1:5W2xD1RspED5o8YsWQXVCued0rvSQ+mT+I5cxcmMvtA= -github.com/wI2L/jettison v0.7.1 h1:XNq/WvSOAiJhFww9F5JZZcBZtKFL2Y/9WHHEHLDq9TE= -github.com/wI2L/jettison v0.7.1/go.mod h1:dj49nOP41M7x6Jql62BqqF/+nW+XJgBaWzJR0hd6M84= +github.com/wI2L/jettison v0.7.2 h1:vJ66luIiQkqpswDHgN2dFq70+22cVL+9omH+NS0YxIo= +github.com/wI2L/jettison v0.7.2/go.mod h1:W3PPso417OeZeWs9nV/olfapp0o4eSZcaeZk4HeSzfM= github.com/yuin/goldmark v1.2.1/go.mod h1:3hX8gzYuyVAZsxl0MRgGTJEmQBFcNTphYh9decYSb74= +github.com/yuin/goldmark v1.3.5/go.mod h1:mwnBkeHKe2W/ZEtQ+71ViKU8L12m81fl3OWwC1Zlc8k= golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.0.0-20200622213623-75b288015ac9/go.mod h1:LzIPMQfyMNhhGPhUkYOs5KpL4U8rLKemX1yGLhDgUto= @@ -91,6 +89,7 @@ golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= golang.org/x/net v0.0.0-20200421231249-e086a090c8fd/go.mod h1:qpuaurCH72eLCgpAm/N6yyVIVM9cpaDIP3A8BGJEC5A= golang.org/x/net v0.0.0-20201021035429-f5854403a974/go.mod h1:sp8m0HH+o8qH0wwXwYZr8TS3Oi6o0r6Gce1SSxlDquU= +golang.org/x/net v0.0.0-20210405180319-a5a99cb37ef4/go.mod h1:p54w0d4576C0XHj96bSt6lcn1PtDYWL6XObtHCRCNQM= golang.org/x/net v0.0.0-20210614182718-04defd469f4e h1:XpT3nA5TvE525Ne3hInMh6+GETgn27Zfm9dxsThnX2Q= golang.org/x/net v0.0.0-20210614182718-04defd469f4e/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= @@ -104,24 +103,27 @@ golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210119212857-b64e53b001e4/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210319071255-635bc2c9138d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210330210617-4fbd30eecc44/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210510120138-977fb7262007/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= -golang.org/x/text v0.3.6 h1:aRYxNxv6iGQlyVaZmk6ZgYEDa+Jg18DxebPSrd6bg1M= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= golang.org/x/tools v0.0.0-20190328211700-ab21143f2384/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20191119224855-298f0cb1881e/go.mod h1:b+2E5dAYhXwXZwtnZ6UAqBI28+e2cm9otk0dWdXHAEo= golang.org/x/tools v0.1.0/go.mod h1:xkSsbof2nBLbhDlRMhhhyNLN/zl3eTqcnHD5viDpcZ0= +golang.org/x/tools v0.1.5/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f h1:BLraFXnmrev5lT+xlilqcH8XK9/i0At2xKjWk4p6zsU= -gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= gopkg.in/yaml.v2 v2.2.8 h1:obN1ZagJSUGI0Ek/LBmuj4SNLPfIny3KsKFopxRdj10= gopkg.in/yaml.v2 v2.2.8/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= diff --git a/pkg/compiler/compiler_waitfor_event_test.go b/pkg/compiler/compiler_waitfor_event_test.go index 5d9b8114..2e84f800 100644 --- a/pkg/compiler/compiler_waitfor_event_test.go +++ b/pkg/compiler/compiler_waitfor_event_test.go @@ -110,8 +110,72 @@ func newCompilerWithObservable() *compiler.Compiler { } func TestWaitforEventExpression(t *testing.T) { - Convey("WAITFOR EVENT X IN Y", t, func() { + Convey("WAITFOR EVENT parser", t, func() { + Convey("Should parse", func() { + c := newCompilerWithObservable() + _, err := c.Compile(` +LET obj = X::CREATE() + +X::EMIT(obj, "test", 100) +WAITFOR EVENT "test" IN obj + +RETURN NONE +`) + + So(err, ShouldBeNil) + }) + + Convey("Should parse 2", func() { + c := newCompilerWithObservable() + + _, err := c.Compile(` +LET obj = X::CREATE() + +X::EMIT(obj, "test", 100) +WAITFOR EVENT "test" IN obj 1000 + +RETURN NONE +`) + + So(err, ShouldBeNil) + }) + + Convey("Should parse 3", func() { + c := newCompilerWithObservable() + + _, err := c.Compile(` +LET obj = X::CREATE() + +X::EMIT(obj, "test", 100) +LET timeout = 1000 +WAITFOR EVENT "test" IN obj timeout + +RETURN NONE +`) + + So(err, ShouldBeNil) + }) + + Convey("Should parse 4", func() { + c := newCompilerWithObservable() + + _, err := c.Compile(` +LET obj = X::CREATE() + +X::EMIT(obj, "test", 100) +LET timeout = 1000 +WAITFOR EVENT "test" IN obj timeout + +X::EMIT(obj, "test", 100) + +RETURN NONE +`) + + So(err, ShouldBeNil) + }) + }) + Convey("WAITFOR EVENT X IN Y runtime", t, func() { Convey("Should wait for a given event", func() { c := newCompilerWithObservable() diff --git a/pkg/compiler/visitor.go b/pkg/compiler/visitor.go index 7cf82cde..d3623522 100644 --- a/pkg/compiler/visitor.go +++ b/pkg/compiler/visitor.go @@ -807,13 +807,13 @@ func (v *visitor) doVisitWaitForTimeoutValueContext(ctx *fql.WaitForTimeoutConte return v.doVisitVariable(variable.(*fql.VariableContext), s) } - if member := ctx.MemberExpression(); member != nil { - return v.doVisitMemberExpression(member.(*fql.MemberExpressionContext), s) - } - - if fnCall := ctx.FunctionCallExpression(); fnCall != nil { - return v.doVisitFunctionCallExpression(fnCall.(*fql.FunctionCallExpressionContext), s) - } + //if member := ctx.MemberExpression(); member != nil { + // return v.doVisitMemberExpression(member.(*fql.MemberExpressionContext), s) + //} + // + //if fnCall := ctx.FunctionCallExpression(); fnCall != nil { + // return v.doVisitFunctionCallExpression(fnCall.(*fql.FunctionCallExpressionContext), s) + //} return nil, ErrNotImplemented } diff --git a/pkg/drivers/cdp/dom/document.go b/pkg/drivers/cdp/dom/document.go index 80b5ae3a..03ab4524 100644 --- a/pkg/drivers/cdp/dom/document.go +++ b/pkg/drivers/cdp/dom/document.go @@ -3,12 +3,12 @@ package dom import ( "context" "fmt" + "github.com/MontFerret/ferret/pkg/runtime/logging" "hash/fnv" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/page" - "github.com/mafredri/cdp/protocol/runtime" "github.com/pkg/errors" "github.com/rs/zerolog" @@ -23,18 +23,18 @@ import ( ) type HTMLDocument struct { - logger *zerolog.Logger + logger zerolog.Logger client *cdp.Client dom *Manager input *input.Manager - exec *eval.ExecutionContext + exec *eval.Runtime frameTree page.FrameTree element *HTMLElement } func LoadRootHTMLDocument( ctx context.Context, - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, domManager *Manager, mouse *input.Mouse, @@ -52,7 +52,7 @@ func LoadRootHTMLDocument( return nil, err } - worldRepl, err := client.Page.CreateIsolatedWorld(ctx, page.NewCreateIsolatedWorldArgs(ftRepl.FrameTree.Frame.ID)) + exec, err := eval.New(ctx, client, ftRepl.FrameTree.Frame.ID) if err != nil { return nil, err @@ -67,23 +67,22 @@ func LoadRootHTMLDocument( keyboard, gdRepl.Root, ftRepl.FrameTree, - worldRepl.ExecutionContextID, + exec, ) } func LoadHTMLDocument( ctx context.Context, - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, domManager *Manager, mouse *input.Mouse, keyboard *input.Keyboard, node dom.Node, frameTree page.FrameTree, - execID runtime.ExecutionContextID, + exec *eval.Runtime, ) (*HTMLDocument, error) { - exec := eval.NewExecutionContext(client, frameTree.Frame, execID) - inputManager := input.NewManager(client, exec, keyboard, mouse) + inputManager := input.NewManager(logger, client, exec, keyboard, mouse) rootElement, err := LoadHTMLElement( ctx, @@ -111,16 +110,16 @@ func LoadHTMLDocument( } func NewHTMLDocument( - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, domManager *Manager, input *input.Manager, - exec *eval.ExecutionContext, + exec *eval.Runtime, rootElement *HTMLElement, frames page.FrameTree, ) *HTMLDocument { doc := new(HTMLDocument) - doc.logger = logger + doc.logger = logging.WithName(logger.With(), "html_document").Logger() doc.client = client doc.dom = domManager doc.input = input @@ -464,7 +463,7 @@ func (doc *HTMLDocument) ScrollByXY(ctx context.Context, x, y values.Float, opti } func (doc *HTMLDocument) Eval(ctx context.Context, expression string) (core.Value, error) { - return doc.exec.EvalWithReturnValue(ctx, expression) + return doc.exec.EvalValue(ctx, expression) } func (doc *HTMLDocument) logError(err error) *zerolog.Event { diff --git a/pkg/drivers/cdp/dom/element.go b/pkg/drivers/cdp/dom/element.go index ecf1b173..38964553 100644 --- a/pkg/drivers/cdp/dom/element.go +++ b/pkg/drivers/cdp/dom/element.go @@ -4,6 +4,7 @@ import ( "context" "encoding/json" "fmt" + "github.com/MontFerret/ferret/pkg/runtime/logging" "hash/fnv" "strconv" "strings" @@ -37,11 +38,11 @@ type ( } HTMLElement struct { - logger *zerolog.Logger + logger zerolog.Logger client *cdp.Client dom *Manager input *input.Manager - exec *eval.ExecutionContext + exec *eval.Runtime id HTMLElementIdentity nodeType html.NodeType nodeName values.String @@ -50,11 +51,11 @@ type ( func LoadHTMLElement( ctx context.Context, - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, domManager *Manager, input *input.Manager, - exec *eval.ExecutionContext, + exec *eval.Runtime, nodeID dom.NodeID, ) (*HTMLElement, error) { if client == nil { @@ -62,7 +63,7 @@ func LoadHTMLElement( } // getting a remote object that represents the current DOM Node - args := dom.NewResolveNodeArgs().SetNodeID(nodeID).SetExecutionContextID(exec.ID()) + args := dom.NewResolveNodeArgs().SetNodeID(nodeID).SetExecutionContextID(exec.ContextID()) obj, err := client.DOM.ResolveNode(ctx, args) @@ -90,11 +91,11 @@ func LoadHTMLElement( func LoadHTMLElementWithID( ctx context.Context, - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, domManager *Manager, input *input.Manager, - exec *eval.ExecutionContext, + exec *eval.Runtime, id HTMLElementIdentity, ) (*HTMLElement, error) { node, err := client.DOM.DescribeNode( @@ -122,17 +123,22 @@ func LoadHTMLElementWithID( } func NewHTMLElement( - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, domManager *Manager, input *input.Manager, - exec *eval.ExecutionContext, + exec *eval.Runtime, id HTMLElementIdentity, nodeType int, nodeName string, ) *HTMLElement { el := new(HTMLElement) - el.logger = logger + el.logger = logging. + WithName(logger.With(), "dom_element"). + Int("node_id", int(id.NodeID)). + Str("object_id", string(id.ObjectID)). + Str("node_name", nodeName). + Logger() el.client = client el.dom = domManager el.input = input @@ -229,9 +235,11 @@ func (el *HTMLElement) GetNodeName() values.String { } func (el *HTMLElement) Length() values.Int { - value, err := el.exec.EvalWithArgumentsAndReturnValue(context.Background(), templates.GetChildrenCount(), runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }) + value, err := el.exec.EvalValue( + context.Background(), + templates.GetChildrenCount(), + eval.WithArgRef(el.id.ObjectID), + ) if err != nil { el.logError(err) @@ -243,9 +251,11 @@ func (el *HTMLElement) Length() values.Int { } func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) { - value, err := el.exec.EvalWithArgumentsAndReturnValue(ctx, templates.GetStyles(), runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }) + value, err := el.exec.EvalValue( + ctx, + templates.GetStyles(), + eval.WithArgRef(el.id.ObjectID), + ) if err != nil { return values.NewObject(), err @@ -259,70 +269,27 @@ func (el *HTMLElement) GetStyles(ctx context.Context) (*values.Object, error) { } func (el *HTMLElement) GetStyle(ctx context.Context, name values.String) (core.Value, error) { - styles, err := el.GetStyles(ctx) - - if err != nil { - return values.None, err - } - - val, found := styles.Get(name) - - if !found { - return values.None, nil - } - - return val, nil + return el.exec.EvalValue( + ctx, + templates.GetStyle(name.String()), + eval.WithArgRef(el.id.ObjectID), + ) } func (el *HTMLElement) SetStyles(ctx context.Context, styles *values.Object) error { - if styles == nil { - return nil - } - - currentStyles, err := el.GetStyles(ctx) - - if err != nil { - return err - } - - styles.ForEach(func(value core.Value, key string) bool { - currentStyles.Set(values.NewString(key), value) - - return true - }) - - str := common.SerializeStyles(ctx, currentStyles) - - return el.SetAttribute(ctx, common.AttrNameStyle, str) + return el.exec.Eval( + ctx, + templates.SetStyles(styles), + eval.WithArgRef(el.id.ObjectID), + ) } func (el *HTMLElement) SetStyle(ctx context.Context, name, value values.String) error { - // we manually set only those that are defined in attribute only - attrValue, err := el.GetAttribute(ctx, common.AttrNameStyle) - - if err != nil { - return err - } - - var styles *values.Object - - if attrValue == values.None { - styles = values.NewObject() - } else { - styleAttr, ok := attrValue.(*values.Object) - - if !ok { - return core.TypeError(attrValue.Type(), types.Object) - } - - styles = styleAttr - } - - styles.Set(name, value) - - str := common.SerializeStyles(ctx, styles) - - return el.SetAttribute(ctx, common.AttrNameStyle, str) + return el.exec.Eval( + ctx, + templates.SetStyle(name.String(), value.String()), + eval.WithArgRef(el.id.ObjectID), + ) } func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) error { @@ -330,30 +297,11 @@ func (el *HTMLElement) RemoveStyle(ctx context.Context, names ...values.String) return nil } - value, err := el.GetAttribute(ctx, common.AttrNameStyle) - - if err != nil { - return err - } - - // no attribute - if value == values.None { - return nil - } - - styles, ok := value.(*values.Object) - - if !ok { - return core.TypeError(styles.Type(), types.Object) - } - - for _, name := range names { - styles.Remove(name) - } - - str := common.SerializeStyles(ctx, styles) - - return el.SetAttribute(ctx, common.AttrNameStyle, str) + return el.exec.Eval( + ctx, + templates.RemoveStyles(names), + eval.WithArgRef(el.id.ObjectID), + ) } func (el *HTMLElement) GetAttributes(ctx context.Context) (*values.Object, error) { @@ -457,10 +405,10 @@ func (el *HTMLElement) RemoveAttribute(ctx context.Context, names ...values.Stri } func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) { - out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.GetChildren(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, + out, err := el.exec.EvalRef( + ctx, + templates.GetChildren(), + eval.WithArgRef(el.id.ObjectID), ) if err != nil { @@ -485,10 +433,10 @@ func (el *HTMLElement) GetChildNodes(ctx context.Context) (*values.Array, error) } func (el *HTMLElement) GetChildNode(ctx context.Context, idx values.Int) (core.Value, error) { - out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.GetChildByIndex(int64(idx)), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, + out, err := el.exec.EvalRef( + ctx, + templates.GetChildByIndex(int64(idx)), + eval.WithArgRef(el.id.ObjectID), ) if err != nil { @@ -511,9 +459,11 @@ func (el *HTMLElement) GetNextElementSibling(ctx context.Context) (core.Value, e } func (el *HTMLElement) evalAndGetElement(ctx context.Context, expr string) (core.Value, error) { - obj, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, expr, runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }) + obj, err := el.exec.EvalRef( + ctx, + expr, + eval.WithArgRef(el.id.ObjectID), + ) if err != nil { return values.None, err @@ -544,32 +494,17 @@ func (el *HTMLElement) evalAndGetElement(ctx context.Context, expr string) (core } func (el *HTMLElement) QuerySelector(ctx context.Context, selector values.String) (core.Value, error) { - selectorArgs := dom.NewQuerySelectorArgs(el.id.NodeID, selector.String()) - found, err := el.client.DOM.QuerySelector(ctx, selectorArgs) + obj, err := el.exec.EvalRef( + ctx, + templates.QuerySelector(selector.String()), + eval.WithArgRef(el.id.ObjectID), + ) if err != nil { return values.None, err } - if found.NodeID == emptyNodeID { - return values.None, nil - } - - res, err := LoadHTMLElement( - ctx, - el.logger, - el.client, - el.dom, - el.input, - el.exec, - found.NodeID, - ) - - if err != nil { - return values.None, nil - } - - return res, nil + return el.convertEvalResult(ctx, obj) } func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { @@ -612,19 +547,11 @@ func (el *HTMLElement) QuerySelectorAll(ctx context.Context, selector values.Str } func (el *HTMLElement) XPath(ctx context.Context, expression values.String) (result core.Value, err error) { - exp, err := expression.MarshalJSON() - - if err != nil { - return values.None, err - } - - out, err := el.exec.EvalWithArgumentsAndReturnReference(ctx, templates.XPath(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: exp, - }, + out, err := el.exec.EvalRef( + ctx, + templates.XPath(), + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(expression), ) if err != nil { @@ -643,21 +570,11 @@ func (el *HTMLElement) SetInnerText(ctx context.Context, innerText values.String } func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector values.String) (values.String, error) { - sel, err := selector.MarshalJSON() - - if err != nil { - return values.EmptyString, err - } - - out, err := el.exec.EvalWithArgumentsAndReturnValue( + out, err := el.exec.EvalValue( ctx, templates.GetInnerTextBySelector(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: sel, - }, + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(selector), ) if err != nil { @@ -668,49 +585,21 @@ func (el *HTMLElement) GetInnerTextBySelector(ctx context.Context, selector valu } func (el *HTMLElement) SetInnerTextBySelector(ctx context.Context, selector, innerText values.String) error { - sel, err := selector.MarshalJSON() - - if err != nil { - return err - } - - val, err := innerText.MarshalJSON() - - if err != nil { - return err - } - - return el.exec.EvalWithArguments( + return el.exec.Eval( ctx, templates.SetInnerTextBySelector(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: sel, - }, - runtime.CallArgument{ - Value: val, - }, + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(selector), + eval.WithArgValue(innerText), ) } func (el *HTMLElement) GetInnerTextBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - sel, err := selector.MarshalJSON() - - if err != nil { - return nil, err - } - - out, err := el.exec.EvalWithArgumentsAndReturnValue( + out, err := el.exec.EvalValue( ctx, templates.GetInnerTextBySelectorAll(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: sel, - }, + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(selector), ) if err != nil { @@ -735,21 +624,11 @@ func (el *HTMLElement) SetInnerHTML(ctx context.Context, innerHTML values.String } func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector values.String) (values.String, error) { - sel, err := selector.MarshalJSON() - - if err != nil { - return values.EmptyString, err - } - - out, err := el.exec.EvalWithArgumentsAndReturnValue( + out, err := el.exec.EvalValue( ctx, templates.GetInnerHTMLBySelector(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: sel, - }, + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(selector), ) if err != nil { @@ -760,49 +639,21 @@ func (el *HTMLElement) GetInnerHTMLBySelector(ctx context.Context, selector valu } func (el *HTMLElement) SetInnerHTMLBySelector(ctx context.Context, selector, innerHTML values.String) error { - sel, err := selector.MarshalJSON() - - if err != nil { - return err - } - - val, err := innerHTML.MarshalJSON() - - if err != nil { - return err - } - - return el.exec.EvalWithArguments( + return el.exec.Eval( ctx, templates.SetInnerHTMLBySelector(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: sel, - }, - runtime.CallArgument{ - Value: val, - }, + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(selector), + eval.WithArgValue(innerHTML), ) } func (el *HTMLElement) GetInnerHTMLBySelectorAll(ctx context.Context, selector values.String) (*values.Array, error) { - sel, err := selector.MarshalJSON() - - if err != nil { - return values.EmptyArray(), err - } - - out, err := el.exec.EvalWithArgumentsAndReturnValue( + out, err := el.exec.EvalValue( ctx, templates.GetInnerHTMLBySelectorAll(), - runtime.CallArgument{ - ObjectID: &el.id.ObjectID, - }, - runtime.CallArgument{ - Value: sel, - }, + eval.WithArgRef(el.id.ObjectID), + eval.WithArgValue(selector), ) if err != nil { @@ -912,9 +763,16 @@ func (el *HTMLElement) WaitForAttribute( } func (el *HTMLElement) WaitForStyle(ctx context.Context, name values.String, value core.Value, when drivers.WaitEvent) error { - task := events.NewValueWaitTask(when, value, func(ctx context.Context) (core.Value, error) { - return el.GetStyle(ctx, name) - }, events.DefaultPolling) + task := events.NewEvalWaitTask( + el.exec, + templates.WaitForStyle( + name.String(), + value.String(), + when, + ), + events.DefaultPolling, + eval.WithArgRef(el.id.ObjectID), + ) _, err := task.Run(ctx) @@ -1023,9 +881,7 @@ func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.Remote } switch typeName { - case "string", "number", "boolean": - return eval.Unmarshal(&out) - case "array": + case "array", "HTMLCollection": if out.ObjectID == nil { return values.None, nil } @@ -1122,6 +978,8 @@ func (el *HTMLElement) convertEvalResult(ctx context.Context, out runtime.Remote ObjectID: *out.ObjectID, }, ) + case "string", "number", "boolean": + return eval.Unmarshal(&out) default: return values.None, nil } diff --git a/pkg/drivers/cdp/dom/frame.go b/pkg/drivers/cdp/dom/frame.go index 2a2ec008..8d98d219 100644 --- a/pkg/drivers/cdp/dom/frame.go +++ b/pkg/drivers/cdp/dom/frame.go @@ -8,9 +8,8 @@ import ( type ( Frame struct { - tree page.FrameTree - node *HTMLDocument - ready bool + tree page.FrameTree + node *HTMLDocument } AtomicFrameID struct { diff --git a/pkg/drivers/cdp/dom/helpers.go b/pkg/drivers/cdp/dom/helpers.go index 3888db50..acf8b0ad 100644 --- a/pkg/drivers/cdp/dom/helpers.go +++ b/pkg/drivers/cdp/dom/helpers.go @@ -3,14 +3,14 @@ package dom import ( "bytes" "context" - "errors" - "time" + "regexp" + "strings" - "github.com/PuerkitoBio/goquery" "github.com/mafredri/cdp" "github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/page" "github.com/mafredri/cdp/protocol/runtime" + "github.com/pkg/errors" "golang.org/x/net/html" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" @@ -18,7 +18,7 @@ import ( "github.com/MontFerret/ferret/pkg/runtime/values" ) -var emptyExpires = time.Time{} +var camelMatcher = regexp.MustCompile("[A-Za-z0-9]+") // traverseAttrs is a helper function that parses a given interleaved array of node attribute names and values, // and calls a given attribute on each key-value pair @@ -34,11 +34,11 @@ func traverseAttrs(attrs []string, predicate func(name, value string) bool) { } } -func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, innerHTML values.String) error { - var objID *runtime.RemoteObjectID +func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, innerHTML values.String) error { + var objID runtime.RemoteObjectID if id.ObjectID != "" { - objID = &id.ObjectID + objID = id.ObjectID } else { repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID)) @@ -50,28 +50,19 @@ func setInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return errors.New("unable to resolve node") } - objID = repl.Object.ObjectID + objID = *repl.Object.ObjectID } - b, err := innerHTML.MarshalJSON() - - if err != nil { - return err - } - - err = exec.EvalWithArguments(ctx, templates.SetInnerHTML(), - runtime.CallArgument{ - ObjectID: objID, - }, - runtime.CallArgument{ - Value: b, - }, + return exec.Eval( + ctx, + templates.SetInnerHTML(), + eval.WithArgRef(objID), + eval.WithArgValue(innerHTML), ) - return err } -func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, nodeType html.NodeType) (values.String, error) { +func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, nodeType html.NodeType) (values.String, error) { // not a document if nodeType != html.DocumentNode { var objID runtime.RemoteObjectID @@ -101,7 +92,7 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return values.NewString(res.String()), nil } - repl, err := exec.EvalWithReturnValue(ctx, "return document.documentElement.innerHTML") + repl, err := exec.EvalValue(ctx, "return document.documentElement.innerHTML") if err != nil { return "", err @@ -110,11 +101,11 @@ func getInnerHTML(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return values.NewString(repl.String()), nil } -func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, innerText values.String) error { - var objID *runtime.RemoteObjectID +func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, innerText values.String) error { + var objID runtime.RemoteObjectID if id.ObjectID != "" { - objID = &id.ObjectID + objID = id.ObjectID } else { repl, err := client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(id.NodeID)) @@ -126,28 +117,18 @@ func setInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return errors.New("unable to resolve node") } - objID = repl.Object.ObjectID + objID = *repl.Object.ObjectID } - b, err := innerText.MarshalJSON() - - if err != nil { - return err - } - - err = exec.EvalWithArguments(ctx, templates.SetInnerText(), - runtime.CallArgument{ - ObjectID: objID, - }, - runtime.CallArgument{ - Value: b, - }, + return exec.Eval( + ctx, + templates.SetInnerText(), + eval.WithArgRef(objID), + eval.WithArgValue(innerText), ) - - return err } -func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionContext, id HTMLElementIdentity, nodeType html.NodeType) (values.String, error) { +func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.Runtime, id HTMLElementIdentity, nodeType html.NodeType) (values.String, error) { // not a document if nodeType != html.DocumentNode { var objID runtime.RemoteObjectID @@ -177,7 +158,7 @@ func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return values.NewString(res.String()), err } - repl, err := exec.EvalWithReturnValue(ctx, "return document.documentElement.innerText") + repl, err := exec.EvalValue(ctx, "return document.documentElement.innerText") if err != nil { return "", err @@ -186,67 +167,34 @@ func getInnerText(ctx context.Context, client *cdp.Client, exec *eval.ExecutionC return values.NewString(repl.String()), nil } -func parseInnerText(innerHTML string) (values.String, error) { - buff := bytes.NewBuffer([]byte(innerHTML)) - - parsed, err := goquery.NewDocumentFromReader(buff) +func resolveFrame(ctx context.Context, client *cdp.Client, frameID page.FrameID) (dom.Node, *eval.Runtime, error) { + exec, err := eval.New(ctx, client, frameID) if err != nil { - return values.EmptyString, err + return dom.Node{}, nil, errors.Wrap(err, "create JS executor") } - return values.NewString(parsed.Text()), nil -} - -func createChildrenArray(nodes []dom.Node) []HTMLElementIdentity { - children := make([]HTMLElementIdentity, len(nodes)) - - for idx, child := range nodes { - child := child - - children[idx] = HTMLElementIdentity{ - NodeID: child.NodeID, - } - } - - return children -} - -func resolveFrame(ctx context.Context, client *cdp.Client, frameID page.FrameID) (dom.Node, runtime.ExecutionContextID, error) { - worldRepl, err := client.Page.CreateIsolatedWorld(ctx, page.NewCreateIsolatedWorldArgs(frameID)) - - if err != nil { - return dom.Node{}, -1, err - } - - evalRes, err := client.Runtime.Evaluate( + evalRes, err := exec.EvalRef( ctx, - runtime.NewEvaluateArgs(eval.PrepareEval("return document")). - SetContextID(worldRepl.ExecutionContextID), + "return document", ) if err != nil { - return dom.Node{}, -1, err + return dom.Node{}, nil, err } - if evalRes.ExceptionDetails != nil { - exception := *evalRes.ExceptionDetails - - return dom.Node{}, -1, errors.New(exception.Text) + if evalRes.ObjectID == nil { + return dom.Node{}, nil, errors.New("failed to resolve frame document") } - if evalRes.Result.ObjectID == nil { - return dom.Node{}, -1, errors.New("failed to resolve frame document") - } - - req, err := client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*evalRes.Result.ObjectID)) + req, err := client.DOM.RequestNode(ctx, dom.NewRequestNodeArgs(*evalRes.ObjectID)) if err != nil { - return dom.Node{}, -1, err + return dom.Node{}, nil, err } if req.NodeID == 0 { - return dom.Node{}, -1, errors.New("framed document is resolved with empty node id") + return dom.Node{}, nil, errors.New("framed document is resolved with empty node id") } desc, err := client.DOM.DescribeNode( @@ -258,12 +206,38 @@ func resolveFrame(ctx context.Context, client *cdp.Client, frameID page.FrameID) ) if err != nil { - return dom.Node{}, -1, err + return dom.Node{}, nil, err } // Returned node, by some reason, does not contain the NodeID // So, we have to set it manually desc.Node.NodeID = req.NodeID - return desc.Node, worldRepl.ExecutionContextID, nil + return desc.Node, exec, nil +} + +func toCamelCase(input string) string { + var buf bytes.Buffer + + matched := camelMatcher.FindAllString(input, -1) + + if matched == nil { + return "" + } + + for i, match := range matched { + res := match + + if i > 0 { + if len(match) > 1 { + res = strings.ToUpper(match[0:1]) + match[1:] + } else { + res = strings.ToUpper(match) + } + } + + buf.WriteString(res) + } + + return buf.String() } diff --git a/pkg/drivers/cdp/dom/helpers_test.go b/pkg/drivers/cdp/dom/helpers_test.go new file mode 100644 index 00000000..18bb3aea --- /dev/null +++ b/pkg/drivers/cdp/dom/helpers_test.go @@ -0,0 +1,55 @@ +package dom + +import ( + "testing" + + . "github.com/smartystreets/goconvey/convey" +) + +func Test_toCamelCase(t *testing.T) { + Convey("toCamelCase", t, func() { + Convey("should format string into camel case", func() { + inputs := []struct { + actual string + expected string + }{ + { + actual: "foo-bar", + expected: "fooBar", + }, + { + actual: "foo-1-bar", + expected: "foo1Bar", + }, + { + actual: "overscroll-behavior-x", + expected: "overscrollBehaviorX", + }, + { + actual: "x", + expected: "x", + }, + { + actual: "foo-x", + expected: "fooX", + }, + { + actual: "foo-$", + expected: "foo", + }, + { + actual: "color", + expected: "color", + }, + { + actual: "textDecorationSkipInk", + expected: "textDecorationSkipInk", + }, + } + + for _, input := range inputs { + So(toCamelCase(input.actual), ShouldEqual, input.expected) + } + }) + }) +} diff --git a/pkg/drivers/cdp/dom/manager.go b/pkg/drivers/cdp/dom/manager.go index b7102b1e..e695eac9 100644 --- a/pkg/drivers/cdp/dom/manager.go +++ b/pkg/drivers/cdp/dom/manager.go @@ -11,13 +11,14 @@ import ( "github.com/MontFerret/ferret/pkg/drivers/cdp/input" "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/logging" "github.com/MontFerret/ferret/pkg/runtime/values" ) type ( Manager struct { mu sync.RWMutex - logger *zerolog.Logger + logger zerolog.Logger client *cdp.Client mouse *input.Mouse keyboard *input.Keyboard @@ -27,14 +28,14 @@ type ( ) func New( - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, mouse *input.Mouse, keyboard *input.Keyboard, ) (manager *Manager, err error) { manager = new(Manager) - manager.logger = logger + manager.logger = logging.WithName(logger.With(), "dom_manager").Logger() manager.client = client manager.mouse = mouse manager.keyboard = keyboard @@ -212,7 +213,7 @@ func (m *Manager) getFrameInternal(ctx context.Context, frameID page.FrameID) (* } // the frames is not loaded yet - node, execID, err := resolveFrame(ctx, m.client, frameID) + node, exec, err := resolveFrame(ctx, m.client, frameID) if err != nil { return nil, errors.Wrapf(err, "failed to resolve frame node: %s", frameID) @@ -227,7 +228,7 @@ func (m *Manager) getFrameInternal(ctx context.Context, frameID page.FrameID) (* m.keyboard, node, frame.tree, - execID, + exec, ) if err != nil { diff --git a/pkg/drivers/cdp/driver.go b/pkg/drivers/cdp/driver.go index 32e0ee51..69affbb0 100644 --- a/pkg/drivers/cdp/driver.go +++ b/pkg/drivers/cdp/driver.go @@ -52,9 +52,7 @@ func (drv *Driver) Open(ctx context.Context, params drivers.Params) (drivers.HTM conn, err := drv.createConnection(ctx, params.KeepCookies) if err != nil { - logger. - Error(). - Timestamp(). + logger.Error(). Err(err). Str("driver", drv.options.Name). Msg("failed to create a new connection") @@ -71,9 +69,7 @@ func (drv *Driver) Parse(ctx context.Context, params drivers.ParseParams) (drive conn, err := drv.createConnection(ctx, true) if err != nil { - logger. - Error(). - Timestamp(). + logger.Error(). Err(err). Str("driver", drv.options.Name). Msg("failed to create a new connection") @@ -158,7 +154,7 @@ func (drv *Driver) init(ctx context.Context) error { bconn, err := rpcc.DialContext( ctx, ver.WebSocketDebuggerURL, - rpcc.WithWriteBufferSize(1048562), + rpcc.WithWriteBufferSize(104857586), rpcc.WithCompression(), ) diff --git a/pkg/drivers/cdp/eval/context.go b/pkg/drivers/cdp/eval/context.go deleted file mode 100644 index 6c177cdf..00000000 --- a/pkg/drivers/cdp/eval/context.go +++ /dev/null @@ -1,366 +0,0 @@ -package eval - -import ( - "context" - "fmt" - "strings" - - "github.com/mafredri/cdp" - "github.com/mafredri/cdp/protocol/dom" - "github.com/mafredri/cdp/protocol/page" - "github.com/mafredri/cdp/protocol/runtime" - "github.com/pkg/errors" - - "github.com/MontFerret/ferret/pkg/drivers" - "github.com/MontFerret/ferret/pkg/runtime/core" - "github.com/MontFerret/ferret/pkg/runtime/values" -) - -const EmptyExecutionContextID = runtime.ExecutionContextID(-1) - -type ExecutionContext struct { - client *cdp.Client - frame page.Frame - contextID runtime.ExecutionContextID -} - -func NewExecutionContextFrom(ctx context.Context, client *cdp.Client, frame page.Frame) (*ExecutionContext, error) { - world, err := client.Page.CreateIsolatedWorld(ctx, page.NewCreateIsolatedWorldArgs(frame.ID)) - - if err != nil { - return nil, err - } - - return NewExecutionContext(client, frame, world.ExecutionContextID), nil -} - -func NewExecutionContext(client *cdp.Client, frame page.Frame, contextID runtime.ExecutionContextID) *ExecutionContext { - ec := new(ExecutionContext) - ec.client = client - ec.frame = frame - ec.contextID = contextID - - return ec -} - -func (ec *ExecutionContext) ID() runtime.ExecutionContextID { - return ec.contextID -} - -func (ec *ExecutionContext) Eval(ctx context.Context, exp string) error { - _, err := ec.evalWithValueInternal( - ctx, - runtime. - NewEvaluateArgs(PrepareEval(exp)), - ) - - return err -} - -func (ec *ExecutionContext) EvalWithArguments(ctx context.Context, exp string, args ...runtime.CallArgument) error { - _, err := ec.evalWithArgumentsInternal(ctx, exp, args, false) - - return err -} - -func (ec *ExecutionContext) EvalWithArgumentsAndReturnValue(ctx context.Context, exp string, args ...runtime.CallArgument) (core.Value, error) { - out, err := ec.evalWithArgumentsInternal(ctx, exp, args, true) - - if err != nil { - return values.None, err - } - - return Unmarshal(&out) -} - -func (ec *ExecutionContext) EvalWithArgumentsAndReturnReference(ctx context.Context, exp string, args ...runtime.CallArgument) (runtime.RemoteObject, error) { - return ec.evalWithArgumentsInternal(ctx, exp, args, false) -} - -func (ec *ExecutionContext) EvalWithReturnValue(ctx context.Context, exp string) (core.Value, error) { - return ec.evalWithValueInternal( - ctx, - runtime. - NewEvaluateArgs(PrepareEval(exp)). - SetReturnByValue(true), - ) -} - -func (ec *ExecutionContext) EvalWithReturnReference(ctx context.Context, exp string) (runtime.RemoteObject, error) { - return ec.evalInternal( - ctx, - runtime. - NewEvaluateArgs(PrepareEval(exp)). - SetReturnByValue(false), - ) -} - -func (ec *ExecutionContext) EvalAsync(ctx context.Context, exp string) (core.Value, error) { - return ec.evalWithValueInternal( - ctx, - runtime. - NewEvaluateArgs(PrepareEval(exp)). - SetReturnByValue(true). - SetAwaitPromise(true), - ) -} - -func (ec *ExecutionContext) CallMethod( - ctx context.Context, - objectID runtime.RemoteObjectID, - methodName string, - args []runtime.CallArgument, -) (*runtime.RemoteObject, error) { - callArgs := runtime.NewCallFunctionOnArgs(methodName). - SetObjectID(objectID). - SetArguments(args) - - if ec.contextID != EmptyExecutionContextID { - callArgs.SetExecutionContextID(ec.contextID) - } - - found, err := ec.client.Runtime.CallFunctionOn( - ctx, - callArgs, - ) - - if err != nil { - return nil, err - } - - err = ec.handleException(found.ExceptionDetails) - - if err != nil { - return nil, err - } - - if found.Result.ObjectID == nil { - return nil, nil - } - - return &found.Result, nil -} - -func (ec *ExecutionContext) ReadPropertyByNodeID( - ctx context.Context, - nodeID dom.NodeID, - propName string, -) (core.Value, error) { - obj, err := ec.client.DOM.ResolveNode(ctx, dom.NewResolveNodeArgs().SetNodeID(nodeID)) - - if err != nil { - return values.None, err - } - - if obj.Object.ObjectID == nil { - return values.None, nil - } - - return ec.ReadProperty(ctx, *obj.Object.ObjectID, propName) -} - -func (ec *ExecutionContext) ReadProperty( - ctx context.Context, - objectID runtime.RemoteObjectID, - propName string, -) (core.Value, error) { - res, err := ec.client.Runtime.GetProperties( - ctx, - runtime.NewGetPropertiesArgs(objectID), - ) - - if err != nil { - return values.None, err - } - - if res.ExceptionDetails != nil { - return values.None, res.ExceptionDetails - } - - // all props - if propName == "" { - arr := values.NewArray(len(res.Result)) - - for _, prop := range res.Result { - val, err := Unmarshal(prop.Value) - - if err != nil { - return values.None, err - } - - arr.Push(val) - } - - return arr, nil - } - - for _, prop := range res.Result { - if prop.Name == propName { - return Unmarshal(prop.Value) - } - } - - return values.None, nil -} - -func (ec *ExecutionContext) DispatchEvent( - ctx context.Context, - objectID runtime.RemoteObjectID, - eventName string, -) (values.Boolean, error) { - args := runtime.NewEvaluateArgs(PrepareEval(fmt.Sprintf(` - return new window.MouseEvent('%s', { bubbles: true, cancelable: true }) - `, eventName))) - - if ec.contextID != EmptyExecutionContextID { - args.SetContextID(ec.contextID) - } - - evt, err := ec.client.Runtime.Evaluate(ctx, args) - - if err != nil { - return values.False, nil - } - - err = ec.handleException(evt.ExceptionDetails) - - if err != nil { - return values.False, nil - } - - if evt.Result.ObjectID == nil { - return values.False, nil - } - - evtID := evt.Result.ObjectID - - // release the event object - defer ec.client.Runtime.ReleaseObject(ctx, runtime.NewReleaseObjectArgs(*evtID)) - - _, err = ec.CallMethod( - ctx, - objectID, - "dispatchEvent", - []runtime.CallArgument{ - { - ObjectID: evt.Result.ObjectID, - }, - }, - ) - - if err != nil { - return values.False, err - } - - return values.True, nil -} - -func (ec *ExecutionContext) ResolveRemoteObject(ctx context.Context, exp string) (runtime.RemoteObject, error) { - res, err := ec.evalInternal(ctx, runtime.NewEvaluateArgs(PrepareEval(exp))) - - if err != nil { - return runtime.RemoteObject{}, err - } - - if res.ObjectID == nil { - return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object") - } - - return res, nil -} - -func (ec *ExecutionContext) ResolveNode(ctx context.Context, nodeID dom.NodeID) (runtime.RemoteObject, error) { - args := dom.NewResolveNodeArgs().SetNodeID(nodeID) - - if ec.contextID != EmptyExecutionContextID { - args.SetExecutionContextID(ec.contextID) - } - - repl, err := ec.client.DOM.ResolveNode(ctx, args) - - if err != nil { - return runtime.RemoteObject{}, err - } - - if repl.Object.ObjectID == nil { - return runtime.RemoteObject{}, errors.Wrap(core.ErrUnexpected, "unable to resolve remote object") - } - - return repl.Object, nil -} - -func (ec *ExecutionContext) evalWithArgumentsInternal(ctx context.Context, exp string, args []runtime.CallArgument, ret bool) (runtime.RemoteObject, error) { - cfArgs := runtime. - NewCallFunctionOnArgs(exp). - SetArguments(args). - SetReturnByValue(ret) - - if ec.contextID != EmptyExecutionContextID { - cfArgs.SetExecutionContextID(ec.contextID) - } - - repl, err := ec.client.Runtime.CallFunctionOn(ctx, cfArgs) - - if err != nil { - return runtime.RemoteObject{}, err - } - - err = ec.handleException(repl.ExceptionDetails) - - if err != nil { - return runtime.RemoteObject{}, err - } - - return repl.Result, nil -} - -func (ec *ExecutionContext) evalWithValueInternal(ctx context.Context, args *runtime.EvaluateArgs) (core.Value, error) { - obj, err := ec.evalInternal(ctx, args) - - if err != nil { - return values.None, err - } - - if obj.Type != "undefined" && obj.Type != "null" { - return values.Unmarshal(obj.Value) - } - - return Unmarshal(&obj) -} - -func (ec *ExecutionContext) evalInternal(ctx context.Context, args *runtime.EvaluateArgs) (runtime.RemoteObject, error) { - if ec.contextID != EmptyExecutionContextID { - args.SetContextID(ec.contextID) - } - - out, err := ec.client.Runtime.Evaluate(ctx, args) - - if err != nil { - return runtime.RemoteObject{}, err - } - - err = ec.handleException(out.ExceptionDetails) - - if err != nil { - return runtime.RemoteObject{}, err - } - - return out.Result, nil -} - -func (ec *ExecutionContext) handleException(details *runtime.ExceptionDetails) error { - if details == nil { - return nil - } - - desc := *details.Exception.Description - - if strings.Contains(desc, drivers.ErrNotFound.Error()) { - return drivers.ErrNotFound - } - - return core.Error( - core.ErrUnexpected, - fmt.Sprintf("%s: %s", details.Text, desc), - ) -} diff --git a/pkg/drivers/cdp/eval/function.go b/pkg/drivers/cdp/eval/function.go new file mode 100644 index 00000000..56be5485 --- /dev/null +++ b/pkg/drivers/cdp/eval/function.go @@ -0,0 +1,127 @@ +package eval + +import ( + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/mafredri/cdp/protocol/runtime" + "strings" +) + +type ( + FunctionReturnType int + + Function struct { + exp string + ownerID *runtime.RemoteObjectID + args []runtime.CallArgument + returnType FunctionReturnType + async bool + } + + FunctionOption func(op *Function) +) + +const ( + ReturnNothing FunctionReturnType = iota + ReturnValue + ReturnRef +) + +func newFunction(exp string, opts []FunctionOption) *Function { + op := new(Function) + op.exp = exp + op.returnType = ReturnNothing + + for _, opt := range opts { + opt(op) + } + + return op +} + +func (fn *Function) Use(opt FunctionOption) { + opt(fn) +} + +func (fn *Function) toArgs(ctx runtime.ExecutionContextID) *runtime.CallFunctionOnArgs { + exp := strings.TrimSpace(fn.exp) + + if !strings.HasPrefix(exp, "(") && !strings.HasPrefix(exp, "function") { + exp = wrapExp(exp) + } + + call := runtime.NewCallFunctionOnArgs(exp). + SetAwaitPromise(fn.async) + + if fn.returnType == ReturnValue { + call.SetReturnByValue(true) + } + + if ctx != EmptyExecutionContextID { + call.SetExecutionContextID(ctx) + } + + if fn.ownerID != nil { + call.SetObjectID(*fn.ownerID) + } + + if len(fn.args) > 0 { + call.SetArguments(fn.args) + } + + return call +} + +func withReturnRef() FunctionOption { + return func(op *Function) { + op.returnType = ReturnRef + } +} + +func withReturnValue() FunctionOption { + return func(op *Function) { + op.returnType = ReturnValue + } +} + +func WithArgs(args ...runtime.CallArgument) FunctionOption { + return func(op *Function) { + if op.args == nil { + op.args = args + } else { + op.args = append(op.args, args...) + } + } +} + +func WithArgValue(value core.Value) FunctionOption { + raw, err := value.MarshalJSON() + + if err != nil { + // we defer error + return WithArgs(runtime.CallArgument{ + Value: []byte(err.Error()), + }) + } + + return WithArgs(runtime.CallArgument{ + Value: raw, + }) +} + +func WithArgRef(id runtime.RemoteObjectID) FunctionOption { + return WithArgs(runtime.CallArgument{ + ObjectID: &id, + }) +} + +func WithOwner(ctx *runtime.RemoteObjectID) FunctionOption { + return func(op *Function) { + op.ownerID = ctx + } +} + +func WithAsync() FunctionOption { + return func(op *Function) { + op.async = true + } +} diff --git a/pkg/drivers/cdp/eval/helpers.go b/pkg/drivers/cdp/eval/helpers.go index 1a69849a..69713071 100644 --- a/pkg/drivers/cdp/eval/helpers.go +++ b/pkg/drivers/cdp/eval/helpers.go @@ -1,17 +1,38 @@ package eval import ( - "fmt" "strconv" + "strings" "github.com/mafredri/cdp/protocol/runtime" + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" ) -func PrepareEval(exp string) string { - return fmt.Sprintf("((function () {%s})())", exp) +func CastToValue(input interface{}) (core.Value, error) { + value, ok := input.(core.Value) + + if !ok { + return values.None, core.Error(core.ErrInvalidType, "eval return type") + } + + return value, nil +} + +func CastToReference(input interface{}) (runtime.RemoteObject, error) { + value, ok := input.(runtime.RemoteObject) + + if !ok { + return runtime.RemoteObject{}, core.Error(core.ErrInvalidType, "eval return type") + } + + return value, nil +} + +func wrapExp(exp string) string { + return "function () {" + exp + "}" } func Unmarshal(obj *runtime.RemoteObject) (core.Value, error) { @@ -34,3 +55,20 @@ func Unmarshal(obj *runtime.RemoteObject) (core.Value, error) { return values.Unmarshal(obj.Value) } } + +func parseRuntimeException(details *runtime.ExceptionDetails) error { + if details == nil || details.Exception == nil { + return nil + } + + desc := *details.Exception.Description + + if strings.Contains(desc, drivers.ErrNotFound.Error()) { + return drivers.ErrNotFound + } + + return core.Error( + core.ErrUnexpected, + desc, + ) +} diff --git a/pkg/drivers/cdp/eval/param.go b/pkg/drivers/cdp/eval/param.go index 5ec33d07..b41925f4 100644 --- a/pkg/drivers/cdp/eval/param.go +++ b/pkg/drivers/cdp/eval/param.go @@ -1,6 +1,7 @@ package eval import ( + "bytes" "strconv" "github.com/MontFerret/ferret/pkg/runtime/core" @@ -24,6 +25,36 @@ func Param(input core.Value) string { } } +func ParamList(inputs []core.Value) string { + var buf bytes.Buffer + lastIndex := len(inputs) - 1 + + for i, input := range inputs { + buf.WriteString(Param(input)) + + if i != lastIndex { + buf.WriteString(",") + } + } + + return buf.String() +} + +func ParamStringList(inputs []values.String) string { + var buf bytes.Buffer + lastIndex := len(inputs) - 1 + + for i, input := range inputs { + buf.WriteString(ParamString(input.String())) + + if i != lastIndex { + buf.WriteString(",") + } + } + + return buf.String() +} + func ParamString(param string) string { return "`" + param + "`" } diff --git a/pkg/drivers/cdp/eval/runtime.go b/pkg/drivers/cdp/eval/runtime.go new file mode 100644 index 00000000..29b6ebf6 --- /dev/null +++ b/pkg/drivers/cdp/eval/runtime.go @@ -0,0 +1,146 @@ +package eval + +import ( + "context" + + "github.com/mafredri/cdp" + "github.com/mafredri/cdp/protocol/page" + "github.com/mafredri/cdp/protocol/runtime" + "github.com/pkg/errors" + + "github.com/MontFerret/ferret/pkg/runtime/core" + "github.com/MontFerret/ferret/pkg/runtime/values" +) + +const EmptyExecutionContextID = runtime.ExecutionContextID(-1) + +type Runtime struct { + client *cdp.Client + frame page.Frame + contextID runtime.ExecutionContextID +} + +func New(ctx context.Context, client *cdp.Client, frameID page.FrameID) (*Runtime, error) { + world, err := client.Page.CreateIsolatedWorld(ctx, page.NewCreateIsolatedWorldArgs(frameID)) + + if err != nil { + return nil, err + } + + return Create(client, world.ExecutionContextID), nil +} + +func Create(client *cdp.Client, contextID runtime.ExecutionContextID) *Runtime { + ec := new(Runtime) + ec.client = client + ec.contextID = contextID + + return ec +} + +func (ex *Runtime) ContextID() runtime.ExecutionContextID { + return ex.contextID +} + +func (ex *Runtime) Eval(ctx context.Context, exp string, opts ...FunctionOption) error { + _, err := ex.call(ctx, newFunction(exp, opts)) + + return err +} + +func (ex *Runtime) EvalValue(ctx context.Context, exp string, opts ...FunctionOption) (core.Value, error) { + fn := newFunction(exp, opts) + fn.Use(withReturnValue()) + + out, err := ex.call(ctx, fn) + + if err != nil { + return values.None, err + } + + return CastToValue(out) +} + +func (ex *Runtime) EvalRef(ctx context.Context, exp string, opts ...FunctionOption) (runtime.RemoteObject, error) { + fn := newFunction(exp, opts) + fn.Use(withReturnRef()) + + out, err := ex.call(ctx, fn) + + if err != nil { + return runtime.RemoteObject{}, err + } + + return CastToReference(out) +} + +func (ex *Runtime) ReadProperty( + ctx context.Context, + objectID runtime.RemoteObjectID, + propName string, +) (core.Value, error) { + res, err := ex.client.Runtime.GetProperties( + ctx, + runtime.NewGetPropertiesArgs(objectID), + ) + + if err != nil { + return values.None, err + } + + if res.ExceptionDetails != nil { + return values.None, res.ExceptionDetails + } + + // all props + if propName == "" { + arr := values.NewArray(len(res.Result)) + + for _, prop := range res.Result { + val, err := Unmarshal(prop.Value) + + if err != nil { + return values.None, err + } + + arr.Push(val) + } + + return arr, nil + } + + for _, prop := range res.Result { + if prop.Name == propName { + return Unmarshal(prop.Value) + } + } + + return values.None, nil +} + +func (ex *Runtime) call(ctx context.Context, fn *Function) (interface{}, error) { + repl, err := ex.client.Runtime.CallFunctionOn(ctx, fn.toArgs(ex.contextID)) + + if err != nil { + return nil, errors.Wrap(err, "runtime call") + } + + if err := parseRuntimeException(repl.ExceptionDetails); err != nil { + return nil, err + } + + switch fn.returnType { + case ReturnValue: + out := repl.Result + + if out.Type != "undefined" && out.Type != "null" { + return values.Unmarshal(out.Value) + } + + return Unmarshal(&out) + case ReturnRef: + return repl.Result, nil + default: + return nil, nil + } +} diff --git a/pkg/drivers/cdp/events/loop.go b/pkg/drivers/cdp/events/loop.go index 16ed2c18..d2872bc3 100644 --- a/pkg/drivers/cdp/events/loop.go +++ b/pkg/drivers/cdp/events/loop.go @@ -2,6 +2,7 @@ package events import ( "context" + "github.com/MontFerret/ferret/pkg/runtime/core" "math/rand" "sync" ) @@ -10,6 +11,7 @@ type Loop struct { mu sync.RWMutex sources *SourceCollection listeners *ListenerCollection + cancel context.CancelFunc } func NewLoop() *Loop { @@ -20,8 +22,33 @@ func NewLoop() *Loop { return loop } -func (loop *Loop) Run(ctx context.Context) { - go loop.run(ctx) +func (loop *Loop) Run(ctx context.Context) error { + loop.mu.Lock() + defer loop.mu.Unlock() + + if loop.cancel != nil { + return core.Error(core.ErrInvalidOperation, "loop is already running") + } + + childCtx, cancel := context.WithCancel(ctx) + + loop.cancel = cancel + + go loop.run(childCtx) + + return nil +} + +func (loop *Loop) Close() error { + loop.mu.Lock() + defer loop.mu.Unlock() + + if loop.cancel != nil { + loop.cancel() + loop.cancel = nil + } + + return loop.sources.Close() } func (loop *Loop) AddSource(source Source) { @@ -38,6 +65,13 @@ func (loop *Loop) RemoveSource(source Source) { loop.sources.Remove(source) } +func (loop *Loop) Listeners(eventID ID) int { + loop.mu.RLock() + defer loop.mu.RUnlock() + + return loop.listeners.Size(eventID) +} + func (loop *Loop) AddListener(eventID ID, handler Handler) ListenerID { loop.mu.RLock() defer loop.mu.RUnlock() @@ -67,11 +101,11 @@ func (loop *Loop) run(ctx context.Context) { size := sources.Size() counter := -1 - // in case event array is empty - // we use this mock noop event source to simplify the logic - noop := newNoopSource() - for { + if isCtxDone(ctx) { + break + } + counter++ if counter >= size { @@ -88,13 +122,12 @@ func (loop *Loop) run(ctx context.Context) { if err == nil { source = found } else { - // might be removed - source = noop // force to reset counter counter = size + continue } } else { - source = noop + continue } select { @@ -129,7 +162,7 @@ func (loop *Loop) emit(ctx context.Context, eventID ID, message interface{}, err case <-ctx.Done(): return default: - // if returned false, it means the loops should call the handler anymore + // if returned false, it means the loops should not call the handler anymore if !listener.Handler(ctx, message) { loop.mu.RLock() loop.listeners.Remove(eventID, listener.ID) diff --git a/pkg/drivers/cdp/events/loop_test.go b/pkg/drivers/cdp/events/loop_test.go index ba25f5f1..4da969df 100644 --- a/pkg/drivers/cdp/events/loop_test.go +++ b/pkg/drivers/cdp/events/loop_test.go @@ -50,9 +50,13 @@ type ( var TestEvent = events.New("test_event") func NewTestEventStream() *TestEventStream { + return NewBufferedTestEventStream(0) +} + +func NewBufferedTestEventStream(buffer int) *TestEventStream { es := new(TestEventStream) - es.ready = make(chan struct{}) - es.message = make(chan interface{}) + es.ready = make(chan struct{}, buffer) + es.message = make(chan interface{}, buffer) return es } @@ -290,7 +294,7 @@ func TestLoop(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - loop.Run(ctx) + So(loop.Run(ctx), ShouldBeNil) defer cancel() loop.AddListener(TestEvent, events.Always(func(ctx context.Context, message interface{}) { @@ -352,7 +356,7 @@ func TestLoop(t *testing.T) { ctx, cancel := context.WithCancel(context.Background()) - loop.Run(ctx) + So(loop.Run(ctx), ShouldBeNil) defer cancel() time.Sleep(time.Duration(100) * time.Millisecond) @@ -363,6 +367,45 @@ func TestLoop(t *testing.T) { So(counter.Value(), ShouldEqual, 1) }) + + Convey("Should stop on Context.Done", t, func() { + loop := events.NewLoop() + eventsToFire := 5 + counter := NewCounter() + + onLoad := &TestLoadEventFiredClient{NewBufferedTestEventStream(10)} + loop.AddSource(events.NewSource(TestEvent, onLoad, func(_ rpcc.Stream) (i interface{}, e error) { + return onLoad.Recv() + })) + + loop.AddListener(TestEvent, events.Always(func(ctx context.Context, message interface{}) { + counter.Increase() + })) + + ctx, cancel := context.WithCancel(context.Background()) + So(loop.Run(ctx), ShouldBeNil) + + for i := 0; i <= eventsToFire; i++ { + time.Sleep(time.Duration(100) * time.Millisecond) + + onLoad.Emit(&page.LoadEventFiredReply{}) + } + + // Stop the loop + cancel() + + time.Sleep(time.Duration(100) * time.Millisecond) + + onLoad.Emit(&page.LoadEventFiredReply{}) + + for i := 0; i <= eventsToFire; i++ { + time.Sleep(time.Duration(100) * time.Millisecond) + + onLoad.Emit(&page.LoadEventFiredReply{}) + } + + So(counter.Value(), ShouldEqual, eventsToFire) + }) } func BenchmarkLoop_AddListenerSync(b *testing.B) { diff --git a/pkg/drivers/cdp/events/noop.go b/pkg/drivers/cdp/events/noop.go deleted file mode 100644 index 40786449..00000000 --- a/pkg/drivers/cdp/events/noop.go +++ /dev/null @@ -1,31 +0,0 @@ -package events - -import ( - "github.com/MontFerret/ferret/pkg/runtime/core" -) - -type noopEvent struct { - c chan struct{} -} - -func newNoopSource() Source { - return noopEvent{ - c: make(chan struct{}), - } -} - -func (n noopEvent) Ready() <-chan struct{} { - return n.c -} - -func (n noopEvent) RecvMsg(_ interface{}) error { - return core.ErrNotSupported -} - -func (n noopEvent) Close() error { - return nil -} - -func (n noopEvent) Recv() (Event, error) { - return Event{}, core.ErrNotSupported -} diff --git a/pkg/drivers/cdp/events/wait.go b/pkg/drivers/cdp/events/wait.go index e455bd3f..78b40620 100644 --- a/pkg/drivers/cdp/events/wait.go +++ b/pkg/drivers/cdp/events/wait.go @@ -57,15 +57,17 @@ func (task *WaitTask) Run(ctx context.Context) (core.Value, error) { } func NewEvalWaitTask( - ec *eval.ExecutionContext, + ec *eval.Runtime, predicate string, polling time.Duration, + opts ...eval.FunctionOption, ) *WaitTask { return NewWaitTask( func(ctx context.Context) (core.Value, error) { - return ec.EvalWithReturnValue( + return ec.EvalValue( ctx, predicate, + opts..., ) }, polling, diff --git a/pkg/drivers/cdp/input/manager.go b/pkg/drivers/cdp/input/manager.go index 9bd39b39..e5c0fa7e 100644 --- a/pkg/drivers/cdp/input/manager.go +++ b/pkg/drivers/cdp/input/manager.go @@ -2,6 +2,8 @@ package input import ( "context" + "github.com/MontFerret/ferret/pkg/runtime/logging" + "github.com/rs/zerolog" "time" "github.com/mafredri/cdp" @@ -23,20 +25,25 @@ type ( } Manager struct { + logger zerolog.Logger client *cdp.Client - exec *eval.ExecutionContext + exec *eval.Runtime keyboard *Keyboard mouse *Mouse } ) func NewManager( + logger zerolog.Logger, client *cdp.Client, - exec *eval.ExecutionContext, + exec *eval.Runtime, keyboard *Keyboard, mouse *Mouse, ) *Manager { + logger = logging.WithName(logger.With(), "input_manager").Logger() + return &Manager{ + logger, client, exec, keyboard, @@ -53,35 +60,111 @@ func (m *Manager) Mouse() *Mouse { } func (m *Manager) ScrollTop(ctx context.Context, options drivers.ScrollOptions) error { - return m.exec.Eval(ctx, templates.ScrollTop(options)) + m.logger.Trace(). + Str("behavior", options.Behavior.String()). + Str("block", options.Block.String()). + Str("inline", options.Inline.String()). + Msg("scrolling to the top") + + if err := m.exec.Eval(ctx, templates.ScrollTop(options)); err != nil { + m.logger.Trace().Err(err).Msg("failed to scroll to the top") + + return err + } + + m.logger.Trace().Msg("scrolled to the top") + + return nil } func (m *Manager) ScrollBottom(ctx context.Context, options drivers.ScrollOptions) error { - return m.exec.Eval(ctx, templates.ScrollBottom(options)) + m.logger.Trace(). + Str("behavior", options.Behavior.String()). + Str("block", options.Block.String()). + Str("inline", options.Inline.String()). + Msg("scrolling to the bottom") + + if err := m.exec.Eval(ctx, templates.ScrollBottom(options)); err != nil { + m.logger.Trace().Err(err).Msg("failed to scroll to the bottom") + + return err + } + + m.logger.Trace().Msg("scrolled to the bottom") + + return nil } func (m *Manager) ScrollIntoView(ctx context.Context, objectID runtime.RemoteObjectID, options drivers.ScrollOptions) error { - return m.exec.EvalWithArguments( + m.logger.Trace(). + Str("object_id", string(objectID)). + Str("behavior", options.Behavior.String()). + Str("block", options.Block.String()). + Str("inline", options.Inline.String()). + Msg("scrolling to an element") + + if err := m.exec.Eval( ctx, templates.ScrollIntoView(options), - runtime.CallArgument{ - ObjectID: &objectID, - }, - ) + eval.WithArgRef(objectID), + ); err != nil { + m.logger.Trace().Err(err).Msg("failed to scroll to an element") + + return err + } + + m.logger.Trace().Msg("scrolled to an element") + + return nil } func (m *Manager) ScrollIntoViewBySelector(ctx context.Context, selector string, options drivers.ScrollOptions) error { - return m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector, options)) + m.logger.Trace(). + Str("selector", selector). + Str("behavior", options.Behavior.String()). + Str("block", options.Block.String()). + Str("inline", options.Inline.String()). + Msg("scrolling to an element by selector") + + if err := m.exec.Eval(ctx, templates.ScrollIntoViewBySelector(selector, options)); err != nil { + m.logger.Trace().Err(err).Msg("failed to scroll to an element by selector") + + return err + } + + m.logger.Trace().Msg("scrolled to an element by selector") + + return nil } func (m *Manager) ScrollByXY(ctx context.Context, x, y float64, options drivers.ScrollOptions) error { - return m.exec.Eval( + m.logger.Trace(). + Float64("x", x). + Float64("y", y). + Str("behavior", options.Behavior.String()). + Str("block", options.Block.String()). + Str("inline", options.Inline.String()). + Msg("scrolling to an element by given coordinates") + + if err := m.exec.Eval( ctx, templates.Scroll(eval.ParamFloat(x), eval.ParamFloat(y), options), - ) + ); err != nil { + m.logger.Trace().Err(err).Msg("failed to scroll to an element by coordinates") + + return err + } + + m.logger.Trace().Msg("scrolled to an element by given coordinates") + + return nil } func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) error { + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("focusing on an element") + err := m.ScrollIntoView(ctx, objectID, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -92,10 +175,23 @@ func (m *Manager) Focus(ctx context.Context, objectID runtime.RemoteObjectID) er return err } - return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)) + if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)); err != nil { + m.logger.Trace().Err(err).Msg("failed focusing on an element") + + return err + } + + m.logger.Trace().Msg("focused on an element") + + return nil } func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Msg("focusing on an element by selector") + err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -106,70 +202,171 @@ func (m *Manager) FocusBySelector(ctx context.Context, parentNodeID dom.NodeID, return err } + m.logger.Trace().Msg("resolving an element by selector") + found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector)) if err != nil { - return nil + m.logger.Trace(). + Err(err). + Msg("failed resolving an element by selector") + + return err } - return m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)) + if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)); err != nil { + m.logger.Trace(). + Err(err). + Msg("failed focusing on an element by selector") + + return err + } + + m.logger.Trace().Msg("focused on an element") + + return nil } func (m *Manager) Blur(ctx context.Context, objectID runtime.RemoteObjectID) error { - return m.exec.EvalWithArguments(ctx, templates.Blur(), runtime.CallArgument{ - ObjectID: &objectID, - }) + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("removing focus from an element") + + if err := m.exec.Eval(ctx, templates.Blur(), eval.WithArgRef(objectID)); err != nil { + m.logger.Trace(). + Err(err). + Msg("failed removing focus from an element") + + return err + } + + m.logger.Trace().Msg("removed focus from an element") + + return nil } func (m *Manager) BlurBySelector(ctx context.Context, parentObjectID runtime.RemoteObjectID, selector string) error { - return m.exec.EvalWithArguments(ctx, templates.BlurBySelector(selector), runtime.CallArgument{ - ObjectID: &parentObjectID, - }) + m.logger.Trace(). + Str("parent_object_id", string(parentObjectID)). + Str("selector", selector). + Msg("removing focus from an element by selector") + + if err := m.exec.Eval(ctx, templates.BlurBySelector(selector), eval.WithArgRef(parentObjectID)); err != nil { + m.logger.Trace(). + Err(err). + Msg("failed removing focus from an element by selector") + + return err + } + + m.logger.Trace().Msg("removed focus from an element by selector") + + return nil } func (m *Manager) MoveMouse(ctx context.Context, objectID runtime.RemoteObjectID) error { + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("starting to move the mouse towards an element") + if err := m.ScrollIntoView(ctx, objectID, drivers.ScrollOptions{}); err != nil { + m.logger.Trace().Err(err).Msg("could not scroll into the object. failed to move the mouse") + return err } + m.logger.Trace().Msg("calculating clickable element points") + q, err := GetClickablePointByObjectID(ctx, m.client, objectID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } - return m.mouse.Move(ctx, q.X, q.Y) + m.logger.Trace().Float64("x", q.X).Float64("y", q.Y).Msg("calculated clickable element points") + + if err := m.mouse.Move(ctx, q.X, q.Y); err != nil { + m.logger.Trace().Err(err).Msg("failed to move the mouse") + + return err + } + + m.logger.Trace().Msg("moved the mouse") + + return nil } func (m *Manager) MoveMouseBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Msg("starting to move the mouse towards an element by selector") + if err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{}); err != nil { return err } + m.logger.Trace().Msg("looking up for an element by selector") + found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to find an element by selector") + return err } - q, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) + m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("calculating clickable element points") + + points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } - return m.mouse.Move(ctx, q.X, q.Y) + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + + if err := m.mouse.Move(ctx, points.X, points.Y); err != nil { + m.logger.Trace().Err(err).Msg("failed to move the mouse") + + return err + } + + m.logger.Trace().Msg("moved the mouse") + + return nil } func (m *Manager) MoveMouseByXY(ctx context.Context, x, y float64) error { + m.logger.Trace(). + Float64("x", x). + Float64("y", y). + Msg("starting to move the mouse towards an element by given coordinates") + if err := m.ScrollByXY(ctx, x, y, drivers.ScrollOptions{}); err != nil { return err } - return m.mouse.Move(ctx, x, y) + if err := m.mouse.Move(ctx, x, y); err != nil { + m.logger.Trace().Err(err).Msg("failed to move the mouse towards an element by given coordinates") + + return err + } + + m.logger.Trace().Msg("moved the mouse") + + return nil } func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, count int) error { + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("starting to click on an element") + if err := m.ScrollIntoView(ctx, objectID, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -178,22 +375,42 @@ func (m *Manager) Click(ctx context.Context, objectID runtime.RemoteObjectID, co return err } + m.logger.Trace().Msg("calculating clickable element points") + points, err := GetClickablePointByObjectID(ctx, m.client, objectID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { - return nil + m.logger.Trace(). + Err(err). + Msg("failed to click on an element") + + return err } + m.logger.Trace(). + Err(err). + Msg("clicked on an element") + return nil } func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Int("count", count). + Msg("starting to click on an element by selector") + if err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -202,28 +419,47 @@ func (m *Manager) ClickBySelector(ctx context.Context, parentNodeID dom.NodeID, return err } + m.logger.Trace().Msg("looking up for an element by selector") + found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to find an element by selector") + return err } + m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("calculating clickable element points") + points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { + m.logger.Trace().Err(err).Msg("failed to click on an element") return nil } + m.logger.Trace().Msg("clicked on an element") + return nil } func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeID, selector string, count int) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Int("count", count). + Msg("starting to click on elements by selector") + if err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -232,34 +468,56 @@ func (m *Manager) ClickBySelectorAll(ctx context.Context, parentNodeID dom.NodeI return err } + m.logger.Trace().Msg("looking up for elements by selector") + found, err := m.client.DOM.QuerySelectorAll(ctx, dom.NewQuerySelectorAllArgs(parentNodeID, selector)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to find elements by selector") + return err } - for _, nodeID := range found.NodeIDs { - beforeTypeDelay := time.Duration(core.NumberLowerBoundary(drivers.DefaultMouseDelay*10)) * time.Millisecond + for idx, nodeID := range found.NodeIDs { + if idx > 0 { + m.logger.Trace().Msg("pausing") + beforeClickDelay := time.Duration(core.NumberLowerBoundary(drivers.DefaultMouseDelay*10)) * time.Millisecond - time.Sleep(beforeTypeDelay) + time.Sleep(beforeClickDelay) + } + + m.logger.Trace().Int("node_id", int(nodeID)).Msg("calculating clickable element points") points, err := GetClickablePointByNodeID(ctx, m.client, nodeID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond if err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, count); err != nil { + m.logger.Trace().Err(err).Msg("failed to click on an element") return nil } + + m.logger.Trace().Msg("clicked on an element") } + m.logger.Trace().Msg("clicked on all elements") + return nil } func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, params TypeParams) error { + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("starting to type text") + err := m.ScrollIntoView(ctx, objectID, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -270,19 +528,29 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, par return err } - err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)) + m.logger.Trace().Msg("focusing on an element") + + if err := m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)); err != nil { + m.logger.Trace().Msg("failed to focus on an element") - if err != nil { return err } + m.logger.Trace().Bool("clear", params.Clear).Msg("is clearing text required?") + if params.Clear { + m.logger.Trace().Msg("calculating clickable element points") + points, err := GetClickablePointByObjectID(ctx, m.client, objectID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + if err := m.ClearByXY(ctx, points); err != nil { return err } @@ -291,12 +559,29 @@ func (m *Manager) Type(ctx context.Context, objectID runtime.RemoteObjectID, par d := core.NumberLowerBoundary(float64(params.Delay)) beforeTypeDelay := time.Duration(d) + m.logger.Trace().Float64("delay", d).Msg("calculated pause delay") + time.Sleep(beforeTypeDelay) - return m.keyboard.Type(ctx, params.Text, params.Delay) + m.logger.Trace().Msg("starting to type text") + + if err := m.keyboard.Type(ctx, params.Text, params.Delay); err != nil { + m.logger.Trace().Err(err).Msg("failed to type text") + + return err + } + + m.logger.Trace().Msg("typed text") + + return nil } func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, params TypeParams) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Msg("starting to type text by selector") + err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -307,25 +592,41 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s return err } + m.logger.Trace().Msg("looking up for an element by selector") + found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to find an element by selector") + return err } + m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("focusing on an element") + err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to focus on an element") + return err } + m.logger.Trace().Bool("clear", params.Clear).Msg("is clearing text required?") + if params.Clear { + m.logger.Trace().Msg("calculating clickable element points") + points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + if err := m.ClearByXY(ctx, points); err != nil { return err } @@ -334,12 +635,28 @@ func (m *Manager) TypeBySelector(ctx context.Context, parentNodeID dom.NodeID, s d := core.NumberLowerBoundary(float64(params.Delay)) beforeTypeDelay := time.Duration(d) + m.logger.Trace().Float64("delay", d).Msg("calculated pause delay") + time.Sleep(beforeTypeDelay) - return m.keyboard.Type(ctx, params.Text, params.Delay) + m.logger.Trace().Msg("starting to type text") + + if err := m.keyboard.Type(ctx, params.Text, params.Delay); err != nil { + m.logger.Trace().Err(err).Msg("failed to type text") + + return err + } + + m.logger.Trace().Msg("typed text") + + return nil } func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) error { + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("starting to clear element") + err := m.ScrollIntoView(ctx, objectID, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -350,22 +667,46 @@ func (m *Manager) Clear(ctx context.Context, objectID runtime.RemoteObjectID) er return err } + m.logger.Trace().Msg("calculating clickable element points") + points, err := GetClickablePointByObjectID(ctx, m.client, objectID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + m.logger.Trace().Msg("focusing on an element") + err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetObjectID(objectID)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to focus on an element") + return err } - return m.ClearByXY(ctx, points) + m.logger.Trace().Msg("clearing element") + + if err := m.ClearByXY(ctx, points); err != nil { + m.logger.Trace().Err(err).Msg("failed to clear element") + + return err + } + + m.logger.Trace().Msg("cleared element") + + return nil } func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Msg("starting to clear element by selector") + err := m.ScrollIntoViewBySelector(ctx, selector, drivers.ScrollOptions{ Behavior: drivers.ScrollBehaviorAuto, Block: drivers.ScrollVerticalAlignmentCenter, @@ -376,43 +717,108 @@ func (m *Manager) ClearBySelector(ctx context.Context, parentNodeID dom.NodeID, return err } + m.logger.Trace().Msg("looking up for an element by selector") + found, err := m.client.DOM.QuerySelector(ctx, dom.NewQuerySelectorArgs(parentNodeID, selector)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to find an element by selector") + return err } + m.logger.Trace().Int("node_id", int(found.NodeID)).Msg("calculating clickable element points") + points, err := GetClickablePointByNodeID(ctx, m.client, found.NodeID) if err != nil { + m.logger.Trace().Err(err).Msg("failed calculating clickable element points") + return err } + m.logger.Trace().Float64("x", points.X).Float64("y", points.Y).Msg("calculated clickable element points") + + m.logger.Trace().Msg("focusing on an element") + err = m.client.DOM.Focus(ctx, dom.NewFocusArgs().SetNodeID(found.NodeID)) if err != nil { + m.logger.Trace().Err(err).Msg("failed to focus on an element") + return err } - return m.ClearByXY(ctx, points) + m.logger.Trace().Msg("clearing element") + + if err := m.ClearByXY(ctx, points); err != nil { + m.logger.Trace().Err(err).Msg("failed to clear element") + + return err + } + + m.logger.Trace().Msg("cleared element") + + return nil } func (m *Manager) ClearByXY(ctx context.Context, points Quad) error { + m.logger.Trace(). + Float64("x", points.X). + Float64("y", points.Y). + Msg("starting to clear element by coordinates") + delay := time.Duration(drivers.DefaultMouseDelay) * time.Millisecond + + m.logger.Trace().Dur("delay", delay).Msg("clicking mouse button to select text") + err := m.mouse.ClickWithCount(ctx, points.X, points.Y, delay, 3) if err != nil { + m.logger.Trace().Err(err).Msg("failed to click mouse button") + return err } - return m.keyboard.Press(ctx, []string{"Backspace"}, 1, time.Duration(drivers.DefaultKeyboardDelay)*time.Millisecond) + delay = time.Duration(drivers.DefaultKeyboardDelay) * time.Millisecond + + m.logger.Trace().Dur("delay", delay).Msg("pressing 'Backspace'") + + if err := m.keyboard.Press(ctx, []string{"Backspace"}, 1, delay); err != nil { + m.logger.Trace().Err(err).Msg("failed to press 'Backspace'") + + return err + } + + return err } func (m *Manager) Press(ctx context.Context, keys []string, count int) error { - return m.keyboard.Press(ctx, keys, count, time.Duration(drivers.DefaultKeyboardDelay)*time.Millisecond) + delay := time.Duration(drivers.DefaultKeyboardDelay) * time.Millisecond + + m.logger.Trace(). + Strs("keys", keys). + Int("count", count). + Dur("delay", delay). + Msg("pressing keyboard keys") + + if err := m.keyboard.Press(ctx, keys, count, delay); err != nil { + m.logger.Trace().Err(err).Msg("failed to press keyboard keys") + + return err + } + + return nil } func (m *Manager) PressBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, keys []string, count int) error { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Strs("keys", keys). + Int("count", count). + Msg("starting to press keyboard keys by selector") + if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil { return err } @@ -421,43 +827,76 @@ func (m *Manager) PressBySelector(ctx context.Context, parentNodeID dom.NodeID, } func (m *Manager) Select(ctx context.Context, objectID runtime.RemoteObjectID, value *values.Array) (*values.Array, error) { + m.logger.Trace(). + Str("object_id", string(objectID)). + Msg("starting to select values") + if err := m.Focus(ctx, objectID); err != nil { return values.NewArray(0), err } - val, err := m.exec.EvalWithArgumentsAndReturnValue(ctx, templates.Select(value.String()), runtime.CallArgument{ - ObjectID: &objectID, - }) + m.logger.Trace().Msg("selecting values") + m.logger.Trace().Msg("evaluating a JS function") + + val, err := m.exec.EvalValue( + ctx, + templates.Select(value.String()), + eval.WithArgRef(objectID), + ) if err != nil { + m.logger.Trace().Err(err).Msg("failed to evaluate a JS function") + return nil, err } + m.logger.Trace().Msg("validating JS result") + arr, ok := val.(*values.Array) if !ok { + m.logger.Trace().Err(err).Msg("JS result validation failed") + return values.NewArray(0), core.ErrUnexpected } + m.logger.Trace().Msg("selected values") + return arr, nil } func (m *Manager) SelectBySelector(ctx context.Context, parentNodeID dom.NodeID, selector string, value *values.Array) (*values.Array, error) { + m.logger.Trace(). + Int("parent_node_id", int(parentNodeID)). + Str("selector", selector). + Msg("starting to select values by selector") + if err := m.FocusBySelector(ctx, parentNodeID, selector); err != nil { return values.NewArray(0), err } - res, err := m.exec.EvalWithReturnValue(ctx, templates.SelectBySelector(selector, value.String())) + m.logger.Trace().Msg("selecting values") + m.logger.Trace().Msg("evaluating a JS function") + + res, err := m.exec.EvalValue(ctx, templates.SelectBySelector(selector, value.String())) if err != nil { + m.logger.Trace().Err(err).Msg("failed to evaluate a JS function") + return values.NewArray(0), err } + m.logger.Trace().Msg("validating JS result") + arr, ok := res.(*values.Array) if !ok { + m.logger.Trace().Err(err).Msg("JS result validation failed") + return values.NewArray(0), core.ErrUnexpected } + m.logger.Trace().Msg("selected values") + return arr, nil } diff --git a/pkg/drivers/cdp/input/mouse.go b/pkg/drivers/cdp/input/mouse.go index e52afee2..c3558582 100644 --- a/pkg/drivers/cdp/input/mouse.go +++ b/pkg/drivers/cdp/input/mouse.go @@ -27,37 +27,37 @@ func (m *Mouse) ClickWithCount(ctx context.Context, x, y float64, delay time.Dur return err } - if err := m.DownWithCount(ctx, "left", count); err != nil { + if err := m.DownWithCount(ctx, input.MouseButtonLeft, count); err != nil { return err } time.Sleep(randomDuration(int(delay))) - return m.UpWithCount(ctx, "left", count) + return m.UpWithCount(ctx, input.MouseButtonLeft, count) } -func (m *Mouse) Down(ctx context.Context, button string) error { +func (m *Mouse) Down(ctx context.Context, button input.MouseButton) error { return m.DownWithCount(ctx, button, 1) } -func (m *Mouse) DownWithCount(ctx context.Context, button string, count int) error { +func (m *Mouse) DownWithCount(ctx context.Context, button input.MouseButton, count int) error { return m.client.Input.DispatchMouseEvent( ctx, input.NewDispatchMouseEventArgs("mousePressed", m.x, m.y). - SetButton(input.MouseButton(button)). + SetButton(button). SetClickCount(count), ) } -func (m *Mouse) Up(ctx context.Context, button string) error { +func (m *Mouse) Up(ctx context.Context, button input.MouseButton) error { return m.UpWithCount(ctx, button, 1) } -func (m *Mouse) UpWithCount(ctx context.Context, button string, count int) error { +func (m *Mouse) UpWithCount(ctx context.Context, button input.MouseButton, count int) error { return m.client.Input.DispatchMouseEvent( ctx, input.NewDispatchMouseEventArgs("mouseReleased", m.x, m.y). - SetButton(input.MouseButton(button)). + SetButton(button). SetClickCount(count), ) } diff --git a/pkg/drivers/cdp/input/quad.go b/pkg/drivers/cdp/input/quad.go index 16e18872..7f4fe17b 100644 --- a/pkg/drivers/cdp/input/quad.go +++ b/pkg/drivers/cdp/input/quad.go @@ -8,6 +8,8 @@ import ( "github.com/mafredri/cdp/protocol/dom" "github.com/mafredri/cdp/protocol/runtime" "github.com/pkg/errors" + + "github.com/MontFerret/ferret/pkg/drivers/cdp/utils" ) type Quad struct { @@ -78,9 +80,7 @@ func getClickablePoint(ctx context.Context, client *cdp.Client, qargs *dom.GetCo return Quad{}, err } - clientWidth := layoutMetricsReply.CSSLayoutViewport.ClientWidth - clientHeight := layoutMetricsReply.CSSLayoutViewport.ClientHeight - + clientWidth, clientHeight := utils.GetLayoutViewportWH(layoutMetricsReply) quads := make([][]Quad, 0, len(contentQuadsReply.Quads)) for _, q := range contentQuadsReply.Quads { @@ -111,6 +111,39 @@ func getClickablePoint(ctx context.Context, client *cdp.Client, qargs *dom.GetCo }, nil } +func getClickablePoint2(ctx context.Context, client *cdp.Client, qargs *dom.GetContentQuadsArgs) (Quad, error) { + contentQuadsReply, err := client.DOM.GetContentQuads(ctx, qargs) + + if err != nil { + return Quad{}, err + } + + if contentQuadsReply.Quads == nil || len(contentQuadsReply.Quads) == 0 { + return Quad{}, errors.New("node is either not visible or not an HTMLElement") + } + + content := contentQuadsReply.Quads[0] + + c := len(content) + + if c%2 != 0 || c < 1 { + return Quad{}, errors.New("node is either not visible or not an HTMLElement") + } + + var x, y float64 + for i := 0; i < c; i += 2 { + x += content[i] + y += content[i+1] + } + x /= float64(c / 2) + y /= float64(c / 2) + + return Quad{ + X: x, + Y: y, + }, nil +} + func GetClickablePointByNodeID(ctx context.Context, client *cdp.Client, nodeID dom.NodeID) (Quad, error) { return getClickablePoint(ctx, client, dom.NewGetContentQuadsArgs().SetNodeID(nodeID)) } diff --git a/pkg/drivers/cdp/network/manager.go b/pkg/drivers/cdp/network/manager.go index fbec6e5a..c19bee51 100644 --- a/pkg/drivers/cdp/network/manager.go +++ b/pkg/drivers/cdp/network/manager.go @@ -3,7 +3,7 @@ package network import ( "context" "encoding/json" - "io" + "github.com/MontFerret/ferret/pkg/runtime/logging" "regexp" "sync" @@ -20,7 +20,6 @@ import ( "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" "github.com/MontFerret/ferret/pkg/drivers/cdp/events" "github.com/MontFerret/ferret/pkg/drivers/cdp/templates" - "github.com/MontFerret/ferret/pkg/drivers/common" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" ) @@ -31,11 +30,12 @@ type ( FrameLoadedListener = func(ctx context.Context, frame page.Frame) Manager struct { - mu sync.Mutex - logger *zerolog.Logger + mu sync.RWMutex + logger zerolog.Logger client *cdp.Client headers *drivers.HTTPHeaders - eventLoop *events.Loop + foregroundLoop *events.Loop + backgroundLoop *events.Loop cancel context.CancelFunc responseListenerID events.ListenerID filterListenerID events.ListenerID @@ -44,42 +44,37 @@ type ( ) func New( - ctx context.Context, - logger *zerolog.Logger, + logger zerolog.Logger, client *cdp.Client, options Options, ) (*Manager, error) { - ctx, cancel := context.WithCancel(ctx) + ctx, cancel := context.WithCancel(context.Background()) m := new(Manager) - m.logger = logger + m.logger = logging.WithName(logger.With(), "network_manager").Logger() m.client = client m.headers = drivers.NewHTTPHeaders() - m.eventLoop = events.NewLoop() + m.foregroundLoop = events.NewLoop() m.cancel = cancel m.response = new(sync.Map) - if options.Cookies != nil && len(options.Cookies) > 0 { - for url, cookies := range options.Cookies { - if err := m.setCookiesInternal(ctx, url, cookies); err != nil { - return nil, err - } - } - } - - if options.Headers != nil && options.Headers.Length() > 0 { - if err := m.setHeadersInternal(ctx, options.Headers); err != nil { - return nil, err - } - } - var err error - closers := make([]io.Closer, 0, 10) - defer func() { if err != nil { - common.CloseAll(logger, closers, "failed to close a DOM event stream") + cancel() + + if m.foregroundLoop != nil { + if err := m.foregroundLoop.Close(); err != nil { + m.logger.Trace().Err(err).Msg("failed to close the foreground loop during cleanup") + } + } + + if m.backgroundLoop != nil { + if err := m.backgroundLoop.Close(); err != nil { + m.logger.Trace().Err(err).Msg("failed to close the background loop during cleanup") + } + } } }() @@ -89,24 +84,24 @@ func New( return nil, err } + m.foregroundLoop.AddSource(events.NewSource(eventFrameLoad, frameNavigatedStream, func(stream rpcc.Stream) (interface{}, error) { + return stream.(page.FrameNavigatedClient).Recv() + })) + responseReceivedStream, err := m.client.Network.ResponseReceived(ctx) if err != nil { return nil, err } - m.eventLoop.AddSource(events.NewSource(eventFrameLoad, frameNavigatedStream, func(stream rpcc.Stream) (interface{}, error) { - return stream.(page.FrameNavigatedClient).Recv() - })) - - m.eventLoop.AddSource(events.NewSource(responseReceived, responseReceivedStream, func(stream rpcc.Stream) (interface{}, error) { + m.foregroundLoop.AddSource(events.NewSource(responseReceived, responseReceivedStream, func(stream rpcc.Stream) (interface{}, error) { return stream.(network.ResponseReceivedClient).Recv() })) - m.responseListenerID = m.eventLoop.AddListener(responseReceived, m.onResponse) + m.responseListenerID = m.foregroundLoop.AddListener(responseReceived, m.onResponse) if options.Filter != nil && len(options.Filter.Patterns) > 0 { - el2 := events.NewLoop() + m.backgroundLoop = events.NewLoop() err = m.client.Fetch.Enable(ctx, toFetchArgs(options.Filter.Patterns)) @@ -120,18 +115,46 @@ func New( return nil, err } - el2.AddSource(events.NewSource(requestPaused, requestPausedStream, func(stream rpcc.Stream) (interface{}, error) { + m.backgroundLoop.AddSource(events.NewSource(requestPaused, requestPausedStream, func(stream rpcc.Stream) (interface{}, error) { return stream.(fetch.RequestPausedClient).Recv() })) - m.filterListenerID = el2.AddListener(requestPaused, m.onRequestPaused) - - // run in a separate loop in order to get higher priority - // TODO: Consider adding support of event priorities to EventLoop - el2.Run(ctx) + m.filterListenerID = m.backgroundLoop.AddListener(requestPaused, m.onRequestPaused) } - m.eventLoop.Run(ctx) + if options.Cookies != nil && len(options.Cookies) > 0 { + for url, cookies := range options.Cookies { + err = m.setCookiesInternal(ctx, url, cookies) + + if err != nil { + return nil, err + } + } + } + + if options.Headers != nil && options.Headers.Length() > 0 { + err = m.setHeadersInternal(ctx, options.Headers) + + if err != nil { + return nil, err + } + } + + err = m.foregroundLoop.Run(ctx) + + if err != nil { + return nil, err + } + + if m.backgroundLoop != nil { + // run in a separate loop in order to get higher priority + // TODO: Consider adding support of event priorities to EventLoop + err = m.backgroundLoop.Run(ctx) + + if err != nil { + return nil, err + } + } return m, nil } @@ -140,24 +163,38 @@ func (m *Manager) Close() error { m.mu.Lock() defer m.mu.Unlock() + m.logger.Trace().Msg("closing") + if m.cancel != nil { m.cancel() m.cancel = nil } + _ = m.foregroundLoop.Close() + + if m.backgroundLoop != nil { + _ = m.backgroundLoop.Close() + } + return nil } func (m *Manager) GetCookies(ctx context.Context) (*drivers.HTTPCookies, error) { + m.logger.Trace().Msg("starting to get cookies") + repl, err := m.client.Network.GetAllCookies(ctx) if err != nil { + m.logger.Trace().Err(err).Msg("failed to get cookies") + return nil, errors.Wrap(err, "failed to get cookies") } cookies := drivers.NewHTTPCookies() if repl.Cookies == nil { + m.logger.Trace().Msg("no cookies found") + return cookies, nil } @@ -165,6 +202,8 @@ func (m *Manager) GetCookies(ctx context.Context) (*drivers.HTTPCookies, error) cookies.Set(toDriverCookie(c)) } + m.logger.Trace().Err(err).Msg("succeeded to get cookies") + return cookies, nil } @@ -176,11 +215,17 @@ func (m *Manager) SetCookies(ctx context.Context, url string, cookies *drivers.H } func (m *Manager) setCookiesInternal(ctx context.Context, url string, cookies *drivers.HTTPCookies) error { + m.logger.Trace().Str("url", url).Msg("starting to set cookies") + if cookies == nil { + m.logger.Trace().Msg("nil cookies passed") + return errors.Wrap(core.ErrMissedArgument, "cookies") } if cookies.Length() == 0 { + m.logger.Trace().Msg("no cookies passed") + return nil } @@ -192,30 +237,52 @@ func (m *Manager) setCookiesInternal(ctx context.Context, url string, cookies *d return true }) - return m.client.Network.SetCookies(ctx, network.NewSetCookiesArgs(params)) + err := m.client.Network.SetCookies(ctx, network.NewSetCookiesArgs(params)) + + if err != nil { + m.logger.Trace().Err(err).Msg("failed to set cookies") + + return err + } + + m.logger.Trace().Msg("succeeded to set cookies") + + return nil } func (m *Manager) DeleteCookies(ctx context.Context, url string, cookies *drivers.HTTPCookies) error { m.mu.Lock() defer m.mu.Unlock() + m.logger.Trace().Str("url", url).Msg("starting to delete cookies") + if cookies == nil { + m.logger.Trace().Msg("nil cookies passed") + return errors.Wrap(core.ErrMissedArgument, "cookies") } if cookies.Length() == 0 { + m.logger.Trace().Msg("no cookies passed") + return nil } var err error cookies.ForEach(func(value drivers.HTTPCookie, _ values.String) bool { + m.logger.Trace().Str("name", value.Name).Msg("deleting a cookie") + err = m.client.Network.DeleteCookies(ctx, fromDriverCookieDelete(url, value)) if err != nil { + m.logger.Trace().Err(err).Str("name", value.Name).Msg("failed to delete a cookie") + return false } + m.logger.Trace().Str("name", value.Name).Msg("succeeded to delete a cookie") + return true }) @@ -241,33 +308,52 @@ func (m *Manager) SetHeaders(ctx context.Context, headers *drivers.HTTPHeaders) } func (m *Manager) setHeadersInternal(ctx context.Context, headers *drivers.HTTPHeaders) error { + m.logger.Trace().Msg("starting to set headers") + if headers.Length() == 0 { + m.logger.Trace().Msg("no headers passed") + return nil } m.headers = headers + m.logger.Trace().Msg("marshaling headers") + j, err := jettison.MarshalOpts(headers, jettison.NoHTMLEscaping()) if err != nil { + m.logger.Trace().Err(err).Msg("failed to marshal headers") + return errors.Wrap(err, "failed to marshal headers") } + m.logger.Trace().Msg("sending headers to browser") + err = m.client.Network.SetExtraHTTPHeaders( ctx, network.NewSetExtraHTTPHeadersArgs(j), ) if err != nil { + m.logger.Trace().Err(err).Msg("failed to set headers") + return errors.Wrap(err, "failed to set headers") } + m.logger.Trace().Msg("succeeded to set headers") + return nil } func (m *Manager) GetResponse(_ context.Context, frameID page.FrameID) (drivers.HTTPResponse, error) { value, found := m.response.Load(frameID) + m.logger.Trace(). + Str("frame_id", string(frameID)). + Bool("found", found). + Msg("getting frame response") + if !found { return drivers.HTTPResponse{}, core.ErrNotFound } @@ -284,16 +370,21 @@ func (m *Manager) Navigate(ctx context.Context, url values.String) error { } urlStr := url.String() + m.logger.Trace().Str("url", urlStr).Msg("starting navigation") repl, err := m.client.Page.Navigate(ctx, page.NewNavigateArgs(urlStr)) + if err == nil && repl.ErrorText != nil { + err = errors.New(*repl.ErrorText) + } + if err != nil { + m.logger.Trace().Err(err).Msg("failed starting navigation") + return err } - if repl.ErrorText != nil { - return errors.New(*repl.ErrorText) - } + m.logger.Trace().Msg("succeeded starting navigation") return m.WaitForNavigation(ctx, nil) } @@ -302,9 +393,17 @@ func (m *Manager) NavigateForward(ctx context.Context, skip values.Int) (values. m.mu.Lock() defer m.mu.Unlock() + m.logger.Trace(). + Int64("skip", int64(skip)). + Msg("starting forward navigation") + history, err := m.client.Page.GetNavigationHistory(ctx) if err != nil { + m.logger.Trace(). + Err(err). + Msg("failed to get navigation history") + return values.False, err } @@ -313,6 +412,12 @@ func (m *Manager) NavigateForward(ctx context.Context, skip values.Int) (values. // nowhere to go forward if history.CurrentIndex == lastIndex { + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_last_index", lastIndex). + Msg("no forward history. nowhere to navigate. done.") + return values.False, nil } @@ -323,23 +428,52 @@ func (m *Manager) NavigateForward(ctx context.Context, skip values.Int) (values. to := int(skip) + history.CurrentIndex if to > lastIndex { - // TODO: Return error? - return values.False, nil + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_last_index", lastIndex). + Int("history_target_index", to). + Msg("not enough history items. using the edge index") + + to = lastIndex } entry := history.Entries[to] err = m.client.Page.NavigateToHistoryEntry(ctx, page.NewNavigateToHistoryEntryArgs(entry.ID)) if err != nil { + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_last_index", lastIndex). + Int("history_target_index", to). + Err(err). + Msg("failed to get navigation history entry") + return values.False, err } err = m.WaitForNavigation(ctx, nil) if err != nil { + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_last_index", lastIndex). + Int("history_target_index", to). + Err(err). + Msg("failed to wait for navigation completion") + return values.False, err } + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_last_index", lastIndex). + Int("history_target_index", to). + Msg("succeeded to wait for navigation completion") + return values.True, nil } @@ -347,14 +481,27 @@ func (m *Manager) NavigateBack(ctx context.Context, skip values.Int) (values.Boo m.mu.Lock() defer m.mu.Unlock() + m.logger.Trace(). + Int64("skip", int64(skip)). + Msg("starting backward navigation") + history, err := m.client.Page.GetNavigationHistory(ctx) if err != nil { + m.logger.Trace().Err(err).Msg("failed to get navigation history") + return values.False, err } + length := len(history.Entries) + // we are in the beginning if history.CurrentIndex == 0 { + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Msg("no backward history. nowhere to navigate. done.") + return values.False, nil } @@ -365,23 +512,48 @@ func (m *Manager) NavigateBack(ctx context.Context, skip values.Int) (values.Boo to := history.CurrentIndex - int(skip) if to < 0 { - // TODO: Return error? - return values.False, nil + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_target_index", to). + Msg("not enough history items. using 0 index") + + to = 0 } entry := history.Entries[to] err = m.client.Page.NavigateToHistoryEntry(ctx, page.NewNavigateToHistoryEntryArgs(entry.ID)) if err != nil { + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_target_index", to). + Err(err). + Msg("failed to get navigation history entry") + return values.False, err } err = m.WaitForNavigation(ctx, nil) if err != nil { + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_target_index", to). + Err(err). + Msg("failed to wait for navigation completion") + return values.False, err } + m.logger.Trace(). + Int("history_entries", length). + Int("history_current_index", history.CurrentIndex). + Int("history_target_index", to). + Msg("succeeded to wait for navigation completion") + return values.True, nil } @@ -392,8 +564,27 @@ func (m *Manager) WaitForNavigation(ctx context.Context, pattern *regexp.Regexp) func (m *Manager) WaitForFrameNavigation(ctx context.Context, frameID page.FrameID, urlPattern *regexp.Regexp) error { onEvent := make(chan struct{}) - m.eventLoop.AddListener(eventFrameLoad, func(_ context.Context, message interface{}) bool { + var urlPatternStr string + + if urlPattern != nil { + urlPatternStr = urlPattern.String() + } + + m.logger.Trace(). + Str("fame_id", string(frameID)). + Str("url_pattern", urlPatternStr). + Msg("starting to wait for frame navigation event") + + m.foregroundLoop.AddListener(eventFrameLoad, func(_ context.Context, message interface{}) bool { repl := message.(*page.FrameNavigatedReply) + log := m.logger.With(). + Str("fame_id", string(frameID)). + Str("event_fame_id", string(repl.Frame.ID)). + Str("event_fame_url", repl.Frame.URL). + Str("url_pattern", urlPatternStr). + Logger() + + log.Trace().Msg("received framed navigation event") var matched bool @@ -409,14 +600,23 @@ func (m *Manager) WaitForFrameNavigation(ctx context.Context, frameID page.Frame } if matched { + log.Trace().Msg("frame navigation url is matched with url pattern") + if ctx.Err() == nil { - ec, err := eval.NewExecutionContextFrom(ctx, m.client, repl.Frame) + log.Trace().Msg("creating frame execution context") + + ec, err := eval.New(ctx, m.client, repl.Frame.ID) if err != nil { + log.Trace().Err(err).Msg("failed to create frame execution context") + close(onEvent) + return false } + log.Trace().Err(err).Msg("starting polling DOM ready event") + _, err = events.NewEvalWaitTask( ec, templates.DOMReady(), @@ -424,10 +624,15 @@ func (m *Manager) WaitForFrameNavigation(ctx context.Context, frameID page.Frame ).Run(ctx) if err != nil { + log.Trace().Err(err).Msg("failed to poll DOM ready event") + close(onEvent) + return false } + log.Trace().Msg("DOM is ready") + onEvent <- struct{}{} close(onEvent) } @@ -439,14 +644,25 @@ func (m *Manager) WaitForFrameNavigation(ctx context.Context, frameID page.Frame select { case <-onEvent: + m.logger.Trace(). + Str("fame_id", string(frameID)). + Str("url_pattern", urlPatternStr). + Msg("navigation has completed") + return nil case <-ctx.Done(): + m.logger.Trace(). + Err(core.ErrTimeout). + Str("fame_id", string(frameID)). + Str("url_pattern", urlPatternStr). + Msg("navigation has failed") + return core.ErrTimeout } } func (m *Manager) AddFrameLoadedListener(listener FrameLoadedListener) events.ListenerID { - return m.eventLoop.AddListener(eventFrameLoad, func(ctx context.Context, message interface{}) bool { + return m.foregroundLoop.AddListener(eventFrameLoad, func(ctx context.Context, message interface{}) bool { repl := message.(*page.FrameNavigatedReply) listener(ctx, repl.Frame) @@ -456,7 +672,7 @@ func (m *Manager) AddFrameLoadedListener(listener FrameLoadedListener) events.Li } func (m *Manager) RemoveFrameLoadedListener(id events.ListenerID) { - m.eventLoop.RemoveListener(eventFrameLoad, id) + m.foregroundLoop.RemoveListener(eventFrameLoad, id) } func (m *Manager) onResponse(_ context.Context, message interface{}) (out bool) { @@ -472,10 +688,28 @@ func (m *Manager) onResponse(_ context.Context, message interface{}) (out bool) return } + if msg.FrameID == nil { + return + } + + log := m.logger.With(). + Str("frame_id", string(*msg.FrameID)). + Str("request_id", string(msg.RequestID)). + Str("loader_id", string(msg.LoaderID)). + Float64("timestamp", float64(msg.Timestamp)). + Str("url", msg.Response.URL). + Int("status_code", msg.Response.Status). + Str("status_text", msg.Response.StatusText). + Logger() + + log.Trace().Msg("received browser response") + response := drivers.HTTPResponse{ - StatusCode: msg.Response.Status, - Status: msg.Response.StatusText, - Headers: drivers.NewHTTPHeaders(), + URL: msg.Response.URL, + StatusCode: msg.Response.Status, + Status: msg.Response.StatusText, + Headers: drivers.NewHTTPHeaders(), + ResponseTime: float64(msg.Response.ResponseTime), } deserialized := make(map[string]string) @@ -484,7 +718,7 @@ func (m *Manager) onResponse(_ context.Context, message interface{}) (out bool) err := json.Unmarshal(msg.Response.Headers, &deserialized) if err != nil { - m.logger.Error().Err(err).Msg("failed to deserialize response headers") + log.Trace().Err(err).Msg("failed to deserialize response headers") } } @@ -494,6 +728,8 @@ func (m *Manager) onResponse(_ context.Context, message interface{}) (out bool) m.response.Store(*msg.FrameID, response) + log.Trace().Msg("updated frame response information") + return } @@ -505,18 +741,25 @@ func (m *Manager) onRequestPaused(ctx context.Context, message interface{}) (out return } + log := m.logger.With(). + Str("request_id", string(msg.RequestID)). + Str("frame_id", string(msg.FrameID)). + Str("resource_type", string(msg.ResourceType)). + Str("url", msg.Request.URL). + Logger() + + log.Trace().Msg("trying to block resource loading") + err := m.client.Fetch.FailRequest(ctx, &fetch.FailRequestArgs{ RequestID: msg.RequestID, ErrorReason: network.ErrorReasonBlockedByClient, }) if err != nil { - m.logger. - Err(err). - Str("resourceType", msg.ResourceType.String()). - Str("url", msg.Request.URL). - Msg("failed to terminate a request") + log.Trace().Err(err).Msg("failed to block resource loading") } + log.Trace().Msg("succeeded to block resource loading") + return } diff --git a/pkg/drivers/cdp/network/manager_test.go b/pkg/drivers/cdp/network/manager_test.go new file mode 100644 index 00000000..4637ccfa --- /dev/null +++ b/pkg/drivers/cdp/network/manager_test.go @@ -0,0 +1,295 @@ +package network_test + +import ( + "context" + "os" + "testing" + + "github.com/mafredri/cdp" + "github.com/mafredri/cdp/protocol/fetch" + network2 "github.com/mafredri/cdp/protocol/network" + "github.com/mafredri/cdp/protocol/page" + "github.com/pkg/errors" + "github.com/rs/zerolog" + . "github.com/smartystreets/goconvey/convey" + "github.com/stretchr/testify/mock" + + "github.com/MontFerret/ferret/pkg/drivers" + "github.com/MontFerret/ferret/pkg/drivers/cdp/network" +) + +type ( + PageAPI struct { + mock.Mock + cdp.Page + frameNavigated func(ctx context.Context) (page.FrameNavigatedClient, error) + } + + NetworkAPI struct { + mock.Mock + cdp.Network + responseReceived func(ctx context.Context) (network2.ResponseReceivedClient, error) + setExtraHTTPHeaders func(ctx context.Context, args *network2.SetExtraHTTPHeadersArgs) error + } + + FetchAPI struct { + mock.Mock + cdp.Fetch + enable func(context.Context, *fetch.EnableArgs) error + requestPaused func(context.Context) (fetch.RequestPausedClient, error) + } + + TestEventStream struct { + mock.Mock + ready chan struct{} + message chan interface{} + } + + FrameNavigatedClient struct { + *TestEventStream + } + + ResponseReceivedClient struct { + *TestEventStream + } + + RequestPausedClient struct { + *TestEventStream + } +) + +func (api *PageAPI) FrameNavigated(ctx context.Context) (page.FrameNavigatedClient, error) { + return api.frameNavigated(ctx) +} + +func (api *NetworkAPI) ResponseReceived(ctx context.Context) (network2.ResponseReceivedClient, error) { + return api.responseReceived(ctx) +} + +func (api *NetworkAPI) SetExtraHTTPHeaders(ctx context.Context, args *network2.SetExtraHTTPHeadersArgs) error { + return api.setExtraHTTPHeaders(ctx, args) +} + +func (api *FetchAPI) Enable(ctx context.Context, args *fetch.EnableArgs) error { + return api.enable(ctx, args) +} + +func (api *FetchAPI) RequestPaused(ctx context.Context) (fetch.RequestPausedClient, error) { + return api.requestPaused(ctx) +} + +func NewTestEventStream() *TestEventStream { + return NewBufferedTestEventStream(0) +} + +func NewBufferedTestEventStream(buffer int) *TestEventStream { + es := new(TestEventStream) + es.ready = make(chan struct{}, buffer) + es.message = make(chan interface{}, buffer) + return es +} + +func (stream *TestEventStream) Ready() <-chan struct{} { + return stream.ready +} + +func (stream *TestEventStream) RecvMsg(i interface{}) error { + return nil +} + +func (stream *TestEventStream) Message() interface{} { + return <-stream.message +} + +func (stream *TestEventStream) Close() error { + stream.Called() + close(stream.message) + close(stream.ready) + return nil +} + +func (stream *TestEventStream) Emit(msg interface{}) { + stream.ready <- struct{}{} + stream.message <- msg +} + +func NewFrameNavigatedClient() *FrameNavigatedClient { + return &FrameNavigatedClient{ + TestEventStream: NewTestEventStream(), + } +} + +func (stream *FrameNavigatedClient) Recv() (*page.FrameNavigatedReply, error) { + <-stream.Ready() + msg := stream.Message() + + repl, ok := msg.(*page.FrameNavigatedReply) + + if !ok { + panic("Invalid message type") + } + + return repl, nil +} + +func NewResponseReceivedClient() *ResponseReceivedClient { + return &ResponseReceivedClient{ + TestEventStream: NewTestEventStream(), + } +} + +func (stream *ResponseReceivedClient) Recv() (*network2.ResponseReceivedReply, error) { + <-stream.Ready() + msg := stream.Message() + + repl, ok := msg.(*network2.ResponseReceivedReply) + + if !ok { + panic("Invalid message type") + } + + return repl, nil +} + +func NewRequestPausedClient() *RequestPausedClient { + return &RequestPausedClient{ + TestEventStream: NewTestEventStream(), + } +} + +func (stream *RequestPausedClient) Recv() (*fetch.RequestPausedReply, error) { + <-stream.Ready() + msg := stream.Message() + + repl, ok := msg.(*fetch.RequestPausedReply) + + if !ok { + panic("Invalid message type") + } + + return repl, nil +} + +func TestManager(t *testing.T) { + Convey("Network manager", t, func() { + + Convey("New", func() { + Convey("Should close all resources on error", func() { + frameNavigatedClient := NewFrameNavigatedClient() + frameNavigatedClient.On("Close", mock.Anything).Once().Return(nil) + + pageAPI := new(PageAPI) + pageAPI.frameNavigated = func(ctx context.Context) (page.FrameNavigatedClient, error) { + return frameNavigatedClient, nil + } + + responseReceivedClient := NewResponseReceivedClient() + responseReceivedClient.On("Close", mock.Anything).Once().Return(nil) + setExtraHTTPHeadersErr := errors.New("test error") + networkAPI := new(NetworkAPI) + networkAPI.responseReceived = func(ctx context.Context) (network2.ResponseReceivedClient, error) { + return responseReceivedClient, nil + } + networkAPI.setExtraHTTPHeaders = func(ctx context.Context, args *network2.SetExtraHTTPHeadersArgs) error { + return setExtraHTTPHeadersErr + } + + requestPausedClient := NewRequestPausedClient() + requestPausedClient.On("Close", mock.Anything).Once().Return(nil) + fetchAPI := new(FetchAPI) + fetchAPI.enable = func(ctx context.Context, args *fetch.EnableArgs) error { + return nil + } + fetchAPI.requestPaused = func(ctx context.Context) (fetch.RequestPausedClient, error) { + return requestPausedClient, nil + } + + client := &cdp.Client{ + Page: pageAPI, + Network: networkAPI, + Fetch: fetchAPI, + } + + _, err := network.New( + zerolog.New(os.Stdout).Level(zerolog.Disabled), + client, + network.Options{ + Headers: drivers.NewHTTPHeadersWith(map[string][]string{"x-correlation-id": {"foo"}}), + Filter: &network.Filter{ + Patterns: []drivers.ResourceFilter{ + { + URL: "http://google.com", + Type: "img", + }, + }, + }, + }, + ) + + So(err, ShouldNotBeNil) + frameNavigatedClient.AssertExpectations(t) + responseReceivedClient.AssertExpectations(t) + requestPausedClient.AssertExpectations(t) + }) + + Convey("Should close all resources on Close", func() { + frameNavigatedClient := NewFrameNavigatedClient() + frameNavigatedClient.On("Close", mock.Anything).Once().Return(nil) + + pageAPI := new(PageAPI) + pageAPI.frameNavigated = func(ctx context.Context) (page.FrameNavigatedClient, error) { + return frameNavigatedClient, nil + } + + responseReceivedClient := NewResponseReceivedClient() + responseReceivedClient.On("Close", mock.Anything).Once().Return(nil) + networkAPI := new(NetworkAPI) + networkAPI.responseReceived = func(ctx context.Context) (network2.ResponseReceivedClient, error) { + return responseReceivedClient, nil + } + networkAPI.setExtraHTTPHeaders = func(ctx context.Context, args *network2.SetExtraHTTPHeadersArgs) error { + return nil + } + + requestPausedClient := NewRequestPausedClient() + requestPausedClient.On("Close", mock.Anything).Once().Return(nil) + fetchAPI := new(FetchAPI) + fetchAPI.enable = func(ctx context.Context, args *fetch.EnableArgs) error { + return nil + } + fetchAPI.requestPaused = func(ctx context.Context) (fetch.RequestPausedClient, error) { + return requestPausedClient, nil + } + + client := &cdp.Client{ + Page: pageAPI, + Network: networkAPI, + Fetch: fetchAPI, + } + + mgr, err := network.New( + zerolog.New(os.Stdout).Level(zerolog.Disabled), + client, + network.Options{ + Headers: drivers.NewHTTPHeadersWith(map[string][]string{"x-correlation-id": {"foo"}}), + Filter: &network.Filter{ + Patterns: []drivers.ResourceFilter{ + { + URL: "http://google.com", + Type: "img", + }, + }, + }, + }, + ) + + So(err, ShouldBeNil) + So(mgr.Close(), ShouldBeNil) + + frameNavigatedClient.AssertExpectations(t) + responseReceivedClient.AssertExpectations(t) + requestPausedClient.AssertExpectations(t) + }) + }) + }) +} diff --git a/pkg/drivers/cdp/page.go b/pkg/drivers/cdp/page.go index bbf0481e..e6095550 100644 --- a/pkg/drivers/cdp/page.go +++ b/pkg/drivers/cdp/page.go @@ -18,6 +18,7 @@ import ( "github.com/MontFerret/ferret/pkg/drivers/cdp/input" net "github.com/MontFerret/ferret/pkg/drivers/cdp/network" "github.com/MontFerret/ferret/pkg/drivers/cdp/templates" + "github.com/MontFerret/ferret/pkg/drivers/cdp/utils" "github.com/MontFerret/ferret/pkg/drivers/common" "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/events" @@ -32,7 +33,7 @@ type ( HTMLPage struct { mu sync.Mutex closed values.Boolean - logger *zerolog.Logger + logger zerolog.Logger conn *rpcc.Conn client *cdp.Client network *net.Manager @@ -93,7 +94,12 @@ func LoadHTMLPage( } } - netManager, err := net.New(ctx, logger, client, netOpts) + netManager, err := net.New( + logger, + client, + netOpts, + ) + if err != nil { return nil, err } @@ -180,7 +186,7 @@ func LoadHTMLPageWithContent( } func NewHTMLPage( - logger *zerolog.Logger, + logger zerolog.Logger, conn *rpcc.Conn, client *cdp.Client, netManager *net.Manager, @@ -190,7 +196,7 @@ func NewHTMLPage( ) *HTMLPage { p := new(HTMLPage) p.closed = values.False - p.logger = logger + p.logger = logging.WithName(logger.With(), "cdp_page").Logger() p.conn = conn p.client = client p.network = netManager @@ -271,14 +277,13 @@ func (p *HTMLPage) Close() error { p.mu.Lock() defer p.mu.Unlock() - url := p.GetURL().String() + url := p.dom.GetMainFrame().GetURL().String() p.closed = values.True err := p.dom.Close() if err != nil { p.logger.Warn(). - Timestamp(). Str("url", url). Err(err). Msg("failed to close dom manager") @@ -288,7 +293,6 @@ func (p *HTMLPage) Close() error { if err != nil { p.logger.Warn(). - Timestamp(). Str("url", url). Err(err). Msg("failed to close network manager") @@ -298,23 +302,15 @@ func (p *HTMLPage) Close() error { if err != nil { p.logger.Warn(). - Timestamp(). Str("url", url). Err(err). Msg("failed to close browser page") } - err = p.conn.Close() + // Ignore errors from the connection object + p.conn.Close() - if err != nil { - p.logger.Warn(). - Timestamp(). - Str("url", url). - Err(err). - Msg("failed to close connection") - } - - return err + return nil } func (p *HTMLPage) IsClosed() values.Boolean { @@ -332,7 +328,6 @@ func (p *HTMLPage) GetURL() values.String { } p.logger.Warn(). - Timestamp(). Err(err). Msg("failed to retrieve URL") @@ -477,12 +472,14 @@ func (p *HTMLPage) CaptureScreenshot(ctx context.Context, params drivers.Screens params.Y = 0 } + clientWidth, clientHeight := utils.GetLayoutViewportWH(metrics) + if params.Width <= 0 { - params.Width = values.Float(metrics.CSSLayoutViewport.ClientWidth) - params.X + params.Width = values.Float(clientWidth) - params.X } if params.Height <= 0 { - params.Height = values.Float(metrics.CSSLayoutViewport.ClientHeight) - params.Y + params.Height = values.Float(clientHeight) - params.Y } clip := page.Viewport{ @@ -615,6 +612,8 @@ func (p *HTMLPage) Subscribe(ctx context.Context, eventName string, options *val Data: data, Err: err, } + + close(ch) }() return ch diff --git a/pkg/drivers/cdp/templates/get_styles.go b/pkg/drivers/cdp/templates/get_styles.go deleted file mode 100644 index 1ca109ea..00000000 --- a/pkg/drivers/cdp/templates/get_styles.go +++ /dev/null @@ -1,22 +0,0 @@ -package templates - -var getStylesTemplate = ` - (el) => { - const out = {}; - const styles = window.getComputedStyle(el); - - Object.keys(styles).forEach((key) => { - if (!isNaN(parseFloat(key))) { - const name = styles[key]; - const value = styles.getPropertyValue(name); - out[name] = value; - } - }); - - return out; - } -` - -func GetStyles() string { - return getStylesTemplate -} diff --git a/pkg/drivers/cdp/templates/query.go b/pkg/drivers/cdp/templates/query.go new file mode 100644 index 00000000..9b2cdae4 --- /dev/null +++ b/pkg/drivers/cdp/templates/query.go @@ -0,0 +1,41 @@ +package templates + +import ( + "fmt" + "github.com/MontFerret/ferret/pkg/drivers" + "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" +) + +func QuerySelector(selector string) string { + return fmt.Sprintf(` + (el) => { + const found = el.querySelector(%s); + + if (found == null) { + throw new Error(%s); + } + + return found; + } + `, + eval.ParamString(selector), + eval.ParamString(drivers.ErrNotFound.Error()), + ) +} + +func QuerySelectorAll(selector string) string { + return fmt.Sprintf(` + (el) => { + const found = el.querySelectorAll(%s); + + if (found == null) { + throw new Error(%s); + } + + return found; + } + `, + eval.ParamString(selector), + eval.ParamString(drivers.ErrNotFound.Error()), + ) +} diff --git a/pkg/drivers/cdp/templates/styles.go b/pkg/drivers/cdp/templates/styles.go index 0e3910b4..c0830548 100644 --- a/pkg/drivers/cdp/templates/styles.go +++ b/pkg/drivers/cdp/templates/styles.go @@ -2,11 +2,86 @@ package templates import ( "fmt" - + "github.com/MontFerret/ferret/pkg/drivers" "github.com/MontFerret/ferret/pkg/drivers/cdp/eval" "github.com/MontFerret/ferret/pkg/runtime/values" ) +var getStylesTemplate = ` + (el) => { + const out = {}; + const styles = window.getComputedStyle(el); + + Object.keys(styles).forEach((key) => { + if (!isNaN(parseFloat(key))) { + const name = styles[key]; + const value = styles.getPropertyValue(name); + out[name] = value; + } + }); + + return out; + } +` + +func GetStyles() string { + return getStylesTemplate +} + +func GetStyle(name string) string { + return fmt.Sprintf(` + (el) => { + const out = {}; + const styles = window.getComputedStyle(el); + + return styles[%s]; + } +`, eval.ParamString(name)) +} + +func SetStyle(name, value string) string { + return fmt.Sprintf(` + (el) => { + el.style[%s] = %s; + } +`, eval.ParamString(name), eval.ParamString(value)) +} + +func SetStyles(pairs *values.Object) string { + return fmt.Sprintf(` + (el) => { + const values = %s; + Object.keys(values).forEach((key) => { + el.style[key] = values[key] + }); + } +`, eval.Param(pairs)) +} + +func RemoveStyles(names []values.String) string { + return fmt.Sprintf(` + (el) => { + const style = el.style; + [%s].forEach((name) => { style[name] = "" }) + } + `, + eval.ParamStringList(names), + ) +} + +func WaitForStyle(name, value string, when drivers.WaitEvent) string { + return fmt.Sprintf(` + (el) => { + const styles = window.getComputedStyle(el); + const actual = styles[%s]; + const expected = %s; + + // null means we need to repeat + return actual %s expected ? true : null ; + } +`, eval.ParamString(name), eval.ParamString(value), WaitEventToEqOperator(when)) +} + func StyleRead(name values.String) string { n := name.String() return fmt.Sprintf(` diff --git a/pkg/drivers/cdp/utils/layout.go b/pkg/drivers/cdp/utils/layout.go new file mode 100644 index 00000000..150f1372 --- /dev/null +++ b/pkg/drivers/cdp/utils/layout.go @@ -0,0 +1,21 @@ +package utils + +import "github.com/mafredri/cdp/protocol/page" + +func GetLayoutViewportWH(metrics *page.GetLayoutMetricsReply) (width int, height int) { + if metrics.CSSLayoutViewport.ClientWidth > 0 { + width = metrics.CSSLayoutViewport.ClientWidth + } else { + // Chrome version <=89 + width = metrics.LayoutViewport.ClientWidth + } + + if metrics.CSSLayoutViewport.ClientHeight > 0 { + height = metrics.CSSLayoutViewport.ClientHeight + } else { + // Chrome version <=89 + height = metrics.LayoutViewport.ClientHeight + } + + return +} diff --git a/pkg/drivers/common/errors.go b/pkg/drivers/common/errors.go index a8af35c2..2f3f94f1 100644 --- a/pkg/drivers/common/errors.go +++ b/pkg/drivers/common/errors.go @@ -12,7 +12,7 @@ var ( ErrInvalidPath = core.Error(core.ErrInvalidOperation, "invalid path") ) -func CloseAll(logger *zerolog.Logger, closers []io.Closer, msg string) { +func CloseAll(logger zerolog.Logger, closers []io.Closer, msg string) { for _, closer := range closers { if err := closer.Close(); err != nil { logger.Error().Err(err).Msg(msg) diff --git a/pkg/drivers/response.go b/pkg/drivers/response.go index bf891ef2..8c4aaf63 100644 --- a/pkg/drivers/response.go +++ b/pkg/drivers/response.go @@ -3,18 +3,20 @@ package drivers import ( "context" + "github.com/wI2L/jettison" + "github.com/MontFerret/ferret/pkg/runtime/core" "github.com/MontFerret/ferret/pkg/runtime/values" "github.com/MontFerret/ferret/pkg/runtime/values/types" - - "github.com/wI2L/jettison" ) // HTTPResponse HTTP response object. type HTTPResponse struct { - StatusCode int - Status string - Headers *HTTPHeaders + URL string + StatusCode int + Status string + Headers *HTTPHeaders + ResponseTime float64 } func (resp *HTTPResponse) Type() core.Type { @@ -60,9 +62,11 @@ func (resp *HTTPResponse) Hash() uint64 { // responseMarshal is a structure that repeats HTTPResponse. It allows // easily Marshal the HTTPResponse object. type responseMarshal struct { - StatusCode int `json:"status_code"` - Status string `json:"status"` - Headers *HTTPHeaders `json:"headers"` + URL string `json:"url"` + StatusCode int `json:"status_code"` + Status string `json:"status"` + Headers *HTTPHeaders `json:"headers"` + ResponseTime float64 `json:"response_time"` } func (resp *HTTPResponse) MarshalJSON() ([]byte, error) { @@ -85,6 +89,8 @@ func (resp *HTTPResponse) GetIn(ctx context.Context, path []core.Value) (core.Va field := path[0].(values.String).String() switch field { + case "url", "URL": + return values.NewString(resp.URL), nil case "status": return values.NewString(resp.Status), nil case "statusCode": @@ -95,6 +101,9 @@ func (resp *HTTPResponse) GetIn(ctx context.Context, path []core.Value) (core.Va } return resp.Headers.GetIn(ctx, path[1:]) + case "responseTime": + return values.NewFloat(resp.ResponseTime), nil + } return values.None, nil diff --git a/pkg/parser/antlr/FqlParser.g4 b/pkg/parser/antlr/FqlParser.g4 index 2cf2b632..bda47a0e 100644 --- a/pkg/parser/antlr/FqlParser.g4 +++ b/pkg/parser/antlr/FqlParser.g4 @@ -157,8 +157,7 @@ waitForExpression waitForTimeout : integerLiteral | variable - | functionCallExpression - | memberExpression + | param ; waitForEventName diff --git a/pkg/parser/fql/FqlParser.interp b/pkg/parser/fql/FqlParser.interp index 0668ce0f..bcea38db 100644 --- a/pkg/parser/fql/FqlParser.interp +++ b/pkg/parser/fql/FqlParser.interp @@ -211,4 +211,4 @@ variable atn: -[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 71, 651, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 3, 2, 7, 2, 134, 10, 2, 12, 2, 14, 2, 137, 11, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 7, 7, 7, 153, 10, 7, 12, 7, 14, 7, 156, 11, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 163, 10, 8, 3, 9, 3, 9, 5, 9, 167, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 175, 10, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 183, 10, 10, 3, 11, 3, 11, 5, 11, 187, 10, 11, 3, 11, 3, 11, 3, 11, 5, 11, 192, 10, 11, 3, 11, 3, 11, 3, 11, 3, 11, 5, 11, 198, 10, 11, 3, 11, 5, 11, 201, 10, 11, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 207, 10, 12, 3, 12, 3, 12, 3, 12, 7, 12, 212, 10, 12, 12, 12, 14, 12, 215, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 222, 10, 12, 3, 12, 3, 12, 3, 12, 7, 12, 227, 10, 12, 12, 12, 14, 12, 230, 11, 12, 3, 12, 3, 12, 5, 12, 234, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 5, 13, 243, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 249, 10, 14, 3, 15, 3, 15, 5, 15, 253, 10, 15, 3, 16, 3, 16, 5, 16, 257, 10, 16, 3, 17, 3, 17, 5, 17, 261, 10, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19, 5, 19, 270, 10, 19, 3, 20, 3, 20, 5, 20, 274, 10, 20, 3, 21, 3, 21, 3, 21, 3, 21, 7, 21, 280, 10, 21, 12, 21, 14, 21, 283, 11, 21, 3, 22, 3, 22, 5, 22, 287, 10, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 5, 23, 307, 10, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 7, 25, 316, 10, 25, 12, 25, 14, 25, 319, 11, 25, 3, 26, 3, 26, 3, 26, 3, 26, 7, 26, 325, 10, 26, 12, 26, 14, 26, 328, 11, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 5, 28, 340, 10, 28, 5, 28, 342, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 5, 30, 353, 10, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 5, 31, 361, 10, 31, 3, 31, 5, 31, 364, 10, 31, 3, 32, 3, 32, 3, 32, 3, 32, 5, 32, 370, 10, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 377, 10, 33, 3, 34, 3, 34, 3, 34, 5, 34, 382, 10, 34, 3, 35, 3, 35, 3, 35, 5, 35, 387, 10, 35, 3, 35, 3, 35, 3, 35, 3, 35, 5, 35, 393, 10, 35, 3, 36, 3, 36, 3, 36, 3, 36, 7, 36, 399, 10, 36, 12, 36, 14, 36, 402, 11, 36, 3, 36, 5, 36, 405, 10, 36, 5, 36, 407, 10, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 7, 37, 415, 10, 37, 12, 37, 14, 37, 418, 11, 37, 3, 37, 5, 37, 421, 10, 37, 5, 37, 423, 10, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 5, 38, 436, 10, 38, 3, 39, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 5, 40, 445, 10, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 478, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 492, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 525, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 534, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 543, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 550, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 561, 10, 47, 3, 47, 3, 47, 7, 47, 565, 10, 47, 12, 47, 14, 47, 568, 11, 47, 3, 48, 3, 48, 6, 48, 572, 10, 48, 13, 48, 14, 48, 573, 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 5, 49, 581, 10, 49, 3, 50, 5, 50, 584, 10, 50, 3, 50, 3, 50, 3, 50, 3, 50, 5, 50, 590, 10, 50, 3, 50, 5, 50, 593, 10, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 7, 53, 602, 10, 53, 12, 53, 14, 53, 605, 11, 53, 3, 54, 3, 54, 3, 54, 3, 54, 7, 54, 611, 10, 54, 12, 54, 14, 54, 614, 11, 54, 5, 54, 616, 10, 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 5, 56, 625, 10, 56, 3, 57, 3, 57, 3, 57, 5, 57, 630, 10, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 2, 3, 92, 67, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 2, 10, 3, 2, 48, 49, 6, 2, 30, 31, 37, 39, 41, 62, 66, 66, 4, 2, 48, 48, 56, 57, 3, 2, 17, 22, 3, 2, 35, 36, 3, 2, 23, 25, 3, 2, 26, 27, 4, 2, 26, 27, 61, 61, 2, 697, 2, 135, 3, 2, 2, 2, 4, 141, 3, 2, 2, 2, 6, 143, 3, 2, 2, 2, 8, 145, 3, 2, 2, 2, 10, 148, 3, 2, 2, 2, 12, 154, 3, 2, 2, 2, 14, 162, 3, 2, 2, 2, 16, 166, 3, 2, 2, 2, 18, 182, 3, 2, 2, 2, 20, 200, 3, 2, 2, 2, 22, 233, 3, 2, 2, 2, 24, 242, 3, 2, 2, 2, 26, 248, 3, 2, 2, 2, 28, 252, 3, 2, 2, 2, 30, 256, 3, 2, 2, 2, 32, 260, 3, 2, 2, 2, 34, 262, 3, 2, 2, 2, 36, 265, 3, 2, 2, 2, 38, 273, 3, 2, 2, 2, 40, 275, 3, 2, 2, 2, 42, 284, 3, 2, 2, 2, 44, 306, 3, 2, 2, 2, 46, 308, 3, 2, 2, 2, 48, 312, 3, 2, 2, 2, 50, 320, 3, 2, 2, 2, 52, 329, 3, 2, 2, 2, 54, 341, 3, 2, 2, 2, 56, 343, 3, 2, 2, 2, 58, 352, 3, 2, 2, 2, 60, 354, 3, 2, 2, 2, 62, 369, 3, 2, 2, 2, 64, 376, 3, 2, 2, 2, 66, 381, 3, 2, 2, 2, 68, 386, 3, 2, 2, 2, 70, 394, 3, 2, 2, 2, 72, 410, 3, 2, 2, 2, 74, 435, 3, 2, 2, 2, 76, 437, 3, 2, 2, 2, 78, 444, 3, 2, 2, 2, 80, 446, 3, 2, 2, 2, 82, 448, 3, 2, 2, 2, 84, 450, 3, 2, 2, 2, 86, 452, 3, 2, 2, 2, 88, 454, 3, 2, 2, 2, 90, 456, 3, 2, 2, 2, 92, 477, 3, 2, 2, 2, 94, 569, 3, 2, 2, 2, 96, 580, 3, 2, 2, 2, 98, 592, 3, 2, 2, 2, 100, 594, 3, 2, 2, 2, 102, 598, 3, 2, 2, 2, 104, 603, 3, 2, 2, 2, 106, 606, 3, 2, 2, 2, 108, 619, 3, 2, 2, 2, 110, 624, 3, 2, 2, 2, 112, 629, 3, 2, 2, 2, 114, 631, 3, 2, 2, 2, 116, 633, 3, 2, 2, 2, 118, 635, 3, 2, 2, 2, 120, 637, 3, 2, 2, 2, 122, 639, 3, 2, 2, 2, 124, 641, 3, 2, 2, 2, 126, 643, 3, 2, 2, 2, 128, 645, 3, 2, 2, 2, 130, 648, 3, 2, 2, 2, 132, 134, 5, 4, 3, 2, 133, 132, 3, 2, 2, 2, 134, 137, 3, 2, 2, 2, 135, 133, 3, 2, 2, 2, 135, 136, 3, 2, 2, 2, 136, 138, 3, 2, 2, 2, 137, 135, 3, 2, 2, 2, 138, 139, 5, 12, 7, 2, 139, 140, 7, 2, 2, 3, 140, 3, 3, 2, 2, 2, 141, 142, 5, 6, 4, 2, 142, 5, 3, 2, 2, 2, 143, 144, 5, 8, 5, 2, 144, 7, 3, 2, 2, 2, 145, 146, 7, 51, 2, 2, 146, 147, 5, 10, 6, 2, 147, 9, 3, 2, 2, 2, 148, 149, 5, 104, 53, 2, 149, 150, 7, 66, 2, 2, 150, 11, 3, 2, 2, 2, 151, 153, 5, 14, 8, 2, 152, 151, 3, 2, 2, 2, 153, 156, 3, 2, 2, 2, 154, 152, 3, 2, 2, 2, 154, 155, 3, 2, 2, 2, 155, 157, 3, 2, 2, 2, 156, 154, 3, 2, 2, 2, 157, 158, 5, 16, 9, 2, 158, 13, 3, 2, 2, 2, 159, 163, 5, 18, 10, 2, 160, 163, 5, 100, 51, 2, 161, 163, 5, 60, 31, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, 2, 2, 2, 163, 15, 3, 2, 2, 2, 164, 167, 5, 20, 11, 2, 165, 167, 5, 22, 12, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 17, 3, 2, 2, 2, 168, 169, 7, 45, 2, 2, 169, 170, 7, 66, 2, 2, 170, 171, 7, 33, 2, 2, 171, 174, 7, 13, 2, 2, 172, 175, 5, 22, 12, 2, 173, 175, 5, 60, 31, 2, 174, 172, 3, 2, 2, 2, 174, 173, 3, 2, 2, 2, 175, 176, 3, 2, 2, 2, 176, 177, 7, 14, 2, 2, 177, 183, 3, 2, 2, 2, 178, 179, 7, 45, 2, 2, 179, 180, 7, 66, 2, 2, 180, 181, 7, 33, 2, 2, 181, 183, 5, 92, 47, 2, 182, 168, 3, 2, 2, 2, 182, 178, 3, 2, 2, 2, 183, 19, 3, 2, 2, 2, 184, 186, 7, 38, 2, 2, 185, 187, 7, 41, 2, 2, 186, 185, 3, 2, 2, 2, 186, 187, 3, 2, 2, 2, 187, 188, 3, 2, 2, 2, 188, 191, 7, 13, 2, 2, 189, 192, 5, 22, 12, 2, 190, 192, 5, 60, 31, 2, 191, 189, 3, 2, 2, 2, 191, 190, 3, 2, 2, 2, 192, 193, 3, 2, 2, 2, 193, 194, 7, 14, 2, 2, 194, 201, 3, 2, 2, 2, 195, 197, 7, 38, 2, 2, 196, 198, 7, 41, 2, 2, 197, 196, 3, 2, 2, 2, 197, 198, 3, 2, 2, 2, 198, 199, 3, 2, 2, 2, 199, 201, 5, 92, 47, 2, 200, 184, 3, 2, 2, 2, 200, 195, 3, 2, 2, 2, 201, 21, 3, 2, 2, 2, 202, 203, 7, 37, 2, 2, 203, 206, 7, 66, 2, 2, 204, 205, 7, 10, 2, 2, 205, 207, 7, 66, 2, 2, 206, 204, 3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 209, 7, 62, 2, 2, 209, 213, 5, 24, 13, 2, 210, 212, 5, 30, 16, 2, 211, 210, 3, 2, 2, 2, 212, 215, 3, 2, 2, 2, 213, 211, 3, 2, 2, 2, 213, 214, 3, 2, 2, 2, 214, 216, 3, 2, 2, 2, 215, 213, 3, 2, 2, 2, 216, 217, 5, 32, 17, 2, 217, 234, 3, 2, 2, 2, 218, 219, 7, 37, 2, 2, 219, 221, 7, 66, 2, 2, 220, 222, 7, 63, 2, 2, 221, 220, 3, 2, 2, 2, 221, 222, 3, 2, 2, 2, 222, 223, 3, 2, 2, 2, 223, 224, 7, 64, 2, 2, 224, 228, 5, 92, 47, 2, 225, 227, 5, 30, 16, 2, 226, 225, 3, 2, 2, 2, 227, 230, 3, 2, 2, 2, 228, 226, 3, 2, 2, 2, 228, 229, 3, 2, 2, 2, 229, 231, 3, 2, 2, 2, 230, 228, 3, 2, 2, 2, 231, 232, 5, 32, 17, 2, 232, 234, 3, 2, 2, 2, 233, 202, 3, 2, 2, 2, 233, 218, 3, 2, 2, 2, 234, 23, 3, 2, 2, 2, 235, 243, 5, 100, 51, 2, 236, 243, 5, 70, 36, 2, 237, 243, 5, 72, 37, 2, 238, 243, 5, 130, 66, 2, 239, 243, 5, 94, 48, 2, 240, 243, 5, 68, 35, 2, 241, 243, 5, 128, 65, 2, 242, 235, 3, 2, 2, 2, 242, 236, 3, 2, 2, 2, 242, 237, 3, 2, 2, 2, 242, 238, 3, 2, 2, 2, 242, 239, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 241, 3, 2, 2, 2, 243, 25, 3, 2, 2, 2, 244, 249, 5, 36, 19, 2, 245, 249, 5, 40, 21, 2, 246, 249, 5, 34, 18, 2, 247, 249, 5, 44, 23, 2, 248, 244, 3, 2, 2, 2, 248, 245, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 248, 247, 3, 2, 2, 2, 249, 27, 3, 2, 2, 2, 250, 253, 5, 18, 10, 2, 251, 253, 5, 100, 51, 2, 252, 250, 3, 2, 2, 2, 252, 251, 3, 2, 2, 2, 253, 29, 3, 2, 2, 2, 254, 257, 5, 28, 15, 2, 255, 257, 5, 26, 14, 2, 256, 254, 3, 2, 2, 2, 256, 255, 3, 2, 2, 2, 257, 31, 3, 2, 2, 2, 258, 261, 5, 20, 11, 2, 259, 261, 5, 22, 12, 2, 260, 258, 3, 2, 2, 2, 260, 259, 3, 2, 2, 2, 261, 33, 3, 2, 2, 2, 262, 263, 7, 42, 2, 2, 263, 264, 5, 92, 47, 2, 264, 35, 3, 2, 2, 2, 265, 266, 7, 44, 2, 2, 266, 269, 5, 38, 20, 2, 267, 268, 7, 10, 2, 2, 268, 270, 5, 38, 20, 2, 269, 267, 3, 2, 2, 2, 269, 270, 3, 2, 2, 2, 270, 37, 3, 2, 2, 2, 271, 274, 7, 68, 2, 2, 272, 274, 5, 128, 65, 2, 273, 271, 3, 2, 2, 2, 273, 272, 3, 2, 2, 2, 274, 39, 3, 2, 2, 2, 275, 276, 7, 43, 2, 2, 276, 281, 5, 42, 22, 2, 277, 278, 7, 10, 2, 2, 278, 280, 5, 42, 22, 2, 279, 277, 3, 2, 2, 2, 280, 283, 3, 2, 2, 2, 281, 279, 3, 2, 2, 2, 281, 282, 3, 2, 2, 2, 282, 41, 3, 2, 2, 2, 283, 281, 3, 2, 2, 2, 284, 286, 5, 92, 47, 2, 285, 287, 7, 47, 2, 2, 286, 285, 3, 2, 2, 2, 286, 287, 3, 2, 2, 2, 287, 43, 3, 2, 2, 2, 288, 289, 7, 46, 2, 2, 289, 307, 5, 56, 29, 2, 290, 291, 7, 46, 2, 2, 291, 307, 5, 50, 26, 2, 292, 293, 7, 46, 2, 2, 293, 294, 5, 48, 25, 2, 294, 295, 5, 50, 26, 2, 295, 307, 3, 2, 2, 2, 296, 297, 7, 46, 2, 2, 297, 298, 5, 48, 25, 2, 298, 299, 5, 54, 28, 2, 299, 307, 3, 2, 2, 2, 300, 301, 7, 46, 2, 2, 301, 302, 5, 48, 25, 2, 302, 303, 5, 56, 29, 2, 303, 307, 3, 2, 2, 2, 304, 305, 7, 46, 2, 2, 305, 307, 5, 48, 25, 2, 306, 288, 3, 2, 2, 2, 306, 290, 3, 2, 2, 2, 306, 292, 3, 2, 2, 2, 306, 296, 3, 2, 2, 2, 306, 300, 3, 2, 2, 2, 306, 304, 3, 2, 2, 2, 307, 45, 3, 2, 2, 2, 308, 309, 7, 66, 2, 2, 309, 310, 7, 33, 2, 2, 310, 311, 5, 92, 47, 2, 311, 47, 3, 2, 2, 2, 312, 317, 5, 46, 24, 2, 313, 314, 7, 10, 2, 2, 314, 316, 5, 46, 24, 2, 315, 313, 3, 2, 2, 2, 316, 319, 3, 2, 2, 2, 317, 315, 3, 2, 2, 2, 317, 318, 3, 2, 2, 2, 318, 49, 3, 2, 2, 2, 319, 317, 3, 2, 2, 2, 320, 321, 7, 58, 2, 2, 321, 326, 5, 52, 27, 2, 322, 323, 7, 10, 2, 2, 323, 325, 5, 52, 27, 2, 324, 322, 3, 2, 2, 2, 325, 328, 3, 2, 2, 2, 326, 324, 3, 2, 2, 2, 326, 327, 3, 2, 2, 2, 327, 51, 3, 2, 2, 2, 328, 326, 3, 2, 2, 2, 329, 330, 7, 66, 2, 2, 330, 331, 7, 33, 2, 2, 331, 332, 5, 100, 51, 2, 332, 53, 3, 2, 2, 2, 333, 334, 7, 52, 2, 2, 334, 342, 5, 46, 24, 2, 335, 336, 7, 52, 2, 2, 336, 339, 7, 66, 2, 2, 337, 338, 7, 53, 2, 2, 338, 340, 7, 66, 2, 2, 339, 337, 3, 2, 2, 2, 339, 340, 3, 2, 2, 2, 340, 342, 3, 2, 2, 2, 341, 333, 3, 2, 2, 2, 341, 335, 3, 2, 2, 2, 342, 55, 3, 2, 2, 2, 343, 344, 7, 54, 2, 2, 344, 345, 7, 55, 2, 2, 345, 346, 7, 52, 2, 2, 346, 347, 7, 66, 2, 2, 347, 57, 3, 2, 2, 2, 348, 349, 7, 40, 2, 2, 349, 353, 5, 72, 37, 2, 350, 351, 7, 40, 2, 2, 351, 353, 5, 130, 66, 2, 352, 348, 3, 2, 2, 2, 352, 350, 3, 2, 2, 2, 353, 59, 3, 2, 2, 2, 354, 355, 7, 39, 2, 2, 355, 356, 7, 59, 2, 2, 356, 357, 5, 64, 33, 2, 357, 358, 7, 62, 2, 2, 358, 360, 5, 66, 34, 2, 359, 361, 5, 58, 30, 2, 360, 359, 3, 2, 2, 2, 360, 361, 3, 2, 2, 2, 361, 363, 3, 2, 2, 2, 362, 364, 5, 62, 32, 2, 363, 362, 3, 2, 2, 2, 363, 364, 3, 2, 2, 2, 364, 61, 3, 2, 2, 2, 365, 370, 5, 86, 44, 2, 366, 370, 5, 130, 66, 2, 367, 370, 5, 100, 51, 2, 368, 370, 5, 94, 48, 2, 369, 365, 3, 2, 2, 2, 369, 366, 3, 2, 2, 2, 369, 367, 3, 2, 2, 2, 369, 368, 3, 2, 2, 2, 370, 63, 3, 2, 2, 2, 371, 377, 5, 82, 42, 2, 372, 377, 5, 130, 66, 2, 373, 377, 5, 128, 65, 2, 374, 377, 5, 100, 51, 2, 375, 377, 5, 94, 48, 2, 376, 371, 3, 2, 2, 2, 376, 372, 3, 2, 2, 2, 376, 373, 3, 2, 2, 2, 376, 374, 3, 2, 2, 2, 376, 375, 3, 2, 2, 2, 377, 65, 3, 2, 2, 2, 378, 382, 5, 100, 51, 2, 379, 382, 5, 130, 66, 2, 380, 382, 5, 94, 48, 2, 381, 378, 3, 2, 2, 2, 381, 379, 3, 2, 2, 2, 381, 380, 3, 2, 2, 2, 382, 67, 3, 2, 2, 2, 383, 387, 5, 86, 44, 2, 384, 387, 5, 130, 66, 2, 385, 387, 5, 128, 65, 2, 386, 383, 3, 2, 2, 2, 386, 384, 3, 2, 2, 2, 386, 385, 3, 2, 2, 2, 387, 388, 3, 2, 2, 2, 388, 392, 7, 32, 2, 2, 389, 393, 5, 86, 44, 2, 390, 393, 5, 130, 66, 2, 391, 393, 5, 128, 65, 2, 392, 389, 3, 2, 2, 2, 392, 390, 3, 2, 2, 2, 392, 391, 3, 2, 2, 2, 393, 69, 3, 2, 2, 2, 394, 406, 7, 11, 2, 2, 395, 400, 5, 92, 47, 2, 396, 397, 7, 10, 2, 2, 397, 399, 5, 92, 47, 2, 398, 396, 3, 2, 2, 2, 399, 402, 3, 2, 2, 2, 400, 398, 3, 2, 2, 2, 400, 401, 3, 2, 2, 2, 401, 404, 3, 2, 2, 2, 402, 400, 3, 2, 2, 2, 403, 405, 7, 10, 2, 2, 404, 403, 3, 2, 2, 2, 404, 405, 3, 2, 2, 2, 405, 407, 3, 2, 2, 2, 406, 395, 3, 2, 2, 2, 406, 407, 3, 2, 2, 2, 407, 408, 3, 2, 2, 2, 408, 409, 7, 12, 2, 2, 409, 71, 3, 2, 2, 2, 410, 422, 7, 15, 2, 2, 411, 416, 5, 74, 38, 2, 412, 413, 7, 10, 2, 2, 413, 415, 5, 74, 38, 2, 414, 412, 3, 2, 2, 2, 415, 418, 3, 2, 2, 2, 416, 414, 3, 2, 2, 2, 416, 417, 3, 2, 2, 2, 417, 420, 3, 2, 2, 2, 418, 416, 3, 2, 2, 2, 419, 421, 7, 10, 2, 2, 420, 419, 3, 2, 2, 2, 420, 421, 3, 2, 2, 2, 421, 423, 3, 2, 2, 2, 422, 411, 3, 2, 2, 2, 422, 423, 3, 2, 2, 2, 423, 424, 3, 2, 2, 2, 424, 425, 7, 16, 2, 2, 425, 73, 3, 2, 2, 2, 426, 427, 5, 78, 40, 2, 427, 428, 7, 7, 2, 2, 428, 429, 5, 92, 47, 2, 429, 436, 3, 2, 2, 2, 430, 431, 5, 76, 39, 2, 431, 432, 7, 7, 2, 2, 432, 433, 5, 92, 47, 2, 433, 436, 3, 2, 2, 2, 434, 436, 5, 130, 66, 2, 435, 426, 3, 2, 2, 2, 435, 430, 3, 2, 2, 2, 435, 434, 3, 2, 2, 2, 436, 75, 3, 2, 2, 2, 437, 438, 7, 11, 2, 2, 438, 439, 5, 92, 47, 2, 439, 440, 7, 12, 2, 2, 440, 77, 3, 2, 2, 2, 441, 445, 7, 66, 2, 2, 442, 445, 5, 82, 42, 2, 443, 445, 5, 128, 65, 2, 444, 441, 3, 2, 2, 2, 444, 442, 3, 2, 2, 2, 444, 443, 3, 2, 2, 2, 445, 79, 3, 2, 2, 2, 446, 447, 7, 50, 2, 2, 447, 81, 3, 2, 2, 2, 448, 449, 7, 67, 2, 2, 449, 83, 3, 2, 2, 2, 450, 451, 7, 69, 2, 2, 451, 85, 3, 2, 2, 2, 452, 453, 7, 68, 2, 2, 453, 87, 3, 2, 2, 2, 454, 455, 9, 2, 2, 2, 455, 89, 3, 2, 2, 2, 456, 457, 7, 13, 2, 2, 457, 458, 5, 92, 47, 2, 458, 459, 7, 14, 2, 2, 459, 91, 3, 2, 2, 2, 460, 461, 8, 47, 1, 2, 461, 462, 5, 126, 64, 2, 462, 463, 5, 92, 47, 29, 463, 478, 3, 2, 2, 2, 464, 478, 5, 68, 35, 2, 465, 478, 5, 82, 42, 2, 466, 478, 5, 84, 43, 2, 467, 478, 5, 86, 44, 2, 468, 478, 5, 80, 41, 2, 469, 478, 5, 70, 36, 2, 470, 478, 5, 72, 37, 2, 471, 478, 5, 94, 48, 2, 472, 478, 5, 100, 51, 2, 473, 478, 5, 128, 65, 2, 474, 478, 5, 130, 66, 2, 475, 478, 5, 88, 45, 2, 476, 478, 5, 90, 46, 2, 477, 460, 3, 2, 2, 2, 477, 464, 3, 2, 2, 2, 477, 465, 3, 2, 2, 2, 477, 466, 3, 2, 2, 2, 477, 467, 3, 2, 2, 2, 477, 468, 3, 2, 2, 2, 477, 469, 3, 2, 2, 2, 477, 470, 3, 2, 2, 2, 477, 471, 3, 2, 2, 2, 477, 472, 3, 2, 2, 2, 477, 473, 3, 2, 2, 2, 477, 474, 3, 2, 2, 2, 477, 475, 3, 2, 2, 2, 477, 476, 3, 2, 2, 2, 478, 566, 3, 2, 2, 2, 479, 480, 12, 28, 2, 2, 480, 481, 5, 122, 62, 2, 481, 482, 5, 92, 47, 29, 482, 565, 3, 2, 2, 2, 483, 484, 12, 27, 2, 2, 484, 485, 5, 124, 63, 2, 485, 486, 5, 92, 47, 28, 486, 565, 3, 2, 2, 2, 487, 488, 12, 26, 2, 2, 488, 491, 5, 108, 55, 2, 489, 492, 5, 110, 56, 2, 490, 492, 5, 114, 58, 2, 491, 489, 3, 2, 2, 2, 491, 490, 3, 2, 2, 2, 492, 493, 3, 2, 2, 2, 493, 494, 5, 92, 47, 27, 494, 565, 3, 2, 2, 2, 495, 496, 12, 25, 2, 2, 496, 497, 5, 110, 56, 2, 497, 498, 5, 92, 47, 26, 498, 565, 3, 2, 2, 2, 499, 500, 12, 24, 2, 2, 500, 501, 5, 112, 57, 2, 501, 502, 5, 92, 47, 25, 502, 565, 3, 2, 2, 2, 503, 504, 12, 23, 2, 2, 504, 505, 5, 114, 58, 2, 505, 506, 5, 92, 47, 24, 506, 565, 3, 2, 2, 2, 507, 508, 12, 22, 2, 2, 508, 509, 5, 116, 59, 2, 509, 510, 5, 92, 47, 23, 510, 565, 3, 2, 2, 2, 511, 512, 12, 21, 2, 2, 512, 513, 5, 118, 60, 2, 513, 514, 5, 92, 47, 22, 514, 565, 3, 2, 2, 2, 515, 516, 12, 20, 2, 2, 516, 517, 5, 120, 61, 2, 517, 518, 5, 92, 47, 21, 518, 565, 3, 2, 2, 2, 519, 520, 12, 17, 2, 2, 520, 521, 7, 34, 2, 2, 521, 524, 7, 13, 2, 2, 522, 525, 5, 22, 12, 2, 523, 525, 5, 60, 31, 2, 524, 522, 3, 2, 2, 2, 524, 523, 3, 2, 2, 2, 525, 526, 3, 2, 2, 2, 526, 527, 7, 14, 2, 2, 527, 528, 7, 7, 2, 2, 528, 529, 5, 92, 47, 18, 529, 565, 3, 2, 2, 2, 530, 531, 12, 16, 2, 2, 531, 533, 7, 34, 2, 2, 532, 534, 5, 92, 47, 2, 533, 532, 3, 2, 2, 2, 533, 534, 3, 2, 2, 2, 534, 535, 3, 2, 2, 2, 535, 536, 7, 7, 2, 2, 536, 565, 5, 92, 47, 17, 537, 538, 12, 19, 2, 2, 538, 539, 7, 34, 2, 2, 539, 542, 7, 13, 2, 2, 540, 543, 5, 22, 12, 2, 541, 543, 5, 60, 31, 2, 542, 540, 3, 2, 2, 2, 542, 541, 3, 2, 2, 2, 543, 544, 3, 2, 2, 2, 544, 545, 7, 14, 2, 2, 545, 546, 7, 7, 2, 2, 546, 549, 7, 13, 2, 2, 547, 550, 5, 22, 12, 2, 548, 550, 5, 60, 31, 2, 549, 547, 3, 2, 2, 2, 549, 548, 3, 2, 2, 2, 550, 551, 3, 2, 2, 2, 551, 552, 7, 14, 2, 2, 552, 565, 3, 2, 2, 2, 553, 554, 12, 18, 2, 2, 554, 555, 7, 34, 2, 2, 555, 556, 5, 92, 47, 2, 556, 557, 7, 7, 2, 2, 557, 560, 7, 13, 2, 2, 558, 561, 5, 22, 12, 2, 559, 561, 5, 60, 31, 2, 560, 558, 3, 2, 2, 2, 560, 559, 3, 2, 2, 2, 561, 562, 3, 2, 2, 2, 562, 563, 7, 14, 2, 2, 563, 565, 3, 2, 2, 2, 564, 479, 3, 2, 2, 2, 564, 483, 3, 2, 2, 2, 564, 487, 3, 2, 2, 2, 564, 495, 3, 2, 2, 2, 564, 499, 3, 2, 2, 2, 564, 503, 3, 2, 2, 2, 564, 507, 3, 2, 2, 2, 564, 511, 3, 2, 2, 2, 564, 515, 3, 2, 2, 2, 564, 519, 3, 2, 2, 2, 564, 530, 3, 2, 2, 2, 564, 537, 3, 2, 2, 2, 564, 553, 3, 2, 2, 2, 565, 568, 3, 2, 2, 2, 566, 564, 3, 2, 2, 2, 566, 567, 3, 2, 2, 2, 567, 93, 3, 2, 2, 2, 568, 566, 3, 2, 2, 2, 569, 571, 5, 96, 49, 2, 570, 572, 5, 98, 50, 2, 571, 570, 3, 2, 2, 2, 572, 573, 3, 2, 2, 2, 573, 571, 3, 2, 2, 2, 573, 574, 3, 2, 2, 2, 574, 95, 3, 2, 2, 2, 575, 581, 5, 130, 66, 2, 576, 581, 5, 128, 65, 2, 577, 581, 5, 100, 51, 2, 578, 581, 5, 70, 36, 2, 579, 581, 5, 72, 37, 2, 580, 575, 3, 2, 2, 2, 580, 576, 3, 2, 2, 2, 580, 577, 3, 2, 2, 2, 580, 578, 3, 2, 2, 2, 580, 579, 3, 2, 2, 2, 581, 97, 3, 2, 2, 2, 582, 584, 7, 34, 2, 2, 583, 582, 3, 2, 2, 2, 583, 584, 3, 2, 2, 2, 584, 585, 3, 2, 2, 2, 585, 586, 7, 9, 2, 2, 586, 593, 5, 78, 40, 2, 587, 588, 7, 34, 2, 2, 588, 590, 7, 9, 2, 2, 589, 587, 3, 2, 2, 2, 589, 590, 3, 2, 2, 2, 590, 591, 3, 2, 2, 2, 591, 593, 5, 76, 39, 2, 592, 583, 3, 2, 2, 2, 592, 589, 3, 2, 2, 2, 593, 99, 3, 2, 2, 2, 594, 595, 5, 104, 53, 2, 595, 596, 5, 102, 52, 2, 596, 597, 5, 106, 54, 2, 597, 101, 3, 2, 2, 2, 598, 599, 9, 3, 2, 2, 599, 103, 3, 2, 2, 2, 600, 602, 7, 70, 2, 2, 601, 600, 3, 2, 2, 2, 602, 605, 3, 2, 2, 2, 603, 601, 3, 2, 2, 2, 603, 604, 3, 2, 2, 2, 604, 105, 3, 2, 2, 2, 605, 603, 3, 2, 2, 2, 606, 615, 7, 13, 2, 2, 607, 612, 5, 92, 47, 2, 608, 609, 7, 10, 2, 2, 609, 611, 5, 92, 47, 2, 610, 608, 3, 2, 2, 2, 611, 614, 3, 2, 2, 2, 612, 610, 3, 2, 2, 2, 612, 613, 3, 2, 2, 2, 613, 616, 3, 2, 2, 2, 614, 612, 3, 2, 2, 2, 615, 607, 3, 2, 2, 2, 615, 616, 3, 2, 2, 2, 616, 617, 3, 2, 2, 2, 617, 618, 7, 14, 2, 2, 618, 107, 3, 2, 2, 2, 619, 620, 9, 4, 2, 2, 620, 109, 3, 2, 2, 2, 621, 625, 7, 62, 2, 2, 622, 623, 7, 61, 2, 2, 623, 625, 7, 62, 2, 2, 624, 621, 3, 2, 2, 2, 624, 622, 3, 2, 2, 2, 625, 111, 3, 2, 2, 2, 626, 630, 7, 60, 2, 2, 627, 628, 7, 61, 2, 2, 628, 630, 7, 60, 2, 2, 629, 626, 3, 2, 2, 2, 629, 627, 3, 2, 2, 2, 630, 113, 3, 2, 2, 2, 631, 632, 9, 5, 2, 2, 632, 115, 3, 2, 2, 2, 633, 634, 9, 6, 2, 2, 634, 117, 3, 2, 2, 2, 635, 636, 7, 30, 2, 2, 636, 119, 3, 2, 2, 2, 637, 638, 7, 31, 2, 2, 638, 121, 3, 2, 2, 2, 639, 640, 9, 7, 2, 2, 640, 123, 3, 2, 2, 2, 641, 642, 9, 8, 2, 2, 642, 125, 3, 2, 2, 2, 643, 644, 9, 9, 2, 2, 644, 127, 3, 2, 2, 2, 645, 646, 7, 65, 2, 2, 646, 647, 7, 66, 2, 2, 647, 129, 3, 2, 2, 2, 648, 649, 7, 66, 2, 2, 649, 131, 3, 2, 2, 2, 66, 135, 154, 162, 166, 174, 182, 186, 191, 197, 200, 206, 213, 221, 228, 233, 242, 248, 252, 256, 260, 269, 273, 281, 286, 306, 317, 326, 339, 341, 352, 360, 363, 369, 376, 381, 386, 392, 400, 404, 406, 416, 420, 422, 435, 444, 477, 491, 524, 533, 542, 549, 560, 564, 566, 573, 580, 583, 589, 592, 603, 612, 615, 624, 629] \ No newline at end of file +[3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 71, 650, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, 18, 4, 19, 9, 19, 4, 20, 9, 20, 4, 21, 9, 21, 4, 22, 9, 22, 4, 23, 9, 23, 4, 24, 9, 24, 4, 25, 9, 25, 4, 26, 9, 26, 4, 27, 9, 27, 4, 28, 9, 28, 4, 29, 9, 29, 4, 30, 9, 30, 4, 31, 9, 31, 4, 32, 9, 32, 4, 33, 9, 33, 4, 34, 9, 34, 4, 35, 9, 35, 4, 36, 9, 36, 4, 37, 9, 37, 4, 38, 9, 38, 4, 39, 9, 39, 4, 40, 9, 40, 4, 41, 9, 41, 4, 42, 9, 42, 4, 43, 9, 43, 4, 44, 9, 44, 4, 45, 9, 45, 4, 46, 9, 46, 4, 47, 9, 47, 4, 48, 9, 48, 4, 49, 9, 49, 4, 50, 9, 50, 4, 51, 9, 51, 4, 52, 9, 52, 4, 53, 9, 53, 4, 54, 9, 54, 4, 55, 9, 55, 4, 56, 9, 56, 4, 57, 9, 57, 4, 58, 9, 58, 4, 59, 9, 59, 4, 60, 9, 60, 4, 61, 9, 61, 4, 62, 9, 62, 4, 63, 9, 63, 4, 64, 9, 64, 4, 65, 9, 65, 4, 66, 9, 66, 3, 2, 7, 2, 134, 10, 2, 12, 2, 14, 2, 137, 11, 2, 3, 2, 3, 2, 3, 2, 3, 3, 3, 3, 3, 4, 3, 4, 3, 5, 3, 5, 3, 5, 3, 6, 3, 6, 3, 6, 3, 7, 7, 7, 153, 10, 7, 12, 7, 14, 7, 156, 11, 7, 3, 7, 3, 7, 3, 8, 3, 8, 3, 8, 5, 8, 163, 10, 8, 3, 9, 3, 9, 5, 9, 167, 10, 9, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 175, 10, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 3, 10, 5, 10, 183, 10, 10, 3, 11, 3, 11, 5, 11, 187, 10, 11, 3, 11, 3, 11, 3, 11, 5, 11, 192, 10, 11, 3, 11, 3, 11, 3, 11, 3, 11, 5, 11, 198, 10, 11, 3, 11, 5, 11, 201, 10, 11, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 207, 10, 12, 3, 12, 3, 12, 3, 12, 7, 12, 212, 10, 12, 12, 12, 14, 12, 215, 11, 12, 3, 12, 3, 12, 3, 12, 3, 12, 3, 12, 5, 12, 222, 10, 12, 3, 12, 3, 12, 3, 12, 7, 12, 227, 10, 12, 12, 12, 14, 12, 230, 11, 12, 3, 12, 3, 12, 5, 12, 234, 10, 12, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 3, 13, 5, 13, 243, 10, 13, 3, 14, 3, 14, 3, 14, 3, 14, 5, 14, 249, 10, 14, 3, 15, 3, 15, 5, 15, 253, 10, 15, 3, 16, 3, 16, 5, 16, 257, 10, 16, 3, 17, 3, 17, 5, 17, 261, 10, 17, 3, 18, 3, 18, 3, 18, 3, 19, 3, 19, 3, 19, 3, 19, 5, 19, 270, 10, 19, 3, 20, 3, 20, 5, 20, 274, 10, 20, 3, 21, 3, 21, 3, 21, 3, 21, 7, 21, 280, 10, 21, 12, 21, 14, 21, 283, 11, 21, 3, 22, 3, 22, 5, 22, 287, 10, 22, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 3, 23, 5, 23, 307, 10, 23, 3, 24, 3, 24, 3, 24, 3, 24, 3, 25, 3, 25, 3, 25, 7, 25, 316, 10, 25, 12, 25, 14, 25, 319, 11, 25, 3, 26, 3, 26, 3, 26, 3, 26, 7, 26, 325, 10, 26, 12, 26, 14, 26, 328, 11, 26, 3, 27, 3, 27, 3, 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 5, 28, 340, 10, 28, 5, 28, 342, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 5, 30, 353, 10, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 5, 31, 361, 10, 31, 3, 31, 5, 31, 364, 10, 31, 3, 32, 3, 32, 3, 32, 5, 32, 369, 10, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 376, 10, 33, 3, 34, 3, 34, 3, 34, 5, 34, 381, 10, 34, 3, 35, 3, 35, 3, 35, 5, 35, 386, 10, 35, 3, 35, 3, 35, 3, 35, 3, 35, 5, 35, 392, 10, 35, 3, 36, 3, 36, 3, 36, 3, 36, 7, 36, 398, 10, 36, 12, 36, 14, 36, 401, 11, 36, 3, 36, 5, 36, 404, 10, 36, 5, 36, 406, 10, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, 37, 7, 37, 414, 10, 37, 12, 37, 14, 37, 417, 11, 37, 3, 37, 5, 37, 420, 10, 37, 5, 37, 422, 10, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 5, 38, 435, 10, 38, 3, 39, 3, 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 5, 40, 444, 10, 40, 3, 41, 3, 41, 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 477, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 491, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 524, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 533, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 542, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 549, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 560, 10, 47, 3, 47, 3, 47, 7, 47, 564, 10, 47, 12, 47, 14, 47, 567, 11, 47, 3, 48, 3, 48, 6, 48, 571, 10, 48, 13, 48, 14, 48, 572, 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 5, 49, 580, 10, 49, 3, 50, 5, 50, 583, 10, 50, 3, 50, 3, 50, 3, 50, 3, 50, 5, 50, 589, 10, 50, 3, 50, 5, 50, 592, 10, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 7, 53, 601, 10, 53, 12, 53, 14, 53, 604, 11, 53, 3, 54, 3, 54, 3, 54, 3, 54, 7, 54, 610, 10, 54, 12, 54, 14, 54, 613, 11, 54, 5, 54, 615, 10, 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 5, 56, 624, 10, 56, 3, 57, 3, 57, 3, 57, 5, 57, 629, 10, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 2, 3, 92, 67, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, 126, 128, 130, 2, 10, 3, 2, 48, 49, 6, 2, 30, 31, 37, 39, 41, 62, 66, 66, 4, 2, 48, 48, 56, 57, 3, 2, 17, 22, 3, 2, 35, 36, 3, 2, 23, 25, 3, 2, 26, 27, 4, 2, 26, 27, 61, 61, 2, 695, 2, 135, 3, 2, 2, 2, 4, 141, 3, 2, 2, 2, 6, 143, 3, 2, 2, 2, 8, 145, 3, 2, 2, 2, 10, 148, 3, 2, 2, 2, 12, 154, 3, 2, 2, 2, 14, 162, 3, 2, 2, 2, 16, 166, 3, 2, 2, 2, 18, 182, 3, 2, 2, 2, 20, 200, 3, 2, 2, 2, 22, 233, 3, 2, 2, 2, 24, 242, 3, 2, 2, 2, 26, 248, 3, 2, 2, 2, 28, 252, 3, 2, 2, 2, 30, 256, 3, 2, 2, 2, 32, 260, 3, 2, 2, 2, 34, 262, 3, 2, 2, 2, 36, 265, 3, 2, 2, 2, 38, 273, 3, 2, 2, 2, 40, 275, 3, 2, 2, 2, 42, 284, 3, 2, 2, 2, 44, 306, 3, 2, 2, 2, 46, 308, 3, 2, 2, 2, 48, 312, 3, 2, 2, 2, 50, 320, 3, 2, 2, 2, 52, 329, 3, 2, 2, 2, 54, 341, 3, 2, 2, 2, 56, 343, 3, 2, 2, 2, 58, 352, 3, 2, 2, 2, 60, 354, 3, 2, 2, 2, 62, 368, 3, 2, 2, 2, 64, 375, 3, 2, 2, 2, 66, 380, 3, 2, 2, 2, 68, 385, 3, 2, 2, 2, 70, 393, 3, 2, 2, 2, 72, 409, 3, 2, 2, 2, 74, 434, 3, 2, 2, 2, 76, 436, 3, 2, 2, 2, 78, 443, 3, 2, 2, 2, 80, 445, 3, 2, 2, 2, 82, 447, 3, 2, 2, 2, 84, 449, 3, 2, 2, 2, 86, 451, 3, 2, 2, 2, 88, 453, 3, 2, 2, 2, 90, 455, 3, 2, 2, 2, 92, 476, 3, 2, 2, 2, 94, 568, 3, 2, 2, 2, 96, 579, 3, 2, 2, 2, 98, 591, 3, 2, 2, 2, 100, 593, 3, 2, 2, 2, 102, 597, 3, 2, 2, 2, 104, 602, 3, 2, 2, 2, 106, 605, 3, 2, 2, 2, 108, 618, 3, 2, 2, 2, 110, 623, 3, 2, 2, 2, 112, 628, 3, 2, 2, 2, 114, 630, 3, 2, 2, 2, 116, 632, 3, 2, 2, 2, 118, 634, 3, 2, 2, 2, 120, 636, 3, 2, 2, 2, 122, 638, 3, 2, 2, 2, 124, 640, 3, 2, 2, 2, 126, 642, 3, 2, 2, 2, 128, 644, 3, 2, 2, 2, 130, 647, 3, 2, 2, 2, 132, 134, 5, 4, 3, 2, 133, 132, 3, 2, 2, 2, 134, 137, 3, 2, 2, 2, 135, 133, 3, 2, 2, 2, 135, 136, 3, 2, 2, 2, 136, 138, 3, 2, 2, 2, 137, 135, 3, 2, 2, 2, 138, 139, 5, 12, 7, 2, 139, 140, 7, 2, 2, 3, 140, 3, 3, 2, 2, 2, 141, 142, 5, 6, 4, 2, 142, 5, 3, 2, 2, 2, 143, 144, 5, 8, 5, 2, 144, 7, 3, 2, 2, 2, 145, 146, 7, 51, 2, 2, 146, 147, 5, 10, 6, 2, 147, 9, 3, 2, 2, 2, 148, 149, 5, 104, 53, 2, 149, 150, 7, 66, 2, 2, 150, 11, 3, 2, 2, 2, 151, 153, 5, 14, 8, 2, 152, 151, 3, 2, 2, 2, 153, 156, 3, 2, 2, 2, 154, 152, 3, 2, 2, 2, 154, 155, 3, 2, 2, 2, 155, 157, 3, 2, 2, 2, 156, 154, 3, 2, 2, 2, 157, 158, 5, 16, 9, 2, 158, 13, 3, 2, 2, 2, 159, 163, 5, 18, 10, 2, 160, 163, 5, 100, 51, 2, 161, 163, 5, 60, 31, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, 2, 2, 2, 163, 15, 3, 2, 2, 2, 164, 167, 5, 20, 11, 2, 165, 167, 5, 22, 12, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 17, 3, 2, 2, 2, 168, 169, 7, 45, 2, 2, 169, 170, 7, 66, 2, 2, 170, 171, 7, 33, 2, 2, 171, 174, 7, 13, 2, 2, 172, 175, 5, 22, 12, 2, 173, 175, 5, 60, 31, 2, 174, 172, 3, 2, 2, 2, 174, 173, 3, 2, 2, 2, 175, 176, 3, 2, 2, 2, 176, 177, 7, 14, 2, 2, 177, 183, 3, 2, 2, 2, 178, 179, 7, 45, 2, 2, 179, 180, 7, 66, 2, 2, 180, 181, 7, 33, 2, 2, 181, 183, 5, 92, 47, 2, 182, 168, 3, 2, 2, 2, 182, 178, 3, 2, 2, 2, 183, 19, 3, 2, 2, 2, 184, 186, 7, 38, 2, 2, 185, 187, 7, 41, 2, 2, 186, 185, 3, 2, 2, 2, 186, 187, 3, 2, 2, 2, 187, 188, 3, 2, 2, 2, 188, 191, 7, 13, 2, 2, 189, 192, 5, 22, 12, 2, 190, 192, 5, 60, 31, 2, 191, 189, 3, 2, 2, 2, 191, 190, 3, 2, 2, 2, 192, 193, 3, 2, 2, 2, 193, 194, 7, 14, 2, 2, 194, 201, 3, 2, 2, 2, 195, 197, 7, 38, 2, 2, 196, 198, 7, 41, 2, 2, 197, 196, 3, 2, 2, 2, 197, 198, 3, 2, 2, 2, 198, 199, 3, 2, 2, 2, 199, 201, 5, 92, 47, 2, 200, 184, 3, 2, 2, 2, 200, 195, 3, 2, 2, 2, 201, 21, 3, 2, 2, 2, 202, 203, 7, 37, 2, 2, 203, 206, 7, 66, 2, 2, 204, 205, 7, 10, 2, 2, 205, 207, 7, 66, 2, 2, 206, 204, 3, 2, 2, 2, 206, 207, 3, 2, 2, 2, 207, 208, 3, 2, 2, 2, 208, 209, 7, 62, 2, 2, 209, 213, 5, 24, 13, 2, 210, 212, 5, 30, 16, 2, 211, 210, 3, 2, 2, 2, 212, 215, 3, 2, 2, 2, 213, 211, 3, 2, 2, 2, 213, 214, 3, 2, 2, 2, 214, 216, 3, 2, 2, 2, 215, 213, 3, 2, 2, 2, 216, 217, 5, 32, 17, 2, 217, 234, 3, 2, 2, 2, 218, 219, 7, 37, 2, 2, 219, 221, 7, 66, 2, 2, 220, 222, 7, 63, 2, 2, 221, 220, 3, 2, 2, 2, 221, 222, 3, 2, 2, 2, 222, 223, 3, 2, 2, 2, 223, 224, 7, 64, 2, 2, 224, 228, 5, 92, 47, 2, 225, 227, 5, 30, 16, 2, 226, 225, 3, 2, 2, 2, 227, 230, 3, 2, 2, 2, 228, 226, 3, 2, 2, 2, 228, 229, 3, 2, 2, 2, 229, 231, 3, 2, 2, 2, 230, 228, 3, 2, 2, 2, 231, 232, 5, 32, 17, 2, 232, 234, 3, 2, 2, 2, 233, 202, 3, 2, 2, 2, 233, 218, 3, 2, 2, 2, 234, 23, 3, 2, 2, 2, 235, 243, 5, 100, 51, 2, 236, 243, 5, 70, 36, 2, 237, 243, 5, 72, 37, 2, 238, 243, 5, 130, 66, 2, 239, 243, 5, 94, 48, 2, 240, 243, 5, 68, 35, 2, 241, 243, 5, 128, 65, 2, 242, 235, 3, 2, 2, 2, 242, 236, 3, 2, 2, 2, 242, 237, 3, 2, 2, 2, 242, 238, 3, 2, 2, 2, 242, 239, 3, 2, 2, 2, 242, 240, 3, 2, 2, 2, 242, 241, 3, 2, 2, 2, 243, 25, 3, 2, 2, 2, 244, 249, 5, 36, 19, 2, 245, 249, 5, 40, 21, 2, 246, 249, 5, 34, 18, 2, 247, 249, 5, 44, 23, 2, 248, 244, 3, 2, 2, 2, 248, 245, 3, 2, 2, 2, 248, 246, 3, 2, 2, 2, 248, 247, 3, 2, 2, 2, 249, 27, 3, 2, 2, 2, 250, 253, 5, 18, 10, 2, 251, 253, 5, 100, 51, 2, 252, 250, 3, 2, 2, 2, 252, 251, 3, 2, 2, 2, 253, 29, 3, 2, 2, 2, 254, 257, 5, 28, 15, 2, 255, 257, 5, 26, 14, 2, 256, 254, 3, 2, 2, 2, 256, 255, 3, 2, 2, 2, 257, 31, 3, 2, 2, 2, 258, 261, 5, 20, 11, 2, 259, 261, 5, 22, 12, 2, 260, 258, 3, 2, 2, 2, 260, 259, 3, 2, 2, 2, 261, 33, 3, 2, 2, 2, 262, 263, 7, 42, 2, 2, 263, 264, 5, 92, 47, 2, 264, 35, 3, 2, 2, 2, 265, 266, 7, 44, 2, 2, 266, 269, 5, 38, 20, 2, 267, 268, 7, 10, 2, 2, 268, 270, 5, 38, 20, 2, 269, 267, 3, 2, 2, 2, 269, 270, 3, 2, 2, 2, 270, 37, 3, 2, 2, 2, 271, 274, 7, 68, 2, 2, 272, 274, 5, 128, 65, 2, 273, 271, 3, 2, 2, 2, 273, 272, 3, 2, 2, 2, 274, 39, 3, 2, 2, 2, 275, 276, 7, 43, 2, 2, 276, 281, 5, 42, 22, 2, 277, 278, 7, 10, 2, 2, 278, 280, 5, 42, 22, 2, 279, 277, 3, 2, 2, 2, 280, 283, 3, 2, 2, 2, 281, 279, 3, 2, 2, 2, 281, 282, 3, 2, 2, 2, 282, 41, 3, 2, 2, 2, 283, 281, 3, 2, 2, 2, 284, 286, 5, 92, 47, 2, 285, 287, 7, 47, 2, 2, 286, 285, 3, 2, 2, 2, 286, 287, 3, 2, 2, 2, 287, 43, 3, 2, 2, 2, 288, 289, 7, 46, 2, 2, 289, 307, 5, 56, 29, 2, 290, 291, 7, 46, 2, 2, 291, 307, 5, 50, 26, 2, 292, 293, 7, 46, 2, 2, 293, 294, 5, 48, 25, 2, 294, 295, 5, 50, 26, 2, 295, 307, 3, 2, 2, 2, 296, 297, 7, 46, 2, 2, 297, 298, 5, 48, 25, 2, 298, 299, 5, 54, 28, 2, 299, 307, 3, 2, 2, 2, 300, 301, 7, 46, 2, 2, 301, 302, 5, 48, 25, 2, 302, 303, 5, 56, 29, 2, 303, 307, 3, 2, 2, 2, 304, 305, 7, 46, 2, 2, 305, 307, 5, 48, 25, 2, 306, 288, 3, 2, 2, 2, 306, 290, 3, 2, 2, 2, 306, 292, 3, 2, 2, 2, 306, 296, 3, 2, 2, 2, 306, 300, 3, 2, 2, 2, 306, 304, 3, 2, 2, 2, 307, 45, 3, 2, 2, 2, 308, 309, 7, 66, 2, 2, 309, 310, 7, 33, 2, 2, 310, 311, 5, 92, 47, 2, 311, 47, 3, 2, 2, 2, 312, 317, 5, 46, 24, 2, 313, 314, 7, 10, 2, 2, 314, 316, 5, 46, 24, 2, 315, 313, 3, 2, 2, 2, 316, 319, 3, 2, 2, 2, 317, 315, 3, 2, 2, 2, 317, 318, 3, 2, 2, 2, 318, 49, 3, 2, 2, 2, 319, 317, 3, 2, 2, 2, 320, 321, 7, 58, 2, 2, 321, 326, 5, 52, 27, 2, 322, 323, 7, 10, 2, 2, 323, 325, 5, 52, 27, 2, 324, 322, 3, 2, 2, 2, 325, 328, 3, 2, 2, 2, 326, 324, 3, 2, 2, 2, 326, 327, 3, 2, 2, 2, 327, 51, 3, 2, 2, 2, 328, 326, 3, 2, 2, 2, 329, 330, 7, 66, 2, 2, 330, 331, 7, 33, 2, 2, 331, 332, 5, 100, 51, 2, 332, 53, 3, 2, 2, 2, 333, 334, 7, 52, 2, 2, 334, 342, 5, 46, 24, 2, 335, 336, 7, 52, 2, 2, 336, 339, 7, 66, 2, 2, 337, 338, 7, 53, 2, 2, 338, 340, 7, 66, 2, 2, 339, 337, 3, 2, 2, 2, 339, 340, 3, 2, 2, 2, 340, 342, 3, 2, 2, 2, 341, 333, 3, 2, 2, 2, 341, 335, 3, 2, 2, 2, 342, 55, 3, 2, 2, 2, 343, 344, 7, 54, 2, 2, 344, 345, 7, 55, 2, 2, 345, 346, 7, 52, 2, 2, 346, 347, 7, 66, 2, 2, 347, 57, 3, 2, 2, 2, 348, 349, 7, 40, 2, 2, 349, 353, 5, 72, 37, 2, 350, 351, 7, 40, 2, 2, 351, 353, 5, 130, 66, 2, 352, 348, 3, 2, 2, 2, 352, 350, 3, 2, 2, 2, 353, 59, 3, 2, 2, 2, 354, 355, 7, 39, 2, 2, 355, 356, 7, 59, 2, 2, 356, 357, 5, 64, 33, 2, 357, 358, 7, 62, 2, 2, 358, 360, 5, 66, 34, 2, 359, 361, 5, 58, 30, 2, 360, 359, 3, 2, 2, 2, 360, 361, 3, 2, 2, 2, 361, 363, 3, 2, 2, 2, 362, 364, 5, 62, 32, 2, 363, 362, 3, 2, 2, 2, 363, 364, 3, 2, 2, 2, 364, 61, 3, 2, 2, 2, 365, 369, 5, 86, 44, 2, 366, 369, 5, 130, 66, 2, 367, 369, 5, 128, 65, 2, 368, 365, 3, 2, 2, 2, 368, 366, 3, 2, 2, 2, 368, 367, 3, 2, 2, 2, 369, 63, 3, 2, 2, 2, 370, 376, 5, 82, 42, 2, 371, 376, 5, 130, 66, 2, 372, 376, 5, 128, 65, 2, 373, 376, 5, 100, 51, 2, 374, 376, 5, 94, 48, 2, 375, 370, 3, 2, 2, 2, 375, 371, 3, 2, 2, 2, 375, 372, 3, 2, 2, 2, 375, 373, 3, 2, 2, 2, 375, 374, 3, 2, 2, 2, 376, 65, 3, 2, 2, 2, 377, 381, 5, 100, 51, 2, 378, 381, 5, 130, 66, 2, 379, 381, 5, 94, 48, 2, 380, 377, 3, 2, 2, 2, 380, 378, 3, 2, 2, 2, 380, 379, 3, 2, 2, 2, 381, 67, 3, 2, 2, 2, 382, 386, 5, 86, 44, 2, 383, 386, 5, 130, 66, 2, 384, 386, 5, 128, 65, 2, 385, 382, 3, 2, 2, 2, 385, 383, 3, 2, 2, 2, 385, 384, 3, 2, 2, 2, 386, 387, 3, 2, 2, 2, 387, 391, 7, 32, 2, 2, 388, 392, 5, 86, 44, 2, 389, 392, 5, 130, 66, 2, 390, 392, 5, 128, 65, 2, 391, 388, 3, 2, 2, 2, 391, 389, 3, 2, 2, 2, 391, 390, 3, 2, 2, 2, 392, 69, 3, 2, 2, 2, 393, 405, 7, 11, 2, 2, 394, 399, 5, 92, 47, 2, 395, 396, 7, 10, 2, 2, 396, 398, 5, 92, 47, 2, 397, 395, 3, 2, 2, 2, 398, 401, 3, 2, 2, 2, 399, 397, 3, 2, 2, 2, 399, 400, 3, 2, 2, 2, 400, 403, 3, 2, 2, 2, 401, 399, 3, 2, 2, 2, 402, 404, 7, 10, 2, 2, 403, 402, 3, 2, 2, 2, 403, 404, 3, 2, 2, 2, 404, 406, 3, 2, 2, 2, 405, 394, 3, 2, 2, 2, 405, 406, 3, 2, 2, 2, 406, 407, 3, 2, 2, 2, 407, 408, 7, 12, 2, 2, 408, 71, 3, 2, 2, 2, 409, 421, 7, 15, 2, 2, 410, 415, 5, 74, 38, 2, 411, 412, 7, 10, 2, 2, 412, 414, 5, 74, 38, 2, 413, 411, 3, 2, 2, 2, 414, 417, 3, 2, 2, 2, 415, 413, 3, 2, 2, 2, 415, 416, 3, 2, 2, 2, 416, 419, 3, 2, 2, 2, 417, 415, 3, 2, 2, 2, 418, 420, 7, 10, 2, 2, 419, 418, 3, 2, 2, 2, 419, 420, 3, 2, 2, 2, 420, 422, 3, 2, 2, 2, 421, 410, 3, 2, 2, 2, 421, 422, 3, 2, 2, 2, 422, 423, 3, 2, 2, 2, 423, 424, 7, 16, 2, 2, 424, 73, 3, 2, 2, 2, 425, 426, 5, 78, 40, 2, 426, 427, 7, 7, 2, 2, 427, 428, 5, 92, 47, 2, 428, 435, 3, 2, 2, 2, 429, 430, 5, 76, 39, 2, 430, 431, 7, 7, 2, 2, 431, 432, 5, 92, 47, 2, 432, 435, 3, 2, 2, 2, 433, 435, 5, 130, 66, 2, 434, 425, 3, 2, 2, 2, 434, 429, 3, 2, 2, 2, 434, 433, 3, 2, 2, 2, 435, 75, 3, 2, 2, 2, 436, 437, 7, 11, 2, 2, 437, 438, 5, 92, 47, 2, 438, 439, 7, 12, 2, 2, 439, 77, 3, 2, 2, 2, 440, 444, 7, 66, 2, 2, 441, 444, 5, 82, 42, 2, 442, 444, 5, 128, 65, 2, 443, 440, 3, 2, 2, 2, 443, 441, 3, 2, 2, 2, 443, 442, 3, 2, 2, 2, 444, 79, 3, 2, 2, 2, 445, 446, 7, 50, 2, 2, 446, 81, 3, 2, 2, 2, 447, 448, 7, 67, 2, 2, 448, 83, 3, 2, 2, 2, 449, 450, 7, 69, 2, 2, 450, 85, 3, 2, 2, 2, 451, 452, 7, 68, 2, 2, 452, 87, 3, 2, 2, 2, 453, 454, 9, 2, 2, 2, 454, 89, 3, 2, 2, 2, 455, 456, 7, 13, 2, 2, 456, 457, 5, 92, 47, 2, 457, 458, 7, 14, 2, 2, 458, 91, 3, 2, 2, 2, 459, 460, 8, 47, 1, 2, 460, 461, 5, 126, 64, 2, 461, 462, 5, 92, 47, 29, 462, 477, 3, 2, 2, 2, 463, 477, 5, 68, 35, 2, 464, 477, 5, 82, 42, 2, 465, 477, 5, 84, 43, 2, 466, 477, 5, 86, 44, 2, 467, 477, 5, 80, 41, 2, 468, 477, 5, 70, 36, 2, 469, 477, 5, 72, 37, 2, 470, 477, 5, 94, 48, 2, 471, 477, 5, 100, 51, 2, 472, 477, 5, 128, 65, 2, 473, 477, 5, 130, 66, 2, 474, 477, 5, 88, 45, 2, 475, 477, 5, 90, 46, 2, 476, 459, 3, 2, 2, 2, 476, 463, 3, 2, 2, 2, 476, 464, 3, 2, 2, 2, 476, 465, 3, 2, 2, 2, 476, 466, 3, 2, 2, 2, 476, 467, 3, 2, 2, 2, 476, 468, 3, 2, 2, 2, 476, 469, 3, 2, 2, 2, 476, 470, 3, 2, 2, 2, 476, 471, 3, 2, 2, 2, 476, 472, 3, 2, 2, 2, 476, 473, 3, 2, 2, 2, 476, 474, 3, 2, 2, 2, 476, 475, 3, 2, 2, 2, 477, 565, 3, 2, 2, 2, 478, 479, 12, 28, 2, 2, 479, 480, 5, 122, 62, 2, 480, 481, 5, 92, 47, 29, 481, 564, 3, 2, 2, 2, 482, 483, 12, 27, 2, 2, 483, 484, 5, 124, 63, 2, 484, 485, 5, 92, 47, 28, 485, 564, 3, 2, 2, 2, 486, 487, 12, 26, 2, 2, 487, 490, 5, 108, 55, 2, 488, 491, 5, 110, 56, 2, 489, 491, 5, 114, 58, 2, 490, 488, 3, 2, 2, 2, 490, 489, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 493, 5, 92, 47, 27, 493, 564, 3, 2, 2, 2, 494, 495, 12, 25, 2, 2, 495, 496, 5, 110, 56, 2, 496, 497, 5, 92, 47, 26, 497, 564, 3, 2, 2, 2, 498, 499, 12, 24, 2, 2, 499, 500, 5, 112, 57, 2, 500, 501, 5, 92, 47, 25, 501, 564, 3, 2, 2, 2, 502, 503, 12, 23, 2, 2, 503, 504, 5, 114, 58, 2, 504, 505, 5, 92, 47, 24, 505, 564, 3, 2, 2, 2, 506, 507, 12, 22, 2, 2, 507, 508, 5, 116, 59, 2, 508, 509, 5, 92, 47, 23, 509, 564, 3, 2, 2, 2, 510, 511, 12, 21, 2, 2, 511, 512, 5, 118, 60, 2, 512, 513, 5, 92, 47, 22, 513, 564, 3, 2, 2, 2, 514, 515, 12, 20, 2, 2, 515, 516, 5, 120, 61, 2, 516, 517, 5, 92, 47, 21, 517, 564, 3, 2, 2, 2, 518, 519, 12, 17, 2, 2, 519, 520, 7, 34, 2, 2, 520, 523, 7, 13, 2, 2, 521, 524, 5, 22, 12, 2, 522, 524, 5, 60, 31, 2, 523, 521, 3, 2, 2, 2, 523, 522, 3, 2, 2, 2, 524, 525, 3, 2, 2, 2, 525, 526, 7, 14, 2, 2, 526, 527, 7, 7, 2, 2, 527, 528, 5, 92, 47, 18, 528, 564, 3, 2, 2, 2, 529, 530, 12, 16, 2, 2, 530, 532, 7, 34, 2, 2, 531, 533, 5, 92, 47, 2, 532, 531, 3, 2, 2, 2, 532, 533, 3, 2, 2, 2, 533, 534, 3, 2, 2, 2, 534, 535, 7, 7, 2, 2, 535, 564, 5, 92, 47, 17, 536, 537, 12, 19, 2, 2, 537, 538, 7, 34, 2, 2, 538, 541, 7, 13, 2, 2, 539, 542, 5, 22, 12, 2, 540, 542, 5, 60, 31, 2, 541, 539, 3, 2, 2, 2, 541, 540, 3, 2, 2, 2, 542, 543, 3, 2, 2, 2, 543, 544, 7, 14, 2, 2, 544, 545, 7, 7, 2, 2, 545, 548, 7, 13, 2, 2, 546, 549, 5, 22, 12, 2, 547, 549, 5, 60, 31, 2, 548, 546, 3, 2, 2, 2, 548, 547, 3, 2, 2, 2, 549, 550, 3, 2, 2, 2, 550, 551, 7, 14, 2, 2, 551, 564, 3, 2, 2, 2, 552, 553, 12, 18, 2, 2, 553, 554, 7, 34, 2, 2, 554, 555, 5, 92, 47, 2, 555, 556, 7, 7, 2, 2, 556, 559, 7, 13, 2, 2, 557, 560, 5, 22, 12, 2, 558, 560, 5, 60, 31, 2, 559, 557, 3, 2, 2, 2, 559, 558, 3, 2, 2, 2, 560, 561, 3, 2, 2, 2, 561, 562, 7, 14, 2, 2, 562, 564, 3, 2, 2, 2, 563, 478, 3, 2, 2, 2, 563, 482, 3, 2, 2, 2, 563, 486, 3, 2, 2, 2, 563, 494, 3, 2, 2, 2, 563, 498, 3, 2, 2, 2, 563, 502, 3, 2, 2, 2, 563, 506, 3, 2, 2, 2, 563, 510, 3, 2, 2, 2, 563, 514, 3, 2, 2, 2, 563, 518, 3, 2, 2, 2, 563, 529, 3, 2, 2, 2, 563, 536, 3, 2, 2, 2, 563, 552, 3, 2, 2, 2, 564, 567, 3, 2, 2, 2, 565, 563, 3, 2, 2, 2, 565, 566, 3, 2, 2, 2, 566, 93, 3, 2, 2, 2, 567, 565, 3, 2, 2, 2, 568, 570, 5, 96, 49, 2, 569, 571, 5, 98, 50, 2, 570, 569, 3, 2, 2, 2, 571, 572, 3, 2, 2, 2, 572, 570, 3, 2, 2, 2, 572, 573, 3, 2, 2, 2, 573, 95, 3, 2, 2, 2, 574, 580, 5, 130, 66, 2, 575, 580, 5, 128, 65, 2, 576, 580, 5, 100, 51, 2, 577, 580, 5, 70, 36, 2, 578, 580, 5, 72, 37, 2, 579, 574, 3, 2, 2, 2, 579, 575, 3, 2, 2, 2, 579, 576, 3, 2, 2, 2, 579, 577, 3, 2, 2, 2, 579, 578, 3, 2, 2, 2, 580, 97, 3, 2, 2, 2, 581, 583, 7, 34, 2, 2, 582, 581, 3, 2, 2, 2, 582, 583, 3, 2, 2, 2, 583, 584, 3, 2, 2, 2, 584, 585, 7, 9, 2, 2, 585, 592, 5, 78, 40, 2, 586, 587, 7, 34, 2, 2, 587, 589, 7, 9, 2, 2, 588, 586, 3, 2, 2, 2, 588, 589, 3, 2, 2, 2, 589, 590, 3, 2, 2, 2, 590, 592, 5, 76, 39, 2, 591, 582, 3, 2, 2, 2, 591, 588, 3, 2, 2, 2, 592, 99, 3, 2, 2, 2, 593, 594, 5, 104, 53, 2, 594, 595, 5, 102, 52, 2, 595, 596, 5, 106, 54, 2, 596, 101, 3, 2, 2, 2, 597, 598, 9, 3, 2, 2, 598, 103, 3, 2, 2, 2, 599, 601, 7, 70, 2, 2, 600, 599, 3, 2, 2, 2, 601, 604, 3, 2, 2, 2, 602, 600, 3, 2, 2, 2, 602, 603, 3, 2, 2, 2, 603, 105, 3, 2, 2, 2, 604, 602, 3, 2, 2, 2, 605, 614, 7, 13, 2, 2, 606, 611, 5, 92, 47, 2, 607, 608, 7, 10, 2, 2, 608, 610, 5, 92, 47, 2, 609, 607, 3, 2, 2, 2, 610, 613, 3, 2, 2, 2, 611, 609, 3, 2, 2, 2, 611, 612, 3, 2, 2, 2, 612, 615, 3, 2, 2, 2, 613, 611, 3, 2, 2, 2, 614, 606, 3, 2, 2, 2, 614, 615, 3, 2, 2, 2, 615, 616, 3, 2, 2, 2, 616, 617, 7, 14, 2, 2, 617, 107, 3, 2, 2, 2, 618, 619, 9, 4, 2, 2, 619, 109, 3, 2, 2, 2, 620, 624, 7, 62, 2, 2, 621, 622, 7, 61, 2, 2, 622, 624, 7, 62, 2, 2, 623, 620, 3, 2, 2, 2, 623, 621, 3, 2, 2, 2, 624, 111, 3, 2, 2, 2, 625, 629, 7, 60, 2, 2, 626, 627, 7, 61, 2, 2, 627, 629, 7, 60, 2, 2, 628, 625, 3, 2, 2, 2, 628, 626, 3, 2, 2, 2, 629, 113, 3, 2, 2, 2, 630, 631, 9, 5, 2, 2, 631, 115, 3, 2, 2, 2, 632, 633, 9, 6, 2, 2, 633, 117, 3, 2, 2, 2, 634, 635, 7, 30, 2, 2, 635, 119, 3, 2, 2, 2, 636, 637, 7, 31, 2, 2, 637, 121, 3, 2, 2, 2, 638, 639, 9, 7, 2, 2, 639, 123, 3, 2, 2, 2, 640, 641, 9, 8, 2, 2, 641, 125, 3, 2, 2, 2, 642, 643, 9, 9, 2, 2, 643, 127, 3, 2, 2, 2, 644, 645, 7, 65, 2, 2, 645, 646, 7, 66, 2, 2, 646, 129, 3, 2, 2, 2, 647, 648, 7, 66, 2, 2, 648, 131, 3, 2, 2, 2, 66, 135, 154, 162, 166, 174, 182, 186, 191, 197, 200, 206, 213, 221, 228, 233, 242, 248, 252, 256, 260, 269, 273, 281, 286, 306, 317, 326, 339, 341, 352, 360, 363, 368, 375, 380, 385, 391, 399, 403, 405, 415, 419, 421, 434, 443, 476, 490, 523, 532, 541, 548, 559, 563, 565, 572, 579, 582, 588, 591, 602, 611, 614, 623, 628] \ No newline at end of file diff --git a/pkg/parser/fql/fql_parser.go b/pkg/parser/fql/fql_parser.go index e46d1c2c..58a790d2 100644 --- a/pkg/parser/fql/fql_parser.go +++ b/pkg/parser/fql/fql_parser.go @@ -15,7 +15,7 @@ var _ = reflect.Copy var _ = strconv.Itoa var parserATN = []uint16{ - 3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 71, 651, + 3, 24715, 42794, 33075, 47597, 16764, 15335, 30598, 22884, 3, 71, 650, 4, 2, 9, 2, 4, 3, 9, 3, 4, 4, 9, 4, 4, 5, 9, 5, 4, 6, 9, 6, 4, 7, 9, 7, 4, 8, 9, 8, 4, 9, 9, 9, 4, 10, 9, 10, 4, 11, 9, 11, 4, 12, 9, 12, 4, 13, 9, 13, 4, 14, 9, 14, 4, 15, 9, 15, 4, 16, 9, 16, 4, 17, 9, 17, 4, 18, 9, @@ -53,85 +53,85 @@ var parserATN = []uint16{ 27, 3, 27, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 3, 28, 5, 28, 340, 10, 28, 5, 28, 342, 10, 28, 3, 29, 3, 29, 3, 29, 3, 29, 3, 29, 3, 30, 3, 30, 3, 30, 3, 30, 5, 30, 353, 10, 30, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, 3, 31, - 5, 31, 361, 10, 31, 3, 31, 5, 31, 364, 10, 31, 3, 32, 3, 32, 3, 32, 3, - 32, 5, 32, 370, 10, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 377, - 10, 33, 3, 34, 3, 34, 3, 34, 5, 34, 382, 10, 34, 3, 35, 3, 35, 3, 35, 5, - 35, 387, 10, 35, 3, 35, 3, 35, 3, 35, 3, 35, 5, 35, 393, 10, 35, 3, 36, - 3, 36, 3, 36, 3, 36, 7, 36, 399, 10, 36, 12, 36, 14, 36, 402, 11, 36, 3, - 36, 5, 36, 405, 10, 36, 5, 36, 407, 10, 36, 3, 36, 3, 36, 3, 37, 3, 37, - 3, 37, 3, 37, 7, 37, 415, 10, 37, 12, 37, 14, 37, 418, 11, 37, 3, 37, 5, - 37, 421, 10, 37, 5, 37, 423, 10, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, - 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 3, 38, 5, 38, 436, 10, 38, 3, 39, 3, - 39, 3, 39, 3, 39, 3, 40, 3, 40, 3, 40, 5, 40, 445, 10, 40, 3, 41, 3, 41, - 3, 42, 3, 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, - 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, - 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 478, 10, - 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, - 3, 47, 3, 47, 5, 47, 492, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, - 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, + 5, 31, 361, 10, 31, 3, 31, 5, 31, 364, 10, 31, 3, 32, 3, 32, 3, 32, 5, + 32, 369, 10, 32, 3, 33, 3, 33, 3, 33, 3, 33, 3, 33, 5, 33, 376, 10, 33, + 3, 34, 3, 34, 3, 34, 5, 34, 381, 10, 34, 3, 35, 3, 35, 3, 35, 5, 35, 386, + 10, 35, 3, 35, 3, 35, 3, 35, 3, 35, 5, 35, 392, 10, 35, 3, 36, 3, 36, 3, + 36, 3, 36, 7, 36, 398, 10, 36, 12, 36, 14, 36, 401, 11, 36, 3, 36, 5, 36, + 404, 10, 36, 5, 36, 406, 10, 36, 3, 36, 3, 36, 3, 37, 3, 37, 3, 37, 3, + 37, 7, 37, 414, 10, 37, 12, 37, 14, 37, 417, 11, 37, 3, 37, 5, 37, 420, + 10, 37, 5, 37, 422, 10, 37, 3, 37, 3, 37, 3, 38, 3, 38, 3, 38, 3, 38, 3, + 38, 3, 38, 3, 38, 3, 38, 3, 38, 5, 38, 435, 10, 38, 3, 39, 3, 39, 3, 39, + 3, 39, 3, 40, 3, 40, 3, 40, 5, 40, 444, 10, 40, 3, 41, 3, 41, 3, 42, 3, + 42, 3, 43, 3, 43, 3, 44, 3, 44, 3, 45, 3, 45, 3, 46, 3, 46, 3, 46, 3, 46, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, - 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 525, 10, 47, 3, 47, 3, 47, 3, 47, - 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 534, 10, 47, 3, 47, 3, 47, 3, 47, 3, - 47, 3, 47, 3, 47, 3, 47, 5, 47, 543, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, - 3, 47, 5, 47, 550, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, - 47, 3, 47, 3, 47, 5, 47, 561, 10, 47, 3, 47, 3, 47, 7, 47, 565, 10, 47, - 12, 47, 14, 47, 568, 11, 47, 3, 48, 3, 48, 6, 48, 572, 10, 48, 13, 48, - 14, 48, 573, 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 5, 49, 581, 10, 49, 3, - 50, 5, 50, 584, 10, 50, 3, 50, 3, 50, 3, 50, 3, 50, 5, 50, 590, 10, 50, - 3, 50, 5, 50, 593, 10, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, - 53, 7, 53, 602, 10, 53, 12, 53, 14, 53, 605, 11, 53, 3, 54, 3, 54, 3, 54, - 3, 54, 7, 54, 611, 10, 54, 12, 54, 14, 54, 614, 11, 54, 5, 54, 616, 10, - 54, 3, 54, 3, 54, 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 5, 56, 625, 10, 56, - 3, 57, 3, 57, 3, 57, 5, 57, 630, 10, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, - 60, 3, 60, 3, 61, 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, - 3, 65, 3, 65, 3, 66, 3, 66, 3, 66, 2, 3, 92, 67, 2, 4, 6, 8, 10, 12, 14, - 16, 18, 20, 22, 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, - 52, 54, 56, 58, 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, - 88, 90, 92, 94, 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, - 120, 122, 124, 126, 128, 130, 2, 10, 3, 2, 48, 49, 6, 2, 30, 31, 37, 39, - 41, 62, 66, 66, 4, 2, 48, 48, 56, 57, 3, 2, 17, 22, 3, 2, 35, 36, 3, 2, - 23, 25, 3, 2, 26, 27, 4, 2, 26, 27, 61, 61, 2, 697, 2, 135, 3, 2, 2, 2, - 4, 141, 3, 2, 2, 2, 6, 143, 3, 2, 2, 2, 8, 145, 3, 2, 2, 2, 10, 148, 3, - 2, 2, 2, 12, 154, 3, 2, 2, 2, 14, 162, 3, 2, 2, 2, 16, 166, 3, 2, 2, 2, - 18, 182, 3, 2, 2, 2, 20, 200, 3, 2, 2, 2, 22, 233, 3, 2, 2, 2, 24, 242, - 3, 2, 2, 2, 26, 248, 3, 2, 2, 2, 28, 252, 3, 2, 2, 2, 30, 256, 3, 2, 2, - 2, 32, 260, 3, 2, 2, 2, 34, 262, 3, 2, 2, 2, 36, 265, 3, 2, 2, 2, 38, 273, - 3, 2, 2, 2, 40, 275, 3, 2, 2, 2, 42, 284, 3, 2, 2, 2, 44, 306, 3, 2, 2, - 2, 46, 308, 3, 2, 2, 2, 48, 312, 3, 2, 2, 2, 50, 320, 3, 2, 2, 2, 52, 329, - 3, 2, 2, 2, 54, 341, 3, 2, 2, 2, 56, 343, 3, 2, 2, 2, 58, 352, 3, 2, 2, - 2, 60, 354, 3, 2, 2, 2, 62, 369, 3, 2, 2, 2, 64, 376, 3, 2, 2, 2, 66, 381, - 3, 2, 2, 2, 68, 386, 3, 2, 2, 2, 70, 394, 3, 2, 2, 2, 72, 410, 3, 2, 2, - 2, 74, 435, 3, 2, 2, 2, 76, 437, 3, 2, 2, 2, 78, 444, 3, 2, 2, 2, 80, 446, - 3, 2, 2, 2, 82, 448, 3, 2, 2, 2, 84, 450, 3, 2, 2, 2, 86, 452, 3, 2, 2, - 2, 88, 454, 3, 2, 2, 2, 90, 456, 3, 2, 2, 2, 92, 477, 3, 2, 2, 2, 94, 569, - 3, 2, 2, 2, 96, 580, 3, 2, 2, 2, 98, 592, 3, 2, 2, 2, 100, 594, 3, 2, 2, - 2, 102, 598, 3, 2, 2, 2, 104, 603, 3, 2, 2, 2, 106, 606, 3, 2, 2, 2, 108, - 619, 3, 2, 2, 2, 110, 624, 3, 2, 2, 2, 112, 629, 3, 2, 2, 2, 114, 631, - 3, 2, 2, 2, 116, 633, 3, 2, 2, 2, 118, 635, 3, 2, 2, 2, 120, 637, 3, 2, - 2, 2, 122, 639, 3, 2, 2, 2, 124, 641, 3, 2, 2, 2, 126, 643, 3, 2, 2, 2, - 128, 645, 3, 2, 2, 2, 130, 648, 3, 2, 2, 2, 132, 134, 5, 4, 3, 2, 133, - 132, 3, 2, 2, 2, 134, 137, 3, 2, 2, 2, 135, 133, 3, 2, 2, 2, 135, 136, - 3, 2, 2, 2, 136, 138, 3, 2, 2, 2, 137, 135, 3, 2, 2, 2, 138, 139, 5, 12, - 7, 2, 139, 140, 7, 2, 2, 3, 140, 3, 3, 2, 2, 2, 141, 142, 5, 6, 4, 2, 142, - 5, 3, 2, 2, 2, 143, 144, 5, 8, 5, 2, 144, 7, 3, 2, 2, 2, 145, 146, 7, 51, - 2, 2, 146, 147, 5, 10, 6, 2, 147, 9, 3, 2, 2, 2, 148, 149, 5, 104, 53, - 2, 149, 150, 7, 66, 2, 2, 150, 11, 3, 2, 2, 2, 151, 153, 5, 14, 8, 2, 152, - 151, 3, 2, 2, 2, 153, 156, 3, 2, 2, 2, 154, 152, 3, 2, 2, 2, 154, 155, - 3, 2, 2, 2, 155, 157, 3, 2, 2, 2, 156, 154, 3, 2, 2, 2, 157, 158, 5, 16, - 9, 2, 158, 13, 3, 2, 2, 2, 159, 163, 5, 18, 10, 2, 160, 163, 5, 100, 51, - 2, 161, 163, 5, 60, 31, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, - 162, 161, 3, 2, 2, 2, 163, 15, 3, 2, 2, 2, 164, 167, 5, 20, 11, 2, 165, - 167, 5, 22, 12, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 17, - 3, 2, 2, 2, 168, 169, 7, 45, 2, 2, 169, 170, 7, 66, 2, 2, 170, 171, 7, - 33, 2, 2, 171, 174, 7, 13, 2, 2, 172, 175, 5, 22, 12, 2, 173, 175, 5, 60, - 31, 2, 174, 172, 3, 2, 2, 2, 174, 173, 3, 2, 2, 2, 175, 176, 3, 2, 2, 2, - 176, 177, 7, 14, 2, 2, 177, 183, 3, 2, 2, 2, 178, 179, 7, 45, 2, 2, 179, - 180, 7, 66, 2, 2, 180, 181, 7, 33, 2, 2, 181, 183, 5, 92, 47, 2, 182, 168, - 3, 2, 2, 2, 182, 178, 3, 2, 2, 2, 183, 19, 3, 2, 2, 2, 184, 186, 7, 38, - 2, 2, 185, 187, 7, 41, 2, 2, 186, 185, 3, 2, 2, 2, 186, 187, 3, 2, 2, 2, - 187, 188, 3, 2, 2, 2, 188, 191, 7, 13, 2, 2, 189, 192, 5, 22, 12, 2, 190, - 192, 5, 60, 31, 2, 191, 189, 3, 2, 2, 2, 191, 190, 3, 2, 2, 2, 192, 193, - 3, 2, 2, 2, 193, 194, 7, 14, 2, 2, 194, 201, 3, 2, 2, 2, 195, 197, 7, 38, + 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, 47, 477, 10, 47, 3, 47, + 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, + 47, 5, 47, 491, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, + 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, + 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, + 3, 47, 3, 47, 3, 47, 5, 47, 524, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, + 47, 3, 47, 3, 47, 5, 47, 533, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, + 3, 47, 3, 47, 5, 47, 542, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 5, + 47, 549, 10, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, 3, 47, + 3, 47, 5, 47, 560, 10, 47, 3, 47, 3, 47, 7, 47, 564, 10, 47, 12, 47, 14, + 47, 567, 11, 47, 3, 48, 3, 48, 6, 48, 571, 10, 48, 13, 48, 14, 48, 572, + 3, 49, 3, 49, 3, 49, 3, 49, 3, 49, 5, 49, 580, 10, 49, 3, 50, 5, 50, 583, + 10, 50, 3, 50, 3, 50, 3, 50, 3, 50, 5, 50, 589, 10, 50, 3, 50, 5, 50, 592, + 10, 50, 3, 51, 3, 51, 3, 51, 3, 51, 3, 52, 3, 52, 3, 53, 7, 53, 601, 10, + 53, 12, 53, 14, 53, 604, 11, 53, 3, 54, 3, 54, 3, 54, 3, 54, 7, 54, 610, + 10, 54, 12, 54, 14, 54, 613, 11, 54, 5, 54, 615, 10, 54, 3, 54, 3, 54, + 3, 55, 3, 55, 3, 56, 3, 56, 3, 56, 5, 56, 624, 10, 56, 3, 57, 3, 57, 3, + 57, 5, 57, 629, 10, 57, 3, 58, 3, 58, 3, 59, 3, 59, 3, 60, 3, 60, 3, 61, + 3, 61, 3, 62, 3, 62, 3, 63, 3, 63, 3, 64, 3, 64, 3, 65, 3, 65, 3, 65, 3, + 66, 3, 66, 3, 66, 2, 3, 92, 67, 2, 4, 6, 8, 10, 12, 14, 16, 18, 20, 22, + 24, 26, 28, 30, 32, 34, 36, 38, 40, 42, 44, 46, 48, 50, 52, 54, 56, 58, + 60, 62, 64, 66, 68, 70, 72, 74, 76, 78, 80, 82, 84, 86, 88, 90, 92, 94, + 96, 98, 100, 102, 104, 106, 108, 110, 112, 114, 116, 118, 120, 122, 124, + 126, 128, 130, 2, 10, 3, 2, 48, 49, 6, 2, 30, 31, 37, 39, 41, 62, 66, 66, + 4, 2, 48, 48, 56, 57, 3, 2, 17, 22, 3, 2, 35, 36, 3, 2, 23, 25, 3, 2, 26, + 27, 4, 2, 26, 27, 61, 61, 2, 695, 2, 135, 3, 2, 2, 2, 4, 141, 3, 2, 2, + 2, 6, 143, 3, 2, 2, 2, 8, 145, 3, 2, 2, 2, 10, 148, 3, 2, 2, 2, 12, 154, + 3, 2, 2, 2, 14, 162, 3, 2, 2, 2, 16, 166, 3, 2, 2, 2, 18, 182, 3, 2, 2, + 2, 20, 200, 3, 2, 2, 2, 22, 233, 3, 2, 2, 2, 24, 242, 3, 2, 2, 2, 26, 248, + 3, 2, 2, 2, 28, 252, 3, 2, 2, 2, 30, 256, 3, 2, 2, 2, 32, 260, 3, 2, 2, + 2, 34, 262, 3, 2, 2, 2, 36, 265, 3, 2, 2, 2, 38, 273, 3, 2, 2, 2, 40, 275, + 3, 2, 2, 2, 42, 284, 3, 2, 2, 2, 44, 306, 3, 2, 2, 2, 46, 308, 3, 2, 2, + 2, 48, 312, 3, 2, 2, 2, 50, 320, 3, 2, 2, 2, 52, 329, 3, 2, 2, 2, 54, 341, + 3, 2, 2, 2, 56, 343, 3, 2, 2, 2, 58, 352, 3, 2, 2, 2, 60, 354, 3, 2, 2, + 2, 62, 368, 3, 2, 2, 2, 64, 375, 3, 2, 2, 2, 66, 380, 3, 2, 2, 2, 68, 385, + 3, 2, 2, 2, 70, 393, 3, 2, 2, 2, 72, 409, 3, 2, 2, 2, 74, 434, 3, 2, 2, + 2, 76, 436, 3, 2, 2, 2, 78, 443, 3, 2, 2, 2, 80, 445, 3, 2, 2, 2, 82, 447, + 3, 2, 2, 2, 84, 449, 3, 2, 2, 2, 86, 451, 3, 2, 2, 2, 88, 453, 3, 2, 2, + 2, 90, 455, 3, 2, 2, 2, 92, 476, 3, 2, 2, 2, 94, 568, 3, 2, 2, 2, 96, 579, + 3, 2, 2, 2, 98, 591, 3, 2, 2, 2, 100, 593, 3, 2, 2, 2, 102, 597, 3, 2, + 2, 2, 104, 602, 3, 2, 2, 2, 106, 605, 3, 2, 2, 2, 108, 618, 3, 2, 2, 2, + 110, 623, 3, 2, 2, 2, 112, 628, 3, 2, 2, 2, 114, 630, 3, 2, 2, 2, 116, + 632, 3, 2, 2, 2, 118, 634, 3, 2, 2, 2, 120, 636, 3, 2, 2, 2, 122, 638, + 3, 2, 2, 2, 124, 640, 3, 2, 2, 2, 126, 642, 3, 2, 2, 2, 128, 644, 3, 2, + 2, 2, 130, 647, 3, 2, 2, 2, 132, 134, 5, 4, 3, 2, 133, 132, 3, 2, 2, 2, + 134, 137, 3, 2, 2, 2, 135, 133, 3, 2, 2, 2, 135, 136, 3, 2, 2, 2, 136, + 138, 3, 2, 2, 2, 137, 135, 3, 2, 2, 2, 138, 139, 5, 12, 7, 2, 139, 140, + 7, 2, 2, 3, 140, 3, 3, 2, 2, 2, 141, 142, 5, 6, 4, 2, 142, 5, 3, 2, 2, + 2, 143, 144, 5, 8, 5, 2, 144, 7, 3, 2, 2, 2, 145, 146, 7, 51, 2, 2, 146, + 147, 5, 10, 6, 2, 147, 9, 3, 2, 2, 2, 148, 149, 5, 104, 53, 2, 149, 150, + 7, 66, 2, 2, 150, 11, 3, 2, 2, 2, 151, 153, 5, 14, 8, 2, 152, 151, 3, 2, + 2, 2, 153, 156, 3, 2, 2, 2, 154, 152, 3, 2, 2, 2, 154, 155, 3, 2, 2, 2, + 155, 157, 3, 2, 2, 2, 156, 154, 3, 2, 2, 2, 157, 158, 5, 16, 9, 2, 158, + 13, 3, 2, 2, 2, 159, 163, 5, 18, 10, 2, 160, 163, 5, 100, 51, 2, 161, 163, + 5, 60, 31, 2, 162, 159, 3, 2, 2, 2, 162, 160, 3, 2, 2, 2, 162, 161, 3, + 2, 2, 2, 163, 15, 3, 2, 2, 2, 164, 167, 5, 20, 11, 2, 165, 167, 5, 22, + 12, 2, 166, 164, 3, 2, 2, 2, 166, 165, 3, 2, 2, 2, 167, 17, 3, 2, 2, 2, + 168, 169, 7, 45, 2, 2, 169, 170, 7, 66, 2, 2, 170, 171, 7, 33, 2, 2, 171, + 174, 7, 13, 2, 2, 172, 175, 5, 22, 12, 2, 173, 175, 5, 60, 31, 2, 174, + 172, 3, 2, 2, 2, 174, 173, 3, 2, 2, 2, 175, 176, 3, 2, 2, 2, 176, 177, + 7, 14, 2, 2, 177, 183, 3, 2, 2, 2, 178, 179, 7, 45, 2, 2, 179, 180, 7, + 66, 2, 2, 180, 181, 7, 33, 2, 2, 181, 183, 5, 92, 47, 2, 182, 168, 3, 2, + 2, 2, 182, 178, 3, 2, 2, 2, 183, 19, 3, 2, 2, 2, 184, 186, 7, 38, 2, 2, + 185, 187, 7, 41, 2, 2, 186, 185, 3, 2, 2, 2, 186, 187, 3, 2, 2, 2, 187, + 188, 3, 2, 2, 2, 188, 191, 7, 13, 2, 2, 189, 192, 5, 22, 12, 2, 190, 192, + 5, 60, 31, 2, 191, 189, 3, 2, 2, 2, 191, 190, 3, 2, 2, 2, 192, 193, 3, + 2, 2, 2, 193, 194, 7, 14, 2, 2, 194, 201, 3, 2, 2, 2, 195, 197, 7, 38, 2, 2, 196, 198, 7, 41, 2, 2, 197, 196, 3, 2, 2, 2, 197, 198, 3, 2, 2, 2, 198, 199, 3, 2, 2, 2, 199, 201, 5, 92, 47, 2, 200, 184, 3, 2, 2, 2, 200, 195, 3, 2, 2, 2, 201, 21, 3, 2, 2, 2, 202, 203, 7, 37, 2, 2, 203, 206, @@ -196,122 +196,122 @@ var parserATN = []uint16{ 62, 2, 2, 358, 360, 5, 66, 34, 2, 359, 361, 5, 58, 30, 2, 360, 359, 3, 2, 2, 2, 360, 361, 3, 2, 2, 2, 361, 363, 3, 2, 2, 2, 362, 364, 5, 62, 32, 2, 363, 362, 3, 2, 2, 2, 363, 364, 3, 2, 2, 2, 364, 61, 3, 2, 2, 2, 365, - 370, 5, 86, 44, 2, 366, 370, 5, 130, 66, 2, 367, 370, 5, 100, 51, 2, 368, - 370, 5, 94, 48, 2, 369, 365, 3, 2, 2, 2, 369, 366, 3, 2, 2, 2, 369, 367, - 3, 2, 2, 2, 369, 368, 3, 2, 2, 2, 370, 63, 3, 2, 2, 2, 371, 377, 5, 82, - 42, 2, 372, 377, 5, 130, 66, 2, 373, 377, 5, 128, 65, 2, 374, 377, 5, 100, - 51, 2, 375, 377, 5, 94, 48, 2, 376, 371, 3, 2, 2, 2, 376, 372, 3, 2, 2, - 2, 376, 373, 3, 2, 2, 2, 376, 374, 3, 2, 2, 2, 376, 375, 3, 2, 2, 2, 377, - 65, 3, 2, 2, 2, 378, 382, 5, 100, 51, 2, 379, 382, 5, 130, 66, 2, 380, - 382, 5, 94, 48, 2, 381, 378, 3, 2, 2, 2, 381, 379, 3, 2, 2, 2, 381, 380, - 3, 2, 2, 2, 382, 67, 3, 2, 2, 2, 383, 387, 5, 86, 44, 2, 384, 387, 5, 130, - 66, 2, 385, 387, 5, 128, 65, 2, 386, 383, 3, 2, 2, 2, 386, 384, 3, 2, 2, - 2, 386, 385, 3, 2, 2, 2, 387, 388, 3, 2, 2, 2, 388, 392, 7, 32, 2, 2, 389, - 393, 5, 86, 44, 2, 390, 393, 5, 130, 66, 2, 391, 393, 5, 128, 65, 2, 392, - 389, 3, 2, 2, 2, 392, 390, 3, 2, 2, 2, 392, 391, 3, 2, 2, 2, 393, 69, 3, - 2, 2, 2, 394, 406, 7, 11, 2, 2, 395, 400, 5, 92, 47, 2, 396, 397, 7, 10, - 2, 2, 397, 399, 5, 92, 47, 2, 398, 396, 3, 2, 2, 2, 399, 402, 3, 2, 2, - 2, 400, 398, 3, 2, 2, 2, 400, 401, 3, 2, 2, 2, 401, 404, 3, 2, 2, 2, 402, - 400, 3, 2, 2, 2, 403, 405, 7, 10, 2, 2, 404, 403, 3, 2, 2, 2, 404, 405, - 3, 2, 2, 2, 405, 407, 3, 2, 2, 2, 406, 395, 3, 2, 2, 2, 406, 407, 3, 2, - 2, 2, 407, 408, 3, 2, 2, 2, 408, 409, 7, 12, 2, 2, 409, 71, 3, 2, 2, 2, - 410, 422, 7, 15, 2, 2, 411, 416, 5, 74, 38, 2, 412, 413, 7, 10, 2, 2, 413, - 415, 5, 74, 38, 2, 414, 412, 3, 2, 2, 2, 415, 418, 3, 2, 2, 2, 416, 414, - 3, 2, 2, 2, 416, 417, 3, 2, 2, 2, 417, 420, 3, 2, 2, 2, 418, 416, 3, 2, - 2, 2, 419, 421, 7, 10, 2, 2, 420, 419, 3, 2, 2, 2, 420, 421, 3, 2, 2, 2, - 421, 423, 3, 2, 2, 2, 422, 411, 3, 2, 2, 2, 422, 423, 3, 2, 2, 2, 423, - 424, 3, 2, 2, 2, 424, 425, 7, 16, 2, 2, 425, 73, 3, 2, 2, 2, 426, 427, - 5, 78, 40, 2, 427, 428, 7, 7, 2, 2, 428, 429, 5, 92, 47, 2, 429, 436, 3, - 2, 2, 2, 430, 431, 5, 76, 39, 2, 431, 432, 7, 7, 2, 2, 432, 433, 5, 92, - 47, 2, 433, 436, 3, 2, 2, 2, 434, 436, 5, 130, 66, 2, 435, 426, 3, 2, 2, - 2, 435, 430, 3, 2, 2, 2, 435, 434, 3, 2, 2, 2, 436, 75, 3, 2, 2, 2, 437, - 438, 7, 11, 2, 2, 438, 439, 5, 92, 47, 2, 439, 440, 7, 12, 2, 2, 440, 77, - 3, 2, 2, 2, 441, 445, 7, 66, 2, 2, 442, 445, 5, 82, 42, 2, 443, 445, 5, - 128, 65, 2, 444, 441, 3, 2, 2, 2, 444, 442, 3, 2, 2, 2, 444, 443, 3, 2, - 2, 2, 445, 79, 3, 2, 2, 2, 446, 447, 7, 50, 2, 2, 447, 81, 3, 2, 2, 2, - 448, 449, 7, 67, 2, 2, 449, 83, 3, 2, 2, 2, 450, 451, 7, 69, 2, 2, 451, - 85, 3, 2, 2, 2, 452, 453, 7, 68, 2, 2, 453, 87, 3, 2, 2, 2, 454, 455, 9, - 2, 2, 2, 455, 89, 3, 2, 2, 2, 456, 457, 7, 13, 2, 2, 457, 458, 5, 92, 47, - 2, 458, 459, 7, 14, 2, 2, 459, 91, 3, 2, 2, 2, 460, 461, 8, 47, 1, 2, 461, - 462, 5, 126, 64, 2, 462, 463, 5, 92, 47, 29, 463, 478, 3, 2, 2, 2, 464, - 478, 5, 68, 35, 2, 465, 478, 5, 82, 42, 2, 466, 478, 5, 84, 43, 2, 467, - 478, 5, 86, 44, 2, 468, 478, 5, 80, 41, 2, 469, 478, 5, 70, 36, 2, 470, - 478, 5, 72, 37, 2, 471, 478, 5, 94, 48, 2, 472, 478, 5, 100, 51, 2, 473, - 478, 5, 128, 65, 2, 474, 478, 5, 130, 66, 2, 475, 478, 5, 88, 45, 2, 476, - 478, 5, 90, 46, 2, 477, 460, 3, 2, 2, 2, 477, 464, 3, 2, 2, 2, 477, 465, - 3, 2, 2, 2, 477, 466, 3, 2, 2, 2, 477, 467, 3, 2, 2, 2, 477, 468, 3, 2, - 2, 2, 477, 469, 3, 2, 2, 2, 477, 470, 3, 2, 2, 2, 477, 471, 3, 2, 2, 2, - 477, 472, 3, 2, 2, 2, 477, 473, 3, 2, 2, 2, 477, 474, 3, 2, 2, 2, 477, - 475, 3, 2, 2, 2, 477, 476, 3, 2, 2, 2, 478, 566, 3, 2, 2, 2, 479, 480, - 12, 28, 2, 2, 480, 481, 5, 122, 62, 2, 481, 482, 5, 92, 47, 29, 482, 565, - 3, 2, 2, 2, 483, 484, 12, 27, 2, 2, 484, 485, 5, 124, 63, 2, 485, 486, - 5, 92, 47, 28, 486, 565, 3, 2, 2, 2, 487, 488, 12, 26, 2, 2, 488, 491, - 5, 108, 55, 2, 489, 492, 5, 110, 56, 2, 490, 492, 5, 114, 58, 2, 491, 489, - 3, 2, 2, 2, 491, 490, 3, 2, 2, 2, 492, 493, 3, 2, 2, 2, 493, 494, 5, 92, - 47, 27, 494, 565, 3, 2, 2, 2, 495, 496, 12, 25, 2, 2, 496, 497, 5, 110, - 56, 2, 497, 498, 5, 92, 47, 26, 498, 565, 3, 2, 2, 2, 499, 500, 12, 24, - 2, 2, 500, 501, 5, 112, 57, 2, 501, 502, 5, 92, 47, 25, 502, 565, 3, 2, - 2, 2, 503, 504, 12, 23, 2, 2, 504, 505, 5, 114, 58, 2, 505, 506, 5, 92, - 47, 24, 506, 565, 3, 2, 2, 2, 507, 508, 12, 22, 2, 2, 508, 509, 5, 116, - 59, 2, 509, 510, 5, 92, 47, 23, 510, 565, 3, 2, 2, 2, 511, 512, 12, 21, - 2, 2, 512, 513, 5, 118, 60, 2, 513, 514, 5, 92, 47, 22, 514, 565, 3, 2, - 2, 2, 515, 516, 12, 20, 2, 2, 516, 517, 5, 120, 61, 2, 517, 518, 5, 92, - 47, 21, 518, 565, 3, 2, 2, 2, 519, 520, 12, 17, 2, 2, 520, 521, 7, 34, - 2, 2, 521, 524, 7, 13, 2, 2, 522, 525, 5, 22, 12, 2, 523, 525, 5, 60, 31, - 2, 524, 522, 3, 2, 2, 2, 524, 523, 3, 2, 2, 2, 525, 526, 3, 2, 2, 2, 526, - 527, 7, 14, 2, 2, 527, 528, 7, 7, 2, 2, 528, 529, 5, 92, 47, 18, 529, 565, - 3, 2, 2, 2, 530, 531, 12, 16, 2, 2, 531, 533, 7, 34, 2, 2, 532, 534, 5, - 92, 47, 2, 533, 532, 3, 2, 2, 2, 533, 534, 3, 2, 2, 2, 534, 535, 3, 2, - 2, 2, 535, 536, 7, 7, 2, 2, 536, 565, 5, 92, 47, 17, 537, 538, 12, 19, - 2, 2, 538, 539, 7, 34, 2, 2, 539, 542, 7, 13, 2, 2, 540, 543, 5, 22, 12, - 2, 541, 543, 5, 60, 31, 2, 542, 540, 3, 2, 2, 2, 542, 541, 3, 2, 2, 2, - 543, 544, 3, 2, 2, 2, 544, 545, 7, 14, 2, 2, 545, 546, 7, 7, 2, 2, 546, - 549, 7, 13, 2, 2, 547, 550, 5, 22, 12, 2, 548, 550, 5, 60, 31, 2, 549, - 547, 3, 2, 2, 2, 549, 548, 3, 2, 2, 2, 550, 551, 3, 2, 2, 2, 551, 552, - 7, 14, 2, 2, 552, 565, 3, 2, 2, 2, 553, 554, 12, 18, 2, 2, 554, 555, 7, - 34, 2, 2, 555, 556, 5, 92, 47, 2, 556, 557, 7, 7, 2, 2, 557, 560, 7, 13, - 2, 2, 558, 561, 5, 22, 12, 2, 559, 561, 5, 60, 31, 2, 560, 558, 3, 2, 2, - 2, 560, 559, 3, 2, 2, 2, 561, 562, 3, 2, 2, 2, 562, 563, 7, 14, 2, 2, 563, - 565, 3, 2, 2, 2, 564, 479, 3, 2, 2, 2, 564, 483, 3, 2, 2, 2, 564, 487, - 3, 2, 2, 2, 564, 495, 3, 2, 2, 2, 564, 499, 3, 2, 2, 2, 564, 503, 3, 2, - 2, 2, 564, 507, 3, 2, 2, 2, 564, 511, 3, 2, 2, 2, 564, 515, 3, 2, 2, 2, - 564, 519, 3, 2, 2, 2, 564, 530, 3, 2, 2, 2, 564, 537, 3, 2, 2, 2, 564, - 553, 3, 2, 2, 2, 565, 568, 3, 2, 2, 2, 566, 564, 3, 2, 2, 2, 566, 567, - 3, 2, 2, 2, 567, 93, 3, 2, 2, 2, 568, 566, 3, 2, 2, 2, 569, 571, 5, 96, - 49, 2, 570, 572, 5, 98, 50, 2, 571, 570, 3, 2, 2, 2, 572, 573, 3, 2, 2, - 2, 573, 571, 3, 2, 2, 2, 573, 574, 3, 2, 2, 2, 574, 95, 3, 2, 2, 2, 575, - 581, 5, 130, 66, 2, 576, 581, 5, 128, 65, 2, 577, 581, 5, 100, 51, 2, 578, - 581, 5, 70, 36, 2, 579, 581, 5, 72, 37, 2, 580, 575, 3, 2, 2, 2, 580, 576, - 3, 2, 2, 2, 580, 577, 3, 2, 2, 2, 580, 578, 3, 2, 2, 2, 580, 579, 3, 2, - 2, 2, 581, 97, 3, 2, 2, 2, 582, 584, 7, 34, 2, 2, 583, 582, 3, 2, 2, 2, - 583, 584, 3, 2, 2, 2, 584, 585, 3, 2, 2, 2, 585, 586, 7, 9, 2, 2, 586, - 593, 5, 78, 40, 2, 587, 588, 7, 34, 2, 2, 588, 590, 7, 9, 2, 2, 589, 587, - 3, 2, 2, 2, 589, 590, 3, 2, 2, 2, 590, 591, 3, 2, 2, 2, 591, 593, 5, 76, - 39, 2, 592, 583, 3, 2, 2, 2, 592, 589, 3, 2, 2, 2, 593, 99, 3, 2, 2, 2, - 594, 595, 5, 104, 53, 2, 595, 596, 5, 102, 52, 2, 596, 597, 5, 106, 54, - 2, 597, 101, 3, 2, 2, 2, 598, 599, 9, 3, 2, 2, 599, 103, 3, 2, 2, 2, 600, - 602, 7, 70, 2, 2, 601, 600, 3, 2, 2, 2, 602, 605, 3, 2, 2, 2, 603, 601, - 3, 2, 2, 2, 603, 604, 3, 2, 2, 2, 604, 105, 3, 2, 2, 2, 605, 603, 3, 2, - 2, 2, 606, 615, 7, 13, 2, 2, 607, 612, 5, 92, 47, 2, 608, 609, 7, 10, 2, - 2, 609, 611, 5, 92, 47, 2, 610, 608, 3, 2, 2, 2, 611, 614, 3, 2, 2, 2, - 612, 610, 3, 2, 2, 2, 612, 613, 3, 2, 2, 2, 613, 616, 3, 2, 2, 2, 614, - 612, 3, 2, 2, 2, 615, 607, 3, 2, 2, 2, 615, 616, 3, 2, 2, 2, 616, 617, - 3, 2, 2, 2, 617, 618, 7, 14, 2, 2, 618, 107, 3, 2, 2, 2, 619, 620, 9, 4, - 2, 2, 620, 109, 3, 2, 2, 2, 621, 625, 7, 62, 2, 2, 622, 623, 7, 61, 2, - 2, 623, 625, 7, 62, 2, 2, 624, 621, 3, 2, 2, 2, 624, 622, 3, 2, 2, 2, 625, - 111, 3, 2, 2, 2, 626, 630, 7, 60, 2, 2, 627, 628, 7, 61, 2, 2, 628, 630, - 7, 60, 2, 2, 629, 626, 3, 2, 2, 2, 629, 627, 3, 2, 2, 2, 630, 113, 3, 2, - 2, 2, 631, 632, 9, 5, 2, 2, 632, 115, 3, 2, 2, 2, 633, 634, 9, 6, 2, 2, - 634, 117, 3, 2, 2, 2, 635, 636, 7, 30, 2, 2, 636, 119, 3, 2, 2, 2, 637, - 638, 7, 31, 2, 2, 638, 121, 3, 2, 2, 2, 639, 640, 9, 7, 2, 2, 640, 123, - 3, 2, 2, 2, 641, 642, 9, 8, 2, 2, 642, 125, 3, 2, 2, 2, 643, 644, 9, 9, - 2, 2, 644, 127, 3, 2, 2, 2, 645, 646, 7, 65, 2, 2, 646, 647, 7, 66, 2, - 2, 647, 129, 3, 2, 2, 2, 648, 649, 7, 66, 2, 2, 649, 131, 3, 2, 2, 2, 66, - 135, 154, 162, 166, 174, 182, 186, 191, 197, 200, 206, 213, 221, 228, 233, - 242, 248, 252, 256, 260, 269, 273, 281, 286, 306, 317, 326, 339, 341, 352, - 360, 363, 369, 376, 381, 386, 392, 400, 404, 406, 416, 420, 422, 435, 444, - 477, 491, 524, 533, 542, 549, 560, 564, 566, 573, 580, 583, 589, 592, 603, - 612, 615, 624, 629, + 369, 5, 86, 44, 2, 366, 369, 5, 130, 66, 2, 367, 369, 5, 128, 65, 2, 368, + 365, 3, 2, 2, 2, 368, 366, 3, 2, 2, 2, 368, 367, 3, 2, 2, 2, 369, 63, 3, + 2, 2, 2, 370, 376, 5, 82, 42, 2, 371, 376, 5, 130, 66, 2, 372, 376, 5, + 128, 65, 2, 373, 376, 5, 100, 51, 2, 374, 376, 5, 94, 48, 2, 375, 370, + 3, 2, 2, 2, 375, 371, 3, 2, 2, 2, 375, 372, 3, 2, 2, 2, 375, 373, 3, 2, + 2, 2, 375, 374, 3, 2, 2, 2, 376, 65, 3, 2, 2, 2, 377, 381, 5, 100, 51, + 2, 378, 381, 5, 130, 66, 2, 379, 381, 5, 94, 48, 2, 380, 377, 3, 2, 2, + 2, 380, 378, 3, 2, 2, 2, 380, 379, 3, 2, 2, 2, 381, 67, 3, 2, 2, 2, 382, + 386, 5, 86, 44, 2, 383, 386, 5, 130, 66, 2, 384, 386, 5, 128, 65, 2, 385, + 382, 3, 2, 2, 2, 385, 383, 3, 2, 2, 2, 385, 384, 3, 2, 2, 2, 386, 387, + 3, 2, 2, 2, 387, 391, 7, 32, 2, 2, 388, 392, 5, 86, 44, 2, 389, 392, 5, + 130, 66, 2, 390, 392, 5, 128, 65, 2, 391, 388, 3, 2, 2, 2, 391, 389, 3, + 2, 2, 2, 391, 390, 3, 2, 2, 2, 392, 69, 3, 2, 2, 2, 393, 405, 7, 11, 2, + 2, 394, 399, 5, 92, 47, 2, 395, 396, 7, 10, 2, 2, 396, 398, 5, 92, 47, + 2, 397, 395, 3, 2, 2, 2, 398, 401, 3, 2, 2, 2, 399, 397, 3, 2, 2, 2, 399, + 400, 3, 2, 2, 2, 400, 403, 3, 2, 2, 2, 401, 399, 3, 2, 2, 2, 402, 404, + 7, 10, 2, 2, 403, 402, 3, 2, 2, 2, 403, 404, 3, 2, 2, 2, 404, 406, 3, 2, + 2, 2, 405, 394, 3, 2, 2, 2, 405, 406, 3, 2, 2, 2, 406, 407, 3, 2, 2, 2, + 407, 408, 7, 12, 2, 2, 408, 71, 3, 2, 2, 2, 409, 421, 7, 15, 2, 2, 410, + 415, 5, 74, 38, 2, 411, 412, 7, 10, 2, 2, 412, 414, 5, 74, 38, 2, 413, + 411, 3, 2, 2, 2, 414, 417, 3, 2, 2, 2, 415, 413, 3, 2, 2, 2, 415, 416, + 3, 2, 2, 2, 416, 419, 3, 2, 2, 2, 417, 415, 3, 2, 2, 2, 418, 420, 7, 10, + 2, 2, 419, 418, 3, 2, 2, 2, 419, 420, 3, 2, 2, 2, 420, 422, 3, 2, 2, 2, + 421, 410, 3, 2, 2, 2, 421, 422, 3, 2, 2, 2, 422, 423, 3, 2, 2, 2, 423, + 424, 7, 16, 2, 2, 424, 73, 3, 2, 2, 2, 425, 426, 5, 78, 40, 2, 426, 427, + 7, 7, 2, 2, 427, 428, 5, 92, 47, 2, 428, 435, 3, 2, 2, 2, 429, 430, 5, + 76, 39, 2, 430, 431, 7, 7, 2, 2, 431, 432, 5, 92, 47, 2, 432, 435, 3, 2, + 2, 2, 433, 435, 5, 130, 66, 2, 434, 425, 3, 2, 2, 2, 434, 429, 3, 2, 2, + 2, 434, 433, 3, 2, 2, 2, 435, 75, 3, 2, 2, 2, 436, 437, 7, 11, 2, 2, 437, + 438, 5, 92, 47, 2, 438, 439, 7, 12, 2, 2, 439, 77, 3, 2, 2, 2, 440, 444, + 7, 66, 2, 2, 441, 444, 5, 82, 42, 2, 442, 444, 5, 128, 65, 2, 443, 440, + 3, 2, 2, 2, 443, 441, 3, 2, 2, 2, 443, 442, 3, 2, 2, 2, 444, 79, 3, 2, + 2, 2, 445, 446, 7, 50, 2, 2, 446, 81, 3, 2, 2, 2, 447, 448, 7, 67, 2, 2, + 448, 83, 3, 2, 2, 2, 449, 450, 7, 69, 2, 2, 450, 85, 3, 2, 2, 2, 451, 452, + 7, 68, 2, 2, 452, 87, 3, 2, 2, 2, 453, 454, 9, 2, 2, 2, 454, 89, 3, 2, + 2, 2, 455, 456, 7, 13, 2, 2, 456, 457, 5, 92, 47, 2, 457, 458, 7, 14, 2, + 2, 458, 91, 3, 2, 2, 2, 459, 460, 8, 47, 1, 2, 460, 461, 5, 126, 64, 2, + 461, 462, 5, 92, 47, 29, 462, 477, 3, 2, 2, 2, 463, 477, 5, 68, 35, 2, + 464, 477, 5, 82, 42, 2, 465, 477, 5, 84, 43, 2, 466, 477, 5, 86, 44, 2, + 467, 477, 5, 80, 41, 2, 468, 477, 5, 70, 36, 2, 469, 477, 5, 72, 37, 2, + 470, 477, 5, 94, 48, 2, 471, 477, 5, 100, 51, 2, 472, 477, 5, 128, 65, + 2, 473, 477, 5, 130, 66, 2, 474, 477, 5, 88, 45, 2, 475, 477, 5, 90, 46, + 2, 476, 459, 3, 2, 2, 2, 476, 463, 3, 2, 2, 2, 476, 464, 3, 2, 2, 2, 476, + 465, 3, 2, 2, 2, 476, 466, 3, 2, 2, 2, 476, 467, 3, 2, 2, 2, 476, 468, + 3, 2, 2, 2, 476, 469, 3, 2, 2, 2, 476, 470, 3, 2, 2, 2, 476, 471, 3, 2, + 2, 2, 476, 472, 3, 2, 2, 2, 476, 473, 3, 2, 2, 2, 476, 474, 3, 2, 2, 2, + 476, 475, 3, 2, 2, 2, 477, 565, 3, 2, 2, 2, 478, 479, 12, 28, 2, 2, 479, + 480, 5, 122, 62, 2, 480, 481, 5, 92, 47, 29, 481, 564, 3, 2, 2, 2, 482, + 483, 12, 27, 2, 2, 483, 484, 5, 124, 63, 2, 484, 485, 5, 92, 47, 28, 485, + 564, 3, 2, 2, 2, 486, 487, 12, 26, 2, 2, 487, 490, 5, 108, 55, 2, 488, + 491, 5, 110, 56, 2, 489, 491, 5, 114, 58, 2, 490, 488, 3, 2, 2, 2, 490, + 489, 3, 2, 2, 2, 491, 492, 3, 2, 2, 2, 492, 493, 5, 92, 47, 27, 493, 564, + 3, 2, 2, 2, 494, 495, 12, 25, 2, 2, 495, 496, 5, 110, 56, 2, 496, 497, + 5, 92, 47, 26, 497, 564, 3, 2, 2, 2, 498, 499, 12, 24, 2, 2, 499, 500, + 5, 112, 57, 2, 500, 501, 5, 92, 47, 25, 501, 564, 3, 2, 2, 2, 502, 503, + 12, 23, 2, 2, 503, 504, 5, 114, 58, 2, 504, 505, 5, 92, 47, 24, 505, 564, + 3, 2, 2, 2, 506, 507, 12, 22, 2, 2, 507, 508, 5, 116, 59, 2, 508, 509, + 5, 92, 47, 23, 509, 564, 3, 2, 2, 2, 510, 511, 12, 21, 2, 2, 511, 512, + 5, 118, 60, 2, 512, 513, 5, 92, 47, 22, 513, 564, 3, 2, 2, 2, 514, 515, + 12, 20, 2, 2, 515, 516, 5, 120, 61, 2, 516, 517, 5, 92, 47, 21, 517, 564, + 3, 2, 2, 2, 518, 519, 12, 17, 2, 2, 519, 520, 7, 34, 2, 2, 520, 523, 7, + 13, 2, 2, 521, 524, 5, 22, 12, 2, 522, 524, 5, 60, 31, 2, 523, 521, 3, + 2, 2, 2, 523, 522, 3, 2, 2, 2, 524, 525, 3, 2, 2, 2, 525, 526, 7, 14, 2, + 2, 526, 527, 7, 7, 2, 2, 527, 528, 5, 92, 47, 18, 528, 564, 3, 2, 2, 2, + 529, 530, 12, 16, 2, 2, 530, 532, 7, 34, 2, 2, 531, 533, 5, 92, 47, 2, + 532, 531, 3, 2, 2, 2, 532, 533, 3, 2, 2, 2, 533, 534, 3, 2, 2, 2, 534, + 535, 7, 7, 2, 2, 535, 564, 5, 92, 47, 17, 536, 537, 12, 19, 2, 2, 537, + 538, 7, 34, 2, 2, 538, 541, 7, 13, 2, 2, 539, 542, 5, 22, 12, 2, 540, 542, + 5, 60, 31, 2, 541, 539, 3, 2, 2, 2, 541, 540, 3, 2, 2, 2, 542, 543, 3, + 2, 2, 2, 543, 544, 7, 14, 2, 2, 544, 545, 7, 7, 2, 2, 545, 548, 7, 13, + 2, 2, 546, 549, 5, 22, 12, 2, 547, 549, 5, 60, 31, 2, 548, 546, 3, 2, 2, + 2, 548, 547, 3, 2, 2, 2, 549, 550, 3, 2, 2, 2, 550, 551, 7, 14, 2, 2, 551, + 564, 3, 2, 2, 2, 552, 553, 12, 18, 2, 2, 553, 554, 7, 34, 2, 2, 554, 555, + 5, 92, 47, 2, 555, 556, 7, 7, 2, 2, 556, 559, 7, 13, 2, 2, 557, 560, 5, + 22, 12, 2, 558, 560, 5, 60, 31, 2, 559, 557, 3, 2, 2, 2, 559, 558, 3, 2, + 2, 2, 560, 561, 3, 2, 2, 2, 561, 562, 7, 14, 2, 2, 562, 564, 3, 2, 2, 2, + 563, 478, 3, 2, 2, 2, 563, 482, 3, 2, 2, 2, 563, 486, 3, 2, 2, 2, 563, + 494, 3, 2, 2, 2, 563, 498, 3, 2, 2, 2, 563, 502, 3, 2, 2, 2, 563, 506, + 3, 2, 2, 2, 563, 510, 3, 2, 2, 2, 563, 514, 3, 2, 2, 2, 563, 518, 3, 2, + 2, 2, 563, 529, 3, 2, 2, 2, 563, 536, 3, 2, 2, 2, 563, 552, 3, 2, 2, 2, + 564, 567, 3, 2, 2, 2, 565, 563, 3, 2, 2, 2, 565, 566, 3, 2, 2, 2, 566, + 93, 3, 2, 2, 2, 567, 565, 3, 2, 2, 2, 568, 570, 5, 96, 49, 2, 569, 571, + 5, 98, 50, 2, 570, 569, 3, 2, 2, 2, 571, 572, 3, 2, 2, 2, 572, 570, 3, + 2, 2, 2, 572, 573, 3, 2, 2, 2, 573, 95, 3, 2, 2, 2, 574, 580, 5, 130, 66, + 2, 575, 580, 5, 128, 65, 2, 576, 580, 5, 100, 51, 2, 577, 580, 5, 70, 36, + 2, 578, 580, 5, 72, 37, 2, 579, 574, 3, 2, 2, 2, 579, 575, 3, 2, 2, 2, + 579, 576, 3, 2, 2, 2, 579, 577, 3, 2, 2, 2, 579, 578, 3, 2, 2, 2, 580, + 97, 3, 2, 2, 2, 581, 583, 7, 34, 2, 2, 582, 581, 3, 2, 2, 2, 582, 583, + 3, 2, 2, 2, 583, 584, 3, 2, 2, 2, 584, 585, 7, 9, 2, 2, 585, 592, 5, 78, + 40, 2, 586, 587, 7, 34, 2, 2, 587, 589, 7, 9, 2, 2, 588, 586, 3, 2, 2, + 2, 588, 589, 3, 2, 2, 2, 589, 590, 3, 2, 2, 2, 590, 592, 5, 76, 39, 2, + 591, 582, 3, 2, 2, 2, 591, 588, 3, 2, 2, 2, 592, 99, 3, 2, 2, 2, 593, 594, + 5, 104, 53, 2, 594, 595, 5, 102, 52, 2, 595, 596, 5, 106, 54, 2, 596, 101, + 3, 2, 2, 2, 597, 598, 9, 3, 2, 2, 598, 103, 3, 2, 2, 2, 599, 601, 7, 70, + 2, 2, 600, 599, 3, 2, 2, 2, 601, 604, 3, 2, 2, 2, 602, 600, 3, 2, 2, 2, + 602, 603, 3, 2, 2, 2, 603, 105, 3, 2, 2, 2, 604, 602, 3, 2, 2, 2, 605, + 614, 7, 13, 2, 2, 606, 611, 5, 92, 47, 2, 607, 608, 7, 10, 2, 2, 608, 610, + 5, 92, 47, 2, 609, 607, 3, 2, 2, 2, 610, 613, 3, 2, 2, 2, 611, 609, 3, + 2, 2, 2, 611, 612, 3, 2, 2, 2, 612, 615, 3, 2, 2, 2, 613, 611, 3, 2, 2, + 2, 614, 606, 3, 2, 2, 2, 614, 615, 3, 2, 2, 2, 615, 616, 3, 2, 2, 2, 616, + 617, 7, 14, 2, 2, 617, 107, 3, 2, 2, 2, 618, 619, 9, 4, 2, 2, 619, 109, + 3, 2, 2, 2, 620, 624, 7, 62, 2, 2, 621, 622, 7, 61, 2, 2, 622, 624, 7, + 62, 2, 2, 623, 620, 3, 2, 2, 2, 623, 621, 3, 2, 2, 2, 624, 111, 3, 2, 2, + 2, 625, 629, 7, 60, 2, 2, 626, 627, 7, 61, 2, 2, 627, 629, 7, 60, 2, 2, + 628, 625, 3, 2, 2, 2, 628, 626, 3, 2, 2, 2, 629, 113, 3, 2, 2, 2, 630, + 631, 9, 5, 2, 2, 631, 115, 3, 2, 2, 2, 632, 633, 9, 6, 2, 2, 633, 117, + 3, 2, 2, 2, 634, 635, 7, 30, 2, 2, 635, 119, 3, 2, 2, 2, 636, 637, 7, 31, + 2, 2, 637, 121, 3, 2, 2, 2, 638, 639, 9, 7, 2, 2, 639, 123, 3, 2, 2, 2, + 640, 641, 9, 8, 2, 2, 641, 125, 3, 2, 2, 2, 642, 643, 9, 9, 2, 2, 643, + 127, 3, 2, 2, 2, 644, 645, 7, 65, 2, 2, 645, 646, 7, 66, 2, 2, 646, 129, + 3, 2, 2, 2, 647, 648, 7, 66, 2, 2, 648, 131, 3, 2, 2, 2, 66, 135, 154, + 162, 166, 174, 182, 186, 191, 197, 200, 206, 213, 221, 228, 233, 242, 248, + 252, 256, 260, 269, 273, 281, 286, 306, 317, 326, 339, 341, 352, 360, 363, + 368, 375, 380, 385, 391, 399, 403, 405, 415, 419, 421, 434, 443, 476, 490, + 523, 532, 541, 548, 559, 563, 565, 572, 579, 582, 588, 591, 602, 611, 614, + 623, 628, } var literalNames = []string{ "", "", "", "", "", "':'", "';'", "'.'", "','", "'['", "']'", "'('", "')'", @@ -5131,24 +5131,14 @@ func (s *WaitForTimeoutContext) Variable() IVariableContext { return t.(IVariableContext) } -func (s *WaitForTimeoutContext) FunctionCallExpression() IFunctionCallExpressionContext { - var t = s.GetTypedRuleContext(reflect.TypeOf((*IFunctionCallExpressionContext)(nil)).Elem(), 0) +func (s *WaitForTimeoutContext) Param() IParamContext { + var t = s.GetTypedRuleContext(reflect.TypeOf((*IParamContext)(nil)).Elem(), 0) if t == nil { return nil } - return t.(IFunctionCallExpressionContext) -} - -func (s *WaitForTimeoutContext) MemberExpression() IMemberExpressionContext { - var t = s.GetTypedRuleContext(reflect.TypeOf((*IMemberExpressionContext)(nil)).Elem(), 0) - - if t == nil { - return nil - } - - return t.(IMemberExpressionContext) + return t.(IParamContext) } func (s *WaitForTimeoutContext) GetRuleContext() antlr.RuleContext { @@ -5201,37 +5191,33 @@ func (p *FqlParser) WaitForTimeout() (localctx IWaitForTimeoutContext) { } }() - p.SetState(367) + p.SetState(366) p.GetErrorHandler().Sync(p) - switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 32, p.GetParserRuleContext()) { - case 1: + + switch p.GetTokenStream().LA(1) { + case FqlParserIntegerLiteral: p.EnterOuterAlt(localctx, 1) { p.SetState(363) p.IntegerLiteral() } - case 2: + case FqlParserIdentifier: p.EnterOuterAlt(localctx, 2) { p.SetState(364) p.Variable() } - case 3: + case FqlParserParam: p.EnterOuterAlt(localctx, 3) { p.SetState(365) - p.FunctionCallExpression() - } - - case 4: - p.EnterOuterAlt(localctx, 4) - { - p.SetState(366) - p.MemberExpression() + p.Param() } + default: + panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) } return localctx @@ -5375,41 +5361,41 @@ func (p *FqlParser) WaitForEventName() (localctx IWaitForEventNameContext) { } }() - p.SetState(374) + p.SetState(373) p.GetErrorHandler().Sync(p) switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 33, p.GetParserRuleContext()) { case 1: p.EnterOuterAlt(localctx, 1) { - p.SetState(369) + p.SetState(368) p.StringLiteral() } case 2: p.EnterOuterAlt(localctx, 2) { - p.SetState(370) + p.SetState(369) p.Variable() } case 3: p.EnterOuterAlt(localctx, 3) { - p.SetState(371) + p.SetState(370) p.Param() } case 4: p.EnterOuterAlt(localctx, 4) { - p.SetState(372) + p.SetState(371) p.FunctionCallExpression() } case 5: p.EnterOuterAlt(localctx, 5) { - p.SetState(373) + p.SetState(372) p.MemberExpression() } @@ -5536,27 +5522,27 @@ func (p *FqlParser) WaitForEventSource() (localctx IWaitForEventSourceContext) { } }() - p.SetState(379) + p.SetState(378) p.GetErrorHandler().Sync(p) switch p.GetInterpreter().AdaptivePredict(p.GetTokenStream(), 34, p.GetParserRuleContext()) { case 1: p.EnterOuterAlt(localctx, 1) { - p.SetState(376) + p.SetState(375) p.FunctionCallExpression() } case 2: p.EnterOuterAlt(localctx, 2) { - p.SetState(377) + p.SetState(376) p.Variable() } case 3: p.EnterOuterAlt(localctx, 3) { - p.SetState(378) + p.SetState(377) p.MemberExpression() } @@ -5727,25 +5713,25 @@ func (p *FqlParser) RangeOperator() (localctx IRangeOperatorContext) { }() p.EnterOuterAlt(localctx, 1) - p.SetState(384) + p.SetState(383) p.GetErrorHandler().Sync(p) switch p.GetTokenStream().LA(1) { case FqlParserIntegerLiteral: { - p.SetState(381) + p.SetState(380) p.IntegerLiteral() } case FqlParserIdentifier: { - p.SetState(382) + p.SetState(381) p.Variable() } case FqlParserParam: { - p.SetState(383) + p.SetState(382) p.Param() } @@ -5753,28 +5739,28 @@ func (p *FqlParser) RangeOperator() (localctx IRangeOperatorContext) { panic(antlr.NewNoViableAltException(p, nil, nil, nil, nil, nil)) } { - p.SetState(386) + p.SetState(385) p.Match(FqlParserRange) } - p.SetState(390) + p.SetState(389) p.GetErrorHandler().Sync(p) switch p.GetTokenStream().LA(1) { case FqlParserIntegerLiteral: { - p.SetState(387) + p.SetState(386) p.IntegerLiteral() } case FqlParserIdentifier: { - p.SetState(388) + p.SetState(387) p.Variable() } case FqlParserParam: { - p.SetState(389) + p.SetState(388) p.Param() } @@ -5917,45 +5903,45 @@ func (p *FqlParser) ArrayLiteral() (localctx IArrayLiteralContext) { p.EnterOuterAlt(localctx, 1) { - p.SetState(392) + p.SetState(391) p.Match(FqlParserOpenBracket) } - p.SetState(404) + p.SetState(403) p.GetErrorHandler().Sync(p) _la = p.GetTokenStream().LA(1) if (((_la-9)&-(0x1f+1)) == 0 && ((1<