github.com/nuvolaris/goja@v0.0.0-20230825100449-967811910c6d/parser/parser.go (about) 1 /* 2 Package parser implements a parser for JavaScript. 3 4 import ( 5 "github.com/nuvolaris/goja/parser" 6 ) 7 8 Parse and return an AST 9 10 filename := "" // A filename is optional 11 src := ` 12 // Sample xyzzy example 13 (function(){ 14 if (3.14159 > 0) { 15 console.log("Hello, World."); 16 return; 17 } 18 19 var xyzzy = NaN; 20 console.log("Nothing happens."); 21 return xyzzy; 22 })(); 23 ` 24 25 // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList 26 program, err := parser.ParseFile(nil, filename, src, 0) 27 28 # Warning 29 30 The parser and AST interfaces are still works-in-progress (particularly where 31 node types are concerned) and may change in the future. 32 */ 33 package parser 34 35 import ( 36 "bytes" 37 "errors" 38 "io" 39 "os" 40 41 "github.com/nuvolaris/goja/ast" 42 "github.com/nuvolaris/goja/file" 43 "github.com/nuvolaris/goja/token" 44 "github.com/nuvolaris/goja/unistring" 45 ) 46 47 // A Mode value is a set of flags (or 0). They control optional parser functionality. 48 type Mode uint 49 50 const ( 51 IgnoreRegExpErrors Mode = 1 << iota // Ignore RegExp compatibility errors (allow backtracking) 52 ) 53 54 type options struct { 55 disableSourceMaps bool 56 sourceMapLoader func(path string) ([]byte, error) 57 } 58 59 // Option represents one of the options for the parser to use in the Parse methods. Currently supported are: 60 // WithDisableSourceMaps and WithSourceMapLoader. 61 type Option func(*options) 62 63 // WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps 64 // are not in use. 65 func WithDisableSourceMaps(opts *options) { 66 opts.disableSourceMaps = true 67 } 68 69 // WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a 70 // URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name 71 // of the file being parsed. Any error returned by the loader will fail the parsing. 72 // Note that setting this to nil does not disable source map support, there is a default loader which reads 73 // from the filesystem. Use WithDisableSourceMaps to disable source map support. 74 func WithSourceMapLoader(loader func(path string) ([]byte, error)) Option { 75 return func(opts *options) { 76 opts.sourceMapLoader = loader 77 } 78 } 79 80 type _parser struct { 81 str string 82 length int 83 base int 84 85 chr rune // The current character 86 chrOffset int // The offset of current character 87 offset int // The offset after current character (may be greater than 1) 88 89 idx file.Idx // The index of token 90 token token.Token // The token 91 literal string // The literal of the token, if any 92 parsedLiteral unistring.String 93 94 scope *_scope 95 insertSemicolon bool // If we see a newline, then insert an implicit semicolon 96 implicitSemicolon bool // An implicit semicolon exists 97 98 errors ErrorList 99 100 recover struct { 101 // Scratch when trying to seek to the next statement, etc. 102 idx file.Idx 103 count int 104 } 105 106 mode Mode 107 opts options 108 109 file *file.File 110 } 111 112 func _newParser(filename, src string, base int, opts ...Option) *_parser { 113 p := &_parser{ 114 chr: ' ', // This is set so we can start scanning by skipping whitespace 115 str: src, 116 length: len(src), 117 base: base, 118 file: file.NewFile(filename, src, base), 119 } 120 for _, opt := range opts { 121 opt(&p.opts) 122 } 123 return p 124 } 125 126 func newParser(filename, src string) *_parser { 127 return _newParser(filename, src, 1) 128 } 129 130 func ReadSource(filename string, src interface{}) ([]byte, error) { 131 if src != nil { 132 switch src := src.(type) { 133 case string: 134 return []byte(src), nil 135 case []byte: 136 return src, nil 137 case *bytes.Buffer: 138 if src != nil { 139 return src.Bytes(), nil 140 } 141 case io.Reader: 142 var bfr bytes.Buffer 143 if _, err := io.Copy(&bfr, src); err != nil { 144 return nil, err 145 } 146 return bfr.Bytes(), nil 147 } 148 return nil, errors.New("invalid source") 149 } 150 return os.ReadFile(filename) 151 } 152 153 // ParseFile parses the source code of a single JavaScript/ECMAScript source file and returns 154 // the corresponding ast.Program node. 155 // 156 // If fileSet == nil, ParseFile parses source without a FileSet. 157 // If fileSet != nil, ParseFile first adds filename and src to fileSet. 158 // 159 // The filename argument is optional and is used for labelling errors, etc. 160 // 161 // src may be a string, a byte slice, a bytes.Buffer, or an io.Reader, but it MUST always be in UTF-8. 162 // 163 // // Parse some JavaScript, yielding a *ast.Program and/or an ErrorList 164 // program, err := parser.ParseFile(nil, "", `if (abc > 1) {}`, 0) 165 func ParseFile(fileSet *file.FileSet, filename string, src interface{}, mode Mode, options ...Option) (*ast.Program, error) { 166 str, err := ReadSource(filename, src) 167 if err != nil { 168 return nil, err 169 } 170 { 171 str := string(str) 172 173 base := 1 174 if fileSet != nil { 175 base = fileSet.AddFile(filename, str) 176 } 177 178 parser := _newParser(filename, str, base, options...) 179 parser.mode = mode 180 return parser.parse() 181 } 182 } 183 184 // ParseFunction parses a given parameter list and body as a function and returns the 185 // corresponding ast.FunctionLiteral node. 186 // 187 // The parameter list, if any, should be a comma-separated list of identifiers. 188 func ParseFunction(parameterList, body string, options ...Option) (*ast.FunctionLiteral, error) { 189 190 src := "(function(" + parameterList + ") {\n" + body + "\n})" 191 192 parser := _newParser("", src, 1, options...) 193 program, err := parser.parse() 194 if err != nil { 195 return nil, err 196 } 197 198 return program.Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil 199 } 200 201 func (self *_parser) slice(idx0, idx1 file.Idx) string { 202 from := int(idx0) - self.base 203 to := int(idx1) - self.base 204 if from >= 0 && to <= len(self.str) { 205 return self.str[from:to] 206 } 207 208 return "" 209 } 210 211 func (self *_parser) parse() (*ast.Program, error) { 212 self.openScope() 213 defer self.closeScope() 214 self.next() 215 program := self.parseProgram() 216 if false { 217 self.errors.Sort() 218 } 219 return program, self.errors.Err() 220 } 221 222 func (self *_parser) next() { 223 self.token, self.literal, self.parsedLiteral, self.idx = self.scan() 224 } 225 226 func (self *_parser) optionalSemicolon() { 227 if self.token == token.SEMICOLON { 228 self.next() 229 return 230 } 231 232 if self.implicitSemicolon { 233 self.implicitSemicolon = false 234 return 235 } 236 237 if self.token != token.EOF && self.token != token.RIGHT_BRACE { 238 self.expect(token.SEMICOLON) 239 } 240 } 241 242 func (self *_parser) semicolon() { 243 if self.token != token.RIGHT_PARENTHESIS && self.token != token.RIGHT_BRACE { 244 if self.implicitSemicolon { 245 self.implicitSemicolon = false 246 return 247 } 248 249 self.expect(token.SEMICOLON) 250 } 251 } 252 253 func (self *_parser) idxOf(offset int) file.Idx { 254 return file.Idx(self.base + offset) 255 } 256 257 func (self *_parser) expect(value token.Token) file.Idx { 258 idx := self.idx 259 if self.token != value { 260 self.errorUnexpectedToken(self.token) 261 } 262 self.next() 263 return idx 264 } 265 266 func (self *_parser) position(idx file.Idx) file.Position { 267 return self.file.Position(int(idx) - self.base) 268 }