mirror of
				https://github.com/go-gitea/gitea
				synced 2025-10-31 03:18:24 +00:00 
			
		
		
		
	* Server-side syntax hilighting for all code This PR does a few things: * Remove all traces of highlight.js * Use chroma library to provide fast syntax hilighting directly on the server * Provide syntax hilighting for diffs * Re-style both unified and split diffs views * Add custom syntax hilighting styling for both regular and arc-green Fixes #7729 Fixes #10157 Fixes #11825 Fixes #7728 Fixes #3872 Fixes #3682 And perhaps gets closer to #9553 * fix line marker * fix repo search * Fix single line select * properly load settings * npm uninstall highlight.js * review suggestion * code review * forgot to call function * fix test * Apply suggestions from code review suggestions from @silverwind thanks Co-authored-by: silverwind <me@silverwind.io> * code review * copy/paste error * Use const for highlight size limit * Update web_src/less/_repository.less Co-authored-by: Lauris BH <lauris@nix.lv> * update size limit to 1MB and other styling tweaks * fix highlighting for certain diff sections * fix test * add worker back as suggested Co-authored-by: silverwind <me@silverwind.io> Co-authored-by: Lauris BH <lauris@nix.lv>
		
			
				
	
	
		
			655 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
			
		
		
	
	
			655 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			Go
		
	
	
	
		
			Vendored
		
	
	
	
| package syntax
 | |
| 
 | |
| import (
 | |
| 	"bytes"
 | |
| 	"fmt"
 | |
| 	"math"
 | |
| 	"strconv"
 | |
| )
 | |
| 
 | |
| type RegexTree struct {
 | |
| 	root       *regexNode
 | |
| 	caps       map[int]int
 | |
| 	capnumlist []int
 | |
| 	captop     int
 | |
| 	Capnames   map[string]int
 | |
| 	Caplist    []string
 | |
| 	options    RegexOptions
 | |
| }
 | |
| 
 | |
| // It is built into a parsed tree for a regular expression.
 | |
| 
 | |
| // Implementation notes:
 | |
| //
 | |
| // Since the node tree is a temporary data structure only used
 | |
| // during compilation of the regexp to integer codes, it's
 | |
| // designed for clarity and convenience rather than
 | |
| // space efficiency.
 | |
| //
 | |
| // RegexNodes are built into a tree, linked by the n.children list.
 | |
| // Each node also has a n.parent and n.ichild member indicating
 | |
| // its parent and which child # it is in its parent's list.
 | |
| //
 | |
| // RegexNodes come in as many types as there are constructs in
 | |
| // a regular expression, for example, "concatenate", "alternate",
 | |
| // "one", "rept", "group". There are also node types for basic
 | |
| // peephole optimizations, e.g., "onerep", "notsetrep", etc.
 | |
| //
 | |
| // Because perl 5 allows "lookback" groups that scan backwards,
 | |
| // each node also gets a "direction". Normally the value of
 | |
| // boolean n.backward = false.
 | |
| //
 | |
| // During parsing, top-level nodes are also stacked onto a parse
 | |
| // stack (a stack of trees). For this purpose we have a n.next
 | |
| // pointer. [Note that to save a few bytes, we could overload the
 | |
| // n.parent pointer instead.]
 | |
| //
 | |
| // On the parse stack, each tree has a "role" - basically, the
 | |
| // nonterminal in the grammar that the parser has currently
 | |
| // assigned to the tree. That code is stored in n.role.
 | |
| //
 | |
| // Finally, some of the different kinds of nodes have data.
 | |
| // Two integers (for the looping constructs) are stored in
 | |
| // n.operands, an an object (either a string or a set)
 | |
| // is stored in n.data
 | |
| type regexNode struct {
 | |
| 	t        nodeType
 | |
| 	children []*regexNode
 | |
| 	str      []rune
 | |
| 	set      *CharSet
 | |
| 	ch       rune
 | |
| 	m        int
 | |
| 	n        int
 | |
| 	options  RegexOptions
 | |
| 	next     *regexNode
 | |
| }
 | |
| 
 | |
| type nodeType int32
 | |
| 
 | |
| const (
 | |
| 	// The following are leaves, and correspond to primitive operations
 | |
| 
 | |
| 	ntOnerep      nodeType = 0  // lef,back char,min,max    a {n}
 | |
| 	ntNotonerep            = 1  // lef,back char,min,max    .{n}
 | |
| 	ntSetrep               = 2  // lef,back set,min,max     [\d]{n}
 | |
| 	ntOneloop              = 3  // lef,back char,min,max    a {,n}
 | |
| 	ntNotoneloop           = 4  // lef,back char,min,max    .{,n}
 | |
| 	ntSetloop              = 5  // lef,back set,min,max     [\d]{,n}
 | |
| 	ntOnelazy              = 6  // lef,back char,min,max    a {,n}?
 | |
| 	ntNotonelazy           = 7  // lef,back char,min,max    .{,n}?
 | |
| 	ntSetlazy              = 8  // lef,back set,min,max     [\d]{,n}?
 | |
| 	ntOne                  = 9  // lef      char            a
 | |
| 	ntNotone               = 10 // lef      char            [^a]
 | |
| 	ntSet                  = 11 // lef      set             [a-z\s]  \w \s \d
 | |
| 	ntMulti                = 12 // lef      string          abcd
 | |
| 	ntRef                  = 13 // lef      group           \#
 | |
| 	ntBol                  = 14 //                          ^
 | |
| 	ntEol                  = 15 //                          $
 | |
| 	ntBoundary             = 16 //                          \b
 | |
| 	ntNonboundary          = 17 //                          \B
 | |
| 	ntBeginning            = 18 //                          \A
 | |
| 	ntStart                = 19 //                          \G
 | |
| 	ntEndZ                 = 20 //                          \Z
 | |
| 	ntEnd                  = 21 //                          \Z
 | |
| 
 | |
| 	// Interior nodes do not correspond to primitive operations, but
 | |
| 	// control structures compositing other operations
 | |
| 
 | |
| 	// Concat and alternate take n children, and can run forward or backwards
 | |
| 
 | |
| 	ntNothing     = 22 //          []
 | |
| 	ntEmpty       = 23 //          ()
 | |
| 	ntAlternate   = 24 //          a|b
 | |
| 	ntConcatenate = 25 //          ab
 | |
| 	ntLoop        = 26 // m,x      * + ? {,}
 | |
| 	ntLazyloop    = 27 // m,x      *? +? ?? {,}?
 | |
| 	ntCapture     = 28 // n        ()
 | |
| 	ntGroup       = 29 //          (?:)
 | |
| 	ntRequire     = 30 //          (?=) (?<=)
 | |
| 	ntPrevent     = 31 //          (?!) (?<!)
 | |
| 	ntGreedy      = 32 //          (?>) (?<)
 | |
| 	ntTestref     = 33 //          (?(n) | )
 | |
| 	ntTestgroup   = 34 //          (?(...) | )
 | |
| 
 | |
| 	ntECMABoundary    = 41 //                          \b
 | |
| 	ntNonECMABoundary = 42 //                          \B
 | |
| )
 | |
| 
 | |
| func newRegexNode(t nodeType, opt RegexOptions) *regexNode {
 | |
| 	return ®exNode{
 | |
| 		t:       t,
 | |
| 		options: opt,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newRegexNodeCh(t nodeType, opt RegexOptions, ch rune) *regexNode {
 | |
| 	return ®exNode{
 | |
| 		t:       t,
 | |
| 		options: opt,
 | |
| 		ch:      ch,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newRegexNodeStr(t nodeType, opt RegexOptions, str []rune) *regexNode {
 | |
| 	return ®exNode{
 | |
| 		t:       t,
 | |
| 		options: opt,
 | |
| 		str:     str,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newRegexNodeSet(t nodeType, opt RegexOptions, set *CharSet) *regexNode {
 | |
| 	return ®exNode{
 | |
| 		t:       t,
 | |
| 		options: opt,
 | |
| 		set:     set,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func newRegexNodeM(t nodeType, opt RegexOptions, m int) *regexNode {
 | |
| 	return ®exNode{
 | |
| 		t:       t,
 | |
| 		options: opt,
 | |
| 		m:       m,
 | |
| 	}
 | |
| }
 | |
| func newRegexNodeMN(t nodeType, opt RegexOptions, m, n int) *regexNode {
 | |
| 	return ®exNode{
 | |
| 		t:       t,
 | |
| 		options: opt,
 | |
| 		m:       m,
 | |
| 		n:       n,
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (n *regexNode) writeStrToBuf(buf *bytes.Buffer) {
 | |
| 	for i := 0; i < len(n.str); i++ {
 | |
| 		buf.WriteRune(n.str[i])
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (n *regexNode) addChild(child *regexNode) {
 | |
| 	reduced := child.reduce()
 | |
| 	n.children = append(n.children, reduced)
 | |
| 	reduced.next = n
 | |
| }
 | |
| 
 | |
| func (n *regexNode) insertChildren(afterIndex int, nodes []*regexNode) {
 | |
| 	newChildren := make([]*regexNode, 0, len(n.children)+len(nodes))
 | |
| 	n.children = append(append(append(newChildren, n.children[:afterIndex]...), nodes...), n.children[afterIndex:]...)
 | |
| }
 | |
| 
 | |
| // removes children including the start but not the end index
 | |
| func (n *regexNode) removeChildren(startIndex, endIndex int) {
 | |
| 	n.children = append(n.children[:startIndex], n.children[endIndex:]...)
 | |
| }
 | |
| 
 | |
| // Pass type as OneLazy or OneLoop
 | |
| func (n *regexNode) makeRep(t nodeType, min, max int) {
 | |
| 	n.t += (t - ntOne)
 | |
| 	n.m = min
 | |
| 	n.n = max
 | |
| }
 | |
| 
 | |
| func (n *regexNode) reduce() *regexNode {
 | |
| 	switch n.t {
 | |
| 	case ntAlternate:
 | |
| 		return n.reduceAlternation()
 | |
| 
 | |
| 	case ntConcatenate:
 | |
| 		return n.reduceConcatenation()
 | |
| 
 | |
| 	case ntLoop, ntLazyloop:
 | |
| 		return n.reduceRep()
 | |
| 
 | |
| 	case ntGroup:
 | |
| 		return n.reduceGroup()
 | |
| 
 | |
| 	case ntSet, ntSetloop:
 | |
| 		return n.reduceSet()
 | |
| 
 | |
| 	default:
 | |
| 		return n
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // Basic optimization. Single-letter alternations can be replaced
 | |
| // by faster set specifications, and nested alternations with no
 | |
| // intervening operators can be flattened:
 | |
| //
 | |
| // a|b|c|def|g|h -> [a-c]|def|[gh]
 | |
| // apple|(?:orange|pear)|grape -> apple|orange|pear|grape
 | |
| func (n *regexNode) reduceAlternation() *regexNode {
 | |
| 	if len(n.children) == 0 {
 | |
| 		return newRegexNode(ntNothing, n.options)
 | |
| 	}
 | |
| 
 | |
| 	wasLastSet := false
 | |
| 	lastNodeCannotMerge := false
 | |
| 	var optionsLast RegexOptions
 | |
| 	var i, j int
 | |
| 
 | |
| 	for i, j = 0, 0; i < len(n.children); i, j = i+1, j+1 {
 | |
| 		at := n.children[i]
 | |
| 
 | |
| 		if j < i {
 | |
| 			n.children[j] = at
 | |
| 		}
 | |
| 
 | |
| 		for {
 | |
| 			if at.t == ntAlternate {
 | |
| 				for k := 0; k < len(at.children); k++ {
 | |
| 					at.children[k].next = n
 | |
| 				}
 | |
| 				n.insertChildren(i+1, at.children)
 | |
| 
 | |
| 				j--
 | |
| 			} else if at.t == ntSet || at.t == ntOne {
 | |
| 				// Cannot merge sets if L or I options differ, or if either are negated.
 | |
| 				optionsAt := at.options & (RightToLeft | IgnoreCase)
 | |
| 
 | |
| 				if at.t == ntSet {
 | |
| 					if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge || !at.set.IsMergeable() {
 | |
| 						wasLastSet = true
 | |
| 						lastNodeCannotMerge = !at.set.IsMergeable()
 | |
| 						optionsLast = optionsAt
 | |
| 						break
 | |
| 					}
 | |
| 				} else if !wasLastSet || optionsLast != optionsAt || lastNodeCannotMerge {
 | |
| 					wasLastSet = true
 | |
| 					lastNodeCannotMerge = false
 | |
| 					optionsLast = optionsAt
 | |
| 					break
 | |
| 				}
 | |
| 
 | |
| 				// The last node was a Set or a One, we're a Set or One and our options are the same.
 | |
| 				// Merge the two nodes.
 | |
| 				j--
 | |
| 				prev := n.children[j]
 | |
| 
 | |
| 				var prevCharClass *CharSet
 | |
| 				if prev.t == ntOne {
 | |
| 					prevCharClass = &CharSet{}
 | |
| 					prevCharClass.addChar(prev.ch)
 | |
| 				} else {
 | |
| 					prevCharClass = prev.set
 | |
| 				}
 | |
| 
 | |
| 				if at.t == ntOne {
 | |
| 					prevCharClass.addChar(at.ch)
 | |
| 				} else {
 | |
| 					prevCharClass.addSet(*at.set)
 | |
| 				}
 | |
| 
 | |
| 				prev.t = ntSet
 | |
| 				prev.set = prevCharClass
 | |
| 			} else if at.t == ntNothing {
 | |
| 				j--
 | |
| 			} else {
 | |
| 				wasLastSet = false
 | |
| 				lastNodeCannotMerge = false
 | |
| 			}
 | |
| 			break
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if j < i {
 | |
| 		n.removeChildren(j, i)
 | |
| 	}
 | |
| 
 | |
| 	return n.stripEnation(ntNothing)
 | |
| }
 | |
| 
 | |
| // Basic optimization. Adjacent strings can be concatenated.
 | |
| //
 | |
| // (?:abc)(?:def) -> abcdef
 | |
| func (n *regexNode) reduceConcatenation() *regexNode {
 | |
| 	// Eliminate empties and concat adjacent strings/chars
 | |
| 
 | |
| 	var optionsLast RegexOptions
 | |
| 	var optionsAt RegexOptions
 | |
| 	var i, j int
 | |
| 
 | |
| 	if len(n.children) == 0 {
 | |
| 		return newRegexNode(ntEmpty, n.options)
 | |
| 	}
 | |
| 
 | |
| 	wasLastString := false
 | |
| 
 | |
| 	for i, j = 0, 0; i < len(n.children); i, j = i+1, j+1 {
 | |
| 		var at, prev *regexNode
 | |
| 
 | |
| 		at = n.children[i]
 | |
| 
 | |
| 		if j < i {
 | |
| 			n.children[j] = at
 | |
| 		}
 | |
| 
 | |
| 		if at.t == ntConcatenate &&
 | |
| 			((at.options & RightToLeft) == (n.options & RightToLeft)) {
 | |
| 			for k := 0; k < len(at.children); k++ {
 | |
| 				at.children[k].next = n
 | |
| 			}
 | |
| 
 | |
| 			//insert at.children at i+1 index in n.children
 | |
| 			n.insertChildren(i+1, at.children)
 | |
| 
 | |
| 			j--
 | |
| 		} else if at.t == ntMulti || at.t == ntOne {
 | |
| 			// Cannot merge strings if L or I options differ
 | |
| 			optionsAt = at.options & (RightToLeft | IgnoreCase)
 | |
| 
 | |
| 			if !wasLastString || optionsLast != optionsAt {
 | |
| 				wasLastString = true
 | |
| 				optionsLast = optionsAt
 | |
| 				continue
 | |
| 			}
 | |
| 
 | |
| 			j--
 | |
| 			prev = n.children[j]
 | |
| 
 | |
| 			if prev.t == ntOne {
 | |
| 				prev.t = ntMulti
 | |
| 				prev.str = []rune{prev.ch}
 | |
| 			}
 | |
| 
 | |
| 			if (optionsAt & RightToLeft) == 0 {
 | |
| 				if at.t == ntOne {
 | |
| 					prev.str = append(prev.str, at.ch)
 | |
| 				} else {
 | |
| 					prev.str = append(prev.str, at.str...)
 | |
| 				}
 | |
| 			} else {
 | |
| 				if at.t == ntOne {
 | |
| 					// insert at the front by expanding our slice, copying the data over, and then setting the value
 | |
| 					prev.str = append(prev.str, 0)
 | |
| 					copy(prev.str[1:], prev.str)
 | |
| 					prev.str[0] = at.ch
 | |
| 				} else {
 | |
| 					//insert at the front...this one we'll make a new slice and copy both into it
 | |
| 					merge := make([]rune, len(prev.str)+len(at.str))
 | |
| 					copy(merge, at.str)
 | |
| 					copy(merge[len(at.str):], prev.str)
 | |
| 					prev.str = merge
 | |
| 				}
 | |
| 			}
 | |
| 		} else if at.t == ntEmpty {
 | |
| 			j--
 | |
| 		} else {
 | |
| 			wasLastString = false
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if j < i {
 | |
| 		// remove indices j through i from the children
 | |
| 		n.removeChildren(j, i)
 | |
| 	}
 | |
| 
 | |
| 	return n.stripEnation(ntEmpty)
 | |
| }
 | |
| 
 | |
| // Nested repeaters just get multiplied with each other if they're not
 | |
| // too lumpy
 | |
| func (n *regexNode) reduceRep() *regexNode {
 | |
| 
 | |
| 	u := n
 | |
| 	t := n.t
 | |
| 	min := n.m
 | |
| 	max := n.n
 | |
| 
 | |
| 	for {
 | |
| 		if len(u.children) == 0 {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		child := u.children[0]
 | |
| 
 | |
| 		// multiply reps of the same type only
 | |
| 		if child.t != t {
 | |
| 			childType := child.t
 | |
| 
 | |
| 			if !(childType >= ntOneloop && childType <= ntSetloop && t == ntLoop ||
 | |
| 				childType >= ntOnelazy && childType <= ntSetlazy && t == ntLazyloop) {
 | |
| 				break
 | |
| 			}
 | |
| 		}
 | |
| 
 | |
| 		// child can be too lumpy to blur, e.g., (a {100,105}) {3} or (a {2,})?
 | |
| 		// [but things like (a {2,})+ are not too lumpy...]
 | |
| 		if u.m == 0 && child.m > 1 || child.n < child.m*2 {
 | |
| 			break
 | |
| 		}
 | |
| 
 | |
| 		u = child
 | |
| 		if u.m > 0 {
 | |
| 			if (math.MaxInt32-1)/u.m < min {
 | |
| 				u.m = math.MaxInt32
 | |
| 			} else {
 | |
| 				u.m = u.m * min
 | |
| 			}
 | |
| 		}
 | |
| 		if u.n > 0 {
 | |
| 			if (math.MaxInt32-1)/u.n < max {
 | |
| 				u.n = math.MaxInt32
 | |
| 			} else {
 | |
| 				u.n = u.n * max
 | |
| 			}
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	if math.MaxInt32 == min {
 | |
| 		return newRegexNode(ntNothing, n.options)
 | |
| 	}
 | |
| 	return u
 | |
| 
 | |
| }
 | |
| 
 | |
| // Simple optimization. If a concatenation or alternation has only
 | |
| // one child strip out the intermediate node. If it has zero children,
 | |
| // turn it into an empty.
 | |
| func (n *regexNode) stripEnation(emptyType nodeType) *regexNode {
 | |
| 	switch len(n.children) {
 | |
| 	case 0:
 | |
| 		return newRegexNode(emptyType, n.options)
 | |
| 	case 1:
 | |
| 		return n.children[0]
 | |
| 	default:
 | |
| 		return n
 | |
| 	}
 | |
| }
 | |
| 
 | |
| func (n *regexNode) reduceGroup() *regexNode {
 | |
| 	u := n
 | |
| 
 | |
| 	for u.t == ntGroup {
 | |
| 		u = u.children[0]
 | |
| 	}
 | |
| 
 | |
| 	return u
 | |
| }
 | |
| 
 | |
| // Simple optimization. If a set is a singleton, an inverse singleton,
 | |
| // or empty, it's transformed accordingly.
 | |
| func (n *regexNode) reduceSet() *regexNode {
 | |
| 	// Extract empty-set, one and not-one case as special
 | |
| 
 | |
| 	if n.set == nil {
 | |
| 		n.t = ntNothing
 | |
| 	} else if n.set.IsSingleton() {
 | |
| 		n.ch = n.set.SingletonChar()
 | |
| 		n.set = nil
 | |
| 		n.t += (ntOne - ntSet)
 | |
| 	} else if n.set.IsSingletonInverse() {
 | |
| 		n.ch = n.set.SingletonChar()
 | |
| 		n.set = nil
 | |
| 		n.t += (ntNotone - ntSet)
 | |
| 	}
 | |
| 
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func (n *regexNode) reverseLeft() *regexNode {
 | |
| 	if n.options&RightToLeft != 0 && n.t == ntConcatenate && len(n.children) > 0 {
 | |
| 		//reverse children order
 | |
| 		for left, right := 0, len(n.children)-1; left < right; left, right = left+1, right-1 {
 | |
| 			n.children[left], n.children[right] = n.children[right], n.children[left]
 | |
| 		}
 | |
| 	}
 | |
| 
 | |
| 	return n
 | |
| }
 | |
| 
 | |
| func (n *regexNode) makeQuantifier(lazy bool, min, max int) *regexNode {
 | |
| 	if min == 0 && max == 0 {
 | |
| 		return newRegexNode(ntEmpty, n.options)
 | |
| 	}
 | |
| 
 | |
| 	if min == 1 && max == 1 {
 | |
| 		return n
 | |
| 	}
 | |
| 
 | |
| 	switch n.t {
 | |
| 	case ntOne, ntNotone, ntSet:
 | |
| 		if lazy {
 | |
| 			n.makeRep(Onelazy, min, max)
 | |
| 		} else {
 | |
| 			n.makeRep(Oneloop, min, max)
 | |
| 		}
 | |
| 		return n
 | |
| 
 | |
| 	default:
 | |
| 		var t nodeType
 | |
| 		if lazy {
 | |
| 			t = ntLazyloop
 | |
| 		} else {
 | |
| 			t = ntLoop
 | |
| 		}
 | |
| 		result := newRegexNodeMN(t, n.options, min, max)
 | |
| 		result.addChild(n)
 | |
| 		return result
 | |
| 	}
 | |
| }
 | |
| 
 | |
| // debug functions
 | |
| 
 | |
| var typeStr = []string{
 | |
| 	"Onerep", "Notonerep", "Setrep",
 | |
| 	"Oneloop", "Notoneloop", "Setloop",
 | |
| 	"Onelazy", "Notonelazy", "Setlazy",
 | |
| 	"One", "Notone", "Set",
 | |
| 	"Multi", "Ref",
 | |
| 	"Bol", "Eol", "Boundary", "Nonboundary",
 | |
| 	"Beginning", "Start", "EndZ", "End",
 | |
| 	"Nothing", "Empty",
 | |
| 	"Alternate", "Concatenate",
 | |
| 	"Loop", "Lazyloop",
 | |
| 	"Capture", "Group", "Require", "Prevent", "Greedy",
 | |
| 	"Testref", "Testgroup",
 | |
| 	"Unknown", "Unknown", "Unknown",
 | |
| 	"Unknown", "Unknown", "Unknown",
 | |
| 	"ECMABoundary", "NonECMABoundary",
 | |
| }
 | |
| 
 | |
| func (n *regexNode) description() string {
 | |
| 	buf := &bytes.Buffer{}
 | |
| 
 | |
| 	buf.WriteString(typeStr[n.t])
 | |
| 
 | |
| 	if (n.options & ExplicitCapture) != 0 {
 | |
| 		buf.WriteString("-C")
 | |
| 	}
 | |
| 	if (n.options & IgnoreCase) != 0 {
 | |
| 		buf.WriteString("-I")
 | |
| 	}
 | |
| 	if (n.options & RightToLeft) != 0 {
 | |
| 		buf.WriteString("-L")
 | |
| 	}
 | |
| 	if (n.options & Multiline) != 0 {
 | |
| 		buf.WriteString("-M")
 | |
| 	}
 | |
| 	if (n.options & Singleline) != 0 {
 | |
| 		buf.WriteString("-S")
 | |
| 	}
 | |
| 	if (n.options & IgnorePatternWhitespace) != 0 {
 | |
| 		buf.WriteString("-X")
 | |
| 	}
 | |
| 	if (n.options & ECMAScript) != 0 {
 | |
| 		buf.WriteString("-E")
 | |
| 	}
 | |
| 
 | |
| 	switch n.t {
 | |
| 	case ntOneloop, ntNotoneloop, ntOnelazy, ntNotonelazy, ntOne, ntNotone:
 | |
| 		buf.WriteString("(Ch = " + CharDescription(n.ch) + ")")
 | |
| 		break
 | |
| 	case ntCapture:
 | |
| 		buf.WriteString("(index = " + strconv.Itoa(n.m) + ", unindex = " + strconv.Itoa(n.n) + ")")
 | |
| 		break
 | |
| 	case ntRef, ntTestref:
 | |
| 		buf.WriteString("(index = " + strconv.Itoa(n.m) + ")")
 | |
| 		break
 | |
| 	case ntMulti:
 | |
| 		fmt.Fprintf(buf, "(String = %s)", string(n.str))
 | |
| 		break
 | |
| 	case ntSet, ntSetloop, ntSetlazy:
 | |
| 		buf.WriteString("(Set = " + n.set.String() + ")")
 | |
| 		break
 | |
| 	}
 | |
| 
 | |
| 	switch n.t {
 | |
| 	case ntOneloop, ntNotoneloop, ntOnelazy, ntNotonelazy, ntSetloop, ntSetlazy, ntLoop, ntLazyloop:
 | |
| 		buf.WriteString("(Min = ")
 | |
| 		buf.WriteString(strconv.Itoa(n.m))
 | |
| 		buf.WriteString(", Max = ")
 | |
| 		if n.n == math.MaxInt32 {
 | |
| 			buf.WriteString("inf")
 | |
| 		} else {
 | |
| 			buf.WriteString(strconv.Itoa(n.n))
 | |
| 		}
 | |
| 		buf.WriteString(")")
 | |
| 
 | |
| 		break
 | |
| 	}
 | |
| 
 | |
| 	return buf.String()
 | |
| }
 | |
| 
 | |
| var padSpace = []byte("                                ")
 | |
| 
 | |
| func (t *RegexTree) Dump() string {
 | |
| 	return t.root.dump()
 | |
| }
 | |
| 
 | |
| func (n *regexNode) dump() string {
 | |
| 	var stack []int
 | |
| 	CurNode := n
 | |
| 	CurChild := 0
 | |
| 
 | |
| 	buf := bytes.NewBufferString(CurNode.description())
 | |
| 	buf.WriteRune('\n')
 | |
| 
 | |
| 	for {
 | |
| 		if CurNode.children != nil && CurChild < len(CurNode.children) {
 | |
| 			stack = append(stack, CurChild+1)
 | |
| 			CurNode = CurNode.children[CurChild]
 | |
| 			CurChild = 0
 | |
| 
 | |
| 			Depth := len(stack)
 | |
| 			if Depth > 32 {
 | |
| 				Depth = 32
 | |
| 			}
 | |
| 			buf.Write(padSpace[:Depth])
 | |
| 			buf.WriteString(CurNode.description())
 | |
| 			buf.WriteRune('\n')
 | |
| 		} else {
 | |
| 			if len(stack) == 0 {
 | |
| 				break
 | |
| 			}
 | |
| 
 | |
| 			CurChild = stack[len(stack)-1]
 | |
| 			stack = stack[:len(stack)-1]
 | |
| 			CurNode = CurNode.next
 | |
| 		}
 | |
| 	}
 | |
| 	return buf.String()
 | |
| }
 |