Skip to content

Commit 5d8ac04

Browse files
committed
feat: add support for duration type with parsing, arithmetic operations, and program formatting
1 parent fa7ebdd commit 5d8ac04

93 files changed

Lines changed: 3116 additions & 2080 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

examples/google-search.fql

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ LET google = DOCUMENT("https://www.google.com/", {
44
})
55

66
HOVER(google, 'input[name="q"]')
7-
WAIT(RAND(100))
7+
WAIT(RAND(100) * 1ms)
88
INPUT(google, 'input[name="q"]', @criteria, 30)
9-
WAIT(RAND(100))
9+
WAIT(RAND(100) * 1ms)
1010
CLICK(google, 'input[name="btnK"]')
1111

1212
WAITFOR EVENT "navigation" IN google
@@ -20,4 +20,4 @@ FOR el IN results
2020
title: INNER_TEXT(el, 'h3')?,
2121
description: INNER_TEXT(el, X("//em/parent::*")),
2222
url: ELEMENT(el, 'a')?.attributes.href
23-
}
23+
}

examples/lazy-loading.fql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ LET lastItem = 50
1111

1212
LET preload = (
1313
FOR i DO WHILE !ELEMENT_EXISTS(doc, '.chartTracksEnd')
14-
LET initial = i > 0 ? SCROLL_BOTTOM(doc) && WAIT(500) && SCROLL_TOP(doc) && false : true
14+
LET initial = i > 0 ? SCROLL_BOTTOM(doc) && WAIT(500ms) && SCROLL_TOP(doc) && false : true
1515

1616
RETURN NONE
1717
)
@@ -26,4 +26,3 @@ FOR track, i IN ELEMENTS(list, 'li')
2626
}
2727

2828

29-

examples/pagination.fql

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ CLICK(amazon, '#nav-search-submit-button')
77

88
WAITFOR EVENT "navigation" IN amazon
99
FILTER current.url =~ "www\.amazon\.com\/s\?k="
10-
TIMEOUT 50000
10+
TIMEOUT 50s
1111

1212
WAIT_ELEMENT(amazon, '[class*="template=PAGINATION"]')
1313

@@ -84,4 +84,3 @@ LET result = (
8484

8585
RETURN FLATTEN(result)
8686

87-

examples/pagination_uncontrolled.fql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ CLICK(amazon, '#nav-search-submit-button')
66

77
WAITFOR EVENT "navigation" IN amazon
88
FILTER current.url =~ "www\.amazon\.com\/s\?k="
9-
TIMEOUT 50000
9+
TIMEOUT 50s
1010

1111
LET resultListSelector = '[data-component-type="s-search-results"]'
1212
LET resultItemSelector = '[data-component-type="s-search-result"]'
@@ -39,4 +39,4 @@ LET result = (
3939
RETURN items
4040
)
4141

42-
RETURN FLATTEN(result)
42+
RETURN FLATTEN(result)

examples/pagination_while.fql

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ WAIT_ELEMENT(doc, elementsSelector)
66

77
FOR i DO WHILE ELEMENT_EXISTS(doc, nextSelector)
88
LET wait = i > 0 ? CLICK(doc, nextSelector) : false
9-
LET nav = wait ? WAIT(2000) && WAIT_ELEMENT(doc, elementsSelector) : false
9+
LET nav = wait ? WAIT(2s) && WAIT_ELEMENT(doc, elementsSelector) : false
1010

1111
FOR el IN ELEMENTS(doc, elementsSelector)
1212
RETURN el

pkg/asm/duration_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package asm
2+
3+
import (
4+
"testing"
5+
"time"
6+
7+
"github.com/MontFerret/ferret/v2/pkg/runtime"
8+
)
9+
10+
func TestDurationConstantFormatting(t *testing.T) {
11+
t.Parallel()
12+
13+
if got := constantAsText(runtime.NewDuration(1500 * time.Millisecond)); got != "1.5s" {
14+
t.Fatalf("duration constant = %q, want 1.5s", got)
15+
}
16+
}

pkg/asm/formatter.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ func constantAsText(constant runtime.Value) string {
2626
return fmt.Sprintf("%d", constant)
2727
}
2828

29+
if _, ok := constant.(runtime.Duration); ok {
30+
return constant.String()
31+
}
32+
2933
return fmt.Sprintf("%q", constant.String())
3034
}
3135

pkg/bytecode/encoding.go

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,13 @@ func encodeConstant(value runtime.Value) (constantJSON, error) {
5050
return constantJSON{Type: encodeConstantType(runtime.TypeInt), Value: stdjson.RawMessage(strconv.FormatInt(int64(v), 10))}, nil
5151
case runtime.Float:
5252
return constantJSON{Type: encodeConstantType(runtime.TypeFloat), Value: stdjson.RawMessage(strconv.FormatFloat(float64(v), 'g', -1, 64))}, nil
53+
case runtime.Duration:
54+
encoded, err := json.Marshal(v.String())
55+
if err != nil {
56+
return constantJSON{}, err
57+
}
58+
59+
return constantJSON{Type: encodeConstantType(runtime.TypeDuration), Value: encoded}, nil
5360
case runtime.String:
5461
encoded, err := json.Marshal(string(v))
5562

@@ -123,6 +130,13 @@ func decodeConstant(frame constantJSON) (runtime.Value, error) {
123130
}
124131

125132
return runtime.NewFloat(value), nil
133+
case encodeConstantType(runtime.TypeDuration):
134+
var value string
135+
if err := json.Unmarshal(frame.Value, &value); err != nil {
136+
return runtime.ZeroDuration, err
137+
}
138+
139+
return runtime.ParseDuration(value)
126140
case encodeConstantType(runtime.TypeString):
127141
var value string
128142

pkg/bytecode/format/json/format_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,10 @@ func TestFormatRoundTrip(t *testing.T) {
4242
if got := decoded.Metadata.AggregatePlans[0].Index["group"]; got != 0 {
4343
t.Fatalf("expected aggregate plan index to be rebuilt, got %d", got)
4444
}
45+
46+
if got := decoded.Constants[1]; got != runtime.NewDuration(1500*time.Millisecond) {
47+
t.Fatalf("duration constant = %v (%T)", got, got)
48+
}
4549
}
4650

4751
func TestFormatRejectsMalformedPayload(t *testing.T) {
@@ -77,6 +81,16 @@ func TestFormatRejectsInvalidConstants(t *testing.T) {
7781
if !errors.Is(err, bytecode.ErrInvalidConstant) {
7882
t.Fatalf("expected ErrInvalidConstant for missing bool value, got %v", err)
7983
}
84+
85+
invalidDuration := "not-a-duration"
86+
frame = validFrame()
87+
frame.Constants = []persist.ConstantFrame{{Type: "duration", Duration: &invalidDuration}}
88+
89+
data = mustMarshalFrame(t, frame)
90+
_, err = Default.Unmarshal(data)
91+
if !errors.Is(err, bytecode.ErrInvalidConstant) {
92+
t.Fatalf("expected ErrInvalidConstant for malformed duration, got %v", err)
93+
}
8094
}
8195

8296
func TestFormatRejectsDuplicateHostsAndLabels(t *testing.T) {
@@ -165,6 +179,7 @@ func newTestProgram() *bytecode.Program {
165179
},
166180
Constants: []runtime.Value{
167181
runtime.NewFloat(1.5),
182+
runtime.NewDuration(1500 * time.Millisecond),
168183
runtime.NewString("hello"),
169184
runtime.NewBinary([]byte("abc")),
170185
runtime.NewDateTime(time.Unix(1700000000, 0).UTC()),

pkg/bytecode/format/msgpack/format_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ func TestFormatRoundTrip(t *testing.T) {
3535
t.Fatalf("expected aggregate plan index to be rebuilt, got %d", got)
3636
}
3737

38+
if got := decoded.Constants[1]; got != runtime.NewDuration(1500*time.Millisecond) {
39+
t.Fatalf("duration constant = %v (%T)", got, got)
40+
}
41+
3842
if line, col := decoded.Source.LocationAt(source.Span{Start: 7, End: 7}); line == 0 || col == 0 {
3943
t.Fatalf("expected source lines to be rebuilt, got line=%d col=%d", line, col)
4044
}
@@ -73,6 +77,16 @@ func TestFormatRejectsInvalidConstants(t *testing.T) {
7377
if !errors.Is(err, bytecode.ErrInvalidConstant) {
7478
t.Fatalf("expected ErrInvalidConstant for missing datetime value, got %v", err)
7579
}
80+
81+
invalidDuration := "not-a-duration"
82+
frame = validFrame()
83+
frame.Constants = []persist.ConstantFrame{{Type: "duration", Duration: &invalidDuration}}
84+
85+
data = mustMarshalFrame(t, frame)
86+
_, err = Default.Unmarshal(data)
87+
if !errors.Is(err, bytecode.ErrInvalidConstant) {
88+
t.Fatalf("expected ErrInvalidConstant for malformed duration, got %v", err)
89+
}
7690
}
7791

7892
func TestFormatRejectsDuplicateHostsAndLabels(t *testing.T) {
@@ -161,6 +175,7 @@ func newTestProgram() *bytecode.Program {
161175
},
162176
Constants: []runtime.Value{
163177
runtime.NewFloat(1.5),
178+
runtime.NewDuration(1500 * time.Millisecond),
164179
runtime.NewString("hello"),
165180
runtime.NewBinary([]byte("abc")),
166181
runtime.NewDateTime(time.Unix(1700000000, 0).UTC()),

0 commit comments

Comments
 (0)