parser.go 8.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  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. "fmt"
  31. "io"
  32. "os"
  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 ignores RegExp compatibility errors (allow backtracking).
  42. IgnoreRegExpErrors Mode = 1 << iota
  43. // StoreComments stores the comments from source to the comments map.
  44. StoreComments
  45. )
  46. type parser struct {
  47. comments *ast.Comments
  48. file *file.File
  49. scope *scope
  50. literal string
  51. str string
  52. errors ErrorList
  53. recover struct {
  54. idx file.Idx
  55. count int
  56. }
  57. idx file.Idx
  58. token token.Token
  59. offset int
  60. chrOffset int
  61. mode Mode
  62. base int
  63. length int
  64. chr rune
  65. insertSemicolon bool
  66. implicitSemicolon bool // Scratch when trying to seek to the next statement, etc.
  67. }
  68. // Parser is implemented by types which can parse JavaScript Code.
  69. type Parser interface {
  70. Scan() (tkn token.Token, literal string, idx file.Idx)
  71. }
  72. func newParser(filename, src string, base int, sm *sourcemap.Consumer) *parser {
  73. return &parser{
  74. chr: ' ', // This is set so we can start scanning by skipping whitespace
  75. str: src,
  76. length: len(src),
  77. base: base,
  78. file: file.NewFile(filename, src, base).WithSourceMap(sm),
  79. comments: ast.NewComments(),
  80. }
  81. }
  82. // NewParser returns a new Parser.
  83. func NewParser(filename, src string) Parser {
  84. return newParser(filename, src, 1, nil)
  85. }
  86. // ReadSource reads code from src if not nil, otherwise reads from filename.
  87. func ReadSource(filename string, src interface{}) ([]byte, error) {
  88. if src != nil {
  89. switch src := src.(type) {
  90. case string:
  91. return []byte(src), nil
  92. case []byte:
  93. return src, nil
  94. case *bytes.Buffer:
  95. if src != nil {
  96. return src.Bytes(), nil
  97. }
  98. case io.Reader:
  99. var bfr bytes.Buffer
  100. if _, err := io.Copy(&bfr, src); err != nil {
  101. return nil, err
  102. }
  103. return bfr.Bytes(), nil
  104. default:
  105. return nil, fmt.Errorf("invalid src type %T", src)
  106. }
  107. }
  108. return os.ReadFile(filename) //nolint:gosec
  109. }
  110. // ReadSourceMap reads the source map from src if not nil, otherwise is a noop.
  111. func ReadSourceMap(filename string, src interface{}) (*sourcemap.Consumer, error) {
  112. if src == nil {
  113. return nil, nil //nolint:nilnil
  114. }
  115. switch src := src.(type) {
  116. case string:
  117. return sourcemap.Parse(filename, []byte(src))
  118. case []byte:
  119. return sourcemap.Parse(filename, src)
  120. case *bytes.Buffer:
  121. return sourcemap.Parse(filename, src.Bytes())
  122. case io.Reader:
  123. var bfr bytes.Buffer
  124. if _, err := io.Copy(&bfr, src); err != nil {
  125. return nil, err
  126. }
  127. return sourcemap.Parse(filename, bfr.Bytes())
  128. case *sourcemap.Consumer:
  129. return src, nil
  130. default:
  131. return nil, fmt.Errorf("invalid sourcemap type %T", src)
  132. }
  133. }
  134. // ParseFileWithSourceMap parses the sourcemap returning the resulting Program.
  135. func ParseFileWithSourceMap(fileSet *file.FileSet, filename string, javascriptSource, sourcemapSource interface{}, mode Mode) (*ast.Program, error) {
  136. src, err := ReadSource(filename, javascriptSource)
  137. if err != nil {
  138. return nil, err
  139. }
  140. if sourcemapSource == nil {
  141. lines := bytes.Split(src, []byte("\n"))
  142. lastLine := lines[len(lines)-1]
  143. if bytes.HasPrefix(lastLine, []byte("//# sourceMappingURL=data:application/json")) {
  144. bits := bytes.SplitN(lastLine, []byte(","), 2)
  145. if len(bits) == 2 {
  146. if d, errDecode := base64.StdEncoding.DecodeString(string(bits[1])); errDecode == nil {
  147. sourcemapSource = d
  148. }
  149. }
  150. }
  151. }
  152. sm, err := ReadSourceMap(filename, sourcemapSource)
  153. if err != nil {
  154. return nil, err
  155. }
  156. base := 1
  157. if fileSet != nil {
  158. base = fileSet.AddFile(filename, string(src))
  159. }
  160. p := newParser(filename, string(src), base, sm)
  161. p.mode = mode
  162. program, err := p.parse()
  163. program.Comments = p.comments.CommentMap
  164. return program, err
  165. }
  166. // ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns
  167. // the corresponding ast.Program node.
  168. //
  169. // If fileSet == nil, ParseFile parses source without a FileSet.
  170. // If fileSet != nil, ParseFile first adds filename and src to fileSet.
  171. //
  172. // The filename argument is optional and is used for labelling errors, etc.
  173. //
  174. // src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8.
  175. //
  176. // // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList
  177. // program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0)
  178. func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode) (*ast.Program, error) {
  179. return ParseFileWithSourceMap(fileSet, filename, src, nil, mode)
  180. }
  181. // ParseFunction parses a given parameter list and body as a function and returns the
  182. // corresponding ast.FunctionLiteral node.
  183. //
  184. // The parameter list, if any, should be a comma-separated list of identifiers.
  185. func ParseFunction(parameterList, body string) (*ast.FunctionLiteral, error) {
  186. src := "(function(" + parameterList + ") {\n" + body + "\n})"
  187. p := newParser("", src, 1, nil)
  188. program, err := p.parse()
  189. if err != nil {
  190. return nil, err
  191. }
  192. return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil
  193. }
  194. // Scan reads a single token from the source at the current offset, increments the offset and
  195. // returns the token.Token token, a string literal representing the value of the token (if applicable)
  196. // and it's current file.Idx index.
  197. func (p *parser) Scan() (token.Token, string, file.Idx) {
  198. return p.scan()
  199. }
  200. func (p *parser) slice(idx0, idx1 file.Idx) string {
  201. from := int(idx0) - p.base
  202. to := int(idx1) - p.base
  203. if from >= 0 && to <= len(p.str) {
  204. return p.str[from:to]
  205. }
  206. return ""
  207. }
  208. func (p *parser) parse() (*ast.Program, error) {
  209. p.next()
  210. program := p.parseProgram()
  211. if false {
  212. p.errors.Sort()
  213. }
  214. if p.mode&StoreComments != 0 {
  215. p.comments.CommentMap.AddComments(program, p.comments.FetchAll(), ast.TRAILING)
  216. }
  217. return program, p.errors.Err()
  218. }
  219. func (p *parser) next() {
  220. p.token, p.literal, p.idx = p.scan()
  221. }
  222. func (p *parser) optionalSemicolon() {
  223. if p.token == token.SEMICOLON {
  224. p.next()
  225. return
  226. }
  227. if p.implicitSemicolon {
  228. p.implicitSemicolon = false
  229. return
  230. }
  231. if p.token != token.EOF && p.token != token.RIGHT_BRACE {
  232. p.expect(token.SEMICOLON)
  233. }
  234. }
  235. func (p *parser) semicolon() {
  236. if p.token != token.RIGHT_PARENTHESIS && p.token != token.RIGHT_BRACE {
  237. if p.implicitSemicolon {
  238. p.implicitSemicolon = false
  239. return
  240. }
  241. p.expect(token.SEMICOLON)
  242. }
  243. }
  244. func (p *parser) idxOf(offset int) file.Idx {
  245. return file.Idx(p.base + offset)
  246. }
  247. func (p *parser) expect(value token.Token) file.Idx {
  248. idx := p.idx
  249. if p.token != value {
  250. p.errorUnexpectedToken(p.token)
  251. }
  252. p.next()
  253. return idx
  254. }
  255. func lineCount(str string) (int, int) {
  256. line, last := 0, -1
  257. pair := false
  258. for index, chr := range str {
  259. switch chr {
  260. case '\r':
  261. line++
  262. last = index
  263. pair = true
  264. continue
  265. case '\n':
  266. if !pair {
  267. line++
  268. }
  269. last = index
  270. case '\u2028', '\u2029':
  271. line++
  272. last = index + 2
  273. }
  274. pair = false
  275. }
  276. return line, last
  277. }
  278. func (p *parser) position(idx file.Idx) file.Position {
  279. position := file.Position{}
  280. offset := int(idx) - p.base
  281. str := p.str[:offset]
  282. position.Filename = p.file.Name()
  283. line, last := lineCount(str)
  284. position.Line = 1 + line
  285. if last >= 0 {
  286. position.Column = offset - last
  287. } else {
  288. position.Column = 1 + len(str)
  289. }
  290. return position
  291. }