cssx

package module
v0.2.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 3, 2026 License: Apache-2.0 Imports: 3 Imported by: 6

README

cssx

cssx is a tiny, standalone Go library that parses extended CSS expressions and compiles them into a linear postfix pipeline IR.

It does not execute anything and does not depend on a browser, CDP, or DOM. It only provides:

  • Parser: input string -> AST
  • Builder: AST -> Pipeline IR

A separate package can then translate this IR into JS or any other runtime.

Installation

go get github.com/MontFerret/cssx

Quick Start

package main

import (
	"fmt"

	"github.com/MontFerret/cssx"
)

func main() {
	pipeline, err := cssx.Compile(`:attr("href", :first(a.cta))`)
	if err != nil {
		panic(err)
	}

	for _, op := range pipeline.Ops {
		fmt.Printf("%s: %+v\n", op.Kind, op)
	}
}

Core Idea

Humans write nested pseudo calls like:

  • :count(.product)
  • :text(:first(h1))
  • :attr("href", :first(a.cta))

cssx compiles them into a linear postfix pipeline:

  • selector steps (Select)
  • call steps (Call)
  • literal call args embedded in each Call.Args

Example:

Input:

:attr("href", :first(a.cta))

Pipeline:

[
  Select("a.cta"),
  Call("first", Arity:1, Args:[]),
  Call("attr", Arity:1, Args:[String("href")]),
]

Syntax

1) Plain CSS selector mode

If input does not start with :, it is treated as a plain selector.

.section .item

Compiles to:

[Select(".section .item")]
2) Expression mode (starts with :)
:text(:nth(2, .section .item))

Rules:

  • Expr := SelectorExpr | CallExpr | StringLit | NumberLit
  • CallExpr := ':' Ident '(' ArgList? ')'
  • ArgList := Arg (',' Arg)*
  • SelectorExpr is raw selector text until , or ) at the current nesting depth
  • Strings support "..." and '...' with escapes
  • Numbers are parsed with strconv.ParseFloat
3) Pipeline sugar (explicit delimiter)
.section .item >> :nth(2) >> :text()

Rules:

  • >> splits pipeline only at top level (not inside quotes/brackets/parens)
  • First segment: plain selector (must not start with :)
  • Each following segment: must be a call :name(...)
  • Zero-arg calls must still include parentheses: :text()

Compiles to:

[
  Select(".section .item"),
  Call("nth", Arity:0, Args:[Number(2)]),
  Call("text", Arity:0, Args:[]),
]

IR Contract

Arity

Op.Arity is the number of non-literal expression args consumed from the stack.

Args

Op.Args contains only literal arguments (string or number) in source order.

literals-first rule

Literal args must come before expression args inside a call.

Valid:

  • :foo("x", 2, :bar(a))

Invalid:

  • :foo(:bar(a), "x")
  • :foo(1, :bar(a), 2)

Invalid calls fail at compile time with:

  • literal args must precede expression args

Supported Examples

  • .product
    • [Select(".product")]
  • :count(.product)
    • [Select(".product"), Call("count", Arity:1, Args:[])]
  • :text(:first(h1))
    • [Select("h1"), Call("first", Arity:1, Args:[]), Call("text", Arity:1, Args:[])]
  • :attr("href", :first(a.cta))
    • [Select("a.cta"), Call("first", Arity:1, Args:[]), Call("attr", Arity:1, Args:[String("href")])]
  • :first(a[href*="x,y"])
    • [Select("a[href*=\"x,y\"]"), Call("first", Arity:1, Args:[])]
  • :text(:nth(2, .section .item))
    • [Select(".section .item"), Call("nth", Arity:1, Args:[Number(2)]), Call("text", Arity:1, Args:[])]
  • .section .item >> :nth(2) >> :text()
    • [Select(".section .item"), Call("nth", Arity:0, Args:[Number(2)]), Call("text", Arity:0, Args:[])]

Public API

package cssx

// Parse parses input into an AST.
func Parse(input string) (AST, error)

// ParseToAST is an alias for Parse.
func ParseToAST(input string) (AST, error)

// BuildPipeline compiles an AST into a postfix pipeline IR.
func BuildPipeline(ast AST) (Pipeline, error)

// Compile parses input and builds a pipeline.
func Compile(input string) (Pipeline, error)

Key types:

type Pipeline struct {
	Ops []Op
}

type Op struct {
	Kind     OpKind
	Selector string
	Name     string
	Arity    int
	Args     []CallArg
}

type CallArg struct {
	Kind CallArgKind
	Str  string
	Num  float64
}

type ParseError struct {
	Message string
	Pos     int // byte offset
}

Error Handling

All syntax errors and compile-time IR validation errors return *ParseError with a byte offset position.

Common error cases:

  • : (missing identifier)
  • :text( (unterminated call)
  • :text(:first(h1) (missing ))
  • :attr("href" :first(a)) (missing comma)
  • empty input (whitespace only)
  • pipeline stage without :name(...)
  • mixed call arg order violating literals-first

Non-Goals

  • No DOM, JS, or runtime execution
  • No CSS selector validation
  • No pseudo validation (names and arity are not checked)
  • No external dependencies

License

See LICENSE.

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type AST

type AST struct {
	Expr Expr
}

AST is the root of a parsed cssx expression.

func Parse

func Parse(input string) (AST, error)

Parse parses input into an AST.

func ParseToAST

func ParseToAST(input string) (AST, error)

ParseToAST is an alias for Parse.

type CallArg added in v0.2.0

type CallArg struct {
	Kind CallArgKind
	Str  string
	Num  float64
}

CallArg is an embedded literal argument for a call operation.

type CallArgKind added in v0.2.0

type CallArgKind int

CallArgKind identifies the kind of a call literal argument.

const (
	CallArgString CallArgKind = iota
	CallArgNumber
)

type CallExpr

type CallExpr struct {
	Name string
	Args []Expr
	// contains filtered or unexported fields
}

CallExpr represents a pseudo call like :name(args...).

func (*CallExpr) Pos

func (c *CallExpr) Pos() int

type Expr

type Expr interface {
	Pos() int
	// contains filtered or unexported methods
}

Expr is any expression node.

type NumberLit

type NumberLit struct {
	Value float64
	// contains filtered or unexported fields
}

NumberLit is a numeric literal.

func (*NumberLit) Pos

func (n *NumberLit) Pos() int

type Op

type Op struct {
	Kind     OpKind
	Selector string
	Name     string
	Arity    int
	Args     []CallArg
}

Op is a single pipeline operation in postfix order.

type OpKind

type OpKind int

OpKind identifies the kind of pipeline operation.

const (
	OpSelect OpKind = iota
	OpCall
)

func (OpKind) String

func (k OpKind) String() string

type ParseError

type ParseError struct {
	Message string
	Pos     int // byte offset
}

ParseError represents a syntax error with a byte position in the input.

func (*ParseError) Error

func (e *ParseError) Error() string

type Pipeline

type Pipeline struct {
	Ops []Op
}

Pipeline is a linear postfix pipeline representation.

func BuildPipeline

func BuildPipeline(ast AST) (Pipeline, error)

BuildPipeline compiles an AST into a postfix pipeline IR.

func Compile

func Compile(input string) (Pipeline, error)

Compile parses input and builds a pipeline.

type PipelineExpr

type PipelineExpr struct {
	Base  *SelectorExpr
	Calls []*CallExpr
	// contains filtered or unexported fields
}

PipelineExpr represents a base selector with a chain of calls (pipeline sugar).

func (*PipelineExpr) Pos

func (p *PipelineExpr) Pos() int

type SelectorExpr

type SelectorExpr struct {
	Raw string
	// contains filtered or unexported fields
}

SelectorExpr is a raw CSS selector.

func (*SelectorExpr) Pos

func (s *SelectorExpr) Pos() int

type StringLit

type StringLit struct {
	Value string
	// contains filtered or unexported fields
}

StringLit is a quoted string literal.

func (*StringLit) Pos

func (s *StringLit) Pos() int

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL