//Copyright 2013 GoGraphviz Authors // //Licensed under the Apache License, Version 2.0 (the "License"); //you may not use this file except in compliance with the License. //You may obtain a copy of the License at // // http://www.apache.org/licenses/LICENSE-2.0 // //Unless required by applicable law or agreed to in writing, software //distributed under the License is distributed on an "AS IS" BASIS, //WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. //See the License for the specific language governing permissions and //limitations under the License. package parser import ( "errors" "strconv" ) import "github.com/awalterschulze/gographviz/token" type ( ActionTab []ActionRow ActionRow map[token.Type]Action ) func (R ActionRow) String() string { s := "" for t, a := range R { s += strconv.Itoa(int(t)) + " : " + a.String() + "\n" } return s } type ( Accept int Shift State Reduce int Action interface { Act() String() string } ) func (this Accept) Act() {} func (this Shift) Act() {} func (this Reduce) Act() {} func (this Accept) String() string { return "Accept(0)" } func (this Shift) String() string { return "Shift(" + strconv.Itoa(int(this)) + ")" } func (this Reduce) String() string { return "Reduce(" + strconv.Itoa(int(this)) + ")" } type ( GotoTab []GotoRow GotoRow map[NT]State State int NT string ) type ( ProdTab []ProdTabEntry ProdTabEntry struct { String string Head NT NumSymbols int ReduceFunc func([]Attrib) (Attrib, error) } Attrib interface { String() string } ) /*** Stack ***/ type stack struct { state []State attrib []Attrib } const INITIAL_STACK_SIZE = 100 func NewStack() *stack { return &stack{state: make([]State, 0, INITIAL_STACK_SIZE), attrib: make([]Attrib, 0, INITIAL_STACK_SIZE), } } func (this *stack) Push(s State, a Attrib) { this.state = append(this.state, s) this.attrib = append(this.attrib, a) } func (this *stack) Top() State { return this.state[len(this.state)-1] } func (this *stack) PopN(items int) []Attrib { lo, hi := len(this.state)-items, len(this.state) attrib := this.attrib[lo:hi] this.state = this.state[:lo] this.attrib = this.attrib[:lo] return attrib } func (S *stack) String() string { res := "stack:\n" for i, st := range S.state { res += "\t" + strconv.Itoa(i) + ": " + strconv.Itoa(int(st)) res += " , " if S.attrib[i] == nil { res += "nil" } else { res += S.attrib[i].String() } res += "\n" } return res } /*** Parser ***/ type Parser struct { actTab ActionTab gotoTab GotoTab prodTab ProdTab stack *stack nextToken *token.Token pos token.Position tokenMap *token.TokenMap } type Scanner interface { Scan() (*token.Token, token.Position) } func NewParser(act ActionTab, gto GotoTab, prod ProdTab, tm *token.TokenMap) *Parser { p := &Parser{actTab: act, gotoTab: gto, prodTab: prod, stack: NewStack(), tokenMap: tm} p.stack.Push(0, nil) //TODO: which attribute should be pushed here? return p } func (P *Parser) Error(err error) error { errmsg := "Error: " + P.TokString(P.nextToken) + " @ " + P.pos.String() if err != nil { errmsg += " " + err.Error() } else { errmsg += ", expected one of: " actRow := P.actTab[P.stack.Top()] i := 0 for t := range actRow { errmsg += P.tokenMap.TokenString(t) if i < len(actRow)-1 { errmsg += " " } i++ } } return errors.New(errmsg) } func (P *Parser) TokString(tok *token.Token) string { msg := P.tokenMap.TokenString(tok.Type) + "(" + strconv.Itoa(int(tok.Type)) + ")" msg += " " + string(tok.Lit) return msg } func (this *Parser) Parse(scanner Scanner) (res interface{}, err error) { this.nextToken, this.pos = scanner.Scan() for acc := false; !acc; { action, ok := this.actTab[this.stack.Top()][this.nextToken.Type] if !ok { return nil, this.Error(nil) } switch act := action.(type) { case Accept: res = this.stack.PopN(1)[0] acc = true case Shift: this.stack.Push(State(act), this.nextToken) this.nextToken, this.pos = scanner.Scan() case Reduce: prod := this.prodTab[int(act)] attrib, err := prod.ReduceFunc(this.stack.PopN(prod.NumSymbols)) if err != nil { return nil, this.Error(err) } else { this.stack.Push(this.gotoTab[this.stack.Top()][prod.Head], attrib) } default: panic("unknown action") } } return res, nil }