implement CSS; ## CSS2 parsing module## CSS2.1 style sheets ## Copyright © 2001, 2005 Vita Nuova Holdings Limited. All rights reserved.# include "sys.m"; sys: Sys; include "css.m"; B, NUMBER, IDENT, STRING, URL, PERCENTAGE, UNIT, HASH, ATKEYWORD, IMPORTANT, IMPORT, PSEUDO, CLASS, INCLUDES, DASHMATCH, FUNCTION: con 16rE000+iota; toknames := array[] of{ B-B => "Zero", NUMBER-B => "NUMBER", IDENT-B => "IDENT", STRING-B => "STRING", URL-B => "URL", PERCENTAGE-B => "PERCENTAGE", UNIT-B => "UNIT", HASH-B => "HASH", ATKEYWORD-B => "ATKEYWORD", IMPORTANT-B => "IMPORTANT", CLASS-B => "CLASS", INCLUDES-B => "INCLUDES", DASHMATCH-B => "DASHMATCH", PSEUDO-B => "PSEUDO", FUNCTION-B => "FUNCTION",}; printdiag := 0; init(d: int){ sys = load Sys Sys->PATH; printdiag = d;} parse(s: string): (ref Stylesheet, string){ return stylesheet(ref Cparse(-1, 0, nil, nil, Clex.new(s,1)));} parsedecl(s: string): (list of ref Decl, string){ return (declarations(ref Cparse(-1, 0, nil, nil, Clex.new(s,0))), nil);} ptok(c: int): string{ if(c < 0) return "eof"; if(c == 0) return "zero?"; if(c >= B) return sys->sprint("%s", toknames[c-B]); return sys->sprint("%c", c);} Cparse: adt { lookahead: int; eof: int; value: string; suffix: string; cs: ref Clex; get: fn(nil: self ref Cparse): int; look: fn(nil: self ref Cparse): int; unget: fn(nil: self ref Cparse, tok: int); skipto: fn(nil: self ref Cparse, followset: string): int; synerr: fn(nil: self ref Cparse, s: string);}; Cparse.get(p: self ref Cparse): int{ if((c := p.lookahead) >= 0){ p.lookahead = -1; return c; } if(p.eof) return -1; (c, p.value, p.suffix) = csslex(p.cs); if(c < 0) p.eof = 1; if(printdiag > 1) sys->print("lex: %s v=%s s=%s\n", ptok(c), p.value, p.suffix); return c;} Cparse.look(p: self ref Cparse): int{ c := p.get(); p.unget(c); return c;} Cparse.unget(p: self ref Cparse, c: int){ if(p.lookahead >= 0) raise "css: internal error: Cparse.unget"; p.lookahead = c; # note that p.value and p.suffix are assumed to be those of c} Cparse.skipto(p: self ref Cparse, followset: string): int{ while((c := p.get()) >= 0) for(i := 0; i < len followset; i++) if(followset[i] == c){ p.unget(c); return c; } return -1;} Cparse.synerr(p: self ref Cparse, s: string){ p.cs.synerr(s);} ## stylesheet:# ["@charset" STRING ';']?# [CDO|CDC]* [import [CDO|CDC]*]*# [[ruleset | media | page ] [CDO|CDC]*]*# import:# "@import" [STRING|URL] [ medium [',' medium]*]? ';'# media:# "@media" medium [',' medium]* '{' ruleset* '}'# medium:# IDENT# page:# "@page" pseudo_page? '{' declaration [';' declaration]* '}'# pseudo_page:# ':' IDENT# stylesheet(p: ref Cparse): (ref Stylesheet, string){ charset: string; if(atkeywd(p, "@charset")){ if(itisa(p, STRING)){ charset = p.value; itisa(p, ';'); }else p.synerr("bad @charset declaration"); } imports: list of ref Import; while(atkeywd(p, "@import")){ c := p.get(); if(c == STRING || c == URL){ name := p.value; media: list of string; c = p.get(); if(c == IDENT){ # optional medium [, ...] p.unget(c); media = medialist(p); } imports = ref Import(name, media) :: imports; }else p.synerr("bad @import"); if(c != ';'){ p.synerr("missing ; in @import"); p.unget(c); if(p.skipto(";}") < 0) break; } } imports = rev(imports); stmts: list of ref Statement; do{ while((c := p.get()) == ATKEYWORD) case p.value { "@media" => # medium[,medium]* { ruleset*} media := medialist(p); if(!itisa(p, '{')){ p.synerr("bad @media"); skipatrule("@media", p); continue; } rules: list of ref Statement.Ruleset; do{ rule := checkrule(p); if(rule != nil) rules = rule :: rules; }while(!itisa(p, '}') && !p.eof); stmts = ref Statement.Media(media, rev(rules)) :: stmts; "@page" => # [:ident]? { declaration [; declaration]* } pseudo: string; if(itisa(p, PSEUDO)) pseudo = p.value; if(!itisa(p, '{')){ p.synerr("bad @page"); skipatrule("@page", p); continue; } decls := declarations(p); if(!itisa(p, '}')){ p.synerr("unclosed @page declaration block"); skipatrule("@page", p); continue; } stmts = ref Statement.Page(pseudo, decls) :: stmts; * => skipatrule(p.value, p); # skip unknown or misplaced at-rule } p.unget(c); rule := checkrule(p); if(rule != nil) stmts = rule :: stmts; }while(!p.eof); rl := stmts; stmts = nil; for(; rl != nil; rl = tl rl) stmts = hd rl :: stmts; return (ref Stylesheet(charset, imports, stmts), nil);} checkrule(p: ref Cparse): ref Statement.Ruleset{ (rule, err) := ruleset(p); if(rule == nil){ if(err != nil){ p.synerr(sys->sprint("bad ruleset: %s", err)); p.get(); # make some progress } } return rule;} medialist(p: ref Cparse): list of string{ media: list of string; do{ c := p.get(); if(c != IDENT){ p.unget(c); p.synerr("missing medium identifier"); break; } media = p.value :: media; }while(itisa(p, ',')); return rev(media);} itisa(p: ref Cparse, expect: int): int{ if((c := p.get()) == expect) return 1; p.unget(c); return 0;} atkeywd(p: ref Cparse, expect: string): int{ if((c := p.get()) == ATKEYWORD && p.value == expect) return 1; p.unget(c); return 0;} skipatrule(name: string, p: ref Cparse){ if(printdiag) sys->print("skip unimplemented or misplaced %s\n", name); if((c := p.get()) == '{'){ # block for(nesting := '}' :: nil; nesting != nil && c >= 0; nesting = tl nesting){ while((c = p.cs.getc()) >= 0 && c != hd nesting) case c { '{' => nesting = '}' :: nesting; '(' => nesting = ')' :: nesting; '[' => nesting = ']' :: nesting; '"' or '\'' => quotedstring(p.cs, c); } } }else{ while(c >= 0 && c != ';') c = p.get(); }} # ruleset:# selector [',' S* selector]* '{' S* declaration [';' S* declaration]* '}' S* ruleset(p: ref Cparse): (ref Statement.Ruleset, string){ selectors: list of list of (int, list of ref Select); c := -1; do{ s := selector(p); if(s == nil){ if(p.eof) return (nil, nil); p.synerr("expected selector"); if(p.skipto(",{}") < 0) return (nil, nil); c = p.look(); }else selectors = s :: selectors; }while((c = p.get()) == ','); if(c != '{') return (nil, "expected declaration block"); sl := selectors; selectors = nil; for(; sl != nil; sl = tl sl) selectors = hd sl :: selectors; decls := declarations(p); if(!itisa(p, '}')){ p.synerr("unclosed declaration block"); } return (ref Statement.Ruleset(selectors, decls), nil);} declarations(p: ref Cparse): list of ref Decl{ decls: list of ref Decl; c: int; do{ (d, e) := declaration(p); if(d != nil) decls = d :: decls; else if(e != nil){ p.synerr("ruleset declaration: "+e); if((c = p.skipto(";}")) < 0) break; } }while((c = p.get()) == ';'); p.unget(c); l := decls; for(decls = nil; l != nil; l = tl l) decls = hd l :: decls; return decls;} # selector:# simple_selector [combinator simple_selector]*# combinator:# '+' S* | '>' S* | /* empty */# selector(p: ref Cparse): list of (int, list of ref Select){ sel: list of (int, list of ref Select); op := ' '; while((s := selector1(p)) != nil){ sel = (op, s) :: sel; if((c := p.look()) == '+' || c == '>') op = p.get(); else op = ' '; } l: list of (int, list of ref Select); for(; sel != nil; sel = tl sel) l = hd sel :: l; return l;} ## simple_selector:# element_name? [HASH | class | attrib | pseudo]* S*# element_name:# IDENT | '*'# class:# '.' IDENT# attrib:# '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [IDENT | STRING] S* ]? ']'# pseudo# ':' [ IDENT | FUNCTION S* IDENT? S* ')' ] selector1(p: ref Cparse): list of ref Select{ sel: list of ref Select; c := p.get(); if(c == IDENT) sel = ref Select.Element(p.value) :: sel; else if(c== '*') sel = ref Select.Any("*") :: sel; else p.unget(c);Sel: for(;;){ c = p.get(); case c { HASH => sel = ref Select.ID(p.value) :: sel; CLASS => sel = ref Select.Class(p.value) :: sel; '[' => if(!itisa(p, IDENT)) break; name := p.value; case c = p.get() { '=' => sel = ref Select.Attrib(name, "=", optaval(p)) :: sel; INCLUDES => sel = ref Select.Attrib(name, "~=", optaval(p)) :: sel; DASHMATCH => sel = ref Select.Attrib(name, "|=", optaval(p)) :: sel; * => sel = ref Select.Attrib(name, nil, nil) :: sel; p.unget(c); } if((c = p.get()) != ']'){ p.synerr("bad attribute syntax"); p.unget(c); break Sel; } PSEUDO => case c = p.get() { IDENT => sel = ref Select.Pseudo(p.value) :: sel; FUNCTION => name := p.value; case c = p.get() { IDENT => sel = ref Select.Pseudofn(name, lowercase(p.value)) :: sel; ')' => p.unget(c); sel = ref Select.Pseudofn(name, nil) :: sel; * => p.synerr("bad pseudo-function syntax"); p.unget(c); break Sel; } if((c = p.get()) != ')'){ p.synerr("missing ')' for pseudo-function"); p.unget(c); break Sel; } * => p.synerr(sys->sprint("unexpected :pseudo: %s:%s", ptok(c), p.value)); p.unget(c); break Sel; } * => p.unget(c); break Sel; } # qualifiers must be adjacent to the first item, and each other c = p.cs.getc(); p.cs.ungetc(c); if(isspace(c)) break; } sl := sel; for(sel = nil; sl != nil; sl = tl sl) sel = hd sl :: sel; return sel;} optaval(p: ref Cparse): ref Value{ case c := p.get() { IDENT => return ref Value.Ident(' ', p.value); STRING => return ref Value.String(' ', p.value); * => p.unget(c); return nil; }} # declaration:# property ':' S* expr prio?# | /* empty */# property:# IDENT# prio:# IMPORTANT S* /* ! important */ declaration(p: ref Cparse): (ref Decl, string){ c := p.get(); if(c != IDENT){ p.unget(c); return (nil, nil); } prop := lowercase(p.value); c = p.get(); if(c != ':'){ p.unget(c); return (nil, "missing :"); } values := expr(p); if(values == nil) return (nil, "missing expression(s)"); prio := 0; if(p.look() == IMPORTANT){Lines 1–500 of 1019. Load more
implement CSS; # # CSS2 parsing module # # CSS2.1 style sheets # # Copyright © 2001, 2005 Vita Nuova Holdings Limited. All rights reserved. # include "sys.m"; sys: Sys; include "css.m"; B, NUMBER, IDENT, STRING, URL, PERCENTAGE, UNIT, HASH, ATKEYWORD, IMPORTANT, IMPORT, PSEUDO, CLASS, INCLUDES, DASHMATCH, FUNCTION: con 16rE000+iota; toknames := array[] of{ B-B => "Zero", NUMBER-B => "NUMBER", IDENT-B => "IDENT", STRING-B => "STRING", URL-B => "URL", PERCENTAGE-B => "PERCENTAGE", UNIT-B => "UNIT", HASH-B => "HASH", ATKEYWORD-B => "ATKEYWORD", IMPORTANT-B => "IMPORTANT", CLASS-B => "CLASS", INCLUDES-B => "INCLUDES", DASHMATCH-B => "DASHMATCH", PSEUDO-B => "PSEUDO", FUNCTION-B => "FUNCTION", }; printdiag := 0; init(d: int) { sys = load Sys Sys->PATH; printdiag = d; } parse(s: string): (ref Stylesheet, string) { return stylesheet(ref Cparse(-1, 0, nil, nil, Clex.new(s,1))); } parsedecl(s: string): (list of ref Decl, string) { return (declarations(ref Cparse(-1, 0, nil, nil, Clex.new(s,0))), nil); } ptok(c: int): string { if(c < 0) return "eof"; if(c == 0) return "zero?"; if(c >= B) return sys->sprint("%s", toknames[c-B]); return sys->sprint("%c", c); } Cparse: adt { lookahead: int; eof: int; value: string; suffix: string; cs: ref Clex; get: fn(nil: self ref Cparse): int; look: fn(nil: self ref Cparse): int; unget: fn(nil: self ref Cparse, tok: int); skipto: fn(nil: self ref Cparse, followset: string): int; synerr: fn(nil: self ref Cparse, s: string); }; Cparse.get(p: self ref Cparse): int { if((c := p.lookahead) >= 0){ p.lookahead = -1; return c; } if(p.eof) return -1; (c, p.value, p.suffix) = csslex(p.cs); if(c < 0) p.eof = 1; if(printdiag > 1) sys->print("lex: %s v=%s s=%s\n", ptok(c), p.value, p.suffix); return c; } Cparse.look(p: self ref Cparse): int { c := p.get(); p.unget(c); return c; } Cparse.unget(p: self ref Cparse, c: int) { if(p.lookahead >= 0) raise "css: internal error: Cparse.unget"; p.lookahead = c; # note that p.value and p.suffix are assumed to be those of c } Cparse.skipto(p: self ref Cparse, followset: string): int { while((c := p.get()) >= 0) for(i := 0; i < len followset; i++) if(followset[i] == c){ p.unget(c); return c; } return -1; } Cparse.synerr(p: self ref Cparse, s: string) { p.cs.synerr(s); } # # stylesheet: # ["@charset" STRING ';']? # [CDO|CDC]* [import [CDO|CDC]*]* # [[ruleset | media | page ] [CDO|CDC]*]* # import: # "@import" [STRING|URL] [ medium [',' medium]*]? ';' # media: # "@media" medium [',' medium]* '{' ruleset* '}' # medium: # IDENT # page: # "@page" pseudo_page? '{' declaration [';' declaration]* '}' # pseudo_page: # ':' IDENT # stylesheet(p: ref Cparse): (ref Stylesheet, string) { charset: string; if(atkeywd(p, "@charset")){ if(itisa(p, STRING)){ charset = p.value; itisa(p, ';'); }else p.synerr("bad @charset declaration"); } imports: list of ref Import; while(atkeywd(p, "@import")){ c := p.get(); if(c == STRING || c == URL){ name := p.value; media: list of string; c = p.get(); if(c == IDENT){ # optional medium [, ...] p.unget(c); media = medialist(p); } imports = ref Import(name, media) :: imports; }else p.synerr("bad @import"); if(c != ';'){ p.synerr("missing ; in @import"); p.unget(c); if(p.skipto(";}") < 0) break; } } imports = rev(imports); stmts: list of ref Statement; do{ while((c := p.get()) == ATKEYWORD) case p.value { "@media" => # medium[,medium]* { ruleset*} media := medialist(p); if(!itisa(p, '{')){ p.synerr("bad @media"); skipatrule("@media", p); continue; } rules: list of ref Statement.Ruleset; do{ rule := checkrule(p); if(rule != nil) rules = rule :: rules; }while(!itisa(p, '}') && !p.eof); stmts = ref Statement.Media(media, rev(rules)) :: stmts; "@page" => # [:ident]? { declaration [; declaration]* } pseudo: string; if(itisa(p, PSEUDO)) pseudo = p.value; if(!itisa(p, '{')){ p.synerr("bad @page"); skipatrule("@page", p); continue; } decls := declarations(p); if(!itisa(p, '}')){ p.synerr("unclosed @page declaration block"); skipatrule("@page", p); continue; } stmts = ref Statement.Page(pseudo, decls) :: stmts; * => skipatrule(p.value, p); # skip unknown or misplaced at-rule } p.unget(c); rule := checkrule(p); if(rule != nil) stmts = rule :: stmts; }while(!p.eof); rl := stmts; stmts = nil; for(; rl != nil; rl = tl rl) stmts = hd rl :: stmts; return (ref Stylesheet(charset, imports, stmts), nil); } checkrule(p: ref Cparse): ref Statement.Ruleset { (rule, err) := ruleset(p); if(rule == nil){ if(err != nil){ p.synerr(sys->sprint("bad ruleset: %s", err)); p.get(); # make some progress } } return rule; } medialist(p: ref Cparse): list of string { media: list of string; do{ c := p.get(); if(c != IDENT){ p.unget(c); p.synerr("missing medium identifier"); break; } media = p.value :: media; }while(itisa(p, ',')); return rev(media); } itisa(p: ref Cparse, expect: int): int { if((c := p.get()) == expect) return 1; p.unget(c); return 0; } atkeywd(p: ref Cparse, expect: string): int { if((c := p.get()) == ATKEYWORD && p.value == expect) return 1; p.unget(c); return 0; } skipatrule(name: string, p: ref Cparse) { if(printdiag) sys->print("skip unimplemented or misplaced %s\n", name); if((c := p.get()) == '{'){ # block for(nesting := '}' :: nil; nesting != nil && c >= 0; nesting = tl nesting){ while((c = p.cs.getc()) >= 0 && c != hd nesting) case c { '{' => nesting = '}' :: nesting; '(' => nesting = ')' :: nesting; '[' => nesting = ']' :: nesting; '"' or '\'' => quotedstring(p.cs, c); } } }else{ while(c >= 0 && c != ';') c = p.get(); } } # ruleset: # selector [',' S* selector]* '{' S* declaration [';' S* declaration]* '}' S* ruleset(p: ref Cparse): (ref Statement.Ruleset, string) { selectors: list of list of (int, list of ref Select); c := -1; do{ s := selector(p); if(s == nil){ if(p.eof) return (nil, nil); p.synerr("expected selector"); if(p.skipto(",{}") < 0) return (nil, nil); c = p.look(); }else selectors = s :: selectors; }while((c = p.get()) == ','); if(c != '{') return (nil, "expected declaration block"); sl := selectors; selectors = nil; for(; sl != nil; sl = tl sl) selectors = hd sl :: selectors; decls := declarations(p); if(!itisa(p, '}')){ p.synerr("unclosed declaration block"); } return (ref Statement.Ruleset(selectors, decls), nil); } declarations(p: ref Cparse): list of ref Decl { decls: list of ref Decl; c: int; do{ (d, e) := declaration(p); if(d != nil) decls = d :: decls; else if(e != nil){ p.synerr("ruleset declaration: "+e); if((c = p.skipto(";}")) < 0) break; } }while((c = p.get()) == ';'); p.unget(c); l := decls; for(decls = nil; l != nil; l = tl l) decls = hd l :: decls; return decls; } # selector: # simple_selector [combinator simple_selector]* # combinator: # '+' S* | '>' S* | /* empty */ # selector(p: ref Cparse): list of (int, list of ref Select) { sel: list of (int, list of ref Select); op := ' '; while((s := selector1(p)) != nil){ sel = (op, s) :: sel; if((c := p.look()) == '+' || c == '>') op = p.get(); else op = ' '; } l: list of (int, list of ref Select); for(; sel != nil; sel = tl sel) l = hd sel :: l; return l; } # # simple_selector: # element_name? [HASH | class | attrib | pseudo]* S* # element_name: # IDENT | '*' # class: # '.' IDENT # attrib: # '[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [IDENT | STRING] S* ]? ']' # pseudo # ':' [ IDENT | FUNCTION S* IDENT? S* ')' ] selector1(p: ref Cparse): list of ref Select { sel: list of ref Select; c := p.get(); if(c == IDENT) sel = ref Select.Element(p.value) :: sel; else if(c== '*') sel = ref Select.Any("*") :: sel; else p.unget(c); Sel: for(;;){ c = p.get(); case c { HASH => sel = ref Select.ID(p.value) :: sel; CLASS => sel = ref Select.Class(p.value) :: sel; '[' => if(!itisa(p, IDENT)) break; name := p.value; case c = p.get() { '=' => sel = ref Select.Attrib(name, "=", optaval(p)) :: sel; INCLUDES => sel = ref Select.Attrib(name, "~=", optaval(p)) :: sel; DASHMATCH => sel = ref Select.Attrib(name, "|=", optaval(p)) :: sel; * => sel = ref Select.Attrib(name, nil, nil) :: sel; p.unget(c); } if((c = p.get()) != ']'){ p.synerr("bad attribute syntax"); p.unget(c); break Sel; } PSEUDO => case c = p.get() { IDENT => sel = ref Select.Pseudo(p.value) :: sel; FUNCTION => name := p.value; case c = p.get() { IDENT => sel = ref Select.Pseudofn(name, lowercase(p.value)) :: sel; ')' => p.unget(c); sel = ref Select.Pseudofn(name, nil) :: sel; * => p.synerr("bad pseudo-function syntax"); p.unget(c); break Sel; } if((c = p.get()) != ')'){ p.synerr("missing ')' for pseudo-function"); p.unget(c); break Sel; } * => p.synerr(sys->sprint("unexpected :pseudo: %s:%s", ptok(c), p.value)); p.unget(c); break Sel; } * => p.unget(c); break Sel; } # qualifiers must be adjacent to the first item, and each other c = p.cs.getc(); p.cs.ungetc(c); if(isspace(c)) break; } sl := sel; for(sel = nil; sl != nil; sl = tl sl) sel = hd sl :: sel; return sel; } optaval(p: ref Cparse): ref Value { case c := p.get() { IDENT => return ref Value.Ident(' ', p.value); STRING => return ref Value.String(' ', p.value); * => p.unget(c); return nil; } } # declaration: # property ':' S* expr prio? # | /* empty */ # property: # IDENT # prio: # IMPORTANT S* /* ! important */ declaration(p: ref Cparse): (ref Decl, string) { c := p.get(); if(c != IDENT){ p.unget(c); return (nil, nil); } prop := lowercase(p.value); c = p.get(); if(c != ':'){ p.unget(c); return (nil, "missing :"); } values := expr(p); if(values == nil) return (nil, "missing expression(s)"); prio := 0; if(p.look() == IMPORTANT){