(* CSS selector parser *) open Selector_ast open Selector_token (* Re-use the Selector_error exception from the lexer for consistency *) let raise_error code = raise (Selector_lexer.Selector_error code) type t = { tokens : Selector_token.t list; mutable pos : int; } let create tokens = { tokens; pos = 0 } let peek t = if t.pos < List.length t.tokens then List.nth t.tokens t.pos else EOF let advance t = if t.pos < List.length t.tokens then t.pos <- t.pos + 1 let consume t = let tok = peek t in advance t; tok let expect t expected = let tok = peek t in if tok <> expected then raise_error (match expected with EOF -> Selector_error_code.Expected_end_of_selector | _ -> Selector_error_code.Unexpected_token) else advance t let parse_simple_selector t = match peek t with | Tag name -> advance t; Some (make_simple Type_tag ~name ()) | Universal -> advance t; Some (make_simple Type_universal ()) | Id name -> advance t; Some (make_simple Type_id ~name ()) | Class name -> advance t; Some (make_simple Type_class ~name ()) | Attr_start -> advance t; let attr_name = match peek t with | Tag name -> advance t; name | _ -> raise_error Selector_error_code.Expected_attribute_name in (match peek t with | Attr_end -> advance t; Some (make_simple Type_attr ~name:attr_name ()) | Attr_op op -> advance t; let value = match peek t with | String v -> advance t; v | _ -> raise_error Selector_error_code.Expected_attribute_value in (match peek t with | Attr_end -> advance t | _ -> raise_error Selector_error_code.Expected_closing_bracket); Some (make_simple Type_attr ~name:attr_name ~operator:op ~value ()) | _ -> raise_error Selector_error_code.Expected_closing_bracket_or_operator) | Colon -> advance t; let name = match peek t with | Tag n -> advance t; n | _ -> raise_error Selector_error_code.Expected_pseudo_class_name in let arg = match peek t with | Paren_open -> advance t; let a = match peek t with | String s -> advance t; Some s | Paren_close -> None | _ -> None in (match peek t with | Paren_close -> advance t | _ -> raise_error Selector_error_code.Expected_closing_paren); a | _ -> None in Some (make_simple Type_pseudo ~name ?arg ()) | _ -> None let parse_compound_selector t = let rec loop acc = match parse_simple_selector t with | Some s -> loop (s :: acc) | None -> acc in let selectors = List.rev (loop []) in if selectors = [] then None else Some (make_compound selectors) let parse_complex_selector t = match parse_compound_selector t with | None -> None | Some first -> let parts = ref [(None, first)] in let rec loop () = match peek t with | Combinator comb -> advance t; (match parse_compound_selector t with | None -> raise_error Selector_error_code.Expected_selector_after_combinator | Some compound -> parts := (Some comb, compound) :: !parts; loop ()) | _ -> () in loop (); Some (make_complex (List.rev !parts)) let parse tokens = let t = create tokens in let rec loop acc = match parse_complex_selector t with | None -> acc | Some sel -> (match peek t with | Comma -> advance t; loop (sel :: acc) | EOF -> sel :: acc | _ -> raise_error Selector_error_code.Unexpected_token) in let selectors = List.rev (loop []) in (match peek t with | EOF -> () | _ -> raise_error Selector_error_code.Expected_end_of_selector); match selectors with | [] -> raise_error Selector_error_code.Empty_selector | [sel] -> Complex sel | sels -> List (make_list sels) let parse_selector input = if String.trim input = "" then raise_error Selector_error_code.Empty_selector; let tokens = Selector_lexer.tokenize input in parse tokens