From 69f6b9c9c57a0b4b3166c9a0041628f4873307bb Mon Sep 17 00:00:00 2001 From: Azareal Date: Thu, 18 Mar 2021 15:10:20 +1000 Subject: [PATCH] refactor rank precedence with precedence struct in router gen add precedence tests remove dead code from router gen make more strides towards using writers in router gen optimise dump request --- gen_router.go | 107 +++++++++++++++++-------------- router_gen/main.go | 138 ++++++++++++++++++---------------------- router_gen/misc_test.go | 60 +++++++++++++++++ router_gen/prec.go | 60 +++++++++++++++++ 4 files changed, 243 insertions(+), 122 deletions(-) create mode 100644 router_gen/misc_test.go create mode 100644 router_gen/prec.go diff --git a/gen_router.go b/gen_router.go index 3d78dcdd..9313e2d3 100644 --- a/gen_router.go +++ b/gen_router.go @@ -998,10 +998,15 @@ func (r *GenRouter) RemoveFunc(pattern string) error { return nil } -// TODO: Some of these sanitisations may be redundant -func (r *GenRouter) dumpRequest(req *http.Request, pre string,log *log.Logger) { +func (r *GenRouter) dumpRequest(req *http.Request, pre string, log *log.Logger) { var sb strings.Builder - sb.WriteString(pre) + r.ddumpRequest(req,pre,log,&sb) +} + +// TODO: Some of these sanitisations may be redundant +var dumpReqLen = len("\nUA: \nMethod: \nHost: \nURL.Path: \nURL.RawQuery: \nIP: \n") + 3 +var dumpReqLen2 = len("\nHead : ") + 2 +func (r *GenRouter) ddumpRequest(req *http.Request, pre string,log *log.Logger, sb *strings.Builder) { nfield := func(label, val string) { sb.WriteString(label) sb.WriteString(val) @@ -1009,7 +1014,10 @@ func (r *GenRouter) dumpRequest(req *http.Request, pre string,log *log.Logger) { field := func(label, val string) { nfield(label,c.SanitiseSingleLine(val)) } - field("\nUA: ",req.UserAgent()) + ua := req.UserAgent() + sb.Grow(dumpReqLen + len(pre) + len(ua) + len(req.Method) + len(req.Host) + (dumpReqLen2 * len(req.Header))) + sb.WriteString(pre) + field("\nUA: ",ua) field("\nMethod: ",req.Method) for key, value := range req.Header { // Avoid logging this for security reasons @@ -1026,7 +1034,10 @@ func (r *GenRouter) dumpRequest(req *http.Request, pre string,log *log.Logger) { field("\nHost: ",req.Host) field("\nURL.Path: ",req.URL.Path) field("\nURL.RawQuery: ",req.URL.RawQuery) - field("\nRef: ",req.Referer()) + ref := req.Referer() + if ref != "" { + field("\nRef: ",req.Referer()) + } nfield("\nIP: ",req.RemoteAddr) sb.WriteString("\n") @@ -1038,15 +1049,30 @@ func (r *GenRouter) DumpRequest(req *http.Request, pre string) { } func (r *GenRouter) SuspiciousRequest(req *http.Request, pre string) { + var sb strings.Builder if pre != "" { - pre += "\nSuspicious Request" + sb.WriteString("Suspicious Request") } else { pre = "Suspicious Request" } - r.dumpRequest(req,pre,r.suspReqLogger) + r.ddumpRequest(req,pre,r.suspReqLogger,&sb) co.AgentViewCounter.Bump(43) } +func (r *GenRouter) unknownUA(req *http.Request) { + if c.Dev.DebugMode { + var presb strings.Builder + presb.WriteString("Unknown UA: ") + for _, ch := range req.UserAgent() { + presb.WriteString(strconv.Itoa(int(ch))) + presb.WriteRune(' ') + } + r.ddumpRequest(req, "", r.requestLogger, &presb) + } else { + r.requestLogger.Print("unknown ua: ", c.SanitiseSingleLine(req.UserAgent())) + } +} + func isLocalHost(h string) bool { return h=="localhost" || h=="127.0.0.1" || h=="::1" } @@ -1191,7 +1217,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } /*if c.Dev.QuicPort != 0 { - w.Header().Set("Alt-Svc", "quic=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=2592000; v=\"44,43,39\", h3-23=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h3-24=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h2=\":443\"; ma=3600") + sQuicPort := strconv.Itoa(c.Dev.QuicPort) + w.Header().Set("Alt-Svc", "quic=\":"+sQuicPort+"\"; ma=2592000; v=\"44,43,39\", h3-23=\":"+sQuicPort+"\"; ma=3600, h3-24=\":"+sQuicPort+"\"; ma=3600, h2=\":443\"; ma=3600") }*/ // Track the user agents. Unfortunately, everyone pretends to be Mozilla, so this'll be a little less efficient than I would like. @@ -1204,13 +1231,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another if ua == "" { co.AgentViewCounter.Bump(41) - if c.Dev.DebugMode { - var pre string - for _, char := range req.UserAgent() { - pre += strconv.Itoa(int(char)) + " " - } - r.DumpRequest(req,"Blank UA: " + pre) - } + r.unknownUA(req) } else { // WIP UA Parser //var ii = uaBufPool.Get() @@ -1307,15 +1328,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if agent == 0 { //co.AgentViewCounter.Bump(0) - if c.Dev.DebugMode { - var pre string - for _, char := range req.UserAgent() { - pre += strconv.Itoa(int(char)) + " " - } - r.DumpRequest(req,"Blank UA: " + pre) - } else { - r.requestLogger.Print("unknown ua: ", c.SanitiseSingleLine(req.UserAgent())) - } + r.unknownUA(req) }// else { //co.AgentViewCounter.Bump(agentMapEnum[agent]) co.AgentViewCounter.Bump(agent) @@ -1492,21 +1505,21 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = c.ParseForm(w,req,user) if err != nil { return err - } - + } + err = routes.ChangeTheme(w,req,user) co.RouteViewCounter.Bump3(5, cn) case "/attachs": err = c.ParseForm(w,req,user) if err != nil { return err - } - - w = r.responseWriter(w) + } + + w = r.responseWriter(w) err = routes.ShowAttachment(w,req,user,extraData) co.RouteViewCounter.Bump3(6, cn) case "/ws": - req.URL.Path += extraData + req.URL.Path += extraData err = c.RouteWebsockets(w,req,user) case "/api": switch(req.URL.Path) { @@ -1527,18 +1540,18 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = c.NoBanned(w,req,user) if err != nil { return err - } - + } + err = c.NoSessionMismatch(w,req,user) if err != nil { return err - } - + } + err = c.MemberOnly(w,req,user) if err != nil { return err - } - + } + switch(req.URL.Path) { case "/report/submit/": err = routes.ReportSubmit(w,req,user,extraData) @@ -1584,8 +1597,8 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = c.SuperModOnly(w,req,user) if err != nil { return err - } - + } + switch(req.URL.Path) { case "/panel/forums/": err = panel.Forums(w,req,user) @@ -2450,7 +2463,7 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = routes.RelationsBlockRemoveSubmit(w,req,user,extraData) co.RouteViewCounter.Bump3(124, cn) default: - req.URL.Path += extraData + req.URL.Path += extraData h, err := c.UserCheckNano(w,req,user,cn) if err != nil { return err @@ -2462,8 +2475,8 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = c.MemberOnly(w,req,user) if err != nil { return err - } - + } + switch(req.URL.Path) { case "/users/ban/submit/": err = c.NoSessionMismatch(w,req,user) @@ -2685,8 +2698,8 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = c.MemberOnly(w,req,user) if err != nil { return err - } - + } + switch(req.URL.Path) { case "/reply/create/": err = c.HandleUploadRoute(w,req,user,int(c.Config.MaxRequestSize)) @@ -2757,13 +2770,13 @@ func (r *GenRouter) routeSwitch(w http.ResponseWriter, req *http.Request, user * err = c.NoSessionMismatch(w,req,user) if err != nil { return err - } - + } + err = c.MemberOnly(w,req,user) if err != nil { return err - } - + } + switch(req.URL.Path) { case "/profile/reply/create/": err = routes.ProfileReplyCreateSubmit(w,req,user) diff --git a/router_gen/main.go b/router_gen/main.go index a9db3883..33f95e51 100644 --- a/router_gen/main.go +++ b/router_gen/main.go @@ -64,9 +64,9 @@ func main() { } return out } - /*o := func(indent int, str string) { + o := func(indent int, str string) { out += countToIndents(indent) + str - }*/ + } on := func(indent int, str string) { out += "\n" + countToIndents(indent) + str } @@ -77,21 +77,18 @@ func main() { out += "\n" + ind2 + "return err\n" + ind + "}" } - runBefore := func(runnables []Runnable, indentCount int) (out string) { - ind := countToIndents(indentCount) + runBefore := func(runnables []Runnable, ind int) { if len(runnables) > 0 { for _, runnable := range runnables { if runnable.Literal { - out += "\n\t" + ind + runnable.Contents + on(ind, runnable.Contents) } else { - out += "\n" + ind + "err = c." + runnable.Contents + "(w,req,user)\n" + - ind + "if err != nil {\n" + - ind + "\treturn err\n" + - ind + "}\n" + ind + on(ind, "err = c."+runnable.Contents+"(w,req,user)") + iferrn(ind) + o(ind, "\n") } } } - return out } userCheckNano := func(indent int, route *RouteImpl) { on(indent, "h, err := c.UserCheckNano(w,req,user,cn)") @@ -113,31 +110,26 @@ func main() { end := len(route.Path) - 1 on(2, "case \""+route.Path[0:end]+"\":") //on(3,"id = " + strconv.Itoa(allRouteMap[route.Name])) - out += runBefore(route.RunBefore, 3) + runBefore(route.RunBefore, 3) if !route.Action && !route.NoHead { - //on(3,"h, err := c.UserCheck(w,req,user)") userCheckNano(3, route) - } /* else if route.Name != "common.RouteWebsockets" { - //on(3,"sa := time.Now()") - //on(3,"cn := uutils.Nanotime()") - }*/ + } writeRoute(3, route) - /*if !route.Action && !route.NoHead { - on(3,"co.RouteViewCounter.Bump2(" + strconv.Itoa(allRouteMap[route.Name]) + ", h.StartedAt)") - } else */if route.Name != "common.RouteWebsockets" { - //on(3,"co.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[route.Name]) + ")") - //on(3,"co.RouteViewCounter.Bump2(" + strconv.Itoa(allRouteMap[route.Name]) + ", sa)") + if route.Name != "common.RouteWebsockets" { on(3, "co.RouteViewCounter.Bump3("+strconv.Itoa(allRouteMap[route.Name])+", cn)") } } + prec := NewPrec() + prec.AddSet("MemberOnly", "SuperModOnly", "AdminOnly", "SuperAdminOnly") + // Hoist runnables which appear on every route to the route group to avoid code duplication -skipRunnableAntiDupe: + dupeMap := make(map[string]int) + //skipRunnableAntiDupe: for _, g := range r.routeGroups { - dupeMap := make(map[string]int) for _, route := range g.RouteList { if len(route.RunBefore) == 0 { - continue skipRunnableAntiDupe + continue //skipRunnableAntiDupe } // TODO: What if there are duplicates of the same runnable on this route? for _, runnable := range route.RunBefore { @@ -148,6 +140,9 @@ skipRunnableAntiDupe: // Unset entries which are already set on the route group for _, gRunnable := range g.RunBefore { delete(dupeMap, gRunnable.Contents) + for _, item := range prec.LessThanItem(gRunnable.Contents) { + delete(dupeMap, item) + } } for runnable, count := range dupeMap { @@ -155,12 +150,16 @@ skipRunnableAntiDupe: g.Before(runnable) } } + // This method is optimised in the compiler to do a bulk delete + for name, _ := range dupeMap { + delete(dupeMap, name) + } } for _, group := range r.routeGroups { end := len(group.Path) - 1 on(2, "case \""+group.Path[0:end]+"\":") - out += runBefore(group.RunBefore, 3) + runBefore(group.RunBefore, 3) on(3, "switch(req.URL.Path) {") defaultRoute := blankRoute() @@ -180,17 +179,7 @@ skipRunnableAntiDupe: if gRunnable.Contents == runnable.Contents { continue skipRunnable } - f := func(e1, e2 string) bool { - return gRunnable.Contents == e1 && runnable.Contents == e2 - } - // TODO: Stop hard-coding these - if f("AdminOnly", "MemberOnly") { - continue skipRunnable - } - if f("AdminOnly", "SuperModOnly") { - continue skipRunnable - } - if f("SuperModOnly", "MemberOnly") { + if prec.GreaterThan(gRunnable.Contents, runnable.Contents) { continue skipRunnable } } @@ -205,36 +194,22 @@ skipRunnableAntiDupe: } } if !route.Action && !route.NoHead && !group.NoHead { - //on(5,"h, err := c.UserCheck(w,req,user)") userCheckNano(5, route) - } else { - //on(5, "cn := uutils.Nanotime()") } writeRoute(5, route) - /*if !route.Action && !route.NoHead && !group.NoHead { - on(5,"co.RouteViewCounter.Bump2(" + strconv.Itoa(allRouteMap[route.Name]) + ", h.StartedAt)") - } else {*/ - //on(5,"co.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[route.Name]) + ")") on(5, "co.RouteViewCounter.Bump3("+strconv.Itoa(allRouteMap[route.Name])+", cn)") - //} } if defaultRoute.Name != "" { mapIt(defaultRoute.Name) on(4, "default:") //on(5,"id = " + strconv.Itoa(allRouteMap[defaultRoute.Name])) - out += runBefore(defaultRoute.RunBefore, 4) + runBefore(defaultRoute.RunBefore, 4) if !defaultRoute.Action && !defaultRoute.NoHead && !group.NoHead { - //on(5, "h, err := c.UserCheck(w,req,user)" userCheckNano(5, defaultRoute) } writeRoute(5, defaultRoute) - /*if !defaultRoute.Action && !defaultRoute.NoHead && !group.NoHead { - on(5,"co.RouteViewCounter.Bump2(" + strconv.Itoa(allRouteMap[defaultRoute.Name]) + ", h.StartedAt)") - } else {*/ - //on(5,co.RouteViewCounter.Bump(" + strconv.Itoa(allRouteMap[defaultRoute.Name]) + ")") on(5, "co.RouteViewCounter.Bump3("+strconv.Itoa(allRouteMap[defaultRoute.Name])+", cn)") - //} } on(3, "}") } @@ -618,10 +593,15 @@ func (r *GenRouter) RemoveFunc(pattern string) error { return nil } -// TODO: Some of these sanitisations may be redundant -func (r *GenRouter) dumpRequest(req *http.Request, pre string,log *log.Logger) { +func (r *GenRouter) dumpRequest(req *http.Request, pre string, log *log.Logger) { var sb strings.Builder - sb.WriteString(pre) + r.ddumpRequest(req,pre,log,&sb) +} + +// TODO: Some of these sanitisations may be redundant +var dumpReqLen = len("\nUA: \nMethod: \nHost: \nURL.Path: \nURL.RawQuery: \nIP: \n") + 3 +var dumpReqLen2 = len("\nHead : ") + 2 +func (r *GenRouter) ddumpRequest(req *http.Request, pre string,log *log.Logger, sb *strings.Builder) { nfield := func(label, val string) { sb.WriteString(label) sb.WriteString(val) @@ -629,7 +609,10 @@ func (r *GenRouter) dumpRequest(req *http.Request, pre string,log *log.Logger) { field := func(label, val string) { nfield(label,c.SanitiseSingleLine(val)) } - field("\nUA: ",req.UserAgent()) + ua := req.UserAgent() + sb.Grow(dumpReqLen + len(pre) + len(ua) + len(req.Method) + len(req.Host) + (dumpReqLen2 * len(req.Header))) + sb.WriteString(pre) + field("\nUA: ",ua) field("\nMethod: ",req.Method) for key, value := range req.Header { // Avoid logging this for security reasons @@ -646,7 +629,10 @@ func (r *GenRouter) dumpRequest(req *http.Request, pre string,log *log.Logger) { field("\nHost: ",req.Host) field("\nURL.Path: ",req.URL.Path) field("\nURL.RawQuery: ",req.URL.RawQuery) - field("\nRef: ",req.Referer()) + ref := req.Referer() + if ref != "" { + field("\nRef: ",req.Referer()) + } nfield("\nIP: ",req.RemoteAddr) sb.WriteString("\n") @@ -658,15 +644,30 @@ func (r *GenRouter) DumpRequest(req *http.Request, pre string) { } func (r *GenRouter) SuspiciousRequest(req *http.Request, pre string) { + var sb strings.Builder if pre != "" { - pre += "\nSuspicious Request" + sb.WriteString("Suspicious Request") } else { pre = "Suspicious Request" } - r.dumpRequest(req,pre,r.suspReqLogger) + r.ddumpRequest(req,pre,r.suspReqLogger,&sb) co.AgentViewCounter.Bump({{.AllAgentMap.suspicious}}) } +func (r *GenRouter) unknownUA(req *http.Request) { + if c.Dev.DebugMode { + var presb strings.Builder + presb.WriteString("Unknown UA: ") + for _, ch := range req.UserAgent() { + presb.WriteString(strconv.Itoa(int(ch))) + presb.WriteRune(' ') + } + r.ddumpRequest(req, "", r.requestLogger, &presb) + } else { + r.requestLogger.Print("unknown ua: ", c.SanitiseSingleLine(req.UserAgent())) + } +} + func isLocalHost(h string) bool { return h=="localhost" || h=="127.0.0.1" || h=="::1" } @@ -811,7 +812,8 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { } /*if c.Dev.QuicPort != 0 { - w.Header().Set("Alt-Svc", "quic=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=2592000; v=\"44,43,39\", h3-23=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h3-24=\":"+strconv.Itoa(c.Dev.QuicPort)+"\"; ma=3600, h2=\":443\"; ma=3600") + sQuicPort := strconv.Itoa(c.Dev.QuicPort) + w.Header().Set("Alt-Svc", "quic=\":"+sQuicPort+"\"; ma=2592000; v=\"44,43,39\", h3-23=\":"+sQuicPort+"\"; ma=3600, h3-24=\":"+sQuicPort+"\"; ma=3600, h2=\":443\"; ma=3600") }*/ // Track the user agents. Unfortunately, everyone pretends to be Mozilla, so this'll be a little less efficient than I would like. @@ -824,13 +826,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { ua := strings.TrimSpace(strings.Replace(strings.TrimPrefix(req.UserAgent(),"Mozilla/5.0 ")," Safari/537.36","",-1)) // Noise, no one's going to be running this and it would require some sort of agent ranking system to determine which identifier should be prioritised over another if ua == "" { co.AgentViewCounter.Bump({{.AllAgentMap.blank}}) - if c.Dev.DebugMode { - var pre string - for _, char := range req.UserAgent() { - pre += strconv.Itoa(int(char)) + " " - } - r.DumpRequest(req,"Blank UA: " + pre) - } + r.unknownUA(req) } else { // WIP UA Parser //var ii = uaBufPool.Get() @@ -927,15 +923,7 @@ func (r *GenRouter) ServeHTTP(w http.ResponseWriter, req *http.Request) { if agent == 0 { //co.AgentViewCounter.Bump({{.AllAgentMap.unknown}}) - if c.Dev.DebugMode { - var pre string - for _, char := range req.UserAgent() { - pre += strconv.Itoa(int(char)) + " " - } - r.DumpRequest(req,"Blank UA: " + pre) - } else { - r.requestLogger.Print("unknown ua: ", c.SanitiseSingleLine(req.UserAgent())) - } + r.unknownUA(req) }// else { //co.AgentViewCounter.Bump(agentMapEnum[agent]) co.AgentViewCounter.Bump(agent) diff --git a/router_gen/misc_test.go b/router_gen/misc_test.go new file mode 100644 index 00000000..82ddaf0a --- /dev/null +++ b/router_gen/misc_test.go @@ -0,0 +1,60 @@ +package main + +import ( + "runtime/debug" + "testing" +) + +func exp(t *testing.T) func(bool, string) { + return func(val bool, msg string) { + if !val { + debug.PrintStack() + t.Error(msg) + } + } +} + +func expf(t *testing.T) func(bool, string, ...interface{}) { + return func(val bool, msg string, params ...interface{}) { + if !val { + debug.PrintStack() + t.Errorf(msg, params...) + } + } +} + +func TestPerc(t *testing.T) { + ex, _, prec := exp(t), expf(t), NewPrec() + ex(!prec.GreaterThan("MemberOnly", "AdminOnly"), "MemberOnly should not be greater then AdminOnly") + ex(!prec.GreaterThan("AdminOnly", "MemberOnly"), "MemberOnly should not be greater then AdminOnly") + ex(!prec.GreaterThan("NotInSet", "AdminOnly"), "NotInSet should not be greater then AdminOnly") + ex(!prec.GreaterThan("AdminOnly", "NotInSet"), "AdminOnly should not be greater then NotInSet") + ex(!prec.InAnySet("MemberOnly"), "MemberOnly should not be in any set") + ex(!prec.InSameSet("MemberOnly", "AdminOnly"), "MemberOnly and AdminOnly should not be in the same set") + ex(!prec.InSameSet("MemberOnly", "NotInSet"), "MemberOnly and NotInSet should not be in the same set") + + prec.AddSet("MemberOnly", "SuperModOnly", "AdminOnly", "SuperAdminOnly") + ex(!prec.GreaterThan("MemberOnly", "AdminOnly"), "MemberOnly should not be greater then AdminOnly") + ex(prec.GreaterThan("AdminOnly", "MemberOnly"), "AdminOnly should be greater then MemberOnly") + ex(!prec.GreaterThan("NotInSet", "AdminOnly"), "NotInSet should not be greater then AdminOnly") + ex(!prec.GreaterThan("AdminOnly", "NotInSet"), "AdminOnly should not be greater then NotInSet") + ex(prec.InAnySet("MemberOnly"), "MemberOnly should be in a set") + ex(!prec.InAnySet("NotInSet"), "NotInSet should not be in any set") + ex(prec.InSameSet("MemberOnly", "AdminOnly"), "MemberOnly and AdminOnly should be in the same set") + ex(!prec.InSameSet("MemberOnly", "NotInSet"), "MemberOnly and NotInSet should not be in the same set") + + items := prec.LessThanItem("AdminOnly") + ex(len(items) > 0, "There should be items which are of a lower precedence than AdminOnly") + imap := make(map[string]bool) + for _, item := range items { + imap[item] = true + } + mex := func(n string, val bool, msg string) { + _, ok := imap[n] + ex(ok == val, msg) + } + mex("SuperModOnly", true, "SuperModOnly should be returned in a list of lower precedence items than AdminOnly") + mex("MemberOnly", true, "MemberOnly should be returned in a list of lower precedence items than AdminOnly") + mex("SuperAdminOnly", false, "SuperAdminOnly should not be returned in a list of lower precedence items than AdminOnly") + mex("NotInSet", false, "NotInSet should not be returned in a list of lower precedence items than AdminOnly") +} diff --git a/router_gen/prec.go b/router_gen/prec.go new file mode 100644 index 00000000..44e08e72 --- /dev/null +++ b/router_gen/prec.go @@ -0,0 +1,60 @@ +package main + +type Prec struct { + Sets []map[string]int + NameToSet map[string]int +} + +func NewPrec() *Prec { + return &Prec{NameToSet: make(map[string]int)} +} + +func (p *Prec) AddSet(precs ...string) { + set := make(map[string]int) + setIndex, i := len(p.Sets), 0 + for _, prec := range precs { + set[prec] = i + p.NameToSet[prec] = setIndex + i++ + } + p.Sets = append(p.Sets, set) +} + +func (p *Prec) InAnySet(name string) bool { + _, ok := p.NameToSet[name] + return ok +} + +func (p *Prec) InSameSet(n, n2 string) bool { + ok, ok2 := p.InAnySet(n), p.InAnySet(n2) + if !ok || !ok2 { + return false + } + set1, set2 := p.NameToSet[n], p.NameToSet[n2] + return set1 == set2 +} + +func (p *Prec) GreaterThan(greater, lesser string) bool { + if !p.InSameSet(greater, lesser) { + return false + } + set := p.Sets[p.NameToSet[greater]] + return set[greater] > set[lesser] +} + +func (p *Prec) LessThanItem(greater string) (l []string) { + if len(p.Sets) == 0 { + return nil + } + + setIndex := p.NameToSet[greater] + set := p.Sets[setIndex] + ref := set[greater] + for name, value := range set { + if value < ref { + l = append(l, name) + } + } + + return l +}