xref: /inferno-os/appl/lib/w3c/css.b (revision 37da2899f40661e3e9631e497da8dc59b971cbd0)
1*37da2899SCharles.Forsythimplement CSS;
2*37da2899SCharles.Forsyth
3*37da2899SCharles.Forsyth#
4*37da2899SCharles.Forsyth# CSS2 parsing module
5*37da2899SCharles.Forsyth#
6*37da2899SCharles.Forsyth# CSS2.1 style sheets
7*37da2899SCharles.Forsyth#
8*37da2899SCharles.Forsyth# Copyright © 2001, 2005 Vita Nuova Holdings Limited.  All rights reserved.
9*37da2899SCharles.Forsyth#
10*37da2899SCharles.Forsyth
11*37da2899SCharles.Forsythinclude "sys.m";
12*37da2899SCharles.Forsyth	sys: Sys;
13*37da2899SCharles.Forsyth
14*37da2899SCharles.Forsythinclude "css.m";
15*37da2899SCharles.Forsyth
16*37da2899SCharles.ForsythB, NUMBER, IDENT, STRING, URL, PERCENTAGE, UNIT,
17*37da2899SCharles.Forsyth	HASH, ATKEYWORD, IMPORTANT, IMPORT, PSEUDO, CLASS, INCLUDES,
18*37da2899SCharles.Forsyth	DASHMATCH, FUNCTION: con 16rE000+iota;
19*37da2899SCharles.Forsyth
20*37da2899SCharles.Forsythtoknames := array[] of{
21*37da2899SCharles.Forsyth	B-B => "Zero",
22*37da2899SCharles.Forsyth	NUMBER-B => "NUMBER",
23*37da2899SCharles.Forsyth	IDENT-B => "IDENT",
24*37da2899SCharles.Forsyth	STRING-B => "STRING",
25*37da2899SCharles.Forsyth	URL-B => "URL",
26*37da2899SCharles.Forsyth	PERCENTAGE-B => "PERCENTAGE",
27*37da2899SCharles.Forsyth	UNIT-B => "UNIT",
28*37da2899SCharles.Forsyth	HASH-B => "HASH",
29*37da2899SCharles.Forsyth	ATKEYWORD-B => "ATKEYWORD",
30*37da2899SCharles.Forsyth	IMPORTANT-B => "IMPORTANT",
31*37da2899SCharles.Forsyth	CLASS-B => "CLASS",
32*37da2899SCharles.Forsyth	INCLUDES-B => "INCLUDES",
33*37da2899SCharles.Forsyth	DASHMATCH-B => "DASHMATCH",
34*37da2899SCharles.Forsyth	PSEUDO-B => "PSEUDO",
35*37da2899SCharles.Forsyth	FUNCTION-B => "FUNCTION",
36*37da2899SCharles.Forsyth};
37*37da2899SCharles.Forsyth
38*37da2899SCharles.Forsythprintdiag := 0;
39*37da2899SCharles.Forsyth
40*37da2899SCharles.Forsythinit(d: int)
41*37da2899SCharles.Forsyth{
42*37da2899SCharles.Forsyth	sys = load Sys Sys->PATH;
43*37da2899SCharles.Forsyth	printdiag = d;
44*37da2899SCharles.Forsyth}
45*37da2899SCharles.Forsyth
46*37da2899SCharles.Forsythparse(s: string): (ref Stylesheet, string)
47*37da2899SCharles.Forsyth{
48*37da2899SCharles.Forsyth	return stylesheet(ref Cparse(-1, 0, nil, nil, Clex.new(s,1)));
49*37da2899SCharles.Forsyth}
50*37da2899SCharles.Forsyth
51*37da2899SCharles.Forsythparsedecl(s: string): (list of ref Decl, string)
52*37da2899SCharles.Forsyth{
53*37da2899SCharles.Forsyth	return (declarations(ref Cparse(-1, 0, nil, nil, Clex.new(s,0))), nil);
54*37da2899SCharles.Forsyth}
55*37da2899SCharles.Forsyth
56*37da2899SCharles.Forsythptok(c: int): string
57*37da2899SCharles.Forsyth{
58*37da2899SCharles.Forsyth	if(c < 0)
59*37da2899SCharles.Forsyth		return "eof";
60*37da2899SCharles.Forsyth	if(c == 0)
61*37da2899SCharles.Forsyth		return "zero?";
62*37da2899SCharles.Forsyth	if(c >= B)
63*37da2899SCharles.Forsyth		return sys->sprint("%s", toknames[c-B]);
64*37da2899SCharles.Forsyth	return sys->sprint("%c", c);
65*37da2899SCharles.Forsyth}
66*37da2899SCharles.Forsyth
67*37da2899SCharles.ForsythCparse: adt {
68*37da2899SCharles.Forsyth	lookahead:	int;
69*37da2899SCharles.Forsyth	eof:	int;
70*37da2899SCharles.Forsyth	value:	string;
71*37da2899SCharles.Forsyth	suffix:	string;
72*37da2899SCharles.Forsyth	cs:	ref Clex;
73*37da2899SCharles.Forsyth
74*37da2899SCharles.Forsyth	get:	fn(nil: self ref Cparse): int;
75*37da2899SCharles.Forsyth	look:	fn(nil: self ref Cparse): int;
76*37da2899SCharles.Forsyth	unget:	fn(nil: self ref Cparse, tok: int);
77*37da2899SCharles.Forsyth	skipto:	fn(nil: self ref Cparse, followset: string): int;
78*37da2899SCharles.Forsyth	synerr:	fn(nil: self ref Cparse, s: string);
79*37da2899SCharles.Forsyth};
80*37da2899SCharles.Forsyth
81*37da2899SCharles.ForsythCparse.get(p: self ref Cparse): int
82*37da2899SCharles.Forsyth{
83*37da2899SCharles.Forsyth	if((c := p.lookahead) >= 0){
84*37da2899SCharles.Forsyth		p.lookahead = -1;
85*37da2899SCharles.Forsyth		return c;
86*37da2899SCharles.Forsyth	}
87*37da2899SCharles.Forsyth	if(p.eof)
88*37da2899SCharles.Forsyth		return -1;
89*37da2899SCharles.Forsyth	(c, p.value, p.suffix) = csslex(p.cs);
90*37da2899SCharles.Forsyth	if(c < 0)
91*37da2899SCharles.Forsyth		p.eof = 1;
92*37da2899SCharles.Forsyth	if(printdiag > 1)
93*37da2899SCharles.Forsyth		sys->print("lex: %s v=%s s=%s\n", ptok(c), p.value, p.suffix);
94*37da2899SCharles.Forsyth	return c;
95*37da2899SCharles.Forsyth}
96*37da2899SCharles.Forsyth
97*37da2899SCharles.ForsythCparse.look(p: self ref Cparse): int
98*37da2899SCharles.Forsyth{
99*37da2899SCharles.Forsyth	c := p.get();
100*37da2899SCharles.Forsyth	p.unget(c);
101*37da2899SCharles.Forsyth	return c;
102*37da2899SCharles.Forsyth}
103*37da2899SCharles.Forsyth
104*37da2899SCharles.ForsythCparse.unget(p: self ref Cparse, c: int)
105*37da2899SCharles.Forsyth{
106*37da2899SCharles.Forsyth	if(p.lookahead >= 0)
107*37da2899SCharles.Forsyth		raise "css: internal error: Cparse.unget";
108*37da2899SCharles.Forsyth	p.lookahead = c;	# note that p.value and p.suffix are assumed to be those of c
109*37da2899SCharles.Forsyth}
110*37da2899SCharles.Forsyth
111*37da2899SCharles.ForsythCparse.skipto(p: self ref Cparse, followset: string): int
112*37da2899SCharles.Forsyth{
113*37da2899SCharles.Forsyth	while((c := p.get()) >= 0)
114*37da2899SCharles.Forsyth		for(i := 0; i < len followset; i++)
115*37da2899SCharles.Forsyth			if(followset[i] == c){
116*37da2899SCharles.Forsyth				p.unget(c);
117*37da2899SCharles.Forsyth				return c;
118*37da2899SCharles.Forsyth			}
119*37da2899SCharles.Forsyth	return -1;
120*37da2899SCharles.Forsyth}
121*37da2899SCharles.Forsyth
122*37da2899SCharles.ForsythCparse.synerr(p: self ref Cparse, s: string)
123*37da2899SCharles.Forsyth{
124*37da2899SCharles.Forsyth	p.cs.synerr(s);
125*37da2899SCharles.Forsyth}
126*37da2899SCharles.Forsyth
127*37da2899SCharles.Forsyth#
128*37da2899SCharles.Forsyth# stylesheet:
129*37da2899SCharles.Forsyth#	["@charset" STRING ';']?
130*37da2899SCharles.Forsyth#	[CDO|CDC]* [import [CDO|CDC]*]*
131*37da2899SCharles.Forsyth#	[[ruleset | media | page ] [CDO|CDC]*]*
132*37da2899SCharles.Forsyth# import:
133*37da2899SCharles.Forsyth#	"@import" [STRING|URL] [ medium [',' medium]*]? ';'
134*37da2899SCharles.Forsyth# media:
135*37da2899SCharles.Forsyth#	"@media" medium [',' medium]* '{' ruleset* '}'
136*37da2899SCharles.Forsyth# medium:
137*37da2899SCharles.Forsyth#	IDENT
138*37da2899SCharles.Forsyth# page:
139*37da2899SCharles.Forsyth#	"@page" pseudo_page? '{' declaration [';' declaration]* '}'
140*37da2899SCharles.Forsyth# pseudo_page:
141*37da2899SCharles.Forsyth#	':' IDENT
142*37da2899SCharles.Forsyth#
143*37da2899SCharles.Forsyth
144*37da2899SCharles.Forsythstylesheet(p: ref Cparse): (ref Stylesheet, string)
145*37da2899SCharles.Forsyth{
146*37da2899SCharles.Forsyth	charset: string;
147*37da2899SCharles.Forsyth	if(atkeywd(p, "@charset")){
148*37da2899SCharles.Forsyth		if(itisa(p, STRING)){
149*37da2899SCharles.Forsyth			charset = p.value;
150*37da2899SCharles.Forsyth			itisa(p, ';');
151*37da2899SCharles.Forsyth		}else
152*37da2899SCharles.Forsyth			p.synerr("bad @charset declaration");
153*37da2899SCharles.Forsyth	}
154*37da2899SCharles.Forsyth	imports: list of ref Import;
155*37da2899SCharles.Forsyth	while(atkeywd(p, "@import")){
156*37da2899SCharles.Forsyth		c := p.get();
157*37da2899SCharles.Forsyth		if(c == STRING || c == URL){
158*37da2899SCharles.Forsyth			name := p.value;
159*37da2899SCharles.Forsyth			media: list of string;
160*37da2899SCharles.Forsyth			c = p.get();
161*37da2899SCharles.Forsyth			if(c == IDENT){	# optional medium [, ...]
162*37da2899SCharles.Forsyth				p.unget(c);
163*37da2899SCharles.Forsyth				media = medialist(p);
164*37da2899SCharles.Forsyth			}
165*37da2899SCharles.Forsyth			imports = ref Import(name, media) :: imports;
166*37da2899SCharles.Forsyth		}else
167*37da2899SCharles.Forsyth			p.synerr("bad @import");
168*37da2899SCharles.Forsyth		if(c != ';'){
169*37da2899SCharles.Forsyth			p.synerr("missing ; in @import");
170*37da2899SCharles.Forsyth			p.unget(c);
171*37da2899SCharles.Forsyth			if(p.skipto(";}") < 0)
172*37da2899SCharles.Forsyth				break;
173*37da2899SCharles.Forsyth		}
174*37da2899SCharles.Forsyth	}
175*37da2899SCharles.Forsyth	imports = rev(imports);
176*37da2899SCharles.Forsyth
177*37da2899SCharles.Forsyth	stmts: list of ref Statement;
178*37da2899SCharles.Forsyth	do{
179*37da2899SCharles.Forsyth		while((c := p.get()) == ATKEYWORD)
180*37da2899SCharles.Forsyth			case p.value {
181*37da2899SCharles.Forsyth			"@media" =>	# medium[,medium]* { ruleset*}
182*37da2899SCharles.Forsyth				media := medialist(p);
183*37da2899SCharles.Forsyth				if(!itisa(p, '{')){
184*37da2899SCharles.Forsyth					p.synerr("bad @media");
185*37da2899SCharles.Forsyth					skipatrule("@media", p);
186*37da2899SCharles.Forsyth					continue;
187*37da2899SCharles.Forsyth				}
188*37da2899SCharles.Forsyth				rules: list of ref Statement.Ruleset;
189*37da2899SCharles.Forsyth				do{
190*37da2899SCharles.Forsyth					rule := checkrule(p);
191*37da2899SCharles.Forsyth					if(rule != nil)
192*37da2899SCharles.Forsyth						rules = rule :: rules;
193*37da2899SCharles.Forsyth				}while(!itisa(p, '}') && !p.eof);
194*37da2899SCharles.Forsyth				stmts = ref Statement.Media(media, rev(rules)) :: stmts;
195*37da2899SCharles.Forsyth			"@page" =>	# [:ident]? { declaration [; declaration]* }
196*37da2899SCharles.Forsyth				pseudo: string;
197*37da2899SCharles.Forsyth				if(itisa(p, PSEUDO))
198*37da2899SCharles.Forsyth					pseudo = p.value;
199*37da2899SCharles.Forsyth				if(!itisa(p, '{')){
200*37da2899SCharles.Forsyth					p.synerr("bad @page");
201*37da2899SCharles.Forsyth					skipatrule("@page", p);
202*37da2899SCharles.Forsyth					continue;
203*37da2899SCharles.Forsyth				}
204*37da2899SCharles.Forsyth				decls := declarations(p);
205*37da2899SCharles.Forsyth				if(!itisa(p, '}')){
206*37da2899SCharles.Forsyth					p.synerr("unclosed @page declaration block");
207*37da2899SCharles.Forsyth					skipatrule("@page", p);
208*37da2899SCharles.Forsyth					continue;
209*37da2899SCharles.Forsyth				}
210*37da2899SCharles.Forsyth				stmts = ref Statement.Page(pseudo, decls) :: stmts;
211*37da2899SCharles.Forsyth			* =>
212*37da2899SCharles.Forsyth				skipatrule(p.value, p);	# skip unknown or misplaced at-rule
213*37da2899SCharles.Forsyth			}
214*37da2899SCharles.Forsyth		p.unget(c);
215*37da2899SCharles.Forsyth		rule := checkrule(p);
216*37da2899SCharles.Forsyth		if(rule != nil)
217*37da2899SCharles.Forsyth			stmts = rule :: stmts;
218*37da2899SCharles.Forsyth	}while(!p.eof);
219*37da2899SCharles.Forsyth	rl := stmts;
220*37da2899SCharles.Forsyth	stmts = nil;
221*37da2899SCharles.Forsyth	for(; rl != nil; rl = tl rl)
222*37da2899SCharles.Forsyth		stmts = hd rl :: stmts;
223*37da2899SCharles.Forsyth	return (ref Stylesheet(charset, imports, stmts), nil);
224*37da2899SCharles.Forsyth}
225*37da2899SCharles.Forsyth
226*37da2899SCharles.Forsythcheckrule(p: ref Cparse): ref Statement.Ruleset
227*37da2899SCharles.Forsyth{
228*37da2899SCharles.Forsyth	(rule, err) := ruleset(p);
229*37da2899SCharles.Forsyth	if(rule == nil){
230*37da2899SCharles.Forsyth		if(err != nil){
231*37da2899SCharles.Forsyth			p.synerr(sys->sprint("bad ruleset: %s", err));
232*37da2899SCharles.Forsyth			p.get();	# make some progress
233*37da2899SCharles.Forsyth		}
234*37da2899SCharles.Forsyth	}
235*37da2899SCharles.Forsyth	return rule;
236*37da2899SCharles.Forsyth}
237*37da2899SCharles.Forsyth
238*37da2899SCharles.Forsythmedialist(p: ref Cparse): list of string
239*37da2899SCharles.Forsyth{
240*37da2899SCharles.Forsyth	media: list of string;
241*37da2899SCharles.Forsyth	do{
242*37da2899SCharles.Forsyth		c := p.get();
243*37da2899SCharles.Forsyth		if(c != IDENT){
244*37da2899SCharles.Forsyth			p.unget(c);
245*37da2899SCharles.Forsyth			p.synerr("missing medium identifier");
246*37da2899SCharles.Forsyth			break;
247*37da2899SCharles.Forsyth		}
248*37da2899SCharles.Forsyth		media = p.value :: media;
249*37da2899SCharles.Forsyth	}while(itisa(p, ','));
250*37da2899SCharles.Forsyth	return rev(media);
251*37da2899SCharles.Forsyth}
252*37da2899SCharles.Forsyth
253*37da2899SCharles.Forsythitisa(p: ref Cparse, expect: int): int
254*37da2899SCharles.Forsyth{
255*37da2899SCharles.Forsyth	if((c := p.get()) == expect)
256*37da2899SCharles.Forsyth		return 1;
257*37da2899SCharles.Forsyth	p.unget(c);
258*37da2899SCharles.Forsyth	return 0;
259*37da2899SCharles.Forsyth}
260*37da2899SCharles.Forsyth
261*37da2899SCharles.Forsythatkeywd(p: ref Cparse, expect: string): int
262*37da2899SCharles.Forsyth{
263*37da2899SCharles.Forsyth	if((c := p.get()) == ATKEYWORD && p.value == expect)
264*37da2899SCharles.Forsyth		return 1;
265*37da2899SCharles.Forsyth	p.unget(c);
266*37da2899SCharles.Forsyth	return 0;
267*37da2899SCharles.Forsyth}
268*37da2899SCharles.Forsyth
269*37da2899SCharles.Forsythskipatrule(name: string, p: ref Cparse)
270*37da2899SCharles.Forsyth{
271*37da2899SCharles.Forsyth	if(printdiag)
272*37da2899SCharles.Forsyth		sys->print("skip unimplemented or misplaced %s\n", name);
273*37da2899SCharles.Forsyth	if((c := p.get()) == '{'){	# block
274*37da2899SCharles.Forsyth		for(nesting := '}' :: nil; nesting != nil && c >= 0; nesting = tl nesting){
275*37da2899SCharles.Forsyth			while((c = p.cs.getc()) >= 0 && c != hd nesting)
276*37da2899SCharles.Forsyth				case c {
277*37da2899SCharles.Forsyth				'{' =>
278*37da2899SCharles.Forsyth					nesting = '}' :: nesting;
279*37da2899SCharles.Forsyth				'(' =>
280*37da2899SCharles.Forsyth					nesting = ')' :: nesting;
281*37da2899SCharles.Forsyth				'[' =>
282*37da2899SCharles.Forsyth					nesting = ']' :: nesting;
283*37da2899SCharles.Forsyth				'"' or '\'' =>
284*37da2899SCharles.Forsyth					quotedstring(p.cs, c);
285*37da2899SCharles.Forsyth				}
286*37da2899SCharles.Forsyth		}
287*37da2899SCharles.Forsyth	}else{
288*37da2899SCharles.Forsyth		while(c >= 0 && c != ';')
289*37da2899SCharles.Forsyth			c = p.get();
290*37da2899SCharles.Forsyth	}
291*37da2899SCharles.Forsyth}
292*37da2899SCharles.Forsyth
293*37da2899SCharles.Forsyth# ruleset:
294*37da2899SCharles.Forsyth#	selector [','  S* selector]* '{' S* declaration [';' S* declaration]* '}' S*
295*37da2899SCharles.Forsyth
296*37da2899SCharles.Forsythruleset(p: ref Cparse): (ref Statement.Ruleset, string)
297*37da2899SCharles.Forsyth{
298*37da2899SCharles.Forsyth	selectors: list of list of (int, list of ref Select);
299*37da2899SCharles.Forsyth	c := -1;
300*37da2899SCharles.Forsyth	do{
301*37da2899SCharles.Forsyth		s := selector(p);
302*37da2899SCharles.Forsyth		if(s == nil){
303*37da2899SCharles.Forsyth			if(p.eof)
304*37da2899SCharles.Forsyth				return (nil, nil);
305*37da2899SCharles.Forsyth			p.synerr("expected selector");
306*37da2899SCharles.Forsyth			if(p.skipto(",{}") < 0)
307*37da2899SCharles.Forsyth				return (nil, nil);
308*37da2899SCharles.Forsyth			c = p.look();
309*37da2899SCharles.Forsyth		}else
310*37da2899SCharles.Forsyth			selectors = s :: selectors;
311*37da2899SCharles.Forsyth	}while((c = p.get()) == ',');
312*37da2899SCharles.Forsyth	if(c != '{')
313*37da2899SCharles.Forsyth		return (nil, "expected declaration block");
314*37da2899SCharles.Forsyth	sl := selectors;
315*37da2899SCharles.Forsyth	selectors = nil;
316*37da2899SCharles.Forsyth	for(; sl != nil; sl = tl sl)
317*37da2899SCharles.Forsyth		selectors = hd sl :: selectors;
318*37da2899SCharles.Forsyth	decls := declarations(p);
319*37da2899SCharles.Forsyth	if(!itisa(p, '}')){
320*37da2899SCharles.Forsyth		p.synerr("unclosed declaration block");
321*37da2899SCharles.Forsyth	}
322*37da2899SCharles.Forsyth	return (ref Statement.Ruleset(selectors, decls), nil);
323*37da2899SCharles.Forsyth}
324*37da2899SCharles.Forsyth
325*37da2899SCharles.Forsythdeclarations(p: ref Cparse): list of ref Decl
326*37da2899SCharles.Forsyth{
327*37da2899SCharles.Forsyth	decls: list of ref Decl;
328*37da2899SCharles.Forsyth	c: int;
329*37da2899SCharles.Forsyth	do{
330*37da2899SCharles.Forsyth		(d, e) := declaration(p);
331*37da2899SCharles.Forsyth		if(d != nil)
332*37da2899SCharles.Forsyth			decls = d :: decls;
333*37da2899SCharles.Forsyth		else if(e != nil){
334*37da2899SCharles.Forsyth			p.synerr("ruleset declaration: "+e);
335*37da2899SCharles.Forsyth			if((c = p.skipto(";}")) < 0)
336*37da2899SCharles.Forsyth				break;
337*37da2899SCharles.Forsyth		}
338*37da2899SCharles.Forsyth	}while((c = p.get()) == ';');
339*37da2899SCharles.Forsyth	p.unget(c);
340*37da2899SCharles.Forsyth	l := decls;
341*37da2899SCharles.Forsyth	for(decls = nil; l != nil; l = tl l)
342*37da2899SCharles.Forsyth		decls = hd l :: decls;
343*37da2899SCharles.Forsyth	return decls;
344*37da2899SCharles.Forsyth}
345*37da2899SCharles.Forsyth
346*37da2899SCharles.Forsyth# selector:
347*37da2899SCharles.Forsyth#	simple_selector [combinator simple_selector]*
348*37da2899SCharles.Forsyth# combinator:
349*37da2899SCharles.Forsyth#	'+' S* | '>' S* | /* empty */
350*37da2899SCharles.Forsyth#
351*37da2899SCharles.Forsyth
352*37da2899SCharles.Forsythselector(p: ref Cparse): list of (int, list of ref Select)
353*37da2899SCharles.Forsyth{
354*37da2899SCharles.Forsyth	sel: list of (int, list of ref Select);
355*37da2899SCharles.Forsyth	op := ' ';
356*37da2899SCharles.Forsyth	while((s := selector1(p)) != nil){
357*37da2899SCharles.Forsyth		sel = (op, s) :: sel;
358*37da2899SCharles.Forsyth		if((c := p.look()) == '+' || c == '>')
359*37da2899SCharles.Forsyth			op = p.get();
360*37da2899SCharles.Forsyth		else
361*37da2899SCharles.Forsyth			op = ' ';
362*37da2899SCharles.Forsyth	}
363*37da2899SCharles.Forsyth	l: list of (int, list of ref Select);
364*37da2899SCharles.Forsyth	for(; sel != nil; sel = tl sel)
365*37da2899SCharles.Forsyth		l = hd sel :: l;
366*37da2899SCharles.Forsyth	return l;
367*37da2899SCharles.Forsyth}
368*37da2899SCharles.Forsyth
369*37da2899SCharles.Forsyth#
370*37da2899SCharles.Forsyth# simple_selector:
371*37da2899SCharles.Forsyth#	element_name? [HASH | class | attrib | pseudo]* S*
372*37da2899SCharles.Forsyth# element_name:
373*37da2899SCharles.Forsyth#	IDENT | '*'
374*37da2899SCharles.Forsyth# class:
375*37da2899SCharles.Forsyth#	'.' IDENT
376*37da2899SCharles.Forsyth# attrib:
377*37da2899SCharles.Forsyth#	'[' S* IDENT S* [ [ '=' | INCLUDES | DASHMATCH ] S* [IDENT | STRING] S* ]? ']'
378*37da2899SCharles.Forsyth# pseudo
379*37da2899SCharles.Forsyth#	':' [ IDENT | FUNCTION S* IDENT? S* ')' ]
380*37da2899SCharles.Forsyth
381*37da2899SCharles.Forsythselector1(p: ref Cparse): list of ref Select
382*37da2899SCharles.Forsyth{
383*37da2899SCharles.Forsyth	sel: list of ref Select;
384*37da2899SCharles.Forsyth	c := p.get();
385*37da2899SCharles.Forsyth	if(c == IDENT)
386*37da2899SCharles.Forsyth		sel = ref Select.Element(p.value) :: sel;
387*37da2899SCharles.Forsyth	else if(c== '*')
388*37da2899SCharles.Forsyth		sel = ref Select.Any("*") :: sel;
389*37da2899SCharles.Forsyth	else
390*37da2899SCharles.Forsyth		p.unget(c);
391*37da2899SCharles.ForsythSel:
392*37da2899SCharles.Forsyth	for(;;){
393*37da2899SCharles.Forsyth		c = p.get();
394*37da2899SCharles.Forsyth		case c {
395*37da2899SCharles.Forsyth		HASH =>
396*37da2899SCharles.Forsyth			sel = ref Select.ID(p.value) :: sel;
397*37da2899SCharles.Forsyth		CLASS =>
398*37da2899SCharles.Forsyth			sel = ref Select.Class(p.value) :: sel;
399*37da2899SCharles.Forsyth		'[' =>
400*37da2899SCharles.Forsyth			if(!itisa(p, IDENT))
401*37da2899SCharles.Forsyth				break;
402*37da2899SCharles.Forsyth			name := p.value;
403*37da2899SCharles.Forsyth			case c = p.get() {
404*37da2899SCharles.Forsyth			'=' =>
405*37da2899SCharles.Forsyth				sel = ref Select.Attrib(name, "=", optaval(p)) :: sel;
406*37da2899SCharles.Forsyth			INCLUDES =>
407*37da2899SCharles.Forsyth				sel = ref Select.Attrib(name, "~=", optaval(p)) :: sel;
408*37da2899SCharles.Forsyth			DASHMATCH =>
409*37da2899SCharles.Forsyth				sel = ref Select.Attrib(name, "|=", optaval(p)) :: sel;
410*37da2899SCharles.Forsyth			* =>
411*37da2899SCharles.Forsyth				sel = ref Select.Attrib(name, nil, nil) :: sel;
412*37da2899SCharles.Forsyth				p.unget(c);
413*37da2899SCharles.Forsyth			}
414*37da2899SCharles.Forsyth			if((c = p.get()) != ']'){
415*37da2899SCharles.Forsyth				p.synerr("bad attribute syntax");
416*37da2899SCharles.Forsyth				p.unget(c);
417*37da2899SCharles.Forsyth				break Sel;
418*37da2899SCharles.Forsyth			}
419*37da2899SCharles.Forsyth		PSEUDO =>
420*37da2899SCharles.Forsyth			case c = p.get() {
421*37da2899SCharles.Forsyth			IDENT =>
422*37da2899SCharles.Forsyth				sel = ref Select.Pseudo(p.value) :: sel;
423*37da2899SCharles.Forsyth			FUNCTION =>
424*37da2899SCharles.Forsyth				name := p.value;
425*37da2899SCharles.Forsyth				case c = p.get() {
426*37da2899SCharles.Forsyth				IDENT =>
427*37da2899SCharles.Forsyth					sel = ref Select.Pseudofn(name, lowercase(p.value)) :: sel;
428*37da2899SCharles.Forsyth				')' =>
429*37da2899SCharles.Forsyth					p.unget(c);
430*37da2899SCharles.Forsyth					sel = ref Select.Pseudofn(name, nil) :: sel;
431*37da2899SCharles.Forsyth				* =>
432*37da2899SCharles.Forsyth					p.synerr("bad pseudo-function syntax");
433*37da2899SCharles.Forsyth					p.unget(c);
434*37da2899SCharles.Forsyth					break Sel;
435*37da2899SCharles.Forsyth				}
436*37da2899SCharles.Forsyth				if((c = p.get()) != ')'){
437*37da2899SCharles.Forsyth					p.synerr("missing ')' for pseudo-function");
438*37da2899SCharles.Forsyth					p.unget(c);
439*37da2899SCharles.Forsyth					break Sel;
440*37da2899SCharles.Forsyth				}
441*37da2899SCharles.Forsyth			* =>
442*37da2899SCharles.Forsyth				p.synerr(sys->sprint("unexpected :pseudo: %s:%s", ptok(c), p.value));
443*37da2899SCharles.Forsyth				p.unget(c);
444*37da2899SCharles.Forsyth				break Sel;
445*37da2899SCharles.Forsyth			}
446*37da2899SCharles.Forsyth		* =>
447*37da2899SCharles.Forsyth			p.unget(c);
448*37da2899SCharles.Forsyth			break Sel;
449*37da2899SCharles.Forsyth		}
450*37da2899SCharles.Forsyth		# qualifiers must be adjacent to the first item, and each other
451*37da2899SCharles.Forsyth		c = p.cs.getc();
452*37da2899SCharles.Forsyth		p.cs.ungetc(c);
453*37da2899SCharles.Forsyth		if(isspace(c))
454*37da2899SCharles.Forsyth			break;
455*37da2899SCharles.Forsyth	}
456*37da2899SCharles.Forsyth	sl := sel;
457*37da2899SCharles.Forsyth	for(sel = nil; sl != nil; sl = tl sl)
458*37da2899SCharles.Forsyth		sel = hd sl :: sel;
459*37da2899SCharles.Forsyth	return sel;
460*37da2899SCharles.Forsyth}
461*37da2899SCharles.Forsyth
462*37da2899SCharles.Forsythoptaval(p: ref Cparse): ref Value
463*37da2899SCharles.Forsyth{
464*37da2899SCharles.Forsyth	case c := p.get() {
465*37da2899SCharles.Forsyth	IDENT =>
466*37da2899SCharles.Forsyth		return ref Value.Ident(' ', p.value);
467*37da2899SCharles.Forsyth	STRING =>
468*37da2899SCharles.Forsyth		return ref Value.String(' ', p.value);
469*37da2899SCharles.Forsyth	* =>
470*37da2899SCharles.Forsyth		p.unget(c);
471*37da2899SCharles.Forsyth		return nil;
472*37da2899SCharles.Forsyth	}
473*37da2899SCharles.Forsyth}
474*37da2899SCharles.Forsyth
475*37da2899SCharles.Forsyth# declaration:
476*37da2899SCharles.Forsyth#	property ':' S* expr prio?
477*37da2899SCharles.Forsyth#  |	/* empty */
478*37da2899SCharles.Forsyth# property:
479*37da2899SCharles.Forsyth#	IDENT
480*37da2899SCharles.Forsyth# prio:
481*37da2899SCharles.Forsyth#	IMPORTANT S*	/* ! important */
482*37da2899SCharles.Forsyth
483*37da2899SCharles.Forsythdeclaration(p: ref Cparse): (ref Decl, string)
484*37da2899SCharles.Forsyth{
485*37da2899SCharles.Forsyth	c := p.get();
486*37da2899SCharles.Forsyth	if(c != IDENT){
487*37da2899SCharles.Forsyth		p.unget(c);
488*37da2899SCharles.Forsyth		return (nil, nil);
489*37da2899SCharles.Forsyth	}
490*37da2899SCharles.Forsyth	prop := lowercase(p.value);
491*37da2899SCharles.Forsyth	c = p.get();
492*37da2899SCharles.Forsyth	if(c != ':'){
493*37da2899SCharles.Forsyth		p.unget(c);
494*37da2899SCharles.Forsyth		return (nil, "missing :");
495*37da2899SCharles.Forsyth	}
496*37da2899SCharles.Forsyth	values := expr(p);
497*37da2899SCharles.Forsyth	if(values == nil)
498*37da2899SCharles.Forsyth		return (nil, "missing expression(s)");
499*37da2899SCharles.Forsyth	prio := 0;
500*37da2899SCharles.Forsyth	if(p.look() == IMPORTANT){
501*37da2899SCharles.Forsyth		p.get();
502*37da2899SCharles.Forsyth		prio = 1;
503*37da2899SCharles.Forsyth	}
504*37da2899SCharles.Forsyth	return (ref Decl(prop, values, prio), nil);
505*37da2899SCharles.Forsyth}
506*37da2899SCharles.Forsyth
507*37da2899SCharles.Forsyth# expr:
508*37da2899SCharles.Forsyth#	term [operator term]*
509*37da2899SCharles.Forsyth# operator:
510*37da2899SCharles.Forsyth#	'/' | ',' | /* empty */
511*37da2899SCharles.Forsyth
512*37da2899SCharles.Forsythexpr(p: ref Cparse): list of ref Value
513*37da2899SCharles.Forsyth{
514*37da2899SCharles.Forsyth	values: list of ref Value;
515*37da2899SCharles.Forsyth	sep := ' ';
516*37da2899SCharles.Forsyth	while((t := term(p, sep)) != nil){
517*37da2899SCharles.Forsyth		values = t :: values;
518*37da2899SCharles.Forsyth		if((c := p.look()) == '/' || c == ',')
519*37da2899SCharles.Forsyth			sep = p.get();		# need something fancier here?
520*37da2899SCharles.Forsyth		else
521*37da2899SCharles.Forsyth			sep = ' ';
522*37da2899SCharles.Forsyth	}
523*37da2899SCharles.Forsyth	vl := values;
524*37da2899SCharles.Forsyth	for(values = nil; vl != nil; vl = tl vl)
525*37da2899SCharles.Forsyth		values = hd vl :: values;
526*37da2899SCharles.Forsyth	return values;
527*37da2899SCharles.Forsyth}
528*37da2899SCharles.Forsyth
529*37da2899SCharles.Forsyth#
530*37da2899SCharles.Forsyth# term:
531*37da2899SCharles.Forsyth#	unary_operator? [NUMBER | PERCENTAGE | LENGTH | EMS | EXS | ANGLE | TIME | FREQ | function]
532*37da2899SCharles.Forsyth#	| STRING | IDENT | URI | RGB | UNICODERANGE | hexcolour
533*37da2899SCharles.Forsyth# function:
534*37da2899SCharles.Forsyth#	FUNCTION expr ')'
535*37da2899SCharles.Forsyth# unary_operator:
536*37da2899SCharles.Forsyth#	'-' | '+'
537*37da2899SCharles.Forsyth# hexcolour:
538*37da2899SCharles.Forsyth#	HASH S*
539*37da2899SCharles.Forsyth#
540*37da2899SCharles.Forsyth# LENGTH, EMS, ... FREQ have been combined into UNIT here
541*37da2899SCharles.Forsyth#
542*37da2899SCharles.Forsyth# TO DO: UNICODERANGE
543*37da2899SCharles.Forsyth
544*37da2899SCharles.Forsythterm(p: ref Cparse, sep: int): ref Value
545*37da2899SCharles.Forsyth{
546*37da2899SCharles.Forsyth	prefix: string;
547*37da2899SCharles.Forsyth	case p.look(){
548*37da2899SCharles.Forsyth	'+' or '-' =>
549*37da2899SCharles.Forsyth		prefix[0] = p.get();
550*37da2899SCharles.Forsyth	}
551*37da2899SCharles.Forsyth	c := p.get();
552*37da2899SCharles.Forsyth	case c {
553*37da2899SCharles.Forsyth	NUMBER =>
554*37da2899SCharles.Forsyth		return ref Value.Number(sep, prefix+p.value);
555*37da2899SCharles.Forsyth	PERCENTAGE =>
556*37da2899SCharles.Forsyth		return ref Value.Percentage(sep, prefix+p.value);
557*37da2899SCharles.Forsyth	UNIT =>
558*37da2899SCharles.Forsyth		return ref Value.Unit(sep, prefix+p.value, p.suffix);
559*37da2899SCharles.Forsyth	}
560*37da2899SCharles.Forsyth	if(prefix != nil)
561*37da2899SCharles.Forsyth		p.synerr("+/- before non-numeric");
562*37da2899SCharles.Forsyth	case c {
563*37da2899SCharles.Forsyth	STRING =>
564*37da2899SCharles.Forsyth		return ref Value.String(sep, p.value);
565*37da2899SCharles.Forsyth	IDENT =>
566*37da2899SCharles.Forsyth		return ref Value.Ident(sep, lowercase(p.value));
567*37da2899SCharles.Forsyth	URL =>
568*37da2899SCharles.Forsyth		return ref Value.Url(sep, p.value);
569*37da2899SCharles.Forsyth	HASH =>
570*37da2899SCharles.Forsyth		# could check value: 3 or 6 hex digits
571*37da2899SCharles.Forsyth		(r, g, b) := torgb(p.value);
572*37da2899SCharles.Forsyth		if(r < 0)
573*37da2899SCharles.Forsyth			return nil;
574*37da2899SCharles.Forsyth		return ref Value.Hexcolour(sep, p.value, (r,g,b));
575*37da2899SCharles.Forsyth	FUNCTION =>
576*37da2899SCharles.Forsyth		name := p.value;
577*37da2899SCharles.Forsyth		args := expr(p);
578*37da2899SCharles.Forsyth		c = p.get();
579*37da2899SCharles.Forsyth		if(c != ')'){
580*37da2899SCharles.Forsyth			p.synerr(sys->sprint("missing ')' for function %s", name));
581*37da2899SCharles.Forsyth			return nil;
582*37da2899SCharles.Forsyth		}
583*37da2899SCharles.Forsyth		if(name == "rgb"){
584*37da2899SCharles.Forsyth			if(len args != 3){
585*37da2899SCharles.Forsyth				p.synerr("wrong number of arguments to rgb()");
586*37da2899SCharles.Forsyth				return nil;
587*37da2899SCharles.Forsyth			}
588*37da2899SCharles.Forsyth			r := colourof(hd args);
589*37da2899SCharles.Forsyth			g := colourof(hd tl args);
590*37da2899SCharles.Forsyth			b := colourof(hd tl tl args);
591*37da2899SCharles.Forsyth			if(r < 0 || g < 0 || b < 0){
592*37da2899SCharles.Forsyth				p.synerr("invalid rgb() parameters");
593*37da2899SCharles.Forsyth				return nil;
594*37da2899SCharles.Forsyth			}
595*37da2899SCharles.Forsyth			return ref Value.RGB(sep, args, (r,g,b));
596*37da2899SCharles.Forsyth		}
597*37da2899SCharles.Forsyth		return ref Value.Function(sep, name, args);
598*37da2899SCharles.Forsyth	* =>
599*37da2899SCharles.Forsyth		p.unget(c);
600*37da2899SCharles.Forsyth		return nil;
601*37da2899SCharles.Forsyth	}
602*37da2899SCharles.Forsyth}
603*37da2899SCharles.Forsyth
604*37da2899SCharles.Forsythtorgb(s: string): (int, int, int)
605*37da2899SCharles.Forsyth{
606*37da2899SCharles.Forsyth	case len s {
607*37da2899SCharles.Forsyth	3 =>
608*37da2899SCharles.Forsyth		r := hex(s[0]);
609*37da2899SCharles.Forsyth		g := hex(s[1]);
610*37da2899SCharles.Forsyth		b := hex(s[2]);
611*37da2899SCharles.Forsyth		if(r >= 0 && g >= 0 && b >= 0)
612*37da2899SCharles.Forsyth			return ((r<<4)|r, (g<<4)|g, (b<<4)|b);
613*37da2899SCharles.Forsyth	6 =>
614*37da2899SCharles.Forsyth		v := 0;
615*37da2899SCharles.Forsyth		for(i := 0; i < 6; i++){
616*37da2899SCharles.Forsyth			n := hex(s[i]);
617*37da2899SCharles.Forsyth			if(n < 0)
618*37da2899SCharles.Forsyth				return (-1, 0, 0);
619*37da2899SCharles.Forsyth			v = (v<<4) | n;
620*37da2899SCharles.Forsyth		}
621*37da2899SCharles.Forsyth		return (v>>16, (v>>8)&16rFF, v&16rFF);
622*37da2899SCharles.Forsyth	}
623*37da2899SCharles.Forsyth	return (-1, 0, 0);
624*37da2899SCharles.Forsyth}
625*37da2899SCharles.Forsyth
626*37da2899SCharles.Forsythcolourof(v: ref Value): int
627*37da2899SCharles.Forsyth{
628*37da2899SCharles.Forsyth	pick r := v {
629*37da2899SCharles.Forsyth	Number =>
630*37da2899SCharles.Forsyth		return clip(int r.value, 0, 255);
631*37da2899SCharles.Forsyth	Percentage =>
632*37da2899SCharles.Forsyth		# just the integer part
633*37da2899SCharles.Forsyth		return clip((int r.value*255 + 50)/100, 0, 255);
634*37da2899SCharles.Forsyth	* =>
635*37da2899SCharles.Forsyth		return -1;
636*37da2899SCharles.Forsyth	}
637*37da2899SCharles.Forsyth}
638*37da2899SCharles.Forsyth
639*37da2899SCharles.Forsythclip(v: int, l: int, u: int): int
640*37da2899SCharles.Forsyth{
641*37da2899SCharles.Forsyth	if(v < l)
642*37da2899SCharles.Forsyth		return l;
643*37da2899SCharles.Forsyth	if(v > u)
644*37da2899SCharles.Forsyth		return u;
645*37da2899SCharles.Forsyth	return v;
646*37da2899SCharles.Forsyth}
647*37da2899SCharles.Forsyth
648*37da2899SCharles.Forsythrev[T](l: list of T): list of T
649*37da2899SCharles.Forsyth{
650*37da2899SCharles.Forsyth	t: list of T;
651*37da2899SCharles.Forsyth	for(; l != nil; l = tl l)
652*37da2899SCharles.Forsyth		t = hd l :: t;
653*37da2899SCharles.Forsyth	return t;
654*37da2899SCharles.Forsyth}
655*37da2899SCharles.Forsyth
656*37da2899SCharles.ForsythClex: adt {
657*37da2899SCharles.Forsyth	context:	list of int;	# characters
658*37da2899SCharles.Forsyth	input:	string;
659*37da2899SCharles.Forsyth	lim:	int;
660*37da2899SCharles.Forsyth	n:	int;
661*37da2899SCharles.Forsyth	lineno:	int;
662*37da2899SCharles.Forsyth
663*37da2899SCharles.Forsyth	new:	fn(s: string, lno: int): ref Clex;
664*37da2899SCharles.Forsyth	getc:	fn(cs: self ref Clex): int;
665*37da2899SCharles.Forsyth	ungetc:	fn(cs: self ref Clex, c: int);
666*37da2899SCharles.Forsyth	synerr:	fn(nil: self ref Clex, s: string);
667*37da2899SCharles.Forsyth};
668*37da2899SCharles.Forsyth
669*37da2899SCharles.ForsythClex.new(s: string, lno: int): ref Clex
670*37da2899SCharles.Forsyth{
671*37da2899SCharles.Forsyth	return ref Clex(nil, s, len s, 0, lno);
672*37da2899SCharles.Forsyth}
673*37da2899SCharles.Forsyth
674*37da2899SCharles.ForsythClex.getc(cs: self ref Clex): int
675*37da2899SCharles.Forsyth{
676*37da2899SCharles.Forsyth	if(cs.context != nil){
677*37da2899SCharles.Forsyth		c := hd cs.context;
678*37da2899SCharles.Forsyth		cs.context = tl cs.context;
679*37da2899SCharles.Forsyth		return c;
680*37da2899SCharles.Forsyth	}
681*37da2899SCharles.Forsyth	if(cs.n >= cs.lim)
682*37da2899SCharles.Forsyth		return -1;
683*37da2899SCharles.Forsyth	c := cs.input[cs.n++];
684*37da2899SCharles.Forsyth	if(c == '\n')
685*37da2899SCharles.Forsyth		cs.lineno++;
686*37da2899SCharles.Forsyth	return c;
687*37da2899SCharles.Forsyth}
688*37da2899SCharles.Forsyth
689*37da2899SCharles.ForsythClex.ungetc(cs: self ref Clex, c: int)
690*37da2899SCharles.Forsyth{
691*37da2899SCharles.Forsyth	cs.context = c :: cs.context;
692*37da2899SCharles.Forsyth}
693*37da2899SCharles.Forsyth
694*37da2899SCharles.ForsythClex.synerr(cs: self ref Clex, s: string)
695*37da2899SCharles.Forsyth{
696*37da2899SCharles.Forsyth	if(printdiag)
697*37da2899SCharles.Forsyth		sys->fprint(sys->fildes(2), "%d: err: %s\n", cs.lineno, s);
698*37da2899SCharles.Forsyth}
699*37da2899SCharles.Forsyth
700*37da2899SCharles.Forsythcsslex(cs: ref Clex): (int, string, string)
701*37da2899SCharles.Forsyth{
702*37da2899SCharles.Forsyth	for(;;){
703*37da2899SCharles.Forsyth		c := skipws(cs);
704*37da2899SCharles.Forsyth		if(c < 0)
705*37da2899SCharles.Forsyth			return (-1, nil, nil);
706*37da2899SCharles.Forsyth		case c {
707*37da2899SCharles.Forsyth		'<' =>
708*37da2899SCharles.Forsyth			if(seq(cs, "!--"))
709*37da2899SCharles.Forsyth				break;		# <!-- ignore HTML comment start (CDO)
710*37da2899SCharles.Forsyth			return (c, nil, nil);
711*37da2899SCharles.Forsyth		'-' =>
712*37da2899SCharles.Forsyth			if(seq(cs, "->"))
713*37da2899SCharles.Forsyth				break;		# --> ignore HTML comment end (CDC)
714*37da2899SCharles.Forsyth			return (c, nil, nil);
715*37da2899SCharles.Forsyth		':' =>
716*37da2899SCharles.Forsyth			c = cs.getc();
717*37da2899SCharles.Forsyth			cs.ungetc(c);
718*37da2899SCharles.Forsyth			if(isnamec(c, 0))
719*37da2899SCharles.Forsyth				return (PSEUDO, nil, nil);
720*37da2899SCharles.Forsyth			return (':', nil, nil);
721*37da2899SCharles.Forsyth		'#' =>
722*37da2899SCharles.Forsyth			c = cs.getc();
723*37da2899SCharles.Forsyth			if(isnamec(c, 1))
724*37da2899SCharles.Forsyth				return (HASH, name(cs, c), nil);
725*37da2899SCharles.Forsyth			cs.ungetc(c);
726*37da2899SCharles.Forsyth			return ('#', nil, nil);
727*37da2899SCharles.Forsyth		'/' =>
728*37da2899SCharles.Forsyth			if(subseq(cs, '*', 1, 0)){
729*37da2899SCharles.Forsyth				comment(cs);
730*37da2899SCharles.Forsyth				break;
731*37da2899SCharles.Forsyth			}
732*37da2899SCharles.Forsyth			return (c, nil, nil);
733*37da2899SCharles.Forsyth		'\'' or '"' =>
734*37da2899SCharles.Forsyth			return (STRING, quotedstring(cs, c), nil);
735*37da2899SCharles.Forsyth		'0' to '9' or '.' =>
736*37da2899SCharles.Forsyth			if(c == '.'){
737*37da2899SCharles.Forsyth				d := cs.getc();
738*37da2899SCharles.Forsyth				cs.ungetc(d);
739*37da2899SCharles.Forsyth				if(!isdigit(d)){
740*37da2899SCharles.Forsyth					if(isnamec(d, 1))
741*37da2899SCharles.Forsyth						return (CLASS, name(cs, cs.getc()), nil);
742*37da2899SCharles.Forsyth					return ('.', nil, nil);
743*37da2899SCharles.Forsyth				}
744*37da2899SCharles.Forsyth				# apply CSS2 treatment: .55 is a number not a class
745*37da2899SCharles.Forsyth			}
746*37da2899SCharles.Forsyth			val := number(cs, c);
747*37da2899SCharles.Forsyth			c = cs.getc();
748*37da2899SCharles.Forsyth			if(c == '%')
749*37da2899SCharles.Forsyth				return (PERCENTAGE, val, "%");
750*37da2899SCharles.Forsyth			if(isnamec(c, 0))	# use CSS2 interpetation
751*37da2899SCharles.Forsyth				return (UNIT, val, lowercase(name(cs, c)));
752*37da2899SCharles.Forsyth			cs.ungetc(c);
753*37da2899SCharles.Forsyth			return (NUMBER, val, nil);
754*37da2899SCharles.Forsyth		'\\' =>
755*37da2899SCharles.Forsyth			d := cs.getc();
756*37da2899SCharles.Forsyth			if(d >= ' ' && d <= '~' || islatin1(d)){	# probably should handle it in name
757*37da2899SCharles.Forsyth				wd := name(cs, d);
758*37da2899SCharles.Forsyth				return (IDENT, "\\"+wd, nil);
759*37da2899SCharles.Forsyth			}
760*37da2899SCharles.Forsyth			cs.ungetc(d);
761*37da2899SCharles.Forsyth			return ('\\', nil, nil);
762*37da2899SCharles.Forsyth		'@' =>
763*37da2899SCharles.Forsyth			c = cs.getc();
764*37da2899SCharles.Forsyth			if(isnamec(c, 0))	# @something
765*37da2899SCharles.Forsyth				return (ATKEYWORD, "@"+lowercase(name(cs,c)), nil);
766*37da2899SCharles.Forsyth			cs.ungetc(c);
767*37da2899SCharles.Forsyth			return ('@', nil, nil);
768*37da2899SCharles.Forsyth		'!' =>
769*37da2899SCharles.Forsyth			c = skipws(cs);
770*37da2899SCharles.Forsyth			if(isnamec(c, 0)){	# !something
771*37da2899SCharles.Forsyth				wd := name(cs, c);
772*37da2899SCharles.Forsyth				if(lowercase(wd) == "important")
773*37da2899SCharles.Forsyth					return (IMPORTANT, nil, nil);
774*37da2899SCharles.Forsyth				pushback(cs, wd);
775*37da2899SCharles.Forsyth			}else
776*37da2899SCharles.Forsyth				cs.ungetc(c);
777*37da2899SCharles.Forsyth			return ('!', nil, nil);
778*37da2899SCharles.Forsyth		'~' =>
779*37da2899SCharles.Forsyth			if(subseq(cs, '=', 1, 0))
780*37da2899SCharles.Forsyth				return (INCLUDES, "~=", nil);
781*37da2899SCharles.Forsyth			return ('~', nil, nil);
782*37da2899SCharles.Forsyth		'|' =>
783*37da2899SCharles.Forsyth			if(subseq(cs, '=', 1, 0))
784*37da2899SCharles.Forsyth				return (DASHMATCH, "|=", nil);
785*37da2899SCharles.Forsyth			return ('|', nil, nil);
786*37da2899SCharles.Forsyth		* =>
787*37da2899SCharles.Forsyth			if(isnamec(c, 0)){
788*37da2899SCharles.Forsyth				wd := name(cs, c);
789*37da2899SCharles.Forsyth				d := cs.getc();
790*37da2899SCharles.Forsyth				if(d != '('){
791*37da2899SCharles.Forsyth					cs.ungetc(d);
792*37da2899SCharles.Forsyth					return (IDENT, wd, nil);
793*37da2899SCharles.Forsyth				}
794*37da2899SCharles.Forsyth				val := lowercase(wd);
795*37da2899SCharles.Forsyth				if(val == "url")
796*37da2899SCharles.Forsyth					return (URL, url(cs), nil);	# bizarre special case
797*37da2899SCharles.Forsyth				return (FUNCTION, val, nil);
798*37da2899SCharles.Forsyth			}
799*37da2899SCharles.Forsyth			return (c, nil, nil);
800*37da2899SCharles.Forsyth		}
801*37da2899SCharles.Forsyth
802*37da2899SCharles.Forsyth	}
803*37da2899SCharles.Forsyth}
804*37da2899SCharles.Forsyth
805*37da2899SCharles.Forsythskipws(cs: ref Clex): int
806*37da2899SCharles.Forsyth{
807*37da2899SCharles.Forsyth	for(;;){
808*37da2899SCharles.Forsyth		while((c := cs.getc()) == ' ' || c == '\t' || c == '\n'  || c == '\r' || c == '\f')
809*37da2899SCharles.Forsyth			;
810*37da2899SCharles.Forsyth		if(c != '/')
811*37da2899SCharles.Forsyth			return c;
812*37da2899SCharles.Forsyth		c = cs.getc();
813*37da2899SCharles.Forsyth		if(c != '*'){
814*37da2899SCharles.Forsyth			cs.ungetc(c);
815*37da2899SCharles.Forsyth			return '/';
816*37da2899SCharles.Forsyth		}
817*37da2899SCharles.Forsyth		comment(cs);
818*37da2899SCharles.Forsyth	}
819*37da2899SCharles.Forsyth}
820*37da2899SCharles.Forsyth
821*37da2899SCharles.Forsythseq(cs: ref Clex, s: string): int
822*37da2899SCharles.Forsyth{
823*37da2899SCharles.Forsyth	for(i := 0; i < len s; i++)
824*37da2899SCharles.Forsyth		if((c := cs.getc()) != s[i])
825*37da2899SCharles.Forsyth			break;
826*37da2899SCharles.Forsyth	if(i == len s)
827*37da2899SCharles.Forsyth		return 1;
828*37da2899SCharles.Forsyth	cs.ungetc(c);
829*37da2899SCharles.Forsyth	while(i > 0)
830*37da2899SCharles.Forsyth		cs.ungetc(s[--i]);
831*37da2899SCharles.Forsyth	if(c < 0)
832*37da2899SCharles.Forsyth		return -1;
833*37da2899SCharles.Forsyth	return 0;
834*37da2899SCharles.Forsyth}
835*37da2899SCharles.Forsyth
836*37da2899SCharles.Forsythsubseq(cs: ref Clex, a: int, t: int, e: int): int
837*37da2899SCharles.Forsyth{
838*37da2899SCharles.Forsyth	if((c := cs.getc()) != a){
839*37da2899SCharles.Forsyth		cs.ungetc(c);
840*37da2899SCharles.Forsyth		return e;
841*37da2899SCharles.Forsyth	}
842*37da2899SCharles.Forsyth	return t;
843*37da2899SCharles.Forsyth}
844*37da2899SCharles.Forsyth
845*37da2899SCharles.Forsythpushback(cs: ref Clex, wd: string)
846*37da2899SCharles.Forsyth{
847*37da2899SCharles.Forsyth	for(i := len wd; --i >= 0;)
848*37da2899SCharles.Forsyth		cs.ungetc(wd[i]);
849*37da2899SCharles.Forsyth}
850*37da2899SCharles.Forsyth
851*37da2899SCharles.Forsythcomment(cs: ref Clex)
852*37da2899SCharles.Forsyth{
853*37da2899SCharles.Forsyth	while((c := cs.getc()) != '*' || (c = cs.getc()) != '/')
854*37da2899SCharles.Forsyth		if(c < 0) {
855*37da2899SCharles.Forsyth			# end of file in comment
856*37da2899SCharles.Forsyth			break;
857*37da2899SCharles.Forsyth		}
858*37da2899SCharles.Forsyth}
859*37da2899SCharles.Forsyth
860*37da2899SCharles.Forsythnumber(cs: ref Clex, c: int): string
861*37da2899SCharles.Forsyth{
862*37da2899SCharles.Forsyth	s: string;
863*37da2899SCharles.Forsyth	for(; isdigit(c); c = cs.getc())
864*37da2899SCharles.Forsyth		s[len s] = c;
865*37da2899SCharles.Forsyth	if(c != '.'){
866*37da2899SCharles.Forsyth		cs.ungetc(c);
867*37da2899SCharles.Forsyth		return s;
868*37da2899SCharles.Forsyth	}
869*37da2899SCharles.Forsyth	if(!isdigit(c = cs.getc())){
870*37da2899SCharles.Forsyth		cs.ungetc(c);
871*37da2899SCharles.Forsyth		cs.ungetc('.');
872*37da2899SCharles.Forsyth		return s;
873*37da2899SCharles.Forsyth	}
874*37da2899SCharles.Forsyth	s[len s] = '.';
875*37da2899SCharles.Forsyth	do{
876*37da2899SCharles.Forsyth		s[len s] = c;
877*37da2899SCharles.Forsyth	}while(isdigit(c = cs.getc()));
878*37da2899SCharles.Forsyth	cs.ungetc(c);
879*37da2899SCharles.Forsyth	return s;
880*37da2899SCharles.Forsyth}
881*37da2899SCharles.Forsyth
882*37da2899SCharles.Forsythname(cs: ref Clex, c: int): string
883*37da2899SCharles.Forsyth{
884*37da2899SCharles.Forsyth	s: string;
885*37da2899SCharles.Forsyth	for(; isnamec(c, 1); c = cs.getc()){
886*37da2899SCharles.Forsyth		s[len s] = c;
887*37da2899SCharles.Forsyth		if(c == '\\'){
888*37da2899SCharles.Forsyth			c = cs.getc();
889*37da2899SCharles.Forsyth			if(isescapable(c))
890*37da2899SCharles.Forsyth				s[len s] = c;
891*37da2899SCharles.Forsyth		}
892*37da2899SCharles.Forsyth	}
893*37da2899SCharles.Forsyth	cs.ungetc(c);
894*37da2899SCharles.Forsyth	return s;
895*37da2899SCharles.Forsyth}
896*37da2899SCharles.Forsyth
897*37da2899SCharles.Forsythisescapable(c: int): int
898*37da2899SCharles.Forsyth{
899*37da2899SCharles.Forsyth	return c >= ' ' && c <= '~' || isnamec(c, 1);
900*37da2899SCharles.Forsyth}
901*37da2899SCharles.Forsyth
902*37da2899SCharles.Forsythislatin1(c: int): int
903*37da2899SCharles.Forsyth{
904*37da2899SCharles.Forsyth	return c >= 16rA1 && c <= 16rFF;	# printable latin-1
905*37da2899SCharles.Forsyth}
906*37da2899SCharles.Forsyth
907*37da2899SCharles.Forsythisnamec(c: int, notfirst: int): int
908*37da2899SCharles.Forsyth{
909*37da2899SCharles.Forsyth	return c >= 'A' && c <= 'Z' || c >= 'a' && c <= 'z' || c == '\\' ||
910*37da2899SCharles.Forsyth		notfirst && (c >= '0' && c <= '9' || c == '-') ||
911*37da2899SCharles.Forsyth		c >= 16rA1 && c <= 16rFF;	# printable latin-1
912*37da2899SCharles.Forsyth}
913*37da2899SCharles.Forsyth
914*37da2899SCharles.Forsythisxdigit(c: int): int
915*37da2899SCharles.Forsyth{
916*37da2899SCharles.Forsyth	return c>='0' && c<='9' || c>='a'&&c<='f' || c>='A'&&c<='F';
917*37da2899SCharles.Forsyth}
918*37da2899SCharles.Forsyth
919*37da2899SCharles.Forsythisdigit(c: int): int
920*37da2899SCharles.Forsyth{
921*37da2899SCharles.Forsyth	return c >= '0' && c <= '9';
922*37da2899SCharles.Forsyth}
923*37da2899SCharles.Forsyth
924*37da2899SCharles.Forsythisspace(c: int): int
925*37da2899SCharles.Forsyth{
926*37da2899SCharles.Forsyth	return c == ' ' || c == '\t' || c == '\n' || c == '\r' || c == '\f';
927*37da2899SCharles.Forsyth}
928*37da2899SCharles.Forsyth
929*37da2899SCharles.Forsythhex(c: int): int
930*37da2899SCharles.Forsyth{
931*37da2899SCharles.Forsyth	if(c >= '0' && c <= '9')
932*37da2899SCharles.Forsyth		return c-'0';
933*37da2899SCharles.Forsyth	if(c >= 'A' && c <= 'F')
934*37da2899SCharles.Forsyth		return c-'A' + 10;
935*37da2899SCharles.Forsyth	if(c >= 'a' && c <= 'f')
936*37da2899SCharles.Forsyth		return c-'a' + 10;
937*37da2899SCharles.Forsyth	return -1;
938*37da2899SCharles.Forsyth}
939*37da2899SCharles.Forsyth
940*37da2899SCharles.Forsythquotedstring(cs: ref Clex, delim: int): string
941*37da2899SCharles.Forsyth{
942*37da2899SCharles.Forsyth	s: string;
943*37da2899SCharles.Forsyth	while((c := cs.getc()) != delim){
944*37da2899SCharles.Forsyth		if(c < 0){
945*37da2899SCharles.Forsyth			cs.synerr("end-of-file in string");
946*37da2899SCharles.Forsyth			return s;
947*37da2899SCharles.Forsyth		}
948*37da2899SCharles.Forsyth		if(c == '\\'){
949*37da2899SCharles.Forsyth			c = cs.getc();
950*37da2899SCharles.Forsyth			if(c < 0){
951*37da2899SCharles.Forsyth				cs.synerr("end-of-file in string");
952*37da2899SCharles.Forsyth				return s;
953*37da2899SCharles.Forsyth			}
954*37da2899SCharles.Forsyth			if(isxdigit(c)){
955*37da2899SCharles.Forsyth				# unicode escape
956*37da2899SCharles.Forsyth				n := 0;
957*37da2899SCharles.Forsyth				for(i := 0;;){
958*37da2899SCharles.Forsyth					n = (n<<4) | hex(c);
959*37da2899SCharles.Forsyth					c = cs.getc();
960*37da2899SCharles.Forsyth					if(!isxdigit(c) || ++i >= 6){
961*37da2899SCharles.Forsyth						if(!isspace(c))
962*37da2899SCharles.Forsyth							cs.ungetc(c);	# CSS2 ignores the first white space following
963*37da2899SCharles.Forsyth						break;
964*37da2899SCharles.Forsyth					}
965*37da2899SCharles.Forsyth				}
966*37da2899SCharles.Forsyth				s[len s] = n;
967*37da2899SCharles.Forsyth			}else if(c == '\n'){
968*37da2899SCharles.Forsyth				;	# escaped newline
969*37da2899SCharles.Forsyth			}else if(isescapable(c))
970*37da2899SCharles.Forsyth				s[len s] = c;
971*37da2899SCharles.Forsyth		}else if(c)
972*37da2899SCharles.Forsyth			s[len s] = c;
973*37da2899SCharles.Forsyth	}
974*37da2899SCharles.Forsyth	return s;
975*37da2899SCharles.Forsyth}
976*37da2899SCharles.Forsyth
977*37da2899SCharles.Forsythurl(cs: ref Clex): string
978*37da2899SCharles.Forsyth{
979*37da2899SCharles.Forsyth	s: string;
980*37da2899SCharles.Forsyth	c := skipws(cs);
981*37da2899SCharles.Forsyth	if(c != '"' && c != '\''){	# not a quoted string
982*37da2899SCharles.Forsyth		while(c != ' ' && c != '\n' && c != '\'' && c != '"' && c != ')'){
983*37da2899SCharles.Forsyth			s[len s] = c;
984*37da2899SCharles.Forsyth			c = cs.getc();
985*37da2899SCharles.Forsyth			if(c == '\\'){
986*37da2899SCharles.Forsyth				c = cs.getc();
987*37da2899SCharles.Forsyth				if(c < 0){
988*37da2899SCharles.Forsyth					cs.synerr("end of file in url parameter");
989*37da2899SCharles.Forsyth					break;
990*37da2899SCharles.Forsyth				}
991*37da2899SCharles.Forsyth				if(c == ' ' || c == '\'' || c == '"' || c == ')')
992*37da2899SCharles.Forsyth					s[len s] = c;
993*37da2899SCharles.Forsyth				else{
994*37da2899SCharles.Forsyth					cs.synerr("invalid escape sequence in url");
995*37da2899SCharles.Forsyth					s[len s] = '\\';
996*37da2899SCharles.Forsyth					s[len s] = c;
997*37da2899SCharles.Forsyth				}
998*37da2899SCharles.Forsyth				c = cs.getc();
999*37da2899SCharles.Forsyth			}
1000*37da2899SCharles.Forsyth		}
1001*37da2899SCharles.Forsyth		cs.ungetc(c);
1002*37da2899SCharles.Forsyth#		if(s == nil)
1003*37da2899SCharles.Forsyth#			p.synerr("empty parameter to url");
1004*37da2899SCharles.Forsyth	}else
1005*37da2899SCharles.Forsyth		s = quotedstring(cs, c);
1006*37da2899SCharles.Forsyth	if((c = skipws(cs)) != ')'){
1007*37da2899SCharles.Forsyth		cs.synerr("unclosed parameter to url");
1008*37da2899SCharles.Forsyth		cs.ungetc(c);
1009*37da2899SCharles.Forsyth	}
1010*37da2899SCharles.Forsyth	return s;
1011*37da2899SCharles.Forsyth}
1012*37da2899SCharles.Forsyth
1013*37da2899SCharles.Forsythlowercase(s: string): string
1014*37da2899SCharles.Forsyth{
1015*37da2899SCharles.Forsyth	for(i := 0; i < len s; i++)
1016*37da2899SCharles.Forsyth		if((c := s[i]) >= 'A' && c <= 'Z')
1017*37da2899SCharles.Forsyth			s[i] = c-'A' + 'a';
1018*37da2899SCharles.Forsyth	return s;
1019*37da2899SCharles.Forsyth}
1020