/* Package parser implements a parser for JavaScript. import ( "github.com/robertkrimen/otto/parser" ) Parse and return an AST filename := "" // A filename is optional src := ` // Sample xyzzy example (function(){ if (3.14159 > 0) { console.log("Hello, World."); return; } var xyzzy = NaN; console.log("Nothing happens."); return xyzzy; })(); ` // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList program, err := parser.ParseFile(nil, filename, src, 0) Warning The parser and AST interfaces are still works-in-progress (particularly where node types are concerned) and may change in the future. */ package parser import ( "bytes" "errors" "io" "io/ioutil" "github.com/robertkrimen/otto/ast" "github.com/robertkrimen/otto/file" "github.com/robertkrimen/otto/token" ) // A Mode value is a set of flags (or 0). They control optional parser functionality. type Mode uint const ( IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking) ) type _parser struct { filename string str string length int base int chr rune // The current character chrOffset int // The offset of current character offset int // The offset after current character (may be greater than 1) idx file.Idx // The index of token token token.Token // The token literal string // The literal of the token, if any scope *_scope insertSemicolon bool // If we see a newline, then insert an implicit semicolon implicitSemicolon bool // An implicit semicolon exists errors ErrorList recover struct { // Scratch when trying to seek to the next statement, etc. idx file.Idx count int } mode Mode } func _newParser(filename, src string, base int) *_parser { return &_parser{ chr: ' ', // This is set so we can start scanning by skipping whitespace str: src, length: len(src), base: base, } } func newParser(filename, src string) *_parser { return _newParser(filename, src, 1) } func ReadSource(filename string, src interface{}) ([]byte, error) { if src != nil { switch src := src.(type) { case string: return []byte(src), nil case []byte: return src, nil case *bytes.Buffer: if src != nil { return src.Bytes(), nil } case io.Reader: var bfr bytes.Buffer if _, err := io.Copy(&bfr, src); err != nil { return nil, err } return bfr.Bytes(), nil } return nil, errors.New("invalid source") } return ioutil.ReadFile(filename) } // ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns // the corresponding ast.Program node. // // If fileSet == nil, ParseFile parses source without a FileSet. // If fileSet != nil, ParseFile first adds filename and src to fileSet. // // The filename argument is optional and is used for labelling errors, etc. // // src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8. // // // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList // program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0) // func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) { str, err := ReadSource(filename, src) if err != nil { return nil, err } { str := string(str) base := 1 if fileSet != nil { base = fileSet.AddFile(filename, str) } parser := _newParser(filename, str, base) parser.mode = mode return parser.parse() } } // ParseFunction parses a given parameter list and body as a function and returns the // corresponding ast.FunctionLiteral node. // // The parameter list, if any, should be a comma-separated list of identifiers. // func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) { src := "(function(" + parameterList + ") {\n" + body + "\n})" parser := _newParser("", src, 1) program, err := parser.parse() if err != nil { return nil, err } return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil } func (self *_parser) slice(idx0, idx1 file.Idx) string { from := int(idx0) - self.base to := int(idx1) - self.base if from >= 0 && to <= len(self.str) { return self.str[from:to] } return "" } func (self *_parser) parse() (*ast.Program, error) { self.next() program := self.parseProgram() if false { self.errors.Sort() } return program, self.errors.Err() } func (self *_parser) next() { self.token, self.literal, self.idx = self.scan() } func (self *_parser) optionalSemicolon() { if self.token == token.SEMICOLON { self.next() return } if self.implicitSemicolon { self.implicitSemicolon = false return } if self.token != token.EOF && self.token != token.RIGHT_BRACE { self.expect(token.SEMICOLON) } } func (self *_parser) semicolon() { if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE { if self.implicitSemicolon { self.implicitSemicolon = false return } self.expect(token.SEMICOLON) } } func (self *_parser) idxOf(offset int) file.Idx { return file.Idx(self.base + offset) } func (self *_parser) expect(value token.Token) file.Idx { idx := self.idx if self.token != value { self.errorUnexpectedToken(self.token) } self.next() return idx } func lineCount(str string) (int, int) { line, last := 0, -1 pair := false for index, chr := range str { switch chr { case '\r': line += 1 last = index pair = true continue case '\n': if !pair { line += 1 } last = index case '\u2028', '\u2029': line += 1 last = index + 2 } pair = false } return line, last } func (self *_parser) position(idx file.Idx) file.Position { position := file.Position{} offset := int(idx) - self.base str := self.str[:offset] position.Filename = self.filename line, last := lineCount(str) position.Line = 1 + line if last >= 0 { position.Column = offset - last } else { position.Column = 1 + len(str) } return position }