@@ -21,20 +21,20 @@ go get github.com/MontFerret/cssx
2121package main
2222
2323import (
24- " fmt"
24+ " fmt"
2525
26- " github.com/MontFerret/cssx"
26+ " github.com/MontFerret/cssx"
2727)
2828
2929func main () {
30- pipeline , err := cssx.Compile (" :text( :first(h1)) " )
31- if err != nil {
32- panic (err)
33- }
34-
35- for _ , op := range pipeline.Ops {
36- fmt.Printf (" %s : %+v \n " , op.Kind , op)
37- }
30+ pipeline , err := cssx.Compile (` :attr("href", :first(a.cta)) ` )
31+ if err != nil {
32+ panic (err)
33+ }
34+
35+ for _ , op := range pipeline.Ops {
36+ fmt.Printf (" %s : %+v \n " , op.Kind , op)
37+ }
3838}
3939```
4040
@@ -48,22 +48,26 @@ Humans write nested pseudo calls like:
4848
4949cssx compiles them into a linear postfix pipeline:
5050
51- - native CSS selector steps (` Select ` )
52- - pseudo/function steps (` Call ` )
53- - constants ( ` Str ` , ` Num ` )
51+ - selector steps (` Select ` )
52+ - call steps (` Call ` )
53+ - literal call args embedded in each ` Call.Args `
5454
5555Example:
5656
5757Input:
5858
5959```
60- :text( :first(h1 ))
60+ :attr("href", :first(a.cta ))
6161```
6262
6363Pipeline:
6464
6565```
66- [Select("h1"), Call("first",1), Call("text",1)]
66+ [
67+ Select("a.cta"),
68+ Call("first", Arity:1, Args:[]),
69+ Call("attr", Arity:1, Args:[String("href")]),
70+ ]
6771```
6872
6973## Syntax
@@ -113,37 +117,56 @@ Rules:
113117Compiles to:
114118
115119```
116- [Select(".section .item"), Num(2), Call("nth",1), Call("text",0)]
120+ [
121+ Select(".section .item"),
122+ Call("nth", Arity:0, Args:[Number(2)]),
123+ Call("text", Arity:0, Args:[]),
124+ ]
117125```
118126
119- ## Supported Examples
127+ ## IR Contract
128+
129+ ### ` Arity `
120130
121- Plain selectors:
131+ ` Op.Arity ` is the number of non-literal expression args consumed from the stack.
122132
123- - ` .product ` -> ` [Select(".product")] `
124- - ` .section .item ` -> ` [Select(".section .item")] `
133+ ### ` Args `
125134
126- Single pseudo:
135+ ` Op.Args ` contains only literal arguments ( ` string ` or ` number ` ) in source order.
127136
128- - ` :count(.product) ` -> ` [Select(".product"), Call("count",1)] `
129- - ` :first(section) ` -> ` [Select("section"), Call("first",1)] `
137+ ### ` literals-first ` rule
130138
131- Nested pseudos:
139+ Literal args must come before expression args inside a call.
132140
133- - ` :text(:first(h1)) ` -> ` [Select("h1"), Call("first",1), Call("text",1)] `
134- - ` :attr("href", :first(a.cta)) ` -> ` [Str("href"), Select("a.cta"), Call("first",1), Call("attr",2)] `
141+ Valid:
135142
136- Selectors with commas inside:
143+ - ` :foo("x", 2, :bar(a)) `
137144
138- - ` :first(a[href*="x,y"]) ` -> ` [Select("a[href*="x,y"]"), Call("first",1)] `
145+ Invalid:
139146
140- Mixed readability:
147+ - ` :foo(:bar(a), "x") `
148+ - ` :foo(1, :bar(a), 2) `
141149
142- - ` :text(:nth(2, .section .item)) ` -> ` [Num(2), Select(".section .item"), Call("nth",2), Call("text",1)] `
150+ Invalid calls fail at compile time with:
143151
144- Pipeline sugar:
152+ - ` literal args must precede expression args `
145153
146- - ` .section .item >> :nth(2) >> :text() ` -> ` [Select(".section .item"), Num(2), Call("nth",1), Call("text",0)] `
154+ ## Supported Examples
155+
156+ - ` .product `
157+ - ` [Select(".product")] `
158+ - ` :count(.product) `
159+ - ` [Select(".product"), Call("count", Arity:1, Args:[])] `
160+ - ` :text(:first(h1)) `
161+ - ` [Select("h1"), Call("first", Arity:1, Args:[]), Call("text", Arity:1, Args:[])] `
162+ - ` :attr("href", :first(a.cta)) `
163+ - ` [Select("a.cta"), Call("first", Arity:1, Args:[]), Call("attr", Arity:1, Args:[String("href")])] `
164+ - ` :first(a[href*="x,y"]) `
165+ - ` [Select("a[href*=\"x,y\"]"), Call("first", Arity:1, Args:[])] `
166+ - ` :text(:nth(2, .section .item)) `
167+ - ` [Select(".section .item"), Call("nth", Arity:1, Args:[Number(2)]), Call("text", Arity:1, Args:[])] `
168+ - ` .section .item >> :nth(2) >> :text() `
169+ - ` [Select(".section .item"), Call("nth", Arity:0, Args:[Number(2)]), Call("text", Arity:0, Args:[])] `
147170
148171## Public API
149172
@@ -167,36 +190,42 @@ Key types:
167190
168191```go
169192type Pipeline struct {
170- Ops []Op
193+ Ops []Op
171194}
172195
173196type Op struct {
174- Kind OpKind
175- Selector string
176- Name string
177- Arity int
178- Str string
179- Num float64
197+ Kind OpKind
198+ Selector string
199+ Name string
200+ Arity int
201+ Args []CallArg
202+ }
203+
204+ type CallArg struct {
205+ Kind CallArgKind
206+ Str string
207+ Num float64
180208}
181209
182210type ParseError struct {
183- Message string
184- Pos int // byte offset
211+ Message string
212+ Pos int // byte offset
185213}
186214```
187215
188216## Error Handling
189217
190- All syntax errors return ` *ParseError ` with a byte offset position.
218+ All syntax errors and compile-time IR validation errors return ` *ParseError ` with a byte offset position.
191219
192220Common error cases:
193221
194222- ` : ` (missing identifier)
195223- ` :text( ` (unterminated call)
196- - ` :text(:first(h1) ` (missing ` ) ` )
224+ - ` :text(:first(h1) ` (missing ` ) ` )
197225- ` :attr("href" :first(a)) ` (missing comma)
198226- empty input (whitespace only)
199227- pipeline stage without ` :name(...) `
228+ - mixed call arg order violating literals-first
200229
201230## Non-Goals
202231
0 commit comments