aboutsummaryrefslogblamecommitdiffstats
path: root/vendor/github.com/robertkrimen/otto/parser/parser.go
blob: 75b7c500c439c67b704b557f6260fb8f5ec53bf3 (plain) (tree)




































                                                                             
                         






                                            
                               






                                                                                                      
                                                                                                 


                     


                     























                                                                                        
 
                              

 




                                                                                  
                        





                                                                                             


         


                                                
























                                                                     































































                                                                                                                                                     













                                                                                                          
                                                                        










                                                                                     
                                             







                                                                                                






                                                                                                      















                                                        
 


                                                                                                     
 










































































                                                                                     
                                            









                                               
/*
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"
    "encoding/base64"
    "errors"
    "io"
    "io/ioutil"

    "github.com/robertkrimen/otto/ast"
    "github.com/robertkrimen/otto/file"
    "github.com/robertkrimen/otto/token"
    "gopkg.in/sourcemap.v1"
)

// 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)
    StoreComments                       // Store the comments from source to the comments map
)

type _parser struct {
    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

    file *file.File

    comments *ast.Comments
}

type Parser interface {
    Scan() (tkn token.Token, literal string, idx file.Idx)
}

func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser {
    return &_parser{
        chr:      ' ', // This is set so we can start scanning by skipping whitespace
        str:      src,
        length:   len(src),
        base:     base,
        file:     file.NewFile(filename, src, base).WithSourceMap(sm),
        comments: ast.NewComments(),
    }
}

// Returns a new Parser.
func NewParser(filename, src string) Parser {
    return _newParser(filename, src, 1, nil)
}

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)
}

func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) {
    if src == nil {
        return nil, nil
    }

    switch src := src.(type) {
    case string:
        return sourcemap.Parse(filename, []byte(src))
    case []byte:
        return sourcemap.Parse(filename, src)
    case *bytes.Buffer:
        if src != nil {
            return sourcemap.Parse(filename, src.Bytes())
        }
    case io.Reader:
        var bfr bytes.Buffer
        if _, err := io.Copy(&bfr, src); err != nil {
            return nil, err
        }
        return sourcemap.Parse(filename, bfr.Bytes())
    case *sourcemap.Consumer:
        return src, nil
    }

    return nil, errors.New("invalid sourcemap type")
}

func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) {
    src, err := ReadSource(filename, javascriptSource)
    if err != nil {
        return nil, err
    }

    if sourcemapSource == nil {
        lines := bytes.Split(src, []byte("\n"))
        lastLine := lines[len(lines)-1]
        if bytes.HasPrefix(lastLine, []byte("//# sourceMappingURL=data:application/json")) {
            bits := bytes.SplitN(lastLine, []byte(","), 2)
            if len(bits) == 2 {
                if d, err := base64.StdEncoding.DecodeString(string(bits[1])); err == nil {
                    sourcemapSource = d
                }
            }
        }
    }

    sm, err := ReadSourceMap(filename, sourcemapSource)
    if err != nil {
        return nil, err
    }

    base := 1
    if fileSet != nil {
        base = fileSet.AddFile(filename, string(src))
    }

    parser := _newParser(filename, string(src), base, sm)
    parser.mode = mode
    program, err := parser.parse()
    program.Comments = parser.comments.CommentMap

    return program, err
}

// 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) {
    return ParseFileWithSourceMap(fileSet, filename, src, nil, mode)
}

// 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, nil)
    program, err := parser.parse()
    if err != nil {
        return nil, err
    }

    return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
}

// Scan reads a single token from the source at the current offset, increments the offset and
// returns the token.Token token, a string literal representing the value of the token (if applicable)
// and it's current file.Idx index.
func (self *_parser) Scan() (tkn token.Token, literal string, idx file.Idx) {
    return self.scan()
}

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()
    }

    if self.mode&StoreComments != 0 {
        self.comments.CommentMap.AddComments(program, self.comments.FetchAll(), ast.TRAILING)
    }

    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.file.Name()
    line, last := lineCount(str)
    position.Line = 1 + line
    if last >= 0 {
        position.Column = offset - last
    } else {
        position.Column = 1 + len(str)
    }

    return position
}