From 0789466810d61fc955d7645281073df84bd61bfb Mon Sep 17 00:00:00 2001 From: shiftky Date: Thu, 7 Dec 2017 00:51:45 +0900 Subject: [PATCH 01/18] Implement lexer --- ltm_lex.go | 121 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 121 insertions(+) create mode 100644 ltm_lex.go diff --git a/ltm_lex.go b/ltm_lex.go new file mode 100644 index 0000000..ac87d0c --- /dev/null +++ b/ltm_lex.go @@ -0,0 +1,121 @@ +package tmsh + +import ( + "bytes" + "strings" +) + +type Token int + +const ( + ILLEGAL Token = iota + EOF + WS + NEWLINE + + L_BRACE + R_BRACE + + IDENT + + LTM + TYPE +) + +type Scanner struct { + r *strings.Reader +} + +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 == ':' +} + +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 Token, 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': + 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 Token, 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 Token, 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() + case "node", "pool", "virtual": + return TYPE, buf.String() + } + + return IDENT, buf.String() +} From 1d56a6b524de124d84b255eab4f9f297d14c623b Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 9 Dec 2017 18:24:52 +0900 Subject: [PATCH 02/18] Add yacc file --- ltm_lex.go | 29 ++-------- ltm_parse_test.go | 18 +++++- parser.go.y | 139 ++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 162 insertions(+), 24 deletions(-) create mode 100644 parser.go.y diff --git a/ltm_lex.go b/ltm_lex.go index ac87d0c..a8074d9 100644 --- a/ltm_lex.go +++ b/ltm_lex.go @@ -5,23 +5,6 @@ import ( "strings" ) -type Token int - -const ( - ILLEGAL Token = iota - EOF - WS - NEWLINE - - L_BRACE - R_BRACE - - IDENT - - LTM - TYPE -) - type Scanner struct { r *strings.Reader } @@ -29,13 +12,15 @@ type Scanner struct { 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 == '/' || ch == '(' || ch == ')' } func isDigit(ch rune) bool { @@ -52,7 +37,7 @@ func (s *Scanner) read() rune { func (s *Scanner) unread() { _ = s.r.UnreadRune() } -func (s *Scanner) Scan() (tok Token, lit string) { +func (s *Scanner) Scan() (tok int, lit string) { ch := s.read() if isWhitespace(ch) { @@ -77,7 +62,7 @@ func (s *Scanner) Scan() (tok Token, lit string) { return ILLEGAL, string(ch) } -func (s *Scanner) scanWhitespace() (tok Token, lit string) { +func (s *Scanner) scanWhitespace() (tok int, lit string) { var buf bytes.Buffer buf.WriteRune(s.read()) @@ -95,7 +80,7 @@ func (s *Scanner) scanWhitespace() (tok Token, lit string) { return WS, buf.String() } -func (s *Scanner) scanIdent() (tok Token, lit string) { +func (s *Scanner) scanIdent() (tok int, lit string) { var buf bytes.Buffer buf.WriteRune(s.read()) @@ -113,8 +98,6 @@ func (s *Scanner) scanIdent() (tok Token, lit string) { switch buf.String() { case "ltm": return LTM, buf.String() - case "node", "pool", "virtual": - return TYPE, buf.String() } return IDENT, buf.String() diff --git a/ltm_parse_test.go b/ltm_parse_test.go index caae780..b406981 100644 --- a/ltm_parse_test.go +++ b/ltm_parse_test.go @@ -70,7 +70,11 @@ func TestParseShowLtmNode(t *testing.T) { tot-requests 0 }` - node := ParseShowLtmNode(str) + var node Node + err := Decode(str, &node) + if err != nil { + t.Errorf("got %v", err) + } expect := &Node{ Addr: "192.0.2.1", @@ -174,6 +178,12 @@ func TestParseShowLtmPool(t *testing.T) { tot-requests 0 }` + var node Node + err := Decode(str, &node) + if err != nil { + t.Errorf("got %v", err) + } + pool := ParseShowLtmPool(str) poolMembers := []PoolMember{ @@ -229,6 +239,12 @@ func TestParseListLtmVirtual(t *testing.T) { vs-index 1234 }` + var node Node + err := Decode(str, &node) + if err != nil { + t.Errorf("got %v", err) + } + vs := ParseListLtmVirtual(str) expect := &VirtualServer{ diff --git a/parser.go.y b/parser.go.y new file mode 100644 index 0000000..53c2bc7 --- /dev/null +++ b/parser.go.y @@ -0,0 +1,139 @@ +%{ +package tmsh + +import ( + "fmt" +) + +type Expression interface{} + +type Token struct { + token int + literal string +} + +type Ltm struct { + restype string + fqdn string + object Object +} + +type Object struct { + members []Member +} + +type Member struct { + key string + value string + members []Member +} + +%} + +%union{ + token Token + expr Expression +} + +%type ltm +%type object +%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 + { + $$ = $1 + } + +object + : L_BRACE NEWLINE R_BRACE + { + $$ = $1 + } + | L_BRACE R_BRACE + { + $$ = $1 + } + | L_BRACE NEWLINE members R_BRACE + { + $$ = $1 + } + +members + : IDENT value NEWLINE + { + $$ = $1 + } + | IDENT object NEWLINE + { + $$ = $1 + } + | members IDENT object NEWLINE + { + $$ = $1 + } + | members IDENT value NEWLINE + { + $$ = $1 + } + +value + : IDENT + { + $$ = $1 + } + | value IDENT + { + $$ = $1 + } + +%% + +type Lexer struct { + s *Scanner + result Expression +} + +func (l *Lexer) Lex(lval *yySymType) int { + var tok int + var lit string + + for { + tok, lit = l.s.Scan() + if tok != WS { + break + } + } + + fmt.Println(tok, lit) + + if tok == EOF { + return 0 + } + + return tok +} + +func (l *Lexer) Error(e string) { + panic(e) +} + +func Decode(data string, node *Node) error { + l := Lexer{s: NewScanner(data)} + if yyParse(&l) != 0 { + panic("Parse error") + } + return nil +} From 4288dc1939d276aa43a7c6861f40bf965ec7689a Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 9 Dec 2017 23:03:31 +0900 Subject: [PATCH 03/18] Modify syntax definition --- parser.go.y | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/parser.go.y b/parser.go.y index 53c2bc7..9e95be3 100644 --- a/parser.go.y +++ b/parser.go.y @@ -35,7 +35,7 @@ type Member struct { expr Expression } -%type ltm +%type ltm_obj %type object %type members %type value @@ -51,18 +51,18 @@ type Member struct { %% -ltm +ltm_obj : LTM IDENT IDENT object { $$ = $1 } object - : L_BRACE NEWLINE R_BRACE + : L_BRACE R_BRACE { $$ = $1 } - | L_BRACE R_BRACE + | L_BRACE NEWLINE R_BRACE { $$ = $1 } From 84f733623d78f4ae89799f9718ec3fdc528d1d8f Mon Sep 17 00:00:00 2001 From: shiftky Date: Sun, 10 Dec 2017 00:53:37 +0900 Subject: [PATCH 04/18] make LTMObject as Lexer.result --- parser.go | 574 ++++++++++++++++++++++++++++++++++++++++++++++++++++ parser.go.y | 62 +++--- y.output | 193 ++++++++++++++++++ 3 files changed, 801 insertions(+), 28 deletions(-) create mode 100644 parser.go create mode 100644 y.output diff --git a/parser.go b/parser.go new file mode 100644 index 0000000..da26779 --- /dev/null +++ b/parser.go @@ -0,0 +1,574 @@ +//line parser.go.y:2 +package tmsh + +import __yyfmt__ "fmt" + +//line parser.go.y:2 +import ( + "fmt" +) + +type Token struct { + token int + literal string +} + +type LTMObject struct { + resType string + fqdn string + object Object +} + +type Object struct { + members []Member +} + +type Member struct { + key string + value interface{} +} + +//line parser.go.y:30 +type yySymType struct { + yys int + token Token + ltm LTMObject + object Object + members []Member + value interface{} +} + +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:108 + +type Lexer struct { + s *Scanner + result LTMObject +} + +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(e) +} + +func Decode(data string, node *Node) error { + l := Lexer{s: NewScanner(data)} + if yyParse(&l) != 0 { + panic("Parse error") + } + return nil +} + +//line yacctab:1 +var yyExca = [...]int{ + -1, 1, + 1, -1, + -2, 0, +} + +const yyPrivate = 57344 + +const yyLast = 25 + +var yyAct = [...]int{ + + 14, 5, 6, 2, 16, 12, 13, 9, 11, 16, + 8, 4, 7, 15, 18, 17, 3, 21, 6, 23, + 22, 20, 19, 10, 1, +} +var yyPact = [...]int{ + + -8, -1000, 6, 1, 10, -1000, 3, -1000, -2, -1000, + -4, -6, -1000, -6, 15, 14, -1, 13, 12, -1000, + -1000, -1000, -1000, -1000, +} +var yyPgo = [...]int{ + + 0, 24, 1, 23, 0, +} +var yyR1 = [...]int{ + + 0, 1, 2, 2, 2, 3, 3, 3, 3, 4, + 4, +} +var yyR2 = [...]int{ + + 0, 4, 2, 3, 4, 3, 3, 4, 4, 2, + 1, +} +var yyChk = [...]int{ + + -1000, -1, 11, 10, 10, -2, 8, 9, 7, 9, + -3, 10, 9, 10, -4, -2, 10, -2, -4, 7, + 7, -4, 7, 7, +} +var yyDef = [...]int{ + + 0, -2, 0, 0, 0, 1, 0, 2, 0, 3, + 0, 0, 4, 0, 0, 0, 10, 0, 0, 5, + 6, 9, 7, 8, +} +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:56 + { + yylex.(*Lexer).result = LTMObject{ + resType: yyDollar[2].token.literal, + fqdn: yyDollar[3].token.literal, + object: yyDollar[4].object, + } + } + case 2: + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:66 + { + yyVAL.object = Object{} + } + case 3: + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:70 + { + yyVAL.object = Object{} + } + case 4: + yyDollar = yyS[yypt-4 : yypt+1] + //line parser.go.y:74 + { + yyVAL.object = Object{members: yyDollar[3].members} + } + case 5: + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:80 + { + yyVAL.members = []Member{Member{key: yyDollar[1].token.literal, value: yyDollar[2].value}} + } + case 6: + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:84 + { + yyVAL.members = []Member{Member{key: yyDollar[1].token.literal, value: yyDollar[2].object}} + } + case 7: + yyDollar = yyS[yypt-4 : yypt+1] + //line parser.go.y:88 + { + m := Member{key: yyDollar[2].token.literal, value: yyDollar[3].object} + yyVAL.members = append(yyDollar[1].members, m) + } + case 8: + yyDollar = yyS[yypt-4 : yypt+1] + //line parser.go.y:93 + { + m := Member{key: yyDollar[2].token.literal, value: yyDollar[3].value} + yyVAL.members = append(yyDollar[1].members, m) + } + case 9: + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:100 + { + yyVAL.value = fmt.Sprintf("%s %s", yyDollar[1].token.literal, yyDollar[2].value) + } + case 10: + yyDollar = yyS[yypt-1 : yypt+1] + //line parser.go.y:104 + { + yyVAL.value = yyDollar[1].token.literal + } + } + goto yystack /* stack new state and value */ +} diff --git a/parser.go.y b/parser.go.y index 9e95be3..7568b1c 100644 --- a/parser.go.y +++ b/parser.go.y @@ -5,15 +5,13 @@ import ( "fmt" ) -type Expression interface{} - type Token struct { token int literal string } -type Ltm struct { - restype string +type LTMObject struct { + resType string fqdn string object Object } @@ -23,22 +21,24 @@ type Object struct { } type Member struct { - key string - value string - members []Member + key string + value interface{} } %} %union{ - token Token - expr Expression + token Token + ltm LTMObject + object Object + members []Member + value interface{} } -%type ltm_obj -%type object -%type members -%type value +%type ltm +%type object +%type members +%type value %token ILLEGAL %token EOF @@ -51,59 +51,65 @@ type Member struct { %% -ltm_obj +ltm : LTM IDENT IDENT object { - $$ = $1 + yylex.(*Lexer).result = LTMObject{ + resType: $2.literal, + fqdn: $3.literal, + object: $4, + } } object : L_BRACE R_BRACE { - $$ = $1 + $$ = Object{} } | L_BRACE NEWLINE R_BRACE { - $$ = $1 + $$ = Object{} } | L_BRACE NEWLINE members R_BRACE { - $$ = $1 + $$ = Object{members: $3} } members : IDENT value NEWLINE { - $$ = $1 + $$ = []Member{Member{key: $1.literal, value: $2}} } | IDENT object NEWLINE { - $$ = $1 + $$ = []Member{Member{key: $1.literal, value: $2}} } | members IDENT object NEWLINE { - $$ = $1 + m := Member{key: $2.literal, value: $3} + $$ = append($1, m) } | members IDENT value NEWLINE { - $$ = $1 + m := Member{key: $2.literal, value: $3} + $$ = append($1, m) } value - : IDENT + : IDENT value { - $$ = $1 + $$ = fmt.Sprintf("%s %s", $1.literal, $2) } - | value IDENT + | IDENT { - $$ = $1 + $$ = $1.literal } %% type Lexer struct { s *Scanner - result Expression + result LTMObject } func (l *Lexer) Lex(lval *yySymType) int { @@ -117,7 +123,7 @@ func (l *Lexer) Lex(lval *yySymType) int { } } - fmt.Println(tok, lit) + lval.token = Token{token: tok, literal: lit} if tok == EOF { return 0 diff --git a/y.output b/y.output new file mode 100644 index 0000000..25cd0e3 --- /dev/null +++ b/y.output @@ -0,0 +1,193 @@ + +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 54) + + +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 64) + + +state 8 + object: L_BRACE NEWLINE.R_BRACE + object: L_BRACE NEWLINE.members R_BRACE + + R_BRACE shift 9 + IDENT shift 11 + . error + + members goto 10 + +state 9 + object: L_BRACE NEWLINE R_BRACE. (3) + + . reduce 3 (src line 69) + + +state 10 + object: L_BRACE NEWLINE members.R_BRACE + members: members.IDENT object NEWLINE + members: members.IDENT value NEWLINE + + R_BRACE shift 12 + IDENT shift 13 + . error + + +state 11 + members: IDENT.value NEWLINE + members: IDENT.object NEWLINE + + L_BRACE shift 6 + IDENT shift 16 + . error + + object goto 15 + value goto 14 + +state 12 + object: L_BRACE NEWLINE members R_BRACE. (4) + + . reduce 4 (src line 73) + + +state 13 + members: members IDENT.object NEWLINE + members: members IDENT.value NEWLINE + + L_BRACE shift 6 + IDENT shift 16 + . error + + object goto 17 + value goto 18 + +state 14 + members: IDENT value.NEWLINE + + NEWLINE shift 19 + . error + + +state 15 + members: IDENT object.NEWLINE + + NEWLINE shift 20 + . error + + +state 16 + value: IDENT.value + value: IDENT. (10) + + IDENT shift 16 + . reduce 10 (src line 103) + + value goto 21 + +state 17 + members: members IDENT object.NEWLINE + + NEWLINE shift 22 + . error + + +state 18 + members: members IDENT value.NEWLINE + + NEWLINE shift 23 + . error + + +state 19 + members: IDENT value NEWLINE. (5) + + . reduce 5 (src line 78) + + +state 20 + members: IDENT object NEWLINE. (6) + + . reduce 6 (src line 83) + + +state 21 + value: IDENT value. (9) + + . reduce 9 (src line 98) + + +state 22 + members: members IDENT object NEWLINE. (7) + + . reduce 7 (src line 87) + + +state 23 + members: members IDENT value NEWLINE. (8) + + . reduce 8 (src line 92) + + +11 terminals, 5 nonterminals +11 grammar rules, 24/8000 states +0 shift/reduce, 0 reduce/reduce conflicts reported +54 working sets used +memory: parser 7/120000 +6 extra closures +19 shift entries, 1 exceptions +8 goto entries +0 entries saved by goto default +Optimizer space used: output 25/120000 +25 table entries, 0 zero +maximum spread: 11, maximum offset: 16 From a9bd0468500ad96b5db594e47759f0a27cc0dbd1 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sun, 10 Dec 2017 01:13:16 +0900 Subject: [PATCH 05/18] Remove Decode() and Add Unmarshal() --- ltm.go | 8 ++++++++ ltm_parse_test.go | 25 +++++++++++-------------- parser.go | 8 -------- parser.go.y | 8 -------- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/ltm.go b/ltm.go index f738a2f..65f61af 100644 --- a/ltm.go +++ b/ltm.go @@ -43,6 +43,14 @@ type VirtualServer struct { Pool string } +func Unmarshal(data string, v interface{}) error { + l := Lexer{s: NewScanner(data)} + if yyParse(&l) != 0 { + return fmt.Errorf("Parse error") + } + return nil +} + func (bigip *BigIP) GetNode(name string) (*Node, error) { ret, _ := bigip.ExecuteCommand("show ltm node " + name + " field-fmt") if strings.Contains(ret, "was not found.") { diff --git a/ltm_parse_test.go b/ltm_parse_test.go index b406981..0172811 100644 --- a/ltm_parse_test.go +++ b/ltm_parse_test.go @@ -71,8 +71,7 @@ func TestParseShowLtmNode(t *testing.T) { }` var node Node - err := Decode(str, &node) - if err != nil { + if err := Unmarshal(str, &node); err != nil { t.Errorf("got %v", err) } @@ -178,13 +177,12 @@ func TestParseShowLtmPool(t *testing.T) { tot-requests 0 }` - var node Node - err := Decode(str, &node) - if err != nil { + var pool Pool + if err := Unmarshal(str, &pool); err != nil { t.Errorf("got %v", err) } - pool := ParseShowLtmPool(str) + poolp := ParseShowLtmPool(str) poolMembers := []PoolMember{ PoolMember{ @@ -219,8 +217,8 @@ func TestParseShowLtmPool(t *testing.T) { PoolMembers: poolMembers, } - if !reflect.DeepEqual(pool, expect) { - t.Errorf("\ngot %v\nwant %v", pool, expect) + if !reflect.DeepEqual(poolp, expect) { + t.Errorf("\ngot %v\nwant %v", poolp, expect) } } @@ -239,13 +237,12 @@ func TestParseListLtmVirtual(t *testing.T) { vs-index 1234 }` - var node Node - err := Decode(str, &node) - if err != nil { + var vs VirtualServer + if err := Unmarshal(str, &vs); err != nil { t.Errorf("got %v", err) } - vs := ParseListLtmVirtual(str) + vsp := ParseListLtmVirtual(str) expect := &VirtualServer{ Destination: "203.0.113.1:http", @@ -255,7 +252,7 @@ func TestParseListLtmVirtual(t *testing.T) { Pool: "api.example.com_80", } - if !reflect.DeepEqual(vs, expect) { - t.Errorf("\ngot %v\nwant %v", vs, expect) + if !reflect.DeepEqual(vsp, expect) { + t.Errorf("\ngot %v\nwant %v", vsp, expect) } } diff --git a/parser.go b/parser.go index da26779..cde8795 100644 --- a/parser.go +++ b/parser.go @@ -97,14 +97,6 @@ func (l *Lexer) Error(e string) { panic(e) } -func Decode(data string, node *Node) error { - l := Lexer{s: NewScanner(data)} - if yyParse(&l) != 0 { - panic("Parse error") - } - return nil -} - //line yacctab:1 var yyExca = [...]int{ -1, 1, diff --git a/parser.go.y b/parser.go.y index 7568b1c..f0abdfe 100644 --- a/parser.go.y +++ b/parser.go.y @@ -135,11 +135,3 @@ func (l *Lexer) Lex(lval *yySymType) int { func (l *Lexer) Error(e string) { panic(e) } - -func Decode(data string, node *Node) error { - l := Lexer{s: NewScanner(data)} - if yyParse(&l) != 0 { - panic("Parse error") - } - return nil -} From 309973c05ad0fd6d73672557532595b6a1904f65 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sun, 10 Dec 2017 02:18:12 +0900 Subject: [PATCH 06/18] Add struct tags --- ltm.go | 50 +++++++++++++++++++++++++------------------------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/ltm.go b/ltm.go index 65f61af..ad2b49f 100644 --- a/ltm.go +++ b/ltm.go @@ -7,40 +7,40 @@ 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:"monitor-rule"` + MonitorRule string `ltm:"name"` + AvailabilityState string `ltm:"status.availability-state available"` + EnabledState string `ltm:"status.enabled-state enabled"` + StatusReason string `ltm:"status.status-reason The pool is available"` + 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:"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 disabled"` + 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 + Destination string `ltm:"destination"` + IpProtocol string `ltm:"ip-protocol"` + Mask string `ltm:"mask"` + Partition string `ltm:"partition"` + Pool string `ltm:"pool"` } func Unmarshal(data string, v interface{}) error { From f8d8d97f027bd3ae360306aa15eadf04701e2f78 Mon Sep 17 00:00:00 2001 From: shiftky Date: Mon, 11 Dec 2017 03:05:07 +0900 Subject: [PATCH 07/18] Fix struct tag; Add Name field to VirtualServer --- ltm.go | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/ltm.go b/ltm.go index ad2b49f..f0a4be3 100644 --- a/ltm.go +++ b/ltm.go @@ -16,11 +16,11 @@ type Node struct { type Pool struct { ActiveMemberCount int `ltm:"active-member-cnt"` - Name string `ltm:"monitor-rule"` - MonitorRule string `ltm:"name"` - AvailabilityState string `ltm:"status.availability-state available"` - EnabledState string `ltm:"status.enabled-state enabled"` - StatusReason string `ltm:"status.status-reason The pool is available"` + 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"` } @@ -30,12 +30,13 @@ type PoolMember struct { Port int `ltm:"port"` MonitorRule string `ltm:"monitor-rule"` MonitorStatus string `ltm:"monitor-status"` - EnabledState string `ltm:"status.enabled-state disabled"` + EnabledState string `ltm:"status.enabled-state"` AvailabilityState string `ltm:"status.availability-state"` StatusReason string `ltm:"status.status-reason"` } type VirtualServer struct { + Name string `ltm:"name"` Destination string `ltm:"destination"` IpProtocol string `ltm:"ip-protocol"` Mask string `ltm:"mask"` From de2863064c6c24d8c999a6bb0472a4f2810dd974 Mon Sep 17 00:00:00 2001 From: shiftky Date: Fri, 15 Dec 2017 20:03:45 +0900 Subject: [PATCH 08/18] Modify parse object --- parser.go | 141 ++++++++++++++++++++++++++++------------------------ parser.go.y | 90 +++++++++++++++++++-------------- y.output | 114 +++++++++++++++++------------------------- 3 files changed, 173 insertions(+), 172 deletions(-) diff --git a/parser.go b/parser.go index cde8795..55f091b 100644 --- a/parser.go +++ b/parser.go @@ -8,34 +8,37 @@ import ( "fmt" ) -type Token struct { - token int - literal string -} +const ( + ltmNodeNode = iota + ltmPoolNode + ltmVirtualNode + keyNode + structNode + scalarNode +) -type LTMObject struct { - resType string - fqdn string - object Object +type node struct { + kind nodeType + value string + children []node } -type Object struct { - members []Member -} +type nodeType int -type Member struct { - key string - value interface{} +type Token struct { + token int + literal string } -//line parser.go.y:30 +//line parser.go.y:32 type yySymType struct { yys int token Token - ltm LTMObject - object Object - members []Member - value interface{} + ltm node + object node + pair node + members []node + value node } const ILLEGAL = 57346 @@ -66,11 +69,11 @@ const yyEofCode = 1 const yyErrCode = 2 const yyInitialStackSize = 16 -//line parser.go.y:108 +//line parser.go.y:122 type Lexer struct { s *Scanner - result LTMObject + result node } func (l *Lexer) Lex(lval *yySymType) int { @@ -106,45 +109,45 @@ var yyExca = [...]int{ const yyPrivate = 57344 -const yyLast = 25 +const yyLast = 23 var yyAct = [...]int{ - 14, 5, 6, 2, 16, 12, 13, 9, 11, 16, - 8, 4, 7, 15, 18, 17, 3, 21, 6, 23, - 22, 20, 19, 10, 1, + 15, 5, 6, 2, 17, 13, 12, 9, 12, 11, + 17, 8, 4, 7, 16, 3, 6, 19, 20, 18, + 14, 10, 1, } var yyPact = [...]int{ - -8, -1000, 6, 1, 10, -1000, 3, -1000, -2, -1000, - -4, -6, -1000, -6, 15, 14, -1, 13, 12, -1000, - -1000, -1000, -1000, -1000, + -8, -1000, 5, 2, 8, -1000, 4, -1000, -2, -1000, + -4, -1000, -6, -1000, -1000, 12, 10, 0, -1000, -1000, + -1000, } var yyPgo = [...]int{ - 0, 24, 1, 23, 0, + 0, 22, 1, 9, 21, 0, } var yyR1 = [...]int{ - 0, 1, 2, 2, 2, 3, 3, 3, 3, 4, - 4, + 0, 1, 2, 2, 2, 4, 4, 3, 3, 5, + 5, } var yyR2 = [...]int{ - 0, 4, 2, 3, 4, 3, 3, 4, 4, 2, + 0, 4, 2, 3, 4, 1, 2, 3, 3, 2, 1, } var yyChk = [...]int{ -1000, -1, 11, 10, 10, -2, 8, 9, 7, 9, - -3, 10, 9, 10, -4, -2, 10, -2, -4, 7, - 7, -4, 7, 7, + -4, -3, 10, 9, -3, -5, -2, 10, 7, 7, + -5, } var yyDef = [...]int{ 0, -2, 0, 0, 0, 1, 0, 2, 0, 3, - 0, 0, 4, 0, 0, 0, 10, 0, 0, 5, - 6, 9, 7, 8, + 0, 5, 0, 4, 6, 0, 0, 10, 7, 8, + 9, } var yyTok1 = [...]int{ @@ -497,69 +500,77 @@ yydefault: case 1: yyDollar = yyS[yypt-4 : yypt+1] - //line parser.go.y:56 + //line parser.go.y:60 { - yylex.(*Lexer).result = LTMObject{ - resType: yyDollar[2].token.literal, - fqdn: yyDollar[3].token.literal, - object: yyDollar[4].object, + 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:66 + //line parser.go.y:79 { - yyVAL.object = Object{} + yyVAL.object = node{kind: structNode, value: "", children: []node{}} } case 3: yyDollar = yyS[yypt-3 : yypt+1] - //line parser.go.y:70 + //line parser.go.y:83 { - yyVAL.object = Object{} + yyVAL.object = node{kind: structNode, value: "", children: []node{}} } case 4: yyDollar = yyS[yypt-4 : yypt+1] - //line parser.go.y:74 + //line parser.go.y:87 { - yyVAL.object = Object{members: yyDollar[3].members} + yyVAL.object = node{kind: structNode, value: "", children: yyDollar[3].members} } case 5: - yyDollar = yyS[yypt-3 : yypt+1] - //line parser.go.y:80 + yyDollar = yyS[yypt-1 : yypt+1] + //line parser.go.y:93 { - yyVAL.members = []Member{Member{key: yyDollar[1].token.literal, value: yyDollar[2].value}} + yyVAL.members = []node{yyDollar[1].pair} } case 6: - yyDollar = yyS[yypt-3 : yypt+1] - //line parser.go.y:84 + yyDollar = yyS[yypt-2 : yypt+1] + //line parser.go.y:97 { - yyVAL.members = []Member{Member{key: yyDollar[1].token.literal, value: yyDollar[2].object}} + yyVAL.members = append(yyDollar[1].members, yyDollar[2].pair) } case 7: - yyDollar = yyS[yypt-4 : yypt+1] - //line parser.go.y:88 + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:103 { - m := Member{key: yyDollar[2].token.literal, value: yyDollar[3].object} - yyVAL.members = append(yyDollar[1].members, m) + yyVAL.pair = node{kind: keyNode, value: yyDollar[1].token.literal, children: []node{yyDollar[2].value}} } case 8: - yyDollar = yyS[yypt-4 : yypt+1] - //line parser.go.y:93 + yyDollar = yyS[yypt-3 : yypt+1] + //line parser.go.y:107 { - m := Member{key: yyDollar[2].token.literal, value: yyDollar[3].value} - yyVAL.members = append(yyDollar[1].members, m) + yyVAL.pair = node{kind: keyNode, value: yyDollar[1].token.literal, children: []node{yyDollar[2].object}} } case 9: yyDollar = yyS[yypt-2 : yypt+1] - //line parser.go.y:100 + //line parser.go.y:113 { - yyVAL.value = fmt.Sprintf("%s %s", yyDollar[1].token.literal, yyDollar[2].value) + s := fmt.Sprintf("%s %s", yyDollar[1].token.literal, yyDollar[2].value.value) + yyVAL.value = node{kind: scalarNode, value: s, children: []node{}} } case 10: yyDollar = yyS[yypt-1 : yypt+1] - //line parser.go.y:104 + //line parser.go.y:118 { - yyVAL.value = yyDollar[1].token.literal + 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 index f0abdfe..f910cf0 100644 --- a/parser.go.y +++ b/parser.go.y @@ -5,38 +5,42 @@ import ( "fmt" ) -type Token struct { - token int - literal string -} +const ( + ltmNodeNode = iota + ltmPoolNode + ltmVirtualNode + keyNode + structNode + scalarNode +) -type LTMObject struct { - resType string - fqdn string - object Object -} +type nodeType int -type Object struct { - members []Member +type node struct { + kind nodeType + value string + children []node } -type Member struct { - key string - value interface{} +type Token struct { + token int + literal string } %} %union{ token Token - ltm LTMObject - object Object - members []Member - value interface{} + ltm node + object node + pair node + members []node + value node } %type ltm %type object +%type pair %type members %type value @@ -54,62 +58,72 @@ type Member struct { ltm : LTM IDENT IDENT object { - yylex.(*Lexer).result = LTMObject{ - resType: $2.literal, - fqdn: $3.literal, - object: $4, + 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 { - $$ = Object{} + $$ = node{kind: structNode, value: "", children: []node{}} } | L_BRACE NEWLINE R_BRACE { - $$ = Object{} + $$ = node{kind: structNode, value: "", children: []node{}} } | L_BRACE NEWLINE members R_BRACE { - $$ = Object{members: $3} + $$ = node{kind: structNode, value: "", children: $3} } members - : IDENT value NEWLINE + : pair { - $$ = []Member{Member{key: $1.literal, value: $2}} + $$ = []node{$1} } - | IDENT object NEWLINE + | members pair { - $$ = []Member{Member{key: $1.literal, value: $2}} + $$ = append($1, $2) } - | members IDENT object NEWLINE + +pair + : IDENT value NEWLINE { - m := Member{key: $2.literal, value: $3} - $$ = append($1, m) + $$ = node{kind: keyNode, value: $1.literal, children: []node{$2}} } - | members IDENT value NEWLINE + | IDENT object NEWLINE { - m := Member{key: $2.literal, value: $3} - $$ = append($1, m) + $$ = node{kind: keyNode, value: $1.literal, children: []node{$2}} } value : IDENT value { - $$ = fmt.Sprintf("%s %s", $1.literal, $2) + s := fmt.Sprintf("%s %s", $1.literal, $2.value) + $$ = node{kind: scalarNode, value: s, children: []node{}} } | IDENT { - $$ = $1.literal + $$ = node{kind: scalarNode, value: $1.literal, children: []node{}} } %% type Lexer struct { s *Scanner - result LTMObject + result node } func (l *Lexer) Lex(lval *yySymType) int { diff --git a/y.output b/y.output index 25cd0e3..db8e8ac 100644 --- a/y.output +++ b/y.output @@ -39,7 +39,7 @@ state 4 state 5 ltm: LTM IDENT IDENT object. (1) - . reduce 1 (src line 54) + . reduce 1 (src line 58) state 6 @@ -55,7 +55,7 @@ state 6 state 7 object: L_BRACE R_BRACE. (2) - . reduce 2 (src line 64) + . reduce 2 (src line 77) state 8 @@ -63,131 +63,107 @@ state 8 object: L_BRACE NEWLINE.members R_BRACE R_BRACE shift 9 - IDENT shift 11 + IDENT shift 12 . error + pair goto 11 members goto 10 state 9 object: L_BRACE NEWLINE R_BRACE. (3) - . reduce 3 (src line 69) + . reduce 3 (src line 82) state 10 object: L_BRACE NEWLINE members.R_BRACE - members: members.IDENT object NEWLINE - members: members.IDENT value NEWLINE + members: members.pair - R_BRACE shift 12 - IDENT shift 13 + R_BRACE shift 13 + IDENT shift 12 . error + pair goto 14 state 11 - members: IDENT.value NEWLINE - members: IDENT.object NEWLINE + members: pair. (5) - L_BRACE shift 6 - IDENT shift 16 - . error + . reduce 5 (src line 91) - object goto 15 - value goto 14 state 12 - object: L_BRACE NEWLINE members R_BRACE. (4) + pair: IDENT.value NEWLINE + pair: IDENT.object NEWLINE - . reduce 4 (src line 73) + L_BRACE shift 6 + IDENT shift 17 + . error + object goto 16 + value goto 15 state 13 - members: members IDENT.object NEWLINE - members: members IDENT.value NEWLINE + object: L_BRACE NEWLINE members R_BRACE. (4) - L_BRACE shift 6 - IDENT shift 16 - . error + . reduce 4 (src line 86) - object goto 17 - value goto 18 state 14 - members: IDENT value.NEWLINE + members: members pair. (6) - NEWLINE shift 19 - . error + . reduce 6 (src line 96) state 15 - members: IDENT object.NEWLINE + pair: IDENT value.NEWLINE - NEWLINE shift 20 + NEWLINE shift 18 . error state 16 - value: IDENT.value - value: IDENT. (10) + pair: IDENT object.NEWLINE - IDENT shift 16 - . reduce 10 (src line 103) + NEWLINE shift 19 + . error - value goto 21 state 17 - members: members IDENT object.NEWLINE + value: IDENT.value + value: IDENT. (10) - NEWLINE shift 22 - . error + IDENT shift 17 + . reduce 10 (src line 117) + value goto 20 state 18 - members: members IDENT value.NEWLINE + pair: IDENT value NEWLINE. (7) - NEWLINE shift 23 - . error + . reduce 7 (src line 101) state 19 - members: IDENT value NEWLINE. (5) + pair: IDENT object NEWLINE. (8) - . reduce 5 (src line 78) + . reduce 8 (src line 106) state 20 - members: IDENT object NEWLINE. (6) - - . reduce 6 (src line 83) - - -state 21 value: IDENT value. (9) - . reduce 9 (src line 98) - - -state 22 - members: members IDENT object NEWLINE. (7) - - . reduce 7 (src line 87) - - -state 23 - members: members IDENT value NEWLINE. (8) - - . reduce 8 (src line 92) + . reduce 9 (src line 111) -11 terminals, 5 nonterminals -11 grammar rules, 24/8000 states +11 terminals, 6 nonterminals +11 grammar rules, 21/8000 states 0 shift/reduce, 0 reduce/reduce conflicts reported -54 working sets used -memory: parser 7/120000 +55 working sets used +memory: parser 8/120000 6 extra closures -19 shift entries, 1 exceptions +15 shift entries, 1 exceptions 8 goto entries 0 entries saved by goto default -Optimizer space used: output 25/120000 -25 table entries, 0 zero -maximum spread: 11, maximum offset: 16 +Optimizer space used: output 23/120000 +23 table entries, 0 zero +maximum spread: 11, maximum offset: 17 From c6637885b677c62a0d73f6d74e3fcb01a818a96a Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 01:00:12 +0900 Subject: [PATCH 09/18] Implement unmarshaler --- ltm.go | 36 ++++++++------ ltm_decode.go | 92 ++++++++++++++++++++++++++++++++++++ ltm_parse_test.go | 118 ++++++++++++++++++++++------------------------ parser.go | 38 +++++++-------- parser.go.y | 34 ++++++------- 5 files changed, 205 insertions(+), 113 deletions(-) create mode 100644 ltm_decode.go diff --git a/ltm.go b/ltm.go index f0a4be3..617b16c 100644 --- a/ltm.go +++ b/ltm.go @@ -25,7 +25,7 @@ type Pool struct { } type PoolMember struct { - Name string `ltm:"name"` + Name string `ltm:"node-name"` Addr string `ltm:"addr"` Port int `ltm:"port"` MonitorRule string `ltm:"monitor-rule"` @@ -44,21 +44,18 @@ type VirtualServer struct { Pool string `ltm:"pool"` } -func Unmarshal(data string, v interface{}) error { - l := Lexer{s: NewScanner(data)} - if yyParse(&l) != 0 { - return fmt.Errorf("Parse error") - } - return nil -} - func (bigip *BigIP) GetNode(name string) (*Node, error) { ret, _ := bigip.ExecuteCommand("show ltm node " + name + " field-fmt") 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 { @@ -98,8 +95,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 { @@ -171,9 +173,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_decode.go b/ltm_decode.go new file mode 100644 index 0000000..de9d4b0 --- /dev/null +++ b/ltm_decode.go @@ -0,0 +1,92 @@ +package tmsh + +import ( + "fmt" + "reflect" + "strconv" +) + +func Unmarshal(data string, out interface{}) error { + 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) { + switch out.Kind() { + case reflect.Struct: + for _, c := range n.children { + unmarshal(c, out) + } + case reflect.Slice: + l := len(n.children) + 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) + } + } +} + +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) + } + } + } +} + +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/ltm_parse_test.go index 0172811..dabb60f 100644 --- a/ltm_parse_test.go +++ b/ltm_parse_test.go @@ -51,23 +51,23 @@ line4` func TestParseShowLtmNode(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 }` var node Node @@ -75,7 +75,7 @@ func TestParseShowLtmNode(t *testing.T) { t.Errorf("got %v", err) } - expect := &Node{ + expect := Node{ Addr: "192.0.2.1", Name: "dev-web01.example.com", MonitorRule: "none", @@ -182,59 +182,55 @@ func TestParseShowLtmPool(t *testing.T) { t.Errorf("got %v", err) } - poolp := 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", - }, - } - - 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(poolp, expect) { - t.Errorf("\ngot %v\nwant %v", poolp, expect) + if !reflect.DeepEqual(pool, expect) { + t.Errorf("\ngot %v\nwant %v", pool, expect) } } 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 + 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 }` var vs VirtualServer @@ -242,9 +238,7 @@ func TestParseListLtmVirtual(t *testing.T) { t.Errorf("got %v", err) } - vsp := ParseListLtmVirtual(str) - - expect := &VirtualServer{ + expect := VirtualServer{ Destination: "203.0.113.1:http", IpProtocol: "tcp", Mask: "255.255.255.255", @@ -252,7 +246,7 @@ func TestParseListLtmVirtual(t *testing.T) { Pool: "api.example.com_80", } - if !reflect.DeepEqual(vsp, expect) { - t.Errorf("\ngot %v\nwant %v", vsp, expect) + if !reflect.DeepEqual(vs, expect) { + t.Errorf("\ngot %v\nwant %v", vs, expect) } } diff --git a/parser.go b/parser.go index 55f091b..d247215 100644 --- a/parser.go +++ b/parser.go @@ -17,14 +17,14 @@ const ( scalarNode ) +type nodeType int + type node struct { kind nodeType value string - children []node + children []*node } -type nodeType int - type Token struct { token int literal string @@ -34,11 +34,11 @@ type Token struct { type yySymType struct { yys int token Token - ltm node - object node - pair node - members []node - value node + ltm *node + object *node + pair *node + members []*node + value *node } const ILLEGAL = 57346 @@ -73,7 +73,7 @@ const yyInitialStackSize = 16 type Lexer struct { s *Scanner - result node + result *node } func (l *Lexer) Lex(lval *yySymType) int { @@ -511,35 +511,35 @@ yydefault: case "virtual": kind = ltmVirtualNode } - yylex.(*Lexer).result = node{ + yylex.(*Lexer).result = &node{ kind: kind, value: yyDollar[3].token.literal, - children: []node{yyDollar[4].object}, + 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{}} + 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{}} + 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} + 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} + yyVAL.members = []*node{yyDollar[1].pair} } case 6: yyDollar = yyS[yypt-2 : yypt+1] @@ -551,26 +551,26 @@ yydefault: yyDollar = yyS[yypt-3 : yypt+1] //line parser.go.y:103 { - yyVAL.pair = node{kind: keyNode, value: yyDollar[1].token.literal, children: []node{yyDollar[2].value}} + yyVAL.pair = &node{kind: keyNode, value: yyDollar[1].token.literal, children: []*node{yyDollar[2].value}} } 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].object}} + yyVAL.pair = &node{kind: keyNode, value: yyDollar[1].token.literal, children: []*node{yyDollar[2].object}} } case 9: yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:113 { s := fmt.Sprintf("%s %s", yyDollar[1].token.literal, yyDollar[2].value.value) - yyVAL.value = node{kind: scalarNode, value: s, children: []node{}} + yyVAL.value = &node{kind: scalarNode, value: s, children: []*node{}} } case 10: yyDollar = yyS[yypt-1 : yypt+1] //line parser.go.y:118 { - yyVAL.value = node{kind: scalarNode, value: yyDollar[1].token.literal, children: []node{}} + 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 index f910cf0..f3b623c 100644 --- a/parser.go.y +++ b/parser.go.y @@ -19,7 +19,7 @@ type nodeType int type node struct { kind nodeType value string - children []node + children []*node } type Token struct { @@ -31,11 +31,11 @@ type Token struct { %union{ token Token - ltm node - object node - pair node - members []node - value node + ltm *node + object *node + pair *node + members []*node + value *node } %type ltm @@ -67,31 +67,31 @@ ltm case "virtual": kind = ltmVirtualNode } - yylex.(*Lexer).result = node{ + yylex.(*Lexer).result = &node{ kind: kind, value: $3.literal, - children: []node{$4}, + children: []*node{$4}, } } object : L_BRACE R_BRACE { - $$ = node{kind: structNode, value: "", children: []node{}} + $$ = &node{kind: structNode, value: "", children: []*node{}} } | L_BRACE NEWLINE R_BRACE { - $$ = node{kind: structNode, value: "", children: []node{}} + $$ = &node{kind: structNode, value: "", children: []*node{}} } | L_BRACE NEWLINE members R_BRACE { - $$ = node{kind: structNode, value: "", children: $3} + $$ = &node{kind: structNode, value: "", children: $3} } members : pair { - $$ = []node{$1} + $$ = []*node{$1} } | members pair { @@ -101,29 +101,29 @@ members pair : IDENT value NEWLINE { - $$ = node{kind: keyNode, value: $1.literal, children: []node{$2}} + $$ = &node{kind: keyNode, value: $1.literal, children: []*node{$2}} } | IDENT object NEWLINE { - $$ = node{kind: keyNode, value: $1.literal, children: []node{$2}} + $$ = &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{}} + $$ = &node{kind: scalarNode, value: s, children: []*node{}} } | IDENT { - $$ = node{kind: scalarNode, value: $1.literal, children: []node{}} + $$ = &node{kind: scalarNode, value: $1.literal, children: []*node{}} } %% type Lexer struct { s *Scanner - result node + result *node } func (l *Lexer) Lex(lval *yySymType) int { From 478027961cce0a1fd90dd8db552866aea984bbe2 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 15:05:19 +0900 Subject: [PATCH 10/18] Support decode to map --- ltm.go | 17 +++++++++++------ ltm_decode.go | 13 +++++++++++-- ltm_parse_test.go | 23 ++++++++++++++++------- parser.go.y | 2 +- 4 files changed, 39 insertions(+), 16 deletions(-) diff --git a/ltm.go b/ltm.go index 617b16c..3836415 100644 --- a/ltm.go +++ b/ltm.go @@ -36,12 +36,17 @@ type PoolMember struct { } type VirtualServer struct { - 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"` + 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) { diff --git a/ltm_decode.go b/ltm_decode.go index de9d4b0..43a3532 100644 --- a/ltm_decode.go +++ b/ltm_decode.go @@ -38,21 +38,30 @@ func unmarshal(n *node, out reflect.Value) { } 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: - l := len(n.children) 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) + } } } diff --git a/ltm_parse_test.go b/ltm_parse_test.go index dabb60f..8c1f034 100644 --- a/ltm_parse_test.go +++ b/ltm_parse_test.go @@ -219,15 +219,20 @@ 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 + //# 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_80 + pool api.example.com_443 profiles { - /Common/tcp { } + /Common/tcp { + context all + } + wildcard.example.com { + context clientside + } } source 0.0.0.0/0 vs-index 1234 @@ -239,11 +244,15 @@ func TestParseListLtmVirtual(t *testing.T) { } expect := VirtualServer{ - Destination: "203.0.113.1:http", + 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/parser.go.y b/parser.go.y index f3b623c..74198de 100644 --- a/parser.go.y +++ b/parser.go.y @@ -17,7 +17,7 @@ const ( type nodeType int type node struct { - kind nodeType + kind nodeType value string children []*node } From 4ae411ab3159ff5cbd94e945dfee214570c208a5 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 16:45:02 +0900 Subject: [PATCH 11/18] Show error line num --- ltm_lex.go | 4 +++- parser.go | 2 +- parser.go.y | 2 +- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/ltm_lex.go b/ltm_lex.go index a8074d9..cb21d40 100644 --- a/ltm_lex.go +++ b/ltm_lex.go @@ -6,7 +6,8 @@ import ( ) type Scanner struct { - r *strings.Reader + r *strings.Reader + line int } func NewScanner(data string) *Scanner { @@ -52,6 +53,7 @@ func (s *Scanner) Scan() (tok int, lit string) { case rune(0): return EOF, "" case '\n': + s.line++ return NEWLINE, string(ch) case '{': return L_BRACE, string(ch) diff --git a/parser.go b/parser.go index d247215..690159f 100644 --- a/parser.go +++ b/parser.go @@ -97,7 +97,7 @@ func (l *Lexer) Lex(lval *yySymType) int { } func (l *Lexer) Error(e string) { - panic(e) + panic(fmt.Sprintf("line %d : %s", l.s.line, e)) } //line yacctab:1 diff --git a/parser.go.y b/parser.go.y index 74198de..b27ce81 100644 --- a/parser.go.y +++ b/parser.go.y @@ -147,5 +147,5 @@ func (l *Lexer) Lex(lval *yySymType) int { } func (l *Lexer) Error(e string) { - panic(e) + panic(fmt.Sprintf("line %d : %s", l.s.line, e)) } From 88b266e75b70b3baf099943d8b3488d3548b7ebb Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 17:19:54 +0900 Subject: [PATCH 12/18] Trim input string --- ltm_decode.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ltm_decode.go b/ltm_decode.go index 43a3532..32d49ed 100644 --- a/ltm_decode.go +++ b/ltm_decode.go @@ -4,9 +4,12 @@ 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") From c7e671654144663f94ba4e27ce4992a5a2b868a7 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 17:40:45 +0900 Subject: [PATCH 13/18] Fix isLetter() --- ltm_lex.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/ltm_lex.go b/ltm_lex.go index cb21d40..d27847a 100644 --- a/ltm_lex.go +++ b/ltm_lex.go @@ -20,8 +20,8 @@ func isWhitespace(ch rune) bool { func isLetter(ch rune) bool { return (ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || - ch == '.' || ch == '_' || ch == '-' || ch == ':' || - ch == '/' || ch == '(' || ch == ')' + ch == '.' || ch == ',' || ch == '_' || ch == '-' || ch == ':' || + ch == '/' || ch == '(' || ch == ')' || ch == '\'' } func isDigit(ch rune) bool { From 8332f4fd69c54ad774b9f1307833813d9c003934 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 18:22:47 +0900 Subject: [PATCH 14/18] Support decode to map[string]string --- ltm_decode.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/ltm_decode.go b/ltm_decode.go index 32d49ed..6674129 100644 --- a/ltm_decode.go +++ b/ltm_decode.go @@ -78,10 +78,13 @@ func decodeKeyNode(n *node, out reflect.Value) { 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) From 366bce761ce755566885eb2b6ea8a8a3431ad9f4 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 21:09:40 +0900 Subject: [PATCH 15/18] Remove unused codes --- ltm_parse.go | 177 ---------------------------------------------- ltm_parse_test.go | 50 +------------ 2 files changed, 3 insertions(+), 224 deletions(-) delete mode 100644 ltm_parse.go 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/ltm_parse_test.go b/ltm_parse_test.go index 8c1f034..dcad3cd 100644 --- a/ltm_parse_test.go +++ b/ltm_parse_test.go @@ -5,51 +5,7 @@ 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 @@ -88,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 @@ -218,7 +174,7 @@ func TestParseShowLtmPool(t *testing.T) { } } -func TestParseListLtmVirtual(t *testing.T) { +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 From 4e6453d85bdd2fd062bf9466ae37b3d532398c1f Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 21:23:41 +0900 Subject: [PATCH 16/18] Rename files --- ltm_decode.go => decode.go | 1 - ltm_parse_test.go => decode_test.go | 0 ltm_lex.go => lex.go | 0 3 files changed, 1 deletion(-) rename ltm_decode.go => decode.go (99%) rename ltm_parse_test.go => decode_test.go (100%) rename ltm_lex.go => lex.go (100%) diff --git a/ltm_decode.go b/decode.go similarity index 99% rename from ltm_decode.go rename to decode.go index 6674129..0ccc8e1 100644 --- a/ltm_decode.go +++ b/decode.go @@ -84,7 +84,6 @@ func decodeKeyNode(n *node, out reflect.Value) { } func decodeScalarNode(n *node, out reflect.Value) { - switch out.Kind() { case reflect.Int: i, _ := strconv.ParseInt(n.value, 10, 64) diff --git a/ltm_parse_test.go b/decode_test.go similarity index 100% rename from ltm_parse_test.go rename to decode_test.go diff --git a/ltm_lex.go b/lex.go similarity index 100% rename from ltm_lex.go rename to lex.go From 2d9f6fe0b557c5bfcea6699f8393a5ba5343d46a Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 21:36:12 +0900 Subject: [PATCH 17/18] Fix syntax error --- parser.go | 52 +++++++++++++++++++++++++++++----------------------- parser.go.y | 10 +++++++--- y.output | 52 ++++++++++++++++++++++++++++++---------------------- 3 files changed, 66 insertions(+), 48 deletions(-) diff --git a/parser.go b/parser.go index 690159f..15dc458 100644 --- a/parser.go +++ b/parser.go @@ -69,7 +69,7 @@ const yyEofCode = 1 const yyErrCode = 2 const yyInitialStackSize = 16 -//line parser.go.y:122 +//line parser.go.y:126 type Lexer struct { s *Scanner @@ -109,45 +109,45 @@ var yyExca = [...]int{ const yyPrivate = 57344 -const yyLast = 23 +const yyLast = 24 var yyAct = [...]int{ - 15, 5, 6, 2, 17, 13, 12, 9, 12, 11, - 17, 8, 4, 7, 16, 3, 6, 19, 20, 18, - 14, 10, 1, + 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{ - -8, -1000, 5, 2, 8, -1000, 4, -1000, -2, -1000, - -4, -1000, -6, -1000, -1000, 12, 10, 0, -1000, -1000, - -1000, + -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, 22, 1, 9, 21, 0, + 0, 23, 1, 10, 22, 0, } var yyR1 = [...]int{ - 0, 1, 2, 2, 2, 4, 4, 3, 3, 5, - 5, + 0, 1, 2, 2, 2, 4, 4, 3, 3, 3, + 5, 5, } var yyR2 = [...]int{ - 0, 4, 2, 3, 4, 1, 2, 3, 3, 2, - 1, + 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, -5, -2, 10, 7, 7, - -5, + -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, 0, 0, 10, 7, 8, - 9, + 0, 5, 0, 4, 6, 7, 0, 0, 11, 8, + 9, 10, } var yyTok1 = [...]int{ @@ -548,27 +548,33 @@ yydefault: yyVAL.members = append(yyDollar[1].members, yyDollar[2].pair) } case 7: - yyDollar = yyS[yypt-3 : yypt+1] + yyDollar = yyS[yypt-2 : yypt+1] //line parser.go.y:103 { - yyVAL.pair = &node{kind: keyNode, value: yyDollar[1].token.literal, children: []*node{yyDollar[2].value}} + 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].object}} + 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:113 + //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 10: + case 11: yyDollar = yyS[yypt-1 : yypt+1] - //line parser.go.y:118 + //line parser.go.y:122 { yyVAL.value = &node{kind: scalarNode, value: yyDollar[1].token.literal, children: []*node{}} } diff --git a/parser.go.y b/parser.go.y index b27ce81..6ea35c0 100644 --- a/parser.go.y +++ b/parser.go.y @@ -89,17 +89,21 @@ object } members - : pair + : pair { $$ = []*node{$1} } - | members pair + | members pair { $$ = append($1, $2) } pair - : IDENT value NEWLINE + : IDENT NEWLINE + { + $$ = &node{kind: keyNode, value: $1.literal, children: []*node{}} + } + | IDENT value NEWLINE { $$ = &node{kind: keyNode, value: $1.literal, children: []*node{$2}} } diff --git a/y.output b/y.output index db8e8ac..efba73b 100644 --- a/y.output +++ b/y.output @@ -92,15 +92,17 @@ state 11 state 12 + pair: IDENT.NEWLINE pair: IDENT.value NEWLINE pair: IDENT.object NEWLINE + NEWLINE shift 15 L_BRACE shift 6 - IDENT shift 17 + IDENT shift 18 . error - object goto 16 - value goto 15 + object goto 17 + value goto 16 state 13 object: L_BRACE NEWLINE members R_BRACE. (4) @@ -115,55 +117,61 @@ state 14 state 15 - pair: IDENT value.NEWLINE + pair: IDENT NEWLINE. (7) - NEWLINE shift 18 - . error + . reduce 7 (src line 101) state 16 - pair: IDENT object.NEWLINE + pair: IDENT value.NEWLINE NEWLINE shift 19 . error state 17 - value: IDENT.value - value: IDENT. (10) + pair: IDENT object.NEWLINE - IDENT shift 17 - . reduce 10 (src line 117) + NEWLINE shift 20 + . error - value goto 20 state 18 - pair: IDENT value NEWLINE. (7) + value: IDENT.value + value: IDENT. (11) - . reduce 7 (src line 101) + IDENT shift 18 + . reduce 11 (src line 121) + value goto 21 state 19 - pair: IDENT object NEWLINE. (8) + pair: IDENT value NEWLINE. (8) . reduce 8 (src line 106) state 20 - value: IDENT value. (9) + pair: IDENT object NEWLINE. (9) + + . reduce 9 (src line 110) + + +state 21 + value: IDENT value. (10) - . reduce 9 (src line 111) + . reduce 10 (src line 115) 11 terminals, 6 nonterminals -11 grammar rules, 21/8000 states +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 -15 shift entries, 1 exceptions +16 shift entries, 1 exceptions 8 goto entries 0 entries saved by goto default -Optimizer space used: output 23/120000 -23 table entries, 0 zero -maximum spread: 11, maximum offset: 17 +Optimizer space used: output 24/120000 +24 table entries, 0 zero +maximum spread: 11, maximum offset: 18 From c6e46c69cf7887f2134f0c235b5a97d3e7f70cd3 Mon Sep 17 00:00:00 2001 From: shiftky Date: Sat, 16 Dec 2017 21:37:40 +0900 Subject: [PATCH 18/18] Add go 1.9 --- .travis.yml | 1 + 1 file changed, 1 insertion(+) 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