forsyth20121205-174063a80cc13 years agoHistory
1019 lines · 19.6 KB · Brainfuck
RawDownload
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.