parser.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344
  1. /*
  2. Package parser implements a parser for JavaScript.
  3. import (
  4. "github.com/robertkrimen/otto/parser"
  5. )
  6. Parse and return an AST
  7. filename := "" // A filename is optional
  8. src := `
  9. // Sample xyzzy example
  10. (function(){
  11. if (3.14159 > 0) {
  12. console.log("Hello, World.");
  13. return;
  14. }
  15. var xyzzy = NaN;
  16. console.log("Nothing happens.");
  17. return xyzzy;
  18. })();
  19. `
  20. // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
  21. program, err := parser.ParseFile(nil, filename, src, 0)
  22. Warning
  23. The parser and AST interfaces are still works-in-progress (particularly where
  24. node types are concerned) and may change in the future.
  25. */
  26. package parser
  27. import (
  28. "bytes"
  29. "encoding/base64"
  30. "errors"
  31. "io"
  32. "io/ioutil"
  33. "github.com/robertkrimen/otto/ast"
  34. "github.com/robertkrimen/otto/file"
  35. "github.com/robertkrimen/otto/token"
  36. "gopkg.in/sourcemap.v1"
  37. )
  38. // A Mode value is a set of flags (or 0). They control optional parser functionality.
  39. type Mode uint
  40. const (
  41. IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking)
  42. StoreComments // Store the comments from source to the comments map
  43. )
  44. type _parser struct {
  45. str string
  46. length int
  47. base int
  48. chr rune // The current character
  49. chrOffset int // The offset of current character
  50. offset int // The offset after current character (may be greater than 1)
  51. idx file.Idx // The index of token
  52. token token.Token // The token
  53. literal string // The literal of the token, if any
  54. scope *_scope
  55. insertSemicolon bool // If we see a newline, then insert an implicit semicolon
  56. implicitSemicolon bool // An implicit semicolon exists
  57. errors ErrorList
  58. recover struct {
  59. // Scratch when trying to seek to the next statement, etc.
  60. idx file.Idx
  61. count int
  62. }
  63. mode Mode
  64. file *file.File
  65. comments *ast.Comments
  66. }
  67. type Parser interface {
  68. Scan() (tkn token.Token, literal string, idx file.Idx)
  69. }
  70. func _newParser(filename, src string, base int, sm *sourcemap.Consumer) *_parser {
  71. return &_parser{
  72. chr: ' ', // This is set so we can start scanning by skipping whitespace
  73. str: src,
  74. length: len(src),
  75. base: base,
  76. file: file.NewFile(filename, src, base).WithSourceMap(sm),
  77. comments: ast.NewComments(),
  78. }
  79. }
  80. // Returns a new Parser.
  81. func NewParser(filename, src string) Parser {
  82. return _newParser(filename, src, 1, nil)
  83. }
  84. func ReadSource(filename string, src interface{}) ([]byte, error) {
  85. if src != nil {
  86. switch src := src.(type) {
  87. case string:
  88. return []byte(src), nil
  89. case []byte:
  90. return src, nil
  91. case *bytes.Buffer:
  92. if src != nil {
  93. return src.Bytes(), nil
  94. }
  95. case io.Reader:
  96. var bfr bytes.Buffer
  97. if _, err := io.Copy(&bfr, src); err != nil {
  98. return nil, err
  99. }
  100. return bfr.Bytes(), nil
  101. }
  102. return nil, errors.New("invalid source")
  103. }
  104. return ioutil.ReadFile(filename)
  105. }
  106. func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) {
  107. if src == nil {
  108. return nil, nil
  109. }
  110. switch src := src.(type) {
  111. case string:
  112. return sourcemap.Parse(filename, []byte(src))
  113. case []byte:
  114. return sourcemap.Parse(filename, src)
  115. case *bytes.Buffer:
  116. if src != nil {
  117. return sourcemap.Parse(filename, src.Bytes())
  118. }
  119. case io.Reader:
  120. var bfr bytes.Buffer
  121. if _, err := io.Copy(&bfr, src); err != nil {
  122. return nil, err
  123. }
  124. return sourcemap.Parse(filename, bfr.Bytes())
  125. case *sourcemap.Consumer:
  126. return src, nil
  127. }
  128. return nil, errors.New("invalid sourcemap type")
  129. }
  130. func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) {
  131. src, err := ReadSource(filename, javascriptSource)
  132. if err != nil {
  133. return nil, err
  134. }
  135. if sourcemapSource == nil {
  136. lines := bytes.Split(src, []byte("\n"))
  137. lastLine := lines[len(lines)-1]
  138. if bytes.HasPrefix(lastLine, []byte("//# sourceMappingURL=data:application/json")) {
  139. bits := bytes.SplitN(lastLine, []byte(","), 2)
  140. if len(bits) == 2 {
  141. if d, err := base64.StdEncoding.DecodeString(string(bits[1])); err == nil {
  142. sourcemapSource = d
  143. }
  144. }
  145. }
  146. }
  147. sm, err := ReadSourceMap(filename, sourcemapSource)
  148. if err != nil {
  149. return nil, err
  150. }
  151. base := 1
  152. if fileSet != nil {
  153. base = fileSet.AddFile(filename, string(src))
  154. }
  155. parser := _newParser(filename, string(src), base, sm)
  156. parser.mode = mode
  157. program, err := parser.parse()
  158. program.Comments = parser.comments.CommentMap
  159. return program, err
  160. }
  161. // ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
  162. // the corresponding ast.Program node.
  163. //
  164. // If fileSet == nil, ParseFile parses source without a FileSet.
  165. // If fileSet != nil, ParseFile first adds filename and src to fileSet.
  166. //
  167. // The filename argument is optional and is used for labelling errors, etc.
  168. //
  169. // src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
  170. //
  171. // // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
  172. // program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
  173. //
  174. func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) {
  175. return ParseFileWithSourceMap(fileSet, filename, src, nil, mode)
  176. }
  177. // ParseFunction parses a given parameter list and body as a function and returns the
  178. // corresponding ast.FunctionLiteral node.
  179. //
  180. // The parameter list, if any, should be a comma-separated list of identifiers.
  181. //
  182. func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
  183. src := "(function(" + parameterList + ") {\n" + body + "\n})"
  184. parser := _newParser("", src, 1, nil)
  185. program, err := parser.parse()
  186. if err != nil {
  187. return nil, err
  188. }
  189. return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
  190. }
  191. // Scan reads a single token from the source at the current offset, increments the offset and
  192. // returns the token.Token token, a string literal representing the value of the token (if applicable)
  193. // and it's current file.Idx index.
  194. func (self *_parser) Scan() (tkn token.Token, literal string, idx file.Idx) {
  195. return self.scan()
  196. }
  197. func (self *_parser) slice(idx0, idx1 file.Idx) string {
  198. from := int(idx0) - self.base
  199. to := int(idx1) - self.base
  200. if from >= 0 && to <= len(self.str) {
  201. return self.str[from:to]
  202. }
  203. return ""
  204. }
  205. func (self *_parser) parse() (*ast.Program, error) {
  206. self.next()
  207. program := self.parseProgram()
  208. if false {
  209. self.errors.Sort()
  210. }
  211. if self.mode&StoreComments != 0 {
  212. self.comments.CommentMap.AddComments(program, self.comments.FetchAll(), ast.TRAILING)
  213. }
  214. return program, self.errors.Err()
  215. }
  216. func (self *_parser) next() {
  217. self.token, self.literal, self.idx = self.scan()
  218. }
  219. func (self *_parser) optionalSemicolon() {
  220. if self.token == token.SEMICOLON {
  221. self.next()
  222. return
  223. }
  224. if self.implicitSemicolon {
  225. self.implicitSemicolon = false
  226. return
  227. }
  228. if self.token != token.EOF && self.token != token.RIGHT_BRACE {
  229. self.expect(token.SEMICOLON)
  230. }
  231. }
  232. func (self *_parser) semicolon() {
  233. if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE {
  234. if self.implicitSemicolon {
  235. self.implicitSemicolon = false
  236. return
  237. }
  238. self.expect(token.SEMICOLON)
  239. }
  240. }
  241. func (self *_parser) idxOf(offset int) file.Idx {
  242. return file.Idx(self.base + offset)
  243. }
  244. func (self *_parser) expect(value token.Token) file.Idx {
  245. idx := self.idx
  246. if self.token != value {
  247. self.errorUnexpectedToken(self.token)
  248. }
  249. self.next()
  250. return idx
  251. }
  252. func lineCount(str string) (int, int) {
  253. line, last := 0, -1
  254. pair := false
  255. for index, chr := range str {
  256. switch chr {
  257. case '\r':
  258. line += 1
  259. last = index
  260. pair = true
  261. continue
  262. case '\n':
  263. if !pair {
  264. line += 1
  265. }
  266. last = index
  267. case '\u2028', '\u2029':
  268. line += 1
  269. last = index + 2
  270. }
  271. pair = false
  272. }
  273. return line, last
  274. }
  275. func (self *_parser) position(idx file.Idx) file.Position {
  276. position := file.Position{}
  277. offset := int(idx) - self.base
  278. str := self.str[:offset]
  279. position.Filename = self.file.Name()
  280. line, last := lineCount(str)
  281. position.Line = 1 + line
  282. if last >= 0 {
  283. position.Column = offset - last
  284. } else {
  285. position.Column = 1 + len(str)
  286. }
  287. return position
  288. }