649 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			649 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|   | // Copyright 2012 The Go Authors. All rights reserved. | ||
|  | // Use of this source code is governed by a BSD-style | ||
|  | // license that can be found in the LICENSE file. | ||
|  | 
 | ||
|  | // +build ignore | ||
|  | 
 | ||
|  | package main | ||
|  | 
 | ||
|  | // This program generates table.go and table_test.go. | ||
|  | // Invoke as | ||
|  | // | ||
|  | //	go run gen.go |gofmt >table.go | ||
|  | //	go run gen.go -test |gofmt >table_test.go | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"flag" | ||
|  | 	"fmt" | ||
|  | 	"math/rand" | ||
|  | 	"os" | ||
|  | 	"sort" | ||
|  | 	"strings" | ||
|  | ) | ||
|  | 
 | ||
|  | // identifier converts s to a Go exported identifier. | ||
|  | // It converts "div" to "Div" and "accept-charset" to "AcceptCharset". | ||
|  | func identifier(s string) string { | ||
|  | 	b := make([]byte, 0, len(s)) | ||
|  | 	cap := true | ||
|  | 	for _, c := range s { | ||
|  | 		if c == '-' { | ||
|  | 			cap = true | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		if cap && 'a' <= c && c <= 'z' { | ||
|  | 			c -= 'a' - 'A' | ||
|  | 		} | ||
|  | 		cap = false | ||
|  | 		b = append(b, byte(c)) | ||
|  | 	} | ||
|  | 	return string(b) | ||
|  | } | ||
|  | 
 | ||
|  | var test = flag.Bool("test", false, "generate table_test.go") | ||
|  | 
 | ||
|  | func main() { | ||
|  | 	flag.Parse() | ||
|  | 
 | ||
|  | 	var all []string | ||
|  | 	all = append(all, elements...) | ||
|  | 	all = append(all, attributes...) | ||
|  | 	all = append(all, eventHandlers...) | ||
|  | 	all = append(all, extra...) | ||
|  | 	sort.Strings(all) | ||
|  | 
 | ||
|  | 	if *test { | ||
|  | 		fmt.Printf("// generated by go run gen.go -test; DO NOT EDIT\n\n") | ||
|  | 		fmt.Printf("package atom\n\n") | ||
|  | 		fmt.Printf("var testAtomList = []string{\n") | ||
|  | 		for _, s := range all { | ||
|  | 			fmt.Printf("\t%q,\n", s) | ||
|  | 		} | ||
|  | 		fmt.Printf("}\n") | ||
|  | 		return | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// uniq - lists have dups | ||
|  | 	// compute max len too | ||
|  | 	maxLen := 0 | ||
|  | 	w := 0 | ||
|  | 	for _, s := range all { | ||
|  | 		if w == 0 || all[w-1] != s { | ||
|  | 			if maxLen < len(s) { | ||
|  | 				maxLen = len(s) | ||
|  | 			} | ||
|  | 			all[w] = s | ||
|  | 			w++ | ||
|  | 		} | ||
|  | 	} | ||
|  | 	all = all[:w] | ||
|  | 
 | ||
|  | 	// Find hash that minimizes table size. | ||
|  | 	var best *table | ||
|  | 	for i := 0; i < 1000000; i++ { | ||
|  | 		if best != nil && 1<<(best.k-1) < len(all) { | ||
|  | 			break | ||
|  | 		} | ||
|  | 		h := rand.Uint32() | ||
|  | 		for k := uint(0); k <= 16; k++ { | ||
|  | 			if best != nil && k >= best.k { | ||
|  | 				break | ||
|  | 			} | ||
|  | 			var t table | ||
|  | 			if t.init(h, k, all) { | ||
|  | 				best = &t | ||
|  | 				break | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 	if best == nil { | ||
|  | 		fmt.Fprintf(os.Stderr, "failed to construct string table\n") | ||
|  | 		os.Exit(1) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Lay out strings, using overlaps when possible. | ||
|  | 	layout := append([]string{}, all...) | ||
|  | 
 | ||
|  | 	// Remove strings that are substrings of other strings | ||
|  | 	for changed := true; changed; { | ||
|  | 		changed = false | ||
|  | 		for i, s := range layout { | ||
|  | 			if s == "" { | ||
|  | 				continue | ||
|  | 			} | ||
|  | 			for j, t := range layout { | ||
|  | 				if i != j && t != "" && strings.Contains(s, t) { | ||
|  | 					changed = true | ||
|  | 					layout[j] = "" | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Join strings where one suffix matches another prefix. | ||
|  | 	for { | ||
|  | 		// Find best i, j, k such that layout[i][len-k:] == layout[j][:k], | ||
|  | 		// maximizing overlap length k. | ||
|  | 		besti := -1 | ||
|  | 		bestj := -1 | ||
|  | 		bestk := 0 | ||
|  | 		for i, s := range layout { | ||
|  | 			if s == "" { | ||
|  | 				continue | ||
|  | 			} | ||
|  | 			for j, t := range layout { | ||
|  | 				if i == j { | ||
|  | 					continue | ||
|  | 				} | ||
|  | 				for k := bestk + 1; k <= len(s) && k <= len(t); k++ { | ||
|  | 					if s[len(s)-k:] == t[:k] { | ||
|  | 						besti = i | ||
|  | 						bestj = j | ||
|  | 						bestk = k | ||
|  | 					} | ||
|  | 				} | ||
|  | 			} | ||
|  | 		} | ||
|  | 		if bestk > 0 { | ||
|  | 			layout[besti] += layout[bestj][bestk:] | ||
|  | 			layout[bestj] = "" | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		break | ||
|  | 	} | ||
|  | 
 | ||
|  | 	text := strings.Join(layout, "") | ||
|  | 
 | ||
|  | 	atom := map[string]uint32{} | ||
|  | 	for _, s := range all { | ||
|  | 		off := strings.Index(text, s) | ||
|  | 		if off < 0 { | ||
|  | 			panic("lost string " + s) | ||
|  | 		} | ||
|  | 		atom[s] = uint32(off<<8 | len(s)) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Generate the Go code. | ||
|  | 	fmt.Printf("// generated by go run gen.go; DO NOT EDIT\n\n") | ||
|  | 	fmt.Printf("package atom\n\nconst (\n") | ||
|  | 	for _, s := range all { | ||
|  | 		fmt.Printf("\t%s Atom = %#x\n", identifier(s), atom[s]) | ||
|  | 	} | ||
|  | 	fmt.Printf(")\n\n") | ||
|  | 
 | ||
|  | 	fmt.Printf("const hash0 = %#x\n\n", best.h0) | ||
|  | 	fmt.Printf("const maxAtomLen = %d\n\n", maxLen) | ||
|  | 
 | ||
|  | 	fmt.Printf("var table = [1<<%d]Atom{\n", best.k) | ||
|  | 	for i, s := range best.tab { | ||
|  | 		if s == "" { | ||
|  | 			continue | ||
|  | 		} | ||
|  | 		fmt.Printf("\t%#x: %#x, // %s\n", i, atom[s], s) | ||
|  | 	} | ||
|  | 	fmt.Printf("}\n") | ||
|  | 	datasize := (1 << best.k) * 4 | ||
|  | 
 | ||
|  | 	fmt.Printf("const atomText =\n") | ||
|  | 	textsize := len(text) | ||
|  | 	for len(text) > 60 { | ||
|  | 		fmt.Printf("\t%q +\n", text[:60]) | ||
|  | 		text = text[60:] | ||
|  | 	} | ||
|  | 	fmt.Printf("\t%q\n\n", text) | ||
|  | 
 | ||
|  | 	fmt.Fprintf(os.Stderr, "%d atoms; %d string bytes + %d tables = %d total data\n", len(all), textsize, datasize, textsize+datasize) | ||
|  | } | ||
|  | 
 | ||
|  | type byLen []string | ||
|  | 
 | ||
|  | func (x byLen) Less(i, j int) bool { return len(x[i]) > len(x[j]) } | ||
|  | func (x byLen) Swap(i, j int)      { x[i], x[j] = x[j], x[i] } | ||
|  | func (x byLen) Len() int           { return len(x) } | ||
|  | 
 | ||
|  | // fnv computes the FNV hash with an arbitrary starting value h. | ||
|  | func fnv(h uint32, s string) uint32 { | ||
|  | 	for i := 0; i < len(s); i++ { | ||
|  | 		h ^= uint32(s[i]) | ||
|  | 		h *= 16777619 | ||
|  | 	} | ||
|  | 	return h | ||
|  | } | ||
|  | 
 | ||
|  | // A table represents an attempt at constructing the lookup table. | ||
|  | // The lookup table uses cuckoo hashing, meaning that each string | ||
|  | // can be found in one of two positions. | ||
|  | type table struct { | ||
|  | 	h0   uint32 | ||
|  | 	k    uint | ||
|  | 	mask uint32 | ||
|  | 	tab  []string | ||
|  | } | ||
|  | 
 | ||
|  | // hash returns the two hashes for s. | ||
|  | func (t *table) hash(s string) (h1, h2 uint32) { | ||
|  | 	h := fnv(t.h0, s) | ||
|  | 	h1 = h & t.mask | ||
|  | 	h2 = (h >> 16) & t.mask | ||
|  | 	return | ||
|  | } | ||
|  | 
 | ||
|  | // init initializes the table with the given parameters. | ||
|  | // h0 is the initial hash value, | ||
|  | // k is the number of bits of hash value to use, and | ||
|  | // x is the list of strings to store in the table. | ||
|  | // init returns false if the table cannot be constructed. | ||
|  | func (t *table) init(h0 uint32, k uint, x []string) bool { | ||
|  | 	t.h0 = h0 | ||
|  | 	t.k = k | ||
|  | 	t.tab = make([]string, 1<<k) | ||
|  | 	t.mask = 1<<k - 1 | ||
|  | 	for _, s := range x { | ||
|  | 		if !t.insert(s) { | ||
|  | 			return false | ||
|  | 		} | ||
|  | 	} | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | // insert inserts s in the table. | ||
|  | func (t *table) insert(s string) bool { | ||
|  | 	h1, h2 := t.hash(s) | ||
|  | 	if t.tab[h1] == "" { | ||
|  | 		t.tab[h1] = s | ||
|  | 		return true | ||
|  | 	} | ||
|  | 	if t.tab[h2] == "" { | ||
|  | 		t.tab[h2] = s | ||
|  | 		return true | ||
|  | 	} | ||
|  | 	if t.push(h1, 0) { | ||
|  | 		t.tab[h1] = s | ||
|  | 		return true | ||
|  | 	} | ||
|  | 	if t.push(h2, 0) { | ||
|  | 		t.tab[h2] = s | ||
|  | 		return true | ||
|  | 	} | ||
|  | 	return false | ||
|  | } | ||
|  | 
 | ||
|  | // push attempts to push aside the entry in slot i. | ||
|  | func (t *table) push(i uint32, depth int) bool { | ||
|  | 	if depth > len(t.tab) { | ||
|  | 		return false | ||
|  | 	} | ||
|  | 	s := t.tab[i] | ||
|  | 	h1, h2 := t.hash(s) | ||
|  | 	j := h1 + h2 - i | ||
|  | 	if t.tab[j] != "" && !t.push(j, depth+1) { | ||
|  | 		return false | ||
|  | 	} | ||
|  | 	t.tab[j] = s | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | // The lists of element names and attribute keys were taken from | ||
|  | // https://html.spec.whatwg.org/multipage/indices.html#index | ||
|  | // as of the "HTML Living Standard - Last Updated 21 February 2015" version. | ||
|  | 
 | ||
|  | var elements = []string{ | ||
|  | 	"a", | ||
|  | 	"abbr", | ||
|  | 	"address", | ||
|  | 	"area", | ||
|  | 	"article", | ||
|  | 	"aside", | ||
|  | 	"audio", | ||
|  | 	"b", | ||
|  | 	"base", | ||
|  | 	"bdi", | ||
|  | 	"bdo", | ||
|  | 	"blockquote", | ||
|  | 	"body", | ||
|  | 	"br", | ||
|  | 	"button", | ||
|  | 	"canvas", | ||
|  | 	"caption", | ||
|  | 	"cite", | ||
|  | 	"code", | ||
|  | 	"col", | ||
|  | 	"colgroup", | ||
|  | 	"command", | ||
|  | 	"data", | ||
|  | 	"datalist", | ||
|  | 	"dd", | ||
|  | 	"del", | ||
|  | 	"details", | ||
|  | 	"dfn", | ||
|  | 	"dialog", | ||
|  | 	"div", | ||
|  | 	"dl", | ||
|  | 	"dt", | ||
|  | 	"em", | ||
|  | 	"embed", | ||
|  | 	"fieldset", | ||
|  | 	"figcaption", | ||
|  | 	"figure", | ||
|  | 	"footer", | ||
|  | 	"form", | ||
|  | 	"h1", | ||
|  | 	"h2", | ||
|  | 	"h3", | ||
|  | 	"h4", | ||
|  | 	"h5", | ||
|  | 	"h6", | ||
|  | 	"head", | ||
|  | 	"header", | ||
|  | 	"hgroup", | ||
|  | 	"hr", | ||
|  | 	"html", | ||
|  | 	"i", | ||
|  | 	"iframe", | ||
|  | 	"img", | ||
|  | 	"input", | ||
|  | 	"ins", | ||
|  | 	"kbd", | ||
|  | 	"keygen", | ||
|  | 	"label", | ||
|  | 	"legend", | ||
|  | 	"li", | ||
|  | 	"link", | ||
|  | 	"map", | ||
|  | 	"mark", | ||
|  | 	"menu", | ||
|  | 	"menuitem", | ||
|  | 	"meta", | ||
|  | 	"meter", | ||
|  | 	"nav", | ||
|  | 	"noscript", | ||
|  | 	"object", | ||
|  | 	"ol", | ||
|  | 	"optgroup", | ||
|  | 	"option", | ||
|  | 	"output", | ||
|  | 	"p", | ||
|  | 	"param", | ||
|  | 	"pre", | ||
|  | 	"progress", | ||
|  | 	"q", | ||
|  | 	"rp", | ||
|  | 	"rt", | ||
|  | 	"ruby", | ||
|  | 	"s", | ||
|  | 	"samp", | ||
|  | 	"script", | ||
|  | 	"section", | ||
|  | 	"select", | ||
|  | 	"small", | ||
|  | 	"source", | ||
|  | 	"span", | ||
|  | 	"strong", | ||
|  | 	"style", | ||
|  | 	"sub", | ||
|  | 	"summary", | ||
|  | 	"sup", | ||
|  | 	"table", | ||
|  | 	"tbody", | ||
|  | 	"td", | ||
|  | 	"template", | ||
|  | 	"textarea", | ||
|  | 	"tfoot", | ||
|  | 	"th", | ||
|  | 	"thead", | ||
|  | 	"time", | ||
|  | 	"title", | ||
|  | 	"tr", | ||
|  | 	"track", | ||
|  | 	"u", | ||
|  | 	"ul", | ||
|  | 	"var", | ||
|  | 	"video", | ||
|  | 	"wbr", | ||
|  | } | ||
|  | 
 | ||
|  | // https://html.spec.whatwg.org/multipage/indices.html#attributes-3 | ||
|  | 
 | ||
|  | var attributes = []string{ | ||
|  | 	"abbr", | ||
|  | 	"accept", | ||
|  | 	"accept-charset", | ||
|  | 	"accesskey", | ||
|  | 	"action", | ||
|  | 	"alt", | ||
|  | 	"async", | ||
|  | 	"autocomplete", | ||
|  | 	"autofocus", | ||
|  | 	"autoplay", | ||
|  | 	"challenge", | ||
|  | 	"charset", | ||
|  | 	"checked", | ||
|  | 	"cite", | ||
|  | 	"class", | ||
|  | 	"cols", | ||
|  | 	"colspan", | ||
|  | 	"command", | ||
|  | 	"content", | ||
|  | 	"contenteditable", | ||
|  | 	"contextmenu", | ||
|  | 	"controls", | ||
|  | 	"coords", | ||
|  | 	"crossorigin", | ||
|  | 	"data", | ||
|  | 	"datetime", | ||
|  | 	"default", | ||
|  | 	"defer", | ||
|  | 	"dir", | ||
|  | 	"dirname", | ||
|  | 	"disabled", | ||
|  | 	"download", | ||
|  | 	"draggable", | ||
|  | 	"dropzone", | ||
|  | 	"enctype", | ||
|  | 	"for", | ||
|  | 	"form", | ||
|  | 	"formaction", | ||
|  | 	"formenctype", | ||
|  | 	"formmethod", | ||
|  | 	"formnovalidate", | ||
|  | 	"formtarget", | ||
|  | 	"headers", | ||
|  | 	"height", | ||
|  | 	"hidden", | ||
|  | 	"high", | ||
|  | 	"href", | ||
|  | 	"hreflang", | ||
|  | 	"http-equiv", | ||
|  | 	"icon", | ||
|  | 	"id", | ||
|  | 	"inputmode", | ||
|  | 	"ismap", | ||
|  | 	"itemid", | ||
|  | 	"itemprop", | ||
|  | 	"itemref", | ||
|  | 	"itemscope", | ||
|  | 	"itemtype", | ||
|  | 	"keytype", | ||
|  | 	"kind", | ||
|  | 	"label", | ||
|  | 	"lang", | ||
|  | 	"list", | ||
|  | 	"loop", | ||
|  | 	"low", | ||
|  | 	"manifest", | ||
|  | 	"max", | ||
|  | 	"maxlength", | ||
|  | 	"media", | ||
|  | 	"mediagroup", | ||
|  | 	"method", | ||
|  | 	"min", | ||
|  | 	"minlength", | ||
|  | 	"multiple", | ||
|  | 	"muted", | ||
|  | 	"name", | ||
|  | 	"novalidate", | ||
|  | 	"open", | ||
|  | 	"optimum", | ||
|  | 	"pattern", | ||
|  | 	"ping", | ||
|  | 	"placeholder", | ||
|  | 	"poster", | ||
|  | 	"preload", | ||
|  | 	"radiogroup", | ||
|  | 	"readonly", | ||
|  | 	"rel", | ||
|  | 	"required", | ||
|  | 	"reversed", | ||
|  | 	"rows", | ||
|  | 	"rowspan", | ||
|  | 	"sandbox", | ||
|  | 	"spellcheck", | ||
|  | 	"scope", | ||
|  | 	"scoped", | ||
|  | 	"seamless", | ||
|  | 	"selected", | ||
|  | 	"shape", | ||
|  | 	"size", | ||
|  | 	"sizes", | ||
|  | 	"sortable", | ||
|  | 	"sorted", | ||
|  | 	"span", | ||
|  | 	"src", | ||
|  | 	"srcdoc", | ||
|  | 	"srclang", | ||
|  | 	"start", | ||
|  | 	"step", | ||
|  | 	"style", | ||
|  | 	"tabindex", | ||
|  | 	"target", | ||
|  | 	"title", | ||
|  | 	"translate", | ||
|  | 	"type", | ||
|  | 	"typemustmatch", | ||
|  | 	"usemap", | ||
|  | 	"value", | ||
|  | 	"width", | ||
|  | 	"wrap", | ||
|  | } | ||
|  | 
 | ||
|  | var eventHandlers = []string{ | ||
|  | 	"onabort", | ||
|  | 	"onautocomplete", | ||
|  | 	"onautocompleteerror", | ||
|  | 	"onafterprint", | ||
|  | 	"onbeforeprint", | ||
|  | 	"onbeforeunload", | ||
|  | 	"onblur", | ||
|  | 	"oncancel", | ||
|  | 	"oncanplay", | ||
|  | 	"oncanplaythrough", | ||
|  | 	"onchange", | ||
|  | 	"onclick", | ||
|  | 	"onclose", | ||
|  | 	"oncontextmenu", | ||
|  | 	"oncuechange", | ||
|  | 	"ondblclick", | ||
|  | 	"ondrag", | ||
|  | 	"ondragend", | ||
|  | 	"ondragenter", | ||
|  | 	"ondragleave", | ||
|  | 	"ondragover", | ||
|  | 	"ondragstart", | ||
|  | 	"ondrop", | ||
|  | 	"ondurationchange", | ||
|  | 	"onemptied", | ||
|  | 	"onended", | ||
|  | 	"onerror", | ||
|  | 	"onfocus", | ||
|  | 	"onhashchange", | ||
|  | 	"oninput", | ||
|  | 	"oninvalid", | ||
|  | 	"onkeydown", | ||
|  | 	"onkeypress", | ||
|  | 	"onkeyup", | ||
|  | 	"onlanguagechange", | ||
|  | 	"onload", | ||
|  | 	"onloadeddata", | ||
|  | 	"onloadedmetadata", | ||
|  | 	"onloadstart", | ||
|  | 	"onmessage", | ||
|  | 	"onmousedown", | ||
|  | 	"onmousemove", | ||
|  | 	"onmouseout", | ||
|  | 	"onmouseover", | ||
|  | 	"onmouseup", | ||
|  | 	"onmousewheel", | ||
|  | 	"onoffline", | ||
|  | 	"ononline", | ||
|  | 	"onpagehide", | ||
|  | 	"onpageshow", | ||
|  | 	"onpause", | ||
|  | 	"onplay", | ||
|  | 	"onplaying", | ||
|  | 	"onpopstate", | ||
|  | 	"onprogress", | ||
|  | 	"onratechange", | ||
|  | 	"onreset", | ||
|  | 	"onresize", | ||
|  | 	"onscroll", | ||
|  | 	"onseeked", | ||
|  | 	"onseeking", | ||
|  | 	"onselect", | ||
|  | 	"onshow", | ||
|  | 	"onsort", | ||
|  | 	"onstalled", | ||
|  | 	"onstorage", | ||
|  | 	"onsubmit", | ||
|  | 	"onsuspend", | ||
|  | 	"ontimeupdate", | ||
|  | 	"ontoggle", | ||
|  | 	"onunload", | ||
|  | 	"onvolumechange", | ||
|  | 	"onwaiting", | ||
|  | } | ||
|  | 
 | ||
|  | // extra are ad-hoc values not covered by any of the lists above. | ||
|  | var extra = []string{ | ||
|  | 	"align", | ||
|  | 	"annotation", | ||
|  | 	"annotation-xml", | ||
|  | 	"applet", | ||
|  | 	"basefont", | ||
|  | 	"bgsound", | ||
|  | 	"big", | ||
|  | 	"blink", | ||
|  | 	"center", | ||
|  | 	"color", | ||
|  | 	"desc", | ||
|  | 	"face", | ||
|  | 	"font", | ||
|  | 	"foreignObject", // HTML is case-insensitive, but SVG-embedded-in-HTML is case-sensitive. | ||
|  | 	"foreignobject", | ||
|  | 	"frame", | ||
|  | 	"frameset", | ||
|  | 	"image", | ||
|  | 	"isindex", | ||
|  | 	"listing", | ||
|  | 	"malignmark", | ||
|  | 	"marquee", | ||
|  | 	"math", | ||
|  | 	"mglyph", | ||
|  | 	"mi", | ||
|  | 	"mn", | ||
|  | 	"mo", | ||
|  | 	"ms", | ||
|  | 	"mtext", | ||
|  | 	"nobr", | ||
|  | 	"noembed", | ||
|  | 	"noframes", | ||
|  | 	"plaintext", | ||
|  | 	"prompt", | ||
|  | 	"public", | ||
|  | 	"spacer", | ||
|  | 	"strike", | ||
|  | 	"svg", | ||
|  | 	"system", | ||
|  | 	"tt", | ||
|  | 	"xmp", | ||
|  | } |