/* Package parser implements a parser for JavaScript. import ( "github.com/dop251/goja/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 ( ) // 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 options struct { disableSourceMaps bool sourceMapLoader func(path string) ([]byte, error) } // Option represents one of the options for the parser to use in the Parse methods. Currently supported are: // WithDisableSourceMaps and WithSourceMapLoader. type Option func(*options) // WithDisableSourceMaps is an option to disable source maps support. May save a bit of time when source maps // are not in use. func ( *options) { .disableSourceMaps = true } // WithSourceMapLoader is an option to set a custom source map loader. The loader will be given a path or a // URL from the sourceMappingURL. If sourceMappingURL is not absolute it is resolved relatively to the name // of the file being parsed. Any error returned by the loader will fail the parsing. // Note that setting this to nil does not disable source map support, there is a default loader which reads // from the filesystem. Use WithDisableSourceMaps to disable source map support. func ( func( string) ([]byte, error)) Option { return func( *options) { .sourceMapLoader = } } 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 parsedLiteral unistring.String 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 opts options file *file.File } func _newParser(, string, int, ...Option) *_parser { := &_parser{ chr: ' ', // This is set so we can start scanning by skipping whitespace str: , length: len(), base: , file: file.NewFile(, , ), } for , := range { (&.opts) } return } func newParser(, string) *_parser { return _newParser(, , 1) } func ( string, interface{}) ([]byte, error) { if != nil { switch src := .(type) { case string: return []byte(), nil case []byte: return , nil case *bytes.Buffer: if != nil { return .Bytes(), nil } case io.Reader: var bytes.Buffer if , := io.Copy(&, ); != nil { return nil, } return .Bytes(), nil } return nil, errors.New("invalid source") } return os.ReadFile() } // 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 ( *file.FileSet, string, interface{}, Mode, ...Option) (*ast.Program, error) { , := ReadSource(, ) if != nil { return nil, } { := string() := 1 if != nil { = .AddFile(, ) } := _newParser(, , , ...) .mode = return .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 (, string, ...Option) (*ast.FunctionLiteral, error) { := "(function(" + + ") {\n" + + "\n})" := _newParser("", , 1, ...) , := .parse() if != nil { return nil, } return .Body[0].(*ast.ExpressionStatement).Expression.(*ast.FunctionLiteral), nil } func ( *_parser) (, file.Idx) string { := int() - .base := int() - .base if >= 0 && <= len(.str) { return .str[:] } return "" } func ( *_parser) () (*ast.Program, error) { .openScope() defer .closeScope() .next() := .parseProgram() if false { .errors.Sort() } return , .errors.Err() } func ( *_parser) () { .token, .literal, .parsedLiteral, .idx = .scan() } func ( *_parser) () { if .token == token.SEMICOLON { .next() return } if .implicitSemicolon { .implicitSemicolon = false return } if .token != token.EOF && .token != token.RIGHT_BRACE { .expect(token.SEMICOLON) } } func ( *_parser) () { if .token != token.RIGHT_PARENTHESIS && .token != token.RIGHT_BRACE { if .implicitSemicolon { .implicitSemicolon = false return } .expect(token.SEMICOLON) } } func ( *_parser) ( int) file.Idx { return file.Idx(.base + ) } func ( *_parser) ( token.Token) file.Idx { := .idx if .token != { .errorUnexpectedToken(.token) } .next() return } func ( *_parser) ( file.Idx) file.Position { return .file.Position(int() - .base) }