diff --git a/.travis.yml b/.travis.yml index 8e0c120..6080147 100644 --- a/.travis.yml +++ b/.travis.yml @@ -3,5 +3,6 @@ language: go go: - 1.7 - 1.8 + - 1.9 after_success: - test -n "$TRAVIS_TAG" && curl -sL https://git.io/goreleaser | bash diff --git a/decode.go b/decode.go new file mode 100644 index 0000000..0ccc8e1 --- /dev/null +++ b/decode.go @@ -0,0 +1,106 @@ +package tmsh + +import ( + "fmt" + "reflect" + "strconv" + "strings" +) + +func Unmarshal(data string, out interface{}) error { + data = strings.Trim(data, "\n") + + l := Lexer{s: NewScanner(data)} + if yyParse(&l) != 0 { + return fmt.Errorf("Parse error") + } + + v := reflect.ValueOf(out) + if v.Kind() == reflect.Ptr && !v.IsNil() { + v = v.Elem() + } + + unmarshal(l.result, v) + + return nil +} + +func unmarshal(n *node, out reflect.Value) { + switch n.kind { + case ltmNodeNode, ltmPoolNode, ltmVirtualNode: + unmarshal(n.children[0], out) + case structNode: + decodeStructNode(n, out) + case keyNode: + decodeKeyNode(n, out) + case scalarNode: + decodeScalarNode(n, out) + default: + panic("Unknown node kind") + } +} + +func decodeStructNode(n *node, out reflect.Value) { + l := len(n.children) + + switch out.Kind() { + case reflect.Struct: + for _, c := range n.children { + unmarshal(c, out) + } + case reflect.Slice: + out.Set(reflect.MakeSlice(out.Type(), l, l)) + et := out.Type().Elem() + for i := 0; i < l; i++ { + e := reflect.New(et).Elem() + unmarshal(n.children[i], e) + out.Index(i).Set(e) + } + case reflect.Map: + out.Set(reflect.MakeMap(out.Type())) + et := out.Type().Elem() + for i := 0; i < l; i++ { + k := reflect.ValueOf(n.children[i].value) + v := reflect.New(et).Elem() + unmarshal(n.children[i], v) + out.SetMapIndex(k, v) + } + } +} + +func decodeKeyNode(n *node, out reflect.Value) { + switch out.Kind() { + case reflect.Struct: + if f, ok := lookupField(n.value, out); ok { + unmarshal(n.children[0], f) + } else { + for _, c := range n.children { + unmarshal(c, out) + } + } + case reflect.String: + unmarshal(n.children[0], out) + } +} + +func decodeScalarNode(n *node, out reflect.Value) { + switch out.Kind() { + case reflect.Int: + i, _ := strconv.ParseInt(n.value, 10, 64) + out.SetInt(i) + case reflect.String: + out.SetString(n.value) + } +} + +func lookupField(t string, v reflect.Value) (reflect.Value, bool) { + typ := v.Type() + for i := 0; i < v.NumField(); i++ { + fi := typ.Field(i) + tagv := fi.Tag.Get("ltm") + if tagv == t { + return v.Field(i), true + } + } + return reflect.Value{}, false +} diff --git a/ltm_parse_test.go b/decode_test.go similarity index 58% rename from ltm_parse_test.go rename to decode_test.go index caae780..dcad3cd 100644 --- a/ltm_parse_test.go +++ b/decode_test.go @@ -5,74 +5,33 @@ import ( "testing" ) -func TestCurrentLine(t *testing.T) { - str := `line1 -line2 -line3 -line4` - - fm := NewFieldManager(str) - - actual := fm.CurrentLine() - expect := "line1" - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("got %v\nwant %v", actual, expect) - } - - fm.currentLineNum += 2 - - actual = fm.CurrentLine() - expect = "line3" - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("got %v\nwant %v", actual, expect) - } -} - -func TestAdvance(t *testing.T) { - str := `line1 -line2 -line3 -line4` - - fm := NewFieldManager(str) - fm.Advance() - fm.Advance() - fm.Advance() - - actual := fm.currentLineNum - expect := 3 - - if !reflect.DeepEqual(actual, expect) { - t.Errorf("got %v\nwant %v", actual, expect) - } -} - -func TestParseShowLtmNode(t *testing.T) { +func TestUnmarshalNode(t *testing.T) { str := `ltm node dev-web01.example.com { - addr 192.0.2.1 - cur-sessions 0 - monitor-rule none - monitor-status unchecked - name dev-web01.example.com - serverside.bits-in 0 - serverside.bits-out 0 - serverside.cur-conns 0 - serverside.max-conns 0 - serverside.pkts-in 0 - serverside.pkts-out 0 - serverside.tot-conns 0 - session-status enabled - status.availability-state unknown - status.enabled-state enabled - status.status-reason Node address does not have service checking enabled - tot-requests 0 + addr 192.0.2.1 + cur-sessions 0 + monitor-rule none + monitor-status unchecked + name dev-web01.example.com + serverside.bits-in 0 + serverside.bits-out 0 + serverside.cur-conns 0 + serverside.max-conns 0 + serverside.pkts-in 0 + serverside.pkts-out 0 + serverside.tot-conns 0 + session-status enabled + status.availability-state unknown + status.enabled-state enabled + status.status-reason Node address does not have service checking enabled + tot-requests 0 }` - node := ParseShowLtmNode(str) + var node Node + if err := Unmarshal(str, &node); err != nil { + t.Errorf("got %v", err) + } - expect := &Node{ + expect := Node{ Addr: "192.0.2.1", Name: "dev-web01.example.com", MonitorRule: "none", @@ -85,7 +44,7 @@ func TestParseShowLtmNode(t *testing.T) { } } -func TestParseShowLtmPool(t *testing.T) { +func TestUnmarshalPool(t *testing.T) { //# show ltm pool api.example.com_8080 members field-fmt str := `ltm pool api.example.com_8080 { active-member-cnt 2 @@ -174,39 +133,40 @@ func TestParseShowLtmPool(t *testing.T) { tot-requests 0 }` - pool := ParseShowLtmPool(str) - - poolMembers := []PoolMember{ - PoolMember{ - Name: "api01.example.com", - Addr: "192.0.2.1", - Port: 8080, - MonitorRule: "/Common/tcp (pool monitor)", - MonitorStatus: "up", - EnabledState: "enabled", - AvailabilityState: "available", - StatusReason: "Pool member is available", - }, - PoolMember{ - Name: "api02.example.com", - Addr: "192.0.2.2", - Port: 8080, - MonitorRule: "none", - MonitorStatus: "unchecked", - EnabledState: "disabled", - AvailabilityState: "unknown", - StatusReason: "Pool member does not have service checking enabled", - }, + var pool Pool + if err := Unmarshal(str, &pool); err != nil { + t.Errorf("got %v", err) } - expect := &Pool{ + expect := Pool{ ActiveMemberCount: 2, MonitorRule: "/Common/tcp", Name: "api.example.com_8080", AvailabilityState: "available", EnabledState: "enabled", StatusReason: "The pool is available", - PoolMembers: poolMembers, + PoolMembers: []PoolMember{ + PoolMember{ + Name: "api01.example.com", + Addr: "192.0.2.1", + Port: 8080, + MonitorRule: "/Common/tcp (pool monitor)", + MonitorStatus: "up", + EnabledState: "enabled", + AvailabilityState: "available", + StatusReason: "Pool member is available", + }, + PoolMember{ + Name: "api02.example.com", + Addr: "192.0.2.2", + Port: 8080, + MonitorRule: "none", + MonitorStatus: "unchecked", + EnabledState: "disabled", + AvailabilityState: "unknown", + StatusReason: "Pool member does not have service checking enabled", + }, + }, } if !reflect.DeepEqual(pool, expect) { @@ -214,29 +174,41 @@ func TestParseShowLtmPool(t *testing.T) { } } -func TestParseListLtmVirtual(t *testing.T) { - //# list ltm virtual api.example.com_80 - str := `ltm virtual api.example.com_80 { - destination 203.0.113.1:http - ip-protocol tcp - mask 255.255.255.255 - partition partition1 - pool api.example.com_80 - profiles { - /Common/tcp { } - } - source 0.0.0.0/0 - vs-index 1234 +func TestUnmarshalVirtualServer(t *testing.T) { + //# list ltm virtual api.example.com_443 + str := `ltm virtual api.example.com_443 { + destination 203.0.113.1:https + ip-protocol tcp + mask 255.255.255.255 + partition partition1 + pool api.example.com_443 + profiles { + /Common/tcp { + context all + } + wildcard.example.com { + context clientside + } + } + source 0.0.0.0/0 + vs-index 1234 }` - vs := ParseListLtmVirtual(str) + var vs VirtualServer + if err := Unmarshal(str, &vs); err != nil { + t.Errorf("got %v", err) + } - expect := &VirtualServer{ - Destination: "203.0.113.1:http", + expect := VirtualServer{ + Destination: "203.0.113.1:https", IpProtocol: "tcp", Mask: "255.255.255.255", Partition: "partition1", - Pool: "api.example.com_80", + Pool: "api.example.com_443", + Profiles: map[string]Profile{ + "/Common/tcp": Profile{Context: "all"}, + "wildcard.example.com": Profile{Context: "clientside"}, + }, } if !reflect.DeepEqual(vs, expect) { diff --git a/lex.go b/lex.go new file mode 100644 index 0000000..d27847a --- /dev/null +++ b/lex.go @@ -0,0 +1,106 @@ +package tmsh + +import ( + "bytes" + "strings" +) + +type Scanner struct { + r *strings.Reader + line int +} + +func NewScanner(data string) *Scanner { + return &Scanner{r: strings.NewReader(data)} +} + +func isWhitespace(ch rune) bool { + return ch == ' ' || ch == '\t' +} + +func isLetter(ch rune) bool { + return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || + ch == '.' || ch == ',' || ch == '_' || ch == '-' || ch == ':' || + ch == '/' || ch == '(' || ch == ')' || ch == '\'' +} + +func isDigit(ch rune) bool { + return (ch >= '0' && ch <= '9') +} + +func (s *Scanner) read() rune { + ch, _, err := s.r.ReadRune() + if err != nil { + return rune(0) + } + return ch +} + +func (s *Scanner) unread() { _ = s.r.UnreadRune() } + +func (s *Scanner) Scan() (tok int, lit string) { + ch := s.read() + + if isWhitespace(ch) { + s.unread() + return s.scanWhitespace() + } else if isLetter(ch) || isDigit(ch) { + s.unread() + return s.scanIdent() + } + + switch ch { + case rune(0): + return EOF, "" + case '\n': + s.line++ + return NEWLINE, string(ch) + case '{': + return L_BRACE, string(ch) + case '}': + return R_BRACE, string(ch) + } + + return ILLEGAL, string(ch) +} + +func (s *Scanner) scanWhitespace() (tok int, lit string) { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.read(); ch == rune(0) { + break + } else if !isWhitespace(ch) { + s.unread() + break + } else { + buf.WriteRune(ch) + } + } + + return WS, buf.String() +} + +func (s *Scanner) scanIdent() (tok int, lit string) { + var buf bytes.Buffer + buf.WriteRune(s.read()) + + for { + if ch := s.read(); ch == rune(0) { + break + } else if !isLetter(ch) && !isDigit(ch) && ch != '_' { + s.unread() + break + } else { + _, _ = buf.WriteRune(ch) + } + } + + switch buf.String() { + case "ltm": + return LTM, buf.String() + } + + return IDENT, buf.String() +} diff --git a/ltm.go b/ltm.go index f738a2f..3836415 100644 --- a/ltm.go +++ b/ltm.go @@ -7,40 +7,46 @@ import ( ) type Node struct { - Name string - Addr string - MonitorRule string - MonitorStatus string - EnabledState string + Name string `ltm:"name"` + Addr string `ltm:"addr"` + MonitorRule string `ltm:"monitor-rule"` + MonitorStatus string `ltm:"monitor-status"` + EnabledState string `ltm:"status.enabled-state"` } type Pool struct { - ActiveMemberCount int - Name string - MonitorRule string - AvailabilityState string - EnabledState string - StatusReason string - PoolMembers []PoolMember + ActiveMemberCount int `ltm:"active-member-cnt"` + Name string `ltm:"name"` + MonitorRule string `ltm:"monitor-rule"` + AvailabilityState string `ltm:"status.availability-state"` + EnabledState string `ltm:"status.enabled-state"` + StatusReason string `ltm:"status.status-reason"` + PoolMembers []PoolMember `ltm:"members"` } type PoolMember struct { - Name string - Addr string - Port int - MonitorRule string - MonitorStatus string - EnabledState string - AvailabilityState string - StatusReason string + Name string `ltm:"node-name"` + Addr string `ltm:"addr"` + Port int `ltm:"port"` + MonitorRule string `ltm:"monitor-rule"` + MonitorStatus string `ltm:"monitor-status"` + EnabledState string `ltm:"status.enabled-state"` + AvailabilityState string `ltm:"status.availability-state"` + StatusReason string `ltm:"status.status-reason"` } type VirtualServer struct { - Destination string - IpProtocol string - Mask string - Partition string - Pool string + Name string `ltm:"name"` + Destination string `ltm:"destination"` + IpProtocol string `ltm:"ip-protocol"` + Mask string `ltm:"mask"` + Partition string `ltm:"partition"` + Pool string `ltm:"pool"` + Profiles map[string]Profile `ltm:"profiles"` +} + +type Profile struct { + Context string `ltm:"context"` } func (bigip *BigIP) GetNode(name string) (*Node, error) { @@ -48,8 +54,13 @@ func (bigip *BigIP) GetNode(name string) (*Node, error) { if strings.Contains(ret, "was not found.") { return nil, fmt.Errorf(ret) } - node := ParseShowLtmNode(ret) - return node, nil + + var node Node + if err := Unmarshal(ret, &node); err != nil { + return nil, err + } + + return &node, nil } func (bigip *BigIP) CreateNode(name, ipaddr string) error { @@ -89,8 +100,13 @@ func (bigip *BigIP) GetPool(name string) (*Pool, error) { if strings.Contains(ret, "was not found.") { return nil, fmt.Errorf(ret) } - pool := ParseShowLtmPool(ret) - return pool, nil + + var pool Pool + if err := Unmarshal(ret, &pool); err != nil { + return nil, err + } + + return &pool, nil } func (bigip *BigIP) CreatePool(name string) error { @@ -162,9 +178,13 @@ func (bigip *BigIP) GetVirtualServer(name string) (*VirtualServer, error) { if strings.Contains(ret, "was not found.") { return nil, fmt.Errorf(ret) } - vs := ParseListLtmVirtual(ret) - return vs, nil + var vs VirtualServer + if err := Unmarshal(ret, &vs); err != nil { + return nil, err + } + + return &vs, nil } func (bigip *BigIP) CreateVirtualServer(vsName, poolName, targetVIP, defaultProfileName string, targetPort int) error { diff --git a/ltm_parse.go b/ltm_parse.go deleted file mode 100644 index cd4a45e..0000000 --- a/ltm_parse.go +++ /dev/null @@ -1,177 +0,0 @@ -package tmsh - -import ( - "fmt" - "strconv" - "strings" -) - -type FieldManager struct { - lines []string - currentLineNum int -} - -func NewFieldManager(str string) FieldManager { - return FieldManager{ - lines: strings.Split(str, "\n"), - currentLineNum: 0, - } -} - -func (fm *FieldManager) CurrentLine() string { - return fm.lines[fm.currentLineNum] -} - -func (fm *FieldManager) Advance() error { - if len(fm.lines) == fm.currentLineNum { - fmt.Errorf("Index out of range") - } - fm.currentLineNum += 1 - return nil -} - -func ParseListLtmVirtual(str string) *VirtualServer { - vs := VirtualServer{} - - fm := NewFieldManager(str) - - for len(fm.lines) > fm.currentLineNum { - line := strings.TrimSpace(fm.CurrentLine()) - columns := strings.SplitAfterN(line, " ", 2) - - switch { - case strings.HasPrefix(columns[0], "destination"): - vs.Destination = columns[1] - case strings.HasPrefix(columns[0], "ip-protocol"): - vs.IpProtocol = columns[1] - case strings.HasPrefix(columns[0], "mask"): - vs.Mask = columns[1] - case strings.HasPrefix(columns[0], "partition"): - vs.Partition = columns[1] - case strings.HasPrefix(columns[0], "pool"): - vs.Pool = columns[1] - } - - fm.Advance() - } - - return &vs -} - -func ParsePoolMemberNodes(fm *FieldManager) []PoolMember { - poolMembers := []PoolMember{} - - if strings.HasSuffix(fm.CurrentLine(), "members {") { - fm.Advance() - } - - for { - // Parse a Node - if strings.HasSuffix(fm.CurrentLine(), "{") { - var member PoolMember - - for { - line := strings.TrimSpace(fm.CurrentLine()) - columns := strings.SplitAfterN(line, " ", 2) - - switch { - case strings.HasPrefix(columns[0], "addr"): - member.Addr = columns[1] - case strings.HasPrefix(columns[0], "node-name"): - member.Name = columns[1] - case strings.HasPrefix(columns[0], "port"): - member.Port, _ = strconv.Atoi(columns[1]) - case strings.HasPrefix(columns[0], "monitor-rule"): - member.MonitorRule = columns[1] - case strings.HasPrefix(columns[0], "monitor-status"): - member.MonitorStatus = columns[1] - case strings.HasPrefix(columns[0], "status.enabled-state"): - member.EnabledState = columns[1] - case strings.HasPrefix(columns[0], "status.availability-state"): - member.AvailabilityState = columns[1] - case strings.HasPrefix(columns[0], "status.status-reason"): - member.StatusReason = columns[1] - } - - if strings.HasSuffix(fm.CurrentLine(), "}") { - poolMembers = append(poolMembers, member) - break - } - - fm.Advance() - } - } - - fm.Advance() - - // Check for end of "members {}" - if strings.HasSuffix(fm.CurrentLine(), "}") { - break - } - } - - return poolMembers -} - -func ParseShowLtmPool(str string) *Pool { - pool := Pool{} - - fm := NewFieldManager(str) - - for len(fm.lines) > fm.currentLineNum { - // Parse Pool Members - if strings.HasSuffix(fm.CurrentLine(), "members {") { - pool.PoolMembers = ParsePoolMemberNodes(&fm) - } - - line := strings.TrimSpace(fm.CurrentLine()) - columns := strings.SplitAfterN(line, " ", 2) - - // Parse Pool - switch { - case strings.HasPrefix(columns[0], "active-member-cnt"): - pool.ActiveMemberCount, _ = strconv.Atoi(columns[1]) - case strings.HasPrefix(columns[0], "monitor-rule"): - pool.MonitorRule = columns[1] - case strings.HasPrefix(columns[0], "name"): - pool.Name = columns[1] - case strings.HasPrefix(columns[0], "status.availability-state"): - pool.AvailabilityState = columns[1] - case strings.HasPrefix(columns[0], "status.enabled-state"): - pool.EnabledState = columns[1] - case strings.HasPrefix(columns[0], "status.status-reason"): - pool.StatusReason = columns[1] - } - - fm.Advance() - } - - return &pool -} - -func ParseShowLtmNode(str string) *Node { - node := Node{} - for _, line := range strings.Split(str, "\n") { - if strings.HasSuffix(line, "{") || strings.HasSuffix(line, "}") { - continue - } - - line = strings.TrimSpace(line) - columns := strings.SplitAfterN(line, " ", 2) - - switch { - case strings.HasPrefix(columns[0], "addr"): - node.Addr = columns[1] - case strings.HasPrefix(columns[0], "name"): - node.Name = columns[1] - case strings.HasPrefix(columns[0], "monitor-rule"): - node.MonitorRule = columns[1] - case strings.HasPrefix(columns[0], "monitor-status"): - node.MonitorStatus = columns[1] - case strings.HasPrefix(columns[0], "status.enabled-state"): - node.EnabledState = columns[1] - } - } - - return &node -} diff --git a/parser.go b/parser.go new file mode 100644 index 0000000..15dc458 --- /dev/null +++ b/parser.go @@ -0,0 +1,583 @@ +//line parser.go.y:2 +package tmsh + +import __yyfmt__ "fmt" + +//line parser.go.y:2 +import ( + "fmt" +) + +const ( + ltmNodeNode = iota + ltmPoolNode + ltmVirtualNode + keyNode + structNode + scalarNode +) + +type nodeType int + +type node struct { + kind nodeType + value string + children []*node +} + +type Token struct { + token int + literal string +} + +//line parser.go.y:32 +type yySymType struct { + yys int + token Token + ltm *node + object *node + pair *node + members []*node + value *node +} + +const ILLEGAL = 57346 +const EOF = 57347 +const WS = 57348 +const NEWLINE = 57349 +const L_BRACE = 57350 +const R_BRACE = 57351 +const IDENT = 57352 +const LTM = 57353 + +var yyToknames = [...]string{ + "$end", + "error", + "$unk", + "ILLEGAL", + "EOF", + "WS", + "NEWLINE", + "L_BRACE", + "R_BRACE", + "IDENT", + "LTM", +} +var yyStatenames = [...]string{} + +const yyEofCode = 1 +const yyErrCode = 2 +const yyInitialStackSize = 16 + +//line parser.go.y:126 + +type Lexer struct { + s *Scanner + result *node +} + +func (l *Lexer) Lex(lval *yySymType) int { + var tok int + var lit string + + for { + tok, lit = l.s.Scan() + if tok != WS { + break + } + } + + lval.token = Token{token: tok, literal: lit} + + if tok == EOF { + return 0 + } + + return tok +} + +func (l *Lexer) Error(e string) { + panic(fmt.Sprintf("line %d : %s", l.s.line, e)) +} + +//line yacctab:1 +var yyExca = [...]int{ + -1, 1, + 1, -1, + -2, 0, +} + +const yyPrivate = 57344 + +const yyLast = 24 + +var yyAct = [...]int{ + + 16, 5, 15, 6, 2, 18, 13, 12, 9, 12, + 11, 18, 4, 8, 17, 7, 6, 3, 20, 21, + 19, 14, 10, 1, +} +var yyPact = [...]int{ + + -7, -1000, 7, 2, 8, -1000, 6, -1000, -1, -1000, + -3, -1000, -5, -1000, -1000, -1000, 13, 11, 1, -1000, + -1000, -1000, +} +var yyPgo = [...]int{ + + 0, 23, 1, 10, 22, 0, +} +var yyR1 = [...]int{ + + 0, 1, 2, 2, 2, 4, 4, 3, 3, 3, + 5, 5, +} +var yyR2 = [...]int{ + + 0, 4, 2, 3, 4, 1, 2, 2, 3, 3, + 2, 1, +} +var yyChk = [...]int{ + + -1000, -1, 11, 10, 10, -2, 8, 9, 7, 9, + -4, -3, 10, 9, -3, 7, -5, -2, 10, 7, + 7, -5, +} +var yyDef = [...]int{ + + 0, -2, 0, 0, 0, 1, 0, 2, 0, 3, + 0, 5, 0, 4, 6, 7, 0, 0, 11, 8, + 9, 10, +} +var yyTok1 = [...]int{ + + 1, +} +var yyTok2 = [...]int{ + + 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, +} +var yyTok3 = [...]int{ + 0, +} + +var yyErrorMessages = [...]struct { + state int + token int + msg string +}{} + +//line yaccpar:1 + +/* parser for yacc output */ + +var ( + yyDebug = 0 + yyErrorVerbose = false +) + +type yyLexer interface { + Lex(lval *yySymType) int + Error(s string) +} + +type yyParser interface { + Parse(yyLexer) int + Lookahead() int +} + +type yyParserImpl struct { + lval yySymType + stack [yyInitialStackSize]yySymType + char int +} + +func (p *yyParserImpl) Lookahead() int { + return p.char +} + +func yyNewParser() yyParser { + return &yyParserImpl{} +} + +const yyFlag = -1000 + +func yyTokname(c int) string { + if c >= 1 && c-1 < len(yyToknames) { + if yyToknames[c-1] != "" { + return yyToknames[c-1] + } + } + return __yyfmt__.Sprintf("tok-%v", c) +} + +func yyStatname(s int) string { + if s >= 0 && s < len(yyStatenames) { + if yyStatenames[s] != "" { + return yyStatenames[s] + } + } + return __yyfmt__.Sprintf("state-%v", s) +} + +func yyErrorMessage(state, lookAhead int) string { + const TOKSTART = 4 + + if !yyErrorVerbose { + return "syntax error" + } + + for _, e := range yyErrorMessages { + if e.state == state && e.token == lookAhead { + return "syntax error: " + e.msg + } + } + + res := "syntax error: unexpected " + yyTokname(lookAhead) + + // To match Bison, suggest at most four expected tokens. + expected := make([]int, 0, 4) + + // Look for shiftable tokens. + base := yyPact[state] + for tok := TOKSTART; tok-1 < len(yyToknames); tok++ { + if n := base + tok; n >= 0 && n < yyLast && yyChk[yyAct[n]] == tok { + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + } + + if yyDef[state] == -2 { + i := 0 + for yyExca[i] != -1 || yyExca[i+1] != state { + i += 2 + } + + // Look for tokens that we accept or reduce. + for i += 2; yyExca[i] >= 0; i += 2 { + tok := yyExca[i] + if tok < TOKSTART || yyExca[i+1] == 0 { + continue + } + if len(expected) == cap(expected) { + return res + } + expected = append(expected, tok) + } + + // If the default action is to accept or reduce, give up. + if yyExca[i+1] != 0 { + return res + } + } + + for i, tok := range expected { + if i == 0 { + res += ", expecting " + } else { + res += " or " + } + res += yyTokname(tok) + } + return res +} + +func yylex1(lex yyLexer, lval *yySymType) (char, token int) { + token = 0 + char = lex.Lex(lval) + if char <= 0 { + token = yyTok1[0] + goto out + } + if char < len(yyTok1) { + token = yyTok1[char] + goto out + } + if char >= yyPrivate { + if char < yyPrivate+len(yyTok2) { + token = yyTok2[char-yyPrivate] + goto out + } + } + for i := 0; i < len(yyTok3); i += 2 { + token = yyTok3[i+0] + if token == char { + token = yyTok3[i+1] + goto out + } + } + +out: + if token == 0 { + token = yyTok2[1] /* unknown char */ + } + if yyDebug >= 3 { + __yyfmt__.Printf("lex %s(%d)\n", yyTokname(token), uint(char)) + } + return char, token +} + +func yyParse(yylex yyLexer) int { + return yyNewParser().Parse(yylex) +} + +func (yyrcvr *yyParserImpl) Parse(yylex yyLexer) int { + var yyn int + var yyVAL yySymType + var yyDollar []yySymType + _ = yyDollar // silence set and not used + yyS := yyrcvr.stack[:] + + Nerrs := 0 /* number of errors */ + Errflag := 0 /* error recovery flag */ + yystate := 0 + yyrcvr.char = -1 + yytoken := -1 // yyrcvr.char translated into internal numbering + defer func() { + // Make sure we report no lookahead when not parsing. + yystate = -1 + yyrcvr.char = -1 + yytoken = -1 + }() + yyp := -1 + goto yystack + +ret0: + return 0 + +ret1: + return 1 + +yystack: + /* put a state and value onto the stack */ + if yyDebug >= 4 { + __yyfmt__.Printf("char %v in %v\n", yyTokname(yytoken), yyStatname(yystate)) + } + + yyp++ + if yyp >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyS[yyp] = yyVAL + yyS[yyp].yys = yystate + +yynewstate: + yyn = yyPact[yystate] + if yyn <= yyFlag { + goto yydefault /* simple state */ + } + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + yyn += yytoken + if yyn < 0 || yyn >= yyLast { + goto yydefault + } + yyn = yyAct[yyn] + if yyChk[yyn] == yytoken { /* valid shift */ + yyrcvr.char = -1 + yytoken = -1 + yyVAL = yyrcvr.lval + yystate = yyn + if Errflag > 0 { + Errflag-- + } + goto yystack + } + +yydefault: + /* default state action */ + yyn = yyDef[yystate] + if yyn == -2 { + if yyrcvr.char < 0 { + yyrcvr.char, yytoken = yylex1(yylex, &yyrcvr.lval) + } + + /* look through exception table */ + xi := 0 + for { + if yyExca[xi+0] == -1 && yyExca[xi+1] == yystate { + break + } + xi += 2 + } + for xi += 2; ; xi += 2 { + yyn = yyExca[xi+0] + if yyn < 0 || yyn == yytoken { + break + } + } + yyn = yyExca[xi+1] + if yyn < 0 { + goto ret0 + } + } + if yyn == 0 { + /* error ... attempt to resume parsing */ + switch Errflag { + case 0: /* brand new error */ + yylex.Error(yyErrorMessage(yystate, yytoken)) + Nerrs++ + if yyDebug >= 1 { + __yyfmt__.Printf("%s", yyStatname(yystate)) + __yyfmt__.Printf(" saw %s\n", yyTokname(yytoken)) + } + fallthrough + + case 1, 2: /* incompletely recovered error ... try again */ + Errflag = 3 + + /* find a state where "error" is a legal shift action */ + for yyp >= 0 { + yyn = yyPact[yyS[yyp].yys] + yyErrCode + if yyn >= 0 && yyn < yyLast { + yystate = yyAct[yyn] /* simulate a shift of "error" */ + if yyChk[yystate] == yyErrCode { + goto yystack + } + } + + /* the current p has no shift on "error", pop stack */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery pops state %d\n", yyS[yyp].yys) + } + yyp-- + } + /* there is no state on the stack with an error shift ... abort */ + goto ret1 + + case 3: /* no shift yet; clobber input char */ + if yyDebug >= 2 { + __yyfmt__.Printf("error recovery discards %s\n", yyTokname(yytoken)) + } + if yytoken == yyEofCode { + goto ret1 + } + yyrcvr.char = -1 + yytoken = -1 + goto yynewstate /* try again in the same state */ + } + } + + /* reduction by production yyn */ + if yyDebug >= 2 { + __yyfmt__.Printf("reduce %v in:\n\t%v\n", yyn, yyStatname(yystate)) + } + + yynt := yyn + yypt := yyp + _ = yypt // guard against "declared and not used" + + yyp -= yyR2[yyn] + // yyp is now the index of $0. Perform the default action. Iff the + // reduced production is ε, $1 is possibly out of range. + if yyp+1 >= len(yyS) { + nyys := make([]yySymType, len(yyS)*2) + copy(nyys, yyS) + yyS = nyys + } + yyVAL = yyS[yyp+1] + + /* consult goto table to find next state */ + yyn = yyR1[yyn] + yyg := yyPgo[yyn] + yyj := yyg + yyS[yyp].yys + 1 + + if yyj >= yyLast { + yystate = yyAct[yyg] + } else { + yystate = yyAct[yyj] + if yyChk[yystate] != -yyn { + yystate = yyAct[yyg] + } + } + // dummy call; replaced with literal code + switch yynt { + + case 1: + yyDollar = yyS[yypt-4 : yypt+1] + //line parser.go.y:60 + { + var kind nodeType + switch yyDollar[2].token.literal { + case "node": + kind = ltmNodeNode + case "pool": + kind = ltmPoolNode + case "virtual": + kind = ltmVirtualNode + } + yylex.(*Lexer).result = &node{ + kind: kind, + value: yyDollar[3].token.literal, + children: []*node{yyDollar[4].object}, + } + } + case 2: + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:79 + { + yyVAL.object = &node{kind: structNode, value: "", children: []*node{}} + } + case 3: + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:83 + { + yyVAL.object = &node{kind: structNode, value: "", children: []*node{}} + } + case 4: + yyDollar = yyS[yypt-4 : yypt+1] + //line parser.go.y:87 + { + yyVAL.object = &node{kind: structNode, value: "", children: yyDollar[3].members} + } + case 5: + yyDollar = yyS[yypt-1 : yypt+1] + //line parser.go.y:93 + { + yyVAL.members = []*node{yyDollar[1].pair} + } + case 6: + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:97 + { + yyVAL.members = append(yyDollar[1].members, yyDollar[2].pair) + } + case 7: + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:103 + { + yyVAL.pair = &node{kind: keyNode, value: yyDollar[1].token.literal, children: []*node{}} + } + case 8: + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:107 + { + yyVAL.pair = &node{kind: keyNode, value: yyDollar[1].token.literal, children: []*node{yyDollar[2].value}} + } + case 9: + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:111 + { + yyVAL.pair = &node{kind: keyNode, value: yyDollar[1].token.literal, children: []*node{yyDollar[2].object}} + } + case 10: + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:117 + { + s := fmt.Sprintf("%s %s", yyDollar[1].token.literal, yyDollar[2].value.value) + yyVAL.value = &node{kind: scalarNode, value: s, children: []*node{}} + } + case 11: + yyDollar = yyS[yypt-1 : yypt+1] + //line parser.go.y:122 + { + yyVAL.value = &node{kind: scalarNode, value: yyDollar[1].token.literal, children: []*node{}} + } + } + goto yystack /* stack new state and value */ +} diff --git a/parser.go.y b/parser.go.y new file mode 100644 index 0000000..6ea35c0 --- /dev/null +++ b/parser.go.y @@ -0,0 +1,155 @@ +%{ +package tmsh + +import ( + "fmt" +) + +const ( + ltmNodeNode = iota + ltmPoolNode + ltmVirtualNode + keyNode + structNode + scalarNode +) + +type nodeType int + +type node struct { + kind nodeType + value string + children []*node +} + +type Token struct { + token int + literal string +} + +%} + +%union{ + token Token + ltm *node + object *node + pair *node + members []*node + value *node +} + +%type ltm +%type object +%type pair +%type members +%type value + +%token ILLEGAL +%token EOF +%token WS +%token NEWLINE +%token L_BRACE +%token R_BRACE +%token IDENT +%token LTM + +%% + +ltm + : LTM IDENT IDENT object + { + var kind nodeType + switch $2.literal { + case "node": + kind = ltmNodeNode + case "pool": + kind = ltmPoolNode + case "virtual": + kind = ltmVirtualNode + } + yylex.(*Lexer).result = &node{ + kind: kind, + value: $3.literal, + children: []*node{$4}, + } + } + +object + : L_BRACE R_BRACE + { + $$ = &node{kind: structNode, value: "", children: []*node{}} + } + | L_BRACE NEWLINE R_BRACE + { + $$ = &node{kind: structNode, value: "", children: []*node{}} + } + | L_BRACE NEWLINE members R_BRACE + { + $$ = &node{kind: structNode, value: "", children: $3} + } + +members + : pair + { + $$ = []*node{$1} + } + | members pair + { + $$ = append($1, $2) + } + +pair + : IDENT NEWLINE + { + $$ = &node{kind: keyNode, value: $1.literal, children: []*node{}} + } + | IDENT value NEWLINE + { + $$ = &node{kind: keyNode, value: $1.literal, children: []*node{$2}} + } + | IDENT object NEWLINE + { + $$ = &node{kind: keyNode, value: $1.literal, children: []*node{$2}} + } + +value + : IDENT value + { + s := fmt.Sprintf("%s %s", $1.literal, $2.value) + $$ = &node{kind: scalarNode, value: s, children: []*node{}} + } + | IDENT + { + $$ = &node{kind: scalarNode, value: $1.literal, children: []*node{}} + } + +%% + +type Lexer struct { + s *Scanner + result *node +} + +func (l *Lexer) Lex(lval *yySymType) int { + var tok int + var lit string + + for { + tok, lit = l.s.Scan() + if tok != WS { + break + } + } + + lval.token = Token{token: tok, literal: lit} + + if tok == EOF { + return 0 + } + + return tok +} + +func (l *Lexer) Error(e string) { + panic(fmt.Sprintf("line %d : %s", l.s.line, e)) +} diff --git a/y.output b/y.output new file mode 100644 index 0000000..efba73b --- /dev/null +++ b/y.output @@ -0,0 +1,177 @@ + +state 0 + $accept: .ltm $end + + LTM shift 2 + . error + + ltm goto 1 + +state 1 + $accept: ltm.$end + + $end accept + . error + + +state 2 + ltm: LTM.IDENT IDENT object + + IDENT shift 3 + . error + + +state 3 + ltm: LTM IDENT.IDENT object + + IDENT shift 4 + . error + + +state 4 + ltm: LTM IDENT IDENT.object + + L_BRACE shift 6 + . error + + object goto 5 + +state 5 + ltm: LTM IDENT IDENT object. (1) + + . reduce 1 (src line 58) + + +state 6 + object: L_BRACE.R_BRACE + object: L_BRACE.NEWLINE R_BRACE + object: L_BRACE.NEWLINE members R_BRACE + + NEWLINE shift 8 + R_BRACE shift 7 + . error + + +state 7 + object: L_BRACE R_BRACE. (2) + + . reduce 2 (src line 77) + + +state 8 + object: L_BRACE NEWLINE.R_BRACE + object: L_BRACE NEWLINE.members R_BRACE + + R_BRACE shift 9 + IDENT shift 12 + . error + + pair goto 11 + members goto 10 + +state 9 + object: L_BRACE NEWLINE R_BRACE. (3) + + . reduce 3 (src line 82) + + +state 10 + object: L_BRACE NEWLINE members.R_BRACE + members: members.pair + + R_BRACE shift 13 + IDENT shift 12 + . error + + pair goto 14 + +state 11 + members: pair. (5) + + . reduce 5 (src line 91) + + +state 12 + pair: IDENT.NEWLINE + pair: IDENT.value NEWLINE + pair: IDENT.object NEWLINE + + NEWLINE shift 15 + L_BRACE shift 6 + IDENT shift 18 + . error + + object goto 17 + value goto 16 + +state 13 + object: L_BRACE NEWLINE members R_BRACE. (4) + + . reduce 4 (src line 86) + + +state 14 + members: members pair. (6) + + . reduce 6 (src line 96) + + +state 15 + pair: IDENT NEWLINE. (7) + + . reduce 7 (src line 101) + + +state 16 + pair: IDENT value.NEWLINE + + NEWLINE shift 19 + . error + + +state 17 + pair: IDENT object.NEWLINE + + NEWLINE shift 20 + . error + + +state 18 + value: IDENT.value + value: IDENT. (11) + + IDENT shift 18 + . reduce 11 (src line 121) + + value goto 21 + +state 19 + pair: IDENT value NEWLINE. (8) + + . reduce 8 (src line 106) + + +state 20 + pair: IDENT object NEWLINE. (9) + + . reduce 9 (src line 110) + + +state 21 + value: IDENT value. (10) + + . reduce 10 (src line 115) + + +11 terminals, 6 nonterminals +12 grammar rules, 22/8000 states +0 shift/reduce, 0 reduce/reduce conflicts reported +55 working sets used +memory: parser 8/120000 +6 extra closures +16 shift entries, 1 exceptions +8 goto entries +0 entries saved by goto default +Optimizer space used: output 24/120000 +24 table entries, 0 zero +maximum spread: 11, maximum offset: 18